headless_lms_server/domain/oauth/
oidc.rs1use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
2use chrono::{DateTime, Utc};
3use jsonwebtoken::{EncodingKey, Header, encode};
4use rsa::RsaPublicKey;
5use rsa::pkcs1::DecodeRsaPublicKey;
6use rsa::pkcs8::{DecodePublicKey, EncodePublicKey};
7use rsa::traits::PublicKeyParts;
8use sha2::{Digest as ShaDigest, Sha256};
9
10use crate::domain::error::{ControllerError, ControllerErrorType, OAuthErrorCode, OAuthErrorData};
11use crate::domain::oauth::claims::Claims;
12use crate::prelude::{ApplicationConfiguration, BackendError};
13
14pub fn rsa_n_e_and_kid_from_pem(public_pem: &str) -> anyhow::Result<(String, String, String)> {
15 let pubkey = match RsaPublicKey::from_pkcs1_pem(public_pem) {
16 Ok(k) => k,
17 Err(_) => RsaPublicKey::from_public_key_pem(public_pem)?,
18 };
19
20 let n_b64 = URL_SAFE_NO_PAD.encode(pubkey.n().to_bytes_be());
21 let e_b64 = URL_SAFE_NO_PAD.encode(pubkey.e().to_bytes_be());
22
23 let spki_der = pubkey.to_public_key_der()?;
24 let kid = URL_SAFE_NO_PAD.encode(Sha256::digest(spki_der.as_bytes()));
25
26 Ok((n_b64, e_b64, kid))
27}
28
29pub fn generate_id_token(
30 user_id: uuid::Uuid,
31 client_id: &str,
32 nonce: &str,
33 expires_at: DateTime<Utc>,
34 issuer: &str,
35 cfg: &ApplicationConfiguration,
36) -> Result<String, ControllerError> {
37 let now = Utc::now().timestamp();
38 let exp = expires_at.timestamp();
39
40 let (_, _, kid) = rsa_n_e_and_kid_from_pem(&cfg.oauth_server_configuration.rsa_public_key)
41 .map_err(|e| {
42 ControllerError::new(
43 ControllerErrorType::OAuthError(Box::new(OAuthErrorData {
44 error: OAuthErrorCode::ServerError.as_str().into(),
45 error_description: "Failed to derive key id (kid) from public key".into(),
46 redirect_uri: None,
47 state: None,
48 nonce: None,
49 })),
50 "Failed to derive kid from public key",
51 Some(e),
52 )
53 })?;
54
55 let claims = Claims {
56 sub: user_id.to_string(),
57 aud: client_id.to_string(),
58 iss: issuer.to_string(),
59 iat: now,
60 exp,
61 nonce: nonce.to_string(),
62 };
63
64 let mut header = Header::new(jsonwebtoken::Algorithm::RS256);
65 header.kid = Some(kid);
66
67 let enc_key =
68 EncodingKey::from_rsa_pem(cfg.oauth_server_configuration.rsa_private_key.as_bytes())
69 .map_err(|e| {
70 ControllerError::new(
71 ControllerErrorType::OAuthError(Box::new(OAuthErrorData {
72 error: OAuthErrorCode::ServerError.as_str().into(),
73 error_description: "Failed to generate ID token".into(),
74 redirect_uri: None,
75 state: None,
76 nonce: None,
77 })),
78 "Failed to generate ID token (invalid private key)",
79 Some(e.into()),
80 )
81 })?;
82
83 encode(&header, &claims, &enc_key).map_err(|e| {
84 ControllerError::new(
85 ControllerErrorType::OAuthError(Box::new(OAuthErrorData {
86 error: OAuthErrorCode::ServerError.as_str().into(),
87 error_description: "Failed to generate ID token".into(),
88 redirect_uri: None,
89 state: None,
90 nonce: None,
91 })),
92 "Failed to generate ID token",
93 Some(e.into()),
94 )
95 })
96}