diff --git a/crates/cli/src/commands/config.rs b/crates/cli/src/commands/config.rs index dff499fa..6b071a12 100644 --- a/crates/cli/src/commands/config.rs +++ b/crates/cli/src/commands/config.rs @@ -26,10 +26,10 @@ use tracing::{info, info_span, warn}; use crate::util::database_connection_from_config; -fn map_import_preference( - config: &mas_config::UpstreamOAuth2ImportPreference, -) -> mas_data_model::UpstreamOAuthProviderImportPreference { - let action = match &config.action { +fn map_import_action( + config: &mas_config::UpstreamOAuth2ImportAction, +) -> mas_data_model::UpstreamOAuthProviderImportAction { + match config { mas_config::UpstreamOAuth2ImportAction::Ignore => { mas_data_model::UpstreamOAuthProviderImportAction::Ignore } @@ -42,9 +42,15 @@ fn map_import_preference( mas_config::UpstreamOAuth2ImportAction::Require => { mas_data_model::UpstreamOAuthProviderImportAction::Require } - }; + } +} - mas_data_model::UpstreamOAuthProviderImportPreference { action } +fn map_import_preference( + config: &mas_config::UpstreamOAuth2ImportPreference, +) -> mas_data_model::UpstreamOAuthProviderImportPreference { + mas_data_model::UpstreamOAuthProviderImportPreference { + action: map_import_action(&config.action), + } } fn map_claims_imports( @@ -64,7 +70,25 @@ fn map_claims_imports( email: config .email .as_ref() - .map(map_import_preference) + .map(|c| mas_data_model::UpstreamOAuthProviderImportPreference { + action: map_import_action(&c.action), + }) + .unwrap_or_default(), + // XXX: this is a bit ugly + verify_email: config + .email + .as_ref() + .map(|c| match c.set_email_verification { + mas_config::UpstreamOAuth2SetEmailVerification::Always => { + mas_data_model::UpsreamOAuthProviderSetEmailVerification::Always + } + mas_config::UpstreamOAuth2SetEmailVerification::Never => { + mas_data_model::UpsreamOAuthProviderSetEmailVerification::Never + } + mas_config::UpstreamOAuth2SetEmailVerification::Import => { + mas_data_model::UpsreamOAuthProviderSetEmailVerification::Import + } + }) .unwrap_or_default(), } } diff --git a/crates/config/src/sections/mod.rs b/crates/config/src/sections/mod.rs index 5cb1dd7b..0f1c506b 100644 --- a/crates/config/src/sections/mod.rs +++ b/crates/config/src/sections/mod.rs @@ -49,8 +49,11 @@ pub use self::{ }, templates::TemplatesConfig, upstream_oauth2::{ - ClaimsImports as UpstreamOAuth2ClaimsImports, ImportAction as UpstreamOAuth2ImportAction, - ImportPreference as UpstreamOAuth2ImportPreference, UpstreamOAuth2Config, + ClaimsImports as UpstreamOAuth2ClaimsImports, + EmailImportPreference as UpstreamOAuth2EmailImportPreference, + ImportAction as UpstreamOAuth2ImportAction, + ImportPreference as UpstreamOAuth2ImportPreference, + SetEmailVerification as UpstreamOAuth2SetEmailVerification, UpstreamOAuth2Config, }, }; use crate::util::ConfigurationSection; diff --git a/crates/config/src/sections/upstream_oauth2.rs b/crates/config/src/sections/upstream_oauth2.rs index 9fa025db..d0593c5b 100644 --- a/crates/config/src/sections/upstream_oauth2.rs +++ b/crates/config/src/sections/upstream_oauth2.rs @@ -104,6 +104,34 @@ pub struct ImportPreference { pub action: ImportAction, } +/// Should the email address be marked as verified +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] +#[serde(rename_all = "lowercase")] +pub enum SetEmailVerification { + /// Mark the email address as verified + Always, + + /// Don't mark the email address as verified + Never, + + /// Mark the email address as verified if the upstream provider says it is + /// through the `email_verified` claim + #[default] + Import, +} + +/// What should be done with the email claim +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] +pub struct EmailImportPreference { + /// How to handle the claim + #[serde(default)] + pub action: ImportAction, + + /// Should the email address be marked as verified + #[serde(default)] + pub set_email_verification: SetEmailVerification, +} + /// How claims should be imported #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] pub struct ClaimsImports { @@ -118,7 +146,7 @@ pub struct ClaimsImports { /// Import the email address of the user based on the `email` and /// `email_verified` claims #[serde(default)] - pub email: Option, + pub email: Option, } #[skip_serializing_none] diff --git a/crates/data-model/src/lib.rs b/crates/data-model/src/lib.rs index 6dfcd04a..0fd81733 100644 --- a/crates/data-model/src/lib.rs +++ b/crates/data-model/src/lib.rs @@ -46,9 +46,10 @@ pub use self::{ AccessToken, AccessTokenState, RefreshToken, RefreshTokenState, TokenFormatError, TokenType, }, upstream_oauth2::{ - UpstreamOAuthAuthorizationSession, UpstreamOAuthAuthorizationSessionState, - UpstreamOAuthLink, UpstreamOAuthProvider, UpstreamOAuthProviderClaimsImports, - UpstreamOAuthProviderImportAction, UpstreamOAuthProviderImportPreference, + UpsreamOAuthProviderSetEmailVerification, UpstreamOAuthAuthorizationSession, + UpstreamOAuthAuthorizationSessionState, UpstreamOAuthLink, UpstreamOAuthProvider, + UpstreamOAuthProviderClaimsImports, UpstreamOAuthProviderImportAction, + UpstreamOAuthProviderImportPreference, }, users::{ Authentication, AuthenticationMethod, BrowserSession, Password, User, UserEmail, diff --git a/crates/data-model/src/upstream_oauth2/mod.rs b/crates/data-model/src/upstream_oauth2/mod.rs index 1af1b0d0..0bd6779d 100644 --- a/crates/data-model/src/upstream_oauth2/mod.rs +++ b/crates/data-model/src/upstream_oauth2/mod.rs @@ -21,7 +21,8 @@ pub use self::{ provider::{ ClaimsImports as UpstreamOAuthProviderClaimsImports, ImportAction as UpstreamOAuthProviderImportAction, - ImportPreference as UpstreamOAuthProviderImportPreference, UpstreamOAuthProvider, + ImportPreference as UpstreamOAuthProviderImportPreference, + SetEmailVerification as UpsreamOAuthProviderSetEmailVerification, UpstreamOAuthProvider, }, session::{UpstreamOAuthAuthorizationSession, UpstreamOAuthAuthorizationSessionState}, }; diff --git a/crates/data-model/src/upstream_oauth2/provider.rs b/crates/data-model/src/upstream_oauth2/provider.rs index 97cc1f81..6aba113e 100644 --- a/crates/data-model/src/upstream_oauth2/provider.rs +++ b/crates/data-model/src/upstream_oauth2/provider.rs @@ -31,6 +31,32 @@ pub struct UpstreamOAuthProvider { pub claims_imports: ClaimsImports, } +/// Whether to set the email as verified when importing it from the upstream +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "lowercase")] +pub enum SetEmailVerification { + /// Set the email as verified + Always, + + /// Never set the email as verified + Never, + + /// Set the email as verified if the upstream provider claims it is verified + #[default] + Import, +} + +impl SetEmailVerification { + #[must_use] + pub fn should_mark_as_verified(&self, upstream_verified: bool) -> bool { + match self { + Self::Always => true, + Self::Never => false, + Self::Import => upstream_verified, + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct ClaimsImports { #[serde(default)] @@ -41,6 +67,9 @@ pub struct ClaimsImports { #[serde(default)] pub email: ImportPreference, + + #[serde(default)] + pub verify_email: SetEmailVerification, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] diff --git a/crates/handlers/src/upstream_oauth2/link.rs b/crates/handlers/src/upstream_oauth2/link.rs index 60518788..9464960f 100644 --- a/crates/handlers/src/upstream_oauth2/link.rs +++ b/crates/handlers/src/upstream_oauth2/link.rs @@ -527,11 +527,19 @@ pub(crate) async fn post( .add(&mut rng, &clock, &user, email) .await?; - // Mark the email as verified if the upstream provider says it is. - if payload.email_verified { - repo.user_email() + // Mark the email as verified according to the policy and whether the provider + // claims it is, and make it the primary email. + if provider + .claims_imports + .verify_email + .should_mark_as_verified(payload.email_verified) + { + let user_email = repo + .user_email() .mark_as_verified(&clock, user_email) .await?; + + repo.user_email().set_as_primary(&user_email).await?; } } diff --git a/docs/config.schema.json b/docs/config.schema.json index 80663733..afad2de4 100644 --- a/docs/config.schema.json +++ b/docs/config.schema.json @@ -307,7 +307,7 @@ "default": null, "allOf": [ { - "$ref": "#/definitions/ImportPreference" + "$ref": "#/definitions/EmailImportPreference" } ] }, @@ -687,6 +687,30 @@ } } }, + "EmailImportPreference": { + "description": "What should be done with the email claim", + "type": "object", + "properties": { + "action": { + "description": "How to handle the claim", + "default": "ignore", + "allOf": [ + { + "$ref": "#/definitions/ImportAction" + } + ] + }, + "set_email_verification": { + "description": "Should the email address be marked as verified", + "default": "import", + "allOf": [ + { + "$ref": "#/definitions/SetEmailVerification" + } + ] + } + } + }, "EmailSmtpMode": { "description": "Encryption mode to use", "oneOf": [ @@ -1752,6 +1776,32 @@ } } }, + "SetEmailVerification": { + "description": "Should the email address be marked as verified", + "oneOf": [ + { + "description": "Mark the email address as verified", + "type": "string", + "enum": [ + "always" + ] + }, + { + "description": "Don't mark the email address as verified", + "type": "string", + "enum": [ + "never" + ] + }, + { + "description": "Mark the email address as verified if the upstream provider says it is through the `email_verified` claim", + "type": "string", + "enum": [ + "import" + ] + } + ] + }, "TelemetryConfig": { "description": "Configuration related to sending monitoring data", "type": "object",