Cloud Functions 서버 기반과 앱 서버 연동 계층 — 계정 생성 트리거 · 약관 동의 기록 · 운영자 권한 · Admin 쓰기 관문 · Firestore·Functions 클라이언트 · 오류 코드 전달
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import { onCall } from 'firebase-functions/v2/https';
|
||||
import { getFirestore, FieldValue } from 'firebase-admin/firestore';
|
||||
|
||||
import { callerUid, requireRole, str } from '../core/callable.js';
|
||||
import { ERR, fail } from '../core/errors.js';
|
||||
|
||||
/**
|
||||
* `fn-admin-write` — 모든 Admin 쓰기의 단일 관문(§7.5).
|
||||
*
|
||||
* Admin 콘솔이 Firestore 를 직접 쓰지 않고 여기를 지나는 이유는 로그 때문이다.
|
||||
* 보안 규칙은 거부만 할 수 있고 로그를 남기지 못한다. 누가 무엇을 왜 바꿨는지는
|
||||
* 여기서만 남길 수 있고, rbac-summary 가 요구하는 작업 로그가 그것이다.
|
||||
*/
|
||||
|
||||
/** 원장·지갑은 이 경로로 못 쓴다. 재화는 반드시 원장 함수를 지나야 한다. */
|
||||
const FORBIDDEN = [
|
||||
/popTransactions/, /popLots/, /\/wallet\//, /fpiSnapshots/,
|
||||
];
|
||||
|
||||
export const fnAdminWrite = onCall(async (req) => {
|
||||
const actorUid = callerUid(req);
|
||||
const role = requireRole(req, ['master', 'ops']);
|
||||
const targetPath = str(req.data, 'targetPath');
|
||||
const reason = str(req.data, 'reason');
|
||||
const patch = (req.data as Record<string, unknown>)?.patch;
|
||||
|
||||
if (typeof patch !== 'object' || patch === null || Array.isArray(patch)) {
|
||||
fail(ERR.permissionDenied, '변경 내용이 올바르지 않습니다.');
|
||||
}
|
||||
if (FORBIDDEN.some((re) => re.test(targetPath))) {
|
||||
// 관리자라도 잔액을 직접 고칠 수 없다. 별첨1 ② 「원장 정합성」의 마지막 방벽이다.
|
||||
fail(ERR.permissionDenied, '재화 관련 문서는 원장 함수로만 변경할 수 있습니다.');
|
||||
}
|
||||
// 경로 깊이가 짝수여야 문서다. 홀수면 컬렉션이라 통째로 덮어쓸 위험이 있다.
|
||||
const segments = targetPath.split('/').filter(Boolean);
|
||||
if (segments.length === 0 || segments.length % 2 !== 0) {
|
||||
fail(ERR.permissionDenied, '문서 경로가 아닙니다.');
|
||||
}
|
||||
|
||||
const db = getFirestore();
|
||||
const ref = db.doc(targetPath);
|
||||
const before = await ref.get();
|
||||
|
||||
await ref.set({
|
||||
...(patch as Record<string, unknown>),
|
||||
updatedAt: FieldValue.serverTimestamp(),
|
||||
}, { merge: true });
|
||||
|
||||
const log = await db.collection('adminActionLogs').add({
|
||||
actorUid,
|
||||
role,
|
||||
targetPath,
|
||||
reason,
|
||||
patch,
|
||||
existedBefore: before.exists,
|
||||
createdAt: FieldValue.serverTimestamp(),
|
||||
});
|
||||
|
||||
return { logId: log.id, targetPath };
|
||||
});
|
||||
Reference in New Issue
Block a user