11use std:: sync:: Arc ;
22
3- use crate :: error:: AuthCalloutError ;
3+ use crate :: error:: { AuthCalloutError , CredentialError } ;
44use crate :: jwt:: {
55 AudienceAccount , ExternalSubject , UserJwtClaims , derive_caller_id, spicedb_principal_from_oidc_claims,
66} ;
@@ -19,7 +19,7 @@ impl OidcIssuerUrl {
1919 }
2020 if s. is_empty ( ) {
2121 return Err ( AuthCalloutError :: CredentialVerification (
22- "OIDC issuer URL is empty" . into ( ) ,
22+ CredentialError :: InvalidCredentials ( "OIDC issuer URL is empty" . into ( ) ) ,
2323 ) ) ;
2424 }
2525 Ok ( Self ( s) )
@@ -38,7 +38,7 @@ impl OidcClientId {
3838 let s = id. into ( ) ;
3939 if s. trim ( ) . is_empty ( ) {
4040 return Err ( AuthCalloutError :: CredentialVerification (
41- "OIDC client ID must not be empty" . into ( ) ,
41+ CredentialError :: InvalidCredentials ( "OIDC client ID must not be empty" . into ( ) ) ,
4242 ) ) ;
4343 }
4444 Ok ( Self ( s) )
@@ -100,43 +100,45 @@ impl JwksOidcVerifier {
100100 . connect_timeout ( std:: time:: Duration :: from_secs ( 5 ) )
101101 . redirect ( reqwest:: redirect:: Policy :: none ( ) )
102102 . build ( )
103- . map_err ( |e| AuthCalloutError :: CredentialVerification ( e. to_string ( ) ) ) ?;
103+ . map_err ( |e| CredentialError :: InvalidCredentials ( e. to_string ( ) ) ) ?;
104104 let uri = format ! ( "{}/.well-known/openid-configuration" , issuer. as_str( ) ) ;
105105 let resp = http
106106 . get ( uri)
107107 . send ( )
108108 . await
109- . map_err ( |e| AuthCalloutError :: CredentialVerification ( e. to_string ( ) ) ) ?;
109+ . map_err ( |e| CredentialError :: InvalidCredentials ( e. to_string ( ) ) ) ?;
110110 let doc: serde_json:: Value = resp
111111 . json ( )
112112 . await
113- . map_err ( |e| AuthCalloutError :: CredentialVerification ( e. to_string ( ) ) ) ?;
113+ . map_err ( |e| CredentialError :: InvalidCredentials ( e. to_string ( ) ) ) ?;
114114 // Refuse a discovery doc whose `issuer` claim doesn't match the one
115115 // we asked for — the doc is fetched off the network and a MITM /
116116 // misconfigured DNS could redirect us at an attacker's IdP.
117117 let doc_issuer = doc
118118 . get ( "issuer" )
119119 . and_then ( |v| v. as_str ( ) )
120- . ok_or_else ( || AuthCalloutError :: CredentialVerification ( "missing issuer in OIDC discovery" . into ( ) ) ) ?;
120+ . ok_or_else ( || CredentialError :: InvalidCredentials ( "missing issuer in OIDC discovery" . into ( ) ) ) ?;
121121 if doc_issuer. trim_end_matches ( '/' ) != issuer. as_str ( ) . trim_end_matches ( '/' ) {
122- return Err ( AuthCalloutError :: CredentialVerification ( format ! (
122+ return Err ( CredentialError :: InvalidCredentials ( format ! (
123123 "OIDC discovery issuer mismatch: configured={:?} discovered={:?}" ,
124124 issuer. as_str( ) ,
125125 doc_issuer
126- ) ) ) ;
126+ ) )
127+ . into ( ) ) ;
127128 }
128129 let jwks_uri = doc
129130 . get ( "jwks_uri" )
130131 . and_then ( |v| v. as_str ( ) )
131- . ok_or_else ( || AuthCalloutError :: CredentialVerification ( "missing jwks_uri in OIDC discovery" . into ( ) ) ) ?;
132+ . ok_or_else ( || CredentialError :: InvalidCredentials ( "missing jwks_uri in OIDC discovery" . into ( ) ) ) ?;
132133 // Constrain jwks_uri to the issuer's origin so a tampered discovery
133134 // doc can't point us at attacker-controlled JWKS while tokens still
134135 // claim the expected `iss`.
135136 if !same_origin ( jwks_uri, issuer. as_str ( ) ) {
136- return Err ( AuthCalloutError :: CredentialVerification ( format ! (
137+ return Err ( CredentialError :: InvalidCredentials ( format ! (
137138 "OIDC jwks_uri {jwks_uri:?} is outside issuer origin {:?}" ,
138139 issuer. as_str( )
139- ) ) ) ;
140+ ) )
141+ . into ( ) ) ;
140142 }
141143 Ok ( Self {
142144 issuer,
@@ -195,21 +197,24 @@ impl JwksOidcVerifier {
195197 . get ( jwks_uri)
196198 . send ( )
197199 . await
198- . map_err ( |e| AuthCalloutError :: CredentialVerification ( e. to_string ( ) ) ) ?;
200+ . map_err ( |e| CredentialError :: InvalidCredentials ( e. to_string ( ) ) ) ?;
199201 resp. json :: < JwkSet > ( )
200202 . await
201- . map_err ( |e| AuthCalloutError :: CredentialVerification ( e. to_string ( ) ) )
203+ . map_err ( |e| AuthCalloutError :: from ( CredentialError :: InvalidCredentials ( e. to_string ( ) ) ) )
202204 }
203205 JwksSource :: Static ( j) => Ok ( ( * * j) . clone ( ) ) ,
204206 }
205207 }
206208
207209 fn decoding_key_for_jwk ( jwk : & jsonwebtoken:: jwk:: Jwk ) -> Result < DecodingKey , AuthCalloutError > {
208210 match & jwk. algorithm {
209- AlgorithmParameters :: RSA ( rsa) => DecodingKey :: from_rsa_components ( & rsa. n , & rsa. e )
210- . map_err ( |e| AuthCalloutError :: CredentialVerification ( format ! ( "invalid RSA JWK components: {e}" ) ) ) ,
211+ AlgorithmParameters :: RSA ( rsa) => DecodingKey :: from_rsa_components ( & rsa. n , & rsa. e ) . map_err ( |e| {
212+ AuthCalloutError :: from ( CredentialError :: InvalidCredentials ( format ! (
213+ "invalid RSA JWK components: {e}"
214+ ) ) )
215+ } ) ,
211216 _ => Err ( AuthCalloutError :: CredentialVerification (
212- "OIDC JWK must be RSA for this verifier" . into ( ) ,
217+ CredentialError :: InvalidCredentials ( "OIDC JWK must be RSA for this verifier" . into ( ) ) ,
213218 ) ) ,
214219 }
215220 }
@@ -221,37 +226,36 @@ impl JwksOidcVerifier {
221226 ) -> Result < UserJwtClaims , AuthCalloutError > {
222227 if self . expected_id_token_audiences . is_empty ( ) {
223228 return Err ( AuthCalloutError :: CredentialVerification (
224- "no expected OIDC token audiences configured" . into ( ) ,
229+ CredentialError :: InvalidCredentials ( "no expected OIDC token audiences configured" . into ( ) ) ,
225230 ) ) ;
226231 }
227232 let jwks = self . fetch_jwks ( ) . await ?;
228233 let header = decode_header ( token. as_str ( ) )
229- . map_err ( |e| AuthCalloutError :: CredentialVerification ( format ! ( "invalid JWT header: {e}" ) ) ) ?;
234+ . map_err ( |e| CredentialError :: InvalidCredentials ( format ! ( "invalid JWT header: {e}" ) ) ) ?;
230235 let kid = header
231236 . kid
232237 . as_ref ( )
233- . ok_or_else ( || AuthCalloutError :: CredentialVerification ( "JWT header missing kid" . into ( ) ) ) ?;
238+ . ok_or_else ( || CredentialError :: InvalidCredentials ( "JWT header missing kid" . into ( ) ) ) ?;
234239 let jwk = jwks
235240 . find ( kid)
236- . ok_or_else ( || AuthCalloutError :: CredentialVerification ( format ! ( "no JWK for kid {kid}" ) ) ) ?;
241+ . ok_or_else ( || CredentialError :: InvalidCredentials ( format ! ( "no JWK for kid {kid}" ) ) ) ?;
237242 let auds: Vec < & str > = self . expected_id_token_audiences . iter ( ) . map ( String :: as_str) . collect ( ) ;
238243 let mut validation = Validation :: new ( header. alg ) ;
239244 validation. set_issuer ( & [ self . issuer . as_str ( ) ] ) ;
240245 validation. set_audience ( & auds) ;
241246 let decoding_key = Self :: decoding_key_for_jwk ( jwk) ?;
242247 let token_data = decode :: < serde_json:: Value > ( token. as_str ( ) , & decoding_key, & validation)
243- . map_err ( |e| AuthCalloutError :: CredentialVerification ( format ! ( "OIDC token validation failed: {e}" ) ) ) ?;
248+ . map_err ( |e| CredentialError :: InvalidCredentials ( format ! ( "OIDC token validation failed: {e}" ) ) ) ?;
244249 let sub_str = token_data
245250 . claims
246251 . get ( "sub" )
247252 . and_then ( |v| v. as_str ( ) )
248- . ok_or_else ( || AuthCalloutError :: CredentialVerification ( "OIDC token missing sub" . into ( ) ) ) ?;
249- let sub = ExternalSubject :: new ( sub_str) . map_err ( |e| {
250- AuthCalloutError :: CredentialVerification ( format ! ( "invalid external subject in OIDC token: {e}" ) )
251- } ) ?;
253+ . ok_or_else ( || CredentialError :: InvalidCredentials ( "OIDC token missing sub" . into ( ) ) ) ?;
254+ let sub = ExternalSubject :: new ( sub_str)
255+ . map_err ( |e| CredentialError :: InvalidCredentials ( format ! ( "invalid external subject in OIDC token: {e}" ) ) ) ?;
252256 let data = spicedb_principal_from_oidc_claims ( & token_data. claims ) ;
253257 let caller_id = derive_caller_id ( sub_str, account)
254- . map_err ( |e| AuthCalloutError :: CredentialVerification ( format ! ( "caller_id derivation failed: {e}" ) ) ) ?;
258+ . map_err ( |e| CredentialError :: InvalidCredentials ( format ! ( "caller_id derivation failed: {e}" ) ) ) ?;
255259 let nats_permissions = IssuedPermissions :: default_for_caller ( & caller_id) ;
256260 Ok ( UserJwtClaims {
257261 kid : crate :: signing_key_source:: unminted_placeholder ( ) ,
@@ -348,7 +352,10 @@ mod tests {
348352 let err = rt
349353 . block_on ( v. verify_internal ( & BearerToken :: new ( "x.y.z" ) , & AudienceAccount :: new ( "acct" ) ) )
350354 . unwrap_err ( ) ;
351- assert ! ( matches!( err, AuthCalloutError :: CredentialVerification ( _) ) ) ;
355+ assert ! ( matches!(
356+ err,
357+ AuthCalloutError :: CredentialVerification ( CredentialError :: InvalidCredentials ( _) )
358+ ) ) ;
352359 }
353360
354361 #[ tokio:: test]
@@ -447,7 +454,10 @@ mod tests {
447454 . verify_internal ( & BearerToken :: new ( bad) , & AudienceAccount :: new ( "acct" ) )
448455 . await
449456 . unwrap_err ( ) ;
450- assert ! ( matches!( err, AuthCalloutError :: CredentialVerification ( _) ) ) ;
457+ assert ! ( matches!(
458+ err,
459+ AuthCalloutError :: CredentialVerification ( CredentialError :: InvalidCredentials ( _) )
460+ ) ) ;
451461 }
452462
453463 #[ tokio:: test]
0 commit comments