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 upstream_oauth_provider.human_name %}
+
+
+ {{ icon.user_profile_solid() }}
+
+
+
+
{% else %}
@@ -41,6 +55,27 @@
{% endif %}
+ {% if upstream_oauth_provider.human_name %}
+
+ {% if upstream_oauth_provider.brand_name %}
+ {{ logo(brand=upstream_oauth_provider.brand_name, class="brand") }}
+ {% else %}
+ {{ icon.user_profile() }}
+ {% endif %}
+
+
+
+ {{- _("mas.upstream_oauth2.register.provider_name", human_name=upstream_oauth_provider.human_name) -}}
+
+ {% if upstream_oauth_link.human_account_name %}
+
+ {{- upstream_oauth_link.human_account_name -}}
+
+ {% endif %}
+
+
+ {% endif %}
+