v0.1.43 - 인증 강제와 회원 탈퇴 흐름 정리
This commit is contained in:
@@ -61,5 +61,10 @@ export async function findAuthenticatedUser(request) {
|
||||
.where(eq(users.id, session.userId))
|
||||
.limit(1)
|
||||
|
||||
if (user && user.role !== 'admin' && !user.emailVerifiedAt) {
|
||||
await db.delete(authSessions).where(eq(authSessions.id, session.id))
|
||||
return null
|
||||
}
|
||||
|
||||
return user ?? null
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@ const passwordSchema = z.object({
|
||||
newPassword: z.string().min(8).max(72),
|
||||
})
|
||||
|
||||
const deleteAccountSchema = z.object({
|
||||
currentPassword: z.string().min(1).max(72),
|
||||
})
|
||||
|
||||
const verificationRequestSchema = z.object({
|
||||
email: z.string().trim().email().optional(),
|
||||
})
|
||||
@@ -110,7 +114,11 @@ function sanitizeUser(user) {
|
||||
}
|
||||
|
||||
function withPreviewUrl(payload, key, previewUrl) {
|
||||
if (!env.AUTH_PREVIEW_LINKS) {
|
||||
const allowPreviewLinks =
|
||||
env.AUTH_PREVIEW_LINKS &&
|
||||
/localhost|127\.0\.0\.1/i.test(env.APP_BASE_URL)
|
||||
|
||||
if (!allowPreviewLinks) {
|
||||
return payload
|
||||
}
|
||||
|
||||
@@ -175,13 +183,12 @@ export async function registerAuthRoutes(app) {
|
||||
nickname,
|
||||
role: 'user',
|
||||
emailVerifiedAt: null,
|
||||
lastLoginAt: now,
|
||||
lastLoginAt: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
})
|
||||
.returning()
|
||||
|
||||
const { token } = await createSession(user.id)
|
||||
const verification = await createEmailVerificationToken(user.id)
|
||||
await sendVerificationEmail({
|
||||
to: user.email,
|
||||
@@ -189,9 +196,8 @@ export async function registerAuthRoutes(app) {
|
||||
})
|
||||
|
||||
return reply.code(201).send(withPreviewUrl({
|
||||
message: '회원가입이 완료되었습니다.',
|
||||
token,
|
||||
user: sanitizeUser(user),
|
||||
message: '회원가입이 완료되었습니다. 이메일 인증 후 로그인해 주세요.',
|
||||
requiresEmailVerification: true,
|
||||
}, 'verificationPreviewUrl', verification.previewUrl))
|
||||
})
|
||||
|
||||
@@ -233,6 +239,12 @@ export async function registerAuthRoutes(app) {
|
||||
})
|
||||
}
|
||||
|
||||
if (user.role !== 'admin' && !user.emailVerifiedAt) {
|
||||
return reply.code(403).send({
|
||||
message: '이메일 인증을 완료한 뒤 로그인해 주세요.',
|
||||
})
|
||||
}
|
||||
|
||||
const now = new Date()
|
||||
|
||||
const [updatedUser] = await db
|
||||
@@ -364,6 +376,46 @@ export async function registerAuthRoutes(app) {
|
||||
}
|
||||
})
|
||||
|
||||
app.delete('/api/auth/account', async (request, reply) => {
|
||||
const user = await findAuthenticatedUser(request)
|
||||
|
||||
if (!user) {
|
||||
return reply.code(401).send({
|
||||
message: '인증이 필요합니다.',
|
||||
})
|
||||
}
|
||||
|
||||
if (user.role === 'admin') {
|
||||
return reply.code(403).send({
|
||||
message: '기본 관리자 계정은 여기서 삭제할 수 없습니다.',
|
||||
})
|
||||
}
|
||||
|
||||
const payload = deleteAccountSchema.safeParse(request.body)
|
||||
|
||||
if (!payload.success) {
|
||||
return reply.code(400).send({
|
||||
message: '회원 탈퇴 확인 비밀번호를 입력해 주세요.',
|
||||
issues: payload.error.flatten(),
|
||||
})
|
||||
}
|
||||
|
||||
const passwordMatches = await verifyPassword(payload.data.currentPassword, user.passwordHash)
|
||||
|
||||
if (!passwordMatches) {
|
||||
return reply.code(401).send({
|
||||
message: '현재 비밀번호가 올바르지 않습니다.',
|
||||
})
|
||||
}
|
||||
|
||||
await db.delete(authSessions).where(eq(authSessions.userId, user.id))
|
||||
await db.delete(users).where(eq(users.id, user.id))
|
||||
|
||||
return {
|
||||
message: '회원 탈퇴가 완료되었습니다.',
|
||||
}
|
||||
})
|
||||
|
||||
app.post('/api/auth/verification/request', async (request, reply) => {
|
||||
const authenticatedUser = await findAuthenticatedUser(request)
|
||||
const payload = verificationRequestSchema.safeParse(request.body ?? {})
|
||||
|
||||
Reference in New Issue
Block a user