diff --git a/services/mana-core-auth/src/auth/auth.module.ts b/services/mana-core-auth/src/auth/auth.module.ts index fc5242d95..7c59135f5 100644 --- a/services/mana-core-auth/src/auth/auth.module.ts +++ b/services/mana-core-auth/src/auth/auth.module.ts @@ -1,11 +1,12 @@ import { Module, forwardRef } from '@nestjs/common'; import { AuthController } from './auth.controller'; +import { BetterAuthPassthroughController } from './better-auth-passthrough.controller'; import { BetterAuthService } from './services/better-auth.service'; import { ReferralsModule } from '../referrals/referrals.module'; @Module({ imports: [forwardRef(() => ReferralsModule)], - controllers: [AuthController], + controllers: [AuthController, BetterAuthPassthroughController], providers: [BetterAuthService], exports: [BetterAuthService], }) diff --git a/services/mana-core-auth/src/auth/better-auth-passthrough.controller.ts b/services/mana-core-auth/src/auth/better-auth-passthrough.controller.ts new file mode 100644 index 000000000..0202a2cba --- /dev/null +++ b/services/mana-core-auth/src/auth/better-auth-passthrough.controller.ts @@ -0,0 +1,56 @@ +/** + * Better Auth Passthrough Controller + * + * This controller handles Better Auth's native routes that are generated + * with the `/api/auth/*` prefix (without the NestJS `/api/v1` prefix). + * + * Routes handled: + * - GET /api/auth/verify-email - Email verification from verification emails + * + * This is necessary because Better Auth generates URLs with `/api/auth/*` + * but our NestJS API uses `/api/v1/*` as the global prefix. + */ + +import { Controller, Get, Query, Res, HttpStatus } from '@nestjs/common'; +import { Response } from 'express'; +import { BetterAuthService } from './services/better-auth.service'; + +@Controller('api/auth') +export class BetterAuthPassthroughController { + constructor(private readonly betterAuthService: BetterAuthService) {} + + /** + * Handle email verification + * + * Better Auth sends verification emails with links to: + * {baseURL}/api/auth/verify-email?token=... + * + * This endpoint calls Better Auth's verifyEmail API and redirects + * the user to the appropriate page. + */ + @Get('verify-email') + async verifyEmail(@Query('token') token: string, @Res() res: Response) { + try { + if (!token) { + return res.redirect('/verification-failed?error=missing_token'); + } + + // Call Better Auth's verifyEmail API + const result = await this.betterAuthService.verifyEmail(token); + + if (result.success) { + // Redirect to success page (frontend should handle this) + const frontendUrl = process.env.FRONTEND_URL || 'https://mana.how'; + return res.redirect(`${frontendUrl}/email-verified`); + } else { + // Redirect to error page + const frontendUrl = process.env.FRONTEND_URL || 'https://mana.how'; + return res.redirect(`${frontendUrl}/verification-failed?error=${result.error}`); + } + } catch (error) { + console.error('[verify-email] Error:', error); + const frontendUrl = process.env.FRONTEND_URL || 'https://mana.how'; + return res.redirect(`${frontendUrl}/verification-failed?error=verification_failed`); + } + } +} diff --git a/services/mana-core-auth/src/auth/services/better-auth.service.ts b/services/mana-core-auth/src/auth/services/better-auth.service.ts index 3d87d70f1..d6ab946be 100644 --- a/services/mana-core-auth/src/auth/services/better-auth.service.ts +++ b/services/mana-core-auth/src/auth/services/better-auth.service.ts @@ -938,6 +938,48 @@ export class BetterAuthService { } } + /** + * Verify email with token + * + * Verifies the user's email using the token from the verification email. + * Uses Better Auth's verifyEmail API. + * + * @param token - Verification token from email link + * @returns Success status + */ + async verifyEmail(token: string): Promise<{ success: boolean; error?: string }> { + try { + // Better Auth's verifyEmail method + // See: https://www.better-auth.com/docs/authentication/email-verification + const api = this.auth.api as any; + + const result = await api.verifyEmail({ + query: { token }, + }); + + console.log('[verifyEmail] Result:', result); + + return { + success: true, + }; + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + console.error('[verifyEmail] Error:', errorMessage); + + if (errorMessage.includes('invalid') || errorMessage.includes('expired')) { + return { + success: false, + error: 'invalid_or_expired_token', + }; + } + + return { + success: false, + error: 'verification_failed', + }; + } + } + /** * Get JWKS (JSON Web Key Set) * diff --git a/services/mana-core-auth/src/main.ts b/services/mana-core-auth/src/main.ts index 353e9d9a3..f0c5f2680 100644 --- a/services/mana-core-auth/src/main.ts +++ b/services/mana-core-auth/src/main.ts @@ -79,9 +79,10 @@ async function bootstrap() { }) ); - // Global prefix (exclude metrics endpoint) + // Global prefix (exclude metrics, health, and Better Auth native routes) + // Better Auth generates verification URLs with /api/auth/* prefix app.setGlobalPrefix('api/v1', { - exclude: ['metrics', 'health'], + exclude: ['metrics', 'health', 'api/auth/(.*)'], }); const port = configService.get('port') || 3001;