Commit
•
fa3b3b4
1
Parent(s):
101f9ef
feat openid login with google (#250)
Browse filesCo-authored-by: coyotte508 <[email protected]>
- .env +5 -3
- src/lib/components/NavMenu.svelte +6 -4
- src/lib/server/auth.ts +2 -3
- src/lib/types/User.ts +2 -1
- src/lib/utils/sha256.ts +0 -2
- src/routes/+layout.server.ts +5 -1
- src/routes/login/callback/updateUser.ts +3 -1
.env
CHANGED
@@ -11,7 +11,9 @@ HF_ACCESS_TOKEN=#hf_<token> from from https://huggingface.co/settings/token
|
|
11 |
# Parameters to enable "Sign in with HF"
|
12 |
OPENID_CLIENT_ID=
|
13 |
OPENID_CLIENT_SECRET=
|
14 |
-
|
|
|
|
|
15 |
|
16 |
# 'name', 'userMessageToken', 'assistantMessageToken' are required
|
17 |
MODELS=`[
|
@@ -54,8 +56,8 @@ PUBLIC_GOOGLE_ANALYTICS_ID=#G-XXXXXXXX / Leave empty to disable
|
|
54 |
PUBLIC_DEPRECATED_GOOGLE_ANALYTICS_ID=#UA-XXXXXXXX-X / Leave empty to disable
|
55 |
PUBLIC_ANNOUNCEMENT_BANNERS=`[
|
56 |
{
|
57 |
-
"title": "Chat UI is now open sourced on GitHub",
|
58 |
-
"linkTitle": "GitHub repo",
|
59 |
"linkHref": "https://github.com/huggingface/chat-ui"
|
60 |
}
|
61 |
]`
|
|
|
11 |
# Parameters to enable "Sign in with HF"
|
12 |
OPENID_CLIENT_ID=
|
13 |
OPENID_CLIENT_SECRET=
|
14 |
+
OPENID_SCOPES="openid profile" # Add "email" for some providers like Google that do not provide preferred_username
|
15 |
+
OPENID_PROVIDER_URL=https://huggingface.co # for Google, use https://accounts.google.com
|
16 |
+
|
17 |
|
18 |
# 'name', 'userMessageToken', 'assistantMessageToken' are required
|
19 |
MODELS=`[
|
|
|
56 |
PUBLIC_DEPRECATED_GOOGLE_ANALYTICS_ID=#UA-XXXXXXXX-X / Leave empty to disable
|
57 |
PUBLIC_ANNOUNCEMENT_BANNERS=`[
|
58 |
{
|
59 |
+
"title": "Chat UI is now open sourced on GitHub",
|
60 |
+
"linkTitle": "GitHub repo",
|
61 |
"linkHref": "https://github.com/huggingface/chat-ui"
|
62 |
}
|
63 |
]`
|
src/lib/components/NavMenu.svelte
CHANGED
@@ -6,6 +6,7 @@
|
|
6 |
import { switchTheme } from "$lib/switchTheme";
|
7 |
import { PUBLIC_ORIGIN } from "$env/static/public";
|
8 |
import NavConversationItem from "./NavConversationItem.svelte";
|
|
|
9 |
|
10 |
const dispatch = createEventDispatcher<{
|
11 |
shareConversation: { id: string; title: string };
|
@@ -17,7 +18,7 @@
|
|
17 |
id: string;
|
18 |
title: string;
|
19 |
}> = [];
|
20 |
-
export let user:
|
21 |
</script>
|
22 |
|
23 |
<div class="sticky top-0 flex flex-none items-center justify-between px-3 py-3.5 max-sm:pt-0">
|
@@ -42,14 +43,15 @@
|
|
42 |
<div
|
43 |
class="mt-0.5 flex flex-col gap-1 rounded-r-xl bg-gradient-to-l from-gray-50 p-3 text-sm dark:from-gray-800/30"
|
44 |
>
|
45 |
-
{#if user?.username}
|
46 |
<form
|
47 |
action="{base}/logout"
|
48 |
method="post"
|
49 |
class="group flex items-center gap-1.5 rounded-lg pl-3 pr-2 hover:bg-gray-100 dark:hover:bg-gray-700"
|
50 |
>
|
51 |
-
<span
|
52 |
-
|
|
|
53 |
>
|
54 |
<button
|
55 |
type="submit"
|
|
|
6 |
import { switchTheme } from "$lib/switchTheme";
|
7 |
import { PUBLIC_ORIGIN } from "$env/static/public";
|
8 |
import NavConversationItem from "./NavConversationItem.svelte";
|
9 |
+
import type { LayoutData } from "../../routes/$types";
|
10 |
|
11 |
const dispatch = createEventDispatcher<{
|
12 |
shareConversation: { id: string; title: string };
|
|
|
18 |
id: string;
|
19 |
title: string;
|
20 |
}> = [];
|
21 |
+
export let user: LayoutData["user"];
|
22 |
</script>
|
23 |
|
24 |
<div class="sticky top-0 flex flex-none items-center justify-between px-3 py-3.5 max-sm:pt-0">
|
|
|
43 |
<div
|
44 |
class="mt-0.5 flex flex-col gap-1 rounded-r-xl bg-gradient-to-l from-gray-50 p-3 text-sm dark:from-gray-800/30"
|
45 |
>
|
46 |
+
{#if user?.username || user?.email}
|
47 |
<form
|
48 |
action="{base}/logout"
|
49 |
method="post"
|
50 |
class="group flex items-center gap-1.5 rounded-lg pl-3 pr-2 hover:bg-gray-100 dark:hover:bg-gray-700"
|
51 |
>
|
52 |
+
<span
|
53 |
+
class="flex h-9 flex-none shrink items-center gap-1.5 truncate pr-2 text-gray-500 dark:text-gray-400"
|
54 |
+
>{user?.username || user?.email}</span
|
55 |
>
|
56 |
<button
|
57 |
type="submit"
|
src/lib/server/auth.ts
CHANGED
@@ -5,6 +5,7 @@ import {
|
|
5 |
OPENID_CLIENT_ID,
|
6 |
OPENID_CLIENT_SECRET,
|
7 |
OPENID_PROVIDER_URL,
|
|
|
8 |
} from "$env/static/private";
|
9 |
import { sha256 } from "$lib/utils/sha256";
|
10 |
import { z } from "zod";
|
@@ -33,8 +34,6 @@ export function refreshSessionCookie(cookies: Cookies, sessionId: string) {
|
|
33 |
});
|
34 |
}
|
35 |
|
36 |
-
export const OIDC_SCOPES = "openid profile";
|
37 |
-
|
38 |
export const authCondition = (locals: App.Locals) => {
|
39 |
return locals.user
|
40 |
? { userId: locals.user._id }
|
@@ -75,7 +74,7 @@ export async function getOIDCAuthorizationUrl(
|
|
75 |
const client = await getOIDCClient(settings);
|
76 |
const csrfToken = await generateCsrfToken(params.sessionId, settings.redirectURI);
|
77 |
const url = client.authorizationUrl({
|
78 |
-
scope:
|
79 |
state: csrfToken,
|
80 |
});
|
81 |
|
|
|
5 |
OPENID_CLIENT_ID,
|
6 |
OPENID_CLIENT_SECRET,
|
7 |
OPENID_PROVIDER_URL,
|
8 |
+
OPENID_SCOPES,
|
9 |
} from "$env/static/private";
|
10 |
import { sha256 } from "$lib/utils/sha256";
|
11 |
import { z } from "zod";
|
|
|
34 |
});
|
35 |
}
|
36 |
|
|
|
|
|
37 |
export const authCondition = (locals: App.Locals) => {
|
38 |
return locals.user
|
39 |
? { userId: locals.user._id }
|
|
|
74 |
const client = await getOIDCClient(settings);
|
75 |
const csrfToken = await generateCsrfToken(params.sessionId, settings.redirectURI);
|
76 |
const url = client.authorizationUrl({
|
77 |
+
scope: OPENID_SCOPES,
|
78 |
state: csrfToken,
|
79 |
});
|
80 |
|
src/lib/types/User.ts
CHANGED
@@ -4,8 +4,9 @@ import type { Timestamps } from "./Timestamps";
|
|
4 |
export interface User extends Timestamps {
|
5 |
_id: ObjectId;
|
6 |
|
7 |
-
username
|
8 |
name: string;
|
|
|
9 |
avatarUrl: string;
|
10 |
hfUserId: string;
|
11 |
|
|
|
4 |
export interface User extends Timestamps {
|
5 |
_id: ObjectId;
|
6 |
|
7 |
+
username?: string;
|
8 |
name: string;
|
9 |
+
email?: string;
|
10 |
avatarUrl: string;
|
11 |
hfUserId: string;
|
12 |
|
src/lib/utils/sha256.ts
CHANGED
@@ -1,5 +1,3 @@
|
|
1 |
-
import * as crypto from "crypto";
|
2 |
-
|
3 |
export async function sha256(input: string): Promise<string> {
|
4 |
const utf8 = new TextEncoder().encode(input);
|
5 |
const hashBuffer = await crypto.subtle.digest("SHA-256", utf8);
|
|
|
|
|
|
|
1 |
export async function sha256(input: string): Promise<string> {
|
2 |
const utf8 = new TextEncoder().encode(input);
|
3 |
const hashBuffer = await crypto.subtle.digest("SHA-256", utf8);
|
src/routes/+layout.server.ts
CHANGED
@@ -72,7 +72,11 @@ export const load: LayoutServerLoad = async ({ locals, depends, url }) => {
|
|
72 |
parameters: model.parameters,
|
73 |
})),
|
74 |
oldModels,
|
75 |
-
user: locals.user && {
|
|
|
|
|
|
|
|
|
76 |
requiresLogin: requiresUser,
|
77 |
};
|
78 |
};
|
|
|
72 |
parameters: model.parameters,
|
73 |
})),
|
74 |
oldModels,
|
75 |
+
user: locals.user && {
|
76 |
+
username: locals.user.username,
|
77 |
+
avatarUrl: locals.user.avatarUrl,
|
78 |
+
email: locals.user.email,
|
79 |
+
},
|
80 |
requiresLogin: requiresUser,
|
81 |
};
|
82 |
};
|
src/routes/login/callback/updateUser.ts
CHANGED
@@ -12,10 +12,10 @@ export async function updateUser(params: {
|
|
12 |
cookies: Cookies;
|
13 |
}) {
|
14 |
const { userData, locals, cookies } = params;
|
15 |
-
|
16 |
const {
|
17 |
preferred_username: username,
|
18 |
name,
|
|
|
19 |
picture: avatarUrl,
|
20 |
sub: hfUserId,
|
21 |
} = z
|
@@ -24,6 +24,7 @@ export async function updateUser(params: {
|
|
24 |
name: z.string(),
|
25 |
picture: z.string(),
|
26 |
sub: z.string(),
|
|
|
27 |
})
|
28 |
.parse(userData);
|
29 |
|
@@ -46,6 +47,7 @@ export async function updateUser(params: {
|
|
46 |
updatedAt: new Date(),
|
47 |
username,
|
48 |
name,
|
|
|
49 |
avatarUrl,
|
50 |
hfUserId,
|
51 |
sessionId: locals.sessionId,
|
|
|
12 |
cookies: Cookies;
|
13 |
}) {
|
14 |
const { userData, locals, cookies } = params;
|
|
|
15 |
const {
|
16 |
preferred_username: username,
|
17 |
name,
|
18 |
+
email,
|
19 |
picture: avatarUrl,
|
20 |
sub: hfUserId,
|
21 |
} = z
|
|
|
24 |
name: z.string(),
|
25 |
picture: z.string(),
|
26 |
sub: z.string(),
|
27 |
+
email: z.string().email().optional(),
|
28 |
})
|
29 |
.parse(userData);
|
30 |
|
|
|
47 |
updatedAt: new Date(),
|
48 |
username,
|
49 |
name,
|
50 |
+
email,
|
51 |
avatarUrl,
|
52 |
hfUserId,
|
53 |
sessionId: locals.sessionId,
|