From 1172d6b2152bf73cf881e52a13f4dae888867f1d Mon Sep 17 00:00:00 2001 From: mcc311 Date: Wed, 28 Feb 2024 21:43:44 +0800 Subject: [PATCH 1/4] Refactor `updateUser` to Support Dynamic `nameClaim` Configuration --- .env | 4 ++- src/lib/server/auth.ts | 4 ++- src/routes/login/callback/updateUser.ts | 38 ++++++++++++++++++------- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/.env b/.env index 95017b46544..94bfe486530 100644 --- a/.env +++ b/.env @@ -28,13 +28,15 @@ OPENID_CONFIG=`{ "PROVIDER_URL": "", "CLIENT_ID": "", "CLIENT_SECRET": "", - "SCOPES": "" + "SCOPES": "", + "NAME_CLAIM": "" }` # /!\ legacy openid settings, prefer the config above OPENID_CLIENT_ID= OPENID_CLIENT_SECRET= OPENID_SCOPES="openid profile" # Add "email" for some providers like Google that do not provide preferred_username +OPENID_NAME_CLAIM="name" # Change to "username" for some providers that do not provide name OPENID_PROVIDER_URL=https://huggingface.co # for Google, use https://accounts.google.com OPENID_TOLERANCE= OPENID_RESOURCE= diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index 50ab3fdb4c3..b708c812e26 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -6,6 +6,7 @@ import { OPENID_CLIENT_SECRET, OPENID_PROVIDER_URL, OPENID_SCOPES, + OPENID_NAME_CLAIM, OPENID_TOLERANCE, OPENID_RESOURCE, OPENID_CONFIG, @@ -32,12 +33,13 @@ const stringWithDefault = (value: string) => .default(value) .transform((el) => (el ? el : value)); -const OIDConfig = z +export const OIDConfig = z .object({ CLIENT_ID: stringWithDefault(OPENID_CLIENT_ID), CLIENT_SECRET: stringWithDefault(OPENID_CLIENT_SECRET), PROVIDER_URL: stringWithDefault(OPENID_PROVIDER_URL), SCOPES: stringWithDefault(OPENID_SCOPES), + NAME_CLAIM: stringWithDefault(OPENID_NAME_CLAIM), TOLERANCE: stringWithDefault(OPENID_TOLERANCE), RESOURCE: stringWithDefault(OPENID_RESOURCE), }) diff --git a/src/routes/login/callback/updateUser.ts b/src/routes/login/callback/updateUser.ts index f2fac350531..48e0bf8445c 100644 --- a/src/routes/login/callback/updateUser.ts +++ b/src/routes/login/callback/updateUser.ts @@ -8,6 +8,7 @@ import { error, type Cookies } from "@sveltejs/kit"; import crypto from "crypto"; import { sha256 } from "$lib/utils/sha256"; import { addWeeks } from "date-fns"; +import { OIDConfig } from "$lib/server/auth"; export async function updateUser(params: { userData: UserinfoResponse; @@ -24,24 +25,41 @@ export async function updateUser(params: { userData.preferred_username = userData.upn as string; } - const { - preferred_username: username, - name, - email, - picture: avatarUrl, - sub: hfUserId, - } = z + const nameClaim: Exclude = OIDConfig.NAME_CLAIM; + + if (['preferred_username', 'email', 'picture', 'sub'].includes(nameClaim)) { + throw new Error(`nameClaim cannot be one of the restricted keys.`); + } + + const parsedUserData = z .object({ preferred_username: z.string().optional(), - name: z.string(), + [nameClaim]: z.string(), // Dynamically set based on NAME_CLAIM picture: z.string().optional(), sub: z.string(), email: z.string().email().optional(), }) - .refine((data) => data.preferred_username || data.email, { + .refine(data => data.preferred_username || data.email, { message: "Either preferred_username or email must be provided by the provider.", }) - .parse(userData); + .parse(userData) as { + preferred_username?: string; + email?: string; + picture?: string; + sub: string; + } & Record; + + const { + preferred_username: username, + email, + picture: avatarUrl, + sub: hfUserId, + } = parsedUserData; + + // Dynamically access user data based on NAME_CLAIM from environment + // This approach allows us to adapt to different OIDC providers flexibly. + const name = parsedUserData[nameClaim]; + // check if user already exists const existingUser = await collections.users.findOne({ hfUserId }); From 8433bc2531839b642712c50992db668a31de5bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=98XGungo=E2=80=99?= <‘daniel2000890311g@gmail.com’> Date: Fri, 8 Mar 2024 20:40:20 +0800 Subject: [PATCH 2/4] style: run Prettier to fix lint errors --- src/routes/login/callback/updateUser.ts | 27 ++++++++++--------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/routes/login/callback/updateUser.ts b/src/routes/login/callback/updateUser.ts index 48e0bf8445c..8c213d09707 100644 --- a/src/routes/login/callback/updateUser.ts +++ b/src/routes/login/callback/updateUser.ts @@ -25,9 +25,10 @@ export async function updateUser(params: { userData.preferred_username = userData.upn as string; } - const nameClaim: Exclude = OIDConfig.NAME_CLAIM; + const nameClaim: Exclude = + OIDConfig.NAME_CLAIM; - if (['preferred_username', 'email', 'picture', 'sub'].includes(nameClaim)) { + if (["preferred_username", "email", "picture", "sub"].includes(nameClaim)) { throw new Error(`nameClaim cannot be one of the restricted keys.`); } @@ -39,28 +40,22 @@ export async function updateUser(params: { sub: z.string(), email: z.string().email().optional(), }) - .refine(data => data.preferred_username || data.email, { + .refine((data) => data.preferred_username || data.email, { message: "Either preferred_username or email must be provided by the provider.", }) .parse(userData) as { - preferred_username?: string; - email?: string; - picture?: string; - sub: string; - } & Record; - - const { - preferred_username: username, - email, - picture: avatarUrl, - sub: hfUserId, - } = parsedUserData; + preferred_username?: string; + email?: string; + picture?: string; + sub: string; + } & Record; + + const { preferred_username: username, email, picture: avatarUrl, sub: hfUserId } = parsedUserData; // Dynamically access user data based on NAME_CLAIM from environment // This approach allows us to adapt to different OIDC providers flexibly. const name = parsedUserData[nameClaim]; - // check if user already exists const existingUser = await collections.users.findOne({ hfUserId }); let userId = existingUser?._id; From c0d98cf0abc16a3fd7309c059c1627d8cda6e949 Mon Sep 17 00:00:00 2001 From: Nathan Sarrazin Date: Fri, 15 Mar 2024 15:16:39 +0100 Subject: [PATCH 3/4] Validate NAME_CLAIM in OIDConfig schema --- src/lib/server/auth.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index b708c812e26..96e6ec8f345 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -39,7 +39,10 @@ export const OIDConfig = z CLIENT_SECRET: stringWithDefault(OPENID_CLIENT_SECRET), PROVIDER_URL: stringWithDefault(OPENID_PROVIDER_URL), SCOPES: stringWithDefault(OPENID_SCOPES), - NAME_CLAIM: stringWithDefault(OPENID_NAME_CLAIM), + NAME_CLAIM: stringWithDefault(OPENID_NAME_CLAIM).refine( + (el) => !["preferred_username", "email", "picture", "sub"].includes(el), + { message: "nameClaim cannot be one of the restricted keys." } + ), TOLERANCE: stringWithDefault(OPENID_TOLERANCE), RESOURCE: stringWithDefault(OPENID_RESOURCE), }) From bc25544209e73cca8f54de210452a029c7cd0302 Mon Sep 17 00:00:00 2001 From: Nathan Sarrazin Date: Fri, 15 Mar 2024 15:16:50 +0100 Subject: [PATCH 4/4] Refactor userData parsing --- src/routes/login/callback/updateUser.ts | 26 +++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/routes/login/callback/updateUser.ts b/src/routes/login/callback/updateUser.ts index 8c213d09707..90b83116121 100644 --- a/src/routes/login/callback/updateUser.ts +++ b/src/routes/login/callback/updateUser.ts @@ -25,36 +25,38 @@ export async function updateUser(params: { userData.preferred_username = userData.upn as string; } - const nameClaim: Exclude = - OIDConfig.NAME_CLAIM; - - if (["preferred_username", "email", "picture", "sub"].includes(nameClaim)) { - throw new Error(`nameClaim cannot be one of the restricted keys.`); - } - - const parsedUserData = z + const { + preferred_username: username, + name, + email, + picture: avatarUrl, + sub: hfUserId, + } = z .object({ preferred_username: z.string().optional(), - [nameClaim]: z.string(), // Dynamically set based on NAME_CLAIM + name: z.string(), picture: z.string().optional(), sub: z.string(), email: z.string().email().optional(), }) + .setKey(OIDConfig.NAME_CLAIM, z.string()) .refine((data) => data.preferred_username || data.email, { message: "Either preferred_username or email must be provided by the provider.", }) + .transform((data) => ({ + ...data, + name: data[OIDConfig.NAME_CLAIM], + })) .parse(userData) as { preferred_username?: string; email?: string; picture?: string; sub: string; + name: string; } & Record; - const { preferred_username: username, email, picture: avatarUrl, sub: hfUserId } = parsedUserData; - // Dynamically access user data based on NAME_CLAIM from environment // This approach allows us to adapt to different OIDC providers flexibly. - const name = parsedUserData[nameClaim]; // check if user already exists const existingUser = await collections.users.findOne({ hfUserId });