feat(auth): phase 3 — replace Supabase GoTrue with bcryptjs+jose
Custom auth stack: bcryptjs password hashing (cost 10, GoTrue-compatible), jose JWT session cookies (edge-safe, 8hr TTL), new API routes for login/logout/reset/change-password, middleware rewritten to JWT-only verification with no DB access. All 38 protected pages and API routes migrated from supabase.auth.getUser() to getSession(). Supabase .from() queries retained for Phase 4. lib/db/index.ts refactored to lazy Proxy singleton to avoid module-level throw during Next.js build. tsc: clean, build: clean, tests: 4/4 passed Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
interface SendEmailParams {
|
||||
to: string
|
||||
subject: string
|
||||
html: string
|
||||
}
|
||||
|
||||
async function sendEmail({ to, subject, html }: SendEmailParams): Promise<void> {
|
||||
const apiKey = process.env.BREVO_API_KEY
|
||||
if (!apiKey) throw new Error('BREVO_API_KEY not configured')
|
||||
|
||||
const from = process.env.BREVO_FROM_EMAIL ?? 'noreply@setia.com.my'
|
||||
|
||||
const res = await fetch('https://api.brevo.com/v3/smtp/email', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'api-key': apiKey,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
sender: { email: from },
|
||||
to: [{ email: to }],
|
||||
subject,
|
||||
htmlContent: html,
|
||||
}),
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text()
|
||||
throw new Error(`Brevo API error ${res.status}: ${text}`)
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendPasswordResetEmail({
|
||||
to,
|
||||
name,
|
||||
resetLink,
|
||||
}: {
|
||||
to: string
|
||||
name: string
|
||||
resetLink: string
|
||||
}): Promise<void> {
|
||||
await sendEmail({
|
||||
to,
|
||||
subject: 'IMS — Reset your password',
|
||||
html: `
|
||||
<p>Hi ${name},</p>
|
||||
<p>Click the link below to reset your IMS password. The link expires in 1 hour.</p>
|
||||
<p><a href="${resetLink}">${resetLink}</a></p>
|
||||
<p>If you did not request a password reset, ignore this email.</p>
|
||||
`,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user