From fa08e3b2f2d391ea449018e9bf3d8cfd3dbfcc39 Mon Sep 17 00:00:00 2001 From: arlo Date: Wed, 15 Jul 2026 23:35:25 +0800 Subject: [PATCH] fix(auth): preserve host-established trust during handshake --- .../__tests__/interactive-auth.test.ts | 33 +++++++++++++++++-- .../devframe/src/recipes/interactive-auth.ts | 2 ++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/packages/devframe/src/recipes/__tests__/interactive-auth.test.ts b/packages/devframe/src/recipes/__tests__/interactive-auth.test.ts index a44f600..29ffe0a 100644 --- a/packages/devframe/src/recipes/__tests__/interactive-auth.test.ts +++ b/packages/devframe/src/recipes/__tests__/interactive-auth.test.ts @@ -25,7 +25,10 @@ async function createTestContext(): Promise { } /** Starts a fully-authenticated server with one trusted-only probe method. */ -async function startAuthenticatedServer(banners: { code: string, url: string }[] = []) { +async function startAuthenticatedServer( + banners: { code: string, url: string }[] = [], + preTrust = false, +) { const context = await createTestContext() context.rpc.register({ name: 'test:trusted-only', @@ -38,7 +41,17 @@ async function startAuthenticatedServer(banners: { code: string, url: string }[] const host = '127.0.0.1' const port = await getPort({ port: 0, host }) - const server = await startHttpAndWs({ context, host, port, auth }) + const server = await startHttpAndWs({ + context, + host, + port, + auth, + onPeerConnect: preTrust + ? (_peer, session) => { + session.meta.isTrusted = true + } + : undefined, + }) return { context, auth, server, host, port } } @@ -121,6 +134,22 @@ describe('recipes/interactive-auth', () => { } }) + it('preserves trust established by the host before the client handshake', async () => { + const { server, host, port } = await startAuthenticatedServer([], true) + + try { + const client = connectClient(host, port) + const handshake = await client.$call('anonymous:devframe:auth', { authToken: '', ua: 'test', origin: 'http://localhost' }) + + expect(handshake).toEqual({ isTrusted: true }) + await expect(client.$call('test:trusted-only' as any)).resolves.toBe('ok') + client.$close() + } + finally { + await server.close() + } + }) + it('self-revoke: devframe:auth:revoke drops the caller to untrusted and invalidates the token', async () => { const { server, host, port } = await startAuthenticatedServer() diff --git a/packages/devframe/src/recipes/interactive-auth.ts b/packages/devframe/src/recipes/interactive-auth.ts index 0fe12b8..74d2f88 100644 --- a/packages/devframe/src/recipes/interactive-auth.ts +++ b/packages/devframe/src/recipes/interactive-auth.ts @@ -96,6 +96,8 @@ export function createInteractiveAuth( const session = context.rpc.getCurrentRpcSession() if (!session) return { isTrusted: false } + if (session.meta.isTrusted) + return { isTrusted: true } if (isStaticToken(params.authToken)) { session.meta.clientAuthToken = params.authToken session.meta.isTrusted = true