Files
sandbox/internal/api/middleware_apikey.go
pptx704 c92cc29b88 Add authentication, authorization, and team-scoped access control
Implement email/password auth with JWT sessions and API key auth for
sandbox lifecycle. Users get a default team on signup; sandboxes,
snapshots, and API keys are scoped to teams.

- Add user, team, users_teams, and team_api_keys tables (goose migrations)
- Add JWT middleware (Bearer token) for user management endpoints
- Add API key middleware (X-API-Key header, SHA-256 hashed) for sandbox ops
- Add signup/login handlers with transactional user+team creation
- Add API key CRUD endpoints (create/list/delete)
- Replace owner_id with team_id on sandboxes and templates
- Update all handlers to use team-scoped queries
- Add godotenv for .env file loading
- Update OpenAPI spec and test UI with auth flows
2026-03-14 03:57:06 +06:00

39 lines
1.1 KiB
Go

package api
import (
"log/slog"
"net/http"
"git.omukk.dev/wrenn/sandbox/internal/auth"
"git.omukk.dev/wrenn/sandbox/internal/db"
)
// requireAPIKey validates the X-API-Key header, looks up the SHA-256 hash in DB,
// and stamps TeamID into the request context.
func requireAPIKey(queries *db.Queries) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
key := r.Header.Get("X-API-Key")
if key == "" {
writeError(w, http.StatusUnauthorized, "unauthorized", "X-API-Key header required")
return
}
hash := auth.HashAPIKey(key)
row, err := queries.GetAPIKeyByHash(r.Context(), hash)
if err != nil {
writeError(w, http.StatusUnauthorized, "unauthorized", "invalid API key")
return
}
// Best-effort update of last_used timestamp.
if err := queries.UpdateAPIKeyLastUsed(r.Context(), row.ID); err != nil {
slog.Warn("failed to update api key last_used", "key_id", row.ID, "error", err)
}
ctx := auth.WithAuthContext(r.Context(), auth.AuthContext{TeamID: row.TeamID})
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}