nextjs-secure 0.1.1 → 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/README.md +243 -0
- package/dist/csrf.cjs +187 -10
- package/dist/csrf.cjs.map +1 -1
- package/dist/csrf.d.cts +71 -22
- package/dist/csrf.d.ts +71 -22
- package/dist/csrf.js +182 -8
- package/dist/csrf.js.map +1 -1
- package/dist/headers.cjs +277 -7
- package/dist/headers.cjs.map +1 -1
- package/dist/headers.d.cts +162 -25
- package/dist/headers.d.ts +162 -25
- package/dist/headers.js +267 -6
- package/dist/headers.js.map +1 -1
- package/dist/index.cjs +469 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +454 -2
- package/dist/index.js.map +1 -1
- package/dist/{memory-g9zyQPy5.d.cts → memory-Dauy-IH3.d.cts} +1 -1
- package/dist/{memory-g9zyQPy5.d.ts → memory-Dauy-IH3.d.ts} +1 -1
- package/dist/rate-limit.d.cts +2 -2
- package/dist/rate-limit.d.ts +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { webcrypto } from 'crypto';
|
|
2
|
+
|
|
1
3
|
// src/core/errors.ts
|
|
2
4
|
var SecureError = class extends Error {
|
|
3
5
|
/**
|
|
@@ -992,10 +994,460 @@ function clearAllRateLimits() {
|
|
|
992
994
|
defaultStore.clear();
|
|
993
995
|
}
|
|
994
996
|
}
|
|
997
|
+
var encoder = new TextEncoder();
|
|
998
|
+
function randomBytes(length) {
|
|
999
|
+
const bytes = new Uint8Array(length);
|
|
1000
|
+
webcrypto.getRandomValues(bytes);
|
|
1001
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1002
|
+
}
|
|
1003
|
+
async function createSignature(data, secret) {
|
|
1004
|
+
const key = await webcrypto.subtle.importKey(
|
|
1005
|
+
"raw",
|
|
1006
|
+
encoder.encode(secret),
|
|
1007
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
1008
|
+
false,
|
|
1009
|
+
["sign"]
|
|
1010
|
+
);
|
|
1011
|
+
const sig = await webcrypto.subtle.sign("HMAC", key, encoder.encode(data));
|
|
1012
|
+
return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1013
|
+
}
|
|
1014
|
+
function safeCompare(a, b) {
|
|
1015
|
+
if (a.length !== b.length) return false;
|
|
1016
|
+
let result = 0;
|
|
1017
|
+
for (let i = 0; i < a.length; i++) {
|
|
1018
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
1019
|
+
}
|
|
1020
|
+
return result === 0;
|
|
1021
|
+
}
|
|
1022
|
+
async function createToken(secret, length = 32) {
|
|
1023
|
+
const data = randomBytes(length);
|
|
1024
|
+
const sig = await createSignature(data, secret);
|
|
1025
|
+
return `${data}.${sig}`;
|
|
1026
|
+
}
|
|
1027
|
+
async function verifyToken(token, secret) {
|
|
1028
|
+
if (!token || typeof token !== "string") return false;
|
|
1029
|
+
const parts = token.split(".");
|
|
1030
|
+
if (parts.length !== 2) return false;
|
|
1031
|
+
const [data, sig] = parts;
|
|
1032
|
+
if (!data || !sig) return false;
|
|
1033
|
+
try {
|
|
1034
|
+
const expected = await createSignature(data, secret);
|
|
1035
|
+
return safeCompare(sig, expected);
|
|
1036
|
+
} catch {
|
|
1037
|
+
return false;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
function tokensMatch(a, b) {
|
|
1041
|
+
if (!a || !b) return false;
|
|
1042
|
+
return safeCompare(a, b);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// src/middleware/csrf/middleware.ts
|
|
1046
|
+
var DEFAULT_COOKIE = {
|
|
1047
|
+
name: "__csrf",
|
|
1048
|
+
path: "/",
|
|
1049
|
+
httpOnly: true,
|
|
1050
|
+
secure: process.env.NODE_ENV === "production",
|
|
1051
|
+
sameSite: "strict",
|
|
1052
|
+
maxAge: 86400
|
|
1053
|
+
// 24h
|
|
1054
|
+
};
|
|
1055
|
+
var DEFAULT_CONFIG2 = {
|
|
1056
|
+
headerName: "x-csrf-token",
|
|
1057
|
+
fieldName: "_csrf",
|
|
1058
|
+
tokenLength: 32,
|
|
1059
|
+
protectedMethods: ["POST", "PUT", "PATCH", "DELETE"]
|
|
1060
|
+
};
|
|
1061
|
+
function getSecret(config) {
|
|
1062
|
+
const secret = config.secret || process.env.CSRF_SECRET;
|
|
1063
|
+
if (!secret) {
|
|
1064
|
+
throw new Error(
|
|
1065
|
+
"CSRF secret is required. Set config.secret or CSRF_SECRET env variable."
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
return secret;
|
|
1069
|
+
}
|
|
1070
|
+
function buildCookieString(name, value, opts) {
|
|
1071
|
+
let cookie = `${name}=${value}`;
|
|
1072
|
+
if (opts.path) cookie += `; Path=${opts.path}`;
|
|
1073
|
+
if (opts.domain) cookie += `; Domain=${opts.domain}`;
|
|
1074
|
+
if (opts.maxAge) cookie += `; Max-Age=${opts.maxAge}`;
|
|
1075
|
+
if (opts.httpOnly) cookie += "; HttpOnly";
|
|
1076
|
+
if (opts.secure) cookie += "; Secure";
|
|
1077
|
+
if (opts.sameSite) cookie += `; SameSite=${opts.sameSite}`;
|
|
1078
|
+
return cookie;
|
|
1079
|
+
}
|
|
1080
|
+
async function extractToken(req, headerName, fieldName) {
|
|
1081
|
+
const headerToken = req.headers.get(headerName);
|
|
1082
|
+
if (headerToken) return headerToken;
|
|
1083
|
+
const contentType = req.headers.get("content-type") || "";
|
|
1084
|
+
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
1085
|
+
try {
|
|
1086
|
+
const cloned = req.clone();
|
|
1087
|
+
const formData = await cloned.formData();
|
|
1088
|
+
const token = formData.get(fieldName);
|
|
1089
|
+
if (typeof token === "string") return token;
|
|
1090
|
+
} catch {
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
if (contentType.includes("application/json")) {
|
|
1094
|
+
try {
|
|
1095
|
+
const cloned = req.clone();
|
|
1096
|
+
const body = await cloned.json();
|
|
1097
|
+
if (body && typeof body[fieldName] === "string") {
|
|
1098
|
+
return body[fieldName];
|
|
1099
|
+
}
|
|
1100
|
+
} catch {
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
return null;
|
|
1104
|
+
}
|
|
1105
|
+
function defaultErrorResponse(_req, reason) {
|
|
1106
|
+
return new Response(JSON.stringify({ error: "CSRF validation failed", reason }), {
|
|
1107
|
+
status: 403,
|
|
1108
|
+
headers: { "Content-Type": "application/json" }
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
function withCSRF(handler, config = {}) {
|
|
1112
|
+
const secret = getSecret(config);
|
|
1113
|
+
const cookieOpts = { ...DEFAULT_COOKIE, ...config.cookie };
|
|
1114
|
+
const headerName = config.headerName || DEFAULT_CONFIG2.headerName;
|
|
1115
|
+
const fieldName = config.fieldName || DEFAULT_CONFIG2.fieldName;
|
|
1116
|
+
const protectedMethods = config.protectedMethods || DEFAULT_CONFIG2.protectedMethods;
|
|
1117
|
+
const onError = config.onError || defaultErrorResponse;
|
|
1118
|
+
return async (req) => {
|
|
1119
|
+
const method = req.method.toUpperCase();
|
|
1120
|
+
if (!protectedMethods.includes(method)) {
|
|
1121
|
+
return handler(req);
|
|
1122
|
+
}
|
|
1123
|
+
if (config.skip) {
|
|
1124
|
+
const shouldSkip = await config.skip(req);
|
|
1125
|
+
if (shouldSkip) return handler(req);
|
|
1126
|
+
}
|
|
1127
|
+
const cookieName = cookieOpts.name || "__csrf";
|
|
1128
|
+
const cookieToken = req.cookies.get(cookieName)?.value;
|
|
1129
|
+
if (!cookieToken) {
|
|
1130
|
+
return onError(req, "missing_cookie");
|
|
1131
|
+
}
|
|
1132
|
+
const cookieValid = await verifyToken(cookieToken, secret);
|
|
1133
|
+
if (!cookieValid) {
|
|
1134
|
+
return onError(req, "invalid_cookie");
|
|
1135
|
+
}
|
|
1136
|
+
const requestToken = await extractToken(req, headerName, fieldName);
|
|
1137
|
+
if (!requestToken) {
|
|
1138
|
+
return onError(req, "missing_token");
|
|
1139
|
+
}
|
|
1140
|
+
if (!tokensMatch(cookieToken, requestToken)) {
|
|
1141
|
+
return onError(req, "token_mismatch");
|
|
1142
|
+
}
|
|
1143
|
+
return handler(req);
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
async function generateCSRF(config = {}) {
|
|
1147
|
+
const secret = getSecret(config);
|
|
1148
|
+
const cookieOpts = { ...DEFAULT_COOKIE, ...config.cookie };
|
|
1149
|
+
const tokenLength = config.tokenLength || DEFAULT_CONFIG2.tokenLength;
|
|
1150
|
+
const cookieName = cookieOpts.name || "__csrf";
|
|
1151
|
+
const token = await createToken(secret, tokenLength);
|
|
1152
|
+
const cookieHeader = buildCookieString(cookieName, token, cookieOpts);
|
|
1153
|
+
return { token, cookieHeader };
|
|
1154
|
+
}
|
|
1155
|
+
async function validateCSRF(req, config = {}) {
|
|
1156
|
+
const secret = getSecret(config);
|
|
1157
|
+
const cookieOpts = { ...DEFAULT_COOKIE, ...config.cookie };
|
|
1158
|
+
const headerName = config.headerName || DEFAULT_CONFIG2.headerName;
|
|
1159
|
+
const fieldName = config.fieldName || DEFAULT_CONFIG2.fieldName;
|
|
1160
|
+
const cookieName = cookieOpts.name || "__csrf";
|
|
1161
|
+
const cookieToken = req.cookies.get(cookieName)?.value;
|
|
1162
|
+
if (!cookieToken) {
|
|
1163
|
+
return { valid: false, reason: "missing_cookie" };
|
|
1164
|
+
}
|
|
1165
|
+
const cookieValid = await verifyToken(cookieToken, secret);
|
|
1166
|
+
if (!cookieValid) {
|
|
1167
|
+
return { valid: false, reason: "invalid_cookie" };
|
|
1168
|
+
}
|
|
1169
|
+
const requestToken = await extractToken(req, headerName, fieldName);
|
|
1170
|
+
if (!requestToken) {
|
|
1171
|
+
return { valid: false, reason: "missing_token" };
|
|
1172
|
+
}
|
|
1173
|
+
if (!tokensMatch(cookieToken, requestToken)) {
|
|
1174
|
+
return { valid: false, reason: "token_mismatch" };
|
|
1175
|
+
}
|
|
1176
|
+
return { valid: true };
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
// src/middleware/headers/builder.ts
|
|
1180
|
+
function buildCSP(policy) {
|
|
1181
|
+
const directives = [];
|
|
1182
|
+
const directiveMap = {
|
|
1183
|
+
defaultSrc: "default-src",
|
|
1184
|
+
scriptSrc: "script-src",
|
|
1185
|
+
styleSrc: "style-src",
|
|
1186
|
+
imgSrc: "img-src",
|
|
1187
|
+
fontSrc: "font-src",
|
|
1188
|
+
connectSrc: "connect-src",
|
|
1189
|
+
mediaSrc: "media-src",
|
|
1190
|
+
objectSrc: "object-src",
|
|
1191
|
+
frameSrc: "frame-src",
|
|
1192
|
+
childSrc: "child-src",
|
|
1193
|
+
workerSrc: "worker-src",
|
|
1194
|
+
frameAncestors: "frame-ancestors",
|
|
1195
|
+
formAction: "form-action",
|
|
1196
|
+
baseUri: "base-uri",
|
|
1197
|
+
manifestSrc: "manifest-src",
|
|
1198
|
+
reportUri: "report-uri",
|
|
1199
|
+
reportTo: "report-to"
|
|
1200
|
+
};
|
|
1201
|
+
for (const [key, directive] of Object.entries(directiveMap)) {
|
|
1202
|
+
const value = policy[key];
|
|
1203
|
+
if (value !== void 0 && value !== false) {
|
|
1204
|
+
if (Array.isArray(value)) {
|
|
1205
|
+
directives.push(`${directive} ${value.join(" ")}`);
|
|
1206
|
+
} else if (typeof value === "string") {
|
|
1207
|
+
directives.push(`${directive} ${value}`);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
if (policy.upgradeInsecureRequests) {
|
|
1212
|
+
directives.push("upgrade-insecure-requests");
|
|
1213
|
+
}
|
|
1214
|
+
if (policy.blockAllMixedContent) {
|
|
1215
|
+
directives.push("block-all-mixed-content");
|
|
1216
|
+
}
|
|
1217
|
+
return directives.join("; ");
|
|
1218
|
+
}
|
|
1219
|
+
function buildHSTS(config) {
|
|
1220
|
+
let value = `max-age=${config.maxAge}`;
|
|
1221
|
+
if (config.includeSubDomains) {
|
|
1222
|
+
value += "; includeSubDomains";
|
|
1223
|
+
}
|
|
1224
|
+
if (config.preload) {
|
|
1225
|
+
value += "; preload";
|
|
1226
|
+
}
|
|
1227
|
+
return value;
|
|
1228
|
+
}
|
|
1229
|
+
function buildPermissionsPolicy(policy) {
|
|
1230
|
+
const directives = [];
|
|
1231
|
+
const featureMap = {
|
|
1232
|
+
accelerometer: "accelerometer",
|
|
1233
|
+
ambientLightSensor: "ambient-light-sensor",
|
|
1234
|
+
autoplay: "autoplay",
|
|
1235
|
+
battery: "battery",
|
|
1236
|
+
camera: "camera",
|
|
1237
|
+
displayCapture: "display-capture",
|
|
1238
|
+
documentDomain: "document-domain",
|
|
1239
|
+
encryptedMedia: "encrypted-media",
|
|
1240
|
+
fullscreen: "fullscreen",
|
|
1241
|
+
geolocation: "geolocation",
|
|
1242
|
+
gyroscope: "gyroscope",
|
|
1243
|
+
magnetometer: "magnetometer",
|
|
1244
|
+
microphone: "microphone",
|
|
1245
|
+
midi: "midi",
|
|
1246
|
+
payment: "payment",
|
|
1247
|
+
pictureInPicture: "picture-in-picture",
|
|
1248
|
+
publicKeyCredentialsGet: "publickey-credentials-get",
|
|
1249
|
+
screenWakeLock: "screen-wake-lock",
|
|
1250
|
+
syncXhr: "sync-xhr",
|
|
1251
|
+
usb: "usb",
|
|
1252
|
+
webShare: "web-share",
|
|
1253
|
+
xrSpatialTracking: "xr-spatial-tracking"
|
|
1254
|
+
};
|
|
1255
|
+
for (const [key, feature] of Object.entries(featureMap)) {
|
|
1256
|
+
const origins = policy[key];
|
|
1257
|
+
if (origins !== void 0) {
|
|
1258
|
+
if (origins.length === 0) {
|
|
1259
|
+
directives.push(`${feature}=()`);
|
|
1260
|
+
} else {
|
|
1261
|
+
const formatted = origins.map((o) => o === "self" ? "self" : `"${o}"`).join(" ");
|
|
1262
|
+
directives.push(`${feature}=(${formatted})`);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
return directives.join(", ");
|
|
1267
|
+
}
|
|
1268
|
+
var PRESET_STRICT = {
|
|
1269
|
+
contentSecurityPolicy: {
|
|
1270
|
+
defaultSrc: ["'self'"],
|
|
1271
|
+
scriptSrc: ["'self'"],
|
|
1272
|
+
styleSrc: ["'self'"],
|
|
1273
|
+
imgSrc: ["'self'", "data:"],
|
|
1274
|
+
fontSrc: ["'self'"],
|
|
1275
|
+
objectSrc: ["'none'"],
|
|
1276
|
+
frameAncestors: ["'none'"],
|
|
1277
|
+
formAction: ["'self'"],
|
|
1278
|
+
baseUri: ["'self'"],
|
|
1279
|
+
upgradeInsecureRequests: true
|
|
1280
|
+
},
|
|
1281
|
+
strictTransportSecurity: {
|
|
1282
|
+
maxAge: 31536e3,
|
|
1283
|
+
// 1 year
|
|
1284
|
+
includeSubDomains: true,
|
|
1285
|
+
preload: true
|
|
1286
|
+
},
|
|
1287
|
+
xFrameOptions: "DENY",
|
|
1288
|
+
xContentTypeOptions: true,
|
|
1289
|
+
xDnsPrefetchControl: "off",
|
|
1290
|
+
xDownloadOptions: true,
|
|
1291
|
+
xPermittedCrossDomainPolicies: "none",
|
|
1292
|
+
referrerPolicy: "strict-origin-when-cross-origin",
|
|
1293
|
+
crossOriginOpenerPolicy: "same-origin",
|
|
1294
|
+
crossOriginEmbedderPolicy: "require-corp",
|
|
1295
|
+
crossOriginResourcePolicy: "same-origin",
|
|
1296
|
+
permissionsPolicy: {
|
|
1297
|
+
camera: [],
|
|
1298
|
+
microphone: [],
|
|
1299
|
+
geolocation: [],
|
|
1300
|
+
payment: []
|
|
1301
|
+
},
|
|
1302
|
+
originAgentCluster: true
|
|
1303
|
+
};
|
|
1304
|
+
var PRESET_RELAXED = {
|
|
1305
|
+
contentSecurityPolicy: {
|
|
1306
|
+
defaultSrc: ["'self'"],
|
|
1307
|
+
scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
|
|
1308
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
1309
|
+
imgSrc: ["'self'", "data:", "blob:", "https:"],
|
|
1310
|
+
fontSrc: ["'self'", "https:", "data:"],
|
|
1311
|
+
connectSrc: ["'self'", "https:", "wss:"],
|
|
1312
|
+
frameSrc: ["'self'"]
|
|
1313
|
+
},
|
|
1314
|
+
strictTransportSecurity: {
|
|
1315
|
+
maxAge: 86400,
|
|
1316
|
+
// 1 day
|
|
1317
|
+
includeSubDomains: false
|
|
1318
|
+
},
|
|
1319
|
+
xFrameOptions: "SAMEORIGIN",
|
|
1320
|
+
xContentTypeOptions: true,
|
|
1321
|
+
referrerPolicy: "no-referrer-when-downgrade"
|
|
1322
|
+
};
|
|
1323
|
+
var PRESET_API = {
|
|
1324
|
+
contentSecurityPolicy: {
|
|
1325
|
+
defaultSrc: ["'none'"],
|
|
1326
|
+
frameAncestors: ["'none'"]
|
|
1327
|
+
},
|
|
1328
|
+
strictTransportSecurity: {
|
|
1329
|
+
maxAge: 31536e3,
|
|
1330
|
+
includeSubDomains: true
|
|
1331
|
+
},
|
|
1332
|
+
xFrameOptions: "DENY",
|
|
1333
|
+
xContentTypeOptions: true,
|
|
1334
|
+
referrerPolicy: "no-referrer",
|
|
1335
|
+
crossOriginResourcePolicy: "same-origin"
|
|
1336
|
+
};
|
|
1337
|
+
function getPreset(name) {
|
|
1338
|
+
switch (name) {
|
|
1339
|
+
case "strict":
|
|
1340
|
+
return PRESET_STRICT;
|
|
1341
|
+
case "relaxed":
|
|
1342
|
+
return PRESET_RELAXED;
|
|
1343
|
+
case "api":
|
|
1344
|
+
return PRESET_API;
|
|
1345
|
+
default:
|
|
1346
|
+
return PRESET_STRICT;
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
function buildHeaders(config) {
|
|
1350
|
+
const headers = new Headers();
|
|
1351
|
+
if (config.contentSecurityPolicy) {
|
|
1352
|
+
const csp = buildCSP(config.contentSecurityPolicy);
|
|
1353
|
+
if (csp) headers.set("Content-Security-Policy", csp);
|
|
1354
|
+
}
|
|
1355
|
+
if (config.strictTransportSecurity) {
|
|
1356
|
+
headers.set("Strict-Transport-Security", buildHSTS(config.strictTransportSecurity));
|
|
1357
|
+
}
|
|
1358
|
+
if (config.xFrameOptions) {
|
|
1359
|
+
headers.set("X-Frame-Options", config.xFrameOptions);
|
|
1360
|
+
}
|
|
1361
|
+
if (config.xContentTypeOptions) {
|
|
1362
|
+
headers.set("X-Content-Type-Options", "nosniff");
|
|
1363
|
+
}
|
|
1364
|
+
if (config.xDnsPrefetchControl) {
|
|
1365
|
+
headers.set("X-DNS-Prefetch-Control", config.xDnsPrefetchControl);
|
|
1366
|
+
}
|
|
1367
|
+
if (config.xDownloadOptions) {
|
|
1368
|
+
headers.set("X-Download-Options", "noopen");
|
|
1369
|
+
}
|
|
1370
|
+
if (config.xPermittedCrossDomainPolicies) {
|
|
1371
|
+
headers.set("X-Permitted-Cross-Domain-Policies", config.xPermittedCrossDomainPolicies);
|
|
1372
|
+
}
|
|
1373
|
+
if (config.referrerPolicy) {
|
|
1374
|
+
const value = Array.isArray(config.referrerPolicy) ? config.referrerPolicy.join(", ") : config.referrerPolicy;
|
|
1375
|
+
headers.set("Referrer-Policy", value);
|
|
1376
|
+
}
|
|
1377
|
+
if (config.crossOriginOpenerPolicy) {
|
|
1378
|
+
headers.set("Cross-Origin-Opener-Policy", config.crossOriginOpenerPolicy);
|
|
1379
|
+
}
|
|
1380
|
+
if (config.crossOriginEmbedderPolicy) {
|
|
1381
|
+
headers.set("Cross-Origin-Embedder-Policy", config.crossOriginEmbedderPolicy);
|
|
1382
|
+
}
|
|
1383
|
+
if (config.crossOriginResourcePolicy) {
|
|
1384
|
+
headers.set("Cross-Origin-Resource-Policy", config.crossOriginResourcePolicy);
|
|
1385
|
+
}
|
|
1386
|
+
if (config.permissionsPolicy) {
|
|
1387
|
+
const pp = buildPermissionsPolicy(config.permissionsPolicy);
|
|
1388
|
+
if (pp) headers.set("Permissions-Policy", pp);
|
|
1389
|
+
}
|
|
1390
|
+
if (config.originAgentCluster) {
|
|
1391
|
+
headers.set("Origin-Agent-Cluster", "?1");
|
|
1392
|
+
}
|
|
1393
|
+
return headers;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// src/middleware/headers/middleware.ts
|
|
1397
|
+
function mergeConfigs(base, custom) {
|
|
1398
|
+
return {
|
|
1399
|
+
...base,
|
|
1400
|
+
...custom,
|
|
1401
|
+
// Deep merge CSP if both exist
|
|
1402
|
+
contentSecurityPolicy: custom.contentSecurityPolicy === false ? false : custom.contentSecurityPolicy ? base.contentSecurityPolicy ? { ...base.contentSecurityPolicy, ...custom.contentSecurityPolicy } : custom.contentSecurityPolicy : base.contentSecurityPolicy,
|
|
1403
|
+
// Deep merge HSTS if both exist
|
|
1404
|
+
strictTransportSecurity: custom.strictTransportSecurity === false ? false : custom.strictTransportSecurity ? base.strictTransportSecurity ? { ...base.strictTransportSecurity, ...custom.strictTransportSecurity } : custom.strictTransportSecurity : base.strictTransportSecurity,
|
|
1405
|
+
// Deep merge Permissions-Policy if both exist
|
|
1406
|
+
permissionsPolicy: custom.permissionsPolicy === false ? false : custom.permissionsPolicy ? base.permissionsPolicy ? { ...base.permissionsPolicy, ...custom.permissionsPolicy } : custom.permissionsPolicy : base.permissionsPolicy
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
function withSecurityHeaders(handler, options = {}) {
|
|
1410
|
+
const { preset, config, override = false } = options;
|
|
1411
|
+
let baseConfig = preset ? getPreset(preset) : PRESET_STRICT;
|
|
1412
|
+
if (config) {
|
|
1413
|
+
baseConfig = mergeConfigs(baseConfig, config);
|
|
1414
|
+
}
|
|
1415
|
+
const securityHeaders = buildHeaders(baseConfig);
|
|
1416
|
+
return async (req) => {
|
|
1417
|
+
const response = await handler(req);
|
|
1418
|
+
const newHeaders = new Headers(response.headers);
|
|
1419
|
+
securityHeaders.forEach((value, key) => {
|
|
1420
|
+
if (override || !newHeaders.has(key)) {
|
|
1421
|
+
newHeaders.set(key, value);
|
|
1422
|
+
}
|
|
1423
|
+
});
|
|
1424
|
+
return new Response(response.body, {
|
|
1425
|
+
status: response.status,
|
|
1426
|
+
statusText: response.statusText,
|
|
1427
|
+
headers: newHeaders
|
|
1428
|
+
});
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
function createSecurityHeaders(options = {}) {
|
|
1432
|
+
const { preset, config } = options;
|
|
1433
|
+
let baseConfig = preset ? getPreset(preset) : PRESET_STRICT;
|
|
1434
|
+
if (config) {
|
|
1435
|
+
baseConfig = mergeConfigs(baseConfig, config);
|
|
1436
|
+
}
|
|
1437
|
+
return buildHeaders(baseConfig);
|
|
1438
|
+
}
|
|
1439
|
+
function createSecurityHeadersObject(options = {}) {
|
|
1440
|
+
const headers = createSecurityHeaders(options);
|
|
1441
|
+
const obj = {};
|
|
1442
|
+
headers.forEach((value, key) => {
|
|
1443
|
+
obj[key] = value;
|
|
1444
|
+
});
|
|
1445
|
+
return obj;
|
|
1446
|
+
}
|
|
995
1447
|
|
|
996
1448
|
// src/index.ts
|
|
997
|
-
var VERSION = "0.
|
|
1449
|
+
var VERSION = "0.3.0";
|
|
998
1450
|
|
|
999
|
-
export { AuthenticationError, AuthorizationError, ConfigurationError, CsrfError, MemoryStore, RateLimitError, SecureError, VERSION, ValidationError, anonymizeIp, checkRateLimit, clearAllRateLimits, createMemoryStore, createRateLimiter, formatDuration, getClientIp, getGeoInfo, getGlobalMemoryStore, getRateLimitStatus, isLocalhost, isPrivateIp, isSecureError, isValidIp, normalizeIp, nowInMs, nowInSeconds, parseDuration, resetRateLimit, sleep, toSecureError, withRateLimit };
|
|
1451
|
+
export { AuthenticationError, AuthorizationError, ConfigurationError, CsrfError, MemoryStore, PRESET_API, PRESET_RELAXED, PRESET_STRICT, RateLimitError, SecureError, VERSION, ValidationError, anonymizeIp, buildCSP, buildHSTS, buildPermissionsPolicy, checkRateLimit, clearAllRateLimits, createToken as createCSRFToken, createMemoryStore, createRateLimiter, createSecurityHeaders, createSecurityHeadersObject, formatDuration, generateCSRF, getClientIp, getGeoInfo, getGlobalMemoryStore, getPreset, getRateLimitStatus, isLocalhost, isPrivateIp, isSecureError, isValidIp, normalizeIp, nowInMs, nowInSeconds, parseDuration, resetRateLimit, sleep, toSecureError, tokensMatch, validateCSRF, verifyToken as verifyCSRFToken, withCSRF, withRateLimit, withSecurityHeaders };
|
|
1000
1452
|
//# sourceMappingURL=index.js.map
|
|
1001
1453
|
//# sourceMappingURL=index.js.map
|