@@ -99,15 +99,33 @@ export interface OAuthProviderOptions {
99
99
* URL(s) for API routes. Requests with URLs starting with any of these prefixes
100
100
* will be treated as API requests and require a valid access token.
101
101
* Can be a single route or an array of routes. Each route can be a full URL or just a path.
102
+ *
103
+ * Used with `apiHandler` for the single-handler configuration. This is incompatible with
104
+ * the `apiHandlers` property. You must use either `apiRoute` + `apiHandler` OR `apiHandlers`, not both.
102
105
*/
103
- apiRoute : string | string [ ] ;
106
+ apiRoute ? : string | string [ ] ;
104
107
105
108
/**
106
109
* Handler for API requests that have a valid access token.
107
110
* This handler will receive the authenticated user properties in ctx.props.
108
111
* Can be either an ExportedHandler object with a fetch method or a class extending WorkerEntrypoint.
112
+ *
113
+ * Used with `apiRoute` for the single-handler configuration. This is incompatible with
114
+ * the `apiHandlers` property. You must use either `apiRoute` + `apiHandler` OR `apiHandlers`, not both.
109
115
*/
110
- apiHandler : ExportedHandlerWithFetch | ( new ( ctx : ExecutionContext , env : any ) => WorkerEntrypointWithFetch ) ;
116
+ apiHandler ?: ExportedHandlerWithFetch | ( new ( ctx : ExecutionContext , env : any ) => WorkerEntrypointWithFetch ) ;
117
+
118
+ /**
119
+ * Map of API routes to their corresponding handlers for the multi-handler configuration.
120
+ * The keys are the API routes (strings only, not arrays), and the values are the handlers.
121
+ * Each route can be a full URL or just a path, and each handler can be either an ExportedHandler
122
+ * object with a fetch method or a class extending WorkerEntrypoint.
123
+ *
124
+ * This is incompatible with the `apiRoute` and `apiHandler` properties. You must use either
125
+ * `apiRoute` + `apiHandler` (single-handler configuration) OR `apiHandlers` (multi-handler
126
+ * configuration), not both.
127
+ */
128
+ apiHandlers ?: Record < string , ExportedHandlerWithFetch | ( new ( ctx : ExecutionContext , env : any ) => WorkerEntrypointWithFetch ) > ;
111
129
112
130
/**
113
131
* Handler for all non-API requests or API requests without a valid token.
@@ -671,26 +689,68 @@ class OAuthProviderImpl {
671
689
/**
672
690
* Represents the validated type of a handler (ExportedHandler or WorkerEntrypoint)
673
691
*/
674
- private typedApiHandler : TypedHandler ;
675
692
private typedDefaultHandler : TypedHandler ;
693
+
694
+ /**
695
+ * Array of tuples of API routes and their validated handlers
696
+ * In the simple case, this will be a single entry with the route and handler from options.apiRoute/apiHandler
697
+ * In the advanced case, this will contain entries from options.apiHandlers
698
+ */
699
+ private typedApiHandlers : Array < [ string , TypedHandler ] > ;
676
700
677
701
/**
678
702
* Creates a new OAuth provider instance
679
703
* @param options - Configuration options for the provider
680
704
*/
681
705
constructor ( options : OAuthProviderOptions ) {
682
- // Validate and determine handler types
683
- this . typedApiHandler = this . validateHandler ( options . apiHandler , 'apiHandler' ) ;
706
+ // Initialize typedApiHandlers as an array
707
+ this . typedApiHandlers = [ ] ;
708
+
709
+ // Check if we have incompatible configuration
710
+ const hasSingleHandlerConfig = ! ! ( options . apiRoute && options . apiHandler ) ;
711
+ const hasMultiHandlerConfig = ! ! options . apiHandlers ;
712
+
713
+ if ( hasSingleHandlerConfig && hasMultiHandlerConfig ) {
714
+ throw new TypeError (
715
+ 'Cannot use both apiRoute/apiHandler and apiHandlers. ' +
716
+ 'Use either apiRoute + apiHandler OR apiHandlers, not both.'
717
+ ) ;
718
+ }
719
+
720
+ if ( ! hasSingleHandlerConfig && ! hasMultiHandlerConfig ) {
721
+ throw new TypeError (
722
+ 'Must provide either apiRoute + apiHandler OR apiHandlers. ' +
723
+ 'No API route configuration provided.'
724
+ ) ;
725
+ }
726
+
727
+ // Validate default handler
684
728
this . typedDefaultHandler = this . validateHandler ( options . defaultHandler , 'defaultHandler' ) ;
685
729
686
- // Validate that the endpoints are either absolute paths or full URLs
687
- if ( Array . isArray ( options . apiRoute ) ) {
688
- options . apiRoute . forEach ( ( route , index ) => {
689
- this . validateEndpoint ( route , `apiRoute[${ index } ]` ) ;
690
- } ) ;
730
+ // Process and validate the API handlers
731
+ if ( hasSingleHandlerConfig ) {
732
+ // Single handler mode with apiRoute + apiHandler
733
+ const apiHandler = this . validateHandler ( options . apiHandler ! , 'apiHandler' ) ;
734
+
735
+ // For single handler mode, process the apiRoute(s) and map them all to the single apiHandler
736
+ if ( Array . isArray ( options . apiRoute ) ) {
737
+ options . apiRoute . forEach ( ( route , index ) => {
738
+ this . validateEndpoint ( route , `apiRoute[${ index } ]` ) ;
739
+ this . typedApiHandlers . push ( [ route , apiHandler ] ) ;
740
+ } ) ;
741
+ } else {
742
+ this . validateEndpoint ( options . apiRoute ! , 'apiRoute' ) ;
743
+ this . typedApiHandlers . push ( [ options . apiRoute ! , apiHandler ] ) ;
744
+ }
691
745
} else {
692
- this . validateEndpoint ( options . apiRoute , 'apiRoute' ) ;
746
+ // Multiple handlers mode with apiHandlers map
747
+ for ( const [ route , handler ] of Object . entries ( options . apiHandlers ! ) ) {
748
+ this . validateEndpoint ( route , `apiHandlers key: ${ route } ` ) ;
749
+ this . typedApiHandlers . push ( [ route , this . validateHandler ( handler , `apiHandlers[${ route } ]` ) ] ) ;
750
+ }
693
751
}
752
+
753
+ // Validate that the oauth endpoints are either absolute paths or full URLs
694
754
this . validateEndpoint ( options . authorizeEndpoint , 'authorizeEndpoint' ) ;
695
755
this . validateEndpoint ( options . tokenEndpoint , 'tokenEndpoint' ) ;
696
756
if ( options . clientRegistrationEndpoint ) {
@@ -896,14 +956,28 @@ class OAuthProviderImpl {
896
956
* @returns True if the URL matches any of the API routes
897
957
*/
898
958
private isApiRequest ( url : URL ) : boolean {
899
- // Handle array of routes
900
- if ( Array . isArray ( this . options . apiRoute ) ) {
901
- // Return true if any route matches
902
- return this . options . apiRoute . some ( ( route ) => this . matchApiRoute ( url , route ) ) ;
903
- } else {
904
- // Handle single route
905
- return this . matchApiRoute ( url , this . options . apiRoute ) ;
959
+ // Check each route in our array of validated API handlers
960
+ for ( const [ route , _ ] of this . typedApiHandlers ) {
961
+ if ( this . matchApiRoute ( url , route ) ) {
962
+ return true ;
963
+ }
906
964
}
965
+ return false ;
966
+ }
967
+
968
+ /**
969
+ * Finds the appropriate API handler for a URL
970
+ * @param url - The URL to find a handler for
971
+ * @returns The TypedHandler for the URL, or undefined if no handler matches
972
+ */
973
+ private findApiHandlerForUrl ( url : URL ) : TypedHandler | undefined {
974
+ // Check each route in our array of validated API handlers
975
+ for ( const [ route , handler ] of this . typedApiHandlers ) {
976
+ if ( this . matchApiRoute ( url , route ) ) {
977
+ return handler ;
978
+ }
979
+ }
980
+ return undefined ;
907
981
}
908
982
909
983
/**
@@ -1760,13 +1834,23 @@ class OAuthProviderImpl {
1760
1834
env . OAUTH_PROVIDER = this . createOAuthHelpers ( env ) ;
1761
1835
}
1762
1836
1837
+ // Find the appropriate API handler for this URL
1838
+ const url = new URL ( request . url ) ;
1839
+ const apiHandler = this . findApiHandlerForUrl ( url ) ;
1840
+
1841
+ if ( ! apiHandler ) {
1842
+ // This shouldn't happen since we already checked with isApiRequest,
1843
+ // but handle it gracefully just in case
1844
+ return this . createErrorResponse ( 'invalid_request' , 'No handler found for API route' , 404 ) ;
1845
+ }
1846
+
1763
1847
// Call the API handler based on its type
1764
- if ( this . typedApiHandler . type === HandlerType . EXPORTED_HANDLER ) {
1848
+ if ( apiHandler . type === HandlerType . EXPORTED_HANDLER ) {
1765
1849
// It's an object with a fetch method
1766
- return this . typedApiHandler . handler . fetch ( request as Parameters < ExportedHandlerWithFetch [ 'fetch' ] > [ 0 ] , env , ctx ) ;
1850
+ return apiHandler . handler . fetch ( request as Parameters < ExportedHandlerWithFetch [ 'fetch' ] > [ 0 ] , env , ctx ) ;
1767
1851
} else {
1768
1852
// It's a WorkerEntrypoint class - instantiate it with ctx and env in that order
1769
- const handler = new this . typedApiHandler . handler ( ctx , env ) ;
1853
+ const handler = new apiHandler . handler ( ctx , env ) ;
1770
1854
return handler . fetch ( request ) ;
1771
1855
}
1772
1856
}
0 commit comments