feat: role-based permissions — owner/partner/associate/paralegal/secretary (P0)
This commit is contained in:
@@ -29,7 +29,14 @@ func New(db *sqlx.DB, authMW *auth.Middleware, cfg *config.Config, calDAVSvc *se
|
||||
deadlineRuleSvc := services.NewDeadlineRuleService(db)
|
||||
calculator := services.NewDeadlineCalculator(holidaySvc)
|
||||
storageCli := services.NewStorageClient(cfg.SupabaseURL, cfg.SupabaseServiceKey)
|
||||
<<<<<<< HEAD
|
||||
documentSvc := services.NewDocumentService(db, storageCli, auditSvc)
|
||||
||||||| 82878df
|
||||
documentSvc := services.NewDocumentService(db, storageCli)
|
||||
=======
|
||||
documentSvc := services.NewDocumentService(db, storageCli)
|
||||
assignmentSvc := services.NewCaseAssignmentService(db)
|
||||
>>>>>>> mai/pike/p0-role-based
|
||||
|
||||
// AI service (optional — only if API key is configured)
|
||||
var aiH *handlers.AIHandler
|
||||
@@ -63,6 +70,7 @@ func New(db *sqlx.DB, authMW *auth.Middleware, cfg *config.Config, calDAVSvc *se
|
||||
noteH := handlers.NewNoteHandler(noteSvc)
|
||||
eventH := handlers.NewCaseEventHandler(db)
|
||||
docH := handlers.NewDocumentHandler(documentSvc)
|
||||
assignmentH := handlers.NewCaseAssignmentHandler(assignmentSvc)
|
||||
|
||||
// Public routes
|
||||
mux.HandleFunc("GET /health", handleHealth(db))
|
||||
@@ -78,76 +86,106 @@ func New(db *sqlx.DB, authMW *auth.Middleware, cfg *config.Config, calDAVSvc *se
|
||||
api.HandleFunc("POST /api/tenants/{id}/invite", tenantH.InviteUser)
|
||||
api.HandleFunc("DELETE /api/tenants/{id}/members/{uid}", tenantH.RemoveMember)
|
||||
api.HandleFunc("GET /api/tenants/{id}/members", tenantH.ListMembers)
|
||||
api.HandleFunc("PUT /api/tenants/{id}/members/{uid}/role", tenantH.UpdateMemberRole)
|
||||
|
||||
// Permission-wrapping helper: wraps a HandlerFunc with a permission check
|
||||
perm := func(p auth.Permission, fn http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
role := auth.UserRoleFromContext(r.Context())
|
||||
if !auth.HasPermission(role, p) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
w.Write([]byte(`{"error":"insufficient permissions"}`))
|
||||
return
|
||||
}
|
||||
fn(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// Tenant-scoped routes (require tenant context)
|
||||
scoped := http.NewServeMux()
|
||||
|
||||
// Cases
|
||||
scoped.HandleFunc("GET /api/cases", caseH.List)
|
||||
scoped.HandleFunc("POST /api/cases", caseH.Create)
|
||||
scoped.HandleFunc("GET /api/cases/{id}", caseH.Get)
|
||||
scoped.HandleFunc("PUT /api/cases/{id}", caseH.Update)
|
||||
scoped.HandleFunc("DELETE /api/cases/{id}", caseH.Delete)
|
||||
// Current user info (role, permissions) — all authenticated users
|
||||
scoped.HandleFunc("GET /api/me", tenantH.GetMe)
|
||||
|
||||
// Parties
|
||||
// Cases — all can view, create needs PermCreateCase, archive needs PermCreateCase
|
||||
scoped.HandleFunc("GET /api/cases", caseH.List)
|
||||
scoped.HandleFunc("POST /api/cases", perm(auth.PermCreateCase, caseH.Create))
|
||||
scoped.HandleFunc("GET /api/cases/{id}", caseH.Get)
|
||||
scoped.HandleFunc("PUT /api/cases/{id}", caseH.Update) // case-level access checked in handler
|
||||
scoped.HandleFunc("DELETE /api/cases/{id}", perm(auth.PermCreateCase, caseH.Delete))
|
||||
|
||||
// Parties — same access as case editing
|
||||
scoped.HandleFunc("GET /api/cases/{id}/parties", partyH.List)
|
||||
scoped.HandleFunc("POST /api/cases/{id}/parties", partyH.Create)
|
||||
scoped.HandleFunc("PUT /api/parties/{partyId}", partyH.Update)
|
||||
scoped.HandleFunc("DELETE /api/parties/{partyId}", partyH.Delete)
|
||||
|
||||
// Deadlines
|
||||
// Deadlines — manage needs PermManageDeadlines, view is open
|
||||
scoped.HandleFunc("GET /api/deadlines/{deadlineID}", deadlineH.Get)
|
||||
scoped.HandleFunc("GET /api/deadlines", deadlineH.ListAll)
|
||||
scoped.HandleFunc("GET /api/cases/{caseID}/deadlines", deadlineH.ListForCase)
|
||||
scoped.HandleFunc("POST /api/cases/{caseID}/deadlines", deadlineH.Create)
|
||||
scoped.HandleFunc("PUT /api/deadlines/{deadlineID}", deadlineH.Update)
|
||||
scoped.HandleFunc("PATCH /api/deadlines/{deadlineID}/complete", deadlineH.Complete)
|
||||
scoped.HandleFunc("DELETE /api/deadlines/{deadlineID}", deadlineH.Delete)
|
||||
scoped.HandleFunc("POST /api/cases/{caseID}/deadlines", perm(auth.PermManageDeadlines, deadlineH.Create))
|
||||
scoped.HandleFunc("PUT /api/deadlines/{deadlineID}", perm(auth.PermManageDeadlines, deadlineH.Update))
|
||||
scoped.HandleFunc("PATCH /api/deadlines/{deadlineID}/complete", perm(auth.PermManageDeadlines, deadlineH.Complete))
|
||||
scoped.HandleFunc("DELETE /api/deadlines/{deadlineID}", perm(auth.PermManageDeadlines, deadlineH.Delete))
|
||||
|
||||
// Deadline rules (reference data)
|
||||
// Deadline rules (reference data) — all can read
|
||||
scoped.HandleFunc("GET /api/deadline-rules", ruleH.List)
|
||||
scoped.HandleFunc("GET /api/deadline-rules/{type}", ruleH.GetRuleTree)
|
||||
scoped.HandleFunc("GET /api/proceeding-types", ruleH.ListProceedingTypes)
|
||||
|
||||
// Deadline calculator
|
||||
// Deadline calculator — all can use
|
||||
scoped.HandleFunc("POST /api/deadlines/calculate", calcH.Calculate)
|
||||
|
||||
// Appointments
|
||||
// Appointments — all can manage (PermManageAppointments granted to all)
|
||||
scoped.HandleFunc("GET /api/appointments/{id}", apptH.Get)
|
||||
scoped.HandleFunc("GET /api/appointments", apptH.List)
|
||||
scoped.HandleFunc("POST /api/appointments", apptH.Create)
|
||||
scoped.HandleFunc("PUT /api/appointments/{id}", apptH.Update)
|
||||
scoped.HandleFunc("DELETE /api/appointments/{id}", apptH.Delete)
|
||||
scoped.HandleFunc("POST /api/appointments", perm(auth.PermManageAppointments, apptH.Create))
|
||||
scoped.HandleFunc("PUT /api/appointments/{id}", perm(auth.PermManageAppointments, apptH.Update))
|
||||
scoped.HandleFunc("DELETE /api/appointments/{id}", perm(auth.PermManageAppointments, apptH.Delete))
|
||||
|
||||
// Case events
|
||||
// Case assignments — manage team required for assign/unassign
|
||||
scoped.HandleFunc("GET /api/cases/{id}/assignments", assignmentH.List)
|
||||
scoped.HandleFunc("POST /api/cases/{id}/assignments", perm(auth.PermManageTeam, assignmentH.Assign))
|
||||
scoped.HandleFunc("DELETE /api/cases/{id}/assignments/{uid}", perm(auth.PermManageTeam, assignmentH.Unassign))
|
||||
|
||||
// Case events — all can view
|
||||
scoped.HandleFunc("GET /api/case-events/{id}", eventH.Get)
|
||||
|
||||
// Notes
|
||||
// Notes — all can manage
|
||||
scoped.HandleFunc("GET /api/notes", noteH.List)
|
||||
scoped.HandleFunc("POST /api/notes", noteH.Create)
|
||||
scoped.HandleFunc("PUT /api/notes/{id}", noteH.Update)
|
||||
scoped.HandleFunc("DELETE /api/notes/{id}", noteH.Delete)
|
||||
|
||||
// Dashboard
|
||||
// Dashboard — all can view
|
||||
scoped.HandleFunc("GET /api/dashboard", dashboardH.Get)
|
||||
|
||||
<<<<<<< HEAD
|
||||
// Audit log
|
||||
scoped.HandleFunc("GET /api/audit-log", auditH.List)
|
||||
|
||||
// Documents
|
||||
||||||| 82878df
|
||||
// Documents
|
||||
=======
|
||||
// Documents — all can upload, delete checked in handler (own vs all)
|
||||
>>>>>>> mai/pike/p0-role-based
|
||||
scoped.HandleFunc("GET /api/cases/{id}/documents", docH.ListByCase)
|
||||
scoped.HandleFunc("POST /api/cases/{id}/documents", docH.Upload)
|
||||
scoped.HandleFunc("POST /api/cases/{id}/documents", perm(auth.PermUploadDocuments, docH.Upload))
|
||||
scoped.HandleFunc("GET /api/documents/{docId}", docH.Download)
|
||||
scoped.HandleFunc("GET /api/documents/{docId}/meta", docH.GetMeta)
|
||||
scoped.HandleFunc("DELETE /api/documents/{docId}", docH.Delete)
|
||||
scoped.HandleFunc("DELETE /api/documents/{docId}", docH.Delete) // permission check inside handler
|
||||
|
||||
// AI endpoints (rate limited: 5 req/min burst 10 per IP)
|
||||
if aiH != nil {
|
||||
aiLimiter := middleware.NewTokenBucket(5.0/60.0, 10)
|
||||
scoped.HandleFunc("POST /api/ai/extract-deadlines", aiLimiter.LimitFunc(aiH.ExtractDeadlines))
|
||||
scoped.HandleFunc("POST /api/ai/summarize-case", aiLimiter.LimitFunc(aiH.SummarizeCase))
|
||||
scoped.HandleFunc("POST /api/ai/extract-deadlines", perm(auth.PermAIExtraction, aiLimiter.LimitFunc(aiH.ExtractDeadlines)))
|
||||
scoped.HandleFunc("POST /api/ai/summarize-case", perm(auth.PermAIExtraction, aiLimiter.LimitFunc(aiH.SummarizeCase)))
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
// Notifications
|
||||
if notifH != nil {
|
||||
scoped.HandleFunc("GET /api/notifications", notifH.List)
|
||||
@@ -159,9 +197,14 @@ func New(db *sqlx.DB, authMW *auth.Middleware, cfg *config.Config, calDAVSvc *se
|
||||
}
|
||||
|
||||
// CalDAV sync endpoints
|
||||
||||||| 82878df
|
||||
// CalDAV sync endpoints
|
||||
=======
|
||||
// CalDAV sync endpoints — settings permission required
|
||||
>>>>>>> mai/pike/p0-role-based
|
||||
if calDAVSvc != nil {
|
||||
calDAVH := handlers.NewCalDAVHandler(calDAVSvc)
|
||||
scoped.HandleFunc("POST /api/caldav/sync", calDAVH.TriggerSync)
|
||||
scoped.HandleFunc("POST /api/caldav/sync", perm(auth.PermManageSettings, calDAVH.TriggerSync))
|
||||
scoped.HandleFunc("GET /api/caldav/status", calDAVH.GetStatus)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user