Add private_key_file option for apple sso and edit docs
This commit is contained in:
@@ -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<String>")]
|
||||
pub private_key_file: Option<Utf8PathBuf>,
|
||||
|
||||
/// The private key used to sign the `id_token`
|
||||
pub private_key: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub private_key: Option<String>,
|
||||
|
||||
/// 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<String>,
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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<String>")]
|
||||
pub private_key_file: Option<Utf8PathBuf>,
|
||||
|
||||
/// The private key used to sign the `id_token`
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub private_key: Option<String>,
|
||||
|
||||
/// 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<ClientCredentials, ProviderCredentialsError> {
|
||||
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<ClientCredentials, AppleCredentialsError> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
@@ -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"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"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 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: "<Service ID>" # TO BE FILLED
|
||||
- id: 01JAYS74TCG3BTWKADN5Q4518C
|
||||
issuer: "https://appleid.apple.com"
|
||||
human_name: "Apple"
|
||||
brand_name: "apple"
|
||||
client_id: "<Service 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: "<Location of the PEM-encoded private key file>" # TO BE FILLED
|
||||
private_key: | # TO BE FILLED
|
||||
# <Contents of the private key>
|
||||
|
||||
team_id: "<Team ID>" # TO BE FILLED
|
||||
key_id: "<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"
|
||||
}
|
||||
```
|
||||
```
|
||||
Reference in New Issue
Block a user