Kakao·LINE 이메일 선택 수집 — 동의한 이메일만 저장 · 이메일 없이 제공자 식별값으로 가입·로그인
This commit is contained in:
@@ -18,13 +18,18 @@ import { ERR, fail } from '../core/errors.js';
|
||||
*/
|
||||
export const fnAuthOnCreate = functionsV1.auth.user().onCreate(async (user) => {
|
||||
const db = getFirestore();
|
||||
const batch = db.batch();
|
||||
|
||||
batch.set(db.doc(`users/${user.uid}`), {
|
||||
const userRef = db.doc(`users/${user.uid}`);
|
||||
const walletRef = db.doc(`users/${user.uid}/wallet/main`);
|
||||
// 이메일 — 플랫폼이 이메일 제공을 승인하고 사용자가 동의한 경우에만 수집한다. 없어도 가입·로그인된다.
|
||||
// Auth 계정 이메일이 비면(OIDC 등) 제공자 정보의 이메일을 쓴다. 카카오(커스텀 토큰)는
|
||||
// 계정을 만든 뒤에 연결 정보가 붙으므로 `fnAuthKakao` 가 따로 쓴다.
|
||||
const email = user.email ?? user.providerData.find((p) => p.email)?.email ?? null;
|
||||
const defaults: Record<string, unknown> = {
|
||||
uid: user.uid,
|
||||
displayName: user.displayName ?? '팬우주 여행자',
|
||||
photoUrl: user.photoURL ?? null,
|
||||
countryCode: 'KR',
|
||||
email,
|
||||
locale: 'ko',
|
||||
fandomId: '',
|
||||
authProviders: user.providerData.map((p) => p.providerId),
|
||||
@@ -34,18 +39,25 @@ export const fnAuthOnCreate = functionsV1.auth.user().onCreate(async (user) => {
|
||||
notificationPrefs: {},
|
||||
createdAt: FieldValue.serverTimestamp(),
|
||||
updatedAt: FieldValue.serverTimestamp(),
|
||||
}, { merge: true });
|
||||
|
||||
batch.set(db.doc(`users/${user.uid}/wallet/main`), {
|
||||
};
|
||||
const wallet: Record<string, unknown> = {
|
||||
activityBalance: 0,
|
||||
supportBalance: 0,
|
||||
pendingActivity: 0,
|
||||
reconcileStatus: 'ok',
|
||||
createdAt: FieldValue.serverTimestamp(),
|
||||
updatedAt: FieldValue.serverTimestamp(),
|
||||
}, { merge: true });
|
||||
};
|
||||
|
||||
await batch.commit();
|
||||
// 트리거가 늦게 돌면 그 사이 서버가 쓴 값(카카오 동의 이메일 등)이 이미 있다.
|
||||
// 없는 필드만 채운다 — 덮어쓰면 먼저 쓴 값이 기본값으로 되돌아간다.
|
||||
await db.runTransaction(async (tx) => {
|
||||
const [u, w] = await Promise.all([tx.get(userRef), tx.get(walletRef)]);
|
||||
const missing = (base: Record<string, unknown>, have: Record<string, unknown> | undefined) =>
|
||||
Object.fromEntries(Object.entries(base).filter(([k]) => have?.[k] == null));
|
||||
tx.set(userRef, missing(defaults, u.data()), { merge: true });
|
||||
tx.set(walletRef, missing(wallet, w.data()), { merge: true });
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getAuth } from 'firebase-admin/auth';
|
||||
import { getFirestore } from 'firebase-admin/firestore';
|
||||
import { defineString } from 'firebase-functions/params';
|
||||
import { onCall } from 'firebase-functions/v2/https';
|
||||
|
||||
@@ -139,5 +140,10 @@ export const fnAuthKakao = onCall(async (req) => {
|
||||
return { linked: true };
|
||||
}
|
||||
const uid = await resolveKakaoUid(profile);
|
||||
// 동의한 이메일만 사용자 문서에 남긴다. Auth 이메일은 다른 제공자 계정과 겹치면 생성이
|
||||
// 거부되므로 쓰지 않는다. 이메일이 없어도 로그인은 그대로 된다.
|
||||
if (profile.email) {
|
||||
await getFirestore().doc(`users/${uid}`).set({ email: profile.email }, { merge: true });
|
||||
}
|
||||
return { token: await getAuth().createCustomToken(uid) };
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user