feat(server): org-scope service layer + handlers + org admin API

Threads org_id through every admin-facing service function (servers, keys,
assignments, secrets, workflows/steps/runs, monitors, channels, audit),
adds RequireRole middleware, and wires /api/org user + OIDC management
routes. Agent/scheduler paths keep unique-key signatures and resolve org
from the loaded record; internal-only helpers (getServerByID,
getRunByID, getMonitorByID) preserve those call sites.
This commit is contained in:
2026-07-21 16:56:39 +01:00
parent d0ed9885e7
commit 850aa0ed05
21 changed files with 451 additions and 209 deletions
+6 -5
View File
@@ -4,6 +4,7 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/mrhid6/vantage/server/internal/auth"
"github.com/mrhid6/vantage/server/internal/models"
"github.com/mrhid6/vantage/server/internal/services"
"go.mongodb.org/mongo-driver/v2/bson"
@@ -18,7 +19,7 @@ func registerChannelRoutes(g *gin.RouterGroup) {
}
func listChannels(c *gin.Context) {
channels, err := services.ListChannels()
channels, err := services.ListChannels(auth.OrgID(c))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@@ -36,7 +37,7 @@ func createChannel(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "name and type are required"})
return
}
created, err := services.CreateChannel(&ch)
created, err := services.CreateChannel(auth.OrgID(c), &ch)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@@ -72,7 +73,7 @@ func updateChannel(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "no fields to update"})
return
}
if err := services.UpdateChannel(c.Param("id"), upd); err != nil {
if err := services.UpdateChannel(auth.OrgID(c), c.Param("id"), upd); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
@@ -80,7 +81,7 @@ func updateChannel(c *gin.Context) {
}
func deleteChannel(c *gin.Context) {
if err := services.DeleteChannel(c.Param("id")); err != nil {
if err := services.DeleteChannel(auth.OrgID(c), c.Param("id")); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
@@ -88,7 +89,7 @@ func deleteChannel(c *gin.Context) {
}
func testChannel(c *gin.Context) {
if err := services.TestChannel(c.Param("id")); err != nil {
if err := services.TestChannel(auth.OrgID(c), c.Param("id")); err != nil {
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
return
}
+5 -4
View File
@@ -8,6 +8,7 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/mrhid6/vantage/server/internal/auth"
"github.com/mrhid6/vantage/server/internal/services"
"github.com/wwt/guac"
)
@@ -29,7 +30,7 @@ func consoleConnect(c *gin.Context) {
return
}
srv, err := services.GetServer(body.ServerID)
srv, err := services.GetServer(auth.OrgID(c), body.ServerID)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "server not found"})
return
@@ -60,7 +61,7 @@ func consoleConnect(c *gin.Context) {
}
}
services.LogEvent("console.opened", actorFromCtx(c), srv.ServerID, "",
services.LogEvent(auth.OrgID(c), "console.opened", actorFromCtx(c), srv.ServerID, "",
"console session opened ("+body.Protocol+")")
c.JSON(http.StatusOK, gin.H{
@@ -107,7 +108,7 @@ func consoleTunnel(c *gin.Context) {
return
}
srv, err := services.GetServer(sess.ServerID)
srv, err := services.GetServer(auth.OrgID(c), sess.ServerID)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "server not found"})
return
@@ -116,7 +117,7 @@ func consoleTunnel(c *gin.Context) {
// Decrypt private key + passphrase in-memory only (ssh).
var privKey, passphrase string
if sess.Protocol == "ssh" && sess.KeyID != "" {
privKey, err = services.GetPrivateKey(sess.KeyID)
privKey, err = services.GetPrivateKey(auth.OrgID(c), sess.KeyID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "selected key has no private material"})
return
+41 -30
View File
@@ -85,11 +85,22 @@ func RegisterRoutes(r *gin.Engine) {
registerWorkflowRoutes(apiGroup)
registerMonitorRoutes(apiGroup)
registerChannelRoutes(apiGroup)
org := apiGroup.Group("/org")
org.Use(auth.RequireRole("owner", "admin"))
{
org.GET("/users", listOrgUsers)
org.POST("/users", createOrgUser)
org.PUT("/users/:id/role", updateOrgUserRole)
org.DELETE("/users/:id", deleteOrgUser)
org.GET("/oidc", getOrgOIDC)
org.PUT("/oidc", putOrgOIDC)
}
}
}
func listServers(c *gin.Context) {
servers, err := services.ListServers()
servers, err := services.ListServers(auth.OrgID(c))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@@ -98,7 +109,7 @@ func listServers(c *gin.Context) {
}
func createServer(c *gin.Context) {
s, token, err := services.CreateServer()
s, token, err := services.CreateServer(auth.OrgID(c))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@@ -111,12 +122,12 @@ func createServer(c *gin.Context) {
}
func newServer(c *gin.Context) {
s, token, err := services.CreateServer()
s, token, err := services.CreateServer(auth.OrgID(c))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
services.LogEvent("server.created", actorFromCtx(c), s.ServerID, "", "pre-registration token issued")
services.LogEvent(auth.OrgID(c), "server.created", actorFromCtx(c), s.ServerID, "", "pre-registration token issued")
giteaHost := os.Getenv("GITEA_HOST")
if giteaHost == "" {
@@ -147,13 +158,13 @@ func newServer(c *gin.Context) {
func getServer(c *gin.Context) {
id := c.Param("id")
s, err := services.GetServer(id)
s, err := services.GetServer(auth.OrgID(c), id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "server not found"})
return
}
assignments, _ := services.GetAssignmentsWithKeysForServer(id)
assignments, _ := services.GetAssignmentsWithKeysForServer(auth.OrgID(c), id)
// Build response matching ServerWithKeys shape expected by frontend
type serverResponse struct {
@@ -168,8 +179,8 @@ func getServer(c *gin.Context) {
func deleteServer(c *gin.Context) {
id := c.Param("id")
s, _ := services.GetServer(id)
if err := services.DeleteServer(id); err != nil {
s, _ := services.GetServer(auth.OrgID(c), id)
if err := services.DeleteServer(auth.OrgID(c), id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
@@ -177,7 +188,7 @@ func deleteServer(c *gin.Context) {
if s != nil {
hostname = s.Hostname
}
services.LogEvent("server.deleted", actorFromCtx(c), id, "", fmt.Sprintf("server %s deleted", hostname))
services.LogEvent(auth.OrgID(c), "server.deleted", actorFromCtx(c), id, "", fmt.Sprintf("server %s deleted", hostname))
c.JSON(http.StatusOK, gin.H{"deleted": true})
}
@@ -196,7 +207,7 @@ func generateKey(c *gin.Context) {
body.Label = "generated"
}
s, err := services.GetServer(id)
s, err := services.GetServer(auth.OrgID(c), id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "server not found"})
return
@@ -214,7 +225,7 @@ func generateKey(c *gin.Context) {
return
}
services.LogEvent("key.generation_dispatched", actorFromCtx(c), s.ServerID, "", fmt.Sprintf("key generation dispatched (label=%s type=%s)", body.Label, body.KeyType))
services.LogEvent(auth.OrgID(c), "key.generation_dispatched", actorFromCtx(c), s.ServerID, "", fmt.Sprintf("key generation dispatched (label=%s type=%s)", body.Label, body.KeyType))
c.JSON(http.StatusAccepted, gin.H{
"message": "key generation command sent to agent",
"command_id": cmdID,
@@ -223,7 +234,7 @@ func generateKey(c *gin.Context) {
}
func listKeys(c *gin.Context) {
keys, err := services.ListKeys()
keys, err := services.ListKeys(auth.OrgID(c))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@@ -243,18 +254,18 @@ func createKey(c *gin.Context) {
return
}
key, err := services.CreateKey(body.Label, body.PublicKey, "uploaded", "", body.PrivateKey, body.Passphrase)
key, err := services.CreateKey(auth.OrgID(c), body.Label, body.PublicKey, "uploaded", "", body.PrivateKey, body.Passphrase)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
services.LogEvent("key.uploaded", actorFromCtx(c), "", key.KeyID, fmt.Sprintf("key '%s' uploaded", key.Label))
services.LogEvent(auth.OrgID(c), "key.uploaded", actorFromCtx(c), "", key.KeyID, fmt.Sprintf("key '%s' uploaded", key.Label))
c.JSON(http.StatusCreated, key)
}
func getPrivateKey(c *gin.Context) {
id := c.Param("id")
plaintext, err := services.GetPrivateKey(id)
plaintext, err := services.GetPrivateKey(auth.OrgID(c), id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
return
@@ -264,13 +275,13 @@ func getPrivateKey(c *gin.Context) {
func getKey(c *gin.Context) {
id := c.Param("id")
key, err := services.GetKey(id)
key, err := services.GetKey(auth.OrgID(c), id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "key not found"})
return
}
assignments, _ := services.GetAssignmentsWithServers(id)
assignments, _ := services.GetAssignmentsWithServers(auth.OrgID(c), id)
type keyResponse struct {
*models.Key
@@ -284,8 +295,8 @@ func getKey(c *gin.Context) {
func deleteKey(c *gin.Context) {
id := c.Param("id")
k, _ := services.GetKey(id)
if err := services.DeleteKey(id); err != nil {
k, _ := services.GetKey(auth.OrgID(c), id)
if err := services.DeleteKey(auth.OrgID(c), id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
@@ -293,7 +304,7 @@ func deleteKey(c *gin.Context) {
if k != nil {
label = k.Label
}
services.LogEvent("key.deleted", actorFromCtx(c), "", id, fmt.Sprintf("key '%s' deleted", label))
services.LogEvent(auth.OrgID(c), "key.deleted", actorFromCtx(c), "", id, fmt.Sprintf("key '%s' deleted", label))
c.JSON(http.StatusOK, gin.H{"deleted": true})
}
@@ -307,12 +318,12 @@ func assignKey(c *gin.Context) {
return
}
a, err := services.AssignKey(keyID, body.ServerID)
a, err := services.AssignKey(auth.OrgID(c), keyID, body.ServerID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
services.LogEvent("key.assigned", actorFromCtx(c), body.ServerID, keyID, fmt.Sprintf("key %s assigned to server %s", keyID, body.ServerID))
services.LogEvent(auth.OrgID(c), "key.assigned", actorFromCtx(c), body.ServerID, keyID, fmt.Sprintf("key %s assigned to server %s", keyID, body.ServerID))
c.JSON(http.StatusCreated, a)
}
@@ -320,11 +331,11 @@ func revokeAssignment(c *gin.Context) {
keyID := c.Param("id")
serverID := c.Param("serverId")
if err := services.RevokeAssignment(keyID, serverID); err != nil {
if err := services.RevokeAssignment(auth.OrgID(c), keyID, serverID); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
services.LogEvent("key.revoked", actorFromCtx(c), serverID, keyID, fmt.Sprintf("key %s revoked from server %s", keyID, serverID))
services.LogEvent(auth.OrgID(c), "key.revoked", actorFromCtx(c), serverID, keyID, fmt.Sprintf("key %s revoked from server %s", keyID, serverID))
c.JSON(http.StatusOK, gin.H{"revoked": true})
}
@@ -339,7 +350,7 @@ func getLatestAgentVersion(c *gin.Context) {
func updateAgent(c *gin.Context) {
id := c.Param("id")
s, err := services.GetServer(id)
s, err := services.GetServer(auth.OrgID(c), id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "server not found"})
return
@@ -350,7 +361,7 @@ func updateAgent(c *gin.Context) {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": err.Error()})
return
}
services.LogEvent("agent.update_dispatched", actorFromCtx(c), s.ServerID, "", fmt.Sprintf("agent update dispatched to %s (version %s)", s.Hostname, version))
services.LogEvent(auth.OrgID(c), "agent.update_dispatched", actorFromCtx(c), s.ServerID, "", fmt.Sprintf("agent update dispatched to %s (version %s)", s.Hostname, version))
c.JSON(http.StatusAccepted, gin.H{
"message": "update command sent to agent",
"version": version,
@@ -359,7 +370,7 @@ func updateAgent(c *gin.Context) {
func applyUpdates(c *gin.Context) {
id := c.Param("id")
s, err := services.GetServer(id)
s, err := services.GetServer(auth.OrgID(c), id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "server not found"})
return
@@ -369,7 +380,7 @@ func applyUpdates(c *gin.Context) {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": err.Error()})
return
}
services.LogEvent("updates.applied", actorFromCtx(c), s.ServerID, "", fmt.Sprintf("package update command dispatched to %s", s.Hostname))
services.LogEvent(auth.OrgID(c), "updates.applied", actorFromCtx(c), s.ServerID, "", fmt.Sprintf("package update command dispatched to %s", s.Hostname))
c.JSON(http.StatusAccepted, gin.H{"message": "apply updates command sent to agent"})
}
@@ -436,7 +447,7 @@ func listAuditEvents(c *gin.Context) {
limit = n
}
}
events, err := services.ListAuditEvents(limit)
events, err := services.ListAuditEvents(auth.OrgID(c), limit)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@@ -467,7 +478,7 @@ func saveSettings(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
services.LogEvent("settings.updated", actorFromCtx(c), "", "", "alert settings updated")
services.LogEvent(auth.OrgID(c), "settings.updated", actorFromCtx(c), "", "", "alert settings updated")
c.JSON(http.StatusOK, gin.H{"saved": true})
}
+6 -5
View File
@@ -5,6 +5,7 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/mrhid6/vantage/server/internal/auth"
"github.com/mrhid6/vantage/server/internal/models"
"github.com/mrhid6/vantage/server/internal/services"
"go.mongodb.org/mongo-driver/v2/bson"
@@ -21,7 +22,7 @@ func registerMonitorRoutes(g *gin.RouterGroup) {
}
func listMonitors(c *gin.Context) {
monitors, err := services.ListMonitors()
monitors, err := services.ListMonitors(auth.OrgID(c))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@@ -39,7 +40,7 @@ func createMonitor(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "name and type are required"})
return
}
created, err := services.CreateMonitor(&m)
created, err := services.CreateMonitor(auth.OrgID(c), &m)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@@ -48,7 +49,7 @@ func createMonitor(c *gin.Context) {
}
func getMonitor(c *gin.Context) {
m, err := services.GetMonitor(c.Param("id"))
m, err := services.GetMonitor(auth.OrgID(c), c.Param("id"))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@@ -104,7 +105,7 @@ func updateMonitor(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "no fields to update"})
return
}
if err := services.UpdateMonitor(c.Param("id"), upd); err != nil {
if err := services.UpdateMonitor(auth.OrgID(c), c.Param("id"), upd); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
@@ -112,7 +113,7 @@ func updateMonitor(c *gin.Context) {
}
func deleteMonitor(c *gin.Context) {
if err := services.DeleteMonitor(c.Param("id")); err != nil {
if err := services.DeleteMonitor(auth.OrgID(c), c.Param("id")); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
+89
View File
@@ -0,0 +1,89 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/mrhid6/vantage/server/internal/auth"
"github.com/mrhid6/vantage/server/internal/services"
)
func listOrgUsers(c *gin.Context) {
users, err := services.ListUsers(auth.OrgID(c))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, users)
}
func createOrgUser(c *gin.Context) {
var body struct {
Email string `json:"email"`
Password string `json:"password"`
Role string `json:"role"`
}
if err := c.ShouldBindJSON(&body); err != nil || body.Email == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "email required"})
return
}
if body.Role == "" {
body.Role = "member"
}
u, err := services.CreateUser(auth.OrgID(c), body.Email, body.Password, body.Role, "local")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, u)
}
func updateOrgUserRole(c *gin.Context) {
var body struct {
Role string `json:"role"`
}
if err := c.ShouldBindJSON(&body); err != nil || body.Role == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "role required"})
return
}
if err := services.UpdateUserRole(auth.OrgID(c), c.Param("id"), body.Role); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"ok": true})
}
func deleteOrgUser(c *gin.Context) {
if err := services.DeleteUser(auth.OrgID(c), c.Param("id")); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"deleted": true})
}
func getOrgOIDC(c *gin.Context) {
cfg, err := services.GetOrgOIDC(auth.OrgID(c))
if err != nil {
c.JSON(http.StatusOK, gin.H{"enabled": false})
return
}
c.JSON(http.StatusOK, cfg)
}
func putOrgOIDC(c *gin.Context) {
var body struct {
Issuer string `json:"issuer"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
Enabled bool `json:"enabled"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := services.SaveOrgOIDC(auth.OrgID(c), body.Issuer, body.ClientID, body.ClientSecret, body.Enabled); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"saved": true})
}
+15 -14
View File
@@ -7,6 +7,7 @@ import (
"strings"
"github.com/gin-gonic/gin"
"github.com/mrhid6/vantage/server/internal/auth"
"github.com/mrhid6/vantage/server/internal/services"
)
@@ -40,7 +41,7 @@ func secretsReadAuth() gin.HandlerFunc {
// (ESO treats 404 as "deleted").
func esoGetGroup(c *gin.Context) {
group := c.Param("group")
values, err := services.GetSecretGroupDecrypted(group)
values, err := services.GetSecretGroupDecryptedAny(group)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "store error"})
return
@@ -53,7 +54,7 @@ func esoGetGroup(c *gin.Context) {
}
func listSecretGroups(c *gin.Context) {
groups, err := services.ListSecretGroups()
groups, err := services.ListSecretGroups(auth.OrgID(c))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@@ -86,17 +87,17 @@ func createSecretGroup(c *gin.Context) {
return
}
}
if err := services.UpsertSecrets(body.Group, body.Values); err != nil {
if err := services.UpsertSecrets(auth.OrgID(c), body.Group, body.Values); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
services.LogEvent("secret.updated", actorFromCtx(c), "", "", fmt.Sprintf("group '%s' created with keys: %s", body.Group, strings.Join(services.SortedKeys(body.Values), ", ")))
services.LogEvent(auth.OrgID(c), "secret.updated", actorFromCtx(c), "", "", fmt.Sprintf("group '%s' created with keys: %s", body.Group, strings.Join(services.SortedKeys(body.Values), ", ")))
c.JSON(http.StatusCreated, gin.H{"group": body.Group})
}
func getSecretGroup(c *gin.Context) {
group := c.Param("group")
secrets, err := services.GetSecretGroup(group)
secrets, err := services.GetSecretGroup(auth.OrgID(c), group)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@@ -130,11 +131,11 @@ func putSecretGroup(c *gin.Context) {
return
}
}
if err := services.UpsertSecrets(group, values); err != nil {
if err := services.UpsertSecrets(auth.OrgID(c), group, values); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
services.LogEvent("secret.updated", actorFromCtx(c), "", "", fmt.Sprintf("group '%s' keys updated: %s", group, strings.Join(services.SortedKeys(values), ", ")))
services.LogEvent(auth.OrgID(c), "secret.updated", actorFromCtx(c), "", "", fmt.Sprintf("group '%s' keys updated: %s", group, strings.Join(services.SortedKeys(values), ", ")))
c.JSON(http.StatusOK, gin.H{"saved": true})
}
@@ -147,33 +148,33 @@ func revealSecret(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
value, err := services.RevealSecret(group, body.Key)
value, err := services.RevealSecret(auth.OrgID(c), group, body.Key)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
return
}
services.LogEvent("secret.revealed", actorFromCtx(c), "", "", fmt.Sprintf("value of '%s/%s' revealed", group, body.Key))
services.LogEvent(auth.OrgID(c), "secret.revealed", actorFromCtx(c), "", "", fmt.Sprintf("value of '%s/%s' revealed", group, body.Key))
c.JSON(http.StatusOK, gin.H{"value": value})
}
func deleteSecretKey(c *gin.Context) {
group := c.Param("group")
key := c.Param("key")
if err := services.DeleteSecret(group, key); err != nil {
if err := services.DeleteSecret(auth.OrgID(c), group, key); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
services.LogEvent("secret.deleted", actorFromCtx(c), "", "", fmt.Sprintf("key '%s' deleted from group '%s'", key, group))
services.LogEvent(auth.OrgID(c), "secret.deleted", actorFromCtx(c), "", "", fmt.Sprintf("key '%s' deleted from group '%s'", key, group))
c.JSON(http.StatusOK, gin.H{"deleted": true})
}
func deleteSecretGroup(c *gin.Context) {
group := c.Param("group")
if err := services.DeleteSecretGroup(group); err != nil {
if err := services.DeleteSecretGroup(auth.OrgID(c), group); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
services.LogEvent("secretgroup.deleted", actorFromCtx(c), "", "", fmt.Sprintf("group '%s' deleted", group))
services.LogEvent(auth.OrgID(c), "secretgroup.deleted", actorFromCtx(c), "", "", fmt.Sprintf("group '%s' deleted", group))
c.JSON(http.StatusOK, gin.H{"deleted": true})
}
@@ -183,6 +184,6 @@ func rotateSecretsToken(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
services.LogEvent("secrets.token_rotated", actorFromCtx(c), "", "", "ESO read token rotated")
services.LogEvent(auth.OrgID(c), "secrets.token_rotated", actorFromCtx(c), "", "", "ESO read token rotated")
c.JSON(http.StatusOK, gin.H{"token": token})
}
+33 -31
View File
@@ -11,6 +11,7 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/mrhid6/vantage/server/internal/auth"
"github.com/mrhid6/vantage/server/internal/models"
"github.com/mrhid6/vantage/server/internal/services"
)
@@ -105,9 +106,10 @@ func streamServerRunLog(c *gin.Context) {
ctx := c.Request.Context()
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
orgID := auth.OrgID(c)
for {
sendNew()
if serverRunTerminal(runID, serverID) {
if serverRunTerminal(orgID, runID, serverID) {
sendNew() // final drain
_, _ = c.Writer.WriteString("event: done\ndata: end\n\n")
flusher.Flush()
@@ -122,8 +124,8 @@ func streamServerRunLog(c *gin.Context) {
}
// serverRunTerminal reports whether the given server-run has reached a terminal status.
func serverRunTerminal(runID, serverID string) bool {
r, err := services.GetRun(runID)
func serverRunTerminal(orgID, runID, serverID string) bool {
r, err := services.GetRun(orgID, runID)
if err != nil {
return true
}
@@ -147,7 +149,7 @@ func splitSSE(b []byte) []string {
}
func listSteps(c *gin.Context) {
steps, err := services.ListSteps()
steps, err := services.ListSteps(auth.OrgID(c))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@@ -156,7 +158,7 @@ func listSteps(c *gin.Context) {
}
func stepUsage(c *gin.Context) {
counts, err := services.StepUsageCounts()
counts, err := services.StepUsageCounts(auth.OrgID(c))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@@ -170,12 +172,12 @@ func createStep(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
out, err := services.CreateStep(s)
out, err := services.CreateStep(auth.OrgID(c), s)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
services.LogEvent("workflow.step_created", actorFromCtx(c), "", out.StepID, fmt.Sprintf("step '%s' created", out.Name))
services.LogEvent(auth.OrgID(c), "workflow.step_created", actorFromCtx(c), "", out.StepID, fmt.Sprintf("step '%s' created", out.Name))
c.JSON(http.StatusCreated, out)
}
@@ -185,25 +187,25 @@ func updateStep(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := services.UpdateStep(c.Param("id"), s); err != nil {
if err := services.UpdateStep(auth.OrgID(c), c.Param("id"), s); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
services.LogEvent("workflow.step_updated", actorFromCtx(c), "", c.Param("id"), "step updated")
services.LogEvent(auth.OrgID(c), "workflow.step_updated", actorFromCtx(c), "", c.Param("id"), "step updated")
c.JSON(http.StatusOK, gin.H{"updated": true})
}
func deleteStep(c *gin.Context) {
if err := services.DeleteStep(c.Param("id")); err != nil {
if err := services.DeleteStep(auth.OrgID(c), c.Param("id")); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
services.LogEvent("workflow.step_deleted", actorFromCtx(c), "", c.Param("id"), "step deleted")
services.LogEvent(auth.OrgID(c), "workflow.step_deleted", actorFromCtx(c), "", c.Param("id"), "step deleted")
c.JSON(http.StatusOK, gin.H{"deleted": true})
}
func exportStep(c *gin.Context) {
b, err := services.ExportStep(c.Param("id"))
b, err := services.ExportStep(auth.OrgID(c), c.Param("id"))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
return
@@ -213,12 +215,12 @@ func exportStep(c *gin.Context) {
}
func seedDefaults(c *gin.Context) {
created, updated, err := services.SeedDefaultSteps()
created, updated, err := services.SeedDefaultSteps(auth.OrgID(c))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
services.LogEvent("workflow.defaults_synced", actorFromCtx(c), "", "", fmt.Sprintf("default steps synced: %d created, %d updated", created, updated))
services.LogEvent(auth.OrgID(c), "workflow.defaults_synced", actorFromCtx(c), "", "", fmt.Sprintf("default steps synced: %d created, %d updated", created, updated))
c.JSON(http.StatusOK, gin.H{"created": created, "updated": updated})
}
@@ -231,12 +233,12 @@ func importStep(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
out, err := services.ImportStepToLibrary(body)
out, err := services.ImportStepToLibrary(auth.OrgID(c), body)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
services.LogEvent("workflow.step_imported", actorFromCtx(c), "", out.StepID, fmt.Sprintf("step '%s' imported", out.Name))
services.LogEvent(auth.OrgID(c), "workflow.step_imported", actorFromCtx(c), "", out.StepID, fmt.Sprintf("step '%s' imported", out.Name))
c.JSON(http.StatusCreated, out)
}
@@ -258,7 +260,7 @@ func parseStep(c *gin.Context) {
}
func listWorkflows(c *gin.Context) {
wfs, err := services.ListWorkflows()
wfs, err := services.ListWorkflows(auth.OrgID(c))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@@ -272,17 +274,17 @@ func createWorkflow(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
out, err := services.CreateWorkflow(w)
out, err := services.CreateWorkflow(auth.OrgID(c), w)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
services.LogEvent("workflow.created", actorFromCtx(c), "", out.WorkflowID, fmt.Sprintf("workflow '%s' created", out.Name))
services.LogEvent(auth.OrgID(c), "workflow.created", actorFromCtx(c), "", out.WorkflowID, fmt.Sprintf("workflow '%s' created", out.Name))
c.JSON(http.StatusCreated, out)
}
func getWorkflow(c *gin.Context) {
w, err := services.GetWorkflow(c.Param("id"))
w, err := services.GetWorkflow(auth.OrgID(c), c.Param("id"))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
return
@@ -296,12 +298,12 @@ func updateWorkflow(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := services.UpdateWorkflow(c.Param("id"), w); err != nil {
if err := services.UpdateWorkflow(auth.OrgID(c), c.Param("id"), w); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
services.LogEvent("workflow.updated", actorFromCtx(c), "", c.Param("id"), "workflow updated")
updated, err := services.GetWorkflow(c.Param("id"))
services.LogEvent(auth.OrgID(c), "workflow.updated", actorFromCtx(c), "", c.Param("id"), "workflow updated")
updated, err := services.GetWorkflow(auth.OrgID(c), c.Param("id"))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@@ -310,21 +312,21 @@ func updateWorkflow(c *gin.Context) {
}
func deleteWorkflow(c *gin.Context) {
if err := services.DeleteWorkflow(c.Param("id")); err != nil {
if err := services.DeleteWorkflow(auth.OrgID(c), c.Param("id")); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
services.LogEvent("workflow.deleted", actorFromCtx(c), "", c.Param("id"), "workflow deleted")
services.LogEvent(auth.OrgID(c), "workflow.deleted", actorFromCtx(c), "", c.Param("id"), "workflow deleted")
c.JSON(http.StatusOK, gin.H{"deleted": true})
}
func runWorkflow(c *gin.Context) {
runID, err := services.TriggerWorkflow(c.Param("id"), actorFromCtx(c))
runID, err := services.TriggerWorkflow(auth.OrgID(c), c.Param("id"), actorFromCtx(c))
if err != nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": err.Error()})
return
}
services.LogEvent("workflow.run_triggered", actorFromCtx(c), "", c.Param("id"), fmt.Sprintf("run %s triggered", runID))
services.LogEvent(auth.OrgID(c), "workflow.run_triggered", actorFromCtx(c), "", c.Param("id"), fmt.Sprintf("run %s triggered", runID))
c.JSON(http.StatusAccepted, gin.H{"run_id": runID})
}
@@ -335,7 +337,7 @@ func listWorkflowRuns(c *gin.Context) {
limit = n
}
}
runs, err := services.ListRuns(c.Param("id"), limit)
runs, err := services.ListRuns(auth.OrgID(c), c.Param("id"), limit)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@@ -344,7 +346,7 @@ func listWorkflowRuns(c *gin.Context) {
}
func getRun(c *gin.Context) {
r, err := services.GetRun(c.Param("runId"))
r, err := services.GetRun(auth.OrgID(c), c.Param("runId"))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
return
@@ -353,10 +355,10 @@ func getRun(c *gin.Context) {
}
func cancelRun(c *gin.Context) {
if err := services.CancelRun(c.Param("runId")); err != nil {
if err := services.CancelRun(auth.OrgID(c), c.Param("runId")); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
services.LogEvent("workflow.run_cancelled", actorFromCtx(c), "", c.Param("runId"), "run cancelled")
services.LogEvent(auth.OrgID(c), "workflow.run_cancelled", actorFromCtx(c), "", c.Param("runId"), "run cancelled")
c.JSON(http.StatusOK, gin.H{"cancelled": true})
}