Initial vibeproxy implementation
Self-contained HTTP/HTTPS/TCP/UDP reverse proxy manager written in Go, with SQLite storage, a Bootstrap 5 admin UI (Go templates, no JS framework), and a REST API under /api/v1. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QTXFrNP1imCW7jtfL5SZrw
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/owen/vibeproxy/internal/auth"
|
||||
"github.com/owen/vibeproxy/internal/database"
|
||||
)
|
||||
|
||||
func (a *API) listTokens(w http.ResponseWriter, r *http.Request) {
|
||||
tokens, err := database.ListAPITokens(a.db)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
// Never expose the stored hash.
|
||||
type dto struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Prefix string `json:"prefix"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
LastUsedAt string `json:"last_used_at,omitempty"`
|
||||
Revoked bool `json:"revoked"`
|
||||
}
|
||||
out := make([]dto, 0, len(tokens))
|
||||
for _, t := range tokens {
|
||||
d := dto{ID: t.ID, Name: t.Name, Prefix: t.Prefix, CreatedAt: t.CreatedAt.Format("2006-01-02T15:04:05Z"), Revoked: t.RevokedAt != nil}
|
||||
if t.LastUsedAt != nil {
|
||||
d.LastUsedAt = t.LastUsedAt.Format("2006-01-02T15:04:05Z")
|
||||
}
|
||||
out = append(out, d)
|
||||
}
|
||||
writeJSON(w, http.StatusOK, out)
|
||||
}
|
||||
|
||||
func (a *API) createToken(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if !decodeJSON(w, r, &req) {
|
||||
return
|
||||
}
|
||||
if req.Name == "" {
|
||||
writeError(w, http.StatusBadRequest, "name is required")
|
||||
return
|
||||
}
|
||||
plaintext, hash, prefix, err := auth.GenerateAPIToken()
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
id, err := database.CreateAPIToken(a.db, req.Name, hash, prefix)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
audit(a.db, r, "token_created", req.Name, "")
|
||||
writeJSON(w, http.StatusCreated, map[string]any{
|
||||
"id": id,
|
||||
"name": req.Name,
|
||||
"token": plaintext, // shown once; never retrievable again
|
||||
})
|
||||
}
|
||||
|
||||
func (a *API) revokeToken(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := pathID(r)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid id")
|
||||
return
|
||||
}
|
||||
if err := database.RevokeAPIToken(a.db, id); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
audit(a.db, r, "token_revoked", "", "")
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
Reference in New Issue
Block a user