mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-11 22:12:22 +00:00
provider mappings
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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!,
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
36
apps/login/src/ui/idps/SignInWithApple.tsx
Normal file
36
apps/login/src/ui/idps/SignInWithApple.tsx
Normal 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";
|
||||
25
apps/login/src/ui/idps/SignInWithGeneric.tsx
Normal file
25
apps/login/src/ui/idps/SignInWithGeneric.tsx
Normal 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
9629
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user