feat: Auth.js v5 인증 시스템 구현 - 카카오 소셜 + 이메일/비번 로그인

- Auth.js v5 (next-auth beta.30) + 커스텀 Prisma 어댑터 (BigInt PK 호환)
- 카카오 OAuth2 소셜 로그인 (PKCE 비활성화, placeholder 이메일 처리)
- 이메일/비밀번호 자격증명 로그인 (argon2 해시)
- JWT 세션 전략 + 커스텀 클레임 (publicId, primaryRole, userStatus)
- 역할 기반 미들웨어 (/admin 운영자 전용, /auth 비인증 전용)
- Prisma 스키마: Account, VerificationToken, InviteToken 모델 추가
- ConsentType enum 7개로 업데이트
- 로그인/회원가입/프로필완성/403 페이지 구현
- 운영자 초대 시스템 (admin/settings/invite)
This commit is contained in:
Johngreen
2026-03-08 12:59:21 +09:00
parent 636eaaca23
commit 5dea44046d
22 changed files with 1912 additions and 19 deletions
+57 -4
View File
@@ -64,11 +64,13 @@ enum ProfileType {
}
enum ConsentType {
PRIVACY_POLICY
TERMS_OF_SERVICE
MARKETING
THIRD_PARTY_SHARING
INFORMATION_DISCLOSURE
PRIVACY_POLICY_REQUIRED
PRIVACY_POLICY_MARKETING
STORE_PUBLICATION_CONSENT
MATCHED_INFO_DISCLOSURE
THIRD_PARTY_MATCHED_PARTY
NOTIFICATION_KAKAO
@@map("consent_type")
}
@@ -378,6 +380,8 @@ model User {
phone String? @map("phone")
phoneNormalized String? @map("phone_normalized")
name String @map("name")
passwordHash String? @map("password_hash")
image String? @map("image")
primaryRole UserRole @map("primary_role")
status UserStatus @default(PENDING_VERIFICATION) @map("status")
emailVerifiedAt DateTime? @map("email_verified_at") @db.Timestamptz(6)
@@ -412,6 +416,8 @@ model User {
disputesResolvedBy DisputeCase[] @relation("DisputeResolvedByUser")
certificationReviewedBy VendorCertification[] @relation("CertificationReviewedByUser")
auditLogs AuditLog[]
accounts Account[]
invitesCreated InviteToken[] @relation("InviteCreator")
@@index([primaryRole, status], map: "idx_user_role_status")
@@map("users")
@@ -452,6 +458,53 @@ model UserConsent {
@@map("user_consents")
}
/// Auth.js 소셜 로그인 계정 연동
model Account {
id BigInt @id @default(autoincrement()) @map("id")
userId BigInt @map("user_id")
type String @map("type")
provider String @map("provider")
providerAccountId String @map("provider_account_id")
refresh_token String? @map("refresh_token") @db.Text
access_token String? @map("access_token") @db.Text
expires_at Int? @map("expires_at")
token_type String? @map("token_type")
scope String? @map("scope")
id_token String? @map("id_token") @db.Text
session_state String? @map("session_state")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
@@map("accounts")
}
/// 이메일 인증 토큰
model VerificationToken {
identifier String @map("identifier")
token String @unique @map("token")
expires DateTime @map("expires") @db.Timestamptz(6)
@@id([identifier, token])
@@map("verification_tokens")
}
/// 운영자 초대 토큰
model InviteToken {
id BigInt @id @default(autoincrement()) @map("id")
email String @map("email")
role UserRole @map("role")
token String @unique @default(cuid()) @map("token")
expires DateTime @map("expires") @db.Timestamptz(6)
usedAt DateTime? @map("used_at") @db.Timestamptz(6)
createdBy BigInt @map("created_by")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
creator User @relation("InviteCreator", fields: [createdBy], references: [id])
@@map("invite_tokens")
}
// =============================================================================
// 2. 매장 공급 도메인
// =============================================================================