provider mappings

This commit is contained in:
peintnermax
2024-09-11 16:49:26 +02:00
parent 39bb4c2994
commit 45a205dd9a
9 changed files with 5645 additions and 4248 deletions

View File

@@ -1,14 +1,31 @@
import { getBrandingSettings } from "@/lib/zitadel"; import { loadMostRecentSession } from "@/lib/session";
import { getBrandingSettings, getLoginSettings } from "@/lib/zitadel";
import Alert from "@/ui/Alert";
import DynamicTheme from "@/ui/DynamicTheme"; import DynamicTheme from "@/ui/DynamicTheme";
import UserAvatar from "@/ui/UserAvatar";
import VerifyEmailForm from "@/ui/VerifyEmailForm"; import VerifyEmailForm from "@/ui/VerifyEmailForm";
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
export default async function Page({ searchParams }: { searchParams: any }) { export default async function Page({ searchParams }: { searchParams: any }) {
const { userId, sessionId, code, submit, organization, authRequestId } = const {
searchParams; userId,
loginName,
sessionId,
code,
submit,
organization,
authRequestId,
} = searchParams;
const sessionFactors = await loadMostRecentSession({
loginName,
organization,
});
const branding = await getBrandingSettings(organization); const branding = await getBrandingSettings(organization);
const loginSettings = await getLoginSettings(organization);
return ( return (
<DynamicTheme branding={branding}> <DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4"> <div className="flex flex-col items-center space-y-4">
@@ -17,14 +34,40 @@ export default async function Page({ searchParams }: { searchParams: any }) {
Enter the Code provided in the verification email. Enter the Code provided in the verification email.
</p> </p>
{(!sessionFactors || !loginName) && (
<div className="py-4">
<Alert>
Could not get the context of the user. Make sure to enter the
username first or provide a loginName as searchParam.
</Alert>
</div>
)}
{sessionFactors && (
<UserAvatar
loginName={loginName ?? sessionFactors.factors?.user?.loginName}
displayName={sessionFactors.factors?.user?.displayName}
showDropdown
searchParams={searchParams}
></UserAvatar>
)}
{userId ? ( {userId ? (
<VerifyEmailForm <VerifyEmailForm
userId={userId} userId={userId}
loginName={loginName}
code={code} code={code}
submit={submit === "true"} submit={submit === "true"}
organization={organization} organization={organization}
authRequestId={authRequestId} authRequestId={authRequestId}
sessionId={sessionId} sessionId={sessionId}
loginSettings={loginSettings}
hasMfaSetUp={
!!sessionFactors?.factors?.otpEmail?.verifiedAt ||
!!sessionFactors?.factors?.otpSms?.verifiedAt ||
!!sessionFactors?.factors?.totp?.verifiedAt ||
!!sessionFactors?.factors?.webAuthN?.verifiedAt
}
/> />
) : ( ) : (
<div className="w-full flex flex-row items-center justify-center border border-yellow-600/40 dark:border-yellow-500/20 bg-yellow-200/30 text-yellow-600 dark:bg-yellow-700/20 dark:text-yellow-200 rounded-md py-2 scroll-px-40"> <div className="w-full flex flex-row items-center justify-center border border-yellow-600/40 dark:border-yellow-500/20 bg-yellow-200/30 text-yellow-600 dark:bg-yellow-700/20 dark:text-yellow-200 rounded-md py-2 scroll-px-40">

View File

@@ -35,6 +35,57 @@ export type OIDC_USER = {
}; };
}; };
const OIDC_MAPPING = (idp: IDPInformation) => {
const rawInfo = idp.rawInformation as OIDC_USER;
return create(AddHumanUserRequestSchema, {
username: idp.userName,
email: {
email: rawInfo.User?.email,
verification: { case: "isVerified", value: true },
},
profile: {
displayName: rawInfo.User?.name ?? "",
givenName: rawInfo.User?.given_name ?? "",
familyName: rawInfo.User?.family_name ?? "",
},
idpLinks: [
{
idpId: idp.idpId,
userId: idp.userId,
userName: idp.userName,
},
],
});
};
const GITHUB_MAPPING = (idp: IDPInformation) => {
const rawInfo = idp.rawInformation as {
email: string;
name: string;
};
return create(AddHumanUserRequestSchema, {
username: idp.userName,
email: {
email: rawInfo.email,
verification: { case: "isVerified", value: true },
},
profile: {
displayName: rawInfo.name ?? "",
givenName: rawInfo.name ?? "",
familyName: rawInfo.name ?? "",
},
idpLinks: [
{
idpId: idp.idpId,
userId: idp.userId,
userName: idp.userName,
},
],
});
};
export const PROVIDER_MAPPING: { export const PROVIDER_MAPPING: {
[provider: string]: (rI: IDPInformation) => AddHumanUserRequest; [provider: string]: (rI: IDPInformation) => AddHumanUserRequest;
} = { } = {
@@ -62,6 +113,11 @@ export const PROVIDER_MAPPING: {
], ],
}); });
}, },
[idpTypeToSlug(IdentityProviderType.GITLAB)]: OIDC_MAPPING,
[idpTypeToSlug(IdentityProviderType.GITLAB_SELF_HOSTED)]: OIDC_MAPPING,
[idpTypeToSlug(IdentityProviderType.OIDC)]: OIDC_MAPPING,
// check
[idpTypeToSlug(IdentityProviderType.OAUTH)]: OIDC_MAPPING,
[idpTypeToSlug(IdentityProviderType.AZURE_AD)]: (idp: IDPInformation) => { [idpTypeToSlug(IdentityProviderType.AZURE_AD)]: (idp: IDPInformation) => {
const rawInfo = idp.rawInformation as { const rawInfo = idp.rawInformation as {
jobTitle: string; jobTitle: string;
@@ -76,8 +132,6 @@ export const PROVIDER_MAPPING: {
userPrincipalName: string; userPrincipalName: string;
}; };
console.log(rawInfo, rawInfo.userPrincipalName);
return create(AddHumanUserRequestSchema, { return create(AddHumanUserRequestSchema, {
username: idp.userName, username: idp.userName,
email: { email: {
@@ -98,22 +152,26 @@ export const PROVIDER_MAPPING: {
], ],
}); });
}, },
[idpTypeToSlug(IdentityProviderType.GITHUB)]: (idp: IDPInformation) => { [idpTypeToSlug(IdentityProviderType.GITHUB)]: GITHUB_MAPPING,
[idpTypeToSlug(IdentityProviderType.GITHUB_ES)]: GITHUB_MAPPING,
[idpTypeToSlug(IdentityProviderType.APPLE)]: (idp: IDPInformation) => {
const rawInfo = idp.rawInformation as { const rawInfo = idp.rawInformation as {
email: string; name?: string;
name: string; firstName?: string;
lastName?: string;
email?: string;
}; };
return create(AddHumanUserRequestSchema, { return create(AddHumanUserRequestSchema, {
username: idp.userName, username: idp.userName,
email: { email: {
email: rawInfo.email, email: rawInfo.email ?? "",
verification: { case: "isVerified", value: true }, verification: { case: "isVerified", value: true },
}, },
profile: { profile: {
displayName: rawInfo.name ?? "", displayName: rawInfo.name ?? "",
givenName: rawInfo.name ?? "", givenName: rawInfo.firstName ?? "",
familyName: rawInfo.name ?? "", familyName: rawInfo.lastName ?? "",
}, },
idpLinks: [ idpLinks: [
{ {

View File

@@ -30,8 +30,6 @@ import { PROVIDER_MAPPING } from "./idp";
const SESSION_LIFETIME_S = 3600; const SESSION_LIFETIME_S = 3600;
console.log("Session lifetime", SESSION_LIFETIME_S);
const transport = createServerTransport( const transport = createServerTransport(
process.env.ZITADEL_SERVICE_USER_TOKEN!, process.env.ZITADEL_SERVICE_USER_TOKEN!,
{ {

View File

@@ -75,19 +75,23 @@ export default function SetPasswordForm({
setError("Could not register user"); setError("Could not register user");
return; return;
} }
const params: any = { userId: response.userId };
const params = new URLSearchParams({ userId: response.userId });
if (response.factors?.user?.loginName) {
params.append("loginName", response.factors.user.loginName);
}
if (authRequestId) { if (authRequestId) {
params.authRequestId = authRequestId; params.append("authRequestId", authRequestId);
} }
if (organization) { if (organization) {
params.organization = organization; params.append("organization", organization);
} }
if (response && response.sessionId) { if (response && response.sessionId) {
params.sessionId = response.sessionId; params.append("sessionId", response.sessionId);
} }
return router.push(`/verify?` + new URLSearchParams(params)); return router.push(`/verify?` + params);
} }
const { errors } = formState; const { errors } = formState;

View File

@@ -9,7 +9,9 @@ import {
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { ReactNode, useState } from "react"; import { ReactNode, useState } from "react";
import Alert from "./Alert"; import Alert from "./Alert";
import { SignInWithApple } from "./idps/SignInWithApple";
import { SignInWithAzureAD } from "./idps/SignInWithAzureAD"; import { SignInWithAzureAD } from "./idps/SignInWithAzureAD";
import { SignInWithGeneric } from "./idps/SignInWithGeneric";
import { SignInWithGithub } from "./idps/SignInWithGithub"; import { SignInWithGithub } from "./idps/SignInWithGithub";
import { SignInWithGitlab } from "./idps/SignInWithGitlab"; import { SignInWithGitlab } from "./idps/SignInWithGitlab";
import { SignInWithGoogle } from "./idps/SignInWithGoogle"; import { SignInWithGoogle } from "./idps/SignInWithGoogle";
@@ -76,10 +78,41 @@ export function SignInWithIDP({
{identityProviders && {identityProviders &&
identityProviders.map((idp, i) => { identityProviders.map((idp, i) => {
switch (idp.type) { switch (idp.type) {
case IdentityProviderType.APPLE:
return (
<SignInWithApple
key={`idp-${i}`}
name={idp.name}
onClick={() =>
navigateToAuthUrl(idp.id, IdentityProviderType.APPLE)
}
></SignInWithApple>
);
case IdentityProviderType.OAUTH:
return (
<SignInWithGeneric
key={`idp-${i}`}
name={idp.name}
onClick={() =>
navigateToAuthUrl(idp.id, IdentityProviderType.OAUTH)
}
></SignInWithGeneric>
);
case IdentityProviderType.OIDC:
return (
<SignInWithGeneric
key={`idp-${i}`}
name={idp.name}
onClick={() =>
navigateToAuthUrl(idp.id, IdentityProviderType.OIDC)
}
></SignInWithGeneric>
);
case IdentityProviderType.GITHUB: case IdentityProviderType.GITHUB:
return ( return (
<SignInWithGithub <SignInWithGithub
key={`idp-${i}`} key={`idp-${i}`}
name={idp.name}
onClick={() => onClick={() =>
navigateToAuthUrl(idp.id, IdentityProviderType.GITHUB) navigateToAuthUrl(idp.id, IdentityProviderType.GITHUB)
} }
@@ -89,6 +122,7 @@ export function SignInWithIDP({
return ( return (
<SignInWithGithub <SignInWithGithub
key={`idp-${i}`} key={`idp-${i}`}
name={idp.name}
onClick={() => onClick={() =>
navigateToAuthUrl(idp.id, IdentityProviderType.GITHUB_ES) navigateToAuthUrl(idp.id, IdentityProviderType.GITHUB_ES)
} }
@@ -98,6 +132,7 @@ export function SignInWithIDP({
return ( return (
<SignInWithAzureAD <SignInWithAzureAD
key={`idp-${i}`} key={`idp-${i}`}
name={idp.name}
onClick={() => onClick={() =>
navigateToAuthUrl(idp.id, IdentityProviderType.AZURE_AD) navigateToAuthUrl(idp.id, IdentityProviderType.AZURE_AD)
} }
@@ -118,6 +153,7 @@ export function SignInWithIDP({
return ( return (
<SignInWithGitlab <SignInWithGitlab
key={`idp-${i}`} key={`idp-${i}`}
name={idp.name}
onClick={() => onClick={() =>
navigateToAuthUrl(idp.id, IdentityProviderType.GITLAB) navigateToAuthUrl(idp.id, IdentityProviderType.GITLAB)
} }
@@ -127,6 +163,7 @@ export function SignInWithIDP({
return ( return (
<SignInWithGitlab <SignInWithGitlab
key={`idp-${i}`} key={`idp-${i}`}
name={idp.name}
onClick={() => onClick={() =>
navigateToAuthUrl( navigateToAuthUrl(
idp.id, idp.id,

View File

@@ -2,6 +2,7 @@
import { resendVerifyEmail, verifyUserByEmail } from "@/lib/server/email"; import { resendVerifyEmail, verifyUserByEmail } from "@/lib/server/email";
import Alert from "@/ui/Alert"; import Alert from "@/ui/Alert";
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
@@ -15,20 +16,26 @@ type Inputs = {
type Props = { type Props = {
userId: string; userId: string;
loginName: string;
code: string; code: string;
submit: boolean; submit: boolean;
organization?: string; organization?: string;
authRequestId?: string; authRequestId?: string;
sessionId?: string; sessionId?: string;
loginSettings?: LoginSettings;
hasMfaSetUp: boolean;
}; };
export default function VerifyEmailForm({ export default function VerifyEmailForm({
userId, userId,
loginName,
code, code,
submit, submit,
organization, organization,
authRequestId, authRequestId,
sessionId, sessionId,
loginSettings,
hasMfaSetUp,
}: Props) { }: Props) {
const { register, handleSubmit, formState } = useForm<Inputs>({ const { register, handleSubmit, formState } = useForm<Inputs>({
mode: "onBlur", mode: "onBlur",
@@ -71,7 +78,7 @@ export default function VerifyEmailForm({
userId, userId,
}).catch((error: Error) => { }).catch((error: Error) => {
setLoading(false); setLoading(false);
setError(error.message); setError(error.message ?? "Could not verify email");
}); });
setLoading(false); setLoading(false);
@@ -81,6 +88,24 @@ export default function VerifyEmailForm({
return; return;
} }
if (loginSettings && loginSettings.forceMfa && !hasMfaSetUp) {
const params = new URLSearchParams({ checkAfter: "true" });
if (loginName) {
params.set("organization", loginName);
}
if (organization) {
params.set("organization", organization);
}
if (authRequestId && sessionId) {
params.set("authRequest", authRequestId);
params.set("sessionId", sessionId);
}
return router.push(`/mfa/set?` + params);
}
const params = new URLSearchParams({}); const params = new URLSearchParams({});
if (organization) { if (organization) {

View File

@@ -0,0 +1,36 @@
"use client";
import { ReactNode, forwardRef } from "react";
import { IdpButtonClasses, SignInWithIdentityProviderProps } from "./classes";
export const SignInWithApple = forwardRef<
HTMLButtonElement,
SignInWithIdentityProviderProps
>(
({ children, className = "", name = "", ...props }, ref): ReactNode => (
<button
type="button"
ref={ref}
className={`${IdpButtonClasses} ${className}`}
{...props}
>
<div className="h-12 w-12 flex items-center justify-center">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlSpace="preserve"
width="814"
height="1000"
>
<path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z" />
</svg>
</div>
{children ? (
children
) : (
<span className="ml-4">{name ? name : "Sign in with Google"}</span>
)}
</button>
),
);
SignInWithApple.displayName = "SignInWithApple";

View File

@@ -0,0 +1,25 @@
"use client";
import { ReactNode, forwardRef } from "react";
import { IdpButtonClasses, SignInWithIdentityProviderProps } from "./classes";
export const SignInWithGeneric = forwardRef<
HTMLButtonElement,
SignInWithIdentityProviderProps
>(
(
{ children, className = "h-[50px] pl-20", name = "", ...props },
ref,
): ReactNode => (
<button
type="button"
ref={ref}
className={`${IdpButtonClasses} ${className}`}
{...props}
>
{children ? children : <span className="">{name}</span>}
</button>
),
);
SignInWithGeneric.displayName = "SignInWithGeneric";

9629
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff