Skip to content

Commit 140afba

Browse files
authored
feat(al): API key management (#1509)
* **New Features** * Full API Key management: create, list, retrieve, update, revoke; keys usable for authentication. * **API** * OpenAPI/schema and server routes added for API key CRUD with comprehensive request/response/error variants. * **Auth & Middleware** * Service-level generation, validation, last-used tracking and middleware path for API key auth. * **Storage & Config** * File-backed API key store with in-memory index and new config path for API keys directory. * **UI** * Admin API Keys page, nav item, create/edit modal, list and revoke flows; simplified role label. * **Tests** * Extensive unit/integration tests covering store, service, API, middleware, and UI.
1 parent 345ef13 commit 140afba

33 files changed

Lines changed: 6572 additions & 397 deletions

api/v2/api.gen.go

Lines changed: 943 additions & 194 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v2/api.yaml

Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ tags:
3737
description: "Authentication operations (login, logout, token management)"
3838
- name: "users"
3939
description: "User management operations (CRUD, password management)"
40+
- name: "api-keys"
41+
description: "API key management operations (admin only)"
4042

4143
paths:
4244
/health:
@@ -435,6 +437,225 @@ paths:
435437
schema:
436438
$ref: "#/components/schemas/Error"
437439

440+
# API Key Management (Admin only)
441+
/api-keys:
442+
get:
443+
summary: "List all API keys"
444+
description: "Returns all API keys. Requires admin role."
445+
operationId: "listAPIKeys"
446+
tags:
447+
- "api-keys"
448+
responses:
449+
"200":
450+
description: "List of API keys"
451+
content:
452+
application/json:
453+
schema:
454+
$ref: "#/components/schemas/APIKeysListResponse"
455+
"401":
456+
description: "Not authenticated"
457+
content:
458+
application/json:
459+
schema:
460+
$ref: "#/components/schemas/Error"
461+
"403":
462+
description: "Requires admin role"
463+
content:
464+
application/json:
465+
schema:
466+
$ref: "#/components/schemas/Error"
467+
default:
468+
description: "Error"
469+
content:
470+
application/json:
471+
schema:
472+
$ref: "#/components/schemas/Error"
473+
474+
post:
475+
summary: "Create API key"
476+
description: "Full key returned only in this response"
477+
operationId: "createAPIKey"
478+
tags:
479+
- "api-keys"
480+
requestBody:
481+
required: true
482+
content:
483+
application/json:
484+
schema:
485+
$ref: "#/components/schemas/CreateAPIKeyRequest"
486+
responses:
487+
"201":
488+
description: "Created"
489+
content:
490+
application/json:
491+
schema:
492+
$ref: "#/components/schemas/CreateAPIKeyResponse"
493+
"400":
494+
description: "Invalid request"
495+
content:
496+
application/json:
497+
schema:
498+
$ref: "#/components/schemas/Error"
499+
"401":
500+
description: "Not authenticated"
501+
content:
502+
application/json:
503+
schema:
504+
$ref: "#/components/schemas/Error"
505+
"403":
506+
description: "Requires admin role"
507+
content:
508+
application/json:
509+
schema:
510+
$ref: "#/components/schemas/Error"
511+
"409":
512+
description: "Name already exists"
513+
content:
514+
application/json:
515+
schema:
516+
$ref: "#/components/schemas/Error"
517+
default:
518+
description: "Error"
519+
content:
520+
application/json:
521+
schema:
522+
$ref: "#/components/schemas/Error"
523+
524+
/api-keys/{keyId}:
525+
get:
526+
summary: "Get API key"
527+
description: "Returns API key by ID. Requires admin role."
528+
operationId: "getAPIKey"
529+
tags:
530+
- "api-keys"
531+
parameters:
532+
- $ref: "#/components/parameters/APIKeyId"
533+
responses:
534+
"200":
535+
description: "API key details"
536+
content:
537+
application/json:
538+
schema:
539+
$ref: "#/components/schemas/APIKeyResponse"
540+
"401":
541+
description: "Not authenticated"
542+
content:
543+
application/json:
544+
schema:
545+
$ref: "#/components/schemas/Error"
546+
"403":
547+
description: "Requires admin role"
548+
content:
549+
application/json:
550+
schema:
551+
$ref: "#/components/schemas/Error"
552+
"404":
553+
description: "Not found"
554+
content:
555+
application/json:
556+
schema:
557+
$ref: "#/components/schemas/Error"
558+
default:
559+
description: "Error"
560+
content:
561+
application/json:
562+
schema:
563+
$ref: "#/components/schemas/Error"
564+
565+
patch:
566+
summary: "Update API key"
567+
description: "Updates API key info. Requires admin role."
568+
operationId: "updateAPIKey"
569+
tags:
570+
- "api-keys"
571+
parameters:
572+
- $ref: "#/components/parameters/APIKeyId"
573+
requestBody:
574+
required: true
575+
content:
576+
application/json:
577+
schema:
578+
$ref: "#/components/schemas/UpdateAPIKeyRequest"
579+
responses:
580+
"200":
581+
description: "Updated API key"
582+
content:
583+
application/json:
584+
schema:
585+
$ref: "#/components/schemas/APIKeyResponse"
586+
"400":
587+
description: "Invalid request"
588+
content:
589+
application/json:
590+
schema:
591+
$ref: "#/components/schemas/Error"
592+
"401":
593+
description: "Not authenticated"
594+
content:
595+
application/json:
596+
schema:
597+
$ref: "#/components/schemas/Error"
598+
"403":
599+
description: "Requires admin role"
600+
content:
601+
application/json:
602+
schema:
603+
$ref: "#/components/schemas/Error"
604+
"404":
605+
description: "Not found"
606+
content:
607+
application/json:
608+
schema:
609+
$ref: "#/components/schemas/Error"
610+
"409":
611+
description: "Name already exists"
612+
content:
613+
application/json:
614+
schema:
615+
$ref: "#/components/schemas/Error"
616+
default:
617+
description: "Error"
618+
content:
619+
application/json:
620+
schema:
621+
$ref: "#/components/schemas/Error"
622+
623+
delete:
624+
summary: "Delete API key"
625+
description: "Revokes an API key. Requires admin role."
626+
operationId: "deleteAPIKey"
627+
tags:
628+
- "api-keys"
629+
parameters:
630+
- $ref: "#/components/parameters/APIKeyId"
631+
responses:
632+
"204":
633+
description: "API key deleted"
634+
"401":
635+
description: "Not authenticated"
636+
content:
637+
application/json:
638+
schema:
639+
$ref: "#/components/schemas/Error"
640+
"403":
641+
description: "Requires admin role"
642+
content:
643+
application/json:
644+
schema:
645+
$ref: "#/components/schemas/Error"
646+
"404":
647+
description: "Not found"
648+
content:
649+
application/json:
650+
schema:
651+
$ref: "#/components/schemas/Error"
652+
default:
653+
description: "Error"
654+
content:
655+
application/json:
656+
schema:
657+
$ref: "#/components/schemas/Error"
658+
438659
/workers:
439660
get:
440661
summary: "List distributed workers"
@@ -2081,6 +2302,15 @@ components:
20812302
type: string
20822303
minLength: 1
20832304

2305+
APIKeyId:
2306+
name: keyId
2307+
in: path
2308+
description: unique identifier of the API key
2309+
required: true
2310+
schema:
2311+
type: string
2312+
minLength: 1
2313+
20842314
PerPage:
20852315
name: perPage
20862316
in: query
@@ -3443,6 +3673,118 @@ components:
34433673
required:
34443674
- users
34453675

3676+
# ============================================================================
3677+
APIKey:
3678+
type: object
3679+
description: "API key information"
3680+
properties:
3681+
id:
3682+
type: string
3683+
description: "Unique identifier"
3684+
name:
3685+
type: string
3686+
description: "Human-readable name"
3687+
description:
3688+
type: string
3689+
description: "Purpose description"
3690+
role:
3691+
$ref: "#/components/schemas/UserRole"
3692+
keyPrefix:
3693+
type: string
3694+
description: "First 8 characters for identification"
3695+
createdAt:
3696+
type: string
3697+
format: date-time
3698+
description: "Creation timestamp"
3699+
updatedAt:
3700+
type: string
3701+
format: date-time
3702+
description: "Last update timestamp"
3703+
createdBy:
3704+
type: string
3705+
description: "Creator user ID"
3706+
lastUsedAt:
3707+
type: string
3708+
format: date-time
3709+
nullable: true
3710+
description: "Last authentication timestamp"
3711+
required:
3712+
- id
3713+
- name
3714+
- role
3715+
- keyPrefix
3716+
- createdAt
3717+
- updatedAt
3718+
- createdBy
3719+
3720+
APIKeyResponse:
3721+
type: object
3722+
description: "API key response"
3723+
properties:
3724+
apiKey:
3725+
$ref: "#/components/schemas/APIKey"
3726+
required:
3727+
- apiKey
3728+
3729+
APIKeysListResponse:
3730+
type: object
3731+
description: "List of API keys"
3732+
properties:
3733+
apiKeys:
3734+
type: array
3735+
items:
3736+
$ref: "#/components/schemas/APIKey"
3737+
required:
3738+
- apiKeys
3739+
3740+
CreateAPIKeyRequest:
3741+
type: object
3742+
description: "Create API key request"
3743+
properties:
3744+
name:
3745+
type: string
3746+
minLength: 1
3747+
maxLength: 100
3748+
description: "Human-readable name"
3749+
description:
3750+
type: string
3751+
maxLength: 500
3752+
description: "Purpose description"
3753+
role:
3754+
$ref: "#/components/schemas/UserRole"
3755+
required:
3756+
- name
3757+
- role
3758+
3759+
CreateAPIKeyResponse:
3760+
type: object
3761+
description: "Create API key response"
3762+
properties:
3763+
apiKey:
3764+
$ref: "#/components/schemas/APIKey"
3765+
key:
3766+
type: string
3767+
description: "Full key secret, only returned once"
3768+
required:
3769+
- apiKey
3770+
- key
3771+
3772+
UpdateAPIKeyRequest:
3773+
type: object
3774+
description: "Update API key request"
3775+
properties:
3776+
name:
3777+
type: string
3778+
minLength: 1
3779+
maxLength: 100
3780+
description: "New name"
3781+
description:
3782+
type: string
3783+
maxLength: 500
3784+
description: "New description"
3785+
role:
3786+
$ref: "#/components/schemas/UserRole"
3787+
34463788
SuccessResponse:
34473789
type: object
34483790
description: "Generic success response"

0 commit comments

Comments
 (0)