mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-13 11:20:00 +00:00
fix: override csp for allowed iframe
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import createNextIntlPlugin from "next-intl/plugin";
|
import createNextIntlPlugin from "next-intl/plugin";
|
||||||
|
import { DEFAULT_CSP } from "./src/lib/csp";
|
||||||
|
|
||||||
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 = [
|
||||||
|
@@ -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,4 +1,5 @@
|
|||||||
import { getAllSessions } from "@/lib/cookies";
|
import { getAllSessions } from "@/lib/cookies";
|
||||||
|
import { DEFAULT_CSP } from "@/lib/csp";
|
||||||
import { idpTypeToSlug } from "@/lib/idp";
|
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";
|
||||||
@@ -12,6 +13,7 @@ import {
|
|||||||
getAuthRequest,
|
getAuthRequest,
|
||||||
getOrgsByDomain,
|
getOrgsByDomain,
|
||||||
getSAMLRequest,
|
getSAMLRequest,
|
||||||
|
getSecuritySettings,
|
||||||
listSessions,
|
listSessions,
|
||||||
startIdentityProviderFlow,
|
startIdentityProviderFlow,
|
||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
@@ -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({
|
||||||
|
2
apps/login/src/lib/csp.ts
Normal file
2
apps/login/src/lib/csp.ts
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;";
|
@@ -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,8 @@
|
|||||||
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 "./lib/csp";
|
||||||
import { getServiceUrlFromHeaders } from "./lib/service";
|
import { getServiceUrlFromHeaders } from "./lib/service";
|
||||||
|
import { getSecuritySettings } from "./lib/zitadel";
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: [
|
matcher: [
|
||||||
@@ -22,6 +24,8 @@ export async function middleware(request: NextRequest) {
|
|||||||
|
|
||||||
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
|
||||||
|
|
||||||
|
const securitySettings = await getSecuritySettings({ serviceUrl });
|
||||||
|
|
||||||
const instanceHost = `${serviceUrl}`
|
const instanceHost = `${serviceUrl}`
|
||||||
.replace("https://", "")
|
.replace("https://", "")
|
||||||
.replace("http://", "");
|
.replace("http://", "");
|
||||||
@@ -39,6 +43,20 @@ 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", "*");
|
||||||
|
|
||||||
|
responseHeaders.set(
|
||||||
|
"Content-Security-Policy",
|
||||||
|
`${DEFAULT_CSP} frame-ancestors 'none'`,
|
||||||
|
);
|
||||||
|
|
||||||
|
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: {
|
||||||
|
Reference in New Issue
Block a user