63 lines
6.1 KiB
Markdown
63 lines
6.1 KiB
Markdown
# Architecture
|
|
|
|
## Services
|
|
|
|
### Web application
|
|
|
|
The Next.js application owns user onboarding, account management, admin configuration and metrics, server-side Minecraft profile validation, sessions, and the HTTP API used by Discord and Velocity integrations. Database-backed portal and console pages are dynamic React Server Components: authentication, queries, filtering, dashboard aggregation, and the initial Natural Earth user-location map execute on the server and return rendered HTML. Administrators can opt into a hydrated Leaflet/OpenStreetMap view; OSM receives requests only for viewed map tiles, while user marker coordinates remain local to the browser.
|
|
|
|
User authentication begins with an opaque, short-lived, single-use token created for a Discord user. Only a cryptographic hash of the token is persisted. Admin authentication is a separate Keycloak OIDC flow and requires the `minecraft-account-manager-admin` role.
|
|
|
|
### RCON administration
|
|
|
|
The administrator console stores one or more RCON server addresses with write-only AES-GCM-encrypted passwords. Browser requests invoke authenticated server actions; only the Next.js runtime opens RCON TCP connections. Administrators may configure any syntactically valid internal or external DNS hostname and port without deployment-managed endpoint configuration; IP literals remain rejected. Commands and responses are bounded and ephemeral, while credential-safe audit events retain the operator, server, command verb, keyed digest, outcome, and duration. Operators remain responsible for endpoint exposure and transport security.
|
|
|
|
### Discord bot
|
|
|
|
The bot creates private login links in response to `/register` and `/account`. Discord user IDs are the canonical Discord identity; mutable usernames are snapshots only. Nickname updates target the deployment guild configured by `DISCORD_GUILD_ID`; the public join button uses `DISCORD_INVITE_URL`.
|
|
|
|
### Velocity plugin
|
|
|
|
Velocity sends the authenticated Java UUID, current username, source IP, server ID, request ID, and occurrence time. The API matches UUID first. Username fallback is allowed only when the stored account has no UUID, after which UUID and canonical username are updated.
|
|
|
|
The admission decision is fail closed. Unknown players, disabled effective groups, out-of-window scheduled groups, disallowed confirmed VPN/proxy/Tor connections, invalid responses, expired requests, authentication failures, and unavailable API responses are denied. Policy order is fixed: registration, enabled effective group, recurring UTC schedule, then anonymized-network exception. Zero schedule windows mean no time restriction; configured starts are inclusive and ends are exclusive. Scheduling is checked only at login and never disconnects an admitted player. Registration, group-access, schedule, and anonymized-network denials use independent operator-configured static templates; validated variables provide player, group, and next-window UTC guidance without executable expressions. Transport and service failures retain the plugin's local fallback. After admission succeeds, `PostLoginEvent` reports a confirmed proxy connection through a fresh, authenticated, replay-protected request. Connection telemetry is best effort and never disconnects an already admitted player.
|
|
|
|
## Trust boundaries
|
|
|
|
- Browser input is untrusted. Minecraft profile resolution occurs on the server.
|
|
- Forwarded IP headers are accepted only from configured reverse proxies.
|
|
- Discord IDs come from bot-authenticated requests or one-time-code records, not browser fields.
|
|
- Velocity requests use hashed per-server bearer credentials, timestamps, and database-unique request IDs for authentication and replay prevention.
|
|
- Session and one-time-code values are random and stored only as hashes.
|
|
- Exact IP addresses are sensitive data and require an explicit retention policy before production deployment.
|
|
- RCON endpoints require syntactically valid DNS hostnames and ports; passwords never cross the browser trust boundary. Cluster egress policy and administrator authorization constrain the resulting outbound-connectivity trust boundary.
|
|
- RCON commands and responses are untrusted, bounded, rendered only as text, and excluded from persistent history and logs.
|
|
|
|
## Database invariants
|
|
|
|
- A Discord user ID maps to one user.
|
|
- An active Minecraft UUID or case-insensitive username maps to one account.
|
|
- A user has at most one active primary Minecraft account.
|
|
- Group schedule boundaries are integer UTC minutes of the recurring Monday-based week; malformed or overlapping persisted windows fail closed during admission.
|
|
- Removed accounts are soft deleted to retain audit history.
|
|
- Audit events are CloudEvents-shaped, append-only application records.
|
|
- `published_at` reserves an outbox-style path for later Kafka publishing.
|
|
|
|
## IP intelligence
|
|
|
|
ProxyCheck.io supplies approximate city/region/country, coordinates, timezone, ASN, network company, connection type, proxy signal, risk, and VPN/proxy/Tor classification. Results are cached in PostgreSQL for 48 hours by default. Portal logins and every bearer-authenticated Velocity login are enriched through the cache before identity resolution; lookup failures do not independently deny a registered player. Confirmed VPN, proxy, and Tor game connections require an exception on the player's effective group. User Minecraft-account additions fail closed for unknown, VPN, proxy, or Tor classifications and record denied attempts. Hosting-provider blocking is optional through `BLOCK_HOSTING_IPS=true`. Private and reserved addresses are never sent to ProxyCheck.
|
|
|
|
## Event naming
|
|
|
|
Events use reverse-DNS names beneath `games.minecraft.account-manager`, including:
|
|
|
|
- `games.minecraft.account-manager.ui.accessed`
|
|
- `games.minecraft.account-manager.auth.magic-link.consumed`
|
|
- `games.minecraft.account-manager.minecraft-account.added`
|
|
- `games.minecraft.account-manager.minecraft-account.removed`
|
|
- `games.minecraft.account-manager.discord.nickname.updated`
|
|
- `games.minecraft.account-manager.network.vpn-blocked`
|
|
- `games.minecraft.account-manager.game.login.allowed`
|
|
- `games.minecraft.account-manager.game.login.denied`
|
|
- `games.minecraft.account-manager.game.player.connected`
|