forked from Fluxora-Org/Fluxora-Backend
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcors.ts
More file actions
120 lines (101 loc) · 2.9 KB
/
Copy pathcors.ts
File metadata and controls
120 lines (101 loc) · 2.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
type CorsRequest = {
method: string;
header: (name: string) => string | undefined;
};
type CorsResponse = {
setHeader: (name: string, value: string) => void;
sendStatus: (code: number) => void;
status: (code: number) => {
json: (body: unknown) => void;
};
};
type CorsNext = (err?: unknown) => void;
const DEFAULT_ALLOWED_METHODS = 'GET,POST,PUT,PATCH,DELETE,OPTIONS';
const DEFAULT_ALLOWED_HEADERS = 'Content-Type,Authorization,X-Correlation-ID';
const PREFLIGHT_MAX_AGE = '86400'; // 24 hours in seconds
export function isOriginAllowed(origin: string, allowedOrigins: Set<string>): boolean {
if (allowedOrigins.has(origin)) {
return true;
}
for (const allowed of allowedOrigins) {
if (allowed.startsWith('*.')) {
const baseDomain = allowed.slice(2);
if (origin.endsWith('.' + baseDomain)) {
return true;
}
}
}
return false;
}
function parseAllowedOrigins(raw: string | undefined): Set<string> {
if (!raw) {
return new Set();
}
return new Set(
raw
.split(',')
.map((origin) => origin.trim())
.filter(Boolean),
);
}
function isProduction(): boolean {
return process.env.NODE_ENV === 'production';
}
function allowOrigin(req: CorsRequest, res: CorsResponse, origin: string): void {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
res.setHeader('Access-Control-Allow-Methods', DEFAULT_ALLOWED_METHODS);
// Echo back the requested headers if present, otherwise use defaults.
const requestedHeaders = req.header('Access-Control-Request-Headers');
res.setHeader(
'Access-Control-Allow-Headers',
requestedHeaders ?? DEFAULT_ALLOWED_HEADERS,
);
}
function isPreflight(req: CorsRequest): boolean {
return req.method === 'OPTIONS' && Boolean(req.header('Origin'));
}
export function corsAllowlistMiddleware(req: CorsRequest, res: CorsResponse, next: CorsNext): void {
const origin = req.header('Origin');
// Non-browser or same-origin requests do not carry Origin.
if (!origin) {
if (req.method === 'OPTIONS') {
res.sendStatus(204);
return;
}
next();
return;
}
if (!isProduction()) {
allowOrigin(req, res, origin);
if (isPreflight(req)) {
res.setHeader('Access-Control-Max-Age', PREFLIGHT_MAX_AGE);
res.sendStatus(204);
return;
}
next();
return;
}
const allowedOrigins = parseAllowedOrigins(process.env.CORS_ALLOWED_ORIGINS);
const isAllowed = isOriginAllowed(origin, allowedOrigins);
if (isAllowed) {
allowOrigin(req, res, origin);
if (isPreflight(req)) {
res.setHeader('Access-Control-Max-Age', PREFLIGHT_MAX_AGE);
res.sendStatus(204);
return;
}
next();
return;
}
if (isPreflight(req)) {
res.status(403).json({
error: {
code: 'CORS_ORIGIN_DENIED',
message: 'Origin is not allowed by CORS policy',
},
});
return;
}
next();
}