All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- Fix no-op RequireProjectAccess middleware to enforce project_ids
- Apply project access middleware to all project-scoped routes
- Filter GET /projects by allowed project IDs for restricted keys
- Add GET /me endpoint with key identity, scopes, and project access info
- Add PATCH /keys/{id} for partial key updates (name, scopes, project_ids, allowed_ips, expires_in)
- Add GET/POST/DELETE /projects/{id}/access for project-centric access management
- Auto-grant creating key access when using POST /project/create-and-build
- Accept grant_to_key_ids in create-and-build to grant multiple keys on project creation
- Move newProvisionerWithDeps test helper from production code to test file
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
100 lines
2.8 KiB
Go
100 lines
2.8 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/orchard9/rdev/internal/auth"
|
|
"github.com/orchard9/rdev/internal/domain"
|
|
"github.com/orchard9/rdev/internal/service"
|
|
"github.com/orchard9/rdev/pkg/api"
|
|
)
|
|
|
|
// MeHandler handles the /me endpoint.
|
|
type MeHandler struct {
|
|
authService *auth.Service
|
|
projectService *service.ProjectService
|
|
}
|
|
|
|
// NewMeHandler creates a new me handler.
|
|
func NewMeHandler(authService *auth.Service, projectService *service.ProjectService) *MeHandler {
|
|
return &MeHandler{
|
|
authService: authService,
|
|
projectService: projectService,
|
|
}
|
|
}
|
|
|
|
// Mount registers the /me route.
|
|
func (h *MeHandler) Mount(r api.Router) {
|
|
r.Get("/me", h.Get)
|
|
}
|
|
|
|
// MeResponse is the JSON response for GET /me.
|
|
type MeResponse struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
KeyPrefix string `json:"key_prefix"`
|
|
Scopes []string `json:"scopes"`
|
|
ProjectAccess string `json:"project_access"` // "unrestricted" | "restricted"
|
|
Projects []ProjectSummary `json:"projects,omitempty"`
|
|
AllowedIPs []string `json:"allowed_ips,omitempty"`
|
|
CreatedAt string `json:"created_at"`
|
|
ExpiresAt *string `json:"expires_at,omitempty"`
|
|
Active bool `json:"active"`
|
|
}
|
|
|
|
// ProjectSummary is a lightweight project view for embedding in /me.
|
|
type ProjectSummary struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Status string `json:"status"`
|
|
}
|
|
|
|
// Get returns the current key's identity, scopes, and project access.
|
|
// GET /me
|
|
func (h *MeHandler) Get(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), TimeoutFastLookup)
|
|
defer cancel()
|
|
|
|
apiKey := auth.GetAPIKey(ctx)
|
|
if apiKey == nil {
|
|
api.WriteUnauthorized(w, r, "Not authenticated")
|
|
return
|
|
}
|
|
|
|
resp := MeResponse{
|
|
ID: string(apiKey.ID),
|
|
Name: apiKey.Name,
|
|
KeyPrefix: apiKey.KeyPrefix,
|
|
Scopes: auth.ScopesToStrings(apiKey.Scopes),
|
|
ProjectAccess: "unrestricted",
|
|
AllowedIPs: apiKey.AllowedIPs,
|
|
CreatedAt: apiKey.CreatedAt.Format(time.RFC3339),
|
|
Active: apiKey.IsActive(),
|
|
}
|
|
|
|
// Populate projects list when key is restricted (non-admin with explicit project_ids)
|
|
if apiKey.ProjectIDs != nil && !apiKey.HasScope(domain.ScopeAdmin) {
|
|
resp.ProjectAccess = "restricted"
|
|
if h.projectService != nil {
|
|
projects, _ := h.projectService.List(ctx, apiKey.ProjectIDs)
|
|
resp.Projects = make([]ProjectSummary, len(projects))
|
|
for i, p := range projects {
|
|
resp.Projects[i] = ProjectSummary{
|
|
ID: string(p.ID),
|
|
Name: p.Name,
|
|
Status: string(p.Status),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if apiKey.ExpiresAt != nil {
|
|
s := apiKey.ExpiresAt.Format(time.RFC3339)
|
|
resp.ExpiresAt = &s
|
|
}
|
|
|
|
api.WriteSuccess(w, r, resp)
|
|
}
|