Chang Chi, Meng nsarrazin HF staff ‘XGungo’ commited on
Commit
c6129c3
1 Parent(s): 8583cf1

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 <[email protected]>
Co-authored-by: ‘XGungo’ <‘[email protected]’>

.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
- .parse(userData);
 
 
 
 
 
 
 
 
 
 
 
 
 
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 });