Skip to content

Authentication System

This page documents the authentication system implementation for contributors working in internal/auth/. If you’re an operator configuring authentication for a server, see the operator auth guide instead.

The authentication system spans three layers: a protocol handler in the gateway that calls core gRPC services, service implementations that enforce security invariants (timing-safe comparisons, argon2id hashing), and a repository layer that persists tokens and sessions to PostgreSQL. The sections below trace each layer from the wire inward.

HoloMUSH uses a three-layer authentication architecture:

flowchart TB
subgraph Protocol["Protocol Layer"]
GH[GatewayHandler]
WH["WebHandler (future)"]
end
subgraph gRPC["gRPC Boundary"]
CC[CoreClient]
end
subgraph Core["Core Server"]
AS[AuthService]
CS[CharacterService]
PRS[PasswordResetService]
end
subgraph Repo["Repository Layer"]
PR[PlayerRepository]
WSR[WebSessionRepository]
PRR[PasswordResetRepository]
end
GH --> CC
WH --> CC
CC --> AS
CC --> CS
AS --> PR
AS --> WSR
CS --> PR
PRS --> PR
PRS --> PRR

Handles core authentication operations.

Method Purpose Source
Login Authenticate player, create session internal/auth/auth_service.go:81
Logout Invalidate session internal/auth/auth_service.go:197
ValidateSession Verify token, update last-seen internal/auth/auth_service.go:215
SelectCharacter Bind character to session internal/auth/auth_service.go:254

Manages character creation with validation.

Method Purpose Source
Create Create character (default limit) internal/auth/character_service.go:56
CreateWithMaxCharacters Create with custom limit internal/auth/character_service.go:61

Character creation validates:

  • Name format (2-32 chars, letters and spaces, normalized to Initial Caps)
  • Name uniqueness (case-insensitive)
  • Player character limit (default: 5)
  • Starting location assignment

Handles password reset flow.

Method Purpose Source
RequestReset Generate reset token for email internal/auth/reset_service.go:80
ValidateToken Verify token validity and expiration internal/auth/reset_service.go:118
ResetPassword Update password, invalidate tokens internal/auth/reset_service.go:148

The auth service uses a dummy hash for non-existent users to prevent username enumeration:

Login(username, password):
1. Look up user by username
2. If not found -> use dummy hash (still verify to maintain constant time)
3. Verify password against hash (real or dummy)
4. Return same error for "user not found" and "wrong password"

See internal/auth/auth_service.go:67-76 for the dummy hash constant and internal/auth/auth_service.go:81-130 for the timing-safe implementation.

Passwords are hashed using argon2id with OWASP-recommended parameters:

Parameter Value
Time 1 iteration
Memory 64 MB
Parallelism 4 threads
Salt 16 bytes (random)
Output 32 bytes

Implementation: internal/auth/hasher.go:30-36

Session token verification uses crypto/subtle.ConstantTimeCompare to prevent timing attacks that could leak token prefixes.

See internal/auth/session.go:115-125 for the implementation with security comments.

Sessions use opaque random tokens rather than signed JWTs. Key design choices:

Aspect Implementation
Token size 32 bytes (256 bits of entropy)
Storage SHA256 hash in database
Expiry TTL after detach (per-role, default 24h)
Revocation Instant (delete database row)
Character bind Mutable (update session row)

See ADR 0001 for the rationale behind choosing opaque tokens over signed JWTs.

sequenceDiagram
participant C as Client
participant GH as GatewayHandler
participant CC as CoreClient (gRPC)
participant A as AuthService
C->>GH: TCP connect
GH->>C: Welcome banner
C->>GH: connect <user> <pass>
GH->>CC: AuthenticatePlayer(username, password)
CC->>A: AuthenticatePlayer
A->>CC: Session + Characters
CC->>GH: Session + Characters
GH->>C: Character list
alt Has characters
C->>GH: play <charname>
GH->>CC: SelectCharacter(sessionID, charID)
CC->>A: SelectCharacter
GH->>C: Enter world
else No characters
C->>GH: create <name>
GH->>CC: CreateCharacter(playerID, name)
CC->>A: CreateCharacter
GH->>C: Enter world
end

The telnet handler calls through the gRPC CoreClient interface, not service methods directly:

Command Handler Method gRPC RPC Source
connect handleConnect AuthenticatePlayer internal/telnet/gateway_handler.go
create handleCreate CreateCharacter internal/telnet/gateway_handler.go
play handlePlay SelectCharacter internal/telnet/gateway_handler.go
quit handleQuit Logout internal/telnet/gateway_handler.go
internal/auth/player.go:128-151

Manages player account persistence including username lookups, email lookups, and password updates.

internal/auth/session.go:127-156

Manages session persistence including token hash lookups, last-seen updates, character binding, and expiration cleanup.

internal/auth/reset.go

Manages reset token persistence with token hash lookups and player-based cleanup.