Feat v0.7.7 API tokens + extension permissions (#61)
Personal access tokens (PATs) for programmatic API access with SHA-256 hashing, permission scoping (git model), and Settings/Admin UI. Extension-declared user permissions with dynamic registry, gate_permission manifest field, permissions Starlark module, and grouped admin UI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.starlark.net/starlark"
|
||||
|
||||
"armature/auth"
|
||||
"armature/models"
|
||||
"armature/sandbox"
|
||||
"armature/store"
|
||||
@@ -125,8 +126,17 @@ func (h *ExtAPIHandler) Handle(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// ── 4b. Gate permission check ──────────────
|
||||
if gatePerm, _ := pkg.Manifest["gate_permission"].(string); gatePerm != "" {
|
||||
userPerms, err := auth.ResolvePermissions(c.Request.Context(), h.stores, getUserID(c))
|
||||
if err != nil || !userPerms[gatePerm] {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "permission required: " + gatePerm})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ── 5. Build request dict ──────────────────
|
||||
reqDict, err := buildRequestDict(c, rawPath)
|
||||
reqDict, err := buildRequestDict(c, rawPath, h.stores)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "failed to read request: " + err.Error()})
|
||||
return
|
||||
@@ -228,7 +238,7 @@ func matchAPIRoute(manifest map[string]any, method, path string) bool {
|
||||
// "body": "...",
|
||||
// "user_id": "uuid",
|
||||
// }
|
||||
func buildRequestDict(c *gin.Context, path string) (*starlark.Dict, error) {
|
||||
func buildRequestDict(c *gin.Context, path string, stores ...store.Stores) (*starlark.Dict, error) {
|
||||
// Read body (capped at 1 MB for safety)
|
||||
body, err := io.ReadAll(io.LimitReader(c.Request.Body, 1<<20))
|
||||
if err != nil {
|
||||
@@ -251,13 +261,24 @@ func buildRequestDict(c *gin.Context, path string) (*starlark.Dict, error) {
|
||||
}
|
||||
}
|
||||
|
||||
d := starlark.NewDict(6)
|
||||
d := starlark.NewDict(7)
|
||||
_ = d.SetKey(starlark.String("method"), starlark.String(c.Request.Method))
|
||||
_ = d.SetKey(starlark.String("path"), starlark.String(path))
|
||||
_ = d.SetKey(starlark.String("headers"), hdrs)
|
||||
_ = d.SetKey(starlark.String("query"), query)
|
||||
_ = d.SetKey(starlark.String("body"), starlark.String(string(body)))
|
||||
_ = d.SetKey(starlark.String("user_id"), starlark.String(getUserID(c)))
|
||||
|
||||
// Resolve user permissions for extension inline checks
|
||||
if len(stores) > 0 {
|
||||
userPerms, _ := auth.ResolvePermissions(c.Request.Context(), stores[0], getUserID(c))
|
||||
permList := make([]starlark.Value, 0, len(userPerms))
|
||||
for p := range userPerms {
|
||||
permList = append(permList, starlark.String(p))
|
||||
}
|
||||
_ = d.SetKey(starlark.String("permissions"), starlark.NewList(permList))
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user