ship-safe 1.0.1 → 3.0.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.
@@ -0,0 +1,322 @@
1
+ /**
2
+ * CORS Configuration Examples
3
+ * ===========================
4
+ *
5
+ * Cross-Origin Resource Sharing (CORS) configurations for common frameworks.
6
+ *
7
+ * WHY CORS MATTERS:
8
+ * - Prevents unauthorized websites from calling your API
9
+ * - Blocks cross-site request forgery (CSRF) attacks
10
+ * - Controls which domains can access your resources
11
+ *
12
+ * COMMON MISTAKES:
13
+ * - Setting origin: '*' in production (allows any site)
14
+ * - Not validating origin properly (regex bypass)
15
+ * - Forgetting credentials handling
16
+ */
17
+
18
+ // =============================================================================
19
+ // NEXT.JS API ROUTES
20
+ // =============================================================================
21
+
22
+ /**
23
+ * Next.js API route with CORS
24
+ * Use in: pages/api/*.ts or app/api/*/route.ts
25
+ */
26
+ export const nextjsCorsConfig = `
27
+ // next.config.js - Basic headers approach
28
+ /** @type {import('next').NextConfig} */
29
+ const nextConfig = {
30
+ async headers() {
31
+ return [
32
+ {
33
+ source: '/api/:path*',
34
+ headers: [
35
+ {
36
+ key: 'Access-Control-Allow-Origin',
37
+ // CHANGE THIS to your frontend domain
38
+ value: process.env.FRONTEND_URL || 'https://yourapp.com',
39
+ },
40
+ {
41
+ key: 'Access-Control-Allow-Methods',
42
+ value: 'GET, POST, PUT, DELETE, OPTIONS',
43
+ },
44
+ {
45
+ key: 'Access-Control-Allow-Headers',
46
+ value: 'Content-Type, Authorization',
47
+ },
48
+ {
49
+ key: 'Access-Control-Allow-Credentials',
50
+ value: 'true',
51
+ },
52
+ ],
53
+ },
54
+ ];
55
+ },
56
+ };
57
+
58
+ module.exports = nextConfig;
59
+ `;
60
+
61
+ /**
62
+ * Next.js API route handler with manual CORS
63
+ */
64
+ export const nextjsRouteHandler = `
65
+ // app/api/example/route.ts
66
+ import { NextResponse } from 'next/server';
67
+ import type { NextRequest } from 'next/server';
68
+
69
+ // Allowed origins - ADD YOUR DOMAINS HERE
70
+ const ALLOWED_ORIGINS = [
71
+ 'https://yourapp.com',
72
+ 'https://www.yourapp.com',
73
+ process.env.NODE_ENV === 'development' && 'http://localhost:3000',
74
+ ].filter(Boolean) as string[];
75
+
76
+ function getCorsHeaders(origin: string | null) {
77
+ const headers: Record<string, string> = {
78
+ 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
79
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
80
+ 'Access-Control-Max-Age': '86400', // 24 hours
81
+ };
82
+
83
+ // Only allow specific origins
84
+ if (origin && ALLOWED_ORIGINS.includes(origin)) {
85
+ headers['Access-Control-Allow-Origin'] = origin;
86
+ headers['Access-Control-Allow-Credentials'] = 'true';
87
+ }
88
+
89
+ return headers;
90
+ }
91
+
92
+ // Handle preflight requests
93
+ export async function OPTIONS(request: NextRequest) {
94
+ const origin = request.headers.get('origin');
95
+ return new NextResponse(null, {
96
+ status: 204,
97
+ headers: getCorsHeaders(origin),
98
+ });
99
+ }
100
+
101
+ export async function GET(request: NextRequest) {
102
+ const origin = request.headers.get('origin');
103
+
104
+ // Your logic here
105
+ const data = { message: 'Hello!' };
106
+
107
+ return NextResponse.json(data, {
108
+ headers: getCorsHeaders(origin),
109
+ });
110
+ }
111
+
112
+ export async function POST(request: NextRequest) {
113
+ const origin = request.headers.get('origin');
114
+
115
+ // Validate origin for mutations
116
+ if (origin && !ALLOWED_ORIGINS.includes(origin)) {
117
+ return new NextResponse('Forbidden', { status: 403 });
118
+ }
119
+
120
+ // Your logic here
121
+ const body = await request.json();
122
+
123
+ return NextResponse.json({ success: true }, {
124
+ headers: getCorsHeaders(origin),
125
+ });
126
+ }
127
+ `;
128
+
129
+ // =============================================================================
130
+ // EXPRESS.JS
131
+ // =============================================================================
132
+
133
+ export const expressCorsConfig = `
134
+ // Express CORS configuration
135
+ import cors from 'cors';
136
+ import express from 'express';
137
+
138
+ const app = express();
139
+
140
+ // BAD: Allows any origin
141
+ // app.use(cors()); // DON'T DO THIS IN PRODUCTION
142
+
143
+ // GOOD: Specific origins
144
+ const ALLOWED_ORIGINS = [
145
+ 'https://yourapp.com',
146
+ 'https://www.yourapp.com',
147
+ process.env.NODE_ENV === 'development' && 'http://localhost:3000',
148
+ ].filter(Boolean);
149
+
150
+ const corsOptions = {
151
+ origin: function (origin, callback) {
152
+ // Allow requests with no origin (mobile apps, curl, etc.)
153
+ // Remove this if you want to block those too
154
+ if (!origin) {
155
+ return callback(null, true);
156
+ }
157
+
158
+ if (ALLOWED_ORIGINS.includes(origin)) {
159
+ callback(null, true);
160
+ } else {
161
+ callback(new Error('CORS not allowed'));
162
+ }
163
+ },
164
+ credentials: true,
165
+ methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
166
+ allowedHeaders: ['Content-Type', 'Authorization'],
167
+ maxAge: 86400, // 24 hours
168
+ };
169
+
170
+ app.use(cors(corsOptions));
171
+
172
+ // Handle CORS errors
173
+ app.use((err, req, res, next) => {
174
+ if (err.message === 'CORS not allowed') {
175
+ return res.status(403).json({ error: 'Origin not allowed' });
176
+ }
177
+ next(err);
178
+ });
179
+ `;
180
+
181
+ // =============================================================================
182
+ // FASTIFY
183
+ // =============================================================================
184
+
185
+ export const fastifyCorsConfig = `
186
+ // Fastify CORS configuration
187
+ import Fastify from 'fastify';
188
+ import cors from '@fastify/cors';
189
+
190
+ const fastify = Fastify();
191
+
192
+ const ALLOWED_ORIGINS = [
193
+ 'https://yourapp.com',
194
+ 'https://www.yourapp.com',
195
+ ];
196
+
197
+ await fastify.register(cors, {
198
+ origin: (origin, callback) => {
199
+ if (!origin || ALLOWED_ORIGINS.includes(origin)) {
200
+ callback(null, true);
201
+ } else {
202
+ callback(new Error('Not allowed'), false);
203
+ }
204
+ },
205
+ credentials: true,
206
+ methods: ['GET', 'POST', 'PUT', 'DELETE'],
207
+ allowedHeaders: ['Content-Type', 'Authorization'],
208
+ });
209
+ `;
210
+
211
+ // =============================================================================
212
+ // HONO (EDGE)
213
+ // =============================================================================
214
+
215
+ export const honoCorsConfig = `
216
+ // Hono CORS configuration (for Cloudflare Workers, etc.)
217
+ import { Hono } from 'hono';
218
+ import { cors } from 'hono/cors';
219
+
220
+ const app = new Hono();
221
+
222
+ const ALLOWED_ORIGINS = [
223
+ 'https://yourapp.com',
224
+ 'https://www.yourapp.com',
225
+ ];
226
+
227
+ app.use('/*', cors({
228
+ origin: (origin) => {
229
+ if (ALLOWED_ORIGINS.includes(origin)) {
230
+ return origin;
231
+ }
232
+ return null; // Block the request
233
+ },
234
+ credentials: true,
235
+ allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
236
+ allowHeaders: ['Content-Type', 'Authorization'],
237
+ maxAge: 86400,
238
+ }));
239
+ `;
240
+
241
+ // =============================================================================
242
+ // VERCEL EDGE FUNCTIONS
243
+ // =============================================================================
244
+
245
+ export const vercelEdgeCors = `
246
+ // Vercel Edge Function with CORS
247
+ // api/example.ts
248
+ import type { NextRequest } from 'next/server';
249
+
250
+ export const config = {
251
+ runtime: 'edge',
252
+ };
253
+
254
+ const ALLOWED_ORIGINS = [
255
+ 'https://yourapp.com',
256
+ 'https://www.yourapp.com',
257
+ ];
258
+
259
+ export default async function handler(request: NextRequest) {
260
+ const origin = request.headers.get('origin') || '';
261
+
262
+ // Validate origin
263
+ const isAllowed = ALLOWED_ORIGINS.includes(origin);
264
+
265
+ // Handle preflight
266
+ if (request.method === 'OPTIONS') {
267
+ return new Response(null, {
268
+ status: 204,
269
+ headers: {
270
+ 'Access-Control-Allow-Origin': isAllowed ? origin : '',
271
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
272
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
273
+ 'Access-Control-Max-Age': '86400',
274
+ },
275
+ });
276
+ }
277
+
278
+ // Block non-allowed origins for mutations
279
+ if (request.method !== 'GET' && !isAllowed) {
280
+ return new Response('Forbidden', { status: 403 });
281
+ }
282
+
283
+ // Your logic here
284
+ const data = { message: 'Hello from the edge!' };
285
+
286
+ return new Response(JSON.stringify(data), {
287
+ headers: {
288
+ 'Content-Type': 'application/json',
289
+ 'Access-Control-Allow-Origin': isAllowed ? origin : '',
290
+ 'Access-Control-Allow-Credentials': 'true',
291
+ },
292
+ });
293
+ }
294
+ `;
295
+
296
+ // =============================================================================
297
+ // ANTI-PATTERNS
298
+ // =============================================================================
299
+
300
+ export const corsAntiPatterns = `
301
+ // CORS ANTI-PATTERNS - DON'T DO THESE
302
+
303
+ // 1. Wildcard origin (allows any site to call your API)
304
+ app.use(cors({ origin: '*' }));
305
+
306
+ // 2. Reflecting any origin (same problem as wildcard)
307
+ app.use(cors({
308
+ origin: (origin, callback) => callback(null, origin), // BAD!
309
+ }));
310
+
311
+ // 3. Regex without anchors (can be bypassed)
312
+ // Attacker can use: https://yourapp.com.evil.com
313
+ const UNSAFE_PATTERN = /yourapp\\.com/; // BAD!
314
+ const SAFE_PATTERN = /^https:\\/\\/(www\\.)?yourapp\\.com$/; // GOOD
315
+
316
+ // 4. Not handling credentials properly
317
+ // If you need cookies/auth headers, you MUST specify exact origins
318
+ // Wildcard doesn't work with credentials
319
+
320
+ // 5. Long maxAge without reviewing
321
+ // If you change CORS config, users won't see it for maxAge seconds
322
+ `;