From 0e50c446055c740ccb3488d1a1b169cb47dc4164 Mon Sep 17 00:00:00 2001 From: Adis Veletanlic Date: Mon, 14 Apr 2025 12:21:00 +0200 Subject: [PATCH 1/9] Add private_key_file option for apple sso and edit docs --- crates/config/src/sections/upstream_oauth2.rs | 13 ++- .../handlers/src/upstream_oauth2/callback.rs | 2 +- crates/handlers/src/upstream_oauth2/mod.rs | 92 ++++++++++++++++--- docs/config.schema.json | 61 ++++++++---- docs/setup/sso.md | 19 ++-- 5 files changed, 143 insertions(+), 44 deletions(-) diff --git a/crates/config/src/sections/upstream_oauth2.rs b/crates/config/src/sections/upstream_oauth2.rs index 98b5f3c3c..ee184f726 100644 --- a/crates/config/src/sections/upstream_oauth2.rs +++ b/crates/config/src/sections/upstream_oauth2.rs @@ -10,6 +10,7 @@ use mas_iana::jose::JsonWebSignatureAlg; use schemars::JsonSchema; use serde::{Deserialize, Serialize, de::Error}; use serde_with::skip_serializing_none; +use camino::Utf8PathBuf; use ulid::Ulid; use url::Url; @@ -383,15 +384,21 @@ fn signed_response_alg_default() -> JsonWebSignatureAlg { #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct SignInWithApple { + /// The private key file used to sign the `id_token` + #[serde(skip_serializing_if = "Option::is_none")] + #[schemars(with = "Option")] + pub private_key_file: Option, + /// The private key used to sign the `id_token` - pub private_key: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub private_key: Option, /// The Team ID of the Apple Developer Portal pub team_id: String, /// The key ID of the Apple Developer Portal pub key_id: String, -} + } /// Configuration for one upstream OAuth 2 provider. #[skip_serializing_none] @@ -558,4 +565,4 @@ pub struct Provider { /// Specify `oidc` here. #[serde(skip_serializing_if = "Option::is_none")] pub synapse_idp_id: Option, -} +} \ No newline at end of file diff --git a/crates/handlers/src/upstream_oauth2/callback.rs b/crates/handlers/src/upstream_oauth2/callback.rs index be4b5a2d1..845ae1b3c 100644 --- a/crates/handlers/src/upstream_oauth2/callback.rs +++ b/crates/handlers/src/upstream_oauth2/callback.rs @@ -294,7 +294,7 @@ pub(crate) async fn handler( lazy_metadata.token_endpoint().await?, &keystore, &encrypter, - )?; + ).await?; let redirect_uri = url_builder.upstream_oauth_callback(provider.id); diff --git a/crates/handlers/src/upstream_oauth2/mod.rs b/crates/handlers/src/upstream_oauth2/mod.rs index c387aca1b..60360562f 100644 --- a/crates/handlers/src/upstream_oauth2/mod.rs +++ b/crates/handlers/src/upstream_oauth2/mod.rs @@ -11,6 +11,8 @@ use mas_iana::jose::JsonWebSignatureAlg; use mas_keystore::{DecryptError, Encrypter, Keystore}; use mas_oidc_client::types::client_credentials::ClientCredentials; use pkcs8::DecodePrivateKey; +use schemars::JsonSchema; +use camino::Utf8PathBuf; use serde::Deserialize; use thiserror::Error; use url::Url; @@ -30,6 +32,9 @@ enum ProviderCredentialsError { #[error("Provider doesn't have a client secret")] MissingClientSecret, + #[error("Missing private key for signing the id_token")] + MissingPrivateKey, + #[error("Could not decrypt client secret")] DecryptClientSecret { #[from] @@ -55,14 +60,37 @@ enum ProviderCredentialsError { }, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Error)] +enum AppleCredentialsError { + #[error("Missing private key for signing the id_token")] + MissingPrivateKey, + + #[error("Duplicate private key for signing the id_token")] + DuplicatePrivateKey, + + #[error(transparent)] + InvalidPrivateKey(#[from] pkcs8::Error), +} + +#[derive(Debug, Deserialize, JsonSchema)] pub struct SignInWithApple { - pub private_key: String, + /// The private key file used to sign the `id_token` + #[serde(skip_serializing_if = "Option::is_none")] + #[schemars(with = "Option")] + pub private_key_file: Option, + + /// The private key used to sign the `id_token` + #[serde(skip_serializing_if = "Option::is_none")] + pub private_key: Option, + + /// The Team ID of the Apple Developer Portal pub team_id: String, + + /// The key ID of the Apple Developer Portal pub key_id: String, } -fn client_credentials_for_provider( +async fn client_credentials_for_provider( provider: &UpstreamOAuthProvider, token_endpoint: &Url, keystore: &Keystore, @@ -70,7 +98,6 @@ fn client_credentials_for_provider( ) -> Result { let client_id = provider.client_id.clone(); - // Decrypt the client secret let client_secret = provider .encrypted_client_secret .as_deref() @@ -124,19 +151,54 @@ fn client_credentials_for_provider( }, UpstreamOAuthProviderTokenAuthMethod::SignInWithApple => { - let params = client_secret.ok_or(ProviderCredentialsError::MissingClientSecret)?; - let params: SignInWithApple = serde_json::from_str(¶ms)?; - - let key = elliptic_curve::SecretKey::from_pkcs8_pem(¶ms.private_key)?; - - ClientCredentials::SignInWithApple { - client_id, - key, - key_id: params.key_id, - team_id: params.team_id, - } + let client_secret = client_secret.ok_or(ProviderCredentialsError::MissingClientSecret)?; + + resolve_apple_credentials(client_id, client_secret) + .await + .map_err(|err| { + match err { + AppleCredentialsError::MissingPrivateKey => ProviderCredentialsError::MissingPrivateKey, + AppleCredentialsError::DuplicatePrivateKey => ProviderCredentialsError::MissingPrivateKey, // maybe define a better one later + AppleCredentialsError::InvalidPrivateKey(inner) => ProviderCredentialsError::InvalidPrivateKey { inner }, + } + })? } }; Ok(client_credentials) } + +async fn resolve_apple_credentials( + client_id: String, + client_secret: String, +) -> Result { + let params: SignInWithApple = serde_json::from_str(&client_secret) + .map_err(|_| AppleCredentialsError::MissingPrivateKey)?; + + if params.private_key.is_none() && params.private_key_file.is_none() { + return Err(AppleCredentialsError::MissingPrivateKey); + } + + if params.private_key.is_some() && params.private_key_file.is_some() { + return Err(AppleCredentialsError::DuplicatePrivateKey); + } + + let private_key_pem = if let Some(private_key) = params.private_key { + private_key + } else if let Some(private_key_file) = params.private_key_file { + tokio::fs::read_to_string(private_key_file) + .await + .map_err(|_| AppleCredentialsError::MissingPrivateKey)? + } else { + unreachable!("already validated") + }; + + let key = elliptic_curve::SecretKey::from_pkcs8_pem(&private_key_pem)?; + + Ok(ClientCredentials::SignInWithApple { + client_id, + key, + key_id: params.key_id, + team_id: params.team_id, + }) +} \ No newline at end of file diff --git a/docs/config.schema.json b/docs/config.schema.json index e49a75754..bd69b42bd 100644 --- a/docs/config.schema.json +++ b/docs/config.schema.json @@ -2156,25 +2156,50 @@ }, "SignInWithApple": { "type": "object", - "required": [ - "key_id", - "private_key", - "team_id" - ], - "properties": { - "private_key": { - "description": "The private key used to sign the `id_token`", - "type": "string" - }, - "team_id": { - "description": "The Team ID of the Apple Developer Portal", - "type": "string" + "oneOf": [ + { + "required": [ + "key_id", + "private_key_file", + "team_id" + ], + "properties": { + "private_key_file": { + "description": "The private key file used to sign the `id_token`", + "type": "string" + }, + "team_id": { + "description": "The Team ID of the Apple Developer Portal", + "type": "string" + }, + "key_id": { + "description": "The key ID of the Apple Developer Portal", + "type": "string" + } + } }, - "key_id": { - "description": "The key ID of the Apple Developer Portal", - "type": "string" + { + "required": [ + "key_id", + "private_key", + "team_id" + ], + "properties": { + "private_key": { + "description": "The private key used to sign the `id_token`", + "type": "string" + }, + "team_id": { + "description": "The Team ID of the Apple Developer Portal", + "type": "string" + }, + "key_id": { + "description": "The key ID of the Apple Developer Portal", + "type": "string" + } + } } - } + ] }, "DiscoveryMode": { "description": "How to discover the provider's configuration", @@ -2571,4 +2596,4 @@ } } } -} \ No newline at end of file +} diff --git a/docs/setup/sso.md b/docs/setup/sso.md index 0dafd9045..8260ff44e 100644 --- a/docs/setup/sso.md +++ b/docs/setup/sso.md @@ -84,18 +84,23 @@ Sign-in with Apple uses special non-standard for authenticating clients, which r ```yaml upstream_oauth2: providers: - - client_id: 01JAYS74TCG3BTWKADN5Q4518C - client_name: "" # TO BE FILLED + - id: 01JAYS74TCG3BTWKADN5Q4518C + issuer: "https://appleid.apple.com" + human_name: "Apple" + brand_name: "apple" + client_id: "" # TO BE FILLED scope: "openid name email" response_mode: "form_post" - token_endpoint_auth_method: "sign_in_with_apple" sign_in_with_apple: - private_key: | - # Content of the PEM-encoded private key file, TO BE FILLED + + # Only one of the below should be filled for the private key + private_key_file: "" # TO BE FILLED + private_key: | # TO BE FILLED + # + team_id: "" # TO BE FILLED key_id: "" # TO BE FILLED - claims_imports: localpart: action: ignore @@ -548,4 +553,4 @@ To use a Rauthy-supported [Ephemeral Client](https://sebadob.github.io/rauthy/wo "access_token_signed_response_alg": "RS256", "id_token_signed_response_alg": "RS256" } -``` +``` \ No newline at end of file From fd9b5ca1d8f257e8e04a18bb285aff983e2312a8 Mon Sep 17 00:00:00 2001 From: Adis Veletanlic Date: Mon, 14 Apr 2025 12:22:13 +0200 Subject: [PATCH 2/9] Fix indent --- crates/config/src/sections/upstream_oauth2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/config/src/sections/upstream_oauth2.rs b/crates/config/src/sections/upstream_oauth2.rs index ee184f726..086291840 100644 --- a/crates/config/src/sections/upstream_oauth2.rs +++ b/crates/config/src/sections/upstream_oauth2.rs @@ -398,7 +398,7 @@ pub struct SignInWithApple { /// The key ID of the Apple Developer Portal pub key_id: String, - } +} /// Configuration for one upstream OAuth 2 provider. #[skip_serializing_none] From a9693c8ef2dee4a7793c9ad3fd1851da7c7b0215 Mon Sep 17 00:00:00 2001 From: Adis Veletanlic Date: Mon, 14 Apr 2025 12:34:44 +0200 Subject: [PATCH 3/9] Simplify credentials getter and just add new provider error for dupe keys --- crates/handlers/src/upstream_oauth2/mod.rs | 91 ++++++++-------------- 1 file changed, 34 insertions(+), 57 deletions(-) diff --git a/crates/handlers/src/upstream_oauth2/mod.rs b/crates/handlers/src/upstream_oauth2/mod.rs index 60360562f..0f56bfd98 100644 --- a/crates/handlers/src/upstream_oauth2/mod.rs +++ b/crates/handlers/src/upstream_oauth2/mod.rs @@ -32,6 +32,9 @@ enum ProviderCredentialsError { #[error("Provider doesn't have a client secret")] MissingClientSecret, + #[error("Duplicate private key and private key file for Sign in with Apple")] + DuplicatePrivateKey, + #[error("Missing private key for signing the id_token")] MissingPrivateKey, @@ -60,18 +63,6 @@ enum ProviderCredentialsError { }, } -#[derive(Debug, Error)] -enum AppleCredentialsError { - #[error("Missing private key for signing the id_token")] - MissingPrivateKey, - - #[error("Duplicate private key for signing the id_token")] - DuplicatePrivateKey, - - #[error(transparent)] - InvalidPrivateKey(#[from] pkcs8::Error), -} - #[derive(Debug, Deserialize, JsonSchema)] pub struct SignInWithApple { /// The private key file used to sign the `id_token` @@ -152,53 +143,39 @@ async fn client_credentials_for_provider( UpstreamOAuthProviderTokenAuthMethod::SignInWithApple => { let client_secret = client_secret.ok_or(ProviderCredentialsError::MissingClientSecret)?; - - resolve_apple_credentials(client_id, client_secret) - .await - .map_err(|err| { - match err { - AppleCredentialsError::MissingPrivateKey => ProviderCredentialsError::MissingPrivateKey, - AppleCredentialsError::DuplicatePrivateKey => ProviderCredentialsError::MissingPrivateKey, // maybe define a better one later - AppleCredentialsError::InvalidPrivateKey(inner) => ProviderCredentialsError::InvalidPrivateKey { inner }, - } - })? + + let params: SignInWithApple = serde_json::from_str(&client_secret) + .map_err(|inner| ProviderCredentialsError::InvalidClientSecretJson { inner })?; + + if params.private_key.is_none() && params.private_key_file.is_none() { + return Err(ProviderCredentialsError::MissingPrivateKey); + } + + if params.private_key.is_some() && params.private_key_file.is_some() { + return Err(ProviderCredentialsError::DuplicatePrivateKey); + } + + let private_key_pem = if let Some(private_key) = params.private_key { + private_key + } else if let Some(private_key_file) = params.private_key_file { + tokio::fs::read_to_string(private_key_file) + .await + .map_err(|_| ProviderCredentialsError::MissingPrivateKey)? + } else { + unreachable!("already validated above") + }; + + let key = elliptic_curve::SecretKey::from_pkcs8_pem(&private_key_pem) + .map_err(|inner| ProviderCredentialsError::InvalidPrivateKey { inner })?; + + ClientCredentials::SignInWithApple { + client_id, + key, + key_id: params.key_id, + team_id: params.team_id, + } } }; Ok(client_credentials) } - -async fn resolve_apple_credentials( - client_id: String, - client_secret: String, -) -> Result { - let params: SignInWithApple = serde_json::from_str(&client_secret) - .map_err(|_| AppleCredentialsError::MissingPrivateKey)?; - - if params.private_key.is_none() && params.private_key_file.is_none() { - return Err(AppleCredentialsError::MissingPrivateKey); - } - - if params.private_key.is_some() && params.private_key_file.is_some() { - return Err(AppleCredentialsError::DuplicatePrivateKey); - } - - let private_key_pem = if let Some(private_key) = params.private_key { - private_key - } else if let Some(private_key_file) = params.private_key_file { - tokio::fs::read_to_string(private_key_file) - .await - .map_err(|_| AppleCredentialsError::MissingPrivateKey)? - } else { - unreachable!("already validated") - }; - - let key = elliptic_curve::SecretKey::from_pkcs8_pem(&private_key_pem)?; - - Ok(ClientCredentials::SignInWithApple { - client_id, - key, - key_id: params.key_id, - team_id: params.team_id, - }) -} \ No newline at end of file From 754c0d8bab63aa0644d3d7ea49e91cc49ab9ed67 Mon Sep 17 00:00:00 2001 From: Adis Veletanlic Date: Mon, 14 Apr 2025 13:41:43 +0200 Subject: [PATCH 4/9] Format project and run misc/update.sh --- crates/config/src/sections/upstream_oauth2.rs | 6 +- .../handlers/src/upstream_oauth2/callback.rs | 3 +- crates/handlers/src/upstream_oauth2/mod.rs | 5 +- docs/config.schema.json | 64 ++++++------------- 4 files changed, 29 insertions(+), 49 deletions(-) diff --git a/crates/config/src/sections/upstream_oauth2.rs b/crates/config/src/sections/upstream_oauth2.rs index 086291840..623a97c14 100644 --- a/crates/config/src/sections/upstream_oauth2.rs +++ b/crates/config/src/sections/upstream_oauth2.rs @@ -6,11 +6,11 @@ use std::collections::BTreeMap; +use camino::Utf8PathBuf; use mas_iana::jose::JsonWebSignatureAlg; use schemars::JsonSchema; use serde::{Deserialize, Serialize, de::Error}; use serde_with::skip_serializing_none; -use camino::Utf8PathBuf; use ulid::Ulid; use url::Url; @@ -390,7 +390,7 @@ pub struct SignInWithApple { pub private_key_file: Option, /// The private key used to sign the `id_token` - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] pub private_key: Option, /// The Team ID of the Apple Developer Portal @@ -565,4 +565,4 @@ pub struct Provider { /// Specify `oidc` here. #[serde(skip_serializing_if = "Option::is_none")] pub synapse_idp_id: Option, -} \ No newline at end of file +} diff --git a/crates/handlers/src/upstream_oauth2/callback.rs b/crates/handlers/src/upstream_oauth2/callback.rs index 845ae1b3c..83e0cdcef 100644 --- a/crates/handlers/src/upstream_oauth2/callback.rs +++ b/crates/handlers/src/upstream_oauth2/callback.rs @@ -294,7 +294,8 @@ pub(crate) async fn handler( lazy_metadata.token_endpoint().await?, &keystore, &encrypter, - ).await?; + ) + .await?; let redirect_uri = url_builder.upstream_oauth_callback(provider.id); diff --git a/crates/handlers/src/upstream_oauth2/mod.rs b/crates/handlers/src/upstream_oauth2/mod.rs index 0f56bfd98..695c50efb 100644 --- a/crates/handlers/src/upstream_oauth2/mod.rs +++ b/crates/handlers/src/upstream_oauth2/mod.rs @@ -6,13 +6,13 @@ use std::string::FromUtf8Error; +use camino::Utf8PathBuf; use mas_data_model::{UpstreamOAuthProvider, UpstreamOAuthProviderTokenAuthMethod}; use mas_iana::jose::JsonWebSignatureAlg; use mas_keystore::{DecryptError, Encrypter, Keystore}; use mas_oidc_client::types::client_credentials::ClientCredentials; use pkcs8::DecodePrivateKey; use schemars::JsonSchema; -use camino::Utf8PathBuf; use serde::Deserialize; use thiserror::Error; use url::Url; @@ -142,7 +142,8 @@ async fn client_credentials_for_provider( }, UpstreamOAuthProviderTokenAuthMethod::SignInWithApple => { - let client_secret = client_secret.ok_or(ProviderCredentialsError::MissingClientSecret)?; + let client_secret = + client_secret.ok_or(ProviderCredentialsError::MissingClientSecret)?; let params: SignInWithApple = serde_json::from_str(&client_secret) .map_err(|inner| ProviderCredentialsError::InvalidClientSecretJson { inner })?; diff --git a/docs/config.schema.json b/docs/config.schema.json index bd69b42bd..165cf947d 100644 --- a/docs/config.schema.json +++ b/docs/config.schema.json @@ -2156,50 +2156,28 @@ }, "SignInWithApple": { "type": "object", - "oneOf": [ - { - "required": [ - "key_id", - "private_key_file", - "team_id" - ], - "properties": { - "private_key_file": { - "description": "The private key file used to sign the `id_token`", - "type": "string" - }, - "team_id": { - "description": "The Team ID of the Apple Developer Portal", - "type": "string" - }, - "key_id": { - "description": "The key ID of the Apple Developer Portal", - "type": "string" - } - } + "required": [ + "key_id", + "team_id" + ], + "properties": { + "private_key_file": { + "description": "The private key file used to sign the `id_token`", + "type": "string" }, - { - "required": [ - "key_id", - "private_key", - "team_id" - ], - "properties": { - "private_key": { - "description": "The private key used to sign the `id_token`", - "type": "string" - }, - "team_id": { - "description": "The Team ID of the Apple Developer Portal", - "type": "string" - }, - "key_id": { - "description": "The key ID of the Apple Developer Portal", - "type": "string" - } - } + "private_key": { + "description": "The private key used to sign the `id_token`", + "type": "string" + }, + "team_id": { + "description": "The Team ID of the Apple Developer Portal", + "type": "string" + }, + "key_id": { + "description": "The key ID of the Apple Developer Portal", + "type": "string" } - ] + } }, "DiscoveryMode": { "description": "How to discover the provider's configuration", @@ -2596,4 +2574,4 @@ } } } -} +} \ No newline at end of file From 43273d212964b46706421d448408d2a4f0dd587e Mon Sep 17 00:00:00 2001 From: Adis Veletanlic Date: Mon, 14 Apr 2025 18:25:28 +0200 Subject: [PATCH 5/9] Populate private key for SIWA in sync.rs so it is simply storing private_key in db using the provided file (if given) --- crates/cli/src/sync.rs | 12 +++-- crates/handlers/src/upstream_oauth2/mod.rs | 56 ++++------------------ 2 files changed, 16 insertions(+), 52 deletions(-) diff --git a/crates/cli/src/sync.rs b/crates/cli/src/sync.rs index 647ef2635..eaf2482d1 100644 --- a/crates/cli/src/sync.rs +++ b/crates/cli/src/sync.rs @@ -189,10 +189,14 @@ pub async fn config_sync( let encrypted_client_secret = if let Some(client_secret) = provider.client_secret.as_deref() { Some(encrypter.encrypt_to_string(client_secret.as_bytes())?) - } else if let Some(siwa) = provider.sign_in_with_apple.as_ref() { - // For SIWA, we JSON-encode the config and encrypt it, reusing the client_secret - // field in the database - let encoded = serde_json::to_vec(siwa)?; + } else if let Some(mut siwa) = provider.sign_in_with_apple.clone() { + if siwa.private_key.is_none() { + if let Some(private_key_file) = siwa.private_key_file.take() { + let key = tokio::fs::read_to_string(private_key_file).await?; + siwa.private_key = Some(key); + } + } + let encoded = serde_json::to_vec(&siwa)?; Some(encrypter.encrypt_to_string(&encoded)?) } else { None diff --git a/crates/handlers/src/upstream_oauth2/mod.rs b/crates/handlers/src/upstream_oauth2/mod.rs index 695c50efb..c8e37a86c 100644 --- a/crates/handlers/src/upstream_oauth2/mod.rs +++ b/crates/handlers/src/upstream_oauth2/mod.rs @@ -6,13 +6,11 @@ use std::string::FromUtf8Error; -use camino::Utf8PathBuf; use mas_data_model::{UpstreamOAuthProvider, UpstreamOAuthProviderTokenAuthMethod}; use mas_iana::jose::JsonWebSignatureAlg; use mas_keystore::{DecryptError, Encrypter, Keystore}; use mas_oidc_client::types::client_credentials::ClientCredentials; use pkcs8::DecodePrivateKey; -use schemars::JsonSchema; use serde::Deserialize; use thiserror::Error; use url::Url; @@ -32,12 +30,6 @@ enum ProviderCredentialsError { #[error("Provider doesn't have a client secret")] MissingClientSecret, - #[error("Duplicate private key and private key file for Sign in with Apple")] - DuplicatePrivateKey, - - #[error("Missing private key for signing the id_token")] - MissingPrivateKey, - #[error("Could not decrypt client secret")] DecryptClientSecret { #[from] @@ -63,25 +55,14 @@ enum ProviderCredentialsError { }, } -#[derive(Debug, Deserialize, JsonSchema)] +#[derive(Debug, Deserialize)] pub struct SignInWithApple { - /// The private key file used to sign the `id_token` - #[serde(skip_serializing_if = "Option::is_none")] - #[schemars(with = "Option")] - pub private_key_file: Option, - - /// The private key used to sign the `id_token` - #[serde(skip_serializing_if = "Option::is_none")] - pub private_key: Option, - - /// The Team ID of the Apple Developer Portal + pub private_key: String, pub team_id: String, - - /// The key ID of the Apple Developer Portal pub key_id: String, } -async fn client_credentials_for_provider( +fn client_credentials_for_provider( provider: &UpstreamOAuthProvider, token_endpoint: &Url, keystore: &Keystore, @@ -89,6 +70,7 @@ async fn client_credentials_for_provider( ) -> Result { let client_id = provider.client_id.clone(); + // Decrypt the client secret let client_secret = provider .encrypted_client_secret .as_deref() @@ -142,32 +124,10 @@ async fn client_credentials_for_provider( }, UpstreamOAuthProviderTokenAuthMethod::SignInWithApple => { - let client_secret = - client_secret.ok_or(ProviderCredentialsError::MissingClientSecret)?; - - let params: SignInWithApple = serde_json::from_str(&client_secret) - .map_err(|inner| ProviderCredentialsError::InvalidClientSecretJson { inner })?; + let params = client_secret.ok_or(ProviderCredentialsError::MissingClientSecret)?; + let params: SignInWithApple = serde_json::from_str(¶ms)?; - if params.private_key.is_none() && params.private_key_file.is_none() { - return Err(ProviderCredentialsError::MissingPrivateKey); - } - - if params.private_key.is_some() && params.private_key_file.is_some() { - return Err(ProviderCredentialsError::DuplicatePrivateKey); - } - - let private_key_pem = if let Some(private_key) = params.private_key { - private_key - } else if let Some(private_key_file) = params.private_key_file { - tokio::fs::read_to_string(private_key_file) - .await - .map_err(|_| ProviderCredentialsError::MissingPrivateKey)? - } else { - unreachable!("already validated above") - }; - - let key = elliptic_curve::SecretKey::from_pkcs8_pem(&private_key_pem) - .map_err(|inner| ProviderCredentialsError::InvalidPrivateKey { inner })?; + let key = elliptic_curve::SecretKey::from_pkcs8_pem(¶ms.private_key)?; ClientCredentials::SignInWithApple { client_id, @@ -179,4 +139,4 @@ async fn client_credentials_for_provider( }; Ok(client_credentials) -} +} \ No newline at end of file From d00e05be547d2d2d2f24f403fdd39aac044c6e7f Mon Sep 17 00:00:00 2001 From: Adis Veletanlic Date: Mon, 14 Apr 2025 18:28:17 +0200 Subject: [PATCH 6/9] Fix incorrect async usage on client_credentials_for_provider --- crates/handlers/src/upstream_oauth2/callback.rs | 3 +-- crates/handlers/src/upstream_oauth2/mod.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/handlers/src/upstream_oauth2/callback.rs b/crates/handlers/src/upstream_oauth2/callback.rs index 83e0cdcef..be4b5a2d1 100644 --- a/crates/handlers/src/upstream_oauth2/callback.rs +++ b/crates/handlers/src/upstream_oauth2/callback.rs @@ -294,8 +294,7 @@ pub(crate) async fn handler( lazy_metadata.token_endpoint().await?, &keystore, &encrypter, - ) - .await?; + )?; let redirect_uri = url_builder.upstream_oauth_callback(provider.id); diff --git a/crates/handlers/src/upstream_oauth2/mod.rs b/crates/handlers/src/upstream_oauth2/mod.rs index c8e37a86c..c387aca1b 100644 --- a/crates/handlers/src/upstream_oauth2/mod.rs +++ b/crates/handlers/src/upstream_oauth2/mod.rs @@ -139,4 +139,4 @@ fn client_credentials_for_provider( }; Ok(client_credentials) -} \ No newline at end of file +} From 512208f7bab0066c85dc2ae36b7097b0a026612b Mon Sep 17 00:00:00 2001 From: Adis Veletanlic Date: Mon, 14 Apr 2025 18:44:18 +0200 Subject: [PATCH 7/9] Add comment to explain sync workflow --- crates/cli/src/sync.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/cli/src/sync.rs b/crates/cli/src/sync.rs index eaf2482d1..a12a9ee5d 100644 --- a/crates/cli/src/sync.rs +++ b/crates/cli/src/sync.rs @@ -190,6 +190,9 @@ pub async fn config_sync( if let Some(client_secret) = provider.client_secret.as_deref() { Some(encrypter.encrypt_to_string(client_secret.as_bytes())?) } else if let Some(mut siwa) = provider.sign_in_with_apple.clone() { + // if private key file is defined and not private key (raw), we populate the private key + // to hold the content of the private key file. private key (raw) takes precedence so + // both can be defined without issues if siwa.private_key.is_none() { if let Some(private_key_file) = siwa.private_key_file.take() { let key = tokio::fs::read_to_string(private_key_file).await?; From 351c1e765485d5aa2ad642dce0d36bd31b223575 Mon Sep 17 00:00:00 2001 From: Adis Veletanlic Date: Mon, 14 Apr 2025 18:52:41 +0200 Subject: [PATCH 8/9] Format project --- crates/cli/src/sync.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli/src/sync.rs b/crates/cli/src/sync.rs index a12a9ee5d..c66f6f004 100644 --- a/crates/cli/src/sync.rs +++ b/crates/cli/src/sync.rs @@ -191,7 +191,7 @@ pub async fn config_sync( Some(encrypter.encrypt_to_string(client_secret.as_bytes())?) } else if let Some(mut siwa) = provider.sign_in_with_apple.clone() { // if private key file is defined and not private key (raw), we populate the private key - // to hold the content of the private key file. private key (raw) takes precedence so + // to hold the content of the private key file. private key (raw) takes precedence so // both can be defined without issues if siwa.private_key.is_none() { if let Some(private_key_file) = siwa.private_key_file.take() { From 41b460b6724957e052a23d291949d9f842dc851a Mon Sep 17 00:00:00 2001 From: Adis Veletanlic Date: Wed, 16 Apr 2025 10:11:02 +0200 Subject: [PATCH 9/9] Format project with nightly toolchain --- crates/cli/src/sync.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/cli/src/sync.rs b/crates/cli/src/sync.rs index c66f6f004..36f9568e8 100644 --- a/crates/cli/src/sync.rs +++ b/crates/cli/src/sync.rs @@ -190,9 +190,10 @@ pub async fn config_sync( if let Some(client_secret) = provider.client_secret.as_deref() { Some(encrypter.encrypt_to_string(client_secret.as_bytes())?) } else if let Some(mut siwa) = provider.sign_in_with_apple.clone() { - // if private key file is defined and not private key (raw), we populate the private key - // to hold the content of the private key file. private key (raw) takes precedence so - // both can be defined without issues + // if private key file is defined and not private key (raw), we populate the + // private key to hold the content of the private key file. + // private key (raw) takes precedence so both can be defined + // without issues if siwa.private_key.is_none() { if let Some(private_key_file) = siwa.private_key_file.take() { let key = tokio::fs::read_to_string(private_key_file).await?;