From 3a32f5e4ebeb9787992fb574de6b92ad04c3c88f Mon Sep 17 00:00:00 2001 From: killer-cf Date: Fri, 7 Nov 2025 23:33:09 -0300 Subject: [PATCH] refactor: remove authentication module and integrate Better Auth - Deleted the `autenticacao.ts` file to streamline the authentication process. - Updated the `auth.ts` file to include new functions for user management and password updates. - Modified the schema to enforce the presence of `authId` for users, ensuring integration with Better Auth. - Refactored the seed process to create users with Better Auth integration, enhancing user management capabilities. - Cleaned up the `usuarios.ts` file to utilize the new authentication functions and improve code clarity. --- packages/backend/convex/_generated/api.d.ts | 2 - packages/backend/convex/autenticacao.ts | 834 -------------------- packages/backend/convex/auth.ts | 52 +- packages/backend/convex/schema.ts | 3 +- packages/backend/convex/seed.ts | 339 +++++--- packages/backend/convex/usuarios.ts | 497 +++--------- 6 files changed, 377 insertions(+), 1350 deletions(-) delete mode 100644 packages/backend/convex/autenticacao.ts diff --git a/packages/backend/convex/_generated/api.d.ts b/packages/backend/convex/_generated/api.d.ts index 411fb3c..a7f3da2 100644 --- a/packages/backend/convex/_generated/api.d.ts +++ b/packages/backend/convex/_generated/api.d.ts @@ -15,7 +15,6 @@ import type * as actions_smtp from "../actions/smtp.js"; import type * as actions_utils_nodeCrypto from "../actions/utils/nodeCrypto.js"; import type * as atestadosLicencas from "../atestadosLicencas.js"; import type * as ausencias from "../ausencias.js"; -import type * as autenticacao from "../autenticacao.js"; import type * as auth_utils from "../auth/utils.js"; import type * as auth from "../auth.js"; import type * as chat from "../chat.js"; @@ -71,7 +70,6 @@ declare const fullApi: ApiFromModules<{ "actions/utils/nodeCrypto": typeof actions_utils_nodeCrypto; atestadosLicencas: typeof atestadosLicencas; ausencias: typeof ausencias; - autenticacao: typeof autenticacao; "auth/utils": typeof auth_utils; auth: typeof auth; chat: typeof chat; diff --git a/packages/backend/convex/autenticacao.ts b/packages/backend/convex/autenticacao.ts deleted file mode 100644 index 44ae1ce..0000000 --- a/packages/backend/convex/autenticacao.ts +++ /dev/null @@ -1,834 +0,0 @@ -import { v } from "convex/values"; -import { mutation, query, internalMutation } from "./_generated/server"; -import { - hashPassword, - verifyPassword, - generateToken, - validarMatricula, - validarSenha, -} from "./auth/utils"; -import { registrarLogin } from "./logsLogin"; -import { Id, Doc } from "./_generated/dataModel"; -import type { QueryCtx, MutationCtx } from "./_generated/server"; - -/** - * Helper para verificar se usuário está bloqueado - */ -async function verificarBloqueioUsuario(ctx: QueryCtx, usuarioId: Id<"usuarios">) { - const bloqueio = await ctx.db - .query("bloqueiosUsuarios") - .withIndex("by_usuario", (q) => q.eq("usuarioId", usuarioId)) - .filter((q) => q.eq(q.field("ativo"), true)) - .first(); - - return bloqueio !== null; -} - -/** - * Helper para verificar rate limiting por IP - */ -async function verificarRateLimitIP(ctx: QueryCtx, ipAddress: string) { - // Últimas 15 minutos - const dataLimite = Date.now() - 15 * 60 * 1000; - - const tentativas = await ctx.db - .query("logsLogin") - .withIndex("by_ip", (q) => q.eq("ipAddress", ipAddress)) - .filter((q) => q.gte(q.field("timestamp"), dataLimite)) - .collect(); - - const falhas = tentativas.filter((t) => !t.sucesso).length; - - // Bloquear se 5 ou mais tentativas falhas em 15 minutos - return falhas >= 5; -} - -/** - * Login do usuário (aceita matrícula OU email) - */ -export const login = mutation({ - args: { - matriculaOuEmail: v.string(), // Aceita matrícula ou email - senha: v.string(), - ipAddress: v.optional(v.string()), - userAgent: v.optional(v.string()), - }, - returns: v.union( - v.object({ - sucesso: v.literal(true), - token: v.string(), - usuario: v.object({ - _id: v.id("usuarios"), - matricula: v.string(), - nome: v.string(), - email: v.string(), - funcionarioId: v.optional(v.id("funcionarios")), - role: v.object({ - _id: v.id("roles"), - nome: v.string(), - nivel: v.number(), - setor: v.optional(v.string()), - }), - primeiroAcesso: v.boolean(), - }), - }), - v.object({ - sucesso: v.literal(false), - erro: v.string(), - }) - ), - handler: async (ctx, args) => { - // Verificar rate limiting por IP - if (args.ipAddress) { - const ipBloqueado = await verificarRateLimitIP(ctx, args.ipAddress); - if (ipBloqueado) { - await registrarLogin(ctx, { - matriculaOuEmail: args.matriculaOuEmail, - sucesso: false, - motivoFalha: "rate_limit_excedido", - ipAddress: args.ipAddress, - userAgent: args.userAgent, - }); - - return { - sucesso: false as const, - erro: "Muitas tentativas de login. Tente novamente em 15 minutos.", - }; - } - } - - // Determinar se é email ou matrícula - const isEmail = args.matriculaOuEmail.includes("@"); - - // Buscar usuário - let usuario: Doc<"usuarios"> | null = null; - if (isEmail) { - usuario = await ctx.db - .query("usuarios") - .withIndex("by_email", (q) => q.eq("email", args.matriculaOuEmail)) - .first(); - } else { - const funcionario: Doc<"funcionarios"> | null = await ctx.db.query("funcionarios").withIndex("by_matricula", (q) => q.eq("matricula", args.matriculaOuEmail)).first(); - if (funcionario) { - usuario = await ctx.db - .query("usuarios") - .withIndex("by_funcionarioId", (q) => q.eq("funcionarioId", funcionario._id)) - .first(); - } - } - - if (!usuario) { - await registrarLogin(ctx, { - matriculaOuEmail: args.matriculaOuEmail, - sucesso: false, - motivoFalha: "usuario_inexistente", - ipAddress: args.ipAddress, - userAgent: args.userAgent, - }); - - return { - sucesso: false as const, - erro: "Credenciais incorretas.", - }; - } - - // Verificar se usuário está bloqueado - if ( - usuario.bloqueado || - (await verificarBloqueioUsuario(ctx, usuario._id)) - ) { - await registrarLogin(ctx, { - usuarioId: usuario._id, - matriculaOuEmail: args.matriculaOuEmail, - sucesso: false, - motivoFalha: "usuario_bloqueado", - ipAddress: args.ipAddress, - userAgent: args.userAgent, - }); - - return { - sucesso: false as const, - erro: "Usuário bloqueado. Entre em contato com o TI.", - }; - } - - // Verificar se usuário está ativo - if (!usuario.ativo) { - await registrarLogin(ctx, { - usuarioId: usuario._id, - matriculaOuEmail: args.matriculaOuEmail, - sucesso: false, - motivoFalha: "usuario_inativo", - ipAddress: args.ipAddress, - userAgent: args.userAgent, - }); - - return { - sucesso: false as const, - erro: "Usuário inativo. Entre em contato com o TI.", - }; - } - - // Verificar tentativas de login (bloqueio temporário) - const tentativasRecentes = usuario.tentativasLogin || 0; - const ultimaTentativa = usuario.ultimaTentativaLogin || 0; - const tempoDecorrido = Date.now() - ultimaTentativa; - const TEMPO_BLOQUEIO = 30 * 60 * 1000; // 30 minutos - - // Se tentou 5 vezes e ainda não passou o tempo de bloqueio - if (tentativasRecentes >= 5 && tempoDecorrido < TEMPO_BLOQUEIO) { - await registrarLogin(ctx, { - usuarioId: usuario._id, - matriculaOuEmail: args.matriculaOuEmail, - sucesso: false, - motivoFalha: "bloqueio_temporario", - ipAddress: args.ipAddress, - userAgent: args.userAgent, - }); - - const minutosRestantes = Math.ceil( - (TEMPO_BLOQUEIO - tempoDecorrido) / 60000 - ); - return { - sucesso: false as const, - erro: `Conta temporariamente bloqueada. Tente novamente em ${minutosRestantes} minutos.`, - }; - } - - // Resetar tentativas se passou o tempo de bloqueio - if (tempoDecorrido > TEMPO_BLOQUEIO) { - await ctx.db.patch(usuario._id, { - tentativasLogin: 0, - ultimaTentativaLogin: Date.now(), - }); - } - - // Verificar senha - const senhaValida = await verifyPassword(args.senha, usuario.senhaHash); - - if (!senhaValida) { - // Incrementar tentativas - const novasTentativas = - tempoDecorrido > TEMPO_BLOQUEIO ? 1 : tentativasRecentes + 1; - - await ctx.db.patch(usuario._id, { - tentativasLogin: novasTentativas, - ultimaTentativaLogin: Date.now(), - }); - - await registrarLogin(ctx, { - usuarioId: usuario._id, - matriculaOuEmail: args.matriculaOuEmail, - sucesso: false, - motivoFalha: "senha_incorreta", - ipAddress: args.ipAddress, - userAgent: args.userAgent, - }); - - const tentativasRestantes = 5 - novasTentativas; - if (tentativasRestantes > 0) { - return { - sucesso: false as const, - erro: `Credenciais incorretas. ${tentativasRestantes} tentativas restantes.`, - }; - } else { - return { - sucesso: false as const, - erro: "Conta bloqueada por 30 minutos devido a múltiplas tentativas falhas.", - }; - } - } - - // Login bem-sucedido! Resetar tentativas - await ctx.db.patch(usuario._id, { - tentativasLogin: 0, - ultimaTentativaLogin: undefined, - }); - - // Buscar role do usuário - const role: Doc<"roles"> | null = await ctx.db.get(usuario.roleId); - if (!role) { - return { - sucesso: false as const, - erro: "Erro ao carregar permissões do usuário.", - }; - } - - // Gerar token de sessão - const token = generateToken(); - const agora = Date.now(); - const expiraEm = agora + 8 * 60 * 60 * 1000; // 8 horas - - // Criar sessão - await ctx.db.insert("sessoes", { - usuarioId: usuario._id, - token, - ipAddress: args.ipAddress, - userAgent: args.userAgent, - criadoEm: agora, - expiraEm, - ativo: true, - }); - - // Atualizar último acesso - await ctx.db.patch(usuario._id, { - ultimoAcesso: agora, - atualizadoEm: agora, - }); - - // Log de login bem-sucedido - await registrarLogin(ctx, { - usuarioId: usuario._id, - matriculaOuEmail: args.matriculaOuEmail, - sucesso: true, - ipAddress: args.ipAddress, - userAgent: args.userAgent, - }); - - await ctx.db.insert("logsAcesso", { - usuarioId: usuario._id, - tipo: "login", - ipAddress: args.ipAddress, - userAgent: args.userAgent, - detalhes: "Login realizado com sucesso", - timestamp: agora, - }); - - // Obter matrícula do funcionário se houver - let matricula: string | undefined = undefined; - if (usuario.funcionarioId) { - const funcionario = await ctx.db.get(usuario.funcionarioId); - matricula = funcionario?.matricula; - } - - return { - sucesso: true as const, - token, - usuario: { - _id: usuario._id, - matricula: matricula || "", - nome: usuario.nome, - email: usuario.email, - funcionarioId: usuario.funcionarioId, - role: { - _id: role._id, - nome: role.nome, - nivel: role.nivel, - setor: role.setor, - }, - primeiroAcesso: usuario.primeiroAcesso, - }, - }; - }, -}); - -/** - * Mutation interna para login via HTTP (com IP extraído do request) - * Usada pelo endpoint HTTP /api/login - */ -export const loginComIP = internalMutation({ - args: { - matriculaOuEmail: v.string(), - senha: v.string(), - ipAddress: v.optional(v.string()), - userAgent: v.optional(v.string()), - }, - returns: v.union( - v.object({ - sucesso: v.literal(true), - token: v.string(), - usuario: v.object({ - _id: v.id("usuarios"), - matricula: v.string(), - nome: v.string(), - email: v.string(), - funcionarioId: v.optional(v.id("funcionarios")), - role: v.object({ - _id: v.id("roles"), - nome: v.string(), - nivel: v.number(), - setor: v.optional(v.string()), - }), - primeiroAcesso: v.boolean(), - }), - }), - v.object({ - sucesso: v.literal(false), - erro: v.string(), - }) - ), - handler: async (ctx, args) => { - // Reutilizar a mesma lógica da mutation pública - // Verificar rate limiting por IP - if (args.ipAddress) { - const ipBloqueado = await verificarRateLimitIP(ctx, args.ipAddress); - if (ipBloqueado) { - await registrarLogin(ctx, { - matriculaOuEmail: args.matriculaOuEmail, - sucesso: false, - motivoFalha: "rate_limit_excedido", - ipAddress: args.ipAddress, - userAgent: args.userAgent, - }); - - return { - sucesso: false as const, - erro: "Muitas tentativas de login. Tente novamente em 15 minutos.", - }; - } - } - - // Determinar se é email ou matrícula - const isEmail = args.matriculaOuEmail.includes("@"); - - // Buscar usuário - let usuario: Doc<"usuarios"> | null = null; - if (isEmail) { - usuario = await ctx.db - .query("usuarios") - .withIndex("by_email", (q) => q.eq("email", args.matriculaOuEmail)) - .first(); - } else { - const funcionario: Doc<"funcionarios"> | null = await ctx.db.query("funcionarios").withIndex("by_matricula", (q) => q.eq("matricula", args.matriculaOuEmail)).first(); - if (funcionario) { - usuario = await ctx.db - .query("usuarios") - .withIndex("by_funcionarioId", (q) => q.eq("funcionarioId", funcionario._id)) - .first(); - } - } - - if (!usuario) { - await registrarLogin(ctx, { - matriculaOuEmail: args.matriculaOuEmail, - sucesso: false, - motivoFalha: "usuario_inexistente", - ipAddress: args.ipAddress, - userAgent: args.userAgent, - }); - - return { - sucesso: false as const, - erro: "Credenciais incorretas.", - }; - } - - // Verificar se usuário está bloqueado - if ( - usuario.bloqueado || - (await verificarBloqueioUsuario(ctx, usuario._id)) - ) { - await registrarLogin(ctx, { - usuarioId: usuario._id, - matriculaOuEmail: args.matriculaOuEmail, - sucesso: false, - motivoFalha: "usuario_bloqueado", - ipAddress: args.ipAddress, - userAgent: args.userAgent, - }); - - return { - sucesso: false as const, - erro: "Usuário bloqueado. Entre em contato com o TI.", - }; - } - - // Verificar se usuário está ativo - if (!usuario.ativo) { - await registrarLogin(ctx, { - usuarioId: usuario._id, - matriculaOuEmail: args.matriculaOuEmail, - sucesso: false, - motivoFalha: "usuario_inativo", - ipAddress: args.ipAddress, - userAgent: args.userAgent, - }); - - return { - sucesso: false as const, - erro: "Usuário inativo. Entre em contato com o TI.", - }; - } - - // Verificar tentativas de login (bloqueio temporário) - const tentativasRecentes = usuario.tentativasLogin || 0; - const ultimaTentativa = usuario.ultimaTentativaLogin || 0; - const tempoDecorrido = Date.now() - ultimaTentativa; - const TEMPO_BLOQUEIO = 30 * 60 * 1000; // 30 minutos - - // Se tentou 5 vezes e ainda não passou o tempo de bloqueio - if (tentativasRecentes >= 5 && tempoDecorrido < TEMPO_BLOQUEIO) { - await registrarLogin(ctx, { - usuarioId: usuario._id, - matriculaOuEmail: args.matriculaOuEmail, - sucesso: false, - motivoFalha: "bloqueio_temporario", - ipAddress: args.ipAddress, - userAgent: args.userAgent, - }); - - const minutosRestantes = Math.ceil( - (TEMPO_BLOQUEIO - tempoDecorrido) / 60000 - ); - return { - sucesso: false as const, - erro: `Conta temporariamente bloqueada. Tente novamente em ${minutosRestantes} minutos.`, - }; - } - - // Resetar tentativas se passou o tempo de bloqueio - if (tempoDecorrido > TEMPO_BLOQUEIO) { - await ctx.db.patch(usuario._id, { - tentativasLogin: 0, - ultimaTentativaLogin: Date.now(), - }); - } - - // Verificar senha - const senhaValida = await verifyPassword(args.senha, usuario.senhaHash); - - if (!senhaValida) { - // Incrementar tentativas - const novasTentativas = - tempoDecorrido > TEMPO_BLOQUEIO ? 1 : tentativasRecentes + 1; - - await ctx.db.patch(usuario._id, { - tentativasLogin: novasTentativas, - ultimaTentativaLogin: Date.now(), - }); - - await registrarLogin(ctx, { - usuarioId: usuario._id, - matriculaOuEmail: args.matriculaOuEmail, - sucesso: false, - motivoFalha: "senha_incorreta", - ipAddress: args.ipAddress, - userAgent: args.userAgent, - }); - - const tentativasRestantes = 5 - novasTentativas; - if (tentativasRestantes > 0) { - return { - sucesso: false as const, - erro: `Credenciais incorretas. ${tentativasRestantes} tentativas restantes.`, - }; - } else { - return { - sucesso: false as const, - erro: "Conta bloqueada por 30 minutos devido a múltiplas tentativas falhas.", - }; - } - } - - // Login bem-sucedido! Resetar tentativas - await ctx.db.patch(usuario._id, { - tentativasLogin: 0, - ultimaTentativaLogin: undefined, - }); - - // Buscar role do usuário - const role: Doc<"roles"> | null = await ctx.db.get(usuario.roleId); - if (!role) { - return { - sucesso: false as const, - erro: "Erro ao carregar permissões do usuário.", - }; - } - - // Gerar token de sessão - const token = generateToken(); - const agora = Date.now(); - const expiraEm = agora + 8 * 60 * 60 * 1000; // 8 horas - - // Criar sessão - await ctx.db.insert("sessoes", { - usuarioId: usuario._id, - token, - ipAddress: args.ipAddress, - userAgent: args.userAgent, - criadoEm: agora, - expiraEm, - ativo: true, - }); - - // Atualizar último acesso - await ctx.db.patch(usuario._id, { - ultimoAcesso: agora, - atualizadoEm: agora, - }); - - // Log de login bem-sucedido - await registrarLogin(ctx, { - usuarioId: usuario._id, - matriculaOuEmail: args.matriculaOuEmail, - sucesso: true, - ipAddress: args.ipAddress, - userAgent: args.userAgent, - }); - - await ctx.db.insert("logsAcesso", { - usuarioId: usuario._id, - tipo: "login", - ipAddress: args.ipAddress, - userAgent: args.userAgent, - detalhes: "Login realizado com sucesso", - timestamp: agora, - }); - - // Obter matrícula do funcionário se houver - let matricula: string | undefined = undefined; - if (usuario.funcionarioId) { - const funcionario = await ctx.db.get(usuario.funcionarioId); - matricula = funcionario?.matricula; - } - - return { - sucesso: true as const, - token, - usuario: { - _id: usuario._id, - matricula: matricula || "", - nome: usuario.nome, - email: usuario.email, - funcionarioId: usuario.funcionarioId, - role: { - _id: role._id, - nome: role.nome, - nivel: role.nivel, - setor: role.setor, - }, - primeiroAcesso: usuario.primeiroAcesso, - }, - }; - }, -}); - -/** - * Logout do usuário - */ -export const logout = mutation({ - args: { - token: v.string(), - }, - returns: v.null(), - handler: async (ctx, args) => { - // Buscar sessão - const sessao = await ctx.db - .query("sessoes") - .withIndex("by_token", (q) => q.eq("token", args.token)) - .first(); - - if (sessao) { - // Desativar sessão - await ctx.db.patch(sessao._id, { - ativo: false, - }); - - // Log de logout - await ctx.db.insert("logsAcesso", { - usuarioId: sessao.usuarioId, - tipo: "logout", - timestamp: Date.now(), - }); - } - - return null; - }, -}); - -/** - * Verificar se token é válido e retornar usuário - */ -export const verificarSessao = query({ - args: { - token: v.string(), - }, - returns: v.union( - v.object({ - valido: v.literal(true), - usuario: v.object({ - _id: v.id("usuarios"), - matricula: v.string(), - nome: v.string(), - email: v.string(), - funcionarioId: v.optional(v.id("funcionarios")), - role: v.object({ - _id: v.id("roles"), - nome: v.string(), - nivel: v.number(), - setor: v.optional(v.string()), - }), - primeiroAcesso: v.boolean(), - }), - }), - v.object({ - valido: v.literal(false), - motivo: v.optional(v.string()), - }) - ), - handler: async (ctx, args) => { - // Buscar sessão - const sessao: Doc<"sessoes"> | null = await ctx.db - .query("sessoes") - .withIndex("by_token", (q) => q.eq("token", args.token)) - .first(); - - if (!sessao || !sessao.ativo) { - return { - valido: false as const, - motivo: "Sessão não encontrada ou inativa", - }; - } - - // Verificar se sessão expirou - if (sessao.expiraEm < Date.now()) { - // Não podemos fazer patch/insert em uma query - // A expiração será tratada por uma mutation separada - return { valido: false as const, motivo: "Sessão expirada" }; - } - - // Buscar usuário - const usuario: Doc<"usuarios"> | null = await ctx.db.get(sessao.usuarioId); - if (!usuario || !usuario.ativo) { - return { - valido: false as const, - motivo: "Usuário não encontrado ou inativo", - }; - } - - // Buscar role - const role: Doc<"roles"> | null = await ctx.db.get(usuario.roleId); - if (!role) { - return { valido: false as const, motivo: "Role não encontrada" }; - } - - // Obter matrícula do funcionário se houver - let matricula: string | undefined = undefined; - if (usuario.funcionarioId) { - const funcionario = await ctx.db.get(usuario.funcionarioId); - matricula = funcionario?.matricula; - } - - return { - valido: true as const, - usuario: { - _id: usuario._id, - matricula: matricula || "", - nome: usuario.nome, - email: usuario.email, - funcionarioId: usuario.funcionarioId, - role: { - _id: role._id, - nome: role.nome, - nivel: role.nivel, - setor: role.setor, - }, - primeiroAcesso: usuario.primeiroAcesso, - }, - }; - }, -}); - -/** - * Limpar sessões expiradas (chamada periodicamente) - */ -export const limparSessoesExpiradas = mutation({ - args: {}, - returns: v.object({ - sessoesLimpas: v.number(), - }), - handler: async (ctx) => { - const agora = Date.now(); - const sessoes = await ctx.db - .query("sessoes") - .withIndex("by_ativo", (q) => q.eq("ativo", true)) - .collect(); - - let sessoesLimpas = 0; - - for (const sessao of sessoes) { - if (sessao.expiraEm < agora) { - await ctx.db.patch(sessao._id, { ativo: false }); - - await ctx.db.insert("logsAcesso", { - usuarioId: sessao.usuarioId, - tipo: "sessao_expirada", - timestamp: agora, - }); - - sessoesLimpas++; - } - } - - return { sessoesLimpas }; - }, -}); - -/** - * Alterar senha (primeiro acesso ou reset) - */ -export const alterarSenha = mutation({ - args: { - token: v.string(), - senhaAtual: v.optional(v.string()), - novaSenha: v.string(), - }, - returns: v.union( - v.object({ sucesso: v.literal(true) }), - v.object({ sucesso: v.literal(false), erro: v.string() }) - ), - handler: async (ctx, args) => { - // Verificar sessão - const sessao: Doc<"sessoes"> | null = await ctx.db - .query("sessoes") - .withIndex("by_token", (q) => q.eq("token", args.token)) - .first(); - - if (!sessao || !sessao.ativo) { - return { sucesso: false as const, erro: "Sessão inválida" }; - } - - const usuario: Doc<"usuarios"> | null = await ctx.db.get(sessao.usuarioId); - if (!usuario) { - return { sucesso: false as const, erro: "Usuário não encontrado" }; - } - - // Se não for primeiro acesso, verificar senha atual - if (!usuario.primeiroAcesso && args.senhaAtual) { - const senhaAtualValida = await verifyPassword( - args.senhaAtual, - usuario.senhaHash - ); - if (!senhaAtualValida) { - return { sucesso: false as const, erro: "Senha atual incorreta" }; - } - } - - // Validar nova senha - if (!validarSenha(args.novaSenha)) { - return { - sucesso: false as const, - erro: "Senha deve ter no mínimo 8 caracteres, incluindo letras, números e símbolos", - }; - } - - // Gerar hash da nova senha - const novoHash = await hashPassword(args.novaSenha); - - // Atualizar senha - await ctx.db.patch(usuario._id, { - senhaHash: novoHash, - primeiroAcesso: false, - atualizadoEm: Date.now(), - }); - - // Log - await ctx.db.insert("logsAcesso", { - usuarioId: usuario._id, - tipo: "senha_alterada", - timestamp: Date.now(), - }); - - return { sucesso: true as const }; - }, -}); diff --git a/packages/backend/convex/auth.ts b/packages/backend/convex/auth.ts index e0fac0a..43290f8 100644 --- a/packages/backend/convex/auth.ts +++ b/packages/backend/convex/auth.ts @@ -2,8 +2,9 @@ import { createClient, type GenericCtx } from "@convex-dev/better-auth"; import { convex } from "@convex-dev/better-auth/plugins"; import { components } from "./_generated/api"; import { type DataModel } from "./_generated/dataModel"; -import { query } from "./_generated/server"; +import { mutation, MutationCtx, query, QueryCtx } from "./_generated/server"; import { betterAuth } from "better-auth"; +import { v } from "convex/values"; const siteUrl = process.env.SITE_URL!; @@ -55,3 +56,52 @@ export const getCurrentUser = query({ return user; }, }); + +export const getCurrentUserFunction = async (ctx: QueryCtx | MutationCtx) => { + const authUser = await authComponent.safeGetAuthUser(ctx as any); + if (!authUser) { + return; + } + + const user = await ctx.db + .query("usuarios") + .withIndex("authId", (q) => q.eq("authId", authUser._id)) + .unique(); + if (!user) { + return; + } + return user; +}; + +export const createAuthUser = async ( + ctx: MutationCtx, + args: { nome: string; email: string; password: string } +) => { + const { auth, headers } = await authComponent.getAuth(createAuth, ctx as any); + + const result = await auth.api.signUpEmail({ + headers, + body: { + name: args.nome, + email: args.email, + password: args.password, + }, + }); + + return result.user.id; +}; + +export const updatePassword = async ( + ctx: MutationCtx, + args: { newPassword: string; currentPassword: string } +) => { + const { auth, headers } = await authComponent.getAuth(createAuth, ctx as any); + + await auth.api.changePassword({ + headers, + body: { + currentPassword: args.currentPassword, + newPassword: args.newPassword, + }, + }); +}; diff --git a/packages/backend/convex/schema.ts b/packages/backend/convex/schema.ts index 1558ca6..c929ca7 100644 --- a/packages/backend/convex/schema.ts +++ b/packages/backend/convex/schema.ts @@ -381,8 +381,7 @@ export default defineSchema({ // Sistema de Autenticação e Controle de Acesso usuarios: defineTable({ - authId: v.optional(v.string()), - senhaHash: v.string(), // Senha criptografada com bcrypt + authId: v.string(), nome: v.string(), email: v.string(), funcionarioId: v.optional(v.id("funcionarios")), diff --git a/packages/backend/convex/seed.ts b/packages/backend/convex/seed.ts index d24f307..414f72d 100644 --- a/packages/backend/convex/seed.ts +++ b/packages/backend/convex/seed.ts @@ -1,8 +1,15 @@ -import { internalMutation, mutation, query } from "./_generated/server"; +import { + action, + internalAction, + internalMutation, + mutation, + query, +} from "./_generated/server"; import { internal } from "./_generated/api"; import { v } from "convex/values"; import { hashPassword } from "./auth/utils"; import { Id } from "./_generated/dataModel"; +import { createAuthUser } from "./auth"; // Dados exportados do Convex Cloud const simbolosData = [ @@ -187,83 +194,63 @@ const solicitacoesAcessoData = [ /** * Seed inicial do banco de dados com os dados exportados do Convex Cloud */ -export const seedDatabase = internalMutation({ +export const seedCreateRoles = internalMutation({ args: {}, returns: v.null(), handler: async (ctx) => { - console.log("🌱 Iniciando seed do banco de dados..."); - - // 1. Criar Roles (Perfis de Acesso) console.log("🔐 Criando roles..."); - // TI_MASTER - Nível 0 - Acesso total irrestrito - const roleTIMaster = await ctx.db.insert("roles", { - nome: "ti_master", - descricao: "TI Master", - nivel: 0, - setor: "ti", - customizado: false, - editavel: false, - }); - console.log(" ✅ Role criada: ti_master (Nível 0 - Acesso Total)"); + const ensureRole = async ( + nome: string, + descricao: string, + nivel: number, + setor?: string, + editavel?: boolean + ) => { + const existing = await ctx.db + .query("roles") + .withIndex("by_nome", (q) => q.eq("nome", nome)) + .first(); + if (existing) { + console.log(` ℹ️ Role já existe: ${nome}`); + return existing._id; + } + const id = await ctx.db.insert("roles", { + nome, + descricao, + nivel, + setor, + customizado: false, + editavel, + }); + console.log(` ✅ Role criada: ${nome}`); + return id; + }; - // ADMIN - Nível 2 - Permissões configuráveis - const roleAdmin = await ctx.db.insert("roles", { - nome: "admin", - descricao: "Administrador Geral", - nivel: 2, - setor: "administrativo", - customizado: false, - editavel: true, // Permissões configuráveis - }); - console.log(" ✅ Role criada: admin (Nível 2 - Configurável)"); + await ensureRole("ti_master", "TI Master", 0, "ti", false); + await ensureRole("admin", "Administrador Geral", 2, "administrativo", true); + await ensureRole("ti_usuario", "TI Usuário", 2, "ti", true); + await ensureRole("rh", "Recursos Humanos", 2, "recursos_humanos", false); + await ensureRole("financeiro", "Financeiro", 2, "financeiro", false); + await ensureRole("usuario", "Usuário Padrão", 3, undefined, false); + // Encadeia próxima etapa + await ctx.scheduler.runAfter(0, internal.seed.seedCreateSimbolos, {}); + return null; + }, +}); - // TI_USUARIO - Nível 2 - Suporte técnico - const roleTIUsuario = await ctx.db.insert("roles", { - nome: "ti_usuario", - descricao: "TI Usuário", - nivel: 2, - setor: "ti", - customizado: false, - editavel: true, - }); - console.log(" ✅ Role criada: ti_usuario (Nível 2 - Suporte)"); - - const roleRH = await ctx.db.insert("roles", { - nome: "rh", - descricao: "Recursos Humanos", - nivel: 2, - setor: "recursos_humanos", - customizado: false, - editavel: false, - }); - console.log(" ✅ Role criada: rh"); - - const roleFinanceiro = await ctx.db.insert("roles", { - nome: "financeiro", - descricao: "Financeiro", - nivel: 2, - setor: "financeiro", - customizado: false, - editavel: false, - }); - console.log(" ✅ Role criada: financeiro"); - - const roleUsuario = await ctx.db.insert("roles", { - nome: "usuario", - descricao: "Usuário Padrão", - nivel: 3, - setor: undefined, - customizado: false, - editavel: false, - }); - console.log(" ✅ Role criada: usuario (Nível 3 - Padrão)"); - - // 2. Criar Símbolos (Cargos) +export const seedCreateSimbolos = internalMutation({ + args: {}, + returns: v.null(), + handler: async (ctx) => { console.log("💰 Criando símbolos..."); - const simbolosMap = new Map>(); - + const existentes = await ctx.db.query("simbolos").collect(); + const nomesExistentes = new Set(existentes.map((s) => s.nome)); for (const simbolo of simbolosData) { - const simboloId = await ctx.db.insert("simbolos", { + if (nomesExistentes.has(simbolo.nome)) { + console.log(` ℹ️ Símbolo já existe: ${simbolo.nome}`); + continue; + } + await ctx.db.insert("simbolos", { nome: simbolo.nome, descricao: simbolo.descricao, tipo: simbolo.tipo, @@ -271,24 +258,43 @@ export const seedDatabase = internalMutation({ repValor: simbolo.repValor || "", vencValor: simbolo.vencValor || "", }); - simbolosMap.set(simbolo.nome, simboloId); console.log(` ✅ Símbolo criado: ${simbolo.nome}`); } + // Encadeia próxima etapa + await ctx.scheduler.runAfter(0, internal.seed.seedCreateFuncionarios, {}); + return null; + }, +}); - // 3. Criar Funcionários +export const seedCreateFuncionarios = internalMutation({ + args: {}, + returns: v.null(), + handler: async (ctx) => { console.log("👥 Criando funcionários..."); - const funcionariosMap = new Map>(); + const simbolos = await ctx.db.query("simbolos").collect(); + const simbolosMap = new Map>(); + for (const s of simbolos) { + simbolosMap.set(s.nome, s._id as Id<"simbolos">); + } for (const funcionario of funcionariosData) { - const simboloId = simbolosMap.get(funcionario.simboloNome); - if (!simboloId) { + // Evitar duplicar por CPF + const existente = await ctx.db + .query("funcionarios") + .withIndex("by_cpf", (q) => q.eq("cpf", funcionario.cpf)) + .first(); + if (existente) { console.log( - ` ❌ Símbolo não encontrado: ${funcionario.simboloNome}` + ` ℹ️ Funcionário já existe (CPF ${funcionario.cpf}): ${existente.nome}` ); continue; } - - const funcId = await ctx.db.insert("funcionarios", { + const simboloId = simbolosMap.get(funcionario.simboloNome); + if (!simboloId) { + console.log(` ❌ Símbolo não encontrado: ${funcionario.simboloNome}`); + continue; + } + await ctx.db.insert("funcionarios", { admissaoData: funcionario.admissaoData, cep: funcionario.cep, cidade: funcionario.cidade, @@ -304,36 +310,126 @@ export const seedDatabase = internalMutation({ telefone: funcionario.telefone, uf: funcionario.uf, }); - funcionariosMap.set(funcionario.matricula, funcId); console.log(` ✅ Funcionário criado: ${funcionario.nome}`); } + // Encadeia próxima etapa + await ctx.scheduler.runAfter( + 0, + internal.seed.seedCreateUsuariosParaFuncionarios, + {} + ); + return null; + }, +}); - // 5. Criar usuários para os funcionários +export const seedCreateUsuariosParaFuncionarios = internalMutation({ + args: {}, + returns: v.null(), + handler: async (ctx) => { console.log("👤 Criando usuários para funcionários..."); + // Agenda criação por funcionário para evitar timeout + let delay = 0; for (const funcionario of funcionariosData) { - const funcId = funcionariosMap.get(funcionario.matricula); - if (!funcId) continue; - - const senhaInicial = await hashPassword("Mudar@123"); - await ctx.db.insert("usuarios", { - senhaHash: senhaInicial, - nome: funcionario.nome, - email: funcionario.email, - funcionarioId: funcId as Id<"funcionarios">, - roleId: roleUsuario, - ativo: true, - primeiroAcesso: true, - criadoEm: Date.now(), - atualizadoEm: Date.now(), - }); - console.log( - ` ✅ Usuário criado: ${funcionario.nome} (senha: Mudar@123)` + await ctx.scheduler.runAfter( + delay, + internal.seed.seedCreateUsuarioParaFuncionario, + { + matricula: funcionario.matricula, + nome: funcionario.nome, + email: funcionario.email, + } ); + delay += 50; + } + // Agenda próxima etapa após as criações individuais + await ctx.scheduler.runAfter( + delay + 300, + internal.seed.seedInserirSolicitacoesAcesso, + {} + ); + return null; + }, +}); + +export const seedCreateUsuarioParaFuncionario = internalMutation({ + args: { + matricula: v.string(), + nome: v.string(), + email: v.string(), + }, + returns: v.null(), + handler: async (ctx, args) => { + // Role "usuario" + const roleUsuario = await ctx.db + .query("roles") + .withIndex("by_nome", (q) => q.eq("nome", "usuario")) + .first(); + if (!roleUsuario) { + console.log(' ❌ Role "usuario" não encontrada'); + return null; } - // 6. Inserir solicitações de acesso + const funcionarioDoc = await ctx.db + .query("funcionarios") + .withIndex("by_matricula", (q) => q.eq("matricula", args.matricula)) + .first(); + if (!funcionarioDoc) { + console.log( + ` ❌ Funcionário não encontrado pela matrícula: ${args.matricula}` + ); + return null; + } + + const usuarioExistente = await ctx.db + .query("usuarios") + .withIndex("by_email", (q) => q.eq("email", args.email)) + .first(); + if (usuarioExistente) { + console.log(` ℹ️ Usuário já existe para ${args.email}`); + return null; + } + + const authUserId = await createAuthUser(ctx, { + nome: args.nome, + email: args.email, + password: "Mudar@123", + }); + + await ctx.db.insert("usuarios", { + authId: authUserId, + nome: args.nome, + email: args.email, + funcionarioId: funcionarioDoc._id as Id<"funcionarios">, + roleId: roleUsuario._id, + ativo: true, + primeiroAcesso: true, + criadoEm: Date.now(), + atualizadoEm: Date.now(), + }); + console.log(` ✅ Usuário criado: ${args.nome} (senha: Mudar@123)`); + return null; + }, +}); + +export const seedInserirSolicitacoesAcesso = internalMutation({ + args: {}, + returns: v.null(), + handler: async (ctx) => { console.log("📋 Inserindo solicitações de acesso..."); for (const solicitacao of solicitacoesAcessoData) { + // Evitar duplicidade por matrícula + const existente = await ctx.db + .query("solicitacoesAcesso") + .withIndex("by_matricula", (q) => + q.eq("matricula", solicitacao.matricula) + ) + .first(); + if (existente) { + console.log( + ` ℹ️ Solicitação já existe p/ matrícula ${solicitacao.matricula}` + ); + continue; + } const dadosSolicitacao: { nome: string; matricula: string; @@ -365,10 +461,24 @@ export const seedDatabase = internalMutation({ ` ✅ Solicitação criada: ${solicitacao.nome} (${solicitacao.status})` ); } - - console.log("✨ Seed do banco de dados concluído com sucesso!"); + console.log("✨ Seed concluído!"); return null; - } + }, +}); + +export const seedDatabase = internalAction({ + args: {}, + returns: v.null(), + handler: async (ctx) => { + console.log("🌱 Iniciando seed do banco de dados (action)..."); + await ctx.runMutation(internal.seed.seedCreateRoles, {}); + await ctx.runMutation(internal.seed.seedCreateSimbolos, {}); + await ctx.runMutation(internal.seed.seedCreateFuncionarios, {}); + await ctx.runMutation(internal.seed.seedCreateUsuariosParaFuncionarios, {}); + await ctx.runMutation(internal.seed.seedInserirSolicitacoesAcesso, {}); + console.log("✨ Seed do banco de dados concluído com sucesso pela action!"); + return null; + }, }); /** @@ -379,10 +489,12 @@ export const popularBanco = mutation({ args: {}, returns: v.null(), handler: async (ctx) => { - console.log("🌱 Executando popularBanco (wrapper público para seedDatabase)..."); - // Chama a internalMutation para reaproveitar a lógica de seed - await ctx.runMutation(internal.seed.seedDatabase, {}); - console.log("✅ Seed concluído pelo wrapper público"); + console.log( + "🌱 Executando popularBanco (wrapper público para seedDatabase)..." + ); + // Agenda apenas a primeira etapa; as demais serão encadeadas internamente + await ctx.scheduler.runAfter(0, internal.seed.seedCreateRoles, {}); + console.log("✅ Seed iniciado (etapa 1 agendada)"); return null; }, }); @@ -525,7 +637,7 @@ export const clearDatabase = internalMutation({ ); // 9. Perfis customizados - + // 10. Templates de mensagens const templatesMensagens = await ctx.db .query("templatesMensagens") @@ -587,9 +699,9 @@ export const clearDatabase = internalMutation({ console.log(` ✅ ${sessoes.length} sessões removidas`); // 14. Menu-permissões personalizadas - + // 15. Menu-permissões - + // 16. Role-permissões const rolePermissoes = await ctx.db.query("rolePermissoes").collect(); for (const rp of rolePermissoes) { @@ -795,7 +907,7 @@ export const limparBanco = mutation({ ); // 9. Perfis customizados (já está no código da internalMutation mas vazio) - + // 10. Templates de mensagens const templatesMensagens = await ctx.db .query("templatesMensagens") @@ -857,9 +969,9 @@ export const limparBanco = mutation({ console.log(` ✅ ${sessoes.length} sessões removidas`); // 14. Menu-permissões personalizadas (já está no código da internalMutation mas vazio) - + // 15. Menu-permissões (já está no código da internalMutation mas vazio) - + // 16. Role-permissões const rolePermissoes = await ctx.db.query("rolePermissoes").collect(); for (const rp of rolePermissoes) { @@ -942,13 +1054,14 @@ export const verificarBanco = query({ const funcionarios = await ctx.db.query("funcionarios").collect(); const roles = await ctx.db.query("roles").collect(); const simbolos = await ctx.db.query("simbolos").collect(); - + return { usuarios: usuarios.length, funcionarios: funcionarios.length, roles: roles.length, simbolos: simbolos.length, - total: usuarios.length + funcionarios.length + roles.length + simbolos.length, + total: + usuarios.length + funcionarios.length + roles.length + simbolos.length, }; }, }); diff --git a/packages/backend/convex/usuarios.ts b/packages/backend/convex/usuarios.ts index 8f2f25c..063a9f5 100644 --- a/packages/backend/convex/usuarios.ts +++ b/packages/backend/convex/usuarios.ts @@ -1,10 +1,10 @@ import { v } from "convex/values"; import { mutation, query } from "./_generated/server"; -import { hashPassword, generateToken } from "./auth/utils"; +import { hashPassword } from "./auth/utils"; import { registrarAtividade } from "./logsAtividades"; import { Id, Doc } from "./_generated/dataModel"; -import { api } from "./_generated/api"; import type { QueryCtx, MutationCtx } from "./_generated/server"; +import { createAuthUser, getCurrentUserFunction } from "./auth"; /** * Helper para obter a matrícula do usuário (do funcionário se houver) @@ -20,33 +20,6 @@ async function obterMatriculaUsuario( return undefined; } -/** - * Helper para obter usuário autenticado (Better Auth ou Sessão) - * Usa a mesma lógica do obterPerfil para garantir consistência - */ -async function getUsuarioAutenticado(ctx: QueryCtx | MutationCtx) { - // FASE 1 IMPLEMENTADA: Usa Custom Auth Provider configurado no convex.config.ts - // O provider busca sessão por token específico (seguro) ou Better Auth - const identity = await ctx.auth.getUserIdentity(); - let usuarioAtual = null; - - if (identity && identity.email) { - usuarioAtual = await ctx.db - .query("usuarios") - .withIndex("by_email", (q) => q.eq("email", identity.email!)) - .first(); - } - - if (!usuarioAtual && identity) { - console.error("⚠️ [getUsuarioAutenticado] Identity encontrada mas usuário não encontrado no banco:", { - email: identity.email, - subject: identity.subject - }); - } - - return usuarioAtual; -} - /** * Associar funcionário a um usuário */ @@ -132,12 +105,17 @@ export const criar = mutation({ return { sucesso: false as const, erro: "E-mail já cadastrado" }; } - // Gerar hash da senha inicial - const senhaHash = await hashPassword(args.senhaInicial); + const senhaTemporaria = args.senhaInicial; + + const authUserId = await createAuthUser(ctx, { + nome: args.nome, + email: args.email, + password: senhaTemporaria, + }); // Criar usuário const usuarioId = await ctx.db.insert("usuarios", { - senhaHash, + authId: authUserId, nome: args.nome, email: args.email, funcionarioId: args.funcionarioId, @@ -161,69 +139,6 @@ export const listar = query({ matricula: v.optional(v.string()), ativo: v.optional(v.boolean()), }, - // returns: v.array( - // v.object({ - // _id: v.id("usuarios"), - // matricula: v.string(), - // nome: v.string(), - // email: v.string(), - // ativo: v.boolean(), - // bloqueado: v.optional(v.boolean()), - // motivoBloqueio: v.optional(v.string()), - // primeiroAcesso: v.boolean(), - // ultimoAcesso: v.optional(v.number()), - // criadoEm: v.number(), - // role: v.union( - // v.object({ - // _id: v.id("roles"), - // _creationTime: v.optional(v.number()), - // criadoPor: v.optional(v.id("usuarios")), - // customizado: v.optional(v.boolean()), - // descricao: v.string(), - // editavel: v.optional(v.boolean()), - // nome: v.string(), - // nivel: v.number(), - // setor: v.optional(v.string()), - // }), - // v.object({ - // _id: v.id("roles"), - // _creationTime: v.optional(v.number()), - // criadoPor: v.optional(v.id("usuarios")), - // customizado: v.optional(v.boolean()), - // descricao: v.literal("Perfil não encontrado"), - // editavel: v.optional(v.boolean()), - // nome: v.literal("erro_role_ausente"), - // nivel: v.literal(999), - // setor: v.optional(v.string()), - // erro: v.literal(true), - // }) - // ), - // funcionario: v.optional( - // v.object({ - // _id: v.id("funcionarios"), - // nome: v.string(), - // matricula: v.optional(v.string()), - // descricaoCargo: v.optional(v.string()), - // simboloTipo: v.union( - // v.literal("cargo_comissionado"), - // v.literal("funcao_gratificada") - // ), - // }) - // ), - // avisos: v.optional( - // v.array( - // v.object({ - // tipo: v.union( - // v.literal("erro"), - // v.literal("aviso"), - // v.literal("info") - // ), - // mensagem: v.string(), - // }) - // ) - // ), - // }) - // ), handler: async (ctx, args) => { let usuarios = await ctx.db.query("usuarios").collect(); @@ -439,34 +354,34 @@ export const alterarStatus = mutation({ /** * Resetar senha do usuário */ -export const resetarSenha = mutation({ - args: { - usuarioId: v.id("usuarios"), - novaSenha: v.string(), - }, - returns: v.null(), - handler: async (ctx, args) => { - const senhaHash = await hashPassword(args.novaSenha); +// export const resetarSenha = mutation({ +// args: { +// usuarioId: v.id("usuarios"), +// novaSenha: v.string(), +// }, +// returns: v.null(), +// handler: async (ctx, args) => { +// const senhaHash = await hashPassword(args.novaSenha); - await ctx.db.patch(args.usuarioId, { - senhaHash, - primeiroAcesso: true, - atualizadoEm: Date.now(), - }); +// await ctx.db.patch(args.usuarioId, { +// senhaHash, +// primeiroAcesso: true, +// atualizadoEm: Date.now(), +// }); - // Desativar todas as sessões - const sessoes = await ctx.db - .query("sessoes") - .withIndex("by_usuario", (q) => q.eq("usuarioId", args.usuarioId)) - .collect(); +// // Desativar todas as sessões +// const sessoes = await ctx.db +// .query("sessoes") +// .withIndex("by_usuario", (q) => q.eq("usuarioId", args.usuarioId)) +// .collect(); - for (const sessao of sessoes) { - await ctx.db.patch(sessao._id, { ativo: false }); - } +// for (const sessao of sessoes) { +// await ctx.db.patch(sessao._id, { ativo: false }); +// } - return null; - }, -}); +// return null; +// }, +// }); /** * Excluir usuário @@ -601,19 +516,6 @@ export const atualizarPerfil = mutation({ .first(); } - // SE NÃO ENCONTROU, BUSCAR POR SESSÃO ATIVA (Sistema customizado) - if (!usuarioAtual) { - const sessaoAtiva = await ctx.db - .query("sessoes") - .filter((q) => q.eq(q.field("ativo"), true)) - .order("desc") - .first(); - - if (sessaoAtiva) { - usuarioAtual = await ctx.db.get(sessaoAtiva.usuarioId); - } - } - if (!usuarioAtual) throw new Error("Usuário não encontrado"); // Validar statusMensagem (max 100 chars) @@ -678,9 +580,6 @@ export const obterPerfil = query({ v.null() ), handler: async (ctx) => { - console.log("=== DEBUG obterPerfil ==="); - - // TENTAR BETTER AUTH PRIMEIRO const identity = await ctx.auth.getUserIdentity(); console.log("Identity:", identity ? "encontrado" : "null"); @@ -693,42 +592,9 @@ export const obterPerfil = query({ .query("usuarios") .withIndex("by_email", (q) => q.eq("email", identity.email!)) .first(); - - console.log( - "Usuário encontrado por email:", - usuarioAtual ? "SIM" : "NÃO" - ); - } - - // SE NÃO ENCONTROU, BUSCAR POR SESSÃO ATIVA (Sistema customizado) - if (!usuarioAtual) { - console.log("Buscando por sessão ativa..."); - const sessaoAtiva = await ctx.db - .query("sessoes") - .filter((q) => q.eq(q.field("ativo"), true)) - .order("desc") - .first(); - - console.log("Sessão ativa encontrada:", sessaoAtiva ? "SIM" : "NÃO"); - - if (sessaoAtiva) { - usuarioAtual = await ctx.db.get(sessaoAtiva.usuarioId); - console.log( - "Usuário da sessão encontrado:", - usuarioAtual ? "SIM" : "NÃO" - ); - } } if (!usuarioAtual) { - console.log("❌ Nenhum usuário encontrado"); - // Listar todos os usuários para debug - const todosUsuarios = await ctx.db.query("usuarios").collect(); - console.log("Total de usuários no banco:", todosUsuarios.length); - console.log( - "Emails cadastrados:", - todosUsuarios.map((u) => u.email) - ); return null; } @@ -789,7 +655,10 @@ export const listarParaChat = query({ ), handler: async (ctx) => { // Obter usuário autenticado usando função helper compartilhada - const usuarioAtual = await getUsuarioAutenticado(ctx); + const usuarioAtual = await getCurrentUserFunction(ctx); + if (!usuarioAtual) { + return []; + } // Buscar todos os usuários ativos const usuarios = await ctx.db @@ -888,6 +757,11 @@ export const bloquearUsuario = mutation({ v.object({ sucesso: v.literal(false), erro: v.string() }) ), handler: async (ctx, args) => { + const usuarioAtual = await getCurrentUserFunction(ctx); + if (!usuarioAtual) { + return { sucesso: false as const, erro: "Usuário não autenticado" }; + } + const usuario = await ctx.db.get(args.usuarioId); if (!usuario) { return { sucesso: false as const, erro: "Usuário não encontrado" }; @@ -948,6 +822,11 @@ export const desbloquearUsuario = mutation({ v.object({ sucesso: v.literal(false), erro: v.string() }) ), handler: async (ctx, args) => { + const usuarioAtual = await getCurrentUserFunction(ctx); + if (!usuarioAtual) { + return { sucesso: false as const, erro: "Usuário não autenticado" }; + } + const usuario = await ctx.db.get(args.usuarioId); if (!usuario) { return { sucesso: false as const, erro: "Usuário não encontrado" }; @@ -995,48 +874,48 @@ export const desbloquearUsuario = mutation({ /** * Resetar senha de usuário (apenas TI_MASTER) */ -export const resetarSenhaUsuario = mutation({ - args: { - usuarioId: v.id("usuarios"), - resetadoPorId: v.id("usuarios"), - novaSenhaTemporaria: v.optional(v.string()), // Se não fornecer, gera automática - }, - returns: v.union( - v.object({ sucesso: v.literal(true), senhaTemporaria: v.string() }), - v.object({ sucesso: v.literal(false), erro: v.string() }) - ), - handler: async (ctx, args) => { - const usuario = await ctx.db.get(args.usuarioId); - if (!usuario) { - return { sucesso: false as const, erro: "Usuário não encontrado" }; - } +// export const resetarSenhaUsuario = mutation({ +// args: { +// usuarioId: v.id("usuarios"), +// resetadoPorId: v.id("usuarios"), +// novaSenhaTemporaria: v.optional(v.string()), // Se não fornecer, gera automática +// }, +// returns: v.union( +// v.object({ sucesso: v.literal(true), senhaTemporaria: v.string() }), +// v.object({ sucesso: v.literal(false), erro: v.string() }) +// ), +// handler: async (ctx, args) => { +// const usuario = await ctx.db.get(args.usuarioId); +// if (!usuario) { +// return { sucesso: false as const, erro: "Usuário não encontrado" }; +// } - // Gerar senha temporária se não foi fornecida - const senhaTemporaria = args.novaSenhaTemporaria || gerarSenhaTemporaria(); - const senhaHash = await hashPassword(senhaTemporaria); +// // Gerar senha temporária se não foi fornecida +// const senhaTemporaria = args.novaSenhaTemporaria || gerarSenhaTemporaria(); +// const senhaHash = await hashPassword(senhaTemporaria); - // Atualizar usuário - await ctx.db.patch(args.usuarioId, { - senhaHash, - primeiroAcesso: true, // Força mudança de senha no próximo login - tentativasLogin: 0, - ultimaTentativaLogin: undefined, - atualizadoEm: Date.now(), - }); +// // Atualizar usuário +// await ctx.db.patch(args.usuarioId, { +// senhaHash, +// primeiroAcesso: true, // Força mudança de senha no próximo login +// tentativasLogin: 0, +// ultimaTentativaLogin: undefined, +// atualizadoEm: Date.now(), +// }); - // Log de atividade - await registrarAtividade( - ctx, - args.resetadoPorId, - "resetar_senha", - "usuarios", - JSON.stringify({ usuarioId: args.usuarioId }), - args.usuarioId - ); +// // Log de atividade +// await registrarAtividade( +// ctx, +// args.resetadoPorId, +// "resetar_senha", +// "usuarios", +// JSON.stringify({ usuarioId: args.usuarioId }), +// args.usuarioId +// ); - return { sucesso: true as const, senhaTemporaria }; - }, -}); +// return { sucesso: true as const, senhaTemporaria }; +// }, +// }); // Helper para gerar senha temporária function gerarSenhaTemporaria(): string { @@ -1066,6 +945,11 @@ export const editarUsuario = mutation({ v.object({ sucesso: v.literal(false), erro: v.string() }) ), handler: async (ctx, args) => { + const usuarioAtual = await getCurrentUserFunction(ctx); + if (!usuarioAtual) { + return { sucesso: false as const, erro: "Usuário não autenticado" }; + } + const usuario = await ctx.db.get(args.usuarioId); if (!usuario) { return { sucesso: false as const, erro: "Usuário não encontrado" }; @@ -1153,7 +1037,12 @@ export const criarAdminMaster = mutation({ } const senhaTemporaria = args.senha || gerarSenhaTemporaria(); - const senhaHash = await hashPassword(senhaTemporaria); + + const authUserId = await createAuthUser(ctx, { + nome: args.nome, + email: args.email, + password: senhaTemporaria, + }); // Verificar se email já existe const existentePorEmail = await ctx.db @@ -1164,11 +1053,11 @@ export const criarAdminMaster = mutation({ // Promove usuário existente por email await ctx.db.patch(existentePorEmail._id, { nome: args.nome, - senhaHash, roleId: roleTIMaster._id, ativo: true, primeiroAcesso: true, atualizadoEm: Date.now(), + authId: authUserId, }); return { sucesso: true as const, @@ -1179,7 +1068,7 @@ export const criarAdminMaster = mutation({ // Criar novo usuário TI Master const usuarioId = await ctx.db.insert("usuarios", { - senhaHash, + authId: authUserId, nome: args.nome, email: args.email, roleId: roleTIMaster._id, @@ -1192,191 +1081,3 @@ export const criarAdminMaster = mutation({ return { sucesso: true as const, usuarioId, senhaTemporaria }; }, }); - -/** - * Desativar usuário logicamente (soft delete - apenas TI_MASTER) - */ -export const excluirUsuarioLogico = mutation({ - args: { - usuarioId: v.id("usuarios"), - excluidoPorId: v.id("usuarios"), - }, - returns: v.union( - v.object({ sucesso: v.literal(true) }), - v.object({ sucesso: v.literal(false), erro: v.string() }) - ), - handler: async (ctx, args) => { - const usuario = await ctx.db.get(args.usuarioId); - if (!usuario) { - return { sucesso: false as const, erro: "Usuário não encontrado" }; - } - - // Marcar como inativo - await ctx.db.patch(args.usuarioId, { - ativo: false, - atualizadoEm: Date.now(), - }); - - // Desativar todas as sessões - const sessoes = await ctx.db - .query("sessoes") - .withIndex("by_usuario", (q) => q.eq("usuarioId", args.usuarioId)) - .filter((q) => q.eq(q.field("ativo"), true)) - .collect(); - - for (const sessao of sessoes) { - await ctx.db.patch(sessao._id, { ativo: false }); - } - - // Log de atividade - await registrarAtividade( - ctx, - args.excluidoPorId, - "excluir", - "usuarios", - JSON.stringify({ usuarioId: args.usuarioId }), - args.usuarioId - ); - - return { sucesso: true as const }; - }, -}); - -/** - * Criar usuário completo com permissões (TI_MASTER) - */ -export const criarUsuarioCompleto = mutation({ - args: { - nome: v.string(), - email: v.string(), - roleId: v.id("roles"), - setor: v.optional(v.string()), - senhaInicial: v.optional(v.string()), - criadoPorId: v.id("usuarios"), - enviarEmailBoasVindas: v.optional(v.boolean()), - }, - returns: v.union( - v.object({ - sucesso: v.literal(true), - usuarioId: v.id("usuarios"), - senhaTemporaria: v.string(), - }), - v.object({ sucesso: v.literal(false), erro: v.string() }) - ), - handler: async (ctx, args) => { - // Verificar se email já existe - const emailExistente = await ctx.db - .query("usuarios") - .withIndex("by_email", (q) => q.eq("email", args.email)) - .first(); - - if (emailExistente) { - return { sucesso: false as const, erro: "E-mail já cadastrado" }; - } - - // Gerar senha inicial se não fornecida - const senhaTemporaria = args.senhaInicial || gerarSenhaTemporaria(); - const senhaHash = await hashPassword(senhaTemporaria); - - // Criar usuário - const usuarioId = await ctx.db.insert("usuarios", { - senhaHash, - nome: args.nome, - email: args.email, - roleId: args.roleId, - setor: args.setor, - ativo: true, - primeiroAcesso: true, - criadoEm: Date.now(), - atualizadoEm: Date.now(), - }); - - // Log de atividade - await registrarAtividade( - ctx, - args.criadoPorId, - "criar", - "usuarios", - JSON.stringify({ usuarioId, nome: args.nome }), - usuarioId - ); - - // TODO: Se enviarEmailBoasVindas = true, enfileirar email - // Isso será implementado quando criarmos o sistema de emails - - return { sucesso: true as const, usuarioId, senhaTemporaria }; - }, -}); - -/** - * Criar (ou garantir) um usuário ADMIN padrão - */ -export const criarAdminPadrao = mutation({ - args: { - nome: v.optional(v.string()), - email: v.optional(v.string()), - senha: v.optional(v.string()), - }, - returns: v.object({ - sucesso: v.boolean(), - usuarioId: v.optional(v.id("usuarios")), - }), - handler: async (ctx, args) => { - const nome = args.nome ?? "Administrador Geral"; - const email = args.email ?? "admin@sgse.pe.gov.br"; - const senha = args.senha ?? "Admin@123"; - - // Garantir role ADMIN (nível 2) - let roleAdmin = await ctx.db - .query("roles") - .withIndex("by_nome", (q) => q.eq("nome", "admin")) - .first(); - if (!roleAdmin) { - const roleId = await ctx.db.insert("roles", { - nome: "admin", - descricao: "Administrador Geral", - nivel: 2, - setor: "administrativo", - customizado: false, - editavel: true, - }); - roleAdmin = await ctx.db.get(roleId); - } - - if (!roleAdmin) return { sucesso: false }; - - // Verificar se já existe por email - const existentePorEmail = await ctx.db - .query("usuarios") - .withIndex("by_email", (q) => q.eq("email", email)) - .first(); - - const senhaHash = await hashPassword(senha); - - if (existentePorEmail) { - await ctx.db.patch(existentePorEmail._id, { - nome, - email, - senhaHash, - roleId: roleAdmin._id, - ativo: true, - primeiroAcesso: false, - atualizadoEm: Date.now(), - }); - return { sucesso: true, usuarioId: existentePorEmail._id }; - } - - const usuarioId = await ctx.db.insert("usuarios", { - senhaHash, - nome, - email, - roleId: roleAdmin._id, - ativo: true, - primeiroAcesso: false, - criadoEm: Date.now(), - atualizadoEm: Date.now(), - }); - - return { sucesso: true, usuarioId }; - }, -});