completed phase 2b
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/forgeo/forgebucket/internal/api/middleware"
|
||||
"github.com/forgeo/forgebucket/internal/models"
|
||||
)
|
||||
|
||||
type RunnerHandler struct{ db *xorm.Engine }
|
||||
|
||||
func NewRunnerHandler(db *xorm.Engine) *RunnerHandler { return &RunnerHandler{db: db} }
|
||||
|
||||
// List returns all registered runners. Admin-only.
|
||||
func (h *RunnerHandler) List(w http.ResponseWriter, r *http.Request) {
|
||||
if !isAdmin(r) {
|
||||
jsonError(w, "admin access required", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
var runners []models.Runner
|
||||
if err := h.db.Find(&runners); err != nil {
|
||||
jsonError(w, "could not list runners", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if runners == nil {
|
||||
runners = []models.Runner{}
|
||||
}
|
||||
jsonOK(w, runners)
|
||||
}
|
||||
|
||||
// Register creates a new runner record and returns the plaintext registration token
|
||||
// (shown once; the server stores only the bcrypt hash).
|
||||
func (h *RunnerHandler) Register(w http.ResponseWriter, r *http.Request) {
|
||||
if !isAdmin(r) {
|
||||
jsonError(w, "admin access required", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
var body struct {
|
||||
Name string `json:"name"`
|
||||
Labels []string `json:"labels"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
jsonError(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if body.Name == "" {
|
||||
jsonError(w, "name is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
raw := make([]byte, 32)
|
||||
if _, err := rand.Read(raw); err != nil {
|
||||
jsonError(w, "could not generate token", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
token := base64.RawURLEncoding.EncodeToString(raw)
|
||||
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(token), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
jsonError(w, "could not hash token", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
labelsJSON, _ := json.Marshal(body.Labels)
|
||||
runner := &models.Runner{
|
||||
Name: body.Name,
|
||||
Labels: string(labelsJSON),
|
||||
Status: "idle",
|
||||
TokenHash: string(hash),
|
||||
}
|
||||
if _, err := h.db.Insert(runner); err != nil {
|
||||
jsonError(w, "runner name already taken", http.StatusConflict)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
jsonOK(w, map[string]any{
|
||||
"id": runner.ID,
|
||||
"name": runner.Name,
|
||||
"token": token, // shown once — store it securely
|
||||
})
|
||||
}
|
||||
|
||||
func isAdmin(r *http.Request) bool {
|
||||
v, _ := r.Context().Value(middleware.ContextKeyIsAdmin).(bool)
|
||||
return v
|
||||
}
|
||||
Reference in New Issue
Block a user