mas_handlers/oauth2/
mod.rs1use std::collections::HashMap;
8
9use chrono::Duration;
10use mas_data_model::{
11 AccessToken, Authentication, AuthorizationGrant, BrowserSession, Client, RefreshToken, Session,
12 TokenType,
13};
14use mas_iana::jose::JsonWebSignatureAlg;
15use mas_jose::{
16 claims::{self, hash_token},
17 constraints::Constrainable,
18 jwt::{JsonWebSignatureHeader, Jwt},
19};
20use mas_keystore::Keystore;
21use mas_router::UrlBuilder;
22use mas_storage::{Clock, RepositoryAccess};
23use thiserror::Error;
24
25pub mod authorization;
26pub mod consent;
27pub mod device;
28pub mod discovery;
29pub mod introspection;
30pub mod keys;
31pub mod registration;
32pub mod revoke;
33pub mod token;
34pub mod userinfo;
35pub mod webfinger;
36
37#[derive(Debug, Error)]
38#[error(transparent)]
39pub(crate) enum IdTokenSignatureError {
40 #[error("The signing key is invalid")]
41 InvalidSigningKey,
42 Claim(#[from] mas_jose::claims::ClaimError),
43 JwtSignature(#[from] mas_jose::jwt::JwtSignatureError),
44 WrongAlgorithm(#[from] mas_keystore::WrongAlgorithmError),
45 TokenHash(#[from] mas_jose::claims::TokenHashError),
46}
47
48pub(crate) fn generate_id_token(
49 rng: &mut (impl rand::RngCore + rand::CryptoRng),
50 clock: &impl Clock,
51 url_builder: &UrlBuilder,
52 key_store: &Keystore,
53 client: &Client,
54 grant: Option<&AuthorizationGrant>,
55 browser_session: &BrowserSession,
56 access_token: Option<&AccessToken>,
57 last_authentication: Option<&Authentication>,
58) -> Result<String, IdTokenSignatureError> {
59 let mut claims = HashMap::new();
60 let now = clock.now();
61 claims::ISS.insert(&mut claims, url_builder.oidc_issuer().to_string())?;
62 claims::SUB.insert(&mut claims, &browser_session.user.sub)?;
63 claims::AUD.insert(&mut claims, client.client_id.clone())?;
64 claims::IAT.insert(&mut claims, now)?;
65 claims::EXP.insert(&mut claims, now + Duration::try_hours(1).unwrap())?;
66
67 if let Some(nonce) = grant.and_then(|grant| grant.nonce.as_ref()) {
68 claims::NONCE.insert(&mut claims, nonce)?;
69 }
70
71 if let Some(last_authentication) = last_authentication {
72 claims::AUTH_TIME.insert(&mut claims, last_authentication.created_at)?;
73 }
74
75 let alg = client
76 .id_token_signed_response_alg
77 .clone()
78 .unwrap_or(JsonWebSignatureAlg::Rs256);
79 let key = key_store
80 .signing_key_for_algorithm(&alg)
81 .ok_or(IdTokenSignatureError::InvalidSigningKey)?;
82
83 if let Some(access_token) = access_token {
84 claims::AT_HASH.insert(&mut claims, hash_token(&alg, &access_token.access_token)?)?;
85 }
86
87 if let Some(code) = grant.and_then(|grant| grant.code.as_ref()) {
88 claims::C_HASH.insert(&mut claims, hash_token(&alg, &code.code)?)?;
89 }
90
91 let signer = key.params().signing_key_for_alg(&alg)?;
92 let header = JsonWebSignatureHeader::new(alg)
93 .with_kid(key.kid().ok_or(IdTokenSignatureError::InvalidSigningKey)?);
94 let id_token = Jwt::sign_with_rng(rng, header, claims, &signer)?;
95
96 Ok(id_token.into_string())
97}
98
99pub(crate) async fn generate_token_pair<R: RepositoryAccess>(
100 rng: &mut (impl rand::RngCore + Send),
101 clock: &impl Clock,
102 repo: &mut R,
103 session: &Session,
104 ttl: Duration,
105) -> Result<(AccessToken, RefreshToken), R::Error> {
106 let access_token_str = TokenType::AccessToken.generate(rng);
107 let refresh_token_str = TokenType::RefreshToken.generate(rng);
108
109 let access_token = repo
110 .oauth2_access_token()
111 .add(rng, clock, session, access_token_str, Some(ttl))
112 .await?;
113
114 let refresh_token = repo
115 .oauth2_refresh_token()
116 .add(rng, clock, session, &access_token, refresh_token_str)
117 .await?;
118
119 Ok((access_token, refresh_token))
120}