11import type { Request , Response } from "express" ;
2+ import OAuth2Server from '@node-oauth/oauth2-server' ;
23import { getConfig } from "../config.ts" ;
34import { logger } from "../logger.ts" ;
45
56/**
67 * OAuth 2.0 Authorization Server Metadata endpoint
78 * RFC 8414: https://tools.ietf.org/html/rfc8414
9+ *
10+ * For AUTH_MODE=full, this describes our OAuth client proxy endpoints
811 */
912export function createAuthorizationServerMetadataHandler ( ) {
1013 return ( req : Request , res : Response ) => {
@@ -19,14 +22,12 @@ export function createAuthorizationServerMetadataHandler() {
1922 response_types_supported : [ "code" ] ,
2023 grant_types_supported : [ "authorization_code" ] ,
2124 code_challenge_methods_supported : [ "S256" ] ,
22- scopes_supported : [ "openid" , "profile" , "email" , "read" , "write" ] ,
23- token_endpoint_auth_methods_supported : [ "client_secret_basic" , "client_secret_post" ] ,
24- revocation_endpoint : `${ baseUrl } /oauth/revoke` ,
25- introspection_endpoint : `${ baseUrl } /oauth/introspect` ,
25+ scopes_supported : [ "read" , "write" , "mcp" ] ,
26+ token_endpoint_auth_methods_supported : [ "none" ]
2627 } ;
2728
2829 logger . info ( "OAuth authorization server metadata requested" , {
29- issuer : metadata . issuer
30+ issuer : metadata . issuer
3031 } ) ;
3132
3233 res . json ( metadata ) ;
@@ -45,6 +46,8 @@ export function createAuthorizationServerMetadataHandler() {
4546/**
4647 * OAuth 2.0 Protected Resource Metadata endpoint
4748 * RFC 8705: https://tools.ietf.org/html/rfc8705
49+ *
50+ * For AUTH_MODE=full, this describes our resource server capabilities
4851 */
4952export function createProtectedResourceMetadataHandler ( ) {
5053 return ( req : Request , res : Response ) => {
@@ -55,13 +58,13 @@ export function createProtectedResourceMetadataHandler() {
5558 const metadata = {
5659 resource : baseUrl ,
5760 authorization_servers : [ baseUrl ] ,
58- scopes_supported : [ "read" , "write" ] ,
61+ scopes_supported : [ "read" , "write" , "mcp" ] ,
5962 bearer_methods_supported : [ "header" ] ,
60- resource_documentation : `${ baseUrl } /docs` ,
63+ resource_documentation : `${ baseUrl } /docs`
6164 } ;
6265
6366 logger . info ( "OAuth protected resource metadata requested" , {
64- resource : metadata . resource
67+ resource : metadata . resource
6568 } ) ;
6669
6770 res . json ( metadata ) ;
@@ -78,145 +81,189 @@ export function createProtectedResourceMetadataHandler() {
7881}
7982
8083/**
81- * OAuth 2.1 token endpoint - proxies token requests to external OAuth provider
84+ * OAuth 2.0 Authorization endpoint
8285 */
83- export function createTokenHandler ( oauthProvider : any ) {
86+ export function createAuthorizeHandler ( oauthServer : OAuth2Server ) {
8487 return async ( req : Request , res : Response ) => {
8588 try {
86- const config = getConfig ( ) ;
87- const { grant_type, code, redirect_uri, client_id, code_verifier } = req . body ;
89+ logger . debug ( "Authorization request received" , {
90+ query : req . query ,
91+ method : req . method
92+ } ) ;
8893
89- // Validate required parameters
90- if ( grant_type !== "authorization_code" ) {
91- return res . status ( 400 ) . json ( {
92- error : "unsupported_grant_type" ,
93- error_description : "Only 'authorization_code' grant type is supported"
94+ // Real OAuth implementation: Check for authenticated user
95+ // In a real implementation, this would:
96+ // 1. Check if user has valid session/cookie
97+ // 2. If not authenticated, redirect to login page
98+ // 3. After login, show consent page
99+ // 4. Only then proceed with authorization
100+
101+ // For now, this implementation requires external authentication
102+ // The user must be authenticated before reaching this endpoint
103+ const userId = req . headers [ 'x-user-id' ] as string ;
104+ const username = req . headers [ 'x-username' ] as string ;
105+
106+ if ( ! userId || ! username ) {
107+ logger . warn ( "Missing user authentication headers" ) ;
108+ return res . status ( 401 ) . json ( {
109+ error : "access_denied" ,
110+ error_description : "User must be authenticated before authorization"
94111 } ) ;
95112 }
113+
114+ const user = {
115+ id : userId ,
116+ username : username
117+ } ;
96118
97- if ( ! code || ! redirect_uri || ! client_id || ! code_verifier ) {
98- return res . status ( 400 ) . json ( {
99- error : "invalid_request" ,
100- error_description : "Missing required parameters: code, redirect_uri, client_id, code_verifier"
101- } ) ;
102- }
119+ logger . debug ( "User authenticated, proceeding with authorization" , { userId : user . id } ) ;
103120
104- // Proxy token request to external OAuth provider
105- const tokenParams = new URLSearchParams ( {
106- grant_type : "authorization_code" ,
107- code,
108- redirect_uri : `${ config . BASE_URL || "http://localhost:3000" } /oauth/callback` ,
109- client_id : config . OAUTH_CLIENT_ID ! ,
110- client_secret : config . OAUTH_CLIENT_SECRET ! ,
111- code_verifier
121+ // Use the OAuth2Server authorize method
122+ const request = new ( OAuth2Server as any ) . Request ( req ) ;
123+ const response = new ( OAuth2Server as any ) . Response ( res ) ;
124+
125+ const authorizationCode = await oauthServer . authorize ( request , response , {
126+ authenticateHandler : {
127+ handle : async ( ) => {
128+ logger . debug ( "Authenticate handler called" ) ;
129+ return user ;
130+ }
131+ }
112132 } ) ;
113133
114- const tokenResponse = await fetch ( `${ config . OAUTH_ISSUER } /oauth/token` , {
115- method : "POST" ,
116- headers : {
117- "Content-Type" : "application/x-www-form-urlencoded" ,
118- } ,
119- body : tokenParams
134+ logger . info ( "Authorization code granted" , {
135+ clientId : authorizationCode . client . id ,
136+ userId : user . id ,
137+ code : authorizationCode . authorizationCode . substring ( 0 , 8 ) + "..."
120138 } ) ;
121139
122- if ( ! tokenResponse . ok ) {
123- logger . warn ( "External OAuth token exchange failed" , {
124- status : tokenResponse . status ,
125- statusText : tokenResponse . statusText
126- } ) ;
127- return res . status ( 400 ) . json ( {
128- error : "invalid_grant" ,
129- error_description : "Authorization code exchange failed"
140+ // Redirect back to client with authorization code
141+ const redirectUri = req . query . redirect_uri as string ;
142+ const state = req . query . state as string ;
143+
144+ if ( redirectUri ) {
145+ const url = new URL ( redirectUri ) ;
146+ url . searchParams . set ( 'code' , authorizationCode . authorizationCode ) ;
147+ if ( state ) url . searchParams . set ( 'state' , state ) ;
148+
149+ logger . info ( "Redirecting to client" , { redirectUrl : url . toString ( ) } ) ;
150+ res . redirect ( url . toString ( ) ) ;
151+ } else {
152+ // Fallback - return as JSON
153+ res . json ( {
154+ authorization_code : authorizationCode . authorizationCode ,
155+ state
130156 } ) ;
131157 }
132158
133- const tokenData = await tokenResponse . json ( ) ;
159+ } catch ( error ) {
160+ logger . error ( "Authorization endpoint error" , {
161+ error : error instanceof Error ? error . message : error ,
162+ stack : error instanceof Error ? error . stack : undefined
163+ } ) ;
164+
165+ res . status ( 400 ) . json ( {
166+ error : "server_error" ,
167+ error_description : error instanceof Error ? error . message : "Failed to process authorization request"
168+ } ) ;
169+ }
170+ } ;
171+ }
172+
173+ /**
174+ * OAuth 2.0 Token endpoint
175+ */
176+ export function createTokenHandler ( oauthServer : OAuth2Server ) {
177+ return async ( req : Request , res : Response ) => {
178+ try {
179+ const request = new ( OAuth2Server as any ) . Request ( req ) ;
180+ const response = new ( OAuth2Server as any ) . Response ( res ) ;
181+
182+ const token = await oauthServer . token ( request , response ) ;
134183
135- logger . info ( "Token exchange successful via external provider" , {
136- client_id,
137- scope : tokenData . scope
184+ logger . info ( "Access token granted" , {
185+ clientId : token . client . id ,
186+ userId : token . user ?. id ,
187+ scope : token . scope
138188 } ) ;
139189
140- // Return tokens (optionally transform or wrap them)
141190 res . json ( {
142- access_token : tokenData . access_token ,
143- token_type : tokenData . token_type || "Bearer" ,
144- expires_in : tokenData . expires_in ,
145- scope : tokenData . scope ,
146- refresh_token : tokenData . refresh_token
191+ access_token : token . accessToken ,
192+ token_type : "Bearer" ,
193+ expires_in : Math . floor ( ( token . accessTokenExpiresAt ! . getTime ( ) - Date . now ( ) ) / 1000 ) ,
194+ scope : Array . isArray ( token . scope ) ? token . scope . join ( ' ' ) : token . scope ,
195+ refresh_token : token . refreshToken
147196 } ) ;
148197
149198 } catch ( error ) {
150- logger . error ( "Token endpoint proxy error" , {
199+ logger . error ( "Token endpoint error" , {
151200 error : error instanceof Error ? error . message : error
152201 } ) ;
153202
154- res . status ( 500 ) . json ( {
155- error : "server_error " ,
156- error_description : "Failed to process token request"
203+ res . status ( 400 ) . json ( {
204+ error : "invalid_request " ,
205+ error_description : error instanceof Error ? error . message : "Token request failed "
157206 } ) ;
158207 }
159208 } ;
160209}
161210
162211/**
163- * Token introspection endpoint - simplified for OAuth proxy pattern
212+ * Token introspection endpoint
164213 */
165- export function createIntrospectionHandler ( oauthProvider ?: any ) {
214+ export function createIntrospectionHandler ( oauthServer : OAuth2Server ) {
166215 return async ( req : Request , res : Response ) => {
167216 try {
168- const { token } = req . body ;
169-
170- if ( ! token ) {
171- return res . status ( 400 ) . json ( {
172- error : "invalid_request" ,
173- error_description : "Missing token parameter"
174- } ) ;
175- }
176-
177- logger . info ( "Token introspection requested" , { token : token . substring ( 0 , 10 ) + "..." } ) ;
217+ const request = new ( OAuth2Server as any ) . Request ( req ) ;
218+ const response = new ( OAuth2Server as any ) . Response ( res ) ;
178219
179- // Return inactive for OAuth proxy pattern - external IdP handles actual validation
180- res . json ( { active : false } ) ;
220+ const token = await oauthServer . authenticate ( request , response ) ;
221+
222+ logger . info ( "Token introspection successful" , {
223+ clientId : token . client . id ,
224+ userId : token . user ?. id ,
225+ scope : token . scope
226+ } ) ;
227+
228+ res . json ( {
229+ active : true ,
230+ scope : Array . isArray ( token . scope ) ? token . scope . join ( ' ' ) : token . scope ,
231+ client_id : token . client . id ,
232+ username : token . user ?. username ,
233+ sub : token . user ?. id ,
234+ exp : Math . floor ( ( token . accessTokenExpiresAt ?. getTime ( ) || 0 ) / 1000 )
235+ } ) ;
181236
182237 } catch ( error ) {
183- logger . error ( "Token introspection error " , {
238+ logger . debug ( "Token introspection failed " , {
184239 error : error instanceof Error ? error . message : error
185240 } ) ;
186- res . status ( 500 ) . json ( {
187- error : "server_error" ,
188- error_description : "Failed to introspect token"
189- } ) ;
241+
242+ res . json ( { active : false } ) ;
190243 }
191244 } ;
192245}
193246
194247/**
195248 * Token revocation endpoint
196249 */
197- export function createRevocationHandler ( ) {
250+ export function createRevocationHandler ( oauthServer : OAuth2Server ) {
198251 return async ( req : Request , res : Response ) => {
199252 try {
200- const { token } = req . body ;
201-
202- if ( ! token ) {
203- return res . status ( 400 ) . json ( {
204- error : "invalid_request" ,
205- error_description : "Missing token parameter"
206- } ) ;
207- }
208-
209- // TODO: Implement actual token revocation
210- logger . info ( "Token revocation requested" , { token : token . substring ( 0 , 10 ) + "..." } ) ;
211-
212- res . status ( 200 ) . send ( ) ; // Success response
253+ const request = new ( OAuth2Server as any ) . Request ( req ) ;
254+ const response = new ( OAuth2Server as any ) . Response ( res ) ;
255+
256+ await oauthServer . revoke ( request , response ) ;
257+
258+ logger . info ( "Token revoked successfully" ) ;
259+ res . status ( 200 ) . send ( ) ;
213260
214261 } catch ( error ) {
215262 logger . error ( "Token revocation error" , {
216263 error : error instanceof Error ? error . message : error
217264 } ) ;
218- res . status ( 500 ) . json ( {
219- error : "server_error " ,
265+ res . status ( 400 ) . json ( {
266+ error : "invalid_request " ,
220267 error_description : "Failed to revoke token"
221268 } ) ;
222269 }
0 commit comments