1+ import { RequestHandler } from "express" ;
2+ import { z } from "zod" ;
3+ import { OAuthRegisteredClientsStore } from "../clients.js" ;
4+
5+ export type AuthorizationHandlerOptions = {
6+ /**
7+ * A store used to read information about registered OAuth clients.
8+ */
9+ store : OAuthRegisteredClientsStore ;
10+ } ;
11+
12+ const ClientAuthorizationParamsSchema = z . object ( {
13+ client_id : z . string ( ) ,
14+ redirect_uri : z . string ( ) . optional ( ) ,
15+ } ) ;
16+
17+ const RequestAuthorizationParamsSchema = z . object ( {
18+ response_type : z . literal ( "code" ) ,
19+ code_challenge : z . string ( ) ,
20+ code_challenge_method : z . literal ( "S256" ) ,
21+ scope : z . string ( ) . optional ( ) ,
22+ state : z . string ( ) . optional ( ) ,
23+ } ) ;
24+
25+ export function authorizationHandler ( { store } : AuthorizationHandlerOptions ) : RequestHandler {
26+ return async ( req , res ) => {
27+ if ( req . method !== "GET" && req . method !== "POST" ) {
28+ res . status ( 405 ) . end ( "Method Not Allowed" ) ;
29+ return ;
30+ }
31+
32+ let client_id , redirect_uri ;
33+ try {
34+ ( { client_id, redirect_uri } = ClientAuthorizationParamsSchema . parse ( req . query ) ) ;
35+ } catch ( error ) {
36+ res . status ( 400 ) . end ( `Bad Request: ${ error } ` ) ;
37+ return ;
38+ }
39+
40+ const client = await store . getClient ( client_id ) ;
41+ if ( ! client ) {
42+ res . status ( 400 ) . end ( "Bad Request: invalid client_id" ) ;
43+ return ;
44+ }
45+
46+ if ( redirect_uri !== undefined ) {
47+ if ( ! client . redirect_uris . includes ( redirect_uri ) ) {
48+ res . status ( 400 ) . end ( "Bad Request: invalid redirect_uri" ) ;
49+ return ;
50+ }
51+ } else if ( client . redirect_uris . length === 1 ) {
52+ redirect_uri = client . redirect_uris [ 0 ] ;
53+ } else {
54+ res . status ( 400 ) . end ( "Bad Request: missing redirect_uri" ) ;
55+ return ;
56+ }
57+
58+ let params ;
59+ try {
60+ params = RequestAuthorizationParamsSchema . parse ( req . query ) ;
61+ } catch ( error ) {
62+ const errorUrl = new URL ( redirect_uri ) ;
63+ errorUrl . searchParams . set ( "error" , "invalid_request" ) ;
64+ errorUrl . searchParams . set ( "error_description" , String ( error ) ) ;
65+ res . redirect ( 302 , errorUrl . href ) ;
66+ return ;
67+ }
68+
69+ if ( params . scope !== undefined && client . scope !== undefined ) {
70+ const requestedScopes = params . scope . split ( " " ) ;
71+ const allowedScopes = new Set ( client . scope . split ( " " ) ) ;
72+
73+ // If any requested scope is not in the client's registered scopes, error out
74+ for ( const scope of requestedScopes ) {
75+ if ( ! allowedScopes . has ( scope ) ) {
76+ const errorUrl = new URL ( redirect_uri ) ;
77+ errorUrl . searchParams . set ( "error" , "invalid_scope" ) ;
78+ errorUrl . searchParams . set ( "error_description" , `Client was not registered with scope ${ scope } ` ) ;
79+ res . redirect ( 302 , errorUrl . href ) ;
80+ return ;
81+ }
82+ }
83+ }
84+
85+ // TODO: Store code challenge
86+ // TODO: Generate authorization code
87+ // TODO: Redirect to redirect_uri (handle in calling code)
88+ } ;
89+ }
0 commit comments