1
1
use std:: collections:: HashMap ;
2
- use std:: fmt:: Debug ;
3
2
4
3
use crate :: axum:: introspection:: IntrospectionState ;
5
4
use axum:: http:: StatusCode ;
@@ -13,10 +12,9 @@ use axum_extra::headers::authorization::Bearer;
13
12
use axum_extra:: headers:: Authorization ;
14
13
use axum_extra:: TypedHeader ;
15
14
use custom_error:: custom_error;
16
- use openidconnect:: TokenIntrospectionResponse ;
17
15
use serde_json:: json;
18
16
19
- use crate :: oidc:: introspection:: { introspect , IntrospectionError , ZitadelIntrospectionResponse } ;
17
+ use crate :: oidc:: introspection:: { claims :: ZitadelClaims , introspect , IntrospectionError } ;
20
18
21
19
custom_error ! {
22
20
/// Error type for guard related errors.
@@ -54,61 +52,31 @@ impl IntoResponse for IntrospectionGuardError {
54
52
}
55
53
}
56
54
57
- /// Struct for the extracted user. The extracted user will always be valid, when fetched in a
58
- /// request function arguments. If not the api will return with an appropriate error.
55
+ /// Type alias for the extracted user.
56
+ ///
57
+ /// The extracted user will always be valid when fetched in request function arguments.
58
+ /// If not, the API will return with an appropriate error.
59
59
///
60
- /// It can be used as a basis for further customized authorization checks with a custom extractor
61
- /// or an extension trait.
60
+ /// # Example
62
61
///
63
62
/// ```
64
63
/// use axum::http::StatusCode;
65
64
/// use axum::response::IntoResponse;
66
65
/// use zitadel::axum::introspection::IntrospectedUser;
67
66
///
68
- /// enum Role {
69
- /// Admin,
70
- /// Client
71
- /// }
72
- ///
73
67
/// async fn my_handler(user: IntrospectedUser) -> impl IntoResponse {
74
- /// if !user.has_role(Role::Admin, "MY-ORG-ID ") {
68
+ /// if !user.has_role("admin ") {
75
69
/// return StatusCode::FORBIDDEN.into_response();
76
70
/// }
77
- /// "Hello Admin".into_response()
78
- /// }
79
- ///
80
- /// trait MyAuthorizationChecks {
81
- /// fn has_role(&self, role: Role, org_id: &str) -> bool;
82
- /// }
83
- ///
84
- /// impl MyAuthorizationChecks for IntrospectedUser {
85
- /// fn has_role(&self, role: Role, org_id: &str) -> bool {
86
- /// let role = match role {
87
- /// Role::Admin => "Admin",
88
- /// Role::Client => "Client",
89
- /// };
90
- /// self.project_roles.as_ref()
91
- /// .and_then(|roles| roles.get(role))
92
- /// .map(|org_ids| org_ids.contains_key(org_id))
93
- /// .unwrap_or(false)
71
+ ///
72
+ /// if user.has_role_in_project("project123", "editor") {
73
+ /// return "Hello Editor".into_response();
94
74
/// }
75
+ ///
76
+ /// "Hello Admin".into_response()
95
77
/// }
96
78
/// ```
97
- #[ derive( Debug ) ]
98
- pub struct IntrospectedUser {
99
- /// UserID of the introspected user (OIDC Field "sub").
100
- pub user_id : String ,
101
- pub username : Option < String > ,
102
- pub name : Option < String > ,
103
- pub given_name : Option < String > ,
104
- pub family_name : Option < String > ,
105
- pub preferred_username : Option < String > ,
106
- pub email : Option < String > ,
107
- pub email_verified : Option < bool > ,
108
- pub locale : Option < String > ,
109
- pub project_roles : Option < HashMap < String , HashMap < String , String > > > ,
110
- pub metadata : Option < HashMap < String , String > > ,
111
- }
79
+ pub type IntrospectedUser = ZitadelClaims ;
112
80
113
81
impl < S > FromRequestParts < S > for IntrospectedUser
114
82
where
@@ -166,37 +134,17 @@ where
166
134
)
167
135
. await ;
168
136
169
- let user: Result < IntrospectedUser , IntrospectionGuardError > = match res {
170
- Ok ( res) => match res. active ( ) {
171
- true if res. sub ( ) . is_some ( ) => Ok ( res. into ( ) ) ,
172
- false => Err ( IntrospectionGuardError :: Inactive ) ,
173
- _ => Err ( IntrospectionGuardError :: NoUserId ) ,
174
- } ,
175
- Err ( source) => return Err ( IntrospectionGuardError :: Introspection { source } ) ,
176
- } ;
177
-
178
- user
179
- }
180
- }
137
+ let claims = res. map_err ( |source| IntrospectionGuardError :: Introspection { source } ) ?;
181
138
182
- impl From < ZitadelIntrospectionResponse > for IntrospectedUser {
183
- fn from ( response : ZitadelIntrospectionResponse ) -> Self {
184
- Self {
185
- user_id : response. sub ( ) . unwrap ( ) . to_string ( ) ,
186
- username : response. username ( ) . map ( |s| s. to_string ( ) ) ,
187
- name : response. extra_fields ( ) . name . clone ( ) ,
188
- given_name : response. extra_fields ( ) . given_name . clone ( ) ,
189
- family_name : response. extra_fields ( ) . family_name . clone ( ) ,
190
- preferred_username : response. extra_fields ( ) . preferred_username . clone ( ) ,
191
- email : response. extra_fields ( ) . email . clone ( ) ,
192
- email_verified : response. extra_fields ( ) . email_verified ,
193
- locale : response. extra_fields ( ) . locale . clone ( ) ,
194
- project_roles : response. extra_fields ( ) . project_roles . clone ( ) ,
195
- metadata : response. extra_fields ( ) . metadata . clone ( ) ,
139
+ if claims. sub . is_empty ( ) {
140
+ return Err ( IntrospectionGuardError :: NoUserId ) ;
196
141
}
142
+
143
+ Ok ( claims)
197
144
}
198
145
}
199
146
147
+
200
148
#[ cfg( test) ]
201
149
mod tests {
202
150
#![ allow( clippy:: all) ]
@@ -229,7 +177,7 @@ mod tests {
229
177
async fn authed ( user : IntrospectedUser ) -> impl IntoResponse {
230
178
format ! (
231
179
"Hello authorized user: {:?} with id {}" ,
232
- user. username, user. user_id
180
+ user. username, user. sub
233
181
)
234
182
}
235
183
@@ -362,7 +310,6 @@ mod tests {
362
310
use super :: * ;
363
311
use crate :: oidc:: introspection:: cache:: in_memory:: InMemoryIntrospectionCache ;
364
312
use crate :: oidc:: introspection:: cache:: IntrospectionCache ;
365
- use crate :: oidc:: introspection:: ZitadelIntrospectionExtraTokenFields ;
366
313
use chrono:: { TimeDelta , Utc } ;
367
314
use http_body_util:: BodyExt ;
368
315
use std:: ops:: Add ;
@@ -393,12 +340,17 @@ mod tests {
393
340
let cache = Arc :: new ( InMemoryIntrospectionCache :: default ( ) ) ;
394
341
let app = app_witch_cache ( cache. clone ( ) ) . await ;
395
342
396
- let mut res = ZitadelIntrospectionResponse :: new (
397
- true ,
398
- ZitadelIntrospectionExtraTokenFields :: default ( ) ,
399
- ) ;
400
- res. set_sub ( Some ( "cached_sub" . to_string ( ) ) ) ;
401
- res. set_exp ( Some ( Utc :: now ( ) . add ( TimeDelta :: days ( 1 ) ) ) ) ;
343
+ use crate :: oidc:: introspection:: claims:: ZitadelClaims ;
344
+ let res = ZitadelClaims {
345
+ sub : "cached_sub" . to_string ( ) ,
346
+ iss : "https://test.zitadel.cloud" . to_string ( ) ,
347
+ aud : vec ! [ "test" . to_string( ) ] ,
348
+ username : Some ( "cached_user" . to_string ( ) ) ,
349
+ exp : Utc :: now ( ) . add ( TimeDelta :: days ( 1 ) ) . timestamp ( ) ,
350
+ iat : Utc :: now ( ) . timestamp ( ) ,
351
+ active : true ,
352
+ ..Default :: default ( )
353
+ } ;
402
354
cache. set ( PERSONAL_ACCESS_TOKEN , res) . await ;
403
355
404
356
let response = app
@@ -454,7 +406,7 @@ mod tests {
454
406
455
407
let cached_response = cache. get ( PERSONAL_ACCESS_TOKEN ) . await . unwrap ( ) ;
456
408
457
- assert ! ( text. contains( cached_response. sub ( ) . unwrap( ) ) ) ;
409
+ assert ! ( text. contains( & cached_response. username . unwrap( ) ) ) ;
458
410
}
459
411
}
460
412
}
0 commit comments