mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 18:01:09 +02:00
feat(auth): server-side tier gating via requireTier middleware
The JWT already carried a `tier` claim but nothing on the server read it
— AuthGate enforcement was client-only, so a valid JWT could hit paid
LLM/research endpoints regardless of the user's access tier.
- shared-hono authMiddleware now extracts `tier` into `c.userTier`,
defaulting unknown/missing claims to `public` (never silently grants
higher access).
- New `requireTier(minTier)` middleware + `hasTier`/`getTierLevel`
helpers. Tier hierarchy (guest < public < beta < alpha < founder) is
mirrored locally to avoid pulling the Svelte-facing shared-branding
package into Bun services.
- Applied `requireTier('beta')` as defense-in-depth on resource-heavy
apps/api modules (chat, context, food, guides, news-research, picture,
plants, research, traces, who) and the MCP endpoint. Pure CRUD modules
stay auth-only — access there is gated by ownership, not tier.
- DEV_BYPASS_AUTH now injects `userTier` (defaults to founder, override
via DEV_USER_TIER).
- Authentication guideline documents the pattern + test suite covers
hierarchy, passes-at-minimum, and rejection paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4efdcfffdb
commit
76d11a84ee
10 changed files with 208 additions and 5 deletions
|
|
@ -656,6 +656,25 @@ Response:
|
|||
}
|
||||
```
|
||||
|
||||
## Server-Side Tier Gating
|
||||
|
||||
The JWT carries a `tier` claim (`guest | public | beta | alpha | founder`) sourced from `auth.users.access_tier`. Client-side `AuthGate` enforcement is not enough — a user can still call the API directly with their token. For any endpoint that consumes shared infrastructure (LLM calls, external search, image/video generation), add a server-side `requireTier` gate on top of `authMiddleware`.
|
||||
|
||||
```typescript
|
||||
import { authMiddleware, requireTier } from '@mana/shared-hono';
|
||||
|
||||
app.use('/api/*', authMiddleware());
|
||||
app.use('/api/v1/research/*', requireTier('beta'));
|
||||
app.use('/api/v1/picture/*', requireTier('beta'));
|
||||
```
|
||||
|
||||
Rules:
|
||||
- Apply at the module-group level in `index.ts`, not inside handlers — easy to audit in one place.
|
||||
- `requireTier` always runs after `authMiddleware`; it relies on the `userTier` context variable the middleware sets.
|
||||
- Missing / unknown `tier` claims default to `public`, so a malformed JWT cannot accidentally grant `alpha`.
|
||||
- Pure CRUD modules that only expose a user's own records don't need a tier gate — the access check is ownership, not tier.
|
||||
- `DEV_BYPASS_AUTH=true` sets `userTier=founder` by default; override with `DEV_USER_TIER=<tier>` when testing rejection paths locally.
|
||||
|
||||
## Development Bypass
|
||||
|
||||
For local development, you can bypass auth:
|
||||
|
|
@ -663,6 +682,7 @@ For local development, you can bypass auth:
|
|||
```env
|
||||
DEV_BYPASS_AUTH=true
|
||||
DEV_USER_ID=dev-user-123
|
||||
DEV_USER_TIER=founder # optional — defaults to founder
|
||||
```
|
||||
|
||||
The guard will inject a mock user:
|
||||
|
|
@ -673,6 +693,7 @@ request.user = {
|
|||
userId: process.env.DEV_USER_ID || 'dev-user',
|
||||
email: 'dev@example.com',
|
||||
role: 'user',
|
||||
tier: process.env.DEV_USER_TIER || 'founder',
|
||||
};
|
||||
```
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue