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
data-model: simplify users and sessions
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -2760,6 +2760,7 @@ dependencies = [
|
||||
"mas-jose",
|
||||
"oauth2-types",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"ulid",
|
||||
@ -3128,6 +3129,7 @@ dependencies = [
|
||||
"mas-data-model",
|
||||
"mas-router",
|
||||
"oauth2-types",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
|
@ -14,10 +14,7 @@
|
||||
|
||||
use axum_extra::extract::cookie::{Cookie, PrivateCookieJar};
|
||||
use mas_data_model::BrowserSession;
|
||||
use mas_storage::{
|
||||
user::{lookup_active_session, ActiveSessionLookupError},
|
||||
PostgresqlBackend,
|
||||
};
|
||||
use mas_storage::user::{lookup_active_session, ActiveSessionLookupError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{Executor, Postgres};
|
||||
use ulid::Ulid;
|
||||
@ -33,9 +30,9 @@ pub struct SessionInfo {
|
||||
impl SessionInfo {
|
||||
/// Forge the cookie from a [`BrowserSession`]
|
||||
#[must_use]
|
||||
pub fn from_session(session: &BrowserSession<PostgresqlBackend>) -> Self {
|
||||
pub fn from_session(session: &BrowserSession) -> Self {
|
||||
Self {
|
||||
current: Some(session.data),
|
||||
current: Some(session.id),
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,7 +47,7 @@ impl SessionInfo {
|
||||
pub async fn load_session(
|
||||
&self,
|
||||
executor: impl Executor<'_, Database = Postgres>,
|
||||
) -> Result<Option<BrowserSession<PostgresqlBackend>>, ActiveSessionLookupError> {
|
||||
) -> Result<Option<BrowserSession>, ActiveSessionLookupError> {
|
||||
let session_id = if let Some(id) = self.current {
|
||||
id
|
||||
} else {
|
||||
@ -70,7 +67,7 @@ pub trait SessionInfoExt {
|
||||
fn update_session_info(self, info: &SessionInfo) -> Self;
|
||||
|
||||
#[must_use]
|
||||
fn set_session(self, session: &BrowserSession<PostgresqlBackend>) -> Self
|
||||
fn set_session(self, session: &BrowserSession) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -204,7 +204,7 @@ impl Options {
|
||||
let user =
|
||||
register_user(&mut txn, &mut rng, &clock, hasher, username, password).await?;
|
||||
txn.commit().await?;
|
||||
info!(user.id = %user.data, %user.username, "User registered");
|
||||
info!(%user.id, %user.username, "User registered");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -217,7 +217,7 @@ impl Options {
|
||||
let user = lookup_user_by_username(&mut txn, username).await?;
|
||||
|
||||
set_password(&mut txn, &mut rng, &clock, hasher, &user, password).await?;
|
||||
info!(user.id = %user.data, %user.username, "Password changed");
|
||||
info!(%user.id, %user.username, "Password changed");
|
||||
txn.commit().await?;
|
||||
|
||||
Ok(())
|
||||
|
@ -16,6 +16,7 @@ use camino::Utf8PathBuf;
|
||||
use clap::Parser;
|
||||
use mas_storage::Clock;
|
||||
use mas_templates::Templates;
|
||||
use rand::SeedableRng;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub(super) struct Options {
|
||||
@ -38,9 +39,11 @@ impl Options {
|
||||
match &self.subcommand {
|
||||
SC::Check { path } => {
|
||||
let clock = Clock::default();
|
||||
// XXX: we should disallow SeedableRng::from_entropy
|
||||
let mut rng = rand_chacha::ChaChaRng::from_entropy();
|
||||
let url_builder = mas_router::UrlBuilder::new("https://example.com/".parse()?);
|
||||
let templates = Templates::load(path.clone(), url_builder).await?;
|
||||
templates.check_render(clock.now()).await?;
|
||||
templates.check_render(clock.now(), &mut rng).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ url = { version = "2.3.1", features = ["serde"] }
|
||||
crc = "3.0.0"
|
||||
rand = "0.8.5"
|
||||
ulid = "1.0.0"
|
||||
rand_chacha = "0.3.1"
|
||||
|
||||
mas-iana = { path = "../iana" }
|
||||
mas-jose = { path = "../jose" }
|
||||
|
@ -86,7 +86,7 @@ impl TryFrom<String> for Device {
|
||||
pub struct CompatSession<T: StorageBackend> {
|
||||
#[serde(skip_serializing)]
|
||||
pub data: T::CompatSessionData,
|
||||
pub user: User<T>,
|
||||
pub user: User,
|
||||
pub device: Device,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub finished_at: Option<DateTime<Utc>>,
|
||||
@ -96,7 +96,7 @@ impl<S: StorageBackendMarker> From<CompatSession<S>> for CompatSession<()> {
|
||||
fn from(t: CompatSession<S>) -> Self {
|
||||
Self {
|
||||
data: (),
|
||||
user: t.user.into(),
|
||||
user: t.user,
|
||||
device: t.device,
|
||||
created_at: t.created_at,
|
||||
finished_at: t.finished_at,
|
||||
@ -125,7 +125,7 @@ impl<S: StorageBackendMarker> From<CompatAccessToken<S>> for CompatAccessToken<(
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct CompatRefreshToken<T: StorageBackend> {
|
||||
pub data: T::RefreshTokenData,
|
||||
pub data: T::CompatRefreshTokenData,
|
||||
pub token: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ use crate::{
|
||||
pub struct Session<T: StorageBackend> {
|
||||
#[serde(skip_serializing)]
|
||||
pub data: T::SessionData,
|
||||
pub browser_session: BrowserSession<T>,
|
||||
pub browser_session: BrowserSession,
|
||||
pub client: Client<T>,
|
||||
pub scope: Scope,
|
||||
}
|
||||
@ -35,7 +35,7 @@ impl<S: StorageBackendMarker> From<Session<S>> for Session<()> {
|
||||
fn from(s: Session<S>) -> Self {
|
||||
Session {
|
||||
data: (),
|
||||
browser_session: s.browser_session.into(),
|
||||
browser_session: s.browser_session,
|
||||
client: s.client.into(),
|
||||
scope: s.scope,
|
||||
}
|
||||
|
@ -30,16 +30,9 @@ impl<T: Clone + Debug + PartialEq + Serialize + DeserializeOwned + Default + Syn
|
||||
}
|
||||
|
||||
pub trait StorageBackend {
|
||||
type UserData: Data;
|
||||
type UserEmailData: Data;
|
||||
type UserEmailVerificationData: Data;
|
||||
type AuthenticationData: Data;
|
||||
type BrowserSessionData: Data;
|
||||
type ClientData: Data;
|
||||
type SessionData: Data;
|
||||
type AuthorizationGrantData: Data;
|
||||
type AccessTokenData: Data;
|
||||
type RefreshTokenData: Data;
|
||||
type CompatAccessTokenData: Data;
|
||||
type CompatRefreshTokenData: Data;
|
||||
type CompatSessionData: Data;
|
||||
@ -47,18 +40,11 @@ pub trait StorageBackend {
|
||||
}
|
||||
|
||||
impl StorageBackend for () {
|
||||
type AccessTokenData = ();
|
||||
type AuthenticationData = ();
|
||||
type AuthorizationGrantData = ();
|
||||
type BrowserSessionData = ();
|
||||
type ClientData = ();
|
||||
type CompatAccessTokenData = ();
|
||||
type CompatRefreshTokenData = ();
|
||||
type CompatSessionData = ();
|
||||
type CompatSsoLoginData = ();
|
||||
type RefreshTokenData = ();
|
||||
type SessionData = ();
|
||||
type UserData = ();
|
||||
type UserEmailData = ();
|
||||
type UserEmailVerificationData = ();
|
||||
}
|
||||
|
@ -13,27 +13,23 @@
|
||||
// limitations under the License.
|
||||
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use rand::{Rng, SeedableRng};
|
||||
use serde::Serialize;
|
||||
use ulid::Ulid;
|
||||
|
||||
use crate::traits::{StorageBackend, StorageBackendMarker};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(bound = "T: StorageBackend")]
|
||||
pub struct User<T: StorageBackend> {
|
||||
pub data: T::UserData,
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct User {
|
||||
pub id: Ulid,
|
||||
pub username: String,
|
||||
pub sub: String,
|
||||
pub primary_email: Option<UserEmail<T>>,
|
||||
pub primary_email: Option<UserEmail>,
|
||||
}
|
||||
|
||||
impl<T: StorageBackend> User<T>
|
||||
where
|
||||
T::UserData: Default,
|
||||
{
|
||||
impl User {
|
||||
#[must_use]
|
||||
pub fn samples(_now: chrono::DateTime<Utc>) -> Vec<Self> {
|
||||
pub fn samples(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self> {
|
||||
vec![User {
|
||||
data: Default::default(),
|
||||
id: Ulid::from_datetime_with_source(now.into(), rng),
|
||||
username: "john".to_owned(),
|
||||
sub: "123-456".to_owned(),
|
||||
primary_email: None,
|
||||
@ -41,55 +37,22 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: StorageBackendMarker> From<User<S>> for User<()> {
|
||||
fn from(u: User<S>) -> Self {
|
||||
User {
|
||||
data: (),
|
||||
username: u.username,
|
||||
sub: u.sub,
|
||||
primary_email: u.primary_email.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(bound = "T: StorageBackend")]
|
||||
pub struct Authentication<T: StorageBackend> {
|
||||
#[serde(skip_serializing)]
|
||||
pub data: T::AuthenticationData,
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct Authentication {
|
||||
pub id: Ulid,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl<S: StorageBackendMarker> From<Authentication<S>> for Authentication<()> {
|
||||
fn from(a: Authentication<S>) -> Self {
|
||||
Authentication {
|
||||
data: (),
|
||||
created_at: a.created_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(bound = "T: StorageBackend")]
|
||||
pub struct BrowserSession<T: StorageBackend> {
|
||||
pub data: T::BrowserSessionData,
|
||||
pub user: User<T>,
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct BrowserSession {
|
||||
pub id: Ulid,
|
||||
pub user: User,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub last_authentication: Option<Authentication<T>>,
|
||||
pub last_authentication: Option<Authentication>,
|
||||
}
|
||||
|
||||
impl<S: StorageBackendMarker> From<BrowserSession<S>> for BrowserSession<()> {
|
||||
fn from(s: BrowserSession<S>) -> Self {
|
||||
BrowserSession {
|
||||
data: (),
|
||||
user: s.user.into(),
|
||||
created_at: s.created_at,
|
||||
last_authentication: s.last_authentication.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: StorageBackend> BrowserSession<S> {
|
||||
impl BrowserSession {
|
||||
#[must_use]
|
||||
pub fn was_authenticated_after(&self, after: DateTime<Utc>) -> bool {
|
||||
if let Some(auth) = &self.last_authentication {
|
||||
auth.created_at > after
|
||||
@ -99,17 +62,13 @@ impl<S: StorageBackend> BrowserSession<S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: StorageBackend> BrowserSession<T>
|
||||
where
|
||||
T::BrowserSessionData: Default,
|
||||
T::UserData: Default,
|
||||
{
|
||||
impl BrowserSession {
|
||||
#[must_use]
|
||||
pub fn samples(now: chrono::DateTime<Utc>) -> Vec<Self> {
|
||||
User::<T>::samples(now)
|
||||
pub fn samples(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self> {
|
||||
User::samples(now, rng)
|
||||
.into_iter()
|
||||
.map(|user| BrowserSession {
|
||||
data: Default::default(),
|
||||
id: Ulid::from_datetime_with_source(now.into(), rng),
|
||||
user,
|
||||
created_at: now,
|
||||
last_authentication: None,
|
||||
@ -118,41 +77,26 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(bound = "T: StorageBackend")]
|
||||
pub struct UserEmail<T: StorageBackend> {
|
||||
pub data: T::UserEmailData,
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct UserEmail {
|
||||
pub id: Ulid,
|
||||
pub email: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub confirmed_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl<S: StorageBackendMarker> From<UserEmail<S>> for UserEmail<()> {
|
||||
fn from(e: UserEmail<S>) -> Self {
|
||||
Self {
|
||||
data: (),
|
||||
email: e.email,
|
||||
created_at: e.created_at,
|
||||
confirmed_at: e.confirmed_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: StorageBackend> UserEmail<T>
|
||||
where
|
||||
T::UserEmailData: Default,
|
||||
{
|
||||
impl UserEmail {
|
||||
#[must_use]
|
||||
pub fn samples(now: chrono::DateTime<Utc>) -> Vec<Self> {
|
||||
pub fn samples(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self> {
|
||||
vec![
|
||||
Self {
|
||||
data: T::UserEmailData::default(),
|
||||
id: Ulid::from_datetime_with_source(now.into(), rng),
|
||||
email: "alice@example.com".to_owned(),
|
||||
created_at: now,
|
||||
confirmed_at: Some(now),
|
||||
},
|
||||
Self {
|
||||
data: T::UserEmailData::default(),
|
||||
id: Ulid::from_datetime_with_source(now.into(), rng),
|
||||
email: "bob@example.com".to_owned(),
|
||||
created_at: now,
|
||||
confirmed_at: None,
|
||||
@ -168,34 +112,18 @@ pub enum UserEmailVerificationState {
|
||||
Valid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(bound = "T: StorageBackend")]
|
||||
pub struct UserEmailVerification<T: StorageBackend> {
|
||||
pub data: T::UserEmailVerificationData,
|
||||
pub email: UserEmail<T>,
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct UserEmailVerification {
|
||||
pub id: Ulid,
|
||||
pub email: UserEmail,
|
||||
pub code: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub state: UserEmailVerificationState,
|
||||
}
|
||||
|
||||
impl<S: StorageBackendMarker> From<UserEmailVerification<S>> for UserEmailVerification<()> {
|
||||
fn from(v: UserEmailVerification<S>) -> Self {
|
||||
Self {
|
||||
data: (),
|
||||
email: v.email.into(),
|
||||
code: v.code,
|
||||
created_at: v.created_at,
|
||||
state: v.state,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: StorageBackend> UserEmailVerification<T>
|
||||
where
|
||||
T::UserEmailData: Default + Clone,
|
||||
{
|
||||
impl UserEmailVerification {
|
||||
#[must_use]
|
||||
pub fn samples(now: chrono::DateTime<Utc>) -> Vec<Self> {
|
||||
pub fn samples(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self> {
|
||||
let states = [
|
||||
UserEmailVerificationState::AlreadyUsed {
|
||||
when: now - Duration::minutes(5),
|
||||
@ -208,9 +136,13 @@ where
|
||||
|
||||
states
|
||||
.into_iter()
|
||||
.flat_map(|state| {
|
||||
UserEmail::samples(now).into_iter().map(move |email| Self {
|
||||
data: Default::default(),
|
||||
.flat_map(move |state| {
|
||||
let mut rng =
|
||||
rand_chacha::ChaChaRng::from_rng(&mut *rng).expect("could not seed rng");
|
||||
UserEmail::samples(now, &mut rng)
|
||||
.into_iter()
|
||||
.map(move |email| Self {
|
||||
id: Ulid::from_datetime_with_source(now.into(), &mut rng),
|
||||
code: "123456".to_owned(),
|
||||
email,
|
||||
created_at: now - Duration::minutes(10),
|
||||
|
@ -114,7 +114,7 @@ impl RootQuery {
|
||||
let Some(session) = session else { return Ok(None) };
|
||||
let current_user = session.user;
|
||||
|
||||
if current_user.data == id {
|
||||
if current_user.id == id {
|
||||
Ok(Some(User(current_user)))
|
||||
} else {
|
||||
Ok(None)
|
||||
@ -141,7 +141,7 @@ impl RootQuery {
|
||||
.to_option()?;
|
||||
|
||||
let ret = browser_session.and_then(|browser_session| {
|
||||
if browser_session.user.data == current_user.data {
|
||||
if browser_session.user.id == current_user.id {
|
||||
Some(BrowserSession(browser_session))
|
||||
} else {
|
||||
None
|
||||
@ -193,7 +193,7 @@ impl RootQuery {
|
||||
.to_option()?;
|
||||
|
||||
// Ensure that the link belongs to the current user
|
||||
let link = link.filter(|link| link.user_id == Some(current_user.data));
|
||||
let link = link.filter(|link| link.user_id == Some(current_user.id));
|
||||
|
||||
Ok(link.map(UpstreamOAuth2Link::new))
|
||||
}
|
||||
|
@ -14,16 +14,15 @@
|
||||
|
||||
use async_graphql::{Description, Object, ID};
|
||||
use chrono::{DateTime, Utc};
|
||||
use mas_storage::PostgresqlBackend;
|
||||
|
||||
use super::{NodeType, User};
|
||||
|
||||
/// A browser session represents a logged in user in a browser.
|
||||
#[derive(Description)]
|
||||
pub struct BrowserSession(pub mas_data_model::BrowserSession<PostgresqlBackend>);
|
||||
pub struct BrowserSession(pub mas_data_model::BrowserSession);
|
||||
|
||||
impl From<mas_data_model::BrowserSession<PostgresqlBackend>> for BrowserSession {
|
||||
fn from(v: mas_data_model::BrowserSession<PostgresqlBackend>) -> Self {
|
||||
impl From<mas_data_model::BrowserSession> for BrowserSession {
|
||||
fn from(v: mas_data_model::BrowserSession) -> Self {
|
||||
Self(v)
|
||||
}
|
||||
}
|
||||
@ -32,7 +31,7 @@ impl From<mas_data_model::BrowserSession<PostgresqlBackend>> for BrowserSession
|
||||
impl BrowserSession {
|
||||
/// ID of the object.
|
||||
pub async fn id(&self) -> ID {
|
||||
NodeType::BrowserSession.id(self.0.data)
|
||||
NodeType::BrowserSession.id(self.0.id)
|
||||
}
|
||||
|
||||
/// The user logged in this session.
|
||||
@ -54,13 +53,13 @@ impl BrowserSession {
|
||||
/// An authentication records when a user enter their credential in a browser
|
||||
/// session.
|
||||
#[derive(Description)]
|
||||
pub struct Authentication(pub mas_data_model::Authentication<PostgresqlBackend>);
|
||||
pub struct Authentication(pub mas_data_model::Authentication);
|
||||
|
||||
#[Object(use_type_description)]
|
||||
impl Authentication {
|
||||
/// ID of the object.
|
||||
pub async fn id(&self) -> ID {
|
||||
NodeType::Authentication.id(self.0.data)
|
||||
NodeType::Authentication.id(self.0.id)
|
||||
}
|
||||
|
||||
/// When the object was created.
|
||||
|
@ -14,7 +14,6 @@
|
||||
|
||||
use async_graphql::{Context, Object, ID};
|
||||
use chrono::{DateTime, Utc};
|
||||
use mas_storage::PostgresqlBackend;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use super::{NodeType, User};
|
||||
@ -69,7 +68,7 @@ impl UpstreamOAuth2Link {
|
||||
pub struct UpstreamOAuth2Link {
|
||||
link: mas_data_model::UpstreamOAuthLink,
|
||||
provider: Option<mas_data_model::UpstreamOAuthProvider>,
|
||||
user: Option<mas_data_model::User<PostgresqlBackend>>,
|
||||
user: Option<mas_data_model::User>,
|
||||
}
|
||||
|
||||
#[Object]
|
||||
|
@ -17,7 +17,6 @@ use async_graphql::{
|
||||
Context, Description, Object, ID,
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
use mas_storage::PostgresqlBackend;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use super::{
|
||||
@ -27,16 +26,16 @@ use super::{
|
||||
|
||||
#[derive(Description)]
|
||||
/// A user is an individual's account.
|
||||
pub struct User(pub mas_data_model::User<PostgresqlBackend>);
|
||||
pub struct User(pub mas_data_model::User);
|
||||
|
||||
impl From<mas_data_model::User<PostgresqlBackend>> for User {
|
||||
fn from(v: mas_data_model::User<PostgresqlBackend>) -> Self {
|
||||
impl From<mas_data_model::User> for User {
|
||||
fn from(v: mas_data_model::User) -> Self {
|
||||
Self(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mas_data_model::BrowserSession<PostgresqlBackend>> for User {
|
||||
fn from(v: mas_data_model::BrowserSession<PostgresqlBackend>) -> Self {
|
||||
impl From<mas_data_model::BrowserSession> for User {
|
||||
fn from(v: mas_data_model::BrowserSession) -> Self {
|
||||
Self(v.user)
|
||||
}
|
||||
}
|
||||
@ -45,7 +44,7 @@ impl From<mas_data_model::BrowserSession<PostgresqlBackend>> for User {
|
||||
impl User {
|
||||
/// ID of the object.
|
||||
pub async fn id(&self) -> ID {
|
||||
NodeType::User.id(self.0.data)
|
||||
NodeType::User.id(self.0.id)
|
||||
}
|
||||
|
||||
/// Username chosen by the user.
|
||||
@ -143,7 +142,7 @@ impl User {
|
||||
let mut connection = Connection::new(has_previous_page, has_next_page);
|
||||
connection.edges.extend(edges.into_iter().map(|u| {
|
||||
Edge::new(
|
||||
OpaqueCursor(NodeCursor(NodeType::BrowserSession, u.data)),
|
||||
OpaqueCursor(NodeCursor(NodeType::BrowserSession, u.id)),
|
||||
BrowserSession(u),
|
||||
)
|
||||
}));
|
||||
@ -195,7 +194,7 @@ impl User {
|
||||
);
|
||||
connection.edges.extend(edges.into_iter().map(|u| {
|
||||
Edge::new(
|
||||
OpaqueCursor(NodeCursor(NodeType::UserEmail, u.data)),
|
||||
OpaqueCursor(NodeCursor(NodeType::UserEmail, u.id)),
|
||||
UserEmail(u),
|
||||
)
|
||||
}));
|
||||
@ -309,13 +308,13 @@ impl User {
|
||||
|
||||
/// A user email address
|
||||
#[derive(Description)]
|
||||
pub struct UserEmail(pub mas_data_model::UserEmail<PostgresqlBackend>);
|
||||
pub struct UserEmail(pub mas_data_model::UserEmail);
|
||||
|
||||
#[Object(use_type_description)]
|
||||
impl UserEmail {
|
||||
/// ID of the object.
|
||||
pub async fn id(&self) -> ID {
|
||||
NodeType::UserEmail.id(self.0.data)
|
||||
NodeType::UserEmail.id(self.0.id)
|
||||
}
|
||||
|
||||
/// Email address
|
||||
@ -335,7 +334,7 @@ impl UserEmail {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UserEmailsPagination(mas_data_model::User<PostgresqlBackend>);
|
||||
pub struct UserEmailsPagination(mas_data_model::User);
|
||||
|
||||
#[Object]
|
||||
impl UserEmailsPagination {
|
||||
|
@ -186,7 +186,7 @@ impl From<IntoCallbackDestinationError> for GrantCompletionError {
|
||||
|
||||
pub(crate) async fn complete(
|
||||
grant: AuthorizationGrant<PostgresqlBackend>,
|
||||
browser_session: BrowserSession<PostgresqlBackend>,
|
||||
browser_session: BrowserSession,
|
||||
policy_factory: &PolicyFactory,
|
||||
mut txn: Transaction<'_, Postgres>,
|
||||
) -> Result<AuthorizationResponse<Option<AccessTokenResponse>>, GrantCompletionError> {
|
||||
|
@ -138,7 +138,7 @@ pub(crate) async fn get(
|
||||
let maybe_user_session = user_session_info.load_session(&mut txn).await?;
|
||||
|
||||
let render = match (maybe_user_session, link.user_id) {
|
||||
(Some(mut session), Some(user_id)) if session.user.data == user_id => {
|
||||
(Some(mut session), Some(user_id)) if session.user.id == user_id => {
|
||||
// Session already linked, and link matches the currently logged
|
||||
// user. Mark the session as consumed and renew the authentication.
|
||||
consume_session(&mut txn, &clock, upstream_session).await?;
|
||||
|
@ -89,7 +89,7 @@ pub(crate) async fn post(
|
||||
};
|
||||
|
||||
let user_email = add_user_email(&mut txn, &mut rng, &clock, &session.user, form.email).await?;
|
||||
let next = mas_router::AccountVerifyEmail::new(user_email.data);
|
||||
let next = mas_router::AccountVerifyEmail::new(user_email.id);
|
||||
let next = if let Some(action) = query.post_auth_action {
|
||||
next.and_then(action)
|
||||
} else {
|
||||
|
@ -32,7 +32,7 @@ use mas_storage::{
|
||||
add_user_email, add_user_email_verification_code, get_user_email, get_user_emails,
|
||||
remove_user_email, set_user_email_as_primary,
|
||||
},
|
||||
Clock, PostgresqlBackend,
|
||||
Clock,
|
||||
};
|
||||
use mas_templates::{AccountEmailsContext, EmailVerificationContext, TemplateContext, Templates};
|
||||
use rand::{distributions::Uniform, Rng};
|
||||
@ -47,9 +47,9 @@ pub mod verify;
|
||||
#[serde(tag = "action", rename_all = "snake_case")]
|
||||
pub enum ManagementForm {
|
||||
Add { email: String },
|
||||
ResendConfirmation { data: String },
|
||||
SetPrimary { data: String },
|
||||
Remove { data: String },
|
||||
ResendConfirmation { id: String },
|
||||
SetPrimary { id: String },
|
||||
Remove { id: String },
|
||||
}
|
||||
|
||||
pub(crate) async fn get(
|
||||
@ -77,7 +77,7 @@ async fn render(
|
||||
rng: impl Rng + Send,
|
||||
clock: &Clock,
|
||||
templates: Templates,
|
||||
session: BrowserSession<PostgresqlBackend>,
|
||||
session: BrowserSession,
|
||||
cookie_jar: PrivateCookieJar<Encrypter>,
|
||||
executor: impl PgExecutor<'_>,
|
||||
) -> Result<Response, FancyError> {
|
||||
@ -99,8 +99,8 @@ async fn start_email_verification(
|
||||
executor: impl PgExecutor<'_>,
|
||||
mut rng: impl Rng + Send,
|
||||
clock: &Clock,
|
||||
user: &User<PostgresqlBackend>,
|
||||
user_email: UserEmail<PostgresqlBackend>,
|
||||
user: &User,
|
||||
user_email: UserEmail,
|
||||
) -> anyhow::Result<()> {
|
||||
// First, generate a code
|
||||
let range = Uniform::<u32>::from(0..1_000_000);
|
||||
@ -126,7 +126,7 @@ async fn start_email_verification(
|
||||
mailer.send_verification_email(mailbox, &context).await?;
|
||||
|
||||
info!(
|
||||
email.id = %verification.email.data,
|
||||
email.id = %verification.email.id,
|
||||
"Verification email sent"
|
||||
);
|
||||
Ok(())
|
||||
@ -159,7 +159,7 @@ pub(crate) async fn post(
|
||||
ManagementForm::Add { email } => {
|
||||
let user_email =
|
||||
add_user_email(&mut txn, &mut rng, &clock, &session.user, email).await?;
|
||||
let next = mas_router::AccountVerifyEmail::new(user_email.data);
|
||||
let next = mas_router::AccountVerifyEmail::new(user_email.id);
|
||||
start_email_verification(
|
||||
&mailer,
|
||||
&mut txn,
|
||||
@ -172,11 +172,11 @@ pub(crate) async fn post(
|
||||
txn.commit().await?;
|
||||
return Ok((cookie_jar, next.go()).into_response());
|
||||
}
|
||||
ManagementForm::ResendConfirmation { data } => {
|
||||
let id = data.parse()?;
|
||||
ManagementForm::ResendConfirmation { id } => {
|
||||
let id = id.parse()?;
|
||||
|
||||
let user_email = get_user_email(&mut txn, &session.user, id).await?;
|
||||
let next = mas_router::AccountVerifyEmail::new(user_email.data);
|
||||
let next = mas_router::AccountVerifyEmail::new(user_email.id);
|
||||
start_email_verification(
|
||||
&mailer,
|
||||
&mut txn,
|
||||
@ -189,14 +189,14 @@ pub(crate) async fn post(
|
||||
txn.commit().await?;
|
||||
return Ok((cookie_jar, next.go()).into_response());
|
||||
}
|
||||
ManagementForm::Remove { data } => {
|
||||
let id = data.parse()?;
|
||||
ManagementForm::Remove { id } => {
|
||||
let id = id.parse()?;
|
||||
|
||||
let email = get_user_email(&mut txn, &session.user, id).await?;
|
||||
remove_user_email(&mut txn, email).await?;
|
||||
}
|
||||
ManagementForm::SetPrimary { data } => {
|
||||
let id = data.parse()?;
|
||||
ManagementForm::SetPrimary { id } => {
|
||||
let id = id.parse()?;
|
||||
let email = get_user_email(&mut txn, &session.user, id).await?;
|
||||
set_user_email_as_primary(&mut txn, &email).await?;
|
||||
session.user.primary_email = Some(email);
|
||||
|
@ -27,7 +27,7 @@ use mas_keystore::Encrypter;
|
||||
use mas_router::Route;
|
||||
use mas_storage::{
|
||||
user::{authenticate_session, set_password},
|
||||
Clock, PostgresqlBackend,
|
||||
Clock,
|
||||
};
|
||||
use mas_templates::{EmptyContext, TemplateContext, Templates};
|
||||
use rand::Rng;
|
||||
@ -65,7 +65,7 @@ async fn render(
|
||||
rng: impl Rng + Send,
|
||||
clock: &Clock,
|
||||
templates: Templates,
|
||||
session: BrowserSession<PostgresqlBackend>,
|
||||
session: BrowserSession,
|
||||
cookie_jar: PrivateCookieJar<Encrypter>,
|
||||
) -> Result<Response, FancyError> {
|
||||
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(clock.now(), rng);
|
||||
|
@ -219,7 +219,7 @@ pub(crate) async fn post(
|
||||
|
||||
mailer.send_verification_email(mailbox, &context).await?;
|
||||
|
||||
let next = mas_router::AccountVerifyEmail::new(verification.email.data)
|
||||
let next = mas_router::AccountVerifyEmail::new(verification.email.id)
|
||||
.and_maybe(query.post_auth_action);
|
||||
|
||||
let session = start_session(&mut txn, &mut rng, &clock, user).await?;
|
||||
|
@ -213,7 +213,7 @@ impl Policy {
|
||||
pub async fn evaluate_authorization_grant<T: StorageBackend + std::fmt::Debug>(
|
||||
&mut self,
|
||||
authorization_grant: &AuthorizationGrant<T>,
|
||||
user: &User<T>,
|
||||
user: &User,
|
||||
) -> Result<EvaluationResult, anyhow::Error> {
|
||||
let authorization_grant = serde_json::to_value(authorization_grant)?;
|
||||
let user = serde_json::to_value(user)?;
|
||||
|
@ -136,7 +136,7 @@ pub async fn lookup_active_compat_access_token(
|
||||
res.user_email_confirmed_at,
|
||||
) {
|
||||
(Some(id), Some(email), Some(created_at), confirmed_at) => Some(UserEmail {
|
||||
data: id.into(),
|
||||
id: id.into(),
|
||||
email,
|
||||
created_at,
|
||||
confirmed_at,
|
||||
@ -147,7 +147,7 @@ pub async fn lookup_active_compat_access_token(
|
||||
|
||||
let id = Ulid::from(res.user_id);
|
||||
let user = User {
|
||||
data: id,
|
||||
id,
|
||||
username: res.user_username,
|
||||
sub: id.to_string(),
|
||||
primary_email,
|
||||
@ -274,7 +274,7 @@ pub async fn lookup_active_compat_refresh_token(
|
||||
res.user_email_confirmed_at,
|
||||
) {
|
||||
(Some(id), Some(email), Some(created_at), confirmed_at) => Some(UserEmail {
|
||||
data: id.into(),
|
||||
id: id.into(),
|
||||
email,
|
||||
created_at,
|
||||
confirmed_at,
|
||||
@ -285,7 +285,7 @@ pub async fn lookup_active_compat_refresh_token(
|
||||
|
||||
let id = Ulid::from(res.user_id);
|
||||
let user = User {
|
||||
data: id,
|
||||
id,
|
||||
username: res.user_username,
|
||||
sub: id.to_string(),
|
||||
primary_email,
|
||||
@ -326,7 +326,7 @@ pub async fn compat_login(
|
||||
|
||||
// First, lookup the user
|
||||
let user = lookup_user_by_username(&mut txn, username).await?;
|
||||
tracing::Span::current().record("user.id", tracing::field::display(user.data));
|
||||
tracing::Span::current().record("user.id", tracing::field::display(user.id));
|
||||
|
||||
// Now, fetch the hashed password from the user associated with that session
|
||||
let hashed_password: String = sqlx::query_scalar!(
|
||||
@ -337,7 +337,7 @@ pub async fn compat_login(
|
||||
ORDER BY up.created_at DESC
|
||||
LIMIT 1
|
||||
"#,
|
||||
Uuid::from(user.data),
|
||||
Uuid::from(user.id),
|
||||
)
|
||||
.fetch_one(&mut txn)
|
||||
.instrument(tracing::info_span!("Lookup hashed password"))
|
||||
@ -365,7 +365,7 @@ pub async fn compat_login(
|
||||
VALUES ($1, $2, $3, $4)
|
||||
"#,
|
||||
Uuid::from(id),
|
||||
Uuid::from(user.data),
|
||||
Uuid::from(user.id),
|
||||
device.as_str(),
|
||||
created_at,
|
||||
)
|
||||
@ -392,7 +392,7 @@ pub async fn compat_login(
|
||||
compat_session.id = %session.data,
|
||||
compat_session.device.id = session.device.as_str(),
|
||||
compat_access_token.id,
|
||||
user.id = %session.user.data,
|
||||
user.id = %session.user.id,
|
||||
),
|
||||
err(Display),
|
||||
)]
|
||||
@ -477,7 +477,7 @@ pub async fn expire_compat_access_token(
|
||||
compat_session.device.id = session.device.as_str(),
|
||||
compat_access_token.id = %access_token.data,
|
||||
compat_refresh_token.id,
|
||||
user.id = %session.user.data,
|
||||
user.id = %session.user.id,
|
||||
),
|
||||
err(Display),
|
||||
)]
|
||||
@ -668,7 +668,7 @@ impl TryFrom<CompatSsoLoginLookup> for CompatSsoLogin<PostgresqlBackend> {
|
||||
res.user_email_confirmed_at,
|
||||
) {
|
||||
(Some(id), Some(email), Some(created_at), confirmed_at) => Some(UserEmail {
|
||||
data: id.into(),
|
||||
id: id.into(),
|
||||
email,
|
||||
created_at,
|
||||
confirmed_at,
|
||||
@ -681,7 +681,7 @@ impl TryFrom<CompatSsoLoginLookup> for CompatSsoLogin<PostgresqlBackend> {
|
||||
(Some(id), Some(username), primary_email) => {
|
||||
let id = Ulid::from(id);
|
||||
Some(User {
|
||||
data: id,
|
||||
id,
|
||||
username,
|
||||
sub: id.to_string(),
|
||||
primary_email,
|
||||
@ -808,14 +808,14 @@ pub async fn get_compat_sso_login_by_id(
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user.id = %user.data,
|
||||
user.username = user.username,
|
||||
%user.id,
|
||||
%user.username,
|
||||
),
|
||||
err(Display),
|
||||
)]
|
||||
pub async fn get_paginated_user_compat_sso_logins(
|
||||
executor: impl PgExecutor<'_>,
|
||||
user: &User<PostgresqlBackend>,
|
||||
user: &User,
|
||||
before: Option<Ulid>,
|
||||
after: Option<Ulid>,
|
||||
first: Option<usize>,
|
||||
@ -854,7 +854,7 @@ pub async fn get_paginated_user_compat_sso_logins(
|
||||
|
||||
query
|
||||
.push(" WHERE cs.user_id = ")
|
||||
.push_bind(Uuid::from(user.data))
|
||||
.push_bind(Uuid::from(user.id))
|
||||
.generate_pagination("cl.compat_sso_login_id", before, after, first, last)?;
|
||||
|
||||
let span = info_span!(
|
||||
@ -919,7 +919,7 @@ pub async fn get_compat_sso_login_by_token(
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user.id = %user.data,
|
||||
%user.id,
|
||||
compat_sso_login.id = %login.data,
|
||||
compat_sso_login.redirect_uri = %login.redirect_uri,
|
||||
compat_session.id,
|
||||
@ -931,7 +931,7 @@ pub async fn fullfill_compat_sso_login(
|
||||
conn: impl Acquire<'_, Database = Postgres> + Send,
|
||||
mut rng: impl Rng + Send,
|
||||
clock: &Clock,
|
||||
user: User<PostgresqlBackend>,
|
||||
user: User,
|
||||
mut login: CompatSsoLogin<PostgresqlBackend>,
|
||||
device: Device,
|
||||
) -> Result<CompatSsoLogin<PostgresqlBackend>, anyhow::Error> {
|
||||
@ -943,7 +943,7 @@ pub async fn fullfill_compat_sso_login(
|
||||
|
||||
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(user.data));
|
||||
tracing::Span::current().record("compat_session.id", tracing::field::display(id));
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@ -951,7 +951,7 @@ pub async fn fullfill_compat_sso_login(
|
||||
VALUES ($1, $2, $3, $4)
|
||||
"#,
|
||||
Uuid::from(id),
|
||||
Uuid::from(user.data),
|
||||
Uuid::from(user.id),
|
||||
device.as_str(),
|
||||
created_at,
|
||||
)
|
||||
|
@ -105,20 +105,13 @@ pub struct DatabaseInconsistencyError;
|
||||
pub struct PostgresqlBackend;
|
||||
|
||||
impl StorageBackend for PostgresqlBackend {
|
||||
type AccessTokenData = Ulid;
|
||||
type AuthenticationData = Ulid;
|
||||
type AuthorizationGrantData = Ulid;
|
||||
type BrowserSessionData = Ulid;
|
||||
type ClientData = Ulid;
|
||||
type CompatAccessTokenData = Ulid;
|
||||
type CompatRefreshTokenData = Ulid;
|
||||
type CompatSessionData = Ulid;
|
||||
type CompatSsoLoginData = Ulid;
|
||||
type RefreshTokenData = Ulid;
|
||||
type SessionData = Ulid;
|
||||
type UserData = Ulid;
|
||||
type UserEmailData = Ulid;
|
||||
type UserEmailVerificationData = Ulid;
|
||||
}
|
||||
|
||||
impl StorageBackendMarker for PostgresqlBackend {}
|
||||
|
@ -29,7 +29,7 @@ use crate::{Clock, DatabaseInconsistencyError, LookupError, PostgresqlBackend};
|
||||
fields(
|
||||
session.id = %session.data,
|
||||
client.id = %session.client.data,
|
||||
user.id = %session.browser_session.user.data,
|
||||
user.id = %session.browser_session.user.id,
|
||||
access_token.id,
|
||||
),
|
||||
err(Debug),
|
||||
@ -178,7 +178,7 @@ pub async fn lookup_active_access_token(
|
||||
res.user_email_confirmed_at,
|
||||
) {
|
||||
(Some(id), Some(email), Some(created_at), confirmed_at) => Some(UserEmail {
|
||||
data: id.into(),
|
||||
id: id.into(),
|
||||
email,
|
||||
created_at,
|
||||
confirmed_at,
|
||||
@ -189,7 +189,7 @@ pub async fn lookup_active_access_token(
|
||||
|
||||
let id = Ulid::from(res.user_id);
|
||||
let user = User {
|
||||
data: id,
|
||||
id,
|
||||
username: res.user_username,
|
||||
sub: id.to_string(),
|
||||
primary_email,
|
||||
@ -201,14 +201,14 @@ pub async fn lookup_active_access_token(
|
||||
) {
|
||||
(None, None) => None,
|
||||
(Some(id), Some(created_at)) => Some(Authentication {
|
||||
data: id.into(),
|
||||
id: id.into(),
|
||||
created_at,
|
||||
}),
|
||||
_ => return Err(DatabaseInconsistencyError.into()),
|
||||
};
|
||||
|
||||
let browser_session = BrowserSession {
|
||||
data: res.user_session_id.into(),
|
||||
id: res.user_session_id.into(),
|
||||
created_at: res.user_session_created_at,
|
||||
user,
|
||||
last_authentication,
|
||||
|
@ -187,7 +187,7 @@ impl GrantLookup {
|
||||
self.user_session_last_authentication_created_at,
|
||||
) {
|
||||
(Some(id), Some(created_at)) => Some(Authentication {
|
||||
data: id.into(),
|
||||
id: id.into(),
|
||||
created_at,
|
||||
}),
|
||||
(None, None) => None,
|
||||
@ -201,7 +201,7 @@ impl GrantLookup {
|
||||
self.user_email_confirmed_at,
|
||||
) {
|
||||
(Some(id), Some(email), Some(created_at), confirmed_at) => Some(UserEmail {
|
||||
data: id.into(),
|
||||
id: id.into(),
|
||||
email,
|
||||
created_at,
|
||||
confirmed_at,
|
||||
@ -230,14 +230,14 @@ impl GrantLookup {
|
||||
) => {
|
||||
let user_id = Ulid::from(user_id);
|
||||
let user = User {
|
||||
data: user_id,
|
||||
id: user_id,
|
||||
username: user_username,
|
||||
sub: user_id.to_string(),
|
||||
primary_email,
|
||||
};
|
||||
|
||||
let browser_session = BrowserSession {
|
||||
data: user_session_id.into(),
|
||||
id: user_session_id.into(),
|
||||
user,
|
||||
created_at: user_session_created_at,
|
||||
last_authentication,
|
||||
@ -500,8 +500,8 @@ pub async fn lookup_grant_by_code(
|
||||
grant.id = %grant.data,
|
||||
client.id = %grant.client.data,
|
||||
session.id,
|
||||
user_session.id = %browser_session.data,
|
||||
user.id = %browser_session.user.data,
|
||||
user_session.id = %browser_session.id,
|
||||
user.id = %browser_session.user.id,
|
||||
),
|
||||
err(Debug),
|
||||
)]
|
||||
@ -510,7 +510,7 @@ pub async fn derive_session(
|
||||
mut rng: impl Rng + Send,
|
||||
clock: &Clock,
|
||||
grant: &AuthorizationGrant<PostgresqlBackend>,
|
||||
browser_session: BrowserSession<PostgresqlBackend>,
|
||||
browser_session: BrowserSession,
|
||||
) -> Result<Session<PostgresqlBackend>, anyhow::Error> {
|
||||
let created_at = clock.now();
|
||||
let id = Ulid::from_datetime_with_source(created_at.into(), &mut rng);
|
||||
@ -532,7 +532,7 @@ pub async fn derive_session(
|
||||
og.oauth2_authorization_grant_id = $4
|
||||
"#,
|
||||
Uuid::from(id),
|
||||
Uuid::from(browser_session.data),
|
||||
Uuid::from(browser_session.id),
|
||||
created_at,
|
||||
Uuid::from(grant.data),
|
||||
)
|
||||
@ -554,8 +554,8 @@ pub async fn derive_session(
|
||||
grant.id = %grant.data,
|
||||
client.id = %grant.client.data,
|
||||
session.id = %session.data,
|
||||
user_session.id = %session.browser_session.data,
|
||||
user.id = %session.browser_session.user.data,
|
||||
user_session.id = %session.browser_session.id,
|
||||
user.id = %session.browser_session.user.id,
|
||||
),
|
||||
err(Debug),
|
||||
)]
|
||||
|
@ -26,14 +26,14 @@ use crate::{Clock, PostgresqlBackend};
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user.id = %user.data,
|
||||
%user.id,
|
||||
client.id = %client.data,
|
||||
),
|
||||
err(Debug),
|
||||
)]
|
||||
pub async fn fetch_client_consent(
|
||||
executor: impl PgExecutor<'_>,
|
||||
user: &User<PostgresqlBackend>,
|
||||
user: &User,
|
||||
client: &Client<PostgresqlBackend>,
|
||||
) -> Result<Scope, anyhow::Error> {
|
||||
let scope_tokens: Vec<String> = sqlx::query_scalar!(
|
||||
@ -42,7 +42,7 @@ pub async fn fetch_client_consent(
|
||||
FROM oauth2_consents
|
||||
WHERE user_id = $1 AND oauth2_client_id = $2
|
||||
"#,
|
||||
Uuid::from(user.data),
|
||||
Uuid::from(user.id),
|
||||
Uuid::from(client.data),
|
||||
)
|
||||
.fetch_all(executor)
|
||||
@ -59,7 +59,7 @@ pub async fn fetch_client_consent(
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user.id = %user.data,
|
||||
%user.id,
|
||||
client.id = %client.data,
|
||||
scope = scope.to_string(),
|
||||
),
|
||||
@ -69,7 +69,7 @@ pub async fn insert_client_consent(
|
||||
executor: impl PgExecutor<'_>,
|
||||
mut rng: impl Rng + Send,
|
||||
clock: &Clock,
|
||||
user: &User<PostgresqlBackend>,
|
||||
user: &User,
|
||||
client: &Client<PostgresqlBackend>,
|
||||
scope: &Scope,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
@ -92,7 +92,7 @@ pub async fn insert_client_consent(
|
||||
ON CONFLICT (user_id, oauth2_client_id, scope_token) DO UPDATE SET refreshed_at = $5
|
||||
"#,
|
||||
&ids,
|
||||
Uuid::from(user.data),
|
||||
Uuid::from(user.id),
|
||||
Uuid::from(client.data),
|
||||
&tokens,
|
||||
now,
|
||||
|
@ -38,8 +38,8 @@ pub mod refresh_token;
|
||||
skip_all,
|
||||
fields(
|
||||
session.id = %session.data,
|
||||
user.id = %session.browser_session.user.data,
|
||||
user_session.id = %session.browser_session.data,
|
||||
user.id = %session.browser_session.user.id,
|
||||
user_session.id = %session.browser_session.id,
|
||||
client.id = %session.client.data,
|
||||
),
|
||||
err(Debug),
|
||||
@ -78,14 +78,14 @@ struct OAuthSessionLookup {
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user.id = %user.data,
|
||||
%user.id,
|
||||
user.username = user.username,
|
||||
),
|
||||
err(Display),
|
||||
)]
|
||||
pub async fn get_paginated_user_oauth_sessions(
|
||||
conn: &mut PgConnection,
|
||||
user: &User<PostgresqlBackend>,
|
||||
user: &User,
|
||||
before: Option<Ulid>,
|
||||
after: Option<Ulid>,
|
||||
first: Option<usize>,
|
||||
@ -108,7 +108,7 @@ pub async fn get_paginated_user_oauth_sessions(
|
||||
|
||||
query
|
||||
.push(" WHERE us.user_id = ")
|
||||
.push_bind(Uuid::from(user.data))
|
||||
.push_bind(Uuid::from(user.id))
|
||||
.generate_pagination("oauth2_session_id", before, after, first, last)?;
|
||||
|
||||
let span = info_span!(
|
||||
@ -135,7 +135,7 @@ pub async fn get_paginated_user_oauth_sessions(
|
||||
|
||||
// TODO: this can generate N queries instead of batching. This is less than
|
||||
// ideal
|
||||
let mut browser_sessions: HashMap<Ulid, BrowserSession<PostgresqlBackend>> = HashMap::new();
|
||||
let mut browser_sessions: HashMap<Ulid, BrowserSession> = HashMap::new();
|
||||
for id in browser_session_ids {
|
||||
let v = lookup_active_session(&mut *conn, id).await?;
|
||||
browser_sessions.insert(id, v);
|
||||
|
@ -30,8 +30,8 @@ use crate::{Clock, DatabaseInconsistencyError, LookupError, PostgresqlBackend};
|
||||
skip_all,
|
||||
fields(
|
||||
session.id = %session.data,
|
||||
user.id = %session.browser_session.user.data,
|
||||
user_session.id = %session.browser_session.data,
|
||||
user.id = %session.browser_session.user.id,
|
||||
user_session.id = %session.browser_session.id,
|
||||
client.id = %session.client.data,
|
||||
refresh_token.id,
|
||||
),
|
||||
@ -206,7 +206,7 @@ pub async fn lookup_active_refresh_token(
|
||||
res.user_email_confirmed_at,
|
||||
) {
|
||||
(Some(id), Some(email), Some(created_at), confirmed_at) => Some(UserEmail {
|
||||
data: id.into(),
|
||||
id: id.into(),
|
||||
email,
|
||||
created_at,
|
||||
confirmed_at,
|
||||
@ -217,7 +217,7 @@ pub async fn lookup_active_refresh_token(
|
||||
|
||||
let id = Ulid::from(res.user_id);
|
||||
let user = User {
|
||||
data: id,
|
||||
id,
|
||||
username: res.user_username,
|
||||
sub: id.to_string(),
|
||||
primary_email,
|
||||
@ -229,14 +229,14 @@ pub async fn lookup_active_refresh_token(
|
||||
) {
|
||||
(None, None) => None,
|
||||
(Some(id), Some(created_at)) => Some(Authentication {
|
||||
data: id.into(),
|
||||
id: id.into(),
|
||||
created_at,
|
||||
}),
|
||||
_ => return Err(DatabaseInconsistencyError.into()),
|
||||
};
|
||||
|
||||
let browser_session = BrowserSession {
|
||||
data: res.user_session_id.into(),
|
||||
id: res.user_session_id.into(),
|
||||
created_at: res.user_session_created_at,
|
||||
user,
|
||||
last_authentication,
|
||||
|
@ -22,7 +22,7 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
pagination::{process_page, QueryBuilderExt},
|
||||
Clock, GenericLookupError, PostgresqlBackend,
|
||||
Clock, GenericLookupError,
|
||||
};
|
||||
|
||||
#[derive(sqlx::FromRow)]
|
||||
@ -168,7 +168,7 @@ pub async fn add_link(
|
||||
fields(
|
||||
%upstream_oauth_link.id,
|
||||
%upstream_oauth_link.subject,
|
||||
user.id = %user.data,
|
||||
%user.id,
|
||||
%user.username,
|
||||
),
|
||||
err,
|
||||
@ -176,7 +176,7 @@ pub async fn add_link(
|
||||
pub async fn associate_link_to_user(
|
||||
executor: impl PgExecutor<'_>,
|
||||
upstream_oauth_link: &UpstreamOAuthLink,
|
||||
user: &User<PostgresqlBackend>,
|
||||
user: &User,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@ -184,7 +184,7 @@ pub async fn associate_link_to_user(
|
||||
SET user_id = $1
|
||||
WHERE upstream_oauth_link_id = $2
|
||||
"#,
|
||||
Uuid::from(user.data),
|
||||
Uuid::from(user.id),
|
||||
Uuid::from(upstream_oauth_link.id),
|
||||
)
|
||||
.execute(executor)
|
||||
@ -193,10 +193,14 @@ pub async fn associate_link_to_user(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, err(Display))]
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(%user.id, %user.username),
|
||||
err(Display)
|
||||
)]
|
||||
pub async fn get_paginated_user_links(
|
||||
executor: impl PgExecutor<'_>,
|
||||
user: &User<PostgresqlBackend>,
|
||||
user: &User,
|
||||
before: Option<Ulid>,
|
||||
after: Option<Ulid>,
|
||||
first: Option<usize>,
|
||||
@ -216,7 +220,7 @@ pub async fn get_paginated_user_links(
|
||||
|
||||
query
|
||||
.push(" WHERE user_id = ")
|
||||
.push_bind(Uuid::from(user.data))
|
||||
.push_bind(Uuid::from(user.id))
|
||||
.generate_pagination("upstream_oauth_link_id", before, after, first, last)?;
|
||||
|
||||
let span = info_span!(
|
||||
|
@ -30,7 +30,7 @@ use tracing::{info_span, Instrument};
|
||||
use ulid::Ulid;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::{DatabaseInconsistencyError, PostgresqlBackend};
|
||||
use super::DatabaseInconsistencyError;
|
||||
use crate::{
|
||||
pagination::{process_page, QueryBuilderExt},
|
||||
Clock, GenericLookupError, LookupError,
|
||||
@ -77,7 +77,7 @@ pub async fn login(
|
||||
clock: &Clock,
|
||||
username: &str,
|
||||
password: &str,
|
||||
) -> Result<BrowserSession<PostgresqlBackend>, LoginError> {
|
||||
) -> Result<BrowserSession, LoginError> {
|
||||
let mut txn = conn.begin().await.context("could not start transaction")?;
|
||||
let user = lookup_user_by_username(&mut txn, username)
|
||||
.await
|
||||
@ -137,10 +137,10 @@ struct SessionLookup {
|
||||
user_email_confirmed_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl TryInto<BrowserSession<PostgresqlBackend>> for SessionLookup {
|
||||
impl TryInto<BrowserSession> for SessionLookup {
|
||||
type Error = DatabaseInconsistencyError;
|
||||
|
||||
fn try_into(self) -> Result<BrowserSession<PostgresqlBackend>, Self::Error> {
|
||||
fn try_into(self) -> Result<BrowserSession, Self::Error> {
|
||||
let primary_email = match (
|
||||
self.user_email_id,
|
||||
self.user_email,
|
||||
@ -148,7 +148,7 @@ impl TryInto<BrowserSession<PostgresqlBackend>> for SessionLookup {
|
||||
self.user_email_confirmed_at,
|
||||
) {
|
||||
(Some(id), Some(email), Some(created_at), confirmed_at) => Some(UserEmail {
|
||||
data: id.into(),
|
||||
id: id.into(),
|
||||
email,
|
||||
created_at,
|
||||
confirmed_at,
|
||||
@ -159,7 +159,7 @@ impl TryInto<BrowserSession<PostgresqlBackend>> for SessionLookup {
|
||||
|
||||
let id = Ulid::from(self.user_id);
|
||||
let user = User {
|
||||
data: id,
|
||||
id,
|
||||
username: self.username,
|
||||
sub: id.to_string(),
|
||||
primary_email,
|
||||
@ -167,7 +167,7 @@ impl TryInto<BrowserSession<PostgresqlBackend>> for SessionLookup {
|
||||
|
||||
let last_authentication = match (self.last_authentication_id, self.last_authd_at) {
|
||||
(Some(id), Some(created_at)) => Some(Authentication {
|
||||
data: id.into(),
|
||||
id: id.into(),
|
||||
created_at,
|
||||
}),
|
||||
(None, None) => None,
|
||||
@ -175,7 +175,7 @@ impl TryInto<BrowserSession<PostgresqlBackend>> for SessionLookup {
|
||||
};
|
||||
|
||||
Ok(BrowserSession {
|
||||
data: self.user_session_id.into(),
|
||||
id: self.user_session_id.into(),
|
||||
user,
|
||||
created_at: self.created_at,
|
||||
last_authentication,
|
||||
@ -191,7 +191,7 @@ impl TryInto<BrowserSession<PostgresqlBackend>> for SessionLookup {
|
||||
pub async fn lookup_active_session(
|
||||
executor: impl PgExecutor<'_>,
|
||||
id: Ulid,
|
||||
) -> Result<BrowserSession<PostgresqlBackend>, ActiveSessionLookupError> {
|
||||
) -> Result<BrowserSession, ActiveSessionLookupError> {
|
||||
let res = sqlx::query_as!(
|
||||
SessionLookup,
|
||||
r#"
|
||||
@ -229,19 +229,19 @@ pub async fn lookup_active_session(
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user.id = %user.data,
|
||||
user.username = user.username,
|
||||
%user.id,
|
||||
%user.username,
|
||||
),
|
||||
err(Display),
|
||||
)]
|
||||
pub async fn get_paginated_user_sessions(
|
||||
executor: impl PgExecutor<'_>,
|
||||
user: &User<PostgresqlBackend>,
|
||||
user: &User,
|
||||
before: Option<Ulid>,
|
||||
after: Option<Ulid>,
|
||||
first: Option<usize>,
|
||||
last: Option<usize>,
|
||||
) -> Result<(bool, bool, Vec<BrowserSession<PostgresqlBackend>>), anyhow::Error> {
|
||||
) -> Result<(bool, bool, Vec<BrowserSession>), anyhow::Error> {
|
||||
let mut query = QueryBuilder::new(
|
||||
r#"
|
||||
SELECT
|
||||
@ -267,7 +267,7 @@ pub async fn get_paginated_user_sessions(
|
||||
|
||||
query
|
||||
.push(" WHERE s.finished_at IS NULL AND s.user_id = ")
|
||||
.push_bind(Uuid::from(user.data))
|
||||
.push_bind(Uuid::from(user.id))
|
||||
.generate_pagination("s.user_session_id", before, after, first, last)?;
|
||||
|
||||
let span = info_span!("Fetch paginated user emails", db.statement = query.sql());
|
||||
@ -286,7 +286,7 @@ pub async fn get_paginated_user_sessions(
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user.id = %user.data,
|
||||
%user.id,
|
||||
user_session.id,
|
||||
),
|
||||
err(Display),
|
||||
@ -295,8 +295,8 @@ pub async fn start_session(
|
||||
executor: impl PgExecutor<'_>,
|
||||
mut rng: impl Rng + Send,
|
||||
clock: &Clock,
|
||||
user: User<PostgresqlBackend>,
|
||||
) -> Result<BrowserSession<PostgresqlBackend>, anyhow::Error> {
|
||||
user: User,
|
||||
) -> Result<BrowserSession, anyhow::Error> {
|
||||
let created_at = clock.now();
|
||||
let id = Ulid::from_datetime_with_source(created_at.into(), &mut rng);
|
||||
tracing::Span::current().record("user_session.id", tracing::field::display(id));
|
||||
@ -307,7 +307,7 @@ pub async fn start_session(
|
||||
VALUES ($1, $2, $3)
|
||||
"#,
|
||||
Uuid::from(id),
|
||||
Uuid::from(user.data),
|
||||
Uuid::from(user.id),
|
||||
created_at,
|
||||
)
|
||||
.execute(executor)
|
||||
@ -315,7 +315,7 @@ pub async fn start_session(
|
||||
.context("could not create session")?;
|
||||
|
||||
let session = BrowserSession {
|
||||
data: id,
|
||||
id,
|
||||
user,
|
||||
created_at,
|
||||
last_authentication: None,
|
||||
@ -326,12 +326,12 @@ pub async fn start_session(
|
||||
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(user.id = %user.data),
|
||||
fields(%user.id),
|
||||
err(Display),
|
||||
)]
|
||||
pub async fn count_active_sessions(
|
||||
executor: impl PgExecutor<'_>,
|
||||
user: &User<PostgresqlBackend>,
|
||||
user: &User,
|
||||
) -> Result<usize, anyhow::Error> {
|
||||
let res = sqlx::query_scalar!(
|
||||
r#"
|
||||
@ -339,7 +339,7 @@ pub async fn count_active_sessions(
|
||||
FROM user_sessions s
|
||||
WHERE s.user_id = $1 AND s.finished_at IS NULL
|
||||
"#,
|
||||
Uuid::from(user.data),
|
||||
Uuid::from(user.id),
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.await?
|
||||
@ -366,8 +366,8 @@ pub enum AuthenticationError {
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user.id = %session.user.data,
|
||||
user_session.id = %session.data,
|
||||
user.id = %user_session.user.id,
|
||||
%user_session.id,
|
||||
user_session_authentication.id,
|
||||
),
|
||||
err,
|
||||
@ -376,7 +376,7 @@ pub async fn authenticate_session(
|
||||
txn: &mut Transaction<'_, Postgres>,
|
||||
mut rng: impl Rng + Send,
|
||||
clock: &Clock,
|
||||
session: &mut BrowserSession<PostgresqlBackend>,
|
||||
user_session: &mut BrowserSession,
|
||||
password: &str,
|
||||
) -> Result<(), AuthenticationError> {
|
||||
// First, fetch the hashed password from the user associated with that session
|
||||
@ -388,7 +388,7 @@ pub async fn authenticate_session(
|
||||
ORDER BY up.created_at DESC
|
||||
LIMIT 1
|
||||
"#,
|
||||
Uuid::from(session.user.data),
|
||||
Uuid::from(user_session.user.id),
|
||||
)
|
||||
.fetch_one(txn.borrow_mut())
|
||||
.instrument(tracing::info_span!("Lookup hashed password"))
|
||||
@ -423,7 +423,7 @@ pub async fn authenticate_session(
|
||||
VALUES ($1, $2, $3)
|
||||
"#,
|
||||
Uuid::from(id),
|
||||
Uuid::from(session.data),
|
||||
Uuid::from(user_session.id),
|
||||
created_at,
|
||||
)
|
||||
.execute(txn.borrow_mut())
|
||||
@ -431,10 +431,7 @@ pub async fn authenticate_session(
|
||||
.await
|
||||
.map_err(AuthenticationError::Save)?;
|
||||
|
||||
session.last_authentication = Some(Authentication {
|
||||
data: id,
|
||||
created_at,
|
||||
});
|
||||
user_session.last_authentication = Some(Authentication { id, created_at });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -442,9 +439,9 @@ pub async fn authenticate_session(
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user.id = %session.user.data,
|
||||
user.id = %user_session.user.id,
|
||||
%upstream_oauth_link.id,
|
||||
user_session.id = %session.data,
|
||||
%user_session.id,
|
||||
user_session_authentication.id,
|
||||
),
|
||||
err,
|
||||
@ -453,7 +450,7 @@ pub async fn authenticate_session_with_upstream(
|
||||
executor: impl PgExecutor<'_>,
|
||||
mut rng: impl Rng + Send,
|
||||
clock: &Clock,
|
||||
session: &mut BrowserSession<PostgresqlBackend>,
|
||||
user_session: &mut BrowserSession,
|
||||
upstream_oauth_link: &UpstreamOAuthLink,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
let created_at = clock.now();
|
||||
@ -470,17 +467,14 @@ pub async fn authenticate_session_with_upstream(
|
||||
VALUES ($1, $2, $3)
|
||||
"#,
|
||||
Uuid::from(id),
|
||||
Uuid::from(session.data),
|
||||
Uuid::from(user_session.id),
|
||||
created_at,
|
||||
)
|
||||
.execute(executor)
|
||||
.instrument(tracing::info_span!("Save authentication"))
|
||||
.await?;
|
||||
|
||||
session.last_authentication = Some(Authentication {
|
||||
data: id,
|
||||
created_at,
|
||||
});
|
||||
user_session.last_authentication = Some(Authentication { id, created_at });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -500,7 +494,7 @@ pub async fn register_user(
|
||||
phf: impl PasswordHasher + Send,
|
||||
username: &str,
|
||||
password: &str,
|
||||
) -> Result<User<PostgresqlBackend>, anyhow::Error> {
|
||||
) -> Result<User, anyhow::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));
|
||||
@ -520,7 +514,7 @@ pub async fn register_user(
|
||||
.context("could not insert user")?;
|
||||
|
||||
let user = User {
|
||||
data: id,
|
||||
id,
|
||||
username: username.to_owned(),
|
||||
sub: id.to_string(),
|
||||
primary_email: None,
|
||||
@ -544,7 +538,7 @@ pub async fn register_passwordless_user(
|
||||
mut rng: impl Rng + Send,
|
||||
clock: &Clock,
|
||||
username: &str,
|
||||
) -> Result<User<PostgresqlBackend>, sqlx::Error> {
|
||||
) -> Result<User, 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));
|
||||
@ -562,7 +556,7 @@ pub async fn register_passwordless_user(
|
||||
.await?;
|
||||
|
||||
Ok(User {
|
||||
data: id,
|
||||
id,
|
||||
username: username.to_owned(),
|
||||
sub: id.to_string(),
|
||||
primary_email: None,
|
||||
@ -572,7 +566,7 @@ pub async fn register_passwordless_user(
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user.id = %user.data,
|
||||
%user.id,
|
||||
user_password.id,
|
||||
),
|
||||
err(Display),
|
||||
@ -582,7 +576,7 @@ pub async fn set_password(
|
||||
mut rng: impl CryptoRng + Rng + Send,
|
||||
clock: &Clock,
|
||||
phf: impl PasswordHasher + Send,
|
||||
user: &User<PostgresqlBackend>,
|
||||
user: &User,
|
||||
password: &str,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let created_at = clock.now();
|
||||
@ -598,7 +592,7 @@ pub async fn set_password(
|
||||
VALUES ($1, $2, $3, $4)
|
||||
"#,
|
||||
Uuid::from(id),
|
||||
Uuid::from(user.data),
|
||||
Uuid::from(user.id),
|
||||
hashed_password.to_string(),
|
||||
created_at,
|
||||
)
|
||||
@ -612,13 +606,13 @@ pub async fn set_password(
|
||||
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(user_session.id = %session.data),
|
||||
fields(%user_session.id),
|
||||
err(Display),
|
||||
)]
|
||||
pub async fn end_session(
|
||||
executor: impl PgExecutor<'_>,
|
||||
clock: &Clock,
|
||||
session: &BrowserSession<PostgresqlBackend>,
|
||||
user_session: &BrowserSession,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let now = clock.now();
|
||||
let res = sqlx::query!(
|
||||
@ -628,7 +622,7 @@ pub async fn end_session(
|
||||
WHERE user_session_id = $2
|
||||
"#,
|
||||
now,
|
||||
Uuid::from(session.data),
|
||||
Uuid::from(user_session.id),
|
||||
)
|
||||
.execute(executor)
|
||||
.instrument(info_span!("End session"))
|
||||
@ -663,7 +657,7 @@ impl LookupError for UserLookupError {
|
||||
pub async fn lookup_user_by_username(
|
||||
executor: impl PgExecutor<'_>,
|
||||
username: &str,
|
||||
) -> Result<User<PostgresqlBackend>, UserLookupError> {
|
||||
) -> Result<User, UserLookupError> {
|
||||
let res = sqlx::query_as!(
|
||||
UserLookup,
|
||||
r#"
|
||||
@ -694,7 +688,7 @@ pub async fn lookup_user_by_username(
|
||||
res.user_email_confirmed_at,
|
||||
) {
|
||||
(Some(id), Some(email), Some(created_at), confirmed_at) => Some(UserEmail {
|
||||
data: id.into(),
|
||||
id: id.into(),
|
||||
email,
|
||||
created_at,
|
||||
confirmed_at,
|
||||
@ -705,7 +699,7 @@ pub async fn lookup_user_by_username(
|
||||
|
||||
let id = Ulid::from(res.user_id);
|
||||
Ok(User {
|
||||
data: id,
|
||||
id,
|
||||
username: res.user_username,
|
||||
sub: id.to_string(),
|
||||
primary_email,
|
||||
@ -717,10 +711,7 @@ pub async fn lookup_user_by_username(
|
||||
fields(user.id = %id),
|
||||
err,
|
||||
)]
|
||||
pub async fn lookup_user(
|
||||
executor: impl PgExecutor<'_>,
|
||||
id: Ulid,
|
||||
) -> Result<User<PostgresqlBackend>, UserLookupError> {
|
||||
pub async fn lookup_user(executor: impl PgExecutor<'_>, id: Ulid) -> Result<User, UserLookupError> {
|
||||
let res = sqlx::query_as!(
|
||||
UserLookup,
|
||||
r#"
|
||||
@ -751,7 +742,7 @@ pub async fn lookup_user(
|
||||
res.user_email_confirmed_at,
|
||||
) {
|
||||
(Some(id), Some(email), Some(created_at), confirmed_at) => Some(UserEmail {
|
||||
data: id.into(),
|
||||
id: id.into(),
|
||||
email,
|
||||
created_at,
|
||||
confirmed_at,
|
||||
@ -762,7 +753,7 @@ pub async fn lookup_user(
|
||||
|
||||
let id = Ulid::from(res.user_id);
|
||||
Ok(User {
|
||||
data: id,
|
||||
id,
|
||||
username: res.user_username,
|
||||
sub: id.to_string(),
|
||||
primary_email,
|
||||
@ -798,10 +789,10 @@ struct UserEmailLookup {
|
||||
user_email_confirmed_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl From<UserEmailLookup> for UserEmail<PostgresqlBackend> {
|
||||
fn from(e: UserEmailLookup) -> UserEmail<PostgresqlBackend> {
|
||||
impl From<UserEmailLookup> for UserEmail {
|
||||
fn from(e: UserEmailLookup) -> UserEmail {
|
||||
UserEmail {
|
||||
data: e.user_email_id.into(),
|
||||
id: e.user_email_id.into(),
|
||||
email: e.user_email,
|
||||
created_at: e.user_email_created_at,
|
||||
confirmed_at: e.user_email_confirmed_at,
|
||||
@ -811,13 +802,13 @@ impl From<UserEmailLookup> for UserEmail<PostgresqlBackend> {
|
||||
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(user.id = %user.data, user.username = user.username),
|
||||
fields(%user.id, %user.username),
|
||||
err(Display),
|
||||
)]
|
||||
pub async fn get_user_emails(
|
||||
executor: impl PgExecutor<'_>,
|
||||
user: &User<PostgresqlBackend>,
|
||||
) -> Result<Vec<UserEmail<PostgresqlBackend>>, anyhow::Error> {
|
||||
user: &User,
|
||||
) -> Result<Vec<UserEmail>, anyhow::Error> {
|
||||
let res = sqlx::query_as!(
|
||||
UserEmailLookup,
|
||||
r#"
|
||||
@ -832,7 +823,7 @@ pub async fn get_user_emails(
|
||||
|
||||
ORDER BY ue.email ASC
|
||||
"#,
|
||||
Uuid::from(user.data),
|
||||
Uuid::from(user.id),
|
||||
)
|
||||
.fetch_all(executor)
|
||||
.instrument(info_span!("Fetch user emails"))
|
||||
@ -843,12 +834,12 @@ pub async fn get_user_emails(
|
||||
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(user.id = %user.data, user.username = user.username),
|
||||
fields(%user.id, %user.username),
|
||||
err(Display),
|
||||
)]
|
||||
pub async fn count_user_emails(
|
||||
executor: impl PgExecutor<'_>,
|
||||
user: &User<PostgresqlBackend>,
|
||||
user: &User,
|
||||
) -> Result<i64, anyhow::Error> {
|
||||
let res = sqlx::query_scalar!(
|
||||
r#"
|
||||
@ -856,7 +847,7 @@ pub async fn count_user_emails(
|
||||
FROM user_emails ue
|
||||
WHERE ue.user_id = $1
|
||||
"#,
|
||||
Uuid::from(user.data),
|
||||
Uuid::from(user.id),
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.instrument(info_span!("Count user emails"))
|
||||
@ -867,20 +858,17 @@ pub async fn count_user_emails(
|
||||
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user.id = %user.data,
|
||||
user.username = user.username,
|
||||
),
|
||||
fields(%user.id, %user.username),
|
||||
err(Display),
|
||||
)]
|
||||
pub async fn get_paginated_user_emails(
|
||||
executor: impl PgExecutor<'_>,
|
||||
user: &User<PostgresqlBackend>,
|
||||
user: &User,
|
||||
before: Option<Ulid>,
|
||||
after: Option<Ulid>,
|
||||
first: Option<usize>,
|
||||
last: Option<usize>,
|
||||
) -> Result<(bool, bool, Vec<UserEmail<PostgresqlBackend>>), anyhow::Error> {
|
||||
) -> Result<(bool, bool, Vec<UserEmail>), anyhow::Error> {
|
||||
let mut query = QueryBuilder::new(
|
||||
r#"
|
||||
SELECT
|
||||
@ -894,7 +882,7 @@ pub async fn get_paginated_user_emails(
|
||||
|
||||
query
|
||||
.push(" WHERE ue.user_id = ")
|
||||
.push_bind(Uuid::from(user.data))
|
||||
.push_bind(Uuid::from(user.id))
|
||||
.generate_pagination("ue.user_email_id", before, after, first, last)?;
|
||||
|
||||
let span = info_span!("Fetch paginated user sessions", db.statement = query.sql());
|
||||
@ -916,17 +904,17 @@ pub async fn get_paginated_user_emails(
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user.id = %user.data,
|
||||
user.username = user.username,
|
||||
%user.id,
|
||||
%user.username,
|
||||
user_email.id = %id,
|
||||
),
|
||||
err(Display),
|
||||
)]
|
||||
pub async fn get_user_email(
|
||||
executor: impl PgExecutor<'_>,
|
||||
user: &User<PostgresqlBackend>,
|
||||
user: &User,
|
||||
id: Ulid,
|
||||
) -> Result<UserEmail<PostgresqlBackend>, anyhow::Error> {
|
||||
) -> Result<UserEmail, anyhow::Error> {
|
||||
let res = sqlx::query_as!(
|
||||
UserEmailLookup,
|
||||
r#"
|
||||
@ -940,7 +928,7 @@ pub async fn get_user_email(
|
||||
WHERE ue.user_id = $1
|
||||
AND ue.user_email_id = $2
|
||||
"#,
|
||||
Uuid::from(user.data),
|
||||
Uuid::from(user.id),
|
||||
Uuid::from(id),
|
||||
)
|
||||
.fetch_one(executor)
|
||||
@ -953,8 +941,8 @@ pub async fn get_user_email(
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user.id = %user.data,
|
||||
user.username = user.username,
|
||||
%user.id,
|
||||
%user.username,
|
||||
user_email.id,
|
||||
user_email.email = %email,
|
||||
),
|
||||
@ -964,9 +952,9 @@ pub async fn add_user_email(
|
||||
executor: impl PgExecutor<'_>,
|
||||
mut rng: impl Rng + Send,
|
||||
clock: &Clock,
|
||||
user: &User<PostgresqlBackend>,
|
||||
user: &User,
|
||||
email: String,
|
||||
) -> Result<UserEmail<PostgresqlBackend>, anyhow::Error> {
|
||||
) -> Result<UserEmail, anyhow::Error> {
|
||||
let created_at = clock.now();
|
||||
let id = Ulid::from_datetime_with_source(created_at.into(), &mut rng);
|
||||
tracing::Span::current().record("user_email.id", tracing::field::display(id));
|
||||
@ -977,7 +965,7 @@ pub async fn add_user_email(
|
||||
VALUES ($1, $2, $3, $4)
|
||||
"#,
|
||||
Uuid::from(id),
|
||||
Uuid::from(user.data),
|
||||
Uuid::from(user.id),
|
||||
&email,
|
||||
created_at,
|
||||
)
|
||||
@ -987,7 +975,7 @@ pub async fn add_user_email(
|
||||
.context("could not insert user email")?;
|
||||
|
||||
Ok(UserEmail {
|
||||
data: id,
|
||||
id,
|
||||
email,
|
||||
created_at,
|
||||
confirmed_at: None,
|
||||
@ -997,14 +985,14 @@ pub async fn add_user_email(
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user_email.id = %email.data,
|
||||
user_email.email = %email.email,
|
||||
%user_email.id,
|
||||
%user_email.email,
|
||||
),
|
||||
err(Display),
|
||||
)]
|
||||
pub async fn set_user_email_as_primary(
|
||||
executor: impl PgExecutor<'_>,
|
||||
email: &UserEmail<PostgresqlBackend>,
|
||||
user_email: &UserEmail,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@ -1014,7 +1002,7 @@ pub async fn set_user_email_as_primary(
|
||||
WHERE user_emails.user_email_id = $1
|
||||
AND users.user_id = user_emails.user_id
|
||||
"#,
|
||||
Uuid::from(email.data),
|
||||
Uuid::from(user_email.id),
|
||||
)
|
||||
.execute(executor)
|
||||
.instrument(info_span!("Add user email"))
|
||||
@ -1027,21 +1015,21 @@ pub async fn set_user_email_as_primary(
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user_email.id = %email.data,
|
||||
user_email.email = %email.email,
|
||||
%user_email.id,
|
||||
%user_email.email,
|
||||
),
|
||||
err(Display),
|
||||
)]
|
||||
pub async fn remove_user_email(
|
||||
executor: impl PgExecutor<'_>,
|
||||
email: UserEmail<PostgresqlBackend>,
|
||||
user_email: UserEmail,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM user_emails
|
||||
WHERE user_emails.user_email_id = $1
|
||||
"#,
|
||||
Uuid::from(email.data),
|
||||
Uuid::from(user_email.id),
|
||||
)
|
||||
.execute(executor)
|
||||
.instrument(info_span!("Remove user email"))
|
||||
@ -1054,16 +1042,16 @@ pub async fn remove_user_email(
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user.id = %user.data,
|
||||
%user.id,
|
||||
user_email.email = email,
|
||||
),
|
||||
err(Display),
|
||||
)]
|
||||
pub async fn lookup_user_email(
|
||||
executor: impl PgExecutor<'_>,
|
||||
user: &User<PostgresqlBackend>,
|
||||
user: &User,
|
||||
email: &str,
|
||||
) -> Result<UserEmail<PostgresqlBackend>, anyhow::Error> {
|
||||
) -> Result<UserEmail, anyhow::Error> {
|
||||
let res = sqlx::query_as!(
|
||||
UserEmailLookup,
|
||||
r#"
|
||||
@ -1077,7 +1065,7 @@ pub async fn lookup_user_email(
|
||||
WHERE ue.user_id = $1
|
||||
AND ue.email = $2
|
||||
"#,
|
||||
Uuid::from(user.data),
|
||||
Uuid::from(user.id),
|
||||
email,
|
||||
)
|
||||
.fetch_one(executor)
|
||||
@ -1091,16 +1079,16 @@ pub async fn lookup_user_email(
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user.id = %user.data,
|
||||
%user.id,
|
||||
user_email.id = %id,
|
||||
),
|
||||
err,
|
||||
)]
|
||||
pub async fn lookup_user_email_by_id(
|
||||
executor: impl PgExecutor<'_>,
|
||||
user: &User<PostgresqlBackend>,
|
||||
user: &User,
|
||||
id: Ulid,
|
||||
) -> Result<UserEmail<PostgresqlBackend>, GenericLookupError> {
|
||||
) -> Result<UserEmail, GenericLookupError> {
|
||||
let res = sqlx::query_as!(
|
||||
UserEmailLookup,
|
||||
r#"
|
||||
@ -1114,7 +1102,7 @@ pub async fn lookup_user_email_by_id(
|
||||
WHERE ue.user_id = $1
|
||||
AND ue.user_email_id = $2
|
||||
"#,
|
||||
Uuid::from(user.data),
|
||||
Uuid::from(user.id),
|
||||
Uuid::from(id),
|
||||
)
|
||||
.fetch_one(executor)
|
||||
@ -1127,16 +1115,14 @@ pub async fn lookup_user_email_by_id(
|
||||
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user_email.id = %email.data,
|
||||
),
|
||||
fields(%user_email.id),
|
||||
err(Display),
|
||||
)]
|
||||
pub async fn mark_user_email_as_verified(
|
||||
executor: impl PgExecutor<'_>,
|
||||
clock: &Clock,
|
||||
mut email: UserEmail<PostgresqlBackend>,
|
||||
) -> Result<UserEmail<PostgresqlBackend>, anyhow::Error> {
|
||||
mut user_email: UserEmail,
|
||||
) -> Result<UserEmail, anyhow::Error> {
|
||||
let confirmed_at = clock.now();
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@ -1144,7 +1130,7 @@ pub async fn mark_user_email_as_verified(
|
||||
SET confirmed_at = $2
|
||||
WHERE user_email_id = $1
|
||||
"#,
|
||||
Uuid::from(email.data),
|
||||
Uuid::from(user_email.id),
|
||||
confirmed_at,
|
||||
)
|
||||
.execute(executor)
|
||||
@ -1152,9 +1138,9 @@ pub async fn mark_user_email_as_verified(
|
||||
.await
|
||||
.context("could not update user email")?;
|
||||
|
||||
email.confirmed_at = Some(confirmed_at);
|
||||
user_email.confirmed_at = Some(confirmed_at);
|
||||
|
||||
Ok(email)
|
||||
Ok(user_email)
|
||||
}
|
||||
|
||||
struct UserEmailConfirmationCodeLookup {
|
||||
@ -1167,17 +1153,15 @@ struct UserEmailConfirmationCodeLookup {
|
||||
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user_email.id = %email.data,
|
||||
),
|
||||
fields(%user_email.id),
|
||||
err(Display),
|
||||
)]
|
||||
pub async fn lookup_user_email_verification_code(
|
||||
executor: impl PgExecutor<'_>,
|
||||
clock: &Clock,
|
||||
email: UserEmail<PostgresqlBackend>,
|
||||
user_email: UserEmail,
|
||||
code: &str,
|
||||
) -> Result<UserEmailVerification<PostgresqlBackend>, anyhow::Error> {
|
||||
) -> Result<UserEmailVerification, anyhow::Error> {
|
||||
let now = clock.now();
|
||||
|
||||
let res = sqlx::query_as!(
|
||||
@ -1194,7 +1178,7 @@ pub async fn lookup_user_email_verification_code(
|
||||
AND ec.user_email_id = $2
|
||||
"#,
|
||||
code,
|
||||
Uuid::from(email.data),
|
||||
Uuid::from(user_email.id),
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.instrument(info_span!("Lookup user email verification"))
|
||||
@ -1212,9 +1196,9 @@ pub async fn lookup_user_email_verification_code(
|
||||
};
|
||||
|
||||
Ok(UserEmailVerification {
|
||||
data: res.user_email_confirmation_code_id.into(),
|
||||
id: res.user_email_confirmation_code_id.into(),
|
||||
code: res.code,
|
||||
email,
|
||||
email: user_email,
|
||||
state,
|
||||
created_at: res.created_at,
|
||||
})
|
||||
@ -1223,16 +1207,19 @@ pub async fn lookup_user_email_verification_code(
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user_email_verification.id = %verification.data,
|
||||
%user_email_verification.id,
|
||||
),
|
||||
err(Display),
|
||||
)]
|
||||
pub async fn consume_email_verification(
|
||||
executor: impl PgExecutor<'_>,
|
||||
clock: &Clock,
|
||||
mut verification: UserEmailVerification<PostgresqlBackend>,
|
||||
) -> Result<UserEmailVerification<PostgresqlBackend>, anyhow::Error> {
|
||||
if !matches!(verification.state, UserEmailVerificationState::Valid) {
|
||||
mut user_email_verification: UserEmailVerification,
|
||||
) -> Result<UserEmailVerification, anyhow::Error> {
|
||||
if !matches!(
|
||||
user_email_verification.state,
|
||||
UserEmailVerificationState::Valid
|
||||
) {
|
||||
bail!("user email verification in wrong state");
|
||||
}
|
||||
|
||||
@ -1244,7 +1231,7 @@ pub async fn consume_email_verification(
|
||||
SET consumed_at = $2
|
||||
WHERE user_email_confirmation_code_id = $1
|
||||
"#,
|
||||
Uuid::from(verification.data),
|
||||
Uuid::from(user_email_verification.id),
|
||||
consumed_at
|
||||
)
|
||||
.execute(executor)
|
||||
@ -1252,16 +1239,16 @@ pub async fn consume_email_verification(
|
||||
.await
|
||||
.context("could not update user email verification")?;
|
||||
|
||||
verification.state = UserEmailVerificationState::AlreadyUsed { when: consumed_at };
|
||||
user_email_verification.state = UserEmailVerificationState::AlreadyUsed { when: consumed_at };
|
||||
|
||||
Ok(verification)
|
||||
Ok(user_email_verification)
|
||||
}
|
||||
|
||||
#[tracing::instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
user_email.id = %email.data,
|
||||
user_email.email = %email.email,
|
||||
%user_email.id,
|
||||
%user_email.email,
|
||||
user_email_confirmation.id,
|
||||
user_email_confirmation.code = code,
|
||||
),
|
||||
@ -1271,10 +1258,10 @@ pub async fn add_user_email_verification_code(
|
||||
executor: impl PgExecutor<'_>,
|
||||
mut rng: impl Rng + Send,
|
||||
clock: &Clock,
|
||||
email: UserEmail<PostgresqlBackend>,
|
||||
user_email: UserEmail,
|
||||
max_age: chrono::Duration,
|
||||
code: String,
|
||||
) -> Result<UserEmailVerification<PostgresqlBackend>, anyhow::Error> {
|
||||
) -> Result<UserEmailVerification, anyhow::Error> {
|
||||
let created_at = clock.now();
|
||||
let id = Ulid::from_datetime_with_source(created_at.into(), &mut rng);
|
||||
tracing::Span::current().record("user_email_confirmation.id", tracing::field::display(id));
|
||||
@ -1287,7 +1274,7 @@ pub async fn add_user_email_verification_code(
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
"#,
|
||||
Uuid::from(id),
|
||||
Uuid::from(email.data),
|
||||
Uuid::from(user_email.id),
|
||||
code,
|
||||
created_at,
|
||||
expires_at,
|
||||
@ -1298,8 +1285,8 @@ pub async fn add_user_email_verification_code(
|
||||
.context("could not insert user email verification code")?;
|
||||
|
||||
let verification = UserEmailVerification {
|
||||
data: id,
|
||||
email,
|
||||
id,
|
||||
email: user_email,
|
||||
code,
|
||||
created_at,
|
||||
state: UserEmailVerificationState::Valid,
|
||||
@ -1331,10 +1318,10 @@ mod tests {
|
||||
assert!(exists);
|
||||
|
||||
let session = login(&mut txn, &mut rng, &clock, "john", "hunter2").await?;
|
||||
assert_eq!(session.user.data, user.data);
|
||||
assert_eq!(session.user.id, user.id);
|
||||
|
||||
let user2 = lookup_user_by_username(&mut txn, "john").await?;
|
||||
assert_eq!(user.data, user2.data);
|
||||
assert_eq!(user.id, user2.id);
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
|
@ -22,6 +22,7 @@ chrono = "0.4.23"
|
||||
url = "2.3.1"
|
||||
http = "0.2.8"
|
||||
ulid = { version = "1.0.0", features = ["serde"] }
|
||||
rand = "0.8.5"
|
||||
|
||||
oauth2-types = { path = "../oauth2-types" }
|
||||
mas-data-model = { path = "../data-model" }
|
||||
|
@ -18,10 +18,11 @@
|
||||
|
||||
use chrono::Utc;
|
||||
use mas_data_model::{
|
||||
AuthorizationGrant, BrowserSession, CompatSsoLogin, CompatSsoLoginState, StorageBackend,
|
||||
UpstreamOAuthLink, UpstreamOAuthProvider, User, UserEmail, UserEmailVerification,
|
||||
AuthorizationGrant, BrowserSession, CompatSsoLogin, CompatSsoLoginState, UpstreamOAuthLink,
|
||||
UpstreamOAuthProvider, User, UserEmail, UserEmailVerification,
|
||||
};
|
||||
use mas_router::{PostAuthAction, Route};
|
||||
use rand::Rng;
|
||||
use serde::{ser::SerializeStruct, Deserialize, Serialize};
|
||||
use ulid::Ulid;
|
||||
use url::Url;
|
||||
@ -31,31 +32,26 @@ use crate::{FormField, FormState};
|
||||
/// Helper trait to construct context wrappers
|
||||
pub trait TemplateContext: Serialize {
|
||||
/// Attach a user session to the template context
|
||||
fn with_session<S: StorageBackend>(
|
||||
self,
|
||||
current_session: BrowserSession<S>,
|
||||
) -> WithSession<Self>
|
||||
fn with_session(self, current_session: BrowserSession) -> WithSession<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
BrowserSession<S>: Into<BrowserSession<()>>,
|
||||
{
|
||||
WithSession {
|
||||
current_session: current_session.into(),
|
||||
current_session,
|
||||
inner: self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attach an optional user session to the template context
|
||||
fn maybe_with_session<S: StorageBackend>(
|
||||
fn maybe_with_session(
|
||||
self,
|
||||
current_session: Option<BrowserSession<S>>,
|
||||
current_session: Option<BrowserSession>,
|
||||
) -> WithOptionalSession<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
BrowserSession<S>: Into<BrowserSession<()>>,
|
||||
{
|
||||
WithOptionalSession {
|
||||
current_session: current_session.map(Into::into),
|
||||
current_session,
|
||||
inner: self,
|
||||
}
|
||||
}
|
||||
@ -77,13 +73,13 @@ pub trait TemplateContext: Serialize {
|
||||
///
|
||||
/// This is then used to check for template validity in unit tests and in
|
||||
/// the CLI (`cargo run -- templates check`)
|
||||
fn sample(now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl TemplateContext for () {
|
||||
fn sample(_now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(_now: chrono::DateTime<Utc>, _rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@ -101,11 +97,11 @@ pub struct WithCsrf<T> {
|
||||
}
|
||||
|
||||
impl<T: TemplateContext> TemplateContext for WithCsrf<T> {
|
||||
fn sample(now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
T::sample(now)
|
||||
T::sample(now, rng)
|
||||
.into_iter()
|
||||
.map(|inner| WithCsrf {
|
||||
csrf_token: "fake_csrf_token".into(),
|
||||
@ -118,21 +114,23 @@ impl<T: TemplateContext> TemplateContext for WithCsrf<T> {
|
||||
/// Context with a user session in it
|
||||
#[derive(Serialize)]
|
||||
pub struct WithSession<T> {
|
||||
current_session: BrowserSession<()>,
|
||||
current_session: BrowserSession,
|
||||
|
||||
#[serde(flatten)]
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T: TemplateContext> TemplateContext for WithSession<T> {
|
||||
fn sample(now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
BrowserSession::samples(now)
|
||||
BrowserSession::samples(now, rng)
|
||||
.into_iter()
|
||||
.flat_map(|session| {
|
||||
T::sample(now).into_iter().map(move |inner| WithSession {
|
||||
T::sample(now, rng)
|
||||
.into_iter()
|
||||
.map(move |inner| WithSession {
|
||||
current_session: session.clone(),
|
||||
inner,
|
||||
})
|
||||
@ -144,23 +142,23 @@ impl<T: TemplateContext> TemplateContext for WithSession<T> {
|
||||
/// Context with an optional user session in it
|
||||
#[derive(Serialize)]
|
||||
pub struct WithOptionalSession<T> {
|
||||
current_session: Option<BrowserSession<()>>,
|
||||
current_session: Option<BrowserSession>,
|
||||
|
||||
#[serde(flatten)]
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T: TemplateContext> TemplateContext for WithOptionalSession<T> {
|
||||
fn sample(now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
BrowserSession::samples(now)
|
||||
BrowserSession::samples(now, rng)
|
||||
.into_iter()
|
||||
.map(Some) // Wrap all samples in an Option
|
||||
.chain(std::iter::once(None)) // Add the "None" option
|
||||
.flat_map(|session| {
|
||||
T::sample(now)
|
||||
T::sample(now, rng)
|
||||
.into_iter()
|
||||
.map(move |inner| WithOptionalSession {
|
||||
current_session: session.clone(),
|
||||
@ -188,7 +186,7 @@ impl Serialize for EmptyContext {
|
||||
}
|
||||
|
||||
impl TemplateContext for EmptyContext {
|
||||
fn sample(_now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(_now: chrono::DateTime<Utc>, _rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@ -212,7 +210,7 @@ impl IndexContext {
|
||||
}
|
||||
|
||||
impl TemplateContext for IndexContext {
|
||||
fn sample(_now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(_now: chrono::DateTime<Utc>, _rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@ -294,7 +292,7 @@ pub struct LoginContext {
|
||||
}
|
||||
|
||||
impl TemplateContext for LoginContext {
|
||||
fn sample(_now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(_now: chrono::DateTime<Utc>, _rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@ -364,7 +362,7 @@ pub struct RegisterContext {
|
||||
}
|
||||
|
||||
impl TemplateContext for RegisterContext {
|
||||
fn sample(_now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(_now: chrono::DateTime<Utc>, _rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@ -401,7 +399,7 @@ pub struct ConsentContext {
|
||||
}
|
||||
|
||||
impl TemplateContext for ConsentContext {
|
||||
fn sample(_now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(_now: chrono::DateTime<Utc>, _rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@ -432,7 +430,7 @@ pub struct PolicyViolationContext {
|
||||
}
|
||||
|
||||
impl TemplateContext for PolicyViolationContext {
|
||||
fn sample(_now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(_now: chrono::DateTime<Utc>, _rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@ -480,7 +478,7 @@ pub struct ReauthContext {
|
||||
}
|
||||
|
||||
impl TemplateContext for ReauthContext {
|
||||
fn sample(_now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(_now: chrono::DateTime<Utc>, _rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@ -519,7 +517,7 @@ pub struct CompatSsoContext {
|
||||
}
|
||||
|
||||
impl TemplateContext for CompatSsoContext {
|
||||
fn sample(now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@ -531,7 +529,9 @@ impl TemplateContext for CompatSsoContext {
|
||||
created_at: now,
|
||||
state: CompatSsoLoginState::Pending,
|
||||
},
|
||||
action: PostAuthAction::ContinueCompatSsoLogin { data: Ulid::nil() },
|
||||
action: PostAuthAction::ContinueCompatSsoLogin {
|
||||
data: Ulid::from_datetime_with_source(now.into(), rng),
|
||||
},
|
||||
}]
|
||||
}
|
||||
}
|
||||
@ -554,7 +554,7 @@ impl CompatSsoContext {
|
||||
#[derive(Serialize)]
|
||||
pub struct AccountContext {
|
||||
active_sessions: usize,
|
||||
emails: Vec<UserEmail<()>>,
|
||||
emails: Vec<UserEmail>,
|
||||
}
|
||||
|
||||
impl AccountContext {
|
||||
@ -562,7 +562,7 @@ impl AccountContext {
|
||||
#[must_use]
|
||||
pub fn new<T>(active_sessions: usize, emails: Vec<T>) -> Self
|
||||
where
|
||||
T: Into<UserEmail<()>>,
|
||||
T: Into<UserEmail>,
|
||||
{
|
||||
Self {
|
||||
active_sessions,
|
||||
@ -572,36 +572,35 @@ impl AccountContext {
|
||||
}
|
||||
|
||||
impl TemplateContext for AccountContext {
|
||||
fn sample(now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let emails: Vec<UserEmail<()>> = UserEmail::samples(now);
|
||||
let emails: Vec<UserEmail> = UserEmail::samples(now, rng);
|
||||
vec![Self::new(5, emails)]
|
||||
}
|
||||
}
|
||||
|
||||
/// Context used by the `account/emails.html` template
|
||||
#[derive(Serialize)]
|
||||
#[serde(bound(serialize = "T: StorageBackend"))]
|
||||
pub struct AccountEmailsContext<T: StorageBackend> {
|
||||
emails: Vec<UserEmail<T>>,
|
||||
pub struct AccountEmailsContext {
|
||||
emails: Vec<UserEmail>,
|
||||
}
|
||||
|
||||
impl<T: StorageBackend> AccountEmailsContext<T> {
|
||||
impl AccountEmailsContext {
|
||||
/// Constructs a context for the email management page
|
||||
#[must_use]
|
||||
pub fn new(emails: Vec<UserEmail<T>>) -> Self {
|
||||
pub fn new(emails: Vec<UserEmail>) -> Self {
|
||||
Self { emails }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: StorageBackend> TemplateContext for AccountEmailsContext<T> {
|
||||
fn sample(now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
impl TemplateContext for AccountEmailsContext {
|
||||
fn sample(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let emails: Vec<UserEmail<T>> = UserEmail::samples(now);
|
||||
let emails: Vec<UserEmail> = UserEmail::samples(now, rng);
|
||||
vec![Self::new(emails)]
|
||||
}
|
||||
}
|
||||
@ -609,35 +608,35 @@ impl<T: StorageBackend> TemplateContext for AccountEmailsContext<T> {
|
||||
/// Context used by the `emails/verification.{txt,html,subject}` templates
|
||||
#[derive(Serialize)]
|
||||
pub struct EmailVerificationContext {
|
||||
user: User<()>,
|
||||
verification: UserEmailVerification<()>,
|
||||
user: User,
|
||||
verification: UserEmailVerification,
|
||||
}
|
||||
|
||||
impl EmailVerificationContext {
|
||||
/// Constructs a context for the verification email
|
||||
#[must_use]
|
||||
pub fn new(user: User<()>, verification: UserEmailVerification<()>) -> Self {
|
||||
pub fn new(user: User, verification: UserEmailVerification) -> Self {
|
||||
Self { user, verification }
|
||||
}
|
||||
}
|
||||
|
||||
impl TemplateContext for EmailVerificationContext {
|
||||
fn sample(now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
User::samples(now)
|
||||
User::samples(now, rng)
|
||||
.into_iter()
|
||||
.map(|user| {
|
||||
let email = UserEmail {
|
||||
data: (),
|
||||
id: Ulid::from_datetime_with_source(now.into(), rng),
|
||||
email: "foobar@example.com".to_owned(),
|
||||
created_at: now,
|
||||
confirmed_at: None,
|
||||
};
|
||||
|
||||
let verification = UserEmailVerification {
|
||||
data: (),
|
||||
id: Ulid::from_datetime_with_source(now.into(), rng),
|
||||
code: "123456".to_owned(),
|
||||
email,
|
||||
created_at: now,
|
||||
@ -670,7 +669,7 @@ impl FormField for EmailVerificationFormField {
|
||||
#[derive(Serialize)]
|
||||
pub struct EmailVerificationPageContext {
|
||||
form: FormState<EmailVerificationFormField>,
|
||||
email: UserEmail<()>,
|
||||
email: UserEmail,
|
||||
}
|
||||
|
||||
impl EmailVerificationPageContext {
|
||||
@ -678,7 +677,7 @@ impl EmailVerificationPageContext {
|
||||
#[must_use]
|
||||
pub fn new<T>(email: T) -> Self
|
||||
where
|
||||
T: Into<UserEmail<()>>,
|
||||
T: Into<UserEmail>,
|
||||
{
|
||||
Self {
|
||||
form: FormState::default(),
|
||||
@ -694,12 +693,12 @@ impl EmailVerificationPageContext {
|
||||
}
|
||||
|
||||
impl TemplateContext for EmailVerificationPageContext {
|
||||
fn sample(now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let email = UserEmail {
|
||||
data: (),
|
||||
id: Ulid::from_datetime_with_source(now.into(), rng),
|
||||
email: "foobar@example.com".to_owned(),
|
||||
created_at: now,
|
||||
confirmed_at: None,
|
||||
@ -749,7 +748,7 @@ impl EmailAddContext {
|
||||
}
|
||||
|
||||
impl TemplateContext for EmailAddContext {
|
||||
fn sample(_now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(_now: chrono::DateTime<Utc>, _rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@ -761,14 +760,14 @@ impl TemplateContext for EmailAddContext {
|
||||
/// templates
|
||||
#[derive(Serialize)]
|
||||
pub struct UpstreamExistingLinkContext {
|
||||
linked_user: User<()>,
|
||||
linked_user: User,
|
||||
}
|
||||
|
||||
impl UpstreamExistingLinkContext {
|
||||
/// Constructs a new context with an existing linked user
|
||||
pub fn new<T>(linked_user: T) -> Self
|
||||
where
|
||||
T: Into<User<()>>,
|
||||
T: Into<User>,
|
||||
{
|
||||
Self {
|
||||
linked_user: linked_user.into(),
|
||||
@ -777,11 +776,11 @@ impl UpstreamExistingLinkContext {
|
||||
}
|
||||
|
||||
impl TemplateContext for UpstreamExistingLinkContext {
|
||||
fn sample(now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
User::samples(now)
|
||||
User::samples(now, rng)
|
||||
.into_iter()
|
||||
.map(|linked_user| Self { linked_user })
|
||||
.collect()
|
||||
@ -805,7 +804,7 @@ impl UpstreamSuggestLink {
|
||||
}
|
||||
|
||||
impl TemplateContext for UpstreamSuggestLink {
|
||||
fn sample(_now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(_now: chrono::DateTime<Utc>, _rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@ -831,7 +830,7 @@ impl UpstreamRegister {
|
||||
}
|
||||
|
||||
impl TemplateContext for UpstreamRegister {
|
||||
fn sample(_now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(_now: chrono::DateTime<Utc>, _rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@ -847,11 +846,11 @@ pub struct FormPostContext<T> {
|
||||
}
|
||||
|
||||
impl<T: TemplateContext> TemplateContext for FormPostContext<T> {
|
||||
fn sample(now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let sample_params = T::sample(now);
|
||||
let sample_params = T::sample(now, rng);
|
||||
sample_params
|
||||
.into_iter()
|
||||
.map(|params| FormPostContext {
|
||||
@ -881,7 +880,7 @@ pub struct ErrorContext {
|
||||
}
|
||||
|
||||
impl TemplateContext for ErrorContext {
|
||||
fn sample(_now: chrono::DateTime<Utc>) -> Vec<Self>
|
||||
fn sample(_now: chrono::DateTime<Utc>, _rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -28,8 +28,8 @@ use std::{collections::HashSet, string::ToString, sync::Arc};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use camino::{Utf8Path, Utf8PathBuf};
|
||||
use mas_data_model::StorageBackend;
|
||||
use mas_router::UrlBuilder;
|
||||
use rand::Rng;
|
||||
use serde::Serialize;
|
||||
use tera::{Context, Error as TeraError, Tera};
|
||||
use thiserror::Error;
|
||||
@ -201,7 +201,7 @@ register_templates! {
|
||||
pub fn render_account_password(WithCsrf<WithSession<EmptyContext>>) { "pages/account/password.html" }
|
||||
|
||||
/// Render the emails management
|
||||
pub fn render_account_emails<T: StorageBackend>(WithCsrf<WithSession<AccountEmailsContext<T>>>) { "pages/account/emails/index.html" }
|
||||
pub fn render_account_emails(WithCsrf<WithSession<AccountEmailsContext>>) { "pages/account/emails/index.html" }
|
||||
|
||||
/// Render the email verification page
|
||||
pub fn render_account_verify_email(WithCsrf<WithSession<EmailVerificationPageContext>>) { "pages/account/emails/verify.html" }
|
||||
@ -246,29 +246,33 @@ register_templates! {
|
||||
impl Templates {
|
||||
/// Render all templates with the generated samples to check if they render
|
||||
/// properly
|
||||
pub async fn check_render(&self, now: chrono::DateTime<chrono::Utc>) -> anyhow::Result<()> {
|
||||
check::render_login(self, now).await?;
|
||||
check::render_register(self, now).await?;
|
||||
check::render_consent(self, now).await?;
|
||||
check::render_policy_violation(self, now).await?;
|
||||
check::render_sso_login(self, now).await?;
|
||||
check::render_index(self, now).await?;
|
||||
check::render_account_index(self, now).await?;
|
||||
check::render_account_password(self, now).await?;
|
||||
check::render_account_emails::<()>(self, now).await?;
|
||||
check::render_account_add_email(self, now).await?;
|
||||
check::render_account_verify_email(self, now).await?;
|
||||
check::render_reauth(self, now).await?;
|
||||
check::render_form_post::<EmptyContext>(self, now).await?;
|
||||
check::render_error(self, now).await?;
|
||||
check::render_email_verification_txt(self, now).await?;
|
||||
check::render_email_verification_html(self, now).await?;
|
||||
check::render_email_verification_subject(self, now).await?;
|
||||
check::render_upstream_oauth2_already_linked(self, now).await?;
|
||||
check::render_upstream_oauth2_link_mismatch(self, now).await?;
|
||||
check::render_upstream_oauth2_suggest_link(self, now).await?;
|
||||
check::render_upstream_oauth2_do_login(self, now).await?;
|
||||
check::render_upstream_oauth2_do_register(self, now).await?;
|
||||
pub async fn check_render(
|
||||
&self,
|
||||
now: chrono::DateTime<chrono::Utc>,
|
||||
rng: &mut impl Rng,
|
||||
) -> anyhow::Result<()> {
|
||||
check::render_login(self, now, rng).await?;
|
||||
check::render_register(self, now, rng).await?;
|
||||
check::render_consent(self, now, rng).await?;
|
||||
check::render_policy_violation(self, now, rng).await?;
|
||||
check::render_sso_login(self, now, rng).await?;
|
||||
check::render_index(self, now, rng).await?;
|
||||
check::render_account_index(self, now, rng).await?;
|
||||
check::render_account_password(self, now, rng).await?;
|
||||
check::render_account_emails(self, now, rng).await?;
|
||||
check::render_account_add_email(self, now, rng).await?;
|
||||
check::render_account_verify_email(self, now, rng).await?;
|
||||
check::render_reauth(self, now, rng).await?;
|
||||
check::render_form_post::<EmptyContext>(self, now, rng).await?;
|
||||
check::render_error(self, now, rng).await?;
|
||||
check::render_email_verification_txt(self, now, rng).await?;
|
||||
check::render_email_verification_html(self, now, rng).await?;
|
||||
check::render_email_verification_subject(self, now, rng).await?;
|
||||
check::render_upstream_oauth2_already_linked(self, now, rng).await?;
|
||||
check::render_upstream_oauth2_link_mismatch(self, now, rng).await?;
|
||||
check::render_upstream_oauth2_suggest_link(self, now, rng).await?;
|
||||
check::render_upstream_oauth2_do_login(self, now, rng).await?;
|
||||
check::render_upstream_oauth2_do_register(self, now, rng).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -281,10 +285,12 @@ mod tests {
|
||||
async fn check_builtin_templates() {
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let now = chrono::Utc::now();
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let path = Utf8Path::new(env!("CARGO_MANIFEST_DIR")).join("../../templates/");
|
||||
let url_builder = UrlBuilder::new("https://example.com/".parse().unwrap());
|
||||
let templates = Templates::load(path, url_builder).await.unwrap();
|
||||
templates.check_render(now).await.unwrap();
|
||||
templates.check_render(now, &mut rng).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -75,9 +75,9 @@ macro_rules! register_templates {
|
||||
#[doc = concat!("Render the `", $template, "` template with sample contexts")]
|
||||
pub async fn $name
|
||||
$(< $( $lt $( : $clt $(+ $dlt )* + TemplateContext )? ),+ >)?
|
||||
(templates: &Templates, now: chrono::DateTime<chrono::Utc>)
|
||||
(templates: &Templates, now: chrono::DateTime<chrono::Utc>, rng: &mut impl rand::Rng)
|
||||
-> anyhow::Result<()> {
|
||||
let samples: Vec< $param > = TemplateContext::sample(now);
|
||||
let samples: Vec< $param > = TemplateContext::sample(now, rng);
|
||||
|
||||
let name = $template;
|
||||
for sample in samples {
|
||||
|
@ -37,7 +37,7 @@ limitations under the License.
|
||||
{% for item in emails %}
|
||||
<form class="flex my-2 items-center justify-items-center" method="POST">
|
||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||
<input type="hidden" name="data" value="{{ item.data }}" />
|
||||
<input type="hidden" name="id" value="{{ item.id }}" />
|
||||
<div class="font-bold flex-1">{{ item.email }}</div>
|
||||
{% if item.confirmed_at %}
|
||||
<div class="mr-4">Verified</div>
|
||||
|
Reference in New Issue
Block a user