가입 시 약관 동의 단계 추가 — 필수·선택 항목 구분 · 개인정보처리방침 고지 · 동의 기록 저장
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import * as functionsV1 from 'firebase-functions/v1';
|
||||
import { onCall } from 'firebase-functions/v2/https';
|
||||
import { HttpsError, onCall } from 'firebase-functions/v2/https';
|
||||
import { getFirestore, FieldValue } from 'firebase-admin/firestore';
|
||||
import { getAuth } from 'firebase-admin/auth';
|
||||
|
||||
@@ -85,6 +85,57 @@ export const fnConsentRecord = onCall(async (req) => {
|
||||
return { consentId };
|
||||
});
|
||||
|
||||
/**
|
||||
* 가입 약관 필수 항목 — 서비스 이용약관 · 개인정보 수집·이용 동의 · 만 14세 이상 확인.
|
||||
*
|
||||
* 위치기반 서비스 약관은 가입 때 받지 않는다(위치기능을 처음 쓰기 직전에 따로 받는다).
|
||||
* 개인정보처리방침은 동의 항목이 아니라 고지(링크)라 기록하지 않는다. 마케팅 수신은 선택 · 기본 OFF.
|
||||
*/
|
||||
export const SIGNUP_REQUIRED_TERMS = ['service', 'privacy', 'age'] as const;
|
||||
|
||||
/**
|
||||
* `fn-signup-consent` — 가입 약관 동의를 한 번에 기록한다.
|
||||
*
|
||||
* 항목마다 `consents` 에 append-only 행을 남기고(약관 버전 포함), 사용자 문서에 `termsAgreedAt` 을
|
||||
* 찍는다. 앱은 이 값이 없으면 로그인 직후 약관 화면으로 보낸다 — 가입 · 로그인 버튼이나 제공자와
|
||||
* 무관하게 약관 · 만 14세 확인 없이 지나가지 못하게 하는 기준이다.
|
||||
* 마케팅 동의는 알림 설정의 마케팅 구분(`notificationPrefs.marketing`)과 같은 값으로 둔다.
|
||||
*/
|
||||
export const fnSignupConsent = onCall(async (req) => {
|
||||
const uid = callerUid(req);
|
||||
const version = str(req.data, 'version');
|
||||
const raw = (req.data as Record<string, unknown>)?.agreed;
|
||||
const agreed = new Set(Array.isArray(raw) ? raw.filter((v): v is string => typeof v === 'string') : []);
|
||||
if (!SIGNUP_REQUIRED_TERMS.every((t) => agreed.has(t))) {
|
||||
throw new HttpsError('invalid-argument', '필수 약관에 모두 동의해 주세요.');
|
||||
}
|
||||
const marketing = agreed.has('marketing');
|
||||
const db = getFirestore();
|
||||
const batch = db.batch();
|
||||
const meta = {
|
||||
docVersion: version,
|
||||
ip: req.rawRequest?.ip ?? null,
|
||||
userAgent: req.rawRequest?.headers?.['user-agent'] ?? null,
|
||||
createdAt: FieldValue.serverTimestamp(),
|
||||
};
|
||||
for (const docType of [...SIGNUP_REQUIRED_TERMS, 'marketing']) {
|
||||
const granted = docType === 'marketing' ? marketing : true;
|
||||
batch.set(db.doc(`users/${uid}/consents/${docType}_${version}_${granted ? 'grant' : 'revoke'}`),
|
||||
{ docType, granted, ...meta }, { merge: true });
|
||||
}
|
||||
batch.set(db.doc(`users/${uid}`), {
|
||||
termsAgreedAt: FieldValue.serverTimestamp(),
|
||||
termsVersion: version,
|
||||
notificationPrefs: { marketing },
|
||||
consentState: {
|
||||
marketing: { granted: marketing, version, at: FieldValue.serverTimestamp() },
|
||||
},
|
||||
updatedAt: FieldValue.serverTimestamp(),
|
||||
}, { merge: true });
|
||||
await batch.commit();
|
||||
return { termsVersion: version, marketing };
|
||||
});
|
||||
|
||||
/**
|
||||
* `fn-admin-set-role` — 마스터 전용. 운영자 등급을 커스텀 클레임으로 심는다.
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import './core/bootstrap.js';
|
||||
|
||||
export {
|
||||
fnAuthOnCreate, fnConsentRecord, fnAdminSetRole,
|
||||
fnAuthOnCreate, fnConsentRecord, fnSignupConsent, fnAdminSetRole,
|
||||
} from './account/account.js';
|
||||
export { fnAuthKakao } from './account/kakao.js';
|
||||
export { fnAuthLine } from './account/line.js';
|
||||
|
||||
Reference in New Issue
Block a user