From 8abad1a242dbd002b75ab6dede9f1917121d64c8 Mon Sep 17 00:00:00 2001 From: Hammer Date: Wed, 28 Jan 2026 21:55:39 +0000 Subject: [PATCH] feat: add email and password change endpoints to profile routes --- src/routes/profile.ts | 76 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/src/routes/profile.ts b/src/routes/profile.ts index b5fb8f0..6d672ec 100644 --- a/src/routes/profile.ts +++ b/src/routes/profile.ts @@ -1,7 +1,7 @@ import { Elysia, t } from 'elysia'; import { db } from '../db'; -import { users, userProfiles } from '../db/schema'; -import { eq } from 'drizzle-orm'; +import { users, userProfiles, accounts } from '../db/schema'; +import { eq, and } from 'drizzle-orm'; import type { User } from '../lib/auth'; export const profileRoutes = new Elysia({ prefix: '/profile' }) @@ -96,4 +96,76 @@ export const profileRoutes = new Elysia({ prefix: '/profile' }) phone: t.Optional(t.String()), emailSignature: t.Optional(t.String()), }), + }) + + // Change email + .put('/email', async ({ body, user, set }: { + body: { newEmail: string }; + user: User; + set: any; + }) => { + // Check if email is already taken + const [existing] = await db.select({ id: users.id }) + .from(users) + .where(eq(users.email, body.newEmail)) + .limit(1); + + if (existing && existing.id !== user.id) { + set.status = 400; + throw new Error('Email is already in use'); + } + + await db.update(users) + .set({ email: body.newEmail, updatedAt: new Date() }) + .where(eq(users.id, user.id)); + + return { success: true, email: body.newEmail }; + }, { + body: t.Object({ + newEmail: t.String({ format: 'email' }), + }), + }) + + // Change password + .put('/password', async ({ body, user, set }: { + body: { currentPassword: string; newPassword: string }; + user: User; + set: any; + }) => { + // Get current password hash from accounts table + const [account] = await db.select({ password: accounts.password }) + .from(accounts) + .where(and( + eq(accounts.userId, user.id), + eq(accounts.providerId, 'credential'), + )) + .limit(1); + + if (!account?.password) { + set.status = 400; + throw new Error('No password set for this account'); + } + + // Verify current password + const isValid = await Bun.password.verify(body.currentPassword, account.password); + if (!isValid) { + set.status = 400; + throw new Error('Current password is incorrect'); + } + + // Hash and update new password + const hashedPassword = await Bun.password.hash(body.newPassword, { algorithm: 'bcrypt', cost: 10 }); + await db.update(accounts) + .set({ password: hashedPassword, updatedAt: new Date() }) + .where(and( + eq(accounts.userId, user.id), + eq(accounts.providerId, 'credential'), + )); + + return { success: true }; + }, { + body: t.Object({ + currentPassword: t.String(), + newPassword: t.String({ minLength: 8 }), + }), });