From 134bab93a5c8c594c9cb580eb4787378295bf772 Mon Sep 17 00:00:00 2001 From: Gabriel Brown Date: Mon, 13 Jul 2026 10:08:48 -0400 Subject: [PATCH] feat(next): box password API --- .../src/app/api/box/set-password/route.ts | 45 +++++++++++++++++++ apps/next/src/lib/agent-worker-proxy.ts | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 apps/next/src/app/api/box/set-password/route.ts diff --git a/apps/next/src/app/api/box/set-password/route.ts b/apps/next/src/app/api/box/set-password/route.ts new file mode 100644 index 0000000..6d31a64 --- /dev/null +++ b/apps/next/src/app/api/box/set-password/route.ts @@ -0,0 +1,45 @@ +import { NextResponse } from 'next/server'; +import { proxyBox, withBox } from '@/lib/agent-worker-proxy'; +import { convexAuthNextjsToken } from '@convex-dev/auth/nextjs/server'; +import { fetchAction } from 'convex/nextjs'; + +import { api } from '@spoon/backend/convex/_generated/api.js'; + +// Set (or clear, with null/'') the box user's password. Convex is the source +// of truth and is written FIRST (box creation re-applies from it); the worker +// call then applies it to the live container. A worker failure downgrades to +// applied:false — the password still takes effect on the next box restart. +export const POST = async (request: Request) => + await withBox(async (username) => { + const body = (await request.json().catch(() => ({}))) as { + password?: string | null; + }; + const password = + typeof body.password === 'string' && body.password.length > 0 + ? body.password + : null; + + const token = await convexAuthNextjsToken(); + const stored = await fetchAction( + api.boxSettingsNode.setBoxPassword, + { password }, + { token }, + ); + + let applied = false; + try { + const workerResponse = await proxyBox(username, 'set-password', { + method: 'POST', + body: JSON.stringify({ password }), + }); + if (workerResponse.ok) { + applied = ((await workerResponse.json()) as { applied?: boolean }) + .applied + ? true + : false; + } + } catch { + // Stored but not live-applied; init picks it up at the next creation. + } + return NextResponse.json({ hasPassword: stored.hasPassword, applied }); + }); diff --git a/apps/next/src/lib/agent-worker-proxy.ts b/apps/next/src/lib/agent-worker-proxy.ts index 0d0b217..d1ec08b 100644 --- a/apps/next/src/lib/agent-worker-proxy.ts +++ b/apps/next/src/lib/agent-worker-proxy.ts @@ -181,7 +181,7 @@ export const resolveBoxUsername = async (): Promise< // ever operates on the caller's own box. export const proxyBox = async ( username: string, - action: 'status' | 'lifecycle' | 'tree' | 'file', + action: 'status' | 'lifecycle' | 'tree' | 'file' | 'set-password', init?: RequestInit, search?: URLSearchParams, ) => {