Authentication & RBAC
Password authentication, JWT tokens, and role-based access control.
6 min readDual-Mode Auth
GroundWave supports two authentication modes, selected at server start-up via the
AUTH_REQUIRED environment variable. This lets operators run an open,
callsign-only deployment for trusted teams while enabling full password protection
for environments with broader access.
| Mode | AUTH_REQUIRED |
Behavior |
|---|---|---|
| Open (Phase 1) | false |
Users register with a callsign only. No password required. All authenticated users are treated as operators. |
| Authenticated | true |
Users must register with a callsign and password. JWT tokens required for all API and Socket.IO access. Roles are enforced. |
Switching from open mode to authenticated mode on an existing deployment requires all existing users to set passwords. Plan the transition before going to the field.
Registration
New users register by posting to POST /api/users/register. In open mode,
only a callsign is required. In authenticated mode, a password must also be supplied.
{ "callsign": "ALPHA-1", "password": "..." }
— password is required when AUTH_REQUIRED=true.
The first user to register is automatically promoted to the
admin role. All subsequent registrations receive the
observer role by default. An admin can promote users to
operator or admin via the admin dashboard.
Passwords are hashed using argon2id — the memory-hard winner of the
Password Hashing Competition. Raw passwords are never stored or logged. The hash is
stored in the users table alongside the salt automatically managed by the
argon2 library.
Login
Registered users authenticate by posting their callsign and password. A successful login returns a signed JWT token that must be included with every subsequent request.
{ "callsign": "ALPHA-1", "password": "..." }Returns:
{ "token": "eyJ...", "user": { "id", "callsign", "role" } }
The login endpoint is rate-limited to prevent brute-force attacks.
After a configurable number of failed attempts from the same IP address, further
attempts are temporarily blocked and a 429 Too Many Requests response
is returned.
JWT Tokens
Tokens are signed with a secret derived from the JWT_SECRET environment
variable. The expiration window is configurable via JWT_EXPIRY
(default: 24h).
Tokens are transmitted in two ways depending on the transport:
-
REST requests: Include the token in the
Authorization: Bearer <token>HTTP header. -
Socket.IO connections: Pass the token as the
authquery parameter when connecting:io('/', { auth: { token } }).
The client monitors token expiry and automatically redirects to the login page when
a token is near expiration or when the server returns a 401 Unauthorized
response. Tokens are stored in localStorage and cleared on logout or
expiry detection.
GroundWave does not currently implement token revocation. If a user's account is disabled, their existing JWT remains valid until it expires. For immediate revocation, set a short JWT_EXPIRY (e.g., 1h) in high-security deployments. The admin can also disable accounts, which blocks fresh logins and Socket.IO re-authentications.
Roles
GroundWave uses a three-tier role hierarchy. Roles are stored in the
users table and enforced on every API request and Socket.IO event.
Higher roles inherit all capabilities of lower roles.
Observer
Observers have read-only access to the operational picture. They can connect to the server, view the live map, see position markers for all users, read chat messages in channels they are members of, and view markers and overlays. They cannot produce any data:
- Cannot send chat messages or direct messages
- Cannot create, edit, or delete markers
- Cannot upload or delete files
- Cannot create or modify map overlays
- Can listen to voice channels but cannot transmit (PTT button disabled)
The Observer role is appropriate for commanders or liaisons who need situational awareness without the ability to modify shared data.
Operator
Operators have full operational capabilities. They can use every feature of GroundWave:
- Send and receive chat messages in channels and direct messages
- Create, edit, and delete markers (points, lines, polygons) with optional photo attachments
- Upload, download, and delete files
- Create and manage map overlays (GeoJSON, GPX, georeferenced images)
- Transmit on voice PTT channels
- Import markers from GeoJSON files
Operators cannot access admin-only routes such as user management, data export, tile management, federation configuration, or system telemetry.
Admin
Admins have all Operator capabilities plus exclusive access to server administration:
- View and manage all user accounts (promote, demote, disable, reset passwords)
- Export operational data (positions, chat, markers as GeoJSON/GPX/JSON)
- Bulk-delete data by type or date range
- Manage tile sets (download, import, set active tileset, delete)
- Configure and monitor server federation peers
- View the real-time log stream and system telemetry
- Reset the first-run setup wizard state
- Access the detailed health endpoint (if API key protection is disabled)
Enforcement
RBAC is enforced at two layers to ensure no path bypasses authorization:
REST Middleware
Every protected route passes through an authenticate middleware that
validates the JWT from the Authorization header. Role-gated routes
additionally pass through a requireRole(minRole) middleware that checks
the decoded token's role against the required minimum. Requests failing either check
receive a 401 or 403 response with no data leaked.
Socket.IO Event Handlers
The Socket.IO server validates the token on connection. Each event handler that
requires a minimum role calls checkSocketRole(socket, minRole), which
fetches the user's current role fresh from the database on every
check — not from the token payload. This means that if an admin demotes a user
mid-session, the change takes effect immediately for all subsequent events on that
connection without requiring the user to reconnect.
Role changes are live. Demoting a user from Operator to Observer takes effect on their next Socket.IO event — no disconnect required. Promoting a user similarly grants new capabilities immediately.
Open Mode Behavior
When AUTH_REQUIRED=false, the authentication middleware is bypassed
entirely — no token is needed for REST or Socket.IO. All requests are treated as
coming from an operator-level user identified only by their callsign. Role checks
are effectively no-ops, and the admin dashboard is accessible by any connected user.
This mode is intended for trusted local networks where user management overhead
is undesirable.