Phase 6: Permissions, Push, WebAuthn scaffolding
Backend: - internal/permissions/permissions.go: permission bitflags (VIEW_CHANNEL through SHARE_SCREEN) - internal/permissions/checker.go: permission checker with role aggregation - internal/middleware/permission.go: RequirePermission middleware - internal/push/handlers.go: push subscription CRUD, VAPID key endpoint, SendPush - internal/auth/webauthn.go: WebAuthn passkey scaffolding (begin/finish endpoints) - internal/db/db.go: is_default on roles, push_subscriptions table, webauthn_credentials table - go.mod: added webpush-go, go-webauthn dependencies Frontend: - stores/role.ts: Zustand store for role management - RoleManager.tsx: role CRUD with permission checkboxes - MemberRoleAssign.tsx: assign roles to members - App.tsx: /servers/:serverId/roles route
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
package permissions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// Checker verifies user permissions against the database.
|
||||
type Checker struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// NewChecker creates a new Checker backed by the given database connection.
|
||||
func NewChecker(db *sql.DB) *Checker {
|
||||
return &Checker{db: db}
|
||||
}
|
||||
|
||||
// GetUserPermissions returns the effective (OR'd) permissions for a user in a server.
|
||||
// It aggregates all permissions from the roles assigned to the user, plus the
|
||||
// @everyone role for that server.
|
||||
func (c *Checker) GetUserPermissions(ctx context.Context, serverID string, userID string) (int64, error) {
|
||||
rows, err := c.db.QueryContext(ctx, `
|
||||
SELECT r.permissions
|
||||
FROM roles r
|
||||
INNER JOIN member_roles mr ON mr.role_id = r.id
|
||||
WHERE mr.user_id = $1 AND mr.server_id = $2
|
||||
`, userID, serverID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var effective int64
|
||||
found := false
|
||||
for rows.Next() {
|
||||
var perm int64
|
||||
if err := rows.Scan(&perm); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
effective |= perm
|
||||
found = true
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Also include the @everyone role permissions for this server.
|
||||
var everyonePerm int64
|
||||
err = c.db.QueryRowContext(ctx, `
|
||||
SELECT permissions FROM roles
|
||||
WHERE server_id = $1 AND is_default = TRUE
|
||||
LIMIT 1
|
||||
`, serverID).Scan(&everyonePerm)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return 0, err
|
||||
}
|
||||
effective |= everyonePerm
|
||||
if everyonePerm != 0 {
|
||||
found = true
|
||||
}
|
||||
|
||||
if !found {
|
||||
return 0, nil
|
||||
}
|
||||
return effective, nil
|
||||
}
|
||||
|
||||
// CheckPermission reports whether the user has all the required permission bits
|
||||
// in the given server. ADMINISTRATOR bypasses all checks.
|
||||
func (c *Checker) CheckPermission(ctx context.Context, serverID string, userID string, required int64) (bool, error) {
|
||||
perms, err := c.GetUserPermissions(ctx, serverID, userID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Administrator bypasses everything.
|
||||
if Has(perms, ADMINISTRATOR) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return Has(perms, required), nil
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package permissions
|
||||
|
||||
// Permission bitflags.
|
||||
const (
|
||||
VIEW_CHANNEL int64 = 1 << 0
|
||||
SEND_MESSAGES int64 = 1 << 1
|
||||
MANAGE_MESSAGES int64 = 1 << 2
|
||||
KICK_MEMBERS int64 = 1 << 3
|
||||
BAN_MEMBERS int64 = 1 << 4
|
||||
MANAGE_SERVER int64 = 1 << 5
|
||||
MANAGE_CHANNELS int64 = 1 << 6
|
||||
ADMINISTRATOR int64 = 1 << 7
|
||||
CONNECT_VOICE int64 = 1 << 8
|
||||
SPEAK_VOICE int64 = 1 << 9
|
||||
SHARE_SCREEN int64 = 1 << 10
|
||||
)
|
||||
|
||||
// DefaultEveryonePermissions is granted to the @everyone role when a server is created.
|
||||
// Bits 0,1,8,9,10 = VIEW_CHANNEL | SEND_MESSAGES | CONNECT_VOICE | SPEAK_VOICE | SHARE_SCREEN = 1539.
|
||||
const DefaultEveryonePermissions int64 = VIEW_CHANNEL | SEND_MESSAGES | CONNECT_VOICE | SPEAK_VOICE | SHARE_SCREEN
|
||||
|
||||
// Has reports whether the permission set contains the given permission bits.
|
||||
func Has(permissions int64, perm int64) bool {
|
||||
return permissions&perm == perm
|
||||
}
|
||||
|
||||
// Add returns permissions with the given bits set.
|
||||
func Add(permissions int64, perm int64) int64 {
|
||||
return permissions | perm
|
||||
}
|
||||
|
||||
// Remove returns permissions with the given bits cleared.
|
||||
func Remove(permissions int64, perm int64) int64 {
|
||||
return permissions &^ perm
|
||||
}
|
||||
Reference in New Issue
Block a user