tuwunel_api/client/session/
jwt.rs1use jwt::{TokenData, Validation, dangerous::insecure_decode, decode};
2use ruma::{
3 OwnedUserId, UserId,
4 api::client::session::login::v3::{Request, Token},
5};
6use serde::Deserialize;
7use tuwunel_core::{Err, Result, at, config::JwtConfig, debug, err, jwt, utils::BoolExt, warn};
8use tuwunel_service::Services;
9
10use crate::Ruma;
11
12#[derive(Debug, Deserialize)]
13struct Claim {
14 sub: String,
16}
17
18pub(super) async fn handle_login(
19 services: &Services,
20 _body: &Ruma<Request>,
21 info: &Token,
22) -> Result<OwnedUserId> {
23 let user_id = validate_user(services, &info.token)?;
24
25 if !services.users.exists(&user_id).await {
26 let config = &services.config.jwt;
27
28 if !config.register_user {
29 return Err!(Request(NotFound("User {user_id} is not registered on this server.")));
30 }
31
32 services
33 .users
34 .create(&user_id, Some("*"), Some("jwt"))
35 .await?;
36 }
37
38 Ok(user_id)
39}
40
41pub(crate) fn validate_user(services: &Services, token: &str) -> Result<OwnedUserId> {
42 let config = &services.config.jwt;
43
44 if !config.enable {
45 return Err!(Request(Unauthorized("JWT login is not enabled.")));
46 }
47
48 let claim = validate(config, token)?;
49 let local = claim.sub.to_lowercase();
50 let server = &services.server.name;
51 let user_id = UserId::parse_with_server_name(local, server).map_err(|e| {
52 err!(Request(InvalidUsername("JWT subject is not a valid user MXID: {e}")))
53 })?;
54
55 Ok(user_id)
56}
57
58fn validate(config: &JwtConfig, token: &str) -> Result<Claim> {
59 let token_data = if cfg!(debug_assertions) && !config.validate_signature {
60 warn!("JWT signature validation is disabled!");
61 insecure_decode(token)
62 } else {
63 let verifier = config.decoding_key()?;
64 let validator = init_validator(config)?;
65
66 decode(token, &verifier, &validator)
67 };
68
69 token_data
70 .map(|decoded: TokenData<Claim>| (decoded.header, decoded.claims))
71 .inspect(|(head, claim)| debug!(?head, ?claim, "JWT token decoded"))
72 .map_err(|e| err!(Request(Forbidden("Invalid JWT token: {e}"))))
73 .map(at!(1))
74}
75
76fn init_validator(config: &JwtConfig) -> Result<Validation> {
77 let has_audience = config.audience.is_empty().is_false();
78 let has_issuer = config.issuer.is_empty().is_false();
79 let required = [
80 Some("sub"),
81 config.require_exp.then_some("exp"),
82 config.require_nbf.then_some("nbf"),
83 has_audience.then_some("aud"),
84 has_issuer.then_some("iss"),
85 ];
86
87 let validator = Validation {
88 required_spec_claims: required
89 .into_iter()
90 .flatten()
91 .map(ToOwned::to_owned)
92 .collect(),
93 validate_exp: config.validate_exp,
94 validate_nbf: config.validate_nbf,
95 aud: has_audience.then(|| config.audience.iter().cloned().collect()),
96 iss: has_issuer.then(|| config.issuer.iter().cloned().collect()),
97 ..Validation::new(config.algorithm()?)
98 };
99
100 debug!(?validator, "JWT configured");
101
102 Ok(validator)
103}