homb RBAC (Role-Based Access Control) 설계
homb 플랫폼 전체에 걸친 권한 모델을 정의한다. 플랫폼 수준, 가족 수준, 모듈 수준의 3계층 RBAC과 관리자 인터페이스 방향을 설계한다.
1. 현재 상태 (As-Is)
| 영역 | 현재 | 문제 |
|---|---|---|
| 플랫폼 | 없음 | 시스템 관리자 구분 불가, 퍼블리셔도 일반 유저와 동일 |
| 가족 | owner/member (DB), UI에 admin/viewer label만 존재 | 실제 권한 분기 없음 (canRunActivityBackfill 정도) |
| 모듈 | ModuleInstall.permissions 필드만 존재 | 누가 설치/설정 변경할 수 있는지 강제 안 됨 |
| 관리자 | 없음 | Firestore 콘솔에서 수동 관리 |
2. RBAC 3계층 모델
┌─────────────────────────────────────────────────────┐
│ Layer 1: Platform Role (시스템 전체) │
│ │
│ super_admin ─ admin ─ support ─ publisher ─ user │
│ │
├─────────────────────────────────────────────────────┤
│ Layer 2: Family Role (가족 내) │
│ │
│ owner ─ admin ─ member ─ viewer │
│ │
├─────────────────────────────────────────────────────┤
│ Layer 3: Module Permission (모듈별) │
│ │
│ module.install ─ module.configure ─ module.use │
│ │
└─────────────────────────────────────────────────────┘
2.1 Layer 1: Platform Role
시스템 전체에 걸친 역할. 모든 사용자는 정확히 하나의 platform role을 가진다.
| Role | 설명 | 권한 |
|---|---|---|
super_admin | 최고 관리자 (1~2명) | 모든 권한 + admin 역할 부여/해제 |
admin | 플랫폼 관리자 | 사용자 관리, 모듈 심사, 퍼블리셔 승인, 통계 |
support | 고객 지원 | 사용자 조회, 가족 조회, 신고 처리 (읽기 + 제한된 쓰기) |
publisher | 모듈 퍼블리셔 | 자기 모듈 관리, API 키 발급, 분석 |
user | 일반 사용자 | 앱 기본 기능, 모듈 사용 |
Platform Role 권한 매트릭스:
super_admin admin support publisher user
─────────────────────────────────────────────────────────────────────
사용자 목록/조회 ✅ ✅ ✅ ❌ ❌
사용자 역할 변경 ✅ ✅ ❌ ❌ ❌
사용자 정지/해제 ✅ ✅ ❌ ❌ ❌
admin 역할 부여 ✅ ❌ ❌ ❌ ❌
가족 목록/조회 ✅ ✅ ✅ ❌ ❌
가족 강제 삭제 ✅ ✅ ❌ ❌ ❌
모듈 심사 (승인/반려) ✅ ✅ ❌ ❌ ❌
모듈 강제 정지 ✅ ✅ ❌ ❌ ❌
모듈 등록/수정 (자기 것) ✅ ✅ ❌ ✅ ❌
퍼블리셔 승인/반려 ✅ ✅ ❌ ❌ ❌
퍼블리셔 등록 ✅ ✅ ❌ ✅(자기) ❌
플랫폼 통계 조회 ✅ ✅ ✅ ❌ ❌
시스템 설정 변경 ✅ ❌ ❌ ❌ ❌
신고 목록/처리 ✅ ✅ ✅ ❌ ❌
신고 접수 ✅ ✅ ✅ ✅ ✅
2.2 Layer 2: Family Role
가족(그룹) 내 역할. 사용자는 가족마다 다른 역할을 가질 수 있다.
| Role | 설명 | 권한 |
|---|---|---|
owner | 가족 생성자/관리자 | 모든 권한 + 가족 삭제 + 역할 변경 + 초대 코드 관리 |
admin | 운영진 | 모듈 설치/제거, 멤버 초대, 채널 관리, 설정 변경 |
member | 일반 구성원 | 모듈 사용, 채널 참여, 콘텐츠 생성 |
viewer | 보기 전용 | 콘텐츠 조회만 가능 (어린 자녀, 게스트 등) |
Family Role 권한 매트릭스:
owner admin member viewer
──────────────────────────────────────────────────────
가족 정보 수정 ✅ ✅ ❌ ❌
가족 삭제 ✅ ❌ ❌ ❌
초대 코드 관리 ✅ ✅ ❌ ❌
멤버 초대 ✅ ✅ ❌ ❌
멤버 역할 변경 ✅ ❌ ❌ ❌
멤버 강퇴 ✅ ✅* ❌ ❌
모듈 설치/제거 ✅ ✅ ❌ ❌
모듈 설정 변경 ✅ ✅ ❌ ❌
모듈 사용 ✅ ✅ ✅ ❌
모듈 데이터 조회 ✅ ✅ ✅ ✅
채널 생성/삭제 ✅ ✅ ❌ ❌
채널 메시지 전송 ✅ ✅ ✅ ❌
채널 메시지 조회 ✅ ✅ ✅ ✅
추억/아카이브 생성 ✅ ✅ ✅ ❌
추억/아카이브 조회 ✅ ✅ ✅ ✅
추억/아카이브 삭제 ✅ ✅ 자기것 ❌
캘린더 이벤트 생성 ✅ ✅ ✅ ❌
캘린더 이벤트 조회 ✅ ✅ ✅ ✅
*admin은 member/viewer만 강퇴 가능 (다른 admin 불가)
2.3 Layer 3: Module Permission
모듈이 요청하는 권한. 설치 시 가족 admin 이상이 동의.
| Permission | 설명 | 체크 주체 |
|---|---|---|
family.read | 가족 기본 정보 | Runtime API 미들웨어 |
members.read | 멤버 목록 | Runtime API 미들웨어 |
storage.write | 파일 업로드 | Runtime API 미들웨어 |
notifications.send | 알림 전송 | Runtime API 미들웨어 |
activity.write | 피드 게시 | Runtime API 미들웨어 |
calendar.read/write | 캘린더 접근 | Runtime API 미들웨어 |
channel.share | 채널 공유 | Runtime API 미들웨어 |
이 권한은 모듈 설치 시점에 결정되며, 가족의 admin+ 역할만 승인할 수 있다.
3. 데이터 모델
3.1 DB 스키마
-- ============================================
-- Platform Role (시스템 수준)
-- ============================================
-- 플랫폼 역할 정의
CREATE TABLE platform_roles (
role TEXT PRIMARY KEY, -- super_admin, admin, support, publisher, user
description TEXT NOT NULL,
sort_order INTEGER NOT NULL DEFAULT 0
);
INSERT INTO platform_roles (role, description, sort_order) VALUES
('super_admin', '최고 관리자', 1),
('admin', '플랫폼 관리자', 2),
('support', '고객 지원', 3),
('publisher', '모듈 퍼블리셔', 4),
('user', '일반 사용자', 5);
-- 플랫폼 역할에 부여된 권한
CREATE TABLE platform_role_permissions (
role TEXT NOT NULL REFERENCES platform_roles(role),
permission TEXT NOT NULL,
PRIMARY KEY (role, permission)
);
-- 권한 목록 시드
INSERT INTO platform_role_permissions (role, permission) VALUES
-- super_admin: 모든 권한
('super_admin', 'users.list'),
('super_admin', 'users.read'),
('super_admin', 'users.update_role'),
('super_admin', 'users.suspend'),
('super_admin', 'users.grant_admin'),
('super_admin', 'families.list'),
('super_admin', 'families.read'),
('super_admin', 'families.delete'),
('super_admin', 'modules.review'),
('super_admin', 'modules.suspend'),
('super_admin', 'publishers.approve'),
('super_admin', 'platform.stats'),
('super_admin', 'platform.settings'),
('super_admin', 'reports.manage'),
-- admin: super_admin에서 grant_admin, platform.settings 제외
('admin', 'users.list'),
('admin', 'users.read'),
('admin', 'users.update_role'),
('admin', 'users.suspend'),
('admin', 'families.list'),
('admin', 'families.read'),
('admin', 'families.delete'),
('admin', 'modules.review'),
('admin', 'modules.suspend'),
('admin', 'publishers.approve'),
('admin', 'platform.stats'),
('admin', 'reports.manage'),
-- support
('support', 'users.list'),
('support', 'users.read'),
('support', 'families.list'),
('support', 'families.read'),
('support', 'platform.stats'),
('support', 'reports.manage'),
-- publisher
('publisher', 'modules.own.manage'),
('publisher', 'publisher.own.manage'),
('publisher', 'reports.submit'),
-- user
('user', 'reports.submit');
-- 사용자-플랫폼 역할 매핑 (users 테이블 확장)
ALTER TABLE users ADD COLUMN platform_role TEXT NOT NULL DEFAULT 'user'
REFERENCES platform_roles(role);
CREATE INDEX idx_users_platform_role ON users(platform_role);
-- ============================================
-- Family Role (가족 수준) — 기존 family_members 확장
-- ============================================
-- family_members.role 이미 존재하지만 CHECK 제약조건 추가
-- (마이그레이션 시 기존 데이터 호환 확인 필요)
-- ALTER TABLE family_members
-- ADD CONSTRAINT chk_family_member_role
-- CHECK (role IN ('owner', 'admin', 'member', 'viewer'));
-- 가족 역할별 권한
CREATE TABLE family_role_permissions (
role TEXT NOT NULL, -- owner, admin, member, viewer
permission TEXT NOT NULL,
PRIMARY KEY (role, permission)
);
INSERT INTO family_role_permissions (role, permission) VALUES
-- owner
('owner', 'family.update'),
('owner', 'family.delete'),
('owner', 'family.invite'),
('owner', 'family.manage_invite'),
('owner', 'family.kick_member'),
('owner', 'family.change_role'),
('owner', 'module.install'),
('owner', 'module.uninstall'),
('owner', 'module.configure'),
('owner', 'module.use'),
('owner', 'channel.create'),
('owner', 'channel.delete'),
('owner', 'channel.write'),
('owner', 'channel.read'),
('owner', 'content.create'),
('owner', 'content.read'),
('owner', 'content.delete_any'),
('owner', 'calendar.write'),
('owner', 'calendar.read'),
-- admin
('admin', 'family.update'),
('admin', 'family.invite'),
('admin', 'family.manage_invite'),
('admin', 'family.kick_member'),
('admin', 'module.install'),
('admin', 'module.uninstall'),
('admin', 'module.configure'),
('admin', 'module.use'),
('admin', 'channel.create'),
('admin', 'channel.delete'),
('admin', 'channel.write'),
('admin', 'channel.read'),
('admin', 'content.create'),
('admin', 'content.read'),
('admin', 'content.delete_own'),
('admin', 'calendar.write'),
('admin', 'calendar.read'),
-- member
('member', 'module.use'),
('member', 'channel.write'),
('member', 'channel.read'),
('member', 'content.create'),
('member', 'content.read'),
('member', 'content.delete_own'),
('member', 'calendar.write'),
('member', 'calendar.read'),
-- viewer
('viewer', 'channel.read'),
('viewer', 'content.read'),
('viewer', 'calendar.read');
-- ============================================
-- 감사 로그
-- ============================================
CREATE TABLE audit_logs (
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
actor_uid TEXT NOT NULL,
action TEXT NOT NULL, -- e.g. 'user.role_changed', 'module.approved'
target_type TEXT NOT NULL, -- e.g. 'user', 'family', 'module'
target_id TEXT NOT NULL,
details JSONB DEFAULT '{}', -- 변경 전/후 데이터
ip_address TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_audit_logs_actor ON audit_logs(actor_uid);
CREATE INDEX idx_audit_logs_target ON audit_logs(target_type, target_id);
CREATE INDEX idx_audit_logs_action ON audit_logs(action);
CREATE INDEX idx_audit_logs_created ON audit_logs(created_at);
3.2 권한 체크 흐름
요청 수신
│
▼
[1] Firebase Auth 토큰 검증 → UID 추출
│
▼
[2] Platform Role 조회 (users.platform_role)
│
├── platform 수준 API? ─▶ platform_role_permissions 확인
│
▼
[3] Family Role 조회 (family_members WHERE uid = :uid AND family_id = :fid)
│
├── family 수준 API? ─▶ family_role_permissions 확인
│
▼
[4] Module Permission 확인 (module_installations.settings.permissions)
│
└── module runtime API? ─▶ 설치 시 승인된 permissions 확인
4. 백엔드 구현
4.1 RBAC 미들웨어
// internal/auth/rbac.go
// RequirePlatformPermission — 플랫폼 권한 체크 미들웨어
func RequirePlatformPermission(db *pgxpool.Pool, permission string) echo.MiddlewareFunc
// RequireFamilyPermission — 가족 권한 체크 미들웨어
func RequireFamilyPermission(db *pgxpool.Pool, permission string) echo.MiddlewareFunc
// 사용 예시:
// admin.GET("/users", userHandler.List,
// auth.RequirePlatformPermission(pool, "users.list"))
//
// api.POST("/families/:familyId/modules/:key/install", moduleInstallHandler.Install,
// auth.RequireFamilyPermission(pool, "module.install"))
4.2 적용 예시
// cmd/server/main.go
// Public
e.GET("/health", healthHandler.Check)
e.GET("/api/marketplace/modules", marketplaceHandler.ListModules)
e.GET("/api/marketplace/modules/:key", marketplaceHandler.GetModule)
e.GET("/api/marketplace/categories", marketplaceHandler.ListCategories)
// Authenticated (모든 로그인 사용자)
api := e.Group("/api", auth.Middleware(fbAuth))
api.POST("/families", familyHandler.Create)
api.POST("/families/join", familyHandler.Join)
api.GET("/families/:id", familyHandler.Get)
// Family-scoped (가족 역할 필요)
api.POST("/families/:familyId/modules/:key/install", moduleInstallHandler.Install,
auth.RequireFamilyPermission(pool, "module.install"))
api.POST("/families/:familyId/modules/:key/uninstall", moduleInstallHandler.Uninstall,
auth.RequireFamilyPermission(pool, "module.uninstall"))
api.GET("/families/:familyId/modules", moduleInstallHandler.ListInstalled)
// Admin (플랫폼 관리자)
admin := api.Group("/admin")
admin.Use(auth.RequirePlatformRole(pool, "admin", "super_admin"))
admin.GET("/users", adminHandler.ListUsers)
admin.PUT("/users/:uid/role", adminHandler.UpdateUserRole)
admin.GET("/modules/pending", adminHandler.ListPendingModules)
admin.POST("/modules/:key/approve", adminHandler.ApproveModule)
admin.POST("/modules/:key/reject", adminHandler.RejectModule)
admin.GET("/stats", adminHandler.GetPlatformStats)
5. 관리자 인터페이스 — 어디에 만들 것인가?
5.1 선택지 비교
| 선택지 | 장점 | 단점 |
|---|---|---|
| A. membloc-app 내 관리자 섹션 | 별도 앱 불필요, 공유 코드 | 앱 번들 크기 증가, 일반 사용자에게 노출 위험, 관리 기능은 데스크톱이 편함 |
| B. 별도 관리자 Flutter 앱 | 모바일에서 긴급 대응 가능 | 두 개 앱 유지보수, Flutter 웹으로도 가능하지만 관리 도구에는 과한 스택 |
| C. 별도 웹 관리자 대시보드 | 데스크톱 UX 최적, Phase 3 Publisher Portal과 통합 가능 | 웹 프로젝트 하나 더 필요 |
| D. Publisher Portal에 Admin 섹션 통합 | 하나의 웹 프로젝트로 관리, 역할에 따라 다른 화면 | 퍼블리셔와 관리자 관심사 혼재 가능 |
5.2 권장: D안 — Publisher Portal에 Admin 통합
developer.membloc.com
├── / ────────────────── 랜딩 (문서 + 시작하기)
├── /login ───────────── Firebase Auth 로그인
│
├── [platform_role = publisher 이상]
│ ├── /dashboard ───── 퍼블리셔 대시보드
│ ├── /modules ──────── 내 모듈 관리
│ └── /settings ─────── 퍼블리셔 설정
│
├── [platform_role = support 이상]
│ ├── /admin ──────────── 관리자 대시보드
│ ├── /admin/users ────── 사용자 관리
│ ├── /admin/families ─── 가족 관리
│ └── /admin/reports ──── 신고 관리
│
├── [platform_role = admin 이상]
│ ├── /admin/modules ──── 모듈 심사
│ ├── /admin/publishers ─ 퍼블리셔 관리
│ └── /admin/stats ────── 플랫폼 통계
│
└── [platform_role = super_admin]
├── /admin/roles ──────── 역할 관리
└── /admin/system ─────── 시스템 설정
이유:
- 하나의 웹 프로젝트 — Phase 3에서 어차피
developer.membloc.com을 만들어야 함 - 역할 기반 라우팅 — 로그인한 사용자의
platform_role에 따라 메뉴가 달라짐 - 코드 공유 — 모듈 관리 UI는 퍼블리셔와 관리자가 거의 동일 (권한만 다름)
- 긴급 모바일 대응 — 반응형 웹이면 모바일 브라우저에서도 사용 가능
5.3 그래도 관리자 앱이 필요한 경우
membloc-app 내에 경량 관리자 모드를 추가:
// platform_role이 admin 이상이면 설정에 "관리자 모드" 토글 표시
// 활성화 시 → 간단한 관리 기능 노출:
// - 신고 알림 + 즉시 처리 (모듈 정지)
// - 긴급 사용자 정지
// - push 알림으로 심사 요청 수신
// 별도 앱은 불필요 — 웹 대시보드 + 앱 내 경량 모드로 충분
6. Flutter 앱 변경
6.1 현재 코드 영향 분석
| 파일 | 변경 내용 |
|---|---|
group_membership.dart | 변경 없음 (이미 role 필드 있음) |
settings_dialogs.dart | roleLabel() — 이미 owner/admin/viewer 처리, 변경 불필요 |
settings_screen.dart | canRunActivityBackfill() 같은 권한 체크를 RBAC 기반으로 전환 |
family_service.dart | 역할 변경 API 호출 추가 |
module_hub_screen.dart | 설치 버튼에 module.install 권한 체크 추가 |
신규 rbac_service.dart | 백엔드에서 권한 캐시 + 체크 유틸 |
6.2 RBAC Service (Flutter)
// core/services/rbac_service.dart
class RbacService {
// 현재 사용자의 platform_role (로그인 시 fetch + 캐시)
String get platformRole => _platformRole;
// 특정 가족에서의 역할
String getFamilyRole(String familyId) => _familyRoles[familyId] ?? 'member';
// 가족 내 권한 체크
bool hasFamilyPermission(String familyId, String permission);
// 플랫폼 권한 체크
bool hasPlatformPermission(String permission);
// 관리자인지 확인
bool get isAdmin => ['super_admin', 'admin'].contains(_platformRole);
bool get isSupport => ['super_admin', 'admin', 'support'].contains(_platformRole);
}
// 사용 예시:
// if (rbacService.hasFamilyPermission(groupId, 'module.install')) {
// showInstallButton();
// }
7. 구현 로드맵
7.1 Phase 1.5 (현재 시점에 추가)
Platform Role + Family Role 강제를 백엔드에 구현. 기존 Phase 1 (마켓플레이스)과 병행 가능.
| # | 작업 | 예상 |
|---|---|---|
| 1 | DB 마이그레이션 (004_rbac.up.sql) | 0.5d |
| 2 | users.platform_role 컬럼 추가 + 기존 사용자 'user'로 초기화 | 0.5d |
| 3 | internal/auth/rbac.go — 미들웨어 구현 | 1d |
| 4 | internal/handler/admin.go — 관리자 API (사용자/모듈/통계) | 2d |
| 5 | internal/service/admin.go — 관리자 비즈니스 로직 | 1d |
| 6 | main.go admin 라우트 등록 | 0.5d |
| 7 | 기존 모듈 설치 API에 family role 체크 적용 | 0.5d |
| 8 | 감사 로그 기록 | 1d |
| 9 | Flutter RbacService 구현 | 1d |
| 10 | Flutter UI 권한 분기 (설치 버튼, 설정 등) | 1d |
7.2 Phase 3에 통합
Publisher Portal 웹 개발 시 Admin 섹션을 함께 구현.
| # | 작업 | 예상 |
|---|---|---|
| 1 | Next.js 프로젝트에 admin 라우트 그룹 추가 | 0.5d |
| 2 | 역할 기반 사이드바/메뉴 | 0.5d |
| 3 | 사용자 관리 페이지 | 1d |
| 4 | 가족 관리 페이지 | 1d |
| 5 | 모듈 심사 페이지 | 1d |
| 6 | 플랫폼 통계 대시보드 | 1d |
| 7 | 신고 관리 페이지 | 1d |
| 8 | 감사 로그 뷰어 | 0.5d |
8. 보안 고려사항
| 항목 | 대책 |
|---|---|
| 역할 상승 공격 | super_admin만 admin 부여 가능, API에서 이중 체크 |
| 토큰 변조 | Firebase Auth 토큰의 UID로 DB 조회, 토큰 자체에 역할 넣지 않음 |
| 감사 추적 | 모든 역할 변경/관리 행위를 audit_logs에 기록 |
| Rate Limiting | admin API도 rate limit 적용 (DDoS 방지) |
| IP 제한 | super_admin API는 IP 화이트리스트 적용 가능 (향후) |
| 세션 관리 | 역할 변경 즉시 반영 (토큰 캐시가 아닌 DB 실시간 조회) |
9. 결론
| 결정 | 내용 |
|---|---|
| RBAC 모델 | 3계층 (Platform → Family → Module) |
| Platform Role | super_admin, admin, support, publisher, user |
| Family Role | owner, admin, member, viewer (기존 확장) |
| 관리자 앱 | 별도 앱 불필요 — developer.membloc.com 웹에 Admin 섹션 통합 |
| 긴급 대응 | membloc-app 설정에 경량 관리자 모드 (platform_role 기반 노출) |
| 구현 시점 | Phase 1.5로 백엔드 RBAC 즉시 구현, Phase 3에서 Admin UI 구축 |