mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-31 04:07:52 +00:00
Takeover of #1558 (rebased onto master, admin/dist + admin-embedded regenerated so asset hashes match; source changes salvaged intact): - /admin/api/register-client now accepts source_id + federated_read (snake and camel case), validated fail-closed against the registered source list via the new normalizeAdminClientSourceScope (src/core/admin-source-scope.ts) before reaching registerClientManual — unknown or malformed ids are a 400, never a silently-hardcoded 'default'. Fixes #1490. - New requireAdmin-gated GET /admin/api/sources feeds the Register Agent form; the form now has a Write Source select + Read Sources checkboxes, and the agents table/drawer show each client's scope. - #1036: the Register Agent form now has a Redirect URIs textarea (one per line). When present, the POST carries redirectUris plus the authorization_code + refresh_token grants, mirroring the CLI's --redirect-uri grant-inference convention (src/commands/auth.ts). Backend plumbing already existed; this was UI-only. Tests: test/admin-source-scope.test.ts (normalization matrix), test/oauth.test.ts (registerClientManual persists source scope into AuthInfo), test/fix-wave-structural.test.ts (endpoint + form pins for #1490 and #1036). Co-authored-by: flamerged <flamerged@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
45 lines
1.8 KiB
TypeScript
45 lines
1.8 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|
import { normalizeAdminClientSourceScope } from '../src/core/admin-source-scope.ts';
|
|
|
|
describe('admin register-client source scope normalization', () => {
|
|
const sources = ['default', 'team-a', 'team-b'];
|
|
|
|
test('defaults write and read scope to default source', () => {
|
|
expect(normalizeAdminClientSourceScope(undefined, undefined, sources)).toEqual({
|
|
sourceId: 'default',
|
|
federatedRead: ['default'],
|
|
});
|
|
});
|
|
|
|
test('accepts snake-case body fields with multiple read sources', () => {
|
|
expect(normalizeAdminClientSourceScope('team-a', ['team-a', 'team-b'], sources)).toEqual({
|
|
sourceId: 'team-a',
|
|
federatedRead: ['team-a', 'team-b'],
|
|
});
|
|
});
|
|
|
|
test('accepts comma-separated federated_read for API callers', () => {
|
|
expect(normalizeAdminClientSourceScope('team-a', 'team-b, default,team-b', sources)).toEqual({
|
|
sourceId: 'team-a',
|
|
federatedRead: ['team-b', 'default'],
|
|
});
|
|
});
|
|
|
|
test('falls back to selected write source when federated_read is empty', () => {
|
|
expect(normalizeAdminClientSourceScope('team-b', [], sources)).toEqual({
|
|
sourceId: 'team-b',
|
|
federatedRead: ['team-b'],
|
|
});
|
|
});
|
|
|
|
test('rejects malformed source ids before registration', () => {
|
|
expect(() => normalizeAdminClientSourceScope('../secret', undefined, sources)).toThrow(/Invalid source_id/);
|
|
expect(() => normalizeAdminClientSourceScope('team-a', ['snake_case'], sources)).toThrow(/Invalid federated_read/);
|
|
});
|
|
|
|
test('rejects valid-looking but unregistered source ids', () => {
|
|
expect(() => normalizeAdminClientSourceScope('ghost', undefined, sources)).toThrow(/Unknown source_id/);
|
|
expect(() => normalizeAdminClientSourceScope('team-a', ['ghost'], sources)).toThrow(/Unknown federated_read/);
|
|
});
|
|
});
|