new version
This commit is contained in:
41
apps/api/src/middleware/auth.ts
Normal file
41
apps/api/src/middleware/auth.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { verifyAccessToken, AuthContext } from '../services/jwt.service';
|
||||
import { logger } from '../lib/logger';
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
auth?: AuthContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function extractToken(req: Request): string | null {
|
||||
const cookie = req.cookies?.access_token;
|
||||
if (cookie) return cookie;
|
||||
|
||||
const auth = req.headers.authorization;
|
||||
if (auth) {
|
||||
const [scheme, token] = auth.split(' ');
|
||||
if (scheme?.toLowerCase() === 'bearer' && token) return token;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function authMiddleware(req: Request, res: Response, next: NextFunction): Promise<void> {
|
||||
const token = extractToken(req);
|
||||
|
||||
if (!token) {
|
||||
res.status(401).json({ success: false, error: 'Not authenticated' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
req.auth = await verifyAccessToken(token);
|
||||
next();
|
||||
} catch (err: any) {
|
||||
logger.warn(`Auth failed: ${err.message}`);
|
||||
res.status(err.status || 401).json({ success: false, error: err.message || 'Invalid token' });
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { jwtVerify, decodeProtectedHeader } from 'jose';
|
||||
import { getSigningKey } from '../services/jwks.service';
|
||||
import { env } from '../config/env';
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: { bitokUserId: string; email?: string };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function bitokAuth(req: Request, res: Response, next: NextFunction): Promise<void> {
|
||||
const header = req.headers.authorization;
|
||||
if (!header?.startsWith('Bearer ')) {
|
||||
res.status(401).json({ success: false, error: 'No token provided' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const token = header.slice(7);
|
||||
|
||||
// Decode header to get kid
|
||||
const protectedHeader = decodeProtectedHeader(token);
|
||||
if (protectedHeader.alg !== 'RS256') {
|
||||
res.status(401).json({ success: false, error: 'Invalid token algorithm' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!protectedHeader.kid) {
|
||||
res.status(401).json({ success: false, error: 'Token missing kid' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the signing key for this kid
|
||||
const key = await getSigningKey(protectedHeader.kid);
|
||||
|
||||
// Verify the token
|
||||
const { payload } = await jwtVerify(token, key, {
|
||||
issuer: env.bitokIssuer,
|
||||
audience: env.bitokAudience,
|
||||
algorithms: ['RS256'],
|
||||
});
|
||||
|
||||
if (!payload.sub) {
|
||||
res.status(401).json({ success: false, error: 'Token missing subject' });
|
||||
return;
|
||||
}
|
||||
|
||||
req.user = {
|
||||
bitokUserId: payload.sub,
|
||||
email: payload.email as string | undefined,
|
||||
};
|
||||
|
||||
next();
|
||||
} catch {
|
||||
res.status(401).json({ success: false, error: 'Invalid or expired token' });
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { logger } from '../lib/logger';
|
||||
|
||||
export function errorHandler(err: Error, _req: Request, res: Response, _next: NextFunction): void {
|
||||
console.error('[ERROR]', err.message);
|
||||
logger.error(err.message);
|
||||
res.status(500).json({ success: false, error: 'Internal server error' });
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import rateLimit from 'express-rate-limit';
|
||||
|
||||
export const loginLimiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000,
|
||||
max: 20,
|
||||
message: { success: false, error: 'Too many login attempts, try again later' },
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
});
|
||||
|
||||
export const registerLimiter = rateLimit({
|
||||
windowMs: 60 * 60 * 1000,
|
||||
max: 3,
|
||||
message: { success: false, error: 'Too many registration attempts, try again later' },
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
});
|
||||
|
||||
export const seedPhraseLimiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000,
|
||||
max: 3,
|
||||
message: { success: false, error: 'Too many attempts. Try again in 15 minutes.' },
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
});
|
||||
15
apps/api/src/middleware/trace.ts
Normal file
15
apps/api/src/middleware/trace.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { generateUlid } from '../utils/ulid';
|
||||
import { traceStore } from '../lib/trace-store';
|
||||
|
||||
export function traceMiddleware(req: Request, res: Response, next: NextFunction): void {
|
||||
const traceId = req.headers['x-trace-id'] as string
|
||||
|| req.headers['x-request-id'] as string
|
||||
|| generateUlid();
|
||||
|
||||
res.setHeader('X-Trace-ID', traceId);
|
||||
|
||||
traceStore.run(traceId, () => {
|
||||
next();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user