mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-15 17:52:16 +00:00
Merge branch 'main' into acceptance-test-suite
This commit is contained in:
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -5,7 +5,7 @@ on: pull_request
|
||||
jobs:
|
||||
quality:
|
||||
env:
|
||||
ZITADEL_IMAGE: ghcr.io/zitadel/zitadel:v2.63.4
|
||||
ZITADEL_IMAGE: ghcr.io/zitadel/zitadel:v2.65.0
|
||||
POSTGRES_IMAGE: postgres:17.0-alpine3.19
|
||||
|
||||
name: Ensure Quality
|
||||
|
||||
@@ -13,7 +13,7 @@ services:
|
||||
condition: "service_healthy"
|
||||
|
||||
db:
|
||||
restart: 'always'
|
||||
restart: "always"
|
||||
image: "${POSTGRES_IMAGE:-postgres:latest}"
|
||||
environment:
|
||||
- POSTGRES_USER=zitadel
|
||||
@@ -23,21 +23,16 @@ services:
|
||||
command: postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all -c shared_buffers=1GB -c work_mem=16MB -c effective_io_concurrency=100 -c wal_level=minimal -c archive_mode=off -c max_wal_senders=0
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready"]
|
||||
interval: '10s'
|
||||
timeout: '30s'
|
||||
interval: "10s"
|
||||
timeout: "30s"
|
||||
retries: 5
|
||||
start_period: '20s'
|
||||
start_period: "20s"
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
wait_for_zitadel:
|
||||
image: curlimages/curl:8.00.1
|
||||
command:
|
||||
[
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"i=0; while ! curl http://zitadel:8080/debug/ready && [ $$i -lt 30 ]; do sleep 1; i=$$((i+1)); done; [ $$i -eq 120 ] && exit 1 || exit 0",
|
||||
]
|
||||
command: /bin/sh -c "until curl -s -o /dev/null -i -f http://zitadel:8080/debug/ready; do echo 'waiting' && sleep 1; done; echo 'ready' && sleep 5;" || false
|
||||
depends_on:
|
||||
- zitadel
|
||||
|
||||
|
||||
@@ -29,6 +29,23 @@ echo "Writing environment file to ${WRITE_ENVIRONMENT_FILE} when done."
|
||||
|
||||
echo "ZITADEL_API_URL=${ZITADEL_API_URL}
|
||||
ZITADEL_SERVICE_USER_ID=${ZITADEL_SERVICE_USER_ID}
|
||||
ZITADEL_SERVICE_USER_TOKEN=${PAT}" > ${WRITE_ENVIRONMENT_FILE}
|
||||
ZITADEL_SERVICE_USER_TOKEN=${PAT}
|
||||
DEBUG=true" > ${WRITE_ENVIRONMENT_FILE}
|
||||
|
||||
echo "Wrote environment file ${WRITE_ENVIRONMENT_FILE}"
|
||||
cat ${WRITE_ENVIRONMENT_FILE}
|
||||
|
||||
DEFAULTORG_RESPONSE_RESULTS=0
|
||||
# waiting for default organization
|
||||
until [ ${DEFAULTORG_RESPONSE_RESULTS} -eq 1 ]
|
||||
do
|
||||
DEFAULTORG_RESPONSE=$(curl -s --request POST \
|
||||
--url "${ZITADEL_API_INTERNAL_URL}/v2/organizations/_search" \
|
||||
--header "Authorization: Bearer ${PAT}" \
|
||||
--header "Host: ${ZITADEL_API_DOMAIN}" \
|
||||
--header "Content-Type: application/json" \
|
||||
-d "{\"queries\": [{\"defaultQuery\":{}}]}" )
|
||||
echo "Received default organization response: ${DEFAULTORG_RESPONSE}"
|
||||
DEFAULTORG_RESPONSE_RESULTS=$(echo $DEFAULTORG_RESPONSE | jq -r '.result | length')
|
||||
echo "Received default organization response result: ${DEFAULTORG_RESPONSE_RESULTS}"
|
||||
done
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
import {test as base} from "@playwright/test";
|
||||
import {PasswordUser} from './user';
|
||||
import path from 'path';
|
||||
import dotenv from 'dotenv';
|
||||
import {loginScreenExpect, loginWithPassword, startLogin} from "./login";
|
||||
import {loginnameScreenExpect} from "./loginname-screen";
|
||||
import {passwordScreenExpect} from "./password-screen";
|
||||
import {loginname} from "./loginname";
|
||||
import {password} from "./password";
|
||||
import { test as base } from "@playwright/test";
|
||||
import dotenv from "dotenv";
|
||||
import path from "path";
|
||||
import { loginScreenExpect, loginWithPassword, startLogin } from "./login";
|
||||
import { loginname } from "./loginname";
|
||||
import { loginnameScreenExpect } from "./loginname-screen";
|
||||
import { password } from "./password";
|
||||
import { passwordScreenExpect } from "./password-screen";
|
||||
import { PasswordUser } from "./user";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({path: path.resolve(__dirname, '.env.local')});
|
||||
dotenv.config({ path: path.resolve(__dirname, ".env.local") });
|
||||
|
||||
const test = base.extend<{ user: PasswordUser }>({
|
||||
user: async ({page}, use) => {
|
||||
const user = new PasswordUser({
|
||||
email: "password@example.com",
|
||||
firstName: "first",
|
||||
lastName: "last",
|
||||
password: "Password1!",
|
||||
organization: "",
|
||||
});
|
||||
await user.ensure(page);
|
||||
await use(user);
|
||||
},
|
||||
user: async ({ page }, use) => {
|
||||
const user = new PasswordUser({
|
||||
email: "password@example.com",
|
||||
firstName: "first",
|
||||
lastName: "last",
|
||||
password: "Password1!",
|
||||
organization: "",
|
||||
});
|
||||
await user.ensure(page);
|
||||
await use(user);
|
||||
},
|
||||
});
|
||||
|
||||
test("username and password login", async ({user, page}) => {
|
||||
await loginWithPassword(page, user.getUsername(), user.getPassword())
|
||||
await loginScreenExpect(page, user.getFullName());
|
||||
test("username and password login", async ({ user, page }) => {
|
||||
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||
await loginScreenExpect(page, user.getFullName());
|
||||
});
|
||||
|
||||
test("username and password login, unknown username", async ({page}) => {
|
||||
const username = "unknown"
|
||||
await startLogin(page);
|
||||
await loginname(page, username)
|
||||
await loginnameScreenExpect(page, username)
|
||||
test("username and password login, unknown username", async ({ page }) => {
|
||||
const username = "unknown";
|
||||
await startLogin(page);
|
||||
await loginname(page, username);
|
||||
await loginnameScreenExpect(page, username);
|
||||
});
|
||||
|
||||
test("username and password login, wrong password", async ({user, page}) => {
|
||||
await startLogin(page);
|
||||
await loginname(page, user.getUsername())
|
||||
await password(page, "wrong")
|
||||
await passwordScreenExpect(page, "wrong")
|
||||
test("username and password login, wrong password", async ({ user, page }) => {
|
||||
await startLogin(page);
|
||||
await loginname(page, user.getUsername());
|
||||
await password(page, "wrong");
|
||||
await passwordScreenExpect(page, "wrong");
|
||||
});
|
||||
|
||||
@@ -2,6 +2,14 @@ import { stub } from "../support/mock";
|
||||
|
||||
describe("login", () => {
|
||||
beforeEach(() => {
|
||||
stub("zitadel.org.v2.OrganizationService", "ListOrganizations", {
|
||||
data: {
|
||||
details: {
|
||||
totalResult: 1,
|
||||
},
|
||||
result: [{ id: "256088834543534543" }],
|
||||
},
|
||||
});
|
||||
stub("zitadel.session.v2.SessionService", "CreateSession", {
|
||||
data: {
|
||||
details: {
|
||||
|
||||
@@ -3,11 +3,12 @@ import { SignInWithIdp } from "@/components/sign-in-with-idp";
|
||||
import { UsernameForm } from "@/components/username-form";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
getLegalAndSupportSettings,
|
||||
getDefaultOrg,
|
||||
getLoginSettings,
|
||||
settingsService,
|
||||
} from "@/lib/zitadel";
|
||||
import { makeReqCtx } from "@zitadel/client/v2";
|
||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
|
||||
function getIdentityProviders(orgId?: string) {
|
||||
@@ -31,16 +32,29 @@ export default async function Page({
|
||||
const organization = searchParams?.organization;
|
||||
const submit: boolean = searchParams?.submit === "true";
|
||||
|
||||
const loginSettings = await getLoginSettings(organization);
|
||||
const legal = await getLegalAndSupportSettings();
|
||||
|
||||
const identityProviders = await getIdentityProviders(organization);
|
||||
let defaultOrganization;
|
||||
if (!organization) {
|
||||
const org: Organization | null = await getDefaultOrg();
|
||||
if (org) {
|
||||
defaultOrganization = org.id;
|
||||
}
|
||||
}
|
||||
|
||||
const host = process.env.VERCEL_URL
|
||||
? `https://${process.env.VERCEL_URL}`
|
||||
: "http://localhost:3000";
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
const loginSettings = await getLoginSettings(
|
||||
organization ?? defaultOrganization,
|
||||
);
|
||||
|
||||
const identityProviders = await getIdentityProviders(
|
||||
organization ?? defaultOrganization,
|
||||
);
|
||||
|
||||
const branding = await getBrandingSettings(
|
||||
organization ?? defaultOrganization,
|
||||
);
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
@@ -51,16 +65,16 @@ export default async function Page({
|
||||
<UsernameForm
|
||||
loginName={loginName}
|
||||
authRequestId={authRequestId}
|
||||
organization={organization}
|
||||
organization={organization} // stick to "organization" as we still want to do user discovery based on the searchParams not the default organization, later the organization is determined by the found user
|
||||
submit={submit}
|
||||
allowRegister={!!loginSettings?.allowRegister}
|
||||
>
|
||||
{legal && identityProviders && process.env.ZITADEL_API_URL && (
|
||||
{identityProviders && process.env.ZITADEL_API_URL && (
|
||||
<SignInWithIdp
|
||||
host={host}
|
||||
identityProviders={identityProviders}
|
||||
authRequestId={authRequestId}
|
||||
organization={organization}
|
||||
organization={organization ?? defaultOrganization} // use the organization from the searchParams here otherwise fallback to the default organization
|
||||
></SignInWithIdp>
|
||||
)}
|
||||
</UsernameForm>
|
||||
|
||||
@@ -3,7 +3,12 @@ import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { PasswordForm } from "@/components/password-form";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import { getBrandingSettings, getLoginSettings } from "@/lib/zitadel";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
getDefaultOrg,
|
||||
getLoginSettings,
|
||||
} from "@/lib/zitadel";
|
||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||
import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
|
||||
@@ -16,7 +21,16 @@ export default async function Page({
|
||||
const t = await getTranslations({ locale, namespace: "password" });
|
||||
const tError = await getTranslations({ locale, namespace: "error" });
|
||||
|
||||
const { loginName, organization, authRequestId, alt } = searchParams;
|
||||
let { loginName, organization, authRequestId, alt } = searchParams;
|
||||
|
||||
let defaultOrganization;
|
||||
if (!organization) {
|
||||
const org: Organization | null = await getDefaultOrg();
|
||||
|
||||
if (org) {
|
||||
defaultOrganization = org.id;
|
||||
}
|
||||
}
|
||||
|
||||
// also allow no session to be found (ignoreUnkownUsername)
|
||||
let sessionFactors;
|
||||
@@ -30,8 +44,12 @@ export default async function Page({
|
||||
console.warn(error);
|
||||
}
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
const loginSettings = await getLoginSettings(organization);
|
||||
const branding = await getBrandingSettings(
|
||||
organization ?? defaultOrganization,
|
||||
);
|
||||
const loginSettings = await getLoginSettings(
|
||||
organization ?? defaultOrganization,
|
||||
);
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
@@ -62,7 +80,7 @@ export default async function Page({
|
||||
<PasswordForm
|
||||
loginName={loginName}
|
||||
authRequestId={authRequestId}
|
||||
organization={organization}
|
||||
organization={organization} // stick to "organization" as we still want to do user discovery based on the searchParams not the default organization, later the organization is determined by the found user
|
||||
loginSettings={loginSettings}
|
||||
promptPasswordless={
|
||||
loginSettings?.passkeysType === PasskeysType.ALLOWED
|
||||
|
||||
@@ -22,13 +22,8 @@ export default async function Page({
|
||||
searchParams;
|
||||
|
||||
if (!organization) {
|
||||
const org: Organization | null = await getDefaultOrg().catch((error) => {
|
||||
console.warn(error);
|
||||
return null;
|
||||
});
|
||||
if (!org) {
|
||||
console.warn("No default organization found");
|
||||
} else {
|
||||
const org: Organization | null = await getDefaultOrg();
|
||||
if (org) {
|
||||
organization = org.id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { create } from "@zitadel/client";
|
||||
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { redirect } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { FieldValues, useForm } from "react-hook-form";
|
||||
import { Alert } from "./alert";
|
||||
@@ -103,6 +104,11 @@ export function ChangePasswordForm({
|
||||
passwordResponse.error
|
||||
) {
|
||||
setError(passwordResponse.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (passwordResponse && passwordResponse.nextStep) {
|
||||
return redirect(passwordResponse.nextStep);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/solid";
|
||||
import { clsx } from "clsx";
|
||||
import {
|
||||
createContext,
|
||||
Dispatch,
|
||||
ReactNode,
|
||||
SetStateAction,
|
||||
useContext,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
const MobileNavContext = createContext<
|
||||
[boolean, Dispatch<SetStateAction<boolean>>] | undefined
|
||||
>(undefined);
|
||||
|
||||
export function MobileNavContextProvider({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
return (
|
||||
<MobileNavContext.Provider value={[isOpen, setIsOpen]}>
|
||||
{children}
|
||||
</MobileNavContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useMobileNavToggle() {
|
||||
const context = useContext(MobileNavContext);
|
||||
if (context === undefined) {
|
||||
throw new Error(
|
||||
"useMobileNavToggle must be used within a MobileNavContextProvider",
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
export function MobileNavToggle({ children }: { children: ReactNode }) {
|
||||
const [isOpen, setIsOpen] = useMobileNavToggle();
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="group absolute right-0 top-0 flex h-14 items-center space-x-2 px-4 lg:hidden"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
<div className="font-medium text-text-light-secondary-500 dark:text-text-dark-secondary-500 group-hover:text-text-light-500 dark:group-hover:text-text-dark-500">
|
||||
Menu
|
||||
</div>
|
||||
{isOpen ? (
|
||||
<XMarkIcon className="block w-6" />
|
||||
) : (
|
||||
<Bars3Icon className="block w-6" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
<div
|
||||
className={clsx("overflow-y-auto lg:static lg:block", {
|
||||
"fixed inset-x-0 bottom-0 top-14 bg-gray-900": isOpen,
|
||||
hidden: !isOpen,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -71,9 +71,12 @@ export function PasswordForm({
|
||||
|
||||
if (response && "error" in response && response.error) {
|
||||
setError(response.error);
|
||||
return;
|
||||
}
|
||||
|
||||
return response;
|
||||
if (response && response.nextStep) {
|
||||
return router.push(response.nextStep);
|
||||
}
|
||||
}
|
||||
|
||||
async function resetPasswordAndContinue() {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { create } from "@zitadel/client";
|
||||
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { redirect } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { FieldValues, useForm } from "react-hook-form";
|
||||
import { Alert } from "./alert";
|
||||
@@ -123,7 +124,14 @@ export function SetPasswordForm({
|
||||
passwordResponse.error
|
||||
) {
|
||||
setError(passwordResponse.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (passwordResponse && passwordResponse.nextStep) {
|
||||
return redirect(passwordResponse.nextStep);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { errors } = formState;
|
||||
|
||||
@@ -124,23 +124,11 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
||||
}
|
||||
}
|
||||
|
||||
const submitted = {
|
||||
sessionId: session.id,
|
||||
factors: session.factors,
|
||||
challenges: session.challenges,
|
||||
authMethods,
|
||||
userState: user.state,
|
||||
};
|
||||
|
||||
if (
|
||||
!submitted ||
|
||||
!submitted.authMethods ||
|
||||
!submitted.factors?.user?.loginName
|
||||
) {
|
||||
if (!authMethods || !session.factors?.user?.loginName) {
|
||||
return { error: "Could not verify password!" };
|
||||
}
|
||||
|
||||
const availableSecondFactors = submitted?.authMethods?.filter(
|
||||
const availableSecondFactors = authMethods?.filter(
|
||||
(m: AuthenticationMethodType) =>
|
||||
m !== AuthenticationMethodType.PASSWORD &&
|
||||
m !== AuthenticationMethodType.PASSKEY,
|
||||
@@ -148,15 +136,18 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
||||
|
||||
if (availableSecondFactors?.length == 1) {
|
||||
const params = new URLSearchParams({
|
||||
loginName: submitted.factors?.user.loginName,
|
||||
loginName: session.factors?.user.loginName,
|
||||
});
|
||||
|
||||
if (command.authRequestId) {
|
||||
params.append("authRequestId", command.authRequestId);
|
||||
}
|
||||
|
||||
if (command.organization) {
|
||||
params.append("organization", command.organization);
|
||||
if (command.organization || session.factors?.user?.organizationId) {
|
||||
params.append(
|
||||
"organization",
|
||||
command.organization ?? session.factors?.user?.organizationId,
|
||||
);
|
||||
}
|
||||
|
||||
const factor = availableSecondFactors[0];
|
||||
@@ -172,35 +163,41 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
||||
}
|
||||
} else if (availableSecondFactors?.length >= 1) {
|
||||
const params = new URLSearchParams({
|
||||
loginName: submitted.factors.user.loginName,
|
||||
loginName: session.factors.user.loginName,
|
||||
});
|
||||
|
||||
if (command.authRequestId) {
|
||||
params.append("authRequestId", command.authRequestId);
|
||||
}
|
||||
|
||||
if (command.organization) {
|
||||
params.append("organization", command.organization);
|
||||
if (command.organization || session.factors?.user?.organizationId) {
|
||||
params.append(
|
||||
"organization",
|
||||
command.organization ?? session.factors?.user?.organizationId,
|
||||
);
|
||||
}
|
||||
|
||||
return redirect(`/mfa?` + params);
|
||||
} else if (submitted.userState === UserState.INITIAL) {
|
||||
} else if (user.state === UserState.INITIAL) {
|
||||
const params = new URLSearchParams({
|
||||
loginName: submitted.factors.user.loginName,
|
||||
loginName: session.factors.user.loginName,
|
||||
});
|
||||
|
||||
if (command.authRequestId) {
|
||||
params.append("authRequestId", command.authRequestId);
|
||||
}
|
||||
|
||||
if (command.organization) {
|
||||
params.append("organization", command.organization);
|
||||
if (command.organization || session.factors?.user?.organizationId) {
|
||||
params.append(
|
||||
"organization",
|
||||
command.organization ?? session.factors?.user?.organizationId,
|
||||
);
|
||||
}
|
||||
|
||||
return redirect(`/password/change?` + params);
|
||||
} else if (command.forceMfa && !availableSecondFactors.length) {
|
||||
const params = new URLSearchParams({
|
||||
loginName: submitted.factors.user.loginName,
|
||||
loginName: session.factors.user.loginName,
|
||||
force: "true", // this defines if the mfa is forced in the settings
|
||||
checkAfter: "true", // this defines if the check is directly made after the setup
|
||||
});
|
||||
@@ -209,8 +206,11 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
||||
params.append("authRequestId", command.authRequestId);
|
||||
}
|
||||
|
||||
if (command.organization) {
|
||||
params.append("organization", command.organization);
|
||||
if (command.organization || session.factors?.user?.organizationId) {
|
||||
params.append(
|
||||
"organization",
|
||||
command.organization ?? session.factors?.user?.organizationId,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: provide a way to setup passkeys on mfa page?
|
||||
@@ -239,33 +239,39 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
||||
|
||||
// return router.push(`/passkey/set?` + params);
|
||||
// }
|
||||
else if (command.authRequestId && submitted.sessionId) {
|
||||
else if (command.authRequestId && session.id) {
|
||||
const params = new URLSearchParams({
|
||||
sessionId: submitted.sessionId,
|
||||
sessionId: session.id,
|
||||
authRequest: command.authRequestId,
|
||||
});
|
||||
|
||||
if (command.organization) {
|
||||
params.append("organization", command.organization);
|
||||
if (command.organization || session.factors?.user?.organizationId) {
|
||||
params.append(
|
||||
"organization",
|
||||
command.organization ?? session.factors?.user?.organizationId,
|
||||
);
|
||||
}
|
||||
|
||||
return redirect(`/login?` + params);
|
||||
return { nextStep: `/login?${params}` };
|
||||
}
|
||||
|
||||
// without OIDC flow
|
||||
const params = new URLSearchParams(
|
||||
command.authRequestId
|
||||
? {
|
||||
loginName: submitted.factors.user.loginName,
|
||||
loginName: session.factors.user.loginName,
|
||||
authRequestId: command.authRequestId,
|
||||
}
|
||||
: {
|
||||
loginName: submitted.factors.user.loginName,
|
||||
loginName: session.factors.user.loginName,
|
||||
},
|
||||
);
|
||||
|
||||
if (command.organization) {
|
||||
params.append("organization", command.organization);
|
||||
if (command.organization || session.factors?.user?.organizationId) {
|
||||
params.append(
|
||||
"organization",
|
||||
command.organization ?? session.factors?.user?.organizationId,
|
||||
);
|
||||
}
|
||||
|
||||
return redirect(`/signedin?` + params);
|
||||
|
||||
Reference in New Issue
Block a user