Changeset 0.28.4 (#190)
This commit is contained in:
@@ -22,16 +22,23 @@ import (
|
||||
)
|
||||
|
||||
type AdminHandler struct {
|
||||
stores store.Stores
|
||||
vault *crypto.KeyResolver
|
||||
uekCache *crypto.UEKCache
|
||||
objStore storage.ObjectStore
|
||||
stores store.Stores
|
||||
vault *crypto.KeyResolver
|
||||
uekCache *crypto.UEKCache
|
||||
objStore storage.ObjectStore
|
||||
onUserChanged func(userID string) // auth cache eviction callback
|
||||
}
|
||||
|
||||
func NewAdminHandler(s store.Stores, vault *crypto.KeyResolver, uekCache *crypto.UEKCache, objStore storage.ObjectStore) *AdminHandler {
|
||||
return &AdminHandler{stores: s, vault: vault, uekCache: uekCache, objStore: objStore}
|
||||
}
|
||||
|
||||
// OnUserChanged registers a callback invoked when a user's role or
|
||||
// active status changes. Used to evict the auth middleware cache.
|
||||
func (h *AdminHandler) OnUserChanged(fn func(userID string)) {
|
||||
h.onUserChanged = fn
|
||||
}
|
||||
|
||||
// ── User Management ─────────────────────────
|
||||
|
||||
func (h *AdminHandler) ListUsers(c *gin.Context) {
|
||||
@@ -135,6 +142,9 @@ func (h *AdminHandler) UpdateUserRole(c *gin.Context) {
|
||||
}
|
||||
|
||||
h.auditLog(c, "user.role_change", "user", id, gin.H{"role": req.Role})
|
||||
if h.onUserChanged != nil {
|
||||
h.onUserChanged(id)
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "role updated"})
|
||||
}
|
||||
|
||||
@@ -154,6 +164,9 @@ func (h *AdminHandler) ToggleUserActive(c *gin.Context) {
|
||||
}
|
||||
|
||||
h.auditLog(c, "user.active_change", "user", id, gin.H{"is_active": req.IsActive})
|
||||
if h.onUserChanged != nil {
|
||||
h.onUserChanged(id)
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "user status updated"})
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ func setupExtensionHarness(t *testing.T) *extensionHarness {
|
||||
} else {
|
||||
stores = postgres.NewStores(database.TestDB)
|
||||
}
|
||||
userCache := middleware.NewUserStatusCache()
|
||||
|
||||
r := gin.New()
|
||||
api := r.Group("/api/v1")
|
||||
@@ -62,7 +63,7 @@ func setupExtensionHarness(t *testing.T) *extensionHarness {
|
||||
|
||||
// Protected routes
|
||||
protected := api.Group("")
|
||||
protected.Use(middleware.Auth(cfg))
|
||||
protected.Use(middleware.Auth(cfg, stores.Users, userCache))
|
||||
|
||||
// User extension endpoints
|
||||
protected.GET("/extensions", extH.ListUserExtensions)
|
||||
@@ -72,7 +73,7 @@ func setupExtensionHarness(t *testing.T) *extensionHarness {
|
||||
|
||||
// Admin extension endpoints
|
||||
admin := api.Group("/admin")
|
||||
admin.Use(middleware.Auth(cfg), middleware.RequireAdmin())
|
||||
admin.Use(middleware.Auth(cfg, stores.Users, userCache), middleware.RequireAdmin())
|
||||
admin.GET("/extensions", extH.AdminListExtensions)
|
||||
admin.POST("/extensions", extH.AdminInstallExtension)
|
||||
admin.PUT("/extensions/:id", extH.AdminUpdateExtension)
|
||||
|
||||
@@ -128,6 +128,7 @@ func setupHarness(t *testing.T) *testHarness {
|
||||
} else {
|
||||
stores = postgres.NewStores(database.TestDB)
|
||||
}
|
||||
userCache := middleware.NewUserStatusCache()
|
||||
|
||||
// Roles resolver (nil vault — test-fire won't work, but CRUD will)
|
||||
roleResolver := roles.NewResolver(stores, nil)
|
||||
@@ -147,7 +148,7 @@ func setupHarness(t *testing.T) *testHarness {
|
||||
|
||||
// Protected routes
|
||||
protected := api.Group("")
|
||||
protected.Use(middleware.Auth(cfg))
|
||||
protected.Use(middleware.Auth(cfg, stores.Users, userCache))
|
||||
|
||||
// Models
|
||||
models := NewModelHandler(stores)
|
||||
@@ -358,7 +359,7 @@ func setupHarness(t *testing.T) *testHarness {
|
||||
|
||||
// Admin routes
|
||||
admin := api.Group("/admin")
|
||||
admin.Use(middleware.Auth(cfg), middleware.RequireAdmin())
|
||||
admin.Use(middleware.Auth(cfg, stores.Users, userCache), middleware.RequireAdmin())
|
||||
admin.GET("/users", adm.ListUsers)
|
||||
admin.POST("/users", adm.CreateUser)
|
||||
admin.PUT("/users/:id/active", adm.ToggleUserActive)
|
||||
|
||||
@@ -44,11 +44,12 @@ func setupNotifHarness(t *testing.T) *notifHarness {
|
||||
} else {
|
||||
stores = postgres.NewStores(database.TestDB)
|
||||
}
|
||||
userCache := middleware.NewUserStatusCache()
|
||||
|
||||
r := gin.New()
|
||||
api := r.Group("/api/v1")
|
||||
protected := api.Group("")
|
||||
protected.Use(middleware.Auth(cfg))
|
||||
protected.Use(middleware.Auth(cfg, stores.Users, userCache))
|
||||
|
||||
notifH := NewNotificationHandler(stores, nil) // no hub in tests
|
||||
protected.GET("/notifications", notifH.List)
|
||||
|
||||
@@ -11,6 +11,9 @@ import (
|
||||
"git.gobha.me/xcaliber/chat-switchboard/config"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/database"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/middleware"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/store"
|
||||
postgres "git.gobha.me/xcaliber/chat-switchboard/store/postgres"
|
||||
sqlite "git.gobha.me/xcaliber/chat-switchboard/store/sqlite"
|
||||
)
|
||||
|
||||
// ── Profile Test Harness ──────────────────
|
||||
@@ -31,10 +34,18 @@ func setupProfileHarness(t *testing.T) *profileHarness {
|
||||
BasePath: "",
|
||||
}
|
||||
|
||||
var stores store.Stores
|
||||
if database.IsSQLite() {
|
||||
stores = sqlite.NewStores(database.TestDB)
|
||||
} else {
|
||||
stores = postgres.NewStores(database.TestDB)
|
||||
}
|
||||
userCache := middleware.NewUserStatusCache()
|
||||
|
||||
r := gin.New()
|
||||
api := r.Group("/api/v1")
|
||||
protected := api.Group("")
|
||||
protected.Use(middleware.Auth(cfg))
|
||||
protected.Use(middleware.Auth(cfg, stores.Users, userCache))
|
||||
|
||||
settings := NewSettingsHandler(nil)
|
||||
protected.GET("/profile", settings.GetProfile)
|
||||
|
||||
@@ -44,11 +44,12 @@ func setupProjectHarness(t *testing.T) *projectHarness {
|
||||
} else {
|
||||
stores = postgres.NewStores(database.TestDB)
|
||||
}
|
||||
userCache := middleware.NewUserStatusCache()
|
||||
|
||||
r := gin.New()
|
||||
api := r.Group("/api/v1")
|
||||
protected := api.Group("")
|
||||
protected.Use(middleware.Auth(cfg))
|
||||
protected.Use(middleware.Auth(cfg, stores.Users, userCache))
|
||||
|
||||
projH := NewProjectHandler(stores)
|
||||
protected.GET("/projects", projH.List)
|
||||
@@ -69,7 +70,7 @@ func setupProjectHarness(t *testing.T) *projectHarness {
|
||||
|
||||
// Admin routes
|
||||
admin := api.Group("/admin")
|
||||
admin.Use(middleware.Auth(cfg))
|
||||
admin.Use(middleware.Auth(cfg, stores.Users, userCache))
|
||||
admin.Use(middleware.RequireAdmin())
|
||||
admin.GET("/projects", projH.AdminList)
|
||||
admin.DELETE("/projects/:id", projH.Delete)
|
||||
|
||||
@@ -39,6 +39,7 @@ func TestRouteRegistration(t *testing.T) {
|
||||
} else {
|
||||
stores = postgres.NewStores(database.TestDB)
|
||||
}
|
||||
userCache := middleware.NewUserStatusCache()
|
||||
|
||||
// This must not panic. If it does, there's a wildcard conflict.
|
||||
r := gin.New()
|
||||
@@ -55,7 +56,7 @@ func TestRouteRegistration(t *testing.T) {
|
||||
|
||||
// Protected API routes
|
||||
protected := api.Group("")
|
||||
protected.Use(middleware.Auth(cfg))
|
||||
protected.Use(middleware.Auth(cfg, stores.Users, userCache))
|
||||
|
||||
// Channels (the route group that conflicts with workflow)
|
||||
channels := NewChannelHandler()
|
||||
@@ -99,7 +100,7 @@ func TestRouteRegistration(t *testing.T) {
|
||||
|
||||
// Session API (the /w/:id group)
|
||||
wfAPI := base.Group("/api/v1/w")
|
||||
wfAPI.Use(middleware.AuthOrSession(cfg, stores))
|
||||
wfAPI.Use(middleware.AuthOrSession(cfg, stores, userCache))
|
||||
wfAPI.POST("/:id/messages", msgs.CreateMessage)
|
||||
wfAPI.GET("/:id/messages", msgs.ListMessages)
|
||||
|
||||
@@ -111,9 +112,9 @@ func TestRouteRegistration(t *testing.T) {
|
||||
|
||||
pageEngine := pages.New(cfg, stores)
|
||||
pageEngine.RegisterPageRoutes(base, pages.PageRouteMiddleware{
|
||||
Authenticated: middleware.AuthOrRedirect(cfg),
|
||||
Admin: []gin.HandlerFunc{middleware.AuthOrRedirect(cfg), middleware.RequireAdminPage()},
|
||||
Session: middleware.AuthOrSession(cfg, stores),
|
||||
Authenticated: middleware.AuthOrRedirect(cfg, stores.Users, userCache),
|
||||
Admin: []gin.HandlerFunc{middleware.AuthOrRedirect(cfg, stores.Users, userCache), middleware.RequireAdminPage()},
|
||||
Session: middleware.AuthOrSession(cfg, stores, userCache),
|
||||
})
|
||||
|
||||
// ── Verify routes actually resolve ────
|
||||
|
||||
@@ -50,6 +50,7 @@ func setupSurfaceHarness(t *testing.T) *surfaceHarness {
|
||||
} else {
|
||||
stores = postgres.NewStores(database.TestDB)
|
||||
}
|
||||
userCache := middleware.NewUserStatusCache()
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", "surface-test-*")
|
||||
if err != nil {
|
||||
@@ -63,13 +64,13 @@ func setupSurfaceHarness(t *testing.T) *surfaceHarness {
|
||||
// User endpoint (authenticated)
|
||||
surfaceH := NewSurfaceHandler(stores)
|
||||
userGroup := api.Group("")
|
||||
userGroup.Use(middleware.Auth(cfg))
|
||||
userGroup.Use(middleware.Auth(cfg, stores.Users, userCache))
|
||||
userGroup.GET("/surfaces", surfaceH.ListEnabledSurfaces)
|
||||
|
||||
// Admin endpoints
|
||||
surfaceAdm := NewSurfaceHandler(stores, tmpDir)
|
||||
adminGroup := api.Group("/admin")
|
||||
adminGroup.Use(middleware.Auth(cfg))
|
||||
adminGroup.Use(middleware.Auth(cfg, stores.Users, userCache))
|
||||
adminGroup.Use(middleware.RequireAdmin())
|
||||
adminGroup.GET("/surfaces", surfaceAdm.ListSurfaces)
|
||||
adminGroup.GET("/surfaces/:id", surfaceAdm.GetSurface)
|
||||
|
||||
@@ -44,6 +44,7 @@ func setupWorkflowInstanceHarness(t *testing.T) *workflowInstanceHarness {
|
||||
} else {
|
||||
stores = postgres.NewStores(database.TestDB)
|
||||
}
|
||||
userCache := middleware.NewUserStatusCache()
|
||||
|
||||
r := gin.New()
|
||||
api := r.Group("/api/v1")
|
||||
@@ -53,7 +54,7 @@ func setupWorkflowInstanceHarness(t *testing.T) *workflowInstanceHarness {
|
||||
api.POST("/auth/register", auth.Register)
|
||||
|
||||
protected := api.Group("")
|
||||
protected.Use(middleware.Auth(cfg))
|
||||
protected.Use(middleware.Auth(cfg, stores.Users, userCache))
|
||||
|
||||
// Workflow CRUD
|
||||
wfH := NewWorkflowHandler(stores)
|
||||
@@ -86,7 +87,7 @@ func setupWorkflowInstanceHarness(t *testing.T) *workflowInstanceHarness {
|
||||
// Teams (admin routes for creating teams + adding members)
|
||||
teamH := NewTeamHandler(stores, nil)
|
||||
admin := api.Group("/admin")
|
||||
admin.Use(middleware.Auth(cfg), middleware.RequireAdmin())
|
||||
admin.Use(middleware.Auth(cfg, stores.Users, userCache), middleware.RequireAdmin())
|
||||
admin.POST("/teams", teamH.CreateTeam)
|
||||
admin.POST("/teams/:id/members", teamH.AddMember)
|
||||
|
||||
|
||||
@@ -283,6 +283,7 @@ func setupWorkflowHarness(t *testing.T) *workflowHarness {
|
||||
} else {
|
||||
stores = postgres.NewStores(database.TestDB)
|
||||
}
|
||||
userCache := middleware.NewUserStatusCache()
|
||||
|
||||
r := gin.New()
|
||||
api := r.Group("/api/v1")
|
||||
@@ -293,7 +294,7 @@ func setupWorkflowHarness(t *testing.T) *workflowHarness {
|
||||
api.POST("/auth/register", auth.Register)
|
||||
|
||||
protected := api.Group("")
|
||||
protected.Use(middleware.Auth(cfg))
|
||||
protected.Use(middleware.Auth(cfg, stores.Users, userCache))
|
||||
|
||||
// Workflow CRUD
|
||||
wfH := NewWorkflowHandler(stores)
|
||||
|
||||
@@ -42,11 +42,12 @@ func setupWorkspaceHarness(t *testing.T) *workspaceHarness {
|
||||
} else {
|
||||
stores = postgres.NewStores(database.TestDB)
|
||||
}
|
||||
userCache := middleware.NewUserStatusCache()
|
||||
|
||||
r := gin.New()
|
||||
api := r.Group("/api/v1")
|
||||
protected := api.Group("")
|
||||
protected.Use(middleware.Auth(cfg))
|
||||
protected.Use(middleware.Auth(cfg, stores.Users, userCache))
|
||||
|
||||
// Workspace handler — nil wfs is fine for CRUD tests that don't touch filesystem
|
||||
wsH := NewWorkspaceHandler(stores, nil)
|
||||
|
||||
Reference in New Issue
Block a user