Files
SkyMoney/api/src/routes/site-access.ts
Ricearoni1245 952684fc25
All checks were successful
Deploy / deploy (push) Successful in 1m32s
Security Tests / security-non-db (push) Successful in 21s
Security Tests / security-db (push) Successful in 27s
phase 8: site-access and admin simplified and compacted
2026-03-18 06:43:19 -05:00

91 lines
2.8 KiB
TypeScript

import type { FastifyPluginAsync } from "fastify";
import { z } from "zod";
type RateLimitRouteOptions = {
config: {
rateLimit: {
max: number;
timeWindow: number;
keyGenerator?: (req: any) => string;
};
};
};
type SiteAccessRoutesOptions = {
underConstructionEnabled: boolean;
breakGlassVerifyEnabled: boolean;
breakGlassVerifyCode: string | null;
siteAccessExpectedToken: string | null;
cookieDomain?: string;
secureCookie: boolean;
siteAccessCookieName: string;
siteAccessMaxAgeSeconds: number;
authRateLimit: RateLimitRouteOptions;
mutationRateLimit: RateLimitRouteOptions;
hasSiteAccessBypass: (req: { cookies?: Record<string, unknown> }) => boolean;
safeEqual: (a: string, b: string) => boolean;
};
const siteAccessRoutes: FastifyPluginAsync<SiteAccessRoutesOptions> = async (app, opts) => {
app.get("/site-access/status", async (req) => {
if (!opts.underConstructionEnabled) {
return { ok: true, enabled: false, unlocked: true };
}
return {
ok: true,
enabled: true,
unlocked: opts.hasSiteAccessBypass(req),
};
});
app.post("/site-access/unlock", opts.authRateLimit, async (req, reply) => {
const Body = z.object({
code: z.string().min(1).max(512),
});
const parsed = Body.safeParse(req.body);
if (!parsed.success) {
return reply.code(400).send({ ok: false, code: "INVALID_PAYLOAD", message: "Invalid payload" });
}
if (!opts.underConstructionEnabled) {
return { ok: true, enabled: false, unlocked: true };
}
if (!opts.breakGlassVerifyEnabled || !opts.siteAccessExpectedToken) {
return reply.code(503).send({
ok: false,
code: "UNDER_CONSTRUCTION_MISCONFIGURED",
message: "Under-construction access is not configured.",
});
}
if (!opts.breakGlassVerifyCode || !opts.safeEqual(parsed.data.code, opts.breakGlassVerifyCode)) {
return reply.code(401).send({
ok: false,
code: "INVALID_ACCESS_CODE",
message: "Invalid access code.",
});
}
reply.setCookie(opts.siteAccessCookieName, opts.siteAccessExpectedToken, {
httpOnly: true,
sameSite: "lax",
secure: opts.secureCookie,
path: "/",
maxAge: opts.siteAccessMaxAgeSeconds,
...(opts.cookieDomain ? { domain: opts.cookieDomain } : {}),
});
return { ok: true, enabled: true, unlocked: true };
});
app.post("/site-access/lock", opts.mutationRateLimit, async (_req, reply) => {
reply.clearCookie(opts.siteAccessCookieName, {
path: "/",
httpOnly: true,
sameSite: "lax",
secure: opts.secureCookie,
...(opts.cookieDomain ? { domain: opts.cookieDomain } : {}),
});
return { ok: true, enabled: opts.underConstructionEnabled, unlocked: false };
});
};
export default siteAccessRoutes;