Skip to content

Permission Management

Overview

Permissions are enforced via middleware in the Hono API server. Roles live in the profile table and are checked at the route level.

See Permissions and Roles for role definitions.

Middleware

All auth middleware is in backend/src/middleware/:

// backend/src/middleware/auth.ts
import { Context, Next } from 'hono';
import { auth } from '../lib/auth';

export async function requireAuth(c: Context, next: Next) {
  const session = await auth.api.getSession({ headers: c.req.raw.headers });
  if (!session) return c.json({ error: 'Unauthorized' }, 401);
  c.set('session', session);
  await next();
}

export function requireRole(...roles: string[]) {
  return async (c: Context, next: Next) => {
    const session = c.get('session');
    const userProfile = await db.query.profile.findFirst({
      where: eq(profile.userId, session.user.id),
    });
    if (!userProfile || !roles.includes(userProfile.role)) {
      return c.json({ error: 'Forbidden' }, 403);
    }
    await next();
  };
}

Protecting Routes

// Only authenticated users
app.use('/api/me/*', requireAuth);

// Platform admins only
app.use('/api/admin/*', requireAuth, requireRole('platform_admin'));

// Org admins and platform admins
app.use(
  '/api/organizations/:id/users/*',
  requireAuth,
  requireRole('platform_admin', 'organization_admin')
);

Adding a New Protected Route

  1. Create backend/src/routes/<resource>/<resource>.router.ts
  2. Apply requireAuth and requireRole(...) middleware
  3. Mount the router in backend/src/app.ts
  4. Add integration tests in backend/__tests__/

Testing Permissions

it('returns 403 for non-admin users', async () => {
  const res = await app.request('/api/organizations', {
    method: 'POST',
    headers: { Cookie: coachSessionCookie },
    body: JSON.stringify({ name: 'Test Org' }),
  });
  expect(res.status).toBe(403);
});