feat: Tier guard middleware (#129) #138

Closed
opened 2026-02-11 03:48:44 +00:00 by egullickson · 1 comment
Owner

Relates to #129

Milestone 0: Tier Guard Middleware

Files

  • backend/src/core/middleware/require-tier.ts (NEW)
  • backend/src/core/config/feature-tiers.ts (read-only reference)

Requirements

  • Create requireTier(featureKey: string) Fastify preHandler middleware
  • Middleware reads user tier from request.user.tier (set by auth middleware)
  • Middleware checks tier against FEATURE_TIERS[featureKey].minTier
  • Returns 403 with { error: 'TIER_REQUIRED', requiredTier, currentTier, featureName, upgradePrompt } if access denied
  • Follows existing auth middleware pattern from backend/src/core/middleware/
  • Exports as reusable preHandler for route registration

Acceptance Criteria

  • requireTier('fuelLog.receiptScan') blocks free-tier users with 403 TIER_REQUIRED
  • requireTier('fuelLog.receiptScan') allows pro-tier users through
  • requireTier('document.scanMaintenanceSchedule') blocks free-tier users with 403 TIER_REQUIRED
  • Enterprise-tier users pass all tier checks (inherits pro access)
  • Response body includes requiredTier, currentTier, featureName, upgradePrompt
  • Middleware is composable with requireAuth in route preHandler arrays

Tests

  • Test files: backend/src/core/middleware/require-tier.test.ts (NEW)
  • Test type: unit (mock request.user.tier)
  • Scenarios:
    • Normal: Pro user passes fuelLog.receiptScan check
    • Normal: Enterprise user passes all checks (tier inheritance)
    • Error: Free user blocked with 403 and correct response body
    • Error: Unknown feature key returns 500
    • Edge: Missing user.tier on request returns 403
Relates to #129 ## Milestone 0: Tier Guard Middleware ### Files - `backend/src/core/middleware/require-tier.ts` (NEW) - `backend/src/core/config/feature-tiers.ts` (read-only reference) ### Requirements - Create `requireTier(featureKey: string)` Fastify preHandler middleware - Middleware reads user tier from `request.user.tier` (set by auth middleware) - Middleware checks tier against `FEATURE_TIERS[featureKey].minTier` - Returns 403 with `{ error: 'TIER_REQUIRED', requiredTier, currentTier, featureName, upgradePrompt }` if access denied - Follows existing auth middleware pattern from `backend/src/core/middleware/` - Exports as reusable preHandler for route registration ### Acceptance Criteria - `requireTier('fuelLog.receiptScan')` blocks free-tier users with 403 TIER_REQUIRED - `requireTier('fuelLog.receiptScan')` allows pro-tier users through - `requireTier('document.scanMaintenanceSchedule')` blocks free-tier users with 403 TIER_REQUIRED - Enterprise-tier users pass all tier checks (inherits pro access) - Response body includes `requiredTier`, `currentTier`, `featureName`, `upgradePrompt` - Middleware is composable with `requireAuth` in route preHandler arrays ### Tests - **Test files**: `backend/src/core/middleware/require-tier.test.ts` (NEW) - **Test type**: unit (mock request.user.tier) - **Scenarios**: - Normal: Pro user passes fuelLog.receiptScan check - Normal: Enterprise user passes all checks (tier inheritance) - Error: Free user blocked with 403 and correct response body - Error: Unknown feature key returns 500 - Edge: Missing user.tier on request returns 403
egullickson added the
status
backlog
type
feature
labels 2026-02-11 03:51:12 +00:00
egullickson added
status
in-progress
and removed
status
backlog
labels 2026-02-11 17:10:07 +00:00
Author
Owner

Milestone: Tier Guard Middleware

Phase: Execution | Agent: Developer | Status: PASS

Implementation

Created standalone requireTier(featureKey: string) middleware in backend/src/core/middleware/require-tier.ts:

  • Simple string API: requireTier('fuelLog.receiptScan') - no options object needed
  • Composable: Designed for use alongside requireAuth in preHandler arrays
  • Fail-closed for unknowns: Returns 500 for unregistered feature keys (vs existing plugin's fail-open)
  • 403 response body: Includes requiredTier, currentTier, featureName, upgradePrompt
  • Reads from request.userContext.subscriptionTier: Set by auth middleware

Tests (9/9 passing)

backend/src/core/middleware/require-tier.test.ts:

  • Pro user passes fuelLog.receiptScan check
  • Enterprise user passes all checks (tier inheritance) - 3 features tested
  • Free user blocked from fuelLog.receiptScan with 403 and correct body
  • Free user blocked from document.scanMaintenanceSchedule with 403
  • Response body includes all required fields (requiredTier, currentTier, featureName, upgradePrompt)
  • Unknown feature key returns 500 INTERNAL_ERROR
  • Missing userContext defaults to free tier and returns 403

Quality

  • Lint: 0 errors
  • Type-check: clean
  • Commit: 1a6400a feat: add standalone requireTier middleware (refs #138)

Verdict: PASS | Next: Ready for QR post-implementation review

## Milestone: Tier Guard Middleware **Phase**: Execution | **Agent**: Developer | **Status**: PASS ### Implementation Created standalone `requireTier(featureKey: string)` middleware in `backend/src/core/middleware/require-tier.ts`: - **Simple string API**: `requireTier('fuelLog.receiptScan')` - no options object needed - **Composable**: Designed for use alongside `requireAuth` in preHandler arrays - **Fail-closed for unknowns**: Returns 500 for unregistered feature keys (vs existing plugin's fail-open) - **403 response body**: Includes `requiredTier`, `currentTier`, `featureName`, `upgradePrompt` - **Reads from `request.userContext.subscriptionTier`**: Set by auth middleware ### Tests (9/9 passing) `backend/src/core/middleware/require-tier.test.ts`: - Pro user passes fuelLog.receiptScan check - Enterprise user passes all checks (tier inheritance) - 3 features tested - Free user blocked from fuelLog.receiptScan with 403 and correct body - Free user blocked from document.scanMaintenanceSchedule with 403 - Response body includes all required fields (requiredTier, currentTier, featureName, upgradePrompt) - Unknown feature key returns 500 INTERNAL_ERROR - Missing userContext defaults to free tier and returns 403 ### Quality - Lint: 0 errors - Type-check: clean - Commit: `1a6400a feat: add standalone requireTier middleware (refs #138)` *Verdict*: PASS | *Next*: Ready for QR post-implementation review
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: egullickson/motovaultpro#138