Skip to content

Commit 4493487

Browse files
committed
Add PKCE and fix CIMD
1 parent a18c267 commit 4493487

File tree

6 files changed

+51
-29
lines changed

6 files changed

+51
-29
lines changed

src/hooks.server.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ export const handle: Handle = async ({ event, resolve }) => {
139139

140140
event.locals.sessionId = auth.sessionId;
141141

142-
if (loginEnabled && !auth.user) {
142+
if (loginEnabled && !auth.user && !event.url.pathname.startsWith(`${base}/.well-known/`)) {
143143
if (config.AUTOMATIC_LOGIN === "true") {
144144
// AUTOMATIC_LOGIN: always redirect to OAuth flow (unless already on login or healthcheck pages)
145145
if (
@@ -148,11 +148,7 @@ export const handle: Handle = async ({ event, resolve }) => {
148148
) {
149149
// To get the same CSRF token after callback
150150
refreshSessionCookie(event.cookies, auth.secretSessionId);
151-
return await triggerOauthFlow({
152-
request: event.request,
153-
url: event.url,
154-
locals: event.locals,
155-
});
151+
return await triggerOauthFlow(event);
156152
}
157153
} else {
158154
// 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 }) => {
168164
!event.url.pathname.startsWith(`${base}/api`)
169165
) {
170166
refreshSessionCookie(event.cookies, auth.secretSessionId);
171-
return triggerOauthFlow({ request: event.request, url: event.url, locals: event.locals });
167+
return triggerOauthFlow(event);
172168
}
173169
}
174170
}

src/lib/server/api/authPlugin.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ export const authPlugin = new Elysia({ name: "auth" }).derive(
1111
}): Promise<{
1212
locals: App.Locals;
1313
}> => {
14-
request.url;
1514
const auth = await authenticateRequest(
1615
{ type: "elysia", value: headers },
1716
{ type: "elysia", value: cookie },
18-
new URL(request.url, config.PUBLIC_ORIGIN),
17+
new URL(request.url, config.PUBLIC_ORIGIN || undefined),
1918
true
2019
);
2120
return {

src/lib/server/auth.ts

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import {
44
type UserinfoResponse,
55
type TokenSet,
66
custom,
7+
generators,
78
} from "openid-client";
9+
import type { RequestEvent } from "@sveltejs/kit";
810
import { addHours, addWeeks, differenceInMinutes, subMinutes } from "date-fns";
911
import { config } from "$lib/server/config";
1012
import { sha256 } from "$lib/utils/sha256";
@@ -54,7 +56,7 @@ export const OIDConfig = z
5456
})
5557
.parse(JSON5.parse(config.OPENID_CONFIG || "{}"));
5658

57-
export const loginEnabled = !!OIDConfig.CLIENT_ID && !!OIDConfig.CLIENT_SECRET;
59+
export const loginEnabled = !!OIDConfig.CLIENT_ID;
5860

5961
const sameSite = z
6062
.enum(["lax", "none", "strict"])
@@ -264,8 +266,8 @@ async function getOIDCClient(settings: OIDCSettings, url: URL): Promise<BaseClie
264266
};
265267

266268
if (OIDConfig.CLIENT_ID === "__CIMD__") {
267-
OIDConfig.CLIENT_ID = new URL(
268-
"/.well-known/oauth-cimd",
269+
client_config.client_id = new URL(
270+
`${base}/.well-known/oauth-cimd`,
269271
config.PUBLIC_ORIGIN || url.origin
270272
).toString();
271273
}
@@ -281,7 +283,7 @@ async function getOIDCClient(settings: OIDCSettings, url: URL): Promise<BaseClie
281283

282284
export async function getOIDCAuthorizationUrl(
283285
settings: OIDCSettings,
284-
params: { sessionId: string; next?: string; url: URL }
286+
params: { sessionId: string; next?: string; url: URL; cookies: Cookies }
285287
): Promise<string> {
286288
const client = await getOIDCClient(settings, params.url);
287289
const csrfToken = await generateCsrfToken(
@@ -290,7 +292,20 @@ export async function getOIDCAuthorizationUrl(
290292
sanitizeReturnPath(params.next)
291293
);
292294

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+
293306
return client.authorizationUrl({
307+
code_challenge_method: "S256",
308+
code_challenge: codeChallenge,
294309
scope: OIDConfig.SCOPES,
295310
state: csrfToken,
296311
resource: OIDConfig.RESOURCE || undefined,
@@ -300,11 +315,19 @@ export async function getOIDCAuthorizationUrl(
300315
export async function getOIDCUserData(
301316
settings: OIDCSettings,
302317
code: string,
318+
codeVerifier: string,
303319
iss: string | undefined,
304320
url: URL
305321
): Promise<OIDCUserInfo> {
306322
const client = await getOIDCClient(settings, url);
307-
const token = await client.callback(settings.redirectURI, { code, iss });
323+
const token = await client.callback(
324+
settings.redirectURI,
325+
{
326+
code,
327+
iss,
328+
},
329+
{ code_verifier: codeVerifier }
330+
);
308331
const userData = await client.userinfo(token);
309332

310333
return { token, userData };
@@ -514,14 +537,7 @@ export async function authenticateRequest(
514537
return { user: undefined, sessionId, secretSessionId, isAdmin: false };
515538
}
516539

517-
export async function triggerOauthFlow({
518-
url,
519-
locals,
520-
}: {
521-
request: Request;
522-
url: URL;
523-
locals: App.Locals;
524-
}): Promise<Response> {
540+
export async function triggerOauthFlow({ url, locals, cookies }: RequestEvent): Promise<Response> {
525541
// const referer = request.headers.get("referer");
526542
// let redirectURI = `${(referer ? new URL(referer) : url).origin}${base}/login/callback`;
527543
let redirectURI = `${url.origin}${base}/login/callback`;
@@ -551,7 +567,7 @@ export async function triggerOauthFlow({
551567

552568
const authorizationUrl = await getOIDCAuthorizationUrl(
553569
{ redirectURI },
554-
{ sessionId: locals.sessionId, next, url }
570+
{ sessionId: locals.sessionId, next, url, cookies }
555571
);
556572

557573
throw redirect(302, authorizationUrl);

src/routes/.well-known/.oauth-cimd/+server.ts renamed to src/routes/.well-known/oauth-cimd/+server.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { base } from "$app/paths";
12
import { OIDConfig } from "$lib/server/auth";
23
import { config } from "$lib/server/config";
34

@@ -6,14 +7,18 @@ export const GET = ({ url }) => {
67
return new Response("Client ID not found", { status: 404 });
78
}
89
if (OIDConfig.CLIENT_ID !== "__CIMD__") {
9-
return new Response("Client ID is manually set to something other than '__CIMD__'", {
10-
status: 404,
11-
});
10+
return new Response(
11+
`Client ID is manually set to something other than '__CIMD__': ${OIDConfig.CLIENT_ID}`,
12+
{
13+
status: 404,
14+
}
15+
);
1216
}
1317
return new Response(
1418
JSON.stringify({
15-
client_id: new URL("/.well-known/oauth-cimd", config.PUBLIC_ORIGIN || url.origin).toString(),
19+
client_id: new URL(url, config.PUBLIC_ORIGIN || url.origin).toString(),
1620
client_name: config.PUBLIC_APP_NAME,
21+
client_uri: `${config.PUBLIC_ORIGIN || url.origin}${base}`,
1722
redirect_uris: [new URL("/login/callback", config.PUBLIC_ORIGIN || url.origin).toString()],
1823
token_endpoint_auth_method: "none",
1924
scopes: OIDConfig.SCOPES,

src/routes/login/+server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { triggerOauthFlow } from "$lib/server/auth";
22

3-
export async function GET({ request, url, locals }) {
4-
return await triggerOauthFlow({ request, url, locals });
3+
export async function GET(event) {
4+
return await triggerOauthFlow(event);
55
}

src/routes/login/callback/+server.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,15 @@ export async function GET({ url, locals, cookies, request, getClientAddress }) {
5252
throw error(403, "Invalid or expired CSRF token");
5353
}
5454

55+
const codeVerifier = cookies.get("hfChat-codeVerifier");
56+
if (!codeVerifier) {
57+
throw error(403, "Code verifier cookie not found");
58+
}
59+
5560
const { userData, token } = await getOIDCUserData(
5661
{ redirectURI: validatedToken.redirectUrl },
5762
code,
63+
codeVerifier,
5864
iss,
5965
url
6066
);

0 commit comments

Comments
 (0)