@shin1ohno/sage 0.3.0 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/http-server-with-config.d.ts +38 -0
- package/dist/cli/http-server-with-config.d.ts.map +1 -0
- package/dist/cli/http-server-with-config.js +458 -0
- package/dist/cli/http-server-with-config.js.map +1 -0
- package/dist/cli/http-server.d.ts +74 -0
- package/dist/cli/http-server.d.ts.map +1 -0
- package/dist/cli/http-server.js +407 -0
- package/dist/cli/http-server.js.map +1 -0
- package/dist/cli/jwt-middleware.d.ts +36 -0
- package/dist/cli/jwt-middleware.d.ts.map +1 -0
- package/dist/cli/jwt-middleware.js +99 -0
- package/dist/cli/jwt-middleware.js.map +1 -0
- package/dist/cli/main-entry.d.ts +41 -0
- package/dist/cli/main-entry.d.ts.map +1 -0
- package/dist/cli/main-entry.js +80 -0
- package/dist/cli/main-entry.js.map +1 -0
- package/dist/cli/mcp-handler.d.ts +56 -0
- package/dist/cli/mcp-handler.d.ts.map +1 -0
- package/dist/cli/mcp-handler.js +2189 -0
- package/dist/cli/mcp-handler.js.map +1 -0
- package/dist/cli/parser.d.ts +43 -0
- package/dist/cli/parser.d.ts.map +1 -0
- package/dist/cli/parser.js +162 -0
- package/dist/cli/parser.js.map +1 -0
- package/dist/cli/remote-config-loader.d.ts +85 -0
- package/dist/cli/remote-config-loader.d.ts.map +1 -0
- package/dist/cli/remote-config-loader.js +129 -0
- package/dist/cli/remote-config-loader.js.map +1 -0
- package/dist/cli/secret-auth.d.ts +47 -0
- package/dist/cli/secret-auth.d.ts.map +1 -0
- package/dist/cli/secret-auth.js +165 -0
- package/dist/cli/secret-auth.js.map +1 -0
- package/dist/cli/sse-stream-handler.d.ts +45 -0
- package/dist/cli/sse-stream-handler.d.ts.map +1 -0
- package/dist/cli/sse-stream-handler.js +125 -0
- package/dist/cli/sse-stream-handler.js.map +1 -0
- package/dist/index.js +885 -209
- package/dist/index.js.map +1 -1
- package/dist/integrations/calendar-event-creator.d.ts +152 -0
- package/dist/integrations/calendar-event-creator.d.ts.map +1 -0
- package/dist/integrations/calendar-event-creator.js +507 -0
- package/dist/integrations/calendar-event-creator.js.map +1 -0
- package/dist/integrations/calendar-event-deleter.d.ts +137 -0
- package/dist/integrations/calendar-event-deleter.d.ts.map +1 -0
- package/dist/integrations/calendar-event-deleter.js +378 -0
- package/dist/integrations/calendar-event-deleter.js.map +1 -0
- package/dist/integrations/calendar-event-response.d.ts +213 -0
- package/dist/integrations/calendar-event-response.d.ts.map +1 -0
- package/dist/integrations/calendar-event-response.js +560 -0
- package/dist/integrations/calendar-event-response.js.map +1 -0
- package/dist/integrations/calendar-service.d.ts +66 -1
- package/dist/integrations/calendar-service.d.ts.map +1 -1
- package/dist/integrations/calendar-service.js +223 -0
- package/dist/integrations/calendar-service.js.map +1 -1
- package/dist/oauth/client-store.d.ts +36 -0
- package/dist/oauth/client-store.d.ts.map +1 -0
- package/dist/oauth/client-store.js +104 -0
- package/dist/oauth/client-store.js.map +1 -0
- package/dist/oauth/code-store.d.ts +48 -0
- package/dist/oauth/code-store.d.ts.map +1 -0
- package/dist/oauth/code-store.js +89 -0
- package/dist/oauth/code-store.js.map +1 -0
- package/dist/oauth/index.d.ts +13 -0
- package/dist/oauth/index.d.ts.map +1 -0
- package/dist/oauth/index.js +21 -0
- package/dist/oauth/index.js.map +1 -0
- package/dist/oauth/oauth-handler.d.ts +101 -0
- package/dist/oauth/oauth-handler.d.ts.map +1 -0
- package/dist/oauth/oauth-handler.js +577 -0
- package/dist/oauth/oauth-handler.js.map +1 -0
- package/dist/oauth/oauth-server.d.ts +165 -0
- package/dist/oauth/oauth-server.d.ts.map +1 -0
- package/dist/oauth/oauth-server.js +489 -0
- package/dist/oauth/oauth-server.js.map +1 -0
- package/dist/oauth/pkce.d.ts +48 -0
- package/dist/oauth/pkce.d.ts.map +1 -0
- package/dist/oauth/pkce.js +106 -0
- package/dist/oauth/pkce.js.map +1 -0
- package/dist/oauth/refresh-token-store.d.ts +45 -0
- package/dist/oauth/refresh-token-store.d.ts.map +1 -0
- package/dist/oauth/refresh-token-store.js +98 -0
- package/dist/oauth/refresh-token-store.js.map +1 -0
- package/dist/oauth/token-service.d.ts +46 -0
- package/dist/oauth/token-service.d.ts.map +1 -0
- package/dist/oauth/token-service.js +199 -0
- package/dist/oauth/token-service.js.map +1 -0
- package/dist/oauth/types.d.ts +264 -0
- package/dist/oauth/types.d.ts.map +1 -0
- package/dist/oauth/types.js +37 -0
- package/dist/oauth/types.js.map +1 -0
- package/dist/version.d.ts +9 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +11 -0
- package/dist/version.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.1 Server
|
|
3
|
+
* Requirements: 21-31 (OAuth 2.1 Authentication)
|
|
4
|
+
*
|
|
5
|
+
* Main OAuth server that coordinates all OAuth components and handles requests.
|
|
6
|
+
*/
|
|
7
|
+
import { randomBytes, createHash } from 'crypto';
|
|
8
|
+
import { SCOPE_DEFINITIONS, DEFAULT_TOKEN_EXPIRY, CLAUDE_CALLBACK_URLS, } from './types.js';
|
|
9
|
+
import { createTokenService, generateKeyPair } from './token-service.js';
|
|
10
|
+
import { createAuthorizationCodeStore } from './code-store.js';
|
|
11
|
+
import { createRefreshTokenStore } from './refresh-token-store.js';
|
|
12
|
+
import { createClientStore } from './client-store.js';
|
|
13
|
+
import { verifyCodeChallenge } from './pkce.js';
|
|
14
|
+
/**
|
|
15
|
+
* In-memory Session Store
|
|
16
|
+
*/
|
|
17
|
+
class InMemorySessionStore {
|
|
18
|
+
sessions = new Map();
|
|
19
|
+
sessionExpiryMs = 24 * 60 * 60 * 1000; // 24 hours
|
|
20
|
+
createSession(userId) {
|
|
21
|
+
const sessionId = randomBytes(32).toString('hex');
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
const session = {
|
|
24
|
+
sessionId,
|
|
25
|
+
userId,
|
|
26
|
+
createdAt: now,
|
|
27
|
+
expiresAt: now + this.sessionExpiryMs,
|
|
28
|
+
};
|
|
29
|
+
this.sessions.set(sessionId, session);
|
|
30
|
+
return session;
|
|
31
|
+
}
|
|
32
|
+
getSession(sessionId) {
|
|
33
|
+
const session = this.sessions.get(sessionId);
|
|
34
|
+
if (!session)
|
|
35
|
+
return null;
|
|
36
|
+
if (Date.now() > session.expiresAt) {
|
|
37
|
+
this.sessions.delete(sessionId);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return session;
|
|
41
|
+
}
|
|
42
|
+
deleteSession(sessionId) {
|
|
43
|
+
this.sessions.delete(sessionId);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* OAuth Server Class
|
|
48
|
+
*/
|
|
49
|
+
export class OAuthServer {
|
|
50
|
+
config;
|
|
51
|
+
tokenService;
|
|
52
|
+
codeStore;
|
|
53
|
+
refreshTokenStore;
|
|
54
|
+
clientStore;
|
|
55
|
+
sessionStore;
|
|
56
|
+
users;
|
|
57
|
+
pendingAuthRequests = new Map();
|
|
58
|
+
loginAttempts = new Map();
|
|
59
|
+
privateKey;
|
|
60
|
+
publicKey;
|
|
61
|
+
constructor(config, keys) {
|
|
62
|
+
this.config = config;
|
|
63
|
+
this.privateKey = keys?.privateKey || '';
|
|
64
|
+
this.publicKey = keys?.publicKey || '';
|
|
65
|
+
// Parse expiry durations to seconds
|
|
66
|
+
const refreshTokenExpirySec = this.parseExpiryToSeconds(config.refreshTokenExpiry || DEFAULT_TOKEN_EXPIRY.refreshToken);
|
|
67
|
+
const authCodeExpirySec = this.parseExpiryToSeconds(config.authorizationCodeExpiry || DEFAULT_TOKEN_EXPIRY.authorizationCode);
|
|
68
|
+
// Initialize stores
|
|
69
|
+
this.codeStore = createAuthorizationCodeStore({ expirySeconds: authCodeExpirySec });
|
|
70
|
+
this.refreshTokenStore = createRefreshTokenStore({ expirySeconds: refreshTokenExpirySec });
|
|
71
|
+
this.clientStore = createClientStore({
|
|
72
|
+
allowedRedirectUris: [...(config.allowedRedirectUris || []), ...CLAUDE_CALLBACK_URLS],
|
|
73
|
+
});
|
|
74
|
+
this.sessionStore = new InMemorySessionStore();
|
|
75
|
+
// Initialize token service (will be updated when keys are available)
|
|
76
|
+
this.tokenService = createTokenService({
|
|
77
|
+
issuer: config.issuer,
|
|
78
|
+
privateKey: this.privateKey,
|
|
79
|
+
publicKey: this.publicKey,
|
|
80
|
+
accessTokenExpiry: config.accessTokenExpiry || DEFAULT_TOKEN_EXPIRY.accessToken,
|
|
81
|
+
});
|
|
82
|
+
// Store users
|
|
83
|
+
this.users = new Map();
|
|
84
|
+
for (const user of config.users || []) {
|
|
85
|
+
this.users.set(user.username, user);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Initialize the server with generated keys if not provided
|
|
90
|
+
*/
|
|
91
|
+
async initialize() {
|
|
92
|
+
if (!this.privateKey || !this.publicKey) {
|
|
93
|
+
const keys = await generateKeyPair();
|
|
94
|
+
this.privateKey = keys.privateKey;
|
|
95
|
+
this.publicKey = keys.publicKey;
|
|
96
|
+
// Recreate token service with new keys
|
|
97
|
+
this.tokenService = createTokenService({
|
|
98
|
+
issuer: this.config.issuer,
|
|
99
|
+
privateKey: this.privateKey,
|
|
100
|
+
publicKey: this.publicKey,
|
|
101
|
+
accessTokenExpiry: this.config.accessTokenExpiry || DEFAULT_TOKEN_EXPIRY.accessToken,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
parseExpiryToSeconds(expiry) {
|
|
106
|
+
const match = expiry.match(/^(\d+)([smhdw])$/);
|
|
107
|
+
if (!match)
|
|
108
|
+
return 3600;
|
|
109
|
+
const value = parseInt(match[1], 10);
|
|
110
|
+
const unit = match[2];
|
|
111
|
+
switch (unit) {
|
|
112
|
+
case 's': return value;
|
|
113
|
+
case 'm': return value * 60;
|
|
114
|
+
case 'h': return value * 3600;
|
|
115
|
+
case 'd': return value * 86400;
|
|
116
|
+
case 'w': return value * 604800;
|
|
117
|
+
default: return 3600;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get Protected Resource Metadata (RFC 9728)
|
|
122
|
+
* Requirement 22.1-22.3
|
|
123
|
+
*/
|
|
124
|
+
getProtectedResourceMetadata() {
|
|
125
|
+
return {
|
|
126
|
+
resource: this.config.issuer,
|
|
127
|
+
authorization_servers: [this.config.issuer],
|
|
128
|
+
scopes_supported: Object.keys(SCOPE_DEFINITIONS),
|
|
129
|
+
bearer_methods_supported: ['header'],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get Authorization Server Metadata (RFC 8414)
|
|
134
|
+
* Requirement 23.1-23.9
|
|
135
|
+
*/
|
|
136
|
+
getAuthorizationServerMetadata() {
|
|
137
|
+
return {
|
|
138
|
+
issuer: this.config.issuer,
|
|
139
|
+
authorization_endpoint: `${this.config.issuer}/oauth/authorize`,
|
|
140
|
+
token_endpoint: `${this.config.issuer}/oauth/token`,
|
|
141
|
+
registration_endpoint: `${this.config.issuer}/oauth/register`,
|
|
142
|
+
scopes_supported: Object.keys(SCOPE_DEFINITIONS),
|
|
143
|
+
response_types_supported: ['code'],
|
|
144
|
+
response_modes_supported: ['query'],
|
|
145
|
+
grant_types_supported: ['authorization_code', 'refresh_token'],
|
|
146
|
+
token_endpoint_auth_methods_supported: ['none', 'client_secret_post'],
|
|
147
|
+
code_challenge_methods_supported: ['S256'],
|
|
148
|
+
service_documentation: 'https://github.com/shin1ohno/sage',
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Get WWW-Authenticate header for 401 responses
|
|
153
|
+
* Requirement 22.4, 22.5
|
|
154
|
+
*/
|
|
155
|
+
getWWWAuthenticateHeader() {
|
|
156
|
+
return `Bearer realm="sage", resource_metadata="${this.config.issuer}/.well-known/oauth-protected-resource"`;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Register a new client (Dynamic Client Registration)
|
|
160
|
+
* Requirement 24.1-24.7
|
|
161
|
+
*/
|
|
162
|
+
async registerClient(request) {
|
|
163
|
+
return this.clientStore.registerClient(request);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get a registered client
|
|
167
|
+
*/
|
|
168
|
+
async getClient(clientId) {
|
|
169
|
+
return this.clientStore.getClient(clientId);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Delete a client (Requirement 24.8)
|
|
173
|
+
*/
|
|
174
|
+
async deleteClient(clientId) {
|
|
175
|
+
// Also revoke all refresh tokens for this client
|
|
176
|
+
await this.refreshTokenStore.revokeAllForClient(clientId);
|
|
177
|
+
return this.clientStore.deleteClient(clientId);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Validate an authorization request
|
|
181
|
+
* Requirement 25.1-25.8
|
|
182
|
+
*/
|
|
183
|
+
async validateAuthorizationRequest(request) {
|
|
184
|
+
// Requirement 25.2: Only support response_type=code
|
|
185
|
+
if (request.response_type !== 'code') {
|
|
186
|
+
return {
|
|
187
|
+
valid: false,
|
|
188
|
+
error: {
|
|
189
|
+
error: 'unsupported_response_type',
|
|
190
|
+
error_description: 'Only response_type=code is supported',
|
|
191
|
+
state: request.state,
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
// Requirement 25.3: client_id is required
|
|
196
|
+
const client = await this.clientStore.getClient(request.client_id);
|
|
197
|
+
if (!client) {
|
|
198
|
+
return {
|
|
199
|
+
valid: false,
|
|
200
|
+
error: {
|
|
201
|
+
error: 'invalid_client',
|
|
202
|
+
error_description: 'Unknown client_id',
|
|
203
|
+
state: request.state,
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
// Requirement 25.4: Validate redirect_uri
|
|
208
|
+
if (!await this.clientStore.isValidRedirectUri(request.client_id, request.redirect_uri)) {
|
|
209
|
+
return {
|
|
210
|
+
valid: false,
|
|
211
|
+
error: {
|
|
212
|
+
error: 'invalid_request',
|
|
213
|
+
error_description: 'Invalid redirect_uri',
|
|
214
|
+
state: request.state,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
// Requirement 25.5, 25.6: PKCE is required, only S256
|
|
219
|
+
if (!request.code_challenge) {
|
|
220
|
+
return {
|
|
221
|
+
valid: false,
|
|
222
|
+
error: {
|
|
223
|
+
error: 'invalid_request',
|
|
224
|
+
error_description: 'code_challenge is required',
|
|
225
|
+
state: request.state,
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (request.code_challenge_method !== 'S256') {
|
|
230
|
+
return {
|
|
231
|
+
valid: false,
|
|
232
|
+
error: {
|
|
233
|
+
error: 'invalid_request',
|
|
234
|
+
error_description: 'Only code_challenge_method=S256 is supported',
|
|
235
|
+
state: request.state,
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
// Requirement 25.7: state is required
|
|
240
|
+
if (!request.state) {
|
|
241
|
+
return {
|
|
242
|
+
valid: false,
|
|
243
|
+
error: {
|
|
244
|
+
error: 'invalid_request',
|
|
245
|
+
error_description: 'state is required',
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
return { valid: true, client };
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Store a pending authorization request (for consent flow)
|
|
253
|
+
*/
|
|
254
|
+
storePendingAuthRequest(requestId, request, client) {
|
|
255
|
+
this.pendingAuthRequests.set(requestId, {
|
|
256
|
+
request,
|
|
257
|
+
client,
|
|
258
|
+
createdAt: Date.now(),
|
|
259
|
+
});
|
|
260
|
+
// Cleanup old requests (older than 10 minutes)
|
|
261
|
+
const cutoff = Date.now() - 10 * 60 * 1000;
|
|
262
|
+
for (const [id, pending] of this.pendingAuthRequests.entries()) {
|
|
263
|
+
if (pending.createdAt < cutoff) {
|
|
264
|
+
this.pendingAuthRequests.delete(id);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Get a pending authorization request
|
|
270
|
+
*/
|
|
271
|
+
getPendingAuthRequest(requestId) {
|
|
272
|
+
return this.pendingAuthRequests.get(requestId) || null;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Complete authorization and generate code
|
|
276
|
+
* Requirement 25.9, 25.10
|
|
277
|
+
*/
|
|
278
|
+
async completeAuthorization(request, userId) {
|
|
279
|
+
const code = await this.codeStore.generateCode({
|
|
280
|
+
clientId: request.client_id,
|
|
281
|
+
redirectUri: request.redirect_uri,
|
|
282
|
+
scope: request.scope || '',
|
|
283
|
+
codeChallenge: request.code_challenge,
|
|
284
|
+
codeChallengeMethod: request.code_challenge_method,
|
|
285
|
+
userId,
|
|
286
|
+
resource: request.resource,
|
|
287
|
+
});
|
|
288
|
+
return code;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Exchange authorization code for tokens
|
|
292
|
+
* Requirement 26.1-26.7
|
|
293
|
+
*/
|
|
294
|
+
async exchangeAuthorizationCode(code, clientId, redirectUri, codeVerifier, resource) {
|
|
295
|
+
// Consume code (marks it as used)
|
|
296
|
+
const codeResult = await this.codeStore.consumeCode(code, clientId);
|
|
297
|
+
if (!codeResult.valid || !codeResult.codeData) {
|
|
298
|
+
return {
|
|
299
|
+
success: false,
|
|
300
|
+
error: {
|
|
301
|
+
error: 'invalid_grant',
|
|
302
|
+
error_description: 'Invalid or expired authorization code',
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
// Verify redirect_uri matches
|
|
307
|
+
if (codeResult.codeData.redirect_uri !== redirectUri) {
|
|
308
|
+
return {
|
|
309
|
+
success: false,
|
|
310
|
+
error: {
|
|
311
|
+
error: 'invalid_grant',
|
|
312
|
+
error_description: 'redirect_uri does not match',
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
// Verify PKCE code_verifier (Requirement 26.4)
|
|
317
|
+
if (!verifyCodeChallenge(codeVerifier, codeResult.codeData.code_challenge, 'S256')) {
|
|
318
|
+
return {
|
|
319
|
+
success: false,
|
|
320
|
+
error: {
|
|
321
|
+
error: 'invalid_grant',
|
|
322
|
+
error_description: 'Invalid code_verifier',
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
// Verify resource if specified (Requirement 26.5)
|
|
327
|
+
if (resource && codeResult.codeData.resource && resource !== codeResult.codeData.resource) {
|
|
328
|
+
return {
|
|
329
|
+
success: false,
|
|
330
|
+
error: {
|
|
331
|
+
error: 'invalid_grant',
|
|
332
|
+
error_description: 'resource does not match',
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
// Generate access token
|
|
337
|
+
const accessTokenResponse = await this.tokenService.generateAccessToken({
|
|
338
|
+
clientId,
|
|
339
|
+
userId: codeResult.codeData.user_id,
|
|
340
|
+
scope: codeResult.codeData.scope,
|
|
341
|
+
audience: resource || this.config.issuer,
|
|
342
|
+
});
|
|
343
|
+
// Generate refresh token (Requirement 21.6)
|
|
344
|
+
const refreshToken = await this.refreshTokenStore.generateToken({
|
|
345
|
+
clientId,
|
|
346
|
+
userId: codeResult.codeData.user_id,
|
|
347
|
+
scope: codeResult.codeData.scope,
|
|
348
|
+
});
|
|
349
|
+
return {
|
|
350
|
+
success: true,
|
|
351
|
+
tokens: {
|
|
352
|
+
...accessTokenResponse,
|
|
353
|
+
refresh_token: refreshToken,
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Exchange refresh token for new tokens
|
|
359
|
+
* Requirement 26.3, 26.8
|
|
360
|
+
*/
|
|
361
|
+
async exchangeRefreshToken(refreshToken, clientId, scope) {
|
|
362
|
+
// Rotate refresh token (Requirement 26.8)
|
|
363
|
+
const newRefreshToken = await this.refreshTokenStore.rotateToken(refreshToken, clientId);
|
|
364
|
+
if (!newRefreshToken) {
|
|
365
|
+
return {
|
|
366
|
+
success: false,
|
|
367
|
+
error: {
|
|
368
|
+
error: 'invalid_grant',
|
|
369
|
+
error_description: 'Invalid or expired refresh token',
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
// Get the original token data before it was rotated
|
|
374
|
+
const tokenResult = await this.refreshTokenStore.validateToken(newRefreshToken, clientId);
|
|
375
|
+
if (!tokenResult.valid || !tokenResult.tokenData) {
|
|
376
|
+
return {
|
|
377
|
+
success: false,
|
|
378
|
+
error: {
|
|
379
|
+
error: 'invalid_grant',
|
|
380
|
+
error_description: 'Invalid refresh token',
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
// Use requested scope or original scope
|
|
385
|
+
const effectiveScope = scope || tokenResult.tokenData.scope;
|
|
386
|
+
// Generate new access token
|
|
387
|
+
const accessTokenResponse = await this.tokenService.generateAccessToken({
|
|
388
|
+
clientId,
|
|
389
|
+
userId: tokenResult.tokenData.user_id,
|
|
390
|
+
scope: effectiveScope,
|
|
391
|
+
audience: this.config.issuer,
|
|
392
|
+
});
|
|
393
|
+
return {
|
|
394
|
+
success: true,
|
|
395
|
+
tokens: {
|
|
396
|
+
...accessTokenResponse,
|
|
397
|
+
refresh_token: newRefreshToken,
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Verify an access token
|
|
403
|
+
* Requirement 27.1-27.5
|
|
404
|
+
*/
|
|
405
|
+
async verifyAccessToken(token, expectedAudience) {
|
|
406
|
+
return this.tokenService.verifyAccessToken(token, expectedAudience);
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Extract token from Authorization header
|
|
410
|
+
* Requirement 27.1
|
|
411
|
+
*/
|
|
412
|
+
extractTokenFromHeader(header) {
|
|
413
|
+
return this.tokenService.extractTokenFromHeader(header);
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Authenticate a user (Requirement 29.1-29.5)
|
|
417
|
+
*/
|
|
418
|
+
async authenticateUser(username, password) {
|
|
419
|
+
// Rate limiting (Requirement 29.5)
|
|
420
|
+
const key = username;
|
|
421
|
+
const attempts = this.loginAttempts.get(key) || { count: 0, lastAttempt: 0 };
|
|
422
|
+
// Reset attempts after 15 minutes
|
|
423
|
+
if (Date.now() - attempts.lastAttempt > 15 * 60 * 1000) {
|
|
424
|
+
attempts.count = 0;
|
|
425
|
+
}
|
|
426
|
+
if (attempts.count >= 5) {
|
|
427
|
+
return { success: false, error: 'Too many login attempts. Please try again later.' };
|
|
428
|
+
}
|
|
429
|
+
const user = this.users.get(username);
|
|
430
|
+
if (!user) {
|
|
431
|
+
attempts.count++;
|
|
432
|
+
attempts.lastAttempt = Date.now();
|
|
433
|
+
this.loginAttempts.set(key, attempts);
|
|
434
|
+
return { success: false, error: 'Invalid username or password' };
|
|
435
|
+
}
|
|
436
|
+
// Verify password (Requirement 29.4: passwords should be hashed)
|
|
437
|
+
// For simplicity, we'll compare against the stored hash
|
|
438
|
+
// In production, use bcrypt or similar
|
|
439
|
+
const passwordHash = createHash('sha256').update(password).digest('hex');
|
|
440
|
+
if (passwordHash !== user.passwordHash) {
|
|
441
|
+
attempts.count++;
|
|
442
|
+
attempts.lastAttempt = Date.now();
|
|
443
|
+
this.loginAttempts.set(key, attempts);
|
|
444
|
+
return { success: false, error: 'Invalid username or password' };
|
|
445
|
+
}
|
|
446
|
+
// Reset login attempts on success
|
|
447
|
+
this.loginAttempts.delete(key);
|
|
448
|
+
// Create session
|
|
449
|
+
const session = this.sessionStore.createSession(user.id);
|
|
450
|
+
return { success: true, session };
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Validate a session
|
|
454
|
+
*/
|
|
455
|
+
validateSession(sessionId) {
|
|
456
|
+
return this.sessionStore.getSession(sessionId);
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Logout user
|
|
460
|
+
*/
|
|
461
|
+
logout(sessionId) {
|
|
462
|
+
this.sessionStore.deleteSession(sessionId);
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Check if a scope includes another scope
|
|
466
|
+
*/
|
|
467
|
+
hasScope(tokenScope, requiredScope) {
|
|
468
|
+
const tokenScopes = tokenScope.split(' ');
|
|
469
|
+
return tokenScopes.includes(requiredScope);
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Get scope descriptions for consent UI
|
|
473
|
+
*/
|
|
474
|
+
getScopeDescriptions(scopes) {
|
|
475
|
+
return scopes.split(' ').filter(s => s in SCOPE_DEFINITIONS).map(scope => ({
|
|
476
|
+
scope,
|
|
477
|
+
description: SCOPE_DEFINITIONS[scope],
|
|
478
|
+
}));
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Create an OAuth Server instance
|
|
483
|
+
*/
|
|
484
|
+
export async function createOAuthServer(config) {
|
|
485
|
+
const server = new OAuthServer(config);
|
|
486
|
+
await server.initialize();
|
|
487
|
+
return server;
|
|
488
|
+
}
|
|
489
|
+
//# sourceMappingURL=oauth-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-server.js","sourceRoot":"","sources":["../../src/oauth/oauth-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth PKCE (S256) Implementation
|
|
3
|
+
* Requirements: 21.2, 26.4
|
|
4
|
+
*
|
|
5
|
+
* Implements PKCE (Proof Key for Code Exchange) using S256 method.
|
|
6
|
+
* Based on RFC 7636.
|
|
7
|
+
*/
|
|
8
|
+
import { CodeChallengeMethod } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Generate a cryptographically random code verifier
|
|
11
|
+
*
|
|
12
|
+
* @param length - Length of the verifier (default: 64, min: 43, max: 128)
|
|
13
|
+
* @returns A random code verifier string
|
|
14
|
+
*/
|
|
15
|
+
export declare function generateCodeVerifier(length?: number): string;
|
|
16
|
+
/**
|
|
17
|
+
* Generate a code challenge from a code verifier using S256 method
|
|
18
|
+
*
|
|
19
|
+
* S256: BASE64URL(SHA256(code_verifier))
|
|
20
|
+
*
|
|
21
|
+
* @param codeVerifier - The code verifier to hash
|
|
22
|
+
* @returns Base64URL encoded SHA256 hash of the verifier
|
|
23
|
+
*/
|
|
24
|
+
export declare function generateCodeChallenge(codeVerifier: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Verify a code verifier against a code challenge
|
|
27
|
+
*
|
|
28
|
+
* @param codeVerifier - The code verifier from the token request
|
|
29
|
+
* @param codeChallenge - The code challenge from the authorization request
|
|
30
|
+
* @param method - The code challenge method (only 'S256' is supported)
|
|
31
|
+
* @returns True if the verifier matches the challenge
|
|
32
|
+
*/
|
|
33
|
+
export declare function verifyCodeChallenge(codeVerifier: string, codeChallenge: string, method: CodeChallengeMethod): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Validate code verifier format
|
|
36
|
+
*
|
|
37
|
+
* @param codeVerifier - The code verifier to validate
|
|
38
|
+
* @returns True if the verifier has valid format
|
|
39
|
+
*/
|
|
40
|
+
export declare function isValidCodeVerifier(codeVerifier: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Validate code challenge format
|
|
43
|
+
*
|
|
44
|
+
* @param codeChallenge - The code challenge to validate
|
|
45
|
+
* @returns True if the challenge has valid format
|
|
46
|
+
*/
|
|
47
|
+
export declare function isValidCodeChallenge(codeChallenge: string): boolean;
|
|
48
|
+
//# sourceMappingURL=pkce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../../src/oauth/pkce.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAkBjD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,GAAE,MAAgC,GAAG,MAAM,CAYrF;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CASlE;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAQT;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAajE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAanE"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth PKCE (S256) Implementation
|
|
3
|
+
* Requirements: 21.2, 26.4
|
|
4
|
+
*
|
|
5
|
+
* Implements PKCE (Proof Key for Code Exchange) using S256 method.
|
|
6
|
+
* Based on RFC 7636.
|
|
7
|
+
*/
|
|
8
|
+
import { createHash, randomBytes } from 'crypto';
|
|
9
|
+
/**
|
|
10
|
+
* Characters allowed in code verifier (unreserved characters per RFC 7636)
|
|
11
|
+
*/
|
|
12
|
+
const UNRESERVED_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
|
|
13
|
+
/**
|
|
14
|
+
* Default code verifier length
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_VERIFIER_LENGTH = 64;
|
|
17
|
+
/**
|
|
18
|
+
* Minimum and maximum verifier length per RFC 7636
|
|
19
|
+
*/
|
|
20
|
+
const MIN_VERIFIER_LENGTH = 43;
|
|
21
|
+
const MAX_VERIFIER_LENGTH = 128;
|
|
22
|
+
/**
|
|
23
|
+
* Generate a cryptographically random code verifier
|
|
24
|
+
*
|
|
25
|
+
* @param length - Length of the verifier (default: 64, min: 43, max: 128)
|
|
26
|
+
* @returns A random code verifier string
|
|
27
|
+
*/
|
|
28
|
+
export function generateCodeVerifier(length = DEFAULT_VERIFIER_LENGTH) {
|
|
29
|
+
// Clamp length to valid range
|
|
30
|
+
const validLength = Math.max(MIN_VERIFIER_LENGTH, Math.min(MAX_VERIFIER_LENGTH, length));
|
|
31
|
+
const bytes = randomBytes(validLength);
|
|
32
|
+
let verifier = '';
|
|
33
|
+
for (let i = 0; i < validLength; i++) {
|
|
34
|
+
verifier += UNRESERVED_CHARS[bytes[i] % UNRESERVED_CHARS.length];
|
|
35
|
+
}
|
|
36
|
+
return verifier;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Generate a code challenge from a code verifier using S256 method
|
|
40
|
+
*
|
|
41
|
+
* S256: BASE64URL(SHA256(code_verifier))
|
|
42
|
+
*
|
|
43
|
+
* @param codeVerifier - The code verifier to hash
|
|
44
|
+
* @returns Base64URL encoded SHA256 hash of the verifier
|
|
45
|
+
*/
|
|
46
|
+
export function generateCodeChallenge(codeVerifier) {
|
|
47
|
+
// Calculate SHA256 hash
|
|
48
|
+
const hash = createHash('sha256').update(codeVerifier, 'ascii').digest();
|
|
49
|
+
// Convert to base64url encoding (no padding)
|
|
50
|
+
const base64 = hash.toString('base64');
|
|
51
|
+
const base64url = base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
52
|
+
return base64url;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Verify a code verifier against a code challenge
|
|
56
|
+
*
|
|
57
|
+
* @param codeVerifier - The code verifier from the token request
|
|
58
|
+
* @param codeChallenge - The code challenge from the authorization request
|
|
59
|
+
* @param method - The code challenge method (only 'S256' is supported)
|
|
60
|
+
* @returns True if the verifier matches the challenge
|
|
61
|
+
*/
|
|
62
|
+
export function verifyCodeChallenge(codeVerifier, codeChallenge, method) {
|
|
63
|
+
if (method !== 'S256') {
|
|
64
|
+
throw new Error('Only S256 code challenge method is supported');
|
|
65
|
+
}
|
|
66
|
+
// Generate challenge from verifier and compare
|
|
67
|
+
const expectedChallenge = generateCodeChallenge(codeVerifier);
|
|
68
|
+
return expectedChallenge === codeChallenge;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Validate code verifier format
|
|
72
|
+
*
|
|
73
|
+
* @param codeVerifier - The code verifier to validate
|
|
74
|
+
* @returns True if the verifier has valid format
|
|
75
|
+
*/
|
|
76
|
+
export function isValidCodeVerifier(codeVerifier) {
|
|
77
|
+
if (!codeVerifier) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
// Check length
|
|
81
|
+
if (codeVerifier.length < MIN_VERIFIER_LENGTH || codeVerifier.length > MAX_VERIFIER_LENGTH) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
// Check characters (unreserved characters only)
|
|
85
|
+
const validPattern = /^[A-Za-z0-9\-._~]+$/;
|
|
86
|
+
return validPattern.test(codeVerifier);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Validate code challenge format
|
|
90
|
+
*
|
|
91
|
+
* @param codeChallenge - The code challenge to validate
|
|
92
|
+
* @returns True if the challenge has valid format
|
|
93
|
+
*/
|
|
94
|
+
export function isValidCodeChallenge(codeChallenge) {
|
|
95
|
+
if (!codeChallenge) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
// S256 challenge should be base64url encoded SHA256 hash (43 characters)
|
|
99
|
+
if (codeChallenge.length !== 43) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
// Check base64url format (no padding)
|
|
103
|
+
const validPattern = /^[A-Za-z0-9\-_]+$/;
|
|
104
|
+
return validPattern.test(codeChallenge);
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=pkce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../src/oauth/pkce.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAGjD;;GAEG;AACH,MAAM,gBAAgB,GAAG,oEAAoE,CAAC;AAE9F;;GAEG;AACH,MAAM,uBAAuB,GAAG,EAAE,CAAC;AAEnC;;GAEG;AACH,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAC/B,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAEhC;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,SAAiB,uBAAuB;IAC3E,8BAA8B;IAC9B,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,mBAAmB,EAAE,IAAI,CAAC,GAAG,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC,CAAC;IAEzF,MAAM,KAAK,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;IACvC,IAAI,QAAQ,GAAG,EAAE,CAAC;IAElB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,QAAQ,IAAI,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACnE,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,YAAoB;IACxD,wBAAwB;IACxB,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC;IAEzE,6CAA6C;IAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAEpF,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CACjC,YAAoB,EACpB,aAAqB,EACrB,MAA2B;IAE3B,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IAED,+CAA+C;IAC/C,MAAM,iBAAiB,GAAG,qBAAqB,CAAC,YAAY,CAAC,CAAC;IAC9D,OAAO,iBAAiB,KAAK,aAAa,CAAC;AAC7C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,YAAoB;IACtD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,eAAe;IACf,IAAI,YAAY,CAAC,MAAM,GAAG,mBAAmB,IAAI,YAAY,CAAC,MAAM,GAAG,mBAAmB,EAAE,CAAC;QAC3F,OAAO,KAAK,CAAC;IACf,CAAC;IAED,gDAAgD;IAChD,MAAM,YAAY,GAAG,qBAAqB,CAAC;IAC3C,OAAO,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;AACzC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,aAAqB;IACxD,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,yEAAyE;IACzE,IAAI,aAAa,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QAChC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,sCAAsC;IACtC,MAAM,YAAY,GAAG,mBAAmB,CAAC;IACzC,OAAO,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;AAC1C,CAAC"}
|