@scalemule/nextjs 0.0.40 → 0.0.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/index.d.mts +118 -1
- package/dist/server/index.d.ts +118 -1
- package/dist/server/index.js +70 -0
- package/dist/server/index.mjs +69 -1
- package/package.json +1 -1
package/dist/server/index.d.mts
CHANGED
|
@@ -1142,4 +1142,121 @@ declare function validateSafeRedirect(input: string | null | undefined, opts?: S
|
|
|
1142
1142
|
*/
|
|
1143
1143
|
declare function isSafeRedirect(input: string | null | undefined, opts?: SafeRedirectOptions): boolean;
|
|
1144
1144
|
|
|
1145
|
-
|
|
1145
|
+
/**
|
|
1146
|
+
* Security-headers helper for Next.js hosts.
|
|
1147
|
+
*
|
|
1148
|
+
* Returns a shape that drops directly into the `headers()` entry in
|
|
1149
|
+
* `next.config.ts` (or the `headers` field of a Route Handler
|
|
1150
|
+
* `NextResponse`). Reasonable defaults for the top-of-OWASP headers
|
|
1151
|
+
* — HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy,
|
|
1152
|
+
* X-XSS-Protection, Permissions-Policy — with knobs for the fields
|
|
1153
|
+
* that typically differ by app (HSTS duration, frame ancestors,
|
|
1154
|
+
* camera/microphone policies).
|
|
1155
|
+
*
|
|
1156
|
+
* Framework-agnostic: no imports from `next/*` so the function works
|
|
1157
|
+
* in Edge / Node / middleware and is trivially unit-testable.
|
|
1158
|
+
*/
|
|
1159
|
+
interface SecurityHeaderEntry {
|
|
1160
|
+
key: string;
|
|
1161
|
+
value: string;
|
|
1162
|
+
}
|
|
1163
|
+
interface BuildSecurityHeadersOptions {
|
|
1164
|
+
/**
|
|
1165
|
+
* HSTS max-age in seconds. Default `31536000` (1 year) — the value
|
|
1166
|
+
* required for inclusion on the HSTS preload list. Set to `0` (or
|
|
1167
|
+
* pass `includeHsts: false`) to opt out entirely.
|
|
1168
|
+
*/
|
|
1169
|
+
hstsMaxAgeSeconds?: number;
|
|
1170
|
+
/** Adds the `includeSubDomains` directive to HSTS. Default `true`. */
|
|
1171
|
+
hstsIncludeSubDomains?: boolean;
|
|
1172
|
+
/**
|
|
1173
|
+
* Adds the `preload` directive to HSTS. Default `false` — only set
|
|
1174
|
+
* to `true` after you've read https://hstspreload.org and are ready
|
|
1175
|
+
* to commit; reversing it requires months of calendar time.
|
|
1176
|
+
*/
|
|
1177
|
+
hstsPreload?: boolean;
|
|
1178
|
+
/** Emit the HSTS header at all. Default `true`. */
|
|
1179
|
+
includeHsts?: boolean;
|
|
1180
|
+
/**
|
|
1181
|
+
* Value for `X-Frame-Options`. Default `'DENY'`. Pass `'SAMEORIGIN'`
|
|
1182
|
+
* if your app is framed by its own subdomain. Set to `null` to
|
|
1183
|
+
* suppress (e.g. when you're already using `frame-ancestors` in
|
|
1184
|
+
* CSP). `SAMEORIGIN` accepts no host parameter; use CSP's
|
|
1185
|
+
* `frame-ancestors` for finer-grained framing policy.
|
|
1186
|
+
*/
|
|
1187
|
+
frameOptions?: 'DENY' | 'SAMEORIGIN' | null;
|
|
1188
|
+
/**
|
|
1189
|
+
* Value for `Referrer-Policy`. Default `'strict-origin-when-cross-origin'`
|
|
1190
|
+
* — same as Next.js's own default, balances analytics with privacy.
|
|
1191
|
+
*/
|
|
1192
|
+
referrerPolicy?: 'no-referrer' | 'no-referrer-when-downgrade' | 'origin' | 'origin-when-cross-origin' | 'same-origin' | 'strict-origin' | 'strict-origin-when-cross-origin' | 'unsafe-url';
|
|
1193
|
+
/**
|
|
1194
|
+
* Permissions-Policy directives. Pass a map of `{ feature: allowlist }`
|
|
1195
|
+
* where the allowlist is either an array of origins (string[]) or
|
|
1196
|
+
* one of the well-known tokens `'self'` / `'*'` / `'()'` (deny).
|
|
1197
|
+
*
|
|
1198
|
+
* Default: camera + microphone allowed for `'self'` only (useful
|
|
1199
|
+
* for the conference track); geolocation + payment denied; every
|
|
1200
|
+
* other common feature left unset (browsers apply their own
|
|
1201
|
+
* conservative defaults).
|
|
1202
|
+
*
|
|
1203
|
+
* Pass `{}` to emit an empty `Permissions-Policy: ` header (rarely
|
|
1204
|
+
* useful). Pass `null` to suppress the header entirely.
|
|
1205
|
+
*/
|
|
1206
|
+
permissionsPolicy?: PermissionsPolicyMap | null;
|
|
1207
|
+
/**
|
|
1208
|
+
* Value for `X-Content-Type-Options`. Default `'nosniff'` — do not
|
|
1209
|
+
* override without a specific reason.
|
|
1210
|
+
*/
|
|
1211
|
+
contentTypeOptions?: string | null;
|
|
1212
|
+
/**
|
|
1213
|
+
* Legacy `X-XSS-Protection`. Default `'0'` (disables the
|
|
1214
|
+
* deprecated IE/Edge filter which had known bypass issues —
|
|
1215
|
+
* recommended by OWASP + Next.js). Set to `'1; mode=block'` only
|
|
1216
|
+
* if you have a specific compliance reason.
|
|
1217
|
+
*/
|
|
1218
|
+
xssProtection?: string | null;
|
|
1219
|
+
/**
|
|
1220
|
+
* Extra headers to merge into the result. Takes precedence over
|
|
1221
|
+
* the computed ones — useful for adding Content-Security-Policy or
|
|
1222
|
+
* Cross-Origin-* headers without forcing a new option for each.
|
|
1223
|
+
*/
|
|
1224
|
+
extraHeaders?: SecurityHeaderEntry[];
|
|
1225
|
+
}
|
|
1226
|
+
type PermissionsPolicyValue = readonly string[] | 'self' | '*' | '()';
|
|
1227
|
+
type PermissionsPolicyMap = Readonly<Record<string, PermissionsPolicyValue>>;
|
|
1228
|
+
declare const DEFAULT_PERMISSIONS_POLICY: PermissionsPolicyMap;
|
|
1229
|
+
/**
|
|
1230
|
+
* Build a reasonable-default set of security response headers in the
|
|
1231
|
+
* exact shape Next.js `headers()` expects.
|
|
1232
|
+
*
|
|
1233
|
+
* @example next.config.ts
|
|
1234
|
+
* ```ts
|
|
1235
|
+
* import { buildSecurityHeaders } from '@scalemule/nextjs/server'
|
|
1236
|
+
*
|
|
1237
|
+
* export default {
|
|
1238
|
+
* async headers() {
|
|
1239
|
+
* return [
|
|
1240
|
+
* { source: '/:path*', headers: buildSecurityHeaders() },
|
|
1241
|
+
* ]
|
|
1242
|
+
* },
|
|
1243
|
+
* }
|
|
1244
|
+
* ```
|
|
1245
|
+
*
|
|
1246
|
+
* @example custom overrides
|
|
1247
|
+
* ```ts
|
|
1248
|
+
* buildSecurityHeaders({
|
|
1249
|
+
* frameOptions: 'SAMEORIGIN',
|
|
1250
|
+
* permissionsPolicy: {
|
|
1251
|
+
* camera: ['https://trusted.example.com'],
|
|
1252
|
+
* microphone: ['https://trusted.example.com'],
|
|
1253
|
+
* },
|
|
1254
|
+
* extraHeaders: [
|
|
1255
|
+
* { key: 'Content-Security-Policy', value: "default-src 'self'" },
|
|
1256
|
+
* ],
|
|
1257
|
+
* })
|
|
1258
|
+
* ```
|
|
1259
|
+
*/
|
|
1260
|
+
declare function buildSecurityHeaders(opts?: BuildSecurityHeadersOptions): SecurityHeaderEntry[];
|
|
1261
|
+
|
|
1262
|
+
export { type AnalyticsRoutesConfig, type AnalyticsTrackingGateConfig, type AuthMiddlewareConfig, type AuthRoutesConfig, type BuildSecurityHeadersOptions, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, DEFAULT_PERMISSIONS_POLICY, type HandlerContext, type HandlerOptions, KNOWN_ACCOUNTS_COOKIE_NAME, type KnownAccountEntry, type MySqlBundle, type NotificationRoutesConfig, OAUTH_STATE_COOKIE_NAME, type OAuthBundle, type PermissionsPolicyMap, type PermissionsPolicyValue, type PostgresBundle, type PushRoutesConfig, type RedisBundle, type S3Bundle, SESSION_COOKIE_NAME, type SafeRedirectOptions, ScaleMuleError, type SecurityHeaderEntry, ServerConfig, type SessionCookieOptions, type SessionData, type SmtpBundle, USER_ID_COOKIE_NAME, apiHandler, appendKnownAccountCookie, buildClientContextHeaders, buildFlagContext, buildSecurityHeaders, clearKnownAccountsCookie, clearOAuthState, clearSession, configureBundles, configureSecrets, createAnalyticsRoutes, createAuthMiddleware, createAuthRoutes, createNotificationRoutes, createPushRoutes, errorCodeToStatus, extractClientContext, extractClientContextFromReq, generateCSRFToken, getAppSecret, getAppSecretOrDefault, getBootstrapFlags, getBundle, getCSRFToken, getKnownAccountsCookieRaw, getKnownAccountsFromRequest, getMySqlBundle, getOAuthBundle, getPostgresBundle, getRedisBundle, getS3Bundle, getSession, getSessionFromRequest, getSmtpBundle, invalidateBundleCache, invalidateSecretCache, isSafeRedirect, normalizeKnownAccountsCookie, prefetchBundles, prefetchSecrets, removeKnownAccountFromCookie, requireAppSecret, requireBundle, requireSession, setOAuthState, unwrap, validateCSRFToken, validateCSRFTokenAsync, validateOAuthState, validateOAuthStateAsync, validateSafeRedirect, withAuth, withCSRFProtection, withCSRFToken, withSession };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1142,4 +1142,121 @@ declare function validateSafeRedirect(input: string | null | undefined, opts?: S
|
|
|
1142
1142
|
*/
|
|
1143
1143
|
declare function isSafeRedirect(input: string | null | undefined, opts?: SafeRedirectOptions): boolean;
|
|
1144
1144
|
|
|
1145
|
-
|
|
1145
|
+
/**
|
|
1146
|
+
* Security-headers helper for Next.js hosts.
|
|
1147
|
+
*
|
|
1148
|
+
* Returns a shape that drops directly into the `headers()` entry in
|
|
1149
|
+
* `next.config.ts` (or the `headers` field of a Route Handler
|
|
1150
|
+
* `NextResponse`). Reasonable defaults for the top-of-OWASP headers
|
|
1151
|
+
* — HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy,
|
|
1152
|
+
* X-XSS-Protection, Permissions-Policy — with knobs for the fields
|
|
1153
|
+
* that typically differ by app (HSTS duration, frame ancestors,
|
|
1154
|
+
* camera/microphone policies).
|
|
1155
|
+
*
|
|
1156
|
+
* Framework-agnostic: no imports from `next/*` so the function works
|
|
1157
|
+
* in Edge / Node / middleware and is trivially unit-testable.
|
|
1158
|
+
*/
|
|
1159
|
+
interface SecurityHeaderEntry {
|
|
1160
|
+
key: string;
|
|
1161
|
+
value: string;
|
|
1162
|
+
}
|
|
1163
|
+
interface BuildSecurityHeadersOptions {
|
|
1164
|
+
/**
|
|
1165
|
+
* HSTS max-age in seconds. Default `31536000` (1 year) — the value
|
|
1166
|
+
* required for inclusion on the HSTS preload list. Set to `0` (or
|
|
1167
|
+
* pass `includeHsts: false`) to opt out entirely.
|
|
1168
|
+
*/
|
|
1169
|
+
hstsMaxAgeSeconds?: number;
|
|
1170
|
+
/** Adds the `includeSubDomains` directive to HSTS. Default `true`. */
|
|
1171
|
+
hstsIncludeSubDomains?: boolean;
|
|
1172
|
+
/**
|
|
1173
|
+
* Adds the `preload` directive to HSTS. Default `false` — only set
|
|
1174
|
+
* to `true` after you've read https://hstspreload.org and are ready
|
|
1175
|
+
* to commit; reversing it requires months of calendar time.
|
|
1176
|
+
*/
|
|
1177
|
+
hstsPreload?: boolean;
|
|
1178
|
+
/** Emit the HSTS header at all. Default `true`. */
|
|
1179
|
+
includeHsts?: boolean;
|
|
1180
|
+
/**
|
|
1181
|
+
* Value for `X-Frame-Options`. Default `'DENY'`. Pass `'SAMEORIGIN'`
|
|
1182
|
+
* if your app is framed by its own subdomain. Set to `null` to
|
|
1183
|
+
* suppress (e.g. when you're already using `frame-ancestors` in
|
|
1184
|
+
* CSP). `SAMEORIGIN` accepts no host parameter; use CSP's
|
|
1185
|
+
* `frame-ancestors` for finer-grained framing policy.
|
|
1186
|
+
*/
|
|
1187
|
+
frameOptions?: 'DENY' | 'SAMEORIGIN' | null;
|
|
1188
|
+
/**
|
|
1189
|
+
* Value for `Referrer-Policy`. Default `'strict-origin-when-cross-origin'`
|
|
1190
|
+
* — same as Next.js's own default, balances analytics with privacy.
|
|
1191
|
+
*/
|
|
1192
|
+
referrerPolicy?: 'no-referrer' | 'no-referrer-when-downgrade' | 'origin' | 'origin-when-cross-origin' | 'same-origin' | 'strict-origin' | 'strict-origin-when-cross-origin' | 'unsafe-url';
|
|
1193
|
+
/**
|
|
1194
|
+
* Permissions-Policy directives. Pass a map of `{ feature: allowlist }`
|
|
1195
|
+
* where the allowlist is either an array of origins (string[]) or
|
|
1196
|
+
* one of the well-known tokens `'self'` / `'*'` / `'()'` (deny).
|
|
1197
|
+
*
|
|
1198
|
+
* Default: camera + microphone allowed for `'self'` only (useful
|
|
1199
|
+
* for the conference track); geolocation + payment denied; every
|
|
1200
|
+
* other common feature left unset (browsers apply their own
|
|
1201
|
+
* conservative defaults).
|
|
1202
|
+
*
|
|
1203
|
+
* Pass `{}` to emit an empty `Permissions-Policy: ` header (rarely
|
|
1204
|
+
* useful). Pass `null` to suppress the header entirely.
|
|
1205
|
+
*/
|
|
1206
|
+
permissionsPolicy?: PermissionsPolicyMap | null;
|
|
1207
|
+
/**
|
|
1208
|
+
* Value for `X-Content-Type-Options`. Default `'nosniff'` — do not
|
|
1209
|
+
* override without a specific reason.
|
|
1210
|
+
*/
|
|
1211
|
+
contentTypeOptions?: string | null;
|
|
1212
|
+
/**
|
|
1213
|
+
* Legacy `X-XSS-Protection`. Default `'0'` (disables the
|
|
1214
|
+
* deprecated IE/Edge filter which had known bypass issues —
|
|
1215
|
+
* recommended by OWASP + Next.js). Set to `'1; mode=block'` only
|
|
1216
|
+
* if you have a specific compliance reason.
|
|
1217
|
+
*/
|
|
1218
|
+
xssProtection?: string | null;
|
|
1219
|
+
/**
|
|
1220
|
+
* Extra headers to merge into the result. Takes precedence over
|
|
1221
|
+
* the computed ones — useful for adding Content-Security-Policy or
|
|
1222
|
+
* Cross-Origin-* headers without forcing a new option for each.
|
|
1223
|
+
*/
|
|
1224
|
+
extraHeaders?: SecurityHeaderEntry[];
|
|
1225
|
+
}
|
|
1226
|
+
type PermissionsPolicyValue = readonly string[] | 'self' | '*' | '()';
|
|
1227
|
+
type PermissionsPolicyMap = Readonly<Record<string, PermissionsPolicyValue>>;
|
|
1228
|
+
declare const DEFAULT_PERMISSIONS_POLICY: PermissionsPolicyMap;
|
|
1229
|
+
/**
|
|
1230
|
+
* Build a reasonable-default set of security response headers in the
|
|
1231
|
+
* exact shape Next.js `headers()` expects.
|
|
1232
|
+
*
|
|
1233
|
+
* @example next.config.ts
|
|
1234
|
+
* ```ts
|
|
1235
|
+
* import { buildSecurityHeaders } from '@scalemule/nextjs/server'
|
|
1236
|
+
*
|
|
1237
|
+
* export default {
|
|
1238
|
+
* async headers() {
|
|
1239
|
+
* return [
|
|
1240
|
+
* { source: '/:path*', headers: buildSecurityHeaders() },
|
|
1241
|
+
* ]
|
|
1242
|
+
* },
|
|
1243
|
+
* }
|
|
1244
|
+
* ```
|
|
1245
|
+
*
|
|
1246
|
+
* @example custom overrides
|
|
1247
|
+
* ```ts
|
|
1248
|
+
* buildSecurityHeaders({
|
|
1249
|
+
* frameOptions: 'SAMEORIGIN',
|
|
1250
|
+
* permissionsPolicy: {
|
|
1251
|
+
* camera: ['https://trusted.example.com'],
|
|
1252
|
+
* microphone: ['https://trusted.example.com'],
|
|
1253
|
+
* },
|
|
1254
|
+
* extraHeaders: [
|
|
1255
|
+
* { key: 'Content-Security-Policy', value: "default-src 'self'" },
|
|
1256
|
+
* ],
|
|
1257
|
+
* })
|
|
1258
|
+
* ```
|
|
1259
|
+
*/
|
|
1260
|
+
declare function buildSecurityHeaders(opts?: BuildSecurityHeadersOptions): SecurityHeaderEntry[];
|
|
1261
|
+
|
|
1262
|
+
export { type AnalyticsRoutesConfig, type AnalyticsTrackingGateConfig, type AuthMiddlewareConfig, type AuthRoutesConfig, type BuildSecurityHeadersOptions, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, DEFAULT_PERMISSIONS_POLICY, type HandlerContext, type HandlerOptions, KNOWN_ACCOUNTS_COOKIE_NAME, type KnownAccountEntry, type MySqlBundle, type NotificationRoutesConfig, OAUTH_STATE_COOKIE_NAME, type OAuthBundle, type PermissionsPolicyMap, type PermissionsPolicyValue, type PostgresBundle, type PushRoutesConfig, type RedisBundle, type S3Bundle, SESSION_COOKIE_NAME, type SafeRedirectOptions, ScaleMuleError, type SecurityHeaderEntry, ServerConfig, type SessionCookieOptions, type SessionData, type SmtpBundle, USER_ID_COOKIE_NAME, apiHandler, appendKnownAccountCookie, buildClientContextHeaders, buildFlagContext, buildSecurityHeaders, clearKnownAccountsCookie, clearOAuthState, clearSession, configureBundles, configureSecrets, createAnalyticsRoutes, createAuthMiddleware, createAuthRoutes, createNotificationRoutes, createPushRoutes, errorCodeToStatus, extractClientContext, extractClientContextFromReq, generateCSRFToken, getAppSecret, getAppSecretOrDefault, getBootstrapFlags, getBundle, getCSRFToken, getKnownAccountsCookieRaw, getKnownAccountsFromRequest, getMySqlBundle, getOAuthBundle, getPostgresBundle, getRedisBundle, getS3Bundle, getSession, getSessionFromRequest, getSmtpBundle, invalidateBundleCache, invalidateSecretCache, isSafeRedirect, normalizeKnownAccountsCookie, prefetchBundles, prefetchSecrets, removeKnownAccountFromCookie, requireAppSecret, requireBundle, requireSession, setOAuthState, unwrap, validateCSRFToken, validateCSRFTokenAsync, validateOAuthState, validateOAuthStateAsync, validateSafeRedirect, withAuth, withCSRFProtection, withCSRFToken, withSession };
|
package/dist/server/index.js
CHANGED
|
@@ -5145,8 +5145,77 @@ function isSafeRedirect(input, opts = {}) {
|
|
|
5145
5145
|
return resolved !== defaultPath;
|
|
5146
5146
|
}
|
|
5147
5147
|
|
|
5148
|
+
// src/server/security-headers.ts
|
|
5149
|
+
var DEFAULT_PERMISSIONS_POLICY = {
|
|
5150
|
+
// Conference track features — allow self-origin only.
|
|
5151
|
+
camera: "self",
|
|
5152
|
+
microphone: "self",
|
|
5153
|
+
"display-capture": "self",
|
|
5154
|
+
// Deny-by-default for features the SDK never uses.
|
|
5155
|
+
geolocation: "()",
|
|
5156
|
+
payment: "()",
|
|
5157
|
+
"publickey-credentials-get": "self",
|
|
5158
|
+
usb: "()",
|
|
5159
|
+
serial: "()",
|
|
5160
|
+
bluetooth: "()"
|
|
5161
|
+
};
|
|
5162
|
+
function formatPermissionsDirective(feature, value) {
|
|
5163
|
+
if (value === "*") return `${feature}=*`;
|
|
5164
|
+
if (value === "()" || Array.isArray(value) && value.length === 0) {
|
|
5165
|
+
return `${feature}=()`;
|
|
5166
|
+
}
|
|
5167
|
+
if (value === "self") return `${feature}=(self)`;
|
|
5168
|
+
const origins = value;
|
|
5169
|
+
const parts = origins.map((o) => o === "self" ? "self" : `"${o}"`);
|
|
5170
|
+
return `${feature}=(${parts.join(" ")})`;
|
|
5171
|
+
}
|
|
5172
|
+
function buildSecurityHeaders(opts = {}) {
|
|
5173
|
+
const headers2 = [];
|
|
5174
|
+
const includeHsts = opts.includeHsts ?? true;
|
|
5175
|
+
const hstsMaxAge = opts.hstsMaxAgeSeconds ?? 31536e3;
|
|
5176
|
+
if (includeHsts && hstsMaxAge > 0) {
|
|
5177
|
+
const parts = [`max-age=${hstsMaxAge}`];
|
|
5178
|
+
if (opts.hstsIncludeSubDomains ?? true) parts.push("includeSubDomains");
|
|
5179
|
+
if (opts.hstsPreload ?? false) parts.push("preload");
|
|
5180
|
+
headers2.push({
|
|
5181
|
+
key: "Strict-Transport-Security",
|
|
5182
|
+
value: parts.join("; ")
|
|
5183
|
+
});
|
|
5184
|
+
}
|
|
5185
|
+
const frameOptions = opts.frameOptions === void 0 ? "DENY" : opts.frameOptions;
|
|
5186
|
+
if (frameOptions !== null) {
|
|
5187
|
+
headers2.push({ key: "X-Frame-Options", value: frameOptions });
|
|
5188
|
+
}
|
|
5189
|
+
const contentTypeOptions = opts.contentTypeOptions === void 0 ? "nosniff" : opts.contentTypeOptions;
|
|
5190
|
+
if (contentTypeOptions !== null) {
|
|
5191
|
+
headers2.push({ key: "X-Content-Type-Options", value: contentTypeOptions });
|
|
5192
|
+
}
|
|
5193
|
+
headers2.push({
|
|
5194
|
+
key: "Referrer-Policy",
|
|
5195
|
+
value: opts.referrerPolicy ?? "strict-origin-when-cross-origin"
|
|
5196
|
+
});
|
|
5197
|
+
const xss = opts.xssProtection === void 0 ? "0" : opts.xssProtection;
|
|
5198
|
+
if (xss !== null) {
|
|
5199
|
+
headers2.push({ key: "X-XSS-Protection", value: xss });
|
|
5200
|
+
}
|
|
5201
|
+
const permissions = opts.permissionsPolicy === void 0 ? DEFAULT_PERMISSIONS_POLICY : opts.permissionsPolicy;
|
|
5202
|
+
if (permissions !== null) {
|
|
5203
|
+
const directives = Object.entries(permissions).map(([feature, value]) => formatPermissionsDirective(feature, value)).join(", ");
|
|
5204
|
+
headers2.push({ key: "Permissions-Policy", value: directives });
|
|
5205
|
+
}
|
|
5206
|
+
if (opts.extraHeaders?.length) {
|
|
5207
|
+
const byKey = new Map(headers2.map((h) => [h.key.toLowerCase(), h]));
|
|
5208
|
+
for (const extra of opts.extraHeaders) {
|
|
5209
|
+
byKey.set(extra.key.toLowerCase(), extra);
|
|
5210
|
+
}
|
|
5211
|
+
return Array.from(byKey.values());
|
|
5212
|
+
}
|
|
5213
|
+
return headers2;
|
|
5214
|
+
}
|
|
5215
|
+
|
|
5148
5216
|
exports.CSRF_COOKIE_NAME = CSRF_COOKIE_NAME;
|
|
5149
5217
|
exports.CSRF_HEADER_NAME = CSRF_HEADER_NAME;
|
|
5218
|
+
exports.DEFAULT_PERMISSIONS_POLICY = DEFAULT_PERMISSIONS_POLICY;
|
|
5150
5219
|
exports.KNOWN_ACCOUNTS_COOKIE_NAME = KNOWN_ACCOUNTS_COOKIE_NAME;
|
|
5151
5220
|
exports.OAUTH_STATE_COOKIE_NAME = OAUTH_STATE_COOKIE_NAME;
|
|
5152
5221
|
exports.SESSION_COOKIE_NAME = SESSION_COOKIE_NAME;
|
|
@@ -5157,6 +5226,7 @@ exports.apiHandler = apiHandler;
|
|
|
5157
5226
|
exports.appendKnownAccountCookie = appendKnownAccountCookie;
|
|
5158
5227
|
exports.buildClientContextHeaders = buildClientContextHeaders;
|
|
5159
5228
|
exports.buildFlagContext = buildFlagContext;
|
|
5229
|
+
exports.buildSecurityHeaders = buildSecurityHeaders;
|
|
5160
5230
|
exports.clearKnownAccountsCookie = clearKnownAccountsCookie;
|
|
5161
5231
|
exports.clearOAuthState = clearOAuthState;
|
|
5162
5232
|
exports.clearSession = clearSession;
|
package/dist/server/index.mjs
CHANGED
|
@@ -5143,4 +5143,72 @@ function isSafeRedirect(input, opts = {}) {
|
|
|
5143
5143
|
return resolved !== defaultPath;
|
|
5144
5144
|
}
|
|
5145
5145
|
|
|
5146
|
-
|
|
5146
|
+
// src/server/security-headers.ts
|
|
5147
|
+
var DEFAULT_PERMISSIONS_POLICY = {
|
|
5148
|
+
// Conference track features — allow self-origin only.
|
|
5149
|
+
camera: "self",
|
|
5150
|
+
microphone: "self",
|
|
5151
|
+
"display-capture": "self",
|
|
5152
|
+
// Deny-by-default for features the SDK never uses.
|
|
5153
|
+
geolocation: "()",
|
|
5154
|
+
payment: "()",
|
|
5155
|
+
"publickey-credentials-get": "self",
|
|
5156
|
+
usb: "()",
|
|
5157
|
+
serial: "()",
|
|
5158
|
+
bluetooth: "()"
|
|
5159
|
+
};
|
|
5160
|
+
function formatPermissionsDirective(feature, value) {
|
|
5161
|
+
if (value === "*") return `${feature}=*`;
|
|
5162
|
+
if (value === "()" || Array.isArray(value) && value.length === 0) {
|
|
5163
|
+
return `${feature}=()`;
|
|
5164
|
+
}
|
|
5165
|
+
if (value === "self") return `${feature}=(self)`;
|
|
5166
|
+
const origins = value;
|
|
5167
|
+
const parts = origins.map((o) => o === "self" ? "self" : `"${o}"`);
|
|
5168
|
+
return `${feature}=(${parts.join(" ")})`;
|
|
5169
|
+
}
|
|
5170
|
+
function buildSecurityHeaders(opts = {}) {
|
|
5171
|
+
const headers2 = [];
|
|
5172
|
+
const includeHsts = opts.includeHsts ?? true;
|
|
5173
|
+
const hstsMaxAge = opts.hstsMaxAgeSeconds ?? 31536e3;
|
|
5174
|
+
if (includeHsts && hstsMaxAge > 0) {
|
|
5175
|
+
const parts = [`max-age=${hstsMaxAge}`];
|
|
5176
|
+
if (opts.hstsIncludeSubDomains ?? true) parts.push("includeSubDomains");
|
|
5177
|
+
if (opts.hstsPreload ?? false) parts.push("preload");
|
|
5178
|
+
headers2.push({
|
|
5179
|
+
key: "Strict-Transport-Security",
|
|
5180
|
+
value: parts.join("; ")
|
|
5181
|
+
});
|
|
5182
|
+
}
|
|
5183
|
+
const frameOptions = opts.frameOptions === void 0 ? "DENY" : opts.frameOptions;
|
|
5184
|
+
if (frameOptions !== null) {
|
|
5185
|
+
headers2.push({ key: "X-Frame-Options", value: frameOptions });
|
|
5186
|
+
}
|
|
5187
|
+
const contentTypeOptions = opts.contentTypeOptions === void 0 ? "nosniff" : opts.contentTypeOptions;
|
|
5188
|
+
if (contentTypeOptions !== null) {
|
|
5189
|
+
headers2.push({ key: "X-Content-Type-Options", value: contentTypeOptions });
|
|
5190
|
+
}
|
|
5191
|
+
headers2.push({
|
|
5192
|
+
key: "Referrer-Policy",
|
|
5193
|
+
value: opts.referrerPolicy ?? "strict-origin-when-cross-origin"
|
|
5194
|
+
});
|
|
5195
|
+
const xss = opts.xssProtection === void 0 ? "0" : opts.xssProtection;
|
|
5196
|
+
if (xss !== null) {
|
|
5197
|
+
headers2.push({ key: "X-XSS-Protection", value: xss });
|
|
5198
|
+
}
|
|
5199
|
+
const permissions = opts.permissionsPolicy === void 0 ? DEFAULT_PERMISSIONS_POLICY : opts.permissionsPolicy;
|
|
5200
|
+
if (permissions !== null) {
|
|
5201
|
+
const directives = Object.entries(permissions).map(([feature, value]) => formatPermissionsDirective(feature, value)).join(", ");
|
|
5202
|
+
headers2.push({ key: "Permissions-Policy", value: directives });
|
|
5203
|
+
}
|
|
5204
|
+
if (opts.extraHeaders?.length) {
|
|
5205
|
+
const byKey = new Map(headers2.map((h) => [h.key.toLowerCase(), h]));
|
|
5206
|
+
for (const extra of opts.extraHeaders) {
|
|
5207
|
+
byKey.set(extra.key.toLowerCase(), extra);
|
|
5208
|
+
}
|
|
5209
|
+
return Array.from(byKey.values());
|
|
5210
|
+
}
|
|
5211
|
+
return headers2;
|
|
5212
|
+
}
|
|
5213
|
+
|
|
5214
|
+
export { CSRF_COOKIE_NAME, CSRF_HEADER_NAME, DEFAULT_PERMISSIONS_POLICY, KNOWN_ACCOUNTS_COOKIE_NAME, OAUTH_STATE_COOKIE_NAME, SESSION_COOKIE_NAME, ScaleMuleError, ScaleMuleServer, USER_ID_COOKIE_NAME, apiHandler, appendKnownAccountCookie, buildClientContextHeaders, buildFlagContext, buildSecurityHeaders, clearKnownAccountsCookie, clearOAuthState, clearSession, configureBundles, configureSecrets, createAnalyticsRoutes, createAuthMiddleware, createAuthRoutes, createNotificationRoutes, createPushRoutes, createServerClient, createWebhookHandler, createWebhookRoutes, errorCodeToStatus, extractClientContext, extractClientContextFromReq, generateCSRFToken, getAppSecret, getAppSecretOrDefault, getBootstrapFlags, getBundle, getCSRFToken, getKnownAccountsCookieRaw, getKnownAccountsFromRequest, getMySqlBundle, getOAuthBundle, getPostgresBundle, getRedisBundle, getS3Bundle, getSession, getSessionFromRequest, getSmtpBundle, invalidateBundleCache, invalidateSecretCache, isSafeRedirect, normalizeKnownAccountsCookie, parseWebhookEvent, prefetchBundles, prefetchSecrets, registerVideoWebhook, removeKnownAccountFromCookie, requireAppSecret, requireBundle, requireSession, resolveGatewayUrl, setOAuthState, unwrap, validateCSRFToken, validateCSRFTokenAsync, validateOAuthState, validateOAuthStateAsync, validateSafeRedirect, verifyWebhookSignature, withAuth, withCSRFProtection, withCSRFToken, withSession };
|
package/package.json
CHANGED