mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 13:27:34 +00:00
Merge branch 'main' into dependabot/github_actions/actions/setup-node-4
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
ZITADEL_API_URL=http://localhost:22222
|
||||
ZITADEL_SERVICE_USER_ID="yolo"
|
||||
ZITADEL_SERVICE_USER_TOKEN="yolo"
|
||||
EMAIL_VERIFICATION=true
|
||||
DEBUG=true
|
||||
NEXT_PUBLIC_BASE_PATH=""
|
31
apps/login/next-env-vars.d.ts
vendored
Normal file
31
apps/login/next-env-vars.d.ts
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
declare namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
// Allow any environment variable that matches the pattern
|
||||
[key: `${string}_AUDIENCE`]: string; // The system api url
|
||||
[key: `${string}_SYSTEM_USER_ID`]: string; // The service user id
|
||||
[key: `${string}_SYSTEM_USER_PRIVATE_KEY`]: string; // The service user private key
|
||||
|
||||
AUDIENCE: string; // The fallback system api url
|
||||
SYSTEM_USER_ID: string; // The fallback service user id
|
||||
SYSTEM_USER_PRIVATE_KEY: string; // The fallback service user private key
|
||||
|
||||
/**
|
||||
* Self hosting: The instance url
|
||||
*/
|
||||
ZITADEL_API_URL: string;
|
||||
|
||||
/**
|
||||
* Self hosting: The service user id
|
||||
*/
|
||||
ZITADEL_SERVICE_USER_ID: string;
|
||||
/**
|
||||
* Self hosting: The service user token
|
||||
*/
|
||||
ZITADEL_SERVICE_USER_TOKEN: string;
|
||||
|
||||
/**
|
||||
* Optional: wheter a user must have verified email
|
||||
*/
|
||||
EMAIL_VERIFICATION: string;
|
||||
}
|
||||
}
|
@@ -35,6 +35,7 @@ const secureHeaders = [
|
||||
];
|
||||
|
||||
const nextConfig = {
|
||||
basePath: process.env.NEXT_PUBLIC_BASE_PATH,
|
||||
reactStrictMode: true, // Recommended for the `pages` directory, default in `app`.
|
||||
experimental: {
|
||||
dynamicIO: true,
|
||||
|
@@ -44,14 +44,15 @@
|
||||
"clsx": "1.2.1",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"deepmerge": "^4.3.1",
|
||||
"jose": "^5.3.0",
|
||||
"moment": "^2.29.4",
|
||||
"next": "15.0.4-canary.23",
|
||||
"next": "15.2.0-canary.33",
|
||||
"next-intl": "^3.25.1",
|
||||
"next-themes": "^0.2.1",
|
||||
"nice-grpc": "2.0.1",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"react": "19.0.0-rc-66855b96-20241106",
|
||||
"react-dom": "19.0.0-rc-66855b96-20241106",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-hook-form": "7.39.5",
|
||||
"swr": "^2.2.0",
|
||||
"tinycolor2": "1.4.2"
|
||||
@@ -62,19 +63,20 @@
|
||||
"@testing-library/react": "^16.0.1",
|
||||
"@types/ms": "0.7.34",
|
||||
"@types/node": "22.9.0",
|
||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
||||
"@types/react": "19.0.2",
|
||||
"@types/react-dom": "19.0.2",
|
||||
"@types/tinycolor2": "1.4.3",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vercel/git-hooks": "1.0.0",
|
||||
"@zitadel/eslint-config": "workspace:*",
|
||||
"@zitadel/prettier-config": "workspace:*",
|
||||
"@zitadel/tailwind-config": "workspace:*",
|
||||
"@zitadel/tsconfig": "workspace:*",
|
||||
"autoprefixer": "10.4.20",
|
||||
"concurrently": "^9.1.0",
|
||||
"cypress": "^13.15.2",
|
||||
"del-cli": "6.0.0",
|
||||
"env-cmd": "^10.0.0",
|
||||
"@zitadel/eslint-config": "workspace:*",
|
||||
"grpc-tools": "1.12.4",
|
||||
"jsdom": "^25.0.1",
|
||||
"lint-staged": "15.2.10",
|
||||
@@ -86,7 +88,6 @@
|
||||
"start-server-and-test": "^2.0.8",
|
||||
"tailwindcss": "3.4.14",
|
||||
"ts-proto": "^2.2.7",
|
||||
"typescript": "^5.6.3",
|
||||
"@zitadel/tailwind-config": "workspace:*"
|
||||
"typescript": "^5.6.3"
|
||||
}
|
||||
}
|
||||
|
@@ -389,10 +389,6 @@ In future, self service options to jump to are shown below, like:
|
||||
|
||||
## Currently NOT Supported
|
||||
|
||||
Timebased features like the multifactor init prompt or password expiry, are not supported due to a current limitation in the API. Lockout settings which keeps track of the password retries, will also be implemented in a later stage.
|
||||
|
||||
- Lockout Settings
|
||||
- Password Expiry Settings
|
||||
- Login Settings: multifactor init prompt
|
||||
- forceMFA on login settings is not checked for IDPs
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { SessionsList } from "@/components/sessions-list";
|
||||
import { getAllSessionCookieIds } from "@/lib/cookies";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
getDefaultOrg,
|
||||
@@ -9,15 +10,24 @@ import {
|
||||
import { UserPlusIcon } from "@heroicons/react/24/outline";
|
||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
import Link from "next/link";
|
||||
|
||||
async function loadSessions() {
|
||||
const ids = await getAllSessionCookieIds();
|
||||
async function loadSessions({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
}: {
|
||||
serviceUrl: string;
|
||||
serviceRegion: string;
|
||||
}) {
|
||||
const ids: (string | undefined)[] = await getAllSessionCookieIds();
|
||||
|
||||
if (ids && ids.length) {
|
||||
const response = await listSessions(
|
||||
ids.filter((id: string | undefined) => !!id),
|
||||
);
|
||||
const response = await listSessions({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
ids: ids.filter((id) => !!id) as string[],
|
||||
});
|
||||
return response?.sessions ?? [];
|
||||
} else {
|
||||
console.info("No session cookie found.");
|
||||
@@ -35,19 +45,27 @@ export default async function Page(props: {
|
||||
const authRequestId = searchParams?.authRequestId;
|
||||
const organization = searchParams?.organization;
|
||||
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
let defaultOrganization;
|
||||
if (!organization) {
|
||||
const org: Organization | null = await getDefaultOrg();
|
||||
const org: Organization | null = await getDefaultOrg({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
});
|
||||
if (org) {
|
||||
defaultOrganization = org.id;
|
||||
}
|
||||
}
|
||||
|
||||
let sessions = await loadSessions();
|
||||
let sessions = await loadSessions({ serviceUrl, serviceRegion });
|
||||
|
||||
const branding = await getBrandingSettings(
|
||||
organization ?? defaultOrganization,
|
||||
);
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: organization ?? defaultOrganization,
|
||||
});
|
||||
|
||||
const params = new URLSearchParams();
|
||||
|
||||
|
@@ -5,6 +5,7 @@ import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { SignInWithIdp } from "@/components/sign-in-with-idp";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getSessionCookieById } from "@/lib/cookies";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import {
|
||||
getActiveIdentityProviders,
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
} from "@/lib/zitadel";
|
||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
@@ -27,19 +29,30 @@ export default async function Page(props: {
|
||||
|
||||
const { loginName, authRequestId, organization, sessionId } = searchParams;
|
||||
|
||||
const sessionWithData = sessionId
|
||||
? await loadSessionById(sessionId, organization)
|
||||
: await loadSessionByLoginname(loginName, organization);
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
async function getAuthMethodsAndUser(session?: Session) {
|
||||
const sessionWithData = sessionId
|
||||
? await loadSessionById(serviceUrl, sessionId, organization)
|
||||
: await loadSessionByLoginname(serviceUrl, loginName, organization);
|
||||
|
||||
async function getAuthMethodsAndUser(
|
||||
serviceUrl: string,
|
||||
serviceRegion: string,
|
||||
session?: Session,
|
||||
) {
|
||||
const userId = session?.factors?.user?.id;
|
||||
|
||||
if (!userId) {
|
||||
throw Error("Could not get user id from session");
|
||||
}
|
||||
|
||||
return listAuthenticationMethodTypes(userId).then((methods) => {
|
||||
return getUserByID(userId).then((user) => {
|
||||
return listAuthenticationMethodTypes({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId,
|
||||
}).then((methods) => {
|
||||
return getUserByID({ serviceUrl, serviceRegion, userId }).then((user) => {
|
||||
const humanUser =
|
||||
user.user?.type.case === "human" ? user.user?.type.value : undefined;
|
||||
|
||||
@@ -55,24 +68,39 @@ export default async function Page(props: {
|
||||
}
|
||||
|
||||
async function loadSessionByLoginname(
|
||||
host: string,
|
||||
loginName?: string,
|
||||
organization?: string,
|
||||
) {
|
||||
return loadMostRecentSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionParams: {
|
||||
loginName,
|
||||
organization,
|
||||
},
|
||||
}).then((session) => {
|
||||
return getAuthMethodsAndUser(session);
|
||||
return getAuthMethodsAndUser(serviceUrl, serviceRegion, session);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadSessionById(sessionId: string, organization?: string) {
|
||||
async function loadSessionById(
|
||||
host: string,
|
||||
sessionId: string,
|
||||
organization?: string,
|
||||
) {
|
||||
const recent = await getSessionCookieById({ sessionId, organization });
|
||||
return getSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: recent.id,
|
||||
sessionToken: recent.token,
|
||||
}).then((sessionResponse) => {
|
||||
return getAuthMethodsAndUser(sessionResponse.session);
|
||||
return getAuthMethodsAndUser(
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionResponse.session,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -80,18 +108,24 @@ export default async function Page(props: {
|
||||
return <Alert>{tError("unknownContext")}</Alert>;
|
||||
}
|
||||
|
||||
const branding = await getBrandingSettings(
|
||||
sessionWithData.factors?.user?.organizationId,
|
||||
);
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: sessionWithData.factors?.user?.organizationId,
|
||||
});
|
||||
|
||||
const loginSettings = await getLoginSettings(
|
||||
sessionWithData.factors?.user?.organizationId,
|
||||
);
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: sessionWithData.factors?.user?.organizationId,
|
||||
});
|
||||
|
||||
const identityProviders = await getActiveIdentityProviders(
|
||||
sessionWithData.factors?.user?.organizationId,
|
||||
true,
|
||||
).then((resp) => {
|
||||
const identityProviders = await getActiveIdentityProviders({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
orgId: sessionWithData.factors?.user?.organizationId,
|
||||
linking_allowed: true,
|
||||
}).then((resp) => {
|
||||
return resp.identityProviders;
|
||||
});
|
||||
|
||||
|
@@ -1,7 +1,9 @@
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import { getBrandingSettings } from "@/lib/zitadel";
|
||||
import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
// This configuration shows the given name in the respective IDP button as fallback
|
||||
const PROVIDER_NAME_MAPPING: {
|
||||
@@ -22,7 +24,14 @@ export default async function Page(props: {
|
||||
|
||||
const { organization } = searchParams;
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
|
@@ -5,6 +5,7 @@ import { linkingSuccess } from "@/components/idps/pages/linking-success";
|
||||
import { loginFailed } from "@/components/idps/pages/login-failed";
|
||||
import { loginSuccess } from "@/components/idps/pages/login-success";
|
||||
import { idpTypeToIdentityProviderType, PROVIDER_MAPPING } from "@/lib/idp";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import {
|
||||
addHuman,
|
||||
addIDPLink,
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
AddHumanUserRequestSchema,
|
||||
} from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
const ORG_SUFFIX_REGEX = /(?<=@)(.+)/;
|
||||
|
||||
@@ -37,13 +39,25 @@ export default async function Page(props: {
|
||||
const { id, token, authRequestId, organization, link } = searchParams;
|
||||
const { provider } = params;
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
if (!provider || !id || !token) {
|
||||
return loginFailed(branding, "IDP context missing");
|
||||
}
|
||||
|
||||
const intent = await retrieveIDPIntent(id, token);
|
||||
const intent = await retrieveIDPIntent({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
id,
|
||||
token,
|
||||
});
|
||||
|
||||
const { idpInformation, userId } = intent;
|
||||
|
||||
@@ -63,7 +77,11 @@ export default async function Page(props: {
|
||||
return loginFailed(branding, "IDP information missing");
|
||||
}
|
||||
|
||||
const idp = await getIDPByID(idpInformation.idpId);
|
||||
const idp = await getIDPByID({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
id: idpInformation.idpId,
|
||||
});
|
||||
const options = idp?.config?.options;
|
||||
|
||||
if (!idp) {
|
||||
@@ -80,14 +98,16 @@ export default async function Page(props: {
|
||||
|
||||
let idpLink;
|
||||
try {
|
||||
idpLink = await addIDPLink(
|
||||
{
|
||||
idpLink = await addIDPLink({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
idp: {
|
||||
id: idpInformation.idpId,
|
||||
userId: idpInformation.userId,
|
||||
userName: idpInformation.userName,
|
||||
},
|
||||
userId,
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return linkingFailed(branding);
|
||||
@@ -111,19 +131,23 @@ export default async function Page(props: {
|
||||
const email = PROVIDER_MAPPING[providerType](idpInformation).email?.email;
|
||||
|
||||
if (options.autoLinking === AutoLinkingOption.EMAIL && email) {
|
||||
foundUser = await listUsers({ email }).then((response) => {
|
||||
foundUser = await listUsers({ serviceUrl, serviceRegion, email }).then(
|
||||
(response) => {
|
||||
return response.result ? response.result[0] : null;
|
||||
});
|
||||
},
|
||||
);
|
||||
} else if (options.autoLinking === AutoLinkingOption.USERNAME) {
|
||||
foundUser = await listUsers(
|
||||
options.autoLinking === AutoLinkingOption.USERNAME
|
||||
? { userName: idpInformation.userName }
|
||||
: { email },
|
||||
? { serviceUrl, serviceRegion, userName: idpInformation.userName }
|
||||
: { serviceUrl, serviceRegion, email },
|
||||
).then((response) => {
|
||||
return response.result ? response.result[0] : null;
|
||||
});
|
||||
} else {
|
||||
foundUser = await listUsers({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userName: idpInformation.userName,
|
||||
email,
|
||||
}).then((response) => {
|
||||
@@ -134,14 +158,16 @@ export default async function Page(props: {
|
||||
if (foundUser) {
|
||||
let idpLink;
|
||||
try {
|
||||
idpLink = await addIDPLink(
|
||||
{
|
||||
idpLink = await addIDPLink({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
idp: {
|
||||
id: idpInformation.idpId,
|
||||
userId: idpInformation.userId,
|
||||
userName: idpInformation.userName,
|
||||
},
|
||||
foundUser.userId,
|
||||
);
|
||||
userId: foundUser.userId,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return linkingFailed(branding);
|
||||
@@ -175,11 +201,19 @@ export default async function Page(props: {
|
||||
const suffix = matched?.[1] ?? "";
|
||||
|
||||
// this just returns orgs where the suffix is set as primary domain
|
||||
const orgs = await getOrgsByDomain(suffix);
|
||||
const orgs = await getOrgsByDomain({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
domain: suffix,
|
||||
});
|
||||
const orgToCheckForDiscovery =
|
||||
orgs.result && orgs.result.length === 1 ? orgs.result[0].id : undefined;
|
||||
|
||||
const orgLoginSettings = await getLoginSettings(orgToCheckForDiscovery);
|
||||
const orgLoginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: orgToCheckForDiscovery,
|
||||
});
|
||||
if (orgLoginSettings?.allowDomainDiscovery) {
|
||||
orgToRegisterOn = orgToCheckForDiscovery;
|
||||
}
|
||||
@@ -196,7 +230,11 @@ export default async function Page(props: {
|
||||
});
|
||||
}
|
||||
|
||||
const newUser = await addHuman(userData);
|
||||
const newUser = await addHuman({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
request: userData,
|
||||
});
|
||||
|
||||
if (newUser) {
|
||||
return (
|
||||
|
@@ -1,16 +1,9 @@
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { SignInWithIdp } from "@/components/sign-in-with-idp";
|
||||
import { getBrandingSettings, settingsService } from "@/lib/zitadel";
|
||||
import { makeReqCtx } from "@zitadel/client/v2";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import { getActiveIdentityProviders, getBrandingSettings } from "@/lib/zitadel";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
|
||||
function getIdentityProviders(orgId?: string) {
|
||||
return settingsService
|
||||
.getActiveIdentityProviders({ ctx: makeReqCtx(orgId) }, {})
|
||||
.then((resp) => {
|
||||
return resp.identityProviders;
|
||||
});
|
||||
}
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
@@ -22,9 +15,22 @@ export default async function Page(props: {
|
||||
const authRequestId = searchParams?.authRequestId;
|
||||
const organization = searchParams?.organization;
|
||||
|
||||
const identityProviders = await getIdentityProviders(organization);
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
const identityProviders = await getActiveIdentityProviders({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
orgId: organization,
|
||||
}).then((resp) => {
|
||||
return resp.identityProviders;
|
||||
});
|
||||
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Alert, AlertType } from "@/components/alert";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { InviteForm } from "@/components/invite-form";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
getDefaultOrg,
|
||||
@@ -8,6 +9,7 @@ import {
|
||||
getPasswordComplexitySettings,
|
||||
} from "@/lib/zitadel";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
@@ -18,8 +20,11 @@ export default async function Page(props: {
|
||||
|
||||
let { firstname, lastname, email, organization } = searchParams;
|
||||
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
if (!organization) {
|
||||
const org = await getDefaultOrg();
|
||||
const org = await getDefaultOrg({ serviceUrl, serviceRegion });
|
||||
if (!org) {
|
||||
throw new Error("No default organization found");
|
||||
}
|
||||
@@ -27,12 +32,23 @@ export default async function Page(props: {
|
||||
organization = org.id;
|
||||
}
|
||||
|
||||
const loginSettings = await getLoginSettings(organization);
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
const passwordComplexitySettings =
|
||||
await getPasswordComplexitySettings(organization);
|
||||
const passwordComplexitySettings = await getPasswordComplexitySettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
|
@@ -2,9 +2,11 @@ import { Alert, AlertType } from "@/components/alert";
|
||||
import { Button, ButtonVariants } from "@/components/button";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import { getBrandingSettings, getDefaultOrg, getUserByID } from "@/lib/zitadel";
|
||||
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
import Link from "next/link";
|
||||
|
||||
export default async function Page(props: {
|
||||
@@ -16,8 +18,11 @@ export default async function Page(props: {
|
||||
|
||||
let { userId, organization } = searchParams;
|
||||
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
if (!organization) {
|
||||
const org = await getDefaultOrg();
|
||||
const org = await getDefaultOrg({ serviceUrl, serviceRegion });
|
||||
if (!org) {
|
||||
throw new Error("No default organization found");
|
||||
}
|
||||
@@ -25,12 +30,20 @@ export default async function Page(props: {
|
||||
organization = org.id;
|
||||
}
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
let user: User | undefined;
|
||||
let human: HumanUser | undefined;
|
||||
if (userId) {
|
||||
const userResponse = await getUserByID(userId);
|
||||
const userResponse = await getUserByID({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId,
|
||||
});
|
||||
if (userResponse) {
|
||||
user = userResponse.user;
|
||||
if (user?.type.case === "human") {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { SignInWithIdp } from "@/components/sign-in-with-idp";
|
||||
import { UsernameForm } from "@/components/username-form";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import {
|
||||
getActiveIdentityProviders,
|
||||
getBrandingSettings,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
} from "@/lib/zitadel";
|
||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
@@ -23,29 +25,45 @@ export default async function Page(props: {
|
||||
const suffix = searchParams?.suffix;
|
||||
const submit: boolean = searchParams?.submit === "true";
|
||||
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
let defaultOrganization;
|
||||
if (!organization) {
|
||||
const org: Organization | null = await getDefaultOrg();
|
||||
const org: Organization | null = await getDefaultOrg({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
});
|
||||
if (org) {
|
||||
defaultOrganization = org.id;
|
||||
}
|
||||
}
|
||||
|
||||
const loginSettings = await getLoginSettings(
|
||||
organization ?? defaultOrganization,
|
||||
);
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: organization ?? defaultOrganization,
|
||||
});
|
||||
|
||||
const contextLoginSettings = await getLoginSettings(organization);
|
||||
const contextLoginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
const identityProviders = await getActiveIdentityProviders(
|
||||
organization ?? defaultOrganization,
|
||||
).then((resp) => {
|
||||
const identityProviders = await getActiveIdentityProviders({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
orgId: organization ?? defaultOrganization,
|
||||
}).then((resp) => {
|
||||
return resp.identityProviders;
|
||||
});
|
||||
|
||||
const branding = await getBrandingSettings(
|
||||
organization ?? defaultOrganization,
|
||||
);
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: organization ?? defaultOrganization,
|
||||
});
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
|
@@ -4,6 +4,7 @@ import { ChooseSecondFactor } from "@/components/choose-second-factor";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getSessionCookieById } from "@/lib/cookies";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
listAuthenticationMethodTypes,
|
||||
} from "@/lib/zitadel";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
@@ -22,41 +24,59 @@ export default async function Page(props: {
|
||||
|
||||
const { loginName, authRequestId, organization, sessionId } = searchParams;
|
||||
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const sessionFactors = sessionId
|
||||
? await loadSessionById(sessionId, organization)
|
||||
: await loadSessionByLoginname(loginName, organization);
|
||||
? await loadSessionById(serviceUrl, sessionId, organization)
|
||||
: await loadSessionByLoginname(serviceUrl, loginName, organization);
|
||||
|
||||
async function loadSessionByLoginname(
|
||||
serviceUrl: string,
|
||||
loginName?: string,
|
||||
organization?: string,
|
||||
) {
|
||||
return loadMostRecentSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionParams: {
|
||||
loginName,
|
||||
organization,
|
||||
},
|
||||
}).then((session) => {
|
||||
if (session && session.factors?.user?.id) {
|
||||
return listAuthenticationMethodTypes(session.factors.user.id).then(
|
||||
(methods) => {
|
||||
return listAuthenticationMethodTypes({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: session.factors.user.id,
|
||||
}).then((methods) => {
|
||||
return {
|
||||
factors: session?.factors,
|
||||
authMethods: methods.authMethodTypes ?? [],
|
||||
};
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function loadSessionById(sessionId: string, organization?: string) {
|
||||
async function loadSessionById(
|
||||
host: string,
|
||||
sessionId: string,
|
||||
organization?: string,
|
||||
) {
|
||||
const recent = await getSessionCookieById({ sessionId, organization });
|
||||
return getSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: recent.id,
|
||||
sessionToken: recent.token,
|
||||
}).then((response) => {
|
||||
if (response?.session && response.session.factors?.user?.id) {
|
||||
return listAuthenticationMethodTypes(
|
||||
response.session.factors.user.id,
|
||||
).then((methods) => {
|
||||
return listAuthenticationMethodTypes({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: response.session.factors.user.id,
|
||||
}).then((methods) => {
|
||||
return {
|
||||
factors: response.session?.factors,
|
||||
authMethods: methods.authMethodTypes ?? [],
|
||||
@@ -66,7 +86,11 @@ export default async function Page(props: {
|
||||
});
|
||||
}
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
|
@@ -4,6 +4,7 @@ import { ChooseSecondFactorToSetup } from "@/components/choose-second-factor-to-
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getSessionCookieById } from "@/lib/cookies";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
import { Timestamp, timestampDate } from "@zitadel/client";
|
||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
function isSessionValid(session: Partial<Session>): {
|
||||
valid: boolean;
|
||||
@@ -49,19 +51,26 @@ export default async function Page(props: {
|
||||
sessionId,
|
||||
} = searchParams;
|
||||
|
||||
const sessionWithData = sessionId
|
||||
? await loadSessionById(sessionId, organization)
|
||||
: await loadSessionByLoginname(loginName, organization);
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
async function getAuthMethodsAndUser(session?: Session) {
|
||||
const sessionWithData = sessionId
|
||||
? await loadSessionById(serviceUrl, sessionId, organization)
|
||||
: await loadSessionByLoginname(serviceUrl, loginName, organization);
|
||||
|
||||
async function getAuthMethodsAndUser(host: string, session?: Session) {
|
||||
const userId = session?.factors?.user?.id;
|
||||
|
||||
if (!userId) {
|
||||
throw Error("Could not get user id from session");
|
||||
}
|
||||
|
||||
return listAuthenticationMethodTypes(userId).then((methods) => {
|
||||
return getUserByID(userId).then((user) => {
|
||||
return listAuthenticationMethodTypes({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId,
|
||||
}).then((methods) => {
|
||||
return getUserByID({ serviceUrl, serviceRegion, userId }).then((user) => {
|
||||
const humanUser =
|
||||
user.user?.type.case === "human" ? user.user?.type.value : undefined;
|
||||
|
||||
@@ -77,31 +86,48 @@ export default async function Page(props: {
|
||||
}
|
||||
|
||||
async function loadSessionByLoginname(
|
||||
host: string,
|
||||
loginName?: string,
|
||||
organization?: string,
|
||||
) {
|
||||
return loadMostRecentSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionParams: {
|
||||
loginName,
|
||||
organization,
|
||||
},
|
||||
}).then((session) => {
|
||||
return getAuthMethodsAndUser(session);
|
||||
return getAuthMethodsAndUser(serviceUrl, session);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadSessionById(sessionId: string, organization?: string) {
|
||||
async function loadSessionById(
|
||||
host: string,
|
||||
sessionId: string,
|
||||
organization?: string,
|
||||
) {
|
||||
const recent = await getSessionCookieById({ sessionId, organization });
|
||||
return getSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: recent.id,
|
||||
sessionToken: recent.token,
|
||||
}).then((sessionResponse) => {
|
||||
return getAuthMethodsAndUser(sessionResponse.session);
|
||||
return getAuthMethodsAndUser(serviceUrl, sessionResponse.session);
|
||||
});
|
||||
}
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
const loginSettings = await getLoginSettings(
|
||||
sessionWithData.factors?.user?.organizationId,
|
||||
);
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: sessionWithData.factors?.user?.organizationId,
|
||||
});
|
||||
|
||||
const { valid } = isSessionValid(sessionWithData);
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { LoginOTP } from "@/components/login-otp";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getSessionCookieById } from "@/lib/cookies";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
@@ -22,6 +23,14 @@ export default async function Page(props: {
|
||||
const t = await getTranslations({ locale, namespace: "otp" });
|
||||
const tError = await getTranslations({ locale, namespace: "error" });
|
||||
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
const host = _headers.get("host");
|
||||
|
||||
if (!host || typeof host !== "string") {
|
||||
throw new Error("No host found");
|
||||
}
|
||||
|
||||
const {
|
||||
loginName, // send from password page
|
||||
userId, // send from email link
|
||||
@@ -35,12 +44,22 @@ export default async function Page(props: {
|
||||
const { method } = params;
|
||||
|
||||
const session = sessionId
|
||||
? await loadSessionById(sessionId, organization)
|
||||
: await loadMostRecentSession({ loginName, organization });
|
||||
? await loadSessionById(serviceUrl, sessionId, organization)
|
||||
: await loadMostRecentSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionParams: { loginName, organization },
|
||||
});
|
||||
|
||||
async function loadSessionById(sessionId: string, organization?: string) {
|
||||
async function loadSessionById(
|
||||
host: string,
|
||||
sessionId: string,
|
||||
organization?: string,
|
||||
) {
|
||||
const recent = await getSessionCookieById({ sessionId, organization });
|
||||
return getSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: recent.id,
|
||||
sessionToken: recent.token,
|
||||
}).then((response) => {
|
||||
@@ -51,15 +70,17 @@ export default async function Page(props: {
|
||||
}
|
||||
|
||||
// email links do not come with organization, thus we need to use the session's organization
|
||||
const branding = await getBrandingSettings(
|
||||
organization ?? session?.factors?.user?.organizationId,
|
||||
);
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: organization ?? session?.factors?.user?.organizationId,
|
||||
});
|
||||
|
||||
const loginSettings = await getLoginSettings(
|
||||
organization ?? session?.factors?.user?.organizationId,
|
||||
);
|
||||
|
||||
const host = (await headers()).get("host");
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: organization ?? session?.factors?.user?.organizationId,
|
||||
});
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
|
@@ -4,6 +4,7 @@ import { Button, ButtonVariants } from "@/components/button";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { TotpRegister } from "@/components/totp-register";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import {
|
||||
addOTPEmail,
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
} from "@/lib/zitadel";
|
||||
import { RegisterTOTPResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
@@ -31,18 +33,37 @@ export default async function Page(props: {
|
||||
searchParams;
|
||||
const { method } = params;
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
const loginSettings = await getLoginSettings(organization);
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
const session = await loadMostRecentSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionParams: {
|
||||
loginName,
|
||||
organization,
|
||||
},
|
||||
});
|
||||
|
||||
let totpResponse: RegisterTOTPResponse | undefined, error: Error | undefined;
|
||||
if (session && session.factors?.user?.id) {
|
||||
if (method === "time-based") {
|
||||
await registerTOTP(session.factors.user.id)
|
||||
await registerTOTP({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: session.factors.user.id,
|
||||
})
|
||||
.then((resp) => {
|
||||
if (resp) {
|
||||
totpResponse = resp;
|
||||
@@ -53,12 +74,20 @@ export default async function Page(props: {
|
||||
});
|
||||
} else if (method === "sms") {
|
||||
// does not work
|
||||
await addOTPSMS(session.factors.user.id).catch((error) => {
|
||||
await addOTPSMS({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: session.factors.user.id,
|
||||
}).catch((error) => {
|
||||
error = new Error("Could not add OTP via SMS");
|
||||
});
|
||||
} else if (method === "email") {
|
||||
// works
|
||||
await addOTPEmail(session.factors.user.id).catch((error) => {
|
||||
await addOTPEmail({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: session.factors.user.id,
|
||||
}).catch((error) => {
|
||||
error = new Error("Could not add OTP via Email");
|
||||
});
|
||||
} else {
|
||||
|
@@ -3,13 +3,11 @@ import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { LoginPasskey } from "@/components/login-passkey";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getSessionCookieById } from "@/lib/cookies";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
getLoginSettings,
|
||||
getSession,
|
||||
} from "@/lib/zitadel";
|
||||
import { getBrandingSettings, getSession } from "@/lib/zitadel";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
@@ -22,13 +20,26 @@ export default async function Page(props: {
|
||||
const { loginName, altPassword, authRequestId, organization, sessionId } =
|
||||
searchParams;
|
||||
|
||||
const sessionFactors = sessionId
|
||||
? await loadSessionById(sessionId, organization)
|
||||
: await loadMostRecentSession({ loginName, organization });
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
async function loadSessionById(sessionId: string, organization?: string) {
|
||||
const sessionFactors = sessionId
|
||||
? await loadSessionById(serviceUrl, sessionId, organization)
|
||||
: await loadMostRecentSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionParams: { loginName, organization },
|
||||
});
|
||||
|
||||
async function loadSessionById(
|
||||
serviceUrl: string,
|
||||
sessionId: string,
|
||||
organization?: string,
|
||||
) {
|
||||
const recent = await getSessionCookieById({ sessionId, organization });
|
||||
return getSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: recent.id,
|
||||
sessionToken: recent.token,
|
||||
}).then((response) => {
|
||||
@@ -38,9 +49,11 @@ export default async function Page(props: {
|
||||
});
|
||||
}
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
|
||||
const loginSettings = await getLoginSettings(organization);
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
@@ -66,7 +79,6 @@ export default async function Page(props: {
|
||||
authRequestId={authRequestId}
|
||||
altPassword={altPassword === "true"}
|
||||
organization={organization}
|
||||
loginSettings={loginSettings}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@@ -2,9 +2,11 @@ import { Alert, AlertType } from "@/components/alert";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { RegisterPasskey } from "@/components/register-passkey";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import { getBrandingSettings } from "@/lib/zitadel";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
@@ -17,12 +19,23 @@ export default async function Page(props: {
|
||||
const { loginName, prompt, organization, authRequestId, userId } =
|
||||
searchParams;
|
||||
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const session = await loadMostRecentSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionParams: {
|
||||
loginName,
|
||||
organization,
|
||||
},
|
||||
});
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
|
@@ -2,6 +2,7 @@ import { Alert } from "@/components/alert";
|
||||
import { ChangePasswordForm } from "@/components/change-password-form";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
@@ -9,10 +10,14 @@ import {
|
||||
getPasswordComplexitySettings,
|
||||
} from "@/lib/zitadel";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
}) {
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const searchParams = await props.searchParams;
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "password" });
|
||||
@@ -22,19 +27,31 @@ export default async function Page(props: {
|
||||
|
||||
// also allow no session to be found (ignoreUnkownUsername)
|
||||
const sessionFactors = await loadMostRecentSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionParams: {
|
||||
loginName,
|
||||
organization,
|
||||
},
|
||||
});
|
||||
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
const passwordComplexity = await getPasswordComplexitySettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: sessionFactors?.factors?.user?.organizationId,
|
||||
});
|
||||
|
||||
const passwordComplexity = await getPasswordComplexitySettings(
|
||||
sessionFactors?.factors?.user?.organizationId,
|
||||
);
|
||||
|
||||
const loginSettings = await getLoginSettings(
|
||||
sessionFactors?.factors?.user?.organizationId,
|
||||
);
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: sessionFactors?.factors?.user?.organizationId,
|
||||
});
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
|
@@ -2,6 +2,7 @@ import { Alert } from "@/components/alert";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { PasswordForm } from "@/components/password-form";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
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";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
@@ -22,9 +24,15 @@ export default async function Page(props: {
|
||||
|
||||
let { loginName, organization, authRequestId, alt } = searchParams;
|
||||
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
let defaultOrganization;
|
||||
if (!organization) {
|
||||
const org: Organization | null = await getDefaultOrg();
|
||||
const org: Organization | null = await getDefaultOrg({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
});
|
||||
|
||||
if (org) {
|
||||
defaultOrganization = org.id;
|
||||
@@ -35,20 +43,28 @@ export default async function Page(props: {
|
||||
let sessionFactors;
|
||||
try {
|
||||
sessionFactors = await loadMostRecentSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionParams: {
|
||||
loginName,
|
||||
organization,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
// ignore error to continue to show the password form
|
||||
console.warn(error);
|
||||
}
|
||||
|
||||
const branding = await getBrandingSettings(
|
||||
organization ?? defaultOrganization,
|
||||
);
|
||||
const loginSettings = await getLoginSettings(
|
||||
organization ?? defaultOrganization,
|
||||
);
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: organization ?? defaultOrganization,
|
||||
});
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: organization ?? defaultOrganization,
|
||||
});
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
|
@@ -2,6 +2,7 @@ import { Alert, AlertType } from "@/components/alert";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { SetPasswordForm } from "@/components/set-password-form";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
@@ -24,27 +26,48 @@ export default async function Page(props: {
|
||||
const { userId, loginName, organization, authRequestId, code, initial } =
|
||||
searchParams;
|
||||
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
// also allow no session to be found (ignoreUnkownUsername)
|
||||
let session: Session | undefined;
|
||||
if (loginName) {
|
||||
session = await loadMostRecentSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionParams: {
|
||||
loginName,
|
||||
organization,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
const passwordComplexity = await getPasswordComplexitySettings(
|
||||
session?.factors?.user?.organizationId,
|
||||
);
|
||||
const passwordComplexity = await getPasswordComplexitySettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: session?.factors?.user?.organizationId,
|
||||
});
|
||||
|
||||
const loginSettings = await getLoginSettings(organization);
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
let user: User | undefined;
|
||||
let displayName: string | undefined;
|
||||
if (userId) {
|
||||
const userResponse = await getUserByID(userId);
|
||||
const userResponse = await getUserByID({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId,
|
||||
});
|
||||
user = userResponse.user;
|
||||
|
||||
if (user?.type.case === "human") {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { RegisterForm } from "@/components/register-form";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
getDefaultOrg,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
} from "@/lib/zitadel";
|
||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
@@ -20,20 +22,41 @@ export default async function Page(props: {
|
||||
let { firstname, lastname, email, organization, authRequestId } =
|
||||
searchParams;
|
||||
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
if (!organization) {
|
||||
const org: Organization | null = await getDefaultOrg();
|
||||
const org: Organization | null = await getDefaultOrg({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
});
|
||||
if (org) {
|
||||
organization = org.id;
|
||||
}
|
||||
}
|
||||
|
||||
const legal = await getLegalAndSupportSettings(organization);
|
||||
const passwordComplexitySettings =
|
||||
await getPasswordComplexitySettings(organization);
|
||||
const legal = await getLegalAndSupportSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
const passwordComplexitySettings = await getPasswordComplexitySettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
const loginSettings = await getLoginSettings(organization);
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
if (!loginSettings?.allowRegister) {
|
||||
return (
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { SetRegisterPasswordForm } from "@/components/set-register-password-form";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
getDefaultOrg,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
} from "@/lib/zitadel";
|
||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
@@ -20,8 +22,14 @@ export default async function Page(props: {
|
||||
let { firstname, lastname, email, organization, authRequestId } =
|
||||
searchParams;
|
||||
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
if (!organization) {
|
||||
const org: Organization | null = await getDefaultOrg();
|
||||
const org: Organization | null = await getDefaultOrg({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
});
|
||||
if (org) {
|
||||
organization = org.id;
|
||||
}
|
||||
@@ -29,13 +37,28 @@ export default async function Page(props: {
|
||||
|
||||
const missingData = !firstname || !lastname || !email;
|
||||
|
||||
const legal = await getLegalAndSupportSettings(organization);
|
||||
const passwordComplexitySettings =
|
||||
await getPasswordComplexitySettings(organization);
|
||||
const legal = await getLegalAndSupportSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
const passwordComplexitySettings = await getPasswordComplexitySettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
const loginSettings = await getLoginSettings(organization);
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
return missingData ? (
|
||||
<DynamicTheme branding={branding}>
|
||||
|
@@ -3,6 +3,7 @@ import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { SelfServiceMenu } from "@/components/self-service-menu";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getMostRecentCookieWithLoginname } from "@/lib/cookies";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import {
|
||||
createCallback,
|
||||
getBrandingSettings,
|
||||
@@ -15,15 +16,23 @@ import {
|
||||
SessionSchema,
|
||||
} from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
async function loadSession(loginName: string, authRequestId?: string) {
|
||||
async function loadSession(
|
||||
serviceUrl: string,
|
||||
serviceRegion: string,
|
||||
loginName: string,
|
||||
authRequestId?: string,
|
||||
) {
|
||||
const recent = await getMostRecentCookieWithLoginname({ loginName });
|
||||
|
||||
if (authRequestId) {
|
||||
return createCallback(
|
||||
create(CreateCallbackRequestSchema, {
|
||||
return createCallback({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
req: create(CreateCallbackRequestSchema, {
|
||||
authRequestId,
|
||||
callbackKind: {
|
||||
case: "session",
|
||||
@@ -33,17 +42,20 @@ async function loadSession(loginName: string, authRequestId?: string) {
|
||||
}),
|
||||
},
|
||||
}),
|
||||
).then(({ callbackUrl }) => {
|
||||
}).then(({ callbackUrl }) => {
|
||||
return redirect(callbackUrl);
|
||||
});
|
||||
}
|
||||
return getSession({ sessionId: recent.id, sessionToken: recent.token }).then(
|
||||
(response) => {
|
||||
return getSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: recent.id,
|
||||
sessionToken: recent.token,
|
||||
}).then((response) => {
|
||||
if (response?.session) {
|
||||
return response.session;
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
@@ -51,14 +63,30 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
const locale = getLocale();
|
||||
const t = await getTranslations({ locale, namespace: "signedin" });
|
||||
|
||||
const { loginName, authRequestId, organization } = searchParams;
|
||||
const sessionFactors = await loadSession(loginName, authRequestId);
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
const { loginName, authRequestId, organization } = searchParams;
|
||||
const sessionFactors = await loadSession(
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
loginName,
|
||||
authRequestId,
|
||||
);
|
||||
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
let loginSettings;
|
||||
if (!authRequestId) {
|
||||
loginSettings = await getLoginSettings(organization);
|
||||
loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
@@ -3,9 +3,11 @@ import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { LoginPasskey } from "@/components/login-passkey";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getSessionCookieById } from "@/lib/cookies";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import { getBrandingSettings, getSession } from "@/lib/zitadel";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
@@ -17,15 +19,37 @@ export default async function Page(props: {
|
||||
|
||||
const { loginName, authRequestId, sessionId, organization } = searchParams;
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
const host = _headers.get("host");
|
||||
|
||||
if (!host || typeof host !== "string") {
|
||||
throw new Error("No host found");
|
||||
}
|
||||
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
const sessionFactors = sessionId
|
||||
? await loadSessionById(sessionId, organization)
|
||||
: await loadMostRecentSession({ loginName, organization });
|
||||
? await loadSessionById(serviceUrl, sessionId, organization)
|
||||
: await loadMostRecentSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionParams: { loginName, organization },
|
||||
});
|
||||
|
||||
async function loadSessionById(sessionId: string, organization?: string) {
|
||||
async function loadSessionById(
|
||||
host: string,
|
||||
sessionId: string,
|
||||
organization?: string,
|
||||
) {
|
||||
const recent = await getSessionCookieById({ sessionId, organization });
|
||||
return getSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: recent.id,
|
||||
sessionToken: recent.token,
|
||||
}).then((response) => {
|
||||
|
@@ -2,9 +2,11 @@ import { Alert } from "@/components/alert";
|
||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { RegisterU2f } from "@/components/register-u2f";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import { getBrandingSettings } from "@/lib/zitadel";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: {
|
||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||
@@ -16,12 +18,23 @@ export default async function Page(props: {
|
||||
|
||||
const { loginName, organization, authRequestId, checkAfter } = searchParams;
|
||||
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const sessionFactors = await loadMostRecentSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionParams: {
|
||||
loginName,
|
||||
organization,
|
||||
},
|
||||
});
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
|
@@ -4,6 +4,7 @@ import { UserAvatar } from "@/components/user-avatar";
|
||||
import { VerifyForm } from "@/components/verify-form";
|
||||
import { VerifyRedirectButton } from "@/components/verify-redirect-button";
|
||||
import { sendEmailCode } from "@/lib/server/verify";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
const searchParams = await props.searchParams;
|
||||
@@ -23,7 +25,19 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
const { userId, loginName, code, organization, authRequestId, invite } =
|
||||
searchParams;
|
||||
|
||||
const branding = await getBrandingSettings(organization);
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
const host = _headers.get("host");
|
||||
|
||||
if (!host || typeof host !== "string") {
|
||||
throw new Error("No host found");
|
||||
}
|
||||
|
||||
const branding = await getBrandingSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
let sessionFactors;
|
||||
let user: User | undefined;
|
||||
@@ -34,14 +48,22 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
|
||||
if ("loginName" in searchParams) {
|
||||
sessionFactors = await loadMostRecentSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionParams: {
|
||||
loginName,
|
||||
organization,
|
||||
},
|
||||
});
|
||||
|
||||
if (doSend && sessionFactors?.factors?.user?.id) {
|
||||
await sendEmailCode({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: sessionFactors?.factors?.user?.id,
|
||||
authRequestId,
|
||||
urlTemplate:
|
||||
`${host.includes("localhost") ? "http://" : "https://"}${host}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` +
|
||||
(authRequestId ? `&authRequestId=${authRequestId}` : ""),
|
||||
}).catch((error) => {
|
||||
console.error("Could not resend verification email", error);
|
||||
throw Error("Failed to send verification email");
|
||||
@@ -50,15 +72,23 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
} else if ("userId" in searchParams && userId) {
|
||||
if (doSend) {
|
||||
await sendEmailCode({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId,
|
||||
authRequestId,
|
||||
urlTemplate:
|
||||
`${host.includes("localhost") ? "http://" : "https://"}${host}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` +
|
||||
(authRequestId ? `&authRequestId=${authRequestId}` : ""),
|
||||
}).catch((error) => {
|
||||
console.error("Could not resend verification email", error);
|
||||
throw Error("Failed to send verification email");
|
||||
});
|
||||
}
|
||||
|
||||
const userResponse = await getUserByID(userId);
|
||||
const userResponse = await getUserByID({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId,
|
||||
});
|
||||
if (userResponse) {
|
||||
user = userResponse.user;
|
||||
if (user?.type.case === "human") {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { getAllSessions } from "@/lib/cookies";
|
||||
import { idpTypeToSlug } from "@/lib/idp";
|
||||
import { sendLoginname, SendLoginnameCommand } from "@/lib/server/loginname";
|
||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
||||
import {
|
||||
createCallback,
|
||||
getActiveIdentityProviders,
|
||||
@@ -22,16 +23,27 @@ import {
|
||||
} from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
|
||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { headers } from "next/headers";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const revalidate = false;
|
||||
export const fetchCache = "default-no-store";
|
||||
|
||||
async function loadSessions(ids: string[]): Promise<Session[]> {
|
||||
const response = await listSessions(
|
||||
ids.filter((id: string | undefined) => !!id),
|
||||
);
|
||||
async function loadSessions({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
ids,
|
||||
}: {
|
||||
serviceUrl: string;
|
||||
serviceRegion: string;
|
||||
ids: string[];
|
||||
}): Promise<Session[]> {
|
||||
const response = await listSessions({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
ids: ids.filter((id: string | undefined) => !!id),
|
||||
});
|
||||
|
||||
return response?.sessions ?? [];
|
||||
}
|
||||
@@ -44,7 +56,11 @@ const IDP_SCOPE_REGEX = /urn:zitadel:iam:org:idp:id:(.+)/;
|
||||
* mfa is required, session is not valid anymore (e.g. session expired, user logged out, etc.)
|
||||
* to check for mfa for automatically selected session -> const response = await listAuthenticationMethodTypes(userId);
|
||||
**/
|
||||
async function isSessionValid(session: Session): Promise<boolean> {
|
||||
async function isSessionValid(
|
||||
serviceUrl: string,
|
||||
serviceRegion: string,
|
||||
session: Session,
|
||||
): Promise<boolean> {
|
||||
// session can't be checked without user
|
||||
if (!session.factors?.user) {
|
||||
console.warn("Session has no user");
|
||||
@@ -53,9 +69,11 @@ async function isSessionValid(session: Session): Promise<boolean> {
|
||||
|
||||
let mfaValid = true;
|
||||
|
||||
const authMethodTypes = await listAuthenticationMethodTypes(
|
||||
session.factors.user.id,
|
||||
);
|
||||
const authMethodTypes = await listAuthenticationMethodTypes({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: session.factors.user.id,
|
||||
});
|
||||
|
||||
const authMethods = authMethodTypes.authMethodTypes;
|
||||
if (authMethods && authMethods.includes(AuthenticationMethodType.TOTP)) {
|
||||
@@ -101,9 +119,11 @@ async function isSessionValid(session: Session): Promise<boolean> {
|
||||
}
|
||||
} else {
|
||||
// only check settings if no auth methods are available, as this would require a setup
|
||||
const loginSettings = await getLoginSettings(
|
||||
session.factors?.user?.organizationId,
|
||||
);
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: session.factors?.user?.organizationId,
|
||||
});
|
||||
if (loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly) {
|
||||
const otpEmail = session.factors.otpEmail?.verifiedAt;
|
||||
const otpSms = session.factors.otpSms?.verifiedAt;
|
||||
@@ -144,6 +164,8 @@ async function isSessionValid(session: Session): Promise<boolean> {
|
||||
}
|
||||
|
||||
async function findValidSession(
|
||||
serviceUrl: string,
|
||||
serviceRegion: string,
|
||||
sessions: Session[],
|
||||
authRequest: AuthRequest,
|
||||
): Promise<Session | undefined> {
|
||||
@@ -170,7 +192,7 @@ async function findValidSession(
|
||||
|
||||
// return the first valid session according to settings
|
||||
for (const session of sessionsWithHint) {
|
||||
if (await isSessionValid(session)) {
|
||||
if (await isSessionValid(serviceUrl, serviceRegion, session)) {
|
||||
return session;
|
||||
}
|
||||
}
|
||||
@@ -183,6 +205,9 @@ export async function GET(request: NextRequest) {
|
||||
const authRequestId = searchParams.get("authRequest");
|
||||
const sessionId = searchParams.get("sessionId");
|
||||
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
// TODO: find a better way to handle _rsc (react server components) requests and block them to avoid conflicts when creating oidc callback
|
||||
const _rsc = searchParams.get("_rsc");
|
||||
if (_rsc) {
|
||||
@@ -193,7 +218,7 @@ export async function GET(request: NextRequest) {
|
||||
const ids = sessionCookies.map((s) => s.id);
|
||||
let sessions: Session[] = [];
|
||||
if (ids && ids.length) {
|
||||
sessions = await loadSessions(ids);
|
||||
sessions = await loadSessions({ serviceUrl, serviceRegion, ids });
|
||||
}
|
||||
|
||||
if (authRequestId && sessionId) {
|
||||
@@ -206,7 +231,11 @@ export async function GET(request: NextRequest) {
|
||||
if (selectedSession && selectedSession.id) {
|
||||
console.log(`Found session ${selectedSession.id}`);
|
||||
|
||||
const isValid = await isSessionValid(selectedSession);
|
||||
const isValid = await isSessionValid(
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
selectedSession,
|
||||
);
|
||||
|
||||
console.log("Session is valid:", isValid);
|
||||
|
||||
@@ -239,15 +268,17 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
// works not with _rsc request
|
||||
try {
|
||||
const { callbackUrl } = await createCallback(
|
||||
create(CreateCallbackRequestSchema, {
|
||||
const { callbackUrl } = await createCallback({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
req: create(CreateCallbackRequestSchema, {
|
||||
authRequestId,
|
||||
callbackKind: {
|
||||
case: "session",
|
||||
value: create(SessionSchema, session),
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
if (callbackUrl) {
|
||||
return NextResponse.redirect(callbackUrl);
|
||||
} else {
|
||||
@@ -265,9 +296,11 @@ export async function GET(request: NextRequest) {
|
||||
"code" in error &&
|
||||
error?.code === 9
|
||||
) {
|
||||
const loginSettings = await getLoginSettings(
|
||||
selectedSession.factors?.user?.organizationId,
|
||||
);
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: selectedSession.factors?.user?.organizationId,
|
||||
});
|
||||
|
||||
if (loginSettings?.defaultRedirectUri) {
|
||||
return NextResponse.redirect(loginSettings.defaultRedirectUri);
|
||||
@@ -297,7 +330,11 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
|
||||
if (authRequestId) {
|
||||
const { authRequest } = await getAuthRequest({ authRequestId });
|
||||
const { authRequest } = await getAuthRequest({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
authRequestId,
|
||||
});
|
||||
|
||||
let organization = "";
|
||||
let suffix = "";
|
||||
@@ -324,7 +361,11 @@ export async function GET(request: NextRequest) {
|
||||
const matched = ORG_DOMAIN_SCOPE_REGEX.exec(orgDomainScope);
|
||||
const orgDomain = matched?.[1] ?? "";
|
||||
if (orgDomain) {
|
||||
const orgs = await getOrgsByDomain(orgDomain);
|
||||
const orgs = await getOrgsByDomain({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
domain: orgDomain,
|
||||
});
|
||||
if (orgs.result && orgs.result.length === 1) {
|
||||
organization = orgs.result[0].id ?? "";
|
||||
suffix = orgDomain;
|
||||
@@ -337,9 +378,11 @@ export async function GET(request: NextRequest) {
|
||||
const matched = IDP_SCOPE_REGEX.exec(idpScope);
|
||||
idpId = matched?.[1] ?? "";
|
||||
|
||||
const identityProviders = await getActiveIdentityProviders(
|
||||
organization ? organization : undefined,
|
||||
).then((resp) => {
|
||||
const identityProviders = await getActiveIdentityProviders({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
orgId: organization ? organization : undefined,
|
||||
}).then((resp) => {
|
||||
return resp.identityProviders;
|
||||
});
|
||||
|
||||
@@ -362,6 +405,8 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
|
||||
return startIdentityProviderFlow({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
idpId,
|
||||
urls: {
|
||||
successUrl:
|
||||
@@ -460,7 +505,12 @@ export async function GET(request: NextRequest) {
|
||||
* This means that the user should not be prompted to enter their password again.
|
||||
* Instead, the server attempts to silently authenticate the user using an existing session or other authentication mechanisms that do not require user interaction
|
||||
**/
|
||||
const selectedSession = await findValidSession(sessions, authRequest);
|
||||
const selectedSession = await findValidSession(
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessions,
|
||||
authRequest,
|
||||
);
|
||||
|
||||
if (!selectedSession || !selectedSession.id) {
|
||||
return NextResponse.json(
|
||||
@@ -485,19 +535,26 @@ export async function GET(request: NextRequest) {
|
||||
sessionToken: cookie.token,
|
||||
};
|
||||
|
||||
const { callbackUrl } = await createCallback(
|
||||
create(CreateCallbackRequestSchema, {
|
||||
const { callbackUrl } = await createCallback({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
req: create(CreateCallbackRequestSchema, {
|
||||
authRequestId,
|
||||
callbackKind: {
|
||||
case: "session",
|
||||
value: create(SessionSchema, session),
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
return NextResponse.redirect(callbackUrl);
|
||||
} else {
|
||||
// check for loginHint, userId hint and valid sessions
|
||||
let selectedSession = await findValidSession(sessions, authRequest);
|
||||
let selectedSession = await findValidSession(
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessions,
|
||||
authRequest,
|
||||
);
|
||||
|
||||
if (!selectedSession || !selectedSession.id) {
|
||||
return gotoAccounts();
|
||||
@@ -517,15 +574,17 @@ export async function GET(request: NextRequest) {
|
||||
};
|
||||
|
||||
try {
|
||||
const { callbackUrl } = await createCallback(
|
||||
create(CreateCallbackRequestSchema, {
|
||||
const { callbackUrl } = await createCallback({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
req: create(CreateCallbackRequestSchema, {
|
||||
authRequestId,
|
||||
callbackKind: {
|
||||
case: "session",
|
||||
value: create(SessionSchema, session),
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
if (callbackUrl) {
|
||||
return NextResponse.redirect(callbackUrl);
|
||||
} else {
|
||||
|
@@ -18,6 +18,7 @@ import { Spinner } from "./spinner";
|
||||
|
||||
// either loginName or sessionId must be provided
|
||||
type Props = {
|
||||
host: string | null;
|
||||
loginName?: string;
|
||||
sessionId?: string;
|
||||
authRequestId?: string;
|
||||
@@ -25,7 +26,6 @@ type Props = {
|
||||
method: string;
|
||||
code?: string;
|
||||
loginSettings?: LoginSettings;
|
||||
host: string | null;
|
||||
};
|
||||
|
||||
type Inputs = {
|
||||
@@ -33,6 +33,7 @@ type Inputs = {
|
||||
};
|
||||
|
||||
export function LoginOTP({
|
||||
host,
|
||||
loginName,
|
||||
sessionId,
|
||||
authRequestId,
|
||||
@@ -40,7 +41,6 @@ export function LoginOTP({
|
||||
method,
|
||||
code,
|
||||
loginSettings,
|
||||
host,
|
||||
}: Props) {
|
||||
const t = useTranslations("otp");
|
||||
|
||||
|
@@ -9,7 +9,6 @@ import {
|
||||
UserVerificationRequirement,
|
||||
} from "@zitadel/proto/zitadel/session/v2/challenge_pb";
|
||||
import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
@@ -26,7 +25,6 @@ type Props = {
|
||||
altPassword: boolean;
|
||||
login?: boolean;
|
||||
organization?: string;
|
||||
loginSettings?: LoginSettings;
|
||||
};
|
||||
|
||||
export function LoginPasskey({
|
||||
@@ -36,7 +34,6 @@ export function LoginPasskey({
|
||||
altPassword,
|
||||
organization,
|
||||
login = true,
|
||||
loginSettings,
|
||||
}: Props) {
|
||||
const t = useTranslations("passkey");
|
||||
|
||||
@@ -47,7 +44,6 @@ export function LoginPasskey({
|
||||
|
||||
const initialized = useRef(false);
|
||||
|
||||
// TODO: move this to server side
|
||||
useEffect(() => {
|
||||
if (!initialized.current) {
|
||||
initialized.current = true;
|
||||
|
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { getNextUrl } from "@/lib/client";
|
||||
import { verifyTOTP } from "@/lib/server-actions";
|
||||
import { verifyTOTP } from "@/lib/server/verify";
|
||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
|
40
apps/login/src/lib/api.ts
Normal file
40
apps/login/src/lib/api.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { newSystemToken } from "@zitadel/client/node";
|
||||
|
||||
export async function systemAPIToken({
|
||||
serviceRegion,
|
||||
}: {
|
||||
serviceRegion: string;
|
||||
}) {
|
||||
const prefix = serviceRegion.toUpperCase();
|
||||
const token = {
|
||||
audience: process.env[prefix + "_AUDIENCE"],
|
||||
userID: process.env[prefix + "_SYSTEM_USER_ID"],
|
||||
token: Buffer.from(
|
||||
process.env[prefix.toUpperCase() + "_SYSTEM_USER_PRIVATE_KEY"] as string,
|
||||
"base64",
|
||||
).toString("utf-8"),
|
||||
};
|
||||
|
||||
if (!token.audience || !token.userID || !token.token) {
|
||||
const fallbackToken = {
|
||||
audience: process.env.AUDIENCE,
|
||||
userID: process.env.SYSTEM_USER_ID,
|
||||
token: Buffer.from(
|
||||
process.env.SYSTEM_USER_PRIVATE_KEY,
|
||||
"base64",
|
||||
).toString("utf-8"),
|
||||
};
|
||||
|
||||
return newSystemToken({
|
||||
audience: fallbackToken.audience,
|
||||
subject: fallbackToken.userID,
|
||||
key: fallbackToken.token,
|
||||
});
|
||||
}
|
||||
|
||||
return newSystemToken({
|
||||
audience: token.audience,
|
||||
subject: token.userID,
|
||||
key: token.token,
|
||||
});
|
||||
}
|
@@ -2,16 +2,20 @@
|
||||
|
||||
import { createServerTransport } from "@zitadel/client/node";
|
||||
import { createUserServiceClient } from "@zitadel/client/v2";
|
||||
import { headers } from "next/headers";
|
||||
import { getSessionCookieById } from "./cookies";
|
||||
import { getServiceUrlFromHeaders } from "./service";
|
||||
import { getSession } from "./zitadel";
|
||||
|
||||
const transport = (token: string) =>
|
||||
createServerTransport(token, {
|
||||
baseUrl: process.env.ZITADEL_API_URL!,
|
||||
const transport = async (serviceUrl: string, token: string) => {
|
||||
return createServerTransport(token, {
|
||||
baseUrl: serviceUrl,
|
||||
});
|
||||
};
|
||||
|
||||
const myUserService = (sessionToken: string) => {
|
||||
return createUserServiceClient(transport(sessionToken));
|
||||
const myUserService = async (serviceUrl: string, sessionToken: string) => {
|
||||
const transportPromise = await transport(serviceUrl, sessionToken);
|
||||
return createUserServiceClient(transportPromise);
|
||||
};
|
||||
|
||||
export async function setMyPassword({
|
||||
@@ -21,9 +25,14 @@ export async function setMyPassword({
|
||||
sessionId: string;
|
||||
password: string;
|
||||
}) {
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const sessionCookie = await getSessionCookieById({ sessionId });
|
||||
|
||||
const { session } = await getSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: sessionCookie.id,
|
||||
sessionToken: sessionCookie.token,
|
||||
});
|
||||
@@ -32,7 +41,7 @@ export async function setMyPassword({
|
||||
return { error: "Could not load session" };
|
||||
}
|
||||
|
||||
const service = await myUserService(`${sessionCookie.token}`);
|
||||
const service = await myUserService(serviceUrl, `${sessionCookie.token}`);
|
||||
|
||||
if (!session?.factors?.user?.id) {
|
||||
return { error: "No user id found in session" };
|
||||
|
@@ -1,21 +0,0 @@
|
||||
"use server";
|
||||
|
||||
import { loadMostRecentSession } from "./session";
|
||||
import { verifyTOTPRegistration } from "./zitadel";
|
||||
|
||||
export async function verifyTOTP(
|
||||
code: string,
|
||||
loginName?: string,
|
||||
organization?: string,
|
||||
) {
|
||||
return loadMostRecentSession({
|
||||
loginName,
|
||||
organization,
|
||||
}).then((session) => {
|
||||
if (session?.factors?.user?.id) {
|
||||
return verifyTOTPRegistration(code, session.factors.user.id);
|
||||
} else {
|
||||
throw Error("No user id found in session.");
|
||||
}
|
||||
});
|
||||
}
|
@@ -7,13 +7,20 @@ import {
|
||||
getSession,
|
||||
setSession,
|
||||
} from "@/lib/zitadel";
|
||||
import { Duration, timestampMs } from "@zitadel/client";
|
||||
import { ConnectError, Duration, timestampMs } from "@zitadel/client";
|
||||
import {
|
||||
CredentialsCheckError,
|
||||
CredentialsCheckErrorSchema,
|
||||
ErrorDetail,
|
||||
} from "@zitadel/proto/zitadel/message_pb";
|
||||
import {
|
||||
Challenges,
|
||||
RequestChallenges,
|
||||
} from "@zitadel/proto/zitadel/session/v2/challenge_pb";
|
||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { headers } from "next/headers";
|
||||
import { getServiceUrlFromHeaders } from "../service";
|
||||
|
||||
type CustomCookieData = {
|
||||
id: string;
|
||||
@@ -26,16 +33,40 @@ type CustomCookieData = {
|
||||
authRequestId?: string; // if its linked to an OIDC flow
|
||||
};
|
||||
|
||||
const passwordAttemptsHandler = (error: ConnectError) => {
|
||||
const details = error.findDetails(CredentialsCheckErrorSchema);
|
||||
|
||||
if (details[0] && "failedAttempts" in details[0]) {
|
||||
const failedAttempts = details[0].failedAttempts;
|
||||
throw {
|
||||
error: `Failed to authenticate: You had ${failedAttempts} password attempts.`,
|
||||
failedAttempts: failedAttempts,
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
};
|
||||
|
||||
export async function createSessionAndUpdateCookie(
|
||||
checks: Checks,
|
||||
challenges: RequestChallenges | undefined,
|
||||
authRequestId: string | undefined,
|
||||
lifetime?: Duration,
|
||||
): Promise<Session> {
|
||||
const createdSession = await createSessionFromChecks(checks, challenges);
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const createdSession = await createSessionFromChecks({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
checks,
|
||||
challenges,
|
||||
lifetime,
|
||||
});
|
||||
|
||||
if (createdSession) {
|
||||
return getSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: createdSession.sessionId,
|
||||
sessionToken: createdSession.sessionToken,
|
||||
}).then((response) => {
|
||||
@@ -85,17 +116,33 @@ export async function createSessionForIdpAndUpdateCookie(
|
||||
authRequestId: string | undefined,
|
||||
lifetime?: Duration,
|
||||
): Promise<Session> {
|
||||
const createdSession = await createSessionForUserIdAndIdpIntent(
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const createdSession = await createSessionForUserIdAndIdpIntent({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId,
|
||||
idpIntent,
|
||||
lifetime,
|
||||
);
|
||||
}).catch((error: ErrorDetail | CredentialsCheckError) => {
|
||||
console.error("Could not set session", error);
|
||||
if ("failedAttempts" in error && error.failedAttempts) {
|
||||
throw {
|
||||
error: `Failed to authenticate: You had ${error.failedAttempts} password attempts.`,
|
||||
failedAttempts: error.failedAttempts,
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
|
||||
if (!createdSession) {
|
||||
throw "Could not create session";
|
||||
}
|
||||
|
||||
const { session } = await getSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: createdSession.sessionId,
|
||||
sessionToken: createdSession.sessionToken,
|
||||
});
|
||||
@@ -142,13 +189,19 @@ export async function setSessionAndUpdateCookie(
|
||||
authRequestId?: string,
|
||||
lifetime?: Duration,
|
||||
) {
|
||||
return setSession(
|
||||
recentCookie.id,
|
||||
recentCookie.token,
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
return setSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: recentCookie.id,
|
||||
sessionToken: recentCookie.token,
|
||||
challenges,
|
||||
checks,
|
||||
lifetime,
|
||||
).then((updatedSession) => {
|
||||
})
|
||||
.then((updatedSession) => {
|
||||
if (updatedSession) {
|
||||
const sessionCookie: CustomCookieData = {
|
||||
id: recentCookie.id,
|
||||
@@ -168,6 +221,8 @@ export async function setSessionAndUpdateCookie(
|
||||
}
|
||||
|
||||
return getSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: sessionCookie.id,
|
||||
sessionToken: sessionCookie.token,
|
||||
}).then((response) => {
|
||||
@@ -200,5 +255,6 @@ export async function setSessionAndUpdateCookie(
|
||||
} else {
|
||||
throw "Session not be set";
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(passwordAttemptsHandler);
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import {
|
||||
} from "@/lib/zitadel";
|
||||
import { headers } from "next/headers";
|
||||
import { getNextUrl } from "../client";
|
||||
import { getServiceUrlFromHeaders } from "../service";
|
||||
import { checkEmailVerification } from "../verify-helper";
|
||||
import { createSessionForIdpAndUpdateCookie } from "./cookie";
|
||||
|
||||
@@ -17,13 +18,17 @@ export type StartIDPFlowCommand = {
|
||||
};
|
||||
|
||||
export async function startIDPFlow(command: StartIDPFlowCommand) {
|
||||
const host = (await headers()).get("host");
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
const host = _headers.get("host");
|
||||
|
||||
if (!host) {
|
||||
return { error: "Could not get host" };
|
||||
}
|
||||
|
||||
return startIdentityProviderFlow({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
idpId: command.idpId,
|
||||
urls: {
|
||||
successUrl: `${host.includes("localhost") ? "http://" : "https://"}${host}${command.successUrl}`,
|
||||
@@ -55,19 +60,33 @@ type CreateNewSessionCommand = {
|
||||
export async function createNewSessionFromIdpIntent(
|
||||
command: CreateNewSessionCommand,
|
||||
) {
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
const host = _headers.get("host");
|
||||
|
||||
if (!host) {
|
||||
return { error: "Could not get domain" };
|
||||
}
|
||||
|
||||
if (!command.userId || !command.idpIntent) {
|
||||
throw new Error("No userId or loginName provided");
|
||||
}
|
||||
|
||||
const userResponse = await getUserByID(command.userId);
|
||||
const userResponse = await getUserByID({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: command.userId,
|
||||
});
|
||||
|
||||
if (!userResponse || !userResponse.user) {
|
||||
return { error: "User not found in the system" };
|
||||
}
|
||||
|
||||
const loginSettings = await getLoginSettings(
|
||||
userResponse.user.details?.resourceOwner,
|
||||
);
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: userResponse.user.details?.resourceOwner,
|
||||
});
|
||||
|
||||
const session = await createSessionForIdpAndUpdateCookie(
|
||||
command.userId,
|
||||
|
@@ -3,6 +3,7 @@
|
||||
import { addHumanUser, createInviteCode } from "@/lib/zitadel";
|
||||
import { Factors } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import { headers } from "next/headers";
|
||||
import { getServiceUrlFromHeaders } from "../service";
|
||||
|
||||
type InviteUserCommand = {
|
||||
email: string;
|
||||
@@ -20,9 +21,17 @@ export type RegisterUserResponse = {
|
||||
};
|
||||
|
||||
export async function inviteUser(command: InviteUserCommand) {
|
||||
const host = (await headers()).get("host");
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
const host = _headers.get("host");
|
||||
|
||||
if (!host) {
|
||||
return { error: "Could not get domain" };
|
||||
}
|
||||
|
||||
const human = await addHumanUser({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
email: command.email,
|
||||
firstName: command.firstName,
|
||||
lastName: command.lastName,
|
||||
@@ -34,7 +43,12 @@ export async function inviteUser(command: InviteUserCommand) {
|
||||
return { error: "Could not create user" };
|
||||
}
|
||||
|
||||
const codeResponse = await createInviteCode(human.userId, host);
|
||||
const codeResponse = await createInviteCode({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
urlTemplate: `${host.includes("localhost") ? "http://" : "https://"}${host}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true`,
|
||||
userId: human.userId,
|
||||
});
|
||||
|
||||
if (!codeResponse || !human) {
|
||||
return { error: "Could not create invite code" };
|
||||
|
@@ -8,6 +8,7 @@ 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 { getServiceUrlFromHeaders } from "../service";
|
||||
import { checkInvite } from "../verify-helper";
|
||||
import {
|
||||
getActiveIdentityProviders,
|
||||
@@ -32,13 +33,27 @@ export type SendLoginnameCommand = {
|
||||
const ORG_SUFFIX_REGEX = /(?<=@)(.+)/;
|
||||
|
||||
export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
const loginSettingsByContext = await getLoginSettings(command.organization);
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
const host = _headers.get("host");
|
||||
|
||||
if (!host) {
|
||||
throw new Error("Could not get domain");
|
||||
}
|
||||
|
||||
const loginSettingsByContext = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: command.organization,
|
||||
});
|
||||
|
||||
if (!loginSettingsByContext) {
|
||||
return { error: "Could not get login settings" };
|
||||
}
|
||||
|
||||
let searchUsersRequest: SearchUsersCommand = {
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
searchValue: command.loginName,
|
||||
organizationId: command.organization,
|
||||
loginSettings: loginSettingsByContext,
|
||||
@@ -58,14 +73,18 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
const { result: potentialUsers } = searchResult;
|
||||
|
||||
const redirectUserToSingleIDPIfAvailable = async () => {
|
||||
const identityProviders = await getActiveIdentityProviders(
|
||||
command.organization,
|
||||
).then((resp) => {
|
||||
const identityProviders = await getActiveIdentityProviders({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
orgId: command.organization,
|
||||
}).then((resp) => {
|
||||
return resp.identityProviders;
|
||||
});
|
||||
|
||||
if (identityProviders.length === 1) {
|
||||
const host = (await headers()).get("host");
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
const host = _headers.get("host");
|
||||
|
||||
if (!host) {
|
||||
return { error: "Could not get host" };
|
||||
@@ -86,6 +105,8 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
}
|
||||
|
||||
const resp = await startIdentityProviderFlow({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
idpId: identityProviders[0].id,
|
||||
urls: {
|
||||
successUrl:
|
||||
@@ -104,12 +125,18 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
};
|
||||
|
||||
const redirectUserToIDP = async (userId: string) => {
|
||||
const identityProviders = await listIDPLinks(userId).then((resp) => {
|
||||
const identityProviders = await listIDPLinks({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId,
|
||||
}).then((resp) => {
|
||||
return resp.result;
|
||||
});
|
||||
|
||||
if (identityProviders.length === 1) {
|
||||
const host = (await headers()).get("host");
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
const host = _headers.get("host");
|
||||
|
||||
if (!host) {
|
||||
return { error: "Could not get host" };
|
||||
@@ -117,7 +144,11 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
|
||||
const identityProviderId = identityProviders[0].idpId;
|
||||
|
||||
const idp = await getIDPByID(identityProviderId);
|
||||
const idp = await getIDPByID({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
id: identityProviderId,
|
||||
});
|
||||
|
||||
const idpType = idp?.type;
|
||||
|
||||
@@ -139,6 +170,8 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
}
|
||||
|
||||
const resp = await startIdentityProviderFlow({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
idpId: idp.id,
|
||||
urls: {
|
||||
successUrl:
|
||||
@@ -162,9 +195,11 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
const user = potentialUsers[0];
|
||||
const userId = potentialUsers[0].userId;
|
||||
|
||||
const userLoginSettings = await getLoginSettings(
|
||||
user.details?.resourceOwner,
|
||||
);
|
||||
const userLoginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: user.details?.resourceOwner,
|
||||
});
|
||||
|
||||
// compare with the concatenated suffix when set
|
||||
const concatLoginname = command.suffix
|
||||
@@ -219,9 +254,11 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
return { error: "Initial User not supported" };
|
||||
}
|
||||
|
||||
const methods = await listAuthenticationMethodTypes(
|
||||
session.factors?.user?.id,
|
||||
);
|
||||
const methods = await listAuthenticationMethodTypes({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: session.factors?.user?.id,
|
||||
});
|
||||
|
||||
// this can be expected to be an invite as users created in console have a password set.
|
||||
if (!methods.authMethodTypes || !methods.authMethodTypes.length) {
|
||||
@@ -376,11 +413,19 @@ export async function sendLoginname(command: SendLoginnameCommand) {
|
||||
const suffix = matched?.[1] ?? "";
|
||||
|
||||
// this just returns orgs where the suffix is set as primary domain
|
||||
const orgs = await getOrgsByDomain(suffix);
|
||||
const orgs = await getOrgsByDomain({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
domain: suffix,
|
||||
});
|
||||
const orgToCheckForDiscovery =
|
||||
orgs.result && orgs.result.length === 1 ? orgs.result[0].id : undefined;
|
||||
|
||||
const orgLoginSettings = await getLoginSettings(orgToCheckForDiscovery);
|
||||
const orgLoginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: orgToCheckForDiscovery,
|
||||
});
|
||||
if (orgLoginSettings?.allowDomainDiscovery) {
|
||||
orgToRegisterOn = orgToCheckForDiscovery;
|
||||
}
|
||||
|
@@ -7,11 +7,13 @@ import {
|
||||
ChecksSchema,
|
||||
CheckTOTPSchema,
|
||||
} from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { headers } from "next/headers";
|
||||
import {
|
||||
getMostRecentSessionCookie,
|
||||
getSessionCookieById,
|
||||
getSessionCookieByLoginName,
|
||||
} from "../cookies";
|
||||
import { getServiceUrlFromHeaders } from "../service";
|
||||
import { getLoginSettings } from "../zitadel";
|
||||
|
||||
export type SetOTPCommand = {
|
||||
@@ -24,6 +26,9 @@ export type SetOTPCommand = {
|
||||
};
|
||||
|
||||
export async function setOTP(command: SetOTPCommand) {
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const recentSession = command.sessionId
|
||||
? await getSessionCookieById({ sessionId: command.sessionId }).catch(
|
||||
(error) => {
|
||||
@@ -57,7 +62,11 @@ export async function setOTP(command: SetOTPCommand) {
|
||||
});
|
||||
}
|
||||
|
||||
const loginSettings = await getLoginSettings(command.organization);
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: command.organization,
|
||||
});
|
||||
|
||||
return setSessionAndUpdateCookie(
|
||||
recentSession,
|
||||
|
@@ -22,6 +22,7 @@ import {
|
||||
getSessionCookieById,
|
||||
getSessionCookieByLoginName,
|
||||
} from "../cookies";
|
||||
import { getServiceUrlFromHeaders } from "../service";
|
||||
import { checkEmailVerification } from "../verify-helper";
|
||||
import { setSessionAndUpdateCookie } from "./cookie";
|
||||
|
||||
@@ -41,18 +42,22 @@ export async function registerPasskeyLink(
|
||||
): Promise<RegisterPasskeyResponse> {
|
||||
const { sessionId } = command;
|
||||
|
||||
const sessionCookie = await getSessionCookieById({ sessionId });
|
||||
const session = await getSession({
|
||||
sessionId: sessionCookie.id,
|
||||
sessionToken: sessionCookie.token,
|
||||
});
|
||||
|
||||
const host = (await headers()).get("host");
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
const host = _headers.get("host");
|
||||
|
||||
if (!host) {
|
||||
throw new Error("Could not get domain");
|
||||
}
|
||||
|
||||
const sessionCookie = await getSessionCookieById({ sessionId });
|
||||
const session = await getSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: sessionCookie.id,
|
||||
sessionToken: sessionCookie.token,
|
||||
});
|
||||
|
||||
const [hostname, port] = host.split(":");
|
||||
|
||||
if (!hostname) {
|
||||
@@ -67,19 +72,29 @@ export async function registerPasskeyLink(
|
||||
// TODO: add org context
|
||||
|
||||
// use session token to add the passkey
|
||||
const registerLink = await createPasskeyRegistrationLink(
|
||||
const registerLink = await createPasskeyRegistrationLink({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId,
|
||||
// sessionCookie.token,
|
||||
);
|
||||
});
|
||||
|
||||
if (!registerLink.code) {
|
||||
throw new Error("Missing code in response");
|
||||
}
|
||||
|
||||
return registerPasskey(userId, registerLink.code, hostname);
|
||||
return registerPasskey({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId,
|
||||
code: registerLink.code,
|
||||
domain: hostname,
|
||||
});
|
||||
}
|
||||
|
||||
export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) {
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
// if no name is provided, try to generate one from the user agent
|
||||
let passkeyName = command.passkeyName;
|
||||
if (!!!passkeyName) {
|
||||
@@ -96,6 +111,8 @@ export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) {
|
||||
sessionId: command.sessionId,
|
||||
});
|
||||
const session = await getSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: sessionCookie.id,
|
||||
sessionToken: sessionCookie.token,
|
||||
});
|
||||
@@ -105,14 +122,16 @@ export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) {
|
||||
throw new Error("Could not get session");
|
||||
}
|
||||
|
||||
return zitadelVerifyPasskeyRegistration(
|
||||
create(VerifyPasskeyRegistrationRequestSchema, {
|
||||
return zitadelVerifyPasskeyRegistration({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
request: create(VerifyPasskeyRegistrationRequestSchema, {
|
||||
passkeyId: command.passkeyId,
|
||||
publicKeyCredential: command.publicKeyCredential,
|
||||
passkeyName,
|
||||
userId,
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
type SendPasskeyCommand = {
|
||||
@@ -138,13 +157,14 @@ export async function sendPasskey(command: SendPasskeyCommand) {
|
||||
};
|
||||
}
|
||||
|
||||
const host = (await headers()).get("host");
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
if (!host) {
|
||||
return { error: "Could not get host" };
|
||||
}
|
||||
|
||||
const loginSettings = await getLoginSettings(organization);
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
const lifetime = checks?.webAuthN
|
||||
? loginSettings?.multiFactorCheckLifetime // TODO different lifetime for webauthn u2f/passkey
|
||||
@@ -164,7 +184,11 @@ export async function sendPasskey(command: SendPasskeyCommand) {
|
||||
return { error: "Could not update session" };
|
||||
}
|
||||
|
||||
const userResponse = await getUserByID(session?.factors?.user?.id);
|
||||
const userResponse = await getUserByID({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: session?.factors?.user?.id,
|
||||
});
|
||||
|
||||
if (!userResponse.user) {
|
||||
return { error: "User not found in the system" };
|
||||
|
@@ -5,7 +5,9 @@ import {
|
||||
setSessionAndUpdateCookie,
|
||||
} from "@/lib/server/cookie";
|
||||
import {
|
||||
getLockoutSettings,
|
||||
getLoginSettings,
|
||||
getPasswordExpirySettings,
|
||||
getSession,
|
||||
getUserByID,
|
||||
listAuthenticationMethodTypes,
|
||||
@@ -30,6 +32,7 @@ import {
|
||||
import { headers } from "next/headers";
|
||||
import { getNextUrl } from "../client";
|
||||
import { getSessionCookieById, getSessionCookieByLoginName } from "../cookies";
|
||||
import { getServiceUrlFromHeaders } from "../service";
|
||||
import {
|
||||
checkEmailVerification,
|
||||
checkMFAFactors,
|
||||
@@ -43,9 +46,17 @@ type ResetPasswordCommand = {
|
||||
};
|
||||
|
||||
export async function resetPassword(command: ResetPasswordCommand) {
|
||||
const host = (await headers()).get("host");
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
const host = _headers.get("host");
|
||||
|
||||
if (!host || typeof host !== "string") {
|
||||
throw new Error("No host found");
|
||||
}
|
||||
|
||||
const users = await listUsers({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
loginName: command.loginName,
|
||||
organizationId: command.organization,
|
||||
});
|
||||
@@ -59,7 +70,14 @@ export async function resetPassword(command: ResetPasswordCommand) {
|
||||
}
|
||||
const userId = users.result[0].userId;
|
||||
|
||||
return passwordReset(userId, host, command.authRequestId);
|
||||
return passwordReset({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId,
|
||||
urlTemplate:
|
||||
`${host.includes("localhost") ? "http://" : "https://"}${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` +
|
||||
(command.authRequestId ? `&authRequestId=${command.authRequestId}` : ""),
|
||||
});
|
||||
}
|
||||
|
||||
export type UpdateSessionCommand = {
|
||||
@@ -70,6 +88,9 @@ export type UpdateSessionCommand = {
|
||||
};
|
||||
|
||||
export async function sendPassword(command: UpdateSessionCommand) {
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
let sessionCookie = await getSessionCookieByLoginName({
|
||||
loginName: command.loginName,
|
||||
organization: command.organization,
|
||||
@@ -83,6 +104,8 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
||||
|
||||
if (!sessionCookie) {
|
||||
const users = await listUsers({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
loginName: command.loginName,
|
||||
organizationId: command.organization,
|
||||
});
|
||||
@@ -95,19 +118,44 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
||||
password: { password: command.checks.password?.password },
|
||||
});
|
||||
|
||||
loginSettings = await getLoginSettings(command.organization);
|
||||
loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: command.organization,
|
||||
});
|
||||
|
||||
try {
|
||||
session = await createSessionAndUpdateCookie(
|
||||
checks,
|
||||
undefined,
|
||||
command.authRequestId,
|
||||
loginSettings?.passwordCheckLifetime,
|
||||
);
|
||||
} catch (error: any) {
|
||||
if ("failedAttempts" in error && error.failedAttempts) {
|
||||
const lockoutSettings = await getLockoutSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
orgId: command.organization,
|
||||
});
|
||||
|
||||
return {
|
||||
error:
|
||||
`Failed to authenticate. You had ${error.failedAttempts} of ${lockoutSettings?.maxPasswordAttempts} password attempts.` +
|
||||
(lockoutSettings?.maxPasswordAttempts &&
|
||||
error.failedAttempts >= lockoutSettings?.maxPasswordAttempts
|
||||
? "Contact your administrator to unlock your account"
|
||||
: ""),
|
||||
};
|
||||
}
|
||||
return { error: "Could not create session for user" };
|
||||
}
|
||||
}
|
||||
|
||||
// this is a fake error message to hide that the user does not even exist
|
||||
return { error: "Could not verify password" };
|
||||
} else {
|
||||
try {
|
||||
session = await setSessionAndUpdateCookie(
|
||||
sessionCookie,
|
||||
command.checks,
|
||||
@@ -115,12 +163,35 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
||||
command.authRequestId,
|
||||
loginSettings?.passwordCheckLifetime,
|
||||
);
|
||||
} catch (error: any) {
|
||||
if ("failedAttempts" in error && error.failedAttempts) {
|
||||
const lockoutSettings = await getLockoutSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
orgId: command.organization,
|
||||
});
|
||||
|
||||
return {
|
||||
error:
|
||||
`Failed to authenticate. You had ${error.failedAttempts} of ${lockoutSettings?.maxPasswordAttempts} password attempts.` +
|
||||
(lockoutSettings?.maxPasswordAttempts &&
|
||||
error.failedAttempts >= lockoutSettings?.maxPasswordAttempts
|
||||
? " Contact your administrator to unlock your account"
|
||||
: ""),
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!session?.factors?.user?.id) {
|
||||
return { error: "Could not create session for user" };
|
||||
}
|
||||
|
||||
const userResponse = await getUserByID(session?.factors?.user?.id);
|
||||
const userResponse = await getUserByID({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: session?.factors?.user?.id,
|
||||
});
|
||||
|
||||
if (!userResponse.user) {
|
||||
return { error: "User not found in the system" };
|
||||
@@ -130,9 +201,12 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
||||
}
|
||||
|
||||
if (!loginSettings) {
|
||||
loginSettings = await getLoginSettings(
|
||||
loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization:
|
||||
command.organization ?? session.factors?.user?.organizationId,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (!session?.factors?.user?.id || !sessionCookie) {
|
||||
@@ -141,8 +215,15 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
||||
|
||||
const humanUser = user.type.case === "human" ? user.type.value : undefined;
|
||||
|
||||
const expirySettings = await getPasswordExpirySettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
orgId: command.organization ?? session.factors?.user?.organizationId,
|
||||
});
|
||||
|
||||
// check if the user has to change password first
|
||||
const passwordChangedCheck = checkPasswordChangeRequired(
|
||||
expirySettings,
|
||||
session,
|
||||
humanUser,
|
||||
command.organization,
|
||||
@@ -173,9 +254,11 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
||||
// if password, check if user has MFA methods
|
||||
let authMethods;
|
||||
if (command.checks && command.checks.password && session.factors?.user?.id) {
|
||||
const response = await listAuthenticationMethodTypes(
|
||||
session.factors.user.id,
|
||||
);
|
||||
const response = await listAuthenticationMethodTypes({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: session.factors.user.id,
|
||||
});
|
||||
if (response.authMethodTypes && response.authMethodTypes.length) {
|
||||
authMethods = response.authMethodTypes;
|
||||
}
|
||||
@@ -227,15 +310,29 @@ export async function changePassword(command: {
|
||||
userId: string;
|
||||
password: string;
|
||||
}) {
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
// check for init state
|
||||
const { user } = await getUserByID(command.userId);
|
||||
const { user } = await getUserByID({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: command.userId,
|
||||
});
|
||||
|
||||
if (!user || user.userId !== command.userId) {
|
||||
return { error: "Could not send Password Reset Link" };
|
||||
}
|
||||
const userId = user.userId;
|
||||
|
||||
return setUserPassword(userId, command.password, user, command.code);
|
||||
return setUserPassword({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId,
|
||||
password: command.password,
|
||||
user,
|
||||
code: command.code,
|
||||
});
|
||||
}
|
||||
|
||||
type CheckSessionAndSetPasswordCommand = {
|
||||
@@ -247,9 +344,14 @@ export async function checkSessionAndSetPassword({
|
||||
sessionId,
|
||||
password,
|
||||
}: CheckSessionAndSetPasswordCommand) {
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const sessionCookie = await getSessionCookieById({ sessionId });
|
||||
|
||||
const { session } = await getSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: sessionCookie.id,
|
||||
sessionToken: sessionCookie.token,
|
||||
});
|
||||
@@ -266,9 +368,11 @@ export async function checkSessionAndSetPassword({
|
||||
});
|
||||
|
||||
// check if the user has no password set in order to set a password
|
||||
const authmethods = await listAuthenticationMethodTypes(
|
||||
session.factors.user.id,
|
||||
);
|
||||
const authmethods = await listAuthenticationMethodTypes({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: session.factors.user.id,
|
||||
});
|
||||
|
||||
if (!authmethods) {
|
||||
return { error: "Could not load auth methods" };
|
||||
@@ -285,9 +389,11 @@ export async function checkSessionAndSetPassword({
|
||||
(method) => !authmethods.authMethodTypes.includes(method),
|
||||
);
|
||||
|
||||
const loginSettings = await getLoginSettings(
|
||||
session.factors.user.organizationId,
|
||||
);
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: session.factors.user.organizationId,
|
||||
});
|
||||
|
||||
const forceMfa = !!(
|
||||
loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly
|
||||
@@ -295,24 +401,32 @@ export async function checkSessionAndSetPassword({
|
||||
|
||||
// if the user has no MFA but MFA is enforced, we can set a password otherwise we use the token of the user
|
||||
if (forceMfa && hasNoMFAMethods) {
|
||||
return setPassword(payload).catch((error) => {
|
||||
return setPassword({ serviceUrl, serviceRegion, payload }).catch(
|
||||
(error) => {
|
||||
// throw error if failed precondition (ex. User is not yet initialized)
|
||||
if (error.code === 9 && error.message) {
|
||||
return { error: "Failed precondition" };
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const myUserService = (sessionToken: string) => {
|
||||
return createUserServiceClient(
|
||||
createServerTransport(sessionToken, {
|
||||
baseUrl: process.env.ZITADEL_API_URL!,
|
||||
}),
|
||||
},
|
||||
);
|
||||
} else {
|
||||
const transport = async (serviceUrl: string, token: string) => {
|
||||
return createServerTransport(token, {
|
||||
baseUrl: serviceUrl,
|
||||
});
|
||||
};
|
||||
|
||||
const selfService = await myUserService(`${sessionCookie.token}`);
|
||||
const myUserService = async (serviceUrl: string, sessionToken: string) => {
|
||||
const transportPromise = await transport(serviceUrl, sessionToken);
|
||||
return createUserServiceClient(transportPromise);
|
||||
};
|
||||
|
||||
const selfService = await myUserService(
|
||||
serviceUrl,
|
||||
`${sessionCookie.token}`,
|
||||
);
|
||||
|
||||
return selfService
|
||||
.setPassword(
|
||||
|
@@ -8,7 +8,9 @@ import {
|
||||
ChecksJson,
|
||||
ChecksSchema,
|
||||
} from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { headers } from "next/headers";
|
||||
import { getNextUrl } from "../client";
|
||||
import { getServiceUrlFromHeaders } from "../service";
|
||||
import { checkEmailVerification } from "../verify-helper";
|
||||
|
||||
type RegisterUserCommand = {
|
||||
@@ -26,7 +28,17 @@ export type RegisterUserResponse = {
|
||||
factors: Factors | undefined;
|
||||
};
|
||||
export async function registerUser(command: RegisterUserCommand) {
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
const host = _headers.get("host");
|
||||
|
||||
if (!host || typeof host !== "string") {
|
||||
throw new Error("No host found");
|
||||
}
|
||||
|
||||
const addResponse = await addHumanUser({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
email: command.email,
|
||||
firstName: command.firstName,
|
||||
lastName: command.lastName,
|
||||
@@ -38,7 +50,11 @@ export async function registerUser(command: RegisterUserCommand) {
|
||||
return { error: "Could not create user" };
|
||||
}
|
||||
|
||||
const loginSettings = await getLoginSettings(command.organization);
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: command.organization,
|
||||
});
|
||||
|
||||
let checkPayload: any = {
|
||||
user: { search: { case: "userId", value: addResponse.userId } },
|
||||
@@ -76,7 +92,11 @@ export async function registerUser(command: RegisterUserCommand) {
|
||||
|
||||
return { redirect: "/passkey/set?" + params };
|
||||
} else {
|
||||
const userResponse = await getUserByID(session?.factors?.user?.id);
|
||||
const userResponse = await getUserByID({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: session?.factors?.user?.id,
|
||||
});
|
||||
|
||||
if (!userResponse.user) {
|
||||
return { error: "User not found in the system" };
|
||||
|
@@ -18,14 +18,20 @@ import {
|
||||
getSessionCookieByLoginName,
|
||||
removeSessionFromCookie,
|
||||
} from "../cookies";
|
||||
import { getServiceUrlFromHeaders } from "../service";
|
||||
|
||||
export async function continueWithSession({
|
||||
authRequestId,
|
||||
...session
|
||||
}: Session & { authRequestId?: string }) {
|
||||
const loginSettings = await getLoginSettings(
|
||||
session.factors?.user?.organizationId,
|
||||
);
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: session.factors?.user?.organizationId,
|
||||
});
|
||||
|
||||
const url =
|
||||
authRequestId && session.id && session.factors?.user
|
||||
@@ -82,7 +88,9 @@ export async function updateSession(options: UpdateSessionCommand) {
|
||||
};
|
||||
}
|
||||
|
||||
const host = (await headers()).get("host");
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
const host = _headers.get("host");
|
||||
|
||||
if (!host) {
|
||||
return { error: "Could not get host" };
|
||||
@@ -99,7 +107,11 @@ export async function updateSession(options: UpdateSessionCommand) {
|
||||
challenges.webAuthN.domain = hostname;
|
||||
}
|
||||
|
||||
const loginSettings = await getLoginSettings(organization);
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization,
|
||||
});
|
||||
|
||||
const lifetime = checks?.webAuthN
|
||||
? loginSettings?.multiFactorCheckLifetime // TODO different lifetime for webauthn u2f/passkey
|
||||
@@ -122,9 +134,11 @@ export async function updateSession(options: UpdateSessionCommand) {
|
||||
// if password, check if user has MFA methods
|
||||
let authMethods;
|
||||
if (checks && checks.password && session.factors?.user?.id) {
|
||||
const response = await listAuthenticationMethodTypes(
|
||||
session.factors.user.id,
|
||||
);
|
||||
const response = await listAuthenticationMethodTypes({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: session.factors.user.id,
|
||||
});
|
||||
if (response.authMethodTypes && response.authMethodTypes.length) {
|
||||
authMethods = response.authMethodTypes;
|
||||
}
|
||||
@@ -143,11 +157,19 @@ type ClearSessionOptions = {
|
||||
};
|
||||
|
||||
export async function clearSession(options: ClearSessionOptions) {
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const { sessionId } = options;
|
||||
|
||||
const session = await getSessionCookieById({ sessionId });
|
||||
|
||||
const deletedSession = await deleteSession(session.id, session.token);
|
||||
const deletedSession = await deleteSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: session.id,
|
||||
sessionToken: session.token,
|
||||
});
|
||||
|
||||
if (deletedSession) {
|
||||
return removeSessionFromCookie(session);
|
||||
@@ -159,12 +181,17 @@ type CleanupSessionCommand = {
|
||||
};
|
||||
|
||||
export async function cleanupSession({ sessionId }: CleanupSessionCommand) {
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const sessionCookie = await getSessionCookieById({ sessionId });
|
||||
|
||||
const deleteResponse = await deleteSession(
|
||||
sessionCookie.id,
|
||||
sessionCookie.token,
|
||||
);
|
||||
const deleteResponse = await deleteSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: sessionCookie.id,
|
||||
sessionToken: sessionCookie.token,
|
||||
});
|
||||
|
||||
if (!deleteResponse) {
|
||||
throw new Error("Could not delete session");
|
||||
|
@@ -6,6 +6,7 @@ import { VerifyU2FRegistrationRequestSchema } from "@zitadel/proto/zitadel/user/
|
||||
import { headers } from "next/headers";
|
||||
import { userAgent } from "next/server";
|
||||
import { getSessionCookieById } from "../cookies";
|
||||
import { getServiceUrlFromHeaders } from "../service";
|
||||
|
||||
type RegisterU2FCommand = {
|
||||
sessionId: string;
|
||||
@@ -19,6 +20,14 @@ type VerifyU2FCommand = {
|
||||
};
|
||||
|
||||
export async function addU2F(command: RegisterU2FCommand) {
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
const host = _headers.get("host");
|
||||
|
||||
if (!host || typeof host !== "string") {
|
||||
throw new Error("No host found");
|
||||
}
|
||||
|
||||
const sessionCookie = await getSessionCookieById({
|
||||
sessionId: command.sessionId,
|
||||
});
|
||||
@@ -28,16 +37,12 @@ export async function addU2F(command: RegisterU2FCommand) {
|
||||
}
|
||||
|
||||
const session = await getSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: sessionCookie.id,
|
||||
sessionToken: sessionCookie.token,
|
||||
});
|
||||
|
||||
const host = (await headers()).get("host");
|
||||
|
||||
if (!host) {
|
||||
return { error: "Could not get domain" };
|
||||
}
|
||||
|
||||
const [hostname, port] = host.split(":");
|
||||
|
||||
if (!hostname) {
|
||||
@@ -50,10 +55,18 @@ export async function addU2F(command: RegisterU2FCommand) {
|
||||
return { error: "Could not get session" };
|
||||
}
|
||||
|
||||
return registerU2F(userId, hostname);
|
||||
return registerU2F({ serviceUrl, serviceRegion, userId, domain: hostname });
|
||||
}
|
||||
|
||||
export async function verifyU2F(command: VerifyU2FCommand) {
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
const host = _headers.get("host");
|
||||
|
||||
if (!host || typeof host !== "string") {
|
||||
throw new Error("No host found");
|
||||
}
|
||||
|
||||
let passkeyName = command.passkeyName;
|
||||
if (!!!passkeyName) {
|
||||
const headersList = await headers();
|
||||
@@ -69,6 +82,8 @@ export async function verifyU2F(command: VerifyU2FCommand) {
|
||||
});
|
||||
|
||||
const session = await getSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: sessionCookie.id,
|
||||
sessionToken: sessionCookie.token,
|
||||
});
|
||||
@@ -79,12 +94,12 @@ export async function verifyU2F(command: VerifyU2FCommand) {
|
||||
return { error: "Could not get session" };
|
||||
}
|
||||
|
||||
const req = create(VerifyU2FRegistrationRequestSchema, {
|
||||
const request = create(VerifyU2FRegistrationRequestSchema, {
|
||||
u2fId: command.u2fId,
|
||||
publicKeyCredential: command.publicKeyCredential,
|
||||
tokenName: passkeyName,
|
||||
userId,
|
||||
});
|
||||
|
||||
return verifyU2FRegistration(req);
|
||||
return verifyU2FRegistration({ serviceUrl, serviceRegion, request });
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ import {
|
||||
resendInviteCode,
|
||||
verifyEmail,
|
||||
verifyInviteCode,
|
||||
verifyTOTPRegistration,
|
||||
sendEmailCode as zitadelSendEmailCode,
|
||||
} from "@/lib/zitadel";
|
||||
import { create } from "@zitadel/client";
|
||||
@@ -18,9 +19,40 @@ import { User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import { headers } from "next/headers";
|
||||
import { getNextUrl } from "../client";
|
||||
import { getSessionCookieByLoginName } from "../cookies";
|
||||
import { getServiceUrlFromHeaders } from "../service";
|
||||
import { loadMostRecentSession } from "../session";
|
||||
import { checkMFAFactors } from "../verify-helper";
|
||||
import { createSessionAndUpdateCookie } from "./cookie";
|
||||
|
||||
export async function verifyTOTP(
|
||||
code: string,
|
||||
loginName?: string,
|
||||
organization?: string,
|
||||
) {
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
return loadMostRecentSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionParams: {
|
||||
loginName,
|
||||
organization,
|
||||
},
|
||||
}).then((session) => {
|
||||
if (session?.factors?.user?.id) {
|
||||
return verifyTOTPRegistration({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
code,
|
||||
userId: session.factors.user.id,
|
||||
});
|
||||
} else {
|
||||
throw Error("No user id found in session.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
type VerifyUserByEmailCommand = {
|
||||
userId: string;
|
||||
loginName?: string; // to determine already existing session
|
||||
@@ -31,11 +63,24 @@ type VerifyUserByEmailCommand = {
|
||||
};
|
||||
|
||||
export async function sendVerification(command: VerifyUserByEmailCommand) {
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const verifyResponse = command.isInvite
|
||||
? await verifyInviteCode(command.userId, command.code).catch(() => {
|
||||
? await verifyInviteCode({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: command.userId,
|
||||
verificationCode: command.code,
|
||||
}).catch(() => {
|
||||
return { error: "Could not verify invite" };
|
||||
})
|
||||
: await verifyEmail(command.userId, command.code).catch(() => {
|
||||
: await verifyEmail({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: command.userId,
|
||||
verificationCode: command.code,
|
||||
}).catch(() => {
|
||||
return { error: "Could not verify email" };
|
||||
});
|
||||
|
||||
@@ -63,6 +108,8 @@ export async function sendVerification(command: VerifyUserByEmailCommand) {
|
||||
}
|
||||
|
||||
session = await getSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: sessionCookie.id,
|
||||
sessionToken: sessionCookie.token,
|
||||
}).then((response) => {
|
||||
@@ -75,7 +122,11 @@ export async function sendVerification(command: VerifyUserByEmailCommand) {
|
||||
return { error: "Could not create session for user" };
|
||||
}
|
||||
|
||||
const userResponse = await getUserByID(session?.factors?.user?.id);
|
||||
const userResponse = await getUserByID({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: session?.factors?.user?.id,
|
||||
});
|
||||
|
||||
if (!userResponse?.user) {
|
||||
return { error: "Could not load user" };
|
||||
@@ -83,7 +134,11 @@ export async function sendVerification(command: VerifyUserByEmailCommand) {
|
||||
|
||||
user = userResponse.user;
|
||||
} else {
|
||||
const userResponse = await getUserByID(command.userId);
|
||||
const userResponse = await getUserByID({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: command.userId,
|
||||
});
|
||||
|
||||
if (!userResponse || !userResponse.user) {
|
||||
return { error: "Could not load user" };
|
||||
@@ -119,9 +174,17 @@ export async function sendVerification(command: VerifyUserByEmailCommand) {
|
||||
return { error: "Could not load user" };
|
||||
}
|
||||
|
||||
const loginSettings = await getLoginSettings(user.details?.resourceOwner);
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: user.details?.resourceOwner,
|
||||
});
|
||||
|
||||
const authMethodResponse = await listAuthenticationMethodTypes(user.userId);
|
||||
const authMethodResponse = await listAuthenticationMethodTypes({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: user.userId,
|
||||
});
|
||||
|
||||
if (!authMethodResponse || !authMethodResponse.authMethodTypes) {
|
||||
return { error: "Could not load possible authenticators" };
|
||||
@@ -189,21 +252,42 @@ type resendVerifyEmailCommand = {
|
||||
};
|
||||
|
||||
export async function resendVerification(command: resendVerifyEmailCommand) {
|
||||
const host = (await headers()).get("host");
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
const host = _headers.get("host");
|
||||
|
||||
if (!host) {
|
||||
return { error: "No host found" };
|
||||
}
|
||||
|
||||
return command.isInvite
|
||||
? resendInviteCode(command.userId)
|
||||
: resendEmailCode(command.userId, host, command.authRequestId);
|
||||
? resendInviteCode({ serviceUrl, serviceRegion, userId: command.userId })
|
||||
: resendEmailCode({
|
||||
userId: command.userId,
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
urlTemplate:
|
||||
`${host.includes("localhost") ? "http://" : "https://"}${host}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` +
|
||||
(command.authRequestId
|
||||
? `&authRequestId=${command.authRequestId}`
|
||||
: ""),
|
||||
});
|
||||
}
|
||||
|
||||
type sendEmailCommand = {
|
||||
serviceUrl: string;
|
||||
serviceRegion: string;
|
||||
userId: string;
|
||||
authRequestId?: string;
|
||||
urlTemplate: string;
|
||||
};
|
||||
|
||||
export async function sendEmailCode(command: sendEmailCommand) {
|
||||
const host = (await headers()).get("host");
|
||||
return zitadelSendEmailCode(command.userId, host, command.authRequestId);
|
||||
return zitadelSendEmailCode({
|
||||
serviceUrl: command.serviceUrl,
|
||||
serviceRegion: command.serviceRegion,
|
||||
userId: command.userId,
|
||||
urlTemplate: command.urlTemplate,
|
||||
});
|
||||
}
|
||||
|
||||
export type SendVerificationRedirectWithoutCheckCommand = {
|
||||
@@ -217,6 +301,9 @@ export type SendVerificationRedirectWithoutCheckCommand = {
|
||||
export async function sendVerificationRedirectWithoutCheck(
|
||||
command: SendVerificationRedirectWithoutCheckCommand,
|
||||
) {
|
||||
const _headers = await headers();
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
if (!("loginName" in command || "userId" in command)) {
|
||||
return { error: "No userId, nor loginname provided" };
|
||||
}
|
||||
@@ -237,6 +324,8 @@ export async function sendVerificationRedirectWithoutCheck(
|
||||
}
|
||||
|
||||
session = await getSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: sessionCookie.id,
|
||||
sessionToken: sessionCookie.token,
|
||||
}).then((response) => {
|
||||
@@ -249,7 +338,11 @@ export async function sendVerificationRedirectWithoutCheck(
|
||||
return { error: "Could not create session for user" };
|
||||
}
|
||||
|
||||
const userResponse = await getUserByID(session?.factors?.user?.id);
|
||||
const userResponse = await getUserByID({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: session?.factors?.user?.id,
|
||||
});
|
||||
|
||||
if (!userResponse?.user) {
|
||||
return { error: "Could not load user" };
|
||||
@@ -257,7 +350,11 @@ export async function sendVerificationRedirectWithoutCheck(
|
||||
|
||||
user = userResponse.user;
|
||||
} else if ("userId" in command) {
|
||||
const userResponse = await getUserByID(command.userId);
|
||||
const userResponse = await getUserByID({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: command.userId,
|
||||
});
|
||||
|
||||
if (!userResponse?.user) {
|
||||
return { error: "Could not load user" };
|
||||
@@ -293,7 +390,11 @@ export async function sendVerificationRedirectWithoutCheck(
|
||||
return { error: "Could not load user" };
|
||||
}
|
||||
|
||||
const authMethodResponse = await listAuthenticationMethodTypes(user.userId);
|
||||
const authMethodResponse = await listAuthenticationMethodTypes({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
userId: user.userId,
|
||||
});
|
||||
|
||||
if (!authMethodResponse || !authMethodResponse.authMethodTypes) {
|
||||
return { error: "Could not load possible authenticators" };
|
||||
@@ -315,7 +416,11 @@ export async function sendVerificationRedirectWithoutCheck(
|
||||
return { redirect: `/authenticator/set?${params}` };
|
||||
}
|
||||
|
||||
const loginSettings = await getLoginSettings(user.details?.resourceOwner);
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
organization: user.details?.resourceOwner,
|
||||
});
|
||||
|
||||
// redirect to mfa factor if user has one, or redirect to set one up
|
||||
const mfaFactorCheck = checkMFAFactors(
|
||||
|
81
apps/login/src/lib/service.ts
Normal file
81
apps/login/src/lib/service.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { createClientFor } from "@zitadel/client";
|
||||
import { createServerTransport } from "@zitadel/client/node";
|
||||
import { IdentityProviderService } from "@zitadel/proto/zitadel/idp/v2/idp_service_pb";
|
||||
import { OIDCService } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
|
||||
import { OrganizationService } from "@zitadel/proto/zitadel/org/v2/org_service_pb";
|
||||
import { SessionService } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb";
|
||||
import { UserService } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapters/headers";
|
||||
import { systemAPIToken } from "./api";
|
||||
|
||||
type ServiceClass =
|
||||
| typeof IdentityProviderService
|
||||
| typeof UserService
|
||||
| typeof OrganizationService
|
||||
| typeof SessionService
|
||||
| typeof OIDCService
|
||||
| typeof SettingsService;
|
||||
|
||||
export async function createServiceForHost<T extends ServiceClass>(
|
||||
service: T,
|
||||
serviceUrl: string,
|
||||
serviceRegion: string,
|
||||
) {
|
||||
let token;
|
||||
|
||||
// if we are running in a multitenancy context, use the system user token
|
||||
if (
|
||||
process.env[serviceRegion + "_AUDIENCE"] &&
|
||||
process.env[serviceRegion + "_SYSTEM_USER_ID"] &&
|
||||
process.env[serviceRegion + "_SYSTEM_USER_PRIVATE_KEY"]
|
||||
) {
|
||||
token = await systemAPIToken({ serviceRegion });
|
||||
} else if (process.env.ZITADEL_SERVICE_USER_TOKEN) {
|
||||
token = process.env.ZITADEL_SERVICE_USER_TOKEN;
|
||||
}
|
||||
|
||||
if (!serviceUrl) {
|
||||
throw new Error("No instance url found");
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
throw new Error("No token found");
|
||||
}
|
||||
|
||||
const transport = createServerTransport(token, {
|
||||
baseUrl: serviceUrl,
|
||||
});
|
||||
|
||||
return createClientFor<T>(service)(transport);
|
||||
}
|
||||
|
||||
export function getServiceUrlFromHeaders(headers: ReadonlyHeaders): {
|
||||
serviceUrl: string;
|
||||
serviceRegion: string;
|
||||
} {
|
||||
let instanceUrl: string = process.env.ZITADEL_API_URL;
|
||||
|
||||
const forwardedHost = headers.get("x-zitadel-forward-host");
|
||||
// use the forwarded host if available (multitenant), otherwise fall back to the host of the deployment itself
|
||||
if (forwardedHost) {
|
||||
instanceUrl = forwardedHost;
|
||||
instanceUrl = instanceUrl.startsWith("https://")
|
||||
? instanceUrl
|
||||
: `https://${instanceUrl}`;
|
||||
} else {
|
||||
const host = headers.get("host");
|
||||
|
||||
if (host) {
|
||||
const [hostname, port] = host.split(":");
|
||||
if (hostname !== "localhost") {
|
||||
instanceUrl = host;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
serviceUrl: instanceUrl,
|
||||
serviceRegion: headers.get("x-zitadel-region") || "",
|
||||
};
|
||||
}
|
@@ -1,17 +1,31 @@
|
||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import { GetSessionResponse } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { getMostRecentCookieWithLoginname } from "./cookies";
|
||||
import { sessionService } from "./zitadel";
|
||||
import { getSession } from "./zitadel";
|
||||
|
||||
export async function loadMostRecentSession(sessionParams: {
|
||||
type LoadMostRecentSessionParams = {
|
||||
serviceUrl: string;
|
||||
serviceRegion: string;
|
||||
sessionParams: {
|
||||
loginName?: string;
|
||||
organization?: string;
|
||||
}): Promise<Session | undefined> {
|
||||
};
|
||||
};
|
||||
|
||||
export async function loadMostRecentSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionParams,
|
||||
}: LoadMostRecentSessionParams): Promise<Session | undefined> {
|
||||
const recent = await getMostRecentCookieWithLoginname({
|
||||
loginName: sessionParams.loginName,
|
||||
organization: sessionParams.organization,
|
||||
});
|
||||
return sessionService
|
||||
.getSession({ sessionId: recent.id, sessionToken: recent.token }, {})
|
||||
.then((resp: GetSessionResponse) => resp.session);
|
||||
|
||||
return getSession({
|
||||
serviceUrl,
|
||||
serviceRegion,
|
||||
sessionId: recent.id,
|
||||
sessionToken: recent.token,
|
||||
}).then((resp: GetSessionResponse) => resp.session);
|
||||
}
|
||||
|
@@ -1,15 +1,29 @@
|
||||
import { timestampDate } from "@zitadel/client";
|
||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { PasswordExpirySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
|
||||
import { HumanUser } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import moment from "moment";
|
||||
|
||||
export function checkPasswordChangeRequired(
|
||||
expirySettings: PasswordExpirySettings | undefined,
|
||||
session: Session,
|
||||
humanUser: HumanUser | undefined,
|
||||
organization?: string,
|
||||
authRequestId?: string,
|
||||
) {
|
||||
if (humanUser?.passwordChangeRequired) {
|
||||
let isOutdated = false;
|
||||
if (expirySettings?.maxAgeDays && humanUser?.passwordChanged) {
|
||||
const maxAgeDays = Number(expirySettings.maxAgeDays); // Convert bigint to number
|
||||
const passwordChangedDate = moment(
|
||||
timestampDate(humanUser.passwordChanged),
|
||||
);
|
||||
const outdatedPassword = passwordChangedDate.add(maxAgeDays, "days");
|
||||
isOutdated = moment().isAfter(outdatedPassword);
|
||||
}
|
||||
|
||||
if (humanUser?.passwordChangeRequired || isOutdated) {
|
||||
const params = new URLSearchParams({
|
||||
loginName: session.factors?.user?.loginName as string,
|
||||
});
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,6 @@
|
||||
import { headers } from "next/headers";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getServiceUrlFromHeaders } from "./lib/service";
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
@@ -9,28 +11,40 @@ export const config = {
|
||||
],
|
||||
};
|
||||
|
||||
const INSTANCE = process.env.ZITADEL_API_URL;
|
||||
const SERVICE_USER_ID = process.env.ZITADEL_SERVICE_USER_ID as string;
|
||||
export async function middleware(request: NextRequest) {
|
||||
// escape proxy if the environment is setup for multitenancy
|
||||
if (
|
||||
!process.env.ZITADEL_API_URL ||
|
||||
!process.env.ZITADEL_SERVICE_USER_ID ||
|
||||
!process.env.ZITADEL_SERVICE_USER_TOKEN
|
||||
) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
const _headers = await headers();
|
||||
|
||||
const { serviceUrl, serviceRegion } = getServiceUrlFromHeaders(_headers);
|
||||
|
||||
const instanceHost = `${serviceUrl}`.replace("https://", "");
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
const requestHeaders = new Headers(request.headers);
|
||||
requestHeaders.set("x-zitadel-login-client", SERVICE_USER_ID);
|
||||
requestHeaders.set(
|
||||
"x-zitadel-login-client",
|
||||
process.env.ZITADEL_SERVICE_USER_ID,
|
||||
);
|
||||
|
||||
// this is a workaround for the next.js server not forwarding the host header
|
||||
// requestHeaders.set("x-zitadel-forwarded", `host="${request.nextUrl.host}"`);
|
||||
requestHeaders.set("x-zitadel-public-host", `${request.nextUrl.host}`);
|
||||
|
||||
// this is a workaround for the next.js server not forwarding the host header
|
||||
requestHeaders.set(
|
||||
"x-zitadel-instance-host",
|
||||
`${INSTANCE}`.replace(/^https?:\/\//, ""),
|
||||
);
|
||||
requestHeaders.set("x-zitadel-instance-host", instanceHost);
|
||||
|
||||
const responseHeaders = new Headers();
|
||||
responseHeaders.set("Access-Control-Allow-Origin", "*");
|
||||
responseHeaders.set("Access-Control-Allow-Headers", "*");
|
||||
|
||||
request.nextUrl.href = `${INSTANCE}${request.nextUrl.pathname}${request.nextUrl.search}`;
|
||||
request.nextUrl.href = `${serviceUrl}${request.nextUrl.pathname}${request.nextUrl.search}`;
|
||||
return NextResponse.rewrite(request.nextUrl, {
|
||||
request: {
|
||||
headers: requestHeaders,
|
||||
|
@@ -2,6 +2,7 @@
|
||||
"extends": "@zitadel/tsconfig/nextjs.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "preserve",
|
||||
"target": "es2022",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
|
@@ -29,9 +29,7 @@
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@typescript-eslint/parser": "^7.9.0",
|
||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1"
|
||||
"@typescript-eslint/parser": "^7.9.0"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@@ -1,5 +1,11 @@
|
||||
# @zitadel/client
|
||||
|
||||
## 1.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 28dc956: dynamic properties for system token utility
|
||||
|
||||
## 1.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@zitadel/client",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
@@ -1,8 +1,10 @@
|
||||
export { toDate } from "./helpers";
|
||||
export { createClientFor, toDate } from "./helpers";
|
||||
export { NewAuthorizationBearerInterceptor } from "./interceptors";
|
||||
|
||||
// TODO: Move this to `./protobuf.ts` and export it from there
|
||||
export { create, fromJson, toJson } from "@bufbuild/protobuf";
|
||||
export type { JsonObject } from "@bufbuild/protobuf";
|
||||
export type { GenService } from "@bufbuild/protobuf/codegenv1";
|
||||
export { TimestampSchema, timestampDate, timestampFromDate, timestampFromMs, timestampMs } from "@bufbuild/protobuf/wkt";
|
||||
export type { Duration, Timestamp } from "@bufbuild/protobuf/wkt";
|
||||
export type { Client, Code, ConnectError } from "@connectrpc/connect";
|
||||
|
@@ -27,13 +27,23 @@ export function createClientTransport(token: string, opts: GrpcTransportOptions)
|
||||
});
|
||||
}
|
||||
|
||||
export async function newSystemToken() {
|
||||
export async function newSystemToken({
|
||||
audience,
|
||||
subject,
|
||||
key,
|
||||
expirationTime,
|
||||
}: {
|
||||
audience: string;
|
||||
subject: string;
|
||||
key: string;
|
||||
expirationTime?: number | string | Date;
|
||||
}) {
|
||||
return await new SignJWT({})
|
||||
.setProtectedHeader({ alg: "RS256" })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime("1h")
|
||||
.setIssuer(process.env.ZITADEL_SYSTEM_API_USERID ?? "")
|
||||
.setSubject(process.env.ZITADEL_SYSTEM_API_USERID ?? "")
|
||||
.setAudience(process.env.ZITADEL_ISSUER ?? "")
|
||||
.sign(await importPKCS8(process.env.ZITADEL_SYSTEM_API_KEY ?? "", "RS256"));
|
||||
.setExpirationTime(expirationTime ?? "1h")
|
||||
.setIssuer(subject)
|
||||
.setSubject(subject)
|
||||
.setAudience(audience)
|
||||
.sign(await importPKCS8(key, "RS256"));
|
||||
}
|
||||
|
341
pnpm-lock.yaml
generated
341
pnpm-lock.yaml
generated
@@ -6,8 +6,6 @@ settings:
|
||||
|
||||
overrides:
|
||||
'@typescript-eslint/parser': ^7.9.0
|
||||
'@types/react': npm:types-react@19.0.0-rc.1
|
||||
'@types/react-dom': npm:types-react-dom@19.0.0-rc.1
|
||||
|
||||
importers:
|
||||
|
||||
@@ -78,16 +76,16 @@ importers:
|
||||
dependencies:
|
||||
'@headlessui/react':
|
||||
specifier: ^2.1.9
|
||||
version: 2.1.9(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)
|
||||
version: 2.1.9(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@heroicons/react':
|
||||
specifier: 2.1.3
|
||||
version: 2.1.3(react@19.0.0-rc-66855b96-20241106)
|
||||
version: 2.1.3(react@19.0.0)
|
||||
'@tailwindcss/forms':
|
||||
specifier: 0.5.7
|
||||
version: 0.5.7(tailwindcss@3.4.14)
|
||||
'@vercel/analytics':
|
||||
specifier: ^1.2.2
|
||||
version: 1.3.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106)
|
||||
version: 1.3.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0)
|
||||
'@zitadel/client':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/zitadel-client
|
||||
@@ -103,36 +101,39 @@ importers:
|
||||
deepmerge:
|
||||
specifier: ^4.3.1
|
||||
version: 4.3.1
|
||||
jose:
|
||||
specifier: ^5.3.0
|
||||
version: 5.8.0
|
||||
moment:
|
||||
specifier: ^2.29.4
|
||||
version: 2.30.1
|
||||
next:
|
||||
specifier: 15.0.4-canary.23
|
||||
version: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7)
|
||||
specifier: 15.2.0-canary.33
|
||||
version: 15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7)
|
||||
next-intl:
|
||||
specifier: ^3.25.1
|
||||
version: 3.25.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106)
|
||||
version: 3.25.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0)
|
||||
next-themes:
|
||||
specifier: ^0.2.1
|
||||
version: 0.2.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)
|
||||
version: 0.2.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
nice-grpc:
|
||||
specifier: 2.0.1
|
||||
version: 2.0.1
|
||||
qrcode.react:
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.0(react@19.0.0-rc-66855b96-20241106)
|
||||
version: 3.1.0(react@19.0.0)
|
||||
react:
|
||||
specifier: 19.0.0-rc-66855b96-20241106
|
||||
version: 19.0.0-rc-66855b96-20241106
|
||||
specifier: 19.0.0
|
||||
version: 19.0.0
|
||||
react-dom:
|
||||
specifier: 19.0.0-rc-66855b96-20241106
|
||||
version: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106)
|
||||
specifier: 19.0.0
|
||||
version: 19.0.0(react@19.0.0)
|
||||
react-hook-form:
|
||||
specifier: 7.39.5
|
||||
version: 7.39.5(react@19.0.0-rc-66855b96-20241106)
|
||||
version: 7.39.5(react@19.0.0)
|
||||
swr:
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.5(react@19.0.0-rc-66855b96-20241106)
|
||||
version: 2.2.5(react@19.0.0)
|
||||
tinycolor2:
|
||||
specifier: 1.4.2
|
||||
version: 1.4.2
|
||||
@@ -145,7 +146,7 @@ importers:
|
||||
version: 6.6.3
|
||||
'@testing-library/react':
|
||||
specifier: ^16.0.1
|
||||
version: 16.0.1(@testing-library/dom@10.4.0)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1)
|
||||
version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@types/ms':
|
||||
specifier: 0.7.34
|
||||
version: 0.7.34
|
||||
@@ -153,11 +154,11 @@ importers:
|
||||
specifier: 22.9.0
|
||||
version: 22.9.0
|
||||
'@types/react':
|
||||
specifier: npm:types-react@19.0.0-rc.1
|
||||
version: types-react@19.0.0-rc.1
|
||||
specifier: 19.0.2
|
||||
version: 19.0.2
|
||||
'@types/react-dom':
|
||||
specifier: npm:types-react-dom@19.0.0-rc.1
|
||||
version: types-react-dom@19.0.0-rc.1
|
||||
specifier: 19.0.2
|
||||
version: 19.0.2(@types/react@19.0.2)
|
||||
'@types/tinycolor2':
|
||||
specifier: 1.4.3
|
||||
version: 1.4.3
|
||||
@@ -1132,56 +1133,56 @@ packages:
|
||||
resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
|
||||
hasBin: true
|
||||
|
||||
'@next/env@15.0.4-canary.23':
|
||||
resolution: {integrity: sha512-NfBMRPa10yaEzQ693kGEsgHL58Y27jSbGCDbyXy14dx3z6UeQZQfEVRAwJ4iG1V6gND9+CzzugtiXvJZfSlC9A==}
|
||||
'@next/env@15.2.0-canary.33':
|
||||
resolution: {integrity: sha512-y3EPM+JYKU8t2K+i6bc0QrotEZVGpqu9eVjprj4cfS8QZyZcL54s+W9aGB0TBuGavU9tQdZ50W186+toeMV+hw==}
|
||||
|
||||
'@next/eslint-plugin-next@14.2.18':
|
||||
resolution: {integrity: sha512-KyYTbZ3GQwWOjX3Vi1YcQbekyGP0gdammb7pbmmi25HBUCINzDReyrzCMOJIeZisK1Q3U6DT5Rlc4nm2/pQeXA==}
|
||||
|
||||
'@next/swc-darwin-arm64@15.0.4-canary.23':
|
||||
resolution: {integrity: sha512-sX3MaDUiFiMT14KSx5mJz6B+IH9k7+buNniNrDP7iz4YG28jssm9e8uHbiWXsbn9jnkQUJu8PHoUOLhgjZgtsQ==}
|
||||
'@next/swc-darwin-arm64@15.2.0-canary.33':
|
||||
resolution: {integrity: sha512-+fCdK2KmR6lWoCTk1fSd5pvbiLZHfZF+D/Xdz3xrXw+pbnBtXWLKQrPT0bCtDseMxD31qcOywq5mAApvI3EGpA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-darwin-x64@15.0.4-canary.23':
|
||||
resolution: {integrity: sha512-KJRSDVvEPuvjRKe9IY3YMAv9KMOmB/U5+7g0c3OTT/50x1KL0XOlgnc+Af2GdZKIrkKiAdTFG54AHaSD584yHg==}
|
||||
'@next/swc-darwin-x64@15.2.0-canary.33':
|
||||
resolution: {integrity: sha512-GrrU+tSmeBRow+7bnn7i5M96g3tc28hPH5t5Y65qUXGmmrZwGZN1e1d+8QbXPdAGkvjEPcOkUNQuQVpp1qpYPA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-linux-arm64-gnu@15.0.4-canary.23':
|
||||
resolution: {integrity: sha512-0EqeqGdlG0MPDYGE/cPtTvBLtBiWDAd7fSRgRhIga6CkuaRVFKuTeRrsjTa0v+51C2OawjQp2N3ww1zBLuBhcg==}
|
||||
'@next/swc-linux-arm64-gnu@15.2.0-canary.33':
|
||||
resolution: {integrity: sha512-8RnGxnUpASHoUf6aHUifmZom5b4Ow5nTdCib/CNYXZ6VLuL5ocvmr+DXs/SKzi9h8OHR7JkLwKXHCcF8WyscSg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-arm64-musl@15.0.4-canary.23':
|
||||
resolution: {integrity: sha512-O06Gw8HU0z9f1b4TiGb0u1o87hgLa0yEW1odyLPE1d3+JKwhkh4L1Ug9uLpeqEUnxCoIrwVomEUyQBPGNQtq0Q==}
|
||||
'@next/swc-linux-arm64-musl@15.2.0-canary.33':
|
||||
resolution: {integrity: sha512-COyE0LzMuLBZSR+Z/TOGilyJPdwSU588Vt0+o8GoECkoDEnjyuO2s2nHa2kDAcEfUEPkhlo0tErU3mF+8AVOTQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-x64-gnu@15.0.4-canary.23':
|
||||
resolution: {integrity: sha512-BvERc3hri6eyUHnasZgwcRCdR8WpfCdKKe/M12Q+ZAkTeJeVkLXNakznaZbBWdlCc77F/NeHz/OWoQWUTpKm3g==}
|
||||
'@next/swc-linux-x64-gnu@15.2.0-canary.33':
|
||||
resolution: {integrity: sha512-3Y9lqJs+ftU9jgbLdCtvAvF8MNJsJYGMH7icb8QMs1+yOyHHbmwkZoElKdjwfUWzQ2sX28ywp73GWq4HbrsoUg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-x64-musl@15.0.4-canary.23':
|
||||
resolution: {integrity: sha512-FF5LNTdra/tHxdHjRR3lb+UxFgRVT+v3EMruueQg6BpOqpciodyCkkYQFrx2DitpADojQ6bBBFBDs6KIb8jB5w==}
|
||||
'@next/swc-linux-x64-musl@15.2.0-canary.33':
|
||||
resolution: {integrity: sha512-FS9iA+RkZlhdWGQEKtsplVBXIYZJUn5nsRB+1UY46b3uaL6dDypu13ODaSwYuAwXGgkrZBVF9AFO3y4biBnPlA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-win32-arm64-msvc@15.0.4-canary.23':
|
||||
resolution: {integrity: sha512-XnHD7fqQYZR1XCCuAf8+yAdkMpzAFz2pWmny2K6g5C7BalrwNuxWLsM5LycW1PTMzSqkzLJeXCG6AZu099u7/w==}
|
||||
'@next/swc-win32-arm64-msvc@15.2.0-canary.33':
|
||||
resolution: {integrity: sha512-Ji9CtBbUx06qvvN/rPohJN2FEFGsUv26F50f2nMRYRwrq3POXDjloGOiRocrjU0ty/cUzCz71qTUfKdmv/ajmg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@next/swc-win32-x64-msvc@15.0.4-canary.23':
|
||||
resolution: {integrity: sha512-HGoW8LjYxbUhkND+vJ/21dWQ7sdv4SIUQDv2r/FpcdHFMzb5M/jgQVqcMFkqg2ibH65ZAcVBM0ICcUnTLlX7PQ==}
|
||||
'@next/swc-win32-x64-msvc@15.2.0-canary.33':
|
||||
resolution: {integrity: sha512-hjdbGnkwIZ8zN2vlS6lNsEJO37HRtcEGimzfkruBMsi/DwJBqkJvZbNC/XCJy3HFcU58igncqV52p1IPjmAJAw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -1478,6 +1479,9 @@ packages:
|
||||
'@swc/helpers@0.5.13':
|
||||
resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==}
|
||||
|
||||
'@swc/helpers@0.5.15':
|
||||
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
|
||||
|
||||
'@swc/helpers@0.5.5':
|
||||
resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
|
||||
|
||||
@@ -1513,8 +1517,8 @@ packages:
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@testing-library/dom': ^10.0.0
|
||||
'@types/react': npm:types-react@19.0.0-rc.1
|
||||
'@types/react-dom': npm:types-react-dom@19.0.0-rc.1
|
||||
'@types/react': ^18.0.0
|
||||
'@types/react-dom': ^18.0.0
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
@@ -1553,11 +1557,13 @@ packages:
|
||||
'@types/node@22.9.0':
|
||||
resolution: {integrity: sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==}
|
||||
|
||||
'@types/prop-types@15.7.12':
|
||||
resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==}
|
||||
'@types/react-dom@19.0.2':
|
||||
resolution: {integrity: sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg==}
|
||||
peerDependencies:
|
||||
'@types/react': ^19.0.0
|
||||
|
||||
'@types/react@18.3.12':
|
||||
resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==}
|
||||
'@types/react@19.0.2':
|
||||
resolution: {integrity: sha512-USU8ZI/xyKJwFTpjSVIrSeHBVAGagkHQKPNbxeWwql/vDmnTIBgx+TJnhFnj1NXgz8XfprU0egV2dROLGpsBEg==}
|
||||
|
||||
'@types/sinonjs__fake-timers@8.1.1':
|
||||
resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==}
|
||||
@@ -3465,16 +3471,16 @@ packages:
|
||||
react: '*'
|
||||
react-dom: '*'
|
||||
|
||||
next@15.0.4-canary.23:
|
||||
resolution: {integrity: sha512-xCjjBx4csWdG4MP9tKV/C25OIDbN0o+zovMC5zd4yvE4lrd43Y5tt+w171IGUueb6VbPLTSlDaXvqOtrxKJXzQ==}
|
||||
next@15.2.0-canary.33:
|
||||
resolution: {integrity: sha512-WF8QLeYkakuYwksdWY/F+Bi8tNJfIbiSYk9hCmldn9sNp1lU3lqI1hrW1ynbcMSaXC+qQEr7yol2OdvVZ4nZYQ==}
|
||||
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.1.0
|
||||
'@playwright/test': ^1.41.2
|
||||
babel-plugin-react-compiler: '*'
|
||||
react: ^18.2.0 || 19.0.0-rc-380f5d67-20241113
|
||||
react-dom: ^18.2.0 || 19.0.0-rc-380f5d67-20241113
|
||||
react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
|
||||
react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
|
||||
sass: ^1.3.0
|
||||
peerDependenciesMeta:
|
||||
'@opentelemetry/api':
|
||||
@@ -3947,10 +3953,10 @@ packages:
|
||||
queue-microtask@1.2.3:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
|
||||
react-dom@19.0.0-rc-66855b96-20241106:
|
||||
resolution: {integrity: sha512-D25vdaytZ1wFIRiwNU98NPQ/upS2P8Co4/oNoa02PzHbh8deWdepjm5qwZM/46OdSiGv4WSWwxP55RO9obqJEQ==}
|
||||
react-dom@19.0.0:
|
||||
resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==}
|
||||
peerDependencies:
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
react: ^19.0.0
|
||||
|
||||
react-hook-form@7.39.5:
|
||||
resolution: {integrity: sha512-OE0HKyz5IPc6svN2wd+e+evidZrw4O4WZWAWYzQVZuHi+hYnHFSLnxOq0ddjbdmaLIsLHut/ab7j72y2QT3+KA==}
|
||||
@@ -3968,8 +3974,8 @@ packages:
|
||||
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
react@19.0.0-rc-66855b96-20241106:
|
||||
resolution: {integrity: sha512-klH7xkT71SxRCx4hb1hly5FJB21Hz0ACyxbXYAECEqssUjtJeFUAaI2U1DgJAzkGEnvEm3DkxuBchMC/9K4ipg==}
|
||||
react@19.0.0:
|
||||
resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
read-cache@1.0.0:
|
||||
@@ -4089,8 +4095,8 @@ packages:
|
||||
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
|
||||
engines: {node: '>=v12.22.7'}
|
||||
|
||||
scheduler@0.25.0-rc-66855b96-20241106:
|
||||
resolution: {integrity: sha512-HQXp/Mnp/MMRSXMQF7urNFla+gmtXW/Gr1KliuR0iboTit4KvZRY8KYaq5ccCTAOJiUqQh2rE2F3wgUekmgdlA==}
|
||||
scheduler@0.25.0:
|
||||
resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==}
|
||||
|
||||
semver@6.3.1:
|
||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||
@@ -4574,12 +4580,6 @@ packages:
|
||||
resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
types-react-dom@19.0.0-rc.1:
|
||||
resolution: {integrity: sha512-VSLZJl8VXCD0fAWp7DUTFUDCcZ8DVXOQmjhJMD03odgeFmu14ZQJHCXeETm3BEAhJqfgJaFkLnGkQv88sRx0fQ==}
|
||||
|
||||
types-react@19.0.0-rc.1:
|
||||
resolution: {integrity: sha512-RshndUfqTW6K3STLPis8BtAYCGOkMbtvYsi90gmVNDZBXUyUc5juf2PE9LfS/JmOlUIRO8cWTS/1MTnmhjDqyQ==}
|
||||
|
||||
typescript@5.6.3:
|
||||
resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
|
||||
engines: {node: '>=14.17'}
|
||||
@@ -5253,7 +5253,7 @@ snapshots:
|
||||
|
||||
'@emnapi/runtime@1.3.1':
|
||||
dependencies:
|
||||
tslib: 2.7.0
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@esbuild/aix-ppc64@0.21.5':
|
||||
@@ -5438,18 +5438,18 @@ snapshots:
|
||||
'@floating-ui/core': 1.6.8
|
||||
'@floating-ui/utils': 0.2.8
|
||||
|
||||
'@floating-ui/react-dom@2.1.2(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)':
|
||||
'@floating-ui/react-dom@2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.6.11
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106)
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
|
||||
'@floating-ui/react@0.26.24(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)':
|
||||
'@floating-ui/react@0.26.24(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@floating-ui/react-dom': 2.1.2(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)
|
||||
'@floating-ui/react-dom': 2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@floating-ui/utils': 0.2.8
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106)
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
tabbable: 6.2.0
|
||||
|
||||
'@floating-ui/utils@0.2.8': {}
|
||||
@@ -5501,18 +5501,18 @@ snapshots:
|
||||
dependencies:
|
||||
'@hapi/hoek': 9.3.0
|
||||
|
||||
'@headlessui/react@2.1.9(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)':
|
||||
'@headlessui/react@2.1.9(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@floating-ui/react': 0.26.24(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)
|
||||
'@react-aria/focus': 3.18.3(react@19.0.0-rc-66855b96-20241106)
|
||||
'@react-aria/interactions': 3.22.3(react@19.0.0-rc-66855b96-20241106)
|
||||
'@tanstack/react-virtual': 3.10.6(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106)
|
||||
'@floating-ui/react': 0.26.24(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@react-aria/focus': 3.18.3(react@19.0.0)
|
||||
'@react-aria/interactions': 3.22.3(react@19.0.0)
|
||||
'@tanstack/react-virtual': 3.10.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
|
||||
'@heroicons/react@2.1.3(react@19.0.0-rc-66855b96-20241106)':
|
||||
'@heroicons/react@2.1.3(react@19.0.0)':
|
||||
dependencies:
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
react: 19.0.0
|
||||
|
||||
'@humanwhocodes/config-array@0.13.0':
|
||||
dependencies:
|
||||
@@ -5660,34 +5660,34 @@ snapshots:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
'@next/env@15.0.4-canary.23': {}
|
||||
'@next/env@15.2.0-canary.33': {}
|
||||
|
||||
'@next/eslint-plugin-next@14.2.18':
|
||||
dependencies:
|
||||
glob: 10.3.10
|
||||
|
||||
'@next/swc-darwin-arm64@15.0.4-canary.23':
|
||||
'@next/swc-darwin-arm64@15.2.0-canary.33':
|
||||
optional: true
|
||||
|
||||
'@next/swc-darwin-x64@15.0.4-canary.23':
|
||||
'@next/swc-darwin-x64@15.2.0-canary.33':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-gnu@15.0.4-canary.23':
|
||||
'@next/swc-linux-arm64-gnu@15.2.0-canary.33':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-musl@15.0.4-canary.23':
|
||||
'@next/swc-linux-arm64-musl@15.2.0-canary.33':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-gnu@15.0.4-canary.23':
|
||||
'@next/swc-linux-x64-gnu@15.2.0-canary.33':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-musl@15.0.4-canary.23':
|
||||
'@next/swc-linux-x64-musl@15.2.0-canary.33':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-arm64-msvc@15.0.4-canary.23':
|
||||
'@next/swc-win32-arm64-msvc@15.2.0-canary.33':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-x64-msvc@15.0.4-canary.23':
|
||||
'@next/swc-win32-x64-msvc@15.2.0-canary.33':
|
||||
optional: true
|
||||
|
||||
'@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1':
|
||||
@@ -5810,45 +5810,45 @@ snapshots:
|
||||
|
||||
'@protobufjs/utf8@1.1.0': {}
|
||||
|
||||
'@react-aria/focus@3.18.3(react@19.0.0-rc-66855b96-20241106)':
|
||||
'@react-aria/focus@3.18.3(react@19.0.0)':
|
||||
dependencies:
|
||||
'@react-aria/interactions': 3.22.3(react@19.0.0-rc-66855b96-20241106)
|
||||
'@react-aria/utils': 3.25.3(react@19.0.0-rc-66855b96-20241106)
|
||||
'@react-types/shared': 3.25.0(react@19.0.0-rc-66855b96-20241106)
|
||||
'@react-aria/interactions': 3.22.3(react@19.0.0)
|
||||
'@react-aria/utils': 3.25.3(react@19.0.0)
|
||||
'@react-types/shared': 3.25.0(react@19.0.0)
|
||||
'@swc/helpers': 0.5.5
|
||||
clsx: 2.1.1
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
react: 19.0.0
|
||||
|
||||
'@react-aria/interactions@3.22.3(react@19.0.0-rc-66855b96-20241106)':
|
||||
'@react-aria/interactions@3.22.3(react@19.0.0)':
|
||||
dependencies:
|
||||
'@react-aria/ssr': 3.9.6(react@19.0.0-rc-66855b96-20241106)
|
||||
'@react-aria/utils': 3.25.3(react@19.0.0-rc-66855b96-20241106)
|
||||
'@react-types/shared': 3.25.0(react@19.0.0-rc-66855b96-20241106)
|
||||
'@react-aria/ssr': 3.9.6(react@19.0.0)
|
||||
'@react-aria/utils': 3.25.3(react@19.0.0)
|
||||
'@react-types/shared': 3.25.0(react@19.0.0)
|
||||
'@swc/helpers': 0.5.5
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
react: 19.0.0
|
||||
|
||||
'@react-aria/ssr@3.9.6(react@19.0.0-rc-66855b96-20241106)':
|
||||
'@react-aria/ssr@3.9.6(react@19.0.0)':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.5
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
react: 19.0.0
|
||||
|
||||
'@react-aria/utils@3.25.3(react@19.0.0-rc-66855b96-20241106)':
|
||||
'@react-aria/utils@3.25.3(react@19.0.0)':
|
||||
dependencies:
|
||||
'@react-aria/ssr': 3.9.6(react@19.0.0-rc-66855b96-20241106)
|
||||
'@react-stately/utils': 3.10.4(react@19.0.0-rc-66855b96-20241106)
|
||||
'@react-types/shared': 3.25.0(react@19.0.0-rc-66855b96-20241106)
|
||||
'@react-aria/ssr': 3.9.6(react@19.0.0)
|
||||
'@react-stately/utils': 3.10.4(react@19.0.0)
|
||||
'@react-types/shared': 3.25.0(react@19.0.0)
|
||||
'@swc/helpers': 0.5.5
|
||||
clsx: 2.1.1
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
react: 19.0.0
|
||||
|
||||
'@react-stately/utils@3.10.4(react@19.0.0-rc-66855b96-20241106)':
|
||||
'@react-stately/utils@3.10.4(react@19.0.0)':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.13
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
react: 19.0.0
|
||||
|
||||
'@react-types/shared@3.25.0(react@19.0.0-rc-66855b96-20241106)':
|
||||
'@react-types/shared@3.25.0(react@19.0.0)':
|
||||
dependencies:
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
react: 19.0.0
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.25.0':
|
||||
optional: true
|
||||
@@ -5922,6 +5922,10 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 2.7.0
|
||||
|
||||
'@swc/helpers@0.5.15':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@swc/helpers@0.5.5':
|
||||
dependencies:
|
||||
'@swc/counter': 0.1.3
|
||||
@@ -5937,11 +5941,11 @@ snapshots:
|
||||
mini-svg-data-uri: 1.4.4
|
||||
tailwindcss: 3.4.14
|
||||
|
||||
'@tanstack/react-virtual@3.10.6(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)':
|
||||
'@tanstack/react-virtual@3.10.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@tanstack/virtual-core': 3.10.6
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106)
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
|
||||
'@tanstack/virtual-core@3.10.6': {}
|
||||
|
||||
@@ -5966,15 +5970,15 @@ snapshots:
|
||||
lodash: 4.17.21
|
||||
redent: 3.0.0
|
||||
|
||||
'@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1)':
|
||||
'@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.25.6
|
||||
'@testing-library/dom': 10.4.0
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106)
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
optionalDependencies:
|
||||
'@types/react': types-react@19.0.0-rc.1
|
||||
'@types/react-dom': types-react-dom@19.0.0-rc.1
|
||||
'@types/react': 19.0.2
|
||||
'@types/react-dom': 19.0.2(@types/react@19.0.2)
|
||||
|
||||
'@types/aria-query@5.0.4': {}
|
||||
|
||||
@@ -6011,11 +6015,12 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 6.19.8
|
||||
|
||||
'@types/prop-types@15.7.12': {}
|
||||
|
||||
'@types/react@18.3.12':
|
||||
'@types/react-dom@19.0.2(@types/react@19.0.2)':
|
||||
dependencies:
|
||||
'@types/react': 19.0.2
|
||||
|
||||
'@types/react@19.0.2':
|
||||
dependencies:
|
||||
'@types/prop-types': 15.7.12
|
||||
csstype: 3.1.3
|
||||
|
||||
'@types/sinonjs__fake-timers@8.1.1': {}
|
||||
@@ -6142,12 +6147,12 @@ snapshots:
|
||||
|
||||
'@ungap/structured-clone@1.2.0': {}
|
||||
|
||||
'@vercel/analytics@1.3.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106)':
|
||||
'@vercel/analytics@1.3.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0)':
|
||||
dependencies:
|
||||
server-only: 0.0.1
|
||||
optionalDependencies:
|
||||
next: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7)
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
next: 15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7)
|
||||
react: 19.0.0
|
||||
|
||||
'@vercel/git-hooks@1.0.0': {}
|
||||
|
||||
@@ -8212,40 +8217,40 @@ snapshots:
|
||||
|
||||
negotiator@1.0.0: {}
|
||||
|
||||
next-intl@3.25.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react@19.0.0-rc-66855b96-20241106):
|
||||
next-intl@3.25.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0):
|
||||
dependencies:
|
||||
'@formatjs/intl-localematcher': 0.5.4
|
||||
negotiator: 1.0.0
|
||||
next: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7)
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
use-intl: 3.25.1(react@19.0.0-rc-66855b96-20241106)
|
||||
next: 15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7)
|
||||
react: 19.0.0
|
||||
use-intl: 3.25.1(react@19.0.0)
|
||||
|
||||
next-themes@0.2.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7))(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106):
|
||||
next-themes@0.2.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
dependencies:
|
||||
next: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7)
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106)
|
||||
next: 15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7)
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
|
||||
next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(sass@1.80.7):
|
||||
next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7):
|
||||
dependencies:
|
||||
'@next/env': 15.0.4-canary.23
|
||||
'@next/env': 15.2.0-canary.33
|
||||
'@swc/counter': 0.1.3
|
||||
'@swc/helpers': 0.5.13
|
||||
'@swc/helpers': 0.5.15
|
||||
busboy: 1.6.0
|
||||
caniuse-lite: 1.0.30001680
|
||||
postcss: 8.4.31
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106)
|
||||
styled-jsx: 5.1.6(@babel/core@7.26.0)(react@19.0.0-rc-66855b96-20241106)
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
styled-jsx: 5.1.6(@babel/core@7.26.0)(react@19.0.0)
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 15.0.4-canary.23
|
||||
'@next/swc-darwin-x64': 15.0.4-canary.23
|
||||
'@next/swc-linux-arm64-gnu': 15.0.4-canary.23
|
||||
'@next/swc-linux-arm64-musl': 15.0.4-canary.23
|
||||
'@next/swc-linux-x64-gnu': 15.0.4-canary.23
|
||||
'@next/swc-linux-x64-musl': 15.0.4-canary.23
|
||||
'@next/swc-win32-arm64-msvc': 15.0.4-canary.23
|
||||
'@next/swc-win32-x64-msvc': 15.0.4-canary.23
|
||||
'@next/swc-darwin-arm64': 15.2.0-canary.33
|
||||
'@next/swc-darwin-x64': 15.2.0-canary.33
|
||||
'@next/swc-linux-arm64-gnu': 15.2.0-canary.33
|
||||
'@next/swc-linux-arm64-musl': 15.2.0-canary.33
|
||||
'@next/swc-linux-x64-gnu': 15.2.0-canary.33
|
||||
'@next/swc-linux-x64-musl': 15.2.0-canary.33
|
||||
'@next/swc-win32-arm64-msvc': 15.2.0-canary.33
|
||||
'@next/swc-win32-x64-msvc': 15.2.0-canary.33
|
||||
'@playwright/test': 1.48.2
|
||||
sass: 1.80.7
|
||||
sharp: 0.33.5
|
||||
@@ -8603,9 +8608,9 @@ snapshots:
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
qrcode.react@3.1.0(react@19.0.0-rc-66855b96-20241106):
|
||||
qrcode.react@3.1.0(react@19.0.0):
|
||||
dependencies:
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
react: 19.0.0
|
||||
|
||||
qs@6.13.0:
|
||||
dependencies:
|
||||
@@ -8613,14 +8618,14 @@ snapshots:
|
||||
|
||||
queue-microtask@1.2.3: {}
|
||||
|
||||
react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106):
|
||||
react-dom@19.0.0(react@19.0.0):
|
||||
dependencies:
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
scheduler: 0.25.0-rc-66855b96-20241106
|
||||
react: 19.0.0
|
||||
scheduler: 0.25.0
|
||||
|
||||
react-hook-form@7.39.5(react@19.0.0-rc-66855b96-20241106):
|
||||
react-hook-form@7.39.5(react@19.0.0):
|
||||
dependencies:
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
react: 19.0.0
|
||||
|
||||
react-is@16.13.1: {}
|
||||
|
||||
@@ -8628,7 +8633,7 @@ snapshots:
|
||||
|
||||
react-refresh@0.14.2: {}
|
||||
|
||||
react@19.0.0-rc-66855b96-20241106: {}
|
||||
react@19.0.0: {}
|
||||
|
||||
read-cache@1.0.0:
|
||||
dependencies:
|
||||
@@ -8782,7 +8787,7 @@ snapshots:
|
||||
dependencies:
|
||||
xmlchars: 2.2.0
|
||||
|
||||
scheduler@0.25.0-rc-66855b96-20241106: {}
|
||||
scheduler@0.25.0: {}
|
||||
|
||||
semver@6.3.1: {}
|
||||
|
||||
@@ -9041,10 +9046,10 @@ snapshots:
|
||||
|
||||
strip-json-comments@3.1.1: {}
|
||||
|
||||
styled-jsx@5.1.6(@babel/core@7.26.0)(react@19.0.0-rc-66855b96-20241106):
|
||||
styled-jsx@5.1.6(@babel/core@7.26.0)(react@19.0.0):
|
||||
dependencies:
|
||||
client-only: 0.0.1
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
react: 19.0.0
|
||||
optionalDependencies:
|
||||
'@babel/core': 7.26.0
|
||||
|
||||
@@ -9072,11 +9077,11 @@ snapshots:
|
||||
|
||||
supports-preserve-symlinks-flag@1.0.0: {}
|
||||
|
||||
swr@2.2.5(react@19.0.0-rc-66855b96-20241106):
|
||||
swr@2.2.5(react@19.0.0):
|
||||
dependencies:
|
||||
client-only: 0.0.1
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
use-sync-external-store: 1.2.2(react@19.0.0-rc-66855b96-20241106)
|
||||
react: 19.0.0
|
||||
use-sync-external-store: 1.2.2(react@19.0.0)
|
||||
|
||||
symbol-tree@3.2.4: {}
|
||||
|
||||
@@ -9329,14 +9334,6 @@ snapshots:
|
||||
is-typed-array: 1.1.13
|
||||
possible-typed-array-names: 1.0.0
|
||||
|
||||
types-react-dom@19.0.0-rc.1:
|
||||
dependencies:
|
||||
'@types/react': 18.3.12
|
||||
|
||||
types-react@19.0.0-rc.1:
|
||||
dependencies:
|
||||
csstype: 3.1.3
|
||||
|
||||
typescript@5.6.3: {}
|
||||
|
||||
unbox-primitive@1.0.2:
|
||||
@@ -9374,15 +9371,15 @@ snapshots:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
use-intl@3.25.1(react@19.0.0-rc-66855b96-20241106):
|
||||
use-intl@3.25.1(react@19.0.0):
|
||||
dependencies:
|
||||
'@formatjs/fast-memoize': 2.2.3
|
||||
intl-messageformat: 10.7.7
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
react: 19.0.0
|
||||
|
||||
use-sync-external-store@1.2.2(react@19.0.0-rc-66855b96-20241106):
|
||||
use-sync-external-store@1.2.2(react@19.0.0):
|
||||
dependencies:
|
||||
react: 19.0.0-rc-66855b96-20241106
|
||||
react: 19.0.0
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
|
19
turbo.json
19
turbo.json
@@ -4,16 +4,21 @@
|
||||
"globalDependencies": ["**/.env.*local"],
|
||||
"globalEnv": [
|
||||
"DEBUG",
|
||||
"VERCEL_URL",
|
||||
"EMAIL_VERIFICATION",
|
||||
"EU1_AUDIENCE",
|
||||
"EU1_SYSTEM_USER_ID",
|
||||
"EU1_SYSTEM_USER_PRIVATE_KEY",
|
||||
"US1_AUDIENCE",
|
||||
"US1_SYSTEM_USER_ID",
|
||||
"US1_SYSTEM_USER_PRIVATE_KEY",
|
||||
"AUDIENCE",
|
||||
"SYSTEM_USER_ID",
|
||||
"SYSTEM_USER_PRIVATE_KEY",
|
||||
"ZITADEL_API_URL",
|
||||
"ZITADEL_SERVICE_USER_ID",
|
||||
"ZITADEL_SERVICE_USER_TOKEN",
|
||||
"ZITADEL_SYSTEM_API_URL",
|
||||
"ZITADEL_SYSTEM_API_USERID",
|
||||
"ZITADEL_SYSTEM_API_KEY",
|
||||
"ZITADEL_ISSUER",
|
||||
"ZITADEL_ADMIN_TOKEN",
|
||||
"EMAIL_VERIFICATION",
|
||||
"VERCEL_URL"
|
||||
"NEXT_PUBLIC_BASE_PATH"
|
||||
],
|
||||
"tasks": {
|
||||
"generate": {
|
||||
|
Reference in New Issue
Block a user