mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-12 08:23:16 +00:00
Merge pull request #307 from zitadel/force-mfa
additional login settings
This commit is contained in:
@@ -49,6 +49,7 @@ describe("login", () => {
|
|||||||
data: {
|
data: {
|
||||||
settings: {
|
settings: {
|
||||||
passkeysType: 1,
|
passkeysType: 1,
|
||||||
|
allowUsernamePassword: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -74,6 +74,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 +86,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 +119,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={loginName ?? sessionWithData.factors?.user?.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 +134,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}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { UserAvatar } from "@/components/user-avatar";
|
|||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import { getBrandingSettings, getLoginSettings } from "@/lib/zitadel";
|
import { getBrandingSettings, getLoginSettings } from "@/lib/zitadel";
|
||||||
import { getLocale, getTranslations } from "next-intl/server";
|
import { getLocale, getTranslations } from "next-intl/server";
|
||||||
|
import { headers } from "next/headers";
|
||||||
|
|
||||||
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>>;
|
||||||
@@ -30,6 +31,8 @@ export default async function Page(props: {
|
|||||||
|
|
||||||
const loginSettings = await getLoginSettings(organization);
|
const loginSettings = await getLoginSettings(organization);
|
||||||
|
|
||||||
|
const host = (await headers()).get("host");
|
||||||
|
|
||||||
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">
|
||||||
@@ -67,6 +70,8 @@ export default async function Page(props: {
|
|||||||
organization={organization}
|
organization={organization}
|
||||||
method={method}
|
method={method}
|
||||||
loginSettings={loginSettings}
|
loginSettings={loginSettings}
|
||||||
|
host={host}
|
||||||
|
code={code}
|
||||||
></LoginOTP>
|
></LoginOTP>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ type Props = {
|
|||||||
method: string;
|
method: string;
|
||||||
code?: string;
|
code?: string;
|
||||||
loginSettings?: LoginSettings;
|
loginSettings?: LoginSettings;
|
||||||
|
host: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Inputs = {
|
type Inputs = {
|
||||||
@@ -39,6 +40,7 @@ export function LoginOTP({
|
|||||||
method,
|
method,
|
||||||
code,
|
code,
|
||||||
loginSettings,
|
loginSettings,
|
||||||
|
host,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const t = useTranslations("otp");
|
const t = useTranslations("otp");
|
||||||
|
|
||||||
@@ -76,7 +78,18 @@ export function LoginOTP({
|
|||||||
|
|
||||||
if (method === "email") {
|
if (method === "email") {
|
||||||
challenges = create(RequestChallengesSchema, {
|
challenges = create(RequestChallengesSchema, {
|
||||||
otpEmail: { deliveryType: { case: "sendCode", value: {} } },
|
otpEmail: {
|
||||||
|
deliveryType: {
|
||||||
|
case: "sendCode",
|
||||||
|
value: host
|
||||||
|
? {
|
||||||
|
urlTemplate:
|
||||||
|
`${host.includes("localhost") ? "http://" : "https://"}${host}/otp/method=${method}?code={{.Code}}&userId={{.UserID}}&sessionId={{.SessionID}}&organization={{.OrgID}}` +
|
||||||
|
(authRequestId ? `&authRequestId=${authRequestId}` : ""),
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ export function PasswordForm({
|
|||||||
password: { password: values.password },
|
password: { password: values.password },
|
||||||
}),
|
}),
|
||||||
authRequestId,
|
authRequestId,
|
||||||
forceMfa: loginSettings?.forceMfa,
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setError("Could not verify password");
|
setError("Could not verify password");
|
||||||
@@ -87,6 +86,7 @@ export function PasswordForm({
|
|||||||
const response = await resetPassword({
|
const response = await resetPassword({
|
||||||
loginName,
|
loginName,
|
||||||
organization,
|
organization,
|
||||||
|
authRequestId,
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setError("Could not reset password");
|
setError("Could not reset password");
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export function SessionItem({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col overflow-hidden">
|
<div className="flex flex-col items-start overflow-hidden">
|
||||||
<span className="">{session.factors?.user?.displayName}</span>
|
<span className="">{session.factors?.user?.displayName}</span>
|
||||||
<span className="text-xs opacity-80 text-ellipsis">
|
<span className="text-xs opacity-80 text-ellipsis">
|
||||||
{session.factors?.user?.loginName}
|
{session.factors?.user?.loginName}
|
||||||
|
|||||||
@@ -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,7 +76,11 @@ 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
|
||||||
|
// .filter((idp) =>
|
||||||
|
// linkOnly ? idp.config?.options.isLinkingAllowed : true,
|
||||||
|
// )
|
||||||
|
.map((idp, i) => {
|
||||||
switch (idp.type) {
|
switch (idp.type) {
|
||||||
case IdentityProviderType.APPLE:
|
case IdentityProviderType.APPLE:
|
||||||
return (
|
return (
|
||||||
@@ -78,7 +88,10 @@ export function SignInWithIdp({
|
|||||||
key={`idp-${i}`}
|
key={`idp-${i}`}
|
||||||
name={idp.name}
|
name={idp.name}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
startFlow(idp.id, idpTypeToSlug(IdentityProviderType.APPLE))
|
startFlow(
|
||||||
|
idp.id,
|
||||||
|
idpTypeToSlug(IdentityProviderType.APPLE),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
></SignInWithApple>
|
></SignInWithApple>
|
||||||
);
|
);
|
||||||
@@ -88,7 +101,10 @@ export function SignInWithIdp({
|
|||||||
key={`idp-${i}`}
|
key={`idp-${i}`}
|
||||||
name={idp.name}
|
name={idp.name}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
startFlow(idp.id, idpTypeToSlug(IdentityProviderType.OAUTH))
|
startFlow(
|
||||||
|
idp.id,
|
||||||
|
idpTypeToSlug(IdentityProviderType.OAUTH),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
></SignInWithGeneric>
|
></SignInWithGeneric>
|
||||||
);
|
);
|
||||||
@@ -98,7 +114,10 @@ export function SignInWithIdp({
|
|||||||
key={`idp-${i}`}
|
key={`idp-${i}`}
|
||||||
name={idp.name}
|
name={idp.name}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
startFlow(idp.id, idpTypeToSlug(IdentityProviderType.OIDC))
|
startFlow(
|
||||||
|
idp.id,
|
||||||
|
idpTypeToSlug(IdentityProviderType.OIDC),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
></SignInWithGeneric>
|
></SignInWithGeneric>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ export function UsernameForm({
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(res, "res");
|
||||||
|
|
||||||
if (res?.redirect) {
|
if (res?.redirect) {
|
||||||
return router.push(res.redirect);
|
return router.push(res.redirect);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,24 +15,29 @@ export async function getNextUrl(
|
|||||||
defaultRedirectUri?: string,
|
defaultRedirectUri?: string,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
if ("sessionId" in command && "authRequestId" in command) {
|
if ("sessionId" in command && "authRequestId" in command) {
|
||||||
const url =
|
const params = new URLSearchParams({
|
||||||
`/login?` +
|
|
||||||
new URLSearchParams({
|
|
||||||
sessionId: command.sessionId,
|
sessionId: command.sessionId,
|
||||||
authRequest: command.authRequestId,
|
authRequest: command.authRequestId,
|
||||||
});
|
});
|
||||||
return url;
|
|
||||||
|
if (command.organization) {
|
||||||
|
params.append("organization", command.organization);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `/login?` + params;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defaultRedirectUri) {
|
if (defaultRedirectUri) {
|
||||||
return defaultRedirectUri;
|
return defaultRedirectUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
const signedInUrl =
|
const params = new URLSearchParams({
|
||||||
`/signedin?` +
|
|
||||||
new URLSearchParams({
|
|
||||||
loginName: command.loginName,
|
loginName: command.loginName,
|
||||||
});
|
});
|
||||||
|
|
||||||
return signedInUrl;
|
if (command.organization) {
|
||||||
|
params.append("organization", command.organization);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `/signedin?` + params;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
import { create } from "@zitadel/client";
|
import { create } from "@zitadel/client";
|
||||||
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||||
import { UserState } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
|
||||||
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import { idpTypeToIdentityProviderType, idpTypeToSlug } from "../idp";
|
import { idpTypeToIdentityProviderType, idpTypeToSlug } from "../idp";
|
||||||
|
|
||||||
|
import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||||
|
import { UserState } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||||
import {
|
import {
|
||||||
getActiveIdentityProviders,
|
getActiveIdentityProviders,
|
||||||
getIDPByID,
|
getIDPByID,
|
||||||
@@ -35,6 +36,15 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
|
|
||||||
const loginSettings = await getLoginSettings(command.organization);
|
const loginSettings = await getLoginSettings(command.organization);
|
||||||
|
|
||||||
|
const potentialUsers = users.result.filter((u) => {
|
||||||
|
const human = u.type.case === "human" ? u.type.value : undefined;
|
||||||
|
return loginSettings?.disableLoginWithEmail
|
||||||
|
? human?.email?.isVerified && human?.email?.email !== command.loginName
|
||||||
|
: loginSettings?.disableLoginWithPhone
|
||||||
|
? human?.phone?.isVerified && human?.phone?.phone !== command.loginName
|
||||||
|
: true;
|
||||||
|
});
|
||||||
|
|
||||||
const redirectUserToSingleIDPIfAvailable = async () => {
|
const redirectUserToSingleIDPIfAvailable = async () => {
|
||||||
const identityProviders = await getActiveIdentityProviders(
|
const identityProviders = await getActiveIdentityProviders(
|
||||||
command.organization,
|
command.organization,
|
||||||
@@ -84,6 +94,7 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
const identityProviderId = identityProviders[0].idpId;
|
const identityProviderId = identityProviders[0].idpId;
|
||||||
|
|
||||||
const idp = await getIDPByID(identityProviderId);
|
const idp = await getIDPByID(identityProviderId);
|
||||||
|
|
||||||
const idpType = idp?.type;
|
const idpType = idp?.type;
|
||||||
|
|
||||||
if (!idp || !idpType) {
|
if (!idp || !idpType) {
|
||||||
@@ -119,8 +130,8 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (users.details?.totalResult == BigInt(1) && users.result[0].userId) {
|
if (potentialUsers.length == 1 && potentialUsers[0].userId) {
|
||||||
const userId = users.result[0].userId;
|
const userId = potentialUsers[0].userId;
|
||||||
|
|
||||||
const checks = create(ChecksSchema, {
|
const checks = create(ChecksSchema, {
|
||||||
user: { search: { case: "userId", value: userId } },
|
user: { search: { case: "userId", value: userId } },
|
||||||
@@ -136,24 +147,9 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
return { error: "Could not create session for user" };
|
return { error: "Could not create session for user" };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (users.result[0].state === UserState.INITIAL) {
|
// TODO: check if handling of userstate INITIAL is needed
|
||||||
const params = new URLSearchParams({
|
if (potentialUsers[0].state === UserState.INITIAL) {
|
||||||
loginName: session.factors?.user?.loginName,
|
return { error: "Initial User not supported" };
|
||||||
initial: "true", // this does not require a code to be set
|
|
||||||
});
|
|
||||||
|
|
||||||
if (command.organization || session.factors?.user?.organizationId) {
|
|
||||||
params.append(
|
|
||||||
"organization",
|
|
||||||
command.organization ?? session.factors?.user?.organizationId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.authRequestId) {
|
|
||||||
params.append("authRequestid", command.authRequestId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { redirect: "/password/set?" + params };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const methods = await listAuthenticationMethodTypes(
|
const methods = await listAuthenticationMethodTypes(
|
||||||
@@ -162,9 +158,9 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
|
|
||||||
if (!methods.authMethodTypes || !methods.authMethodTypes.length) {
|
if (!methods.authMethodTypes || !methods.authMethodTypes.length) {
|
||||||
if (
|
if (
|
||||||
users.result[0].type.case === "human" &&
|
potentialUsers[0].type.case === "human" &&
|
||||||
users.result[0].type.value.email &&
|
potentialUsers[0].type.value.email &&
|
||||||
!users.result[0].type.value.email.isVerified
|
!potentialUsers[0].type.value.email.isVerified
|
||||||
) {
|
) {
|
||||||
const paramsVerify = new URLSearchParams({
|
const paramsVerify = new URLSearchParams({
|
||||||
loginName: session.factors?.user?.loginName,
|
loginName: session.factors?.user?.loginName,
|
||||||
@@ -209,6 +205,13 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
const method = methods.authMethodTypes[0];
|
const method = methods.authMethodTypes[0];
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case AuthenticationMethodType.PASSWORD: // user has only password as auth method
|
case AuthenticationMethodType.PASSWORD: // user has only password as auth method
|
||||||
|
if (!loginSettings?.allowUsernamePassword) {
|
||||||
|
return {
|
||||||
|
error:
|
||||||
|
"Username Password not allowed! Contact your administrator for more information.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const paramsPassword: any = {
|
const paramsPassword: any = {
|
||||||
loginName: session.factors?.user?.loginName,
|
loginName: session.factors?.user?.loginName,
|
||||||
};
|
};
|
||||||
@@ -229,6 +232,13 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
case AuthenticationMethodType.PASSKEY: // AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSKEY
|
case AuthenticationMethodType.PASSKEY: // AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSKEY
|
||||||
|
if (loginSettings?.passkeysType === PasskeysType.NOT_ALLOWED) {
|
||||||
|
return {
|
||||||
|
error:
|
||||||
|
"Passkeys not allowed! Contact your administrator for more information.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const paramsPasskey: any = { loginName: command.loginName };
|
const paramsPasskey: any = { loginName: command.loginName };
|
||||||
if (command.authRequestId) {
|
if (command.authRequestId) {
|
||||||
paramsPasskey.authRequestId = command.authRequestId;
|
paramsPasskey.authRequestId = command.authRequestId;
|
||||||
@@ -262,7 +272,7 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
} else if (
|
} else if (
|
||||||
methods.authMethodTypes.includes(AuthenticationMethodType.IDP)
|
methods.authMethodTypes.includes(AuthenticationMethodType.IDP)
|
||||||
) {
|
) {
|
||||||
await redirectUserToIDP(userId);
|
return redirectUserToIDP(userId);
|
||||||
} else if (
|
} else if (
|
||||||
methods.authMethodTypes.includes(AuthenticationMethodType.PASSWORD)
|
methods.authMethodTypes.includes(AuthenticationMethodType.PASSWORD)
|
||||||
) {
|
) {
|
||||||
@@ -288,8 +298,10 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
|||||||
// user not found, check if register is enabled on organization
|
// user not found, check if register is enabled on organization
|
||||||
if (loginSettings?.allowRegister && !loginSettings?.allowUsernamePassword) {
|
if (loginSettings?.allowRegister && !loginSettings?.allowUsernamePassword) {
|
||||||
// TODO: do we need to handle login hints for IDPs here?
|
// TODO: do we need to handle login hints for IDPs here?
|
||||||
await redirectUserToSingleIDPIfAvailable();
|
const resp = await redirectUserToSingleIDPIfAvailable();
|
||||||
|
if (resp) {
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
return { error: "Could not find user" };
|
return { error: "Could not find user" };
|
||||||
} else if (
|
} else if (
|
||||||
loginSettings?.allowRegister &&
|
loginSettings?.allowRegister &&
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { getSessionCookieByLoginName } from "../cookies";
|
|||||||
type ResetPasswordCommand = {
|
type ResetPasswordCommand = {
|
||||||
loginName: string;
|
loginName: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
|
authRequestId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function resetPassword(command: ResetPasswordCommand) {
|
export async function resetPassword(command: ResetPasswordCommand) {
|
||||||
@@ -46,7 +47,7 @@ export async function resetPassword(command: ResetPasswordCommand) {
|
|||||||
}
|
}
|
||||||
const userId = users.result[0].userId;
|
const userId = users.result[0].userId;
|
||||||
|
|
||||||
return passwordReset(userId, host);
|
return passwordReset(userId, host, command.authRequestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UpdateSessionCommand = {
|
export type UpdateSessionCommand = {
|
||||||
@@ -54,7 +55,6 @@ export type UpdateSessionCommand = {
|
|||||||
organization?: string;
|
organization?: string;
|
||||||
checks: Checks;
|
checks: Checks;
|
||||||
authRequestId?: string;
|
authRequestId?: string;
|
||||||
forceMfa?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function sendPassword(command: UpdateSessionCommand) {
|
export async function sendPassword(command: UpdateSessionCommand) {
|
||||||
@@ -148,6 +148,27 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
|||||||
m !== AuthenticationMethodType.PASSKEY,
|
m !== AuthenticationMethodType.PASSKEY,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const humanUser = user.type.case === "human" ? user.type.value : undefined;
|
||||||
|
console.log("humanUser", humanUser);
|
||||||
|
if (
|
||||||
|
availableSecondFactors?.length == 0 &&
|
||||||
|
humanUser?.passwordChangeRequired
|
||||||
|
) {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
loginName: session.factors?.user?.loginName,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (command.organization || session.factors?.user?.organizationId) {
|
||||||
|
params.append("organization", session.factors?.user?.organizationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.authRequestId) {
|
||||||
|
params.append("authRequestId", command.authRequestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { redirect: "/password/change?" + params };
|
||||||
|
}
|
||||||
|
|
||||||
if (availableSecondFactors?.length == 1) {
|
if (availableSecondFactors?.length == 1) {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
loginName: session.factors?.user.loginName,
|
loginName: session.factors?.user.loginName,
|
||||||
@@ -192,24 +213,14 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return { redirect: `/mfa?` + params };
|
return { redirect: `/mfa?` + params };
|
||||||
} else if (user.state === UserState.INITIAL) {
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
loginName: session.factors.user.loginName,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (command.authRequestId) {
|
|
||||||
params.append("authRequestId", command.authRequestId);
|
|
||||||
}
|
}
|
||||||
|
// TODO: check if handling of userstate INITIAL is needed
|
||||||
if (command.organization || session.factors?.user?.organizationId) {
|
else if (user.state === UserState.INITIAL) {
|
||||||
params.append(
|
return { error: "Initial User not supported" };
|
||||||
"organization",
|
} else if (
|
||||||
command.organization ?? session.factors?.user?.organizationId,
|
(loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly) &&
|
||||||
);
|
!availableSecondFactors.length
|
||||||
}
|
) {
|
||||||
|
|
||||||
return { redirect: `/password/change?` + params };
|
|
||||||
} else if (command.forceMfa && !availableSecondFactors.length) {
|
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
loginName: session.factors.user.loginName,
|
loginName: session.factors.user.loginName,
|
||||||
force: "true", // this defines if the mfa is forced in the settings
|
force: "true", // this defines if the mfa is forced in the settings
|
||||||
|
|||||||
@@ -504,7 +504,11 @@ export function createUser(
|
|||||||
* @param userId the id of the user where the email should be set
|
* @param userId the id of the user where the email should be set
|
||||||
* @returns the newly set email
|
* @returns the newly set email
|
||||||
*/
|
*/
|
||||||
export async function passwordReset(userId: string, host: string | null) {
|
export async function passwordReset(
|
||||||
|
userId: string,
|
||||||
|
host: string | null,
|
||||||
|
authRequestId?: string,
|
||||||
|
) {
|
||||||
let medium = create(SendPasswordResetLinkSchema, {
|
let medium = create(SendPasswordResetLinkSchema, {
|
||||||
notificationType: NotificationType.Email,
|
notificationType: NotificationType.Email,
|
||||||
});
|
});
|
||||||
@@ -512,7 +516,9 @@ export async function passwordReset(userId: string, host: string | null) {
|
|||||||
if (host) {
|
if (host) {
|
||||||
medium = {
|
medium = {
|
||||||
...medium,
|
...medium,
|
||||||
urlTemplate: `${host.includes("localhost") ? "http://" : "https://"}${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}`,
|
urlTemplate:
|
||||||
|
`${host.includes("localhost") ? "http://" : "https://"}${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` +
|
||||||
|
(authRequestId ? `&authRequestId=${authRequestId}` : ""),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user