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}