@silkweave/auth 1.3.1 → 1.3.2
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/.turbo/turbo-build.log +17 -18
- package/.turbo/turbo-check.log +3 -7
- package/.turbo/turbo-lint.log +1 -1
- package/build/index.d.ts +41 -42
- package/build/index.js +251 -218
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/errors.ts +0 -4
- package/src/provider/index.ts +0 -1
- package/src/provider/proxy.ts +309 -253
- package/src/provider/types.ts +7 -1
- package/src/types.ts +2 -8
package/build/index.js
CHANGED
|
@@ -15,9 +15,6 @@ function invalidToken(message = "Invalid token") {
|
|
|
15
15
|
function insufficientScope(message = "Insufficient scope") {
|
|
16
16
|
return new AuthError(message, "insufficient_scope", 403);
|
|
17
17
|
}
|
|
18
|
-
function missingToken(message = "No authorization provided") {
|
|
19
|
-
return new AuthError(message, "missing_token", 401);
|
|
20
|
-
}
|
|
21
18
|
|
|
22
19
|
// src/extract.ts
|
|
23
20
|
function extractBearerToken(header) {
|
|
@@ -50,6 +47,10 @@ function generateProtectedResourceMetadata(resourceUrl, authorizationServers) {
|
|
|
50
47
|
};
|
|
51
48
|
}
|
|
52
49
|
|
|
50
|
+
// src/provider/proxy.ts
|
|
51
|
+
import { randomBytes, randomUUID } from "crypto";
|
|
52
|
+
import { SignJWT, jwtVerify } from "jose";
|
|
53
|
+
|
|
53
54
|
// src/store/memory-store.ts
|
|
54
55
|
function withTtl(map, key) {
|
|
55
56
|
const item = map.get(key);
|
|
@@ -122,10 +123,6 @@ function createMemoryStore() {
|
|
|
122
123
|
};
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
// src/provider/proxy.ts
|
|
126
|
-
import { randomBytes, randomUUID } from "crypto";
|
|
127
|
-
import { SignJWT, jwtVerify } from "jose";
|
|
128
|
-
|
|
129
126
|
// src/provider/uri.ts
|
|
130
127
|
function matchRedirectUri(uri, patterns) {
|
|
131
128
|
return patterns.some((pattern) => {
|
|
@@ -144,6 +141,233 @@ var CORS_HEADERS = {
|
|
|
144
141
|
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
145
142
|
"Access-Control-Allow-Headers": "*"
|
|
146
143
|
};
|
|
144
|
+
function generateCode() {
|
|
145
|
+
return randomBytes(32).toString("base64url");
|
|
146
|
+
}
|
|
147
|
+
async function generatePkce() {
|
|
148
|
+
const verifier = randomBytes(32).toString("base64url");
|
|
149
|
+
const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
150
|
+
const challenge = Buffer.from(hash).toString("base64url");
|
|
151
|
+
return { verifier, challenge };
|
|
152
|
+
}
|
|
153
|
+
async function verifyPkce(verifier, challenge) {
|
|
154
|
+
const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
155
|
+
const computed = Buffer.from(hash).toString("base64url");
|
|
156
|
+
return computed === challenge;
|
|
157
|
+
}
|
|
158
|
+
function jsonResponse(status, body, headers = {}) {
|
|
159
|
+
return {
|
|
160
|
+
status,
|
|
161
|
+
headers: { "Content-Type": "application/json", ...CORS_HEADERS, ...headers },
|
|
162
|
+
body
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function redirectResponse(url) {
|
|
166
|
+
return {
|
|
167
|
+
status: 302,
|
|
168
|
+
headers: { Location: url, ...CORS_HEADERS },
|
|
169
|
+
body: void 0
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function errorResponse(status, error, description) {
|
|
173
|
+
return jsonResponse(status, { error, error_description: description });
|
|
174
|
+
}
|
|
175
|
+
async function signAccessToken(key, opts) {
|
|
176
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
177
|
+
return new SignJWT({ scope: opts.scopes.join(" "), email: opts.email }).setProtectedHeader({ alg: "HS256" }).setSubject(opts.sub ?? opts.email ?? opts.clientId).setIssuer(opts.issuer).setAudience(opts.issuer).setIssuedAt(now).setExpirationTime(now + opts.ttl).sign(key);
|
|
178
|
+
}
|
|
179
|
+
async function handleRegister(req, store, allowedRedirectUris) {
|
|
180
|
+
const body = req.body;
|
|
181
|
+
if (!body) {
|
|
182
|
+
return errorResponse(400, "invalid_request", "Missing request body");
|
|
183
|
+
}
|
|
184
|
+
const redirectUris = body.redirect_uris;
|
|
185
|
+
if (!redirectUris) {
|
|
186
|
+
return errorResponse(400, "invalid_request", "redirect_uris is required");
|
|
187
|
+
}
|
|
188
|
+
let uris;
|
|
189
|
+
try {
|
|
190
|
+
uris = typeof redirectUris === "string" ? JSON.parse(redirectUris) : redirectUris;
|
|
191
|
+
} catch {
|
|
192
|
+
uris = [redirectUris];
|
|
193
|
+
}
|
|
194
|
+
if (!Array.isArray(uris) || uris.length === 0) {
|
|
195
|
+
return errorResponse(400, "invalid_request", "redirect_uris must be a non-empty array");
|
|
196
|
+
}
|
|
197
|
+
for (const uri of uris) {
|
|
198
|
+
if (!matchRedirectUri(uri, allowedRedirectUris)) {
|
|
199
|
+
return errorResponse(400, "invalid_redirect_uri", `Redirect URI not allowed: ${uri}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const clientId = randomUUID();
|
|
203
|
+
const clientSecret = randomBytes(32).toString("base64url");
|
|
204
|
+
const registration = {
|
|
205
|
+
clientId,
|
|
206
|
+
clientSecret,
|
|
207
|
+
redirectUris: uris,
|
|
208
|
+
clientName: body.client_name,
|
|
209
|
+
createdAt: Date.now() / 1e3
|
|
210
|
+
};
|
|
211
|
+
await store.saveClient(clientId, registration);
|
|
212
|
+
return jsonResponse(201, {
|
|
213
|
+
client_id: clientId,
|
|
214
|
+
client_secret: clientSecret,
|
|
215
|
+
redirect_uris: uris,
|
|
216
|
+
client_name: body.client_name,
|
|
217
|
+
token_endpoint_auth_method: "none"
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
async function resolveClient(clientId, store, allowedRedirectUris) {
|
|
221
|
+
const existing = await store.getClient(clientId);
|
|
222
|
+
if (existing) {
|
|
223
|
+
return existing;
|
|
224
|
+
}
|
|
225
|
+
if (!clientId.startsWith("https://")) {
|
|
226
|
+
return errorResponse(400, "invalid_client", "Unknown client_id");
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
const metaRes = await fetch(clientId);
|
|
230
|
+
if (!metaRes.ok) {
|
|
231
|
+
return errorResponse(400, "invalid_client", "Failed to fetch client metadata document");
|
|
232
|
+
}
|
|
233
|
+
const meta = await metaRes.json();
|
|
234
|
+
const metaRedirectUris = meta.redirect_uris;
|
|
235
|
+
if (!Array.isArray(metaRedirectUris) || metaRedirectUris.length === 0) {
|
|
236
|
+
return errorResponse(400, "invalid_client", "Client metadata must include redirect_uris");
|
|
237
|
+
}
|
|
238
|
+
for (const uri of metaRedirectUris) {
|
|
239
|
+
if (!matchRedirectUri(uri, allowedRedirectUris)) {
|
|
240
|
+
return errorResponse(400, "invalid_redirect_uri", `Redirect URI not allowed: ${uri}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const client = {
|
|
244
|
+
clientId,
|
|
245
|
+
clientSecret: "",
|
|
246
|
+
redirectUris: metaRedirectUris,
|
|
247
|
+
clientName: meta.client_name,
|
|
248
|
+
createdAt: Date.now() / 1e3
|
|
249
|
+
};
|
|
250
|
+
await store.saveClient(clientId, client);
|
|
251
|
+
return client;
|
|
252
|
+
} catch {
|
|
253
|
+
return errorResponse(400, "invalid_client", "Failed to fetch client metadata document");
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
async function exchangeUpstreamCode(code, config, callbackPath, pkceVerifier) {
|
|
257
|
+
const tokenBody = new URLSearchParams({
|
|
258
|
+
grant_type: "authorization_code",
|
|
259
|
+
code,
|
|
260
|
+
redirect_uri: `${config.resourceUrl}${callbackPath}`,
|
|
261
|
+
client_id: config.clientId,
|
|
262
|
+
client_secret: config.clientSecret,
|
|
263
|
+
code_verifier: pkceVerifier
|
|
264
|
+
});
|
|
265
|
+
const tokenResponse = await fetch(config.tokenUrl, {
|
|
266
|
+
method: "POST",
|
|
267
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
268
|
+
body: tokenBody.toString()
|
|
269
|
+
});
|
|
270
|
+
if (!tokenResponse.ok) {
|
|
271
|
+
const errorBody = await tokenResponse.text();
|
|
272
|
+
console.error("Upstream token exchange failed:", errorBody);
|
|
273
|
+
return errorResponse(502, "upstream_error", "Failed to exchange code with upstream provider");
|
|
274
|
+
}
|
|
275
|
+
const tokens = await tokenResponse.json();
|
|
276
|
+
return {
|
|
277
|
+
accessToken: tokens.access_token,
|
|
278
|
+
idToken: tokens.id_token
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
async function fetchUserinfo(userinfoUrl, accessToken) {
|
|
282
|
+
if (!userinfoUrl || !accessToken) {
|
|
283
|
+
return {};
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
const res = await fetch(userinfoUrl, {
|
|
287
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
288
|
+
});
|
|
289
|
+
if (res.ok) {
|
|
290
|
+
const userinfo = await res.json();
|
|
291
|
+
return { email: userinfo.email, sub: userinfo.sub };
|
|
292
|
+
}
|
|
293
|
+
} catch {
|
|
294
|
+
}
|
|
295
|
+
return {};
|
|
296
|
+
}
|
|
297
|
+
async function handleRefreshToken(body, store, getSigningKey, config) {
|
|
298
|
+
const refreshToken = body.refresh_token;
|
|
299
|
+
if (!refreshToken) {
|
|
300
|
+
return errorResponse(400, "invalid_request", "Missing refresh_token");
|
|
301
|
+
}
|
|
302
|
+
const tokenData = await store.getRefreshToken(refreshToken);
|
|
303
|
+
if (!tokenData) {
|
|
304
|
+
return errorResponse(400, "invalid_grant", "Invalid or expired refresh token");
|
|
305
|
+
}
|
|
306
|
+
const key = await getSigningKey();
|
|
307
|
+
const accessToken = await signAccessToken(key, {
|
|
308
|
+
scopes: tokenData.scopes,
|
|
309
|
+
email: tokenData.email,
|
|
310
|
+
sub: tokenData.sub,
|
|
311
|
+
clientId: tokenData.clientId,
|
|
312
|
+
issuer: config.resourceUrl,
|
|
313
|
+
ttl: config.tokenTtl
|
|
314
|
+
});
|
|
315
|
+
return jsonResponse(200, {
|
|
316
|
+
access_token: accessToken,
|
|
317
|
+
token_type: "Bearer",
|
|
318
|
+
expires_in: config.tokenTtl,
|
|
319
|
+
scope: tokenData.scopes.join(" "),
|
|
320
|
+
refresh_token: refreshToken
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
async function handleAuthorizationCode(body, store, getSigningKey, config) {
|
|
324
|
+
const code = body.code;
|
|
325
|
+
const redirectUri = body.redirect_uri;
|
|
326
|
+
const clientId = body.client_id;
|
|
327
|
+
const codeVerifier = body.code_verifier;
|
|
328
|
+
if (!code || !codeVerifier || !clientId) {
|
|
329
|
+
return errorResponse(400, "invalid_request", "Missing required parameters");
|
|
330
|
+
}
|
|
331
|
+
const authCode = await store.getAuthCode(code);
|
|
332
|
+
if (!authCode) {
|
|
333
|
+
return errorResponse(400, "invalid_grant", "Invalid or expired authorization code");
|
|
334
|
+
}
|
|
335
|
+
await store.deleteAuthCode(code);
|
|
336
|
+
if (authCode.clientId !== clientId) {
|
|
337
|
+
return errorResponse(400, "invalid_grant", "client_id mismatch");
|
|
338
|
+
}
|
|
339
|
+
if (redirectUri && authCode.redirectUri !== redirectUri) {
|
|
340
|
+
return errorResponse(400, "invalid_grant", "redirect_uri mismatch");
|
|
341
|
+
}
|
|
342
|
+
const pkceValid = await verifyPkce(codeVerifier, authCode.codeChallenge);
|
|
343
|
+
if (!pkceValid) {
|
|
344
|
+
return errorResponse(400, "invalid_grant", "PKCE verification failed");
|
|
345
|
+
}
|
|
346
|
+
const key = await getSigningKey();
|
|
347
|
+
const accessToken = await signAccessToken(key, {
|
|
348
|
+
scopes: authCode.scopes,
|
|
349
|
+
email: authCode.email,
|
|
350
|
+
sub: authCode.sub,
|
|
351
|
+
clientId: authCode.clientId,
|
|
352
|
+
issuer: config.resourceUrl,
|
|
353
|
+
ttl: config.tokenTtl
|
|
354
|
+
});
|
|
355
|
+
const refreshToken = randomBytes(32).toString("base64url");
|
|
356
|
+
await store.saveRefreshToken(refreshToken, {
|
|
357
|
+
clientId: authCode.clientId,
|
|
358
|
+
scopes: authCode.scopes,
|
|
359
|
+
email: authCode.email,
|
|
360
|
+
sub: authCode.sub,
|
|
361
|
+
expiresAt: Math.floor(Date.now() / 1e3) + 30 * 24 * 3600
|
|
362
|
+
});
|
|
363
|
+
return jsonResponse(200, {
|
|
364
|
+
access_token: accessToken,
|
|
365
|
+
token_type: "Bearer",
|
|
366
|
+
expires_in: config.tokenTtl,
|
|
367
|
+
scope: authCode.scopes.join(" "),
|
|
368
|
+
refresh_token: refreshToken
|
|
369
|
+
});
|
|
370
|
+
}
|
|
147
371
|
function createOAuthProxy(config) {
|
|
148
372
|
const store = config.store ?? createMemoryStore();
|
|
149
373
|
const callbackPath = config.callbackPath ?? "/auth/callback";
|
|
@@ -161,38 +385,7 @@ function createOAuthProxy(config) {
|
|
|
161
385
|
}
|
|
162
386
|
return signingKey;
|
|
163
387
|
}
|
|
164
|
-
|
|
165
|
-
return randomBytes(32).toString("base64url");
|
|
166
|
-
}
|
|
167
|
-
async function generatePkce() {
|
|
168
|
-
const verifier = randomBytes(32).toString("base64url");
|
|
169
|
-
const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
170
|
-
const challenge = Buffer.from(hash).toString("base64url");
|
|
171
|
-
return { verifier, challenge };
|
|
172
|
-
}
|
|
173
|
-
async function verifyPkce(verifier, challenge) {
|
|
174
|
-
const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
175
|
-
const computed = Buffer.from(hash).toString("base64url");
|
|
176
|
-
return computed === challenge;
|
|
177
|
-
}
|
|
178
|
-
function jsonResponse(status, body, headers = {}) {
|
|
179
|
-
return {
|
|
180
|
-
status,
|
|
181
|
-
headers: { "Content-Type": "application/json", ...CORS_HEADERS, ...headers },
|
|
182
|
-
body
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
function redirectResponse(url) {
|
|
186
|
-
return {
|
|
187
|
-
status: 302,
|
|
188
|
-
headers: { Location: url, ...CORS_HEADERS },
|
|
189
|
-
body: void 0
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
function errorResponse(status, error, description) {
|
|
193
|
-
return jsonResponse(status, { error, error_description: description });
|
|
194
|
-
}
|
|
195
|
-
const provider = {
|
|
388
|
+
return {
|
|
196
389
|
metadata() {
|
|
197
390
|
return jsonResponse(200, {
|
|
198
391
|
issuer: config.resourceUrl,
|
|
@@ -206,46 +399,8 @@ function createOAuthProxy(config) {
|
|
|
206
399
|
scopes_supported: scopes
|
|
207
400
|
}, { "Cache-Control": "max-age=3600" });
|
|
208
401
|
},
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (!body) {
|
|
212
|
-
return errorResponse(400, "invalid_request", "Missing request body");
|
|
213
|
-
}
|
|
214
|
-
const redirectUris = body.redirect_uris;
|
|
215
|
-
if (!redirectUris) {
|
|
216
|
-
return errorResponse(400, "invalid_request", "redirect_uris is required");
|
|
217
|
-
}
|
|
218
|
-
let uris;
|
|
219
|
-
try {
|
|
220
|
-
uris = typeof redirectUris === "string" ? JSON.parse(redirectUris) : redirectUris;
|
|
221
|
-
} catch {
|
|
222
|
-
uris = [redirectUris];
|
|
223
|
-
}
|
|
224
|
-
if (!Array.isArray(uris) || uris.length === 0) {
|
|
225
|
-
return errorResponse(400, "invalid_request", "redirect_uris must be a non-empty array");
|
|
226
|
-
}
|
|
227
|
-
for (const uri of uris) {
|
|
228
|
-
if (!matchRedirectUri(uri, config.redirectUris)) {
|
|
229
|
-
return errorResponse(400, "invalid_redirect_uri", `Redirect URI not allowed: ${uri}`);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
const clientId = randomUUID();
|
|
233
|
-
const clientSecret = randomBytes(32).toString("base64url");
|
|
234
|
-
const registration = {
|
|
235
|
-
clientId,
|
|
236
|
-
clientSecret,
|
|
237
|
-
redirectUris: uris,
|
|
238
|
-
clientName: body.client_name,
|
|
239
|
-
createdAt: Date.now() / 1e3
|
|
240
|
-
};
|
|
241
|
-
await store.saveClient(clientId, registration);
|
|
242
|
-
return jsonResponse(201, {
|
|
243
|
-
client_id: clientId,
|
|
244
|
-
client_secret: clientSecret,
|
|
245
|
-
redirect_uris: uris,
|
|
246
|
-
client_name: body.client_name,
|
|
247
|
-
token_endpoint_auth_method: "none"
|
|
248
|
-
});
|
|
402
|
+
register(req) {
|
|
403
|
+
return handleRegister(req, store, config.redirectUris);
|
|
249
404
|
},
|
|
250
405
|
async authorize(req) {
|
|
251
406
|
const params = req.url.searchParams;
|
|
@@ -269,38 +424,11 @@ function createOAuthProxy(config) {
|
|
|
269
424
|
if (!codeChallenge || codeChallengeMethod !== "S256") {
|
|
270
425
|
return errorResponse(400, "invalid_request", "PKCE with S256 is required");
|
|
271
426
|
}
|
|
272
|
-
|
|
273
|
-
if (
|
|
274
|
-
|
|
275
|
-
const metaRes = await fetch(clientId);
|
|
276
|
-
if (!metaRes.ok) {
|
|
277
|
-
return errorResponse(400, "invalid_client", "Failed to fetch client metadata document");
|
|
278
|
-
}
|
|
279
|
-
const meta = await metaRes.json();
|
|
280
|
-
const metaRedirectUris = meta.redirect_uris;
|
|
281
|
-
if (!Array.isArray(metaRedirectUris) || metaRedirectUris.length === 0) {
|
|
282
|
-
return errorResponse(400, "invalid_client", "Client metadata must include redirect_uris");
|
|
283
|
-
}
|
|
284
|
-
for (const uri of metaRedirectUris) {
|
|
285
|
-
if (!matchRedirectUri(uri, config.redirectUris)) {
|
|
286
|
-
return errorResponse(400, "invalid_redirect_uri", `Redirect URI not allowed: ${uri}`);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
client = {
|
|
290
|
-
clientId,
|
|
291
|
-
clientSecret: "",
|
|
292
|
-
redirectUris: metaRedirectUris,
|
|
293
|
-
clientName: meta.client_name,
|
|
294
|
-
createdAt: Date.now() / 1e3
|
|
295
|
-
};
|
|
296
|
-
await store.saveClient(clientId, client);
|
|
297
|
-
} catch {
|
|
298
|
-
return errorResponse(400, "invalid_client", "Failed to fetch client metadata document");
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
if (!client) {
|
|
302
|
-
return errorResponse(400, "invalid_client", "Unknown client_id");
|
|
427
|
+
const result = await resolveClient(clientId, store, config.redirectUris);
|
|
428
|
+
if ("status" in result) {
|
|
429
|
+
return result;
|
|
303
430
|
}
|
|
431
|
+
const client = result;
|
|
304
432
|
if (!client.redirectUris.includes(redirectUri)) {
|
|
305
433
|
return errorResponse(400, "invalid_redirect_uri", "redirect_uri does not match registered URIs");
|
|
306
434
|
}
|
|
@@ -352,42 +480,11 @@ function createOAuthProxy(config) {
|
|
|
352
480
|
}
|
|
353
481
|
await store.deletePendingAuth(proxyState);
|
|
354
482
|
await store.deletePkceVerifier(proxyState);
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
redirect_uri: `${config.resourceUrl}${callbackPath}`,
|
|
359
|
-
client_id: config.clientId,
|
|
360
|
-
client_secret: config.clientSecret,
|
|
361
|
-
code_verifier: pkceVerifier
|
|
362
|
-
});
|
|
363
|
-
const tokenResponse = await fetch(config.tokenUrl, {
|
|
364
|
-
method: "POST",
|
|
365
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
366
|
-
body: tokenBody.toString()
|
|
367
|
-
});
|
|
368
|
-
if (!tokenResponse.ok) {
|
|
369
|
-
const errorBody = await tokenResponse.text();
|
|
370
|
-
console.error("Upstream token exchange failed:", errorBody);
|
|
371
|
-
return errorResponse(502, "upstream_error", "Failed to exchange code with upstream provider");
|
|
372
|
-
}
|
|
373
|
-
const tokens = await tokenResponse.json();
|
|
374
|
-
const upstreamAccessToken = tokens.access_token;
|
|
375
|
-
const upstreamIdToken = tokens.id_token;
|
|
376
|
-
let email;
|
|
377
|
-
let sub;
|
|
378
|
-
if (config.userinfoUrl && upstreamAccessToken) {
|
|
379
|
-
try {
|
|
380
|
-
const userinfoResponse = await fetch(config.userinfoUrl, {
|
|
381
|
-
headers: { Authorization: `Bearer ${upstreamAccessToken}` }
|
|
382
|
-
});
|
|
383
|
-
if (userinfoResponse.ok) {
|
|
384
|
-
const userinfo = await userinfoResponse.json();
|
|
385
|
-
email = userinfo.email;
|
|
386
|
-
sub = userinfo.sub;
|
|
387
|
-
}
|
|
388
|
-
} catch {
|
|
389
|
-
}
|
|
483
|
+
const upstream = await exchangeUpstreamCode(upstreamCode, config, callbackPath, pkceVerifier);
|
|
484
|
+
if ("status" in upstream) {
|
|
485
|
+
return upstream;
|
|
390
486
|
}
|
|
487
|
+
const userinfo = await fetchUserinfo(config.userinfoUrl, upstream.accessToken);
|
|
391
488
|
const mcpCode = generateCode();
|
|
392
489
|
await store.saveAuthCode(mcpCode, {
|
|
393
490
|
clientId: pending.clientId,
|
|
@@ -395,10 +492,10 @@ function createOAuthProxy(config) {
|
|
|
395
492
|
codeChallenge: pending.codeChallenge,
|
|
396
493
|
codeChallengeMethod: pending.codeChallengeMethod,
|
|
397
494
|
scopes: pending.scope.split(" ").filter(Boolean),
|
|
398
|
-
upstreamAccessToken,
|
|
399
|
-
upstreamIdToken,
|
|
400
|
-
email,
|
|
401
|
-
sub,
|
|
495
|
+
upstreamAccessToken: upstream.accessToken,
|
|
496
|
+
upstreamIdToken: upstream.idToken,
|
|
497
|
+
email: userinfo.email,
|
|
498
|
+
sub: userinfo.sub,
|
|
402
499
|
expiresAt: Date.now() / 1e3 + 600
|
|
403
500
|
});
|
|
404
501
|
const clientRedirect = new URL(pending.redirectUri);
|
|
@@ -413,76 +510,14 @@ function createOAuthProxy(config) {
|
|
|
413
510
|
if (!body) {
|
|
414
511
|
return errorResponse(400, "invalid_request", "Missing request body");
|
|
415
512
|
}
|
|
416
|
-
const
|
|
417
|
-
if (
|
|
418
|
-
|
|
419
|
-
if (!refreshToken2) {
|
|
420
|
-
return errorResponse(400, "invalid_request", "Missing refresh_token");
|
|
421
|
-
}
|
|
422
|
-
const tokenData = await store.getRefreshToken(refreshToken2);
|
|
423
|
-
if (!tokenData) {
|
|
424
|
-
return errorResponse(400, "invalid_grant", "Invalid or expired refresh token");
|
|
425
|
-
}
|
|
426
|
-
const key2 = await getSigningKey();
|
|
427
|
-
const now2 = Math.floor(Date.now() / 1e3);
|
|
428
|
-
const accessToken2 = await new SignJWT({
|
|
429
|
-
scope: tokenData.scopes.join(" "),
|
|
430
|
-
email: tokenData.email
|
|
431
|
-
}).setProtectedHeader({ alg: "HS256" }).setSubject(tokenData.sub ?? tokenData.email ?? tokenData.clientId).setIssuer(config.resourceUrl).setAudience(config.resourceUrl).setIssuedAt(now2).setExpirationTime(now2 + tokenTtl).sign(key2);
|
|
432
|
-
return jsonResponse(200, {
|
|
433
|
-
access_token: accessToken2,
|
|
434
|
-
token_type: "Bearer",
|
|
435
|
-
expires_in: tokenTtl,
|
|
436
|
-
scope: tokenData.scopes.join(" "),
|
|
437
|
-
refresh_token: refreshToken2
|
|
438
|
-
});
|
|
513
|
+
const tokenConfig = { resourceUrl: config.resourceUrl, tokenTtl };
|
|
514
|
+
if (body.grant_type === "refresh_token") {
|
|
515
|
+
return handleRefreshToken(body, store, getSigningKey, tokenConfig);
|
|
439
516
|
}
|
|
440
|
-
if (
|
|
517
|
+
if (body.grant_type !== "authorization_code") {
|
|
441
518
|
return errorResponse(400, "unsupported_grant_type", "Supported grant types: authorization_code, refresh_token");
|
|
442
519
|
}
|
|
443
|
-
|
|
444
|
-
const redirectUri = body.redirect_uri;
|
|
445
|
-
const clientId = body.client_id;
|
|
446
|
-
const codeVerifier = body.code_verifier;
|
|
447
|
-
if (!code || !codeVerifier || !clientId) {
|
|
448
|
-
return errorResponse(400, "invalid_request", "Missing required parameters");
|
|
449
|
-
}
|
|
450
|
-
const authCode = await store.getAuthCode(code);
|
|
451
|
-
if (!authCode) {
|
|
452
|
-
return errorResponse(400, "invalid_grant", "Invalid or expired authorization code");
|
|
453
|
-
}
|
|
454
|
-
await store.deleteAuthCode(code);
|
|
455
|
-
if (authCode.clientId !== clientId) {
|
|
456
|
-
return errorResponse(400, "invalid_grant", "client_id mismatch");
|
|
457
|
-
}
|
|
458
|
-
if (redirectUri && authCode.redirectUri !== redirectUri) {
|
|
459
|
-
return errorResponse(400, "invalid_grant", "redirect_uri mismatch");
|
|
460
|
-
}
|
|
461
|
-
const pkceValid = await verifyPkce(codeVerifier, authCode.codeChallenge);
|
|
462
|
-
if (!pkceValid) {
|
|
463
|
-
return errorResponse(400, "invalid_grant", "PKCE verification failed");
|
|
464
|
-
}
|
|
465
|
-
const key = await getSigningKey();
|
|
466
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
467
|
-
const accessToken = await new SignJWT({
|
|
468
|
-
scope: authCode.scopes.join(" "),
|
|
469
|
-
email: authCode.email
|
|
470
|
-
}).setProtectedHeader({ alg: "HS256" }).setSubject(authCode.sub ?? authCode.email ?? authCode.clientId).setIssuer(config.resourceUrl).setAudience(config.resourceUrl).setIssuedAt(now).setExpirationTime(now + tokenTtl).sign(key);
|
|
471
|
-
const refreshToken = randomBytes(32).toString("base64url");
|
|
472
|
-
await store.saveRefreshToken(refreshToken, {
|
|
473
|
-
clientId: authCode.clientId,
|
|
474
|
-
scopes: authCode.scopes,
|
|
475
|
-
email: authCode.email,
|
|
476
|
-
sub: authCode.sub,
|
|
477
|
-
expiresAt: now + 30 * 24 * 3600
|
|
478
|
-
});
|
|
479
|
-
return jsonResponse(200, {
|
|
480
|
-
access_token: accessToken,
|
|
481
|
-
token_type: "Bearer",
|
|
482
|
-
expires_in: tokenTtl,
|
|
483
|
-
scope: authCode.scopes.join(" "),
|
|
484
|
-
refresh_token: refreshToken
|
|
485
|
-
});
|
|
520
|
+
return handleAuthorizationCode(body, store, getSigningKey, tokenConfig);
|
|
486
521
|
},
|
|
487
522
|
async verifyToken(token) {
|
|
488
523
|
try {
|
|
@@ -503,7 +538,6 @@ function createOAuthProxy(config) {
|
|
|
503
538
|
}
|
|
504
539
|
}
|
|
505
540
|
};
|
|
506
|
-
return provider;
|
|
507
541
|
}
|
|
508
542
|
|
|
509
543
|
// src/provider/google.ts
|
|
@@ -779,7 +813,6 @@ export {
|
|
|
779
813
|
insufficientScope,
|
|
780
814
|
invalidToken,
|
|
781
815
|
matchRedirectUri,
|
|
782
|
-
missingToken,
|
|
783
816
|
validateToken
|
|
784
817
|
};
|
|
785
818
|
//# sourceMappingURL=index.js.map
|