mirror of
https://github.com/docmost/docmost.git
synced 2026-06-14 03:29:56 +00:00
feat: iframe configuration
This commit is contained in:
@@ -48,6 +48,13 @@ GOTENBERG_URL=
|
|||||||
|
|
||||||
DISABLE_TELEMETRY=false
|
DISABLE_TELEMETRY=false
|
||||||
|
|
||||||
|
# Allow other sites to embed Docmost in an iframe.
|
||||||
|
IFRAME_EMBED_ALLOWED=false
|
||||||
|
|
||||||
|
# Only used when IFRAME_EMBED_ALLOWED=true. When empty, any origin is allowed.
|
||||||
|
# Example: https://intranet.example.com,https://portal.example.com
|
||||||
|
IFRAME_ALLOWED_ORIGINS=
|
||||||
|
|
||||||
# Enable debug logging in production (default: false)
|
# Enable debug logging in production (default: false)
|
||||||
DEBUG_MODE=false
|
DEBUG_MODE=false
|
||||||
|
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ export * from './utils';
|
|||||||
export * from './nanoid.utils';
|
export * from './nanoid.utils';
|
||||||
export * from './file.helper';
|
export * from './file.helper';
|
||||||
export * from './constants';
|
export * from './constants';
|
||||||
|
export * from './security-headers';
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
export type SecurityHeader = { name: string; value: string };
|
||||||
|
|
||||||
|
export function resolveFrameHeader(
|
||||||
|
iframeEmbedAllowed: boolean,
|
||||||
|
allowedOrigins: string[],
|
||||||
|
): SecurityHeader | null {
|
||||||
|
if (!iframeEmbedAllowed) {
|
||||||
|
return { name: 'X-Frame-Options', value: 'SAMEORIGIN' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowedOrigins.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'Content-Security-Policy',
|
||||||
|
value: `frame-ancestors 'self' ${allowedOrigins.join(' ')}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -325,4 +325,19 @@ export class EnvironmentService {
|
|||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
return disabled === 'true';
|
return disabled === 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isIframeEmbedAllowed(): boolean {
|
||||||
|
const allowed = this.configService
|
||||||
|
.get<string>('IFRAME_EMBED_ALLOWED', 'false')
|
||||||
|
.toLowerCase();
|
||||||
|
return allowed === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
getIframeAllowedOrigins(): string[] {
|
||||||
|
const raw = this.configService.get<string>('IFRAME_ALLOWED_ORIGINS', '');
|
||||||
|
return raw
|
||||||
|
.split(',')
|
||||||
|
.map((o) => o.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import fastifyMultipart from '@fastify/multipart';
|
|||||||
import fastifyCookie from '@fastify/cookie';
|
import fastifyCookie from '@fastify/cookie';
|
||||||
import fastifyIp from 'fastify-ip';
|
import fastifyIp from 'fastify-ip';
|
||||||
import { InternalLogFilter } from './common/logger/internal-log-filter';
|
import { InternalLogFilter } from './common/logger/internal-log-filter';
|
||||||
|
import { EnvironmentService } from './integrations/environment/environment.service';
|
||||||
|
import { resolveFrameHeader } from './common/helpers';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create<NestFastifyApplication>(
|
const app = await NestFactory.create<NestFastifyApplication>(
|
||||||
@@ -50,6 +52,28 @@ async function bootstrap() {
|
|||||||
await app.register(fastifyMultipart);
|
await app.register(fastifyMultipart);
|
||||||
await app.register(fastifyCookie);
|
await app.register(fastifyCookie);
|
||||||
|
|
||||||
|
const environmentService = app.get(EnvironmentService);
|
||||||
|
const frameHeader = resolveFrameHeader(
|
||||||
|
environmentService.isIframeEmbedAllowed(),
|
||||||
|
environmentService.getIframeAllowedOrigins(),
|
||||||
|
);
|
||||||
|
if (frameHeader) {
|
||||||
|
// Skipped routes:
|
||||||
|
// /api/files/ - attachment controller sets its own CSP we'd overwrite
|
||||||
|
// /share/ 0 public share pages are safe to embed
|
||||||
|
const frameHeaderSkippedPrefixes = ['/api/files/', '/share/'];
|
||||||
|
app
|
||||||
|
.getHttpAdapter()
|
||||||
|
.getInstance()
|
||||||
|
.addHook('onSend', (req, reply, payload, done) => {
|
||||||
|
if (frameHeaderSkippedPrefixes.some((p) => req.url.startsWith(p))) {
|
||||||
|
return done(null, payload);
|
||||||
|
}
|
||||||
|
reply.header(frameHeader.name, frameHeader.value);
|
||||||
|
done(null, payload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
app
|
app
|
||||||
.getHttpAdapter()
|
.getHttpAdapter()
|
||||||
.getInstance()
|
.getInstance()
|
||||||
|
|||||||
Reference in New Issue
Block a user