mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-12 14:54:33 +00:00
org context, idp failure page, cleanup signup
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
import { Session } from "@zitadel/server";
|
import { Session } from "@zitadel/server";
|
||||||
import { listSessions, server } from "#/lib/zitadel";
|
import { getBrandingSettings, listSessions, server } from "#/lib/zitadel";
|
||||||
import { getAllSessionCookieIds } from "#/utils/cookies";
|
import { getAllSessionCookieIds } from "#/utils/cookies";
|
||||||
import { UserPlusIcon } from "@heroicons/react/24/outline";
|
import { UserPlusIcon } from "@heroicons/react/24/outline";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import SessionsList from "#/ui/SessionsList";
|
import SessionsList from "#/ui/SessionsList";
|
||||||
|
import DynamicTheme from "#/ui/DynamicTheme";
|
||||||
|
|
||||||
async function loadSessions(): Promise<Session[]> {
|
async function loadSessions(): Promise<Session[]> {
|
||||||
const ids = await getAllSessionCookieIds();
|
const ids = await getAllSessionCookieIds();
|
||||||
@@ -26,34 +27,39 @@ export default async function Page({
|
|||||||
searchParams: Record<string | number | symbol, string | undefined>;
|
searchParams: Record<string | number | symbol, string | undefined>;
|
||||||
}) {
|
}) {
|
||||||
const authRequestId = searchParams?.authRequestId;
|
const authRequestId = searchParams?.authRequestId;
|
||||||
|
const organization = searchParams?.organization;
|
||||||
|
|
||||||
let sessions = await loadSessions();
|
let sessions = await loadSessions();
|
||||||
|
|
||||||
return (
|
const branding = await getBrandingSettings(server, organization);
|
||||||
<div className="flex flex-col items-center space-y-4">
|
|
||||||
<h1>Accounts</h1>
|
|
||||||
<p className="ztdl-p mb-6 block">Use your ZITADEL Account</p>
|
|
||||||
|
|
||||||
<div className="flex flex-col w-full space-y-2">
|
return (
|
||||||
<SessionsList sessions={sessions} authRequestId={authRequestId} />
|
<DynamicTheme branding={branding}>
|
||||||
<Link
|
<div className="flex flex-col items-center space-y-4">
|
||||||
href={
|
<h1>Accounts</h1>
|
||||||
authRequestId
|
<p className="ztdl-p mb-6 block">Use your ZITADEL Account</p>
|
||||||
? `/loginname?` +
|
|
||||||
new URLSearchParams({
|
<div className="flex flex-col w-full space-y-2">
|
||||||
authRequestId,
|
<SessionsList sessions={sessions} authRequestId={authRequestId} />
|
||||||
})
|
<Link
|
||||||
: "/loginname"
|
href={
|
||||||
}
|
authRequestId
|
||||||
>
|
? `/loginname?` +
|
||||||
<div className="flex flex-row items-center py-3 px-4 hover:bg-black/10 dark:hover:bg-white/10 rounded-md transition-all">
|
new URLSearchParams({
|
||||||
<div className="w-8 h-8 mr-4 flex flex-row justify-center items-center rounded-full bg-black/5 dark:bg-white/5">
|
authRequestId,
|
||||||
<UserPlusIcon className="h-5 w-5" />
|
})
|
||||||
|
: "/loginname"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center py-3 px-4 hover:bg-black/10 dark:hover:bg-white/10 rounded-md transition-all">
|
||||||
|
<div className="w-8 h-8 mr-4 flex flex-row justify-center items-center rounded-full bg-black/5 dark:bg-white/5">
|
||||||
|
<UserPlusIcon className="h-5 w-5" />
|
||||||
|
</div>
|
||||||
|
<span className="text-sm">Add another account</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm">Add another account</span>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</DynamicTheme>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
61
apps/login/app/(login)/idp/[provider]/failure/page.tsx
Normal file
61
apps/login/app/(login)/idp/[provider]/failure/page.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { ProviderSlug } from "#/lib/demos";
|
||||||
|
import { getBrandingSettings, server } from "#/lib/zitadel";
|
||||||
|
import Alert, { AlertType } from "#/ui/Alert";
|
||||||
|
import DynamicTheme from "#/ui/DynamicTheme";
|
||||||
|
import IdpSignin from "#/ui/IdpSignin";
|
||||||
|
import {
|
||||||
|
AddHumanUserRequest,
|
||||||
|
IDPInformation,
|
||||||
|
RetrieveIdentityProviderIntentResponse,
|
||||||
|
user,
|
||||||
|
IDPLink,
|
||||||
|
} from "@zitadel/server";
|
||||||
|
import { ClientError } from "nice-grpc";
|
||||||
|
|
||||||
|
const PROVIDER_NAME_MAPPING: {
|
||||||
|
[provider: string]: string;
|
||||||
|
} = {
|
||||||
|
[ProviderSlug.GOOGLE]: "Google",
|
||||||
|
[ProviderSlug.GITHUB]: "GitHub",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function Page({
|
||||||
|
searchParams,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
searchParams: Record<string | number | symbol, string | undefined>;
|
||||||
|
params: { provider: ProviderSlug };
|
||||||
|
}) {
|
||||||
|
const { id, token, authRequestId, organization } = searchParams;
|
||||||
|
const { provider } = params;
|
||||||
|
|
||||||
|
const branding = await getBrandingSettings(server, organization);
|
||||||
|
|
||||||
|
if (provider) {
|
||||||
|
return (
|
||||||
|
<DynamicTheme branding={branding}>
|
||||||
|
<div className="flex flex-col items-center space-y-4">
|
||||||
|
<h1>Login failure</h1>
|
||||||
|
<div>
|
||||||
|
An error signing in with{" "}
|
||||||
|
{PROVIDER_NAME_MAPPING[provider]
|
||||||
|
? PROVIDER_NAME_MAPPING[provider]
|
||||||
|
: provider}{" "}
|
||||||
|
happened!
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* <Alert type={AlertType.ALERT}>
|
||||||
|
{}
|
||||||
|
</Alert> */}
|
||||||
|
</div>
|
||||||
|
</DynamicTheme>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center space-y-4">
|
||||||
|
<h1>Register</h1>
|
||||||
|
<p className="ztdl-p">No id and token received!</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
import { ProviderSlug } from "#/lib/demos";
|
import { ProviderSlug } from "#/lib/demos";
|
||||||
import { server } from "#/lib/zitadel";
|
import { getBrandingSettings, server } from "#/lib/zitadel";
|
||||||
import Alert, { AlertType } from "#/ui/Alert";
|
import Alert, { AlertType } from "#/ui/Alert";
|
||||||
|
import DynamicTheme from "#/ui/DynamicTheme";
|
||||||
import IdpSignin from "#/ui/IdpSignin";
|
import IdpSignin from "#/ui/IdpSignin";
|
||||||
import { createSessionForIdpAndUpdateCookie } from "#/utils/session";
|
|
||||||
import {
|
import {
|
||||||
AddHumanUserRequest,
|
AddHumanUserRequest,
|
||||||
IDPInformation,
|
IDPInformation,
|
||||||
RetrieveIdentityProviderIntentResponse,
|
RetrieveIdentityProviderIntentResponse,
|
||||||
user,
|
user,
|
||||||
IDPLink,
|
IDPLink,
|
||||||
Session,
|
|
||||||
} from "@zitadel/server";
|
} from "@zitadel/server";
|
||||||
import { ClientError } from "nice-grpc";
|
import { ClientError } from "nice-grpc";
|
||||||
|
|
||||||
@@ -89,9 +88,11 @@ export default async function Page({
|
|||||||
searchParams: Record<string | number | symbol, string | undefined>;
|
searchParams: Record<string | number | symbol, string | undefined>;
|
||||||
params: { provider: ProviderSlug };
|
params: { provider: ProviderSlug };
|
||||||
}) {
|
}) {
|
||||||
const { id, token, authRequestId } = searchParams;
|
const { id, token, authRequestId, organization } = searchParams;
|
||||||
const { provider } = params;
|
const { provider } = params;
|
||||||
|
|
||||||
|
const branding = await getBrandingSettings(server, organization);
|
||||||
|
|
||||||
if (provider && id && token) {
|
if (provider && id && token) {
|
||||||
return retrieveIDPIntent(id, token)
|
return retrieveIDPIntent(id, token)
|
||||||
.then((resp) => {
|
.then((resp) => {
|
||||||
@@ -100,40 +101,46 @@ export default async function Page({
|
|||||||
// handle login
|
// handle login
|
||||||
if (userId) {
|
if (userId) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center space-y-4">
|
<DynamicTheme branding={branding}>
|
||||||
<h1>Login successful</h1>
|
<div className="flex flex-col items-center space-y-4">
|
||||||
<div>You have successfully been loggedIn!</div>
|
<h1>Login successful</h1>
|
||||||
|
<div>You have successfully been loggedIn!</div>
|
||||||
|
|
||||||
<IdpSignin
|
<IdpSignin
|
||||||
userId={userId}
|
userId={userId}
|
||||||
idpIntent={{ idpIntentId: id, idpIntentToken: token }}
|
idpIntent={{ idpIntentId: id, idpIntentToken: token }}
|
||||||
authRequestId={authRequestId}
|
authRequestId={authRequestId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</DynamicTheme>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// handle register
|
// handle register
|
||||||
return createUser(provider, idpInformation)
|
return createUser(provider, idpInformation)
|
||||||
.then((userId) => {
|
.then((userId) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center space-y-4">
|
<DynamicTheme branding={branding}>
|
||||||
<h1>Register successful</h1>
|
<div className="flex flex-col items-center space-y-4">
|
||||||
<div>You have successfully been registered!</div>
|
<h1>Register successful</h1>
|
||||||
</div>
|
<div>You have successfully been registered!</div>
|
||||||
|
</div>
|
||||||
|
</DynamicTheme>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch((error: ClientError) => {
|
.catch((error: ClientError) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center space-y-4">
|
<DynamicTheme branding={branding}>
|
||||||
<h1>Register failed</h1>
|
<div className="flex flex-col items-center space-y-4">
|
||||||
<div className="w-full">
|
<h1>Register failed</h1>
|
||||||
{
|
<div className="w-full">
|
||||||
<Alert type={AlertType.ALERT}>
|
{
|
||||||
{JSON.stringify(error.message)}
|
<Alert type={AlertType.ALERT}>
|
||||||
</Alert>
|
{JSON.stringify(error.message)}
|
||||||
}
|
</Alert>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</DynamicTheme>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -143,16 +150,18 @@ export default async function Page({
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center space-y-4">
|
<DynamicTheme branding={branding}>
|
||||||
<h1>An error occurred</h1>
|
<div className="flex flex-col items-center space-y-4">
|
||||||
<div className="w-full">
|
<h1>An error occurred</h1>
|
||||||
{
|
<div className="w-full">
|
||||||
<Alert type={AlertType.ALERT}>
|
{
|
||||||
{JSON.stringify(error.message)}
|
<Alert type={AlertType.ALERT}>
|
||||||
</Alert>
|
{JSON.stringify(error.message)}
|
||||||
}
|
</Alert>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</DynamicTheme>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { getLegalAndSupportSettings, server } from "#/lib/zitadel";
|
import {
|
||||||
|
getBrandingSettings,
|
||||||
|
getLegalAndSupportSettings,
|
||||||
|
server,
|
||||||
|
} from "#/lib/zitadel";
|
||||||
|
import DynamicTheme from "#/ui/DynamicTheme";
|
||||||
import { SignInWithIDP } from "#/ui/SignInWithIDP";
|
import { SignInWithIDP } from "#/ui/SignInWithIDP";
|
||||||
import {
|
import {
|
||||||
GetActiveIdentityProvidersResponse,
|
GetActiveIdentityProvidersResponse,
|
||||||
@@ -28,30 +33,35 @@ export default async function Page({
|
|||||||
searchParams: Record<string | number | symbol, string | undefined>;
|
searchParams: Record<string | number | symbol, string | undefined>;
|
||||||
}) {
|
}) {
|
||||||
const authRequestId = searchParams?.authRequestId;
|
const authRequestId = searchParams?.authRequestId;
|
||||||
|
const organization = searchParams?.organization;
|
||||||
|
|
||||||
const legal = await getLegalAndSupportSettings(server);
|
const legal = await getLegalAndSupportSettings(server, organization);
|
||||||
|
|
||||||
// TODO if org idps should be shown replace emptystring with the orgId.
|
const identityProviders = await getIdentityProviders(server, organization);
|
||||||
const identityProviders = await getIdentityProviders(server, "");
|
|
||||||
|
|
||||||
const host = process.env.VERCEL_URL
|
const host = process.env.VERCEL_URL
|
||||||
? `https://${process.env.VERCEL_URL}`
|
? `https://${process.env.VERCEL_URL}`
|
||||||
: "http://localhost:3000";
|
: "http://localhost:3000";
|
||||||
|
|
||||||
return (
|
const branding = await getBrandingSettings(server, organization);
|
||||||
<div className="flex flex-col items-center space-y-4">
|
|
||||||
<h1>Register</h1>
|
|
||||||
<p className="ztdl-p">
|
|
||||||
Select one of the following providers to register
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{legal && identityProviders && process.env.ZITADEL_API_URL && (
|
return (
|
||||||
<SignInWithIDP
|
<DynamicTheme branding={branding}>
|
||||||
host={host}
|
<div className="flex flex-col items-center space-y-4">
|
||||||
identityProviders={identityProviders}
|
<h1>Register</h1>
|
||||||
authRequestId={authRequestId}
|
<p className="ztdl-p">
|
||||||
></SignInWithIDP>
|
Select one of the following providers to register
|
||||||
)}
|
</p>
|
||||||
</div>
|
|
||||||
|
{legal && identityProviders && process.env.ZITADEL_API_URL && (
|
||||||
|
<SignInWithIDP
|
||||||
|
host={host}
|
||||||
|
identityProviders={identityProviders}
|
||||||
|
authRequestId={authRequestId}
|
||||||
|
organization={organization}
|
||||||
|
></SignInWithIDP>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DynamicTheme>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ import {
|
|||||||
} from "#/lib/zitadel";
|
} from "#/lib/zitadel";
|
||||||
import DynamicTheme from "#/ui/DynamicTheme";
|
import DynamicTheme from "#/ui/DynamicTheme";
|
||||||
import { SignInWithIDP } from "#/ui/SignInWithIDP";
|
import { SignInWithIDP } from "#/ui/SignInWithIDP";
|
||||||
import ThemeWrapper from "#/ui/ThemeWrapper";
|
|
||||||
import UsernameForm from "#/ui/UsernameForm";
|
import UsernameForm from "#/ui/UsernameForm";
|
||||||
import { BrandingSettings } from "@zitadel/server";
|
|
||||||
import {
|
import {
|
||||||
GetActiveIdentityProvidersResponse,
|
GetActiveIdentityProvidersResponse,
|
||||||
IdentityProvider,
|
IdentityProvider,
|
||||||
@@ -71,6 +69,7 @@ export default async function Page({
|
|||||||
host={host}
|
host={host}
|
||||||
identityProviders={identityProviders}
|
identityProviders={identityProviders}
|
||||||
authRequestId={authRequestId}
|
authRequestId={authRequestId}
|
||||||
|
organization={organization}
|
||||||
></SignInWithIDP>
|
></SignInWithIDP>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { getSession, server } from "#/lib/zitadel";
|
import { getBrandingSettings, getSession, server } from "#/lib/zitadel";
|
||||||
import Alert, { AlertType } from "#/ui/Alert";
|
import Alert, { AlertType } from "#/ui/Alert";
|
||||||
|
import DynamicTheme from "#/ui/DynamicTheme";
|
||||||
import RegisterPasskey from "#/ui/RegisterPasskey";
|
import RegisterPasskey from "#/ui/RegisterPasskey";
|
||||||
import UserAvatar from "#/ui/UserAvatar";
|
import UserAvatar from "#/ui/UserAvatar";
|
||||||
import { getMostRecentCookieWithLoginname } from "#/utils/cookies";
|
import { getMostRecentCookieWithLoginname } from "#/utils/cookies";
|
||||||
@@ -31,48 +32,52 @@ export default async function Page({
|
|||||||
? "When set up, you will be able to authenticate without a password."
|
? "When set up, you will be able to authenticate without a password."
|
||||||
: "Your device will ask for your fingerprint, face, or screen lock";
|
: "Your device will ask for your fingerprint, face, or screen lock";
|
||||||
|
|
||||||
|
const branding = await getBrandingSettings(server, organization);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center space-y-4">
|
<DynamicTheme branding={branding}>
|
||||||
<h1>{title}</h1>
|
<div className="flex flex-col items-center space-y-4">
|
||||||
|
<h1>{title}</h1>
|
||||||
|
|
||||||
{sessionFactors && (
|
{sessionFactors && (
|
||||||
<UserAvatar
|
<UserAvatar
|
||||||
loginName={loginName ?? sessionFactors.factors?.user?.loginName}
|
loginName={loginName ?? sessionFactors.factors?.user?.loginName}
|
||||||
displayName={sessionFactors.factors?.user?.displayName}
|
displayName={sessionFactors.factors?.user?.displayName}
|
||||||
showDropdown
|
showDropdown
|
||||||
></UserAvatar>
|
></UserAvatar>
|
||||||
)}
|
)}
|
||||||
<p className="ztdl-p mb-6 block">{description}</p>
|
<p className="ztdl-p mb-6 block">{description}</p>
|
||||||
|
|
||||||
<Alert type={AlertType.INFO}>
|
<Alert type={AlertType.INFO}>
|
||||||
<span>
|
<span>
|
||||||
A passkey is an authentication method on a device like your
|
A passkey is an authentication method on a device like your
|
||||||
fingerprint, Apple FaceID or similar.
|
fingerprint, Apple FaceID or similar.
|
||||||
<a
|
<a
|
||||||
className="text-primary-light-500 dark:text-primary-dark-500 hover:text-primary-light-300 hover:dark:text-primary-dark-300"
|
className="text-primary-light-500 dark:text-primary-dark-500 hover:text-primary-light-300 hover:dark:text-primary-dark-300"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://zitadel.com/docs/guides/manage/user/reg-create-user#with-passwordless"
|
href="https://zitadel.com/docs/guides/manage/user/reg-create-user#with-passwordless"
|
||||||
>
|
>
|
||||||
Passwordless Authentication
|
Passwordless Authentication
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
{!sessionFactors && (
|
{!sessionFactors && (
|
||||||
<div className="py-4">
|
<div className="py-4">
|
||||||
<Alert>
|
<Alert>
|
||||||
Could not get the context of the user. Make sure to enter the
|
Could not get the context of the user. Make sure to enter the
|
||||||
username first or provide a loginName as searchParam.
|
username first or provide a loginName as searchParam.
|
||||||
</Alert>
|
</Alert>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{sessionFactors?.id && (
|
{sessionFactors?.id && (
|
||||||
<RegisterPasskey
|
<RegisterPasskey
|
||||||
sessionId={sessionFactors.id}
|
sessionId={sessionFactors.id}
|
||||||
isPrompt={!!promptPasswordless}
|
isPrompt={!!promptPasswordless}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</DynamicTheme>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
|
getBrandingSettings,
|
||||||
getLegalAndSupportSettings,
|
getLegalAndSupportSettings,
|
||||||
getPasswordComplexitySettings,
|
getPasswordComplexitySettings,
|
||||||
server,
|
server,
|
||||||
} from "#/lib/zitadel";
|
} from "#/lib/zitadel";
|
||||||
|
import DynamicTheme from "#/ui/DynamicTheme";
|
||||||
import RegisterFormWithoutPassword from "#/ui/RegisterFormWithoutPassword";
|
import RegisterFormWithoutPassword from "#/ui/RegisterFormWithoutPassword";
|
||||||
import SetPasswordForm from "#/ui/SetPasswordForm";
|
import SetPasswordForm from "#/ui/SetPasswordForm";
|
||||||
|
|
||||||
@@ -16,40 +18,46 @@ export default async function Page({
|
|||||||
|
|
||||||
const setPassword = !!(firstname && lastname && email);
|
const setPassword = !!(firstname && lastname && email);
|
||||||
|
|
||||||
const legal = await getLegalAndSupportSettings(server);
|
const legal = await getLegalAndSupportSettings(server, organization);
|
||||||
const passwordComplexitySettings = await getPasswordComplexitySettings(
|
const passwordComplexitySettings = await getPasswordComplexitySettings(
|
||||||
server,
|
server,
|
||||||
organization
|
organization
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const branding = await getBrandingSettings(server, organization);
|
||||||
|
|
||||||
return setPassword ? (
|
return setPassword ? (
|
||||||
<div className="flex flex-col items-center space-y-4">
|
<DynamicTheme branding={branding}>
|
||||||
<h1>Set Password</h1>
|
<div className="flex flex-col items-center space-y-4">
|
||||||
<p className="ztdl-p">Set the password for your account</p>
|
<h1>Set Password</h1>
|
||||||
|
<p className="ztdl-p">Set the password for your account</p>
|
||||||
|
|
||||||
{legal && passwordComplexitySettings && (
|
{legal && passwordComplexitySettings && (
|
||||||
<SetPasswordForm
|
<SetPasswordForm
|
||||||
passwordComplexitySettings={passwordComplexitySettings}
|
passwordComplexitySettings={passwordComplexitySettings}
|
||||||
email={email}
|
email={email}
|
||||||
firstname={firstname}
|
firstname={firstname}
|
||||||
lastname={lastname}
|
lastname={lastname}
|
||||||
organization={organization}
|
organization={organization}
|
||||||
authRequestId={authRequestId}
|
authRequestId={authRequestId}
|
||||||
></SetPasswordForm>
|
></SetPasswordForm>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</DynamicTheme>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col items-center space-y-4">
|
<DynamicTheme branding={branding}>
|
||||||
<h1>Register</h1>
|
<div className="flex flex-col items-center space-y-4">
|
||||||
<p className="ztdl-p">Create your ZITADEL account.</p>
|
<h1>Register</h1>
|
||||||
|
<p className="ztdl-p">Create your ZITADEL account.</p>
|
||||||
|
|
||||||
{legal && passwordComplexitySettings && (
|
{legal && passwordComplexitySettings && (
|
||||||
<RegisterFormWithoutPassword
|
<RegisterFormWithoutPassword
|
||||||
legal={legal}
|
legal={legal}
|
||||||
organization={organization}
|
organization={organization}
|
||||||
authRequestId={authRequestId}
|
authRequestId={authRequestId}
|
||||||
></RegisterFormWithoutPassword>
|
></RegisterFormWithoutPassword>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</DynamicTheme>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import { createCallback, getSession, server } from "#/lib/zitadel";
|
import {
|
||||||
|
createCallback,
|
||||||
|
getBrandingSettings,
|
||||||
|
getSession,
|
||||||
|
server,
|
||||||
|
} from "#/lib/zitadel";
|
||||||
|
import DynamicTheme from "#/ui/DynamicTheme";
|
||||||
import UserAvatar from "#/ui/UserAvatar";
|
import UserAvatar from "#/ui/UserAvatar";
|
||||||
import { getMostRecentCookieWithLoginname } from "#/utils/cookies";
|
import { getMostRecentCookieWithLoginname } from "#/utils/cookies";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
@@ -22,19 +28,23 @@ async function loadSession(loginName: string, authRequestId?: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page({ searchParams }: { searchParams: any }) {
|
export default async function Page({ searchParams }: { searchParams: any }) {
|
||||||
const { loginName, authRequestId } = searchParams;
|
const { loginName, authRequestId, organization } = searchParams;
|
||||||
const sessionFactors = await loadSession(loginName, authRequestId);
|
const sessionFactors = await loadSession(loginName, authRequestId);
|
||||||
|
|
||||||
return (
|
const branding = await getBrandingSettings(server, organization);
|
||||||
<div className="flex flex-col items-center space-y-4">
|
|
||||||
<h1>{`Welcome ${sessionFactors?.factors?.user?.displayName}`}</h1>
|
|
||||||
<p className="ztdl-p mb-6 block">You are signed in.</p>
|
|
||||||
|
|
||||||
<UserAvatar
|
return (
|
||||||
loginName={loginName ?? sessionFactors?.factors?.user?.loginName}
|
<DynamicTheme branding={branding}>
|
||||||
displayName={sessionFactors?.factors?.user?.displayName}
|
<div className="flex flex-col items-center space-y-4">
|
||||||
showDropdown
|
<h1>{`Welcome ${sessionFactors?.factors?.user?.displayName}`}</h1>
|
||||||
></UserAvatar>
|
<p className="ztdl-p mb-6 block">You are signed in.</p>
|
||||||
</div>
|
|
||||||
|
<UserAvatar
|
||||||
|
loginName={loginName ?? sessionFactors?.factors?.user?.loginName}
|
||||||
|
displayName={sessionFactors?.factors?.user?.displayName}
|
||||||
|
showDropdown
|
||||||
|
></UserAvatar>
|
||||||
|
</div>
|
||||||
|
</DynamicTheme>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import {
|
|
||||||
getLegalAndSupportSettings,
|
|
||||||
getPasswordComplexitySettings,
|
|
||||||
server,
|
|
||||||
} from "#/lib/zitadel";
|
|
||||||
import RegisterForm from "#/ui/RegisterForm";
|
|
||||||
|
|
||||||
export default async function Page() {
|
|
||||||
const legal = await getLegalAndSupportSettings(server);
|
|
||||||
const passwordComplexitySettings = await getPasswordComplexitySettings(
|
|
||||||
server
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col items-center space-y-4">
|
|
||||||
<h1>Register</h1>
|
|
||||||
<p className="ztdl-p">Create your ZITADEL account.</p>
|
|
||||||
|
|
||||||
{legal && passwordComplexitySettings && (
|
|
||||||
<RegisterForm
|
|
||||||
legal={legal}
|
|
||||||
passwordComplexitySettings={passwordComplexitySettings}
|
|
||||||
></RegisterForm>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,28 +1,36 @@
|
|||||||
|
import { getBrandingSettings, server } from "#/lib/zitadel";
|
||||||
|
import DynamicTheme from "#/ui/DynamicTheme";
|
||||||
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, code, submit, orgID, loginname, passwordset } = searchParams;
|
const { userID, code, submit, organization, loginname, passwordset } =
|
||||||
|
searchParams;
|
||||||
|
|
||||||
|
const branding = await getBrandingSettings(server, organization);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center space-y-4">
|
<DynamicTheme branding={branding}>
|
||||||
<h1>Verify user</h1>
|
<div className="flex flex-col items-center space-y-4">
|
||||||
<p className="ztdl-p mb-6 block">
|
<h1>Verify user</h1>
|
||||||
Enter the Code provided in the verification email.
|
<p className="ztdl-p mb-6 block">
|
||||||
</p>
|
Enter the Code provided in the verification email.
|
||||||
|
</p>
|
||||||
|
|
||||||
{userID ? (
|
{userID ? (
|
||||||
<VerifyEmailForm
|
<VerifyEmailForm
|
||||||
userId={userID}
|
userId={userID}
|
||||||
code={code}
|
code={code}
|
||||||
submit={submit === "true"}
|
submit={submit === "true"}
|
||||||
/>
|
organization={organization}
|
||||||
) : (
|
/>
|
||||||
<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">
|
) : (
|
||||||
<ExclamationTriangleIcon className="h-5 w-5 mr-2" />
|
<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">
|
||||||
<span className="text-center text-sm">No userId provided!</span>
|
<ExclamationTriangleIcon className="h-5 w-5 mr-2" />
|
||||||
</div>
|
<span className="text-center text-sm">No userId provided!</span>
|
||||||
)}
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
|
</DynamicTheme>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,21 @@ import { NextRequest, NextResponse } from "next/server";
|
|||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
if (body) {
|
if (body) {
|
||||||
const { email, password, firstName, lastName } = body;
|
const {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
organization,
|
||||||
|
authRequestId,
|
||||||
|
} = body;
|
||||||
|
|
||||||
return addHumanUser(server, {
|
return addHumanUser(server, {
|
||||||
email: email,
|
email: email,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
password: password ? password : undefined,
|
password: password ? password : undefined,
|
||||||
|
organization,
|
||||||
})
|
})
|
||||||
.then((userId) => {
|
.then((userId) => {
|
||||||
return NextResponse.json({ userId });
|
return NextResponse.json({ userId });
|
||||||
|
|||||||
@@ -89,9 +89,11 @@ export async function GET(request: NextRequest) {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
authRequest?.scope &&
|
authRequest?.scope &&
|
||||||
authRequest.scope.find((s) => ORG_SCOPE_REGEX.test(s))
|
authRequest.scope.find((s: string) => ORG_SCOPE_REGEX.test(s))
|
||||||
) {
|
) {
|
||||||
const orgId = authRequest.scope.find((s) => ORG_SCOPE_REGEX.test(s));
|
const orgId = authRequest.scope.find((s: string) =>
|
||||||
|
ORG_SCOPE_REGEX.test(s)
|
||||||
|
);
|
||||||
|
|
||||||
if (orgId) {
|
if (orgId) {
|
||||||
const matched = ORG_SCOPE_REGEX.exec(orgId);
|
const matched = ORG_SCOPE_REGEX.exec(orgId);
|
||||||
|
|||||||
@@ -87,11 +87,15 @@ export async function getGeneralSettings(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getLegalAndSupportSettings(
|
export async function getLegalAndSupportSettings(
|
||||||
server: ZitadelServer
|
server: ZitadelServer,
|
||||||
|
organization?: string
|
||||||
): Promise<LegalAndSupportSettings | undefined> {
|
): Promise<LegalAndSupportSettings | undefined> {
|
||||||
const settingsService = settings.getSettings(server);
|
const settingsService = settings.getSettings(server);
|
||||||
return settingsService
|
return settingsService
|
||||||
.getLegalAndSupportSettings({}, {})
|
.getLegalAndSupportSettings(
|
||||||
|
{ ctx: organization ? { orgId: organization } : { instance: true } },
|
||||||
|
{}
|
||||||
|
)
|
||||||
.then((resp: GetLegalAndSupportSettingsResponse) => {
|
.then((resp: GetLegalAndSupportSettingsResponse) => {
|
||||||
return resp.settings;
|
return resp.settings;
|
||||||
});
|
});
|
||||||
@@ -261,11 +265,12 @@ export type AddHumanUserData = {
|
|||||||
lastName: string;
|
lastName: string;
|
||||||
email: string;
|
email: string;
|
||||||
password: string | undefined;
|
password: string | undefined;
|
||||||
|
organization: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function addHumanUser(
|
export async function addHumanUser(
|
||||||
server: ZitadelServer,
|
server: ZitadelServer,
|
||||||
{ email, firstName, lastName, password }: AddHumanUserData
|
{ email, firstName, lastName, password, organization }: AddHumanUserData
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const userService = user.getUser(server);
|
const userService = user.getUser(server);
|
||||||
|
|
||||||
@@ -274,6 +279,11 @@ export async function addHumanUser(
|
|||||||
username: email,
|
username: email,
|
||||||
profile: { givenName: firstName, familyName: lastName },
|
profile: { givenName: firstName, familyName: lastName },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (organization) {
|
||||||
|
payload.organization = { orgId: organization };
|
||||||
|
}
|
||||||
|
|
||||||
return userService
|
return userService
|
||||||
.addHumanUser(
|
.addHumanUser(
|
||||||
password
|
password
|
||||||
|
|||||||
@@ -32,11 +32,13 @@ type Inputs =
|
|||||||
type Props = {
|
type Props = {
|
||||||
legal: LegalAndSupportSettings;
|
legal: LegalAndSupportSettings;
|
||||||
passwordComplexitySettings: PasswordComplexitySettings;
|
passwordComplexitySettings: PasswordComplexitySettings;
|
||||||
|
organization?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RegisterForm({
|
export default function RegisterForm({
|
||||||
legal,
|
legal,
|
||||||
passwordComplexitySettings,
|
passwordComplexitySettings,
|
||||||
|
organization,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { register, handleSubmit, watch, formState } = useForm<Inputs>({
|
const { register, handleSubmit, watch, formState } = useForm<Inputs>({
|
||||||
mode: "onBlur",
|
mode: "onBlur",
|
||||||
@@ -58,6 +60,7 @@ export default function RegisterForm({
|
|||||||
password: values.password,
|
password: values.password,
|
||||||
firstName: values.firstname,
|
firstName: values.firstname,
|
||||||
lastName: values.lastname,
|
lastName: values.lastname,
|
||||||
|
organization: organization,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -72,7 +75,13 @@ export default function RegisterForm({
|
|||||||
|
|
||||||
function submitAndLink(value: Inputs): Promise<boolean | void> {
|
function submitAndLink(value: Inputs): Promise<boolean | void> {
|
||||||
return submitRegister(value).then((resp: any) => {
|
return submitRegister(value).then((resp: any) => {
|
||||||
return router.push(`/verify?userID=${resp.userId}`);
|
const params: any = { userID: resp.userId };
|
||||||
|
|
||||||
|
if (organization) {
|
||||||
|
params.organization = organization;
|
||||||
|
}
|
||||||
|
|
||||||
|
return router.push(`/verify?` + new URLSearchParams(params));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export default function RegisterFormWithoutPassword({
|
|||||||
email: values.email,
|
email: values.email,
|
||||||
firstName: values.firstname,
|
firstName: values.firstname,
|
||||||
lastName: values.lastname,
|
lastName: values.lastname,
|
||||||
|
organization: organization,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -88,16 +89,30 @@ export default function RegisterFormWithoutPassword({
|
|||||||
value: Inputs,
|
value: Inputs,
|
||||||
withPassword: boolean = false
|
withPassword: boolean = false
|
||||||
) {
|
) {
|
||||||
|
const registerParams: any = value;
|
||||||
|
|
||||||
|
if (organization) {
|
||||||
|
registerParams.organization = organization;
|
||||||
|
}
|
||||||
|
|
||||||
return withPassword
|
return withPassword
|
||||||
? router.push(`/register?` + new URLSearchParams(value))
|
? router.push(`/register?` + new URLSearchParams(registerParams))
|
||||||
: submitAndRegister(value)
|
: submitAndRegister(value)
|
||||||
.then((resp: any) => {
|
.then((resp: any) => {
|
||||||
createSessionWithLoginName(value.email).then(({ factors }) => {
|
createSessionWithLoginName(value.email).then(({ factors }) => {
|
||||||
setError("");
|
setError("");
|
||||||
return router.push(
|
|
||||||
`/passkey/add?` +
|
const params: any = { loginName: factors.user.loginName };
|
||||||
new URLSearchParams({ loginName: factors.user.loginName })
|
|
||||||
);
|
if (organization) {
|
||||||
|
params.organization = organization;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authRequestId) {
|
||||||
|
params.authRequestId = authRequestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return router.push(`/passkey/add?` + new URLSearchParams(params));
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((errorDetails: Error) => {
|
.catch((errorDetails: Error) => {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export interface SignInWithIDPProps {
|
|||||||
host: string;
|
host: string;
|
||||||
identityProviders: any[];
|
identityProviders: any[];
|
||||||
authRequestId?: string;
|
authRequestId?: string;
|
||||||
|
organization?: string;
|
||||||
startIDPFlowPath?: (idpId: string) => string;
|
startIDPFlowPath?: (idpId: string) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ export function SignInWithIDP({
|
|||||||
host,
|
host,
|
||||||
identityProviders,
|
identityProviders,
|
||||||
authRequestId,
|
authRequestId,
|
||||||
|
organization,
|
||||||
startIDPFlowPath = START_IDP_FLOW_PATH,
|
startIDPFlowPath = START_IDP_FLOW_PATH,
|
||||||
}: SignInWithIDPProps) {
|
}: SignInWithIDPProps) {
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
@@ -35,6 +37,16 @@ export function SignInWithIDP({
|
|||||||
async function startFlow(idpId: string, provider: ProviderSlug) {
|
async function startFlow(idpId: string, provider: ProviderSlug) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
if (authRequestId) {
|
||||||
|
params.set("authRequestId", authRequestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (organization) {
|
||||||
|
params.set("organization", organization);
|
||||||
|
}
|
||||||
|
|
||||||
const res = await fetch("/api/idp/start", {
|
const res = await fetch("/api/idp/start", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -42,11 +54,10 @@ export function SignInWithIDP({
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
idpId,
|
idpId,
|
||||||
successUrl: authRequestId
|
successUrl:
|
||||||
? `${host}/idp/${provider}/success?` +
|
`${host}/idp/${provider}/success?` + new URLSearchParams(params),
|
||||||
new URLSearchParams({ authRequestId })
|
failureUrl:
|
||||||
: `${host}/idp/${provider}/success`,
|
`${host}/idp/${provider}/failure?` + new URLSearchParams(params),
|
||||||
failureUrl: `${host}/idp/${provider}/failure`,
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -16,9 +16,15 @@ type Props = {
|
|||||||
userId: string;
|
userId: string;
|
||||||
code: string;
|
code: string;
|
||||||
submit: boolean;
|
submit: boolean;
|
||||||
|
organization?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function VerifyEmailForm({ userId, code, submit }: Props) {
|
export default function VerifyEmailForm({
|
||||||
|
userId,
|
||||||
|
code,
|
||||||
|
submit,
|
||||||
|
organization,
|
||||||
|
}: Props) {
|
||||||
const { register, handleSubmit, formState } = useForm<Inputs>({
|
const { register, handleSubmit, formState } = useForm<Inputs>({
|
||||||
mode: "onBlur",
|
mode: "onBlur",
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -50,6 +56,7 @@ export default function VerifyEmailForm({ userId, code, submit }: Props) {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
code: values.code,
|
code: values.code,
|
||||||
userId,
|
userId,
|
||||||
|
organization,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user