MapCTF is split into two backend services:
mapctf-apifor JSON API workflows.mapctf-mapfor the browser-facing map experience, session-backed gameplay pages, JSON feeds, and admin operations.
The current backend design is UUID-scoped. A single map service instance is configured for one active game UUID, and nearly every browser route and persistence lookup is filtered through that UUID.
This document focuses on the backend runtime, with emphasis on the current mapctf-map service. Frontend implementation details are intentionally omitted.
┌─────────────────────┐
│ Browser Client │
│ login/gameboard/ │
│ admin pages │
└──────────┬──────────┘
│ HTTP + session cookie
▼
┌──────────────────────────────┐
│ mapctf-map │
│ (:8082) │
│ │
│ - HTML template rendering │
│ - static asset delivery │
│ - session auth via SCS │
│ - JSON feeds for game UI │
│ - admin import/export flows │
└──────────┬───────────┬────────┘
│ │
│ └──────────────────────┐
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ PostgreSQL │ │ Redis │
│ domain + settings + │ │ session store via │
│ logs + map metadata │ │ goredisstore/SCS │
└─────────────────────┘ └─────────────────────┘
mapctf/
├── backend/
│ ├── cmd/
│ │ ├── api/ # JSON API service
│ │ ├── cli/ # CLI utilities
│ │ └── map/ # Map service entrypoint, handlers, templates
│ └── pkg/
│ ├── backend/ # DB lifecycle and retry logic
│ ├── cache/ # Redis lifecycle and retry logic
│ ├── challenges/ # Challenges and categories
│ ├── config/ # Flags, YAML config, validation
│ ├── countries/ # SVG/map country metadata and assignment state
│ ├── logs/ # Activity, announcement, scoreboard, hint, failure, registration logs
│ ├── settings/ # UUID-scoped runtime settings and audit logs
│ ├── teams/ # Teams, memberships, scores, logos
│ ├── users/ # Users, auth, sessions metadata
│ └── version/ # Build metadata
├── deploy/
├── docker-compose-dev.yml
└── tools/
backend/cmd/map/main.go wires the service in this order:
- Load config and ensure a UUID exists.
- Connect to PostgreSQL with retry support.
- Connect to Redis with retry support.
- Initialize managers:
teamsuserschallengessettingslogscountries
- Seed mutable runtime data:
- team logos from
Map.LogosFile - countries from
Map.CountriesFile - default settings via
SettingsManager.Initialization()
- team logos from
- Create an SCS session manager backed by Redis.
- Build a Chi router and register public, authenticated, JSON, and admin routes.
The map service also optionally enables request-level HTTP debug logging through a rotating lumberjack logger when DebugHTTP.Enabled is configured.
The map service renders the user-facing HTML templates under backend/cmd/map/templates:
- landing page:
GET /{uuid}/ - login page:
GET /{uuid}/login - registration page:
GET /{uuid}/registration - countdown page:
GET /{uuid}/countdown - rules page:
GET /{uuid}/rules - form posts:
POST /{uuid}/loginPOST /{uuid}/registrationPOST /{uuid}/logout
Service-level utility routes are also served directly:
GET /healthGET /errorGET /forbiddenGET /favicon.icoGET /static/*
After session authentication, the service exposes:
GET /{uuid}/gameboard- JSON feeds under
/{uuid}/json/*:GET /activityGET /announcementsGET /challengesGET /teams
These JSON endpoints are used by the gameboard/admin templates to refresh dynamic state without moving that logic into the API service.
The map service now owns a substantial admin surface under /{uuid}/admin, gated by both session auth and admin role checks.
Main admin pages:
GET /admin/GET /admin/settingsGET /admin/controlsGET /admin/challengesGET /admin/usersGET /admin/teamsGET /admin/activityGET /admin/announcementsGET /admin/countries
Admin write paths include:
- settings update, export, import, and reset-to-defaults
- full game export/import
- challenge CRUD, category CRUD, bulk enable/disable/delete, import/export
- user CRUD, bulk enable/disable/delete, import/export
- team CRUD, team visibility toggles, team bulk delete, import/export
- team logo CRUD, bulk enable/disable/delete, custom SVG uploads
- country active-state updates and challenge assignment views
The admin handlers support both browser form workflows and JSON/XHR-style responses depending on request headers.
The mapctf-map router uses Chi with:
- request IDs
- real IP extraction
- request logging
- panic recovery
- 30-second request timeout
sessionManager.LoadAndSavefor SCS session persistence
Route protection is layered:
- public routes are available under
/{uuid}without auth RequireAuthredirects unauthenticated users to/{uuid}/loginRequireAdminblocks non-admin users from/adminroutes
Every template and mutation handler also validates that the URL UUID matches the configured service UUID before continuing.
The map service does not use JWTs for browser flows. It uses SCS server-side sessions stored in Redis.
Session behavior from backend/cmd/map/main.go:
- cookie name:
session_id HttpOnly: enabledSecure: enabled- path:
/ - persistent cookie: enabled
- lifetime: 24 hours
- idle timeout: 30 minutes
On login:
- credentials are checked through
pkg/users - the SCS token is renewed
- username and admin status are stored in the session
- user session metadata is updated with IP address and user agent
On logout:
- the SCS session is destroyed
- the client is redirected back to
/{uuid}/login
The map service now depends heavily on pkg/settings, which auto-migrates:
platform_settingssetting_logs
SettingsManager.Initialization() creates missing default settings for the active UUID. Current setting groups include:
- booleans:
login_enabledlogin_strong_passwordsregistration_enabledregistration_namesregistration_emailsscoring_enabledgame_pausedgame_started
- strings:
custom_orgcustom_logolanguageregistration_token
- dates:
game_start_timegame_end_time
- integers:
registration_typeleaderboard_limit
These settings directly control browser and admin behavior such as:
- whether login is open to non-admin users
- whether registration is open, token-gated, or requires extra fields
- event timing and paused/started state
- scoring visibility and leaderboard size
- custom branding strings/assets
All settings mutations are audited through setting_logs.
pkg/countries is a newer part of the runtime model and auto-migrates map_countries.
Countries are seeded from a JSON file referenced by Map.CountriesFile. Each record stores:
- country name and code
- active/inactive state
- whether it is assigned to a challenge
- the assigned challenge ID
- SVG/vector metadata used to render the map:
- land path/class/style
- marker path/class/style/transform
The admin countries page combines this country data with challenge lookups so operators can understand which challenges are attached to which map regions.
pkg/logs now provides the data backing both JSON feeds and admin reporting. It auto-migrates:
activity_logsannouncementsscoreboard_logshints_logsfailures_logsregistration_logs
The map service currently surfaces:
- activity streams through
/json/activityand the admin activity page - announcements through
/json/announcementsand the admin announcements page
This keeps event-style operational data close to the browser-facing service rather than routing it through the API service.
The map service composes several domain managers rather than reimplementing domain logic:
pkg/teams- teams
- memberships
- scores
- team logos
- initial logo seeding
pkg/users- credential validation
- user registration
- session metadata updates
pkg/challenges- challenge CRUD
- category CRUD
- active challenge retrieval for the gameboard
Admin import/export handlers in backend/cmd/map/handlers/admin.go orchestrate these managers to move settings, users, teams, logos, categories, and challenges as versioned JSON payloads.
The map-side backend currently persists at least the following table families through GORM auto-migrations in the manager packages:
- users:
platform_users
- teams:
platform_teamsteam_membershipsteam_scoresteam_logos
- challenges:
challengescategories
- map metadata:
map_countries
- settings/audit:
platform_settingssetting_logs
- event logs:
activity_logsannouncementsscoreboard_logshints_logsfailures_logsregistration_logs
UUID scoping is enforced across the core game data so multiple game instances can coexist in the same database without route or query collisions.
For local development, docker-compose-dev.yml runs:
mapctf-apimapctf-map- PostgreSQL
- Redis
The map service depends on both PostgreSQL and Redis being reachable during startup because it initializes domain managers before serving traffic.
- The map service is configured for one active UUID at a time, even though the database schema itself is UUID-scoped.
- Some legacy naming and older documentation may still refer to earlier entity or frontend-driven flows.
FaviconHandlerserves/static/img/favicon.png, so static asset pathing needs to remain aligned with deployment layout.