Files
sori.studio/server/api/auth/password-reset/confirm.post.js

66 lines
1.8 KiB
JavaScript

import bcrypt from 'bcrypt'
import { z } from 'zod'
import { createError, readBody } from 'h3'
import { updateMemberPasswordByEmail } from '../../../repositories/member-repository'
import { verifyAndConsumeEmailOtp } from '../../../repositories/email-otp-repository'
const bodySchema = z.object({
email: z.string().trim().email(),
code: z.string().regex(/^\d{6}$/),
newPassword: z.string().min(8).max(32)
})
/**
* 이메일 OTP로 비밀번호를 재설정한다.
* @param {import('h3').H3Event} event - 요청 이벤트
* @returns {Promise<{ ok: boolean }>}
*/
export default defineEventHandler(async (event) => {
const parsed = bodySchema.safeParse(await readBody(event))
if (!parsed.success) {
throw createError({
statusCode: 400,
message: '요청 형식이 올바르지 않습니다.'
})
}
const config = useRuntimeConfig()
const pepper = String(config.emailOtpPepper || config.memberSessionSecret || '').trim()
if (!pepper) {
throw createError({
statusCode: 500,
message: '서버 인증 설정이 올바르지 않습니다.'
})
}
const email = parsed.data.email.trim().toLowerCase()
const verify = await verifyAndConsumeEmailOtp({
email,
purpose: 'password_reset',
code: parsed.data.code,
pepper
})
if (!verify.ok) {
throw createError({
statusCode: 400,
message: '인증번호가 올바르지 않거나 만료되었습니다.'
})
}
const nextHash = await bcrypt.hash(parsed.data.newPassword, 12)
const updated = await updateMemberPasswordByEmail({
email,
passwordHash: nextHash
})
if (!updated) {
throw createError({
statusCode: 400,
message: '계정을 찾을 수 없습니다. 다시 인증번호를 요청해 주세요.'
})
}
return { ok: true }
})