initial commit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-03-23 08:46:51 +01:00
commit 4608494a3f
130 changed files with 40361 additions and 0 deletions

51
src/middleware/auth.ts Normal file
View File

@@ -0,0 +1,51 @@
import { FastifyRequest, FastifyReply } from 'fastify';
import { verifyAccessToken } from '../services/auth';
import { error } from '../utils/response';
import { AuthData } from '../types';
export async function requireAuth(
request: FastifyRequest,
reply: FastifyReply,
): Promise<void> {
const authHeader = request.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return error(reply, 'Vyžadována autentizace', 401);
}
const token = authHeader.slice(7);
const authData = await verifyAccessToken(token);
if (!authData) {
return error(reply, 'Neplatný nebo expirovaný token', 401);
}
request.authData = authData;
}
export async function optionalAuth(
request: FastifyRequest,
_reply: FastifyReply,
): Promise<void> {
const authHeader = request.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) return;
const token = authHeader.slice(7);
request.authData = (await verifyAccessToken(token)) ?? undefined;
}
export function requirePermission(...permissionNames: string[]) {
return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {
await requireAuth(request, reply);
if (reply.sent) return;
const authData = request.authData!;
// Admin has all permissions
if (authData.roleName === 'admin') return;
const hasAll = permissionNames.every((p) => authData.permissions.includes(p));
if (!hasAll) {
return error(reply, 'Nedostatečná oprávnění', 403);
}
};
}

View File

@@ -0,0 +1,15 @@
import { FastifyReply, FastifyRequest } from 'fastify';
import { config } from '../config/env';
export async function securityHeaders(
_request: FastifyRequest,
reply: FastifyReply,
): Promise<void> {
reply.header('X-Content-Type-Options', 'nosniff');
reply.header('X-Frame-Options', 'DENY');
reply.header('Referrer-Policy', 'strict-origin-when-cross-origin');
if (config.isProduction) {
reply.header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
}
}