Enhance Dynamic User Attribute Handling in OIDC Integration (#885)
Browse files* Refactor `updateUser` to Support Dynamic `nameClaim` Configuration
* style: run Prettier to fix lint errors
* Validate NAME_CLAIM in OIDConfig schema
* Refactor userData parsing
---------
Co-authored-by: Nathan Sarrazin <sarrazin.nathan@gmail.com>
Co-authored-by: ‘XGungo’ <‘daniel2000890311g@gmail.com’>
- .env +3 -1
- src/lib/server/auth.ts +6 -1
- src/routes/login/callback/updateUser.ts +16 -1
.env
CHANGED
@@ -29,13 +29,15 @@ OPENID_CONFIG=`{
|
|
29 |
"PROVIDER_URL": "",
|
30 |
"CLIENT_ID": "",
|
31 |
"CLIENT_SECRET": "",
|
32 |
-
"SCOPES": ""
|
|
|
33 |
}`
|
34 |
|
35 |
# /!\ legacy openid settings, prefer the config above
|
36 |
OPENID_CLIENT_ID=
|
37 |
OPENID_CLIENT_SECRET=
|
38 |
OPENID_SCOPES="openid profile" # Add "email" for some providers like Google that do not provide preferred_username
|
|
|
39 |
OPENID_PROVIDER_URL=https://huggingface.co # for Google, use https://accounts.google.com
|
40 |
OPENID_TOLERANCE=
|
41 |
OPENID_RESOURCE=
|
|
|
29 |
"PROVIDER_URL": "",
|
30 |
"CLIENT_ID": "",
|
31 |
"CLIENT_SECRET": "",
|
32 |
+
"SCOPES": "",
|
33 |
+
"NAME_CLAIM": ""
|
34 |
}`
|
35 |
|
36 |
# /!\ legacy openid settings, prefer the config above
|
37 |
OPENID_CLIENT_ID=
|
38 |
OPENID_CLIENT_SECRET=
|
39 |
OPENID_SCOPES="openid profile" # Add "email" for some providers like Google that do not provide preferred_username
|
40 |
+
OPENID_NAME_CLAIM="name" # Change to "username" for some providers that do not provide name
|
41 |
OPENID_PROVIDER_URL=https://huggingface.co # for Google, use https://accounts.google.com
|
42 |
OPENID_TOLERANCE=
|
43 |
OPENID_RESOURCE=
|
src/lib/server/auth.ts
CHANGED
@@ -6,6 +6,7 @@ import {
|
|
6 |
OPENID_CLIENT_SECRET,
|
7 |
OPENID_PROVIDER_URL,
|
8 |
OPENID_SCOPES,
|
|
|
9 |
OPENID_TOLERANCE,
|
10 |
OPENID_RESOURCE,
|
11 |
OPENID_CONFIG,
|
@@ -32,12 +33,16 @@ const stringWithDefault = (value: string) =>
|
|
32 |
.default(value)
|
33 |
.transform((el) => (el ? el : value));
|
34 |
|
35 |
-
const OIDConfig = z
|
36 |
.object({
|
37 |
CLIENT_ID: stringWithDefault(OPENID_CLIENT_ID),
|
38 |
CLIENT_SECRET: stringWithDefault(OPENID_CLIENT_SECRET),
|
39 |
PROVIDER_URL: stringWithDefault(OPENID_PROVIDER_URL),
|
40 |
SCOPES: stringWithDefault(OPENID_SCOPES),
|
|
|
|
|
|
|
|
|
41 |
TOLERANCE: stringWithDefault(OPENID_TOLERANCE),
|
42 |
RESOURCE: stringWithDefault(OPENID_RESOURCE),
|
43 |
})
|
|
|
6 |
OPENID_CLIENT_SECRET,
|
7 |
OPENID_PROVIDER_URL,
|
8 |
OPENID_SCOPES,
|
9 |
+
OPENID_NAME_CLAIM,
|
10 |
OPENID_TOLERANCE,
|
11 |
OPENID_RESOURCE,
|
12 |
OPENID_CONFIG,
|
|
|
33 |
.default(value)
|
34 |
.transform((el) => (el ? el : value));
|
35 |
|
36 |
+
export const OIDConfig = z
|
37 |
.object({
|
38 |
CLIENT_ID: stringWithDefault(OPENID_CLIENT_ID),
|
39 |
CLIENT_SECRET: stringWithDefault(OPENID_CLIENT_SECRET),
|
40 |
PROVIDER_URL: stringWithDefault(OPENID_PROVIDER_URL),
|
41 |
SCOPES: stringWithDefault(OPENID_SCOPES),
|
42 |
+
NAME_CLAIM: stringWithDefault(OPENID_NAME_CLAIM).refine(
|
43 |
+
(el) => !["preferred_username", "email", "picture", "sub"].includes(el),
|
44 |
+
{ message: "nameClaim cannot be one of the restricted keys." }
|
45 |
+
),
|
46 |
TOLERANCE: stringWithDefault(OPENID_TOLERANCE),
|
47 |
RESOURCE: stringWithDefault(OPENID_RESOURCE),
|
48 |
})
|
src/routes/login/callback/updateUser.ts
CHANGED
@@ -8,6 +8,7 @@ import { error, type Cookies } from "@sveltejs/kit";
|
|
8 |
import crypto from "crypto";
|
9 |
import { sha256 } from "$lib/utils/sha256";
|
10 |
import { addWeeks } from "date-fns";
|
|
|
11 |
|
12 |
export async function updateUser(params: {
|
13 |
userData: UserinfoResponse;
|
@@ -38,10 +39,24 @@ export async function updateUser(params: {
|
|
38 |
sub: z.string(),
|
39 |
email: z.string().email().optional(),
|
40 |
})
|
|
|
41 |
.refine((data) => data.preferred_username || data.email, {
|
42 |
message: "Either preferred_username or email must be provided by the provider.",
|
43 |
})
|
44 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
|
46 |
// check if user already exists
|
47 |
const existingUser = await collections.users.findOne({ hfUserId });
|
|
|
8 |
import crypto from "crypto";
|
9 |
import { sha256 } from "$lib/utils/sha256";
|
10 |
import { addWeeks } from "date-fns";
|
11 |
+
import { OIDConfig } from "$lib/server/auth";
|
12 |
|
13 |
export async function updateUser(params: {
|
14 |
userData: UserinfoResponse;
|
|
|
39 |
sub: z.string(),
|
40 |
email: z.string().email().optional(),
|
41 |
})
|
42 |
+
.setKey(OIDConfig.NAME_CLAIM, z.string())
|
43 |
.refine((data) => data.preferred_username || data.email, {
|
44 |
message: "Either preferred_username or email must be provided by the provider.",
|
45 |
})
|
46 |
+
.transform((data) => ({
|
47 |
+
...data,
|
48 |
+
name: data[OIDConfig.NAME_CLAIM],
|
49 |
+
}))
|
50 |
+
.parse(userData) as {
|
51 |
+
preferred_username?: string;
|
52 |
+
email?: string;
|
53 |
+
picture?: string;
|
54 |
+
sub: string;
|
55 |
+
name: string;
|
56 |
+
} & Record<string, string>;
|
57 |
+
|
58 |
+
// Dynamically access user data based on NAME_CLAIM from environment
|
59 |
+
// This approach allows us to adapt to different OIDC providers flexibly.
|
60 |
|
61 |
// check if user already exists
|
62 |
const existingUser = await collections.users.findOne({ hfUserId });
|