diff --git a/coderd/aigatewaycoderdkey/aigatewaycoderdkey.go b/coderd/aigatewaycoderdkey/aigatewaycoderdkey.go index fbe229de29..5675bf0935 100644 --- a/coderd/aigatewaycoderdkey/aigatewaycoderdkey.go +++ b/coderd/aigatewaycoderdkey/aigatewaycoderdkey.go @@ -28,7 +28,7 @@ const ( // // Key shape: "cgw_" + 7 random chars + 32 random chars = 43 chars total. func New(name string) (database.InsertAIGatewayCoderdKeyParams, string, error) { - secret, hashed, err := apikey.GenerateSecret(KeyLength) + secret, hashed, err := apikey.GenerateSecret(visiblePrefixLength + privateSuffixLength) if err != nil { return database.InsertAIGatewayCoderdKeyParams{}, "", xerrors.Errorf("generate secret: %w", err) } diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 4793cdd487..fa66ef1bdb 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -64,100 +64,6 @@ const docTemplate = `{ } } }, - "/aibridge/coderd-keys": { - "get": { - "produces": [ - "application/json" - ], - "tags": [ - "Enterprise" - ], - "summary": "List AI Gateway coderd keys", - "operationId": "list-ai-gateway-coderd-keys", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.AIGatewayCoderdKey" - } - } - } - }, - "security": [ - { - "CoderSessionToken": [] - } - ] - }, - "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Enterprise" - ], - "summary": "Create AI Gateway coderd key", - "operationId": "create-ai-gateway-coderd-key", - "parameters": [ - { - "description": "Create AI Gateway coderd key request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/codersdk.CreateAIGatewayCoderdKeyRequest" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/codersdk.CreateAIGatewayCoderdKeyResponse" - } - } - }, - "security": [ - { - "CoderSessionToken": [] - } - ] - } - }, - "/aibridge/coderd-keys/{key}": { - "delete": { - "tags": [ - "Enterprise" - ], - "summary": "Delete AI Gateway coderd key", - "operationId": "delete-ai-gateway-coderd-key", - "parameters": [ - { - "type": "string", - "format": "uuid", - "description": "Key ID", - "name": "key", - "in": "path", - "required": true - } - ], - "responses": { - "204": { - "description": "No Content" - } - }, - "security": [ - { - "CoderSessionToken": [] - } - ] - } - }, "/api/experimental/chats": { "get": { "description": "Experimental: this endpoint is subject to change.", @@ -1516,6 +1422,100 @@ const docTemplate = `{ ] } }, + "/api/v2/aibridge/coderd-keys": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Enterprise" + ], + "summary": "List AI Gateway coderd keys", + "operationId": "list-ai-gateway-coderd-keys", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.AIGatewayCoderdKey" + } + } + } + }, + "security": [ + { + "CoderSessionToken": [] + } + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Enterprise" + ], + "summary": "Create AI Gateway coderd key", + "operationId": "create-ai-gateway-coderd-key", + "parameters": [ + { + "description": "Create AI Gateway coderd key request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/codersdk.CreateAIGatewayCoderdKeyRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/codersdk.CreateAIGatewayCoderdKeyResponse" + } + } + }, + "security": [ + { + "CoderSessionToken": [] + } + ] + } + }, + "/api/v2/aibridge/coderd-keys/{key}": { + "delete": { + "tags": [ + "Enterprise" + ], + "summary": "Delete AI Gateway coderd key", + "operationId": "delete-ai-gateway-coderd-key", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Key ID", + "name": "key", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + }, + "security": [ + { + "CoderSessionToken": [] + } + ] + } + }, "/api/v2/aibridge/interceptions": { "get": { "produces": [ diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 665bc1f211..2a0823a023 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -49,88 +49,6 @@ } } }, - "/aibridge/coderd-keys": { - "get": { - "produces": ["application/json"], - "tags": ["Enterprise"], - "summary": "List AI Gateway coderd keys", - "operationId": "list-ai-gateway-coderd-keys", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.AIGatewayCoderdKey" - } - } - } - }, - "security": [ - { - "CoderSessionToken": [] - } - ] - }, - "post": { - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Enterprise"], - "summary": "Create AI Gateway coderd key", - "operationId": "create-ai-gateway-coderd-key", - "parameters": [ - { - "description": "Create AI Gateway coderd key request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/codersdk.CreateAIGatewayCoderdKeyRequest" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/codersdk.CreateAIGatewayCoderdKeyResponse" - } - } - }, - "security": [ - { - "CoderSessionToken": [] - } - ] - } - }, - "/aibridge/coderd-keys/{key}": { - "delete": { - "tags": ["Enterprise"], - "summary": "Delete AI Gateway coderd key", - "operationId": "delete-ai-gateway-coderd-key", - "parameters": [ - { - "type": "string", - "format": "uuid", - "description": "Key ID", - "name": "key", - "in": "path", - "required": true - } - ], - "responses": { - "204": { - "description": "No Content" - } - }, - "security": [ - { - "CoderSessionToken": [] - } - ] - } - }, "/api/experimental/chats": { "get": { "description": "Experimental: this endpoint is subject to change.", @@ -1337,6 +1255,88 @@ ] } }, + "/api/v2/aibridge/coderd-keys": { + "get": { + "produces": ["application/json"], + "tags": ["Enterprise"], + "summary": "List AI Gateway coderd keys", + "operationId": "list-ai-gateway-coderd-keys", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.AIGatewayCoderdKey" + } + } + } + }, + "security": [ + { + "CoderSessionToken": [] + } + ] + }, + "post": { + "consumes": ["application/json"], + "produces": ["application/json"], + "tags": ["Enterprise"], + "summary": "Create AI Gateway coderd key", + "operationId": "create-ai-gateway-coderd-key", + "parameters": [ + { + "description": "Create AI Gateway coderd key request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/codersdk.CreateAIGatewayCoderdKeyRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/codersdk.CreateAIGatewayCoderdKeyResponse" + } + } + }, + "security": [ + { + "CoderSessionToken": [] + } + ] + } + }, + "/api/v2/aibridge/coderd-keys/{key}": { + "delete": { + "tags": ["Enterprise"], + "summary": "Delete AI Gateway coderd key", + "operationId": "delete-ai-gateway-coderd-key", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Key ID", + "name": "key", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + }, + "security": [ + { + "CoderSessionToken": [] + } + ] + } + }, "/api/v2/aibridge/interceptions": { "get": { "produces": ["application/json"], diff --git a/codersdk/aigatewaycoderdkeys.go b/codersdk/aigatewaycoderdkeys.go index 2b33052bbf..6a27edb3b2 100644 --- a/codersdk/aigatewaycoderdkeys.go +++ b/codersdk/aigatewaycoderdkeys.go @@ -11,7 +11,7 @@ import ( "golang.org/x/xerrors" ) -// AIGatewayCoderdKey is a shared secret used by an standalone AI Gateway +// AIGatewayCoderdKey is a shared secret used by a standalone AI Gateway // to authenticate into coderd. type AIGatewayCoderdKey struct { ID uuid.UUID `json:"id" format:"uuid"` diff --git a/docs/reference/api/enterprise.md b/docs/reference/api/enterprise.md index a49dc15c48..b1c7fa6b0a 100644 --- a/docs/reference/api/enterprise.md +++ b/docs/reference/api/enterprise.md @@ -90,12 +90,12 @@ curl -X GET http://coder-server:8080/.well-known/oauth-protected-resource \ ```shell # Example request using curl -curl -X GET http://coder-server:8080/aibridge/coderd-keys \ +curl -X GET http://coder-server:8080/api/v2/aibridge/coderd-keys \ -H 'Accept: application/json' \ -H 'Coder-Session-Token: API_KEY' ``` -`GET /aibridge/coderd-keys` +`GET /api/v2/aibridge/coderd-keys` ### Example responses @@ -140,13 +140,13 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio ```shell # Example request using curl -curl -X POST http://coder-server:8080/aibridge/coderd-keys \ +curl -X POST http://coder-server:8080/api/v2/aibridge/coderd-keys \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -H 'Coder-Session-Token: API_KEY' ``` -`POST /aibridge/coderd-keys` +`POST /api/v2/aibridge/coderd-keys` > Body parameter @@ -190,11 +190,11 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio ```shell # Example request using curl -curl -X DELETE http://coder-server:8080/aibridge/coderd-keys/{key} \ +curl -X DELETE http://coder-server:8080/api/v2/aibridge/coderd-keys/{key} \ -H 'Coder-Session-Token: API_KEY' ``` -`DELETE /aibridge/coderd-keys/{key}` +`DELETE /api/v2/aibridge/coderd-keys/{key}` ### Parameters diff --git a/enterprise/coderd/aigatewaycoderdkeys.go b/enterprise/coderd/aigatewaycoderdkeys.go index 898c14b409..cf9b215379 100644 --- a/enterprise/coderd/aigatewaycoderdkeys.go +++ b/enterprise/coderd/aigatewaycoderdkeys.go @@ -11,6 +11,7 @@ import ( "github.com/google/uuid" "github.com/coder/coder/v2/coderd/aigatewaycoderdkey" + "github.com/coder/coder/v2/coderd/audit" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/codersdk" @@ -21,7 +22,7 @@ import ( const maxKeyInsertAttempts = 7 // nameFormatDetail is the human-readable description of valid key names. -const nameFormatDetail = "Must be 64 characters or fewer, lowercase letters, numbers, and non-consecutive hyphens, cannot start or end with a hyphen." +const nameFormatDetail = "Must be 64 characters or fewer, lowercase letters, numbers, and non-consecutive hyphens, cannot start or end with a hyphen." // @Summary Create AI Gateway coderd key // @ID create-ai-gateway-coderd-key @@ -31,9 +32,19 @@ const nameFormatDetail = "Must be 64 characters or fewer, lowercase letters, nu // @Tags Enterprise // @Param request body codersdk.CreateAIGatewayCoderdKeyRequest true "Create AI Gateway coderd key request" // @Success 201 {object} codersdk.CreateAIGatewayCoderdKeyResponse -// @Router /aibridge/coderd-keys [post] +// @Router /api/v2/aibridge/coderd-keys [post] func (api *API) postAIGatewayCoderdKey(rw http.ResponseWriter, r *http.Request) { - ctx := r.Context() + var ( + ctx = r.Context() + auditor = api.AGPL.Auditor.Load() + aReq, commitAudit = audit.InitRequest[database.AiGatewayCoderdKey](rw, &audit.RequestParams{ + Audit: *auditor, + Log: api.Logger, + Request: r, + Action: database.AuditActionCreate, + }) + ) + defer commitAudit() var req codersdk.CreateAIGatewayCoderdKeyRequest if !httpapi.Read(ctx, rw, r, &req) { @@ -49,6 +60,13 @@ func (api *API) postAIGatewayCoderdKey(rw http.ResponseWriter, r *http.Request) return } + aReq.New = database.AiGatewayCoderdKey{ + ID: row.ID, + Name: row.Name, + SecretPrefix: row.SecretPrefix, + CreatedAt: row.CreatedAt, + } + httpapi.Write(ctx, rw, http.StatusCreated, codersdk.CreateAIGatewayCoderdKeyResponse{ ID: row.ID, Name: row.Name, @@ -109,7 +127,7 @@ func writeKeyInsertError(ctx context.Context, rw http.ResponseWriter, err error) // @Produce json // @Tags Enterprise // @Success 200 {array} codersdk.AIGatewayCoderdKey -// @Router /aibridge/coderd-keys [get] +// @Router /api/v2/aibridge/coderd-keys [get] func (api *API) aiGatewayCoderdKeys(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -137,9 +155,19 @@ func (api *API) aiGatewayCoderdKeys(rw http.ResponseWriter, r *http.Request) { // @Tags Enterprise // @Param key path string true "Key ID" format(uuid) // @Success 204 -// @Router /aibridge/coderd-keys/{key} [delete] +// @Router /api/v2/aibridge/coderd-keys/{key} [delete] func (api *API) deleteAIGatewayCoderdKey(rw http.ResponseWriter, r *http.Request) { - ctx := r.Context() + var ( + ctx = r.Context() + auditor = api.AGPL.Auditor.Load() + aReq, commitAudit = audit.InitRequest[database.AiGatewayCoderdKey](rw, &audit.RequestParams{ + Audit: *auditor, + Log: api.Logger, + Request: r, + Action: database.AuditActionDelete, + }) + ) + defer commitAudit() id, err := uuid.Parse(chi.URLParam(r, "key")) if err != nil { @@ -150,7 +178,8 @@ func (api *API) deleteAIGatewayCoderdKey(rw http.ResponseWriter, r *http.Request return } - if _, err := api.Database.DeleteAIGatewayCoderdKey(ctx, id); err != nil { + deleted, err := api.Database.DeleteAIGatewayCoderdKey(ctx, id) + if err != nil { if httpapi.IsUnauthorizedError(err) { httpapi.Forbidden(rw) return @@ -163,6 +192,15 @@ func (api *API) deleteAIGatewayCoderdKey(rw http.ResponseWriter, r *http.Request return } + aReq.Old = database.AiGatewayCoderdKey{ + ID: deleted.ID, + Name: deleted.Name, + SecretPrefix: deleted.SecretPrefix, + HashedSecret: deleted.HashedSecret, + CreatedAt: deleted.CreatedAt, + LastUsedAt: deleted.LastUsedAt, + } + httpapi.Write(ctx, rw, http.StatusNoContent, nil) } diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index edb3de1a6a..8f0976cba8 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -306,7 +306,7 @@ export interface AIConfig { // From codersdk/aigatewaycoderdkeys.go /** - * AIGatewayCoderdKey is a shared secret used by an standalone AI Gateway + * AIGatewayCoderdKey is a shared secret used by a standalone AI Gateway * to authenticate into coderd. */ export interface AIGatewayCoderdKey {