11import type {
2- ExecutionContext ,
3- KVNamespace ,
4- Response ,
2+ ExecutionContext ,
3+ KVNamespace ,
4+ Response ,
55} from "@cloudflare/workers-types" ;
66import type { Request } from "@cloudflare/workers-types" ;
77import type {
8- AccountMetadata ,
9- ApiKeyMetadata ,
10- CoreServiceConfig ,
8+ AccountMetadata ,
9+ ApiKeyMetadata ,
10+ CoreServiceConfig ,
1111} from "../core/api.js" ;
1212import { authorize } from "../core/authorize/index.js" ;
1313import type { AuthorizationInput } from "../core/authorize/index.js" ;
@@ -20,197 +20,205 @@ export * from "../core/rateLimit/index.js";
2020export * from "../core/usageLimit/index.js" ;
2121
2222export type WorkerServiceConfig = CoreServiceConfig & {
23- kvStore : KVNamespace ;
24- ctx : ExecutionContext ;
25- cacheTtlSeconds ?: number ;
23+ kvStore : KVNamespace ;
24+ ctx : ExecutionContext ;
25+ cacheTtlSeconds ?: number ;
2626} ;
2727
2828const DEFAULT_CACHE_TTL_SECONDS = 60 ;
2929
3030type AuthInput = CoreAuthInput & {
31- req : Request ;
31+ req : Request ;
3232} ;
3333
3434export async function authorizeWorker (
35- authInput : AuthInput ,
36- serviceConfig : WorkerServiceConfig ,
35+ authInput : AuthInput ,
36+ serviceConfig : WorkerServiceConfig ,
3737) : Promise < AuthorizationResult > {
38- let authData : AuthorizationInput ;
39- try {
40- authData = await extractAuthorizationData ( authInput ) ;
41- } catch ( e ) {
42- if ( e instanceof Error && e . message === "KEY_CONFLICT" ) {
43- return {
44- authorized : false ,
45- status : 400 ,
46- errorMessage : "Please pass either a client id or a secret key." ,
47- errorCode : "KEY_CONFLICT" ,
48- } ;
49- }
50- return {
51- authorized : false ,
52- status : 500 ,
53- errorMessage : "Internal Server Error" ,
54- errorCode : "INTERNAL_SERVER_ERROR" ,
55- } ;
56- }
57-
58- return await authorize ( authData , serviceConfig , {
59- get : async ( clientId : string ) => serviceConfig . kvStore . get ( clientId ) ,
60- put : ( clientId : string , apiKeyMeta : ApiKeyMetadata | AccountMetadata ) =>
61- serviceConfig . ctx . waitUntil (
62- serviceConfig . kvStore . put (
63- clientId ,
64- JSON . stringify ( {
65- updatedAt : Date . now ( ) ,
66- apiKeyMeta,
67- } ) ,
68- {
69- expirationTtl :
70- serviceConfig . cacheTtlSeconds &&
71- serviceConfig . cacheTtlSeconds >= DEFAULT_CACHE_TTL_SECONDS
72- ? serviceConfig . cacheTtlSeconds
73- : DEFAULT_CACHE_TTL_SECONDS ,
74- } ,
75- ) ,
76- ) ,
77- cacheTtlSeconds : serviceConfig . cacheTtlSeconds ?? DEFAULT_CACHE_TTL_SECONDS ,
78- } ) ;
38+ let authData : AuthorizationInput ;
39+ try {
40+ authData = await extractAuthorizationData ( authInput ) ;
41+ } catch ( e ) {
42+ if ( e instanceof Error && e . message === "KEY_CONFLICT" ) {
43+ return {
44+ authorized : false ,
45+ status : 400 ,
46+ errorMessage : "Please pass either a client id or a secret key." ,
47+ errorCode : "KEY_CONFLICT" ,
48+ } ;
49+ }
50+ return {
51+ authorized : false ,
52+ status : 500 ,
53+ errorMessage : "Internal Server Error" ,
54+ errorCode : "INTERNAL_SERVER_ERROR" ,
55+ } ;
56+ }
57+
58+ return await authorize ( authData , serviceConfig , {
59+ get : async ( clientId : string ) => serviceConfig . kvStore . get ( clientId ) ,
60+ put : ( clientId : string , apiKeyMeta : ApiKeyMetadata | AccountMetadata ) =>
61+ serviceConfig . ctx . waitUntil (
62+ serviceConfig . kvStore . put (
63+ clientId ,
64+ JSON . stringify ( {
65+ updatedAt : Date . now ( ) ,
66+ apiKeyMeta,
67+ } ) ,
68+ {
69+ expirationTtl :
70+ serviceConfig . cacheTtlSeconds &&
71+ serviceConfig . cacheTtlSeconds >= DEFAULT_CACHE_TTL_SECONDS
72+ ? serviceConfig . cacheTtlSeconds
73+ : DEFAULT_CACHE_TTL_SECONDS ,
74+ } ,
75+ ) ,
76+ ) ,
77+ cacheTtlSeconds : serviceConfig . cacheTtlSeconds ?? DEFAULT_CACHE_TTL_SECONDS ,
78+ } ) ;
7979}
8080
8181export async function extractAuthorizationData (
82- authInput : AuthInput ,
82+ authInput : AuthInput ,
8383) : Promise < AuthorizationInput > {
84- const requestUrl = new URL ( authInput . req . url ) ;
85- const headers = authInput . req . headers ;
86- const secretKey = headers . get ( "x-secret-key" ) ;
87-
88- // prefer clientId that is explicitly passed in
89- let clientId = authInput . clientId ?? null ;
90-
91- if ( ! clientId ) {
92- // next preference is clientId from header
93- clientId = headers . get ( "x-client-id" ) ;
94- }
95-
96- // next preference is search param
97- if ( ! clientId ) {
98- clientId = requestUrl . searchParams . get ( "clientId" ) ;
99- }
100- // bundle id from header is first preference
101- let bundleId = headers . get ( "x-bundle-id" ) ;
102-
103- // next preference is search param
104- if ( ! bundleId ) {
105- bundleId = requestUrl . searchParams . get ( "bundleId" ) ;
106- }
107-
108- let origin = headers . get ( "origin" ) ;
109- // if origin header is not available we'll fall back to referrer;
110- if ( ! origin ) {
111- origin = headers . get ( "referer" ) ;
112- }
113- // if we have an origin at this point, normalize it
114- if ( origin ) {
115- try {
116- origin = new URL ( origin ) . host ;
117- } catch ( e ) {
118- console . warn ( "failed to parse origin" , origin , e ) ;
119- }
120- }
121-
122- // handle if we a secret key is passed in the headers
123- let secretKeyHash : string | null = null ;
124- if ( secretKey ) {
125- // hash the secret key
126- secretKeyHash = await hashSecretKey ( secretKey ) ;
127- // derive the client id from the secret key hash
128- const derivedClientId = deriveClientIdFromSecretKeyHash ( secretKeyHash ) ;
129- // if we already have a client id passed in we need to make sure they match
130- if ( clientId && clientId !== derivedClientId ) {
131- throw new Error ( "KEY_CONFLICT" ) ;
132- }
133- // otherwise set the client id to the derived client id (client id based off of secret key)
134- clientId = derivedClientId ;
135- }
136-
137- let jwt : string | null = null ;
138- if ( headers . has ( "authorization" ) ) {
139- const authHeader = headers . get ( "authorization" ) ;
140- if ( authHeader ) {
141- const [ type , token ] = authHeader . split ( " " ) ;
142- if ( type ?. toLowerCase ( ) === "bearer" && ! ! token ) {
143- jwt = token ;
144- }
145- }
146- }
147-
148- return {
149- jwt,
150- hashedJWT : jwt ? await hashSecretKey ( jwt ) : null ,
151- secretKey,
152- clientId,
153- origin,
154- bundleId,
155- secretKeyHash,
156- targetAddress : authInput . targetAddress ,
157- } ;
84+ const requestUrl = new URL ( authInput . req . url ) ;
85+ const headers = authInput . req . headers ;
86+ const secretKey = headers . get ( "x-secret-key" ) ;
87+
88+ // prefer clientId that is explicitly passed in
89+ let clientId = authInput . clientId ?? null ;
90+
91+ if ( ! clientId ) {
92+ // next preference is clientId from header
93+ clientId = headers . get ( "x-client-id" ) ;
94+ }
95+
96+ // next preference is search param
97+ if ( ! clientId ) {
98+ clientId = requestUrl . searchParams . get ( "clientId" ) ;
99+ }
100+ // bundle id from header is first preference
101+ let bundleId = headers . get ( "x-bundle-id" ) ;
102+
103+ // next preference is search param
104+ if ( ! bundleId ) {
105+ bundleId = requestUrl . searchParams . get ( "bundleId" ) ;
106+ }
107+
108+ let ecosystemId =
109+ headers . get ( "x-ecosystem-id" ) ?? requestUrl . searchParams . get ( "ecosystemId" ) ;
110+ let ecosystemPartnerId =
111+ headers . get ( "x-ecosystem-partner-id" ) ??
112+ requestUrl . searchParams . get ( "ecosystemPartnerId" ) ;
113+
114+ let origin = headers . get ( "origin" ) ;
115+ // if origin header is not available we'll fall back to referrer;
116+ if ( ! origin ) {
117+ origin = headers . get ( "referer" ) ;
118+ }
119+ // if we have an origin at this point, normalize it
120+ if ( origin ) {
121+ try {
122+ origin = new URL ( origin ) . host ;
123+ } catch ( e ) {
124+ console . warn ( "failed to parse origin" , origin , e ) ;
125+ }
126+ }
127+
128+ // handle if we a secret key is passed in the headers
129+ let secretKeyHash : string | null = null ;
130+ if ( secretKey ) {
131+ // hash the secret key
132+ secretKeyHash = await hashSecretKey ( secretKey ) ;
133+ // derive the client id from the secret key hash
134+ const derivedClientId = deriveClientIdFromSecretKeyHash ( secretKeyHash ) ;
135+ // if we already have a client id passed in we need to make sure they match
136+ if ( clientId && clientId !== derivedClientId ) {
137+ throw new Error ( "KEY_CONFLICT" ) ;
138+ }
139+ // otherwise set the client id to the derived client id (client id based off of secret key)
140+ clientId = derivedClientId ;
141+ }
142+
143+ let jwt : string | null = null ;
144+ if ( headers . has ( "authorization" ) ) {
145+ const authHeader = headers . get ( "authorization" ) ;
146+ if ( authHeader ) {
147+ const [ type , token ] = authHeader . split ( " " ) ;
148+ if ( type ?. toLowerCase ( ) === "bearer" && ! ! token ) {
149+ jwt = token ;
150+ }
151+ }
152+ }
153+
154+ return {
155+ jwt,
156+ hashedJWT : jwt ? await hashSecretKey ( jwt ) : null ,
157+ secretKey,
158+ clientId,
159+ ecosystemId,
160+ ecosystemPartnerId,
161+ origin,
162+ bundleId,
163+ secretKeyHash,
164+ targetAddress : authInput . targetAddress ,
165+ } ;
158166}
159167
160168export async function hashSecretKey ( secretKey : string ) {
161- return bufferToHex (
162- await crypto . subtle . digest ( "SHA-256" , new TextEncoder ( ) . encode ( secretKey ) ) ,
163- ) ;
169+ return bufferToHex (
170+ await crypto . subtle . digest ( "SHA-256" , new TextEncoder ( ) . encode ( secretKey ) ) ,
171+ ) ;
164172}
165173
166174export function deriveClientIdFromSecretKeyHash ( secretKeyHash : string ) {
167- return secretKeyHash . slice ( 0 , 32 ) ;
175+ return secretKeyHash . slice ( 0 , 32 ) ;
168176}
169177
170178function bufferToHex ( buffer : ArrayBuffer ) {
171- return [ ...new Uint8Array ( buffer ) ]
172- . map ( ( x ) => x . toString ( 16 ) . padStart ( 2 , "0" ) )
173- . join ( "" ) ;
179+ return [ ...new Uint8Array ( buffer ) ]
180+ . map ( ( x ) => x . toString ( 16 ) . padStart ( 2 , "0" ) )
181+ . join ( "" ) ;
174182}
175183
176184export async function logHttpRequest ( {
177- clientId,
178- req,
179- res,
180- isAuthed,
181- statusMessage,
182- latencyMs,
185+ clientId,
186+ req,
187+ res,
188+ isAuthed,
189+ statusMessage,
190+ latencyMs,
183191} : AuthInput & {
184- // @deprecated
185- source : string ;
186- res : Response ;
187- isAuthed ?: boolean ;
188- statusMessage ?: Error | string ;
189- latencyMs ?: number ;
192+ // @deprecated
193+ source : string ;
194+ res : Response ;
195+ isAuthed ?: boolean ;
196+ statusMessage ?: Error | string ;
197+ latencyMs ?: number ;
190198} ) {
191- try {
192- const authorizationData = await extractAuthorizationData ( { req, clientId } ) ;
193- const headers = req . headers ;
194-
195- console . log (
196- JSON . stringify ( {
197- method : req . method ,
198- pathname : req . url ,
199- hasSecretKey : ! ! authorizationData . secretKey ,
200- hasClientId : ! ! authorizationData . clientId ,
201- hasJwt : ! ! authorizationData . jwt ,
202- clientId : authorizationData . clientId ,
203- isAuthed,
204- status : res . status ,
205- sdkName : headers . get ( "x-sdk-name" ) ?? undefined ,
206- sdkVersion : headers . get ( "x-sdk-version" ) ?? undefined ,
207- platform : headers . get ( "x-sdk-platform" ) ?? undefined ,
208- os : headers . get ( "x-sdk-os" ) ?? undefined ,
209- latencyMs,
210- } ) ,
211- ) ;
212- if ( statusMessage ) {
213- console . log ( `statusMessage=${ statusMessage } ` ) ;
214- }
215- } catch { }
199+ try {
200+ const authorizationData = await extractAuthorizationData ( { req, clientId } ) ;
201+ const headers = req . headers ;
202+
203+ console . log (
204+ JSON . stringify ( {
205+ method : req . method ,
206+ pathname : req . url ,
207+ hasSecretKey : ! ! authorizationData . secretKey ,
208+ hasClientId : ! ! authorizationData . clientId ,
209+ hasJwt : ! ! authorizationData . jwt ,
210+ clientId : authorizationData . clientId ,
211+ isAuthed,
212+ status : res . status ,
213+ sdkName : headers . get ( "x-sdk-name" ) ?? undefined ,
214+ sdkVersion : headers . get ( "x-sdk-version" ) ?? undefined ,
215+ platform : headers . get ( "x-sdk-platform" ) ?? undefined ,
216+ os : headers . get ( "x-sdk-os" ) ?? undefined ,
217+ latencyMs,
218+ } ) ,
219+ ) ;
220+ if ( statusMessage ) {
221+ console . log ( `statusMessage=${ statusMessage } ` ) ;
222+ }
223+ } catch { }
216224}
0 commit comments