171 lines
6.7 KiB
JavaScript
171 lines
6.7 KiB
JavaScript
import 'dotenv/config';
|
|
|
|
import adapterAuto from '@sveltejs/adapter-auto';
|
|
import adapterNode from '@sveltejs/adapter-node';
|
|
import path from 'path';
|
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
|
|
|
const useNodeAdapter = process.env.PUBLIC_IS_SELFHOSTED === 'true';
|
|
const API_ORIGIN = process.env.PUBLIC_SERVER_URL;
|
|
const IS_CLOUDFLARE = process.env.CI_ENVIRONMENT === 'cloudflare';
|
|
|
|
const adapterCloudflare = IS_CLOUDFLARE ? (await import('@sveltejs/adapter-cloudflare')).default : null;
|
|
|
|
/**
|
|
* Parse comma-separated domain list from env.
|
|
* Normalizes to https:// URLs. Returns null when env var is unset (use defaults).
|
|
*/
|
|
function parseDomainsFromEnv(envVar) {
|
|
const value = process.env[envVar];
|
|
if (!value || typeof value !== 'string') return null;
|
|
return value
|
|
.split(',')
|
|
.map((d) => d.trim())
|
|
.filter(Boolean)
|
|
.map((d) => (d.startsWith('http://') || d.startsWith('https://') ? d : `https://${d}`));
|
|
}
|
|
|
|
const ALLOWED_EXTERNAL_DOMAINS = parseDomainsFromEnv('ALLOWED_EXTERNAL_DOMAINS');
|
|
const ALLOWED_SCRIPT_SRC = parseDomainsFromEnv('CSP_SCRIPT_SRC_DOMAINS');
|
|
const ALLOWED_STYLE_SRC = parseDomainsFromEnv('CSP_STYLE_SRC_DOMAINS');
|
|
const ALLOWED_CONNECT_SRC = parseDomainsFromEnv('CSP_CONNECT_SRC_DOMAINS');
|
|
const ALLOWED_FRAME_SRC = parseDomainsFromEnv('CSP_FRAME_SRC_DOMAINS');
|
|
const ALLOWED_FONT_SRC = parseDomainsFromEnv('CSP_FONT_SRC_DOMAINS');
|
|
|
|
const defaultScriptSrcDomains = [
|
|
'https://assets.cdn.clsrio.com',
|
|
'https://cdnjs.cloudflare.com',
|
|
'https://*.posthog.com',
|
|
'https://*.senja.io',
|
|
'https://umami.hz.oncws.com',
|
|
'https://www.youtube.com',
|
|
'https://youtube.com',
|
|
'https://google.com'
|
|
];
|
|
const defaultStyleSrcDomains = [
|
|
'https://cdn.plyr.io',
|
|
'https://unpkg.com/[email protected]/dist/katex.min.css',
|
|
'https://assets.cdn.clsrio.com/eqneditor_1.css',
|
|
'https://fonts.googleapis.com'
|
|
];
|
|
const defaultConnectSrcDomains = [
|
|
'https://*.classroomio.com',
|
|
'https://classroomio.com',
|
|
'https://app.classroomio.com',
|
|
'https://api.classroomio.com',
|
|
'https://pgrest.classroomio.com',
|
|
'https://play.classroomio.com',
|
|
'wss://*.classroomio.com',
|
|
'https://assets.cdn.clsrio.com',
|
|
'https://cdn.plyr.io',
|
|
'https://*.posthog.com',
|
|
'https://umami.hz.oncws.com',
|
|
'https://*.r2.cloudflarestorage.com',
|
|
'https://*.senja.io',
|
|
'https://*.ytimg.com',
|
|
'https://noembed.com'
|
|
];
|
|
const defaultFrameSrcDomains = [
|
|
'https://www.youtube.com',
|
|
'https://youtube.com',
|
|
'https://www.youtube-nocookie.com',
|
|
'https://www.google.com',
|
|
'https://google.com'
|
|
];
|
|
const defaultFontSrcDomains = ['https://fonts.gstatic.com', 'https://cdn.plyr.io'];
|
|
|
|
// Self-hosted: use ONLY env vars, no defaults (strict CISO allowlists, data residency).
|
|
// SaaS: use defaults when env vars are unset (backward compatible).
|
|
const scriptSrcDomains = useNodeAdapter
|
|
? (ALLOWED_EXTERNAL_DOMAINS ?? ALLOWED_SCRIPT_SRC ?? [])
|
|
: (ALLOWED_EXTERNAL_DOMAINS ?? ALLOWED_SCRIPT_SRC ?? defaultScriptSrcDomains);
|
|
const styleSrcDomains = useNodeAdapter
|
|
? (ALLOWED_EXTERNAL_DOMAINS ?? ALLOWED_STYLE_SRC ?? [])
|
|
: (ALLOWED_EXTERNAL_DOMAINS ?? ALLOWED_STYLE_SRC ?? defaultStyleSrcDomains);
|
|
const connectSrcDomains = useNodeAdapter
|
|
? (ALLOWED_EXTERNAL_DOMAINS ?? ALLOWED_CONNECT_SRC ?? [])
|
|
: (ALLOWED_EXTERNAL_DOMAINS ?? ALLOWED_CONNECT_SRC ?? defaultConnectSrcDomains);
|
|
const frameSrcDomains = useNodeAdapter
|
|
? (ALLOWED_EXTERNAL_DOMAINS ?? ALLOWED_FRAME_SRC ?? [])
|
|
: (ALLOWED_EXTERNAL_DOMAINS ?? ALLOWED_FRAME_SRC ?? defaultFrameSrcDomains);
|
|
const fontSrcDomains = useNodeAdapter
|
|
? (ALLOWED_EXTERNAL_DOMAINS ?? ALLOWED_FONT_SRC ?? [])
|
|
: (ALLOWED_EXTERNAL_DOMAINS ?? ALLOWED_FONT_SRC ?? defaultFontSrcDomains);
|
|
|
|
/** @type {import('@sveltejs/kit').Config} */
|
|
const config = {
|
|
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
|
// for more information about preprocessors
|
|
preprocess: [vitePreprocess({})],
|
|
kit: {
|
|
adapter: IS_CLOUDFLARE ? adapterCloudflare() : useNodeAdapter ? adapterNode() : adapterAuto(),
|
|
alias: {
|
|
$lib: path.resolve('./src/lib'),
|
|
$features: path.resolve('./src/lib/features'),
|
|
$mail: path.resolve('./src/mail'),
|
|
'$src/tools': path.resolve('./node_modules/@cio/ui/src/tools/index.ts'),
|
|
'$src/base/*': path.resolve('./node_modules/@cio/ui/src/base/*'),
|
|
'@cio/ui': path.resolve('./node_modules/@cio/ui/src'),
|
|
'@cio/ui/*': path.resolve('./node_modules/@cio/ui/src/*'),
|
|
'@cio/api': path.resolve('./node_modules/@cio/api/dist'),
|
|
'@cio/api/*': path.resolve('./node_modules/@cio/api/dist/*'),
|
|
'@cio/utils': path.resolve('./node_modules/@cio/utils/dist'),
|
|
'@cio/utils/*': path.resolve('./node_modules/@cio/utils/dist/*'),
|
|
'@cio/db/types': path.resolve('./node_modules/@cio/db/src/types.ts')
|
|
},
|
|
csp: {
|
|
mode: 'auto',
|
|
directives: {
|
|
'default-src': ['self'],
|
|
'script-src': ['self', ...scriptSrcDomains, 'unsafe-hashes', 'unsafe-eval'],
|
|
'style-src': ['self', 'unsafe-inline', ...styleSrcDomains],
|
|
'style-src-elem': ['self', 'unsafe-inline', ...styleSrcDomains],
|
|
'font-src': ['self', ...fontSrcDomains],
|
|
'img-src': ['self', 'data:', 'https:', 'blob:', 'http://localhost:9000'],
|
|
'media-src': ['self', 'https:', 'data:', 'blob:', 'http://localhost:9000'],
|
|
'frame-src': ['self', ...frameSrcDomains],
|
|
'connect-src': [
|
|
'self',
|
|
'blob:',
|
|
'http://localhost:3002',
|
|
'http://localhost:9000',
|
|
...(API_ORIGIN ? [API_ORIGIN] : []),
|
|
...connectSrcDomains
|
|
],
|
|
'worker-src': ['self', 'blob:'],
|
|
'object-src': ['none'],
|
|
'base-uri': ['self'],
|
|
'form-action': ['self'],
|
|
'frame-ancestors': ['none'],
|
|
'upgrade-insecure-requests': true
|
|
},
|
|
reportOnly: {
|
|
'default-src': ['self'],
|
|
'script-src': ['self', ...scriptSrcDomains, 'unsafe-hashes', 'unsafe-eval'],
|
|
'style-src': ['self', 'unsafe-inline', ...styleSrcDomains],
|
|
'style-src-elem': ['self', 'unsafe-inline', ...styleSrcDomains],
|
|
'font-src': ['self', ...fontSrcDomains],
|
|
'img-src': ['self', 'data:', 'https:', 'blob:', 'http://localhost:9000'],
|
|
'media-src': ['self', 'https:', 'data:', 'blob:', 'http://localhost:9000'],
|
|
'frame-src': ['self', ...frameSrcDomains],
|
|
'connect-src': [
|
|
'self',
|
|
'blob:',
|
|
'http://localhost:3002',
|
|
'http://localhost:9000',
|
|
...(API_ORIGIN ? [API_ORIGIN] : []),
|
|
...connectSrcDomains
|
|
],
|
|
'worker-src': ['self', 'blob:'],
|
|
'object-src': ['none'],
|
|
'base-uri': ['self'],
|
|
'form-action': ['self'],
|
|
'frame-ancestors': ['none'],
|
|
'report-uri': ['/csp-report']
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
export default config;
|