Skip to content

Commit d19d66f

Browse files
bricefclaude
andcommitted
Generate API key on actor create, return in response
Previously, POST /actors created an actor with no API key — the APIKeyHash field was json:"-" and the generic jsonBody handler had no way to generate one. The actor was created but unusable. Now the handler generates a random API key, hashes it, stores the hash, and returns the plaintext key as api_key in the response (shown once). This matches the seed admin flow and makes actor creation via CLI/API/MCP fully functional. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ba53378 commit d19d66f

2 files changed

Lines changed: 40 additions & 1 deletion

File tree

internal/http/handlers.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ package http
22

33
import (
44
"context"
5+
"crypto/rand"
6+
"encoding/base64"
57
"encoding/json"
8+
"fmt"
69
"io"
710
"net/http"
811

@@ -20,6 +23,42 @@ func (s *Server) updateActor(ctx context.Context, r *http.Request) (any, error)
2023
return s.svc.UpdateActor(ctx, p)
2124
}
2225

26+
// --- Actors ---
27+
28+
func (s *Server) createActor(ctx context.Context, r *http.Request) (any, error) {
29+
var p model.CreateActorParams
30+
if err := decodeBody(r, &p); err != nil {
31+
return nil, err
32+
}
33+
34+
// Generate an API key for the new actor.
35+
apiKey, err := generateAPIKey()
36+
if err != nil {
37+
return nil, fmt.Errorf("generating API key: %w", err)
38+
}
39+
p.APIKeyHash = HashAPIKey(apiKey)
40+
41+
actor, err := s.svc.CreateActor(ctx, p)
42+
if err != nil {
43+
return nil, err
44+
}
45+
46+
// Return the actor with the plaintext API key (shown once).
47+
type actorWithKey struct {
48+
model.Actor
49+
APIKey string `json:"api_key"`
50+
}
51+
return actorWithKey{Actor: actor, APIKey: apiKey}, nil
52+
}
53+
54+
func generateAPIKey() (string, error) {
55+
b := make([]byte, 32)
56+
if _, err := rand.Read(b); err != nil {
57+
return "", err
58+
}
59+
return base64.RawURLEncoding.EncodeToString(b), nil
60+
}
61+
2362
// --- Boards ---
2463

2564
func (s *Server) listBoards(ctx context.Context, r *http.Request) (any, error) {

internal/http/routes.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func (s *Server) resourceHandlers() map[string]handler {
137137
func (s *Server) operationHandlers() map[string]handler {
138138
return map[string]handler{
139139
// Actors
140-
"actor_create": jsonBody(s.svc.CreateActor),
140+
"actor_create": s.createActor,
141141
"actor_update": s.updateActor,
142142

143143
// Boards

0 commit comments

Comments
 (0)