Skip to content

Commit 055ef95

Browse files
committed
fix(patch): oauth2 workflow failed, added basic auth support and error handling to check client credentials
1 parent 0d24459 commit 055ef95

File tree

3 files changed

+58
-5
lines changed

3 files changed

+58
-5
lines changed

src/auth/oauth2/client.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,17 @@ const migrateClientSecret = async (db, clientId, plainSecret) => {
4444
* Supports both hashed and plain-text secrets (for migration).
4545
*/
4646
export const validateClient = async (clientId, clientSecret) => {
47+
if (!clientId || !clientSecret) {
48+
logger.warn('OAuth2 client validation failed: missing client_id or client_secret');
49+
throw new AuthenticationError('Invalid client credentials');
50+
}
51+
4752
pruneExpiredCodes();
4853
const db = getDb();
4954
const client = db.prepare('SELECT * FROM clients WHERE client_id = ?').get(clientId);
5055

5156
if (!client) {
57+
logger.warn(`OAuth2 client validation failed: client_id not found: ${clientId}`);
5258
throw new AuthenticationError('Invalid client credentials');
5359
}
5460

@@ -57,17 +63,20 @@ export const validateClient = async (clientId, clientSecret) => {
5763
// Compare with hashed secret
5864
const isValid = await compareClientSecret(clientSecret, client.client_secret);
5965
if (!isValid) {
66+
logger.warn(`OAuth2 client validation failed: invalid client_secret for client_id: ${clientId}`);
6067
throw new AuthenticationError('Invalid client credentials');
6168
}
6269
} else {
6370
// Legacy: compare plain text (and migrate to hashed)
6471
if (client.client_secret !== clientSecret) {
72+
logger.warn(`OAuth2 client validation failed: invalid client_secret for client_id: ${clientId}`);
6573
throw new AuthenticationError('Invalid client credentials');
6674
}
6775
// Migrate to hashed format
6876
await migrateClientSecret(db, clientId, clientSecret);
6977
}
7078

79+
logger.debug(`OAuth2 client validation successful: ${clientId}`);
7180
return client;
7281
};
7382

src/routes/oauth2.js

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,25 +81,69 @@ router.get('/authorize', asyncHandler(async (req, res) => {
8181
res.redirect(redirectUrl.toString());
8282
}));
8383

84+
/**
85+
* Extract client credentials from request.
86+
* Supports both:
87+
* 1. HTTP Basic Authentication (Authorization header) - OAuth2 recommended
88+
* 2. Request body parameters (client_id, client_secret)
89+
*/
90+
const extractClientCredentials = (req) => {
91+
// Method 1: Try Basic Auth header first (OAuth2 recommended)
92+
const authHeader = req.headers.authorization;
93+
if (authHeader && authHeader.startsWith('Basic ')) {
94+
try {
95+
const base64Credentials = authHeader.slice(6); // Remove 'Basic ' prefix
96+
const credentials = Buffer.from(base64Credentials, 'base64').toString('utf-8');
97+
const [clientId, clientSecret] = credentials.split(':', 2);
98+
99+
if (clientId && clientSecret) {
100+
return { clientId, clientSecret };
101+
}
102+
} catch {
103+
// Invalid Basic Auth format, fall through to body method
104+
}
105+
}
106+
107+
// Method 2: Fall back to request body
108+
const { client_id, client_secret } = req.body;
109+
if (client_id && client_secret) {
110+
return { clientId: client_id, clientSecret: client_secret };
111+
}
112+
113+
return null;
114+
};
115+
84116
/**
85117
* POST /oauth/token
86118
*
87119
* OAuth2 token endpoint. Exchanges authorization code for access token.
88120
* Validates client credentials and authorization code before issuing tokens.
121+
*
122+
* Supports client credentials via:
123+
* - HTTP Basic Authentication (Authorization: Basic <base64(client_id:client_secret)>) - Recommended
124+
* - Request body (client_id, client_secret) - form-encoded or JSON
89125
*/
90-
router.post('/token', express.urlencoded({ extended: true }), asyncHandler(async (req, res) => {
91-
const { grant_type, code, client_id, client_secret, redirect_uri } = req.body;
126+
router.post('/token', express.json(), express.urlencoded({ extended: true }), asyncHandler(async (req, res) => {
127+
const { grant_type, code, redirect_uri } = req.body;
92128

93129
// Only support authorization code grant
94130
if (grant_type !== 'authorization_code') {
95131
throwBadRequest('Unsupported grant_type');
96132
}
97133

134+
// Extract client credentials (supports Basic Auth or body)
135+
const credentials = extractClientCredentials(req);
136+
if (!credentials) {
137+
throwBadRequest('Client credentials required. Provide via Basic Auth header or request body (client_id, client_secret)');
138+
}
139+
140+
const { clientId, clientSecret } = credentials;
141+
98142
// Validate client credentials
99-
await validateClient(client_id, client_secret);
143+
await validateClient(clientId, clientSecret);
100144

101145
// Validate and exchange authorization code
102-
const { userId, scope } = validateAuthCode(code, client_id, redirect_uri);
146+
const { userId, scope } = validateAuthCode(code, clientId, redirect_uri);
103147

104148
// Get user details for token issuance
105149
const db = getDb();

0 commit comments

Comments
 (0)