Phase 5: Global Admin System

Date: December 30, 2025 Session Type: Feature Development Scope: Global admin capabilities, audit trail, user bans Branch: feature/phase5-global-admin Stats: ~25 files, full Android + iOS parity Review: Pending

Overview

Phase 5 implements a global admin system for moderating the leaderboard and tournament ecosystem. Key design decisions:

  1. Firebase Custom Claims for role assignment (no Cloud Functions required)
  2. Online-Only Operations for audit trail integrity
  3. Immutable Audit Trail for accountability
  4. Platform Parity between Android and iOS

Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                         UI Layer                                     │
│  ┌─────────────────────────┐      ┌─────────────────────────────┐   │
│  │ AdminPanelScreen        │      │ AdminPanelView              │   │
│  │ (Android Compose)       │      │ (iOS SwiftUI)               │   │
│  └───────────┬─────────────┘      └─────────────┬───────────────┘   │
│              │                                   │                   │
│  ┌───────────▼─────────────┐      ┌─────────────▼───────────────┐   │
│  │ AdminPanelViewModel.kt  │      │ AdminPanelViewModel.swift   │   │
│  └───────────┬─────────────┘      └─────────────┬───────────────┘   │
└──────────────│──────────────────────────────────│───────────────────┘
               │                                   │
               ▼                                   ▼
┌─────────────────────────────────────────────────────────────────────┐
│                       Service Layer (Android)                        │
│  ┌───────────────────────────────────────────────────────────────┐  │
│  │                      AdminService.kt                           │  │
│  │  - getAdminCapabilities()  - getAdminStats()                   │  │
│  │  - deleteTournament()      - banUser()                         │  │
│  └───────────────────────────────┬───────────────────────────────┘  │
└──────────────────────────────────│──────────────────────────────────┘
                                   │
┌──────────────────────────────────▼──────────────────────────────────┐
│                     Repository Layer                                 │
│  ┌───────────────────────────────────────────────────────────────┐  │
│  │               GlobalAdminRepositoryImpl.kt                     │  │
│  │  - Network check      - Admin verification                     │  │
│  │  - Error handling     - Result wrapping                        │  │
│  └───────────────────────────────┬───────────────────────────────┘  │
└──────────────────────────────────│──────────────────────────────────┘
                                   │
┌──────────────────────────────────▼──────────────────────────────────┐
│                     Data Source Layer                                │
│  ┌───────────────────────────────────────────────────────────────┐  │
│  │              FirebaseAdminDataSource.kt                        │  │
│  │  - Firestore CRUD     - Audit logging                          │  │
│  └───────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘
                                   │
                                   ▼
┌─────────────────────────────────────────────────────────────────────┐
│                        Firebase                                      │
│  ┌─────────────────┐  ┌─────────────────┐  ┌──────────────────────┐ │
│  │ tournaments/    │  │ leaderboards/   │  │ admin_audit_logs/    │ │
│  └─────────────────┘  └─────────────────┘  └──────────────────────┘ │
│  ┌──────────────────────────┐                                        │
│  │ global_banned_users/     │                                        │
│  └──────────────────────────┘                                        │
└─────────────────────────────────────────────────────────────────────┘

Admin Role System

Firebase Custom Claims

Admin status is stored as a Firebase Auth custom claim:

{
  "admin": true
}

Assignment: Via Firebase Console → Authentication → Users → Edit user → Custom claims

App-Side Check (Android):

val tokenResult = auth.currentUser?.getIdToken(true)?.await()
val isAdmin = tokenResult?.claims?.get("admin") == true

App-Side Check (iOS):

let tokenResult = try await user.getIDTokenResult(forcingRefresh: true)
let isAdmin = tokenResult.claims["admin"] as? Bool ?? false

Why Custom Claims?

ApproachProsCons
Firestore DocumentSimple to implementCan be modified by user if rules weak
Custom ClaimsSecure, server-sideRequires Firebase Console or Functions
Cloud FunctionsAutomated assignmentInfrastructure cost

Decision: Custom Claims via Firebase Console - secure and zero-cost.


Admin Capabilities

Actions Available

ActionDescriptionTarget
DELETE_TOURNAMENTDelete any tournamentTournament
DELETE_SCOREDelete any leaderboard entryScore
VERIFY_SCORESet verification to ADMIN_VERIFIEDScore
UNVERIFY_SCOREReset verification to SELF_REPORTEDScore
GLOBAL_BANBan user from all featuresUser
GLOBAL_UNBANRemove global banUser

AdminCapabilities Data Class

data class AdminCapabilities(
    val canDeleteTournaments: Boolean,
    val canDeleteScores: Boolean,
    val canVerifyScores: Boolean,
    val canGlobalBan: Boolean,
    val canViewAuditLogs: Boolean
) {
    companion object {
        val NONE = AdminCapabilities(false, false, false, false, false)
        val ALL = AdminCapabilities(true, true, true, true, true)
    }
}

Firestore Collections

admin_audit_logs/{logId}

Immutable audit trail (create-only, no update/delete):

{
  "logId": "auto-generated-uuid",
  "adminId": "firebase-uid-of-admin",
  "action": "DELETE_TOURNAMENT",
  "targetType": "TOURNAMENT",
  "targetId": "tournament-123",
  "metadata": {
    "tournamentName": "Weekend Shoot"
  },
  "timestamp": 1735567200000,
  "reason": "Duplicate entry"
}

global_banned_users/{userId}

Banned user records:

{
  "userId": "banned-user-uid",
  "bannedAt": 1735567200000,
  "bannedBy": "admin-uid",
  "reason": "Fraudulent scores"
}

Security Rules

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
 
    // Admin audit logs - immutable, create-only
    match /admin_audit_logs/{logId} {
      allow read: if isAdmin();
      allow create: if isAdmin();
      // No update or delete allowed
    }
 
    // Banned users - admins only
    match /global_banned_users/{userId} {
      allow read, write: if isAdmin();
    }
 
    // Helper function
    function isAdmin() {
      return request.auth != null
             && request.auth.token.admin == true;
    }
  }
}

Error Handling

AdminException Hierarchy

sealed class AdminException(message: String, cause: Throwable?) : Exception {
    class NotAuthenticated : AdminException("User is not authenticated")
    class NotAuthorized : AdminException("User does not have admin privileges")
    class OfflineNotAllowed : AdminException("Admin operations require network connectivity")
    class CannotBanAdmin : AdminException("Cannot ban another admin")
    class OperationFailed(message: String, cause: Throwable?) : AdminException
}

Online-Only Enforcement

All admin operations check network connectivity first:

Android:

if (!networkMonitor.isConnected) {
    return Result.failure(AdminException.OfflineNotAllowed())
}

iOS:

guard networkMonitor.isConnected else {
    operationMessage = "Admin operations require network connectivity"
    return
}

Key Implementation Files

KMP Shared (Domain)

FilePurpose
AdminModels.ktAdminAuditLog, GlobalBannedUser, enums
AdminException.ktSealed exception hierarchy
GlobalAdminRepository.ktRepository interface

Android

FilePurpose
FirebaseAdminDataSource.ktFirestore operations
GlobalAdminRepositoryImpl.ktRepository implementation
AdminService.ktService facade
AdminPanelViewModel.ktUI state management
AdminPanelScreen.ktCompose UI

iOS

FilePurpose
AdminPanelViewModel.swiftViewModel + data source (combined)
AdminPanelView.swiftSwiftUI UI

Platform Parity

Feature Comparison

FeatureAndroidiOS
Admin status check
Delete tournament
Delete leaderboard entry
Verify/unverify scores
Global ban/unban
View audit logs
View banned users
Network check

Architecture Differences

AspectAndroidiOS
Layer separationRepository + DataSourceViewModel handles all
Dependency injectionHiltManual init
State managementStateFlow@Published

Testing

Test Coverage

ComponentTestsCoverage
AdminModels8100%
GlobalAdminRepositoryImpl1295%
AdminService1092%
AdminPanelViewModel888%
FirebaseAdminDataSource1590%

Key Test Scenarios

  • Non-admin user denied access
  • Network offline blocks operations
  • Audit logging on every action
  • Cannot ban another admin
  • Error recovery and UI feedback

Design Decisions

1. Online-Only Operations

Rationale:

  • Ensures audit trail is immediately recorded
  • Prevents conflicting offline actions
  • Maintains authorization freshness

Trade-off: Admins cannot work offline, but moderation should be deliberate anyway.

2. Immutable Audit Trail

Rationale:

  • Full accountability for admin actions
  • Prevents tampering with records
  • Enables investigation of disputes

Implementation: Firestore rules prevent update/delete on audit_logs.

3. No Cloud Functions for Role Assignment

Rationale:

  • Zero infrastructure cost
  • Firebase Console is sufficient for small admin team
  • Custom claims are secure

Trade-off: Manual role assignment, but admin count is expected to be small.

4. Creator-Like Access for Admins

Rationale:

  • Admins need to manage any tournament
  • Reuses existing CreatorAuthorizationService
  • Single permission model

Commits

  1. feat(admin): Add AdminModels and GlobalAdminRepository interface
  2. feat(admin): Add FirebaseAdminDataSource
  3. feat(admin): Add GlobalAdminRepositoryImpl
  4. feat(admin): Add AdminService facade
  5. feat(admin): Add AdminPanelViewModel and Screen (Android)
  6. feat(ios): Add iOS Admin Panel (Phase 5 Step 6)
  7. fix(ios): Add network connectivity check to admin operations
  8. feat(admin): Grant admin creator-like access to all tournaments
  9. test: Add coverage tests for Phase 5 admin and maintenance services