// db import { selPool, updPool } from "#db"; import { DbSchema } from "#db-schema"; import { sql } from "slonik"; // api import { api } from "#api"; // error import { ApiError } from "#exceptions/api-error.js"; // other import { z } from "zod"; import bcript from "bcrypt"; import { v7 as uuidv7 } from "uuid"; import tokenService from "../services/token-service.js"; import { UserAuthService } from "../services/user-auth-service.js"; import { ConfirmPinsService } from "#modules/users/confirm-pins/confirm-pins-service.js"; import { RouterUtils } from "#utils/router-utils.js"; import { config } from "#config"; import { Request, Response } from "express"; class authController { // --- Регистрация --- async register( req: Request, res: Response, // next: NextFunction ) { // валидация запроса const { email } = api.auth.POST_Registration.req.parse(req.body); const isUserExist = await UserAuthService.checkUserExistByEmail(email); // если пользователь уже зарегистрирован if (isUserExist) { RouterUtils.validAndSendResponse( api.auth.POST_Registration.res, res, { code: "alreadyExists" }, 400, ); return; } // отправка пина const transactionId = uuidv7(); try { await ConfirmPinsService.sendConfirmPin(transactionId, email); } catch { RouterUtils.validAndSendResponse( api.auth.POST_Registration.res, res, { code: "pinIsNotSent" }, 400, ); return; } RouterUtils.validAndSendResponse(api.auth.POST_Registration.res, res, { code: "pinIsSent", transactionId: transactionId, }); } async confirmRegistration(req: Request, res: Response) { // валидация запроса const { password, transactionId, confirmPin } = api.auth.POST_ConfirmRegistration.req.parse(req.body); // проверка пина const pinInfo = await ConfirmPinsService.checkConfirmPin( transactionId, confirmPin, ); switch (pinInfo.status) { case "rotten": { RouterUtils.validAndSendResponse( api.auth.POST_ConfirmRegistration.res, res, { code: "pinIsRotten" }, 400, ); return; } case "tooManyTries": { RouterUtils.validAndSendResponse( api.auth.POST_ConfirmRegistration.res, res, { code: "tooManyTries" }, 400, ); return; } case "wrong": { RouterUtils.validAndSendResponse( api.auth.POST_ConfirmRegistration.res, res, { code: "pinIsWrong", triesRemained: pinInfo.triesRemained, }, 400, ); return; } } // пин правильный const email = pinInfo.email; // регистрация const hashPassword = await bcript.hash(password, 3); const userId = uuidv7(); await updPool.query( sql.unsafe` insert into usr.users (user_id, email, password) values (${userId}, ${email}, ${hashPassword})`, ); // токены const { accessToken, refreshToken } = tokenService.generateTokens({ email, userId, }); await tokenService.insertRefreshToken(userId, refreshToken); tokenService.setRefreshTokenInCookie(res, refreshToken); RouterUtils.validAndSendResponse( api.auth.POST_ConfirmRegistration.res, res, { code: "registered", accessToken, userData: { email, userId, }, }, ); } async login(req: Request, res: Response) { // валидация запроса const { email, password } = api.auth.POST_Login.req.parse(req.body); // поиск юзера const user = await selPool.maybeOne( sql.type( z.object({ userId: DbSchema.usr.users.userId, password: DbSchema.usr.users.password, wrongPassTries: DbSchema.usr.users.wrongPassTries, }), )` select user_id as "userId", password, wrong_pass_tries as "wrongPassTries" from usr.users where email = ${email}`, ); if (!user) { RouterUtils.validAndSendResponse( api.auth.POST_Login.res, res, { code: "userNotFound", }, 400, ); return; } // если количество попыток превышено if (user.wrongPassTries > config.PASSWORD_MAX_TRIES - 1) { RouterUtils.validAndSendResponse( api.auth.POST_Login.res, res, { code: "tooManyTries", // TODO: сделать дату разблокировки unblockingDate: new Date().toISOString(), }, 400, ); return; } // проверка пароля const isPassEquals = await bcript.compare(password, user.password); if (!isPassEquals) { await UserAuthService.authTriesIncrement(user.userId); const triesRemained = config.PASSWORD_MAX_TRIES - 1 - user.wrongPassTries; RouterUtils.validAndSendResponse( api.auth.POST_Login.res, res, { code: "passIsWrong", triesRemained, }, 400, ); return; } // успешный логин await UserAuthService.resetAuthTries(user.userId); // токены const { accessToken, refreshToken } = tokenService.generateTokens({ email, userId: user.userId, }); await tokenService.insertRefreshToken(user.userId, refreshToken); tokenService.setRefreshTokenInCookie(res, refreshToken); RouterUtils.validAndSendResponse( api.auth.POST_Login.res, res, { code: "success", accessToken, userData: { email, userId: user.userId }, }, 200, ); } async logout(req: Request, res: Response) { const { refreshToken } = req.cookies; const userData = tokenService.validateRefreshToken(refreshToken); await tokenService.removeToken(userData.userId, refreshToken); res.clearCookie("refreshToken"); RouterUtils.validAndSendResponse(api.auth.POST_Logout.res, res, { code: "success", }); } async logoutAllDevices(req: Request, res: Response) { const { refreshToken } = req.cookies; const userData = tokenService.validateRefreshToken(refreshToken); await tokenService.removeAllUserTokens(userData.userId); res.clearCookie("refreshToken"); RouterUtils.validAndSendResponse(api.auth.POST_LogoutAllDevices.res, res, { code: "success", }); } async refresh(req: Request, res: Response) { const { refreshToken } = req.cookies; if (!refreshToken) throw ApiError.UnauthorizedError(); const userData = tokenService.validateRefreshToken(refreshToken); const tokenIsExist = await tokenService.refreshTokenIsExist( userData.userId, refreshToken, ); if (!userData || !tokenIsExist) { res.clearCookie("refreshToken"); throw ApiError.UnauthorizedError(); } // обновляем токены const newUserData = await selPool.maybeOne( sql.type( z.object({ email: DbSchema.usr.users.email, }), )`select email from usr.users where user_id = ${userData.userId}`, ); if (!newUserData) { throw ApiError.UnauthorizedError(); } const newTokens = tokenService.generateTokens({ userId: userData.userId, email: newUserData.email, }); await tokenService.updateRefreshToken( userData.userId, refreshToken, newTokens.refreshToken, ); tokenService.setRefreshTokenInCookie(res, newTokens.refreshToken); RouterUtils.validAndSendResponse(api.auth.POST_Refresh.res, res, { code: "success", accessToken: newTokens.accessToken, userData: { email: newUserData.email, userId: userData.userId, }, }); } } export const AuthController = new authController();