Loopback Fastify auth-broker + esbuild SPA. Operator password login (constant-time, signed HttpOnly session cookie, per-forwarded-IP rate-limit) → session-gated proxy that mints a fresh 60s manage capability token per call to the control-plane admin API: list hosts, mint pairing codes (with QR + pair command), revoke hosts. Security headers + CSP, CP_URL pinned loopback (anti-SSRF), hostId dot-segment guard. 55 tests pass; security-reviewed. Deployed behind nginx panel.terminal.yaojia.wang.
27 lines
1.3 KiB
TypeScript
27 lines
1.3 KiB
TypeScript
/**
|
|
* Request-scoped security helpers shared by the route plugins: detect HTTPS (drives the Secure
|
|
* cookie flag) and read/verify the session cookie off an incoming Fastify request.
|
|
*/
|
|
import type { FastifyRequest } from 'fastify'
|
|
import { parseCookieHeader, SESSION_COOKIE_NAME } from './cookies.js'
|
|
import { verifySessionToken } from './session.js'
|
|
|
|
/**
|
|
* True iff the request arrived over HTTPS — directly (`socket.encrypted`) or via a TLS-terminating
|
|
* edge that set `x-forwarded-proto: https`. Even though the panel binds loopback, a reverse proxy
|
|
* may front it; honour XFP so the Secure flag is correct on the tunnel path.
|
|
*/
|
|
export function isSecureRequest(req: FastifyRequest): boolean {
|
|
const xfp = req.headers['x-forwarded-proto']
|
|
const proto = Array.isArray(xfp) ? xfp[0] : xfp
|
|
if (typeof proto === 'string' && proto.split(',')[0]?.trim().toLowerCase() === 'https') return true
|
|
const socket = req.raw.socket as { encrypted?: boolean } | undefined
|
|
return socket?.encrypted === true
|
|
}
|
|
|
|
/** True iff the request carries a valid, unexpired session cookie signed with `sessionSecret`. */
|
|
export function requestIsAuthed(req: FastifyRequest, sessionSecret: string, nowMs: number): boolean {
|
|
const cookies = parseCookieHeader(req.headers.cookie)
|
|
return verifySessionToken(sessionSecret, cookies[SESSION_COOKIE_NAME], nowMs)
|
|
}
|