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
data-model: Make the user_id optional in the OAuth 2.0 sessions
This commit is contained in:
@ -29,6 +29,7 @@ use futures_util::TryStreamExt;
|
||||
use headers::{authorization::Bearer, Authorization, ContentType, HeaderValue};
|
||||
use hyper::header::CACHE_CONTROL;
|
||||
use mas_axum_utils::{cookies::CookieJar, FancyError, SessionInfo, SessionInfoExt};
|
||||
use mas_data_model::User;
|
||||
use mas_graphql::{Requester, Schema};
|
||||
use mas_matrix::HomeserverConnection;
|
||||
use mas_policy::{InstantiateError, Policy, PolicyFactory};
|
||||
@ -204,13 +205,22 @@ async fn get_requester(
|
||||
.await?
|
||||
.ok_or(RouteError::LoadFailed)?;
|
||||
|
||||
let user = repo
|
||||
.user()
|
||||
.lookup(session.user_id)
|
||||
.await?
|
||||
.ok_or(RouteError::LoadFailed)?;
|
||||
// Load the user if there is one
|
||||
let user = if let Some(user_id) = session.user_id {
|
||||
let user = repo
|
||||
.user()
|
||||
.lookup(user_id)
|
||||
.await?
|
||||
.ok_or(RouteError::LoadFailed)?;
|
||||
Some(user)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if !token.is_valid(clock.now()) || !session.is_valid() || !user.is_valid() {
|
||||
// If there is a user for this session, check that it is not locked
|
||||
let user_valid = user.as_ref().map_or(false, User::is_valid);
|
||||
|
||||
if !token.is_valid(clock.now()) || !session.is_valid() || !user_valid {
|
||||
return Err(RouteError::InvalidToken);
|
||||
}
|
||||
|
||||
|
@ -184,24 +184,32 @@ pub(crate) async fn post(
|
||||
// XXX: is that the right error to bubble up?
|
||||
.ok_or(RouteError::UnknownToken)?;
|
||||
|
||||
let user = repo
|
||||
.user()
|
||||
.lookup(session.user_id)
|
||||
.await?
|
||||
.filter(User::is_valid)
|
||||
// XXX: is that the right error to bubble up?
|
||||
.ok_or(RouteError::UnknownToken)?;
|
||||
// The session might not have a user on it (for Client Credentials grants for
|
||||
// example), so we're optionally fetching the user
|
||||
let (sub, username) = if let Some(user_id) = session.user_id {
|
||||
let user = repo
|
||||
.user()
|
||||
.lookup(user_id)
|
||||
.await?
|
||||
// Fail if the user is not valid (e.g. locked)
|
||||
.filter(User::is_valid)
|
||||
.ok_or(RouteError::UnknownToken)?;
|
||||
|
||||
(Some(user.sub), Some(user.username))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
IntrospectionResponse {
|
||||
active: true,
|
||||
scope: Some(session.scope),
|
||||
client_id: Some(session.client_id.to_string()),
|
||||
username: Some(user.username),
|
||||
username,
|
||||
token_type: Some(OAuthTokenTypeHint::AccessToken),
|
||||
exp: Some(token.expires_at),
|
||||
iat: Some(token.created_at),
|
||||
nbf: Some(token.created_at),
|
||||
sub: Some(user.sub),
|
||||
sub,
|
||||
aud: None,
|
||||
iss: None,
|
||||
jti: Some(token.jti()),
|
||||
@ -224,24 +232,32 @@ pub(crate) async fn post(
|
||||
// XXX: is that the right error to bubble up?
|
||||
.ok_or(RouteError::UnknownToken)?;
|
||||
|
||||
let user = repo
|
||||
.user()
|
||||
.lookup(session.user_id)
|
||||
.await?
|
||||
.filter(User::is_valid)
|
||||
// XXX: is that the right error to bubble up?
|
||||
.ok_or(RouteError::UnknownToken)?;
|
||||
// The session might not have a user on it (for Client Credentials grants for
|
||||
// example), so we're optionally fetching the user
|
||||
let (sub, username) = if let Some(user_id) = session.user_id {
|
||||
let user = repo
|
||||
.user()
|
||||
.lookup(user_id)
|
||||
.await?
|
||||
// Fail if the user is not valid (e.g. locked)
|
||||
.filter(User::is_valid)
|
||||
.ok_or(RouteError::UnknownToken)?;
|
||||
|
||||
(Some(user.sub), Some(user.username))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
IntrospectionResponse {
|
||||
active: true,
|
||||
scope: Some(session.scope),
|
||||
client_id: Some(session.client_id.to_string()),
|
||||
username: Some(user.username),
|
||||
username,
|
||||
token_type: Some(OAuthTokenTypeHint::RefreshToken),
|
||||
exp: None,
|
||||
iat: Some(token.created_at),
|
||||
nbf: Some(token.created_at),
|
||||
sub: Some(user.sub),
|
||||
sub,
|
||||
aud: None,
|
||||
iss: None,
|
||||
jti: Some(token.jti()),
|
||||
|
@ -199,25 +199,29 @@ pub(crate) async fn post(
|
||||
return Err(RouteError::UnauthorizedClient);
|
||||
}
|
||||
|
||||
// Fetch the user
|
||||
let user = repo
|
||||
.user()
|
||||
.lookup(session.user_id)
|
||||
.await?
|
||||
.ok_or(RouteError::UnknownToken)?;
|
||||
// If the session is associated with a user, make sure we schedule a device
|
||||
// deletion job for all the devices associated with the session.
|
||||
if let Some(user_id) = session.user_id {
|
||||
// Fetch the user
|
||||
let user = repo
|
||||
.user()
|
||||
.lookup(user_id)
|
||||
.await?
|
||||
.ok_or(RouteError::UnknownToken)?;
|
||||
|
||||
// Scan the scopes of the session to find if there is any device that should be
|
||||
// deleted from the Matrix server.
|
||||
// TODO: this should be moved in a higher level "end oauth session" method.
|
||||
// XXX: this might not be the right semantic, but it's the best we
|
||||
// can do for now, since we're not explicitly storing devices for OAuth2
|
||||
// sessions.
|
||||
for scope in &*session.scope {
|
||||
if let Some(device) = Device::from_scope_token(scope) {
|
||||
// Schedule a job to delete the device.
|
||||
repo.job()
|
||||
.schedule_job(DeleteDeviceJob::new(&user, &device))
|
||||
.await?;
|
||||
// Scan the scopes of the session to find if there is any device that should be
|
||||
// deleted from the Matrix server.
|
||||
// TODO: this should be moved in a higher level "end oauth session" method.
|
||||
// XXX: this might not be the right semantic, but it's the best we
|
||||
// can do for now, since we're not explicitly storing devices for OAuth2
|
||||
// sessions.
|
||||
for scope in &*session.scope {
|
||||
if let Some(device) = Device::from_scope_token(scope) {
|
||||
// Schedule a job to delete the device.
|
||||
repo.job()
|
||||
.schedule_job(DeleteDeviceJob::new(&user, &device))
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,6 +65,9 @@ pub enum RouteError {
|
||||
#[from] AuthorizationVerificationError<mas_storage::RepositoryError>,
|
||||
),
|
||||
|
||||
#[error("session is not allowed to access the userinfo endpoint")]
|
||||
Unauthorized,
|
||||
|
||||
#[error("no suitable key found for signing")]
|
||||
InvalidSigningKey,
|
||||
|
||||
@ -86,7 +89,9 @@ impl IntoResponse for RouteError {
|
||||
Self::Internal(_) | Self::InvalidSigningKey | Self::NoSuchClient | Self::NoSuchUser => {
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response()
|
||||
}
|
||||
Self::AuthorizationVerificationError(_e) => StatusCode::UNAUTHORIZED.into_response(),
|
||||
Self::AuthorizationVerificationError(_) | Self::Unauthorized => {
|
||||
StatusCode::UNAUTHORIZED.into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -102,9 +107,19 @@ pub async fn get(
|
||||
) -> Result<Response, RouteError> {
|
||||
let session = user_authorization.protected(&mut repo, &clock).await?;
|
||||
|
||||
// This endpoint requires the `openid` scope.
|
||||
if !session.scope.contains("openid") {
|
||||
return Err(RouteError::Unauthorized);
|
||||
}
|
||||
|
||||
// Fail if the session is not associated with a user.
|
||||
let Some(user_id) = session.user_id else {
|
||||
return Err(RouteError::Unauthorized);
|
||||
};
|
||||
|
||||
let user = repo
|
||||
.user()
|
||||
.lookup(session.user_id)
|
||||
.lookup(user_id)
|
||||
.await?
|
||||
.ok_or(RouteError::NoSuchUser)?;
|
||||
|
||||
|
Reference in New Issue
Block a user