All checks were successful
CI/CD / detect-changes (pull_request) Successful in 4s
CI/CD / test-frontend (pull_request) Successful in 5s
CI/CD / test-go-pg (pull_request) Successful in 2m22s
CI/CD / test-sqlite (pull_request) Successful in 2m35s
CI/CD / build-and-deploy (pull_request) Successful in 1m18s
The role column was a pre-RBAC artifact. All authorization now flows through explicit group membership and permission grants: - Everyone group: all users added on creation (no implicit membership) - Admins group: grants surface.admin.access + all platform permissions - JWT claims, login response, profile: role field removed - OIDC: isIdPAdmin() maps IdP claims → Admins group (no role writes) - Admin UI: role dropdown removed, admin managed through groups - Middleware cache simplified to isActive only 28 files changed, -79 lines net. Zero magic roles. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
109 lines
3.3 KiB
Go
109 lines
3.3 KiB
Go
package handlers
|
|
|
|
// profile_bootstrap.go — Single-call boot payload for the SDK.
|
|
//
|
|
// GET /api/v1/profile/bootstrap
|
|
//
|
|
// Collapses what previously required 3-4 sequential requests
|
|
// (profile, permissions, teams/mine, settings) into one call.
|
|
// The SDK calls this at startup and on token refresh.
|
|
//
|
|
// v0.37.15
|
|
|
|
import (
|
|
"net/http"
|
|
"sort"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"switchboard-core/auth"
|
|
"switchboard-core/store"
|
|
)
|
|
|
|
// ProfileBootstrapHandler serves the combined boot payload.
|
|
type ProfileBootstrapHandler struct {
|
|
stores store.Stores
|
|
}
|
|
|
|
func NewProfileBootstrapHandler(s store.Stores) *ProfileBootstrapHandler {
|
|
return &ProfileBootstrapHandler{stores: s}
|
|
}
|
|
|
|
// GetBootstrap returns everything the shell needs at startup.
|
|
// GET /api/v1/profile/bootstrap
|
|
func (h *ProfileBootstrapHandler) GetBootstrap(c *gin.Context) {
|
|
userID := getUserID(c)
|
|
ctx := c.Request.Context()
|
|
|
|
// ── User profile ────────────────────────
|
|
user, err := h.stores.Users.GetByID(ctx, userID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
|
return
|
|
}
|
|
|
|
userPayload := gin.H{
|
|
"id": user.ID,
|
|
"username": user.Username,
|
|
"display_name": user.DisplayName,
|
|
"email": user.Email,
|
|
}
|
|
if user.AvatarURL != "" {
|
|
userPayload["avatar"] = user.AvatarURL
|
|
}
|
|
|
|
// ── Permissions ─────────────────────────
|
|
perms, err := auth.ResolvePermissions(ctx, h.stores, userID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to resolve permissions"})
|
|
return
|
|
}
|
|
permList := make([]string, 0, len(perms))
|
|
for p := range perms {
|
|
permList = append(permList, p)
|
|
}
|
|
sort.Strings(permList)
|
|
|
|
// ── Groups ──────────────────────────────
|
|
groupIDs, _ := h.stores.Groups.GetUserGroupIDs(ctx, userID)
|
|
if groupIDs == nil {
|
|
groupIDs = []string{}
|
|
}
|
|
|
|
// ── Teams ───────────────────────────────
|
|
teams, _ := h.stores.Teams.ListForUser(ctx, userID)
|
|
teamData := make([]gin.H, 0, len(teams))
|
|
for _, t := range teams {
|
|
teamData = append(teamData, gin.H{
|
|
"id": t.ID,
|
|
"name": t.Name,
|
|
"my_role": t.MyRole,
|
|
})
|
|
}
|
|
|
|
// ── Policies ────────────────────────────
|
|
policies := make(map[string]bool)
|
|
if ps := h.stores.Policies; ps != nil {
|
|
policies["allow_user_byok"], _ = ps.GetBool(ctx, "allow_user_byok")
|
|
policies["allow_user_personas"], _ = ps.GetBool(ctx, "allow_user_personas")
|
|
policies["allow_raw_model_access"], _ = ps.GetBool(ctx, "allow_raw_model_access")
|
|
policies["kb_direct_access"], _ = ps.GetBool(ctx, "kb_direct_access")
|
|
}
|
|
|
|
// ── Settings ────────────────────────────
|
|
settings := make(map[string]interface{})
|
|
if user.Settings != nil {
|
|
settings = user.Settings
|
|
}
|
|
|
|
// ── Response ────────────────────────────
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"user": userPayload,
|
|
"permissions": permList,
|
|
"groups": groupIDs,
|
|
"teams": teamData,
|
|
"policies": policies,
|
|
"settings": settings,
|
|
})
|
|
}
|