diff --git a/docs/docs.go b/docs/docs.go index 8d6a8f1..4a9bce6 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -19,6 +19,235 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { + "/api/v1/auth/keys": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Returns a list of all API keys for the system user (excludes key hash and plain-text key)", + "produces": [ + "application/json" + ], + "tags": [ + "Keys" + ], + "summary": "List all API keys", + "responses": { + "200": { + "description": "List of API keys", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/server.KeyResponse" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "description": "Creates a new API key with the specified permissions and returns the plain-text key (only shown once)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Keys" + ], + "summary": "Create a new API key", + "parameters": [ + { + "description": "API key configuration", + "name": "key", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/server.CreateKeyRequest" + } + } + ], + "responses": { + "201": { + "description": "Created API key with plain-text key", + "schema": { + "$ref": "#/definitions/server.CreateKeyResponse" + } + }, + "400": { + "description": "Invalid request body or validation error", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/auth/keys/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Returns details for a specific API key by ID (excludes key hash and plain-text key)", + "produces": [ + "application/json" + ], + "tags": [ + "Keys" + ], + "summary": "Get details of a specific API key", + "parameters": [ + { + "type": "integer", + "description": "Key ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "API key details", + "schema": { + "$ref": "#/definitions/server.KeyResponse" + } + }, + "400": { + "description": "Invalid key ID", + "schema": { + "type": "string" + } + }, + "404": { + "description": "API key not found", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Deletes an API key by ID", + "tags": [ + "Keys" + ], + "summary": "Delete an API key", + "parameters": [ + { + "type": "integer", + "description": "Key ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "API key deleted successfully" + }, + "400": { + "description": "Invalid key ID", + "schema": { + "type": "string" + } + }, + "404": { + "description": "API key not found", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/auth/keys/{id}/permissions": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Returns the instance-level permissions for a specific API key (includes instance names)", + "produces": [ + "application/json" + ], + "tags": [ + "Keys" + ], + "summary": "Get API key permissions", + "parameters": [ + { + "type": "integer", + "description": "Key ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "List of key permissions", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/server.KeyPermissionResponse" + } + } + }, + "400": { + "description": "Invalid key ID", + "schema": { + "type": "string" + } + }, + "404": { + "description": "API key not found", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, "/api/v1/backends/llama-cpp/devices": { "get": { "security": [ @@ -1503,6 +1732,17 @@ const docTemplate = `{ } }, "definitions": { + "auth.PermissionMode": { + "type": "string", + "enum": [ + "allow_all", + "per_instance" + ], + "x-enum-varnames": [ + "PermissionModeAllowAll", + "PermissionModePerInstance" + ] + }, "config.AppConfig": { "type": "object", "properties": { @@ -1518,6 +1758,13 @@ const docTemplate = `{ "commit_hash": { "type": "string" }, + "data_dir": { + "description": "Directory where all llamactl data will be stored (database, instances, logs, etc.)", + "type": "string" + }, + "database": { + "$ref": "#/definitions/config.DatabaseConfig" + }, "instances": { "$ref": "#/definitions/config.InstancesConfig" }, @@ -1608,6 +1855,26 @@ const docTemplate = `{ } } }, + "config.DatabaseConfig": { + "type": "object", + "properties": { + "connection_max_lifetime": { + "type": "string", + "example": "1h" + }, + "max_idle_connections": { + "type": "integer" + }, + "max_open_connections": { + "description": "Connection settings", + "type": "integer" + }, + "path": { + "description": "Database file path (relative to the top-level data_dir or absolute)", + "type": "string" + } + } + }, "config.DockerSettings": { "type": "object", "properties": { @@ -1639,11 +1906,7 @@ const docTemplate = `{ "type": "boolean" }, "configs_dir": { - "description": "Instance config directory override", - "type": "string" - }, - "data_dir": { - "description": "Directory where all llamactl data will be stored (instances.json, logs, etc.)", + "description": "Instance config directory override (relative to data_dir if not absolute)", "type": "string" }, "default_auto_restart": { @@ -1667,7 +1930,7 @@ const docTemplate = `{ "type": "boolean" }, "logs_dir": { - "description": "Logs directory override", + "description": "Logs directory override (relative to data_dir if not absolute)", "type": "string" }, "max_instances": { @@ -1748,7 +2011,10 @@ const docTemplate = `{ "type": "object", "properties": { "created": { - "description": "Unix timestamp when the instance was created", + "description": "Unix timestamp when instance was created", + "type": "integer" + }, + "id": { "type": "integer" }, "name": { @@ -1794,6 +2060,125 @@ const docTemplate = `{ } } }, + "server.CreateKeyRequest": { + "type": "object", + "properties": { + "expiresAt": { + "type": "integer", + "format": "int64" + }, + "instancePermissions": { + "type": "array", + "items": { + "$ref": "#/definitions/server.InstancePermission" + } + }, + "name": { + "type": "string" + }, + "permissionMode": { + "$ref": "#/definitions/auth.PermissionMode" + } + } + }, + "server.CreateKeyResponse": { + "type": "object", + "properties": { + "created_at": { + "type": "integer" + }, + "enabled": { + "type": "boolean" + }, + "expires_at": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "key": { + "type": "string" + }, + "last_used_at": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "permission_mode": { + "$ref": "#/definitions/auth.PermissionMode" + }, + "updated_at": { + "type": "integer" + }, + "user_id": { + "type": "string" + } + } + }, + "server.InstancePermission": { + "type": "object", + "properties": { + "can_infer": { + "type": "boolean" + }, + "can_view_logs": { + "type": "boolean" + }, + "instance_id": { + "type": "integer" + } + } + }, + "server.KeyPermissionResponse": { + "type": "object", + "properties": { + "can_infer": { + "type": "boolean" + }, + "can_view_logs": { + "type": "boolean" + }, + "instance_id": { + "type": "integer" + }, + "instance_name": { + "type": "string" + } + } + }, + "server.KeyResponse": { + "type": "object", + "properties": { + "created_at": { + "type": "integer" + }, + "enabled": { + "type": "boolean" + }, + "expires_at": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "last_used_at": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "permission_mode": { + "$ref": "#/definitions/auth.PermissionMode" + }, + "updated_at": { + "type": "integer" + }, + "user_id": { + "type": "string" + } + } + }, "server.NodeResponse": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index f79a008..25cf87d 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -12,6 +12,235 @@ }, "basePath": "/api/v1", "paths": { + "/api/v1/auth/keys": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Returns a list of all API keys for the system user (excludes key hash and plain-text key)", + "produces": [ + "application/json" + ], + "tags": [ + "Keys" + ], + "summary": "List all API keys", + "responses": { + "200": { + "description": "List of API keys", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/server.KeyResponse" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "description": "Creates a new API key with the specified permissions and returns the plain-text key (only shown once)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Keys" + ], + "summary": "Create a new API key", + "parameters": [ + { + "description": "API key configuration", + "name": "key", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/server.CreateKeyRequest" + } + } + ], + "responses": { + "201": { + "description": "Created API key with plain-text key", + "schema": { + "$ref": "#/definitions/server.CreateKeyResponse" + } + }, + "400": { + "description": "Invalid request body or validation error", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/auth/keys/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Returns details for a specific API key by ID (excludes key hash and plain-text key)", + "produces": [ + "application/json" + ], + "tags": [ + "Keys" + ], + "summary": "Get details of a specific API key", + "parameters": [ + { + "type": "integer", + "description": "Key ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "API key details", + "schema": { + "$ref": "#/definitions/server.KeyResponse" + } + }, + "400": { + "description": "Invalid key ID", + "schema": { + "type": "string" + } + }, + "404": { + "description": "API key not found", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Deletes an API key by ID", + "tags": [ + "Keys" + ], + "summary": "Delete an API key", + "parameters": [ + { + "type": "integer", + "description": "Key ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "API key deleted successfully" + }, + "400": { + "description": "Invalid key ID", + "schema": { + "type": "string" + } + }, + "404": { + "description": "API key not found", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/auth/keys/{id}/permissions": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Returns the instance-level permissions for a specific API key (includes instance names)", + "produces": [ + "application/json" + ], + "tags": [ + "Keys" + ], + "summary": "Get API key permissions", + "parameters": [ + { + "type": "integer", + "description": "Key ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "List of key permissions", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/server.KeyPermissionResponse" + } + } + }, + "400": { + "description": "Invalid key ID", + "schema": { + "type": "string" + } + }, + "404": { + "description": "API key not found", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, "/api/v1/backends/llama-cpp/devices": { "get": { "security": [ @@ -1496,6 +1725,17 @@ } }, "definitions": { + "auth.PermissionMode": { + "type": "string", + "enum": [ + "allow_all", + "per_instance" + ], + "x-enum-varnames": [ + "PermissionModeAllowAll", + "PermissionModePerInstance" + ] + }, "config.AppConfig": { "type": "object", "properties": { @@ -1511,6 +1751,13 @@ "commit_hash": { "type": "string" }, + "data_dir": { + "description": "Directory where all llamactl data will be stored (database, instances, logs, etc.)", + "type": "string" + }, + "database": { + "$ref": "#/definitions/config.DatabaseConfig" + }, "instances": { "$ref": "#/definitions/config.InstancesConfig" }, @@ -1601,6 +1848,26 @@ } } }, + "config.DatabaseConfig": { + "type": "object", + "properties": { + "connection_max_lifetime": { + "type": "string", + "example": "1h" + }, + "max_idle_connections": { + "type": "integer" + }, + "max_open_connections": { + "description": "Connection settings", + "type": "integer" + }, + "path": { + "description": "Database file path (relative to the top-level data_dir or absolute)", + "type": "string" + } + } + }, "config.DockerSettings": { "type": "object", "properties": { @@ -1632,11 +1899,7 @@ "type": "boolean" }, "configs_dir": { - "description": "Instance config directory override", - "type": "string" - }, - "data_dir": { - "description": "Directory where all llamactl data will be stored (instances.json, logs, etc.)", + "description": "Instance config directory override (relative to data_dir if not absolute)", "type": "string" }, "default_auto_restart": { @@ -1660,7 +1923,7 @@ "type": "boolean" }, "logs_dir": { - "description": "Logs directory override", + "description": "Logs directory override (relative to data_dir if not absolute)", "type": "string" }, "max_instances": { @@ -1741,7 +2004,10 @@ "type": "object", "properties": { "created": { - "description": "Unix timestamp when the instance was created", + "description": "Unix timestamp when instance was created", + "type": "integer" + }, + "id": { "type": "integer" }, "name": { @@ -1787,6 +2053,125 @@ } } }, + "server.CreateKeyRequest": { + "type": "object", + "properties": { + "expiresAt": { + "type": "integer", + "format": "int64" + }, + "instancePermissions": { + "type": "array", + "items": { + "$ref": "#/definitions/server.InstancePermission" + } + }, + "name": { + "type": "string" + }, + "permissionMode": { + "$ref": "#/definitions/auth.PermissionMode" + } + } + }, + "server.CreateKeyResponse": { + "type": "object", + "properties": { + "created_at": { + "type": "integer" + }, + "enabled": { + "type": "boolean" + }, + "expires_at": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "key": { + "type": "string" + }, + "last_used_at": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "permission_mode": { + "$ref": "#/definitions/auth.PermissionMode" + }, + "updated_at": { + "type": "integer" + }, + "user_id": { + "type": "string" + } + } + }, + "server.InstancePermission": { + "type": "object", + "properties": { + "can_infer": { + "type": "boolean" + }, + "can_view_logs": { + "type": "boolean" + }, + "instance_id": { + "type": "integer" + } + } + }, + "server.KeyPermissionResponse": { + "type": "object", + "properties": { + "can_infer": { + "type": "boolean" + }, + "can_view_logs": { + "type": "boolean" + }, + "instance_id": { + "type": "integer" + }, + "instance_name": { + "type": "string" + } + } + }, + "server.KeyResponse": { + "type": "object", + "properties": { + "created_at": { + "type": "integer" + }, + "enabled": { + "type": "boolean" + }, + "expires_at": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "last_used_at": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "permission_mode": { + "$ref": "#/definitions/auth.PermissionMode" + }, + "updated_at": { + "type": "integer" + }, + "user_id": { + "type": "string" + } + } + }, "server.NodeResponse": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 2888ce1..8143bc3 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,5 +1,13 @@ basePath: /api/v1 definitions: + auth.PermissionMode: + enum: + - allow_all + - per_instance + type: string + x-enum-varnames: + - PermissionModeAllowAll + - PermissionModePerInstance config.AppConfig: properties: auth: @@ -10,6 +18,12 @@ definitions: type: string commit_hash: type: string + data_dir: + description: Directory where all llamactl data will be stored (database, instances, + logs, etc.) + type: string + database: + $ref: '#/definitions/config.DatabaseConfig' instances: $ref: '#/definitions/config.InstancesConfig' local_node: @@ -70,6 +84,20 @@ definitions: type: string type: object type: object + config.DatabaseConfig: + properties: + connection_max_lifetime: + example: 1h + type: string + max_idle_connections: + type: integer + max_open_connections: + description: Connection settings + type: integer + path: + description: Database file path (relative to the top-level data_dir or absolute) + type: string + type: object config.DockerSettings: properties: args: @@ -91,11 +119,8 @@ definitions: description: Automatically create the data directory if it doesn't exist type: boolean configs_dir: - description: Instance config directory override - type: string - data_dir: - description: Directory where all llamactl data will be stored (instances.json, - logs, etc.) + description: Instance config directory override (relative to data_dir if not + absolute) type: string default_auto_restart: description: Default auto-restart setting for new instances @@ -113,7 +138,7 @@ definitions: description: Enable LRU eviction for instance logs type: boolean logs_dir: - description: Logs directory override + description: Logs directory override (relative to data_dir if not absolute) type: string max_instances: description: Maximum number of instances that can be created @@ -171,7 +196,9 @@ definitions: instance.Instance: properties: created: - description: Unix timestamp when the instance was created + description: Unix timestamp when instance was created + type: integer + id: type: integer name: type: string @@ -203,6 +230,84 @@ definitions: description: seconds type: integer type: object + server.CreateKeyRequest: + properties: + expiresAt: + format: int64 + type: integer + instancePermissions: + items: + $ref: '#/definitions/server.InstancePermission' + type: array + name: + type: string + permissionMode: + $ref: '#/definitions/auth.PermissionMode' + type: object + server.CreateKeyResponse: + properties: + created_at: + type: integer + enabled: + type: boolean + expires_at: + type: integer + id: + type: integer + key: + type: string + last_used_at: + type: integer + name: + type: string + permission_mode: + $ref: '#/definitions/auth.PermissionMode' + updated_at: + type: integer + user_id: + type: string + type: object + server.InstancePermission: + properties: + can_infer: + type: boolean + can_view_logs: + type: boolean + instance_id: + type: integer + type: object + server.KeyPermissionResponse: + properties: + can_infer: + type: boolean + can_view_logs: + type: boolean + instance_id: + type: integer + instance_name: + type: string + type: object + server.KeyResponse: + properties: + created_at: + type: integer + enabled: + type: boolean + expires_at: + type: integer + id: + type: integer + last_used_at: + type: integer + name: + type: string + permission_mode: + $ref: '#/definitions/auth.PermissionMode' + updated_at: + type: integer + user_id: + type: string + type: object server.NodeResponse: properties: address: @@ -242,6 +347,156 @@ info: title: llamactl API version: "1.0" paths: + /api/v1/auth/keys: + get: + description: Returns a list of all API keys for the system user (excludes key + hash and plain-text key) + produces: + - application/json + responses: + "200": + description: List of API keys + schema: + items: + $ref: '#/definitions/server.KeyResponse' + type: array + "500": + description: Internal Server Error + schema: + type: string + security: + - ApiKeyAuth: [] + summary: List all API keys + tags: + - Keys + post: + consumes: + - application/json + description: Creates a new API key with the specified permissions and returns + the plain-text key (only shown once) + parameters: + - description: API key configuration + in: body + name: key + required: true + schema: + $ref: '#/definitions/server.CreateKeyRequest' + produces: + - application/json + responses: + "201": + description: Created API key with plain-text key + schema: + $ref: '#/definitions/server.CreateKeyResponse' + "400": + description: Invalid request body or validation error + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: Create a new API key + tags: + - Keys + /api/v1/auth/keys/{id}: + delete: + description: Deletes an API key by ID + parameters: + - description: Key ID + in: path + name: id + required: true + type: integer + responses: + "204": + description: API key deleted successfully + "400": + description: Invalid key ID + schema: + type: string + "404": + description: API key not found + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + security: + - ApiKeyAuth: [] + summary: Delete an API key + tags: + - Keys + get: + description: Returns details for a specific API key by ID (excludes key hash + and plain-text key) + parameters: + - description: Key ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: API key details + schema: + $ref: '#/definitions/server.KeyResponse' + "400": + description: Invalid key ID + schema: + type: string + "404": + description: API key not found + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + security: + - ApiKeyAuth: [] + summary: Get details of a specific API key + tags: + - Keys + /api/v1/auth/keys/{id}/permissions: + get: + description: Returns the instance-level permissions for a specific API key (includes + instance names) + parameters: + - description: Key ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: List of key permissions + schema: + items: + $ref: '#/definitions/server.KeyPermissionResponse' + type: array + "400": + description: Invalid key ID + schema: + type: string + "404": + description: API key not found + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + security: + - ApiKeyAuth: [] + summary: Get API key permissions + tags: + - Keys /api/v1/backends/llama-cpp/devices: get: description: Returns a list of available devices for the llama server diff --git a/pkg/config/config.go b/pkg/config/config.go index 1c53c17..3f6a35c 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -85,7 +85,7 @@ type DatabaseConfig struct { // Connection settings MaxOpenConnections int `yaml:"max_open_connections" json:"max_open_connections"` MaxIdleConnections int `yaml:"max_idle_connections" json:"max_idle_connections"` - ConnMaxLifetime time.Duration `yaml:"connection_max_lifetime" json:"connection_max_lifetime"` + ConnMaxLifetime time.Duration `yaml:"connection_max_lifetime" json:"connection_max_lifetime" swaggertype:"string" example:"1h"` } // InstancesConfig contains instance management configuration diff --git a/pkg/server/handlers_auth.go b/pkg/server/handlers_auth.go index 2be79b0..70c5e20 100644 --- a/pkg/server/handlers_auth.go +++ b/pkg/server/handlers_auth.go @@ -11,12 +11,14 @@ import ( "github.com/go-chi/chi/v5" ) +// InstancePermission defines the permissions for an API key on a specific instance. type InstancePermission struct { InstanceID int `json:"instance_id"` CanInfer bool `json:"can_infer"` CanViewLogs bool `json:"can_view_logs"` } +// CreateKeyRequest represents the request body for creating a new API key. type CreateKeyRequest struct { Name string PermissionMode auth.PermissionMode @@ -24,31 +26,34 @@ type CreateKeyRequest struct { InstancePermissions []InstancePermission } +// CreateKeyResponse represents the response returned when creating a new API key. type CreateKeyResponse struct { - ID int `json:"id"` - Name string `json:"name"` - UserID string `json:"user_id"` - PermissionMode auth.PermissionMode `json:"permission_mode"` - ExpiresAt *int64 `json:"expires_at"` - Enabled bool `json:"enabled"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` - LastUsedAt *int64 `json:"last_used_at"` - Key string `json:"key"` + ID int `json:"id"` + Name string `json:"name"` + UserID string `json:"user_id"` + PermissionMode auth.PermissionMode `json:"permission_mode"` + ExpiresAt *int64 `json:"expires_at"` + Enabled bool `json:"enabled"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` + LastUsedAt *int64 `json:"last_used_at"` + Key string `json:"key"` } +// KeyResponse represents an API key in responses for list and get operations. type KeyResponse struct { - ID int `json:"id"` - Name string `json:"name"` - UserID string `json:"user_id"` - PermissionMode auth.PermissionMode `json:"permission_mode"` - ExpiresAt *int64 `json:"expires_at"` - Enabled bool `json:"enabled"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` - LastUsedAt *int64 `json:"last_used_at"` + ID int `json:"id"` + Name string `json:"name"` + UserID string `json:"user_id"` + PermissionMode auth.PermissionMode `json:"permission_mode"` + ExpiresAt *int64 `json:"expires_at"` + Enabled bool `json:"enabled"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` + LastUsedAt *int64 `json:"last_used_at"` } +// KeyPermissionResponse represents the permissions for an API key on a specific instance. type KeyPermissionResponse struct { InstanceID int `json:"instance_id"` InstanceName string `json:"instance_name"` @@ -56,8 +61,18 @@ type KeyPermissionResponse struct { CanViewLogs bool `json:"can_view_logs"` } -// CreateInferenceKey handles POST /api/v1/keys -func (h *Handler) CreateInferenceKey() http.HandlerFunc { +// CreateKey godoc +// @Summary Create a new API key +// @Description Creates a new API key with the specified permissions and returns the plain-text key (only shown once) +// @Tags Keys +// @Accept json +// @Produce json +// @Param key body CreateKeyRequest true "API key configuration" +// @Success 201 {object} CreateKeyResponse "Created API key with plain-text key" +// @Failure 400 {string} string "Invalid request body or validation error" +// @Failure 500 {string} string "Internal Server Error" +// @Router /api/v1/auth/keys [post] +func (h *Handler) CreateKey() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req CreateKeyRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { @@ -172,8 +187,16 @@ func (h *Handler) CreateInferenceKey() http.HandlerFunc { } } -// ListInferenceKeys handles GET /api/v1/keys -func (h *Handler) ListInferenceKeys() http.HandlerFunc { +// ListKeys godoc +// @Summary List all API keys +// @Description Returns a list of all API keys for the system user (excludes key hash and plain-text key) +// @Tags Keys +// @Security ApiKeyAuth +// @Produce json +// @Success 200 {array} KeyResponse "List of API keys" +// @Failure 500 {string} string "Internal Server Error" +// @Router /api/v1/auth/keys [get] +func (h *Handler) ListKeys() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { keys, err := h.authStore.GetUserKeys(r.Context(), "system") if err != nil { @@ -202,8 +225,19 @@ func (h *Handler) ListInferenceKeys() http.HandlerFunc { } } -// GetInferenceKey handles GET /api/v1/keys/{id} -func (h *Handler) GetInferenceKey() http.HandlerFunc { +// GetKey godoc +// @Summary Get details of a specific API key +// @Description Returns details for a specific API key by ID (excludes key hash and plain-text key) +// @Tags Keys +// @Security ApiKeyAuth +// @Produce json +// @Param id path int true "Key ID" +// @Success 200 {object} KeyResponse "API key details" +// @Failure 400 {string} string "Invalid key ID" +// @Failure 404 {string} string "API key not found" +// @Failure 500 {string} string "Internal Server Error" +// @Router /api/v1/auth/keys/{id} [get] +func (h *Handler) GetKey() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { idStr := chi.URLParam(r, "id") id, err := strconv.Atoi(idStr) @@ -240,8 +274,18 @@ func (h *Handler) GetInferenceKey() http.HandlerFunc { } } -// DeleteInferenceKey handles DELETE /api/v1/keys/{id} -func (h *Handler) DeleteInferenceKey() http.HandlerFunc { +// DeleteKey godoc +// @Summary Delete an API key +// @Description Deletes an API key by ID +// @Tags Keys +// @Security ApiKeyAuth +// @Param id path int true "Key ID" +// @Success 204 "API key deleted successfully" +// @Failure 400 {string} string "Invalid key ID" +// @Failure 404 {string} string "API key not found" +// @Failure 500 {string} string "Internal Server Error" +// @Router /api/v1/auth/keys/{id} [delete] +func (h *Handler) DeleteKey() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { idStr := chi.URLParam(r, "id") id, err := strconv.Atoi(idStr) @@ -264,8 +308,19 @@ func (h *Handler) DeleteInferenceKey() http.HandlerFunc { } } -// GetInferenceKeyPermissions handles GET /api/v1/keys/{id}/permissions -func (h *Handler) GetInferenceKeyPermissions() http.HandlerFunc { +// GetKeyPermissions godoc +// @Summary Get API key permissions +// @Description Returns the instance-level permissions for a specific API key (includes instance names) +// @Tags Keys +// @Security ApiKeyAuth +// @Produce json +// @Param id path int true "Key ID" +// @Success 200 {array} KeyPermissionResponse "List of key permissions" +// @Failure 400 {string} string "Invalid key ID" +// @Failure 404 {string} string "API key not found" +// @Failure 500 {string} string "Internal Server Error" +// @Router /api/v1/auth/keys/{id}/permissions [get] +func (h *Handler) GetKeyPermissions() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { idStr := chi.URLParam(r, "id") id, err := strconv.Atoi(idStr) diff --git a/pkg/server/routes.go b/pkg/server/routes.go index 6920a61..f5825da 100644 --- a/pkg/server/routes.go +++ b/pkg/server/routes.go @@ -49,11 +49,11 @@ func SetupRouter(handler *Handler) *chi.Mux { // API key management endpoints r.Route("/auth", func(r chi.Router) { r.Route("/keys", func(r chi.Router) { - r.Post("/", handler.CreateInferenceKey()) // Create API key - r.Get("/", handler.ListInferenceKeys()) // List API keys - r.Get("/{id}", handler.GetInferenceKey()) // Get API key details - r.Delete("/{id}", handler.DeleteInferenceKey()) // Delete API key - r.Get("/{id}/permissions", handler.GetInferenceKeyPermissions()) // Get key permissions + r.Post("/", handler.CreateKey()) // Create API key + r.Get("/", handler.ListKeys()) // List API keys + r.Get("/{id}", handler.GetKey()) // Get API key details + r.Delete("/{id}", handler.DeleteKey()) // Delete API key + r.Get("/{id}/permissions", handler.GetKeyPermissions()) // Get key permissions }) })