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.cjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var crypto$1 = require('crypto');
|
|
4
|
+
|
|
3
5
|
// src/core/errors.ts
|
|
4
6
|
var SecureError = class extends Error {
|
|
5
7
|
/**
|
|
@@ -994,28 +996,489 @@ function clearAllRateLimits() {
|
|
|
994
996
|
defaultStore.clear();
|
|
995
997
|
}
|
|
996
998
|
}
|
|
999
|
+
var encoder = new TextEncoder();
|
|
1000
|
+
function randomBytes(length) {
|
|
1001
|
+
const bytes = new Uint8Array(length);
|
|
1002
|
+
crypto$1.webcrypto.getRandomValues(bytes);
|
|
1003
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1004
|
+
}
|
|
1005
|
+
async function createSignature(data, secret) {
|
|
1006
|
+
const key = await crypto$1.webcrypto.subtle.importKey(
|
|
1007
|
+
"raw",
|
|
1008
|
+
encoder.encode(secret),
|
|
1009
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
1010
|
+
false,
|
|
1011
|
+
["sign"]
|
|
1012
|
+
);
|
|
1013
|
+
const sig = await crypto$1.webcrypto.subtle.sign("HMAC", key, encoder.encode(data));
|
|
1014
|
+
return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1015
|
+
}
|
|
1016
|
+
function safeCompare(a, b) {
|
|
1017
|
+
if (a.length !== b.length) return false;
|
|
1018
|
+
let result = 0;
|
|
1019
|
+
for (let i = 0; i < a.length; i++) {
|
|
1020
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
1021
|
+
}
|
|
1022
|
+
return result === 0;
|
|
1023
|
+
}
|
|
1024
|
+
async function createToken(secret, length = 32) {
|
|
1025
|
+
const data = randomBytes(length);
|
|
1026
|
+
const sig = await createSignature(data, secret);
|
|
1027
|
+
return `${data}.${sig}`;
|
|
1028
|
+
}
|
|
1029
|
+
async function verifyToken(token, secret) {
|
|
1030
|
+
if (!token || typeof token !== "string") return false;
|
|
1031
|
+
const parts = token.split(".");
|
|
1032
|
+
if (parts.length !== 2) return false;
|
|
1033
|
+
const [data, sig] = parts;
|
|
1034
|
+
if (!data || !sig) return false;
|
|
1035
|
+
try {
|
|
1036
|
+
const expected = await createSignature(data, secret);
|
|
1037
|
+
return safeCompare(sig, expected);
|
|
1038
|
+
} catch {
|
|
1039
|
+
return false;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
function tokensMatch(a, b) {
|
|
1043
|
+
if (!a || !b) return false;
|
|
1044
|
+
return safeCompare(a, b);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// src/middleware/csrf/middleware.ts
|
|
1048
|
+
var DEFAULT_COOKIE = {
|
|
1049
|
+
name: "__csrf",
|
|
1050
|
+
path: "/",
|
|
1051
|
+
httpOnly: true,
|
|
1052
|
+
secure: process.env.NODE_ENV === "production",
|
|
1053
|
+
sameSite: "strict",
|
|
1054
|
+
maxAge: 86400
|
|
1055
|
+
// 24h
|
|
1056
|
+
};
|
|
1057
|
+
var DEFAULT_CONFIG2 = {
|
|
1058
|
+
headerName: "x-csrf-token",
|
|
1059
|
+
fieldName: "_csrf",
|
|
1060
|
+
tokenLength: 32,
|
|
1061
|
+
protectedMethods: ["POST", "PUT", "PATCH", "DELETE"]
|
|
1062
|
+
};
|
|
1063
|
+
function getSecret(config) {
|
|
1064
|
+
const secret = config.secret || process.env.CSRF_SECRET;
|
|
1065
|
+
if (!secret) {
|
|
1066
|
+
throw new Error(
|
|
1067
|
+
"CSRF secret is required. Set config.secret or CSRF_SECRET env variable."
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
return secret;
|
|
1071
|
+
}
|
|
1072
|
+
function buildCookieString(name, value, opts) {
|
|
1073
|
+
let cookie = `${name}=${value}`;
|
|
1074
|
+
if (opts.path) cookie += `; Path=${opts.path}`;
|
|
1075
|
+
if (opts.domain) cookie += `; Domain=${opts.domain}`;
|
|
1076
|
+
if (opts.maxAge) cookie += `; Max-Age=${opts.maxAge}`;
|
|
1077
|
+
if (opts.httpOnly) cookie += "; HttpOnly";
|
|
1078
|
+
if (opts.secure) cookie += "; Secure";
|
|
1079
|
+
if (opts.sameSite) cookie += `; SameSite=${opts.sameSite}`;
|
|
1080
|
+
return cookie;
|
|
1081
|
+
}
|
|
1082
|
+
async function extractToken(req, headerName, fieldName) {
|
|
1083
|
+
const headerToken = req.headers.get(headerName);
|
|
1084
|
+
if (headerToken) return headerToken;
|
|
1085
|
+
const contentType = req.headers.get("content-type") || "";
|
|
1086
|
+
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
1087
|
+
try {
|
|
1088
|
+
const cloned = req.clone();
|
|
1089
|
+
const formData = await cloned.formData();
|
|
1090
|
+
const token = formData.get(fieldName);
|
|
1091
|
+
if (typeof token === "string") return token;
|
|
1092
|
+
} catch {
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
if (contentType.includes("application/json")) {
|
|
1096
|
+
try {
|
|
1097
|
+
const cloned = req.clone();
|
|
1098
|
+
const body = await cloned.json();
|
|
1099
|
+
if (body && typeof body[fieldName] === "string") {
|
|
1100
|
+
return body[fieldName];
|
|
1101
|
+
}
|
|
1102
|
+
} catch {
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
return null;
|
|
1106
|
+
}
|
|
1107
|
+
function defaultErrorResponse(_req, reason) {
|
|
1108
|
+
return new Response(JSON.stringify({ error: "CSRF validation failed", reason }), {
|
|
1109
|
+
status: 403,
|
|
1110
|
+
headers: { "Content-Type": "application/json" }
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
function withCSRF(handler, config = {}) {
|
|
1114
|
+
const secret = getSecret(config);
|
|
1115
|
+
const cookieOpts = { ...DEFAULT_COOKIE, ...config.cookie };
|
|
1116
|
+
const headerName = config.headerName || DEFAULT_CONFIG2.headerName;
|
|
1117
|
+
const fieldName = config.fieldName || DEFAULT_CONFIG2.fieldName;
|
|
1118
|
+
const protectedMethods = config.protectedMethods || DEFAULT_CONFIG2.protectedMethods;
|
|
1119
|
+
const onError = config.onError || defaultErrorResponse;
|
|
1120
|
+
return async (req) => {
|
|
1121
|
+
const method = req.method.toUpperCase();
|
|
1122
|
+
if (!protectedMethods.includes(method)) {
|
|
1123
|
+
return handler(req);
|
|
1124
|
+
}
|
|
1125
|
+
if (config.skip) {
|
|
1126
|
+
const shouldSkip = await config.skip(req);
|
|
1127
|
+
if (shouldSkip) return handler(req);
|
|
1128
|
+
}
|
|
1129
|
+
const cookieName = cookieOpts.name || "__csrf";
|
|
1130
|
+
const cookieToken = req.cookies.get(cookieName)?.value;
|
|
1131
|
+
if (!cookieToken) {
|
|
1132
|
+
return onError(req, "missing_cookie");
|
|
1133
|
+
}
|
|
1134
|
+
const cookieValid = await verifyToken(cookieToken, secret);
|
|
1135
|
+
if (!cookieValid) {
|
|
1136
|
+
return onError(req, "invalid_cookie");
|
|
1137
|
+
}
|
|
1138
|
+
const requestToken = await extractToken(req, headerName, fieldName);
|
|
1139
|
+
if (!requestToken) {
|
|
1140
|
+
return onError(req, "missing_token");
|
|
1141
|
+
}
|
|
1142
|
+
if (!tokensMatch(cookieToken, requestToken)) {
|
|
1143
|
+
return onError(req, "token_mismatch");
|
|
1144
|
+
}
|
|
1145
|
+
return handler(req);
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
async function generateCSRF(config = {}) {
|
|
1149
|
+
const secret = getSecret(config);
|
|
1150
|
+
const cookieOpts = { ...DEFAULT_COOKIE, ...config.cookie };
|
|
1151
|
+
const tokenLength = config.tokenLength || DEFAULT_CONFIG2.tokenLength;
|
|
1152
|
+
const cookieName = cookieOpts.name || "__csrf";
|
|
1153
|
+
const token = await createToken(secret, tokenLength);
|
|
1154
|
+
const cookieHeader = buildCookieString(cookieName, token, cookieOpts);
|
|
1155
|
+
return { token, cookieHeader };
|
|
1156
|
+
}
|
|
1157
|
+
async function validateCSRF(req, config = {}) {
|
|
1158
|
+
const secret = getSecret(config);
|
|
1159
|
+
const cookieOpts = { ...DEFAULT_COOKIE, ...config.cookie };
|
|
1160
|
+
const headerName = config.headerName || DEFAULT_CONFIG2.headerName;
|
|
1161
|
+
const fieldName = config.fieldName || DEFAULT_CONFIG2.fieldName;
|
|
1162
|
+
const cookieName = cookieOpts.name || "__csrf";
|
|
1163
|
+
const cookieToken = req.cookies.get(cookieName)?.value;
|
|
1164
|
+
if (!cookieToken) {
|
|
1165
|
+
return { valid: false, reason: "missing_cookie" };
|
|
1166
|
+
}
|
|
1167
|
+
const cookieValid = await verifyToken(cookieToken, secret);
|
|
1168
|
+
if (!cookieValid) {
|
|
1169
|
+
return { valid: false, reason: "invalid_cookie" };
|
|
1170
|
+
}
|
|
1171
|
+
const requestToken = await extractToken(req, headerName, fieldName);
|
|
1172
|
+
if (!requestToken) {
|
|
1173
|
+
return { valid: false, reason: "missing_token" };
|
|
1174
|
+
}
|
|
1175
|
+
if (!tokensMatch(cookieToken, requestToken)) {
|
|
1176
|
+
return { valid: false, reason: "token_mismatch" };
|
|
1177
|
+
}
|
|
1178
|
+
return { valid: true };
|
|
1179
|
+
}
|
|
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
|
+
}
|
|
997
1449
|
|
|
998
1450
|
// src/index.ts
|
|
999
|
-
var VERSION = "0.
|
|
1451
|
+
var VERSION = "0.3.0";
|
|
1000
1452
|
|
|
1001
1453
|
exports.AuthenticationError = AuthenticationError;
|
|
1002
1454
|
exports.AuthorizationError = AuthorizationError;
|
|
1003
1455
|
exports.ConfigurationError = ConfigurationError;
|
|
1004
1456
|
exports.CsrfError = CsrfError;
|
|
1005
1457
|
exports.MemoryStore = MemoryStore;
|
|
1458
|
+
exports.PRESET_API = PRESET_API;
|
|
1459
|
+
exports.PRESET_RELAXED = PRESET_RELAXED;
|
|
1460
|
+
exports.PRESET_STRICT = PRESET_STRICT;
|
|
1006
1461
|
exports.RateLimitError = RateLimitError;
|
|
1007
1462
|
exports.SecureError = SecureError;
|
|
1008
1463
|
exports.VERSION = VERSION;
|
|
1009
1464
|
exports.ValidationError = ValidationError;
|
|
1010
1465
|
exports.anonymizeIp = anonymizeIp;
|
|
1466
|
+
exports.buildCSP = buildCSP;
|
|
1467
|
+
exports.buildHSTS = buildHSTS;
|
|
1468
|
+
exports.buildPermissionsPolicy = buildPermissionsPolicy;
|
|
1011
1469
|
exports.checkRateLimit = checkRateLimit;
|
|
1012
1470
|
exports.clearAllRateLimits = clearAllRateLimits;
|
|
1471
|
+
exports.createCSRFToken = createToken;
|
|
1013
1472
|
exports.createMemoryStore = createMemoryStore;
|
|
1014
1473
|
exports.createRateLimiter = createRateLimiter;
|
|
1474
|
+
exports.createSecurityHeaders = createSecurityHeaders;
|
|
1475
|
+
exports.createSecurityHeadersObject = createSecurityHeadersObject;
|
|
1015
1476
|
exports.formatDuration = formatDuration;
|
|
1477
|
+
exports.generateCSRF = generateCSRF;
|
|
1016
1478
|
exports.getClientIp = getClientIp;
|
|
1017
1479
|
exports.getGeoInfo = getGeoInfo;
|
|
1018
1480
|
exports.getGlobalMemoryStore = getGlobalMemoryStore;
|
|
1481
|
+
exports.getPreset = getPreset;
|
|
1019
1482
|
exports.getRateLimitStatus = getRateLimitStatus;
|
|
1020
1483
|
exports.isLocalhost = isLocalhost;
|
|
1021
1484
|
exports.isPrivateIp = isPrivateIp;
|
|
@@ -1028,6 +1491,11 @@ exports.parseDuration = parseDuration;
|
|
|
1028
1491
|
exports.resetRateLimit = resetRateLimit;
|
|
1029
1492
|
exports.sleep = sleep;
|
|
1030
1493
|
exports.toSecureError = toSecureError;
|
|
1494
|
+
exports.tokensMatch = tokensMatch;
|
|
1495
|
+
exports.validateCSRF = validateCSRF;
|
|
1496
|
+
exports.verifyCSRFToken = verifyToken;
|
|
1497
|
+
exports.withCSRF = withCSRF;
|
|
1031
1498
|
exports.withRateLimit = withRateLimit;
|
|
1499
|
+
exports.withSecurityHeaders = withSecurityHeaders;
|
|
1032
1500
|
//# sourceMappingURL=index.cjs.map
|
|
1033
1501
|
//# sourceMappingURL=index.cjs.map
|