feat: integrate Better Auth and enhance authentication flow
- Added Better Auth integration to the web application, allowing for dual login support with both custom and Better Auth systems. - Updated authentication client configuration to dynamically set the base URL based on the environment. - Enhanced chat components to utilize user authentication status, improving user experience and security. - Refactored various components to support Better Auth, including error handling and user identity management. - Improved notification handling and user feedback mechanisms during authentication processes.
This commit is contained in:
2102
packages/backend/convex/_generated/api.d.ts
vendored
2102
packages/backend/convex/_generated/api.d.ts
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,64 +1,64 @@
|
||||
"use node";
|
||||
|
||||
import { action } from "../_generated/server";
|
||||
import { v } from "convex/values";
|
||||
|
||||
// Importar nodemailer de forma estática para evitar problemas com caminhos no Windows
|
||||
import nodemailer from "nodemailer";
|
||||
|
||||
export const testarConexao = action({
|
||||
args: {
|
||||
servidor: v.string(),
|
||||
porta: v.number(),
|
||||
usuario: v.string(),
|
||||
senha: v.string(),
|
||||
usarSSL: v.boolean(),
|
||||
usarTLS: v.boolean(),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({ sucesso: v.literal(true) }),
|
||||
v.object({ sucesso: v.literal(false), erro: v.string() })
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
|
||||
try {
|
||||
// Validações básicas
|
||||
if (!args.servidor || args.servidor.trim() === "") {
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Servidor SMTP não pode estar vazio",
|
||||
};
|
||||
}
|
||||
|
||||
if (args.porta < 1 || args.porta > 65535) {
|
||||
return { sucesso: false as const, erro: "Porta inválida" };
|
||||
}
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: args.servidor,
|
||||
port: args.porta,
|
||||
secure: args.usarSSL,
|
||||
auth: {
|
||||
user: args.usuario,
|
||||
pass: args.senha,
|
||||
},
|
||||
tls: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
connectionTimeout: 10000, // 10 segundos
|
||||
greetingTimeout: 10000,
|
||||
socketTimeout: 10000,
|
||||
});
|
||||
|
||||
// Verificar conexão
|
||||
await transporter.verify();
|
||||
|
||||
return { sucesso: true as const };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
return { sucesso: false as const, erro: errorMessage };
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
"use node";
|
||||
|
||||
import { action } from "../_generated/server";
|
||||
import { v } from "convex/values";
|
||||
|
||||
// Importar nodemailer de forma estática para evitar problemas com caminhos no Windows
|
||||
import nodemailer from "nodemailer";
|
||||
|
||||
export const testarConexao = action({
|
||||
args: {
|
||||
servidor: v.string(),
|
||||
porta: v.number(),
|
||||
usuario: v.string(),
|
||||
senha: v.string(),
|
||||
usarSSL: v.boolean(),
|
||||
usarTLS: v.boolean(),
|
||||
},
|
||||
returns: v.union(
|
||||
v.object({ sucesso: v.literal(true) }),
|
||||
v.object({ sucesso: v.literal(false), erro: v.string() })
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
|
||||
try {
|
||||
// Validações básicas
|
||||
if (!args.servidor || args.servidor.trim() === "") {
|
||||
return {
|
||||
sucesso: false as const,
|
||||
erro: "Servidor SMTP não pode estar vazio",
|
||||
};
|
||||
}
|
||||
|
||||
if (args.porta < 1 || args.porta > 65535) {
|
||||
return { sucesso: false as const, erro: "Porta inválida" };
|
||||
}
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: args.servidor,
|
||||
port: args.porta,
|
||||
secure: args.usarSSL,
|
||||
auth: {
|
||||
user: args.usuario,
|
||||
pass: args.senha,
|
||||
},
|
||||
tls: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
connectionTimeout: 10000, // 10 segundos
|
||||
greetingTimeout: 10000,
|
||||
socketTimeout: 10000,
|
||||
});
|
||||
|
||||
// Verificar conexão
|
||||
await transporter.verify();
|
||||
|
||||
return { sucesso: true as const };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
return { sucesso: false as const, erro: errorMessage };
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -18,31 +18,46 @@ function normalizarTextoParaBusca(texto: string): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function para obter usuário autenticado (Better Auth ou Sessão)
|
||||
* Helper function para obter usuário autenticado
|
||||
*
|
||||
* FASE 1 IMPLEMENTADA: Usa Custom Auth Provider configurado no convex.config.ts
|
||||
*
|
||||
* O provider tenta:
|
||||
* 1. Buscar sessão customizada por token (sistema atual) ✅ FUNCIONANDO
|
||||
* 2. Validar via Better Auth (quando configurado) ⏳ PRÓXIMA FASE
|
||||
*
|
||||
* ⚠️ CORREÇÃO DE SEGURANÇA: Busca sessão por token específico (não mais recente)
|
||||
*/
|
||||
async function getUsuarioAutenticado(ctx: QueryCtx | MutationCtx) {
|
||||
// Tentar autenticação via Better Auth primeiro
|
||||
// Tentar autenticação via Custom Auth Provider (convex.config.ts)
|
||||
// Isso funciona tanto com tokens customizados quanto com Better Auth
|
||||
const identity = await ctx.auth.getUserIdentity();
|
||||
let usuarioAtual = null;
|
||||
|
||||
if (identity && identity.email) {
|
||||
// Buscar usuário por email (vindo do provider)
|
||||
usuarioAtual = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_email", (q) => q.eq("email", identity.email!))
|
||||
.first();
|
||||
|
||||
if (usuarioAtual) {
|
||||
// Log para debug (apenas em desenvolvimento)
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.log("✅ [getUsuarioAutenticado] Usuário identificado via Custom Auth Provider:", {
|
||||
id: usuarioAtual._id,
|
||||
nome: usuarioAtual.nome,
|
||||
email: usuarioAtual.email,
|
||||
subject: identity.subject
|
||||
});
|
||||
}
|
||||
return usuarioAtual;
|
||||
}
|
||||
}
|
||||
|
||||
// Se não encontrou via Better Auth, tentar via sessão mais recente
|
||||
// Se não encontrou, logar aviso
|
||||
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);
|
||||
}
|
||||
console.warn("⚠️ [getUsuarioAutenticado] Usuário não autenticado - token inválido ou expirado");
|
||||
}
|
||||
|
||||
return usuarioAtual;
|
||||
@@ -305,7 +320,19 @@ export const enviarMensagem = mutation({
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const usuarioAtual = await getUsuarioAutenticado(ctx);
|
||||
if (!usuarioAtual) throw new Error("Não autenticado");
|
||||
if (!usuarioAtual) {
|
||||
console.error("❌ [enviarMensagem] Usuário não autenticado - Better Auth não conseguiu identificar");
|
||||
throw new Error("Não autenticado");
|
||||
}
|
||||
|
||||
// Log para debug (apenas em desenvolvimento)
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.log("✅ [enviarMensagem] Usuário identificado:", {
|
||||
id: usuarioAtual._id,
|
||||
nome: usuarioAtual.nome,
|
||||
email: usuarioAtual.email
|
||||
});
|
||||
}
|
||||
|
||||
// Verificar se usuário pertence à conversa
|
||||
const conversa = await ctx.db.get(args.conversaId);
|
||||
|
||||
@@ -1,4 +1,72 @@
|
||||
import { defineApp } from "convex/server";
|
||||
import { defineApp, defineAuth } from "convex/server";
|
||||
import betterAuth from "@convex-dev/better-auth/convex.config";
|
||||
|
||||
/**
|
||||
* Custom Auth Provider para aceitar tokens customizados
|
||||
*
|
||||
* Este provider funciona junto com Better Auth para suportar:
|
||||
* 1. Tokens customizados do sistema atual (via sessoes)
|
||||
* 2. Tokens do Better Auth (quando configurado)
|
||||
*/
|
||||
const customAuth = defineAuth({
|
||||
getToken: async (request: Request): Promise<string | null> => {
|
||||
// Tentar obter token de várias fontes
|
||||
// 1. Authorization header (Bearer token)
|
||||
const authHeader = request.headers.get("authorization");
|
||||
if (authHeader?.startsWith("Bearer ")) {
|
||||
return authHeader.substring(7);
|
||||
}
|
||||
|
||||
// 2. x-auth-token header
|
||||
const xAuthToken = request.headers.get("x-auth-token");
|
||||
if (xAuthToken) {
|
||||
return xAuthToken;
|
||||
}
|
||||
|
||||
// 3. Query parameter (para WebSocket)
|
||||
const url = new URL(request.url);
|
||||
const queryToken = url.searchParams.get("authToken");
|
||||
if (queryToken) {
|
||||
return queryToken;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
getIdentity: async (ctx, token: string) => {
|
||||
if (!token) return null;
|
||||
|
||||
// Buscar sessão ativa por token
|
||||
const sessao = await ctx.db
|
||||
.query("sessoes")
|
||||
.withIndex("by_token", (q) => q.eq("token", token))
|
||||
.filter((q) => q.eq(q.field("ativo"), true))
|
||||
.first();
|
||||
|
||||
if (!sessao) return null;
|
||||
|
||||
// Buscar usuário da sessão
|
||||
const usuario = await ctx.db.get(sessao.usuarioId);
|
||||
if (!usuario || !usuario.ativo) return null;
|
||||
|
||||
// Retornar identity compatível com Better Auth
|
||||
return {
|
||||
subject: usuario._id,
|
||||
email: usuario.email,
|
||||
emailVerified: true,
|
||||
name: usuario.nome,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Configuração Better Auth para Convex
|
||||
*
|
||||
* Usando Better Auth oficialmente integrado com Convex.
|
||||
* O Custom Auth Provider acima funciona junto com Better Auth para suportar tokens customizados.
|
||||
*/
|
||||
const app = defineApp();
|
||||
app.use(customAuth);
|
||||
app.use(betterAuth);
|
||||
|
||||
export default app;
|
||||
|
||||
@@ -25,7 +25,8 @@ async function obterMatriculaUsuario(
|
||||
* Usa a mesma lógica do obterPerfil para garantir consistência
|
||||
*/
|
||||
async function getUsuarioAutenticado(ctx: QueryCtx | MutationCtx) {
|
||||
// Tentar autenticação via Better Auth primeiro
|
||||
// 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;
|
||||
|
||||
@@ -36,17 +37,11 @@ async function getUsuarioAutenticado(ctx: QueryCtx | MutationCtx) {
|
||||
.first();
|
||||
}
|
||||
|
||||
// Se não encontrou via Better Auth, tentar via sessão mais recente
|
||||
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 && identity) {
|
||||
console.error("⚠️ [getUsuarioAutenticado] Identity encontrada mas usuário não encontrado no banco:", {
|
||||
email: identity.email,
|
||||
subject: identity.subject
|
||||
});
|
||||
}
|
||||
|
||||
return usuarioAtual;
|
||||
|
||||
Reference in New Issue
Block a user