Skill Tracer
Skills › gstack › ios-qa › auth-mint.ts

auth-mint.ts

89 lines · ios-qa/daemon/src/auth-mint.ts at 3f79d81

1// /auth/mint endpoint handler. Two trust models, kept distinct:
2//
3// 1. Self-service mint: caller's tailnet identity (from WhoIs) must already
4// be in the allowlist. NEVER auto-allowlists.
5// 2. Owner-granted mint: not on /auth/mint at all — that's the CLI
6// `gstack-ios-qa-mint --remote <identity>` writing to the allowlist file.
7
8import { SessionTokenStore } from './session-tokens';
9import { hasCapability, loadAllowlist } from './allowlist';
10import { writeAttempt } from './audit';
11import type { Capability } from './types';
12import { capabilityCovers } from './types';
14export interface MintRequest {
15 capability?: Capability; // requested tier; default 'interact'
16 device_udid?: string;
19export interface MintResponse {
20 session_token: string;
21 expires_at: number;
22 capability: Capability;
25export interface MintError {
26 error: 'identity_not_allowed' | 'capability_insufficient' | 'rate_limited';
29export async function mintForCaller(opts: {
30 callerIdentity: string;
31 request: MintRequest;
32 tokenStore: SessionTokenStore;
33 allowlistPath?: string;
34 attemptsPath?: string;
35 endpoint?: string;
36}): Promise<MintResponse | MintError> {
37 const allowlist = await loadAllowlist(opts.allowlistPath);
38 const wantedCap: Capability = opts.request.capability ?? 'interact';
40 // Must be in the allowlist.
41 if (!hasCapability(allowlist, opts.callerIdentity, 'observe')) {
42 await writeAttempt({
43 rawIdentity: opts.callerIdentity,
44 endpoint: opts.endpoint ?? '/auth/mint',
45 reason: 'identity_not_allowed',
46 path: opts.attemptsPath,
47 });
48 return { error: 'identity_not_allowed' };
49 }
51 // Must have at least the requested capability.
52 if (!hasCapability(allowlist, opts.callerIdentity, wantedCap)) {
53 await writeAttempt({
54 rawIdentity: opts.callerIdentity,
55 endpoint: opts.endpoint ?? '/auth/mint',
56 reason: 'capability_insufficient',
57 path: opts.attemptsPath,
58 });
59 return { error: 'capability_insufficient' };
60 }
62 // Find the entry to determine the highest tier they can hold.
63 const entry = allowlist.entries.find(e => e.identity === opts.callerIdentity);
64 // Mint at the requested tier, capped at the highest granted tier.
65 const grantedTier = entry?.capabilities.find(c => capabilityCovers(c, wantedCap)) ?? wantedCap;
67 const result = opts.tokenStore.mint({
68 identity: opts.callerIdentity,
69 capability: grantedTier,
70 deviceUdid: opts.request.device_udid ?? null,
71 origin: 'self_service',
72 });
74 if ('error' in result) {
75 await writeAttempt({
76 rawIdentity: opts.callerIdentity,
77 endpoint: opts.endpoint ?? '/auth/mint',
78 reason: 'rate_limited',
79 path: opts.attemptsPath,
80 });
81 return { error: 'rate_limited' };
82 }
84 return {
85 session_token: result.token,
86 expires_at: result.expires_at,
87 capability: result.capability,
88 };