1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-29 22:01:14 +03:00

Reply with proper errors on the OAuth token endpoint

This commit is contained in:
Quentin Gliech
2022-02-25 11:28:23 +01:00
parent 1d6f37554c
commit cad6d54ddb
2 changed files with 50 additions and 15 deletions

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, convert::Infallible, sync::Arc};
use anyhow::Context; use anyhow::Context;
use chrono::{DateTime, Duration, Utc}; use chrono::{DateTime, Duration, Utc};
@ -31,7 +31,10 @@ use mas_storage::{
access_token::{add_access_token, revoke_access_token}, access_token::{add_access_token, revoke_access_token},
authorization_grant::{exchange_grant, lookup_grant_by_code}, authorization_grant::{exchange_grant, lookup_grant_by_code},
end_oauth_session, end_oauth_session,
refresh_token::{add_refresh_token, lookup_active_refresh_token, replace_refresh_token}, refresh_token::{
add_refresh_token, lookup_active_refresh_token, replace_refresh_token,
RefreshTokenLookupError,
},
}, },
DatabaseInconsistencyError, DatabaseInconsistencyError,
}; };
@ -41,7 +44,9 @@ use mas_warp_utils::{
reply::with_typed_header, reply::with_typed_header,
}; };
use oauth2_types::{ use oauth2_types::{
errors::{InvalidGrant, InvalidRequest, OAuth2Error, OAuth2ErrorCode, UnauthorizedClient}, errors::{
InvalidGrant, InvalidRequest, OAuth2Error, OAuth2ErrorCode, ServerError, UnauthorizedClient,
},
requests::{ requests::{
AccessTokenRequest, AccessTokenResponse, AuthorizationCodeGrant, RefreshTokenGrant, AccessTokenRequest, AccessTokenResponse, AuthorizationCodeGrant, RefreshTokenGrant,
}, },
@ -122,12 +127,23 @@ pub fn filter(
.boxed() .boxed()
} }
async fn recover(rejection: Rejection) -> Result<Box<dyn Reply>, Rejection> { async fn recover(rejection: Rejection) -> Result<Box<dyn Reply>, Infallible> {
if let Some(Error { json, status }) = rejection.find::<Error>() { fn reply<E: OAuth2ErrorCode>(err: E) -> Box<dyn Reply> {
Ok(Box::new(with_status(warp::reply::json(json), *status))) let status = err.status();
} else { Box::new(with_status(warp::reply::json(&err.into_response()), status))
Err(rejection)
} }
if let Some(Error { json, status }) = rejection.find::<Error>() {
return Ok(Box::new(with_status(warp::reply::json(json), *status)));
}
if let Some(e) = rejection.find::<RefreshTokenLookupError>() {
if e.not_found() {
return Ok(reply(InvalidGrant));
}
};
Ok(reply(ServerError))
} }
async fn token( async fn token(
@ -333,9 +349,8 @@ async fn refresh_token_grant(
conn: &mut PoolConnection<Postgres>, conn: &mut PoolConnection<Postgres>,
) -> Result<AccessTokenResponse, Rejection> { ) -> Result<AccessTokenResponse, Rejection> {
let mut txn = conn.begin().await.wrap_error()?; let mut txn = conn.begin().await.wrap_error()?;
let (refresh_token, session) = lookup_active_refresh_token(&mut txn, &grant.refresh_token) let (refresh_token, session) =
.await lookup_active_refresh_token(&mut txn, &grant.refresh_token).await?;
.wrap_error()?;
if client.client_id != session.client.client_id { if client.client_id != session.client.client_id {
// As per https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 // As per https://datatracker.ietf.org/doc/html/rfc6749#section-5.2

View File

@ -18,6 +18,8 @@ use mas_data_model::{
AccessToken, Authentication, BrowserSession, Client, RefreshToken, Session, User, UserEmail, AccessToken, Authentication, BrowserSession, Client, RefreshToken, Session, User, UserEmail,
}; };
use sqlx::PgExecutor; use sqlx::PgExecutor;
use thiserror::Error;
use warp::reject::Reject;
use crate::{DatabaseInconsistencyError, IdAndCreationTime, PostgresqlBackend}; use crate::{DatabaseInconsistencyError, IdAndCreationTime, PostgresqlBackend};
@ -76,11 +78,28 @@ struct OAuth2RefreshTokenLookup {
user_email_confirmed_at: Option<DateTime<Utc>>, user_email_confirmed_at: Option<DateTime<Utc>>,
} }
#[derive(Error, Debug)]
#[error("could not lookup refresh token")]
pub enum RefreshTokenLookupError {
Fetch(#[from] sqlx::Error),
Conversion(#[from] DatabaseInconsistencyError),
}
impl Reject for RefreshTokenLookupError {}
impl RefreshTokenLookupError {
#[must_use]
pub fn not_found(&self) -> bool {
matches!(self, Self::Fetch(sqlx::Error::RowNotFound))
}
}
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
pub async fn lookup_active_refresh_token( pub async fn lookup_active_refresh_token(
executor: impl PgExecutor<'_>, executor: impl PgExecutor<'_>,
token: &str, token: &str,
) -> anyhow::Result<(RefreshToken<PostgresqlBackend>, Session<PostgresqlBackend>)> { ) -> Result<(RefreshToken<PostgresqlBackend>, Session<PostgresqlBackend>), RefreshTokenLookupError>
{
let res = sqlx::query_as!( let res = sqlx::query_as!(
OAuth2RefreshTokenLookup, OAuth2RefreshTokenLookup,
r#" r#"
@ -130,8 +149,7 @@ pub async fn lookup_active_refresh_token(
token, token,
) )
.fetch_one(executor) .fetch_one(executor)
.await .await?;
.context("failed to fetch oauth2 refresh token")?;
let access_token = match ( let access_token = match (
res.access_token_id, res.access_token_id,
@ -204,11 +222,13 @@ pub async fn lookup_active_refresh_token(
last_authentication, last_authentication,
}; };
let scope = res.scope.parse().map_err(|_e| DatabaseInconsistencyError)?;
let session = Session { let session = Session {
data: res.session_id, data: res.session_id,
client, client,
browser_session, browser_session,
scope: res.scope.parse().context("invalid scope in database")?, scope,
}; };
Ok((refresh_token, session)) Ok((refresh_token, session))