tuwunel_api/router/auth/
jwt.rs1use jwt::{TokenData, Validation, dangerous::insecure_decode, decode};
2use ruma::{OwnedUserId, UserId};
3use serde::Deserialize;
4use tuwunel_core::{Err, Result, at, config::JwtConfig, debug, err, jwt, utils::BoolExt, warn};
5use tuwunel_service::Services;
6
7#[derive(Debug, Deserialize)]
8struct Claim {
9 sub: String,
11}
12
13pub fn validate_user(services: &Services, token: &str) -> Result<OwnedUserId> {
17 let config = &services.config.jwt;
18
19 if !config.enable {
20 return Err!(Request(Unauthorized("JWT login is not enabled.")));
21 }
22
23 let claim = validate(config, token)?;
24 let local = claim.sub.to_lowercase();
25 let server = &services.server.name;
26 let user_id = UserId::parse_with_server_name(local, server).map_err(|e| {
27 err!(Request(InvalidUsername("JWT subject is not a valid user MXID: {e}")))
28 })?;
29
30 Ok(user_id)
31}
32
33fn validate(config: &JwtConfig, token: &str) -> Result<Claim> {
34 let token_data = if cfg!(debug_assertions) && !config.validate_signature {
35 warn!("JWT signature validation is disabled!");
36 insecure_decode(token)
37 } else {
38 let verifier = config.decoding_key()?;
39 let validator = init_validator(config)?;
40
41 decode(token, &verifier, &validator)
42 };
43
44 token_data
45 .map(|decoded: TokenData<Claim>| (decoded.header, decoded.claims))
46 .inspect(|(head, claim)| debug!(?head, ?claim, "JWT token decoded"))
47 .map_err(|e| err!(Request(Forbidden("Invalid JWT token: {e}"))))
48 .map(at!(1))
49}
50
51fn init_validator(config: &JwtConfig) -> Result<Validation> {
52 let has_audience = config.audience.is_empty().is_false();
53 let has_issuer = config.issuer.is_empty().is_false();
54 let required = [
55 Some("sub"),
56 config.require_exp.then_some("exp"),
57 config.require_nbf.then_some("nbf"),
58 has_audience.then_some("aud"),
59 has_issuer.then_some("iss"),
60 ];
61
62 let validator = Validation {
63 required_spec_claims: required
64 .into_iter()
65 .flatten()
66 .map(ToOwned::to_owned)
67 .collect(),
68 validate_exp: config.validate_exp,
69 validate_nbf: config.validate_nbf,
70 aud: has_audience.then(|| config.audience.iter().cloned().collect()),
71 iss: has_issuer.then(|| config.issuer.iter().cloned().collect()),
72 ..Validation::new(config.algorithm()?)
73 };
74
75 debug!(?validator, "JWT configured");
76
77 Ok(validator)
78}