-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
93 lines (85 loc) · 3.16 KB
/
Copy pathindex.js
File metadata and controls
93 lines (85 loc) · 3.16 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
const crypto = require('node:crypto');
// Unlock hash is injected by CDK at deploy time (see infrastructure/lib/cert-stack.ts).
const UNLOCK_HASH = '__UNLOCK_HASH__';
function getCookies(request) {
const raw = (request.headers.cookie || []).map((h) => h.value).join('; ');
const result = {};
for (const part of raw.split(';')) {
const idx = part.indexOf('=');
if (idx < 0) continue;
const k = part.slice(0, idx).trim();
const v = part.slice(idx + 1).trim();
if (k) result[k] = v;
}
return result;
}
/** CloudFront passes application/x-www-form-urlencoded style querystrings. URLSearchParams treats '+' as a space, which breaks base64-like unlock tokens that contain '+'. */
function getUnlockTokenRaw(querystring) {
if (!querystring) return null;
const needle = 'unlock=';
const start = querystring.indexOf(needle);
if (start === -1) return null;
const i = start + needle.length;
const amp = querystring.indexOf('&', i);
const end = amp === -1 ? querystring.length : amp;
const enc = querystring.slice(i, end);
try {
return decodeURIComponent(enc);
} catch (_) {
return enc;
}
}
function removeUnlockParam(querystring) {
if (!querystring) return '';
return querystring
.split('&')
.filter((p) => {
const eq = p.indexOf('=');
const k = eq >= 0 ? p.slice(0, eq) : p;
return k !== 'unlock';
})
.join('&');
}
exports.handler = async (event) => {
const request = event.Records[0].cf.request;
const cookies = getCookies(request);
// Valid auth cookie — pass through
if (cookies.htok === UNLOCK_HASH) {
if (request.querystring?.includes('unlock=')) {
request.querystring = removeUnlockParam(request.querystring);
}
return request;
}
// Unlock via querystring: ?unlock=<token>
const tok = getUnlockTokenRaw(request.querystring || '');
if (tok) {
const tokHash = crypto.createHash('sha256').update(tok).digest('hex');
if (tokHash === UNLOCK_HASH) {
const qs = removeUnlockParam(request.querystring || '');
const dest = request.uri + (qs ? '?' + qs : '');
return {
status: '302',
statusDescription: 'Found',
headers: {
location: [{ key: 'Location', value: dest }],
'set-cookie': [
{
key: 'Set-Cookie',
value: 'htok=' + UNLOCK_HASH + '; Path=/; Max-Age=31536000; HttpOnly; Secure; SameSite=Lax',
},
],
'cache-control': [{ key: 'Cache-Control', value: 'no-store, no-cache' }],
},
};
}
}
return {
status: '403',
statusDescription: 'Forbidden',
headers: {
'content-type': [{ key: 'Content-Type', value: 'text/html; charset=utf-8' }],
'cache-control': [{ key: 'Cache-Control', value: 'no-store, no-cache, private' }],
},
body: '<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>private</title><style>body{background:#0a0a0b;color:#4a4a55;font-family:ui-monospace,monospace;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;font-size:13px;letter-spacing:0.1em}</style></head><body>private</body></html>',
};
};