style: run prettier on entire codebase

This commit is contained in:
BOHA
2026-03-24 19:59:14 +01:00
parent 872be42107
commit 3c167cf5c4
148 changed files with 26740 additions and 13990 deletions

View File

@@ -1,21 +1,26 @@
import crypto from 'crypto';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';
import { FastifyRequest, FastifyReply } from 'fastify';
import prisma from '../config/database';
import { config } from '../config/env';
import { AuthData, JwtPayload } from '../types';
import crypto from "crypto";
import jwt from "jsonwebtoken";
import bcrypt from "bcryptjs";
import { FastifyRequest, FastifyReply } from "fastify";
import prisma from "../config/database";
import { config } from "../config/env";
import { AuthData, JwtPayload } from "../types";
// Pre-computed bcrypt hash for timing-safe comparison when user not found
const DUMMY_HASH = '$2a$12$LJ3m4ys3Lg4oLBFnYP2amuPBzJnJBbGzCl5Y6X9Y8r0q5.s3L6OyO';
const DUMMY_HASH =
"$2a$12$LJ3m4ys3Lg4oLBFnYP2amuPBzJnJBbGzCl5Y6X9Y8r0q5.s3L6OyO";
// --- Token helpers ---
function hashToken(token: string): string {
return crypto.createHash('sha256').update(token).digest('hex');
return crypto.createHash("sha256").update(token).digest("hex");
}
function generateAccessToken(user: { id: number; username: string; roleName: string | null }): string {
function generateAccessToken(user: {
id: number;
username: string;
roleName: string | null;
}): string {
return jwt.sign(
{ sub: user.id, username: user.username, role: user.roleName },
config.jwt.secret,
@@ -24,7 +29,7 @@ function generateAccessToken(user: { id: number; username: string; roleName: str
}
function generateRefreshToken(): string {
return crypto.randomBytes(32).toString('hex');
return crypto.randomBytes(32).toString("hex");
}
// --- Auth data loading ---
@@ -45,10 +50,12 @@ async function loadAuthData(userId: number): Promise<AuthData | null> {
if (!user || !user.is_active) return null;
const isAdmin = user.roles?.name === 'admin';
const isAdmin = user.roles?.name === "admin";
const permissions = isAdmin
? (await prisma.permissions.findMany()).map((p: { name: string }) => p.name)
: (user.roles?.role_permissions ?? []).map((rp: { permissions: { name: string } }) => rp.permissions.name);
: (user.roles?.role_permissions ?? []).map(
(rp: { permissions: { name: string } }) => rp.permissions.name,
);
return {
userId: user.id,
@@ -60,7 +67,9 @@ async function loadAuthData(userId: number): Promise<AuthData | null> {
roleName: user.roles?.name ?? null,
permissions,
totp_enabled: !!user.totp_enabled,
require_2fa: !!(await prisma.company_settings.findFirst({ select: { require_2fa: true } }))?.require_2fa,
require_2fa: !!(
await prisma.company_settings.findFirst({ select: { require_2fa: true } })
)?.require_2fa,
};
}
@@ -72,9 +81,14 @@ export async function login(
rememberMe: boolean,
request: FastifyRequest,
): Promise<
| { type: 'success'; accessToken: string; refreshToken: string; user: AuthData }
| { type: 'totp_required'; loginToken: string }
| { type: 'error'; message: string; status: number }
| {
type: "success";
accessToken: string;
refreshToken: string;
user: AuthData;
}
| { type: "totp_required"; loginToken: string }
| { type: "error"; message: string; status: number }
> {
const user = await prisma.users.findFirst({
where: {
@@ -86,40 +100,60 @@ export async function login(
if (!user) {
// Timing-safe: run bcrypt even when user not found
await bcrypt.compare(password, DUMMY_HASH);
return { type: 'error', message: 'Neplatné přihlašovací údaje', status: 401 };
return {
type: "error",
message: "Neplatné přihlašovací údaje",
status: 401,
};
}
if (!user.is_active) {
return { type: 'error', message: 'Účet je deaktivován', status: 403 };
return { type: "error", message: "Účet je deaktivován", status: 403 };
}
// Check lockout
if (user.locked_until && new Date(user.locked_until) > new Date()) {
return { type: 'error', message: 'Účet je dočasně uzamčen. Zkuste to později.', status: 429 };
return {
type: "error",
message: "Účet je dočasně uzamčen. Zkuste to později.",
status: 429,
};
}
const passwordValid = await bcrypt.compare(password, user.password_hash);
if (!passwordValid) {
const attempts = (user.failed_login_attempts ?? 0) + 1;
const updateData: Record<string, unknown> = { failed_login_attempts: attempts };
const updateData: Record<string, unknown> = {
failed_login_attempts: attempts,
};
if (attempts >= config.security.maxLoginAttempts) {
updateData.locked_until = new Date(Date.now() + config.security.lockoutMinutes * 60_000);
updateData.locked_until = new Date(
Date.now() + config.security.lockoutMinutes * 60_000,
);
}
await prisma.users.update({ where: { id: user.id }, data: updateData });
return { type: 'error', message: 'Neplatné přihlašovací údaje', status: 401 };
return {
type: "error",
message: "Neplatné přihlašovací údaje",
status: 401,
};
}
// Reset failed attempts
await prisma.users.update({
where: { id: user.id },
data: { failed_login_attempts: 0, locked_until: null, last_login: new Date() },
data: {
failed_login_attempts: 0,
locked_until: null,
last_login: new Date(),
},
});
// Check if 2FA is enabled
if (user.totp_enabled) {
const loginToken = crypto.randomBytes(32).toString('hex');
const loginToken = crypto.randomBytes(32).toString("hex");
const tokenHash = hashToken(loginToken);
await prisma.totp_login_tokens.create({
@@ -130,13 +164,17 @@ export async function login(
},
});
return { type: 'totp_required', loginToken };
return { type: "totp_required", loginToken };
}
// Generate tokens
const authData = await loadAuthData(user.id);
if (!authData) {
return { type: 'error', message: 'Chyba načítání uživatelských dat', status: 500 };
return {
type: "error",
message: "Chyba načítání uživatelských dat",
status: 500,
};
}
const accessToken = generateAccessToken({
@@ -159,19 +197,30 @@ export async function login(
expires_at: new Date(Date.now() + expiresIn * 1000),
remember_me: rememberMe,
ip_address: request.ip,
user_agent: request.headers['user-agent'] ?? null,
user_agent: request.headers["user-agent"] ?? null,
},
});
return { type: 'success', accessToken, refreshToken: refreshTokenRaw, user: authData };
return {
type: "success",
accessToken,
refreshToken: refreshTokenRaw,
user: authData,
};
}
export async function refreshAccessToken(
refreshTokenRaw: string,
request: FastifyRequest,
): Promise<
| { type: 'success'; accessToken: string; refreshToken: string; user: AuthData; rememberMe: boolean }
| { type: 'error'; message: string; status: number }
| {
type: "success";
accessToken: string;
refreshToken: string;
user: AuthData;
rememberMe: boolean;
}
| { type: "error"; message: string; status: number }
> {
const tokenHash = hashToken(refreshTokenRaw);
@@ -179,13 +228,17 @@ export async function refreshAccessToken(
where: { token_hash: tokenHash },
});
if (!storedToken || storedToken.replaced_at || new Date(storedToken.expires_at) < new Date()) {
return { type: 'error', message: 'Neplatný refresh token', status: 401 };
if (
!storedToken ||
storedToken.replaced_at ||
new Date(storedToken.expires_at) < new Date()
) {
return { type: "error", message: "Neplatný refresh token", status: 401 };
}
const authData = await loadAuthData(storedToken.user_id);
if (!authData) {
return { type: 'error', message: 'Uživatel nenalezen', status: 401 };
return { type: "error", message: "Uživatel nenalezen", status: 401 };
}
// Rotate refresh token
@@ -208,7 +261,7 @@ export async function refreshAccessToken(
expires_at: new Date(Date.now() + expiresIn * 1000),
remember_me: storedToken.remember_me,
ip_address: request.ip,
user_agent: request.headers['user-agent'] ?? null,
user_agent: request.headers["user-agent"] ?? null,
},
}),
]);
@@ -219,12 +272,20 @@ export async function refreshAccessToken(
roleName: authData.roleName,
});
return { type: 'success', accessToken, refreshToken: newRefreshTokenRaw, user: authData, rememberMe: storedToken.remember_me ?? false };
return {
type: "success",
accessToken,
refreshToken: newRefreshTokenRaw,
user: authData,
rememberMe: storedToken.remember_me ?? false,
};
}
export async function logout(refreshTokenRaw: string): Promise<void> {
const tokenHash = hashToken(refreshTokenRaw);
const token = await prisma.refresh_tokens.findFirst({ where: { token_hash: tokenHash } });
const token = await prisma.refresh_tokens.findFirst({
where: { token_hash: tokenHash },
});
if (token) {
// Delete all tokens for this user from the same IP + user agent (same browser session)
@@ -237,16 +298,25 @@ export async function logout(refreshTokenRaw: string): Promise<void> {
});
} else {
// Fallback: just delete by hash
await prisma.refresh_tokens.deleteMany({ where: { token_hash: tokenHash } });
await prisma.refresh_tokens.deleteMany({
where: { token_hash: tokenHash },
});
}
// Clean up expired tokens
await prisma.refresh_tokens.deleteMany({ where: { expires_at: { lt: new Date() } } });
await prisma.refresh_tokens.deleteMany({
where: { expires_at: { lt: new Date() } },
});
}
export async function verifyAccessToken(token: string): Promise<AuthData | null> {
export async function verifyAccessToken(
token: string,
): Promise<AuthData | null> {
try {
const payload = jwt.verify(token, config.jwt.secret) as unknown as JwtPayload;
const payload = jwt.verify(
token,
config.jwt.secret,
) as unknown as JwtPayload;
return loadAuthData(payload.sub);
} catch {
return null;