@@ -8,6 +8,12 @@ interface ClientConfig {
88 role : 'readonly' | 'readwrite' | 'admin' ;
99}
1010
11+ interface UserConfig {
12+ username : string ;
13+ password : string ;
14+ role : 'readonly' | 'readwrite' | 'admin' ;
15+ }
16+
1117// Load client configurations from environment variables
1218const clients : ClientConfig [ ] = [
1319 {
@@ -27,27 +33,37 @@ const clients: ClientConfig[] = [
2733 } ,
2834] . filter ( ( client ) => client . clientId && client . clientSecret ) as ClientConfig [ ] ; // Filter out unconfigured clients
2935
36+ // Load user configurations from environment variables
37+ // Format: OAUTH_USER_USERNAME=password:role (e.g., OAUTH_USER_ADMIN=secret123:admin)
38+ // Or use separate variables: OAUTH_USERNAME_READONLY, OAUTH_PASSWORD_READONLY, etc.
39+ const users : UserConfig [ ] = [
40+ {
41+ username : process . env . OAUTH_USERNAME_READONLY || '' ,
42+ password : process . env . OAUTH_PASSWORD_READONLY || '' ,
43+ role : 'readonly' as const ,
44+ } ,
45+ {
46+ username : process . env . OAUTH_USERNAME_READWRITE || '' ,
47+ password : process . env . OAUTH_PASSWORD_READWRITE || '' ,
48+ role : 'readwrite' as const ,
49+ } ,
50+ {
51+ username : process . env . OAUTH_USERNAME_ADMIN || '' ,
52+ password : process . env . OAUTH_PASSWORD_ADMIN || '' ,
53+ role : 'admin' as const ,
54+ } ,
55+ ] . filter ( ( user ) => user . username && user . password ) as UserConfig [ ] ; // Filter out unconfigured users
56+
3057const TOKEN_EXPIRES_IN = parseInt ( process . env . OAUTH_TOKEN_EXPIRES_IN || '3600' , 10 ) ;
3158
3259/**
3360 * OAuth 2.0 Token endpoint handler
34- * Supports client_credentials grant type
61+ * Supports client_credentials and password grant types
3562 */
3663export async function tokenHandler ( req : Request , res : Response ) : Promise < void > {
3764 try {
38- // Validate that at least one client is configured
39- if ( clients . length === 0 ) {
40- res . status ( 500 ) . json ( {
41- error : 'server_error' ,
42- error_description : 'OAuth server configuration error: No clients configured' ,
43- } ) ;
44- return ;
45- }
46-
4765 // OAuth token endpoint expects application/x-www-form-urlencoded
4866 const grantType = req . body . grant_type ;
49- const clientId = req . body . client_id ;
50- const clientSecret = req . body . client_secret ;
5167 const scope = req . body . scope ;
5268
5369 // Validate grant_type
@@ -59,42 +75,110 @@ export async function tokenHandler(req: Request, res: Response): Promise<void> {
5975 return ;
6076 }
6177
62- if ( grantType !== 'client_credentials' ) {
63- res . status ( 400 ) . json ( {
64- error : 'invalid_grant' ,
65- error_description : 'Unsupported grant type' ,
66- } ) ;
67- return ;
78+ let role : 'readonly' | 'readwrite' | 'admin' ;
79+ let subject : string ; // client_id or username
80+
81+ // Handle client_credentials grant
82+ if ( grantType === 'client_credentials' ) {
83+ // Validate that at least one client is configured
84+ if ( clients . length === 0 ) {
85+ res . status ( 500 ) . json ( {
86+ error : 'server_error' ,
87+ error_description : 'OAuth server configuration error: No clients configured' ,
88+ } ) ;
89+ return ;
90+ }
91+
92+ const clientId = req . body . client_id ;
93+ const clientSecret = req . body . client_secret ;
94+
95+ // Validate client_id
96+ if ( ! clientId ) {
97+ res . status ( 400 ) . json ( {
98+ error : 'invalid_request' ,
99+ error_description : 'Missing required parameter: client_id' ,
100+ } ) ;
101+ return ;
102+ }
103+
104+ // Validate client_secret
105+ if ( ! clientSecret ) {
106+ res . status ( 400 ) . json ( {
107+ error : 'invalid_request' ,
108+ error_description : 'Missing required parameter: client_secret' ,
109+ } ) ;
110+ return ;
111+ }
112+
113+ // Look up client in configuration
114+ const client = clients . find (
115+ ( c ) => c . clientId === clientId && c . clientSecret === clientSecret
116+ ) ;
117+
118+ // Authenticate client
119+ if ( ! client ) {
120+ res . status ( 401 ) . json ( {
121+ error : 'invalid_client' ,
122+ error_description : 'Invalid client credentials' ,
123+ } ) ;
124+ return ;
125+ }
126+
127+ role = client . role ;
128+ subject = clientId ;
68129 }
69-
70- // Validate client_id
71- if ( ! clientId ) {
130+ // Handle password grant
131+ else if ( grantType === 'password' ) {
132+ // Validate that at least one user is configured
133+ if ( users . length === 0 ) {
134+ res . status ( 500 ) . json ( {
135+ error : 'server_error' ,
136+ error_description : 'OAuth server configuration error: No users configured' ,
137+ } ) ;
138+ return ;
139+ }
140+
141+ const username = req . body . username ;
142+ const password = req . body . password ;
143+
144+ // Validate username
145+ if ( ! username ) {
146+ res . status ( 400 ) . json ( {
147+ error : 'invalid_request' ,
148+ error_description : 'Missing required parameter: username' ,
149+ } ) ;
150+ return ;
151+ }
152+
153+ // Validate password
154+ if ( ! password ) {
155+ res . status ( 400 ) . json ( {
156+ error : 'invalid_request' ,
157+ error_description : 'Missing required parameter: password' ,
158+ } ) ;
159+ return ;
160+ }
161+
162+ // Look up user in configuration
163+ const user = users . find (
164+ ( u ) => u . username === username && u . password === password
165+ ) ;
166+
167+ // Authenticate user
168+ if ( ! user ) {
169+ res . status ( 401 ) . json ( {
170+ error : 'invalid_grant' ,
171+ error_description : 'Invalid username or password' ,
172+ } ) ;
173+ return ;
174+ }
175+
176+ role = user . role ;
177+ subject = username ;
178+ } else {
72179 res . status ( 400 ) . json ( {
73- error : 'invalid_request' ,
74- error_description : 'Missing required parameter: client_id' ,
75- } ) ;
76- return ;
77- }
78-
79- // Validate client_secret
80- if ( ! clientSecret ) {
81- res . status ( 400 ) . json ( {
82- error : 'invalid_request' ,
83- error_description : 'Missing required parameter: client_secret' ,
84- } ) ;
85- return ;
86- }
87-
88- // Look up client in configuration
89- const client = clients . find (
90- ( c ) => c . clientId === clientId && c . clientSecret === clientSecret
91- ) ;
92-
93- // Authenticate client
94- if ( ! client ) {
95- res . status ( 401 ) . json ( {
96- error : 'invalid_client' ,
97- error_description : 'Invalid client credentials' ,
180+ error : 'invalid_grant' ,
181+ error_description : 'Unsupported grant type. Supported types: client_credentials, password' ,
98182 } ) ;
99183 return ;
100184 }
@@ -105,11 +189,11 @@ export async function tokenHandler(req: Request, res: Response): Promise<void> {
105189
106190 const payload : jwt . JwtPayload = {
107191 iss : 'example-app' , // Issuer
108- sub : clientId , // Subject (client_id)
192+ sub : subject , // Subject (client_id or username )
109193 aud : 'example-app' , // Audience
110194 exp : expiresAt , // Expiration time
111195 iat : now , // Issued at
112- role : client . role , // Role based on authenticated client
196+ role : role , // Role based on authenticated client or user
113197 } ;
114198
115199 // Add scope if provided
0 commit comments