Spaces:
Running
Running
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>
- .env +11 -1
- README.md +8 -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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
93 |
-
|
94 |
-
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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(
|
|
|
62 |
return new issuer.Client({
|
63 |
-
client_id:
|
64 |
-
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 |
-
|
77 |
-
|
|
|
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> {
|