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
storage: unify most of the remaining errors
This commit is contained in:
@ -47,6 +47,9 @@ pub enum RouteError {
|
|||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Anyhow(#[from] anyhow::Error),
|
Anyhow(#[from] anyhow::Error),
|
||||||
|
|
||||||
|
#[error("authorization grant was not found")]
|
||||||
|
NotFound,
|
||||||
|
|
||||||
#[error("authorization grant is not in a pending state")]
|
#[error("authorization grant is not in a pending state")]
|
||||||
NotPending,
|
NotPending,
|
||||||
}
|
}
|
||||||
@ -55,6 +58,9 @@ impl IntoResponse for RouteError {
|
|||||||
fn into_response(self) -> axum::response::Response {
|
fn into_response(self) -> axum::response::Response {
|
||||||
// TODO: better error pages
|
// TODO: better error pages
|
||||||
match self {
|
match self {
|
||||||
|
RouteError::NotFound => {
|
||||||
|
(StatusCode::NOT_FOUND, "authorization grant was not found").into_response()
|
||||||
|
}
|
||||||
RouteError::NotPending => (
|
RouteError::NotPending => (
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
"authorization grant not in a pending state",
|
"authorization grant not in a pending state",
|
||||||
@ -88,10 +94,12 @@ pub(crate) async fn get(
|
|||||||
|
|
||||||
let maybe_session = session_info.load_session(&mut txn).await?;
|
let maybe_session = session_info.load_session(&mut txn).await?;
|
||||||
|
|
||||||
let grant = get_grant_by_id(&mut txn, grant_id).await?;
|
let grant = get_grant_by_id(&mut txn, grant_id)
|
||||||
|
.await?
|
||||||
|
.ok_or(RouteError::NotFound)?;
|
||||||
|
|
||||||
let callback_destination = CallbackDestination::try_from(&grant)?;
|
let callback_destination = CallbackDestination::try_from(&grant)?;
|
||||||
let continue_grant = PostAuthAction::continue_grant(grant_id);
|
let continue_grant = PostAuthAction::continue_grant(grant.id);
|
||||||
|
|
||||||
let session = if let Some(session) = maybe_session {
|
let session = if let Some(session) = maybe_session {
|
||||||
session
|
session
|
||||||
@ -143,6 +151,7 @@ pub enum GrantCompletionError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl_from_error_for_route!(GrantCompletionError: sqlx::Error);
|
impl_from_error_for_route!(GrantCompletionError: sqlx::Error);
|
||||||
|
impl_from_error_for_route!(GrantCompletionError: mas_storage::DatabaseError);
|
||||||
impl_from_error_for_route!(GrantCompletionError: super::callback::IntoCallbackDestinationError);
|
impl_from_error_for_route!(GrantCompletionError: super::callback::IntoCallbackDestinationError);
|
||||||
|
|
||||||
pub(crate) async fn complete(
|
pub(crate) async fn complete(
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Context;
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Form, Path, State},
|
extract::{Form, Path, State},
|
||||||
response::{Html, IntoResponse, Response},
|
response::{Html, IntoResponse, Response},
|
||||||
@ -38,12 +37,33 @@ use sqlx::PgPool;
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
|
|
||||||
|
use crate::impl_from_error_for_route;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum RouteError {
|
pub enum RouteError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Internal(Box<dyn std::error::Error + Send + Sync>),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Anyhow(#[from] anyhow::Error),
|
Anyhow(#[from] anyhow::Error),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
Csrf(#[from] mas_axum_utils::csrf::CsrfError),
|
||||||
|
|
||||||
|
#[error("Authorization grant not found")]
|
||||||
|
GrantNotFound,
|
||||||
|
|
||||||
|
#[error("Authorization grant already used")]
|
||||||
|
GrantNotPending,
|
||||||
|
|
||||||
|
#[error("Policy violation")]
|
||||||
|
PolicyViolation,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_from_error_for_route!(sqlx::Error);
|
||||||
|
impl_from_error_for_route!(mas_templates::TemplateError);
|
||||||
|
impl_from_error_for_route!(mas_storage::DatabaseError);
|
||||||
|
|
||||||
impl IntoResponse for RouteError {
|
impl IntoResponse for RouteError {
|
||||||
fn into_response(self) -> axum::response::Response {
|
fn into_response(self) -> axum::response::Response {
|
||||||
StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
||||||
@ -58,22 +78,18 @@ pub(crate) async fn get(
|
|||||||
Path(grant_id): Path<Ulid>,
|
Path(grant_id): Path<Ulid>,
|
||||||
) -> Result<Response, RouteError> {
|
) -> Result<Response, RouteError> {
|
||||||
let (clock, mut rng) = crate::rng_and_clock()?;
|
let (clock, mut rng) = crate::rng_and_clock()?;
|
||||||
let mut conn = pool
|
let mut conn = pool.acquire().await?;
|
||||||
.acquire()
|
|
||||||
.await
|
|
||||||
.context("failed to acquire db connection")?;
|
|
||||||
|
|
||||||
let (session_info, cookie_jar) = cookie_jar.session_info();
|
let (session_info, cookie_jar) = cookie_jar.session_info();
|
||||||
|
|
||||||
let maybe_session = session_info
|
let maybe_session = session_info.load_session(&mut conn).await?;
|
||||||
.load_session(&mut conn)
|
|
||||||
.await
|
|
||||||
.context("could not load session")?;
|
|
||||||
|
|
||||||
let grant = get_grant_by_id(&mut conn, grant_id).await?;
|
let grant = get_grant_by_id(&mut conn, grant_id)
|
||||||
|
.await?
|
||||||
|
.ok_or(RouteError::GrantNotFound)?;
|
||||||
|
|
||||||
if !matches!(grant.stage, AuthorizationGrantStage::Pending) {
|
if !matches!(grant.stage, AuthorizationGrantStage::Pending) {
|
||||||
return Err(anyhow::anyhow!("authorization grant not pending").into());
|
return Err(RouteError::GrantNotPending);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(session) = maybe_session {
|
if let Some(session) = maybe_session {
|
||||||
@ -89,10 +105,7 @@ pub(crate) async fn get(
|
|||||||
.with_session(session)
|
.with_session(session)
|
||||||
.with_csrf(csrf_token.form_value());
|
.with_csrf(csrf_token.form_value());
|
||||||
|
|
||||||
let content = templates
|
let content = templates.render_consent(&ctx).await?;
|
||||||
.render_consent(&ctx)
|
|
||||||
.await
|
|
||||||
.context("failed to render template")?;
|
|
||||||
|
|
||||||
Ok((cookie_jar, Html(content)).into_response())
|
Ok((cookie_jar, Html(content)).into_response())
|
||||||
} else {
|
} else {
|
||||||
@ -100,10 +113,7 @@ pub(crate) async fn get(
|
|||||||
.with_session(session)
|
.with_session(session)
|
||||||
.with_csrf(csrf_token.form_value());
|
.with_csrf(csrf_token.form_value());
|
||||||
|
|
||||||
let content = templates
|
let content = templates.render_policy_violation(&ctx).await?;
|
||||||
.render_policy_violation(&ctx)
|
|
||||||
.await
|
|
||||||
.context("failed to render template")?;
|
|
||||||
|
|
||||||
Ok((cookie_jar, Html(content)).into_response())
|
Ok((cookie_jar, Html(content)).into_response())
|
||||||
}
|
}
|
||||||
@ -121,23 +131,17 @@ pub(crate) async fn post(
|
|||||||
Form(form): Form<ProtectedForm<()>>,
|
Form(form): Form<ProtectedForm<()>>,
|
||||||
) -> Result<Response, RouteError> {
|
) -> Result<Response, RouteError> {
|
||||||
let (clock, mut rng) = crate::rng_and_clock()?;
|
let (clock, mut rng) = crate::rng_and_clock()?;
|
||||||
let mut txn = pool
|
let mut txn = pool.begin().await?;
|
||||||
.begin()
|
|
||||||
.await
|
|
||||||
.context("failed to begin db transaction")?;
|
|
||||||
|
|
||||||
cookie_jar
|
cookie_jar.verify_form(clock.now(), form)?;
|
||||||
.verify_form(clock.now(), form)
|
|
||||||
.context("csrf verification failed")?;
|
|
||||||
|
|
||||||
let (session_info, cookie_jar) = cookie_jar.session_info();
|
let (session_info, cookie_jar) = cookie_jar.session_info();
|
||||||
|
|
||||||
let maybe_session = session_info
|
let maybe_session = session_info.load_session(&mut txn).await?;
|
||||||
.load_session(&mut txn)
|
|
||||||
.await
|
|
||||||
.context("could not load session")?;
|
|
||||||
|
|
||||||
let grant = get_grant_by_id(&mut txn, grant_id).await?;
|
let grant = get_grant_by_id(&mut txn, grant_id)
|
||||||
|
.await?
|
||||||
|
.ok_or(RouteError::GrantNotFound)?;
|
||||||
let next = PostAuthAction::continue_grant(grant_id);
|
let next = PostAuthAction::continue_grant(grant_id);
|
||||||
|
|
||||||
let session = if let Some(session) = maybe_session {
|
let session = if let Some(session) = maybe_session {
|
||||||
@ -153,7 +157,7 @@ pub(crate) async fn post(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if !res.valid() {
|
if !res.valid() {
|
||||||
return Err(anyhow::anyhow!("policy violation").into());
|
return Err(RouteError::PolicyViolation);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not consent for the "urn:matrix:org.matrix.msc2967.client:device:*" scope
|
// Do not consent for the "urn:matrix:org.matrix.msc2967.client:device:*" scope
|
||||||
@ -173,11 +177,9 @@ pub(crate) async fn post(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let _grant = give_consent_to_grant(&mut txn, grant)
|
let _grant = give_consent_to_grant(&mut txn, grant).await?;
|
||||||
.await
|
|
||||||
.context("failed to give consent to grant")?;
|
|
||||||
|
|
||||||
txn.commit().await.context("could not commit txn")?;
|
txn.commit().await?;
|
||||||
|
|
||||||
Ok((cookie_jar, next.go_next()).into_response())
|
Ok((cookie_jar, next.go_next()).into_response())
|
||||||
}
|
}
|
||||||
|
@ -98,6 +98,9 @@ pub(crate) enum RouteError {
|
|||||||
#[error("could not verify client credentials")]
|
#[error("could not verify client credentials")]
|
||||||
ClientCredentialsVerification(#[from] CredentialsVerificationError),
|
ClientCredentialsVerification(#[from] CredentialsVerificationError),
|
||||||
|
|
||||||
|
#[error("grant not found")]
|
||||||
|
GrantNotFound,
|
||||||
|
|
||||||
#[error("invalid grant")]
|
#[error("invalid grant")]
|
||||||
InvalidGrant,
|
InvalidGrant,
|
||||||
|
|
||||||
@ -131,7 +134,7 @@ impl IntoResponse for RouteError {
|
|||||||
StatusCode::UNAUTHORIZED,
|
StatusCode::UNAUTHORIZED,
|
||||||
Json(ClientError::from(ClientErrorCode::UnauthorizedClient)),
|
Json(ClientError::from(ClientErrorCode::UnauthorizedClient)),
|
||||||
),
|
),
|
||||||
Self::InvalidGrant => (
|
Self::InvalidGrant | Self::GrantNotFound => (
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
Json(ClientError::from(ClientErrorCode::InvalidGrant)),
|
Json(ClientError::from(ClientErrorCode::InvalidGrant)),
|
||||||
),
|
),
|
||||||
@ -207,7 +210,9 @@ async fn authorization_code_grant(
|
|||||||
|
|
||||||
// TODO: there is a bunch of unnecessary cloning here
|
// TODO: there is a bunch of unnecessary cloning here
|
||||||
// TODO: handle "not found" cases
|
// TODO: handle "not found" cases
|
||||||
let authz_grant = lookup_grant_by_code(&mut txn, &grant.code).await?;
|
let authz_grant = lookup_grant_by_code(&mut txn, &grant.code)
|
||||||
|
.await?
|
||||||
|
.ok_or(RouteError::GrantNotFound)?;
|
||||||
|
|
||||||
let now = clock.now();
|
let now = clock.now();
|
||||||
|
|
||||||
|
@ -45,7 +45,9 @@ impl OptionalPostAuthAction {
|
|||||||
let Some(action) = self.post_auth_action.clone() else { return Ok(None) };
|
let Some(action) = self.post_auth_action.clone() else { return Ok(None) };
|
||||||
let ctx = match action {
|
let ctx = match action {
|
||||||
PostAuthAction::ContinueAuthorizationGrant { data } => {
|
PostAuthAction::ContinueAuthorizationGrant { data } => {
|
||||||
let grant = get_grant_by_id(conn, data).await?;
|
let grant = get_grant_by_id(conn, data)
|
||||||
|
.await?
|
||||||
|
.context("Failed to load authorization grant")?;
|
||||||
let grant = Box::new(grant);
|
let grant = Box::new(grant);
|
||||||
PostAuthContextInner::ContinueAuthorizationGrant { grant }
|
PostAuthContextInner::ContinueAuthorizationGrant { grant }
|
||||||
}
|
}
|
||||||
|
@ -911,7 +911,7 @@ pub async fn fullfill_compat_sso_login(
|
|||||||
device: Device,
|
device: Device,
|
||||||
) -> Result<CompatSsoLogin, DatabaseError> {
|
) -> Result<CompatSsoLogin, DatabaseError> {
|
||||||
if !matches!(compat_sso_login.state, CompatSsoLoginState::Pending) {
|
if !matches!(compat_sso_login.state, CompatSsoLoginState::Pending) {
|
||||||
return Err(DatabaseError::InvalidOperation);
|
return Err(DatabaseError::invalid_operation());
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut txn = conn.begin().await?;
|
let mut txn = conn.begin().await?;
|
||||||
@ -986,7 +986,7 @@ pub async fn mark_compat_sso_login_as_exchanged(
|
|||||||
mut compat_sso_login: CompatSsoLogin,
|
mut compat_sso_login: CompatSsoLogin,
|
||||||
) -> Result<CompatSsoLogin, DatabaseError> {
|
) -> Result<CompatSsoLogin, DatabaseError> {
|
||||||
let CompatSsoLoginState::Fulfilled { fulfilled_at, session } = compat_sso_login.state else {
|
let CompatSsoLoginState::Fulfilled { fulfilled_at, session } = compat_sso_login.state else {
|
||||||
return Err(DatabaseError::InvalidOperation);
|
return Err(DatabaseError::invalid_operation());
|
||||||
};
|
};
|
||||||
|
|
||||||
let exchanged_at = clock.now();
|
let exchanged_at = clock.now();
|
||||||
|
@ -104,7 +104,10 @@ pub enum DatabaseError {
|
|||||||
/// An error which happened because the requested database operation is
|
/// An error which happened because the requested database operation is
|
||||||
/// invalid
|
/// invalid
|
||||||
#[error("Invalid database operation")]
|
#[error("Invalid database operation")]
|
||||||
InvalidOperation,
|
InvalidOperation {
|
||||||
|
#[source]
|
||||||
|
source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
|
||||||
|
},
|
||||||
|
|
||||||
/// An error which happens when an operation affects not enough or too many
|
/// An error which happens when an operation affects not enough or too many
|
||||||
/// rows
|
/// rows
|
||||||
@ -124,6 +127,16 @@ impl DatabaseError {
|
|||||||
Err(DatabaseError::RowsAffected { expected, actual })
|
Err(DatabaseError::RowsAffected { expected, actual })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn to_invalid_operation<E: std::error::Error + Send + Sync + 'static>(e: E) -> Self {
|
||||||
|
Self::InvalidOperation {
|
||||||
|
source: Some(Box::new(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) const fn invalid_operation() -> Self {
|
||||||
|
Self::InvalidOperation { source: None }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
// 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 anyhow::Context;
|
|
||||||
use chrono::{DateTime, Duration, Utc};
|
use chrono::{DateTime, Duration, Utc};
|
||||||
use mas_data_model::{AccessToken, Authentication, BrowserSession, Session, User, UserEmail};
|
use mas_data_model::{AccessToken, Authentication, BrowserSession, Session, User, UserEmail};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
@ -40,7 +39,7 @@ pub async fn add_access_token(
|
|||||||
session: &Session,
|
session: &Session,
|
||||||
access_token: String,
|
access_token: String,
|
||||||
expires_after: Duration,
|
expires_after: Duration,
|
||||||
) -> Result<AccessToken, anyhow::Error> {
|
) -> Result<AccessToken, sqlx::Error> {
|
||||||
let created_at = clock.now();
|
let created_at = clock.now();
|
||||||
let expires_at = created_at + expires_after;
|
let expires_at = created_at + expires_after;
|
||||||
let id = Ulid::from_datetime_with_source(created_at.into(), &mut rng);
|
let id = Ulid::from_datetime_with_source(created_at.into(), &mut rng);
|
||||||
@ -61,8 +60,7 @@ pub async fn add_access_token(
|
|||||||
expires_at,
|
expires_at,
|
||||||
)
|
)
|
||||||
.execute(executor)
|
.execute(executor)
|
||||||
.await
|
.await?;
|
||||||
.context("could not insert oauth2 access token")?;
|
|
||||||
|
|
||||||
Ok(AccessToken {
|
Ok(AccessToken {
|
||||||
id,
|
id,
|
||||||
|
@ -12,11 +12,8 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#![allow(clippy::unused_async)]
|
|
||||||
|
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
use anyhow::Context;
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use mas_data_model::{
|
use mas_data_model::{
|
||||||
Authentication, AuthorizationCode, AuthorizationGrant, AuthorizationGrantStage, BrowserSession,
|
Authentication, AuthorizationCode, AuthorizationGrant, AuthorizationGrantStage, BrowserSession,
|
||||||
@ -31,7 +28,7 @@ use url::Url;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::client::lookup_client;
|
use super::client::lookup_client;
|
||||||
use crate::{Clock, DatabaseInconsistencyError};
|
use crate::{Clock, DatabaseError, DatabaseInconsistencyError2, LookupResultExt};
|
||||||
|
|
||||||
#[tracing::instrument(
|
#[tracing::instrument(
|
||||||
skip_all,
|
skip_all,
|
||||||
@ -39,7 +36,7 @@ use crate::{Clock, DatabaseInconsistencyError};
|
|||||||
%client.id,
|
%client.id,
|
||||||
grant.id,
|
grant.id,
|
||||||
),
|
),
|
||||||
err(Debug),
|
err,
|
||||||
)]
|
)]
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn new_authorization_grant(
|
pub async fn new_authorization_grant(
|
||||||
@ -57,7 +54,7 @@ pub async fn new_authorization_grant(
|
|||||||
response_mode: ResponseMode,
|
response_mode: ResponseMode,
|
||||||
response_type_id_token: bool,
|
response_type_id_token: bool,
|
||||||
requires_consent: bool,
|
requires_consent: bool,
|
||||||
) -> Result<AuthorizationGrant, anyhow::Error> {
|
) -> Result<AuthorizationGrant, sqlx::Error> {
|
||||||
let code_challenge = code
|
let code_challenge = code
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|c| c.pkce.as_ref())
|
.and_then(|c| c.pkce.as_ref())
|
||||||
@ -113,8 +110,7 @@ pub async fn new_authorization_grant(
|
|||||||
created_at,
|
created_at,
|
||||||
)
|
)
|
||||||
.execute(executor)
|
.execute(executor)
|
||||||
.await
|
.await?;
|
||||||
.context("could not insert oauth2 authorization grant")?;
|
|
||||||
|
|
||||||
Ok(AuthorizationGrant {
|
Ok(AuthorizationGrant {
|
||||||
id,
|
id,
|
||||||
@ -171,17 +167,23 @@ impl GrantLookup {
|
|||||||
async fn into_authorization_grant(
|
async fn into_authorization_grant(
|
||||||
self,
|
self,
|
||||||
executor: impl PgExecutor<'_>,
|
executor: impl PgExecutor<'_>,
|
||||||
) -> Result<AuthorizationGrant, DatabaseInconsistencyError> {
|
) -> Result<AuthorizationGrant, DatabaseError> {
|
||||||
let scope: Scope = self
|
let id = self.oauth2_authorization_grant_id.into();
|
||||||
.oauth2_authorization_grant_scope
|
let scope: Scope = self.oauth2_authorization_grant_scope.parse().map_err(|e| {
|
||||||
.parse()
|
DatabaseInconsistencyError2::on("oauth2_authorization_grants")
|
||||||
.map_err(|_e| DatabaseInconsistencyError)?;
|
.column("scope")
|
||||||
|
.row(id)
|
||||||
|
.source(e)
|
||||||
|
})?;
|
||||||
|
|
||||||
// TODO: don't unwrap
|
// TODO: don't unwrap
|
||||||
let client = lookup_client(executor, self.oauth2_client_id.into())
|
let client = lookup_client(executor, self.oauth2_client_id.into())
|
||||||
.await
|
.await?
|
||||||
.unwrap()
|
.ok_or_else(|| {
|
||||||
.unwrap();
|
DatabaseInconsistencyError2::on("oauth2_authorization_grants")
|
||||||
|
.column("client_id")
|
||||||
|
.row(id)
|
||||||
|
})?;
|
||||||
|
|
||||||
let last_authentication = match (
|
let last_authentication = match (
|
||||||
self.user_session_last_authentication_id,
|
self.user_session_last_authentication_id,
|
||||||
@ -192,7 +194,9 @@ impl GrantLookup {
|
|||||||
created_at,
|
created_at,
|
||||||
}),
|
}),
|
||||||
(None, None) => None,
|
(None, None) => None,
|
||||||
_ => return Err(DatabaseInconsistencyError),
|
_ => {
|
||||||
|
return Err(DatabaseInconsistencyError2::on("user_session_authentications").into())
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let primary_email = match (
|
let primary_email = match (
|
||||||
@ -208,7 +212,11 @@ impl GrantLookup {
|
|||||||
confirmed_at,
|
confirmed_at,
|
||||||
}),
|
}),
|
||||||
(None, None, None, None) => None,
|
(None, None, None, None) => None,
|
||||||
_ => return Err(DatabaseInconsistencyError),
|
_ => {
|
||||||
|
return Err(DatabaseInconsistencyError2::on("users")
|
||||||
|
.column("primary_user_email_id")
|
||||||
|
.into())
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let session = match (
|
let session = match (
|
||||||
@ -257,7 +265,14 @@ impl GrantLookup {
|
|||||||
Some(session)
|
Some(session)
|
||||||
}
|
}
|
||||||
(None, None, None, None, None, None, None) => None,
|
(None, None, None, None, None, None, None) => None,
|
||||||
_ => return Err(DatabaseInconsistencyError),
|
_ => {
|
||||||
|
return Err(
|
||||||
|
DatabaseInconsistencyError2::on("oauth2_authorization_grants")
|
||||||
|
.column("oauth2_session_id")
|
||||||
|
.row(id)
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let stage = match (
|
let stage = match (
|
||||||
@ -282,7 +297,12 @@ impl GrantLookup {
|
|||||||
AuthorizationGrantStage::Cancelled { cancelled_at }
|
AuthorizationGrantStage::Cancelled { cancelled_at }
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(DatabaseInconsistencyError);
|
return Err(
|
||||||
|
DatabaseInconsistencyError2::on("oauth2_authorization_grants")
|
||||||
|
.column("stage")
|
||||||
|
.row(id)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -302,7 +322,12 @@ impl GrantLookup {
|
|||||||
}),
|
}),
|
||||||
(None, None) => None,
|
(None, None) => None,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(DatabaseInconsistencyError);
|
return Err(
|
||||||
|
DatabaseInconsistencyError2::on("oauth2_authorization_grants")
|
||||||
|
.column("code_challenge_method")
|
||||||
|
.row(id)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -314,38 +339,63 @@ impl GrantLookup {
|
|||||||
(false, None, None) => None,
|
(false, None, None) => None,
|
||||||
(true, Some(code), pkce) => Some(AuthorizationCode { code, pkce }),
|
(true, Some(code), pkce) => Some(AuthorizationCode { code, pkce }),
|
||||||
_ => {
|
_ => {
|
||||||
return Err(DatabaseInconsistencyError);
|
return Err(
|
||||||
|
DatabaseInconsistencyError2::on("oauth2_authorization_grants")
|
||||||
|
.column("authorization_code")
|
||||||
|
.row(id)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let redirect_uri = self
|
let redirect_uri = self
|
||||||
.oauth2_authorization_grant_redirect_uri
|
.oauth2_authorization_grant_redirect_uri
|
||||||
.parse()
|
.parse()
|
||||||
.map_err(|_e| DatabaseInconsistencyError)?;
|
.map_err(|e| {
|
||||||
|
DatabaseInconsistencyError2::on("oauth2_authorization_grants")
|
||||||
|
.column("redirect_uri")
|
||||||
|
.row(id)
|
||||||
|
.source(e)
|
||||||
|
})?;
|
||||||
|
|
||||||
let response_mode = self
|
let response_mode = self
|
||||||
.oauth2_authorization_grant_response_mode
|
.oauth2_authorization_grant_response_mode
|
||||||
.parse()
|
.parse()
|
||||||
.map_err(|_e| DatabaseInconsistencyError)?;
|
.map_err(|e| {
|
||||||
|
DatabaseInconsistencyError2::on("oauth2_authorization_grants")
|
||||||
|
.column("response_mode")
|
||||||
|
.row(id)
|
||||||
|
.source(e)
|
||||||
|
})?;
|
||||||
|
|
||||||
let max_age = self
|
let max_age = self
|
||||||
.oauth2_authorization_grant_max_age
|
.oauth2_authorization_grant_max_age
|
||||||
.map(u32::try_from)
|
.map(u32::try_from)
|
||||||
.transpose()
|
.transpose()
|
||||||
.map_err(|_e| DatabaseInconsistencyError)?
|
.map_err(|e| {
|
||||||
|
DatabaseInconsistencyError2::on("oauth2_authorization_grants")
|
||||||
|
.column("max_age")
|
||||||
|
.row(id)
|
||||||
|
.source(e)
|
||||||
|
})?
|
||||||
.map(NonZeroU32::try_from)
|
.map(NonZeroU32::try_from)
|
||||||
.transpose()
|
.transpose()
|
||||||
.map_err(|_e| DatabaseInconsistencyError)?;
|
.map_err(|e| {
|
||||||
|
DatabaseInconsistencyError2::on("oauth2_authorization_grants")
|
||||||
|
.column("max_age")
|
||||||
|
.row(id)
|
||||||
|
.source(e)
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(AuthorizationGrant {
|
Ok(AuthorizationGrant {
|
||||||
id: self.oauth2_authorization_grant_id.into(),
|
id,
|
||||||
stage,
|
stage,
|
||||||
client,
|
client,
|
||||||
code,
|
code,
|
||||||
scope,
|
scope,
|
||||||
state: self.oauth2_authorization_grant_state,
|
state: self.oauth2_authorization_grant_state,
|
||||||
nonce: self.oauth2_authorization_grant_nonce,
|
nonce: self.oauth2_authorization_grant_nonce,
|
||||||
max_age, // TODO
|
max_age,
|
||||||
response_mode,
|
response_mode,
|
||||||
redirect_uri,
|
redirect_uri,
|
||||||
created_at: self.oauth2_authorization_grant_created_at,
|
created_at: self.oauth2_authorization_grant_created_at,
|
||||||
@ -358,13 +408,12 @@ impl GrantLookup {
|
|||||||
#[tracing::instrument(
|
#[tracing::instrument(
|
||||||
skip_all,
|
skip_all,
|
||||||
fields(grant.id = %id),
|
fields(grant.id = %id),
|
||||||
err(Debug),
|
err,
|
||||||
)]
|
)]
|
||||||
pub async fn get_grant_by_id(
|
pub async fn get_grant_by_id(
|
||||||
conn: &mut PgConnection,
|
conn: &mut PgConnection,
|
||||||
id: Ulid,
|
id: Ulid,
|
||||||
) -> Result<AuthorizationGrant, anyhow::Error> {
|
) -> Result<Option<AuthorizationGrant>, DatabaseError> {
|
||||||
// TODO: handle "not found" cases
|
|
||||||
let res = sqlx::query_as!(
|
let res = sqlx::query_as!(
|
||||||
GrantLookup,
|
GrantLookup,
|
||||||
r#"
|
r#"
|
||||||
@ -420,19 +469,20 @@ pub async fn get_grant_by_id(
|
|||||||
)
|
)
|
||||||
.fetch_one(&mut *conn)
|
.fetch_one(&mut *conn)
|
||||||
.await
|
.await
|
||||||
.context("failed to get grant by id")?;
|
.to_option()?;
|
||||||
|
|
||||||
|
let Some(res) = res else { return Ok(None) };
|
||||||
|
|
||||||
let grant = res.into_authorization_grant(&mut *conn).await?;
|
let grant = res.into_authorization_grant(&mut *conn).await?;
|
||||||
|
|
||||||
Ok(grant)
|
Ok(Some(grant))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, err(Debug))]
|
#[tracing::instrument(skip_all, err)]
|
||||||
pub async fn lookup_grant_by_code(
|
pub async fn lookup_grant_by_code(
|
||||||
conn: &mut PgConnection,
|
conn: &mut PgConnection,
|
||||||
code: &str,
|
code: &str,
|
||||||
) -> Result<AuthorizationGrant, anyhow::Error> {
|
) -> Result<Option<AuthorizationGrant>, DatabaseError> {
|
||||||
// TODO: handle "not found" cases
|
|
||||||
let res = sqlx::query_as!(
|
let res = sqlx::query_as!(
|
||||||
GrantLookup,
|
GrantLookup,
|
||||||
r#"
|
r#"
|
||||||
@ -488,11 +538,13 @@ pub async fn lookup_grant_by_code(
|
|||||||
)
|
)
|
||||||
.fetch_one(&mut *conn)
|
.fetch_one(&mut *conn)
|
||||||
.await
|
.await
|
||||||
.context("failed to lookup grant by code")?;
|
.to_option()?;
|
||||||
|
|
||||||
|
let Some(res) = res else { return Ok(None) };
|
||||||
|
|
||||||
let grant = res.into_authorization_grant(&mut *conn).await?;
|
let grant = res.into_authorization_grant(&mut *conn).await?;
|
||||||
|
|
||||||
Ok(grant)
|
Ok(Some(grant))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(
|
#[tracing::instrument(
|
||||||
@ -504,7 +556,7 @@ pub async fn lookup_grant_by_code(
|
|||||||
user_session.id = %browser_session.id,
|
user_session.id = %browser_session.id,
|
||||||
user.id = %browser_session.user.id,
|
user.id = %browser_session.user.id,
|
||||||
),
|
),
|
||||||
err(Debug),
|
err,
|
||||||
)]
|
)]
|
||||||
pub async fn derive_session(
|
pub async fn derive_session(
|
||||||
executor: impl PgExecutor<'_>,
|
executor: impl PgExecutor<'_>,
|
||||||
@ -512,7 +564,7 @@ pub async fn derive_session(
|
|||||||
clock: &Clock,
|
clock: &Clock,
|
||||||
grant: &AuthorizationGrant,
|
grant: &AuthorizationGrant,
|
||||||
browser_session: BrowserSession,
|
browser_session: BrowserSession,
|
||||||
) -> Result<Session, anyhow::Error> {
|
) -> Result<Session, sqlx::Error> {
|
||||||
let created_at = clock.now();
|
let created_at = clock.now();
|
||||||
let id = Ulid::from_datetime_with_source(created_at.into(), &mut rng);
|
let id = Ulid::from_datetime_with_source(created_at.into(), &mut rng);
|
||||||
tracing::Span::current().record("session.id", tracing::field::display(id));
|
tracing::Span::current().record("session.id", tracing::field::display(id));
|
||||||
@ -538,8 +590,7 @@ pub async fn derive_session(
|
|||||||
Uuid::from(grant.id),
|
Uuid::from(grant.id),
|
||||||
)
|
)
|
||||||
.execute(executor)
|
.execute(executor)
|
||||||
.await
|
.await?;
|
||||||
.context("could not insert oauth2 session")?;
|
|
||||||
|
|
||||||
Ok(Session {
|
Ok(Session {
|
||||||
id,
|
id,
|
||||||
@ -558,13 +609,13 @@ pub async fn derive_session(
|
|||||||
user_session.id = %session.browser_session.id,
|
user_session.id = %session.browser_session.id,
|
||||||
user.id = %session.browser_session.user.id,
|
user.id = %session.browser_session.user.id,
|
||||||
),
|
),
|
||||||
err(Debug),
|
err,
|
||||||
)]
|
)]
|
||||||
pub async fn fulfill_grant(
|
pub async fn fulfill_grant(
|
||||||
executor: impl PgExecutor<'_>,
|
executor: impl PgExecutor<'_>,
|
||||||
mut grant: AuthorizationGrant,
|
mut grant: AuthorizationGrant,
|
||||||
session: Session,
|
session: Session,
|
||||||
) -> Result<AuthorizationGrant, anyhow::Error> {
|
) -> Result<AuthorizationGrant, DatabaseError> {
|
||||||
let fulfilled_at = sqlx::query_scalar!(
|
let fulfilled_at = sqlx::query_scalar!(
|
||||||
r#"
|
r#"
|
||||||
UPDATE oauth2_authorization_grants AS og
|
UPDATE oauth2_authorization_grants AS og
|
||||||
@ -581,10 +632,12 @@ pub async fn fulfill_grant(
|
|||||||
Uuid::from(session.id),
|
Uuid::from(session.id),
|
||||||
)
|
)
|
||||||
.fetch_one(executor)
|
.fetch_one(executor)
|
||||||
.await
|
.await?;
|
||||||
.context("could not mark grant as fulfilled")?;
|
|
||||||
|
|
||||||
grant.stage = grant.stage.fulfill(fulfilled_at, session)?;
|
grant.stage = grant
|
||||||
|
.stage
|
||||||
|
.fulfill(fulfilled_at, session)
|
||||||
|
.map_err(DatabaseError::to_invalid_operation)?;
|
||||||
|
|
||||||
Ok(grant)
|
Ok(grant)
|
||||||
}
|
}
|
||||||
@ -595,7 +648,7 @@ pub async fn fulfill_grant(
|
|||||||
%grant.id,
|
%grant.id,
|
||||||
client.id = %grant.client.id,
|
client.id = %grant.client.id,
|
||||||
),
|
),
|
||||||
err(Debug),
|
err,
|
||||||
)]
|
)]
|
||||||
pub async fn give_consent_to_grant(
|
pub async fn give_consent_to_grant(
|
||||||
executor: impl PgExecutor<'_>,
|
executor: impl PgExecutor<'_>,
|
||||||
@ -625,13 +678,13 @@ pub async fn give_consent_to_grant(
|
|||||||
%grant.id,
|
%grant.id,
|
||||||
client.id = %grant.client.id,
|
client.id = %grant.client.id,
|
||||||
),
|
),
|
||||||
err(Debug),
|
err,
|
||||||
)]
|
)]
|
||||||
pub async fn exchange_grant(
|
pub async fn exchange_grant(
|
||||||
executor: impl PgExecutor<'_>,
|
executor: impl PgExecutor<'_>,
|
||||||
clock: &Clock,
|
clock: &Clock,
|
||||||
mut grant: AuthorizationGrant,
|
mut grant: AuthorizationGrant,
|
||||||
) -> Result<AuthorizationGrant, anyhow::Error> {
|
) -> Result<AuthorizationGrant, DatabaseError> {
|
||||||
let exchanged_at = clock.now();
|
let exchanged_at = clock.now();
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
@ -643,10 +696,12 @@ pub async fn exchange_grant(
|
|||||||
exchanged_at,
|
exchanged_at,
|
||||||
)
|
)
|
||||||
.execute(executor)
|
.execute(executor)
|
||||||
.await
|
.await?;
|
||||||
.context("could not mark grant as exchanged")?;
|
|
||||||
|
|
||||||
grant.stage = grant.stage.exchange(exchanged_at)?;
|
grant.stage = grant
|
||||||
|
.stage
|
||||||
|
.exchange(exchanged_at)
|
||||||
|
.map_err(DatabaseError::to_invalid_operation)?;
|
||||||
|
|
||||||
Ok(grant)
|
Ok(grant)
|
||||||
}
|
}
|
||||||
|
@ -468,8 +468,12 @@ pub async fn insert_client_from_config(
|
|||||||
jwks: Option<&PublicJsonWebKeySet>,
|
jwks: Option<&PublicJsonWebKeySet>,
|
||||||
jwks_uri: Option<&Url>,
|
jwks_uri: Option<&Url>,
|
||||||
redirect_uris: &[Url],
|
redirect_uris: &[Url],
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), DatabaseError> {
|
||||||
let jwks = jwks.map(serde_json::to_value).transpose()?;
|
let jwks = jwks
|
||||||
|
.map(serde_json::to_value)
|
||||||
|
.transpose()
|
||||||
|
.map_err(DatabaseError::to_invalid_operation)?;
|
||||||
|
|
||||||
let jwks_uri = jwks_uri.map(Url::as_str);
|
let jwks_uri = jwks_uri.map(Url::as_str);
|
||||||
|
|
||||||
let client_auth_method = client_auth_method.to_string();
|
let client_auth_method = client_auth_method.to_string();
|
||||||
@ -526,7 +530,7 @@ pub async fn insert_client_from_config(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn truncate_clients(executor: impl PgExecutor<'_>) -> Result<(), anyhow::Error> {
|
pub async fn truncate_clients(executor: impl PgExecutor<'_>) -> Result<(), sqlx::Error> {
|
||||||
sqlx::query!("TRUNCATE oauth2_client_redirect_uris, oauth2_clients CASCADE")
|
sqlx::query!("TRUNCATE oauth2_client_redirect_uris, oauth2_clients CASCADE")
|
||||||
.execute(executor)
|
.execute(executor)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -21,7 +21,7 @@ use sqlx::PgExecutor;
|
|||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::Clock;
|
use crate::{Clock, DatabaseError, DatabaseInconsistencyError2};
|
||||||
|
|
||||||
#[tracing::instrument(
|
#[tracing::instrument(
|
||||||
skip_all,
|
skip_all,
|
||||||
@ -29,13 +29,13 @@ use crate::Clock;
|
|||||||
%user.id,
|
%user.id,
|
||||||
%client.id,
|
%client.id,
|
||||||
),
|
),
|
||||||
err(Debug),
|
err,
|
||||||
)]
|
)]
|
||||||
pub async fn fetch_client_consent(
|
pub async fn fetch_client_consent(
|
||||||
executor: impl PgExecutor<'_>,
|
executor: impl PgExecutor<'_>,
|
||||||
user: &User,
|
user: &User,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
) -> Result<Scope, anyhow::Error> {
|
) -> Result<Scope, DatabaseError> {
|
||||||
let scope_tokens: Vec<String> = sqlx::query_scalar!(
|
let scope_tokens: Vec<String> = sqlx::query_scalar!(
|
||||||
r#"
|
r#"
|
||||||
SELECT scope_token
|
SELECT scope_token
|
||||||
@ -53,7 +53,13 @@ pub async fn fetch_client_consent(
|
|||||||
.map(|s| ScopeToken::from_str(&s))
|
.map(|s| ScopeToken::from_str(&s))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(scope?)
|
let scope = scope.map_err(|e| {
|
||||||
|
DatabaseInconsistencyError2::on("oauth2_consents")
|
||||||
|
.column("scope_token")
|
||||||
|
.source(e)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(
|
#[tracing::instrument(
|
||||||
@ -63,7 +69,7 @@ pub async fn fetch_client_consent(
|
|||||||
%client.id,
|
%client.id,
|
||||||
%scope,
|
%scope,
|
||||||
),
|
),
|
||||||
err(Debug),
|
err,
|
||||||
)]
|
)]
|
||||||
pub async fn insert_client_consent(
|
pub async fn insert_client_consent(
|
||||||
executor: impl PgExecutor<'_>,
|
executor: impl PgExecutor<'_>,
|
||||||
@ -72,7 +78,7 @@ pub async fn insert_client_consent(
|
|||||||
user: &User,
|
user: &User,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
scope: &Scope,
|
scope: &Scope,
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), sqlx::Error> {
|
||||||
let now = clock.now();
|
let now = clock.now();
|
||||||
let (tokens, ids): (Vec<String>, Vec<Uuid>) = scope
|
let (tokens, ids): (Vec<String>, Vec<Uuid>) = scope
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
use std::collections::{BTreeSet, HashMap};
|
use std::collections::{BTreeSet, HashMap};
|
||||||
|
|
||||||
use anyhow::Context;
|
|
||||||
use mas_data_model::{BrowserSession, Session, User};
|
use mas_data_model::{BrowserSession, Session, User};
|
||||||
use sqlx::{PgConnection, PgExecutor, QueryBuilder};
|
use sqlx::{PgConnection, PgExecutor, QueryBuilder};
|
||||||
use tracing::{info_span, Instrument};
|
use tracing::{info_span, Instrument};
|
||||||
@ -25,7 +24,7 @@ use self::client::lookup_clients;
|
|||||||
use crate::{
|
use crate::{
|
||||||
pagination::{process_page, QueryBuilderExt},
|
pagination::{process_page, QueryBuilderExt},
|
||||||
user::lookup_active_session,
|
user::lookup_active_session,
|
||||||
Clock,
|
Clock, DatabaseError, DatabaseInconsistencyError2,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod access_token;
|
pub mod access_token;
|
||||||
@ -42,13 +41,13 @@ pub mod refresh_token;
|
|||||||
user_session.id = %session.browser_session.id,
|
user_session.id = %session.browser_session.id,
|
||||||
client.id = %session.client.id,
|
client.id = %session.client.id,
|
||||||
),
|
),
|
||||||
err(Debug),
|
err,
|
||||||
)]
|
)]
|
||||||
pub async fn end_oauth_session(
|
pub async fn end_oauth_session(
|
||||||
executor: impl PgExecutor<'_>,
|
executor: impl PgExecutor<'_>,
|
||||||
clock: &Clock,
|
clock: &Clock,
|
||||||
session: Session,
|
session: Session,
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), DatabaseError> {
|
||||||
let finished_at = clock.now();
|
let finished_at = clock.now();
|
||||||
let res = sqlx::query!(
|
let res = sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
@ -62,9 +61,7 @@ pub async fn end_oauth_session(
|
|||||||
.execute(executor)
|
.execute(executor)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
anyhow::ensure!(res.rows_affected() == 1);
|
DatabaseError::ensure_affected_rows(&res, 1)
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(sqlx::FromRow)]
|
#[derive(sqlx::FromRow)]
|
||||||
@ -81,7 +78,7 @@ struct OAuthSessionLookup {
|
|||||||
%user.id,
|
%user.id,
|
||||||
%user.username,
|
%user.username,
|
||||||
),
|
),
|
||||||
err(Display),
|
err,
|
||||||
)]
|
)]
|
||||||
pub async fn get_paginated_user_oauth_sessions(
|
pub async fn get_paginated_user_oauth_sessions(
|
||||||
conn: &mut PgConnection,
|
conn: &mut PgConnection,
|
||||||
@ -90,7 +87,7 @@ pub async fn get_paginated_user_oauth_sessions(
|
|||||||
after: Option<Ulid>,
|
after: Option<Ulid>,
|
||||||
first: Option<usize>,
|
first: Option<usize>,
|
||||||
last: Option<usize>,
|
last: Option<usize>,
|
||||||
) -> Result<(bool, bool, Vec<Session>), anyhow::Error> {
|
) -> Result<(bool, bool, Vec<Session>), DatabaseError> {
|
||||||
let mut query = QueryBuilder::new(
|
let mut query = QueryBuilder::new(
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
@ -139,26 +136,42 @@ pub async fn get_paginated_user_oauth_sessions(
|
|||||||
for id in browser_session_ids {
|
for id in browser_session_ids {
|
||||||
let v = lookup_active_session(&mut *conn, id)
|
let v = lookup_active_session(&mut *conn, id)
|
||||||
.await?
|
.await?
|
||||||
.context("Failed to load active session")?;
|
.ok_or_else(|| {
|
||||||
|
DatabaseInconsistencyError2::on("oauth2_sessions").column("user_session_id")
|
||||||
|
})?;
|
||||||
browser_sessions.insert(id, v);
|
browser_sessions.insert(id, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
let page: Result<Vec<_>, _> = page
|
let page: Result<Vec<_>, DatabaseInconsistencyError2> = page
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|item| {
|
.map(|item| {
|
||||||
|
let id = Ulid::from(item.oauth2_session_id);
|
||||||
let client = clients
|
let client = clients
|
||||||
.get(&Ulid::from(item.oauth2_client_id))
|
.get(&Ulid::from(item.oauth2_client_id))
|
||||||
.context("client was not fetched")?
|
.ok_or_else(|| {
|
||||||
|
DatabaseInconsistencyError2::on("oauth2_sessions")
|
||||||
|
.column("oauth2_client_id")
|
||||||
|
.row(id)
|
||||||
|
})?
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
let browser_session = browser_sessions
|
let browser_session = browser_sessions
|
||||||
.get(&Ulid::from(item.user_session_id))
|
.get(&Ulid::from(item.user_session_id))
|
||||||
.context("browser session was not fetched")?
|
.ok_or_else(|| {
|
||||||
|
DatabaseInconsistencyError2::on("oauth2_sessions")
|
||||||
|
.column("user_session_id")
|
||||||
|
.row(id)
|
||||||
|
})?
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
let scope = item.scope.parse()?;
|
let scope = item.scope.parse().map_err(|e| {
|
||||||
|
DatabaseInconsistencyError2::on("oauth2_sessions")
|
||||||
|
.column("scope")
|
||||||
|
.row(id)
|
||||||
|
.source(e)
|
||||||
|
})?;
|
||||||
|
|
||||||
anyhow::Ok(Session {
|
Ok(Session {
|
||||||
id: Ulid::from(item.oauth2_session_id),
|
id: Ulid::from(item.oauth2_session_id),
|
||||||
client,
|
client,
|
||||||
browser_session,
|
browser_session,
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
// 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 anyhow::Context;
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use mas_data_model::{
|
use mas_data_model::{
|
||||||
AccessToken, Authentication, BrowserSession, RefreshToken, Session, User, UserEmail,
|
AccessToken, Authentication, BrowserSession, RefreshToken, Session, User, UserEmail,
|
||||||
@ -43,7 +42,7 @@ pub async fn add_refresh_token(
|
|||||||
session: &Session,
|
session: &Session,
|
||||||
access_token: AccessToken,
|
access_token: AccessToken,
|
||||||
refresh_token: String,
|
refresh_token: String,
|
||||||
) -> anyhow::Result<RefreshToken> {
|
) -> Result<RefreshToken, sqlx::Error> {
|
||||||
let created_at = clock.now();
|
let created_at = clock.now();
|
||||||
let id = Ulid::from_datetime_with_source(created_at.into(), &mut rng);
|
let id = Ulid::from_datetime_with_source(created_at.into(), &mut rng);
|
||||||
tracing::Span::current().record("refresh_token.id", tracing::field::display(id));
|
tracing::Span::current().record("refresh_token.id", tracing::field::display(id));
|
||||||
@ -63,8 +62,7 @@ pub async fn add_refresh_token(
|
|||||||
created_at,
|
created_at,
|
||||||
)
|
)
|
||||||
.execute(executor)
|
.execute(executor)
|
||||||
.await
|
.await?;
|
||||||
.context("could not insert oauth2 refresh token")?;
|
|
||||||
|
|
||||||
Ok(RefreshToken {
|
Ok(RefreshToken {
|
||||||
id,
|
id,
|
||||||
|
@ -179,14 +179,14 @@ pub async fn add_provider(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, err(Display))]
|
#[tracing::instrument(skip_all, err)]
|
||||||
pub async fn get_paginated_providers(
|
pub async fn get_paginated_providers(
|
||||||
executor: impl PgExecutor<'_>,
|
executor: impl PgExecutor<'_>,
|
||||||
before: Option<Ulid>,
|
before: Option<Ulid>,
|
||||||
after: Option<Ulid>,
|
after: Option<Ulid>,
|
||||||
first: Option<usize>,
|
first: Option<usize>,
|
||||||
last: Option<usize>,
|
last: Option<usize>,
|
||||||
) -> Result<(bool, bool, Vec<UpstreamOAuthProvider>), anyhow::Error> {
|
) -> Result<(bool, bool, Vec<UpstreamOAuthProvider>), DatabaseError> {
|
||||||
let mut query = QueryBuilder::new(
|
let mut query = QueryBuilder::new(
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
@ -224,7 +224,7 @@ pub async fn get_paginated_providers(
|
|||||||
#[tracing::instrument(skip_all, err)]
|
#[tracing::instrument(skip_all, err)]
|
||||||
pub async fn get_providers(
|
pub async fn get_providers(
|
||||||
executor: impl PgExecutor<'_>,
|
executor: impl PgExecutor<'_>,
|
||||||
) -> Result<Vec<UpstreamOAuthProvider>, anyhow::Error> {
|
) -> Result<Vec<UpstreamOAuthProvider>, DatabaseError> {
|
||||||
let res = sqlx::query_as!(
|
let res = sqlx::query_as!(
|
||||||
ProviderLookup,
|
ProviderLookup,
|
||||||
r#"
|
r#"
|
||||||
|
@ -1205,7 +1205,7 @@ pub async fn consume_email_verification(
|
|||||||
user_email_verification.state,
|
user_email_verification.state,
|
||||||
UserEmailVerificationState::Valid
|
UserEmailVerificationState::Valid
|
||||||
) {
|
) {
|
||||||
return Err(DatabaseError::InvalidOperation);
|
return Err(DatabaseError::invalid_operation());
|
||||||
}
|
}
|
||||||
|
|
||||||
let consumed_at = clock.now();
|
let consumed_at = clock.now();
|
||||||
|
Reference in New Issue
Block a user