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 <[email protected]>
- .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> {
|