nextjs-secure 0.2.0 → 0.3.0

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/index.cjs CHANGED
@@ -1178,29 +1178,307 @@ async function validateCSRF(req, config = {}) {
1178
1178
  return { valid: true };
1179
1179
  }
1180
1180
 
1181
+ // src/middleware/headers/builder.ts
1182
+ function buildCSP(policy) {
1183
+ const directives = [];
1184
+ const directiveMap = {
1185
+ defaultSrc: "default-src",
1186
+ scriptSrc: "script-src",
1187
+ styleSrc: "style-src",
1188
+ imgSrc: "img-src",
1189
+ fontSrc: "font-src",
1190
+ connectSrc: "connect-src",
1191
+ mediaSrc: "media-src",
1192
+ objectSrc: "object-src",
1193
+ frameSrc: "frame-src",
1194
+ childSrc: "child-src",
1195
+ workerSrc: "worker-src",
1196
+ frameAncestors: "frame-ancestors",
1197
+ formAction: "form-action",
1198
+ baseUri: "base-uri",
1199
+ manifestSrc: "manifest-src",
1200
+ reportUri: "report-uri",
1201
+ reportTo: "report-to"
1202
+ };
1203
+ for (const [key, directive] of Object.entries(directiveMap)) {
1204
+ const value = policy[key];
1205
+ if (value !== void 0 && value !== false) {
1206
+ if (Array.isArray(value)) {
1207
+ directives.push(`${directive} ${value.join(" ")}`);
1208
+ } else if (typeof value === "string") {
1209
+ directives.push(`${directive} ${value}`);
1210
+ }
1211
+ }
1212
+ }
1213
+ if (policy.upgradeInsecureRequests) {
1214
+ directives.push("upgrade-insecure-requests");
1215
+ }
1216
+ if (policy.blockAllMixedContent) {
1217
+ directives.push("block-all-mixed-content");
1218
+ }
1219
+ return directives.join("; ");
1220
+ }
1221
+ function buildHSTS(config) {
1222
+ let value = `max-age=${config.maxAge}`;
1223
+ if (config.includeSubDomains) {
1224
+ value += "; includeSubDomains";
1225
+ }
1226
+ if (config.preload) {
1227
+ value += "; preload";
1228
+ }
1229
+ return value;
1230
+ }
1231
+ function buildPermissionsPolicy(policy) {
1232
+ const directives = [];
1233
+ const featureMap = {
1234
+ accelerometer: "accelerometer",
1235
+ ambientLightSensor: "ambient-light-sensor",
1236
+ autoplay: "autoplay",
1237
+ battery: "battery",
1238
+ camera: "camera",
1239
+ displayCapture: "display-capture",
1240
+ documentDomain: "document-domain",
1241
+ encryptedMedia: "encrypted-media",
1242
+ fullscreen: "fullscreen",
1243
+ geolocation: "geolocation",
1244
+ gyroscope: "gyroscope",
1245
+ magnetometer: "magnetometer",
1246
+ microphone: "microphone",
1247
+ midi: "midi",
1248
+ payment: "payment",
1249
+ pictureInPicture: "picture-in-picture",
1250
+ publicKeyCredentialsGet: "publickey-credentials-get",
1251
+ screenWakeLock: "screen-wake-lock",
1252
+ syncXhr: "sync-xhr",
1253
+ usb: "usb",
1254
+ webShare: "web-share",
1255
+ xrSpatialTracking: "xr-spatial-tracking"
1256
+ };
1257
+ for (const [key, feature] of Object.entries(featureMap)) {
1258
+ const origins = policy[key];
1259
+ if (origins !== void 0) {
1260
+ if (origins.length === 0) {
1261
+ directives.push(`${feature}=()`);
1262
+ } else {
1263
+ const formatted = origins.map((o) => o === "self" ? "self" : `"${o}"`).join(" ");
1264
+ directives.push(`${feature}=(${formatted})`);
1265
+ }
1266
+ }
1267
+ }
1268
+ return directives.join(", ");
1269
+ }
1270
+ var PRESET_STRICT = {
1271
+ contentSecurityPolicy: {
1272
+ defaultSrc: ["'self'"],
1273
+ scriptSrc: ["'self'"],
1274
+ styleSrc: ["'self'"],
1275
+ imgSrc: ["'self'", "data:"],
1276
+ fontSrc: ["'self'"],
1277
+ objectSrc: ["'none'"],
1278
+ frameAncestors: ["'none'"],
1279
+ formAction: ["'self'"],
1280
+ baseUri: ["'self'"],
1281
+ upgradeInsecureRequests: true
1282
+ },
1283
+ strictTransportSecurity: {
1284
+ maxAge: 31536e3,
1285
+ // 1 year
1286
+ includeSubDomains: true,
1287
+ preload: true
1288
+ },
1289
+ xFrameOptions: "DENY",
1290
+ xContentTypeOptions: true,
1291
+ xDnsPrefetchControl: "off",
1292
+ xDownloadOptions: true,
1293
+ xPermittedCrossDomainPolicies: "none",
1294
+ referrerPolicy: "strict-origin-when-cross-origin",
1295
+ crossOriginOpenerPolicy: "same-origin",
1296
+ crossOriginEmbedderPolicy: "require-corp",
1297
+ crossOriginResourcePolicy: "same-origin",
1298
+ permissionsPolicy: {
1299
+ camera: [],
1300
+ microphone: [],
1301
+ geolocation: [],
1302
+ payment: []
1303
+ },
1304
+ originAgentCluster: true
1305
+ };
1306
+ var PRESET_RELAXED = {
1307
+ contentSecurityPolicy: {
1308
+ defaultSrc: ["'self'"],
1309
+ scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
1310
+ styleSrc: ["'self'", "'unsafe-inline'"],
1311
+ imgSrc: ["'self'", "data:", "blob:", "https:"],
1312
+ fontSrc: ["'self'", "https:", "data:"],
1313
+ connectSrc: ["'self'", "https:", "wss:"],
1314
+ frameSrc: ["'self'"]
1315
+ },
1316
+ strictTransportSecurity: {
1317
+ maxAge: 86400,
1318
+ // 1 day
1319
+ includeSubDomains: false
1320
+ },
1321
+ xFrameOptions: "SAMEORIGIN",
1322
+ xContentTypeOptions: true,
1323
+ referrerPolicy: "no-referrer-when-downgrade"
1324
+ };
1325
+ var PRESET_API = {
1326
+ contentSecurityPolicy: {
1327
+ defaultSrc: ["'none'"],
1328
+ frameAncestors: ["'none'"]
1329
+ },
1330
+ strictTransportSecurity: {
1331
+ maxAge: 31536e3,
1332
+ includeSubDomains: true
1333
+ },
1334
+ xFrameOptions: "DENY",
1335
+ xContentTypeOptions: true,
1336
+ referrerPolicy: "no-referrer",
1337
+ crossOriginResourcePolicy: "same-origin"
1338
+ };
1339
+ function getPreset(name) {
1340
+ switch (name) {
1341
+ case "strict":
1342
+ return PRESET_STRICT;
1343
+ case "relaxed":
1344
+ return PRESET_RELAXED;
1345
+ case "api":
1346
+ return PRESET_API;
1347
+ default:
1348
+ return PRESET_STRICT;
1349
+ }
1350
+ }
1351
+ function buildHeaders(config) {
1352
+ const headers = new Headers();
1353
+ if (config.contentSecurityPolicy) {
1354
+ const csp = buildCSP(config.contentSecurityPolicy);
1355
+ if (csp) headers.set("Content-Security-Policy", csp);
1356
+ }
1357
+ if (config.strictTransportSecurity) {
1358
+ headers.set("Strict-Transport-Security", buildHSTS(config.strictTransportSecurity));
1359
+ }
1360
+ if (config.xFrameOptions) {
1361
+ headers.set("X-Frame-Options", config.xFrameOptions);
1362
+ }
1363
+ if (config.xContentTypeOptions) {
1364
+ headers.set("X-Content-Type-Options", "nosniff");
1365
+ }
1366
+ if (config.xDnsPrefetchControl) {
1367
+ headers.set("X-DNS-Prefetch-Control", config.xDnsPrefetchControl);
1368
+ }
1369
+ if (config.xDownloadOptions) {
1370
+ headers.set("X-Download-Options", "noopen");
1371
+ }
1372
+ if (config.xPermittedCrossDomainPolicies) {
1373
+ headers.set("X-Permitted-Cross-Domain-Policies", config.xPermittedCrossDomainPolicies);
1374
+ }
1375
+ if (config.referrerPolicy) {
1376
+ const value = Array.isArray(config.referrerPolicy) ? config.referrerPolicy.join(", ") : config.referrerPolicy;
1377
+ headers.set("Referrer-Policy", value);
1378
+ }
1379
+ if (config.crossOriginOpenerPolicy) {
1380
+ headers.set("Cross-Origin-Opener-Policy", config.crossOriginOpenerPolicy);
1381
+ }
1382
+ if (config.crossOriginEmbedderPolicy) {
1383
+ headers.set("Cross-Origin-Embedder-Policy", config.crossOriginEmbedderPolicy);
1384
+ }
1385
+ if (config.crossOriginResourcePolicy) {
1386
+ headers.set("Cross-Origin-Resource-Policy", config.crossOriginResourcePolicy);
1387
+ }
1388
+ if (config.permissionsPolicy) {
1389
+ const pp = buildPermissionsPolicy(config.permissionsPolicy);
1390
+ if (pp) headers.set("Permissions-Policy", pp);
1391
+ }
1392
+ if (config.originAgentCluster) {
1393
+ headers.set("Origin-Agent-Cluster", "?1");
1394
+ }
1395
+ return headers;
1396
+ }
1397
+
1398
+ // src/middleware/headers/middleware.ts
1399
+ function mergeConfigs(base, custom) {
1400
+ return {
1401
+ ...base,
1402
+ ...custom,
1403
+ // Deep merge CSP if both exist
1404
+ contentSecurityPolicy: custom.contentSecurityPolicy === false ? false : custom.contentSecurityPolicy ? base.contentSecurityPolicy ? { ...base.contentSecurityPolicy, ...custom.contentSecurityPolicy } : custom.contentSecurityPolicy : base.contentSecurityPolicy,
1405
+ // Deep merge HSTS if both exist
1406
+ strictTransportSecurity: custom.strictTransportSecurity === false ? false : custom.strictTransportSecurity ? base.strictTransportSecurity ? { ...base.strictTransportSecurity, ...custom.strictTransportSecurity } : custom.strictTransportSecurity : base.strictTransportSecurity,
1407
+ // Deep merge Permissions-Policy if both exist
1408
+ permissionsPolicy: custom.permissionsPolicy === false ? false : custom.permissionsPolicy ? base.permissionsPolicy ? { ...base.permissionsPolicy, ...custom.permissionsPolicy } : custom.permissionsPolicy : base.permissionsPolicy
1409
+ };
1410
+ }
1411
+ function withSecurityHeaders(handler, options = {}) {
1412
+ const { preset, config, override = false } = options;
1413
+ let baseConfig = preset ? getPreset(preset) : PRESET_STRICT;
1414
+ if (config) {
1415
+ baseConfig = mergeConfigs(baseConfig, config);
1416
+ }
1417
+ const securityHeaders = buildHeaders(baseConfig);
1418
+ return async (req) => {
1419
+ const response = await handler(req);
1420
+ const newHeaders = new Headers(response.headers);
1421
+ securityHeaders.forEach((value, key) => {
1422
+ if (override || !newHeaders.has(key)) {
1423
+ newHeaders.set(key, value);
1424
+ }
1425
+ });
1426
+ return new Response(response.body, {
1427
+ status: response.status,
1428
+ statusText: response.statusText,
1429
+ headers: newHeaders
1430
+ });
1431
+ };
1432
+ }
1433
+ function createSecurityHeaders(options = {}) {
1434
+ const { preset, config } = options;
1435
+ let baseConfig = preset ? getPreset(preset) : PRESET_STRICT;
1436
+ if (config) {
1437
+ baseConfig = mergeConfigs(baseConfig, config);
1438
+ }
1439
+ return buildHeaders(baseConfig);
1440
+ }
1441
+ function createSecurityHeadersObject(options = {}) {
1442
+ const headers = createSecurityHeaders(options);
1443
+ const obj = {};
1444
+ headers.forEach((value, key) => {
1445
+ obj[key] = value;
1446
+ });
1447
+ return obj;
1448
+ }
1449
+
1181
1450
  // src/index.ts
1182
- var VERSION = "0.2.0";
1451
+ var VERSION = "0.3.0";
1183
1452
 
1184
1453
  exports.AuthenticationError = AuthenticationError;
1185
1454
  exports.AuthorizationError = AuthorizationError;
1186
1455
  exports.ConfigurationError = ConfigurationError;
1187
1456
  exports.CsrfError = CsrfError;
1188
1457
  exports.MemoryStore = MemoryStore;
1458
+ exports.PRESET_API = PRESET_API;
1459
+ exports.PRESET_RELAXED = PRESET_RELAXED;
1460
+ exports.PRESET_STRICT = PRESET_STRICT;
1189
1461
  exports.RateLimitError = RateLimitError;
1190
1462
  exports.SecureError = SecureError;
1191
1463
  exports.VERSION = VERSION;
1192
1464
  exports.ValidationError = ValidationError;
1193
1465
  exports.anonymizeIp = anonymizeIp;
1466
+ exports.buildCSP = buildCSP;
1467
+ exports.buildHSTS = buildHSTS;
1468
+ exports.buildPermissionsPolicy = buildPermissionsPolicy;
1194
1469
  exports.checkRateLimit = checkRateLimit;
1195
1470
  exports.clearAllRateLimits = clearAllRateLimits;
1196
1471
  exports.createCSRFToken = createToken;
1197
1472
  exports.createMemoryStore = createMemoryStore;
1198
1473
  exports.createRateLimiter = createRateLimiter;
1474
+ exports.createSecurityHeaders = createSecurityHeaders;
1475
+ exports.createSecurityHeadersObject = createSecurityHeadersObject;
1199
1476
  exports.formatDuration = formatDuration;
1200
1477
  exports.generateCSRF = generateCSRF;
1201
1478
  exports.getClientIp = getClientIp;
1202
1479
  exports.getGeoInfo = getGeoInfo;
1203
1480
  exports.getGlobalMemoryStore = getGlobalMemoryStore;
1481
+ exports.getPreset = getPreset;
1204
1482
  exports.getRateLimitStatus = getRateLimitStatus;
1205
1483
  exports.isLocalhost = isLocalhost;
1206
1484
  exports.isPrivateIp = isPrivateIp;
@@ -1218,5 +1496,6 @@ exports.validateCSRF = validateCSRF;
1218
1496
  exports.verifyCSRFToken = verifyToken;
1219
1497
  exports.withCSRF = withCSRF;
1220
1498
  exports.withRateLimit = withRateLimit;
1499
+ exports.withSecurityHeaders = withSecurityHeaders;
1221
1500
  //# sourceMappingURL=index.cjs.map
1222
1501
  //# sourceMappingURL=index.cjs.map