update osm
This commit is contained in:
+80
-11
@@ -536,6 +536,69 @@ function readJsonBody(req) {
|
||||
})
|
||||
}
|
||||
|
||||
const TURNSTILE_SESSION_COOKIE = 'tssbot_turnstile'
|
||||
const TURNSTILE_SESSION_TTL_SECONDS = 30 * 60
|
||||
const TURNSTILE_SESSION_HMAC_KEY = crypto
|
||||
.createHash('sha256')
|
||||
.update(`tssbot-turnstile-session|${TURNSTILE_SECRET_KEY}`)
|
||||
.digest()
|
||||
|
||||
function signTurnstileSession(expiresAt) {
|
||||
return crypto
|
||||
.createHmac('sha256', TURNSTILE_SESSION_HMAC_KEY)
|
||||
.update(String(expiresAt))
|
||||
.digest('base64url')
|
||||
}
|
||||
|
||||
function buildTurnstileSessionCookie(req) {
|
||||
const expiresAt = Math.floor(Date.now() / 1000) + TURNSTILE_SESSION_TTL_SECONDS
|
||||
const signature = signTurnstileSession(expiresAt)
|
||||
const value = `${expiresAt}.${signature}`
|
||||
const proto = req.headers['x-forwarded-proto'] || (req.socket.encrypted ? 'https' : 'http')
|
||||
const isHttps = String(proto).split(',')[0].trim() === 'https'
|
||||
const parts = [
|
||||
`${TURNSTILE_SESSION_COOKIE}=${value}`,
|
||||
'Path=/',
|
||||
`Max-Age=${TURNSTILE_SESSION_TTL_SECONDS}`,
|
||||
'HttpOnly',
|
||||
'SameSite=Strict',
|
||||
]
|
||||
if (isHttps) parts.push('Secure')
|
||||
return parts.join('; ')
|
||||
}
|
||||
|
||||
function parseRequestCookies(req) {
|
||||
const header = req.headers.cookie || ''
|
||||
const out = {}
|
||||
for (const part of header.split(/;\s*/)) {
|
||||
if (!part) continue
|
||||
const eq = part.indexOf('=')
|
||||
if (eq < 1) continue
|
||||
out[part.slice(0, eq)] = part.slice(eq + 1)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function isTurnstileSessionVerified(req) {
|
||||
if (!TURNSTILE_SECRET_KEY) return true
|
||||
const cookies = parseRequestCookies(req)
|
||||
const cookie = cookies[TURNSTILE_SESSION_COOKIE]
|
||||
if (!cookie) return false
|
||||
const dot = cookie.indexOf('.')
|
||||
if (dot < 1) return false
|
||||
const expiresAtStr = cookie.slice(0, dot)
|
||||
const signature = cookie.slice(dot + 1)
|
||||
const expiresAt = Number(expiresAtStr)
|
||||
if (!Number.isFinite(expiresAt) || expiresAt < Math.floor(Date.now() / 1000)) return false
|
||||
const expected = signTurnstileSession(expiresAt)
|
||||
if (signature.length !== expected.length) return false
|
||||
try {
|
||||
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function callTurnstileSiteverify(token, remoteIp, idempotencyKey) {
|
||||
return new Promise((resolve) => {
|
||||
const params = new URLSearchParams()
|
||||
@@ -1446,16 +1509,13 @@ const server = http.createServer((req, res) => {
|
||||
return
|
||||
}
|
||||
|
||||
if (!isTurnstileSessionVerified(req)) {
|
||||
sendJson(res, 403, { error: 'Turnstile session required', detail: 'Solve the site challenge first' })
|
||||
return
|
||||
}
|
||||
|
||||
readJsonBody(req)
|
||||
.then(async (payload) => {
|
||||
const verification = await verifyTurnstileToken(payload.turnstile_token, clientIp(req), {
|
||||
expectedAction: 'analytics-delete',
|
||||
expectedHostname: expectedTurnstileHostname(),
|
||||
})
|
||||
if (!verification.success) {
|
||||
sendJson(res, 403, { error: 'Turnstile verification required', detail: verification.error })
|
||||
return
|
||||
}
|
||||
.then((payload) => {
|
||||
const result = deleteViewerData(payload)
|
||||
sendJson(res, 200, result)
|
||||
})
|
||||
@@ -1477,19 +1537,28 @@ const server = http.createServer((req, res) => {
|
||||
readJsonBody(req)
|
||||
.then(async (payload) => {
|
||||
const verification = await verifyTurnstileToken(payload.token, clientIp(req), {
|
||||
expectedAction: typeof payload.action === 'string' ? payload.action : undefined,
|
||||
expectedAction: 'site-gate',
|
||||
expectedHostname: expectedTurnstileHostname(),
|
||||
})
|
||||
if (!verification.success) {
|
||||
sendJson(res, 403, { error: 'Turnstile verification failed', detail: verification.error })
|
||||
return
|
||||
}
|
||||
sendJson(res, 200, { success: true })
|
||||
const headers = TURNSTILE_SECRET_KEY ? { 'set-cookie': buildTurnstileSessionCookie(req) } : {}
|
||||
send(res, 200, JSON.stringify({ success: true, ttl: TURNSTILE_SESSION_TTL_SECONDS }), {
|
||||
...jsonHeaders,
|
||||
...headers,
|
||||
})
|
||||
})
|
||||
.catch((error) => sendJson(res, 400, { error: error.message }))
|
||||
return
|
||||
}
|
||||
|
||||
if (req.method === 'GET' && req.url === '/api/turnstile/session') {
|
||||
sendJson(res, 200, { verified: isTurnstileSessionVerified(req) })
|
||||
return
|
||||
}
|
||||
|
||||
if (req.method === 'OPTIONS' && req.url.startsWith('/api/')) {
|
||||
sendJson(res, 403, { error: 'CORS requests are not allowed' })
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user