@@ -168,7 +168,7 @@ where
168168 let mut header_error = None ;
169169 let user_and_tenant_id = get_user_and_tenant ( & self . action , & mut req, & mut header_error) ;
170170
171- // If an X-API-KEY header is present , resolve it to the backing
171+ // If an API key is present in X-API-KEY or as a Bearer token , resolve it to the backing
172172 // `UserType::ApiKey` user and register an ephemeral session whose
173173 // permissions are derived from the user's assigned roles (same flow
174174 // as native/OAuth users). The request then falls through to the
@@ -188,12 +188,11 @@ where
188188 permissions,
189189 & user. tenant ,
190190 ) ;
191- if PARSEABLE . options . is_multi_tenant ( ) {
192- req. headers_mut ( ) . insert (
193- HeaderName :: from_static ( TENANT_ID ) ,
194- HeaderValue :: from_str ( tenant) . unwrap ( ) ,
195- ) ;
196- }
191+ insert_api_key_tenant_header (
192+ & mut req,
193+ & user,
194+ PARSEABLE . options . is_multi_tenant ( ) ,
195+ ) ;
197196 req. extensions_mut ( ) . insert ( session_key) ;
198197 Some ( session_id)
199198 }
@@ -326,12 +325,44 @@ fn extract_kinesis_headers(req: &mut ServiceRequest) {
326325 }
327326}
328327
329- /// Extract X-API-KEY header value if present (independent of action).
328+ /// Extract an API key from X-API-KEY or an Authorization Bearer token.
329+ /// X-API-KEY takes precedence when both headers are present.
330330fn extract_api_key ( req : & ServiceRequest ) -> Option < String > {
331- req. headers ( )
331+ if let Some ( api_key) = req
332+ . headers ( )
332333 . get ( "x-api-key" )
333334 . and_then ( |v| v. to_str ( ) . ok ( ) )
334335 . map ( String :: from)
336+ {
337+ return Some ( api_key) ;
338+ }
339+
340+ req. headers ( )
341+ . get ( header:: AUTHORIZATION )
342+ . and_then ( |value| value. to_str ( ) . ok ( ) )
343+ . and_then ( |value| value. split_once ( ' ' ) )
344+ . and_then ( |( scheme, api_key) | {
345+ scheme
346+ . eq_ignore_ascii_case ( "Bearer" )
347+ . then_some ( api_key. trim_start_matches ( ' ' ) )
348+ } )
349+ . filter ( |api_key| !api_key. is_empty ( ) )
350+ . map ( String :: from)
351+ }
352+
353+ /// Add the tenant resolved from an API-key user to a multi-tenant request.
354+ fn insert_api_key_tenant_header (
355+ req : & mut ServiceRequest ,
356+ user : & user:: User ,
357+ is_multi_tenant : bool ,
358+ ) {
359+ if is_multi_tenant {
360+ let tenant = user. tenant . as_deref ( ) . unwrap_or ( DEFAULT_TENANT ) ;
361+ req. headers_mut ( ) . insert (
362+ HeaderName :: from_static ( TENANT_ID ) ,
363+ HeaderValue :: from_str ( tenant) . unwrap ( ) ,
364+ ) ;
365+ }
335366}
336367
337368/// Resolve an incoming API key value to its backing `UserType::ApiKey` user.
@@ -856,3 +887,96 @@ where
856887 }
857888 }
858889}
890+
891+ #[ cfg( test) ]
892+ mod api_key_header_tests {
893+ use std:: collections:: HashSet ;
894+
895+ use actix_web:: test as actix_test;
896+
897+ use super :: * ;
898+
899+ #[ test]
900+ fn extracts_x_api_key ( ) {
901+ let req = actix_test:: TestRequest :: default ( )
902+ . insert_header ( ( "x-api-key" , "x-api-key-value" ) )
903+ . to_srv_request ( ) ;
904+
905+ assert_eq ! ( extract_api_key( & req) . as_deref( ) , Some ( "x-api-key-value" ) ) ;
906+ }
907+
908+ #[ test]
909+ fn extracts_bearer_api_key ( ) {
910+ let req = actix_test:: TestRequest :: default ( )
911+ . insert_header ( ( header:: AUTHORIZATION , "Bearer bearer-api-key-value" ) )
912+ . to_srv_request ( ) ;
913+
914+ assert_eq ! (
915+ extract_api_key( & req) . as_deref( ) ,
916+ Some ( "bearer-api-key-value" )
917+ ) ;
918+ }
919+
920+ #[ test]
921+ fn bearer_scheme_is_case_insensitive ( ) {
922+ let req = actix_test:: TestRequest :: default ( )
923+ . insert_header ( ( header:: AUTHORIZATION , "bearer api-key-value" ) )
924+ . to_srv_request ( ) ;
925+
926+ assert_eq ! ( extract_api_key( & req) . as_deref( ) , Some ( "api-key-value" ) ) ;
927+ }
928+
929+ #[ test]
930+ fn bearer_allows_repeated_spaces_before_api_key ( ) {
931+ let req = actix_test:: TestRequest :: default ( )
932+ . insert_header ( ( header:: AUTHORIZATION , "Bearer api-key-value" ) )
933+ . to_srv_request ( ) ;
934+
935+ assert_eq ! ( extract_api_key( & req) . as_deref( ) , Some ( "api-key-value" ) ) ;
936+ }
937+
938+ #[ test]
939+ fn bearer_requires_api_key ( ) {
940+ let req = actix_test:: TestRequest :: default ( )
941+ . insert_header ( ( header:: AUTHORIZATION , "Bearer " ) )
942+ . to_srv_request ( ) ;
943+
944+ assert_eq ! ( extract_api_key( & req) , None ) ;
945+ }
946+
947+ #[ test]
948+ fn ignores_non_bearer_authorization ( ) {
949+ let req = actix_test:: TestRequest :: default ( )
950+ . insert_header ( ( header:: AUTHORIZATION , "Basic dXNlcjpwYXNzd29yZA==" ) )
951+ . to_srv_request ( ) ;
952+
953+ assert_eq ! ( extract_api_key( & req) , None ) ;
954+ }
955+
956+ #[ test]
957+ fn x_api_key_takes_precedence_over_bearer ( ) {
958+ let req = actix_test:: TestRequest :: default ( )
959+ . insert_header ( ( "x-api-key" , "x-api-key-value" ) )
960+ . insert_header ( ( header:: AUTHORIZATION , "Bearer bearer-api-key-value" ) )
961+ . to_srv_request ( ) ;
962+
963+ assert_eq ! ( extract_api_key( & req) . as_deref( ) , Some ( "x-api-key-value" ) ) ;
964+ }
965+
966+ #[ test]
967+ fn resolved_api_key_user_sets_multi_tenant_header ( ) {
968+ let user = user:: User :: new_api_key (
969+ Ulid :: new ( ) ,
970+ "valid-api-key" . to_owned ( ) ,
971+ "test-key" . to_owned ( ) ,
972+ HashSet :: new ( ) ,
973+ "admin" . to_owned ( ) ,
974+ Some ( "acme" . to_owned ( ) ) ,
975+ ) ;
976+ let mut req = actix_test:: TestRequest :: default ( ) . to_srv_request ( ) ;
977+
978+ insert_api_key_tenant_header ( & mut req, & user, true ) ;
979+
980+ assert_eq ! ( req. headers( ) . get( TENANT_ID ) . unwrap( ) , "acme" ) ;
981+ }
982+ }
0 commit comments