refactor(server): consolidate workspace permission checks into middleware
Move workspace membership and role validation from individual handlers into dedicated Chi middleware. The new middleware resolves workspace ID (from query param, X-Workspace-ID header, or URL param), validates membership via DB, and injects the member into request context. Handlers now read workspace ID and member from context instead of calling requireWorkspaceMember/requireWorkspaceRole directly. This eliminates ~17 duplicated permission checks across handlers and makes it harder to accidentally omit access control on new routes.
This commit is contained in:
parent
e1e4079da1
commit
f4a6e7c475
8 changed files with 198 additions and 64 deletions
|
|
@ -6,11 +6,13 @@ import (
|
|||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
db "github.com/multica-ai/multica/server/pkg/db/generated"
|
||||
"github.com/multica-ai/multica/server/internal/events"
|
||||
"github.com/multica-ai/multica/server/internal/middleware"
|
||||
"github.com/multica-ai/multica/server/internal/realtime"
|
||||
"github.com/multica-ai/multica/server/internal/service"
|
||||
"github.com/multica-ai/multica/server/internal/util"
|
||||
|
|
@ -109,6 +111,10 @@ func requireUserID(w http.ResponseWriter, r *http.Request) (string, bool) {
|
|||
}
|
||||
|
||||
func resolveWorkspaceID(r *http.Request) string {
|
||||
// Prefer context value set by workspace middleware.
|
||||
if id := middleware.WorkspaceIDFromContext(r.Context()); id != "" {
|
||||
return id
|
||||
}
|
||||
workspaceID := r.URL.Query().Get("workspace_id")
|
||||
if workspaceID != "" {
|
||||
return workspaceID
|
||||
|
|
@ -116,6 +122,33 @@ func resolveWorkspaceID(r *http.Request) string {
|
|||
return r.Header.Get("X-Workspace-ID")
|
||||
}
|
||||
|
||||
// ctxMember returns the workspace member from context (set by workspace middleware).
|
||||
func ctxMember(ctx context.Context) (db.Member, bool) {
|
||||
return middleware.MemberFromContext(ctx)
|
||||
}
|
||||
|
||||
// ctxWorkspaceID returns the workspace ID from context (set by workspace middleware).
|
||||
func ctxWorkspaceID(ctx context.Context) string {
|
||||
return middleware.WorkspaceIDFromContext(ctx)
|
||||
}
|
||||
|
||||
// workspaceIDFromURL returns the workspace ID from context (preferred) or chi URL param (fallback).
|
||||
func workspaceIDFromURL(r *http.Request, param string) string {
|
||||
if id := middleware.WorkspaceIDFromContext(r.Context()); id != "" {
|
||||
return id
|
||||
}
|
||||
return chi.URLParam(r, param)
|
||||
}
|
||||
|
||||
// workspaceMember returns the member from middleware context, or falls back to a DB
|
||||
// lookup when the handler is called directly (e.g. in tests).
|
||||
func (h *Handler) workspaceMember(w http.ResponseWriter, r *http.Request, workspaceID string) (db.Member, bool) {
|
||||
if m, ok := ctxMember(r.Context()); ok {
|
||||
return m, true
|
||||
}
|
||||
return h.requireWorkspaceMember(w, r, workspaceID, "workspace not found")
|
||||
}
|
||||
|
||||
func roleAllowed(role string, roles ...string) bool {
|
||||
for _, candidate := range roles {
|
||||
if role == candidate {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue