import { jwtVerify } from 'jose' import { isOnsite } from './ip-check.js' const APP_SLUG = process.env.APP_SLUG || 'utilities' const secret = new TextEncoder().encode(process.env.CENTRAL_AUTH_SECRET || '') export async function requireAuth(request, reply) { const token = request.cookies?.hnf_session if (!token) return reply.status(401).send({ error: 'Not authenticated' }) let payload try { const { payload: p } = await jwtVerify(token, secret) payload = p } catch { return reply.status(401).send({ error: 'Invalid session' }) } if (!payload.apps?.includes(APP_SLUG)) { return reply.status(403).send({ error: 'No permission for this app' }) } if (!payload.offsite_allowed) { const clientIP = request.headers['x-real-ip'] || request.ip if (!(await isOnsite(clientIP))) { return reply.status(403).send({ error: 'Access restricted to site network' }) } } const prefix = `${APP_SLUG}:` let caps if (Array.isArray(payload.caps)) { caps = payload.caps.filter(c => c.startsWith(prefix)).map(c => c.slice(prefix.length)) } else { // Legacy token — grant all non-settings caps until re-login caps = ['readings', 'meters', 'tariffs', 'reports', 'estimates'] } request.user = { email: payload.sub, name: payload.name, is_admin: payload.is_admin ?? false, caps, } } export function hasCap(request, cap) { return request.user?.is_admin === true || request.user?.caps?.includes(cap) === true } export function requireCap(cap) { return async (request, reply) => { if (!hasCap(request, cap)) { return reply.status(403).send({ error: `Missing capability: ${cap}` }) } } }