new version

This commit is contained in:
ZOMBIIIIIII
2026-04-14 20:22:51 +03:00
parent 37146f7375
commit 89cb6174b7
144 changed files with 1710 additions and 17258 deletions

View 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' });
}
}

View File

@@ -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' });
}
}

View File

@@ -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' });
}

View File

@@ -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,
});

View 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();
});
}