fix(api-tokens): harden with security fixes, design compliance, and test coverage (#567)

Security: add JWT-level expiry ceiling (400d), per-user token count limit (25),
and token name uniqueness enforcement. Fix async clipboard copy.

Design: migrate Select to Combobox, apply card bevel styling, fix icon
strokeWidth, add tabular-nums to timestamps, fix destructive button pattern.

Tests: expand from ~20 to 47 test cases covering creation validation, token
limits, name uniqueness, last_used_at tracking, ownership constraints, delete
edge cases, and registry blocked endpoints.

Docs: update API Tokens docs with token limits, name uniqueness, registry
restrictions, and JWT expiry ceiling. Update OpenAPI spec with 409 response.
This commit is contained in:
Anso
2026-04-13 18:32:07 -04:00
committed by GitHub
parent 02c2d24004
commit e0d1ca9dc0
8 changed files with 328 additions and 45 deletions
+13
View File
@@ -1407,6 +1407,19 @@ export class DatabaseService {
this.db.prepare('UPDATE api_tokens SET last_used_at = ? WHERE id = ?').run(Date.now(), id);
}
public getActiveApiTokenCountByUser(userId: number): number {
const row = this.db.prepare(
'SELECT COUNT(*) AS cnt FROM api_tokens WHERE user_id = ? AND revoked_at IS NULL'
).get(userId) as { cnt: number };
return row.cnt;
}
public getActiveApiTokenByNameAndUser(name: string, userId: number): ApiToken | undefined {
return this.db.prepare(
'SELECT * FROM api_tokens WHERE name = ? AND user_id = ? AND revoked_at IS NULL LIMIT 1'
).get(name, userId) as ApiToken | undefined;
}
// --- Registries ---
public getRegistries(): Registry[] {