From 2e3b8bdd864e0c8299c4357f1b834c7d7bd1581e Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 29 Nov 2024 12:30:18 +0100 Subject: [PATCH] Allow setting an explicit upstream account name (#3600) --- crates/cli/src/commands/manage.rs | 3 +- crates/cli/src/sync.rs | 3 + crates/config/src/sections/upstream_oauth2.rs | 24 +++++++ crates/data-model/src/upstream_oauth2/link.rs | 1 + .../src/upstream_oauth2/provider.rs | 4 ++ .../handlers/src/upstream_oauth2/callback.rs | 22 +++++- crates/handlers/src/upstream_oauth2/link.rs | 12 +++- ...72ecb536092b46c92a7dda01598962842323.json} | 10 ++- ...c999d601f3a9730e6bbb40cfc43c04195c61.json} | 10 ++- ...afe24f01d9ec34609f373db5c535ccb58516.json} | 5 +- ...1057_upstream_oauth2_link_account_name.sql | 9 +++ crates/storage-pg/src/iden.rs | 1 + crates/storage-pg/src/upstream_oauth2/link.rs | 18 ++++- crates/storage-pg/src/upstream_oauth2/mod.rs | 2 +- crates/storage/src/upstream_oauth2/link.rs | 3 + crates/templates/src/context.rs | 68 ++++++++++++++++--- docs/config.schema.json | 18 +++++ .../PageHeading/PageHeading.module.css | 4 +- frontend/src/templates.css | 36 ++++++++++ templates/components/idp_brand.html | 4 +- .../pages/upstream_oauth2/do_register.html | 47 ++++++++++++- translations/en.json | 40 +++++++---- 22 files changed, 302 insertions(+), 42 deletions(-) rename crates/storage-pg/.sqlx/{query-e6dc63984aced9e19c20e90e9cd75d6f6d7ade64f782697715ac4da077b2e1fc.json => query-5402b8ddb674d05319830477eb3e72ecb536092b46c92a7dda01598962842323.json} (69%) rename crates/storage-pg/.sqlx/{query-4187907bfc770b2c76f741671d5e672f5c35eed7c9a9e57ff52888b1768a5ed6.json => query-785e6bceed803cb1caccc373cde0c999d601f3a9730e6bbb40cfc43c04195c61.json} (71%) rename crates/storage-pg/.sqlx/{query-5f6b7e38ef9bc3b39deabba277d0255fb8cfb2adaa65f47b78a8fac11d8c91c3.json => query-9eaf35f045aaca8473efc4a1f529afe24f01d9ec34609f373db5c535ccb58516.json} (60%) create mode 100644 crates/storage-pg/migrations/20241129091057_upstream_oauth2_link_account_name.sql diff --git a/crates/cli/src/commands/manage.rs b/crates/cli/src/commands/manage.rs index d8b55777b..c2fe37a10 100644 --- a/crates/cli/src/commands/manage.rs +++ b/crates/cli/src/commands/manage.rs @@ -955,9 +955,10 @@ impl UserCreationRequest<'_> { } for (provider, subject) in upstream_provider_mappings { + // Note that we don't pass a human_account_name here, as we don't ask for it let link = repo .upstream_oauth_link() - .add(rng, clock, provider, subject) + .add(rng, clock, provider, subject, None) .await?; repo.upstream_oauth_link() diff --git a/crates/cli/src/sync.rs b/crates/cli/src/sync.rs index f96fe9b69..68c94f886 100644 --- a/crates/cli/src/sync.rs +++ b/crates/cli/src/sync.rs @@ -67,6 +67,9 @@ fn map_claims_imports( mas_data_model::UpsreamOAuthProviderSetEmailVerification::Import } }, + account_name: mas_data_model::UpstreamOAuthProviderSubjectPreference { + template: config.account_name.template.clone(), + }, } } diff --git a/crates/config/src/sections/upstream_oauth2.rs b/crates/config/src/sections/upstream_oauth2.rs index ba18f6207..4ca1c7627 100644 --- a/crates/config/src/sections/upstream_oauth2.rs +++ b/crates/config/src/sections/upstream_oauth2.rs @@ -285,6 +285,23 @@ impl EmailImportPreference { } } +/// What should be done for the account name attribute +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] +pub struct AccountNameImportPreference { + /// The Jinja2 template to use for the account name. This name is only used + /// for display purposes. + /// + /// If not provided, it will be ignored. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub template: Option, +} + +impl AccountNameImportPreference { + const fn is_default(&self) -> bool { + self.template.is_none() + } +} + /// How claims should be imported #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] pub struct ClaimsImports { @@ -307,6 +324,13 @@ pub struct ClaimsImports { /// `email_verified` claims #[serde(default, skip_serializing_if = "EmailImportPreference::is_default")] pub email: EmailImportPreference, + + /// Set a human-readable name for the upstream account for display purposes + #[serde( + default, + skip_serializing_if = "AccountNameImportPreference::is_default" + )] + pub account_name: AccountNameImportPreference, } impl ClaimsImports { diff --git a/crates/data-model/src/upstream_oauth2/link.rs b/crates/data-model/src/upstream_oauth2/link.rs index b1b010968..e932b7384 100644 --- a/crates/data-model/src/upstream_oauth2/link.rs +++ b/crates/data-model/src/upstream_oauth2/link.rs @@ -14,5 +14,6 @@ pub struct UpstreamOAuthLink { pub provider_id: Ulid, pub user_id: Option, pub subject: String, + pub human_account_name: Option, pub created_at: DateTime, } diff --git a/crates/data-model/src/upstream_oauth2/provider.rs b/crates/data-model/src/upstream_oauth2/provider.rs index bf939f96e..ff6f1d1fc 100644 --- a/crates/data-model/src/upstream_oauth2/provider.rs +++ b/crates/data-model/src/upstream_oauth2/provider.rs @@ -301,10 +301,14 @@ pub struct ClaimsImports { #[serde(default)] pub email: ImportPreference, + #[serde(default)] + pub account_name: SubjectPreference, + #[serde(default)] pub verify_email: SetEmailVerification, } +// XXX: this should have another name #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct SubjectPreference { #[serde(default)] diff --git a/crates/handlers/src/upstream_oauth2/callback.rs b/crates/handlers/src/upstream_oauth2/callback.rs index ed5f0ac95..6ce4b8e39 100644 --- a/crates/handlers/src/upstream_oauth2/callback.rs +++ b/crates/handlers/src/upstream_oauth2/callback.rs @@ -359,7 +359,7 @@ pub(crate) async fn handler( .as_deref() .unwrap_or("{{ user.sub }}"); let subject = env - .render_str(template, context) + .render_str(template, context.clone()) .map_err(RouteError::ExtractSubject)?; if subject.is_empty() { @@ -375,8 +375,26 @@ pub(crate) async fn handler( let link = if let Some(link) = maybe_link { link } else { + // Try to render the human account name if we have one, + // but just log if it fails + let human_account_name = provider + .claims_imports + .account_name + .template + .as_deref() + .and_then(|template| match env.render_str(template, context) { + Ok(name) => Some(name), + Err(e) => { + tracing::warn!( + error = &e as &dyn std::error::Error, + "Failed to render account name" + ); + None + } + }); + repo.upstream_oauth_link() - .add(&mut rng, &clock, &provider, subject) + .add(&mut rng, &clock, &provider, subject, human_account_name) .await? }; diff --git a/crates/handlers/src/upstream_oauth2/link.rs b/crates/handlers/src/upstream_oauth2/link.rs index a4136192f..aea5d32b7 100644 --- a/crates/handlers/src/upstream_oauth2/link.rs +++ b/crates/handlers/src/upstream_oauth2/link.rs @@ -332,7 +332,7 @@ pub(crate) async fn get( .await? .ok_or(RouteError::ProviderNotFound)?; - let ctx = UpstreamRegister::default(); + let ctx = UpstreamRegister::new(link.clone(), provider.clone()); let env = environment(); @@ -596,7 +596,7 @@ pub(crate) async fn post( .map_or(false, |v| v == "true"); // Create a template context in case we need to re-render because of an error - let ctx = UpstreamRegister::default(); + let ctx = UpstreamRegister::new(link.clone(), provider.clone()); let display_name = if provider .claims_imports @@ -954,7 +954,13 @@ mod tests { let link = repo .upstream_oauth_link() - .add(&mut rng, &state.clock, &provider, "subject".to_owned()) + .add( + &mut rng, + &state.clock, + &provider, + "subject".to_owned(), + None, + ) .await .unwrap(); diff --git a/crates/storage-pg/.sqlx/query-e6dc63984aced9e19c20e90e9cd75d6f6d7ade64f782697715ac4da077b2e1fc.json b/crates/storage-pg/.sqlx/query-5402b8ddb674d05319830477eb3e72ecb536092b46c92a7dda01598962842323.json similarity index 69% rename from crates/storage-pg/.sqlx/query-e6dc63984aced9e19c20e90e9cd75d6f6d7ade64f782697715ac4da077b2e1fc.json rename to crates/storage-pg/.sqlx/query-5402b8ddb674d05319830477eb3e72ecb536092b46c92a7dda01598962842323.json index f76d49370..ae378948f 100644 --- a/crates/storage-pg/.sqlx/query-e6dc63984aced9e19c20e90e9cd75d6f6d7ade64f782697715ac4da077b2e1fc.json +++ b/crates/storage-pg/.sqlx/query-5402b8ddb674d05319830477eb3e72ecb536092b46c92a7dda01598962842323.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n upstream_oauth_link_id,\n upstream_oauth_provider_id,\n user_id,\n subject,\n created_at\n FROM upstream_oauth_links\n WHERE upstream_oauth_provider_id = $1\n AND subject = $2\n ", + "query": "\n SELECT\n upstream_oauth_link_id,\n upstream_oauth_provider_id,\n user_id,\n subject,\n human_account_name,\n created_at\n FROM upstream_oauth_links\n WHERE upstream_oauth_provider_id = $1\n AND subject = $2\n ", "describe": { "columns": [ { @@ -25,6 +25,11 @@ }, { "ordinal": 4, + "name": "human_account_name", + "type_info": "Text" + }, + { + "ordinal": 5, "name": "created_at", "type_info": "Timestamptz" } @@ -40,8 +45,9 @@ false, true, false, + true, false ] }, - "hash": "e6dc63984aced9e19c20e90e9cd75d6f6d7ade64f782697715ac4da077b2e1fc" + "hash": "5402b8ddb674d05319830477eb3e72ecb536092b46c92a7dda01598962842323" } diff --git a/crates/storage-pg/.sqlx/query-4187907bfc770b2c76f741671d5e672f5c35eed7c9a9e57ff52888b1768a5ed6.json b/crates/storage-pg/.sqlx/query-785e6bceed803cb1caccc373cde0c999d601f3a9730e6bbb40cfc43c04195c61.json similarity index 71% rename from crates/storage-pg/.sqlx/query-4187907bfc770b2c76f741671d5e672f5c35eed7c9a9e57ff52888b1768a5ed6.json rename to crates/storage-pg/.sqlx/query-785e6bceed803cb1caccc373cde0c999d601f3a9730e6bbb40cfc43c04195c61.json index 3f64eeb6f..f09603b28 100644 --- a/crates/storage-pg/.sqlx/query-4187907bfc770b2c76f741671d5e672f5c35eed7c9a9e57ff52888b1768a5ed6.json +++ b/crates/storage-pg/.sqlx/query-785e6bceed803cb1caccc373cde0c999d601f3a9730e6bbb40cfc43c04195c61.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n upstream_oauth_link_id,\n upstream_oauth_provider_id,\n user_id,\n subject,\n created_at\n FROM upstream_oauth_links\n WHERE upstream_oauth_link_id = $1\n ", + "query": "\n SELECT\n upstream_oauth_link_id,\n upstream_oauth_provider_id,\n user_id,\n subject,\n human_account_name,\n created_at\n FROM upstream_oauth_links\n WHERE upstream_oauth_link_id = $1\n ", "describe": { "columns": [ { @@ -25,6 +25,11 @@ }, { "ordinal": 4, + "name": "human_account_name", + "type_info": "Text" + }, + { + "ordinal": 5, "name": "created_at", "type_info": "Timestamptz" } @@ -39,8 +44,9 @@ false, true, false, + true, false ] }, - "hash": "4187907bfc770b2c76f741671d5e672f5c35eed7c9a9e57ff52888b1768a5ed6" + "hash": "785e6bceed803cb1caccc373cde0c999d601f3a9730e6bbb40cfc43c04195c61" } diff --git a/crates/storage-pg/.sqlx/query-5f6b7e38ef9bc3b39deabba277d0255fb8cfb2adaa65f47b78a8fac11d8c91c3.json b/crates/storage-pg/.sqlx/query-9eaf35f045aaca8473efc4a1f529afe24f01d9ec34609f373db5c535ccb58516.json similarity index 60% rename from crates/storage-pg/.sqlx/query-5f6b7e38ef9bc3b39deabba277d0255fb8cfb2adaa65f47b78a8fac11d8c91c3.json rename to crates/storage-pg/.sqlx/query-9eaf35f045aaca8473efc4a1f529afe24f01d9ec34609f373db5c535ccb58516.json index 7c6279bc5..37173d78b 100644 --- a/crates/storage-pg/.sqlx/query-5f6b7e38ef9bc3b39deabba277d0255fb8cfb2adaa65f47b78a8fac11d8c91c3.json +++ b/crates/storage-pg/.sqlx/query-9eaf35f045aaca8473efc4a1f529afe24f01d9ec34609f373db5c535ccb58516.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO upstream_oauth_links (\n upstream_oauth_link_id,\n upstream_oauth_provider_id,\n user_id,\n subject,\n created_at\n ) VALUES ($1, $2, NULL, $3, $4)\n ", + "query": "\n INSERT INTO upstream_oauth_links (\n upstream_oauth_link_id,\n upstream_oauth_provider_id,\n user_id,\n subject,\n human_account_name,\n created_at\n ) VALUES ($1, $2, NULL, $3, $4, $5)\n ", "describe": { "columns": [], "parameters": { @@ -8,10 +8,11 @@ "Uuid", "Uuid", "Text", + "Text", "Timestamptz" ] }, "nullable": [] }, - "hash": "5f6b7e38ef9bc3b39deabba277d0255fb8cfb2adaa65f47b78a8fac11d8c91c3" + "hash": "9eaf35f045aaca8473efc4a1f529afe24f01d9ec34609f373db5c535ccb58516" } diff --git a/crates/storage-pg/migrations/20241129091057_upstream_oauth2_link_account_name.sql b/crates/storage-pg/migrations/20241129091057_upstream_oauth2_link_account_name.sql new file mode 100644 index 000000000..3dad420eb --- /dev/null +++ b/crates/storage-pg/migrations/20241129091057_upstream_oauth2_link_account_name.sql @@ -0,0 +1,9 @@ +-- Copyright 2024 New Vector Ltd. +-- +-- SPDX-License-Identifier: AGPL-3.0-only +-- Please see LICENSE in the repository root for full details. + +-- Add the human_account_name column to the upstream_oauth_links table to store +-- a human-readable name for the upstream account +ALTER TABLE "upstream_oauth_links" + ADD COLUMN "human_account_name" TEXT; diff --git a/crates/storage-pg/src/iden.rs b/crates/storage-pg/src/iden.rs index 1d35646c4..c6f53f3b0 100644 --- a/crates/storage-pg/src/iden.rs +++ b/crates/storage-pg/src/iden.rs @@ -122,5 +122,6 @@ pub enum UpstreamOAuthLinks { UpstreamOAuthProviderId, UserId, Subject, + HumanAccountName, CreatedAt, } diff --git a/crates/storage-pg/src/upstream_oauth2/link.rs b/crates/storage-pg/src/upstream_oauth2/link.rs index 02ae4ca62..15ac3d55b 100644 --- a/crates/storage-pg/src/upstream_oauth2/link.rs +++ b/crates/storage-pg/src/upstream_oauth2/link.rs @@ -47,6 +47,7 @@ struct LinkLookup { upstream_oauth_provider_id: Uuid, user_id: Option, subject: String, + human_account_name: Option, created_at: DateTime, } @@ -57,6 +58,7 @@ impl From for UpstreamOAuthLink { provider_id: Ulid::from(value.upstream_oauth_provider_id), user_id: value.user_id.map(Ulid::from), subject: value.subject, + human_account_name: value.human_account_name, created_at: value.created_at, } } @@ -124,6 +126,7 @@ impl<'c> UpstreamOAuthLinkRepository for PgUpstreamOAuthLinkRepository<'c> { upstream_oauth_provider_id, user_id, subject, + human_account_name, created_at FROM upstream_oauth_links WHERE upstream_oauth_link_id = $1 @@ -163,6 +166,7 @@ impl<'c> UpstreamOAuthLinkRepository for PgUpstreamOAuthLinkRepository<'c> { upstream_oauth_provider_id, user_id, subject, + human_account_name, created_at FROM upstream_oauth_links WHERE upstream_oauth_provider_id = $1 @@ -186,6 +190,7 @@ impl<'c> UpstreamOAuthLinkRepository for PgUpstreamOAuthLinkRepository<'c> { db.query.text, upstream_oauth_link.id, upstream_oauth_link.subject = subject, + upstream_oauth_link.human_account_name = human_account_name, %upstream_oauth_provider.id, %upstream_oauth_provider.issuer, %upstream_oauth_provider.client_id, @@ -198,6 +203,7 @@ impl<'c> UpstreamOAuthLinkRepository for PgUpstreamOAuthLinkRepository<'c> { clock: &dyn Clock, upstream_oauth_provider: &UpstreamOAuthProvider, subject: String, + human_account_name: Option, ) -> Result { let created_at = clock.now(); let id = Ulid::from_datetime_with_source(created_at.into(), rng); @@ -210,12 +216,14 @@ impl<'c> UpstreamOAuthLinkRepository for PgUpstreamOAuthLinkRepository<'c> { upstream_oauth_provider_id, user_id, subject, + human_account_name, created_at - ) VALUES ($1, $2, NULL, $3, $4) + ) VALUES ($1, $2, NULL, $3, $4, $5) "#, Uuid::from(id), Uuid::from(upstream_oauth_provider.id), &subject, + human_account_name.as_deref(), created_at, ) .traced() @@ -227,6 +235,7 @@ impl<'c> UpstreamOAuthLinkRepository for PgUpstreamOAuthLinkRepository<'c> { provider_id: upstream_oauth_provider.id, user_id: None, subject, + human_account_name, created_at, }) } @@ -300,6 +309,13 @@ impl<'c> UpstreamOAuthLinkRepository for PgUpstreamOAuthLinkRepository<'c> { Expr::col((UpstreamOAuthLinks::Table, UpstreamOAuthLinks::Subject)), LinkLookupIden::Subject, ) + .expr_as( + Expr::col(( + UpstreamOAuthLinks::Table, + UpstreamOAuthLinks::HumanAccountName, + )), + LinkLookupIden::HumanAccountName, + ) .expr_as( Expr::col((UpstreamOAuthLinks::Table, UpstreamOAuthLinks::CreatedAt)), LinkLookupIden::CreatedAt, diff --git a/crates/storage-pg/src/upstream_oauth2/mod.rs b/crates/storage-pg/src/upstream_oauth2/mod.rs index 48c67fe91..04a774185 100644 --- a/crates/storage-pg/src/upstream_oauth2/mod.rs +++ b/crates/storage-pg/src/upstream_oauth2/mod.rs @@ -124,7 +124,7 @@ mod tests { // Create a link let link = repo .upstream_oauth_link() - .add(&mut rng, &clock, &provider, "a-subject".to_owned()) + .add(&mut rng, &clock, &provider, "a-subject".to_owned(), None) .await .unwrap(); diff --git a/crates/storage/src/upstream_oauth2/link.rs b/crates/storage/src/upstream_oauth2/link.rs index 3088fbab0..18f5963d8 100644 --- a/crates/storage/src/upstream_oauth2/link.rs +++ b/crates/storage/src/upstream_oauth2/link.rs @@ -128,6 +128,7 @@ pub trait UpstreamOAuthLinkRepository: Send + Sync { /// * `upsream_oauth_provider`: The upstream OAuth provider for which to /// create the link /// * `subject`: The subject of the upstream OAuth link to create + /// * `human_account_name`: A human-readable name for the upstream account /// /// # Errors /// @@ -138,6 +139,7 @@ pub trait UpstreamOAuthLinkRepository: Send + Sync { clock: &dyn Clock, upstream_oauth_provider: &UpstreamOAuthProvider, subject: String, + human_account_name: Option, ) -> Result; /// Associate an upstream OAuth link to a user @@ -201,6 +203,7 @@ repository_impl!(UpstreamOAuthLinkRepository: clock: &dyn Clock, upstream_oauth_provider: &UpstreamOAuthProvider, subject: String, + human_account_name: Option, ) -> Result; async fn associate_to_user( diff --git a/crates/templates/src/context.rs b/crates/templates/src/context.rs index 3f384b303..5bf207b11 100644 --- a/crates/templates/src/context.rs +++ b/crates/templates/src/context.rs @@ -20,12 +20,14 @@ use chrono::{DateTime, Duration, Utc}; use http::{Method, Uri, Version}; use mas_data_model::{ AuthorizationGrant, BrowserSession, Client, CompatSsoLogin, CompatSsoLoginState, - DeviceCodeGrant, UpstreamOAuthLink, UpstreamOAuthProvider, User, UserAgent, UserEmail, - UserEmailVerification, UserRecoverySession, + DeviceCodeGrant, UpstreamOAuthLink, UpstreamOAuthProvider, UpstreamOAuthProviderClaimsImports, + UpstreamOAuthProviderDiscoveryMode, UpstreamOAuthProviderPkceMode, + UpstreamOAuthProviderResponseMode, UpstreamOAuthProviderTokenAuthMethod, User, UserAgent, + UserEmail, UserEmailVerification, UserRecoverySession, }; use mas_i18n::DataLocale; use mas_router::{Account, GraphQL, PostAuthAction, UrlBuilder}; -use oauth2_types::scope::OPENID; +use oauth2_types::scope::{Scope, OPENID}; use rand::{ distributions::{Alphanumeric, DistString}, Rng, @@ -1277,8 +1279,10 @@ impl FormField for UpstreamRegisterFormField { /// Context used by the `pages/upstream_oauth2/do_register.html` /// templates -#[derive(Serialize, Default)] +#[derive(Serialize)] pub struct UpstreamRegister { + upstream_oauth_link: UpstreamOAuthLink, + upstream_oauth_provider: UpstreamOAuthProvider, imported_localpart: Option, force_localpart: bool, imported_display_name: Option, @@ -1289,10 +1293,24 @@ pub struct UpstreamRegister { } impl UpstreamRegister { - /// Constructs a new context with an existing linked user + /// Constructs a new context for registering a new user from an upstream + /// provider #[must_use] - pub fn new() -> Self { - Self::default() + pub fn new( + upstream_oauth_link: UpstreamOAuthLink, + upstream_oauth_provider: UpstreamOAuthProvider, + ) -> Self { + Self { + upstream_oauth_link, + upstream_oauth_provider, + imported_localpart: None, + force_localpart: false, + imported_display_name: None, + force_display_name: false, + imported_email: None, + force_email: false, + form_state: FormState::default(), + } } /// Set the imported localpart @@ -1356,11 +1374,43 @@ impl UpstreamRegister { } impl TemplateContext for UpstreamRegister { - fn sample(_now: chrono::DateTime, _rng: &mut impl Rng) -> Vec + fn sample(now: chrono::DateTime, _rng: &mut impl Rng) -> Vec where Self: Sized, { - vec![Self::new()] + vec![Self::new( + UpstreamOAuthLink { + id: Ulid::nil(), + provider_id: Ulid::nil(), + user_id: None, + subject: "subject".to_owned(), + human_account_name: Some("@john".to_owned()), + created_at: now, + }, + UpstreamOAuthProvider { + id: Ulid::nil(), + issuer: "https://example.com/".to_owned(), + human_name: Some("Example Ltd.".to_owned()), + brand_name: None, + scope: Scope::from_iter([OPENID]), + token_endpoint_auth_method: UpstreamOAuthProviderTokenAuthMethod::ClientSecretBasic, + token_endpoint_signing_alg: None, + client_id: "client-id".to_owned(), + encrypted_client_secret: None, + claims_imports: UpstreamOAuthProviderClaimsImports::default(), + authorization_endpoint_override: None, + token_endpoint_override: None, + jwks_uri_override: None, + userinfo_endpoint_override: None, + fetch_userinfo: false, + discovery_mode: UpstreamOAuthProviderDiscoveryMode::Oidc, + pkce_mode: UpstreamOAuthProviderPkceMode::Auto, + response_mode: UpstreamOAuthProviderResponseMode::Query, + additional_authorization_parameters: Vec::new(), + created_at: now, + disabled_at: None, + }, + )] } } diff --git a/docs/config.schema.json b/docs/config.schema.json index b2959c56e..2396c7cfc 100644 --- a/docs/config.schema.json +++ b/docs/config.schema.json @@ -2120,6 +2120,14 @@ "$ref": "#/definitions/EmailImportPreference" } ] + }, + "account_name": { + "description": "Set a human-readable name for the upstream account for display purposes", + "allOf": [ + { + "$ref": "#/definitions/AccountNameImportPreference" + } + ] } } }, @@ -2254,6 +2262,16 @@ } ] }, + "AccountNameImportPreference": { + "description": "What should be done for the account name attribute", + "type": "object", + "properties": { + "template": { + "description": "The Jinja2 template to use for the account name. This name is only used for display purposes.\n\nIf not provided, it will be ignored.", + "type": "string" + } + } + }, "BrandingConfig": { "description": "Configuration section for tweaking the branding of the service", "type": "object", diff --git a/frontend/src/components/PageHeading/PageHeading.module.css b/frontend/src/components/PageHeading/PageHeading.module.css index 36f61dd40..3ce16f96b 100644 --- a/frontend/src/components/PageHeading/PageHeading.module.css +++ b/frontend/src/components/PageHeading/PageHeading.module.css @@ -42,8 +42,8 @@ } & svg { - height: var(--cpd-space-10x); - width: var(--cpd-space-10x); + height: var(--cpd-space-8x); + width: var(--cpd-space-8x); color: var(--cpd-color-icon-secondary); } } diff --git a/frontend/src/templates.css b/frontend/src/templates.css index 5c2ee9e79..00572eafa 100644 --- a/frontend/src/templates.css +++ b/frontend/src/templates.css @@ -139,3 +139,39 @@ color: var(--cpd-color-text-primary); } } + +.upstream-oauth2-provider-account { + display: flex; + gap: var(--cpd-space-4x); + background-color: var(--cpd-color-bg-text-on-solid-primary); + border: solid 1px var(--cpd-color-gray-400); + border-radius: var(--cpd-space-3x); + padding: var(--cpd-space-4x); + align-items: center; + + & > svg { + height: var(--cpd-space-6x); + width: var(--cpd-space-6x); + + &:not(.brand) { + color: var(--cpd-color-icon-secondary); + } + } + + & .infos { + display: flex; + flex-direction: column; + + & .provider { + font: var(--cpd-font-body-md-semibold); + letter-spacing: var(--cpd-font-letter-spacing-body-md); + color: var(--cpd-color-text-primary); + } + + & .account { + font: var(--cpd-font-body-md-regular); + letter-spacing: var(--cpd-font-letter-spacing-body-md); + color: var(--cpd-color-text-secondary); + } + } +} diff --git a/templates/components/idp_brand.html b/templates/components/idp_brand.html index a22d4de6a..14a8fa386 100644 --- a/templates/components/idp_brand.html +++ b/templates/components/idp_brand.html @@ -29,8 +29,8 @@ {% elif brand == "github" %} - - + + {% elif brand == "facebook" %} diff --git a/templates/pages/upstream_oauth2/do_register.html b/templates/pages/upstream_oauth2/do_register.html index f8b6a92d9..c9b893542 100644 --- a/templates/pages/upstream_oauth2/do_register.html +++ b/templates/pages/upstream_oauth2/do_register.html @@ -8,6 +8,8 @@ {% extends "base.html" %} +{% from "components/idp_brand.html" import logo %} + {% block content %} {% if force_localpart %}
@@ -24,6 +26,18 @@

+ {% elif upstream_oauth_provider.human_name %} +
+
+ {{ icon.user_profile_solid() }} +
+ +
+

+ {{ _("mas.upstream_oauth2.register.signup_with_upstream.heading", human_name=upstream_oauth_provider.human_name) }} +

+
+
{% else %}
@@ -41,6 +55,27 @@

{% endif %} + {% if upstream_oauth_provider.human_name %} + + {% endif %} +
@@ -80,7 +115,11 @@

- {{- _("mas.upstream_oauth2.register.imported_from_upstream") -}} + {% if upstream_oauth_provider.human_name %} + {{- _("mas.upstream_oauth2.register.imported_from_upstream_with_name", human_name=upstream_oauth_provider.human_name) -}} + {% else %} + {{- _("mas.upstream_oauth2.register.imported_from_upstream") -}} + {% endif %}
{% endcall %} @@ -108,7 +147,11 @@

- {{- _("mas.upstream_oauth2.register.imported_from_upstream") -}} + {% if upstream_oauth_provider.human_name %} + {{- _("mas.upstream_oauth2.register.imported_from_upstream_with_name", human_name=upstream_oauth_provider.human_name) -}} + {% else %} + {{- _("mas.upstream_oauth2.register.imported_from_upstream") -}} + {% endif %}
{% endcall %} diff --git a/translations/en.json b/translations/en.json index 811aa9726..4404ca4ca 100644 --- a/translations/en.json +++ b/translations/en.json @@ -14,7 +14,7 @@ }, "create_account": "Create Account", "@create_account": { - "context": "pages/login.html:68:35-61, pages/upstream_oauth2/do_register.html:149:26-52" + "context": "pages/login.html:68:35-61, pages/upstream_oauth2/do_register.html:192:26-52" }, "sign_in": "Sign in", "@sign_in": { @@ -71,11 +71,11 @@ "common": { "display_name": "Display Name", "@display_name": { - "context": "pages/upstream_oauth2/do_register.html:107:37-61" + "context": "pages/upstream_oauth2/do_register.html:146:37-61" }, "email_address": "Email address", "@email_address": { - "context": "pages/account/emails/add.html:33:33-58, pages/recovery/start.html:34:33-58, pages/register.html:40:35-60, pages/upstream_oauth2/do_register.html:79:37-62" + "context": "pages/account/emails/add.html:33:33-58, pages/recovery/start.html:34:33-58, pages/register.html:40:35-60, pages/upstream_oauth2/do_register.html:114:37-62" }, "loading": "Loading…", "@loading": { @@ -83,7 +83,7 @@ }, "mxid": "Matrix ID", "@mxid": { - "context": "pages/upstream_oauth2/do_register.html:58:35-51" + "context": "pages/upstream_oauth2/do_register.html:93:35-51" }, "password": "Password", "@password": { @@ -95,7 +95,7 @@ }, "username": "Username", "@username": { - "context": "pages/login.html:46:37-57, pages/register.html:36:35-55, pages/upstream_oauth2/do_register.html:66:35-55, pages/upstream_oauth2/do_register.html:71:39-59" + "context": "pages/login.html:46:37-57, pages/register.html:36:35-55, pages/upstream_oauth2/do_register.html:101:35-55, pages/upstream_oauth2/do_register.html:106:39-59" } }, "error": { @@ -519,7 +519,7 @@ }, "terms_of_service": "I agree to the Terms and Conditions", "@terms_of_service": { - "context": "pages/register.html:53:37-97, pages/upstream_oauth2/do_register.html:136:35-95" + "context": "pages/register.html:53:37-97, pages/upstream_oauth2/do_register.html:179:35-95" } }, "scope": { @@ -570,11 +570,11 @@ "choose_username": { "description": "This cannot be changed later.", "@description": { - "context": "pages/upstream_oauth2/do_register.html:38:13-74" + "context": "pages/upstream_oauth2/do_register.html:52:13-74" }, "heading": "Choose your username", "@heading": { - "context": "pages/upstream_oauth2/do_register.html:35:13-70", + "context": "pages/upstream_oauth2/do_register.html:49:13-70", "description": "Displayed when creating a new account from an SSO login, and the username is not forced" } }, @@ -584,7 +584,7 @@ }, "enforced_by_policy": "Enforced by server policy", "@enforced_by_policy": { - "context": "pages/upstream_oauth2/do_register.html:62:14-66" + "context": "pages/upstream_oauth2/do_register.html:97:14-66" }, "forced_display_name": "Will use the following display name", "@forced_display_name": { @@ -601,21 +601,35 @@ "import_data": { "description": "Confirm the information that will be linked to your new %(server_name)s account.", "@description": { - "context": "pages/upstream_oauth2/do_register.html:23:13-104" + "context": "pages/upstream_oauth2/do_register.html:25:13-104" }, "heading": "Import your data", "@heading": { - "context": "pages/upstream_oauth2/do_register.html:20:13-66" + "context": "pages/upstream_oauth2/do_register.html:22:13-66" } }, "imported_from_upstream": "Imported from your upstream account", "@imported_from_upstream": { - "context": "pages/upstream_oauth2/do_register.html:111:16-72, pages/upstream_oauth2/do_register.html:83:16-72" + "context": "pages/upstream_oauth2/do_register.html:121:18-74, pages/upstream_oauth2/do_register.html:153:18-74" + }, + "imported_from_upstream_with_name": "Imported from your %(human_name)s account", + "@imported_from_upstream_with_name": { + "context": "pages/upstream_oauth2/do_register.html:119:18-131, pages/upstream_oauth2/do_register.html:151:18-131" }, "link_existing": "Link to an existing account", "@link_existing": { "description": "Button to link an existing account after an SSO login" }, + "provider_name": "%(human_name)s account", + "@provider_name": { + "context": "pages/upstream_oauth2/do_register.html:68:14-108" + }, + "signup_with_upstream": { + "heading": "Continue signing up with your %(human_name)s account", + "@heading": { + "context": "pages/upstream_oauth2/do_register.html:37:13-122" + } + }, "suggested_display_name": "Import display name", "@suggested_display_name": { "description": "Option to let the user import their display name after an SSO login" @@ -626,7 +640,7 @@ }, "use": "Use", "@use": { - "context": "pages/upstream_oauth2/do_register.html:127:20-57, pages/upstream_oauth2/do_register.html:98:18-55" + "context": "pages/upstream_oauth2/do_register.html:137:18-55, pages/upstream_oauth2/do_register.html:170:20-57" } }, "suggest_link": {