feat: add password reset functionality for users, including modal interface for generating temporary passwords and copying to clipboard; enhance backend mutation for secure password management and email notifications

This commit is contained in:
2025-12-09 07:41:19 -03:00
parent 4110b12724
commit 2172d9a937
2 changed files with 275 additions and 39 deletions

View File

@@ -841,48 +841,107 @@ 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);
// Verificar permissão (apenas TI_MASTER)
const resetadoPor = await ctx.db.get(args.resetadoPorId);
if (!resetadoPor) {
return { sucesso: false as const, erro: 'Usuário que está resetando não encontrado' };
}
// // 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(),
// });
// Buscar a role do usuário
if (!resetadoPor.roleId) {
return { sucesso: false as const, erro: 'Usuário não possui role definida' };
}
// // Log de atividade
// await registrarAtividade(
// ctx,
// args.resetadoPorId,
// "resetar_senha",
// "usuarios",
// JSON.stringify({ usuarioId: args.usuarioId }),
// args.usuarioId
// );
const role = await ctx.db.get(resetadoPor.roleId);
if (!role) {
return { sucesso: false as const, erro: 'Role do usuário não encontrada' };
}
// return { sucesso: true as const, senhaTemporaria };
// },
// });
// Permitir TI_MASTER, TI_USUARIO e ADMIN
const rolesPermitidas = ['ti_master', 'ti_usuario', 'admin'];
if (!rolesPermitidas.includes(role.nome)) {
return {
sucesso: false as const,
erro: 'Apenas usuários de TI ou administradores podem resetar senhas'
};
}
// Gerar senha temporária se não foi fornecida
const senhaTemporaria = args.novaSenhaTemporaria || gerarSenhaTemporaria();
try {
// Fazer hash da senha
const { hashPassword } = await import('./auth/utils');
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()
});
// Desativar todas as sessões ativas
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 });
}
// Enviar email com a nova senha usando template
try {
await ctx.scheduler.runAfter(0, api.email.enviarEmailComTemplate, {
destinatario: usuario.email,
destinatarioId: args.usuarioId,
templateCodigo: 'SENHA_RESETADA',
variaveis: {
senha: senhaTemporaria
},
enviadoPor: args.resetadoPorId
});
} catch (emailError) {
console.error('Erro ao agendar envio de email:', emailError);
// Não falhar a mutation se o email falhar, apenas logar o erro
}
// Log de atividade
await registrarAtividade(
ctx,
args.resetadoPorId,
'resetar_senha',
'usuarios',
JSON.stringify({ usuarioId: args.usuarioId }),
args.usuarioId
);
return { sucesso: true as const, senhaTemporaria };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return { sucesso: false as const, erro: `Erro ao resetar senha: ${errorMessage}` };
}
}
});
// Helper para gerar senha temporária
function gerarSenhaTemporaria(): string {