mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-12 08:23:16 +00:00
idp for invites
This commit is contained in:
@@ -2,10 +2,12 @@ import { Alert } from "@/components/alert";
|
|||||||
import { BackButton } from "@/components/back-button";
|
import { BackButton } from "@/components/back-button";
|
||||||
import { ChooseAuthenticatorToSetup } from "@/components/choose-authenticator-to-setup";
|
import { ChooseAuthenticatorToSetup } from "@/components/choose-authenticator-to-setup";
|
||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
|
import { SignInWithIdp } from "@/components/sign-in-with-idp";
|
||||||
import { UserAvatar } from "@/components/user-avatar";
|
import { UserAvatar } from "@/components/user-avatar";
|
||||||
import { getSessionCookieById } from "@/lib/cookies";
|
import { getSessionCookieById } from "@/lib/cookies";
|
||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import {
|
import {
|
||||||
|
getActiveIdentityProviders,
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
getLoginSettings,
|
getLoginSettings,
|
||||||
getSession,
|
getSession,
|
||||||
@@ -74,6 +76,10 @@ export default async function Page(props: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!sessionWithData) {
|
||||||
|
return <Alert>{tError("unknownContext")}</Alert>;
|
||||||
|
}
|
||||||
|
|
||||||
const branding = await getBrandingSettings(
|
const branding = await getBrandingSettings(
|
||||||
sessionWithData.factors?.user?.organizationId,
|
sessionWithData.factors?.user?.organizationId,
|
||||||
);
|
);
|
||||||
@@ -82,22 +88,32 @@ export default async function Page(props: {
|
|||||||
sessionWithData.factors?.user?.organizationId,
|
sessionWithData.factors?.user?.organizationId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const identityProviders = await getActiveIdentityProviders(
|
||||||
|
sessionWithData.factors?.user?.organizationId,
|
||||||
|
).then((resp) => {
|
||||||
|
return resp.identityProviders;
|
||||||
|
});
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
initial: "true", // defines that a code is not required and is therefore not shown in the UI
|
initial: "true", // defines that a code is not required and is therefore not shown in the UI
|
||||||
});
|
});
|
||||||
|
|
||||||
if (loginName) {
|
if (sessionWithData.factors?.user?.loginName) {
|
||||||
params.set("loginName", loginName);
|
params.set("loginName", sessionWithData.factors?.user?.loginName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (organization) {
|
if (sessionWithData.factors?.user?.organizationId) {
|
||||||
params.set("organization", organization);
|
params.set("organization", sessionWithData.factors?.user?.organizationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authRequestId) {
|
if (authRequestId) {
|
||||||
params.set("authRequestId", authRequestId);
|
params.set("authRequestId", authRequestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const host = process.env.VERCEL_URL
|
||||||
|
? `https://${process.env.VERCEL_URL}`
|
||||||
|
: "http://localhost:3000";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DynamicTheme branding={branding}>
|
<DynamicTheme branding={branding}>
|
||||||
<div className="flex flex-col items-center space-y-4">
|
<div className="flex flex-col items-center space-y-4">
|
||||||
@@ -105,18 +121,14 @@ export default async function Page(props: {
|
|||||||
|
|
||||||
<p className="ztdl-p">{t("description")}</p>
|
<p className="ztdl-p">{t("description")}</p>
|
||||||
|
|
||||||
{sessionWithData && (
|
<UserAvatar
|
||||||
<UserAvatar
|
loginName={sessionWithData.factors?.user?.loginName}
|
||||||
loginName={loginName ?? sessionWithData.factors?.user?.loginName}
|
displayName={sessionWithData.factors?.user?.displayName}
|
||||||
displayName={sessionWithData.factors?.user?.displayName}
|
showDropdown
|
||||||
showDropdown
|
searchParams={searchParams}
|
||||||
searchParams={searchParams}
|
></UserAvatar>
|
||||||
></UserAvatar>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!(loginName || sessionId) && <Alert>{tError("unknownContext")}</Alert>}
|
{loginSettings && (
|
||||||
|
|
||||||
{loginSettings && sessionWithData && (
|
|
||||||
<ChooseAuthenticatorToSetup
|
<ChooseAuthenticatorToSetup
|
||||||
authMethods={sessionWithData.authMethods}
|
authMethods={sessionWithData.authMethods}
|
||||||
loginSettings={loginSettings}
|
loginSettings={loginSettings}
|
||||||
@@ -124,6 +136,20 @@ export default async function Page(props: {
|
|||||||
></ChooseAuthenticatorToSetup>
|
></ChooseAuthenticatorToSetup>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<p className="ztdl-p text-center">
|
||||||
|
or sign in with an Identity Provider
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{loginSettings?.allowExternalIdp && identityProviders && (
|
||||||
|
<SignInWithIdp
|
||||||
|
host={host}
|
||||||
|
identityProviders={identityProviders}
|
||||||
|
authRequestId={authRequestId}
|
||||||
|
organization={sessionWithData.factors?.user?.organizationId}
|
||||||
|
linkOnly={true} // tell the callback function to just link the IDP and not login, to get an error when user is already available
|
||||||
|
></SignInWithIdp>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="mt-8 flex w-full flex-row items-center">
|
<div className="mt-8 flex w-full flex-row items-center">
|
||||||
<BackButton />
|
<BackButton />
|
||||||
<span className="flex-grow"></span>
|
<span className="flex-grow"></span>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default async function Page(props: {
|
|||||||
const searchParams = await props.searchParams;
|
const searchParams = await props.searchParams;
|
||||||
const locale = getLocale();
|
const locale = getLocale();
|
||||||
const t = await getTranslations({ locale, namespace: "idp" });
|
const t = await getTranslations({ locale, namespace: "idp" });
|
||||||
const { id, token, authRequestId, organization } = searchParams;
|
const { id, token, authRequestId, organization, link } = searchParams;
|
||||||
const { provider } = params;
|
const { provider } = params;
|
||||||
|
|
||||||
const branding = await getBrandingSettings(organization);
|
const branding = await getBrandingSettings(organization);
|
||||||
@@ -50,7 +50,8 @@ export default async function Page(props: {
|
|||||||
|
|
||||||
const { idpInformation, userId } = intent;
|
const { idpInformation, userId } = intent;
|
||||||
|
|
||||||
if (userId) {
|
// sign in user. If user should be linked continue
|
||||||
|
if (userId && !link) {
|
||||||
// TODO: update user if idp.options.isAutoUpdate is true
|
// TODO: update user if idp.options.isAutoUpdate is true
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,23 +2,14 @@ import { DynamicTheme } from "@/components/dynamic-theme";
|
|||||||
import { SignInWithIdp } from "@/components/sign-in-with-idp";
|
import { SignInWithIdp } from "@/components/sign-in-with-idp";
|
||||||
import { UsernameForm } from "@/components/username-form";
|
import { UsernameForm } from "@/components/username-form";
|
||||||
import {
|
import {
|
||||||
|
getActiveIdentityProviders,
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
getDefaultOrg,
|
getDefaultOrg,
|
||||||
getLoginSettings,
|
getLoginSettings,
|
||||||
settingsService,
|
|
||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
import { makeReqCtx } from "@zitadel/client/v2";
|
|
||||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||||
import { getLocale, getTranslations } from "next-intl/server";
|
import { getLocale, getTranslations } from "next-intl/server";
|
||||||
|
|
||||||
function getIdentityProviders(orgId?: string) {
|
|
||||||
return settingsService
|
|
||||||
.getActiveIdentityProviders({ ctx: makeReqCtx(orgId) }, {})
|
|
||||||
.then((resp) => {
|
|
||||||
return resp.identityProviders;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||||
}) {
|
}) {
|
||||||
@@ -47,9 +38,11 @@ export default async function Page(props: {
|
|||||||
organization ?? defaultOrganization,
|
organization ?? defaultOrganization,
|
||||||
);
|
);
|
||||||
|
|
||||||
const identityProviders = await getIdentityProviders(
|
const identityProviders = await getActiveIdentityProviders(
|
||||||
organization ?? defaultOrganization,
|
organization ?? defaultOrganization,
|
||||||
);
|
).then((resp) => {
|
||||||
|
return resp.identityProviders;
|
||||||
|
});
|
||||||
|
|
||||||
const branding = await getBrandingSettings(
|
const branding = await getBrandingSettings(
|
||||||
organization ?? defaultOrganization,
|
organization ?? defaultOrganization,
|
||||||
@@ -68,7 +61,7 @@ export default async function Page(props: {
|
|||||||
submit={submit}
|
submit={submit}
|
||||||
allowRegister={!!loginSettings?.allowRegister}
|
allowRegister={!!loginSettings?.allowRegister}
|
||||||
>
|
>
|
||||||
{identityProviders && process.env.ZITADEL_API_URL && (
|
{identityProviders && (
|
||||||
<SignInWithIdp
|
<SignInWithIdp
|
||||||
host={host}
|
host={host}
|
||||||
identityProviders={identityProviders}
|
identityProviders={identityProviders}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export interface SignInWithIDPProps {
|
|||||||
identityProviders: IdentityProvider[];
|
identityProviders: IdentityProvider[];
|
||||||
authRequestId?: string;
|
authRequestId?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
|
linkOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SignInWithIdp({
|
export function SignInWithIdp({
|
||||||
@@ -29,6 +30,7 @@ export function SignInWithIdp({
|
|||||||
identityProviders,
|
identityProviders,
|
||||||
authRequestId,
|
authRequestId,
|
||||||
organization,
|
organization,
|
||||||
|
linkOnly,
|
||||||
}: SignInWithIDPProps) {
|
}: SignInWithIDPProps) {
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [error, setError] = useState<string>("");
|
const [error, setError] = useState<string>("");
|
||||||
@@ -39,6 +41,10 @@ export function SignInWithIdp({
|
|||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
if (linkOnly) {
|
||||||
|
params.set("link", "true");
|
||||||
|
}
|
||||||
|
|
||||||
if (authRequestId) {
|
if (authRequestId) {
|
||||||
params.set("authRequestId", authRequestId);
|
params.set("authRequestId", authRequestId);
|
||||||
}
|
}
|
||||||
@@ -70,121 +76,134 @@ export function SignInWithIdp({
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full space-y-2 text-sm">
|
<div className="flex flex-col w-full space-y-2 text-sm">
|
||||||
{identityProviders &&
|
{identityProviders &&
|
||||||
identityProviders.map((idp, i) => {
|
identityProviders
|
||||||
switch (idp.type) {
|
// .filter((idp) =>
|
||||||
case IdentityProviderType.APPLE:
|
// linkOnly ? idp.config?.options.isLinkingAllowed : true,
|
||||||
return (
|
// )
|
||||||
<SignInWithApple
|
.map((idp, i) => {
|
||||||
key={`idp-${i}`}
|
switch (idp.type) {
|
||||||
name={idp.name}
|
case IdentityProviderType.APPLE:
|
||||||
onClick={() =>
|
return (
|
||||||
startFlow(idp.id, idpTypeToSlug(IdentityProviderType.APPLE))
|
<SignInWithApple
|
||||||
}
|
key={`idp-${i}`}
|
||||||
></SignInWithApple>
|
name={idp.name}
|
||||||
);
|
onClick={() =>
|
||||||
case IdentityProviderType.OAUTH:
|
startFlow(
|
||||||
return (
|
idp.id,
|
||||||
<SignInWithGeneric
|
idpTypeToSlug(IdentityProviderType.APPLE),
|
||||||
key={`idp-${i}`}
|
)
|
||||||
name={idp.name}
|
}
|
||||||
onClick={() =>
|
></SignInWithApple>
|
||||||
startFlow(idp.id, idpTypeToSlug(IdentityProviderType.OAUTH))
|
);
|
||||||
}
|
case IdentityProviderType.OAUTH:
|
||||||
></SignInWithGeneric>
|
return (
|
||||||
);
|
<SignInWithGeneric
|
||||||
case IdentityProviderType.OIDC:
|
key={`idp-${i}`}
|
||||||
return (
|
name={idp.name}
|
||||||
<SignInWithGeneric
|
onClick={() =>
|
||||||
key={`idp-${i}`}
|
startFlow(
|
||||||
name={idp.name}
|
idp.id,
|
||||||
onClick={() =>
|
idpTypeToSlug(IdentityProviderType.OAUTH),
|
||||||
startFlow(idp.id, idpTypeToSlug(IdentityProviderType.OIDC))
|
)
|
||||||
}
|
}
|
||||||
></SignInWithGeneric>
|
></SignInWithGeneric>
|
||||||
);
|
);
|
||||||
case IdentityProviderType.GITHUB:
|
case IdentityProviderType.OIDC:
|
||||||
return (
|
return (
|
||||||
<SignInWithGithub
|
<SignInWithGeneric
|
||||||
key={`idp-${i}`}
|
key={`idp-${i}`}
|
||||||
name={idp.name}
|
name={idp.name}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
startFlow(
|
startFlow(
|
||||||
idp.id,
|
idp.id,
|
||||||
idpTypeToSlug(IdentityProviderType.GITHUB),
|
idpTypeToSlug(IdentityProviderType.OIDC),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
></SignInWithGithub>
|
></SignInWithGeneric>
|
||||||
);
|
);
|
||||||
case IdentityProviderType.GITHUB_ES:
|
case IdentityProviderType.GITHUB:
|
||||||
return (
|
return (
|
||||||
<SignInWithGithub
|
<SignInWithGithub
|
||||||
key={`idp-${i}`}
|
key={`idp-${i}`}
|
||||||
name={idp.name}
|
name={idp.name}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
startFlow(
|
startFlow(
|
||||||
idp.id,
|
idp.id,
|
||||||
idpTypeToSlug(IdentityProviderType.GITHUB_ES),
|
idpTypeToSlug(IdentityProviderType.GITHUB),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
></SignInWithGithub>
|
></SignInWithGithub>
|
||||||
);
|
);
|
||||||
case IdentityProviderType.AZURE_AD:
|
case IdentityProviderType.GITHUB_ES:
|
||||||
return (
|
return (
|
||||||
<SignInWithAzureAd
|
<SignInWithGithub
|
||||||
key={`idp-${i}`}
|
key={`idp-${i}`}
|
||||||
name={idp.name}
|
name={idp.name}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
startFlow(
|
startFlow(
|
||||||
idp.id,
|
idp.id,
|
||||||
idpTypeToSlug(IdentityProviderType.AZURE_AD),
|
idpTypeToSlug(IdentityProviderType.GITHUB_ES),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
></SignInWithAzureAd>
|
></SignInWithGithub>
|
||||||
);
|
);
|
||||||
case IdentityProviderType.GOOGLE:
|
case IdentityProviderType.AZURE_AD:
|
||||||
return (
|
return (
|
||||||
<SignInWithGoogle
|
<SignInWithAzureAd
|
||||||
key={`idp-${i}`}
|
key={`idp-${i}`}
|
||||||
e2e="google"
|
name={idp.name}
|
||||||
name={idp.name}
|
onClick={() =>
|
||||||
onClick={() =>
|
startFlow(
|
||||||
startFlow(
|
idp.id,
|
||||||
idp.id,
|
idpTypeToSlug(IdentityProviderType.AZURE_AD),
|
||||||
idpTypeToSlug(IdentityProviderType.GOOGLE),
|
)
|
||||||
)
|
}
|
||||||
}
|
></SignInWithAzureAd>
|
||||||
></SignInWithGoogle>
|
);
|
||||||
);
|
case IdentityProviderType.GOOGLE:
|
||||||
case IdentityProviderType.GITLAB:
|
return (
|
||||||
return (
|
<SignInWithGoogle
|
||||||
<SignInWithGitlab
|
key={`idp-${i}`}
|
||||||
key={`idp-${i}`}
|
e2e="google"
|
||||||
name={idp.name}
|
name={idp.name}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
startFlow(
|
startFlow(
|
||||||
idp.id,
|
idp.id,
|
||||||
idpTypeToSlug(IdentityProviderType.GITLAB),
|
idpTypeToSlug(IdentityProviderType.GOOGLE),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
></SignInWithGitlab>
|
></SignInWithGoogle>
|
||||||
);
|
);
|
||||||
case IdentityProviderType.GITLAB_SELF_HOSTED:
|
case IdentityProviderType.GITLAB:
|
||||||
return (
|
return (
|
||||||
<SignInWithGitlab
|
<SignInWithGitlab
|
||||||
key={`idp-${i}`}
|
key={`idp-${i}`}
|
||||||
name={idp.name}
|
name={idp.name}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
startFlow(
|
startFlow(
|
||||||
idp.id,
|
idp.id,
|
||||||
idpTypeToSlug(IdentityProviderType.GITLAB_SELF_HOSTED),
|
idpTypeToSlug(IdentityProviderType.GITLAB),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
></SignInWithGitlab>
|
></SignInWithGitlab>
|
||||||
);
|
);
|
||||||
default:
|
case IdentityProviderType.GITLAB_SELF_HOSTED:
|
||||||
return null;
|
return (
|
||||||
}
|
<SignInWithGitlab
|
||||||
})}
|
key={`idp-${i}`}
|
||||||
|
name={idp.name}
|
||||||
|
onClick={() =>
|
||||||
|
startFlow(
|
||||||
|
idp.id,
|
||||||
|
idpTypeToSlug(IdentityProviderType.GITLAB_SELF_HOSTED),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
></SignInWithGitlab>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})}
|
||||||
{error && (
|
{error && (
|
||||||
<div className="py-4">
|
<div className="py-4">
|
||||||
<Alert>{error}</Alert>
|
<Alert>{error}</Alert>
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const humanUser = user.type.case === "human" ? user.type.value : undefined;
|
const humanUser = user.type.case === "human" ? user.type.value : undefined;
|
||||||
|
console.log("humanUser", humanUser);
|
||||||
if (
|
if (
|
||||||
availableSecondFactors?.length == 0 &&
|
availableSecondFactors?.length == 0 &&
|
||||||
humanUser?.passwordChangeRequired
|
humanUser?.passwordChangeRequired
|
||||||
|
|||||||
Reference in New Issue
Block a user