Skip to main content

reauth_types/
claims.rs

1use serde::{Deserialize, Serialize};
2
3/// JWT claims for domain end-users.
4///
5/// Issued by the Reauth API and verified by SDKs.
6/// Contains user identity, roles, and subscription information.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct DomainEndUserClaims {
9    /// User ID (subject) - the end_user_id
10    pub sub: String,
11
12    /// Domain ID (UUID as string)
13    pub domain_id: String,
14
15    /// Root domain (e.g., "example.com")
16    pub domain: String,
17
18    /// User's roles (e.g., ["admin", "user"])
19    pub roles: Vec<String>,
20
21    /// Subscription information (always present)
22    pub subscription: SubscriptionClaims,
23
24    /// Token expiration (Unix timestamp)
25    pub exp: i64,
26
27    /// Token issued at (Unix timestamp)
28    pub iat: i64,
29}
30
31/// Subscription info embedded in JWT claims.
32///
33/// Uses snake_case to match the JSON format used in tokens.
34#[derive(Debug, Clone, Default, Serialize, Deserialize)]
35pub struct SubscriptionClaims {
36    /// Current subscription status (e.g., "active", "trialing", "none")
37    pub status: String,
38
39    /// Machine-readable plan identifier (e.g., "pro")
40    pub plan_code: Option<String>,
41
42    /// Human-readable plan name (e.g., "Pro Plan")
43    pub plan_name: Option<String>,
44
45    /// Unix timestamp when current period ends
46    pub current_period_end: Option<i64>,
47
48    /// Whether subscription will cancel at period end
49    pub cancel_at_period_end: Option<bool>,
50
51    /// Unix timestamp when trial ends (if applicable)
52    pub trial_ends_at: Option<i64>,
53
54    /// Stripe subscription ID (for backend use)
55    pub subscription_id: Option<String>,
56}
57
58impl SubscriptionClaims {
59    /// Create a "none" subscription for users without a subscription.
60    pub fn none() -> Self {
61        Self {
62            status: "none".to_string(),
63            ..Default::default()
64        }
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn test_subscription_claims_none() {
74        let sub = SubscriptionClaims::none();
75        assert_eq!(sub.status, "none");
76        assert!(sub.plan_code.is_none());
77        assert!(sub.plan_name.is_none());
78    }
79
80    #[test]
81    fn test_domain_end_user_claims_serde() {
82        let claims = DomainEndUserClaims {
83            sub: "user123".to_string(),
84            domain_id: "00000000-0000-0000-0000-000000000001".to_string(),
85            domain: "example.com".to_string(),
86            roles: vec!["user".to_string()],
87            subscription: SubscriptionClaims {
88                status: "active".to_string(),
89                plan_code: Some("pro".to_string()),
90                plan_name: Some("Pro Plan".to_string()),
91                current_period_end: Some(1735689600),
92                cancel_at_period_end: Some(false),
93                trial_ends_at: None,
94                subscription_id: Some("sub_123".to_string()),
95            },
96            exp: 1735689600,
97            iat: 1735603200,
98        };
99
100        let json = serde_json::to_string(&claims).unwrap();
101        let parsed: DomainEndUserClaims = serde_json::from_str(&json).unwrap();
102
103        assert_eq!(parsed.sub, "user123");
104        assert_eq!(parsed.domain, "example.com");
105        assert_eq!(parsed.subscription.status, "active");
106    }
107}