| import type { PaperclipConfig } from "../config/schema.js"; |
| import type { CheckResult } from "./index.js"; |
|
|
| function isLoopbackHost(host: string) { |
| const normalized = host.trim().toLowerCase(); |
| return normalized === "127.0.0.1" || normalized === "localhost" || normalized === "::1"; |
| } |
|
|
| export function deploymentAuthCheck(config: PaperclipConfig): CheckResult { |
| const mode = config.server.deploymentMode; |
| const exposure = config.server.exposure; |
| const auth = config.auth; |
|
|
| if (mode === "local_trusted") { |
| if (!isLoopbackHost(config.server.host)) { |
| return { |
| name: "Deployment/auth mode", |
| status: "fail", |
| message: `local_trusted requires loopback host binding (found ${config.server.host})`, |
| canRepair: false, |
| repairHint: "Run `paperclipai configure --section server` and set host to 127.0.0.1", |
| }; |
| } |
| return { |
| name: "Deployment/auth mode", |
| status: "pass", |
| message: "local_trusted mode is configured for loopback-only access", |
| }; |
| } |
|
|
| const secret = |
| process.env.BETTER_AUTH_SECRET?.trim() ?? |
| process.env.PAPERCLIP_AGENT_JWT_SECRET?.trim(); |
| if (!secret) { |
| return { |
| name: "Deployment/auth mode", |
| status: "fail", |
| message: "authenticated mode requires BETTER_AUTH_SECRET (or PAPERCLIP_AGENT_JWT_SECRET)", |
| canRepair: false, |
| repairHint: "Set BETTER_AUTH_SECRET before starting Paperclip", |
| }; |
| } |
|
|
| if (auth.baseUrlMode === "explicit" && !auth.publicBaseUrl) { |
| return { |
| name: "Deployment/auth mode", |
| status: "fail", |
| message: "auth.baseUrlMode=explicit requires auth.publicBaseUrl", |
| canRepair: false, |
| repairHint: "Run `paperclipai configure --section server` and provide a base URL", |
| }; |
| } |
|
|
| if (exposure === "public") { |
| if (auth.baseUrlMode !== "explicit" || !auth.publicBaseUrl) { |
| return { |
| name: "Deployment/auth mode", |
| status: "fail", |
| message: "authenticated/public requires explicit auth.publicBaseUrl", |
| canRepair: false, |
| repairHint: "Run `paperclipai configure --section server` and select public exposure", |
| }; |
| } |
| try { |
| const url = new URL(auth.publicBaseUrl); |
| if (url.protocol !== "https:") { |
| return { |
| name: "Deployment/auth mode", |
| status: "warn", |
| message: "Public exposure should use an https:// auth.publicBaseUrl", |
| canRepair: false, |
| repairHint: "Use HTTPS in production for secure session cookies", |
| }; |
| } |
| } catch { |
| return { |
| name: "Deployment/auth mode", |
| status: "fail", |
| message: "auth.publicBaseUrl is not a valid URL", |
| canRepair: false, |
| repairHint: "Run `paperclipai configure --section server` and provide a valid URL", |
| }; |
| } |
| } |
|
|
| return { |
| name: "Deployment/auth mode", |
| status: "pass", |
| message: `Mode ${mode}/${exposure} with auth URL mode ${auth.baseUrlMode}`, |
| }; |
| } |
|
|