1
//! Per-user JWT signing/verification for the web layer.
2
//!
3
//! Tokens are signed with each user's own RSA key (private key in their per-user
4
//! DB, public key in the global `users` directory). This module wraps the
5
//! `server` key lookups + the `token` codec into mint/verify helpers the auth
6
//! handlers call.
7
//!
8
//! Verification always reads the authoritative public key from the directory —
9
//! no public-key cache. A cache would risk accepting tokens signed with a
10
//! rotated/revoked key until process restart; correctness wins over saving one
11
//! indexed primary-key lookup. The DoS surface of unauthenticated lookups is
12
//! addressed by rate-limiting the auth endpoints, not by caching.
13

            
14
use crate::token::{self, TokenDetails, TokenType};
15
use uuid::Uuid;
16

            
17
/// Whether auth cookies should carry the `Secure` attribute (sent only over
18
/// HTTPS). Defaults to `true`; set `INSECURE_COOKIES=1` for local plain-HTTP
19
/// development. Bearer cookies (access/refresh tokens) must be `Secure` in
20
/// production so they can't leak over a downgraded HTTP request.
21
#[must_use]
22
pub fn secure_cookies() -> bool {
23
    !matches!(
24
        std::env::var("INSECURE_COOKIES").as_deref(),
25
        Ok("1") | Ok("true")
26
    )
27
}
28

            
29
/// Mints a token of `token_type` for `user_id`, signed with that user's
30
/// private key.
31
pub async fn mint(
32
    user_id: Uuid,
33
    ttl: i64,
34
    token_type: TokenType,
35
) -> Result<TokenDetails, MintError> {
36
    let private_key = server::auth_keys::private_key_for(user_id)
37
        .await
38
        .map_err(|_| MintError::KeyUnavailable)?;
39
    token::generate_jwt_token(user_id, ttl, &private_key, token_type).map_err(|_| MintError::Sign)
40
}
41

            
42
/// Verifies a token of the EXPECTED type against its owner's public key.
43
///
44
/// Reads the `sub` claim unverified as a routing hint ONLY, fetches that user's
45
/// authoritative public key from the directory, re-verifies the signature, and
46
/// requires the claimed `token_type` to match `expected` — so an access token
47
/// can never be replayed where a refresh token is required (or vice-versa).
48
/// Returns `None` for any failure — unknown user, missing key, bad signature,
49
/// or wrong type — so callers cannot distinguish failure modes (existence
50
/// masking). Always hits the DB for the current key, so a rotated/cleared key
51
/// takes effect immediately (no stale-key window).
52
1
pub async fn verify(token: &str, expected: TokenType) -> Option<TokenDetails> {
53
1
    let user_id = token::unverified_user_id(token).ok()?;
54
    let public_key = server::auth_keys::public_key_for(user_id).await.ok()??;
55
    let details = token::verify_jwt_token(&public_key, token).ok()?;
56
    if details.token_type != Some(expected) {
57
        return None;
58
    }
59
    Some(details)
60
1
}
61

            
62
#[derive(Debug)]
63
pub enum MintError {
64
    /// The user has no stored signing key (a provisioning invariant violation).
65
    KeyUnavailable,
66
    /// Signing failed (malformed key material).
67
    Sign,
68
}
69

            
70
impl std::fmt::Display for MintError {
71
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72
        match self {
73
            MintError::KeyUnavailable => write!(f, "no signing key available for user"),
74
            MintError::Sign => write!(f, "failed to sign token"),
75
        }
76
    }
77
}