Files
gbrain/test/whoami.test.ts
T
26b938c37d fix(auth): expose OAuth source grants in whoami (takeover of #3279) (#3332)
Rebase of #3279 onto current master: the whoami oauth shape gains
source_id (AuthInfo.sourceId, null when absent) and federated_read
(AuthInfo.allowedSources, [] when absent) — read-only self-introspection
that widens no grant. Re-applied against the post-#3091 description
string (stdio transport shape preserved) and merged the grant tests
into the current whoami.test.ts alongside the stdio cases.

Co-authored-by: Garry Tan <garrytan@gmail.com>
Co-authored-by: boundless-forest <boundless-forest@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:28:22 -07:00

222 lines
7.5 KiB
TypeScript

/**
* whoami op contract tests — pins the v0.28 transport-detection shape.
*
* The test surface is the op's handler called against synthesized
* OperationContext rather than the full HTTP stack — keeps the test pure
* and fast. End-to-end coverage (real HTTP MCP) lives in
* test/e2e/serve-http-oauth.test.ts and test/e2e/sources-remote-mcp.test.ts.
*/
import { test, expect, describe } from 'bun:test';
import { operations, OperationError } from '../src/core/operations.ts';
import type { OperationContext, AuthInfo } from '../src/core/operations.ts';
const whoami = operations.find(o => o.name === 'whoami')!;
function ctxWith(overrides: Partial<OperationContext>): OperationContext {
// Shape exposes only what whoami reads. Every required field gets a
// safe stub; the test-relevant overrides come last to win.
return {
engine: {} as any,
config: {} as any,
logger: { info() {}, warn() {}, error() {} },
dryRun: false,
remote: true, // default for tests; specific cases override
...overrides,
} as OperationContext;
}
describe('whoami op contract', () => {
test('local transport (ctx.remote === false) returns empty scopes', async () => {
const result = (await whoami.handler(
ctxWith({ remote: false }),
{},
)) as any;
expect(result.transport).toBe('local');
expect(result.scopes).toEqual([]);
});
test('local transport ignores ctx.auth even if a stale value leaked through', async () => {
// Defense in depth: even if some buggy transport set both remote=false
// AND a stale auth blob, the local return shape stays explicit.
const result = (await whoami.handler(
ctxWith({
remote: false,
auth: {
token: 'x',
clientId: 'gbrain_cl_123',
scopes: ['admin'],
expiresAt: 999999,
} as AuthInfo,
}),
{},
)) as any;
expect(result.transport).toBe('local');
expect(result.scopes).toEqual([]);
});
test('oauth transport returns client identity and exact source grants', async () => {
const auth: AuthInfo = {
token: 'gbrain_at_xxx',
clientId: 'gbrain_cl_abc',
clientName: 'gstack-test',
scopes: ['read', 'sources_admin'],
expiresAt: 1234567890,
sourceId: 'hot-memory',
allowedSources: ['hot-memory', 'canonical-brain'],
};
const result = (await whoami.handler(
ctxWith({ remote: true, sourceId: 'transport-fallback', auth }),
{},
)) as any;
expect(result).toEqual({
transport: 'oauth',
client_id: 'gbrain_cl_abc',
client_name: 'gstack-test',
scopes: ['read', 'sources_admin'],
expires_at: 1234567890,
source_id: 'hot-memory',
federated_read: ['hot-memory', 'canonical-brain'],
});
});
test('oauth transport uses fail-closed empty values when source grants are absent', async () => {
const auth: AuthInfo = {
token: 'gbrain_at_pre_migration',
clientId: 'gbrain_cl_pre_migration',
scopes: ['read'],
};
const result = (await whoami.handler(
ctxWith({ remote: true, sourceId: 'transport-fallback', auth }),
{},
)) as any;
expect(result.source_id).toBeNull();
expect(result.federated_read).toEqual([]);
});
test('oauth transport preserves an explicit empty federated grant', async () => {
const auth: AuthInfo = {
token: 'gbrain_at_empty',
clientId: 'gbrain_cl_empty',
scopes: ['read', 'write'],
sourceId: 'hot-memory',
allowedSources: [],
};
const result = (await whoami.handler(
ctxWith({ remote: true, auth }),
{},
)) as any;
expect(result.source_id).toBe('hot-memory');
expect(result.federated_read).toEqual([]);
});
test('oauth transport does not widen federated_read with the write source', async () => {
const auth: AuthInfo = {
token: 'gbrain_at_narrow',
clientId: 'gbrain_cl_narrow',
scopes: ['read', 'write'],
sourceId: 'hot-memory',
allowedSources: ['canonical-brain'],
};
const result = (await whoami.handler(
ctxWith({ remote: true, auth }),
{},
)) as any;
expect(result.source_id).toBe('hot-memory');
expect(result.federated_read).toEqual(['canonical-brain']);
});
test('legacy transport (token name as clientId, no gbrain_cl_ prefix)', async () => {
const auth: AuthInfo = {
token: 'legacy-token',
clientId: 'my-personal-token',
clientName: 'my-personal-token',
scopes: ['read', 'write', 'admin'],
// Legacy tokens have a synthetic 1y expiry — whoami exposes null
// since legacy tokens don't actually expire.
expiresAt: 999999999,
};
const result = (await whoami.handler(
ctxWith({ remote: true, auth }),
{},
)) as any;
expect(result.transport).toBe('legacy');
expect(result.token_name).toBe('my-personal-token');
expect(result.scopes).toEqual(['read', 'write', 'admin']);
expect(result.expires_at).toBeNull();
});
// #1061: stdio MCP is remote/untrusted by design but has no per-token auth
// (local pipe). The stdio dispatch marks ctx.transport='stdio'; whoami
// reports it instead of throwing unknown_transport.
test('stdio transport (remote=true, no auth, transport marker) reports stdio', async () => {
const result = (await whoami.handler(
ctxWith({ remote: true, auth: undefined, transport: 'stdio' }),
{},
)) as any;
expect(result.transport).toBe('stdio');
expect(result.scopes).toEqual([]);
});
test('stdio marker does not mask real auth (auth still wins)', async () => {
const result = (await whoami.handler(
ctxWith({
remote: true,
transport: 'stdio',
auth: {
token: 'gbrain_at_xxx',
clientId: 'gbrain_cl_abc',
scopes: ['read'],
expiresAt: 1,
} as AuthInfo,
}),
{},
)) as any;
expect(result.transport).toBe('oauth');
});
// Q3: ambiguous transport — fail-closed. The footgun this guards against
// is a future transport that lands without threading auth, where a buggy
// caller might trust whoami's output to gate sensitive ops.
test('unknown_transport throws when remote=true AND auth is missing', async () => {
try {
await whoami.handler(ctxWith({ remote: true, auth: undefined }), {});
throw new Error('expected throw');
} catch (e) {
expect(e).toBeInstanceOf(OperationError);
expect((e as OperationError).message).toMatch(/unknown_transport|did not thread/);
}
});
test('unknown_transport throws when remote is undefined (cast bypass guard)', async () => {
// F7b contract: ctx.remote is REQUIRED. If a caller widens the type to
// Partial<> and passes through undefined, whoami should treat it as
// remote (the fail-closed default) and throw because auth is missing.
try {
await whoami.handler(ctxWith({ remote: undefined as any, auth: undefined }), {});
throw new Error('expected throw');
} catch (e) {
expect(e).toBeInstanceOf(OperationError);
}
});
});
describe('whoami op metadata', () => {
test('description documents OAuth source grant fields', () => {
expect(whoami.description).toContain('source_id');
expect(whoami.description).toContain('federated_read');
});
test('scope is read (any authenticated caller can introspect itself)', () => {
expect(whoami.scope).toBe('read');
});
test('not localOnly (must work over HTTP MCP for gstack /setup-gbrain)', () => {
expect(whoami.localOnly).toBeFalsy();
});
test('mutating is false', () => {
expect(whoami.mutating).toBeFalsy();
});
});