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
Legacy login via m.login.sso
This commit is contained in:
@ -20,6 +20,7 @@ use rand::{
|
||||
};
|
||||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
use crate::{StorageBackend, StorageBackendMarker, User};
|
||||
|
||||
@ -114,3 +115,29 @@ impl<S: StorageBackendMarker> From<CompatAccessToken<S>> for CompatAccessToken<(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(bound = "T: StorageBackend")]
|
||||
pub enum CompatSsoLoginState<T: StorageBackend> {
|
||||
Pending,
|
||||
Fullfilled {
|
||||
fullfilled_at: DateTime<Utc>,
|
||||
session: CompatSession<T>,
|
||||
},
|
||||
Exchanged {
|
||||
fullfilled_at: DateTime<Utc>,
|
||||
exchanged_at: DateTime<Utc>,
|
||||
session: CompatSession<T>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(bound = "T: StorageBackend")]
|
||||
pub struct CompatSsoLogin<T: StorageBackend> {
|
||||
#[serde(skip_serializing)]
|
||||
pub data: T::CompatSsoLoginData,
|
||||
pub redirect_uri: Url,
|
||||
pub token: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub state: CompatSsoLoginState<T>,
|
||||
}
|
||||
|
@ -29,7 +29,10 @@ pub(crate) mod traits;
|
||||
pub(crate) mod users;
|
||||
|
||||
pub use self::{
|
||||
compat::{CompatAccessToken, CompatRefreshToken, CompatSession, Device},
|
||||
compat::{
|
||||
CompatAccessToken, CompatRefreshToken, CompatSession, CompatSsoLogin, CompatSsoLoginState,
|
||||
Device,
|
||||
},
|
||||
oauth2::{
|
||||
AuthorizationCode, AuthorizationGrant, AuthorizationGrantStage, Client,
|
||||
InvalidRedirectUriError, JwksOrJwksUri, Pkce, Session,
|
||||
|
@ -37,6 +37,7 @@ pub trait StorageBackend {
|
||||
type CompatAccessTokenData: Clone + Debug + PartialEq + Serialize + DeserializeOwned + Default;
|
||||
type CompatRefreshTokenData: Clone + Debug + PartialEq + Serialize + DeserializeOwned + Default;
|
||||
type CompatSessionData: Clone + Debug + PartialEq + Serialize + DeserializeOwned + Default;
|
||||
type CompatSsoLoginData: Clone + Debug + PartialEq + Serialize + DeserializeOwned + Default;
|
||||
}
|
||||
|
||||
impl StorageBackend for () {
|
||||
@ -48,6 +49,7 @@ impl StorageBackend for () {
|
||||
type CompatAccessTokenData = ();
|
||||
type CompatRefreshTokenData = ();
|
||||
type CompatSessionData = ();
|
||||
type CompatSsoLoginData = ();
|
||||
type RefreshTokenData = ();
|
||||
type SessionData = ();
|
||||
type UserData = ();
|
||||
|
@ -16,31 +16,56 @@ use axum::{response::IntoResponse, Extension, Json};
|
||||
use chrono::Duration;
|
||||
use hyper::StatusCode;
|
||||
use mas_config::MatrixConfig;
|
||||
use mas_data_model::{Device, TokenType};
|
||||
use mas_storage::compat::{add_compat_access_token, add_compat_refresh_token, compat_login};
|
||||
use mas_data_model::{CompatSession, Device, TokenType};
|
||||
use mas_storage::{
|
||||
compat::{
|
||||
add_compat_access_token, add_compat_refresh_token, compat_login,
|
||||
get_compat_sso_login_by_token, mark_compat_sso_login_as_exchanged,
|
||||
},
|
||||
PostgresqlBackend,
|
||||
};
|
||||
use rand::thread_rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{serde_as, skip_serializing_none, DurationMilliSeconds};
|
||||
use sqlx::PgPool;
|
||||
use sqlx::{PgPool, Postgres, Transaction};
|
||||
use thiserror::Error;
|
||||
|
||||
use super::MatrixError;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
enum LoginType {
|
||||
#[serde(rename = "m.login.password")]
|
||||
Password,
|
||||
|
||||
#[serde(rename = "m.login.sso")]
|
||||
Sso {
|
||||
identity_providers: Vec<SsoIdentityProvider>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize)]
|
||||
struct SsoIdentityProvider {
|
||||
id: &'static str,
|
||||
name: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct LoginTypes {
|
||||
flows: Vec<LoginType>,
|
||||
}
|
||||
|
||||
pub(crate) async fn get() -> impl IntoResponse {
|
||||
let res = LoginTypes {
|
||||
flows: vec![LoginType::Password],
|
||||
flows: vec![
|
||||
LoginType::Password,
|
||||
LoginType::Sso {
|
||||
identity_providers: vec![SsoIdentityProvider {
|
||||
id: "legacy",
|
||||
name: "SSO",
|
||||
}],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
Json(res)
|
||||
@ -64,6 +89,9 @@ pub enum Credentials {
|
||||
password: String,
|
||||
},
|
||||
|
||||
#[serde(rename = "m.login.token")]
|
||||
Token { token: String },
|
||||
|
||||
#[serde(other)]
|
||||
Unsupported,
|
||||
}
|
||||
@ -140,23 +168,20 @@ pub(crate) async fn post(
|
||||
Extension(config): Extension<MatrixConfig>,
|
||||
Json(input): Json<RequestBody>,
|
||||
) -> Result<impl IntoResponse, RouteError> {
|
||||
let (username, password) = match input.credentials {
|
||||
let mut txn = pool.begin().await?;
|
||||
let session = match input.credentials {
|
||||
Credentials::Password {
|
||||
identifier: Identifier::User { user },
|
||||
password,
|
||||
} => (user, password),
|
||||
} => user_password_login(&mut txn, user, password).await?,
|
||||
|
||||
Credentials::Token { token } => token_login(&mut txn, &token).await?,
|
||||
|
||||
_ => {
|
||||
return Err(RouteError::Unsupported);
|
||||
}
|
||||
};
|
||||
|
||||
let mut txn = pool.begin().await?;
|
||||
|
||||
let device = Device::generate(&mut thread_rng());
|
||||
let session = compat_login(&mut txn, &username, &password, device)
|
||||
.await
|
||||
.map_err(|_| RouteError::LoginFailed)?;
|
||||
|
||||
let user_id = format!("@{}:{}", session.user.username, config.homeserver);
|
||||
|
||||
// If the client asked for a refreshable token, make it expire
|
||||
@ -190,3 +215,29 @@ pub(crate) async fn post(
|
||||
expires_in_ms: expires_in,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn token_login(
|
||||
txn: &mut Transaction<'_, Postgres>,
|
||||
token: &str,
|
||||
) -> Result<CompatSession<PostgresqlBackend>, RouteError> {
|
||||
let login = get_compat_sso_login_by_token(&mut *txn, token).await?;
|
||||
let login = mark_compat_sso_login_as_exchanged(&mut *txn, login).await?;
|
||||
|
||||
match login.state {
|
||||
mas_data_model::CompatSsoLoginState::Exchanged { session, .. } => Ok(session),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn user_password_login(
|
||||
txn: &mut Transaction<'_, Postgres>,
|
||||
username: String,
|
||||
password: String,
|
||||
) -> Result<CompatSession<PostgresqlBackend>, RouteError> {
|
||||
let device = Device::generate(&mut thread_rng());
|
||||
let session = compat_login(txn, &username, &password, device)
|
||||
.await
|
||||
.map_err(|_| RouteError::LoginFailed)?;
|
||||
|
||||
Ok(session)
|
||||
}
|
||||
|
86
crates/handlers/src/compat/login_sso_complete.rs
Normal file
86
crates/handlers/src/compat/login_sso_complete.rs
Normal file
@ -0,0 +1,86 @@
|
||||
// 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 std::collections::HashMap;
|
||||
|
||||
use axum::{
|
||||
extract::Path,
|
||||
response::{IntoResponse, Redirect, Response},
|
||||
Extension,
|
||||
};
|
||||
use axum_extra::extract::PrivateCookieJar;
|
||||
use mas_axum_utils::{FancyError, SessionInfoExt};
|
||||
use mas_config::Encrypter;
|
||||
use mas_data_model::Device;
|
||||
use mas_router::Route;
|
||||
use mas_storage::compat::{fullfill_compat_sso_login, get_compat_sso_login_by_id};
|
||||
use rand::thread_rng;
|
||||
use serde::Serialize;
|
||||
use sqlx::PgPool;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct AllParams<'s> {
|
||||
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
||||
existing_params: Option<HashMap<&'s str, &'s str>>,
|
||||
|
||||
#[serde(rename = "loginToken")]
|
||||
login_token: &'s str,
|
||||
}
|
||||
|
||||
pub async fn get(
|
||||
Extension(pool): Extension<PgPool>,
|
||||
cookie_jar: PrivateCookieJar<Encrypter>,
|
||||
Path(id): Path<i64>,
|
||||
) -> Result<Response, FancyError> {
|
||||
let mut txn = pool.begin().await?;
|
||||
|
||||
let (session_info, cookie_jar) = cookie_jar.session_info();
|
||||
|
||||
let maybe_session = session_info.load_session(&mut txn).await?;
|
||||
|
||||
let session = if let Some(session) = maybe_session {
|
||||
session
|
||||
} else {
|
||||
// If there is no session, redirect to the login screen
|
||||
let login = mas_router::Login::and_continue_compat_sso_login(id);
|
||||
return Ok((cookie_jar, login.go()).into_response());
|
||||
};
|
||||
|
||||
let login = get_compat_sso_login_by_id(&mut txn, id).await?;
|
||||
|
||||
let redirect_uri = {
|
||||
let mut redirect_uri = login.redirect_uri.clone();
|
||||
let existing_params = redirect_uri
|
||||
.query()
|
||||
.map(serde_urlencoded::from_str)
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
||||
let params = AllParams {
|
||||
existing_params,
|
||||
login_token: &login.token,
|
||||
};
|
||||
let query = serde_urlencoded::to_string(¶ms)?;
|
||||
redirect_uri.set_query(Some(&query));
|
||||
redirect_uri
|
||||
};
|
||||
|
||||
let device = Device::generate(&mut thread_rng());
|
||||
let _login = fullfill_compat_sso_login(&mut txn, session.user, login, device).await?;
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
Ok((cookie_jar, Redirect::to(redirect_uri.as_str())).into_response())
|
||||
}
|
76
crates/handlers/src/compat/login_sso_redirect.rs
Normal file
76
crates/handlers/src/compat/login_sso_redirect.rs
Normal file
@ -0,0 +1,76 @@
|
||||
// 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 axum::{extract::Query, response::IntoResponse, Extension};
|
||||
use hyper::StatusCode;
|
||||
use mas_router::{CompatLoginSsoComplete, UrlBuilder};
|
||||
use mas_storage::compat::insert_compat_sso_login;
|
||||
use rand::{
|
||||
distributions::{Alphanumeric, DistString},
|
||||
thread_rng,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Params {
|
||||
#[serde(rename = "redirectUrl")]
|
||||
redirect_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RouteError {
|
||||
#[error(transparent)]
|
||||
Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||
|
||||
#[error(transparent)]
|
||||
Anyhow(#[from] anyhow::Error),
|
||||
|
||||
#[error("missing redirect_url")]
|
||||
MissingRedirectUrl,
|
||||
|
||||
#[error("invalid redirect_url")]
|
||||
InvalidRedirectUrl,
|
||||
}
|
||||
|
||||
impl From<sqlx::Error> for RouteError {
|
||||
fn from(e: sqlx::Error) -> Self {
|
||||
Self::Internal(Box::new(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for RouteError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, format!("{}", self)).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(pool, url_builder), err)]
|
||||
pub async fn get(
|
||||
Extension(pool): Extension<PgPool>,
|
||||
Extension(url_builder): Extension<UrlBuilder>,
|
||||
Query(params): Query<Params>,
|
||||
) -> Result<impl IntoResponse, RouteError> {
|
||||
let redirect_url = params.redirect_url.ok_or(RouteError::MissingRedirectUrl)?;
|
||||
let redirect_url = Url::parse(&redirect_url).map_err(|_| RouteError::InvalidRedirectUrl)?;
|
||||
|
||||
let token = Alphanumeric.sample_string(&mut thread_rng(), 32);
|
||||
let mut conn = pool.acquire().await?;
|
||||
let login = insert_compat_sso_login(&mut conn, token, redirect_url).await?;
|
||||
|
||||
Ok(url_builder.absolute_redirect(&CompatLoginSsoComplete(login.data)))
|
||||
}
|
@ -17,6 +17,8 @@ use hyper::StatusCode;
|
||||
use serde::Serialize;
|
||||
|
||||
pub(crate) mod login;
|
||||
pub(crate) mod login_sso_complete;
|
||||
pub(crate) mod login_sso_redirect;
|
||||
pub(crate) mod logout;
|
||||
pub(crate) mod refresh;
|
||||
|
||||
|
@ -183,6 +183,18 @@ where
|
||||
mas_router::Consent::route(),
|
||||
get(self::oauth2::consent::get).post(self::oauth2::consent::post),
|
||||
)
|
||||
.route(
|
||||
mas_router::CompatLoginSsoRedirect::route(),
|
||||
get(self::compat::login_sso_redirect::get),
|
||||
)
|
||||
.route(
|
||||
mas_router::CompatLoginSsoRedirectIdp::route(),
|
||||
get(self::compat::login_sso_redirect::get),
|
||||
)
|
||||
.route(
|
||||
mas_router::CompatLoginSsoComplete::route(),
|
||||
get(self::compat::login_sso_complete::get),
|
||||
)
|
||||
.layer(ThenLayer::new(
|
||||
move |result: Result<axum::response::Response, Infallible>| async move {
|
||||
let response = result.unwrap();
|
||||
|
@ -41,6 +41,9 @@ impl OptionalPostAuthAction {
|
||||
let grant = Box::new(grant.into());
|
||||
Ok(Some(PostAuthContext::ContinueAuthorizationGrant { grant }))
|
||||
}
|
||||
Some(PostAuthAction::ContinueCompatSsoLogin { .. }) => {
|
||||
Ok(Some(PostAuthContext::ContinueCompatSsoLogin))
|
||||
}
|
||||
Some(PostAuthAction::ChangePassword) => Ok(Some(PostAuthContext::ChangePassword)),
|
||||
None => Ok(None),
|
||||
}
|
||||
|
@ -23,6 +23,10 @@ pub enum PostAuthAction {
|
||||
#[serde(deserialize_with = "serde_with::rust::display_fromstr::deserialize")]
|
||||
data: i64,
|
||||
},
|
||||
ContinueCompatSsoLogin {
|
||||
#[serde(deserialize_with = "serde_with::rust::display_fromstr::deserialize")]
|
||||
data: i64,
|
||||
},
|
||||
ChangePassword,
|
||||
}
|
||||
|
||||
@ -32,10 +36,16 @@ impl PostAuthAction {
|
||||
PostAuthAction::ContinueAuthorizationGrant { data }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn continue_compat_sso_login(data: i64) -> Self {
|
||||
PostAuthAction::ContinueCompatSsoLogin { data }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn go_next(&self) -> axum::response::Redirect {
|
||||
match self {
|
||||
Self::ContinueAuthorizationGrant { data } => ContinueAuthorizationGrant(*data).go(),
|
||||
Self::ContinueCompatSsoLogin { data } => CompatLoginSsoComplete(*data).go(),
|
||||
Self::ChangePassword => AccountPassword.go(),
|
||||
}
|
||||
}
|
||||
@ -161,6 +171,13 @@ impl Login {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn and_continue_compat_sso_login(data: i64) -> Self {
|
||||
Self {
|
||||
post_auth_action: Some(PostAuthAction::continue_compat_sso_login(data)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the login's post auth action.
|
||||
#[must_use]
|
||||
pub fn post_auth_action(&self) -> Option<&PostAuthAction> {
|
||||
@ -387,3 +404,31 @@ pub struct CompatRefresh;
|
||||
impl SimpleRoute for CompatRefresh {
|
||||
const PATH: &'static str = "/_matrix/client/:version/refresh";
|
||||
}
|
||||
|
||||
/// `POST /_matrix/client/v3/login/sso/redirect`
|
||||
pub struct CompatLoginSsoRedirect;
|
||||
|
||||
impl SimpleRoute for CompatLoginSsoRedirect {
|
||||
const PATH: &'static str = "/_matrix/client/:version/login/sso/redirect";
|
||||
}
|
||||
|
||||
/// `POST /_matrix/client/v3/login/sso/redirect/:idp`
|
||||
pub struct CompatLoginSsoRedirectIdp;
|
||||
|
||||
impl SimpleRoute for CompatLoginSsoRedirectIdp {
|
||||
const PATH: &'static str = "/_matrix/client/:version/login/sso/redirect/:idp";
|
||||
}
|
||||
|
||||
/// `GET|POST /complete-compat-sso/:id`
|
||||
pub struct CompatLoginSsoComplete(pub i64);
|
||||
|
||||
impl Route for CompatLoginSsoComplete {
|
||||
type Query = ();
|
||||
fn route() -> &'static str {
|
||||
"/complete-compat-sso/:grant_id"
|
||||
}
|
||||
|
||||
fn path(&self) -> std::borrow::Cow<'static, str> {
|
||||
format!("/complete-compat-sso/{}", self.0).into()
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +46,10 @@ pub trait Route {
|
||||
fn go(&self) -> axum::response::Redirect {
|
||||
axum::response::Redirect::to(&self.relative_url())
|
||||
}
|
||||
|
||||
fn go_absolute(&self, base: &Url) -> axum::response::Redirect {
|
||||
axum::response::Redirect::to(self.absolute_url(base).as_str())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SimpleRoute {
|
||||
|
@ -31,6 +31,13 @@ impl UrlBuilder {
|
||||
destination.absolute_url(&self.base)
|
||||
}
|
||||
|
||||
pub fn absolute_redirect<U>(&self, destination: &U) -> axum::response::Redirect
|
||||
where
|
||||
U: Route,
|
||||
{
|
||||
destination.go_absolute(&self.base)
|
||||
}
|
||||
|
||||
/// Create a new [`UrlBuilder`] from a base URL
|
||||
#[must_use]
|
||||
pub fn new(base: Url) -> Self {
|
||||
|
@ -0,0 +1,15 @@
|
||||
-- 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.
|
||||
|
||||
DROP TABLE compat_sso_logins;
|
@ -0,0 +1,25 @@
|
||||
-- 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.
|
||||
|
||||
CREATE TABLE compat_sso_logins (
|
||||
"id" BIGSERIAL PRIMARY KEY,
|
||||
"redirect_uri" TEXT NOT NULL,
|
||||
"token" TEXT UNIQUE NOT NULL,
|
||||
|
||||
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
"fullfilled_at" TIMESTAMP WITH TIME ZONE,
|
||||
"exchanged_at" TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
"compat_session_id" BIGINT REFERENCES compat_sessions (id) ON DELETE CASCADE
|
||||
);
|
@ -346,6 +346,122 @@
|
||||
},
|
||||
"query": "\n INSERT INTO user_sessions (user_id)\n VALUES ($1)\n RETURNING id, created_at\n "
|
||||
},
|
||||
"32c94e013dc6cc0422dd8bc9ceaaf9100fee09df0cf52f84086a619de2fbbaaf": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "compat_refresh_token_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "compat_refresh_token",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "compat_refresh_token_created_at",
|
||||
"ordinal": 2,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "compat_access_token_id",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "compat_access_token",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "compat_access_token_created_at",
|
||||
"ordinal": 5,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "compat_access_token_expires_at",
|
||||
"ordinal": 6,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "compat_session_id",
|
||||
"ordinal": 7,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "compat_session_created_at",
|
||||
"ordinal": 8,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "compat_session_deleted_at",
|
||||
"ordinal": 9,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "compat_session_device_id",
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "user_id!",
|
||||
"ordinal": 11,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "user_username!",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "user_email_id?",
|
||||
"ordinal": 13,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "user_email?",
|
||||
"ordinal": 14,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "user_email_created_at?",
|
||||
"ordinal": 15,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "user_email_confirmed_at?",
|
||||
"ordinal": 16,
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "\n SELECT\n cr.id AS \"compat_refresh_token_id\",\n cr.token AS \"compat_refresh_token\",\n cr.created_at AS \"compat_refresh_token_created_at\",\n ct.id AS \"compat_access_token_id\",\n ct.token AS \"compat_access_token\",\n ct.created_at AS \"compat_access_token_created_at\",\n ct.expires_at AS \"compat_access_token_expires_at\",\n cs.id AS \"compat_session_id\",\n cs.created_at AS \"compat_session_created_at\",\n cs.deleted_at AS \"compat_session_deleted_at\",\n cs.device_id AS \"compat_session_device_id\",\n u.id AS \"user_id!\",\n u.username AS \"user_username!\",\n ue.id AS \"user_email_id?\",\n ue.email AS \"user_email?\",\n ue.created_at AS \"user_email_created_at?\",\n ue.confirmed_at AS \"user_email_confirmed_at?\"\n\n FROM compat_refresh_tokens cr\n INNER JOIN compat_access_tokens ct\n ON ct.id = cr.compat_access_token_id\n INNER JOIN compat_sessions cs\n ON cs.id = cr.compat_session_id\n INNER JOIN users u\n ON u.id = cs.user_id\n LEFT JOIN user_emails ue\n ON ue.id = u.primary_email_id\n\n WHERE cr.token = $1\n AND cr.next_token_id IS NULL\n AND cs.deleted_at IS NULL\n "
|
||||
},
|
||||
"366ea127c7b220960f17fd1b651600826ac10b8baf92f0e936fd07f34a7dc0fc": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@ -470,6 +586,34 @@
|
||||
},
|
||||
"query": "\n SELECT\n s.id,\n u.id AS user_id,\n u.username,\n s.created_at,\n a.id AS \"last_authentication_id?\",\n a.created_at AS \"last_authd_at?\",\n ue.id AS \"user_email_id?\",\n ue.email AS \"user_email?\",\n ue.created_at AS \"user_email_created_at?\",\n ue.confirmed_at AS \"user_email_confirmed_at?\"\n FROM user_sessions s\n INNER JOIN users u \n ON s.user_id = u.id\n LEFT JOIN user_session_authentications a\n ON a.session_id = s.id\n LEFT JOIN user_emails ue\n ON ue.id = u.primary_email_id\n WHERE s.id = $1 AND s.active\n ORDER BY a.created_at DESC\n LIMIT 1\n "
|
||||
},
|
||||
"4a6bee8775e2c614a28dc691e7e59d0e685859dc6cda07296326f2d9cfb09114": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"ordinal": 1,
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text",
|
||||
"Interval"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "\n INSERT INTO compat_access_tokens (compat_session_id, token, created_at, expires_at)\n VALUES ($1, $2, NOW(), NOW() + $3)\n RETURNING id, created_at\n "
|
||||
},
|
||||
"4b9de6face2e21117c947b4f550cc747ad8397b6dfadb6bc6a84124763dc66e8": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@ -503,6 +647,33 @@
|
||||
},
|
||||
"query": "\n SELECT scope_token\n FROM oauth2_consents\n WHERE user_id = $1 AND oauth2_client_id = $2\n "
|
||||
},
|
||||
"51d148123a4a4254f3fc16574a7136ed015808d5e967f00431f1f9ed12f72c93": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"ordinal": 1,
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "\n INSERT INTO compat_sessions (user_id, device_id)\n VALUES ($1, $2)\n RETURNING id, created_at\n "
|
||||
},
|
||||
"581243a7f0c033548cc9644e0c60855ecb8bfefe51779eb135dd7547b886de79": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@ -724,7 +895,7 @@
|
||||
},
|
||||
"query": "\n INSERT INTO oauth2_clients\n (client_id,\n encrypted_client_secret,\n response_types,\n grant_type_authorization_code,\n grant_type_refresh_token,\n contacts,\n client_name,\n logo_uri,\n client_uri,\n policy_uri,\n tos_uri,\n jwks_uri,\n jwks,\n id_token_signed_response_alg,\n userinfo_signed_response_alg,\n token_endpoint_auth_method,\n token_endpoint_auth_signing_alg,\n initiate_login_uri)\n VALUES\n ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)\n RETURNING id\n "
|
||||
},
|
||||
"5ee505120c3bfddccd7c933de356dd035d18d56316ddf4d0be0d13530b8a643c": {
|
||||
"63522dddec4f2218fdb099473a80411d7f1c19b3750e27bb1f087357588e40c9": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@ -1217,6 +1388,226 @@
|
||||
},
|
||||
"query": "\n UPDATE user_emails\n SET confirmed_at = NOW()\n WHERE id = $1\n RETURNING confirmed_at\n "
|
||||
},
|
||||
"81c673253d86035037695e0d2a3e24bfc8bbb5603c9291f9b5bcac64b43e1c04": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "compat_sso_login_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "compat_sso_login_token",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "compat_sso_login_redirect_uri",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "compat_sso_login_created_at",
|
||||
"ordinal": 3,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "compat_sso_login_fullfilled_at",
|
||||
"ordinal": 4,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "compat_sso_login_exchanged_at",
|
||||
"ordinal": 5,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "compat_session_id?",
|
||||
"ordinal": 6,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "compat_session_created_at?",
|
||||
"ordinal": 7,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "compat_session_deleted_at?",
|
||||
"ordinal": 8,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "compat_session_device_id?",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "user_id?",
|
||||
"ordinal": 10,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "user_username?",
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "user_email_id?",
|
||||
"ordinal": 12,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "user_email?",
|
||||
"ordinal": 13,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "user_email_created_at?",
|
||||
"ordinal": 14,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "user_email_confirmed_at?",
|
||||
"ordinal": 15,
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "\n SELECT\n cl.id AS \"compat_sso_login_id\",\n cl.token AS \"compat_sso_login_token\",\n cl.redirect_uri AS \"compat_sso_login_redirect_uri\",\n cl.created_at AS \"compat_sso_login_created_at\",\n cl.fullfilled_at AS \"compat_sso_login_fullfilled_at\",\n cl.exchanged_at AS \"compat_sso_login_exchanged_at\",\n cs.id AS \"compat_session_id?\",\n cs.created_at AS \"compat_session_created_at?\",\n cs.deleted_at AS \"compat_session_deleted_at?\",\n cs.device_id AS \"compat_session_device_id?\",\n u.id AS \"user_id?\",\n u.username AS \"user_username?\",\n ue.id AS \"user_email_id?\",\n ue.email AS \"user_email?\",\n ue.created_at AS \"user_email_created_at?\",\n ue.confirmed_at AS \"user_email_confirmed_at?\"\n FROM compat_sso_logins cl\n LEFT JOIN compat_sessions cs\n ON cs.id = cl.compat_session_id\n LEFT JOIN users u\n ON u.id = cs.user_id\n LEFT JOIN user_emails ue\n ON ue.id = u.primary_email_id\n WHERE cl.id = $1\n "
|
||||
},
|
||||
"860722788c244caf722d1941e4b83aa421fd179586f9a1c2342c539fcb6c6361": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "compat_sso_login_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "compat_sso_login_token",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "compat_sso_login_redirect_uri",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "compat_sso_login_created_at",
|
||||
"ordinal": 3,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "compat_sso_login_fullfilled_at",
|
||||
"ordinal": 4,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "compat_sso_login_exchanged_at",
|
||||
"ordinal": 5,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "compat_session_id?",
|
||||
"ordinal": 6,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "compat_session_created_at?",
|
||||
"ordinal": 7,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "compat_session_deleted_at?",
|
||||
"ordinal": 8,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "compat_session_device_id?",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "user_id?",
|
||||
"ordinal": 10,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "user_username?",
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "user_email_id?",
|
||||
"ordinal": 12,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "user_email?",
|
||||
"ordinal": 13,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "user_email_created_at?",
|
||||
"ordinal": 14,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "user_email_confirmed_at?",
|
||||
"ordinal": 15,
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "\n SELECT\n cl.id AS \"compat_sso_login_id\",\n cl.token AS \"compat_sso_login_token\",\n cl.redirect_uri AS \"compat_sso_login_redirect_uri\",\n cl.created_at AS \"compat_sso_login_created_at\",\n cl.fullfilled_at AS \"compat_sso_login_fullfilled_at\",\n cl.exchanged_at AS \"compat_sso_login_exchanged_at\",\n cs.id AS \"compat_session_id?\",\n cs.created_at AS \"compat_session_created_at?\",\n cs.deleted_at AS \"compat_session_deleted_at?\",\n cs.device_id AS \"compat_session_device_id?\",\n u.id AS \"user_id?\",\n u.username AS \"user_username?\",\n ue.id AS \"user_email_id?\",\n ue.email AS \"user_email?\",\n ue.created_at AS \"user_email_created_at?\",\n ue.confirmed_at AS \"user_email_confirmed_at?\"\n FROM compat_sso_logins cl\n LEFT JOIN compat_sessions cs\n ON cs.id = cl.compat_session_id\n LEFT JOIN users u\n ON u.id = cs.user_id\n LEFT JOIN user_emails ue\n ON ue.id = u.primary_email_id\n WHERE cl.token = $1\n "
|
||||
},
|
||||
"88ac8783bd5881c42eafd9cf87a16fe6031f3153fd6a8618e689694584aeb2de": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@ -1256,7 +1647,7 @@
|
||||
},
|
||||
"query": "\n INSERT INTO compat_sessions (user_id, device_id)\n VALUES ($1, $2)\n RETURNING id, created_at\n "
|
||||
},
|
||||
"8aed8f0b7aec4854f8dfc88f43e3e6029ef563189eff6ed1e33c3421b395040c": {
|
||||
"929605e8e86ab15a34721b8cbbe29f1bff90102e5641bc49ded86f6539810c73": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@ -1276,13 +1667,12 @@
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text",
|
||||
"Interval"
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "\n INSERT INTO compat_access_tokens (compat_session_id, token, created_at, expires_at)\n VALUES ($1, $2, NOW(), NOW() + $3)\n RETURNING id, created_at\n "
|
||||
"query": "\n INSERT INTO compat_sso_logins (token, redirect_uri)\n VALUES ($1, $2)\n RETURNING id, created_at\n "
|
||||
},
|
||||
"9882e49f34dff80c1442565f035a1b47ed4dbae1a405f58cf2db198885bb9f47": {
|
||||
"describe": {
|
||||
@ -1522,122 +1912,6 @@
|
||||
},
|
||||
"query": "\n INSERT INTO oauth2_client_redirect_uris (oauth2_client_id, redirect_uri)\n SELECT $1, uri FROM UNNEST($2::text[]) uri\n "
|
||||
},
|
||||
"ab800ea65b9c703a56b6c3b7dd47402dbbe0c9900f6d965c908b84332b2aa148": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "compat_refresh_token_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "compat_refresh_token",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "compat_refresh_token_created_at",
|
||||
"ordinal": 2,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "compat_access_token_id",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "compat_access_token",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "compat_access_token_created_at",
|
||||
"ordinal": 5,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "compat_access_token_expires_at",
|
||||
"ordinal": 6,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "compat_session_id",
|
||||
"ordinal": 7,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "compat_session_created_at",
|
||||
"ordinal": 8,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "compat_session_deleted_at",
|
||||
"ordinal": 9,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "compat_session_device_id",
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "user_id!",
|
||||
"ordinal": 11,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "user_username!",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "user_email_id?",
|
||||
"ordinal": 13,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "user_email?",
|
||||
"ordinal": 14,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "user_email_created_at?",
|
||||
"ordinal": 15,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "user_email_confirmed_at?",
|
||||
"ordinal": 16,
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "\n SELECT\n cr.id AS \"compat_refresh_token_id\",\n cr.token AS \"compat_refresh_token\",\n cr.created_at AS \"compat_refresh_token_created_at\",\n ct.id AS \"compat_access_token_id\",\n ct.token AS \"compat_access_token\",\n ct.created_at AS \"compat_access_token_created_at\",\n ct.expires_at AS \"compat_access_token_expires_at\",\n cs.id AS \"compat_session_id\",\n cs.created_at AS \"compat_session_created_at\",\n cs.deleted_at AS \"compat_session_deleted_at\",\n cs.device_id AS \"compat_session_device_id\",\n u.id AS \"user_id!\",\n u.username AS \"user_username!\",\n ue.id AS \"user_email_id?\",\n ue.email AS \"user_email?\",\n ue.created_at AS \"user_email_created_at?\",\n ue.confirmed_at AS \"user_email_confirmed_at?\"\n\n FROM compat_refresh_tokens cr\n INNER JOIN compat_access_tokens ct\n ON ct.id = cr.compat_access_token_id\n INNER JOIN compat_sessions cs\n ON cs.id = cr.compat_session_id\n INNER JOIN users u\n ON u.id = cs.user_id\n LEFT JOIN user_emails ue\n ON ue.id = u.primary_email_id\n\n WHERE cr.token = $1\n AND cr.next_token_id IS NULL\n AND cs.deleted_at IS NULL\n "
|
||||
},
|
||||
"aea289a04e151da235825305a5085bc6aa100fce139dbf10a2c1bed4867fc52a": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@ -1822,6 +2096,27 @@
|
||||
},
|
||||
"query": "\n SELECT\n ev.id AS \"verification_id\",\n (ev.created_at + $2 < NOW()) AS \"verification_expired!\",\n ev.created_at AS \"verification_created_at\",\n ev.consumed_at AS \"verification_consumed_at\",\n ue.id AS \"user_email_id\",\n ue.email AS \"user_email\",\n ue.created_at AS \"user_email_created_at\",\n ue.confirmed_at AS \"user_email_confirmed_at\"\n FROM user_email_verifications ev\n INNER JOIN user_emails ue\n ON ue.id = ev.user_email_id\n WHERE ev.code = $1\n "
|
||||
},
|
||||
"bb5c43feb0e2ac1b8b31ab18423176057e795618ac64527bfb04fee91d86be2a": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "fullfilled_at!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "\n UPDATE compat_sso_logins\n SET\n fullfilled_at = NOW(),\n compat_session_id = $2\n WHERE\n id = $1\n RETURNING fullfilled_at AS \"fullfilled_at!\"\n "
|
||||
},
|
||||
"c2c402cfe0adcafa615f14a499caba4c96ca71d9ffb163e1feb05e5d85f3462c": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@ -1835,32 +2130,25 @@
|
||||
},
|
||||
"query": "\n UPDATE oauth2_refresh_tokens\n SET next_token_id = $2\n WHERE id = $1\n "
|
||||
},
|
||||
"c2de0bb4b67834bd6dd8106c3fcf9874d0e9d1c5d54cbbedfa070dcd0b372475": {
|
||||
"cd14bbd315bec758b846f619202fdfd26634dfdcc185d5117a394b556c019473": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"name": "exchanged_at!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"ordinal": 1,
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text"
|
||||
"Int8"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "\n INSERT INTO compat_access_tokens (compat_session_id, token)\n VALUES ($1, $2)\n RETURNING id, created_at\n "
|
||||
"query": "\n UPDATE compat_sso_logins\n SET\n exchanged_at = NOW()\n WHERE\n id = $1\n RETURNING exchanged_at AS \"exchanged_at!\"\n "
|
||||
},
|
||||
"d2f767218ec2489058db9a0382ca0eea20379c30aeae9f492da4ba35b66f4dc7": {
|
||||
"describe": {
|
||||
@ -1933,6 +2221,33 @@
|
||||
},
|
||||
"query": "\n INSERT INTO user_session_authentications (session_id)\n VALUES ($1)\n RETURNING id, created_at\n "
|
||||
},
|
||||
"d9deefd13877e64c44fa7b60d07e97380ca6a9612b3f08fb8c341b32c3a63a27": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"ordinal": 1,
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "\n INSERT INTO compat_access_tokens (compat_session_id, token)\n VALUES ($1, $2)\n RETURNING id, created_at\n "
|
||||
},
|
||||
"db34b3d7fa5d824e63f388d660615d748e11c1406e8166da907e0a54a665e37a": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
|
@ -12,16 +12,18 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::{bail, Context};
|
||||
use argon2::{Argon2, PasswordHash};
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use mas_data_model::{
|
||||
CompatAccessToken, CompatRefreshToken, CompatSession, Device, User, UserEmail,
|
||||
CompatAccessToken, CompatRefreshToken, CompatSession, CompatSsoLogin, CompatSsoLoginState,
|
||||
Device, User, UserEmail,
|
||||
};
|
||||
use sqlx::{postgres::types::PgInterval, Acquire, PgExecutor, Postgres};
|
||||
use thiserror::Error;
|
||||
use tokio::task;
|
||||
use tracing::{info_span, Instrument};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
user::lookup_user_by_username, DatabaseInconsistencyError, IdAndCreationTime, PostgresqlBackend,
|
||||
@ -518,3 +520,324 @@ pub async fn replace_compat_refresh_token(
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn insert_compat_sso_login(
|
||||
executor: impl PgExecutor<'_>,
|
||||
token: String,
|
||||
redirect_uri: Url,
|
||||
) -> anyhow::Result<CompatSsoLogin<PostgresqlBackend>> {
|
||||
let res = sqlx::query_as!(
|
||||
IdAndCreationTime,
|
||||
r#"
|
||||
INSERT INTO compat_sso_logins (token, redirect_uri)
|
||||
VALUES ($1, $2)
|
||||
RETURNING id, created_at
|
||||
"#,
|
||||
&token,
|
||||
redirect_uri.as_str(),
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.instrument(tracing::info_span!("Insert compat SSO login"))
|
||||
.await
|
||||
.context("could not insert compat SSO login")?;
|
||||
|
||||
Ok(CompatSsoLogin {
|
||||
data: res.id,
|
||||
token,
|
||||
redirect_uri,
|
||||
created_at: res.created_at,
|
||||
state: CompatSsoLoginState::Pending,
|
||||
})
|
||||
}
|
||||
|
||||
struct CompatSsoLoginLookup {
|
||||
compat_sso_login_id: i64,
|
||||
compat_sso_login_token: String,
|
||||
compat_sso_login_redirect_uri: String,
|
||||
compat_sso_login_created_at: DateTime<Utc>,
|
||||
compat_sso_login_fullfilled_at: Option<DateTime<Utc>>,
|
||||
compat_sso_login_exchanged_at: Option<DateTime<Utc>>,
|
||||
compat_session_id: Option<i64>,
|
||||
compat_session_created_at: Option<DateTime<Utc>>,
|
||||
compat_session_deleted_at: Option<DateTime<Utc>>,
|
||||
compat_session_device_id: Option<String>,
|
||||
user_id: Option<i64>,
|
||||
user_username: Option<String>,
|
||||
user_email_id: Option<i64>,
|
||||
user_email: Option<String>,
|
||||
user_email_created_at: Option<DateTime<Utc>>,
|
||||
user_email_confirmed_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl TryFrom<CompatSsoLoginLookup> for CompatSsoLogin<PostgresqlBackend> {
|
||||
type Error = DatabaseInconsistencyError;
|
||||
|
||||
fn try_from(res: CompatSsoLoginLookup) -> Result<Self, Self::Error> {
|
||||
let redirect_uri = Url::parse(&res.compat_sso_login_redirect_uri)
|
||||
.map_err(|_| DatabaseInconsistencyError)?;
|
||||
|
||||
let primary_email = match (
|
||||
res.user_email_id,
|
||||
res.user_email,
|
||||
res.user_email_created_at,
|
||||
res.user_email_confirmed_at,
|
||||
) {
|
||||
(Some(id), Some(email), Some(created_at), confirmed_at) => Some(UserEmail {
|
||||
data: id,
|
||||
email,
|
||||
created_at,
|
||||
confirmed_at,
|
||||
}),
|
||||
(None, None, None, None) => None,
|
||||
_ => return Err(DatabaseInconsistencyError),
|
||||
};
|
||||
|
||||
let user = match (res.user_id, res.user_username, primary_email) {
|
||||
(Some(id), Some(username), primary_email) => Some(User {
|
||||
data: id,
|
||||
username,
|
||||
sub: format!("fake-sub-{}", id),
|
||||
primary_email,
|
||||
}),
|
||||
(None, None, None) => None,
|
||||
_ => return Err(DatabaseInconsistencyError),
|
||||
};
|
||||
|
||||
let session = match (
|
||||
res.compat_session_id,
|
||||
res.compat_session_device_id,
|
||||
res.compat_session_created_at,
|
||||
res.compat_session_deleted_at,
|
||||
user,
|
||||
) {
|
||||
(Some(id), Some(device_id), Some(created_at), deleted_at, Some(user)) => {
|
||||
let device = Device::try_from(device_id).map_err(|_| DatabaseInconsistencyError)?;
|
||||
Some(CompatSession {
|
||||
data: id,
|
||||
user,
|
||||
device,
|
||||
created_at,
|
||||
deleted_at,
|
||||
})
|
||||
}
|
||||
(None, None, None, None, None) => None,
|
||||
_ => return Err(DatabaseInconsistencyError),
|
||||
};
|
||||
|
||||
let state = match (
|
||||
res.compat_sso_login_fullfilled_at,
|
||||
res.compat_sso_login_exchanged_at,
|
||||
session,
|
||||
) {
|
||||
(None, None, None) => CompatSsoLoginState::Pending,
|
||||
(Some(fullfilled_at), None, Some(session)) => CompatSsoLoginState::Fullfilled {
|
||||
fullfilled_at,
|
||||
session,
|
||||
},
|
||||
(Some(fullfilled_at), Some(exchanged_at), Some(session)) => {
|
||||
CompatSsoLoginState::Exchanged {
|
||||
fullfilled_at,
|
||||
exchanged_at,
|
||||
session,
|
||||
}
|
||||
}
|
||||
_ => return Err(DatabaseInconsistencyError),
|
||||
};
|
||||
|
||||
Ok(CompatSsoLogin {
|
||||
data: res.compat_sso_login_id,
|
||||
token: res.compat_sso_login_token,
|
||||
redirect_uri,
|
||||
created_at: res.compat_sso_login_created_at,
|
||||
state,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[tracing::instrument(skip(executor), err)]
|
||||
pub async fn get_compat_sso_login_by_id(
|
||||
executor: impl PgExecutor<'_>,
|
||||
id: i64,
|
||||
) -> anyhow::Result<CompatSsoLogin<PostgresqlBackend>> {
|
||||
let res = sqlx::query_as!(
|
||||
CompatSsoLoginLookup,
|
||||
r#"
|
||||
SELECT
|
||||
cl.id AS "compat_sso_login_id",
|
||||
cl.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.fullfilled_at AS "compat_sso_login_fullfilled_at",
|
||||
cl.exchanged_at AS "compat_sso_login_exchanged_at",
|
||||
cs.id AS "compat_session_id?",
|
||||
cs.created_at AS "compat_session_created_at?",
|
||||
cs.deleted_at AS "compat_session_deleted_at?",
|
||||
cs.device_id AS "compat_session_device_id?",
|
||||
u.id AS "user_id?",
|
||||
u.username AS "user_username?",
|
||||
ue.id AS "user_email_id?",
|
||||
ue.email AS "user_email?",
|
||||
ue.created_at AS "user_email_created_at?",
|
||||
ue.confirmed_at AS "user_email_confirmed_at?"
|
||||
FROM compat_sso_logins cl
|
||||
LEFT JOIN compat_sessions cs
|
||||
ON cs.id = cl.compat_session_id
|
||||
LEFT JOIN users u
|
||||
ON u.id = cs.user_id
|
||||
LEFT JOIN user_emails ue
|
||||
ON ue.id = u.primary_email_id
|
||||
WHERE cl.id = $1
|
||||
"#,
|
||||
id,
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.instrument(tracing::info_span!("Lookup compat SSO login"))
|
||||
.await
|
||||
.context("could not lookup compat SSO login")?;
|
||||
|
||||
Ok(res.try_into()?)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[tracing::instrument(skip(executor), err)]
|
||||
pub async fn get_compat_sso_login_by_token(
|
||||
executor: impl PgExecutor<'_>,
|
||||
token: &str,
|
||||
) -> anyhow::Result<CompatSsoLogin<PostgresqlBackend>> {
|
||||
let res = sqlx::query_as!(
|
||||
CompatSsoLoginLookup,
|
||||
r#"
|
||||
SELECT
|
||||
cl.id AS "compat_sso_login_id",
|
||||
cl.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.fullfilled_at AS "compat_sso_login_fullfilled_at",
|
||||
cl.exchanged_at AS "compat_sso_login_exchanged_at",
|
||||
cs.id AS "compat_session_id?",
|
||||
cs.created_at AS "compat_session_created_at?",
|
||||
cs.deleted_at AS "compat_session_deleted_at?",
|
||||
cs.device_id AS "compat_session_device_id?",
|
||||
u.id AS "user_id?",
|
||||
u.username AS "user_username?",
|
||||
ue.id AS "user_email_id?",
|
||||
ue.email AS "user_email?",
|
||||
ue.created_at AS "user_email_created_at?",
|
||||
ue.confirmed_at AS "user_email_confirmed_at?"
|
||||
FROM compat_sso_logins cl
|
||||
LEFT JOIN compat_sessions cs
|
||||
ON cs.id = cl.compat_session_id
|
||||
LEFT JOIN users u
|
||||
ON u.id = cs.user_id
|
||||
LEFT JOIN user_emails ue
|
||||
ON ue.id = u.primary_email_id
|
||||
WHERE cl.token = $1
|
||||
"#,
|
||||
token,
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.instrument(tracing::info_span!("Lookup compat SSO login"))
|
||||
.await
|
||||
.context("could not lookup compat SSO login")?;
|
||||
|
||||
Ok(res.try_into()?)
|
||||
}
|
||||
|
||||
pub async fn fullfill_compat_sso_login(
|
||||
conn: impl Acquire<'_, Database = Postgres>,
|
||||
user: User<PostgresqlBackend>,
|
||||
mut login: CompatSsoLogin<PostgresqlBackend>,
|
||||
device: Device,
|
||||
) -> anyhow::Result<CompatSsoLogin<PostgresqlBackend>> {
|
||||
// TODO: check if login is in pending state
|
||||
let mut txn = conn.begin().await.context("could not start transaction")?;
|
||||
let res = sqlx::query_as!(
|
||||
IdAndCreationTime,
|
||||
r#"
|
||||
INSERT INTO compat_sessions (user_id, device_id)
|
||||
VALUES ($1, $2)
|
||||
RETURNING id, created_at
|
||||
"#,
|
||||
user.data,
|
||||
device.as_str(),
|
||||
)
|
||||
.fetch_one(&mut txn)
|
||||
.instrument(tracing::info_span!("Insert compat session"))
|
||||
.await
|
||||
.context("could not insert compat session")?;
|
||||
|
||||
let session = CompatSession {
|
||||
data: res.id,
|
||||
user,
|
||||
device,
|
||||
created_at: res.created_at,
|
||||
deleted_at: None,
|
||||
};
|
||||
|
||||
let res = sqlx::query_scalar!(
|
||||
r#"
|
||||
UPDATE compat_sso_logins
|
||||
SET
|
||||
fullfilled_at = NOW(),
|
||||
compat_session_id = $2
|
||||
WHERE
|
||||
id = $1
|
||||
RETURNING fullfilled_at AS "fullfilled_at!"
|
||||
"#,
|
||||
login.data,
|
||||
session.data,
|
||||
)
|
||||
.fetch_one(&mut txn)
|
||||
.instrument(tracing::info_span!("Update compat SSO login"))
|
||||
.await
|
||||
.context("could not update compat SSO login")?;
|
||||
|
||||
let state = CompatSsoLoginState::Fullfilled {
|
||||
fullfilled_at: res,
|
||||
session,
|
||||
};
|
||||
|
||||
login.state = state;
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
Ok(login)
|
||||
}
|
||||
|
||||
pub async fn mark_compat_sso_login_as_exchanged(
|
||||
executor: impl PgExecutor<'_>,
|
||||
mut login: CompatSsoLogin<PostgresqlBackend>,
|
||||
) -> anyhow::Result<CompatSsoLogin<PostgresqlBackend>> {
|
||||
let (fullfilled_at, session) = match login.state {
|
||||
CompatSsoLoginState::Fullfilled {
|
||||
fullfilled_at,
|
||||
session,
|
||||
} => (fullfilled_at, session),
|
||||
_ => bail!("sso login in wrong state"),
|
||||
};
|
||||
|
||||
let res = sqlx::query_scalar!(
|
||||
r#"
|
||||
UPDATE compat_sso_logins
|
||||
SET
|
||||
exchanged_at = NOW()
|
||||
WHERE
|
||||
id = $1
|
||||
RETURNING exchanged_at AS "exchanged_at!"
|
||||
"#,
|
||||
login.data,
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.instrument(tracing::info_span!("Update compat SSO login"))
|
||||
.await
|
||||
.context("could not update compat SSO login")?;
|
||||
|
||||
let state = CompatSsoLoginState::Exchanged {
|
||||
fullfilled_at,
|
||||
exchanged_at: res,
|
||||
session,
|
||||
};
|
||||
login.state = state;
|
||||
Ok(login)
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ impl StorageBackend for PostgresqlBackend {
|
||||
type CompatAccessTokenData = i64;
|
||||
type CompatRefreshTokenData = i64;
|
||||
type CompatSessionData = i64;
|
||||
type CompatSsoLoginData = i64;
|
||||
type RefreshTokenData = i64;
|
||||
type SessionData = i64;
|
||||
type UserData = i64;
|
||||
|
@ -248,6 +248,10 @@ pub enum PostAuthContext {
|
||||
grant: Box<AuthorizationGrant<()>>,
|
||||
},
|
||||
|
||||
/// Continue legacy login
|
||||
/// TODO: add the login context in there
|
||||
ContinueCompatSsoLogin,
|
||||
|
||||
/// Change the account password
|
||||
ChangePassword,
|
||||
}
|
||||
|
Reference in New Issue
Block a user