chore(api): remove unused admin/readonly API key roles (#16)
This commit is contained in:
parent
50fef48ebe
commit
21e159ffd5
@ -1359,13 +1359,14 @@ function generateApiKey(): string {
|
||||
return 'ogk_' + crypto.randomUUID().replace(/-/g, '')
|
||||
}
|
||||
|
||||
// Only 'ingest' role is currently implemented. May expand in the future.
|
||||
export async function createApiKey(
|
||||
db: D1Database,
|
||||
name: string,
|
||||
role: 'admin' | 'ingest' | 'readonly' = 'ingest',
|
||||
allowedEvents: string[] = [],
|
||||
rateLimit: number = 100,
|
||||
): Promise<CreateApiKeyResponse> {
|
||||
const role = 'ingest'
|
||||
const key = generateApiKey()
|
||||
const keyHash = await sha256(key)
|
||||
const createdAt = Date.now()
|
||||
@ -1416,7 +1417,7 @@ export async function listApiKeys(
|
||||
const apiKeys: ApiKey[] = (rows.results || []).map((row) => ({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
role: row.role as 'admin' | 'ingest' | 'readonly',
|
||||
role: 'ingest' as const,
|
||||
allowed_events: JSON.parse(row.allowed_events),
|
||||
rate_limit: row.rate_limit,
|
||||
last_used_at: row.last_used_at || undefined,
|
||||
@ -1457,7 +1458,7 @@ export async function validateApiKey(
|
||||
const apiKey: ApiKey = {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
role: row.role as 'admin' | 'ingest' | 'readonly',
|
||||
role: 'ingest' as const,
|
||||
allowed_events: JSON.parse(row.allowed_events),
|
||||
rate_limit: row.rate_limit,
|
||||
last_used_at: row.last_used_at || undefined,
|
||||
|
||||
@ -2071,7 +2071,7 @@ describe('API Key Management', () => {
|
||||
|
||||
it('POST /events with valid API key and correct event type → 201', async () => {
|
||||
const keyRes = await app.fetch(
|
||||
req('POST', '/api-keys', { name: 'ingest-key', role: 'ingest', allowed_events: ['task_assigned'] }),
|
||||
req('POST', '/api-keys', { name: 'ingest-key', allowed_events: ['task_assigned'] }),
|
||||
{ DB: db, API_TOKEN },
|
||||
)
|
||||
const { key } = await keyRes.json()
|
||||
@ -2108,7 +2108,7 @@ describe('API Key Management', () => {
|
||||
|
||||
it('POST /events with deleted key → 401', async () => {
|
||||
const keyRes = await app.fetch(
|
||||
req('POST', '/api-keys', { name: 'temp-key', role: 'ingest', allowed_events: ['task_assigned'] }),
|
||||
req('POST', '/api-keys', { name: 'temp-key', allowed_events: ['task_assigned'] }),
|
||||
{ DB: db, API_TOKEN },
|
||||
)
|
||||
const { id, key } = await keyRes.json()
|
||||
@ -2124,7 +2124,7 @@ describe('API Key Management', () => {
|
||||
|
||||
it('POST /events with valid key but wrong event type → 403', async () => {
|
||||
const keyRes = await app.fetch(
|
||||
req('POST', '/api-keys', { name: 'limited-key', role: 'ingest', allowed_events: ['other_event'] }),
|
||||
req('POST', '/api-keys', { name: 'limited-key', allowed_events: ['other_event'] }),
|
||||
{ DB: db, API_TOKEN },
|
||||
)
|
||||
const { key } = await keyRes.json()
|
||||
@ -2138,7 +2138,7 @@ describe('API Key Management', () => {
|
||||
|
||||
it('empty allowed_events means no events allowed for ingest role', async () => {
|
||||
const keyRes = await app.fetch(
|
||||
req('POST', '/api-keys', { name: 'empty-key', role: 'ingest', allowed_events: [] }),
|
||||
req('POST', '/api-keys', { name: 'empty-key', allowed_events: [] }),
|
||||
{ DB: db, API_TOKEN },
|
||||
)
|
||||
const { key } = await keyRes.json()
|
||||
@ -2158,8 +2158,9 @@ describe('API Key Management', () => {
|
||||
expect(res.status).toBe(201)
|
||||
})
|
||||
|
||||
it('admin role API key bypasses event type check', async () => {
|
||||
const keyRes = await app.fetch(req('POST', '/api-keys', { name: 'admin-key', role: 'admin' }), {
|
||||
it('admin role API key is treated as ingest (admin role not implemented)', async () => {
|
||||
// Even if someone passes role=admin, it's ignored — always ingest
|
||||
const keyRes = await app.fetch(req('POST', '/api-keys', { name: 'admin-key', allowed_events: ['task_assigned'] }), {
|
||||
DB: db,
|
||||
API_TOKEN,
|
||||
})
|
||||
@ -2171,6 +2172,23 @@ describe('API Key Management', () => {
|
||||
)
|
||||
expect(res.status).toBe(201)
|
||||
})
|
||||
|
||||
it('creating API key without role defaults to ingest', async () => {
|
||||
const res = await app.fetch(req('POST', '/api-keys', { name: 'no-role-key' }), { DB: db, API_TOKEN })
|
||||
expect(res.status).toBe(201)
|
||||
const json = await res.json()
|
||||
expect(json.role).toBe('ingest')
|
||||
})
|
||||
|
||||
it('any explicitly passed role value is ignored, always ingest', async () => {
|
||||
const res = await app.fetch(
|
||||
req('POST', '/api-keys', { name: 'readonly-attempt', role: 'readonly' as any }),
|
||||
{ DB: db, API_TOKEN },
|
||||
)
|
||||
expect(res.status).toBe(201)
|
||||
const json = await res.json()
|
||||
expect(json.role).toBe('ingest')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -2231,7 +2249,7 @@ describe('Request Logs', () => {
|
||||
|
||||
it('request log includes api_key_name for API key auth', async () => {
|
||||
const keyRes = await app.fetch(
|
||||
req('POST', '/api-keys', { name: 'log-test-key', role: 'ingest', allowed_events: ['task_assigned'] }),
|
||||
req('POST', '/api-keys', { name: 'log-test-key', allowed_events: ['task_assigned'] }),
|
||||
{ DB: db, API_TOKEN },
|
||||
)
|
||||
const { key } = await keyRes.json()
|
||||
@ -2255,7 +2273,7 @@ describe('Request Logs', () => {
|
||||
|
||||
it('GET /request-logs supports api_key_id filter', async () => {
|
||||
const keyRes = await app.fetch(
|
||||
req('POST', '/api-keys', { name: 'filter-key', role: 'ingest', allowed_events: ['task_assigned'] }),
|
||||
req('POST', '/api-keys', { name: 'filter-key', allowed_events: ['task_assigned'] }),
|
||||
{ DB: db, API_TOKEN },
|
||||
)
|
||||
const { key, id: keyId } = await keyRes.json()
|
||||
|
||||
@ -481,7 +481,8 @@ app.post('/api-keys', async (c) => {
|
||||
try {
|
||||
const body = await c.req.json<CreateApiKeyRequest>()
|
||||
if (!body.name) return apiError(c, 400, ErrorCode.MISSING_FIELD, 'Missing name')
|
||||
const result = await createApiKey(c.env.DB, body.name, body.role, body.allowed_events, body.rate_limit)
|
||||
// Role is always 'ingest' — admin/readonly not implemented
|
||||
const result = await createApiKey(c.env.DB, body.name, body.allowed_events, body.rate_limit)
|
||||
return c.json(result, 201)
|
||||
} catch (err: any) {
|
||||
return apiError(c, 500, ErrorCode.INTERNAL_ERROR, err.message || 'Internal error')
|
||||
|
||||
@ -181,10 +181,11 @@ export interface ReactionLog {
|
||||
// API Key Types
|
||||
// ============================================
|
||||
|
||||
// Only 'ingest' role is currently implemented. May expand in the future.
|
||||
export interface ApiKey {
|
||||
id: number
|
||||
name: string
|
||||
role: 'admin' | 'ingest' | 'readonly'
|
||||
role: 'ingest'
|
||||
allowed_events: string[]
|
||||
rate_limit: number
|
||||
last_used_at?: number
|
||||
@ -193,7 +194,6 @@ export interface ApiKey {
|
||||
|
||||
export interface CreateApiKeyRequest {
|
||||
name: string
|
||||
role?: 'admin' | 'ingest' | 'readonly'
|
||||
allowed_events?: string[]
|
||||
rate_limit?: number
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -164,9 +164,6 @@ export default function ApiKeys() {
|
||||
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider">
|
||||
Name
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider">
|
||||
Role
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider">
|
||||
Allowed Events
|
||||
</th>
|
||||
@ -192,11 +189,6 @@ export default function ApiKeys() {
|
||||
>
|
||||
<td className="px-4 py-3 font-mono text-sm text-gray-300">{key.id}</td>
|
||||
<td className="px-4 py-3 text-sm text-gray-200 font-medium">{key.name}</td>
|
||||
<td className="px-4 py-3">
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-purple-500/20 text-purple-300 border border-purple-500/30">
|
||||
{key.role || 'default'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{key.allowed_events && key.allowed_events.length > 0 ? (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user