managarten/services/mana-events/src/middleware/jwt-auth.ts
Till JS 216746721e feat(events): add mana-events service + public RSVP flow (Phase 1b)
New Hono+Bun service at services/mana-events on port 3065 with two
schemas in mana_platform: events_published (snapshots) and public_rsvps
(unauthenticated responses), plus a per-token hourly rate-limit bucket.

- Host endpoints (JWT) for publish/update/unpublish/list-rsvps
- Public endpoints for snapshot fetch + RSVP upsert with rate limiting
- New /rsvp/[token] page outside the auth gate, SSR-loads the snapshot
- Client store wires publishEvent/unpublishEvent to the server, syncs
  snapshot updates after edits, and deletes the snapshot on event delete
- DetailView polls GET /events/:id/rsvps every 30s while open and lets
  hosts import a public response into their local guest list
- generate-env, setup-databases.sh, .env.development, hooks.server.ts,
  package.json wired for local dev
2026-04-07 14:27:48 +02:00

50 lines
1.2 KiB
TypeScript

/**
* JWT Authentication Middleware — validates Bearer tokens via JWKS from mana-auth.
*/
import type { MiddlewareHandler } from 'hono';
import { createRemoteJWKSet, jwtVerify } from 'jose';
import { UnauthorizedError } from '../lib/errors';
let jwks: ReturnType<typeof createRemoteJWKSet> | null = null;
function getJwks(authUrl: string) {
if (!jwks) {
jwks = createRemoteJWKSet(new URL('/api/auth/jwks', authUrl));
}
return jwks;
}
export interface AuthUser {
userId: string;
email: string;
role: string;
}
export function jwtAuth(authUrl: string): MiddlewareHandler {
return async (c, next) => {
const authHeader = c.req.header('Authorization');
if (!authHeader?.startsWith('Bearer ')) {
throw new UnauthorizedError('Missing or invalid Authorization header');
}
const token = authHeader.slice(7);
try {
const { payload } = await jwtVerify(token, getJwks(authUrl), {
issuer: authUrl,
audience: 'mana',
});
const user: AuthUser = {
userId: payload.sub || '',
email: (payload.email as string) || '',
role: (payload.role as string) || 'user',
};
c.set('user', user);
await next();
} catch {
throw new UnauthorizedError('Invalid or expired token');
}
};
}