From 2379f87a206936ee45e44a3c196ca081bfc0fd18 Mon Sep 17 00:00:00 2001 From: peintnermax Date: Wed, 21 Aug 2024 12:13:24 +0200 Subject: [PATCH] username, email check for linking --- .../(login)/idp/[provider]/success/page.tsx | 127 ++++-------------- apps/login/src/lib/zitadel.ts | 113 +++++++++++----- 2 files changed, 107 insertions(+), 133 deletions(-) diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index 91bbdb7d41e..134ea2ff656 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -5,6 +5,7 @@ import { getBrandingSettings, getIDPByID, listUsers, + PROVIDER_MAPPING, retrieveIDPIntent, } from "@/lib/zitadel"; import Alert, { AlertType } from "@/ui/Alert"; @@ -12,104 +13,10 @@ import DynamicTheme from "@/ui/DynamicTheme"; import IdpSignin from "@/ui/IdpSignin"; import { AddHumanUserRequest } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { IDPInformation, IDPLink } from "@zitadel/proto/zitadel/user/v2/idp_pb"; +import { AutoLinkingOption } from "@zitadel/proto/zitadel/idp/v2/idp_pb"; + import { PartialMessage } from "@zitadel/client"; -const PROVIDER_MAPPING: { - [provider: string]: ( - rI: IDPInformation, - ) => PartialMessage; -} = { - [ProviderSlug.GOOGLE]: (idp: IDPInformation) => { - const rawInfo = idp.rawInformation?.toJson() as { - User: { - email: string; - name?: string; - given_name?: string; - family_name?: string; - }; - }; - - const idpLink: PartialMessage = { - idpId: idp.idpId, - userId: idp.userId, - userName: idp.userName, - }; - - const req: PartialMessage = { - username: idp.userName, - email: { - email: rawInfo.User?.email, - verification: { case: "isVerified", value: true }, - }, - // organisation: Organisation | undefined; - profile: { - displayName: rawInfo.User?.name ?? "", - givenName: rawInfo.User?.given_name ?? "", - familyName: rawInfo.User?.family_name ?? "", - }, - idpLinks: [idpLink], - }; - return req; - }, - [ProviderSlug.AZURE]: (idp: IDPInformation) => { - const rawInfo = idp.rawInformation?.toJson() as { - mail: string; - displayName?: string; - givenName?: string; - surname?: string; - }; - - const idpLink: PartialMessage = { - idpId: idp.idpId, - userId: idp.userId, - userName: idp.userName, - }; - - const req: PartialMessage = { - username: idp.userName, - email: { - email: rawInfo?.mail, - verification: { case: "isVerified", value: true }, - }, - // organisation: Organisation | undefined; - profile: { - displayName: rawInfo?.displayName ?? "", - givenName: rawInfo?.givenName ?? "", - familyName: rawInfo?.surname ?? "", - }, - idpLinks: [idpLink], - }; - - return req; - }, - [ProviderSlug.GITHUB]: (idp: IDPInformation) => { - const rawInfo = idp.rawInformation?.toJson() as { - email: string; - name: string; - }; - const idpLink: PartialMessage = { - idpId: idp.idpId, - userId: idp.userId, - userName: idp.userName, - }; - const req: PartialMessage = { - username: idp.userName, - email: { - email: rawInfo?.email, - verification: { case: "isVerified", value: true }, - }, - // organisation: Organisation | undefined; - profile: { - displayName: rawInfo?.name ?? "", - givenName: rawInfo?.name ?? "", - familyName: rawInfo?.name ?? "", - }, - idpLinks: [idpLink], - }; - return req; - }, -}; - export default async function Page({ searchParams, params, @@ -151,11 +58,30 @@ export default async function Page({ // search for potential user via username, then link if (options?.isLinkingAllowed) { - const foundUser = await listUsers(idpInformation.userName).then( - (response) => { + let foundUser; + const email = + PROVIDER_MAPPING[provider](idpInformation).email?.email; + + if (options.autoLinking === AutoLinkingOption.EMAIL && email) { + foundUser = await listUsers({ email }).then((response) => { return response.result ? response.result[0] : null; - }, - ); + }); + } else if (options.autoLinking === AutoLinkingOption.USERNAME) { + foundUser = await listUsers( + options.autoLinking === AutoLinkingOption.USERNAME + ? { userName: idpInformation.userName } + : { email }, + ).then((response) => { + return response.result ? response.result[0] : null; + }); + } else { + foundUser = await listUsers({ + userName: idpInformation.userName, + email, + }).then((response) => { + return response.result ? response.result[0] : null; + }); + } if (foundUser) { const idpLink = await addIDPLink( @@ -184,6 +110,7 @@ export default async function Page({ if (idpLink) { return ( + // TODO: possibily login user now

Account successfully linked

diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index a6787d8597a..59dbfde29f9 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -22,6 +22,8 @@ import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb"; import type { RedirectURLs } from "@zitadel/proto/zitadel/user/v2/idp_pb"; import { ProviderSlug } from "./demos"; import { PartialMessage, PlainMessage } from "@zitadel/client"; +import VerifyEmailForm from "@/ui/VerifyEmailForm"; +import { SearchQuery as UserSearchQuery } from "@zitadel/proto/zitadel/user/v2/query_pb"; const SESSION_LIFETIME_S = 3000; @@ -237,40 +239,54 @@ export async function getUserByID(userId: string) { return userService.getUserByID({ userId }, {}); } -export async function listUsers(userName: string, organizationId?: string) { +export async function listUsers({ + userName, + email, + organizationId, +}: { + userName?: string; + email?: string; + organizationId?: string; +}) { + const queries: PartialMessage[] = []; + + if (userName) { + queries.push({ + query: { + case: "userNameQuery", + value: { + userName, + method: TextQueryMethod.EQUALS, + }, + }, + }); + } + + if (organizationId) { + queries.push({ + query: { + case: "organizationIdQuery", + value: { + organizationId, + }, + }, + }); + } + + if (email) { + queries.push({ + query: { + case: "emailQuery", + value: { + emailAddress: email, + }, + }, + }); + } + return userService.listUsers( { - queries: organizationId - ? [ - { - query: { - case: "userNameQuery", - value: { - userName, - method: TextQueryMethod.EQUALS, - }, - }, - }, - { - query: { - case: "organizationIdQuery", - value: { - organizationId, - }, - }, - }, - ] - : [ - { - query: { - case: "userNameQuery", - value: { - userName, - method: TextQueryMethod.EQUALS, - }, - }, - }, - ], + queries: queries, }, {}, ); @@ -384,7 +400,7 @@ export function addIDPLink( ); } -const PROVIDER_MAPPING: { +export const PROVIDER_MAPPING: { [provider: string]: ( rI: IDPInformation, ) => PartialMessage; @@ -421,6 +437,37 @@ const PROVIDER_MAPPING: { }; return req; }, + [ProviderSlug.AZURE]: (idp: IDPInformation) => { + const rawInfo = idp.rawInformation?.toJson() as { + mail: string; + displayName?: string; + givenName?: string; + surname?: string; + }; + + const idpLink: PartialMessage = { + idpId: idp.idpId, + userId: idp.userId, + userName: idp.userName, + }; + + const req: PartialMessage = { + username: idp.userName, + email: { + email: rawInfo?.mail, + verification: { case: "isVerified", value: true }, + }, + // organisation: Organisation | undefined; + profile: { + displayName: rawInfo?.displayName ?? "", + givenName: rawInfo?.givenName ?? "", + familyName: rawInfo?.surname ?? "", + }, + idpLinks: [idpLink], + }; + + return req; + }, [ProviderSlug.GITHUB]: (idp: IDPInformation) => { const rawInfo = idp.rawInformation?.toJson() as { email: string;