- Add getCurrentUserFromRequest() to auth-server.ts (checks cookies first, then Authorization header)
- Update all 34 proxy routes: getCurrentUser(req.headers.get('authorization')) → getCurrentUserFromRequest(req)
- Add createProxyFetch() shared helper in lib/proxy-fetch.ts (injects auth + product-id headers)
- Update 15 admin pages: replace inline fetch helpers with createProxyFetch
- Root cause: newer pages used bare fetch() without Authorization headers, causing 401s on all proxy routes
85 lines
2.6 KiB
TypeScript
85 lines
2.6 KiB
TypeScript
/**
|
|
* Server-side auth utilities — proxies to platform-service for token verification.
|
|
* Used by Next.js API routes only (never imported in client components).
|
|
*
|
|
* All JWT signing/verification is handled by platform-service (single auth source).
|
|
* This module provides helper functions that verify tokens via platform-service
|
|
* and return a lightweight UserDoc for use in API route guards.
|
|
*/
|
|
|
|
import { hashPassword, verifyPassword } from '@bytelyst/auth';
|
|
import { getMeViaService } from '@/lib/platform-client';
|
|
|
|
export { hashPassword, verifyPassword };
|
|
|
|
export interface UserDoc {
|
|
id: string;
|
|
email: string;
|
|
name: string;
|
|
role: string;
|
|
plan: string;
|
|
status?: string;
|
|
}
|
|
|
|
/**
|
|
* Get the current user from an Authorization header value.
|
|
* Verifies the token via platform-service /auth/me.
|
|
*/
|
|
export async function getCurrentUser(authHeader: string | null): Promise<UserDoc | null> {
|
|
if (!authHeader?.startsWith('Bearer ')) return null;
|
|
const token = authHeader.slice(7);
|
|
try {
|
|
const user = await getMeViaService(token);
|
|
return {
|
|
id: user.id,
|
|
email: user.email,
|
|
name: user.displayName,
|
|
role: user.role,
|
|
plan: user.plan,
|
|
};
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the current user from a Request object.
|
|
* Checks cookies first (Next.js httpOnly cookie), then Authorization header.
|
|
* Returns null if not authenticated.
|
|
*/
|
|
export async function getCurrentUserFromRequest(request: Request): Promise<UserDoc | null> {
|
|
const cookieHeader = request.headers.get('cookie') || '';
|
|
const tokenMatch = cookieHeader.match(/(?:^|;\s*)token=([^;]+)/);
|
|
const authHeader = request.headers.get('authorization');
|
|
|
|
if (tokenMatch) {
|
|
return getCurrentUser(`Bearer ${tokenMatch[1]}`);
|
|
}
|
|
if (authHeader) {
|
|
return getCurrentUser(authHeader);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Require an admin user from the request. Checks cookies first, then Authorization header.
|
|
* Throws Error("Unauthorized") if not authenticated or not admin/super_admin role.
|
|
*/
|
|
export async function requireAdmin(request: Request): Promise<UserDoc> {
|
|
// Try cookie first (Next.js httpOnly cookie), then Authorization header
|
|
const cookieHeader = request.headers.get('cookie') || '';
|
|
const tokenMatch = cookieHeader.match(/(?:^|;\s*)token=([^;]+)/);
|
|
const authHeader = request.headers.get('authorization');
|
|
|
|
let user: UserDoc | null = null;
|
|
if (tokenMatch) {
|
|
user = await getCurrentUser(`Bearer ${tokenMatch[1]}`);
|
|
} else if (authHeader) {
|
|
user = await getCurrentUser(authHeader);
|
|
}
|
|
|
|
if (!user) throw new Error('Unauthorized');
|
|
if (!['admin', 'super_admin'].includes(user.role)) throw new Error('Unauthorized');
|
|
return user;
|
|
}
|