@spfn/auth 0.2.0-beta.83 → 0.2.0-beta.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -268,10 +268,13 @@ no Google account is linked or no refresh token is available).
268
268
  Kakao's `is_email_valid` and `is_email_verified` claims are both required before its email can
269
269
  link an existing SPFN account. GitHub uses the primary email from `/user/emails` (needs the
270
270
  `user:email` scope) and treats it as verified only when GitHub marks it verified; without that
271
- scope it falls back to the public profile email, unverified. Naver provides an email address but no verified-email claim, so
272
- Naver login never links an existing account by email; new Naver users can verify and add their
273
- email through the application's normal onboarding flow. Until then, the OAuth account is identified
274
- by its provider and provider user ID, so its user row may have both email and phone unset.
271
+ scope it falls back to the public profile email, unverified. Naver's profile email is either the
272
+ Naver account email or a contact email that passed Naver's own verification, so a present email
273
+ is treated as verified it is stored on the user row and may link an existing account by email,
274
+ the same trust level as Kakao. Accounts created before this policy (user row with `email` null)
275
+ are backfilled on their next login: if the provider reports a verified email and no other account
276
+ owns it, `email` and `emailVerifiedAt` are filled in (best-effort; a conflict skips the backfill
277
+ and the login continues).
275
278
 
276
279
  ### Provider-initiated unlink notifications (`unlink-notify`)
277
280
 
package/dist/server.js CHANGED
@@ -10097,8 +10097,9 @@ var naverProvider = {
10097
10097
  return {
10098
10098
  providerUserId: profile.id,
10099
10099
  email: typeof profile.email === "string" ? profile.email : null,
10100
- // Naver returns an email address but no independently verified-email claim.
10101
- emailVerified: false,
10100
+ // 네이버 프로필 이메일은 네이버 계정 이메일이거나 인증 절차를 거친 연락처
10101
+ // 이메일이다 — 존재하면 검증된 것으로 취급한다(카카오와 같은 신뢰 수준).
10102
+ emailVerified: typeof profile.email === "string",
10102
10103
  name: typeof profile.name === "string" ? profile.name : typeof profile.nickname === "string" ? profile.nickname : void 0,
10103
10104
  avatar: typeof profile.profile_image === "string" ? profile.profile_image : void 0
10104
10105
  };
@@ -10223,6 +10224,7 @@ async function oauthCallbackService(params) {
10223
10224
  refreshToken: tokens.refreshToken ?? existingSocialAccount.refreshToken,
10224
10225
  tokenExpiresAt: tokenExpiryDate(tokens.expiresIn)
10225
10226
  });
10227
+ await backfillVerifiedEmail(userId, identity);
10226
10228
  } else {
10227
10229
  const result = await createOrLinkUser(provider, identity, tokens, stateData.metadata);
10228
10230
  userId = result.userId;
@@ -10280,6 +10282,30 @@ async function assertActiveForOAuthSession(userId) {
10280
10282
  }
10281
10283
  throw new AccountDisabledError2({ status: user.status });
10282
10284
  }
10285
+ async function backfillVerifiedEmail(userId, identity) {
10286
+ if (!identity.email || !identity.emailVerified) {
10287
+ return;
10288
+ }
10289
+ const user = await usersRepository.findById(userId);
10290
+ if (!user || user.email) {
10291
+ return;
10292
+ }
10293
+ const emailOwner = await usersRepository.findByEmail(identity.email);
10294
+ if (emailOwner && emailOwner.id !== userId) {
10295
+ return;
10296
+ }
10297
+ try {
10298
+ await usersRepository.updateById(userId, {
10299
+ email: identity.email,
10300
+ emailVerifiedAt: /* @__PURE__ */ new Date()
10301
+ });
10302
+ } catch (error) {
10303
+ authLogger.service.warn("Verified-email backfill failed; continuing login", {
10304
+ userId: String(userId),
10305
+ error: error instanceof Error ? error.message : String(error)
10306
+ });
10307
+ }
10308
+ }
10283
10309
  async function createOrLinkUser(provider, identity, tokens, metadata) {
10284
10310
  const existingUser = identity.email ? await usersRepository.findByEmail(identity.email) : null;
10285
10311
  let userId;
@@ -10425,6 +10451,7 @@ async function persistNativeLogin(identity, params) {
10425
10451
  let isNewUser = false;
10426
10452
  if (existing) {
10427
10453
  userId = existing.userId;
10454
+ await backfillVerifiedEmail(userId, identity);
10428
10455
  } else {
10429
10456
  const result = await createOrLinkUser(params.provider, identity, void 0, params.metadata);
10430
10457
  userId = result.userId;