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

storage: Load with less joins

This is done to simplify some queries, to avoid loading more data than
necessary, and in preparation of a proper cache layer
This commit is contained in:
Quentin Gliech
2023-01-04 18:06:17 +01:00
parent a7883618be
commit e26f75246d
16 changed files with 824 additions and 1209 deletions

View File

@ -39,8 +39,6 @@ struct CompatAccessTokenLookup {
compat_session_finished_at: Option<DateTime<Utc>>,
compat_session_device_id: String,
user_id: Uuid,
user_username: String,
user_primary_user_email_id: Option<Uuid>,
}
#[tracing::instrument(skip_all, err)]
@ -52,24 +50,19 @@ pub async fn lookup_active_compat_access_token(
let res = sqlx::query_as!(
CompatAccessTokenLookup,
r#"
SELECT
ct.compat_access_token_id,
ct.access_token AS "compat_access_token",
ct.created_at AS "compat_access_token_created_at",
ct.expires_at AS "compat_access_token_expires_at",
cs.compat_session_id,
cs.created_at AS "compat_session_created_at",
cs.finished_at AS "compat_session_finished_at",
cs.device_id AS "compat_session_device_id",
u.user_id AS "user_id!",
u.username AS "user_username!",
u.primary_user_email_id AS "user_primary_user_email_id"
SELECT ct.compat_access_token_id
, ct.access_token AS "compat_access_token"
, ct.created_at AS "compat_access_token_created_at"
, ct.expires_at AS "compat_access_token_expires_at"
, cs.compat_session_id
, cs.created_at AS "compat_session_created_at"
, cs.finished_at AS "compat_session_finished_at"
, cs.device_id AS "compat_session_device_id"
, cs.user_id AS "user_id!"
FROM compat_access_tokens ct
INNER JOIN compat_sessions cs
USING (compat_session_id)
INNER JOIN users u
USING (user_id)
WHERE ct.access_token = $1
AND (ct.expires_at < $2 OR ct.expires_at IS NULL)
@ -92,14 +85,6 @@ pub async fn lookup_active_compat_access_token(
expires_at: res.compat_access_token_expires_at,
};
let user_id = Ulid::from(res.user_id);
let user = User {
id: user_id,
username: res.user_username,
sub: user_id.to_string(),
primary_user_email_id: res.user_primary_user_email_id.map(Into::into),
};
let id = res.compat_session_id.into();
let device = Device::try_from(res.compat_session_device_id).map_err(|e| {
DatabaseInconsistencyError::on("compat_sessions")
@ -110,7 +95,7 @@ pub async fn lookup_active_compat_access_token(
let session = CompatSession {
id,
user,
user_id: res.user_id.into(),
device,
created_at: res.compat_session_created_at,
finished_at: res.compat_session_finished_at,
@ -132,8 +117,6 @@ pub struct CompatRefreshTokenLookup {
compat_session_finished_at: Option<DateTime<Utc>>,
compat_session_device_id: String,
user_id: Uuid,
user_username: String,
user_primary_user_email_id: Option<Uuid>,
}
#[tracing::instrument(skip_all, err)]
@ -145,29 +128,24 @@ pub async fn lookup_active_compat_refresh_token(
let res = sqlx::query_as!(
CompatRefreshTokenLookup,
r#"
SELECT
cr.compat_refresh_token_id,
cr.refresh_token AS "compat_refresh_token",
cr.created_at AS "compat_refresh_token_created_at",
ct.compat_access_token_id,
ct.access_token AS "compat_access_token",
ct.created_at AS "compat_access_token_created_at",
ct.expires_at AS "compat_access_token_expires_at",
cs.compat_session_id,
cs.created_at AS "compat_session_created_at",
cs.finished_at AS "compat_session_finished_at",
cs.device_id AS "compat_session_device_id",
u.user_id,
u.username AS "user_username!",
u.primary_user_email_id AS "user_primary_user_email_id"
SELECT cr.compat_refresh_token_id
, cr.refresh_token AS "compat_refresh_token"
, cr.created_at AS "compat_refresh_token_created_at"
, ct.compat_access_token_id
, ct.access_token AS "compat_access_token"
, ct.created_at AS "compat_access_token_created_at"
, ct.expires_at AS "compat_access_token_expires_at"
, cs.compat_session_id
, cs.created_at AS "compat_session_created_at"
, cs.finished_at AS "compat_session_finished_at"
, cs.device_id AS "compat_session_device_id"
, cs.user_id
FROM compat_refresh_tokens cr
INNER JOIN compat_sessions cs
USING (compat_session_id)
INNER JOIN compat_access_tokens ct
USING (compat_access_token_id)
INNER JOIN users u
USING (user_id)
WHERE cr.refresh_token = $1
AND cr.consumed_at IS NULL
@ -195,25 +173,17 @@ pub async fn lookup_active_compat_refresh_token(
expires_at: res.compat_access_token_expires_at,
};
let user_id = Ulid::from(res.user_id);
let user = User {
id: user_id,
username: res.user_username,
sub: user_id.to_string(),
primary_user_email_id: res.user_primary_user_email_id.map(Into::into),
};
let session_id = res.compat_session_id.into();
let id = res.compat_session_id.into();
let device = Device::try_from(res.compat_session_device_id).map_err(|e| {
DatabaseInconsistencyError::on("compat_sessions")
.column("device_id")
.row(session_id)
.row(id)
.source(e)
})?;
let session = CompatSession {
id: session_id,
user,
id,
user_id: res.user_id.into(),
device,
created_at: res.compat_session_created_at,
finished_at: res.compat_session_finished_at,
@ -228,7 +198,7 @@ pub async fn lookup_active_compat_refresh_token(
compat_session.id = %session.id,
compat_session.device.id = session.device.as_str(),
compat_access_token.id,
user.id = %session.user.id,
user.id = %session.user_id,
),
err,
)]
@ -305,7 +275,7 @@ pub async fn expire_compat_access_token(
compat_session.device.id = session.device.as_str(),
compat_access_token.id = %access_token.id,
compat_refresh_token.id,
user.id = %session.user.id,
user.id = %session.user_id,
),
err,
)]
@ -469,8 +439,6 @@ struct CompatSsoLoginLookup {
compat_session_finished_at: Option<DateTime<Utc>>,
compat_session_device_id: Option<String>,
user_id: Option<Uuid>,
user_username: Option<String>,
user_primary_user_email_id: Option<Uuid>,
}
impl TryFrom<CompatSsoLoginLookup> for CompatSsoLogin {
@ -485,33 +453,14 @@ impl TryFrom<CompatSsoLoginLookup> for CompatSsoLogin {
.source(e)
})?;
let user = match (
res.user_id,
res.user_username,
res.user_primary_user_email_id,
) {
(Some(id), Some(username), primary_email_id) => {
let id = Ulid::from(id);
Some(User {
id,
username,
sub: id.to_string(),
primary_user_email_id: primary_email_id.map(Into::into),
})
}
(None, None, None) => None,
_ => return Err(DatabaseInconsistencyError::on("compat_sessions").column("user_id")),
};
let session = match (
res.compat_session_id,
res.compat_session_device_id,
res.compat_session_created_at,
res.compat_session_finished_at,
user,
res.user_id,
) {
(Some(id), Some(device_id), Some(created_at), finished_at, Some(user)) => {
(Some(id), Some(device_id), Some(created_at), finished_at, Some(user_id)) => {
let id = id.into();
let device = Device::try_from(device_id).map_err(|e| {
DatabaseInconsistencyError::on("compat_sessions")
@ -521,7 +470,7 @@ impl TryFrom<CompatSsoLoginLookup> for CompatSsoLogin {
})?;
Some(CompatSession {
id,
user,
user_id: user_id.into(),
device,
created_at,
finished_at,
@ -579,25 +528,21 @@ pub async fn get_compat_sso_login_by_id(
let res = sqlx::query_as!(
CompatSsoLoginLookup,
r#"
SELECT
cl.compat_sso_login_id,
cl.login_token AS "compat_sso_login_token",
cl.redirect_uri AS "compat_sso_login_redirect_uri",
cl.created_at AS "compat_sso_login_created_at",
cl.fulfilled_at AS "compat_sso_login_fulfilled_at",
cl.exchanged_at AS "compat_sso_login_exchanged_at",
cs.compat_session_id AS "compat_session_id?",
cs.created_at AS "compat_session_created_at?",
cs.finished_at AS "compat_session_finished_at?",
cs.device_id AS "compat_session_device_id?",
u.user_id AS "user_id?",
u.username AS "user_username?",
u.primary_user_email_id AS "user_primary_user_email_id?"
SELECT cl.compat_sso_login_id
, cl.login_token AS "compat_sso_login_token"
, cl.redirect_uri AS "compat_sso_login_redirect_uri"
, cl.created_at AS "compat_sso_login_created_at"
, cl.fulfilled_at AS "compat_sso_login_fulfilled_at"
, cl.exchanged_at AS "compat_sso_login_exchanged_at"
, cs.compat_session_id AS "compat_session_id?"
, cs.created_at AS "compat_session_created_at?"
, cs.finished_at AS "compat_session_finished_at?"
, cs.device_id AS "compat_session_device_id?"
, cs.user_id AS "user_id?"
FROM compat_sso_logins cl
LEFT JOIN compat_sessions cs
USING (compat_session_id)
LEFT JOIN users u
USING (user_id)
WHERE cl.compat_sso_login_id = $1
"#,
Uuid::from(id),
@ -632,25 +577,20 @@ pub async fn get_paginated_user_compat_sso_logins(
// because we already have them
let mut query = QueryBuilder::new(
r#"
SELECT
cl.compat_sso_login_id,
cl.login_token AS "compat_sso_login_token",
cl.redirect_uri AS "compat_sso_login_redirect_uri",
cl.created_at AS "compat_sso_login_created_at",
cl.fulfilled_at AS "compat_sso_login_fulfilled_at",
cl.exchanged_at AS "compat_sso_login_exchanged_at",
cs.compat_session_id AS "compat_session_id",
cs.created_at AS "compat_session_created_at",
cs.finished_at AS "compat_session_finished_at",
cs.device_id AS "compat_session_device_id",
u.user_id AS "user_id",
u.username AS "user_username",
u.primary_user_email_id AS "user_primary_user_email_id?"
SELECT cl.compat_sso_login_id
, cl.login_token AS "compat_sso_login_token"
, cl.redirect_uri AS "compat_sso_login_redirect_uri"
, cl.created_at AS "compat_sso_login_created_at"
, cl.fulfilled_at AS "compat_sso_login_fulfilled_at"
, cl.exchanged_at AS "compat_sso_login_exchanged_at"
, cs.compat_session_id AS "compat_session_id"
, cs.created_at AS "compat_session_created_at"
, cs.finished_at AS "compat_session_finished_at"
, cs.device_id AS "compat_session_device_id"
, cs.user_id
FROM compat_sso_logins cl
LEFT JOIN compat_sessions cs
USING (compat_session_id)
LEFT JOIN users u
USING (user_id)
"#,
);
@ -683,25 +623,20 @@ pub async fn get_compat_sso_login_by_token(
let res = sqlx::query_as!(
CompatSsoLoginLookup,
r#"
SELECT
cl.compat_sso_login_id,
cl.login_token AS "compat_sso_login_token",
cl.redirect_uri AS "compat_sso_login_redirect_uri",
cl.created_at AS "compat_sso_login_created_at",
cl.fulfilled_at AS "compat_sso_login_fulfilled_at",
cl.exchanged_at AS "compat_sso_login_exchanged_at",
cs.compat_session_id AS "compat_session_id?",
cs.created_at AS "compat_session_created_at?",
cs.finished_at AS "compat_session_finished_at?",
cs.device_id AS "compat_session_device_id?",
u.user_id AS "user_id?",
u.username AS "user_username?",
u.primary_user_email_id AS "user_primary_user_email_id?"
SELECT cl.compat_sso_login_id
, cl.login_token AS "compat_sso_login_token"
, cl.redirect_uri AS "compat_sso_login_redirect_uri"
, cl.created_at AS "compat_sso_login_created_at"
, cl.fulfilled_at AS "compat_sso_login_fulfilled_at"
, cl.exchanged_at AS "compat_sso_login_exchanged_at"
, cs.compat_session_id AS "compat_session_id?"
, cs.created_at AS "compat_session_created_at?"
, cs.finished_at AS "compat_session_finished_at?"
, cs.device_id AS "compat_session_device_id?"
, cs.user_id AS "user_id?"
FROM compat_sso_logins cl
LEFT JOIN compat_sessions cs
USING (compat_session_id)
LEFT JOIN users u
USING (user_id)
WHERE cl.login_token = $1
"#,
token,
@ -729,7 +664,7 @@ pub async fn start_compat_session(
executor: impl PgExecutor<'_>,
mut rng: impl Rng + Send,
clock: &Clock,
user: User,
user: &User,
device: Device,
) -> Result<CompatSession, DatabaseError> {
let created_at = clock.now();
@ -751,7 +686,7 @@ pub async fn start_compat_session(
Ok(CompatSession {
id,
user,
user_id: user.id,
device,
created_at,
finished_at: None,
@ -773,7 +708,7 @@ pub async fn fullfill_compat_sso_login(
conn: impl Acquire<'_, Database = Postgres> + Send,
mut rng: impl Rng + Send,
clock: &Clock,
user: User,
user: &User,
mut compat_sso_login: CompatSsoLogin,
device: Device,
) -> Result<CompatSsoLogin, DatabaseError> {

View File

@ -13,21 +13,20 @@
// limitations under the License.
use chrono::{DateTime, Duration, Utc};
use mas_data_model::{AccessToken, Authentication, BrowserSession, Session, User};
use mas_data_model::{AccessToken, Session};
use rand::Rng;
use sqlx::{PgConnection, PgExecutor};
use ulid::Ulid;
use uuid::Uuid;
use super::client::OAuth2ClientRepository;
use crate::{Clock, DatabaseError, DatabaseInconsistencyError, Repository};
use crate::{Clock, DatabaseError, DatabaseInconsistencyError};
#[tracing::instrument(
skip_all,
fields(
%session.id,
client.id = %session.client.id,
user.id = %session.browser_session.user.id,
user_session.id = %session.user_session_id,
client.id = %session.client_id,
access_token.id,
),
err,
@ -81,12 +80,6 @@ pub struct OAuth2AccessTokenLookup {
oauth2_client_id: Uuid,
scope: String,
user_session_id: Uuid,
user_session_created_at: DateTime<Utc>,
user_id: Uuid,
user_username: String,
user_primary_user_email_id: Option<Uuid>,
user_session_last_authentication_id: Option<Uuid>,
user_session_last_authentication_created_at: Option<DateTime<Utc>>,
}
#[allow(clippy::too_many_lines)]
@ -104,30 +97,15 @@ pub async fn lookup_active_access_token(
, os.oauth2_session_id AS "oauth2_session_id!"
, os.oauth2_client_id AS "oauth2_client_id!"
, os.scope AS "scope!"
, us.user_session_id AS "user_session_id!"
, us.created_at AS "user_session_created_at!"
, u.user_id AS "user_id!"
, u.username AS "user_username!"
, u.primary_user_email_id AS "user_primary_user_email_id"
, usa.user_session_authentication_id AS "user_session_last_authentication_id?"
, usa.created_at AS "user_session_last_authentication_created_at?"
, os.user_session_id AS "user_session_id!"
FROM oauth2_access_tokens at
INNER JOIN oauth2_sessions os
USING (oauth2_session_id)
INNER JOIN user_sessions us
USING (user_session_id)
INNER JOIN users u
USING (user_id)
LEFT JOIN user_session_authentications usa
USING (user_session_id)
WHERE at.access_token = $1
AND at.revoked_at IS NULL
AND os.finished_at IS NULL
ORDER BY usa.created_at DESC
LIMIT 1
"#,
token,
)
@ -144,44 +122,6 @@ pub async fn lookup_active_access_token(
};
let session_id = res.oauth2_session_id.into();
let client = conn
.oauth2_client()
.lookup(res.oauth2_client_id.into())
.await?
.ok_or_else(|| {
DatabaseInconsistencyError::on("oauth2_sessions")
.column("client_id")
.row(session_id)
})?;
let user_id = Ulid::from(res.user_id);
let user = User {
id: user_id,
username: res.user_username,
sub: user_id.to_string(),
primary_user_email_id: res.user_primary_user_email_id.map(Into::into),
};
let last_authentication = match (
res.user_session_last_authentication_id,
res.user_session_last_authentication_created_at,
) {
(None, None) => None,
(Some(id), Some(created_at)) => Some(Authentication {
id: id.into(),
created_at,
}),
_ => return Err(DatabaseInconsistencyError::on("user_session_authentications").into()),
};
let browser_session = BrowserSession {
id: res.user_session_id.into(),
created_at: res.user_session_created_at,
finished_at: None,
user,
last_authentication,
};
let scope = res.scope.parse().map_err(|e| {
DatabaseInconsistencyError::on("oauth2_sessions")
.column("scope")
@ -191,8 +131,8 @@ pub async fn lookup_active_access_token(
let session = Session {
id: session_id,
client,
browser_session,
client_id: res.oauth2_client_id.into(),
user_session_id: res.user_session_id.into(),
scope,
};

View File

@ -16,8 +16,8 @@ use std::num::NonZeroU32;
use chrono::{DateTime, Utc};
use mas_data_model::{
Authentication, AuthorizationCode, AuthorizationGrant, AuthorizationGrantStage, BrowserSession,
Client, Pkce, Session, User,
AuthorizationCode, AuthorizationGrant, AuthorizationGrantStage, BrowserSession, Client, Pkce,
Session,
};
use mas_iana::oauth::PkceCodeChallengeMethod;
use oauth2_types::{requests::ResponseMode, scope::Scope};
@ -151,12 +151,6 @@ struct GrantLookup {
oauth2_client_id: Uuid,
oauth2_session_id: Option<Uuid>,
user_session_id: Option<Uuid>,
user_session_created_at: Option<DateTime<Utc>>,
user_id: Option<Uuid>,
user_username: Option<String>,
user_primary_user_email_id: Option<Uuid>,
user_session_last_authentication_id: Option<Uuid>,
user_session_last_authentication_created_at: Option<DateTime<Utc>>,
}
impl GrantLookup {
@ -183,65 +177,20 @@ impl GrantLookup {
.row(id)
})?;
let last_authentication = match (
self.user_session_last_authentication_id,
self.user_session_last_authentication_created_at,
) {
(Some(id), Some(created_at)) => Some(Authentication {
id: id.into(),
created_at,
}),
(None, None) => None,
_ => return Err(DatabaseInconsistencyError::on("user_session_authentications").into()),
};
let session = match (
self.oauth2_session_id,
self.user_session_id,
self.user_session_created_at,
self.user_id,
self.user_username,
self.user_primary_user_email_id,
last_authentication,
) {
(
Some(session_id),
Some(user_session_id),
Some(user_session_created_at),
Some(user_id),
Some(user_username),
user_primary_user_email_id,
last_authentication,
) => {
let user_id = Ulid::from(user_id);
let user = User {
id: user_id,
username: user_username,
sub: user_id.to_string(),
primary_user_email_id: user_primary_user_email_id.map(Into::into),
};
let browser_session = BrowserSession {
id: user_session_id.into(),
user,
created_at: user_session_created_at,
finished_at: None,
last_authentication,
};
let client = client.clone();
let session = match (self.oauth2_session_id, self.user_session_id) {
(Some(session_id), Some(user_session_id)) => {
let scope = scope.clone();
let session = Session {
id: session_id.into(),
client,
browser_session,
client_id: client.id,
user_session_id: user_session_id.into(),
scope,
};
Some(session)
}
(None, None, None, None, None, None, None) => None,
(None, None) => None,
_ => {
return Err(
DatabaseInconsistencyError::on("oauth2_authorization_grants")
@ -394,48 +343,32 @@ pub async fn get_grant_by_id(
let res = sqlx::query_as!(
GrantLookup,
r#"
SELECT
og.oauth2_authorization_grant_id,
og.created_at AS oauth2_authorization_grant_created_at,
og.cancelled_at AS oauth2_authorization_grant_cancelled_at,
og.fulfilled_at AS oauth2_authorization_grant_fulfilled_at,
og.exchanged_at AS oauth2_authorization_grant_exchanged_at,
og.scope AS oauth2_authorization_grant_scope,
og.state AS oauth2_authorization_grant_state,
og.redirect_uri AS oauth2_authorization_grant_redirect_uri,
og.response_mode AS oauth2_authorization_grant_response_mode,
og.nonce AS oauth2_authorization_grant_nonce,
og.max_age AS oauth2_authorization_grant_max_age,
og.oauth2_client_id AS oauth2_client_id,
og.authorization_code AS oauth2_authorization_grant_code,
og.response_type_code AS oauth2_authorization_grant_response_type_code,
og.response_type_id_token AS oauth2_authorization_grant_response_type_id_token,
og.code_challenge AS oauth2_authorization_grant_code_challenge,
og.code_challenge_method AS oauth2_authorization_grant_code_challenge_method,
og.requires_consent AS oauth2_authorization_grant_requires_consent,
os.oauth2_session_id AS "oauth2_session_id?",
us.user_session_id AS "user_session_id?",
us.created_at AS "user_session_created_at?",
u.user_id AS "user_id?",
u.username AS "user_username?",
u.primary_user_email_id AS "user_primary_user_email_id?",
usa.user_session_authentication_id AS "user_session_last_authentication_id?",
usa.created_at AS "user_session_last_authentication_created_at?"
SELECT og.oauth2_authorization_grant_id
, og.created_at AS oauth2_authorization_grant_created_at
, og.cancelled_at AS oauth2_authorization_grant_cancelled_at
, og.fulfilled_at AS oauth2_authorization_grant_fulfilled_at
, og.exchanged_at AS oauth2_authorization_grant_exchanged_at
, og.scope AS oauth2_authorization_grant_scope
, og.state AS oauth2_authorization_grant_state
, og.redirect_uri AS oauth2_authorization_grant_redirect_uri
, og.response_mode AS oauth2_authorization_grant_response_mode
, og.nonce AS oauth2_authorization_grant_nonce
, og.max_age AS oauth2_authorization_grant_max_age
, og.oauth2_client_id AS oauth2_client_id
, og.authorization_code AS oauth2_authorization_grant_code
, og.response_type_code AS oauth2_authorization_grant_response_type_code
, og.response_type_id_token AS oauth2_authorization_grant_response_type_id_token
, og.code_challenge AS oauth2_authorization_grant_code_challenge
, og.code_challenge_method AS oauth2_authorization_grant_code_challenge_method
, og.requires_consent AS oauth2_authorization_grant_requires_consent
, os.oauth2_session_id AS "oauth2_session_id?"
, os.user_session_id AS "user_session_id?"
FROM
oauth2_authorization_grants og
LEFT JOIN oauth2_sessions os
USING (oauth2_session_id)
LEFT JOIN user_sessions us
USING (user_session_id)
LEFT JOIN users u
USING (user_id)
LEFT JOIN user_session_authentications usa
USING (user_session_id)
WHERE og.oauth2_authorization_grant_id = $1
ORDER BY usa.created_at DESC
LIMIT 1
"#,
Uuid::from(id),
)
@ -458,48 +391,32 @@ pub async fn lookup_grant_by_code(
let res = sqlx::query_as!(
GrantLookup,
r#"
SELECT
og.oauth2_authorization_grant_id,
og.created_at AS oauth2_authorization_grant_created_at,
og.cancelled_at AS oauth2_authorization_grant_cancelled_at,
og.fulfilled_at AS oauth2_authorization_grant_fulfilled_at,
og.exchanged_at AS oauth2_authorization_grant_exchanged_at,
og.scope AS oauth2_authorization_grant_scope,
og.state AS oauth2_authorization_grant_state,
og.redirect_uri AS oauth2_authorization_grant_redirect_uri,
og.response_mode AS oauth2_authorization_grant_response_mode,
og.nonce AS oauth2_authorization_grant_nonce,
og.max_age AS oauth2_authorization_grant_max_age,
og.oauth2_client_id AS oauth2_client_id,
og.authorization_code AS oauth2_authorization_grant_code,
og.response_type_code AS oauth2_authorization_grant_response_type_code,
og.response_type_id_token AS oauth2_authorization_grant_response_type_id_token,
og.code_challenge AS oauth2_authorization_grant_code_challenge,
og.code_challenge_method AS oauth2_authorization_grant_code_challenge_method,
og.requires_consent AS oauth2_authorization_grant_requires_consent,
os.oauth2_session_id AS "oauth2_session_id?",
us.user_session_id AS "user_session_id?",
us.created_at AS "user_session_created_at?",
u.user_id AS "user_id?",
u.username AS "user_username?",
u.primary_user_email_id AS "user_primary_user_email_id?",
usa.user_session_authentication_id AS "user_session_last_authentication_id?",
usa.created_at AS "user_session_last_authentication_created_at?"
SELECT og.oauth2_authorization_grant_id
, og.created_at AS oauth2_authorization_grant_created_at
, og.cancelled_at AS oauth2_authorization_grant_cancelled_at
, og.fulfilled_at AS oauth2_authorization_grant_fulfilled_at
, og.exchanged_at AS oauth2_authorization_grant_exchanged_at
, og.scope AS oauth2_authorization_grant_scope
, og.state AS oauth2_authorization_grant_state
, og.redirect_uri AS oauth2_authorization_grant_redirect_uri
, og.response_mode AS oauth2_authorization_grant_response_mode
, og.nonce AS oauth2_authorization_grant_nonce
, og.max_age AS oauth2_authorization_grant_max_age
, og.oauth2_client_id AS oauth2_client_id
, og.authorization_code AS oauth2_authorization_grant_code
, og.response_type_code AS oauth2_authorization_grant_response_type_code
, og.response_type_id_token AS oauth2_authorization_grant_response_type_id_token
, og.code_challenge AS oauth2_authorization_grant_code_challenge
, og.code_challenge_method AS oauth2_authorization_grant_code_challenge_method
, og.requires_consent AS oauth2_authorization_grant_requires_consent
, os.oauth2_session_id AS "oauth2_session_id?"
, os.user_session_id AS "user_session_id?"
FROM
oauth2_authorization_grants og
LEFT JOIN oauth2_sessions os
USING (oauth2_session_id)
LEFT JOIN user_sessions us
USING (user_session_id)
LEFT JOIN users u
USING (user_id)
LEFT JOIN user_session_authentications usa
USING (user_session_id)
WHERE og.authorization_code = $1
ORDER BY usa.created_at DESC
LIMIT 1
"#,
code,
)
@ -561,8 +478,8 @@ pub async fn derive_session(
Ok(Session {
id,
browser_session,
client: grant.client.clone(),
user_session_id: browser_session.id,
client_id: grant.client.id,
scope: grant.scope.clone(),
})
}
@ -573,8 +490,7 @@ pub async fn derive_session(
%grant.id,
client.id = %grant.client.id,
%session.id,
user_session.id = %session.browser_session.id,
user.id = %session.browser_session.user.id,
user_session.id = %session.user_session_id,
),
err,
)]

View File

@ -12,19 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::collections::{BTreeSet, HashMap};
use mas_data_model::{BrowserSession, Session, User};
use mas_data_model::{Session, User};
use sqlx::{PgConnection, PgExecutor, QueryBuilder};
use tracing::{info_span, Instrument};
use ulid::Ulid;
use uuid::Uuid;
use self::client::OAuth2ClientRepository;
use crate::{
pagination::{process_page, QueryBuilderExt},
user::BrowserSessionRepository,
Clock, DatabaseError, DatabaseInconsistencyError, Repository,
Clock, DatabaseError, DatabaseInconsistencyError,
};
pub mod access_token;
@ -32,14 +28,14 @@ pub mod authorization_grant;
pub mod client;
pub mod consent;
pub mod refresh_token;
pub mod session;
#[tracing::instrument(
skip_all,
fields(
%session.id,
user.id = %session.browser_session.user.id,
user_session.id = %session.browser_session.id,
client.id = %session.client.id,
user_session.id = %session.user_session_id,
client.id = %session.client_id,
),
err,
)]
@ -120,49 +116,10 @@ pub async fn get_paginated_user_oauth_sessions(
let (has_previous_page, has_next_page, page) = process_page(page, first, last)?;
let client_ids: BTreeSet<Ulid> = page
.iter()
.map(|i| Ulid::from(i.oauth2_client_id))
.collect();
let browser_session_ids: BTreeSet<Ulid> =
page.iter().map(|i| Ulid::from(i.user_session_id)).collect();
let clients = conn.oauth2_client().load_batch(client_ids).await?;
// TODO: this can generate N queries instead of batching. This is less than
// ideal
let mut browser_sessions: HashMap<Ulid, BrowserSession> = HashMap::new();
for id in browser_session_ids {
let v = conn.browser_session().lookup(id).await?.ok_or_else(|| {
DatabaseInconsistencyError::on("oauth2_sessions").column("user_session_id")
})?;
browser_sessions.insert(id, v);
}
let page: Result<Vec<_>, DatabaseInconsistencyError> = page
.into_iter()
.map(|item| {
let id = Ulid::from(item.oauth2_session_id);
let client = clients
.get(&Ulid::from(item.oauth2_client_id))
.ok_or_else(|| {
DatabaseInconsistencyError::on("oauth2_sessions")
.column("oauth2_client_id")
.row(id)
})?
.clone();
let browser_session = browser_sessions
.get(&Ulid::from(item.user_session_id))
.ok_or_else(|| {
DatabaseInconsistencyError::on("oauth2_sessions")
.column("user_session_id")
.row(id)
})?
.clone();
let scope = item.scope.parse().map_err(|e| {
DatabaseInconsistencyError::on("oauth2_sessions")
.column("scope")
@ -172,8 +129,8 @@ pub async fn get_paginated_user_oauth_sessions(
Ok(Session {
id: Ulid::from(item.oauth2_session_id),
client,
browser_session,
client_id: item.oauth2_client_id.into(),
user_session_id: item.user_session_id.into(),
scope,
})
})

View File

@ -13,22 +13,20 @@
// limitations under the License.
use chrono::{DateTime, Utc};
use mas_data_model::{AccessToken, Authentication, BrowserSession, RefreshToken, Session, User};
use mas_data_model::{AccessToken, RefreshToken, Session};
use rand::Rng;
use sqlx::{PgConnection, PgExecutor};
use ulid::Ulid;
use uuid::Uuid;
use super::client::OAuth2ClientRepository;
use crate::{Clock, DatabaseError, DatabaseInconsistencyError, Repository};
use crate::{Clock, DatabaseError, DatabaseInconsistencyError};
#[tracing::instrument(
skip_all,
fields(
%session.id,
user.id = %session.browser_session.user.id,
user_session.id = %session.browser_session.id,
client.id = %session.client.id,
user_session.id = %session.user_session_id,
client.id = %session.client_id,
refresh_token.id,
),
err,
@ -82,12 +80,6 @@ struct OAuth2RefreshTokenLookup {
oauth2_client_id: Uuid,
oauth2_session_scope: String,
user_session_id: Uuid,
user_session_created_at: DateTime<Utc>,
user_id: Uuid,
user_username: String,
user_primary_user_email_id: Option<Uuid>,
user_session_last_authentication_id: Option<Uuid>,
user_session_last_authentication_created_at: Option<DateTime<Utc>>,
}
#[tracing::instrument(skip_all, err)]
@ -99,46 +91,27 @@ pub async fn lookup_active_refresh_token(
let res = sqlx::query_as!(
OAuth2RefreshTokenLookup,
r#"
SELECT
rt.oauth2_refresh_token_id,
rt.refresh_token AS oauth2_refresh_token,
rt.created_at AS oauth2_refresh_token_created_at,
at.oauth2_access_token_id AS "oauth2_access_token_id?",
at.access_token AS "oauth2_access_token?",
at.created_at AS "oauth2_access_token_created_at?",
at.expires_at AS "oauth2_access_token_expires_at?",
os.oauth2_session_id AS "oauth2_session_id!",
os.oauth2_client_id AS "oauth2_client_id!",
os.scope AS "oauth2_session_scope!",
us.user_session_id AS "user_session_id!",
us.created_at AS "user_session_created_at!",
u.user_id AS "user_id!",
u.username AS "user_username!",
u.primary_user_email_id AS "user_primary_user_email_id",
usa.user_session_authentication_id AS "user_session_last_authentication_id?",
usa.created_at AS "user_session_last_authentication_created_at?"
SELECT rt.oauth2_refresh_token_id
, rt.refresh_token AS oauth2_refresh_token
, rt.created_at AS oauth2_refresh_token_created_at
, at.oauth2_access_token_id AS "oauth2_access_token_id?"
, at.access_token AS "oauth2_access_token?"
, at.created_at AS "oauth2_access_token_created_at?"
, at.expires_at AS "oauth2_access_token_expires_at?"
, os.oauth2_session_id AS "oauth2_session_id!"
, os.oauth2_client_id AS "oauth2_client_id!"
, os.scope AS "oauth2_session_scope!"
, os.user_session_id AS "user_session_id!"
FROM oauth2_refresh_tokens rt
INNER JOIN oauth2_sessions os
USING (oauth2_session_id)
LEFT JOIN oauth2_access_tokens at
USING (oauth2_access_token_id)
INNER JOIN user_sessions us
USING (user_session_id)
INNER JOIN users u
USING (user_id)
LEFT JOIN user_session_authentications usa
USING (user_session_id)
LEFT JOIN user_emails ue
ON ue.user_email_id = u.primary_user_email_id
WHERE rt.refresh_token = $1
AND rt.consumed_at IS NULL
AND rt.revoked_at IS NULL
AND us.finished_at IS NULL
AND os.finished_at IS NULL
ORDER BY usa.created_at DESC
LIMIT 1
"#,
token,
)
@ -173,44 +146,6 @@ pub async fn lookup_active_refresh_token(
};
let session_id = res.oauth2_session_id.into();
let client = conn
.oauth2_client()
.lookup(res.oauth2_client_id.into())
.await?
.ok_or_else(|| {
DatabaseInconsistencyError::on("oauth2_sessions")
.column("client_id")
.row(session_id)
})?;
let user_id = Ulid::from(res.user_id);
let user = User {
id: user_id,
username: res.user_username,
sub: user_id.to_string(),
primary_user_email_id: res.user_primary_user_email_id.map(Into::into),
};
let last_authentication = match (
res.user_session_last_authentication_id,
res.user_session_last_authentication_created_at,
) {
(None, None) => None,
(Some(id), Some(created_at)) => Some(Authentication {
id: id.into(),
created_at,
}),
_ => return Err(DatabaseInconsistencyError::on("user_session_authentications").into()),
};
let browser_session = BrowserSession {
id: res.user_session_id.into(),
created_at: res.user_session_created_at,
finished_at: None,
user,
last_authentication,
};
let scope = res.oauth2_session_scope.parse().map_err(|e| {
DatabaseInconsistencyError::on("oauth2_sessions")
.column("scope")
@ -220,8 +155,8 @@ pub async fn lookup_active_refresh_token(
let session = Session {
id: session_id,
client,
browser_session,
client_id: res.oauth2_client_id.into(),
user_session_id: res.user_session_id.into(),
scope,
};

View File

@ -0,0 +1,20 @@
// Copyright 2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use async_trait::async_trait;
#[async_trait]
pub trait OAuth2SessionRepository {
type Error;
}