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.
This commit is contained in:
2025-11-07 23:33:09 -03:00
parent 427c78ec37
commit 3a32f5e4eb
6 changed files with 377 additions and 1350 deletions

View File

@@ -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 };
},
});