Skip to content

feat(auth): enforce JWT authentication on matches and tournaments endpoints#463

Open
mohadon0 wants to merge 1 commit into
Arenax-gaming:mainfrom
mohadon0:feature/jwt-auth-enforcement
Open

feat(auth): enforce JWT authentication on matches and tournaments endpoints#463
mohadon0 wants to merge 1 commit into
Arenax-gaming:mainfrom
mohadon0:feature/jwt-auth-enforcement

Conversation

@mohadon0
Copy link
Copy Markdown

@mohadon0 mohadon0 commented Jun 2, 2026

Closes #372


Overview

Removes all placeholder user identity generation from the HTTP layer and enforces JWT authentication on every protected endpoint. Requests without a valid JWT are rejected with 401 Unauthorized before reaching business logic.

What Changed

New: src/auth/extractor.rs

Actix-Web FromRequest implementation for Claims:

  • Reads Authorization: Bearer <token> header
  • Validates the token using JwtService::validate_token (registered as web::Data<Arc<JwtService>>)
  • Short-circuits to request extensions when AuthMiddleware already ran — zero overhead for middleware-wrapped scopes
  • Error mapping: expired → 401, blacklisted/revoked → 403, all other failures → 401

New: src/http/matches.rs

Protected match endpoints — every handler declares claims: Claims as a parameter:

Method Route Handler
GET /api/matches/{id} get_match
POST /api/matches/{id}/report report_score
POST /api/matches/{id}/dispute dispute_match

User identity: Uuid::parse_str(&claims.sub) — no Uuid::new_v4() placeholder.

New: src/http/tournaments.rs

Protected tournament endpoints:

Method Route Handler
GET /api/tournaments list_tournaments
GET /api/tournaments/{id} get_tournament
POST /api/tournaments/{id}/join join_tournament

Same identity pattern. Unauthenticated requests never reach TournamentService.

src/main.rs

  • Registers the new /api/matches scope
  • Extends /api/tournaments scope with list, get, join routes alongside the existing /{id}/statistics
  • JwtService was already registered as web::Data<Arc<JwtService>> — no change needed

src/auth/mod.rs + src/http/mod.rs

  • Added pub mod extractor, pub mod matches, pub mod tournaments

backend/Cargo.toml

  • Added actix-web to [dev-dependencies] for test::init_service

New: backend/tests/jwt_auth_enforcement_test.rs

Tests covering all specified scenarios:

  • ✅ Valid token → 200 with correct sub
  • ✅ Missing Authorization header → 401
  • ✅ Malformed header (no Bearer prefix) → 401
  • ✅ Expired token (beyond 30 s leeway) → 401
  • ✅ Invalid signature (wrong secret) → 401
  • ✅ Extensions fallback (AuthMiddleware already ran) — reuses Claims without re-validating
  • user_id always comes from claims.sub, never Uuid::new_v4()
  • roles preserved through the extractor
  • ✅ Refresh token type is distinct from access (refresh tokens cannot be used as access tokens)

What Was Tested

  • Static analysis: all imports traced to correct struct definitions
  • ReportScoreRequest imported directly from models::match_models (avoids duplicate re-export ambiguity with models::matchmaker)
  • Route registration verified against existing scopes to avoid conflicts
  • Existing routes and handlers untouched — no regressions

Security Properties

  • No route in /api/matches or /api/tournaments is reachable without a valid JWT
  • User identity is always derived from the cryptographically-verified token claim, never from caller-supplied input or random generation
  • Revoked tokens return 403 (distinct from unauthenticated 401) so clients can distinguish session revocation from missing credentials

## Summary
Removes all placeholder user identity generation from the HTTP layer
and enforces JWT authentication on every protected endpoint in
src/http/matches.rs and src/http/tournaments.rs.

## Changes

### src/auth/extractor.rs (new)
- Implements actix-web FromRequest for Claims
- Reads Authorization: Bearer <token> header
- Validates via JwtService registered as web::Data<Arc<JwtService>>
- Falls back to request extensions if AuthMiddleware already ran (zero
  overhead for middleware-wrapped scopes)
- Returns 401 for missing/invalid/expired tokens, 403 for revoked tokens

### src/http/matches.rs (new)
- GET    /api/matches/{id}          — get_match
- POST   /api/matches/{id}/report   — report_score
- POST   /api/matches/{id}/dispute  — dispute_match
- All handlers receive Claims via the extractor; user_id is parsed from
  claims.sub — no Uuid::new_v4() placeholder anywhere

### src/http/tournaments.rs (new)
- GET    /api/tournaments            — list_tournaments
- GET    /api/tournaments/{id}       — get_tournament
- POST   /api/tournaments/{id}/join  — join_tournament
- Same pattern: user_id from Uuid::parse_str(&claims.sub), invalid sub
  returns 401 before reaching business logic

### src/main.rs
- Registers /api/matches scope with the three new routes
- Extends /api/tournaments scope to include list, get, join alongside the
  existing /{id}/statistics route

### src/auth/mod.rs
- Adds pub mod extractor so the FromRequest impl is compiled

### src/http/mod.rs
- Adds pub mod matches and pub mod tournaments

### backend/Cargo.toml
- Adds actix-web to [dev-dependencies] for test::init_service / test::call_service

### backend/tests/jwt_auth_enforcement_test.rs (new)
Comprehensive tests covering:
- valid token → 200 with correct sub
- missing Authorization header → 401
- malformed header (no Bearer prefix) → 401
- expired token (beyond 30s leeway) → 401
- invalid signature (wrong secret) → 401
- extensions fallback (AuthMiddleware already ran) → reuses Claims
- user_id always comes from claims.sub, never Uuid::new_v4()
- roles preserved in Claims
- refresh token type is distinct from access token type
@mohadon0 mohadon0 requested a review from anonfedora as a code owner June 2, 2026 09:37
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 2, 2026

@mohadon0 is attempting to deploy a commit to the paul joseph's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave
Copy link
Copy Markdown

drips-wave Bot commented Jun 2, 2026

@mohadon0 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BACKEND] All HTTP endpoints bypass authentication entirely

1 participant