You've already forked authentication-service
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:
@ -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
|
||||||
|
@ -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))
|
||||||
|
Reference in New Issue
Block a user