Spaces:
Running
Running
coyotte508
commited on
Commit
·
46d21da
1
Parent(s):
8ce6ae3
support PKCE
Browse files- src/hooks.server.ts +2 -6
- src/lib/server/auth.ts +24 -11
- src/routes/login/+server.ts +2 -2
- src/routes/login/callback/+server.ts +6 -0
src/hooks.server.ts
CHANGED
|
@@ -148,11 +148,7 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|
| 148 |
) {
|
| 149 |
// To get the same CSRF token after callback
|
| 150 |
refreshSessionCookie(event.cookies, auth.secretSessionId);
|
| 151 |
-
return await triggerOauthFlow(
|
| 152 |
-
request: event.request,
|
| 153 |
-
url: event.url,
|
| 154 |
-
locals: event.locals,
|
| 155 |
-
});
|
| 156 |
}
|
| 157 |
} else {
|
| 158 |
// Redirect to OAuth flow unless on the authorized pages (home, shared conversation, login, healthcheck, model thumbnails)
|
|
@@ -168,7 +164,7 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|
| 168 |
!event.url.pathname.startsWith(`${base}/api`)
|
| 169 |
) {
|
| 170 |
refreshSessionCookie(event.cookies, auth.secretSessionId);
|
| 171 |
-
return triggerOauthFlow(
|
| 172 |
}
|
| 173 |
}
|
| 174 |
}
|
|
|
|
| 148 |
) {
|
| 149 |
// To get the same CSRF token after callback
|
| 150 |
refreshSessionCookie(event.cookies, auth.secretSessionId);
|
| 151 |
+
return await triggerOauthFlow(event);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
}
|
| 153 |
} else {
|
| 154 |
// Redirect to OAuth flow unless on the authorized pages (home, shared conversation, login, healthcheck, model thumbnails)
|
|
|
|
| 164 |
!event.url.pathname.startsWith(`${base}/api`)
|
| 165 |
) {
|
| 166 |
refreshSessionCookie(event.cookies, auth.secretSessionId);
|
| 167 |
+
return triggerOauthFlow(event);
|
| 168 |
}
|
| 169 |
}
|
| 170 |
}
|
src/lib/server/auth.ts
CHANGED
|
@@ -4,7 +4,9 @@ import {
|
|
| 4 |
type UserinfoResponse,
|
| 5 |
type TokenSet,
|
| 6 |
custom,
|
|
|
|
| 7 |
} from "openid-client";
|
|
|
|
| 8 |
import { addHours, addWeeks, differenceInMinutes, subMinutes } from "date-fns";
|
| 9 |
import { config } from "$lib/server/config";
|
| 10 |
import { sha256 } from "$lib/utils/sha256";
|
|
@@ -281,7 +283,7 @@ async function getOIDCClient(settings: OIDCSettings, url: URL): Promise<BaseClie
|
|
| 281 |
|
| 282 |
export async function getOIDCAuthorizationUrl(
|
| 283 |
settings: OIDCSettings,
|
| 284 |
-
params: { sessionId: string; next?: string; url: URL }
|
| 285 |
): Promise<string> {
|
| 286 |
const client = await getOIDCClient(settings, params.url);
|
| 287 |
const csrfToken = await generateCsrfToken(
|
|
@@ -290,7 +292,20 @@ export async function getOIDCAuthorizationUrl(
|
|
| 290 |
sanitizeReturnPath(params.next)
|
| 291 |
);
|
| 292 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 293 |
return client.authorizationUrl({
|
|
|
|
|
|
|
| 294 |
scope: OIDConfig.SCOPES,
|
| 295 |
state: csrfToken,
|
| 296 |
resource: OIDConfig.RESOURCE || undefined,
|
|
@@ -300,11 +315,16 @@ export async function getOIDCAuthorizationUrl(
|
|
| 300 |
export async function getOIDCUserData(
|
| 301 |
settings: OIDCSettings,
|
| 302 |
code: string,
|
|
|
|
| 303 |
iss: string | undefined,
|
| 304 |
url: URL
|
| 305 |
): Promise<OIDCUserInfo> {
|
| 306 |
const client = await getOIDCClient(settings, url);
|
| 307 |
-
const token = await client.callback(settings.redirectURI, {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
const userData = await client.userinfo(token);
|
| 309 |
|
| 310 |
return { token, userData };
|
|
@@ -514,14 +534,7 @@ export async function authenticateRequest(
|
|
| 514 |
return { user: undefined, sessionId, secretSessionId, isAdmin: false };
|
| 515 |
}
|
| 516 |
|
| 517 |
-
export async function triggerOauthFlow({
|
| 518 |
-
url,
|
| 519 |
-
locals,
|
| 520 |
-
}: {
|
| 521 |
-
request: Request;
|
| 522 |
-
url: URL;
|
| 523 |
-
locals: App.Locals;
|
| 524 |
-
}): Promise<Response> {
|
| 525 |
// const referer = request.headers.get("referer");
|
| 526 |
// let redirectURI = `${(referer ? new URL(referer) : url).origin}${base}/login/callback`;
|
| 527 |
let redirectURI = `${url.origin}${base}/login/callback`;
|
|
@@ -551,7 +564,7 @@ export async function triggerOauthFlow({
|
|
| 551 |
|
| 552 |
const authorizationUrl = await getOIDCAuthorizationUrl(
|
| 553 |
{ redirectURI },
|
| 554 |
-
{ sessionId: locals.sessionId, next, url }
|
| 555 |
);
|
| 556 |
|
| 557 |
throw redirect(302, authorizationUrl);
|
|
|
|
| 4 |
type UserinfoResponse,
|
| 5 |
type TokenSet,
|
| 6 |
custom,
|
| 7 |
+
generators,
|
| 8 |
} from "openid-client";
|
| 9 |
+
import type { RequestEvent } from "@sveltejs/kit";
|
| 10 |
import { addHours, addWeeks, differenceInMinutes, subMinutes } from "date-fns";
|
| 11 |
import { config } from "$lib/server/config";
|
| 12 |
import { sha256 } from "$lib/utils/sha256";
|
|
|
|
| 283 |
|
| 284 |
export async function getOIDCAuthorizationUrl(
|
| 285 |
settings: OIDCSettings,
|
| 286 |
+
params: { sessionId: string; next?: string; url: URL; cookies: Cookies }
|
| 287 |
): Promise<string> {
|
| 288 |
const client = await getOIDCClient(settings, params.url);
|
| 289 |
const csrfToken = await generateCsrfToken(
|
|
|
|
| 292 |
sanitizeReturnPath(params.next)
|
| 293 |
);
|
| 294 |
|
| 295 |
+
const codeVerifier = generators.codeVerifier();
|
| 296 |
+
const codeChallenge = generators.codeChallenge(codeVerifier);
|
| 297 |
+
|
| 298 |
+
params.cookies.set("hfChat-codeVerifier", codeVerifier, {
|
| 299 |
+
path: "/",
|
| 300 |
+
sameSite,
|
| 301 |
+
secure,
|
| 302 |
+
httpOnly: true,
|
| 303 |
+
expires: addHours(new Date(), 1),
|
| 304 |
+
});
|
| 305 |
+
|
| 306 |
return client.authorizationUrl({
|
| 307 |
+
code_challenge_method: "S256",
|
| 308 |
+
code_challenge: codeChallenge,
|
| 309 |
scope: OIDConfig.SCOPES,
|
| 310 |
state: csrfToken,
|
| 311 |
resource: OIDConfig.RESOURCE || undefined,
|
|
|
|
| 315 |
export async function getOIDCUserData(
|
| 316 |
settings: OIDCSettings,
|
| 317 |
code: string,
|
| 318 |
+
codeVerifier: string,
|
| 319 |
iss: string | undefined,
|
| 320 |
url: URL
|
| 321 |
): Promise<OIDCUserInfo> {
|
| 322 |
const client = await getOIDCClient(settings, url);
|
| 323 |
+
const token = await client.callback(settings.redirectURI, {
|
| 324 |
+
code,
|
| 325 |
+
iss,
|
| 326 |
+
checks: { code_verifier: codeVerifier },
|
| 327 |
+
});
|
| 328 |
const userData = await client.userinfo(token);
|
| 329 |
|
| 330 |
return { token, userData };
|
|
|
|
| 534 |
return { user: undefined, sessionId, secretSessionId, isAdmin: false };
|
| 535 |
}
|
| 536 |
|
| 537 |
+
export async function triggerOauthFlow({ url, locals, cookies }: RequestEvent): Promise<Response> {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 538 |
// const referer = request.headers.get("referer");
|
| 539 |
// let redirectURI = `${(referer ? new URL(referer) : url).origin}${base}/login/callback`;
|
| 540 |
let redirectURI = `${url.origin}${base}/login/callback`;
|
|
|
|
| 564 |
|
| 565 |
const authorizationUrl = await getOIDCAuthorizationUrl(
|
| 566 |
{ redirectURI },
|
| 567 |
+
{ sessionId: locals.sessionId, next, url, cookies }
|
| 568 |
);
|
| 569 |
|
| 570 |
throw redirect(302, authorizationUrl);
|
src/routes/login/+server.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
import { triggerOauthFlow } from "$lib/server/auth";
|
| 2 |
|
| 3 |
-
export async function GET(
|
| 4 |
-
return await triggerOauthFlow(
|
| 5 |
}
|
|
|
|
| 1 |
import { triggerOauthFlow } from "$lib/server/auth";
|
| 2 |
|
| 3 |
+
export async function GET(event) {
|
| 4 |
+
return await triggerOauthFlow(event);
|
| 5 |
}
|
src/routes/login/callback/+server.ts
CHANGED
|
@@ -52,9 +52,15 @@ export async function GET({ url, locals, cookies, request, getClientAddress }) {
|
|
| 52 |
throw error(403, "Invalid or expired CSRF token");
|
| 53 |
}
|
| 54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
const { userData, token } = await getOIDCUserData(
|
| 56 |
{ redirectURI: validatedToken.redirectUrl },
|
| 57 |
code,
|
|
|
|
| 58 |
iss,
|
| 59 |
url
|
| 60 |
);
|
|
|
|
| 52 |
throw error(403, "Invalid or expired CSRF token");
|
| 53 |
}
|
| 54 |
|
| 55 |
+
const codeVerifier = cookies.get("hfChat-codeVerifier");
|
| 56 |
+
if (!codeVerifier) {
|
| 57 |
+
throw error(403, "Code verifier cookie not found");
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
const { userData, token } = await getOIDCUserData(
|
| 61 |
{ redirectURI: validatedToken.redirectUrl },
|
| 62 |
code,
|
| 63 |
+
codeVerifier,
|
| 64 |
iss,
|
| 65 |
url
|
| 66 |
);
|