mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-13 10:57:32 +00:00
Merge pull request #445 from zitadel/qa
Promote qa to prod: iframe options, fix middleware for edge runtime
This commit is contained in:
3
.github/workflows/docker.yml
vendored
3
.github/workflows/docker.yml
vendored
@@ -4,6 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- qa
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@@ -41,7 +42,7 @@ jobs:
|
|||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
with:
|
with:
|
||||||
driver-opts: 'image=moby/buildkit:v0.11.6'
|
driver: docker-container
|
||||||
|
|
||||||
- name: Login Public
|
- name: Login Public
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
|
2
apps/login/constants/csp.js
Normal file
2
apps/login/constants/csp.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export const DEFAULT_CSP =
|
||||||
|
"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://va.vercel-scripts.com; connect-src 'self'; child-src; style-src 'self' 'unsafe-inline'; font-src 'self'; object-src 'none'; img-src 'self' https://vercel.com;";
|
@@ -6,6 +6,13 @@
|
|||||||
"data": {}
|
"data": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"service": "zitadel.settings.v2.SettingsService",
|
||||||
|
"method": "GetSecuritySettings",
|
||||||
|
"out": {
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"service": "zitadel.settings.v2.SettingsService",
|
"service": "zitadel.settings.v2.SettingsService",
|
||||||
"method": "GetLegalAndSupportSettings",
|
"method": "GetLegalAndSupportSettings",
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import createNextIntlPlugin from "next-intl/plugin";
|
import createNextIntlPlugin from "next-intl/plugin";
|
||||||
|
import { DEFAULT_CSP } from "./constants/csp.js";
|
||||||
|
|
||||||
const withNextIntl = createNextIntlPlugin();
|
const withNextIntl = createNextIntlPlugin();
|
||||||
|
|
||||||
@@ -29,9 +30,9 @@ const secureHeaders = [
|
|||||||
// script-src va.vercel-scripts.com for analytics/vercel scripts
|
// script-src va.vercel-scripts.com for analytics/vercel scripts
|
||||||
{
|
{
|
||||||
key: "Content-Security-Policy",
|
key: "Content-Security-Policy",
|
||||||
value:
|
value: `${DEFAULT_CSP} frame-ancestors 'none'`,
|
||||||
"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://va.vercel-scripts.com; connect-src 'self'; child-src; style-src 'self' 'unsafe-inline'; font-src 'self'; object-src 'none'; img-src 'self' https://vercel.com;",
|
|
||||||
},
|
},
|
||||||
|
{ key: "X-Frame-Options", value: "deny" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const imageRemotePatterns = [
|
const imageRemotePatterns = [
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
import { SessionsList } from "@/components/sessions-list";
|
import { SessionsList } from "@/components/sessions-list";
|
||||||
import { getAllSessionCookieIds } from "@/lib/cookies";
|
import { getAllSessionCookieIds } from "@/lib/cookies";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import {
|
import {
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
getDefaultOrg,
|
getDefaultOrg,
|
||||||
|
@@ -5,7 +5,7 @@ import { DynamicTheme } from "@/components/dynamic-theme";
|
|||||||
import { SignInWithIdp } from "@/components/sign-in-with-idp";
|
import { SignInWithIdp } from "@/components/sign-in-with-idp";
|
||||||
import { UserAvatar } from "@/components/user-avatar";
|
import { UserAvatar } from "@/components/user-avatar";
|
||||||
import { getSessionCookieById } from "@/lib/cookies";
|
import { getSessionCookieById } from "@/lib/cookies";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import {
|
import {
|
||||||
getActiveIdentityProviders,
|
getActiveIdentityProviders,
|
||||||
@@ -33,8 +33,8 @@ export default async function Page(props: {
|
|||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
|
|
||||||
const sessionWithData = sessionId
|
const sessionWithData = sessionId
|
||||||
? await loadSessionById(serviceUrl, sessionId, organization)
|
? await loadSessionById(sessionId, organization)
|
||||||
: await loadSessionByLoginname(serviceUrl, loginName, organization);
|
: await loadSessionByLoginname(loginName, organization);
|
||||||
|
|
||||||
async function getAuthMethodsAndUser(
|
async function getAuthMethodsAndUser(
|
||||||
serviceUrl: string,
|
serviceUrl: string,
|
||||||
@@ -67,7 +67,6 @@ export default async function Page(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadSessionByLoginname(
|
async function loadSessionByLoginname(
|
||||||
host: string,
|
|
||||||
loginName?: string,
|
loginName?: string,
|
||||||
organization?: string,
|
organization?: string,
|
||||||
) {
|
) {
|
||||||
@@ -82,11 +81,7 @@ export default async function Page(props: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadSessionById(
|
async function loadSessionById(sessionId: string, organization?: string) {
|
||||||
host: string,
|
|
||||||
sessionId: string,
|
|
||||||
organization?: string,
|
|
||||||
) {
|
|
||||||
const recent = await getSessionCookieById({ sessionId, organization });
|
const recent = await getSessionCookieById({ sessionId, organization });
|
||||||
return getSession({
|
return getSession({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { ConsentScreen } from "@/components/consent";
|
import { ConsentScreen } from "@/components/consent";
|
||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import {
|
import {
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
getDefaultOrg,
|
getDefaultOrg,
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { DeviceCodeForm } from "@/components/device-code-form";
|
import { DeviceCodeForm } from "@/components/device-code-form";
|
||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { getBrandingSettings, getDefaultOrg } from "@/lib/zitadel";
|
import { getBrandingSettings, getDefaultOrg } from "@/lib/zitadel";
|
||||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||||
import { getLocale, getTranslations } from "next-intl/server";
|
import { getLocale, getTranslations } from "next-intl/server";
|
||||||
|
@@ -2,7 +2,7 @@ import { Alert, AlertType } from "@/components/alert";
|
|||||||
import { ChooseAuthenticatorToLogin } from "@/components/choose-authenticator-to-login";
|
import { ChooseAuthenticatorToLogin } from "@/components/choose-authenticator-to-login";
|
||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
import { UserAvatar } from "@/components/user-avatar";
|
import { UserAvatar } from "@/components/user-avatar";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import {
|
import {
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
getLoginSettings,
|
getLoginSettings,
|
||||||
|
@@ -4,8 +4,7 @@ import { linkingFailed } from "@/components/idps/pages/linking-failed";
|
|||||||
import { linkingSuccess } from "@/components/idps/pages/linking-success";
|
import { linkingSuccess } from "@/components/idps/pages/linking-success";
|
||||||
import { loginFailed } from "@/components/idps/pages/login-failed";
|
import { loginFailed } from "@/components/idps/pages/login-failed";
|
||||||
import { loginSuccess } from "@/components/idps/pages/login-success";
|
import { loginSuccess } from "@/components/idps/pages/login-success";
|
||||||
import { idpTypeToIdentityProviderType } from "@/lib/idp";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
|
||||||
import {
|
import {
|
||||||
addHuman,
|
addHuman,
|
||||||
addIDPLink,
|
addIDPLink,
|
||||||
@@ -16,10 +15,13 @@ import {
|
|||||||
listUsers,
|
listUsers,
|
||||||
retrieveIDPIntent,
|
retrieveIDPIntent,
|
||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
import { create } from "@zitadel/client";
|
import { ConnectError, create } from "@zitadel/client";
|
||||||
import { AutoLinkingOption } from "@zitadel/proto/zitadel/idp/v2/idp_pb";
|
import { AutoLinkingOption } from "@zitadel/proto/zitadel/idp/v2/idp_pb";
|
||||||
import { OrganizationSchema } from "@zitadel/proto/zitadel/object/v2/object_pb";
|
import { OrganizationSchema } from "@zitadel/proto/zitadel/object/v2/object_pb";
|
||||||
import { AddHumanUserRequestSchema } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
import {
|
||||||
|
AddHumanUserRequest,
|
||||||
|
AddHumanUserRequestSchema,
|
||||||
|
} from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||||
import { getLocale, getTranslations } from "next-intl/server";
|
import { getLocale, getTranslations } from "next-intl/server";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
|
|
||||||
@@ -83,8 +85,6 @@ export default async function Page(props: {
|
|||||||
throw new Error("IDP not found");
|
throw new Error("IDP not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const providerType = idpTypeToIdentityProviderType(idp.type);
|
|
||||||
|
|
||||||
if (link) {
|
if (link) {
|
||||||
if (!options?.isLinkingAllowed) {
|
if (!options?.isLinkingAllowed) {
|
||||||
// linking was probably disallowed since the invitation was created
|
// linking was probably disallowed since the invitation was created
|
||||||
@@ -205,20 +205,42 @@ export default async function Page(props: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addHumanUser && orgToRegisterOn) {
|
if (addHumanUser) {
|
||||||
const organizationSchema = create(OrganizationSchema, {
|
let addHumanUserWithOrganization: AddHumanUserRequest;
|
||||||
org: { case: "orgId", value: orgToRegisterOn },
|
if (orgToRegisterOn) {
|
||||||
});
|
const organizationSchema = create(OrganizationSchema, {
|
||||||
|
org: { case: "orgId", value: orgToRegisterOn },
|
||||||
|
});
|
||||||
|
|
||||||
const addHumanUserWithOrganization = create(AddHumanUserRequestSchema, {
|
addHumanUserWithOrganization = create(AddHumanUserRequestSchema, {
|
||||||
...addHumanUser,
|
...addHumanUser,
|
||||||
organization: organizationSchema,
|
organization: organizationSchema,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
addHumanUserWithOrganization = create(
|
||||||
|
AddHumanUserRequestSchema,
|
||||||
|
addHumanUser,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
newUser = await addHuman({
|
try {
|
||||||
serviceUrl,
|
newUser = await addHuman({
|
||||||
request: addHumanUserWithOrganization,
|
serviceUrl,
|
||||||
});
|
request: addHumanUserWithOrganization,
|
||||||
|
});
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error(
|
||||||
|
"An error occurred while creating the user:",
|
||||||
|
error,
|
||||||
|
addHumanUser,
|
||||||
|
);
|
||||||
|
return loginFailed(
|
||||||
|
branding,
|
||||||
|
(error as ConnectError).message
|
||||||
|
? (error as ConnectError).message
|
||||||
|
: "Could not create user",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newUser) {
|
if (newUser) {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
import { SignInWithIdp } from "@/components/sign-in-with-idp";
|
import { SignInWithIdp } from "@/components/sign-in-with-idp";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { getActiveIdentityProviders, getBrandingSettings } from "@/lib/zitadel";
|
import { getActiveIdentityProviders, getBrandingSettings } from "@/lib/zitadel";
|
||||||
import { getLocale, getTranslations } from "next-intl/server";
|
import { getLocale, getTranslations } from "next-intl/server";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { Alert, AlertType } from "@/components/alert";
|
import { Alert, AlertType } from "@/components/alert";
|
||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
import { InviteForm } from "@/components/invite-form";
|
import { InviteForm } from "@/components/invite-form";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import {
|
import {
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
getDefaultOrg,
|
getDefaultOrg,
|
||||||
|
@@ -2,7 +2,7 @@ import { Alert, AlertType } from "@/components/alert";
|
|||||||
import { Button, ButtonVariants } from "@/components/button";
|
import { Button, ButtonVariants } from "@/components/button";
|
||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
import { UserAvatar } from "@/components/user-avatar";
|
import { UserAvatar } from "@/components/user-avatar";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { getBrandingSettings, getDefaultOrg, getUserByID } from "@/lib/zitadel";
|
import { getBrandingSettings, getDefaultOrg, getUserByID } from "@/lib/zitadel";
|
||||||
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||||
import { getLocale, getTranslations } from "next-intl/server";
|
import { getLocale, getTranslations } from "next-intl/server";
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
import { SignInWithIdp } from "@/components/sign-in-with-idp";
|
import { SignInWithIdp } from "@/components/sign-in-with-idp";
|
||||||
import { UsernameForm } from "@/components/username-form";
|
import { UsernameForm } from "@/components/username-form";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import {
|
import {
|
||||||
getActiveIdentityProviders,
|
getActiveIdentityProviders,
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
|
@@ -4,7 +4,7 @@ import { ChooseSecondFactor } from "@/components/choose-second-factor";
|
|||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
import { UserAvatar } from "@/components/user-avatar";
|
import { UserAvatar } from "@/components/user-avatar";
|
||||||
import { getSessionCookieById } from "@/lib/cookies";
|
import { getSessionCookieById } from "@/lib/cookies";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import {
|
import {
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
|
@@ -4,7 +4,7 @@ import { ChooseSecondFactorToSetup } from "@/components/choose-second-factor-to-
|
|||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
import { UserAvatar } from "@/components/user-avatar";
|
import { UserAvatar } from "@/components/user-avatar";
|
||||||
import { getSessionCookieById } from "@/lib/cookies";
|
import { getSessionCookieById } from "@/lib/cookies";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import {
|
import {
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
|
@@ -3,7 +3,7 @@ import { DynamicTheme } from "@/components/dynamic-theme";
|
|||||||
import { LoginOTP } from "@/components/login-otp";
|
import { LoginOTP } from "@/components/login-otp";
|
||||||
import { UserAvatar } from "@/components/user-avatar";
|
import { UserAvatar } from "@/components/user-avatar";
|
||||||
import { getSessionCookieById } from "@/lib/cookies";
|
import { getSessionCookieById } from "@/lib/cookies";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import {
|
import {
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
|
@@ -4,7 +4,7 @@ import { Button, ButtonVariants } from "@/components/button";
|
|||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
import { TotpRegister } from "@/components/totp-register";
|
import { TotpRegister } from "@/components/totp-register";
|
||||||
import { UserAvatar } from "@/components/user-avatar";
|
import { UserAvatar } from "@/components/user-avatar";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import {
|
import {
|
||||||
addOTPEmail,
|
addOTPEmail,
|
||||||
|
@@ -3,7 +3,7 @@ import { DynamicTheme } from "@/components/dynamic-theme";
|
|||||||
import { LoginPasskey } from "@/components/login-passkey";
|
import { LoginPasskey } from "@/components/login-passkey";
|
||||||
import { UserAvatar } from "@/components/user-avatar";
|
import { UserAvatar } from "@/components/user-avatar";
|
||||||
import { getSessionCookieById } from "@/lib/cookies";
|
import { getSessionCookieById } from "@/lib/cookies";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import { getBrandingSettings, getSession } from "@/lib/zitadel";
|
import { getBrandingSettings, getSession } from "@/lib/zitadel";
|
||||||
import { getLocale, getTranslations } from "next-intl/server";
|
import { getLocale, getTranslations } from "next-intl/server";
|
||||||
|
@@ -2,7 +2,7 @@ import { Alert, AlertType } from "@/components/alert";
|
|||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
import { RegisterPasskey } from "@/components/register-passkey";
|
import { RegisterPasskey } from "@/components/register-passkey";
|
||||||
import { UserAvatar } from "@/components/user-avatar";
|
import { UserAvatar } from "@/components/user-avatar";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import { getBrandingSettings } from "@/lib/zitadel";
|
import { getBrandingSettings } from "@/lib/zitadel";
|
||||||
import { getLocale, getTranslations } from "next-intl/server";
|
import { getLocale, getTranslations } from "next-intl/server";
|
||||||
|
@@ -2,7 +2,7 @@ import { Alert } from "@/components/alert";
|
|||||||
import { ChangePasswordForm } from "@/components/change-password-form";
|
import { ChangePasswordForm } from "@/components/change-password-form";
|
||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
import { UserAvatar } from "@/components/user-avatar";
|
import { UserAvatar } from "@/components/user-avatar";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import {
|
import {
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
|
@@ -2,7 +2,7 @@ import { Alert } from "@/components/alert";
|
|||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
import { PasswordForm } from "@/components/password-form";
|
import { PasswordForm } from "@/components/password-form";
|
||||||
import { UserAvatar } from "@/components/user-avatar";
|
import { UserAvatar } from "@/components/user-avatar";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import {
|
import {
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
|
@@ -2,7 +2,7 @@ import { Alert, AlertType } from "@/components/alert";
|
|||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
import { SetPasswordForm } from "@/components/set-password-form";
|
import { SetPasswordForm } from "@/components/set-password-form";
|
||||||
import { UserAvatar } from "@/components/user-avatar";
|
import { UserAvatar } from "@/components/user-avatar";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import {
|
import {
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
import { RegisterForm } from "@/components/register-form";
|
import { RegisterForm } from "@/components/register-form";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import {
|
import {
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
getDefaultOrg,
|
getDefaultOrg,
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
import { SetRegisterPasswordForm } from "@/components/set-register-password-form";
|
import { SetRegisterPasswordForm } from "@/components/set-register-password-form";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import {
|
import {
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
getDefaultOrg,
|
getDefaultOrg,
|
||||||
|
@@ -7,7 +7,7 @@ import {
|
|||||||
getSessionCookieById,
|
getSessionCookieById,
|
||||||
} from "@/lib/cookies";
|
} from "@/lib/cookies";
|
||||||
import { completeDeviceAuthorization } from "@/lib/server/device";
|
import { completeDeviceAuthorization } from "@/lib/server/device";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import {
|
import {
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
|
@@ -3,7 +3,7 @@ import { DynamicTheme } from "@/components/dynamic-theme";
|
|||||||
import { LoginPasskey } from "@/components/login-passkey";
|
import { LoginPasskey } from "@/components/login-passkey";
|
||||||
import { UserAvatar } from "@/components/user-avatar";
|
import { UserAvatar } from "@/components/user-avatar";
|
||||||
import { getSessionCookieById } from "@/lib/cookies";
|
import { getSessionCookieById } from "@/lib/cookies";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import { getBrandingSettings, getSession } from "@/lib/zitadel";
|
import { getBrandingSettings, getSession } from "@/lib/zitadel";
|
||||||
import { getLocale, getTranslations } from "next-intl/server";
|
import { getLocale, getTranslations } from "next-intl/server";
|
||||||
|
@@ -2,7 +2,7 @@ import { Alert } from "@/components/alert";
|
|||||||
import { DynamicTheme } from "@/components/dynamic-theme";
|
import { DynamicTheme } from "@/components/dynamic-theme";
|
||||||
import { RegisterU2f } from "@/components/register-u2f";
|
import { RegisterU2f } from "@/components/register-u2f";
|
||||||
import { UserAvatar } from "@/components/user-avatar";
|
import { UserAvatar } from "@/components/user-avatar";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import { getBrandingSettings } from "@/lib/zitadel";
|
import { getBrandingSettings } from "@/lib/zitadel";
|
||||||
import { getLocale, getTranslations } from "next-intl/server";
|
import { getLocale, getTranslations } from "next-intl/server";
|
||||||
|
@@ -4,7 +4,7 @@ import { UserAvatar } from "@/components/user-avatar";
|
|||||||
import { VerifyForm } from "@/components/verify-form";
|
import { VerifyForm } from "@/components/verify-form";
|
||||||
import { VerifyRedirectButton } from "@/components/verify-redirect-button";
|
import { VerifyRedirectButton } from "@/components/verify-redirect-button";
|
||||||
import { sendEmailCode } from "@/lib/server/verify";
|
import { sendEmailCode } from "@/lib/server/verify";
|
||||||
import { getServiceUrlFromHeaders } from "@/lib/service";
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { loadMostRecentSession } from "@/lib/session";
|
import { loadMostRecentSession } from "@/lib/session";
|
||||||
import {
|
import {
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
|
@@ -3,7 +3,7 @@ import { idpTypeToSlug } from "@/lib/idp";
|
|||||||
import { loginWithOIDCAndSession } from "@/lib/oidc";
|
import { loginWithOIDCAndSession } from "@/lib/oidc";
|
||||||
import { loginWithSAMLAndSession } from "@/lib/saml";
|
import { loginWithSAMLAndSession } from "@/lib/saml";
|
||||||
import { sendLoginname, SendLoginnameCommand } from "@/lib/server/loginname";
|
import { sendLoginname, SendLoginnameCommand } from "@/lib/server/loginname";
|
||||||
import { constructUrl, getServiceUrlFromHeaders } from "@/lib/service";
|
import { constructUrl, getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
import { findValidSession } from "@/lib/session";
|
import { findValidSession } from "@/lib/session";
|
||||||
import {
|
import {
|
||||||
createCallback,
|
createCallback,
|
||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
getAuthRequest,
|
getAuthRequest,
|
||||||
getOrgsByDomain,
|
getOrgsByDomain,
|
||||||
getSAMLRequest,
|
getSAMLRequest,
|
||||||
|
getSecuritySettings,
|
||||||
listSessions,
|
listSessions,
|
||||||
startIdentityProviderFlow,
|
startIdentityProviderFlow,
|
||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
@@ -25,6 +26,7 @@ import { CreateResponseRequestSchema } from "@zitadel/proto/zitadel/saml/v2/saml
|
|||||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { DEFAULT_CSP } from "../../../constants/csp";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
export const revalidate = false;
|
export const revalidate = false;
|
||||||
@@ -293,17 +295,32 @@ export async function GET(request: NextRequest) {
|
|||||||
* This means that the user should not be prompted to enter their password again.
|
* 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
|
* Instead, the server attempts to silently authenticate the user using an existing session or other authentication mechanisms that do not require user interaction
|
||||||
**/
|
**/
|
||||||
|
const securitySettings = await getSecuritySettings({
|
||||||
|
serviceUrl,
|
||||||
|
});
|
||||||
|
|
||||||
const selectedSession = await findValidSession({
|
const selectedSession = await findValidSession({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
sessions,
|
sessions,
|
||||||
authRequest,
|
authRequest,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!selectedSession || !selectedSession.id) {
|
const noSessionResponse = NextResponse.json(
|
||||||
return NextResponse.json(
|
{ error: "No active session found" },
|
||||||
{ error: "No active session found" },
|
{ status: 400 },
|
||||||
{ status: 400 },
|
);
|
||||||
|
|
||||||
|
if (securitySettings?.embeddedIframe?.enabled) {
|
||||||
|
securitySettings.embeddedIframe.allowedOrigins;
|
||||||
|
noSessionResponse.headers.set(
|
||||||
|
"Content-Security-Policy",
|
||||||
|
`${DEFAULT_CSP} frame-ancestors ${securitySettings.embeddedIframe.allowedOrigins.join(" ")};`,
|
||||||
);
|
);
|
||||||
|
noSessionResponse.headers.delete("X-Frame-Options");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedSession || !selectedSession.id) {
|
||||||
|
return noSessionResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cookie = sessionCookies.find(
|
const cookie = sessionCookies.find(
|
||||||
@@ -311,10 +328,7 @@ export async function GET(request: NextRequest) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!cookie || !cookie.id || !cookie.token) {
|
if (!cookie || !cookie.id || !cookie.token) {
|
||||||
return NextResponse.json(
|
return noSessionResponse;
|
||||||
{ error: "No active session found" },
|
|
||||||
{ status: 400 },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = {
|
const session = {
|
||||||
@@ -332,7 +346,19 @@ export async function GET(request: NextRequest) {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
return NextResponse.redirect(callbackUrl);
|
|
||||||
|
const callbackResponse = NextResponse.redirect(callbackUrl);
|
||||||
|
|
||||||
|
if (securitySettings?.embeddedIframe?.enabled) {
|
||||||
|
securitySettings.embeddedIframe.allowedOrigins;
|
||||||
|
callbackResponse.headers.set(
|
||||||
|
"Content-Security-Policy",
|
||||||
|
`${DEFAULT_CSP} frame-ancestors ${securitySettings.embeddedIframe.allowedOrigins.join(" ")};`,
|
||||||
|
);
|
||||||
|
callbackResponse.headers.delete("X-Frame-Options");
|
||||||
|
}
|
||||||
|
|
||||||
|
return callbackResponse;
|
||||||
} else {
|
} else {
|
||||||
// check for loginHint, userId hint and valid sessions
|
// check for loginHint, userId hint and valid sessions
|
||||||
let selectedSession = await findValidSession({
|
let selectedSession = await findValidSession({
|
||||||
|
28
apps/login/src/app/security/route.ts
Normal file
28
apps/login/src/app/security/route.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { createServiceForHost } from "@/lib/service";
|
||||||
|
import { getServiceUrlFromHeaders } from "@/lib/service-url";
|
||||||
|
import { Client } from "@zitadel/client";
|
||||||
|
import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb";
|
||||||
|
import { headers } from "next/headers";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const _headers = await headers();
|
||||||
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
|
|
||||||
|
const settingsService: Client<typeof SettingsService> =
|
||||||
|
await createServiceForHost(SettingsService, serviceUrl);
|
||||||
|
|
||||||
|
const settings = await settingsService
|
||||||
|
.getSecuritySettings({})
|
||||||
|
.then((resp) => (resp.settings ? resp.settings : undefined));
|
||||||
|
|
||||||
|
const response = NextResponse.json({ settings }, { status: 200 });
|
||||||
|
|
||||||
|
// Add Cache-Control header to cache the response for up to 1 hour
|
||||||
|
response.headers.set(
|
||||||
|
"Cache-Control",
|
||||||
|
"public, max-age=3600, stale-while-revalidate=86400",
|
||||||
|
);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
@@ -20,7 +20,10 @@ export type Cookie = {
|
|||||||
|
|
||||||
type SessionCookie<T> = Cookie & T;
|
type SessionCookie<T> = Cookie & T;
|
||||||
|
|
||||||
async function setSessionHttpOnlyCookie<T>(sessions: SessionCookie<T>[]) {
|
async function setSessionHttpOnlyCookie<T>(
|
||||||
|
sessions: SessionCookie<T>[],
|
||||||
|
sameSite: boolean | "lax" | "strict" | "none" = true,
|
||||||
|
) {
|
||||||
const cookiesList = await cookies();
|
const cookiesList = await cookies();
|
||||||
|
|
||||||
return cookiesList.set({
|
return cookiesList.set({
|
||||||
@@ -28,6 +31,7 @@ async function setSessionHttpOnlyCookie<T>(sessions: SessionCookie<T>[]) {
|
|||||||
value: JSON.stringify(sessions),
|
value: JSON.stringify(sessions),
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
path: "/",
|
path: "/",
|
||||||
|
sameSite,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,10 +46,15 @@ export async function setLanguageCookie(language: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addSessionToCookie<T>(
|
export async function addSessionToCookie<T>({
|
||||||
session: SessionCookie<T>,
|
session,
|
||||||
cleanup: boolean = false,
|
cleanup,
|
||||||
): Promise<any> {
|
sameSite,
|
||||||
|
}: {
|
||||||
|
session: SessionCookie<T>;
|
||||||
|
cleanup?: boolean;
|
||||||
|
sameSite?: boolean | "lax" | "strict" | "none" | undefined;
|
||||||
|
}): Promise<any> {
|
||||||
const cookiesList = await cookies();
|
const cookiesList = await cookies();
|
||||||
const stringifiedCookie = cookiesList.get("sessions");
|
const stringifiedCookie = cookiesList.get("sessions");
|
||||||
|
|
||||||
@@ -79,17 +88,23 @@ export async function addSessionToCookie<T>(
|
|||||||
? timestampDate(timestampFromMs(Number(session.expirationTs))) > now
|
? timestampDate(timestampFromMs(Number(session.expirationTs))) > now
|
||||||
: true,
|
: true,
|
||||||
);
|
);
|
||||||
return setSessionHttpOnlyCookie(filteredSessions);
|
return setSessionHttpOnlyCookie(filteredSessions, sameSite);
|
||||||
} else {
|
} else {
|
||||||
return setSessionHttpOnlyCookie(currentSessions);
|
return setSessionHttpOnlyCookie(currentSessions, sameSite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateSessionCookie<T>(
|
export async function updateSessionCookie<T>({
|
||||||
id: string,
|
id,
|
||||||
session: SessionCookie<T>,
|
session,
|
||||||
cleanup: boolean = false,
|
cleanup,
|
||||||
): Promise<any> {
|
sameSite,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
session: SessionCookie<T>;
|
||||||
|
cleanup?: boolean;
|
||||||
|
sameSite?: boolean | "lax" | "strict" | "none" | undefined;
|
||||||
|
}): Promise<any> {
|
||||||
const cookiesList = await cookies();
|
const cookiesList = await cookies();
|
||||||
const stringifiedCookie = cookiesList.get("sessions");
|
const stringifiedCookie = cookiesList.get("sessions");
|
||||||
|
|
||||||
@@ -108,19 +123,24 @@ export async function updateSessionCookie<T>(
|
|||||||
? timestampDate(timestampFromMs(Number(session.expirationTs))) > now
|
? timestampDate(timestampFromMs(Number(session.expirationTs))) > now
|
||||||
: true,
|
: true,
|
||||||
);
|
);
|
||||||
return setSessionHttpOnlyCookie(filteredSessions);
|
return setSessionHttpOnlyCookie(filteredSessions, sameSite);
|
||||||
} else {
|
} else {
|
||||||
return setSessionHttpOnlyCookie(sessions);
|
return setSessionHttpOnlyCookie(sessions, sameSite);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw "updateSessionCookie<T>: session id now found";
|
throw "updateSessionCookie<T>: session id now found";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeSessionFromCookie<T>(
|
export async function removeSessionFromCookie<T>({
|
||||||
session: SessionCookie<T>,
|
session,
|
||||||
cleanup: boolean = false,
|
cleanup,
|
||||||
): Promise<any> {
|
sameSite,
|
||||||
|
}: {
|
||||||
|
session: SessionCookie<T>;
|
||||||
|
cleanup?: boolean;
|
||||||
|
sameSite?: boolean | "lax" | "strict" | "none" | undefined;
|
||||||
|
}): Promise<any> {
|
||||||
const cookiesList = await cookies();
|
const cookiesList = await cookies();
|
||||||
const stringifiedCookie = cookiesList.get("sessions");
|
const stringifiedCookie = cookiesList.get("sessions");
|
||||||
|
|
||||||
@@ -136,9 +156,9 @@ export async function removeSessionFromCookie<T>(
|
|||||||
? timestampDate(timestampFromMs(Number(session.expirationTs))) > now
|
? timestampDate(timestampFromMs(Number(session.expirationTs))) > now
|
||||||
: true,
|
: true,
|
||||||
);
|
);
|
||||||
return setSessionHttpOnlyCookie(filteredSessions);
|
return setSessionHttpOnlyCookie(filteredSessions, sameSite);
|
||||||
} else {
|
} else {
|
||||||
return setSessionHttpOnlyCookie(reducedSessions);
|
return setSessionHttpOnlyCookie(reducedSessions, sameSite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,7 +8,7 @@ import {
|
|||||||
} from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
|
} from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
|
||||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { constructUrl } from "./service";
|
import { constructUrl } from "./service-url";
|
||||||
import { isSessionValid } from "./session";
|
import { isSessionValid } from "./session";
|
||||||
|
|
||||||
type LoginWithOIDCAndSession = {
|
type LoginWithOIDCAndSession = {
|
||||||
|
@@ -5,7 +5,7 @@ import { create } from "@zitadel/client";
|
|||||||
import { CreateResponseRequestSchema } from "@zitadel/proto/zitadel/saml/v2/saml_service_pb";
|
import { CreateResponseRequestSchema } from "@zitadel/proto/zitadel/saml/v2/saml_service_pb";
|
||||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { constructUrl } from "./service";
|
import { constructUrl } from "./service-url";
|
||||||
import { isSessionValid } from "./session";
|
import { isSessionValid } from "./session";
|
||||||
|
|
||||||
type LoginWithSAMLAndSession = {
|
type LoginWithSAMLAndSession = {
|
||||||
|
@@ -4,7 +4,7 @@ import { createServerTransport } from "@zitadel/client/node";
|
|||||||
import { createUserServiceClient } from "@zitadel/client/v2";
|
import { createUserServiceClient } from "@zitadel/client/v2";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import { getSessionCookieById } from "./cookies";
|
import { getSessionCookieById } from "./cookies";
|
||||||
import { getServiceUrlFromHeaders } from "./service";
|
import { getServiceUrlFromHeaders } from "./service-url";
|
||||||
import { getSession } from "./zitadel";
|
import { getSession } from "./zitadel";
|
||||||
|
|
||||||
const transport = async (serviceUrl: string, token: string) => {
|
const transport = async (serviceUrl: string, token: string) => {
|
||||||
|
@@ -4,6 +4,7 @@ import { addSessionToCookie, updateSessionCookie } from "@/lib/cookies";
|
|||||||
import {
|
import {
|
||||||
createSessionForUserIdAndIdpIntent,
|
createSessionForUserIdAndIdpIntent,
|
||||||
createSessionFromChecks,
|
createSessionFromChecks,
|
||||||
|
getSecuritySettings,
|
||||||
getSession,
|
getSession,
|
||||||
setSession,
|
setSession,
|
||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
@@ -20,7 +21,7 @@ import {
|
|||||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||||
import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import { getServiceUrlFromHeaders } from "../service";
|
import { getServiceUrlFromHeaders } from "../service-url";
|
||||||
|
|
||||||
type CustomCookieData = {
|
type CustomCookieData = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -65,7 +66,7 @@ export async function createSessionAndUpdateCookie(command: {
|
|||||||
serviceUrl,
|
serviceUrl,
|
||||||
sessionId: createdSession.sessionId,
|
sessionId: createdSession.sessionId,
|
||||||
sessionToken: createdSession.sessionToken,
|
sessionToken: createdSession.sessionToken,
|
||||||
}).then((response) => {
|
}).then(async (response) => {
|
||||||
if (response?.session && response.session?.factors?.user?.loginName) {
|
if (response?.session && response.session?.factors?.user?.loginName) {
|
||||||
const sessionCookie: CustomCookieData = {
|
const sessionCookie: CustomCookieData = {
|
||||||
id: createdSession.sessionId,
|
id: createdSession.sessionId,
|
||||||
@@ -91,9 +92,14 @@ export async function createSessionAndUpdateCookie(command: {
|
|||||||
response.session.factors.user.organizationId;
|
response.session.factors.user.organizationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
return addSessionToCookie(sessionCookie).then(() => {
|
const securitySettings = await getSecuritySettings({ serviceUrl });
|
||||||
return response.session as Session;
|
const sameSite = securitySettings?.embeddedIframe?.enabled
|
||||||
});
|
? "none"
|
||||||
|
: true;
|
||||||
|
|
||||||
|
await addSessionToCookie({ session: sessionCookie, sameSite });
|
||||||
|
|
||||||
|
return response.session as Session;
|
||||||
} else {
|
} else {
|
||||||
throw "could not get session or session does not have loginName";
|
throw "could not get session or session does not have loginName";
|
||||||
}
|
}
|
||||||
@@ -167,7 +173,10 @@ export async function createSessionForIdpAndUpdateCookie(
|
|||||||
sessionCookie.organization = session.factors.user.organizationId;
|
sessionCookie.organization = session.factors.user.organizationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
return addSessionToCookie(sessionCookie).then(() => {
|
const securitySettings = await getSecuritySettings({ serviceUrl });
|
||||||
|
const sameSite = securitySettings?.embeddedIframe?.enabled ? "none" : true;
|
||||||
|
|
||||||
|
return addSessionToCookie({ session: sessionCookie, sameSite }).then(() => {
|
||||||
return session as Session;
|
return session as Session;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -217,32 +226,44 @@ export async function setSessionAndUpdateCookie(
|
|||||||
serviceUrl,
|
serviceUrl,
|
||||||
sessionId: sessionCookie.id,
|
sessionId: sessionCookie.id,
|
||||||
sessionToken: sessionCookie.token,
|
sessionToken: sessionCookie.token,
|
||||||
}).then((response) => {
|
}).then(async (response) => {
|
||||||
if (response?.session && response.session.factors?.user?.loginName) {
|
if (
|
||||||
const { session } = response;
|
!response?.session ||
|
||||||
const newCookie: CustomCookieData = {
|
!response.session.factors?.user?.loginName
|
||||||
id: sessionCookie.id,
|
) {
|
||||||
token: updatedSession.sessionToken,
|
|
||||||
creationTs: sessionCookie.creationTs,
|
|
||||||
expirationTs: sessionCookie.expirationTs,
|
|
||||||
// just overwrite the changeDate with the new one
|
|
||||||
changeTs: updatedSession.details?.changeDate
|
|
||||||
? `${timestampMs(updatedSession.details.changeDate)}`
|
|
||||||
: "",
|
|
||||||
loginName: session.factors?.user?.loginName ?? "",
|
|
||||||
organization: session.factors?.user?.organizationId ?? "",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (sessionCookie.requestId) {
|
|
||||||
newCookie.requestId = sessionCookie.requestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
return updateSessionCookie(sessionCookie.id, newCookie).then(() => {
|
|
||||||
return { challenges: updatedSession.challenges, ...session };
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw "could not get session or session does not have loginName";
|
throw "could not get session or session does not have loginName";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { session } = response;
|
||||||
|
const newCookie: CustomCookieData = {
|
||||||
|
id: sessionCookie.id,
|
||||||
|
token: updatedSession.sessionToken,
|
||||||
|
creationTs: sessionCookie.creationTs,
|
||||||
|
expirationTs: sessionCookie.expirationTs,
|
||||||
|
// just overwrite the changeDate with the new one
|
||||||
|
changeTs: updatedSession.details?.changeDate
|
||||||
|
? `${timestampMs(updatedSession.details.changeDate)}`
|
||||||
|
: "",
|
||||||
|
loginName: session.factors?.user?.loginName ?? "",
|
||||||
|
organization: session.factors?.user?.organizationId ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (sessionCookie.requestId) {
|
||||||
|
newCookie.requestId = sessionCookie.requestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const securitySettings = await getSecuritySettings({ serviceUrl });
|
||||||
|
const sameSite = securitySettings?.embeddedIframe?.enabled
|
||||||
|
? "none"
|
||||||
|
: true;
|
||||||
|
|
||||||
|
return updateSessionCookie({
|
||||||
|
id: sessionCookie.id,
|
||||||
|
session: newCookie,
|
||||||
|
sameSite,
|
||||||
|
}).then(() => {
|
||||||
|
return { challenges: updatedSession.challenges, ...session };
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw "Session not be set";
|
throw "Session not be set";
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { authorizeOrDenyDeviceAuthorization } from "@/lib/zitadel";
|
import { authorizeOrDenyDeviceAuthorization } from "@/lib/zitadel";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import { getServiceUrlFromHeaders } from "../service";
|
import { getServiceUrlFromHeaders } from "../service-url";
|
||||||
|
|
||||||
export async function completeDeviceAuthorization(
|
export async function completeDeviceAuthorization(
|
||||||
deviceAuthorizationId: string,
|
deviceAuthorizationId: string,
|
||||||
|
@@ -8,7 +8,7 @@ import {
|
|||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { getNextUrl } from "../client";
|
import { getNextUrl } from "../client";
|
||||||
import { getServiceUrlFromHeaders } from "../service";
|
import { getServiceUrlFromHeaders } from "../service-url";
|
||||||
import { checkEmailVerification } from "../verify-helper";
|
import { checkEmailVerification } from "../verify-helper";
|
||||||
import { createSessionForIdpAndUpdateCookie } from "./cookie";
|
import { createSessionForIdpAndUpdateCookie } from "./cookie";
|
||||||
|
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
import { addHumanUser, createInviteCode } from "@/lib/zitadel";
|
import { addHumanUser, createInviteCode } from "@/lib/zitadel";
|
||||||
import { Factors } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
import { Factors } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import { getServiceUrlFromHeaders } from "../service";
|
import { getServiceUrlFromHeaders } from "../service-url";
|
||||||
|
|
||||||
type InviteUserCommand = {
|
type InviteUserCommand = {
|
||||||
email: string;
|
email: string;
|
||||||
|
@@ -8,7 +8,7 @@ import { idpTypeToIdentityProviderType, idpTypeToSlug } from "../idp";
|
|||||||
|
|
||||||
import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||||
import { UserState } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
import { UserState } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||||
import { getServiceUrlFromHeaders } from "../service";
|
import { getServiceUrlFromHeaders } from "../service-url";
|
||||||
import { checkInvite } from "../verify-helper";
|
import { checkInvite } from "../verify-helper";
|
||||||
import {
|
import {
|
||||||
getActiveIdentityProviders,
|
getActiveIdentityProviders,
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { getDeviceAuthorizationRequest as zitadelGetDeviceAuthorizationRequest } from "@/lib/zitadel";
|
import { getDeviceAuthorizationRequest as zitadelGetDeviceAuthorizationRequest } from "@/lib/zitadel";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import { getServiceUrlFromHeaders } from "../service";
|
import { getServiceUrlFromHeaders } from "../service-url";
|
||||||
|
|
||||||
export async function getDeviceAuthorizationRequest(userCode: string) {
|
export async function getDeviceAuthorizationRequest(userCode: string) {
|
||||||
const _headers = await headers();
|
const _headers = await headers();
|
||||||
|
@@ -13,7 +13,7 @@ import {
|
|||||||
getSessionCookieById,
|
getSessionCookieById,
|
||||||
getSessionCookieByLoginName,
|
getSessionCookieByLoginName,
|
||||||
} from "../cookies";
|
} from "../cookies";
|
||||||
import { getServiceUrlFromHeaders } from "../service";
|
import { getServiceUrlFromHeaders } from "../service-url";
|
||||||
import { getLoginSettings } from "../zitadel";
|
import { getLoginSettings } from "../zitadel";
|
||||||
|
|
||||||
export type SetOTPCommand = {
|
export type SetOTPCommand = {
|
||||||
|
@@ -22,7 +22,7 @@ import {
|
|||||||
getSessionCookieById,
|
getSessionCookieById,
|
||||||
getSessionCookieByLoginName,
|
getSessionCookieByLoginName,
|
||||||
} from "../cookies";
|
} from "../cookies";
|
||||||
import { getServiceUrlFromHeaders } from "../service";
|
import { getServiceUrlFromHeaders } from "../service-url";
|
||||||
import { checkEmailVerification } from "../verify-helper";
|
import { checkEmailVerification } from "../verify-helper";
|
||||||
import { setSessionAndUpdateCookie } from "./cookie";
|
import { setSessionAndUpdateCookie } from "./cookie";
|
||||||
|
|
||||||
|
@@ -32,7 +32,7 @@ import {
|
|||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import { getNextUrl } from "../client";
|
import { getNextUrl } from "../client";
|
||||||
import { getSessionCookieById, getSessionCookieByLoginName } from "../cookies";
|
import { getSessionCookieById, getSessionCookieByLoginName } from "../cookies";
|
||||||
import { getServiceUrlFromHeaders } from "../service";
|
import { getServiceUrlFromHeaders } from "../service-url";
|
||||||
import {
|
import {
|
||||||
checkEmailVerification,
|
checkEmailVerification,
|
||||||
checkMFAFactors,
|
checkMFAFactors,
|
||||||
|
@@ -10,7 +10,7 @@ import {
|
|||||||
} from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
} from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import { getNextUrl } from "../client";
|
import { getNextUrl } from "../client";
|
||||||
import { getServiceUrlFromHeaders } from "../service";
|
import { getServiceUrlFromHeaders } from "../service-url";
|
||||||
import { checkEmailVerification } from "../verify-helper";
|
import { checkEmailVerification } from "../verify-helper";
|
||||||
|
|
||||||
type RegisterUserCommand = {
|
type RegisterUserCommand = {
|
||||||
|
@@ -4,6 +4,7 @@ import { setSessionAndUpdateCookie } from "@/lib/server/cookie";
|
|||||||
import {
|
import {
|
||||||
deleteSession,
|
deleteSession,
|
||||||
getLoginSettings,
|
getLoginSettings,
|
||||||
|
getSecuritySettings,
|
||||||
humanMFAInitSkipped,
|
humanMFAInitSkipped,
|
||||||
listAuthenticationMethodTypes,
|
listAuthenticationMethodTypes,
|
||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
@@ -19,7 +20,7 @@ import {
|
|||||||
getSessionCookieByLoginName,
|
getSessionCookieByLoginName,
|
||||||
removeSessionFromCookie,
|
removeSessionFromCookie,
|
||||||
} from "../cookies";
|
} from "../cookies";
|
||||||
import { getServiceUrlFromHeaders } from "../service";
|
import { getServiceUrlFromHeaders } from "../service-url";
|
||||||
|
|
||||||
export async function skipMFAAndContinueWithNextUrl({
|
export async function skipMFAAndContinueWithNextUrl({
|
||||||
userId,
|
userId,
|
||||||
@@ -209,8 +210,11 @@ export async function clearSession(options: ClearSessionOptions) {
|
|||||||
sessionToken: session.token,
|
sessionToken: session.token,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const securitySettings = await getSecuritySettings({ serviceUrl });
|
||||||
|
const sameSite = securitySettings?.embeddedIframe?.enabled ? "none" : true;
|
||||||
|
|
||||||
if (deletedSession) {
|
if (deletedSession) {
|
||||||
return removeSessionFromCookie(session);
|
return removeSessionFromCookie({ session, sameSite });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,9 +234,12 @@ export async function cleanupSession({ sessionId }: CleanupSessionCommand) {
|
|||||||
sessionToken: sessionCookie.token,
|
sessionToken: sessionCookie.token,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const securitySettings = await getSecuritySettings({ serviceUrl });
|
||||||
|
const sameSite = securitySettings?.embeddedIframe?.enabled ? "none" : true;
|
||||||
|
|
||||||
if (!deleteResponse) {
|
if (!deleteResponse) {
|
||||||
throw new Error("Could not delete session");
|
throw new Error("Could not delete session");
|
||||||
}
|
}
|
||||||
|
|
||||||
return removeSessionFromCookie(sessionCookie);
|
return removeSessionFromCookie({ session: sessionCookie, sameSite });
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ import { VerifyU2FRegistrationRequestSchema } from "@zitadel/proto/zitadel/user/
|
|||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import { userAgent } from "next/server";
|
import { userAgent } from "next/server";
|
||||||
import { getSessionCookieById } from "../cookies";
|
import { getSessionCookieById } from "../cookies";
|
||||||
import { getServiceUrlFromHeaders } from "../service";
|
import { getServiceUrlFromHeaders } from "../service-url";
|
||||||
|
|
||||||
type RegisterU2FCommand = {
|
type RegisterU2FCommand = {
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
|
@@ -19,7 +19,7 @@ import { User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
|||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import { getNextUrl } from "../client";
|
import { getNextUrl } from "../client";
|
||||||
import { getSessionCookieByLoginName } from "../cookies";
|
import { getSessionCookieByLoginName } from "../cookies";
|
||||||
import { getServiceUrlFromHeaders } from "../service";
|
import { getServiceUrlFromHeaders } from "../service-url";
|
||||||
import { loadMostRecentSession } from "../session";
|
import { loadMostRecentSession } from "../session";
|
||||||
import { checkMFAFactors } from "../verify-helper";
|
import { checkMFAFactors } from "../verify-helper";
|
||||||
import { createSessionAndUpdateCookie } from "./cookie";
|
import { createSessionAndUpdateCookie } from "./cookie";
|
||||||
|
58
apps/login/src/lib/service-url.ts
Normal file
58
apps/login/src/lib/service-url.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapters/headers";
|
||||||
|
import { NextRequest } from "next/server";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the service url and region from the headers if used in a multitenant context (host, x-zitadel-forward-host header)
|
||||||
|
* or falls back to the ZITADEL_API_URL for a self hosting deployment
|
||||||
|
* or falls back to the host header for a self hosting deployment using custom domains
|
||||||
|
* @param headers
|
||||||
|
* @returns the service url and region from the headers
|
||||||
|
* @throws if the service url could not be determined
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export function getServiceUrlFromHeaders(headers: ReadonlyHeaders): {
|
||||||
|
serviceUrl: string;
|
||||||
|
} {
|
||||||
|
let instanceUrl;
|
||||||
|
|
||||||
|
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("http://")
|
||||||
|
? instanceUrl
|
||||||
|
: `https://${instanceUrl}`;
|
||||||
|
} else if (process.env.ZITADEL_API_URL) {
|
||||||
|
instanceUrl = process.env.ZITADEL_API_URL;
|
||||||
|
} else {
|
||||||
|
const host = headers.get("host");
|
||||||
|
|
||||||
|
if (host) {
|
||||||
|
const [hostname, port] = host.split(":");
|
||||||
|
if (hostname !== "localhost") {
|
||||||
|
instanceUrl = host.startsWith("http") ? host : `https://${host}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!instanceUrl) {
|
||||||
|
throw new Error("Service URL could not be determined");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
serviceUrl: instanceUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function constructUrl(request: NextRequest, path: string) {
|
||||||
|
const forwardedProto = request.headers.get("x-forwarded-proto")
|
||||||
|
? `${request.headers.get("x-forwarded-proto")}:`
|
||||||
|
: request.nextUrl.protocol;
|
||||||
|
|
||||||
|
const forwardedHost =
|
||||||
|
request.headers.get("x-zitadel-forward-host") ??
|
||||||
|
request.headers.get("x-forwarded-host") ??
|
||||||
|
request.headers.get("host");
|
||||||
|
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || "";
|
||||||
|
return new URL(`${basePath}${path}`, `${forwardedProto}//${forwardedHost}`);
|
||||||
|
}
|
@@ -7,8 +7,6 @@ import { SAMLService } from "@zitadel/proto/zitadel/saml/v2/saml_service_pb";
|
|||||||
import { SessionService } from "@zitadel/proto/zitadel/session/v2/session_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 { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb";
|
||||||
import { UserService } from "@zitadel/proto/zitadel/user/v2/user_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 { NextRequest } from "next/server";
|
|
||||||
import { systemAPIToken } from "./api";
|
import { systemAPIToken } from "./api";
|
||||||
|
|
||||||
type ServiceClass =
|
type ServiceClass =
|
||||||
@@ -66,59 +64,3 @@ export async function createServiceForHost<T extends ServiceClass>(
|
|||||||
|
|
||||||
return createClientFor<T>(service)(transport);
|
return createClientFor<T>(service)(transport);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the service url and region from the headers if used in a multitenant context (host, x-zitadel-forward-host header)
|
|
||||||
* or falls back to the ZITADEL_API_URL for a self hosting deployment
|
|
||||||
* or falls back to the host header for a self hosting deployment using custom domains
|
|
||||||
* @param headers
|
|
||||||
* @returns the service url and region from the headers
|
|
||||||
* @throws if the service url could not be determined
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export function getServiceUrlFromHeaders(headers: ReadonlyHeaders): {
|
|
||||||
serviceUrl: string;
|
|
||||||
} {
|
|
||||||
let instanceUrl;
|
|
||||||
|
|
||||||
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("http://")
|
|
||||||
? instanceUrl
|
|
||||||
: `https://${instanceUrl}`;
|
|
||||||
} else if (process.env.ZITADEL_API_URL) {
|
|
||||||
instanceUrl = process.env.ZITADEL_API_URL;
|
|
||||||
} else {
|
|
||||||
const host = headers.get("host");
|
|
||||||
|
|
||||||
if (host) {
|
|
||||||
const [hostname, port] = host.split(":");
|
|
||||||
if (hostname !== "localhost") {
|
|
||||||
instanceUrl = host.startsWith("http") ? host : `https://${host}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!instanceUrl) {
|
|
||||||
throw new Error("Service URL could not be determined");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
serviceUrl: instanceUrl,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function constructUrl(request: NextRequest, path: string) {
|
|
||||||
const forwardedProto = request.headers.get("x-forwarded-proto")
|
|
||||||
? `${request.headers.get("x-forwarded-proto")}:`
|
|
||||||
: request.nextUrl.protocol;
|
|
||||||
|
|
||||||
const forwardedHost =
|
|
||||||
request.headers.get("x-zitadel-forward-host") ??
|
|
||||||
request.headers.get("x-forwarded-host") ??
|
|
||||||
request.headers.get("host");
|
|
||||||
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || "";
|
|
||||||
return new URL(`${basePath}${path}`, `${forwardedProto}//${forwardedHost}`);
|
|
||||||
}
|
|
||||||
|
@@ -92,6 +92,21 @@ export async function getLoginSettings({
|
|||||||
return useCache ? cacheWrapper(callback) : callback;
|
return useCache ? cacheWrapper(callback) : callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getSecuritySettings({
|
||||||
|
serviceUrl,
|
||||||
|
}: {
|
||||||
|
serviceUrl: string;
|
||||||
|
}) {
|
||||||
|
const settingsService: Client<typeof SettingsService> =
|
||||||
|
await createServiceForHost(SettingsService, serviceUrl);
|
||||||
|
|
||||||
|
const callback = settingsService
|
||||||
|
.getSecuritySettings({})
|
||||||
|
.then((resp) => (resp.settings ? resp.settings : undefined));
|
||||||
|
|
||||||
|
return useCache ? cacheWrapper(callback) : callback;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getLockoutSettings({
|
export async function getLockoutSettings({
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
orgId,
|
orgId,
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { getServiceUrlFromHeaders } from "./lib/service";
|
import { DEFAULT_CSP } from "../constants/csp";
|
||||||
|
import { getServiceUrlFromHeaders } from "./lib/service-url";
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: [
|
matcher: [
|
||||||
@@ -22,6 +23,20 @@ export async function middleware(request: NextRequest) {
|
|||||||
|
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
|
|
||||||
|
// Call the /security route handler
|
||||||
|
// TODO check this on cloud run deployment
|
||||||
|
const securityResponse = await fetch(`${request.nextUrl.origin}/security`);
|
||||||
|
|
||||||
|
if (!securityResponse.ok) {
|
||||||
|
console.error(
|
||||||
|
"Failed to fetch security settings:",
|
||||||
|
securityResponse.statusText,
|
||||||
|
);
|
||||||
|
return NextResponse.next(); // Fallback if the request fails
|
||||||
|
}
|
||||||
|
|
||||||
|
const { settings: securitySettings } = await securityResponse.json();
|
||||||
|
|
||||||
const instanceHost = `${serviceUrl}`
|
const instanceHost = `${serviceUrl}`
|
||||||
.replace("https://", "")
|
.replace("https://", "")
|
||||||
.replace("http://", "");
|
.replace("http://", "");
|
||||||
@@ -39,7 +54,17 @@ export async function middleware(request: NextRequest) {
|
|||||||
responseHeaders.set("Access-Control-Allow-Origin", "*");
|
responseHeaders.set("Access-Control-Allow-Origin", "*");
|
||||||
responseHeaders.set("Access-Control-Allow-Headers", "*");
|
responseHeaders.set("Access-Control-Allow-Headers", "*");
|
||||||
|
|
||||||
|
if (securitySettings?.embeddedIframe?.enabled) {
|
||||||
|
securitySettings.embeddedIframe.allowedOrigins;
|
||||||
|
responseHeaders.set(
|
||||||
|
"Content-Security-Policy",
|
||||||
|
`${DEFAULT_CSP} frame-ancestors ${securitySettings.embeddedIframe.allowedOrigins.join(" ")};`,
|
||||||
|
);
|
||||||
|
responseHeaders.delete("X-Frame-Options");
|
||||||
|
}
|
||||||
|
|
||||||
request.nextUrl.href = `${serviceUrl}${request.nextUrl.pathname}${request.nextUrl.search}`;
|
request.nextUrl.href = `${serviceUrl}${request.nextUrl.pathname}${request.nextUrl.search}`;
|
||||||
|
|
||||||
return NextResponse.rewrite(request.nextUrl, {
|
return NextResponse.rewrite(request.nextUrl, {
|
||||||
request: {
|
request: {
|
||||||
headers: requestHeaders,
|
headers: requestHeaders,
|
||||||
|
Reference in New Issue
Block a user