Skip to main content

reauth_types/
claims.rs

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