From 78aae034b5d8a157bcbbda272e4216dad20b897e Mon Sep 17 00:00:00 2001 From: Gabriel Brown Date: Mon, 13 Jul 2026 10:07:06 -0400 Subject: [PATCH] feat(worker): live box password apply route --- apps/agent-worker/src/server.ts | 24 ++++++++++++++++++- .../agent-worker/tests/unit/box-route.test.ts | 3 ++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/apps/agent-worker/src/server.ts b/apps/agent-worker/src/server.ts index a65ea78..38e4009 100644 --- a/apps/agent-worker/src/server.ts +++ b/apps/agent-worker/src/server.ts @@ -8,6 +8,7 @@ import { listBoxTree, readBoxFile, restartBox, + setBoxUserPassword, startBox, stopBox, writeBoxFile, @@ -66,7 +67,9 @@ const jobRoute = (pathname: string) => { }; export const boxRoute = (pathname: string) => { - const match = /^\/box\/(status|lifecycle|tree|file)$/.exec(pathname); + const match = /^\/box\/(status|lifecycle|tree|file|set-password)$/.exec( + pathname, + ); return match?.[1] ? { action: match[1] } : null; }; @@ -136,6 +139,25 @@ export const startWorkerServer = () => { ); return; } + if (request.method === 'POST' && box.action === 'set-password') { + const body = await parseJson<{ password?: string | null }>(request); + // Convex owns validation/storage; this route only applies to the + // live container. Re-check bounds anyway so a bad caller can't + // push a malformed chpasswd line. + const password = body.password ?? null; + if ( + password !== null && + (password.length < 8 || + password.length > 128 || + // eslint-disable-next-line no-control-regex -- rejecting control chars is the point + /[\u0000-\u001f\u007f]/.test(password)) + ) { + sendJson(response, 400, { error: 'Invalid password' }); + return; + } + sendJson(response, 200, await setBoxUserPassword(user, password)); + return; + } sendJson(response, 404, { error: 'Not found' }); return; } diff --git a/apps/agent-worker/tests/unit/box-route.test.ts b/apps/agent-worker/tests/unit/box-route.test.ts index 09c1a9e..2d10695 100644 --- a/apps/agent-worker/tests/unit/box-route.test.ts +++ b/apps/agent-worker/tests/unit/box-route.test.ts @@ -13,12 +13,13 @@ const load = async () => { describe('boxRoute', () => { afterEach(() => vi.resetModules()); - test('matches the four box actions', async () => { + test('matches the five box actions', async () => { const { boxRoute } = await load(); expect(boxRoute('/box/status')).toEqual({ action: 'status' }); expect(boxRoute('/box/lifecycle')).toEqual({ action: 'lifecycle' }); expect(boxRoute('/box/tree')).toEqual({ action: 'tree' }); expect(boxRoute('/box/file')).toEqual({ action: 'file' }); + expect(boxRoute('/box/set-password')).toEqual({ action: 'set-password' }); }); test('rejects unknown, nested, and trailing paths', async () => {