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 UserAvatar from "@/ui/UserAvatar";
import VerifyEmailForm from "@/ui/VerifyEmailForm";
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
export default async function Page({ searchParams }: { searchParams: any }) {
const { userId, sessionId, code, submit, organization, authRequestId } =
searchParams;
const {
userId,
loginName,
sessionId,
code,
submit,
organization,
authRequestId,
} = searchParams;
const sessionFactors = await loadMostRecentSession({
loginName,
organization,
});
const branding = await getBrandingSettings(organization);
const loginSettings = await getLoginSettings(organization);
return (
<DynamicTheme branding={branding}>
<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.
</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 ? (
<VerifyEmailForm
userId={userId}
loginName={loginName}
code={code}
submit={submit === "true"}
organization={organization}
authRequestId={authRequestId}
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">

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: {
[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) => {
const rawInfo = idp.rawInformation as {
jobTitle: string;
@@ -76,8 +132,6 @@ export const PROVIDER_MAPPING: {
userPrincipalName: string;
};
console.log(rawInfo, rawInfo.userPrincipalName);
return create(AddHumanUserRequestSchema, {
username: idp.userName,
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 {
email: string;
name: string;
name?: string;
firstName?: string;
lastName?: string;
email?: string;
};
return create(AddHumanUserRequestSchema, {
username: idp.userName,
email: {
email: rawInfo.email,
email: rawInfo.email ?? "",
verification: { case: "isVerified", value: true },
},
profile: {
displayName: rawInfo.name ?? "",
givenName: rawInfo.name ?? "",
familyName: rawInfo.name ?? "",
givenName: rawInfo.firstName ?? "",
familyName: rawInfo.lastName ?? "",
},
idpLinks: [
{

View File

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

View File

@@ -75,19 +75,23 @@ export default function SetPasswordForm({
setError("Could not register user");
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) {
params.authRequestId = authRequestId;
params.append("authRequestId", authRequestId);
}
if (organization) {
params.organization = organization;
params.append("organization", organization);
}
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;

View File

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

View File

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