mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
Server: - Unified permission checker (permissions/checker.go) replaces 3 duplicated implementations - WS handler registry pattern (ws/registry.go) replaces monolithic switch (747→184 lines) - Split handlers into domain files: handlers_chat.go, handlers_presence.go, handlers_reaction.go - Shared message type constants (ws/message_types.go) — no more string literals - Admin API split into helpers.go, types.go, middleware.go (api.go now 61 lines) - Dev seed script (scripts/seed.go) with -confirm-dev safety flag - Air hot reload config (.air.toml) - Fix: DM attachment permission now uses participant check, not role check - Fix: Typing broadcast now checks ReadMessages permission for non-DM channels Client: - Extract preferences to @lib/preferences.ts (fixes lib→component dependency) - Extract roles to dedicated roles.store.ts (was mixed into channels store) - Decompose SidebarArea (921→598 lines) into 4 sub-components - Shared modal factory (lib/modalFactory.ts) with tests - Global showToast() helper (lib/toast.ts) — 18 call sites migrated - Protocol type constants (lib/protocolTypes.ts) synced with server - Remove 38 unnecessary type casts across 17 files - Component test harness (tests/helpers/test-harness.ts) with 8 tests - Fix: DM section "View All" respects collapsed state - Fix: Modal onClose fires on external signal abort - Fix: savePref wrapped in try/catch for quota exceeded - Fix: loadPref null guard added Triple-reviewed: Claude code-review agent + OpenAI Codex CLI + GitHub Copilot
87 lines
2.8 KiB
Go
87 lines
2.8 KiB
Go
package admin
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
|
|
"github.com/owncord/server/auth"
|
|
"github.com/owncord/server/db"
|
|
"github.com/owncord/server/permissions"
|
|
)
|
|
|
|
// ─── Middleware ───────────────────────────────────────────────────────────────
|
|
|
|
// adminAuthMiddleware validates the Bearer token and requires ADMINISTRATOR.
|
|
// On success it stores the *db.User and *db.Session in the request context so
|
|
// downstream handlers can retrieve them without re-querying the database.
|
|
func adminAuthMiddleware(database *db.DB) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
token, ok := auth.ExtractBearerToken(r)
|
|
if !ok {
|
|
writeErr(w, http.StatusUnauthorized, "UNAUTHORIZED", "missing or invalid authorization header")
|
|
return
|
|
}
|
|
|
|
hash := auth.HashToken(token)
|
|
sess, err := database.GetSessionByTokenHash(hash)
|
|
if err != nil || sess == nil {
|
|
writeErr(w, http.StatusUnauthorized, "UNAUTHORIZED", "invalid or expired session")
|
|
return
|
|
}
|
|
|
|
if auth.IsSessionExpired(sess.ExpiresAt) {
|
|
writeErr(w, http.StatusUnauthorized, "UNAUTHORIZED", "session has expired")
|
|
return
|
|
}
|
|
|
|
user, err := database.GetUserByID(sess.UserID)
|
|
if err != nil || user == nil {
|
|
writeErr(w, http.StatusUnauthorized, "UNAUTHORIZED", "user not found")
|
|
return
|
|
}
|
|
|
|
role, err := database.GetRoleByID(user.RoleID)
|
|
if err != nil || role == nil {
|
|
writeErr(w, http.StatusUnauthorized, "UNAUTHORIZED", "role not found")
|
|
return
|
|
}
|
|
|
|
if !permissions.HasAdmin(role.Permissions) {
|
|
writeErr(w, http.StatusForbidden, "FORBIDDEN", "administrator permission required")
|
|
return
|
|
}
|
|
|
|
ctx := context.WithValue(r.Context(), adminUserKey, user)
|
|
ctx = context.WithValue(ctx, adminSessionKey, sess)
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|
|
}
|
|
|
|
// ownerOnlyMiddleware wraps a handler to require Owner role (position == 100).
|
|
// It reads the user from context (set by adminAuthMiddleware) rather than
|
|
// re-authenticating, avoiding redundant DB queries and session-expiry gaps.
|
|
func ownerOnlyMiddleware(database *db.DB, next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
user, ok := r.Context().Value(adminUserKey).(*db.User)
|
|
if !ok || user == nil {
|
|
writeErr(w, http.StatusUnauthorized, "UNAUTHORIZED", "not authenticated")
|
|
return
|
|
}
|
|
|
|
role, err := database.GetRoleByID(user.RoleID)
|
|
if err != nil || role == nil {
|
|
writeErr(w, http.StatusForbidden, "FORBIDDEN", "role not found")
|
|
return
|
|
}
|
|
|
|
if role.Position < permissions.OwnerRolePosition {
|
|
writeErr(w, http.StatusForbidden, "FORBIDDEN", "owner role required")
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|