mas_handlers/oauth2/
mod.rs

1// Copyright 2024 New Vector Ltd.
2// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
3//
4// SPDX-License-Identifier: AGPL-3.0-only
5// Please see LICENSE in the repository root for full details.
6
7use 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 device;
27pub mod discovery;
28pub mod introspection;
29pub mod keys;
30pub mod registration;
31pub mod revoke;
32pub mod token;
33pub mod userinfo;
34pub mod webfinger;
35
36#[derive(Debug, Error)]
37#[error(transparent)]
38pub(crate) enum IdTokenSignatureError {
39    #[error("The signing key is invalid")]
40    InvalidSigningKey,
41    Claim(#[from] mas_jose::claims::ClaimError),
42    JwtSignature(#[from] mas_jose::jwt::JwtSignatureError),
43    WrongAlgorithm(#[from] mas_keystore::WrongAlgorithmError),
44    TokenHash(#[from] mas_jose::claims::TokenHashError),
45}
46
47pub(crate) fn generate_id_token(
48    rng: &mut (impl rand::RngCore + rand::CryptoRng),
49    clock: &impl Clock,
50    url_builder: &UrlBuilder,
51    key_store: &Keystore,
52    client: &Client,
53    grant: Option<&AuthorizationGrant>,
54    browser_session: &BrowserSession,
55    access_token: Option<&AccessToken>,
56    last_authentication: Option<&Authentication>,
57) -> Result<String, IdTokenSignatureError> {
58    let mut claims = HashMap::new();
59    let now = clock.now();
60    claims::ISS.insert(&mut claims, url_builder.oidc_issuer().to_string())?;
61    claims::SUB.insert(&mut claims, &browser_session.user.sub)?;
62    claims::AUD.insert(&mut claims, client.client_id.clone())?;
63    claims::IAT.insert(&mut claims, now)?;
64    claims::EXP.insert(&mut claims, now + Duration::try_hours(1).unwrap())?;
65
66    if let Some(nonce) = grant.and_then(|grant| grant.nonce.as_ref()) {
67        claims::NONCE.insert(&mut claims, nonce)?;
68    }
69
70    if let Some(last_authentication) = last_authentication {
71        claims::AUTH_TIME.insert(&mut claims, last_authentication.created_at)?;
72    }
73
74    let alg = client
75        .id_token_signed_response_alg
76        .clone()
77        .unwrap_or(JsonWebSignatureAlg::Rs256);
78    let key = key_store
79        .signing_key_for_algorithm(&alg)
80        .ok_or(IdTokenSignatureError::InvalidSigningKey)?;
81
82    if let Some(access_token) = access_token {
83        claims::AT_HASH.insert(&mut claims, hash_token(&alg, &access_token.access_token)?)?;
84    }
85
86    if let Some(code) = grant.and_then(|grant| grant.code.as_ref()) {
87        claims::C_HASH.insert(&mut claims, hash_token(&alg, &code.code)?)?;
88    }
89
90    let signer = key.params().signing_key_for_alg(&alg)?;
91    let header = JsonWebSignatureHeader::new(alg)
92        .with_kid(key.kid().ok_or(IdTokenSignatureError::InvalidSigningKey)?);
93    let id_token = Jwt::sign_with_rng(rng, header, claims, &signer)?;
94
95    Ok(id_token.into_string())
96}
97
98pub(crate) async fn generate_token_pair<R: RepositoryAccess>(
99    rng: &mut (impl rand::RngCore + Send),
100    clock: &impl Clock,
101    repo: &mut R,
102    session: &Session,
103    ttl: Duration,
104) -> Result<(AccessToken, RefreshToken), R::Error> {
105    let access_token_str = TokenType::AccessToken.generate(rng);
106    let refresh_token_str = TokenType::RefreshToken.generate(rng);
107
108    let access_token = repo
109        .oauth2_access_token()
110        .add(rng, clock, session, access_token_str, Some(ttl))
111        .await?;
112
113    let refresh_token = repo
114        .oauth2_refresh_token()
115        .add(rng, clock, session, &access_token, refresh_token_str)
116        .await?;
117
118    Ok((access_token, refresh_token))
119}