mirror of https://github.com/garrytan/gstack.git
104 lines
3.5 KiB
TypeScript
104 lines
3.5 KiB
TypeScript
// /auth/mint endpoint tests. Codex-flagged: identity allowlist, capability
|
|
// cap, rate-limit cap, self-service vs owner-granted distinction.
|
|
|
|
import { describe, test, expect, beforeEach } from 'bun:test';
|
|
import { mkdtempSync, rmSync } from 'fs';
|
|
import { tmpdir } from 'os';
|
|
import { join } from 'path';
|
|
import { mintForCaller } from '../src/auth-mint';
|
|
import { SessionTokenStore } from '../src/session-tokens';
|
|
import { grantIdentity } from '../src/allowlist';
|
|
|
|
let tmpDir: string;
|
|
let listPath: string;
|
|
|
|
beforeEach(() => {
|
|
tmpDir = mkdtempSync(join(tmpdir(), 'ios-qa-mint-'));
|
|
listPath = join(tmpDir, 'allowlist.json');
|
|
});
|
|
|
|
describe('mintForCaller', () => {
|
|
test('rejects unknown identity', async () => {
|
|
const store = new SessionTokenStore();
|
|
const r = await mintForCaller({
|
|
callerIdentity: 'stranger@example.com',
|
|
request: { capability: 'observe' },
|
|
tokenStore: store,
|
|
allowlistPath: listPath,
|
|
});
|
|
expect(r).toEqual({ error: 'identity_not_allowed' });
|
|
});
|
|
|
|
test('mints at the requested tier when allowlisted at that tier', async () => {
|
|
await grantIdentity({ identity: 'u@e.com', capability: 'mutate', path: listPath });
|
|
const store = new SessionTokenStore();
|
|
const r = await mintForCaller({
|
|
callerIdentity: 'u@e.com',
|
|
request: { capability: 'interact' },
|
|
tokenStore: store,
|
|
allowlistPath: listPath,
|
|
});
|
|
expect('error' in r).toBe(false);
|
|
if ('error' in r) throw new Error('unexpected');
|
|
expect(r.capability).toBe('mutate'); // returns the granted tier (higher covers interact)
|
|
expect(r.session_token.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('refuses to mint above the allowlisted tier', async () => {
|
|
await grantIdentity({ identity: 'observe-only@e.com', capability: 'observe', path: listPath });
|
|
const store = new SessionTokenStore();
|
|
const r = await mintForCaller({
|
|
callerIdentity: 'observe-only@e.com',
|
|
request: { capability: 'mutate' },
|
|
tokenStore: store,
|
|
allowlistPath: listPath,
|
|
});
|
|
expect(r).toEqual({ error: 'capability_insufficient' });
|
|
});
|
|
|
|
test('rate limits hit at 11th mint per identity', async () => {
|
|
await grantIdentity({ identity: 'spammer@e.com', capability: 'observe', path: listPath });
|
|
const store = new SessionTokenStore();
|
|
let lastError: unknown = null;
|
|
let success = 0;
|
|
for (let i = 0; i < 11; i++) {
|
|
const r = await mintForCaller({
|
|
callerIdentity: 'spammer@e.com',
|
|
request: { capability: 'observe' },
|
|
tokenStore: store,
|
|
allowlistPath: listPath,
|
|
});
|
|
if ('error' in r) lastError = r;
|
|
else success++;
|
|
}
|
|
expect(success).toBe(10);
|
|
expect(lastError).toEqual({ error: 'rate_limited' });
|
|
});
|
|
|
|
test('expired allowlist entries reject the mint', async () => {
|
|
// Write an expired entry directly.
|
|
const { saveAllowlist } = await import('../src/allowlist');
|
|
await saveAllowlist({
|
|
version: 1,
|
|
entries: [{
|
|
identity: 'expired@e.com',
|
|
capabilities: ['restore'],
|
|
expires_at: new Date(Date.now() - 60_000).toISOString(),
|
|
}],
|
|
}, listPath);
|
|
const store = new SessionTokenStore();
|
|
const r = await mintForCaller({
|
|
callerIdentity: 'expired@e.com',
|
|
request: { capability: 'observe' },
|
|
tokenStore: store,
|
|
allowlistPath: listPath,
|
|
});
|
|
expect(r).toEqual({ error: 'identity_not_allowed' });
|
|
});
|
|
});
|
|
|
|
import { afterEach } from 'bun:test';
|
|
afterEach(() => {
|
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
});
|