1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-31 09:24:31 +03:00

OIDC account linking and login

This commit is contained in:
Quentin Gliech
2022-11-24 12:34:46 +01:00
parent 22a337cd45
commit bf432a31e1
13 changed files with 383 additions and 137 deletions

View File

@ -13,13 +13,13 @@
// limitations under the License.
use chrono::{DateTime, Utc};
use mas_data_model::{UpstreamOAuthLink, UpstreamOAuthProvider};
use mas_data_model::{UpstreamOAuthLink, UpstreamOAuthProvider, User};
use rand::Rng;
use sqlx::PgExecutor;
use ulid::Ulid;
use uuid::Uuid;
use crate::{Clock, GenericLookupError};
use crate::{Clock, GenericLookupError, PostgresqlBackend};
struct LinkLookup {
upstream_oauth_link_id: Uuid,
@ -158,3 +158,33 @@ pub async fn add_link(
created_at,
})
}
#[tracing::instrument(
skip_all,
fields(
%upstream_oauth_link.id,
%upstream_oauth_link.subject,
user.id = %user.data,
%user.username,
),
err,
)]
pub async fn associate_link_to_user(
executor: impl PgExecutor<'_>,
upstream_oauth_link: &UpstreamOAuthLink,
user: &User<PostgresqlBackend>,
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
UPDATE upstream_oauth_links
SET user_id = $1
WHERE upstream_oauth_link_id = $2
"#,
Uuid::from(user.data),
Uuid::from(upstream_oauth_link.id),
)
.execute(executor)
.await?;
Ok(())
}

View File

@ -17,9 +17,10 @@ mod provider;
mod session;
pub use self::{
link::{add_link, lookup_link, lookup_link_by_subject},
link::{add_link, associate_link_to_user, lookup_link, lookup_link_by_subject},
provider::{add_provider, lookup_provider, ProviderLookupError},
session::{
add_session, complete_session, lookup_session, lookup_session_on_link, SessionLookupError,
add_session, complete_session, consume_session, lookup_session, lookup_session_on_link,
SessionLookupError,
},
};

View File

@ -43,6 +43,7 @@ struct SessionAndProviderLookup {
nonce: String,
created_at: DateTime<Utc>,
completed_at: Option<DateTime<Utc>>,
consumed_at: Option<DateTime<Utc>>,
provider_issuer: String,
provider_scope: String,
provider_client_id: String,
@ -73,6 +74,7 @@ pub async fn lookup_session(
ua.nonce,
ua.created_at,
ua.completed_at,
ua.consumed_at,
up.issuer AS "provider_issuer",
up.scope AS "provider_scope",
up.client_id AS "provider_client_id",
@ -121,6 +123,7 @@ pub async fn lookup_session(
nonce: res.nonce,
created_at: res.created_at,
completed_at: res.completed_at,
consumed_at: res.consumed_at,
};
Ok((provider, session))
@ -162,8 +165,9 @@ pub async fn add_session(
code_challenge_verifier,
nonce,
created_at,
completed_at
) VALUES ($1, $2, $3, $4, $5, $6, NULL)
completed_at,
consumed_at
) VALUES ($1, $2, $3, $4, $5, $6, NULL, NULL)
"#,
Uuid::from(id),
Uuid::from(upstream_oauth_provider.id),
@ -182,6 +186,7 @@ pub async fn add_session(
nonce,
created_at,
completed_at: None,
consumed_at: None,
})
}
@ -206,9 +211,11 @@ pub async fn complete_session(
UPDATE upstream_oauth_authorization_sessions
SET upstream_oauth_link_id = $1,
completed_at = $2
WHERE upstream_oauth_authorization_session_id = $3
"#,
Uuid::from(upstream_oauth_link.id),
completed_at,
Uuid::from(upstream_oauth_authorization_session.id),
)
.execute(executor)
.await?;
@ -218,6 +225,37 @@ pub async fn complete_session(
Ok(upstream_oauth_authorization_session)
}
/// Mark a session as consumed
#[tracing::instrument(
skip_all,
fields(
%upstream_oauth_authorization_session.id,
),
err,
)]
pub async fn consume_session(
executor: impl PgExecutor<'_>,
clock: &Clock,
mut upstream_oauth_authorization_session: UpstreamOAuthAuthorizationSession,
) -> Result<UpstreamOAuthAuthorizationSession, sqlx::Error> {
let consumed_at = clock.now();
sqlx::query!(
r#"
UPDATE upstream_oauth_authorization_sessions
SET consumed_at = $1
WHERE upstream_oauth_authorization_session_id = $2
"#,
consumed_at,
Uuid::from(upstream_oauth_authorization_session.id),
)
.execute(executor)
.await?;
upstream_oauth_authorization_session.consumed_at = Some(consumed_at);
Ok(upstream_oauth_authorization_session)
}
struct SessionLookup {
upstream_oauth_authorization_session_id: Uuid,
state: String,
@ -225,6 +263,7 @@ struct SessionLookup {
nonce: String,
created_at: DateTime<Utc>,
completed_at: Option<DateTime<Utc>>,
consumed_at: Option<DateTime<Utc>>,
}
/// Lookup a session, which belongs to a link, by its ID
@ -250,7 +289,8 @@ pub async fn lookup_session_on_link(
code_challenge_verifier,
nonce,
created_at,
completed_at
completed_at,
consumed_at
FROM upstream_oauth_authorization_sessions
WHERE upstream_oauth_authorization_session_id = $1
AND upstream_oauth_link_id = $2
@ -271,5 +311,6 @@ pub async fn lookup_session_on_link(
nonce: res.nonce,
created_at: res.created_at,
completed_at: res.completed_at,
consumed_at: res.consumed_at,
})
}

View File

@ -18,7 +18,7 @@ use anyhow::{bail, Context};
use argon2::Argon2;
use chrono::{DateTime, Utc};
use mas_data_model::{
Authentication, BrowserSession, User, UserEmail, UserEmailVerification,
Authentication, BrowserSession, UpstreamOAuthLink, User, UserEmail, UserEmailVerification,
UserEmailVerificationState,
};
use password_hash::{PasswordHash, PasswordHasher, SaltString};
@ -439,6 +439,52 @@ pub async fn authenticate_session(
Ok(())
}
#[tracing::instrument(
skip_all,
fields(
user.id = %session.user.data,
%upstream_oauth_link.id,
user_session.id = %session.data,
user_session_authentication.id,
),
err,
)]
pub async fn authenticate_session_with_upstream(
executor: impl PgExecutor<'_>,
mut rng: impl Rng + Send,
clock: &Clock,
session: &mut BrowserSession<PostgresqlBackend>,
upstream_oauth_link: &UpstreamOAuthLink,
) -> Result<(), sqlx::Error> {
let created_at = clock.now();
let id = Ulid::from_datetime_with_source(created_at.into(), &mut rng);
tracing::Span::current().record(
"user_session_authentication.id",
tracing::field::display(id),
);
sqlx::query!(
r#"
INSERT INTO user_session_authentications
(user_session_authentication_id, user_session_id, created_at)
VALUES ($1, $2, $3)
"#,
Uuid::from(id),
Uuid::from(session.data),
created_at,
)
.execute(executor)
.instrument(tracing::info_span!("Save authentication"))
.await?;
session.last_authentication = Some(Authentication {
data: id,
created_at,
});
Ok(())
}
#[tracing::instrument(
skip_all,
fields(
@ -485,6 +531,44 @@ pub async fn register_user(
Ok(user)
}
#[tracing::instrument(
skip_all,
fields(
user.username = username,
user.id,
),
err,
)]
pub async fn register_passwordless_user(
executor: impl PgExecutor<'_>,
mut rng: impl Rng + Send,
clock: &Clock,
username: &str,
) -> Result<User<PostgresqlBackend>, sqlx::Error> {
let created_at = clock.now();
let id = Ulid::from_datetime_with_source(created_at.into(), &mut rng);
tracing::Span::current().record("user.id", tracing::field::display(id));
sqlx::query!(
r#"
INSERT INTO users (user_id, username, created_at)
VALUES ($1, $2, $3)
"#,
Uuid::from(id),
username,
created_at,
)
.execute(executor)
.await?;
Ok(User {
data: id,
username: username.to_owned(),
sub: id.to_string(),
primary_email: None,
})
}
#[tracing::instrument(
skip_all,
fields(