itaybar nsarrazin HF staff commited on
Commit
e69cb4a
1 Parent(s): 1e5090f

Feature/add OIDC optional tolerance and resource (#496)

Browse files

* Added optional tolerance for OIDC jwt

* Added optional OIDC resource for authorizationURL

* Refactored open id params with zod

---------

Co-authored-by: Nathan Sarrazin <sarrazin.nathan@gmail.com>

Files changed (3) hide show
  1. .env +11 -1
  2. README.md +8 -3
  3. src/lib/server/auth.ts +31 -9
.env CHANGED
@@ -13,11 +13,21 @@ HF_API_ROOT=https://api-inference.huggingface.co/models
13
  SERPER_API_KEY=#your serper.dev api key here
14
  SERPAPI_KEY=#your serpapi key here
15
 
16
- # Parameters to enable "Sign in with HF"
 
 
 
 
 
 
 
 
17
  OPENID_CLIENT_ID=
18
  OPENID_CLIENT_SECRET=
19
  OPENID_SCOPES="openid profile" # Add "email" for some providers like Google that do not provide preferred_username
20
  OPENID_PROVIDER_URL=https://huggingface.co # for Google, use https://accounts.google.com
 
 
21
 
22
  # Parameters to enable a global mTLS context for client fetch requests
23
  USE_CLIENT_CERTIFICATE=false
 
13
  SERPER_API_KEY=#your serper.dev api key here
14
  SERPAPI_KEY=#your serpapi key here
15
 
16
+ # Parameters to enable open id login
17
+ OPENID_CONFIG=`{
18
+ "PROVIDER_URL": "",
19
+ "CLIENT_ID": "",
20
+ "CLIENT_SECRET": "",
21
+ "SCOPES": ""
22
+ }`
23
+
24
+ # /!\ legacy openid settings, prefer the config above
25
  OPENID_CLIENT_ID=
26
  OPENID_CLIENT_SECRET=
27
  OPENID_SCOPES="openid profile" # Add "email" for some providers like Google that do not provide preferred_username
28
  OPENID_PROVIDER_URL=https://huggingface.co # for Google, use https://accounts.google.com
29
+ OPENID_TOLERANCE=
30
+ OPENID_RESOURCE=
31
 
32
  # Parameters to enable a global mTLS context for client fetch requests
33
  USE_CLIENT_CERTIFICATE=false
README.md CHANGED
@@ -89,9 +89,14 @@ Chat UI features a powerful Web Search feature. It works by:
89
  The login feature is disabled by default and users are attributed a unique ID based on their browser. But if you want to use OpenID to authenticate your users, you can add the following to your `.env.local` file:
90
 
91
  ```env
92
- OPENID_PROVIDER_URL=<your OIDC issuer>
93
- OPENID_CLIENT_ID=<your OIDC client ID>
94
- OPENID_CLIENT_SECRET=<your OIDC client secret>
 
 
 
 
 
95
  ```
96
 
97
  These variables will enable the openID sign-in modal for users.
 
89
  The login feature is disabled by default and users are attributed a unique ID based on their browser. But if you want to use OpenID to authenticate your users, you can add the following to your `.env.local` file:
90
 
91
  ```env
92
+ OPENID_CONFIG=`{
93
+ PROVIDER_URL: "<your OIDC issuer>",
94
+ CLIENT_ID: "<your OIDC client ID>",
95
+ CLIENT_SECRET: "<your OIDC client secret>",
96
+ SCOPES: "openid profile",
97
+ TOLERANCE: // optional
98
+ RESOURCE: // optional
99
+ }`
100
  ```
101
 
102
  These variables will enable the openID sign-in modal for users.
src/lib/server/auth.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Issuer, BaseClient, type UserinfoResponse, TokenSet } from "openid-client";
2
  import { addHours, addYears } from "date-fns";
3
  import {
4
  COOKIE_NAME,
@@ -6,6 +6,9 @@ import {
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";
@@ -21,7 +24,24 @@ export interface OIDCUserInfo {
21
  userData: UserinfoResponse;
22
  }
23
 
24
- export const requiresUser = !!OPENID_CLIENT_ID && !!OPENID_CLIENT_SECRET;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
  export function refreshSessionCookie(cookies: Cookies, sessionId: string) {
27
  cookies.set(COOKIE_NAME, sessionId, {
@@ -58,12 +78,14 @@ export async function generateCsrfToken(sessionId: string, redirectUrl: string):
58
  }
59
 
60
  async function getOIDCClient(settings: OIDCSettings): Promise<BaseClient> {
61
- const issuer = await Issuer.discover(OPENID_PROVIDER_URL);
 
62
  return new issuer.Client({
63
- client_id: OPENID_CLIENT_ID,
64
- client_secret: OPENID_CLIENT_SECRET,
65
  redirect_uris: [settings.redirectURI],
66
  response_types: ["code"],
 
67
  });
68
  }
69
 
@@ -73,12 +95,12 @@ export async function getOIDCAuthorizationUrl(
73
  ): Promise<string> {
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
-
81
- return url;
82
  }
83
 
84
  export async function getOIDCUserData(settings: OIDCSettings, code: string): Promise<OIDCUserInfo> {
 
1
+ import { Issuer, BaseClient, type UserinfoResponse, TokenSet, custom } from "openid-client";
2
  import { addHours, addYears } from "date-fns";
3
  import {
4
  COOKIE_NAME,
 
6
  OPENID_CLIENT_SECRET,
7
  OPENID_PROVIDER_URL,
8
  OPENID_SCOPES,
9
+ OPENID_TOLERANCE,
10
+ OPENID_RESOURCE,
11
+ OPENID_CONFIG,
12
  } from "$env/static/private";
13
  import { sha256 } from "$lib/utils/sha256";
14
  import { z } from "zod";
 
24
  userData: UserinfoResponse;
25
  }
26
 
27
+ const stringWithDefault = (value: string) =>
28
+ z
29
+ .string()
30
+ .default(value)
31
+ .transform((el) => (el ? el : value));
32
+
33
+ const OIDConfig = z
34
+ .object({
35
+ CLIENT_ID: stringWithDefault(OPENID_CLIENT_ID),
36
+ CLIENT_SECRET: stringWithDefault(OPENID_CLIENT_SECRET),
37
+ PROVIDER_URL: stringWithDefault(OPENID_PROVIDER_URL),
38
+ SCOPES: stringWithDefault(OPENID_SCOPES),
39
+ TOLERANCE: stringWithDefault(OPENID_TOLERANCE),
40
+ RESOURCE: stringWithDefault(OPENID_RESOURCE),
41
+ })
42
+ .parse(JSON.parse(OPENID_CONFIG));
43
+
44
+ export const requiresUser = !!OIDConfig.CLIENT_ID && !!OIDConfig.CLIENT_SECRET;
45
 
46
  export function refreshSessionCookie(cookies: Cookies, sessionId: string) {
47
  cookies.set(COOKIE_NAME, sessionId, {
 
78
  }
79
 
80
  async function getOIDCClient(settings: OIDCSettings): Promise<BaseClient> {
81
+ const issuer = await Issuer.discover(OIDConfig.PROVIDER_URL);
82
+
83
  return new issuer.Client({
84
+ client_id: OIDConfig.CLIENT_ID,
85
+ client_secret: OIDConfig.CLIENT_SECRET,
86
  redirect_uris: [settings.redirectURI],
87
  response_types: ["code"],
88
+ [custom.clock_tolerance]: OIDConfig.TOLERANCE || undefined,
89
  });
90
  }
91
 
 
95
  ): Promise<string> {
96
  const client = await getOIDCClient(settings);
97
  const csrfToken = await generateCsrfToken(params.sessionId, settings.redirectURI);
98
+
99
+ return client.authorizationUrl({
100
+ scope: OIDConfig.SCOPES,
101
  state: csrfToken,
102
+ resource: OIDConfig.RESOURCE || undefined,
103
  });
 
 
104
  }
105
 
106
  export async function getOIDCUserData(settings: OIDCSettings, code: string): Promise<OIDCUserInfo> {