Some checks are pending
CI Pipeline / Run Tests (push) Waiting to run
CI Pipeline / Lint Code (push) Waiting to run
CI Pipeline / Security Scan (push) Waiting to run
CI Pipeline / Build Docker Images (push) Blocked by required conditions
CI Pipeline / E2E Tests (push) Blocked by required conditions
226 lines
6.2 KiB
Go
226 lines
6.2 KiB
Go
package admin
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.sovbit.dev/enki/torrentGateway/internal/auth"
|
|
)
|
|
|
|
// AdminAuth handles admin authentication and authorization
|
|
type AdminAuth struct {
|
|
adminPubkeys map[string]bool
|
|
nostrAuth *auth.NostrAuth
|
|
db *sql.DB
|
|
}
|
|
|
|
// NewAdminAuth creates a new admin authentication handler
|
|
func NewAdminAuth(adminPubkeys []string, nostrAuth *auth.NostrAuth, db *sql.DB) *AdminAuth {
|
|
pubkeyMap := make(map[string]bool)
|
|
for _, pubkey := range adminPubkeys {
|
|
pubkeyMap[pubkey] = true
|
|
}
|
|
|
|
return &AdminAuth{
|
|
adminPubkeys: pubkeyMap,
|
|
nostrAuth: nostrAuth,
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
// IsAdmin checks if a pubkey belongs to an admin
|
|
func (aa *AdminAuth) IsAdmin(pubkey string) bool {
|
|
return aa.adminPubkeys[pubkey]
|
|
}
|
|
|
|
// ValidateAdminRequest validates that the request comes from an authenticated admin
|
|
func (aa *AdminAuth) ValidateAdminRequest(r *http.Request) (string, error) {
|
|
// Extract session token from header or cookie
|
|
var token string
|
|
authHeader := r.Header.Get("Authorization")
|
|
if authHeader != "" && strings.HasPrefix(authHeader, "Bearer ") {
|
|
token = strings.TrimPrefix(authHeader, "Bearer ")
|
|
} else if cookie, err := r.Cookie("session_token"); err == nil {
|
|
token = cookie.Value
|
|
}
|
|
|
|
if token == "" {
|
|
return "", fmt.Errorf("no session token found")
|
|
}
|
|
|
|
// Validate session
|
|
pubkey, err := aa.nostrAuth.ValidateSession(token)
|
|
if err != nil {
|
|
return "", fmt.Errorf("invalid session: %w", err)
|
|
}
|
|
|
|
// Check if user is admin
|
|
if !aa.IsAdmin(pubkey) {
|
|
return "", fmt.Errorf("access denied: user is not an admin")
|
|
}
|
|
|
|
return pubkey, nil
|
|
}
|
|
|
|
// LogAdminAction logs an admin action to the database
|
|
func (aa *AdminAuth) LogAdminAction(adminPubkey, actionType, targetID, reason string) error {
|
|
_, err := aa.db.Exec(`
|
|
INSERT INTO admin_actions (admin_pubkey, action_type, target_id, reason, timestamp)
|
|
VALUES (?, ?, ?, ?, ?)
|
|
`, adminPubkey, actionType, targetID, reason, time.Now())
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to log admin action: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetAdminActions retrieves admin actions with optional filtering
|
|
func (aa *AdminAuth) GetAdminActions(limit int, offset int, adminPubkey string) ([]AdminAction, error) {
|
|
query := `
|
|
SELECT id, admin_pubkey, action_type, target_id, reason, timestamp
|
|
FROM admin_actions
|
|
`
|
|
args := []interface{}{}
|
|
|
|
if adminPubkey != "" {
|
|
query += " WHERE admin_pubkey = ?"
|
|
args = append(args, adminPubkey)
|
|
}
|
|
|
|
query += " ORDER BY timestamp DESC LIMIT ? OFFSET ?"
|
|
args = append(args, limit, offset)
|
|
|
|
rows, err := aa.db.Query(query, args...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query admin actions: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var actions []AdminAction
|
|
for rows.Next() {
|
|
var action AdminAction
|
|
err := rows.Scan(&action.ID, &action.AdminPubkey, &action.ActionType,
|
|
&action.TargetID, &action.Reason, &action.Timestamp)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to scan admin action: %w", err)
|
|
}
|
|
actions = append(actions, action)
|
|
}
|
|
|
|
return actions, nil
|
|
}
|
|
|
|
// AdminAction represents an admin action log entry
|
|
type AdminAction struct {
|
|
ID int `json:"id"`
|
|
AdminPubkey string `json:"admin_pubkey"`
|
|
ActionType string `json:"action_type"`
|
|
TargetID string `json:"target_id"`
|
|
Reason string `json:"reason"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// BannedUser represents a banned user
|
|
type BannedUser struct {
|
|
Pubkey string `json:"pubkey"`
|
|
BannedBy string `json:"banned_by"`
|
|
Reason string `json:"reason"`
|
|
BannedAt time.Time `json:"banned_at"`
|
|
}
|
|
|
|
// ContentReport represents a content report
|
|
type ContentReport struct {
|
|
ID int `json:"id"`
|
|
FileHash string `json:"file_hash"`
|
|
ReporterPubkey string `json:"reporter_pubkey"`
|
|
Reason string `json:"reason"`
|
|
Status string `json:"status"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// BanUser bans a user with the given reason
|
|
func (aa *AdminAuth) BanUser(userPubkey, adminPubkey, reason string) error {
|
|
// Check if user is already banned
|
|
var exists bool
|
|
err := aa.db.QueryRow("SELECT EXISTS(SELECT 1 FROM banned_users WHERE pubkey = ?)", userPubkey).Scan(&exists)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check ban status: %w", err)
|
|
}
|
|
|
|
if exists {
|
|
return fmt.Errorf("user is already banned")
|
|
}
|
|
|
|
// Insert ban record
|
|
_, err = aa.db.Exec(`
|
|
INSERT INTO banned_users (pubkey, banned_by, reason, banned_at)
|
|
VALUES (?, ?, ?, ?)
|
|
`, userPubkey, adminPubkey, reason, time.Now())
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to ban user: %w", err)
|
|
}
|
|
|
|
// Log admin action
|
|
return aa.LogAdminAction(adminPubkey, "ban_user", userPubkey, reason)
|
|
}
|
|
|
|
// UnbanUser removes a user ban
|
|
func (aa *AdminAuth) UnbanUser(userPubkey, adminPubkey, reason string) error {
|
|
result, err := aa.db.Exec("DELETE FROM banned_users WHERE pubkey = ?", userPubkey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to unban user: %w", err)
|
|
}
|
|
|
|
rowsAffected, err := result.RowsAffected()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check unban result: %w", err)
|
|
}
|
|
|
|
if rowsAffected == 0 {
|
|
return fmt.Errorf("user is not banned")
|
|
}
|
|
|
|
// Log admin action
|
|
return aa.LogAdminAction(adminPubkey, "unban_user", userPubkey, reason)
|
|
}
|
|
|
|
// IsUserBanned checks if a user is banned
|
|
func (aa *AdminAuth) IsUserBanned(pubkey string) (bool, error) {
|
|
var exists bool
|
|
err := aa.db.QueryRow("SELECT EXISTS(SELECT 1 FROM banned_users WHERE pubkey = ?)", pubkey).Scan(&exists)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to check ban status: %w", err)
|
|
}
|
|
return exists, nil
|
|
}
|
|
|
|
// GetBannedUsers returns list of banned users
|
|
func (aa *AdminAuth) GetBannedUsers() ([]BannedUser, error) {
|
|
rows, err := aa.db.Query(`
|
|
SELECT pubkey, banned_by, reason, banned_at
|
|
FROM banned_users
|
|
ORDER BY banned_at DESC
|
|
`)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query banned users: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var bannedUsers []BannedUser
|
|
for rows.Next() {
|
|
var user BannedUser
|
|
err := rows.Scan(&user.Pubkey, &user.BannedBy, &user.Reason, &user.BannedAt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to scan banned user: %w", err)
|
|
}
|
|
bannedUsers = append(bannedUsers, user)
|
|
}
|
|
|
|
return bannedUsers, nil
|
|
} |