|
|
@@ -1,179 +1,7 @@
|
|
|
import { Router } from "express";
|
|
|
-import { z } from "zod";
|
|
|
-import bcrypt from "bcryptjs";
|
|
|
-import { db } from "../db/knex.js";
|
|
|
-import { config } from "../config/index.js";
|
|
|
-import { generateRefreshToken, hashRefreshToken, signAccessToken } from "../services/authTokens.js";
|
|
|
+import { UsuarioController } from "../controllers/Usuario.Controller.js";
|
|
|
|
|
|
export const authRouter = Router();
|
|
|
|
|
|
-const loginBodySchema = z.object({
|
|
|
- login: z.string().min(1),
|
|
|
- senha: z.string().min(1)
|
|
|
-});
|
|
|
-
|
|
|
-const refreshBodySchema = z.object({
|
|
|
- refreshToken: z.string().min(1)
|
|
|
-});
|
|
|
-
|
|
|
-const logoutBodySchema = z.object({
|
|
|
- refreshToken: z.string().min(1).optional()
|
|
|
-});
|
|
|
-
|
|
|
-function toPublicUser(u) {
|
|
|
- return {
|
|
|
- id: u.Id,
|
|
|
- nome: u.Nome,
|
|
|
- login: u.Login,
|
|
|
- email: u.Email,
|
|
|
- status: u.Status,
|
|
|
- nivel: u.Nivel,
|
|
|
- setor: u.Setor
|
|
|
- };
|
|
|
-}
|
|
|
-
|
|
|
-function isUserBlocked(u) {
|
|
|
- const status = String(u?.Status ?? "").trim();
|
|
|
- return status === "0";
|
|
|
-}
|
|
|
-
|
|
|
-async function issueTokensForUser(trx, user) {
|
|
|
- const now = Date.now();
|
|
|
- const refreshToken = generateRefreshToken();
|
|
|
- const refreshTokenHash = hashRefreshToken(refreshToken);
|
|
|
- const expiresAt = new Date(now + config.jwt.refreshTtlSeconds * 1000);
|
|
|
-
|
|
|
- await trx("refresh_tokens").insert({
|
|
|
- UsuarioId: user.Id,
|
|
|
- TokenHash: refreshTokenHash,
|
|
|
- ExpiresAt: expiresAt
|
|
|
- });
|
|
|
-
|
|
|
- const accessToken = signAccessToken({
|
|
|
- sub: String(user.Id),
|
|
|
- login: user.Login,
|
|
|
- nivel: user.Nivel,
|
|
|
- setor: user.Setor
|
|
|
- });
|
|
|
-
|
|
|
- return { accessToken, refreshToken };
|
|
|
-}
|
|
|
-
|
|
|
-authRouter.post("/login", async (req, res, next) => {
|
|
|
- try {
|
|
|
- const body = loginBodySchema.parse(req.body);
|
|
|
-
|
|
|
- const login = body.login.trim();
|
|
|
- const isEmail = login.includes("@");
|
|
|
-
|
|
|
- const user = await db("usuarios")
|
|
|
- .where(isEmail ? { Email: login } : { Login: login })
|
|
|
- .first();
|
|
|
-
|
|
|
- if (!user) {
|
|
|
- res.status(401).json({ error: "invalid_credentials" });
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (isUserBlocked(user)) {
|
|
|
- res.status(403).json({ error: "user_inactive" });
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const ok = await bcrypt.compare(body.senha, String(user.Senha ?? ""));
|
|
|
- if (!ok) {
|
|
|
- res.status(401).json({ error: "invalid_credentials" });
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const tokens = await db.transaction(async (trx) => issueTokensForUser(trx, user));
|
|
|
- res.json({ ...tokens, user: toPublicUser(user) });
|
|
|
- } catch (err) {
|
|
|
- next(err);
|
|
|
- }
|
|
|
-});
|
|
|
-
|
|
|
-authRouter.post("/refresh", async (req, res, next) => {
|
|
|
- try {
|
|
|
- const body = refreshBodySchema.parse(req.body);
|
|
|
- const tokenHash = hashRefreshToken(body.refreshToken);
|
|
|
-
|
|
|
- const now = new Date();
|
|
|
-
|
|
|
- const result = await db.transaction(async (trx) => {
|
|
|
- const tokenRow = await trx("refresh_tokens").where({ TokenHash: tokenHash }).first();
|
|
|
-
|
|
|
- if (!tokenRow) return null;
|
|
|
- if (tokenRow.RevokedAt) return null;
|
|
|
- if (new Date(tokenRow.ExpiresAt) <= now) return null;
|
|
|
-
|
|
|
- const user = await trx("usuarios").where({ Id: tokenRow.UsuarioId }).first();
|
|
|
- if (!user) return null;
|
|
|
- if (isUserBlocked(user)) return null;
|
|
|
-
|
|
|
- await trx("refresh_tokens").where({ Id: tokenRow.Id }).update({ RevokedAt: now });
|
|
|
-
|
|
|
- const tokens = await issueTokensForUser(trx, user);
|
|
|
- return { tokens, user };
|
|
|
- });
|
|
|
-
|
|
|
- if (!result) {
|
|
|
- res.status(401).json({ error: "invalid_refresh_token" });
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- res.json({ ...result.tokens, user: toPublicUser(result.user) });
|
|
|
- } catch (err) {
|
|
|
- next(err);
|
|
|
- }
|
|
|
-});
|
|
|
-
|
|
|
-authRouter.post("/logout", async (req, res, next) => {
|
|
|
- try {
|
|
|
- const body = logoutBodySchema.safeParse(req.body);
|
|
|
- const refreshToken = body.success ? body.data.refreshToken : undefined;
|
|
|
-
|
|
|
- const now = new Date();
|
|
|
- const userId = req.user?.sub ? Number(req.user.sub) : null;
|
|
|
-
|
|
|
- if (userId) {
|
|
|
- await db("refresh_tokens")
|
|
|
- .where({ UsuarioId: userId })
|
|
|
- .whereNull("RevokedAt")
|
|
|
- .update({ RevokedAt: now });
|
|
|
- res.json({ ok: true });
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (typeof refreshToken !== "string" || !refreshToken.trim()) {
|
|
|
- res.status(400).json({ error: "refresh_token_required" });
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const tokenHash = hashRefreshToken(refreshToken.trim());
|
|
|
- await db("refresh_tokens").where({ TokenHash: tokenHash }).update({ RevokedAt: now });
|
|
|
- res.json({ ok: true });
|
|
|
- } catch (err) {
|
|
|
- next(err);
|
|
|
- }
|
|
|
-});
|
|
|
-
|
|
|
-authRouter.get("/me", async (req, res, next) => {
|
|
|
- try {
|
|
|
- const userId = req.user?.sub ? Number(req.user.sub) : null;
|
|
|
- if (!userId) {
|
|
|
- res.status(401).json({ error: "unauthorized" });
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const user = await db("usuarios").where({ Id: userId }).first();
|
|
|
- if (!user) {
|
|
|
- res.status(404).json({ error: "user_not_found" });
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- res.json({ user: toPublicUser(user) });
|
|
|
- } catch (err) {
|
|
|
- next(err);
|
|
|
- }
|
|
|
-});
|
|
|
+authRouter.post("/login", UsuarioController.Login);
|
|
|
+authRouter.post("/logout", UsuarioController.Logout);
|