Skip to content

Commit 9a57612

Browse files
authored
Merge branch 'main' into feat/rfc8252-loopback-port-flexibility
2 parents 068b10e + 734738c commit 9a57612

File tree

3 files changed

+53
-42
lines changed

3 files changed

+53
-42
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cloudflare/workers-oauth-provider': patch
3+
---
4+
5+
Fix TypeScript types by making OAuthProviderOptions generic over Env, eliminating the need for @ts-expect-error workarounds when using typed environments

__tests__/mocks/cloudflare-workers.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
* Provides a minimal implementation of WorkerEntrypoint for testing
44
*/
55

6-
export class WorkerEntrypoint {
6+
export class WorkerEntrypoint<Env = any> {
77
ctx: any;
8-
env: any;
8+
env: Env;
99

10-
constructor(ctx: any, env: any) {
10+
constructor(ctx: any, env: Env) {
1111
this.ctx = ctx;
1212
this.env = env;
1313
}

src/oauth-provider.ts

Lines changed: 45 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,31 @@ export enum GrantType {
3030
TOKEN_EXCHANGE = 'urn:ietf:params:oauth:grant-type:token-exchange',
3131
}
3232

33+
/** ExecutionContext with writable props — ctx.props is read-only in types but writable at runtime */
34+
type MutableExecutionContext = Omit<ExecutionContext, 'props'> & { props: any };
35+
36+
/**
37+
* Aliases for either type of Handler that makes .fetch required
38+
*/
39+
type ExportedHandlerWithFetch<Env = Cloudflare.Env> = ExportedHandler<Env> &
40+
Pick<Required<ExportedHandler<Env>>, 'fetch'>;
41+
type WorkerEntrypointWithFetch<Env = Cloudflare.Env> = WorkerEntrypoint<Env> & {
42+
fetch: NonNullable<WorkerEntrypoint['fetch']>;
43+
};
44+
3345
/**
3446
* Discriminated union type for handlers
3547
*/
36-
type TypedHandler =
48+
type TypedHandler<Env = Cloudflare.Env> =
3749
| {
3850
type: HandlerType.EXPORTED_HANDLER;
39-
handler: ExportedHandlerWithFetch;
51+
handler: ExportedHandlerWithFetch<Env>;
4052
}
4153
| {
4254
type: HandlerType.WORKER_ENTRYPOINT;
43-
handler: new (ctx: ExecutionContext, env: any) => WorkerEntrypointWithFetch;
55+
handler: new (ctx: ExecutionContext, env: Env) => WorkerEntrypointWithFetch<Env>;
4456
};
4557

46-
/**
47-
* Aliases for either type of Handler that makes .fetch required
48-
*/
49-
type ExportedHandlerWithFetch = ExportedHandler & Pick<Required<ExportedHandler>, 'fetch'>;
50-
type WorkerEntrypointWithFetch = WorkerEntrypoint & Pick<Required<WorkerEntrypoint>, 'fetch'>;
51-
5258
/**
5359
* Configuration options for the OAuth Provider
5460
*/
@@ -169,7 +175,7 @@ export interface ResolveExternalTokenResult {
169175
audience?: string | string[];
170176
}
171177

172-
export interface OAuthProviderOptions {
178+
export interface OAuthProviderOptions<Env = Cloudflare.Env> {
173179
/**
174180
* URL(s) for API routes. Requests with URLs starting with any of these prefixes
175181
* will be treated as API requests and require a valid access token.
@@ -188,7 +194,9 @@ export interface OAuthProviderOptions {
188194
* Used with `apiRoute` for the single-handler configuration. This is incompatible with
189195
* the `apiHandlers` property. You must use either `apiRoute` + `apiHandler` OR `apiHandlers`, not both.
190196
*/
191-
apiHandler?: ExportedHandlerWithFetch | (new (ctx: ExecutionContext, env: any) => WorkerEntrypointWithFetch);
197+
apiHandler?:
198+
| ExportedHandlerWithFetch<Env>
199+
| (new (ctx: ExecutionContext, env: Env) => WorkerEntrypointWithFetch<Env>);
192200

193201
/**
194202
* Map of API routes to their corresponding handlers for the multi-handler configuration.
@@ -202,14 +210,14 @@ export interface OAuthProviderOptions {
202210
*/
203211
apiHandlers?: Record<
204212
string,
205-
ExportedHandlerWithFetch | (new (ctx: ExecutionContext, env: any) => WorkerEntrypointWithFetch)
213+
ExportedHandlerWithFetch<Env> | (new (ctx: ExecutionContext, env: Env) => WorkerEntrypointWithFetch<Env>)
206214
>;
207215

208216
/**
209217
* Handler for all non-API requests or API requests without a valid token.
210218
* Can be either an ExportedHandler object with a fetch method or a class extending WorkerEntrypoint.
211219
*/
212-
defaultHandler: ExportedHandler | (new (ctx: ExecutionContext, env: any) => WorkerEntrypointWithFetch);
220+
defaultHandler: ExportedHandler<Env> | (new (ctx: ExecutionContext, env: Env) => WorkerEntrypointWithFetch<Env>);
213221

214222
/**
215223
* URL of the OAuth authorization endpoint where users can grant permissions.
@@ -957,15 +965,15 @@ interface CreateAccessTokenOptions {
957965
* Implements authorization code flow with support for refresh tokens
958966
* and dynamic client registration.
959967
*/
960-
export class OAuthProvider {
961-
#impl: OAuthProviderImpl;
968+
export class OAuthProvider<Env = Cloudflare.Env> {
969+
#impl: OAuthProviderImpl<Env>;
962970

963971
/**
964972
* Creates a new OAuth provider instance
965973
* @param options - Configuration options for the provider
966974
*/
967-
constructor(options: OAuthProviderOptions) {
968-
this.#impl = new OAuthProviderImpl(options);
975+
constructor(options: OAuthProviderOptions<Env>) {
976+
this.#impl = new OAuthProviderImpl<Env>(options);
969977
}
970978

971979
/**
@@ -976,7 +984,7 @@ export class OAuthProvider {
976984
* @param ctx - Cloudflare Worker execution context
977985
* @returns A Promise resolving to an HTTP Response
978986
*/
979-
fetch(request: Request, env: any, ctx: ExecutionContext): Promise<Response> {
987+
fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
980988
return this.#impl.fetch(request, env, ctx);
981989
}
982990
}
@@ -987,8 +995,8 @@ export class OAuthProvider {
987995
* @param env - Cloudflare Worker environment variables
988996
* @returns An instance of OAuthHelpers
989997
*/
990-
export function getOAuthApi(options: OAuthProviderOptions, env: any): OAuthHelpers {
991-
const impl = new OAuthProviderImpl(options);
998+
export function getOAuthApi<Env = Cloudflare.Env>(options: OAuthProviderOptions<Env>, env: Env): OAuthHelpers {
999+
const impl = new OAuthProviderImpl<Env>(options);
9921000
return impl.createOAuthHelpers(env);
9931001
}
9941002

@@ -1000,29 +1008,29 @@ export function getOAuthApi(options: OAuthProviderOptions, env: any): OAuthHelpe
10001008
* annotation, and does not actually prevent the method from being called from outside the class,
10011009
* including over RPC.
10021010
*/
1003-
class OAuthProviderImpl {
1011+
class OAuthProviderImpl<Env = Cloudflare.Env> {
10041012
/**
10051013
* Configuration options for the provider
10061014
*/
1007-
options: OAuthProviderOptions;
1015+
options: OAuthProviderOptions<Env>;
10081016

10091017
/**
10101018
* Represents the validated type of a handler (ExportedHandler or WorkerEntrypoint)
10111019
*/
1012-
private typedDefaultHandler: TypedHandler;
1020+
private typedDefaultHandler: TypedHandler<Env>;
10131021

10141022
/**
10151023
* Array of tuples of API routes and their validated handlers
10161024
* In the simple case, this will be a single entry with the route and handler from options.apiRoute/apiHandler
10171025
* In the advanced case, this will contain entries from options.apiHandlers
10181026
*/
1019-
private typedApiHandlers: Array<[string, TypedHandler]>;
1027+
private typedApiHandlers: Array<[string, TypedHandler<Env>]>;
10201028

10211029
/**
10221030
* Creates a new OAuth provider instance
10231031
* @param options - Configuration options for the provider
10241032
*/
1025-
constructor(options: OAuthProviderOptions) {
1033+
constructor(options: OAuthProviderOptions<Env>) {
10261034
// Initialize typedApiHandlers as an array
10271035
this.typedApiHandlers = [];
10281036

@@ -1113,7 +1121,7 @@ class OAuthProviderImpl {
11131121
* @returns The type of the handler (EXPORTED_HANDLER or WORKER_ENTRYPOINT)
11141122
* @throws TypeError if the handler is invalid
11151123
*/
1116-
private validateHandler(handler: any, name: string): TypedHandler {
1124+
private validateHandler(handler: any, name: string): TypedHandler<Env> {
11171125
if (typeof handler === 'object' && handler !== null && typeof handler.fetch === 'function') {
11181126
// It's an ExportedHandler object
11191127
return { type: HandlerType.EXPORTED_HANDLER, handler };
@@ -1137,7 +1145,7 @@ class OAuthProviderImpl {
11371145
* @param ctx - Cloudflare Worker execution context
11381146
* @returns A Promise resolving to an HTTP Response
11391147
*/
1140-
async fetch(request: Request, env: any, ctx: ExecutionContext): Promise<Response> {
1148+
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
11411149
const url = new URL(request.url);
11421150

11431151
// Special handling for OPTIONS requests (CORS preflight)
@@ -1207,16 +1215,16 @@ class OAuthProviderImpl {
12071215
}
12081216

12091217
// Inject OAuth helpers into env if not already present
1210-
if (!env.OAUTH_PROVIDER) {
1211-
env.OAUTH_PROVIDER = this.createOAuthHelpers(env);
1218+
if (!(env as Record<string, unknown>).OAUTH_PROVIDER) {
1219+
(env as Record<string, unknown>).OAUTH_PROVIDER = this.createOAuthHelpers(env);
12121220
}
12131221

12141222
// Call the default handler based on its type
12151223
// Note: We don't add CORS headers to default handler responses
12161224
if (this.typedDefaultHandler.type === HandlerType.EXPORTED_HANDLER) {
12171225
// It's an object with a fetch method
12181226
return this.typedDefaultHandler.handler.fetch(
1219-
request as Parameters<ExportedHandlerWithFetch['fetch']>[0],
1227+
request as Parameters<ExportedHandlerWithFetch<Env>['fetch']>[0],
12201228
env,
12211229
ctx
12221230
);
@@ -1462,7 +1470,7 @@ class OAuthProviderImpl {
14621470
* @param url - The URL to find a handler for
14631471
* @returns The TypedHandler for the URL, or undefined if no handler matches
14641472
*/
1465-
private findApiHandlerForUrl(url: URL): TypedHandler | undefined {
1473+
private findApiHandlerForUrl(url: URL): TypedHandler<Env> | undefined {
14661474
// Check each route in our array of validated API handlers
14671475
for (const [route, handler] of this.typedApiHandlers) {
14681476
if (this.matchApiRoute(url, route)) {
@@ -2769,8 +2777,7 @@ class OAuthProviderImpl {
27692777
const decryptedProps = await decryptProps(encryptionKey, tokenData.grant.encryptedProps);
27702778

27712779
// Set the decrypted props on the context object
2772-
// @ts-expect-error - ctx.props is actually writable https://github.com/cloudflare/workers-oauth-provider/issues/124
2773-
ctx.props = decryptedProps;
2780+
(ctx as MutableExecutionContext).props = decryptedProps;
27742781
} else if (this.options.resolveExternalToken) {
27752782
// No token data was found, so we validate the provided token with the provided validator
27762783
const ext = await this.options.resolveExternalToken({ token: accessToken, request, env });
@@ -2802,13 +2809,12 @@ class OAuthProviderImpl {
28022809
}
28032810

28042811
// Set the external props on the context object
2805-
// @ts-expect-error - ctx.props is actually writable https://github.com/cloudflare/workers-oauth-provider/issues/124
2806-
ctx.props = ext.props;
2812+
(ctx as MutableExecutionContext).props = ext.props;
28072813
}
28082814

28092815
// Inject OAuth helpers into env if not already present
2810-
if (!env.OAUTH_PROVIDER) {
2811-
env.OAUTH_PROVIDER = this.createOAuthHelpers(env);
2816+
if (!(env as Record<string, unknown>).OAUTH_PROVIDER) {
2817+
(env as Record<string, unknown>).OAUTH_PROVIDER = this.createOAuthHelpers(env);
28122818
}
28132819

28142820
// Find the appropriate API handler for this URL
@@ -3641,14 +3647,14 @@ async function unwrapKeyWithToken(tokenStr: string, wrappedKeyBase64: string): P
36413647
*/
36423648
class OAuthHelpersImpl implements OAuthHelpers {
36433649
private env: any;
3644-
private provider: OAuthProviderImpl;
3650+
private provider: OAuthProviderImpl<any>;
36453651

36463652
/**
36473653
* Creates a new OAuthHelpers instance
36483654
* @param env - Cloudflare Worker environment variables
36493655
* @param provider - Reference to the parent provider instance
36503656
*/
3651-
constructor(env: any, provider: OAuthProviderImpl) {
3657+
constructor(env: any, provider: OAuthProviderImpl<any>) {
36523658
this.env = env;
36533659
this.provider = provider;
36543660
}

0 commit comments

Comments
 (0)