xjp_oidc/
types.rs

1//! Public types for the xjp-oidc SDK
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// OAuth2/OIDC Provider metadata from discovery endpoint
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct OidcProviderMetadata {
9    /// Issuer identifier
10    pub issuer: String,
11    /// Authorization endpoint URL
12    pub authorization_endpoint: String,
13    /// Token endpoint URL
14    pub token_endpoint: String,
15    /// JWKS URI for key discovery
16    pub jwks_uri: String,
17    /// UserInfo endpoint URL (optional)
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub userinfo_endpoint: Option<String>,
20    /// End session endpoint URL (optional)
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub end_session_endpoint: Option<String>,
23    /// Registration endpoint URL (optional)
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub registration_endpoint: Option<String>,
26    /// Supported response types
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub response_types_supported: Option<Vec<String>>,
29    /// Supported grant types
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub grant_types_supported: Option<Vec<String>>,
32    /// Supported scopes
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub scopes_supported: Option<Vec<String>>,
35    /// Supported token endpoint auth methods
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub token_endpoint_auth_methods_supported: Option<Vec<String>>,
38    /// Supported ID token signing algorithms
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub id_token_signing_alg_values_supported: Option<Vec<String>>,
41    /// PKCE code challenge methods supported
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub code_challenge_methods_supported: Option<Vec<String>>,
44    /// Subject identifier types supported (REQUIRED by OIDC spec)
45    #[serde(default = "default_subject_types_supported")]
46    pub subject_types_supported: Vec<String>,
47    /// Token introspection endpoint URL (optional)
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub introspection_endpoint: Option<String>,
50    /// Token revocation endpoint URL (optional)
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub revocation_endpoint: Option<String>,
53    /// Frontchannel logout supported
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub frontchannel_logout_supported: Option<bool>,
56    /// Frontchannel logout session supported
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub frontchannel_logout_session_supported: Option<bool>,
59    /// Backchannel logout supported
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub backchannel_logout_supported: Option<bool>,
62    /// Backchannel logout session supported
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub backchannel_logout_session_supported: Option<bool>,
65    /// Tenant ID (for multi-tenant setups)
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub tenant_id: Option<i64>,
68    /// Tenant slug (for multi-tenant setups)
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub tenant_slug: Option<String>,
71}
72
73/// Default value for subject_types_supported
74fn default_subject_types_supported() -> Vec<String> {
75    vec!["public".to_string()]
76}
77
78/// Token response from token endpoint
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct TokenResponse {
81    /// Access token for API calls
82    pub access_token: String,
83    /// Token type (typically "Bearer")
84    pub token_type: String,
85    /// Token lifetime in seconds
86    pub expires_in: i64,
87    /// Refresh token (if offline_access scope granted)
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub refresh_token: Option<String>,
90    /// Granted scopes space-delimited
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub scope: Option<String>,
93    /// ID token (if openid scope granted)
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub id_token: Option<String>,
96}
97
98/// Verified ID Token with parsed claims
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct VerifiedIdToken {
101    // Standard OIDC claims
102    /// Issuer
103    pub iss: String,
104    /// Subject (user ID)
105    pub sub: String,
106    /// Audience
107    pub aud: String,
108    /// Expiration time (Unix timestamp)
109    pub exp: i64,
110    /// Issued at time (Unix timestamp)
111    pub iat: i64,
112    /// Nonce (if provided)
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub nonce: Option<String>,
115    /// Session ID
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub sid: Option<String>,
118
119    // Profile claims
120    /// User's full name
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub name: Option<String>,
123    /// User's email
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub email: Option<String>,
126    /// User's picture URL
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub picture: Option<String>,
129
130    // Custom claims
131    /// Authentication methods reference (e.g., ["wechat_qr"])
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub amr: Option<Vec<String>>,
134    /// Authentication time (Unix timestamp)
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub auth_time: Option<i64>,
137    /// Admin flag for XiaojinPro admin users
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub xjp_admin: Option<bool>,
140}
141
142/// Result from building an authorization URL
143#[derive(Debug, Clone)]
144pub struct AuthUrlResult {
145    /// The generated authorization URL
146    pub url: url::Url,
147    /// The state parameter (either provided or generated)
148    pub state: String,
149    /// The nonce parameter (either provided or generated, if openid scope requested)
150    pub nonce: Option<String>,
151}
152
153/// Parameters for building authorization URL
154#[derive(Debug, Clone, Default)]
155pub struct BuildAuthUrl {
156    /// Issuer URL
157    pub issuer: String,
158    /// Client ID
159    pub client_id: String,
160    /// Redirect URI
161    pub redirect_uri: String,
162    /// Requested scopes (space-separated)
163    pub scope: String,
164    /// State parameter (will be auto-generated if not provided)
165    pub state: Option<String>,
166    /// Nonce for ID token (will be auto-generated if not provided)
167    pub nonce: Option<String>,
168    /// Prompt parameter (e.g., "login", "consent")
169    pub prompt: Option<String>,
170    /// PKCE code challenge
171    pub code_challenge: String,
172    /// Extra query parameters
173    pub extra_params: Option<HashMap<String, String>>,
174    /// Tenant identifier (for multi-tenant setups)
175    pub tenant: Option<String>,
176    /// Optional authorization endpoint from discovery metadata
177    #[cfg_attr(not(feature = "verifier"), serde(skip_serializing_if = "Option::is_none"))]
178    pub authorization_endpoint: Option<String>,
179}
180
181/// Parameters for exchanging authorization code
182#[derive(Debug, Clone)]
183pub struct ExchangeCode {
184    /// Issuer URL
185    pub issuer: String,
186    /// Client ID
187    pub client_id: String,
188    /// Authorization code
189    pub code: String,
190    /// Redirect URI (must match the one used in authorization)
191    pub redirect_uri: String,
192    /// PKCE code verifier (optional for confidential clients)
193    pub code_verifier: Option<String>,
194    /// Client secret (for confidential clients)
195    pub client_secret: Option<String>,
196    /// Token endpoint authentication method from discovery metadata
197    /// (e.g., "client_secret_basic", "client_secret_post", "none")
198    pub token_endpoint_auth_method: Option<String>,
199}
200
201/// Parameters for end session (logout)
202#[derive(Debug, Clone, Default)]
203pub struct EndSession {
204    /// Issuer URL
205    pub issuer: String,
206    /// ID token hint
207    pub id_token_hint: String,
208    /// Post-logout redirect URI
209    pub post_logout_redirect_uri: Option<String>,
210    /// State parameter
211    pub state: Option<String>,
212    /// End session endpoint from discovery metadata
213    /// If not provided, will use default path: issuer + "/oidc/end_session"
214    pub end_session_endpoint: Option<String>,
215}
216
217/// Callback parameters from authorization response
218#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct CallbackParams {
220    /// Authorization code
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub code: Option<String>,
223    /// State parameter
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub state: Option<String>,
226    /// Error code
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub error: Option<String>,
229    /// Error description
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub error_description: Option<String>,
232}
233
234/// Dynamic Client Registration request
235#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct RegisterRequest {
237    /// Application type (web, native)
238    #[serde(skip_serializing_if = "Option::is_none")]
239    pub application_type: Option<String>,
240    /// Redirect URIs
241    pub redirect_uris: Vec<String>,
242    /// Post-logout redirect URIs
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub post_logout_redirect_uris: Option<Vec<String>>,
245    /// Grant types
246    pub grant_types: Vec<String>,
247    /// Token endpoint auth method
248    pub token_endpoint_auth_method: String,
249    /// Requested scopes
250    pub scope: String,
251    /// Contact emails
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub contacts: Option<Vec<String>>,
254    /// Software ID
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub software_id: Option<String>,
257    /// Client name
258    #[serde(skip_serializing_if = "Option::is_none")]
259    pub client_name: Option<String>,
260}
261
262/// Client registration result
263#[derive(Debug, Clone, Serialize, Deserialize)]
264pub struct ClientRegistrationResult {
265    /// Client ID
266    pub client_id: String,
267    /// Client secret (for confidential clients)
268    #[serde(skip_serializing_if = "Option::is_none")]
269    pub client_secret: Option<String>,
270    /// Registration status
271    pub status: ClientStatus,
272    /// Client name
273    pub client_name: String,
274    /// Redirect URIs
275    pub redirect_uris: Vec<String>,
276    /// Post-logout redirect URIs
277    #[serde(skip_serializing_if = "Option::is_none")]
278    pub post_logout_redirect_uris: Option<Vec<String>>,
279    /// Grant types
280    pub grant_types: Vec<String>,
281    /// Token endpoint auth method
282    pub token_endpoint_auth_method: String,
283    /// Allowed scopes
284    pub scope: String,
285}
286
287/// Client registration status
288#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
289#[serde(rename_all = "lowercase")]
290pub enum ClientStatus {
291    /// Client is active and can be used
292    Active,
293    /// Client is pending approval
294    Pending,
295    /// Client is suspended
296    Suspended,
297}
298
299/// Verified claims from access token (for Resource Server)
300#[cfg(feature = "verifier")]
301#[derive(Debug, Clone, Serialize, Deserialize)]
302pub struct VerifiedClaims {
303    /// Issuer
304    pub iss: String,
305    /// Subject
306    pub sub: String,
307    /// Audience
308    pub aud: String,
309    /// Expiration
310    pub exp: i64,
311    /// Issued at
312    pub iat: i64,
313    /// JWT ID
314    pub jti: String,
315    /// Scopes
316    #[serde(skip_serializing_if = "Option::is_none")]
317    pub scope: Option<String>,
318    /// Admin flag
319    #[serde(skip_serializing_if = "Option::is_none")]
320    pub xjp_admin: Option<bool>,
321    /// Authentication methods
322    #[serde(skip_serializing_if = "Option::is_none")]
323    pub amr: Option<Vec<String>>,
324    /// Authentication time
325    #[serde(skip_serializing_if = "Option::is_none")]
326    pub auth_time: Option<i64>,
327}
328
329/// Options for verifying ID tokens
330#[derive(Clone)]
331pub struct VerifyOptions<'a> {
332    /// Expected issuer
333    pub issuer: &'a str,
334    /// Expected audience
335    pub audience: &'a str,
336    /// Expected nonce (if any)
337    pub nonce: Option<&'a str>,
338    /// Maximum age in seconds (for auth_time validation)
339    pub max_age_sec: Option<i64>,
340    /// Clock skew tolerance in seconds
341    pub clock_skew_sec: Option<i64>,
342    /// HTTP client for fetching JWKS
343    pub http: &'a dyn crate::http::HttpClient,
344    /// Cache for JWKS
345    pub cache: &'a dyn crate::cache::Cache<String, crate::jwks::Jwks>,
346}
347
348impl Default for VerifyOptions<'_> {
349    fn default() -> Self {
350        panic!("VerifyOptions requires explicit construction with required fields")
351    }
352}
353
354/// Token introspection request
355#[derive(Debug, Clone)]
356pub struct IntrospectRequest {
357    /// Issuer URL
358    pub issuer: String,
359    /// Client ID
360    pub client_id: String,
361    /// Client secret (for confidential clients)
362    pub client_secret: Option<String>,
363    /// Token to introspect
364    pub token: String,
365    /// Hint about the type of the token
366    pub token_type_hint: Option<String>,
367    /// Token endpoint authentication method
368    pub token_endpoint_auth_method: Option<String>,
369}
370
371/// Token introspection response (RFC 7662)
372#[derive(Debug, Clone, Serialize, Deserialize)]
373pub struct IntrospectResponse {
374    /// Whether the token is active
375    pub active: bool,
376    /// Space-separated list of scopes
377    #[serde(skip_serializing_if = "Option::is_none")]
378    pub scope: Option<String>,
379    /// Client identifier
380    #[serde(skip_serializing_if = "Option::is_none")]
381    pub client_id: Option<String>,
382    /// Human-readable identifier for the resource owner
383    #[serde(skip_serializing_if = "Option::is_none")]
384    pub username: Option<String>,
385    /// Type of the token
386    #[serde(skip_serializing_if = "Option::is_none")]
387    pub token_type: Option<String>,
388    /// Expiration time (Unix timestamp)
389    #[serde(skip_serializing_if = "Option::is_none")]
390    pub exp: Option<i64>,
391    /// Issued at time (Unix timestamp)
392    #[serde(skip_serializing_if = "Option::is_none")]
393    pub iat: Option<i64>,
394    /// Not before time (Unix timestamp)
395    #[serde(skip_serializing_if = "Option::is_none")]
396    pub nbf: Option<i64>,
397    /// Subject of the token
398    #[serde(skip_serializing_if = "Option::is_none")]
399    pub sub: Option<String>,
400    /// Audience of the token
401    #[serde(skip_serializing_if = "Option::is_none")]
402    pub aud: Option<Vec<String>>,
403    /// Issuer of the token
404    #[serde(skip_serializing_if = "Option::is_none")]
405    pub iss: Option<String>,
406    /// Unique identifier for the token
407    #[serde(skip_serializing_if = "Option::is_none")]
408    pub jti: Option<String>,
409}
410
411/// Refresh token request
412#[derive(Debug, Clone)]
413pub struct RefreshTokenRequest {
414    /// Issuer URL
415    pub issuer: String,
416    /// Client ID
417    pub client_id: String,
418    /// Client secret (for confidential clients)
419    pub client_secret: Option<String>,
420    /// Refresh token
421    pub refresh_token: String,
422    /// Requested scope (optional)
423    pub scope: Option<String>,
424    /// Token endpoint authentication method
425    pub token_endpoint_auth_method: Option<String>,
426}
427
428/// UserInfo response
429#[derive(Debug, Clone, Serialize, Deserialize)]
430pub struct UserInfo {
431    /// Subject - Identifier for the End-User
432    pub sub: String,
433    /// End-User's full name
434    #[serde(skip_serializing_if = "Option::is_none")]
435    pub name: Option<String>,
436    /// Given name(s) or first name(s)
437    #[serde(skip_serializing_if = "Option::is_none")]
438    pub given_name: Option<String>,
439    /// Surname(s) or last name(s)
440    #[serde(skip_serializing_if = "Option::is_none")]
441    pub family_name: Option<String>,
442    /// Middle name(s)
443    #[serde(skip_serializing_if = "Option::is_none")]
444    pub middle_name: Option<String>,
445    /// Casual name
446    #[serde(skip_serializing_if = "Option::is_none")]
447    pub nickname: Option<String>,
448    /// Preferred username
449    #[serde(skip_serializing_if = "Option::is_none")]
450    pub preferred_username: Option<String>,
451    /// Profile page URL
452    #[serde(skip_serializing_if = "Option::is_none")]
453    pub profile: Option<String>,
454    /// Profile picture URL
455    #[serde(skip_serializing_if = "Option::is_none")]
456    pub picture: Option<String>,
457    /// Web page or blog URL
458    #[serde(skip_serializing_if = "Option::is_none")]
459    pub website: Option<String>,
460    /// Preferred e-mail address
461    #[serde(skip_serializing_if = "Option::is_none")]
462    pub email: Option<String>,
463    /// True if e-mail address has been verified
464    #[serde(skip_serializing_if = "Option::is_none")]
465    pub email_verified: Option<bool>,
466    /// Gender
467    #[serde(skip_serializing_if = "Option::is_none")]
468    pub gender: Option<String>,
469    /// Birthday (ISO 8601:2004 YYYY-MM-DD format)
470    #[serde(skip_serializing_if = "Option::is_none")]
471    pub birthdate: Option<String>,
472    /// Time zone (e.g., Europe/Paris, America/Los_Angeles)
473    #[serde(skip_serializing_if = "Option::is_none")]
474    pub zoneinfo: Option<String>,
475    /// Locale (e.g., en-US, fr-CA)
476    #[serde(skip_serializing_if = "Option::is_none")]
477    pub locale: Option<String>,
478    /// Preferred telephone number
479    #[serde(skip_serializing_if = "Option::is_none")]
480    pub phone_number: Option<String>,
481    /// True if phone number has been verified
482    #[serde(skip_serializing_if = "Option::is_none")]
483    pub phone_number_verified: Option<bool>,
484    /// Preferred postal address
485    #[serde(skip_serializing_if = "Option::is_none")]
486    pub address: Option<serde_json::Value>,
487    /// Time the information was last updated
488    #[serde(skip_serializing_if = "Option::is_none")]
489    pub updated_at: Option<i64>,
490    // Custom claims for XiaojinPro
491    /// Admin flag for XiaojinPro admin users
492    #[serde(skip_serializing_if = "Option::is_none")]
493    pub xjp_admin: Option<bool>,
494    /// Authentication methods reference
495    #[serde(skip_serializing_if = "Option::is_none")]
496    pub amr: Option<Vec<String>>,
497    /// Authentication time
498    #[serde(skip_serializing_if = "Option::is_none")]
499    pub auth_time: Option<i64>,
500}
501
502/// Client configuration from DCR GET endpoint
503#[derive(Debug, Clone, Serialize, Deserialize)]
504pub struct ClientConfig {
505    /// Client ID
506    pub client_id: String,
507    /// Client secret (for confidential clients)
508    #[serde(skip_serializing_if = "Option::is_none")]
509    pub client_secret: Option<String>,
510    /// Client name
511    pub client_name: String,
512    /// Redirect URIs
513    pub redirect_uris: Vec<String>,
514    /// Post-logout redirect URIs
515    #[serde(skip_serializing_if = "Option::is_none")]
516    pub post_logout_redirect_uris: Option<Vec<String>>,
517    /// Grant types
518    pub grant_types: Vec<String>,
519    /// Response types
520    pub response_types: Vec<String>,
521    /// Token endpoint auth method
522    pub token_endpoint_auth_method: String,
523    /// Allowed scopes
524    pub scope: String,
525    /// Client secret expiration time
526    #[serde(skip_serializing_if = "Option::is_none")]
527    pub client_secret_expires_at: Option<i64>,
528}