oxide_framework_core/auth/claims.rs
1//! JWT payload claims inserted into request extensions after successful auth.
2
3use serde::{Deserialize, Serialize};
4
5/// Standard claims decoded from a JWT (Bearer or session cookie).
6///
7/// Include `roles` as a JSON array of strings in the token payload, for example:
8/// `{"sub":"user-1","roles":["admin","user"],"exp":...}`.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct AuthClaims {
11 /// Subject (typically user id).
12 pub sub: String,
13 /// Role names for [`crate::auth::RequireRole`] and [`AuthClaims::has_role`].
14 #[serde(default)]
15 pub roles: Vec<String>,
16 /// Expiration time (Unix seconds). Required for validation.
17 pub exp: u64,
18 /// Issuer — include when validating with [`crate::auth::AuthConfig::with_issuer`].
19 #[serde(default)]
20 pub iss: Option<String>,
21 /// Audience — include when validating with [`crate::auth::AuthConfig::with_audience`].
22 #[serde(default)]
23 pub aud: Option<String>,
24}
25
26impl AuthClaims {
27 /// Build claims with `exp` set to now + `ttl_secs`.
28 pub fn new(sub: impl Into<String>, roles: Vec<String>, ttl_secs: u64) -> Self {
29 let exp = jsonwebtoken::get_current_timestamp().saturating_add(ttl_secs);
30 Self {
31 sub: sub.into(),
32 roles,
33 exp,
34 iss: None,
35 aud: None,
36 }
37 }
38
39 /// Returns true if `role` is present in [`Self::roles`].
40 pub fn has_role(&self, role: &str) -> bool {
41 self.roles.iter().any(|r| r == role)
42 }
43
44 /// Returns true if the principal has **any** of the given roles.
45 pub fn has_any_role(&self, roles: &[&str]) -> bool {
46 roles.iter().any(|r| self.has_role(r))
47 }
48
49 /// Returns true if the principal has **all** of the given roles.
50 pub fn has_all_roles(&self, roles: &[&str]) -> bool {
51 roles.iter().all(|r| self.has_role(r))
52 }
53}
54