Files
sgse-app/packages/backend/convex/auth.ts

147 lines
4.3 KiB
TypeScript

import { createClient, type GenericCtx } from '@convex-dev/better-auth';
import { convex } from '@convex-dev/better-auth/plugins';
import { betterAuth } from 'better-auth';
import { components } from './_generated/api';
import type { DataModel } from './_generated/dataModel';
import { type MutationCtx, type QueryCtx, type ActionCtx, query } from './_generated/server';
// Usar SITE_URL se disponível, caso contrário usar CONVEX_SITE_URL ou um valor padrão
const siteUrl = process.env.SITE_URL || process.env.CONVEX_SITE_URL || 'http://localhost:5173';
// The component client has methods needed for integrating Convex with Better Auth,
// as well as helper methods for general use.
export const authComponent = createClient<DataModel>(components.betterAuth);
/**
* Helper type para converter contextos do Convex para GenericCtx do better-auth
* Isso resolve incompatibilidade de tipos entre versões do Convex sem usar 'any'
*/
type ConvexCtx = QueryCtx | MutationCtx | ActionCtx;
/**
* Função helper para converter contexto do Convex para GenericCtx do better-auth
* Os tipos são estruturalmente compatíveis, apenas há diferença nas definições de tipo
*/
export function toGenericCtx(ctx: ConvexCtx): GenericCtx<DataModel> {
// Os tipos são estruturalmente idênticos, apenas há diferença nas definições de tipo
// entre a versão do Convex usada pelo projeto e a usada pelo @convex-dev/better-auth
return ctx as unknown as GenericCtx<DataModel>;
}
export const createAuth = (
ctx: GenericCtx<DataModel>,
{ optionsOnly } = { optionsOnly: false }
) => {
return betterAuth({
// disable logging when createAuth is called just to generate options.
// this is not required, but there's a lot of noise in logs without it.
logger: {
disabled: optionsOnly
},
trustedOrigins: ['https://vite.kilder.dev', 'http://localhost:5173', 'http://127.0.0.1:5173'],
baseURL: siteUrl,
database: authComponent.adapter(ctx),
// Configure simple, non-verified email/password to get started
emailAndPassword: {
enabled: true,
requireEmailVerification: false
},
plugins: [
// The Convex plugin is required for Convex compatibility
convex()
]
});
};
// Example function for getting the current user
// Feel free to edit, omit, etc.
export const getCurrentUser = query({
args: {},
handler: async (ctx) => {
try {
const authUser = await authComponent.safeGetAuthUser(toGenericCtx(ctx));
if (!authUser) {
return;
}
const user = await ctx.db
.query('usuarios')
.withIndex('authId', (q) => q.eq('authId', authUser._id))
.unique();
if (!user) {
return;
}
// Buscar foto de perfil e role em paralelo para melhor performance
const [fotoPerfilUrl, role] = await Promise.all([
user.fotoPerfil
? ctx.storage.getUrl(user.fotoPerfil).catch(() => null)
: Promise.resolve(null),
user.roleId
? ctx.db
.query('roles')
.withIndex('by_id', (q) => q.eq('_id', user.roleId))
.unique()
.catch(() => null)
: Promise.resolve(null)
]);
return { ...user, role: role || null, fotoPerfilUrl };
} catch (error) {
// Log do erro mas não falhar completamente - retornar null para permitir retry
console.error('Erro ao buscar usuário atual:', error);
return;
}
}
});
export const getCurrentUserFunction = async (ctx: QueryCtx | MutationCtx) => {
const authUser = await authComponent.safeGetAuthUser(toGenericCtx(ctx));
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, toGenericCtx(ctx));
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, toGenericCtx(ctx));
await auth.api.changePassword({
headers,
body: {
currentPassword: args.currentPassword,
newPassword: args.newPassword
}
});
};