You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-31 09:24:31 +03:00
WIP: upstream OIDC provider support
This commit is contained in:
84
crates/storage/migrations/20221121151402_upstream_oauth.sql
Normal file
84
crates/storage/migrations/20221121151402_upstream_oauth.sql
Normal file
@ -0,0 +1,84 @@
|
||||
-- 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 "upstream_oauth_providers" (
|
||||
"upstream_oauth_provider_id" UUID NOT NULL
|
||||
CONSTRAINT "upstream_oauth_providers_pkey"
|
||||
PRIMARY KEY,
|
||||
|
||||
"issuer" TEXT NOT NULL,
|
||||
|
||||
"scope" TEXT NOT NULL,
|
||||
|
||||
"client_id" TEXT NOT NULL,
|
||||
|
||||
-- Used for client_secret_basic, client_secret_post and client_secret_jwt auth methods
|
||||
"encrypted_client_secret" TEXT,
|
||||
|
||||
-- Used for client_secret_jwt and private_key_jwt auth methods
|
||||
"token_endpoint_signing_alg" TEXT,
|
||||
|
||||
"token_endpoint_auth_method" TEXT NOT NULL,
|
||||
|
||||
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "upstream_oauth_links" (
|
||||
"upstream_oauth_link_id" UUID NOT NULL
|
||||
CONSTRAINT "upstream_oauth_links_pkey"
|
||||
PRIMARY KEY,
|
||||
|
||||
"upstream_oauth_provider_id" UUID NOT NULL
|
||||
CONSTRAINT "upstream_oauth_links_upstream_oauth_provider_fkey"
|
||||
REFERENCES "upstream_oauth_providers" ("upstream_oauth_provider_id"),
|
||||
|
||||
-- The user is initially NULL when logging in the first time.
|
||||
-- It then either links to an existing account, or creates a new one from scratch.
|
||||
"user_id" UUID
|
||||
CONSTRAINT "upstream_oauth_link_user_fkey"
|
||||
REFERENCES "users" ("user_id"),
|
||||
|
||||
"subject" TEXT NOT NULL,
|
||||
|
||||
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
|
||||
-- There should only be one entry per subject/provider tuple
|
||||
CONSTRAINT "upstream_oauth_links_subject_unique"
|
||||
UNIQUE ("upstream_oauth_provider_id", "subject")
|
||||
);
|
||||
|
||||
CREATE TABLE "upstream_oauth_authorization_sessions" (
|
||||
"upstream_oauth_authorization_session_id" UUID NOT NULL
|
||||
CONSTRAINT "upstream_oauth_authorization_sessions_pkey"
|
||||
PRIMARY KEY,
|
||||
|
||||
"upstream_oauth_provider_id" UUID NOT NULL
|
||||
CONSTRAINT "upstream_oauth_authorization_sessions_upstream_oauth_provider_fkey"
|
||||
REFERENCES "upstream_oauth_providers" ("upstream_oauth_provider_id"),
|
||||
|
||||
-- The link it resolves to at the end of the authorization grant
|
||||
"upstream_oauth_link_id" UUID
|
||||
CONSTRAINT "upstream_oauth_authorization_sessions_upstream_oauth_link_fkey"
|
||||
REFERENCES "upstream_oauth_links" ("upstream_oauth_link_id"),
|
||||
|
||||
"state" TEXT NOT NULL
|
||||
CONSTRAINT "upstream_oauth_authorization_sessions_state_unique"
|
||||
UNIQUE,
|
||||
|
||||
"code_challenge_verifier" TEXT,
|
||||
"nonce" TEXT NOT NULL,
|
||||
|
||||
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"completed_at" TIMESTAMP WITH TIME ZONE
|
||||
);
|
@ -214,6 +214,68 @@
|
||||
},
|
||||
"query": "\n SELECT\n c.oauth2_client_id,\n c.encrypted_client_secret,\n ARRAY(\n SELECT redirect_uri\n FROM oauth2_client_redirect_uris r\n WHERE r.oauth2_client_id = c.oauth2_client_id\n ) AS \"redirect_uris!\",\n c.grant_type_authorization_code,\n c.grant_type_refresh_token,\n c.client_name,\n c.logo_uri,\n c.client_uri,\n c.policy_uri,\n c.tos_uri,\n c.jwks_uri,\n c.jwks,\n c.id_token_signed_response_alg,\n c.userinfo_signed_response_alg,\n c.token_endpoint_auth_method,\n c.token_endpoint_auth_signing_alg,\n c.initiate_login_uri\n FROM oauth2_clients c\n\n WHERE c.oauth2_client_id = $1\n "
|
||||
},
|
||||
"0af182315b36766eca8e232280986bade0202d1b1d64160a99cd14eadcbfc25b": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "upstream_oauth_provider_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"name": "issuer",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "scope",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "client_id",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "encrypted_client_secret",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "token_endpoint_signing_alg",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "token_endpoint_auth_method",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"ordinal": 7,
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "\n SELECT\n upstream_oauth_provider_id,\n issuer,\n scope,\n client_id,\n encrypted_client_secret,\n token_endpoint_signing_alg,\n token_endpoint_auth_method,\n created_at\n FROM upstream_oauth_providers\n WHERE upstream_oauth_provider_id = $1\n "
|
||||
},
|
||||
"0b49cde0b7b79f79ec261502ab89bcffa81f9f5ed2f922a41b1718274b9e3073": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@ -291,6 +353,25 @@
|
||||
},
|
||||
"query": "\n INSERT INTO user_session_authentications\n (user_session_authentication_id, user_session_id, created_at)\n VALUES ($1, $2, $3)\n "
|
||||
},
|
||||
"1ee5cecfafd4726a4ebc08da8a34c09178e6e1e072581c8fca9d3d76967792cb": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Timestamptz"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "\n INSERT INTO upstream_oauth_providers (\n upstream_oauth_provider_id,\n issuer,\n scope,\n token_endpoint_auth_method,\n token_endpoint_signing_alg,\n client_id,\n encrypted_client_secret,\n created_at\n ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n "
|
||||
},
|
||||
"2153118b364a33582e7f598acce3789fcb8d938948a819b15cf0b6d37edf58b2": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@ -1010,6 +1091,23 @@
|
||||
},
|
||||
"query": "\n SELECT scope_token\n FROM oauth2_consents\n WHERE user_id = $1 AND oauth2_client_id = $2\n "
|
||||
},
|
||||
"53a652f0892d25654fe937962913f2f964463fd09f518066fbc83808edc5b394": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Uuid",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Timestamptz"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "\n INSERT INTO upstream_oauth_authorization_sessions (\n upstream_oauth_authorization_session_id,\n upstream_oauth_provider_id,\n state,\n code_challenge_verifier,\n nonce,\n created_at,\n completed_at\n ) VALUES ($1, $2, $3, $4, $5, $6, NULL)\n "
|
||||
},
|
||||
"559a486756d08d101eb7188ef6637b9d24c024d056795b8121f7f04a7f9db6a3": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@ -1157,6 +1255,104 @@
|
||||
},
|
||||
"query": "\n UPDATE oauth2_access_tokens\n SET revoked_at = $2\n WHERE oauth2_access_token_id = $1\n "
|
||||
},
|
||||
"6c8816b2618db8d04ab9393429866d9af59ad280949947fc025c89baffe6a455": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "upstream_oauth_authorization_session_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"name": "upstream_oauth_provider_id",
|
||||
"ordinal": 1,
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"name": "state",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "code_challenge_verifier",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "nonce",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"ordinal": 5,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "completed_at",
|
||||
"ordinal": 6,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "provider_issuer",
|
||||
"ordinal": 7,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "provider_scope",
|
||||
"ordinal": 8,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "provider_client_id",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "provider_encrypted_client_secret",
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "provider_token_endpoint_auth_method",
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "provider_token_endpoint_signing_alg",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "provider_created_at",
|
||||
"ordinal": 13,
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "\n SELECT\n ua.upstream_oauth_authorization_session_id,\n ua.upstream_oauth_provider_id,\n ua.state,\n ua.code_challenge_verifier,\n ua.nonce,\n ua.created_at,\n ua.completed_at,\n up.issuer AS \"provider_issuer\",\n up.scope AS \"provider_scope\",\n up.client_id AS \"provider_client_id\",\n up.encrypted_client_secret AS \"provider_encrypted_client_secret\",\n up.token_endpoint_auth_method AS \"provider_token_endpoint_auth_method\",\n up.token_endpoint_signing_alg AS \"provider_token_endpoint_signing_alg\",\n up.created_at AS \"provider_created_at\"\n FROM upstream_oauth_authorization_sessions ua\n INNER JOIN upstream_oauth_providers up\n USING (upstream_oauth_provider_id)\n WHERE upstream_oauth_authorization_session_id = $1\n "
|
||||
},
|
||||
"7262f81a335a984c4051383d2ede7455ff65ed90fbd3151d625f8a21fd26cb05": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
|
@ -126,6 +126,7 @@ impl StorageBackendMarker for PostgresqlBackend {}
|
||||
pub mod compat;
|
||||
pub mod oauth2;
|
||||
pub(crate) mod pagination;
|
||||
pub mod upstream_oauth2;
|
||||
pub mod user;
|
||||
|
||||
/// Embedded migrations, allowing them to run on startup
|
||||
|
21
crates/storage/src/upstream_oauth2/mod.rs
Normal file
21
crates/storage/src/upstream_oauth2/mod.rs
Normal file
@ -0,0 +1,21 @@
|
||||
// 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.
|
||||
|
||||
mod provider;
|
||||
mod session;
|
||||
|
||||
pub use self::{
|
||||
provider::{add_provider, lookup_provider, ProviderLookupError},
|
||||
session::{add_session, lookup_session, SessionLookupError},
|
||||
};
|
159
crates/storage/src/upstream_oauth2/provider.rs
Normal file
159
crates/storage/src/upstream_oauth2/provider.rs
Normal file
@ -0,0 +1,159 @@
|
||||
// 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 chrono::{DateTime, Utc};
|
||||
use mas_data_model::UpstreamOAuthProvider;
|
||||
use mas_iana::{jose::JsonWebSignatureAlg, oauth::OAuthClientAuthenticationMethod};
|
||||
use oauth2_types::scope::Scope;
|
||||
use rand::Rng;
|
||||
use sqlx::PgExecutor;
|
||||
use thiserror::Error;
|
||||
use ulid::Ulid;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{Clock, DatabaseInconsistencyError, LookupError};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("Failed to lookup upstream OAuth 2.0 provider")]
|
||||
pub enum ProviderLookupError {
|
||||
Driver(#[from] sqlx::Error),
|
||||
Inconcistency(#[from] DatabaseInconsistencyError),
|
||||
}
|
||||
|
||||
impl LookupError for ProviderLookupError {
|
||||
fn not_found(&self) -> bool {
|
||||
matches!(self, Self::Driver(sqlx::Error::RowNotFound))
|
||||
}
|
||||
}
|
||||
|
||||
struct ProviderLookup {
|
||||
upstream_oauth_provider_id: Uuid,
|
||||
issuer: String,
|
||||
scope: String,
|
||||
client_id: String,
|
||||
encrypted_client_secret: Option<String>,
|
||||
token_endpoint_signing_alg: Option<String>,
|
||||
token_endpoint_auth_method: String,
|
||||
created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(upstream_oauth_provider.id = %id),
|
||||
err,
|
||||
)]
|
||||
pub async fn lookup_provider(
|
||||
executor: impl PgExecutor<'_>,
|
||||
id: Ulid,
|
||||
) -> Result<UpstreamOAuthProvider, ProviderLookupError> {
|
||||
let res = sqlx::query_as!(
|
||||
ProviderLookup,
|
||||
r#"
|
||||
SELECT
|
||||
upstream_oauth_provider_id,
|
||||
issuer,
|
||||
scope,
|
||||
client_id,
|
||||
encrypted_client_secret,
|
||||
token_endpoint_signing_alg,
|
||||
token_endpoint_auth_method,
|
||||
created_at
|
||||
FROM upstream_oauth_providers
|
||||
WHERE upstream_oauth_provider_id = $1
|
||||
"#,
|
||||
Uuid::from(id),
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.await?;
|
||||
|
||||
Ok(UpstreamOAuthProvider {
|
||||
id: res.upstream_oauth_provider_id.into(),
|
||||
issuer: res.issuer,
|
||||
scope: res.scope.parse().map_err(|_| DatabaseInconsistencyError)?,
|
||||
client_id: res.client_id,
|
||||
encrypted_client_secret: res.encrypted_client_secret,
|
||||
token_endpoint_auth_method: res
|
||||
.token_endpoint_auth_method
|
||||
.parse()
|
||||
.map_err(|_| DatabaseInconsistencyError)?,
|
||||
token_endpoint_signing_alg: res
|
||||
.token_endpoint_signing_alg
|
||||
.map(|x| x.parse())
|
||||
.transpose()
|
||||
.map_err(|_| DatabaseInconsistencyError)?,
|
||||
created_at: res.created_at,
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
upstream_oauth_provider.id,
|
||||
upstream_oauth_provider.issuer = %issuer,
|
||||
upstream_oauth_provider.client_id = %client_id,
|
||||
),
|
||||
err,
|
||||
)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn add_provider(
|
||||
executor: impl PgExecutor<'_>,
|
||||
mut rng: impl Rng + Send,
|
||||
clock: &Clock,
|
||||
issuer: String,
|
||||
scope: Scope,
|
||||
token_endpoint_auth_method: OAuthClientAuthenticationMethod,
|
||||
token_endpoint_signing_alg: Option<JsonWebSignatureAlg>,
|
||||
client_id: String,
|
||||
encrypted_client_secret: Option<String>,
|
||||
) -> Result<UpstreamOAuthProvider, sqlx::Error> {
|
||||
let created_at = clock.now();
|
||||
let id = Ulid::from_datetime_with_source(created_at.into(), &mut rng);
|
||||
tracing::Span::current().record("upstream_oauth_provider.id", tracing::field::display(id));
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO upstream_oauth_providers (
|
||||
upstream_oauth_provider_id,
|
||||
issuer,
|
||||
scope,
|
||||
token_endpoint_auth_method,
|
||||
token_endpoint_signing_alg,
|
||||
client_id,
|
||||
encrypted_client_secret,
|
||||
created_at
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
"#,
|
||||
Uuid::from(id),
|
||||
&issuer,
|
||||
scope.to_string(),
|
||||
token_endpoint_auth_method.to_string(),
|
||||
token_endpoint_signing_alg.as_ref().map(ToString::to_string),
|
||||
&client_id,
|
||||
encrypted_client_secret.as_deref(),
|
||||
created_at,
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
|
||||
Ok(UpstreamOAuthProvider {
|
||||
id,
|
||||
issuer,
|
||||
scope,
|
||||
client_id,
|
||||
encrypted_client_secret,
|
||||
token_endpoint_signing_alg,
|
||||
token_endpoint_auth_method,
|
||||
created_at,
|
||||
})
|
||||
}
|
184
crates/storage/src/upstream_oauth2/session.rs
Normal file
184
crates/storage/src/upstream_oauth2/session.rs
Normal file
@ -0,0 +1,184 @@
|
||||
// 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 chrono::{DateTime, Utc};
|
||||
use mas_data_model::{UpstreamOAuthAuthorizationSession, UpstreamOAuthProvider};
|
||||
use rand::Rng;
|
||||
use sqlx::PgExecutor;
|
||||
use thiserror::Error;
|
||||
use ulid::Ulid;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{Clock, DatabaseInconsistencyError, LookupError};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("Failed to lookup upstream OAuth 2.0 authorization session")]
|
||||
pub enum SessionLookupError {
|
||||
Driver(#[from] sqlx::Error),
|
||||
Inconcistency(#[from] DatabaseInconsistencyError),
|
||||
}
|
||||
|
||||
impl LookupError for SessionLookupError {
|
||||
fn not_found(&self) -> bool {
|
||||
matches!(self, Self::Driver(sqlx::Error::RowNotFound))
|
||||
}
|
||||
}
|
||||
|
||||
struct SessionLookup {
|
||||
upstream_oauth_authorization_session_id: Uuid,
|
||||
upstream_oauth_provider_id: Uuid,
|
||||
state: String,
|
||||
code_challenge_verifier: Option<String>,
|
||||
nonce: String,
|
||||
created_at: DateTime<Utc>,
|
||||
completed_at: Option<DateTime<Utc>>,
|
||||
provider_issuer: String,
|
||||
provider_scope: String,
|
||||
provider_client_id: String,
|
||||
provider_encrypted_client_secret: Option<String>,
|
||||
provider_token_endpoint_auth_method: String,
|
||||
provider_token_endpoint_signing_alg: Option<String>,
|
||||
provider_created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(upstream_oauth_authorization_session.id = %id),
|
||||
err,
|
||||
)]
|
||||
pub async fn lookup_session(
|
||||
executor: impl PgExecutor<'_>,
|
||||
id: Ulid,
|
||||
) -> Result<(UpstreamOAuthProvider, UpstreamOAuthAuthorizationSession), SessionLookupError> {
|
||||
let res = sqlx::query_as!(
|
||||
SessionLookup,
|
||||
r#"
|
||||
SELECT
|
||||
ua.upstream_oauth_authorization_session_id,
|
||||
ua.upstream_oauth_provider_id,
|
||||
ua.state,
|
||||
ua.code_challenge_verifier,
|
||||
ua.nonce,
|
||||
ua.created_at,
|
||||
ua.completed_at,
|
||||
up.issuer AS "provider_issuer",
|
||||
up.scope AS "provider_scope",
|
||||
up.client_id AS "provider_client_id",
|
||||
up.encrypted_client_secret AS "provider_encrypted_client_secret",
|
||||
up.token_endpoint_auth_method AS "provider_token_endpoint_auth_method",
|
||||
up.token_endpoint_signing_alg AS "provider_token_endpoint_signing_alg",
|
||||
up.created_at AS "provider_created_at"
|
||||
FROM upstream_oauth_authorization_sessions ua
|
||||
INNER JOIN upstream_oauth_providers up
|
||||
USING (upstream_oauth_provider_id)
|
||||
WHERE upstream_oauth_authorization_session_id = $1
|
||||
"#,
|
||||
Uuid::from(id),
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.await?;
|
||||
|
||||
let provider = UpstreamOAuthProvider {
|
||||
id: res.upstream_oauth_provider_id.into(),
|
||||
issuer: res
|
||||
.provider_issuer
|
||||
.parse()
|
||||
.map_err(|_| DatabaseInconsistencyError)?,
|
||||
scope: res
|
||||
.provider_scope
|
||||
.parse()
|
||||
.map_err(|_| DatabaseInconsistencyError)?,
|
||||
client_id: res.provider_client_id,
|
||||
encrypted_client_secret: res.provider_encrypted_client_secret,
|
||||
token_endpoint_auth_method: res
|
||||
.provider_token_endpoint_auth_method
|
||||
.parse()
|
||||
.map_err(|_| DatabaseInconsistencyError)?,
|
||||
token_endpoint_signing_alg: res
|
||||
.provider_token_endpoint_signing_alg
|
||||
.map(|x| x.parse())
|
||||
.transpose()
|
||||
.map_err(|_| DatabaseInconsistencyError)?,
|
||||
created_at: res.provider_created_at,
|
||||
};
|
||||
|
||||
let session = UpstreamOAuthAuthorizationSession {
|
||||
id: res.upstream_oauth_authorization_session_id.into(),
|
||||
state: res.state,
|
||||
code_challenge_verifier: res.code_challenge_verifier,
|
||||
nonce: res.nonce,
|
||||
created_at: res.created_at,
|
||||
completed_at: res.completed_at,
|
||||
};
|
||||
|
||||
Ok((provider, session))
|
||||
}
|
||||
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
upstream_oauth_provider.id = %provider.id,
|
||||
upstream_oauth_provider.issuer = %provider.issuer,
|
||||
upstream_oauth_provider.client_id = %provider.client_id,
|
||||
upstream_oauth_authorization_session.id,
|
||||
),
|
||||
err,
|
||||
)]
|
||||
pub async fn add_session(
|
||||
executor: impl PgExecutor<'_>,
|
||||
mut rng: impl Rng + Send,
|
||||
clock: &Clock,
|
||||
provider: &UpstreamOAuthProvider,
|
||||
state: String,
|
||||
code_challenge_verifier: Option<String>,
|
||||
nonce: String,
|
||||
) -> Result<UpstreamOAuthAuthorizationSession, sqlx::Error> {
|
||||
let created_at = clock.now();
|
||||
let id = Ulid::from_datetime_with_source(created_at.into(), &mut rng);
|
||||
tracing::Span::current().record(
|
||||
"upstream_oauth_authorization_session.id",
|
||||
tracing::field::display(id),
|
||||
);
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO upstream_oauth_authorization_sessions (
|
||||
upstream_oauth_authorization_session_id,
|
||||
upstream_oauth_provider_id,
|
||||
state,
|
||||
code_challenge_verifier,
|
||||
nonce,
|
||||
created_at,
|
||||
completed_at
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, NULL)
|
||||
"#,
|
||||
Uuid::from(id),
|
||||
Uuid::from(provider.id),
|
||||
&state,
|
||||
code_challenge_verifier.as_deref(),
|
||||
nonce,
|
||||
created_at,
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
|
||||
Ok(UpstreamOAuthAuthorizationSession {
|
||||
id,
|
||||
state,
|
||||
code_challenge_verifier,
|
||||
nonce,
|
||||
created_at,
|
||||
completed_at: None,
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user