enki b3204ea07a
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
first commit
2025-08-18 00:40:15 -07:00

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
}