diff --git a/crates/handlers/src/oauth2/introspection.rs b/crates/handlers/src/oauth2/introspection.rs index a97c62ab6..c196ac37e 100644 --- a/crates/handlers/src/oauth2/introspection.rs +++ b/crates/handlers/src/oauth2/introspection.rs @@ -10,7 +10,7 @@ use axum::{Json, extract::State, http::HeaderValue, response::IntoResponse}; use hyper::{HeaderMap, StatusCode}; use mas_axum_utils::{ client_authorization::{ClientAuthorization, CredentialsVerificationError}, - sentry::SentryEventID, + record_error, }; use mas_data_model::{Device, TokenFormatError, TokenType}; use mas_iana::oauth::{OAuthClientAuthenticationMethod, OAuthTokenTypeHint}; @@ -28,6 +28,7 @@ use oauth2_types::{ }; use opentelemetry::{Key, KeyValue, metrics::Counter}; use thiserror::Error; +use ulid::Ulid; use crate::{ActivityTracker, METER, impl_from_error_for_route}; @@ -53,8 +54,8 @@ pub enum RouteError { ClientNotFound, /// The client is not allowed to introspect. - #[error("client is not allowed to introspect")] - NotAllowed, + #[error("client {0} is not allowed to introspect")] + NotAllowed(Ulid), /// The token type is not the one expected. #[error("unexpected token type")] @@ -73,30 +74,30 @@ pub enum RouteError { InvalidToken(TokenType), /// The OAuth session is not valid. - #[error("invalid oauth session")] - InvalidOAuthSession, + #[error("invalid oauth session {0}")] + InvalidOAuthSession(Ulid), /// The OAuth session could not be found in the database. - #[error("unknown oauth session")] - CantLoadOAuthSession, + #[error("unknown oauth session {0}")] + CantLoadOAuthSession(Ulid), /// The compat session is not valid. - #[error("invalid compat session")] - InvalidCompatSession, + #[error("invalid compat session {0}")] + InvalidCompatSession(Ulid), /// The compat session could not be found in the database. - #[error("unknown compat session")] - CantLoadCompatSession, + #[error("unknown compat session {0}")] + CantLoadCompatSession(Ulid), /// The Device ID in the compat session can't be encoded as a scope #[error("device ID contains characters that are not allowed in a scope")] CantEncodeDeviceID(#[from] mas_data_model::ToScopeTokenError), - #[error("invalid user")] - InvalidUser, + #[error("invalid user {0}")] + InvalidUser(Ulid), - #[error("unknown user")] - CantLoadUser, + #[error("unknown user {0}")] + CantLoadUser(Ulid), #[error("bad request")] BadRequest, @@ -107,12 +108,19 @@ pub enum RouteError { impl IntoResponse for RouteError { fn into_response(self) -> axum::response::Response { - let event_id = sentry::capture_error(&self); + let sentry_event_id = record_error!( + self, + Self::Internal(_) + | Self::CantLoadCompatSession(_) + | Self::CantLoadOAuthSession(_) + | Self::CantLoadUser(_) + ); + let response = match self { e @ (Self::Internal(_) - | Self::CantLoadCompatSession - | Self::CantLoadOAuthSession - | Self::CantLoadUser) => ( + | Self::CantLoadCompatSession(_) + | Self::CantLoadOAuthSession(_) + | Self::CantLoadUser(_)) => ( StatusCode::INTERNAL_SERVER_ERROR, Json( ClientError::from(ClientErrorCode::ServerError).with_description(e.to_string()), @@ -136,9 +144,9 @@ impl IntoResponse for RouteError { Self::UnknownToken(_) | Self::UnexpectedTokenType | Self::InvalidToken(_) - | Self::InvalidUser - | Self::InvalidCompatSession - | Self::InvalidOAuthSession + | Self::InvalidUser(_) + | Self::InvalidCompatSession(_) + | Self::InvalidOAuthSession(_) | Self::InvalidTokenFormat(_) | Self::CantEncodeDeviceID(_) => { INTROSPECTION_COUNTER.add(1, &[KeyValue::new(ACTIVE.clone(), false)]); @@ -146,11 +154,12 @@ impl IntoResponse for RouteError { Json(INACTIVE).into_response() } - Self::NotAllowed => ( + Self::NotAllowed(_) => ( StatusCode::UNAUTHORIZED, Json(ClientError::from(ClientErrorCode::AccessDenied)), ) .into_response(), + Self::BadRequest => ( StatusCode::BAD_REQUEST, Json(ClientError::from(ClientErrorCode::InvalidRequest)), @@ -158,7 +167,7 @@ impl IntoResponse for RouteError { .into_response(), }; - (SentryEventID::from(event_id), response).into_response() + (sentry_event_id, response).into_response() } } @@ -208,7 +217,7 @@ pub(crate) async fn post( let method = match &client.token_endpoint_auth_method { None | Some(OAuthClientAuthenticationMethod::None) => { - return Err(RouteError::NotAllowed); + return Err(RouteError::NotAllowed(client.id)); } Some(c) => c, }; @@ -259,10 +268,10 @@ pub(crate) async fn post( .oauth2_session() .lookup(access_token.session_id) .await? - .ok_or(RouteError::InvalidOAuthSession)?; + .ok_or(RouteError::CantLoadOAuthSession(access_token.session_id))?; if !session.is_valid() { - return Err(RouteError::InvalidOAuthSession); + return Err(RouteError::InvalidOAuthSession(session.id)); } // If this is the first time we're using this token, mark it as used @@ -280,10 +289,10 @@ pub(crate) async fn post( .user() .lookup(user_id) .await? - .ok_or(RouteError::CantLoadUser)?; + .ok_or(RouteError::CantLoadUser(user_id))?; if !user.is_valid() { - return Err(RouteError::InvalidUser); + return Err(RouteError::InvalidUser(user.id)); } (Some(user.sub), Some(user.username)) @@ -338,10 +347,10 @@ pub(crate) async fn post( .oauth2_session() .lookup(refresh_token.session_id) .await? - .ok_or(RouteError::CantLoadOAuthSession)?; + .ok_or(RouteError::CantLoadOAuthSession(refresh_token.session_id))?; if !session.is_valid() { - return Err(RouteError::InvalidOAuthSession); + return Err(RouteError::InvalidOAuthSession(session.id)); } // The session might not have a user on it (for Client Credentials grants for @@ -351,10 +360,10 @@ pub(crate) async fn post( .user() .lookup(user_id) .await? - .ok_or(RouteError::CantLoadUser)?; + .ok_or(RouteError::CantLoadUser(user_id))?; if !user.is_valid() { - return Err(RouteError::InvalidUser); + return Err(RouteError::InvalidUser(user.id)); } (Some(user.sub), Some(user.username)) @@ -407,20 +416,20 @@ pub(crate) async fn post( .compat_session() .lookup(access_token.session_id) .await? - .ok_or(RouteError::CantLoadCompatSession)?; + .ok_or(RouteError::CantLoadCompatSession(access_token.session_id))?; if !session.is_valid() { - return Err(RouteError::InvalidCompatSession); + return Err(RouteError::InvalidCompatSession(session.id)); } let user = repo .user() .lookup(session.user_id) .await? - .ok_or(RouteError::CantLoadUser)?; + .ok_or(RouteError::CantLoadUser(session.user_id))?; if !user.is_valid() { - return Err(RouteError::InvalidUser)?; + return Err(RouteError::InvalidUser(user.id))?; } // Grant the synapse admin scope if the session has the admin flag set. @@ -491,20 +500,20 @@ pub(crate) async fn post( .compat_session() .lookup(refresh_token.session_id) .await? - .ok_or(RouteError::CantLoadCompatSession)?; + .ok_or(RouteError::CantLoadCompatSession(refresh_token.session_id))?; if !session.is_valid() { - return Err(RouteError::InvalidCompatSession); + return Err(RouteError::InvalidCompatSession(session.id)); } let user = repo .user() .lookup(session.user_id) .await? - .ok_or(RouteError::CantLoadUser)?; + .ok_or(RouteError::CantLoadUser(session.user_id))?; if !user.is_valid() { - return Err(RouteError::InvalidUser)?; + return Err(RouteError::InvalidUser(user.id))?; } // Grant the synapse admin scope if the session has the admin flag set.