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 }