Skip to main content

fraiseql_core/security/oidc/
audience.rs

1//! Audience validation types for OIDC token claims.
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7// ============================================================================
8// JWT Claims
9// ============================================================================
10
11/// Standard JWT claims for validation.
12#[derive(Debug, Clone, Deserialize)]
13pub struct JwtClaims {
14    /// Subject (user ID)
15    pub sub: Option<String>,
16
17    /// Issuer
18    pub iss: Option<String>,
19
20    /// Audience (can be string or array)
21    #[serde(default)]
22    pub aud: Audience,
23
24    /// Expiration time (Unix timestamp)
25    pub exp: Option<i64>,
26
27    /// Issued at (Unix timestamp)
28    pub iat: Option<i64>,
29
30    /// Not before (Unix timestamp)
31    pub nbf: Option<i64>,
32
33    /// JWT ID — unique identifier for this token.
34    ///
35    /// Used by the replay cache to detect reuse of a stolen token.
36    pub jti: Option<String>,
37
38    /// Scope (space-separated string, common in Auth0/Okta)
39    pub scope: Option<String>,
40
41    /// Scopes (array, common in some providers)
42    pub scp: Option<Vec<String>>,
43
44    /// Permissions (array, common in Auth0)
45    pub permissions: Option<Vec<String>>,
46
47    /// Email claim (may be a flat string or a nested object).
48    pub email: Option<serde_json::Value>,
49
50    /// Email verified
51    pub email_verified: Option<bool>,
52
53    /// Name claim (may be a flat string or a nested object).
54    pub name: Option<serde_json::Value>,
55
56    /// Arbitrary extra claims not captured by named fields above.
57    ///
58    /// Captures custom OIDC claims such as `"email"`, `"tenant_id"`, or
59    /// namespaced claims like `"https://myapp.com/role"` that are not part of
60    /// the standard JWT claim set.  Used by `GET /auth/me` to reflect a
61    /// configurable subset of the token's claims to the frontend.
62    #[serde(flatten)]
63    pub extra: HashMap<String, serde_json::Value>,
64}
65
66/// Audience can be a single string or array of strings.
67#[derive(Debug, Clone, Default, Deserialize, Serialize)]
68#[serde(untagged)]
69#[non_exhaustive]
70pub enum Audience {
71    /// No audience specified.
72    #[default]
73    None,
74    /// Single audience string.
75    Single(String),
76    /// Multiple audiences as an array.
77    Multiple(Vec<String>),
78}
79
80impl Audience {
81    /// Check if the audience contains a specific value.
82    #[must_use]
83    pub fn contains(&self, value: &str) -> bool {
84        match self {
85            Self::None => false,
86            Self::Single(s) => s == value,
87            Self::Multiple(v) => v.iter().any(|s| s == value),
88        }
89    }
90
91    /// Get all audience values as a vector.
92    #[must_use]
93    pub fn to_vec(&self) -> Vec<String> {
94        match self {
95            Self::None => Vec::new(),
96            Self::Single(s) => vec![s.clone()],
97            Self::Multiple(v) => v.clone(),
98        }
99    }
100}