// // AuthMiddleware.swift // Zyquo Router // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // Local API-key gate (Bearer `zyquo-sk-…`). With no keys configured the // router is open (localhost-only posture); LAN exposure forces at least one // key at start time. Per-key rate limits and model allow-lists apply in the // chat route. // import Foundation struct AuthMiddleware: Sendable { /// Keys in force; empty ⇒ no auth required (localhost default). var keys: [APIKeyRecord] = [] enum Decision: Sendable { case allowed(APIKeyRecord?) case unauthorized(message: String) } func authorize(_ request: RouteRequest) -> Decision { guard !keys.isEmpty else { return .allowed(nil) } guard let auth = request.headers.first(name: "Authorization"), auth.lowercased().hasPrefix("bearer ") else { return .unauthorized(message: "Missing bearer token. Pass a local router key: Authorization: Bearer zyquo-sk-…") } let token = String(auth.dropFirst("bearer ".count)).trimmingCharacters(in: .whitespaces) let hash = APIKeyRecord.hash(token) guard let match = keys.first(where: { $0.tokenHash == hash }) else { return .unauthorized(message: "Invalid API key.") } guard match.enabled else { return .unauthorized(message: "This API key has been revoked.") } return .allowed(match) } }