fredguth coyotte508 HF staff commited on
Commit
fa3b3b4
1 Parent(s): 101f9ef

feat openid login with google (#250)

Browse files

Co-authored-by: coyotte508 <coyotte508@gmail.com>

.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
- OPENID_PROVIDER_URL=https://huggingface.co
 
 
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: { username: string } | undefined;
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 class="flex h-9 flex-none items-center gap-1.5 pr-2 text-gray-500 dark:text-gray-400"
52
- >{user?.username}</span
 
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: OIDC_SCOPES,
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: string;
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 && { username: locals.user.username, avatarUrl: locals.user.avatarUrl },
 
 
 
 
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,