Spaces:
Runtime error
Runtime error
feat openid login with google (#250)
Browse filesCo-authored-by: coyotte508 <coyotte508@gmail.com>
- .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,
|