org context, idp failure page, cleanup signup

This commit is contained in:
peintnermax
2024-04-01 13:57:39 +02:00
parent bf316fe5a3
commit 8530015244
17 changed files with 371 additions and 220 deletions

View File

@@ -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>
); );
} }

View 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>
);
}
}

View File

@@ -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 {

View File

@@ -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>
); );
} }

View File

@@ -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>

View File

@@ -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>
); );
} }

View File

@@ -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>
); );
} }

View File

@@ -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>
); );
} }

View File

@@ -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>
);
}

View File

@@ -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>
); );
} }

View File

@@ -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 });

View File

@@ -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);

View File

@@ -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

View File

@@ -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));
}); });
} }

View File

@@ -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) => {

View File

@@ -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`,
}), }),
}); });

View File

@@ -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,
}), }),
}); });