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/README.md +115 -0
- 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 +280 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +271 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/headers.d.ts
CHANGED
|
@@ -1,35 +1,172 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Content-Security-Policy directive values
|
|
5
|
+
*/
|
|
6
|
+
type CSPDirectiveValue = string | string[];
|
|
7
|
+
interface ContentSecurityPolicy {
|
|
8
|
+
defaultSrc?: CSPDirectiveValue;
|
|
9
|
+
scriptSrc?: CSPDirectiveValue;
|
|
10
|
+
styleSrc?: CSPDirectiveValue;
|
|
11
|
+
imgSrc?: CSPDirectiveValue;
|
|
12
|
+
fontSrc?: CSPDirectiveValue;
|
|
13
|
+
connectSrc?: CSPDirectiveValue;
|
|
14
|
+
mediaSrc?: CSPDirectiveValue;
|
|
15
|
+
objectSrc?: CSPDirectiveValue;
|
|
16
|
+
frameSrc?: CSPDirectiveValue;
|
|
17
|
+
childSrc?: CSPDirectiveValue;
|
|
18
|
+
workerSrc?: CSPDirectiveValue;
|
|
19
|
+
frameAncestors?: CSPDirectiveValue;
|
|
20
|
+
formAction?: CSPDirectiveValue;
|
|
21
|
+
baseUri?: CSPDirectiveValue;
|
|
22
|
+
manifestSrc?: CSPDirectiveValue;
|
|
23
|
+
upgradeInsecureRequests?: boolean;
|
|
24
|
+
blockAllMixedContent?: boolean;
|
|
25
|
+
reportUri?: string;
|
|
26
|
+
reportTo?: string;
|
|
27
|
+
}
|
|
28
|
+
interface StrictTransportSecurity {
|
|
29
|
+
maxAge: number;
|
|
30
|
+
includeSubDomains?: boolean;
|
|
31
|
+
preload?: boolean;
|
|
32
|
+
}
|
|
33
|
+
type XFrameOptions = 'DENY' | 'SAMEORIGIN';
|
|
34
|
+
type ReferrerPolicy = 'no-referrer' | 'no-referrer-when-downgrade' | 'origin' | 'origin-when-cross-origin' | 'same-origin' | 'strict-origin' | 'strict-origin-when-cross-origin' | 'unsafe-url';
|
|
35
|
+
type CrossOriginOpenerPolicy = 'unsafe-none' | 'same-origin-allow-popups' | 'same-origin';
|
|
36
|
+
type CrossOriginEmbedderPolicy = 'unsafe-none' | 'require-corp' | 'credentialless';
|
|
37
|
+
type CrossOriginResourcePolicy = 'same-site' | 'same-origin' | 'cross-origin';
|
|
38
|
+
interface PermissionsPolicy {
|
|
39
|
+
accelerometer?: string[];
|
|
40
|
+
ambientLightSensor?: string[];
|
|
41
|
+
autoplay?: string[];
|
|
42
|
+
battery?: string[];
|
|
43
|
+
camera?: string[];
|
|
44
|
+
displayCapture?: string[];
|
|
45
|
+
documentDomain?: string[];
|
|
46
|
+
encryptedMedia?: string[];
|
|
47
|
+
fullscreen?: string[];
|
|
48
|
+
geolocation?: string[];
|
|
49
|
+
gyroscope?: string[];
|
|
50
|
+
magnetometer?: string[];
|
|
51
|
+
microphone?: string[];
|
|
52
|
+
midi?: string[];
|
|
53
|
+
payment?: string[];
|
|
54
|
+
pictureInPicture?: string[];
|
|
55
|
+
publicKeyCredentialsGet?: string[];
|
|
56
|
+
screenWakeLock?: string[];
|
|
57
|
+
syncXhr?: string[];
|
|
58
|
+
usb?: string[];
|
|
59
|
+
webShare?: string[];
|
|
60
|
+
xrSpatialTracking?: string[];
|
|
61
|
+
}
|
|
62
|
+
interface SecurityHeadersConfig {
|
|
63
|
+
/** Content-Security-Policy */
|
|
64
|
+
contentSecurityPolicy?: ContentSecurityPolicy | false;
|
|
65
|
+
/** Strict-Transport-Security */
|
|
66
|
+
strictTransportSecurity?: StrictTransportSecurity | false;
|
|
67
|
+
/** X-Frame-Options */
|
|
68
|
+
xFrameOptions?: XFrameOptions | false;
|
|
69
|
+
/** X-Content-Type-Options: nosniff */
|
|
70
|
+
xContentTypeOptions?: boolean;
|
|
71
|
+
/** X-DNS-Prefetch-Control */
|
|
72
|
+
xDnsPrefetchControl?: 'on' | 'off' | false;
|
|
73
|
+
/** X-Download-Options: noopen (IE specific) */
|
|
74
|
+
xDownloadOptions?: boolean;
|
|
75
|
+
/** X-Permitted-Cross-Domain-Policies */
|
|
76
|
+
xPermittedCrossDomainPolicies?: 'none' | 'master-only' | 'by-content-type' | 'all' | false;
|
|
77
|
+
/** Referrer-Policy */
|
|
78
|
+
referrerPolicy?: ReferrerPolicy | ReferrerPolicy[] | false;
|
|
79
|
+
/** Cross-Origin-Opener-Policy */
|
|
80
|
+
crossOriginOpenerPolicy?: CrossOriginOpenerPolicy | false;
|
|
81
|
+
/** Cross-Origin-Embedder-Policy */
|
|
82
|
+
crossOriginEmbedderPolicy?: CrossOriginEmbedderPolicy | false;
|
|
83
|
+
/** Cross-Origin-Resource-Policy */
|
|
84
|
+
crossOriginResourcePolicy?: CrossOriginResourcePolicy | false;
|
|
85
|
+
/** Permissions-Policy */
|
|
86
|
+
permissionsPolicy?: PermissionsPolicy | false;
|
|
87
|
+
/** Origin-Agent-Cluster */
|
|
88
|
+
originAgentCluster?: boolean;
|
|
89
|
+
}
|
|
90
|
+
type SecurityHeadersPreset = 'strict' | 'relaxed' | 'api';
|
|
91
|
+
|
|
92
|
+
type RouteHandler = (req: NextRequest) => Response | Promise<Response>;
|
|
93
|
+
interface WithSecurityHeadersOptions {
|
|
94
|
+
/** Use a preset configuration */
|
|
95
|
+
preset?: SecurityHeadersPreset;
|
|
96
|
+
/** Custom header configuration (merged with preset if provided) */
|
|
97
|
+
config?: SecurityHeadersConfig;
|
|
98
|
+
/** Override response headers instead of merging */
|
|
99
|
+
override?: boolean;
|
|
100
|
+
}
|
|
1
101
|
/**
|
|
2
|
-
* Security
|
|
102
|
+
* Security headers middleware
|
|
103
|
+
*
|
|
104
|
+
* Adds security headers to responses. Use presets for quick setup
|
|
105
|
+
* or provide custom configuration.
|
|
3
106
|
*
|
|
4
107
|
* @example
|
|
5
108
|
* ```typescript
|
|
6
|
-
* //
|
|
7
|
-
*
|
|
109
|
+
* // Use strict preset
|
|
110
|
+
* export const GET = withSecurityHeaders(handler, { preset: 'strict' })
|
|
8
111
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* headers: securityHeaders({
|
|
15
|
-
* contentSecurityPolicy: {
|
|
16
|
-
* directives: {
|
|
17
|
-
* defaultSrc: ["'self'"],
|
|
18
|
-
* scriptSrc: ["'self'", "'unsafe-inline'"],
|
|
19
|
-
* }
|
|
20
|
-
* },
|
|
21
|
-
* strictTransportSecurity: true,
|
|
22
|
-
* xFrameOptions: 'DENY',
|
|
23
|
-
* })
|
|
24
|
-
* }
|
|
25
|
-
* ]
|
|
112
|
+
* // Custom config
|
|
113
|
+
* export const GET = withSecurityHeaders(handler, {
|
|
114
|
+
* config: {
|
|
115
|
+
* xFrameOptions: 'SAMEORIGIN',
|
|
116
|
+
* referrerPolicy: 'no-referrer'
|
|
26
117
|
* }
|
|
27
|
-
* }
|
|
118
|
+
* })
|
|
28
119
|
* ```
|
|
120
|
+
*/
|
|
121
|
+
declare function withSecurityHeaders(handler: RouteHandler, options?: WithSecurityHeadersOptions): RouteHandler;
|
|
122
|
+
/**
|
|
123
|
+
* Create headers object for use in responses
|
|
124
|
+
* Useful when you want to add headers manually
|
|
29
125
|
*
|
|
30
|
-
* @
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* const headers = createSecurityHeaders({ preset: 'api' })
|
|
129
|
+
*
|
|
130
|
+
* return Response.json(data, { headers })
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
declare function createSecurityHeaders(options?: WithSecurityHeadersOptions): Headers;
|
|
134
|
+
/**
|
|
135
|
+
* Create a headers object as a plain object (for Next.js headers())
|
|
136
|
+
*/
|
|
137
|
+
declare function createSecurityHeadersObject(options?: WithSecurityHeadersOptions): Record<string, string>;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Build CSP header string from config
|
|
141
|
+
*/
|
|
142
|
+
declare function buildCSP(policy: ContentSecurityPolicy): string;
|
|
143
|
+
/**
|
|
144
|
+
* Build HSTS header string
|
|
145
|
+
*/
|
|
146
|
+
declare function buildHSTS(config: StrictTransportSecurity): string;
|
|
147
|
+
/**
|
|
148
|
+
* Build Permissions-Policy header string
|
|
149
|
+
*/
|
|
150
|
+
declare function buildPermissionsPolicy(policy: PermissionsPolicy): string;
|
|
151
|
+
/**
|
|
152
|
+
* Preset: Strict security headers
|
|
153
|
+
*/
|
|
154
|
+
declare const PRESET_STRICT: SecurityHeadersConfig;
|
|
155
|
+
/**
|
|
156
|
+
* Preset: Relaxed security headers (for development or less strict needs)
|
|
157
|
+
*/
|
|
158
|
+
declare const PRESET_RELAXED: SecurityHeadersConfig;
|
|
159
|
+
/**
|
|
160
|
+
* Preset: API-focused security headers
|
|
161
|
+
*/
|
|
162
|
+
declare const PRESET_API: SecurityHeadersConfig;
|
|
163
|
+
/**
|
|
164
|
+
* Get preset config by name
|
|
165
|
+
*/
|
|
166
|
+
declare function getPreset(name: SecurityHeadersPreset): SecurityHeadersConfig;
|
|
167
|
+
/**
|
|
168
|
+
* Build all headers from config
|
|
31
169
|
*/
|
|
32
|
-
declare function
|
|
33
|
-
declare function createCsp(): void;
|
|
170
|
+
declare function buildHeaders(config: SecurityHeadersConfig): Headers;
|
|
34
171
|
|
|
35
|
-
export {
|
|
172
|
+
export { type ContentSecurityPolicy, type CrossOriginEmbedderPolicy, type CrossOriginOpenerPolicy, type CrossOriginResourcePolicy, PRESET_API, PRESET_RELAXED, PRESET_STRICT, type PermissionsPolicy, type ReferrerPolicy, type SecurityHeadersConfig, type SecurityHeadersPreset, type StrictTransportSecurity, type XFrameOptions, buildCSP, buildHSTS, buildHeaders, buildPermissionsPolicy, createSecurityHeaders, createSecurityHeadersObject, getPreset, withSecurityHeaders };
|
package/dist/headers.js
CHANGED
|
@@ -1,11 +1,272 @@
|
|
|
1
|
-
// src/middleware/headers/
|
|
2
|
-
function
|
|
3
|
-
|
|
1
|
+
// src/middleware/headers/builder.ts
|
|
2
|
+
function buildCSP(policy) {
|
|
3
|
+
const directives = [];
|
|
4
|
+
const directiveMap = {
|
|
5
|
+
defaultSrc: "default-src",
|
|
6
|
+
scriptSrc: "script-src",
|
|
7
|
+
styleSrc: "style-src",
|
|
8
|
+
imgSrc: "img-src",
|
|
9
|
+
fontSrc: "font-src",
|
|
10
|
+
connectSrc: "connect-src",
|
|
11
|
+
mediaSrc: "media-src",
|
|
12
|
+
objectSrc: "object-src",
|
|
13
|
+
frameSrc: "frame-src",
|
|
14
|
+
childSrc: "child-src",
|
|
15
|
+
workerSrc: "worker-src",
|
|
16
|
+
frameAncestors: "frame-ancestors",
|
|
17
|
+
formAction: "form-action",
|
|
18
|
+
baseUri: "base-uri",
|
|
19
|
+
manifestSrc: "manifest-src",
|
|
20
|
+
reportUri: "report-uri",
|
|
21
|
+
reportTo: "report-to"
|
|
22
|
+
};
|
|
23
|
+
for (const [key, directive] of Object.entries(directiveMap)) {
|
|
24
|
+
const value = policy[key];
|
|
25
|
+
if (value !== void 0 && value !== false) {
|
|
26
|
+
if (Array.isArray(value)) {
|
|
27
|
+
directives.push(`${directive} ${value.join(" ")}`);
|
|
28
|
+
} else if (typeof value === "string") {
|
|
29
|
+
directives.push(`${directive} ${value}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (policy.upgradeInsecureRequests) {
|
|
34
|
+
directives.push("upgrade-insecure-requests");
|
|
35
|
+
}
|
|
36
|
+
if (policy.blockAllMixedContent) {
|
|
37
|
+
directives.push("block-all-mixed-content");
|
|
38
|
+
}
|
|
39
|
+
return directives.join("; ");
|
|
4
40
|
}
|
|
5
|
-
function
|
|
6
|
-
|
|
41
|
+
function buildHSTS(config) {
|
|
42
|
+
let value = `max-age=${config.maxAge}`;
|
|
43
|
+
if (config.includeSubDomains) {
|
|
44
|
+
value += "; includeSubDomains";
|
|
45
|
+
}
|
|
46
|
+
if (config.preload) {
|
|
47
|
+
value += "; preload";
|
|
48
|
+
}
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
function buildPermissionsPolicy(policy) {
|
|
52
|
+
const directives = [];
|
|
53
|
+
const featureMap = {
|
|
54
|
+
accelerometer: "accelerometer",
|
|
55
|
+
ambientLightSensor: "ambient-light-sensor",
|
|
56
|
+
autoplay: "autoplay",
|
|
57
|
+
battery: "battery",
|
|
58
|
+
camera: "camera",
|
|
59
|
+
displayCapture: "display-capture",
|
|
60
|
+
documentDomain: "document-domain",
|
|
61
|
+
encryptedMedia: "encrypted-media",
|
|
62
|
+
fullscreen: "fullscreen",
|
|
63
|
+
geolocation: "geolocation",
|
|
64
|
+
gyroscope: "gyroscope",
|
|
65
|
+
magnetometer: "magnetometer",
|
|
66
|
+
microphone: "microphone",
|
|
67
|
+
midi: "midi",
|
|
68
|
+
payment: "payment",
|
|
69
|
+
pictureInPicture: "picture-in-picture",
|
|
70
|
+
publicKeyCredentialsGet: "publickey-credentials-get",
|
|
71
|
+
screenWakeLock: "screen-wake-lock",
|
|
72
|
+
syncXhr: "sync-xhr",
|
|
73
|
+
usb: "usb",
|
|
74
|
+
webShare: "web-share",
|
|
75
|
+
xrSpatialTracking: "xr-spatial-tracking"
|
|
76
|
+
};
|
|
77
|
+
for (const [key, feature] of Object.entries(featureMap)) {
|
|
78
|
+
const origins = policy[key];
|
|
79
|
+
if (origins !== void 0) {
|
|
80
|
+
if (origins.length === 0) {
|
|
81
|
+
directives.push(`${feature}=()`);
|
|
82
|
+
} else {
|
|
83
|
+
const formatted = origins.map((o) => o === "self" ? "self" : `"${o}"`).join(" ");
|
|
84
|
+
directives.push(`${feature}=(${formatted})`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return directives.join(", ");
|
|
89
|
+
}
|
|
90
|
+
var PRESET_STRICT = {
|
|
91
|
+
contentSecurityPolicy: {
|
|
92
|
+
defaultSrc: ["'self'"],
|
|
93
|
+
scriptSrc: ["'self'"],
|
|
94
|
+
styleSrc: ["'self'"],
|
|
95
|
+
imgSrc: ["'self'", "data:"],
|
|
96
|
+
fontSrc: ["'self'"],
|
|
97
|
+
objectSrc: ["'none'"],
|
|
98
|
+
frameAncestors: ["'none'"],
|
|
99
|
+
formAction: ["'self'"],
|
|
100
|
+
baseUri: ["'self'"],
|
|
101
|
+
upgradeInsecureRequests: true
|
|
102
|
+
},
|
|
103
|
+
strictTransportSecurity: {
|
|
104
|
+
maxAge: 31536e3,
|
|
105
|
+
// 1 year
|
|
106
|
+
includeSubDomains: true,
|
|
107
|
+
preload: true
|
|
108
|
+
},
|
|
109
|
+
xFrameOptions: "DENY",
|
|
110
|
+
xContentTypeOptions: true,
|
|
111
|
+
xDnsPrefetchControl: "off",
|
|
112
|
+
xDownloadOptions: true,
|
|
113
|
+
xPermittedCrossDomainPolicies: "none",
|
|
114
|
+
referrerPolicy: "strict-origin-when-cross-origin",
|
|
115
|
+
crossOriginOpenerPolicy: "same-origin",
|
|
116
|
+
crossOriginEmbedderPolicy: "require-corp",
|
|
117
|
+
crossOriginResourcePolicy: "same-origin",
|
|
118
|
+
permissionsPolicy: {
|
|
119
|
+
camera: [],
|
|
120
|
+
microphone: [],
|
|
121
|
+
geolocation: [],
|
|
122
|
+
payment: []
|
|
123
|
+
},
|
|
124
|
+
originAgentCluster: true
|
|
125
|
+
};
|
|
126
|
+
var PRESET_RELAXED = {
|
|
127
|
+
contentSecurityPolicy: {
|
|
128
|
+
defaultSrc: ["'self'"],
|
|
129
|
+
scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
|
|
130
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
131
|
+
imgSrc: ["'self'", "data:", "blob:", "https:"],
|
|
132
|
+
fontSrc: ["'self'", "https:", "data:"],
|
|
133
|
+
connectSrc: ["'self'", "https:", "wss:"],
|
|
134
|
+
frameSrc: ["'self'"]
|
|
135
|
+
},
|
|
136
|
+
strictTransportSecurity: {
|
|
137
|
+
maxAge: 86400,
|
|
138
|
+
// 1 day
|
|
139
|
+
includeSubDomains: false
|
|
140
|
+
},
|
|
141
|
+
xFrameOptions: "SAMEORIGIN",
|
|
142
|
+
xContentTypeOptions: true,
|
|
143
|
+
referrerPolicy: "no-referrer-when-downgrade"
|
|
144
|
+
};
|
|
145
|
+
var PRESET_API = {
|
|
146
|
+
contentSecurityPolicy: {
|
|
147
|
+
defaultSrc: ["'none'"],
|
|
148
|
+
frameAncestors: ["'none'"]
|
|
149
|
+
},
|
|
150
|
+
strictTransportSecurity: {
|
|
151
|
+
maxAge: 31536e3,
|
|
152
|
+
includeSubDomains: true
|
|
153
|
+
},
|
|
154
|
+
xFrameOptions: "DENY",
|
|
155
|
+
xContentTypeOptions: true,
|
|
156
|
+
referrerPolicy: "no-referrer",
|
|
157
|
+
crossOriginResourcePolicy: "same-origin"
|
|
158
|
+
};
|
|
159
|
+
function getPreset(name) {
|
|
160
|
+
switch (name) {
|
|
161
|
+
case "strict":
|
|
162
|
+
return PRESET_STRICT;
|
|
163
|
+
case "relaxed":
|
|
164
|
+
return PRESET_RELAXED;
|
|
165
|
+
case "api":
|
|
166
|
+
return PRESET_API;
|
|
167
|
+
default:
|
|
168
|
+
return PRESET_STRICT;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function buildHeaders(config) {
|
|
172
|
+
const headers = new Headers();
|
|
173
|
+
if (config.contentSecurityPolicy) {
|
|
174
|
+
const csp = buildCSP(config.contentSecurityPolicy);
|
|
175
|
+
if (csp) headers.set("Content-Security-Policy", csp);
|
|
176
|
+
}
|
|
177
|
+
if (config.strictTransportSecurity) {
|
|
178
|
+
headers.set("Strict-Transport-Security", buildHSTS(config.strictTransportSecurity));
|
|
179
|
+
}
|
|
180
|
+
if (config.xFrameOptions) {
|
|
181
|
+
headers.set("X-Frame-Options", config.xFrameOptions);
|
|
182
|
+
}
|
|
183
|
+
if (config.xContentTypeOptions) {
|
|
184
|
+
headers.set("X-Content-Type-Options", "nosniff");
|
|
185
|
+
}
|
|
186
|
+
if (config.xDnsPrefetchControl) {
|
|
187
|
+
headers.set("X-DNS-Prefetch-Control", config.xDnsPrefetchControl);
|
|
188
|
+
}
|
|
189
|
+
if (config.xDownloadOptions) {
|
|
190
|
+
headers.set("X-Download-Options", "noopen");
|
|
191
|
+
}
|
|
192
|
+
if (config.xPermittedCrossDomainPolicies) {
|
|
193
|
+
headers.set("X-Permitted-Cross-Domain-Policies", config.xPermittedCrossDomainPolicies);
|
|
194
|
+
}
|
|
195
|
+
if (config.referrerPolicy) {
|
|
196
|
+
const value = Array.isArray(config.referrerPolicy) ? config.referrerPolicy.join(", ") : config.referrerPolicy;
|
|
197
|
+
headers.set("Referrer-Policy", value);
|
|
198
|
+
}
|
|
199
|
+
if (config.crossOriginOpenerPolicy) {
|
|
200
|
+
headers.set("Cross-Origin-Opener-Policy", config.crossOriginOpenerPolicy);
|
|
201
|
+
}
|
|
202
|
+
if (config.crossOriginEmbedderPolicy) {
|
|
203
|
+
headers.set("Cross-Origin-Embedder-Policy", config.crossOriginEmbedderPolicy);
|
|
204
|
+
}
|
|
205
|
+
if (config.crossOriginResourcePolicy) {
|
|
206
|
+
headers.set("Cross-Origin-Resource-Policy", config.crossOriginResourcePolicy);
|
|
207
|
+
}
|
|
208
|
+
if (config.permissionsPolicy) {
|
|
209
|
+
const pp = buildPermissionsPolicy(config.permissionsPolicy);
|
|
210
|
+
if (pp) headers.set("Permissions-Policy", pp);
|
|
211
|
+
}
|
|
212
|
+
if (config.originAgentCluster) {
|
|
213
|
+
headers.set("Origin-Agent-Cluster", "?1");
|
|
214
|
+
}
|
|
215
|
+
return headers;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/middleware/headers/middleware.ts
|
|
219
|
+
function mergeConfigs(base, custom) {
|
|
220
|
+
return {
|
|
221
|
+
...base,
|
|
222
|
+
...custom,
|
|
223
|
+
// Deep merge CSP if both exist
|
|
224
|
+
contentSecurityPolicy: custom.contentSecurityPolicy === false ? false : custom.contentSecurityPolicy ? base.contentSecurityPolicy ? { ...base.contentSecurityPolicy, ...custom.contentSecurityPolicy } : custom.contentSecurityPolicy : base.contentSecurityPolicy,
|
|
225
|
+
// Deep merge HSTS if both exist
|
|
226
|
+
strictTransportSecurity: custom.strictTransportSecurity === false ? false : custom.strictTransportSecurity ? base.strictTransportSecurity ? { ...base.strictTransportSecurity, ...custom.strictTransportSecurity } : custom.strictTransportSecurity : base.strictTransportSecurity,
|
|
227
|
+
// Deep merge Permissions-Policy if both exist
|
|
228
|
+
permissionsPolicy: custom.permissionsPolicy === false ? false : custom.permissionsPolicy ? base.permissionsPolicy ? { ...base.permissionsPolicy, ...custom.permissionsPolicy } : custom.permissionsPolicy : base.permissionsPolicy
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function withSecurityHeaders(handler, options = {}) {
|
|
232
|
+
const { preset, config, override = false } = options;
|
|
233
|
+
let baseConfig = preset ? getPreset(preset) : PRESET_STRICT;
|
|
234
|
+
if (config) {
|
|
235
|
+
baseConfig = mergeConfigs(baseConfig, config);
|
|
236
|
+
}
|
|
237
|
+
const securityHeaders = buildHeaders(baseConfig);
|
|
238
|
+
return async (req) => {
|
|
239
|
+
const response = await handler(req);
|
|
240
|
+
const newHeaders = new Headers(response.headers);
|
|
241
|
+
securityHeaders.forEach((value, key) => {
|
|
242
|
+
if (override || !newHeaders.has(key)) {
|
|
243
|
+
newHeaders.set(key, value);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
return new Response(response.body, {
|
|
247
|
+
status: response.status,
|
|
248
|
+
statusText: response.statusText,
|
|
249
|
+
headers: newHeaders
|
|
250
|
+
});
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function createSecurityHeaders(options = {}) {
|
|
254
|
+
const { preset, config } = options;
|
|
255
|
+
let baseConfig = preset ? getPreset(preset) : PRESET_STRICT;
|
|
256
|
+
if (config) {
|
|
257
|
+
baseConfig = mergeConfigs(baseConfig, config);
|
|
258
|
+
}
|
|
259
|
+
return buildHeaders(baseConfig);
|
|
260
|
+
}
|
|
261
|
+
function createSecurityHeadersObject(options = {}) {
|
|
262
|
+
const headers = createSecurityHeaders(options);
|
|
263
|
+
const obj = {};
|
|
264
|
+
headers.forEach((value, key) => {
|
|
265
|
+
obj[key] = value;
|
|
266
|
+
});
|
|
267
|
+
return obj;
|
|
7
268
|
}
|
|
8
269
|
|
|
9
|
-
export {
|
|
270
|
+
export { PRESET_API, PRESET_RELAXED, PRESET_STRICT, buildCSP, buildHSTS, buildHeaders, buildPermissionsPolicy, createSecurityHeaders, createSecurityHeadersObject, getPreset, withSecurityHeaders };
|
|
10
271
|
//# sourceMappingURL=headers.js.map
|
|
11
272
|
//# sourceMappingURL=headers.js.map
|
package/dist/headers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/middleware/headers/index.ts"],"names":[],"mappings":";AAiCO,SAAS,eAAA,GAAkB;AAChC,EAAA,MAAM,IAAI,MAAM,wCAAwC,CAAA;AAC1D;AAEO,SAAS,SAAA,GAAY;AAC1B,EAAA,MAAM,IAAI,MAAM,wCAAwC,CAAA;AAC1D","file":"headers.js","sourcesContent":["/**\n * Security Headers Middleware (Coming Soon)\n *\n * @example\n * ```typescript\n * // next.config.js\n * import { securityHeaders } from 'next-secure/headers'\n *\n * export default {\n * async headers() {\n * return [\n * {\n * source: '/:path*',\n * headers: securityHeaders({\n * contentSecurityPolicy: {\n * directives: {\n * defaultSrc: [\"'self'\"],\n * scriptSrc: [\"'self'\", \"'unsafe-inline'\"],\n * }\n * },\n * strictTransportSecurity: true,\n * xFrameOptions: 'DENY',\n * })\n * }\n * ]\n * }\n * }\n * ```\n *\n * @packageDocumentation\n */\n\n// Placeholder for security headers\nexport function securityHeaders() {\n throw new Error('Security headers coming soon in v0.2.0')\n}\n\nexport function createCsp() {\n throw new Error('Security headers coming soon in v0.2.0')\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/middleware/headers/builder.ts","../src/middleware/headers/middleware.ts"],"names":[],"mappings":"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","file":"headers.js","sourcesContent":["import type {\n ContentSecurityPolicy,\n StrictTransportSecurity,\n PermissionsPolicy,\n SecurityHeadersConfig,\n SecurityHeadersPreset,\n} from './types'\n\n/**\n * Build CSP header string from config\n */\nexport function buildCSP(policy: ContentSecurityPolicy): string {\n const directives: string[] = []\n\n const directiveMap: Record<string, string> = {\n defaultSrc: 'default-src',\n scriptSrc: 'script-src',\n styleSrc: 'style-src',\n imgSrc: 'img-src',\n fontSrc: 'font-src',\n connectSrc: 'connect-src',\n mediaSrc: 'media-src',\n objectSrc: 'object-src',\n frameSrc: 'frame-src',\n childSrc: 'child-src',\n workerSrc: 'worker-src',\n frameAncestors: 'frame-ancestors',\n formAction: 'form-action',\n baseUri: 'base-uri',\n manifestSrc: 'manifest-src',\n reportUri: 'report-uri',\n reportTo: 'report-to',\n }\n\n for (const [key, directive] of Object.entries(directiveMap)) {\n const value = policy[key as keyof ContentSecurityPolicy]\n if (value !== undefined && value !== false) {\n if (Array.isArray(value)) {\n directives.push(`${directive} ${value.join(' ')}`)\n } else if (typeof value === 'string') {\n directives.push(`${directive} ${value}`)\n }\n }\n }\n\n if (policy.upgradeInsecureRequests) {\n directives.push('upgrade-insecure-requests')\n }\n\n if (policy.blockAllMixedContent) {\n directives.push('block-all-mixed-content')\n }\n\n return directives.join('; ')\n}\n\n/**\n * Build HSTS header string\n */\nexport function buildHSTS(config: StrictTransportSecurity): string {\n let value = `max-age=${config.maxAge}`\n\n if (config.includeSubDomains) {\n value += '; includeSubDomains'\n }\n\n if (config.preload) {\n value += '; preload'\n }\n\n return value\n}\n\n/**\n * Build Permissions-Policy header string\n */\nexport function buildPermissionsPolicy(policy: PermissionsPolicy): string {\n const directives: string[] = []\n\n const featureMap: Record<string, string> = {\n accelerometer: 'accelerometer',\n ambientLightSensor: 'ambient-light-sensor',\n autoplay: 'autoplay',\n battery: 'battery',\n camera: 'camera',\n displayCapture: 'display-capture',\n documentDomain: 'document-domain',\n encryptedMedia: 'encrypted-media',\n fullscreen: 'fullscreen',\n geolocation: 'geolocation',\n gyroscope: 'gyroscope',\n magnetometer: 'magnetometer',\n microphone: 'microphone',\n midi: 'midi',\n payment: 'payment',\n pictureInPicture: 'picture-in-picture',\n publicKeyCredentialsGet: 'publickey-credentials-get',\n screenWakeLock: 'screen-wake-lock',\n syncXhr: 'sync-xhr',\n usb: 'usb',\n webShare: 'web-share',\n xrSpatialTracking: 'xr-spatial-tracking',\n }\n\n for (const [key, feature] of Object.entries(featureMap)) {\n const origins = policy[key as keyof PermissionsPolicy]\n if (origins !== undefined) {\n if (origins.length === 0) {\n directives.push(`${feature}=()`)\n } else {\n const formatted = origins.map((o) => (o === 'self' ? 'self' : `\"${o}\"`)).join(' ')\n directives.push(`${feature}=(${formatted})`)\n }\n }\n }\n\n return directives.join(', ')\n}\n\n/**\n * Preset: Strict security headers\n */\nexport const PRESET_STRICT: SecurityHeadersConfig = {\n contentSecurityPolicy: {\n defaultSrc: [\"'self'\"],\n scriptSrc: [\"'self'\"],\n styleSrc: [\"'self'\"],\n imgSrc: [\"'self'\", 'data:'],\n fontSrc: [\"'self'\"],\n objectSrc: [\"'none'\"],\n frameAncestors: [\"'none'\"],\n formAction: [\"'self'\"],\n baseUri: [\"'self'\"],\n upgradeInsecureRequests: true,\n },\n strictTransportSecurity: {\n maxAge: 31536000, // 1 year\n includeSubDomains: true,\n preload: true,\n },\n xFrameOptions: 'DENY',\n xContentTypeOptions: true,\n xDnsPrefetchControl: 'off',\n xDownloadOptions: true,\n xPermittedCrossDomainPolicies: 'none',\n referrerPolicy: 'strict-origin-when-cross-origin',\n crossOriginOpenerPolicy: 'same-origin',\n crossOriginEmbedderPolicy: 'require-corp',\n crossOriginResourcePolicy: 'same-origin',\n permissionsPolicy: {\n camera: [],\n microphone: [],\n geolocation: [],\n payment: [],\n },\n originAgentCluster: true,\n}\n\n/**\n * Preset: Relaxed security headers (for development or less strict needs)\n */\nexport const PRESET_RELAXED: SecurityHeadersConfig = {\n contentSecurityPolicy: {\n defaultSrc: [\"'self'\"],\n scriptSrc: [\"'self'\", \"'unsafe-inline'\", \"'unsafe-eval'\"],\n styleSrc: [\"'self'\", \"'unsafe-inline'\"],\n imgSrc: [\"'self'\", 'data:', 'blob:', 'https:'],\n fontSrc: [\"'self'\", 'https:', 'data:'],\n connectSrc: [\"'self'\", 'https:', 'wss:'],\n frameSrc: [\"'self'\"],\n },\n strictTransportSecurity: {\n maxAge: 86400, // 1 day\n includeSubDomains: false,\n },\n xFrameOptions: 'SAMEORIGIN',\n xContentTypeOptions: true,\n referrerPolicy: 'no-referrer-when-downgrade',\n}\n\n/**\n * Preset: API-focused security headers\n */\nexport const PRESET_API: SecurityHeadersConfig = {\n contentSecurityPolicy: {\n defaultSrc: [\"'none'\"],\n frameAncestors: [\"'none'\"],\n },\n strictTransportSecurity: {\n maxAge: 31536000,\n includeSubDomains: true,\n },\n xFrameOptions: 'DENY',\n xContentTypeOptions: true,\n referrerPolicy: 'no-referrer',\n crossOriginResourcePolicy: 'same-origin',\n}\n\n/**\n * Get preset config by name\n */\nexport function getPreset(name: SecurityHeadersPreset): SecurityHeadersConfig {\n switch (name) {\n case 'strict':\n return PRESET_STRICT\n case 'relaxed':\n return PRESET_RELAXED\n case 'api':\n return PRESET_API\n default:\n return PRESET_STRICT\n }\n}\n\n/**\n * Build all headers from config\n */\nexport function buildHeaders(config: SecurityHeadersConfig): Headers {\n const headers = new Headers()\n\n // CSP\n if (config.contentSecurityPolicy) {\n const csp = buildCSP(config.contentSecurityPolicy)\n if (csp) headers.set('Content-Security-Policy', csp)\n }\n\n // HSTS\n if (config.strictTransportSecurity) {\n headers.set('Strict-Transport-Security', buildHSTS(config.strictTransportSecurity))\n }\n\n // X-Frame-Options\n if (config.xFrameOptions) {\n headers.set('X-Frame-Options', config.xFrameOptions)\n }\n\n // X-Content-Type-Options\n if (config.xContentTypeOptions) {\n headers.set('X-Content-Type-Options', 'nosniff')\n }\n\n // X-DNS-Prefetch-Control\n if (config.xDnsPrefetchControl) {\n headers.set('X-DNS-Prefetch-Control', config.xDnsPrefetchControl)\n }\n\n // X-Download-Options\n if (config.xDownloadOptions) {\n headers.set('X-Download-Options', 'noopen')\n }\n\n // X-Permitted-Cross-Domain-Policies\n if (config.xPermittedCrossDomainPolicies) {\n headers.set('X-Permitted-Cross-Domain-Policies', config.xPermittedCrossDomainPolicies)\n }\n\n // Referrer-Policy\n if (config.referrerPolicy) {\n const value = Array.isArray(config.referrerPolicy)\n ? config.referrerPolicy.join(', ')\n : config.referrerPolicy\n headers.set('Referrer-Policy', value)\n }\n\n // COOP\n if (config.crossOriginOpenerPolicy) {\n headers.set('Cross-Origin-Opener-Policy', config.crossOriginOpenerPolicy)\n }\n\n // COEP\n if (config.crossOriginEmbedderPolicy) {\n headers.set('Cross-Origin-Embedder-Policy', config.crossOriginEmbedderPolicy)\n }\n\n // CORP\n if (config.crossOriginResourcePolicy) {\n headers.set('Cross-Origin-Resource-Policy', config.crossOriginResourcePolicy)\n }\n\n // Permissions-Policy\n if (config.permissionsPolicy) {\n const pp = buildPermissionsPolicy(config.permissionsPolicy)\n if (pp) headers.set('Permissions-Policy', pp)\n }\n\n // Origin-Agent-Cluster\n if (config.originAgentCluster) {\n headers.set('Origin-Agent-Cluster', '?1')\n }\n\n return headers\n}\n","import type { NextRequest } from 'next/server'\nimport type { SecurityHeadersConfig, SecurityHeadersPreset } from './types'\nimport { buildHeaders, getPreset, PRESET_STRICT } from './builder'\n\ntype RouteHandler = (req: NextRequest) => Response | Promise<Response>\n\nexport interface WithSecurityHeadersOptions {\n /** Use a preset configuration */\n preset?: SecurityHeadersPreset\n\n /** Custom header configuration (merged with preset if provided) */\n config?: SecurityHeadersConfig\n\n /** Override response headers instead of merging */\n override?: boolean\n}\n\n/**\n * Merge two configs, with custom taking precedence\n */\nfunction mergeConfigs(\n base: SecurityHeadersConfig,\n custom: SecurityHeadersConfig\n): SecurityHeadersConfig {\n return {\n ...base,\n ...custom,\n // Deep merge CSP if both exist\n contentSecurityPolicy:\n custom.contentSecurityPolicy === false\n ? false\n : custom.contentSecurityPolicy\n ? base.contentSecurityPolicy\n ? { ...(base.contentSecurityPolicy as object), ...custom.contentSecurityPolicy }\n : custom.contentSecurityPolicy\n : base.contentSecurityPolicy,\n // Deep merge HSTS if both exist\n strictTransportSecurity:\n custom.strictTransportSecurity === false\n ? false\n : custom.strictTransportSecurity\n ? base.strictTransportSecurity\n ? { ...(base.strictTransportSecurity as object), ...custom.strictTransportSecurity }\n : custom.strictTransportSecurity\n : base.strictTransportSecurity,\n // Deep merge Permissions-Policy if both exist\n permissionsPolicy:\n custom.permissionsPolicy === false\n ? false\n : custom.permissionsPolicy\n ? base.permissionsPolicy\n ? { ...(base.permissionsPolicy as object), ...custom.permissionsPolicy }\n : custom.permissionsPolicy\n : base.permissionsPolicy,\n }\n}\n\n/**\n * Security headers middleware\n *\n * Adds security headers to responses. Use presets for quick setup\n * or provide custom configuration.\n *\n * @example\n * ```typescript\n * // Use strict preset\n * export const GET = withSecurityHeaders(handler, { preset: 'strict' })\n *\n * // Custom config\n * export const GET = withSecurityHeaders(handler, {\n * config: {\n * xFrameOptions: 'SAMEORIGIN',\n * referrerPolicy: 'no-referrer'\n * }\n * })\n * ```\n */\nexport function withSecurityHeaders(\n handler: RouteHandler,\n options: WithSecurityHeadersOptions = {}\n): RouteHandler {\n const { preset, config, override = false } = options\n\n // Get base config from preset or default to strict\n let baseConfig: SecurityHeadersConfig = preset ? getPreset(preset) : PRESET_STRICT\n\n // Merge with custom config if provided\n if (config) {\n baseConfig = mergeConfigs(baseConfig, config)\n }\n\n // Pre-build headers for performance\n const securityHeaders = buildHeaders(baseConfig)\n\n return async (req: NextRequest): Promise<Response> => {\n const response = await handler(req)\n\n // Clone response to modify headers\n const newHeaders = new Headers(response.headers)\n\n // Add security headers\n securityHeaders.forEach((value, key) => {\n if (override || !newHeaders.has(key)) {\n newHeaders.set(key, value)\n }\n })\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: newHeaders,\n })\n }\n}\n\n/**\n * Create headers object for use in responses\n * Useful when you want to add headers manually\n *\n * @example\n * ```typescript\n * const headers = createSecurityHeaders({ preset: 'api' })\n *\n * return Response.json(data, { headers })\n * ```\n */\nexport function createSecurityHeaders(\n options: WithSecurityHeadersOptions = {}\n): Headers {\n const { preset, config } = options\n\n let baseConfig: SecurityHeadersConfig = preset ? getPreset(preset) : PRESET_STRICT\n\n if (config) {\n baseConfig = mergeConfigs(baseConfig, config)\n }\n\n return buildHeaders(baseConfig)\n}\n\n/**\n * Create a headers object as a plain object (for Next.js headers())\n */\nexport function createSecurityHeadersObject(\n options: WithSecurityHeadersOptions = {}\n): Record<string, string> {\n const headers = createSecurityHeaders(options)\n const obj: Record<string, string> = {}\n\n headers.forEach((value, key) => {\n obj[key] = value\n })\n\n return obj\n}\n"]}
|