공개 제보 https://github.com/gnuboard/g7/issues/122 대응 — 초기 부트스트랩 리소스(다국어 병합·컴포넌트 정의·라우트·확장 번들·템플릿 dist)를 캐시 버전 디렉토리(public/build/ext/{v}/)에 실파일로 게시해 웹서버가 rewrite 전에 직접 서빙한다. 부트 임계 경로의 PHP 왕복을 제거하고(실측 TTFB 131~144ms → 1~7ms), 미게시·부분게시·GC 직후에는 fetch·태그·번들 3계층이 종전 API 로 즉시 폴백한다. - 게시: 원자적 tmp→rename→manifest(존재=완료), 캐시 락 단일 실행, 인라인 GC (현재+직전 1개), incrementExtensionCacheVersion 단일 지점 terminating 트리거 + blade 자가 치유 + 설치기 태스크(best_effort) + 일일 cleanup 스케줄, sudo 업데이트 대비 소유권 정상화(normalizeOwnership)·prune/백업 제외 - 프론트(engine-v1.61.0): blade 주입 cache_version 1급 시드(이중 부트 로드 제거), fetchStaticFirst 즉시 폴백, ComponentRegistry 버전 키드 매니페스트, ModuleAssetLoader 번들 정적→레거시 폴백, asset-url-recovery staticToLegacy 역변환 - 폴백 API 품질: lang/components/routes ETag+304 + 환경 분기 Cache-Control, 열화 라우트 스냅샷 공개 캐시 금지(서버측 캐시 회피와 대칭), 게시 .htaccess mod_deflate + nginx gzip 스니펫(압축 전송량 회귀 방지) - SEO 정합: 봇 HTML 은 GC 대상 정적 URL 미사용(allowStatic:false), props $switch 봇측 해석 구현(engine-v1.56.0 패리티), 패리티 룰 expression-dialect 그룹 신설, 상주 allow 헤더 제거로 잠금 복원, _comment* 접두 주석 키 분류 - 검증: 전 수정 red→green 4단계, Playwright 라이브 21건, Chrome MCP 24축+M1~M3, 봇 curl 3축, 캐시 저장소(file/redis/database) 축 판정, 히스토리·공개이슈·커밋 이력 전수 재조사 반영 - 코어 7.0.10, engine-v1.61.0. kill-switch: G7_STATIC_CACHE=false
1214 lines
82 KiB
PHP
1214 lines
82 KiB
PHP
<?php
|
|
|
|
return [
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 환경설정 입력 한계값
|
|
|--------------------------------------------------------------------------
|
|
| 관리자 환경설정 화면의 숫자 입력 경계값(min/max)과 저장 검증 규칙이 공유하는 단일 출처.
|
|
|
|
|
| 화면과 규칙이 각자 리터럴을 들면 두 값이 조용히 갈라져 "화면이 허용한 값인데 저장에서
|
|
| 422" 또는 그 반대가 됩니다. 규칙(SaveSettingsRequest)은 이 값을 읽어 규칙 문자열을
|
|
| 만들고, 화면은 설정 응답의 `_meta.limits` 로 같은 값을 받아 바인딩합니다.
|
|
|
|
|
| 키 이름은 `{카테고리}_{필드}_{min|max}` 로, 설정 키(`upload.max_file_size`)와 1:1 대응합니다.
|
|
| 카테고리를 접두사로 두는 이유는 `seo.cache_ttl` 과 `advanced.seo_cache_ttl` 처럼 필드명이
|
|
| 겹치는 조합이 실제로 있기 때문입니다.
|
|
*/
|
|
'settings_limits' => [
|
|
// 업로드
|
|
'upload_max_file_size_min' => 1,
|
|
'upload_max_file_size_max' => 1024,
|
|
'upload_image_max_width_min' => 100,
|
|
'upload_image_max_width_max' => 10000,
|
|
'upload_image_max_height_min' => 100,
|
|
'upload_image_max_height_max' => 10000,
|
|
'upload_image_quality_min' => 1,
|
|
'upload_image_quality_max' => 100,
|
|
'upload_orphan_retention_days_min' => 1,
|
|
'upload_orphan_retention_days_max' => 3650,
|
|
|
|
// SEO
|
|
'seo_og_image_default_width_min' => 0,
|
|
'seo_og_image_default_width_max' => 8000,
|
|
'seo_og_image_default_height_min' => 0,
|
|
'seo_og_image_default_height_max' => 8000,
|
|
'seo_cache_ttl_min' => 60,
|
|
'seo_cache_ttl_max' => 86400,
|
|
'seo_sitemap_cache_ttl_min' => 3600,
|
|
'seo_sitemap_cache_ttl_max' => 604800,
|
|
'seo_sitemap_urls_per_file_min' => 1000,
|
|
'seo_sitemap_urls_per_file_max' => 50000,
|
|
|
|
// 보안
|
|
'security_password_min_length_min' => 6,
|
|
'security_password_min_length_max' => 64,
|
|
'security_auth_token_lifetime_min' => 0,
|
|
'security_auth_token_lifetime_max' => 3600,
|
|
'security_max_login_attempts_min' => 0,
|
|
'security_max_login_attempts_max' => 100,
|
|
'security_login_lockout_time_min' => 0,
|
|
'security_login_lockout_time_max' => 1440,
|
|
|
|
// 고급 (캐시 TTL)
|
|
'advanced_cache_ttl_min' => 0,
|
|
'advanced_cache_ttl_max' => 14400,
|
|
'advanced_seo_sitemap_cache_ttl_min' => 3600,
|
|
'advanced_seo_sitemap_cache_ttl_max' => 604800,
|
|
|
|
// 드라이버
|
|
'drivers_redis_port_min' => 1,
|
|
'drivers_redis_port_max' => 65535,
|
|
'drivers_redis_database_min' => 0,
|
|
'drivers_redis_database_max' => 15,
|
|
'drivers_memcached_port_min' => 1,
|
|
'drivers_memcached_port_max' => 65535,
|
|
'drivers_session_lifetime_min' => 1,
|
|
'drivers_session_lifetime_max' => 43200,
|
|
'drivers_websocket_port_min' => 1,
|
|
'drivers_websocket_port_max' => 65535,
|
|
'drivers_websocket_server_port_min' => 1,
|
|
'drivers_websocket_server_port_max' => 65535,
|
|
'drivers_log_days_min' => 1,
|
|
'drivers_log_days_max' => 365,
|
|
|
|
// 본인인증
|
|
'identity_challenge_ttl_minutes_min' => 1,
|
|
'identity_challenge_ttl_minutes_max' => 1440,
|
|
'identity_max_attempts_min' => 1,
|
|
'identity_max_attempts_max' => 20,
|
|
|
|
// 목록 한계값 (0 = 무제한)
|
|
'advanced_pagination_result_cap_min' => 0,
|
|
'advanced_pagination_result_cap_max' => 1000000,
|
|
'advanced_pagination_max_page_min' => 0,
|
|
'advanced_pagination_max_page_max' => 100000,
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 목록 한계값 기본값
|
|
|--------------------------------------------------------------------------
|
|
| 관리자 환경설정(`pagination` 카테고리)이 비어 있을 때 쓰는 코드 기본값입니다.
|
|
| 실제 해석은 App\Support\Query\PaginationLimits 가 단독으로 수행하며, 확장은
|
|
| 이 값을 리터럴로 다시 적지 않고 필터 훅으로만 조정합니다.
|
|
|
|
|
| - result_cap: 총 건수를 정확히 세는 상한. 이 값을 넘는 매칭은 "이상" 으로만 보고합니다.
|
|
| 페이지 이동은 상한과 무관하게 끝까지 열려 있고, 계산이 불가능해지는 것은
|
|
| 마지막 페이지 번호 하나뿐입니다.
|
|
| - max_page: 직접 요청할 수 있는 페이지 번호 상한 (남용 차단용).
|
|
|
|
|
| 둘 다 0 이면 무제한입니다.
|
|
*/
|
|
'pagination' => [
|
|
'result_cap' => 10000,
|
|
'max_page' => 1000,
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 부트스트랩 리소스 정적 게시 (bake)
|
|
|--------------------------------------------------------------------------
|
|
| 초기 부트스트랩 리소스(다국어 병합·컴포넌트 정의·라우트·확장 번들·템플릿
|
|
| dist 에셋)를 캐시 버전 디렉토리(`public/build/ext/{v}/`)에 실파일로 게시해
|
|
| 웹서버가 rewrite 전에 직접 서빙하는 fast path 의 스위치입니다.
|
|
|
|
|
| 끄면(false) 게시가 중단되고 blade 가 정적 URL 을 방출하지 않아 전면 API
|
|
| 폴백(종전 동작)으로 돌아갑니다. 이미 게시된 파일은 참조되지 않은 채 남습니다.
|
|
*/
|
|
'static_cache' => [
|
|
'enabled' => env('G7_STATIC_CACHE', true),
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 아웃바운드 프록시 연결 테스트
|
|
|--------------------------------------------------------------------------
|
|
| 운영자가 환경설정에 입력한 프록시가 실제로 동작하는지, 그리고 그 프록시를 거쳐
|
|
| 나갔을 때 상대편에 어떤 IP 로 보이는지 확인하는 데 쓰는 조회 대상입니다.
|
|
|
|
|
| 출발지 IP 는 운영자가 결제사·외부 서비스에 등록해야 하는 값이라, 프록시를 켠 상태의
|
|
| 실제 값을 알려주는 것이 이 테스트의 목적입니다. 목록은 순차 시도하며 먼저 유효한
|
|
| IP 를 돌려준 곳에서 멈춥니다. 폐쇄망 등 외부 조회가 불가능한 환경에서는 목록을
|
|
| 비워 두면 도달성만 확인하고 IP 는 보고하지 않습니다.
|
|
*/
|
|
'outbound_proxy' => [
|
|
'egress_lookup_urls' => [
|
|
'https://api.ipify.org',
|
|
'https://ifconfig.me/ip',
|
|
'https://icanhazip.com',
|
|
],
|
|
'test_timeout_seconds' => 10,
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 검색 — DBMS 별 부분일치 연산자
|
|
|--------------------------------------------------------------------------
|
|
| 전문검색을 제공하지 않는 DBMS 로 설치된 사이트에서는 부분일치(LIKE)가 정상 검색
|
|
| 경로입니다. 그런데 "대소문자를 구분하지 않는 부분일치" 를 어떤 연산자로 쓰는지는
|
|
| DBMS 마다 다릅니다 — 대부분 기본 비교가 구분하지 않아 `like` 로 충분하지만,
|
|
| 그렇지 않은 DBMS 는 전용 연산자를 씁니다.
|
|
|
|
|
| 코어 코드에 드라이버명을 적지 않기 위해 이 표에 선언합니다. 새 DBMS 를 공식 지원할
|
|
| 때는 여기에 한 줄을 더하면 되고, 확장은 `core.search.like_operators` 필터 훅으로
|
|
| 조정합니다. 표에 없는 드라이버는 `like_operator_default` 를 씁니다.
|
|
|
|
|
| 키는 `DB::getDriverName()` 이 돌려주는 값입니다.
|
|
*/
|
|
'search' => [
|
|
'like_operators' => [
|
|
'pgsql' => 'ilike',
|
|
],
|
|
'like_operator_default' => 'like',
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 스토리지 — 공개 자산 직접 URL 서빙 디스크
|
|
|--------------------------------------------------------------------------
|
|
| 완전 공개 자산(상품/카테고리/리뷰/에디터 이미지)의 직접 URL(CDN) 서빙에 쓸
|
|
| 디스크입니다. 빈 문자열이면 미설정(기존 PHP 스트리밍 유지)이며, 값은 관리자
|
|
| 환경설정(drivers.public_asset_disk)에서 SettingsServiceProvider 가 주입합니다.
|
|
| 확장은 개별 설정으로 이 전역값을 오버라이드할 수 있습니다.
|
|
*/
|
|
'storage' => [
|
|
'public_asset_disk' => '',
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 코어 권한 정의
|
|
|--------------------------------------------------------------------------
|
|
| RolePermissionSeeder 및 CoreUpdateService::syncCoreRolesAndPermissions()에서 사용
|
|
| 새 권한 추가 시 이 배열에 추가하면 설치/업데이트 모두 반영됩니다.
|
|
|
|
|
| 구조: 모듈(1레벨) → 카테고리(2레벨) → 개별 권한(3레벨)
|
|
*/
|
|
'permissions' => [
|
|
// 1레벨: 코어 모듈
|
|
'module' => [
|
|
'identifier' => 'core',
|
|
'name' => ['ko' => '코어', 'en' => 'Core'],
|
|
'description' => ['ko' => '코어 시스템 권한', 'en' => 'Core system permissions'],
|
|
'order' => 1,
|
|
],
|
|
|
|
// 2레벨: 카테고리들 + 3레벨: 개별 권한
|
|
'categories' => [
|
|
[
|
|
'identifier' => 'core.users',
|
|
'name' => ['ko' => '사용자 관리', 'en' => 'User Management'],
|
|
'description' => ['ko' => '사용자 관리 권한', 'en' => 'User management permissions'],
|
|
'category' => 'users',
|
|
'order' => 1,
|
|
'type' => 'admin',
|
|
'permissions' => [
|
|
['identifier' => 'core.users.read', 'type' => 'admin', 'name' => ['ko' => '사용자 조회', 'en' => 'View Users'], 'description' => ['ko' => '사용자 목록 및 상세 정보를 조회할 수 있습니다.', 'en' => 'Can view user list and details.'], 'order' => 1, 'resource_route_key' => 'user', 'owner_key' => 'id'],
|
|
['identifier' => 'core.users.create', 'type' => 'admin', 'name' => ['ko' => '사용자 생성', 'en' => 'Create Users'], 'description' => ['ko' => '새로운 사용자를 생성할 수 있습니다.', 'en' => 'Can create new users.'], 'order' => 2],
|
|
['identifier' => 'core.users.update', 'type' => 'admin', 'name' => ['ko' => '사용자 수정', 'en' => 'Update Users'], 'description' => ['ko' => '사용자 정보를 수정할 수 있습니다.', 'en' => 'Can update user information.'], 'order' => 3, 'resource_route_key' => 'user', 'owner_key' => 'id'],
|
|
['identifier' => 'core.users.delete', 'type' => 'admin', 'name' => ['ko' => '사용자 삭제', 'en' => 'Delete Users'], 'description' => ['ko' => '사용자를 삭제할 수 있습니다.', 'en' => 'Can delete users.'], 'order' => 4, 'resource_route_key' => 'user', 'owner_key' => 'id'],
|
|
],
|
|
],
|
|
[
|
|
'identifier' => 'core.menus',
|
|
'name' => ['ko' => '메뉴 관리', 'en' => 'Menu Management'],
|
|
'description' => ['ko' => '메뉴 관리 권한', 'en' => 'Menu management permissions'],
|
|
'category' => 'menus',
|
|
'order' => 2,
|
|
'type' => 'admin',
|
|
'permissions' => [
|
|
['identifier' => 'core.menus.read', 'type' => 'admin', 'name' => ['ko' => '메뉴 조회', 'en' => 'View Menus'], 'description' => ['ko' => '메뉴 목록을 조회할 수 있습니다.', 'en' => 'Can view menu list.'], 'order' => 1, 'resource_route_key' => 'menu', 'owner_key' => 'created_by'],
|
|
['identifier' => 'core.menus.create', 'type' => 'admin', 'name' => ['ko' => '메뉴 생성', 'en' => 'Create Menus'], 'description' => ['ko' => '새로운 메뉴를 생성할 수 있습니다.', 'en' => 'Can create new menus.'], 'order' => 2],
|
|
['identifier' => 'core.menus.update', 'type' => 'admin', 'name' => ['ko' => '메뉴 수정', 'en' => 'Update Menus'], 'description' => ['ko' => '메뉴 정보를 수정할 수 있습니다.', 'en' => 'Can update menu information.'], 'order' => 3, 'resource_route_key' => 'menu', 'owner_key' => 'created_by'],
|
|
['identifier' => 'core.menus.delete', 'type' => 'admin', 'name' => ['ko' => '메뉴 삭제', 'en' => 'Delete Menus'], 'description' => ['ko' => '메뉴를 삭제할 수 있습니다.', 'en' => 'Can delete menus.'], 'order' => 4, 'resource_route_key' => 'menu', 'owner_key' => 'created_by'],
|
|
],
|
|
],
|
|
[
|
|
'identifier' => 'core.modules',
|
|
'name' => ['ko' => '모듈 관리', 'en' => 'Module Management'],
|
|
'description' => ['ko' => '모듈 관리 권한', 'en' => 'Module management permissions'],
|
|
'category' => 'modules',
|
|
'order' => 3,
|
|
'type' => 'admin',
|
|
'permissions' => [
|
|
['identifier' => 'core.modules.read', 'type' => 'admin', 'name' => ['ko' => '모듈 조회', 'en' => 'View Modules'], 'description' => ['ko' => '모듈 목록을 조회할 수 있습니다.', 'en' => 'Can view module list.'], 'order' => 1],
|
|
['identifier' => 'core.modules.install', 'type' => 'admin', 'name' => ['ko' => '모듈 설치', 'en' => 'Install Modules'], 'description' => ['ko' => '새로운 모듈을 설치할 수 있습니다.', 'en' => 'Can install new modules.'], 'order' => 2],
|
|
['identifier' => 'core.modules.activate', 'type' => 'admin', 'name' => ['ko' => '모듈 활성화', 'en' => 'Activate Modules'], 'description' => ['ko' => '모듈을 활성화/비활성화할 수 있습니다.', 'en' => 'Can activate/deactivate modules.'], 'order' => 3],
|
|
['identifier' => 'core.modules.uninstall', 'type' => 'admin', 'name' => ['ko' => '모듈 삭제', 'en' => 'Uninstall Modules'], 'description' => ['ko' => '모듈을 삭제할 수 있습니다.', 'en' => 'Can uninstall modules.'], 'order' => 4],
|
|
],
|
|
],
|
|
[
|
|
'identifier' => 'core.plugins',
|
|
'name' => ['ko' => '플러그인 관리', 'en' => 'Plugin Management'],
|
|
'description' => ['ko' => '플러그인 관리 권한', 'en' => 'Plugin management permissions'],
|
|
'category' => 'plugins',
|
|
'order' => 4,
|
|
'type' => 'admin',
|
|
'permissions' => [
|
|
['identifier' => 'core.plugins.read', 'type' => 'admin', 'name' => ['ko' => '플러그인 조회', 'en' => 'View Plugins'], 'description' => ['ko' => '플러그인 목록을 조회할 수 있습니다.', 'en' => 'Can view plugin list.'], 'order' => 1],
|
|
['identifier' => 'core.plugins.install', 'type' => 'admin', 'name' => ['ko' => '플러그인 설치', 'en' => 'Install Plugins'], 'description' => ['ko' => '새로운 플러그인을 설치할 수 있습니다.', 'en' => 'Can install new plugins.'], 'order' => 2],
|
|
['identifier' => 'core.plugins.activate', 'type' => 'admin', 'name' => ['ko' => '플러그인 활성화', 'en' => 'Activate Plugins'], 'description' => ['ko' => '플러그인을 활성화/비활성화할 수 있습니다.', 'en' => 'Can activate/deactivate plugins.'], 'order' => 3],
|
|
['identifier' => 'core.plugins.update', 'type' => 'admin', 'name' => ['ko' => '플러그인 설정', 'en' => 'Configure Plugins'], 'description' => ['ko' => '플러그인 환경설정을 수정할 수 있습니다.', 'en' => 'Can update plugin settings.'], 'order' => 4],
|
|
['identifier' => 'core.plugins.uninstall', 'type' => 'admin', 'name' => ['ko' => '플러그인 삭제', 'en' => 'Uninstall Plugins'], 'description' => ['ko' => '플러그인을 삭제할 수 있습니다.', 'en' => 'Can uninstall plugins.'], 'order' => 5],
|
|
],
|
|
],
|
|
[
|
|
'identifier' => 'core.templates',
|
|
'name' => ['ko' => '템플릿 관리', 'en' => 'Template Management'],
|
|
'description' => ['ko' => '템플릿 관리 권한', 'en' => 'Template management permissions'],
|
|
'category' => 'templates',
|
|
'order' => 5,
|
|
'type' => 'admin',
|
|
'permissions' => [
|
|
['identifier' => 'core.templates.read', 'type' => 'admin', 'name' => ['ko' => '템플릿 조회', 'en' => 'View Templates'], 'description' => ['ko' => '템플릿 목록을 조회할 수 있습니다.', 'en' => 'Can view template list.'], 'order' => 1],
|
|
['identifier' => 'core.templates.install', 'type' => 'admin', 'name' => ['ko' => '템플릿 설치', 'en' => 'Install Templates'], 'description' => ['ko' => '새로운 템플릿을 설치할 수 있습니다.', 'en' => 'Can install new templates.'], 'order' => 2],
|
|
['identifier' => 'core.templates.activate', 'type' => 'admin', 'name' => ['ko' => '템플릿 활성화', 'en' => 'Activate Templates'], 'description' => ['ko' => '템플릿을 활성화/비활성화할 수 있습니다.', 'en' => 'Can activate/deactivate templates.'], 'order' => 3],
|
|
['identifier' => 'core.templates.uninstall', 'type' => 'admin', 'name' => ['ko' => '템플릿 삭제', 'en' => 'Uninstall Templates'], 'description' => ['ko' => '템플릿을 삭제할 수 있습니다.', 'en' => 'Can uninstall templates.'], 'order' => 4],
|
|
['identifier' => 'core.templates.layouts.edit', 'type' => 'admin', 'name' => ['ko' => '레이아웃 편집', 'en' => 'Edit Layouts'], 'description' => ['ko' => '템플릿 레이아웃을 편집할 수 있습니다.', 'en' => 'Can edit template layouts.'], 'order' => 5],
|
|
],
|
|
],
|
|
[
|
|
'identifier' => 'core.permissions',
|
|
'name' => ['ko' => '권한 관리', 'en' => 'Permission Management'],
|
|
'description' => ['ko' => '역할 및 권한 관리 권한', 'en' => 'Role and permission management permissions'],
|
|
'category' => 'permissions',
|
|
'order' => 6,
|
|
'type' => 'admin',
|
|
'permissions' => [
|
|
['identifier' => 'core.permissions.read', 'type' => 'admin', 'name' => ['ko' => '권한 조회', 'en' => 'View Permissions'], 'description' => ['ko' => '역할 및 권한 목록을 조회할 수 있습니다.', 'en' => 'Can view roles and permissions.'], 'order' => 1],
|
|
['identifier' => 'core.permissions.create', 'type' => 'admin', 'name' => ['ko' => '역할 생성', 'en' => 'Create Roles'], 'description' => ['ko' => '새로운 역할을 생성할 수 있습니다.', 'en' => 'Can create new roles.'], 'order' => 2],
|
|
['identifier' => 'core.permissions.update', 'type' => 'admin', 'name' => ['ko' => '역할 수정', 'en' => 'Update Roles'], 'description' => ['ko' => '역할 정보와 권한을 수정할 수 있습니다.', 'en' => 'Can update role information and permissions.'], 'order' => 3],
|
|
['identifier' => 'core.permissions.delete', 'type' => 'admin', 'name' => ['ko' => '역할 삭제', 'en' => 'Delete Roles'], 'description' => ['ko' => '역할을 삭제할 수 있습니다.', 'en' => 'Can delete roles.'], 'order' => 4],
|
|
],
|
|
],
|
|
[
|
|
'identifier' => 'core.notification-logs',
|
|
'name' => ['ko' => '알림 발송 이력', 'en' => 'Notification Logs'],
|
|
'description' => ['ko' => '알림 발송 이력 관리 권한', 'en' => 'Notification log management permissions'],
|
|
'category' => 'notification-logs',
|
|
'order' => 7,
|
|
'type' => 'admin',
|
|
'permissions' => [
|
|
['identifier' => 'core.notification-logs.read', 'type' => 'admin', 'name' => ['ko' => '발송 이력 조회', 'en' => 'View Notification Logs'], 'description' => ['ko' => '알림 발송 이력을 조회할 수 있습니다.', 'en' => 'Can view notification logs.'], 'order' => 1],
|
|
['identifier' => 'core.notification-logs.delete', 'type' => 'admin', 'name' => ['ko' => '발송 이력 삭제', 'en' => 'Delete Notification Logs'], 'description' => ['ko' => '알림 발송 이력을 삭제할 수 있습니다.', 'en' => 'Can delete notification logs.'], 'order' => 2],
|
|
],
|
|
],
|
|
[
|
|
'identifier' => 'core.notifications',
|
|
'name' => ['ko' => '알림 (관리자)', 'en' => 'Notifications (Admin)'],
|
|
'description' => ['ko' => '관리자용 알림 관리 권한 (관리자 화면에서 사용)', 'en' => 'Admin notification management permissions (used in admin UI)'],
|
|
'category' => 'notifications',
|
|
'order' => 7.5,
|
|
'type' => 'admin',
|
|
'permissions' => [
|
|
['identifier' => 'core.notifications.read', 'type' => 'admin', 'name' => ['ko' => '알림 조회', 'en' => 'View Notifications'], 'description' => ['ko' => '알림 목록 및 읽지 않은 수를 조회할 수 있습니다.', 'en' => 'Can view notification list and unread count.'], 'order' => 1],
|
|
['identifier' => 'core.notifications.update', 'type' => 'admin', 'name' => ['ko' => '알림 읽음 처리', 'en' => 'Mark Notifications Read'], 'description' => ['ko' => '알림을 읽음 처리할 수 있습니다.', 'en' => 'Can mark notifications as read.'], 'order' => 2],
|
|
['identifier' => 'core.notifications.delete', 'type' => 'admin', 'name' => ['ko' => '알림 삭제', 'en' => 'Delete Notifications'], 'description' => ['ko' => '알림을 삭제할 수 있습니다.', 'en' => 'Can delete notifications.'], 'order' => 3],
|
|
],
|
|
],
|
|
[
|
|
'identifier' => 'core.user-notifications',
|
|
'name' => ['ko' => '알림 (사용자)', 'en' => 'Notifications (User)'],
|
|
'description' => ['ko' => '사용자용 알림 권한 (사용자 화면에서 본인 알림 관리)', 'en' => 'User notification permissions (managing own notifications in user UI)'],
|
|
'category' => 'user-notifications',
|
|
'order' => 7.6,
|
|
'type' => 'user',
|
|
'permissions' => [
|
|
['identifier' => 'core.user-notifications.read', 'type' => 'user', 'name' => ['ko' => '알림 조회', 'en' => 'View Notifications'], 'description' => ['ko' => '본인의 알림 목록 및 읽지 않은 수를 조회할 수 있습니다.', 'en' => 'Can view own notification list and unread count.'], 'order' => 1],
|
|
['identifier' => 'core.user-notifications.update', 'type' => 'user', 'name' => ['ko' => '알림 읽음 처리', 'en' => 'Mark Notifications Read'], 'description' => ['ko' => '본인의 알림을 읽음 처리할 수 있습니다.', 'en' => 'Can mark own notifications as read.'], 'order' => 2],
|
|
['identifier' => 'core.user-notifications.delete', 'type' => 'user', 'name' => ['ko' => '알림 삭제', 'en' => 'Delete Notifications'], 'description' => ['ko' => '본인의 알림을 삭제할 수 있습니다.', 'en' => 'Can delete own notifications.'], 'order' => 3],
|
|
],
|
|
],
|
|
[
|
|
'identifier' => 'core.identity',
|
|
'name' => ['ko' => '본인인증 (사용자)', 'en' => 'Identity Verification (User)'],
|
|
'description' => ['ko' => '로그인 사용자의 본인인증 challenge 요청/검증/취소 권한', 'en' => 'Permissions for authenticated users to request/verify/cancel IDV challenges'],
|
|
'category' => 'identity',
|
|
'order' => 7.7,
|
|
'type' => 'user',
|
|
'permissions' => [
|
|
['identifier' => 'core.identity.request', 'type' => 'user', 'name' => ['ko' => 'IDV 요청', 'en' => 'Request IDV Challenge'], 'description' => ['ko' => '본인인증 challenge 를 요청할 수 있습니다.', 'en' => 'Can request identity verification challenges.'], 'order' => 1],
|
|
['identifier' => 'core.identity.verify', 'type' => 'user', 'name' => ['ko' => 'IDV 검증', 'en' => 'Verify IDV Challenge'], 'description' => ['ko' => '본인의 본인인증 challenge 를 검증할 수 있습니다.', 'en' => 'Can verify own identity verification challenges.'], 'order' => 2, 'resource_route_key' => 'challenge', 'owner_key' => 'user_id'],
|
|
['identifier' => 'core.identity.cancel', 'type' => 'user', 'name' => ['ko' => 'IDV 취소', 'en' => 'Cancel IDV Challenge'], 'description' => ['ko' => '본인의 본인인증 challenge 를 취소할 수 있습니다.', 'en' => 'Can cancel own identity verification challenges.'], 'order' => 3, 'resource_route_key' => 'challenge', 'owner_key' => 'user_id'],
|
|
],
|
|
],
|
|
[
|
|
'identifier' => 'core.admin.identity',
|
|
'name' => ['ko' => '본인인증 관리 (관리자)', 'en' => 'Identity Verification Management (Admin)'],
|
|
'description' => ['ko' => '관리자용 IDV 프로바이더/정책/로그 관리 권한', 'en' => 'Admin permissions for IDV providers, policies, and logs'],
|
|
'category' => 'identity',
|
|
'order' => 7.8,
|
|
'type' => 'admin',
|
|
'permissions' => [
|
|
['identifier' => 'core.admin.identity.providers.read', 'type' => 'admin', 'name' => ['ko' => '프로바이더 설정 조회', 'en' => 'View IDV Providers'], 'description' => ['ko' => '본인인증 프로바이더 설정을 조회할 수 있습니다.', 'en' => 'Can view identity verification providers.'], 'order' => 1],
|
|
['identifier' => 'core.admin.identity.providers.update', 'type' => 'admin', 'name' => ['ko' => '프로바이더 설정 수정', 'en' => 'Update IDV Providers'], 'description' => ['ko' => '본인인증 프로바이더 설정을 수정할 수 있습니다.', 'en' => 'Can update identity verification providers.'], 'order' => 2],
|
|
['identifier' => 'core.admin.identity.policies.read', 'type' => 'admin', 'name' => ['ko' => '정책 조회', 'en' => 'View IDV Policies'], 'description' => ['ko' => '본인인증 정책(라우트/훅별)을 조회할 수 있습니다.', 'en' => 'Can view identity verification policies by route/hook.'], 'order' => 3],
|
|
['identifier' => 'core.admin.identity.policies.update', 'type' => 'admin', 'name' => ['ko' => '정책 수정', 'en' => 'Update IDV Policies'], 'description' => ['ko' => '본인인증 정책(라우트/훅별)을 CRUD 할 수 있습니다.', 'en' => 'Can CRUD identity verification policies by route/hook.'], 'order' => 4],
|
|
['identifier' => 'core.admin.identity.logs.read', 'type' => 'admin', 'name' => ['ko' => '로그 열람', 'en' => 'View IDV Logs'], 'description' => ['ko' => '본인인증 이력을 조회할 수 있습니다.', 'en' => 'Can view identity verification logs.'], 'order' => 5],
|
|
['identifier' => 'core.admin.identity.logs.purge', 'type' => 'admin', 'name' => ['ko' => '로그 파기', 'en' => 'Purge IDV Logs'], 'description' => ['ko' => '본인인증 이력을 파기(보관주기 외)할 수 있습니다.', 'en' => 'Can purge identity verification logs (retention-based).'], 'order' => 6],
|
|
['identifier' => 'core.admin.identity.messages.read', 'type' => 'admin', 'name' => ['ko' => '메시지 템플릿 조회', 'en' => 'View IDV Message Templates'], 'description' => ['ko' => '본인인증 메시지 정의/템플릿을 조회할 수 있습니다.', 'en' => 'Can view identity verification message definitions and templates.'], 'order' => 7],
|
|
['identifier' => 'core.admin.identity.messages.update', 'type' => 'admin', 'name' => ['ko' => '메시지 템플릿 수정', 'en' => 'Update IDV Message Templates'], 'description' => ['ko' => '본인인증 메시지 정의/템플릿을 수정할 수 있습니다.', 'en' => 'Can update identity verification message definitions and templates.'], 'order' => 8],
|
|
],
|
|
],
|
|
[
|
|
'identifier' => 'core.settings',
|
|
'name' => ['ko' => '환경설정', 'en' => 'Settings'],
|
|
'description' => ['ko' => '시스템 환경설정 권한', 'en' => 'System settings permissions'],
|
|
'category' => 'settings',
|
|
'order' => 8,
|
|
'type' => 'admin',
|
|
'permissions' => [
|
|
['identifier' => 'core.settings.read', 'type' => 'admin', 'name' => ['ko' => '설정 조회', 'en' => 'View Settings'], 'description' => ['ko' => '시스템 설정을 조회할 수 있습니다.', 'en' => 'Can view system settings.'], 'order' => 1],
|
|
['identifier' => 'core.settings.update', 'type' => 'admin', 'name' => ['ko' => '설정 수정', 'en' => 'Update Settings'], 'description' => ['ko' => '시스템 설정을 수정할 수 있습니다.', 'en' => 'Can update system settings.'], 'order' => 2],
|
|
],
|
|
],
|
|
[
|
|
'identifier' => 'core.dashboard',
|
|
'name' => ['ko' => '대시보드', 'en' => 'Dashboard'],
|
|
'description' => ['ko' => '대시보드 접근 권한', 'en' => 'Dashboard access permissions'],
|
|
'category' => 'dashboard',
|
|
'order' => 9,
|
|
'type' => 'admin',
|
|
'permissions' => [
|
|
['identifier' => 'core.dashboard.read', 'type' => 'admin', 'name' => ['ko' => '대시보드 조회', 'en' => 'View Dashboard'], 'description' => ['ko' => '대시보드 통계 및 정보를 조회할 수 있습니다.', 'en' => 'Can view dashboard statistics and information.'], 'order' => 1],
|
|
['identifier' => 'core.dashboard.system-status', 'type' => 'admin', 'name' => ['ko' => '시스템 상태', 'en' => 'System Status'], 'description' => ['ko' => '시스템 상태 정보를 조회할 수 있습니다.', 'en' => 'Can view system status information.'], 'order' => 2],
|
|
['identifier' => 'core.dashboard.resources', 'type' => 'admin', 'name' => ['ko' => '시스템 리소스', 'en' => 'System Resources'], 'description' => ['ko' => 'CPU, 메모리, 디스크 사용량을 조회할 수 있습니다.', 'en' => 'Can view CPU, memory, and disk usage.'], 'order' => 3],
|
|
['identifier' => 'core.dashboard.activities', 'type' => 'admin', 'name' => ['ko' => '최근 활동', 'en' => 'Recent Activities'], 'description' => ['ko' => '최근 활동 이력을 조회할 수 있습니다.', 'en' => 'Can view recent activity history.'], 'order' => 4, 'resource_route_key' => 'activityLog', 'owner_key' => 'user_id'],
|
|
['identifier' => 'core.dashboard.alerts', 'type' => 'admin', 'name' => ['ko' => '시스템 알림', 'en' => 'System Alerts'], 'description' => ['ko' => '시스템 알림을 조회할 수 있습니다.', 'en' => 'Can view system alerts.'], 'order' => 5],
|
|
],
|
|
],
|
|
[
|
|
'identifier' => 'core.activities',
|
|
'name' => ['ko' => '활동 로그', 'en' => 'Activity Logs'],
|
|
'description' => ['ko' => '활동 로그 조회 권한', 'en' => 'Activity log access permissions'],
|
|
'category' => 'activities',
|
|
'order' => 10,
|
|
'type' => 'admin',
|
|
'permissions' => [
|
|
['identifier' => 'core.activities.read', 'type' => 'admin', 'name' => ['ko' => '활동 로그 조회', 'en' => 'View Activity Logs'], 'description' => ['ko' => '활동 로그를 조회할 수 있습니다.', 'en' => 'Can view activity logs.'], 'order' => 1, 'resource_route_key' => 'activityLog', 'owner_key' => 'user_id'],
|
|
['identifier' => 'core.activities.delete', 'type' => 'admin', 'name' => ['ko' => '활동 로그 삭제', 'en' => 'Delete Activity Logs'], 'description' => ['ko' => '활동 로그를 삭제할 수 있습니다.', 'en' => 'Can delete activity logs.'], 'order' => 2],
|
|
],
|
|
],
|
|
[
|
|
'identifier' => 'core.attachments',
|
|
'name' => ['ko' => '첨부파일 관리', 'en' => 'Attachment Management'],
|
|
'description' => ['ko' => '첨부파일 관리 권한', 'en' => 'Attachment management permissions'],
|
|
'category' => 'attachments',
|
|
'order' => 11,
|
|
'type' => 'admin',
|
|
'permissions' => [
|
|
['identifier' => 'core.attachments.create', 'type' => 'admin', 'name' => ['ko' => '첨부파일 업로드', 'en' => 'Upload Attachments'], 'description' => ['ko' => '첨부파일을 업로드할 수 있습니다.', 'en' => 'Can upload attachments.'], 'order' => 1],
|
|
['identifier' => 'core.attachments.update', 'type' => 'admin', 'name' => ['ko' => '첨부파일 수정', 'en' => 'Update Attachments'], 'description' => ['ko' => '첨부파일 정보 및 순서를 수정할 수 있습니다.', 'en' => 'Can update attachment information and order.'], 'order' => 2, 'resource_route_key' => 'attachment', 'owner_key' => 'created_by'],
|
|
['identifier' => 'core.attachments.delete', 'type' => 'admin', 'name' => ['ko' => '첨부파일 삭제', 'en' => 'Delete Attachments'], 'description' => ['ko' => '첨부파일을 삭제할 수 있습니다.', 'en' => 'Can delete attachments.'], 'order' => 3, 'resource_route_key' => 'attachment', 'owner_key' => 'created_by'],
|
|
],
|
|
],
|
|
[
|
|
'identifier' => 'core.schedules',
|
|
'name' => ['ko' => '스케줄 관리', 'en' => 'Schedule Management'],
|
|
'description' => ['ko' => '스케줄 작업 관리 권한', 'en' => 'Schedule task management permissions'],
|
|
'category' => 'schedules',
|
|
'order' => 12,
|
|
'type' => 'admin',
|
|
'permissions' => [
|
|
['identifier' => 'core.schedules.read', 'type' => 'admin', 'name' => ['ko' => '스케줄 조회', 'en' => 'View Schedules'], 'description' => ['ko' => '스케줄 목록 및 상세 정보를 조회할 수 있습니다.', 'en' => 'Can view schedule list and details.'], 'order' => 1],
|
|
['identifier' => 'core.schedules.create', 'type' => 'admin', 'name' => ['ko' => '스케줄 생성', 'en' => 'Create Schedules'], 'description' => ['ko' => '새로운 스케줄을 생성할 수 있습니다.', 'en' => 'Can create new schedules.'], 'order' => 2],
|
|
['identifier' => 'core.schedules.update', 'type' => 'admin', 'name' => ['ko' => '스케줄 수정', 'en' => 'Update Schedules'], 'description' => ['ko' => '스케줄 정보를 수정할 수 있습니다.', 'en' => 'Can update schedule information.'], 'order' => 3],
|
|
['identifier' => 'core.schedules.delete', 'type' => 'admin', 'name' => ['ko' => '스케줄 삭제', 'en' => 'Delete Schedules'], 'description' => ['ko' => '스케줄을 삭제할 수 있습니다.', 'en' => 'Can delete schedules.'], 'order' => 4],
|
|
['identifier' => 'core.schedules.run', 'type' => 'admin', 'name' => ['ko' => '스케줄 실행', 'en' => 'Run Schedules'], 'description' => ['ko' => '스케줄을 수동으로 실행할 수 있습니다.', 'en' => 'Can manually run schedules.'], 'order' => 5],
|
|
],
|
|
],
|
|
[
|
|
'identifier' => 'core.language_packs',
|
|
'name' => ['ko' => '언어팩 관리', 'en' => 'Language Pack Management'],
|
|
'description' => ['ko' => '언어팩 설치/제거/활성화 권한', 'en' => 'Language pack install/uninstall/activation permissions'],
|
|
'category' => 'language_packs',
|
|
'order' => 13,
|
|
'type' => 'admin',
|
|
'permissions' => [
|
|
['identifier' => 'core.language_packs.read', 'type' => 'admin', 'name' => ['ko' => '언어팩 조회', 'en' => 'View Language Packs'], 'description' => ['ko' => '설치된 언어팩 목록 및 상세 정보를 조회할 수 있습니다.', 'en' => 'Can view installed language pack list and details.'], 'order' => 1],
|
|
['identifier' => 'core.language_packs.install', 'type' => 'admin', 'name' => ['ko' => '언어팩 설치', 'en' => 'Install Language Packs'], 'description' => ['ko' => 'ZIP/GitHub/URL 로 언어팩을 설치할 수 있습니다.', 'en' => 'Can install language packs from ZIP/GitHub/URL.'], 'order' => 2],
|
|
['identifier' => 'core.language_packs.manage', 'type' => 'admin', 'name' => ['ko' => '언어팩 관리', 'en' => 'Manage Language Packs'], 'description' => ['ko' => '언어팩을 활성화/비활성화/제거할 수 있습니다.', 'en' => 'Can activate/deactivate/uninstall language packs.'], 'order' => 3],
|
|
['identifier' => 'core.language_packs.update', 'type' => 'admin', 'name' => ['ko' => '언어팩 업데이트', 'en' => 'Update Language Packs'], 'description' => ['ko' => '설치된 언어팩의 업데이트를 확인하고 실행할 수 있습니다.', 'en' => 'Can check and apply updates for installed language packs.'], 'order' => 4],
|
|
],
|
|
],
|
|
],
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 코어 역할 정의
|
|
|--------------------------------------------------------------------------
|
|
| RolePermissionSeeder 및 CoreUpdateService::syncCoreRolesAndPermissions()에서 사용
|
|
*/
|
|
'roles' => [
|
|
[
|
|
'identifier' => 'admin',
|
|
'name' => ['ko' => '관리자', 'en' => 'Administrator'],
|
|
'description' => ['ko' => '시스템의 모든 기능에 접근할 수 있는 최고 관리자입니다.', 'en' => 'Super administrator with access to all system features.'],
|
|
'attributes' => ['is_active' => true],
|
|
'permissions' => 'all_leaf', // 모든 리프 권한 할당
|
|
],
|
|
[
|
|
'identifier' => 'manager',
|
|
'name' => ['ko' => '매니저', 'en' => 'Manager'],
|
|
'description' => ['ko' => '콘텐츠 및 사용자 관리 권한을 가진 관리자입니다.', 'en' => 'Manager with content and user management permissions.'],
|
|
'attributes' => ['is_active' => true],
|
|
'permissions' => [
|
|
'core.users.read', 'core.users.create', 'core.users.update',
|
|
'core.menus.read', 'core.menus.create', 'core.menus.update',
|
|
'core.dashboard.read',
|
|
'core.dashboard.system-status',
|
|
'core.dashboard.activities',
|
|
'core.activities.read',
|
|
'core.attachments.create', 'core.attachments.update', 'core.attachments.delete',
|
|
'core.notification-logs.read',
|
|
],
|
|
// 권한별 스코프 지정 (self: 본인 소유만, role: 같은 역할 범위, 미지정: 전체)
|
|
'permission_scopes' => [
|
|
'core.users.read' => 'self',
|
|
'core.users.update' => 'self',
|
|
'core.activities.read' => 'self',
|
|
'core.attachments.update' => 'self',
|
|
'core.attachments.delete' => 'self',
|
|
'core.notification-logs.read' => 'self',
|
|
],
|
|
],
|
|
[
|
|
'identifier' => 'user',
|
|
'name' => ['ko' => '일반 사용자', 'en' => 'User'],
|
|
'description' => ['ko' => '기본 사용자 역할입니다.', 'en' => 'Default user role.'],
|
|
'attributes' => ['is_active' => true],
|
|
'permissions' => [
|
|
'core.user-notifications.read', 'core.user-notifications.update', 'core.user-notifications.delete',
|
|
'core.identity.request', 'core.identity.verify', 'core.identity.cancel',
|
|
],
|
|
// verify/cancel 은 본인 challenge(user_id 일치) 만 — PermissionMiddleware 의 scope=self 가드가 자동 검증
|
|
'permission_scopes' => [
|
|
'core.identity.verify' => 'self',
|
|
'core.identity.cancel' => 'self',
|
|
],
|
|
],
|
|
[
|
|
'identifier' => 'guest',
|
|
'name' => ['ko' => '비회원', 'en' => 'Guest'],
|
|
'description' => [
|
|
'ko' => '인증되지 않은 사용자 역할입니다. 관리자가 권한을 부여할 수 있습니다.',
|
|
'en' => 'Unauthenticated user role. Permissions can be granted by administrators.',
|
|
],
|
|
'attributes' => ['is_active' => true],
|
|
// 비로그인 가입(Mode B) · 비밀번호 재설정 등 IDV 진입에 필요 — PermissionMiddleware 가 비인증 시 scope 검사를 자동 스킵하므로 owner 가드는 throttle + UUID 추정 어려움에 의존
|
|
'permissions' => [
|
|
'core.identity.request', 'core.identity.verify', 'core.identity.cancel',
|
|
],
|
|
],
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 코어 메뉴 정의
|
|
|--------------------------------------------------------------------------
|
|
| CoreAdminMenuSeeder 및 CoreUpdateService::syncCoreMenus()에서 사용
|
|
*/
|
|
'menus' => [
|
|
[
|
|
'slug' => 'admin-dashboard',
|
|
'name' => ['ko' => '대시보드', 'en' => 'Dashboard'],
|
|
'url' => '/admin/dashboard',
|
|
'icon' => 'fas fa-tachometer-alt',
|
|
'parent_id' => null,
|
|
'order' => 1,
|
|
'is_active' => true,
|
|
],
|
|
[
|
|
'slug' => 'admin-settings',
|
|
'name' => ['ko' => '환경설정', 'en' => 'Settings'],
|
|
'url' => '/admin/settings',
|
|
'icon' => 'fas fa-cog',
|
|
'parent_id' => null,
|
|
'order' => 2,
|
|
'is_active' => true,
|
|
],
|
|
[
|
|
'slug' => 'admin-notification-logs',
|
|
'name' => ['ko' => '알림 발송 이력', 'en' => 'Notification Logs'],
|
|
'url' => '/admin/notification-logs',
|
|
'icon' => 'fas fa-bell',
|
|
'parent_id' => null,
|
|
'order' => 3,
|
|
'is_active' => true,
|
|
],
|
|
[
|
|
'slug' => 'admin-identity-logs',
|
|
'name' => ['ko' => '본인인증 이력', 'en' => 'Identity Logs'],
|
|
'url' => '/admin/identity/logs',
|
|
'icon' => 'fas fa-clipboard-check',
|
|
'parent_id' => null,
|
|
'order' => 4,
|
|
'is_active' => true,
|
|
],
|
|
[
|
|
'slug' => 'admin-activity-logs',
|
|
'name' => ['ko' => '활동 로그', 'en' => 'Activity Logs'],
|
|
'url' => '/admin/activity-logs',
|
|
'icon' => 'fas fa-history',
|
|
'parent_id' => null,
|
|
'order' => 5,
|
|
'is_active' => true,
|
|
],
|
|
[
|
|
'slug' => 'admin-menus',
|
|
'name' => ['ko' => '메뉴 관리', 'en' => 'Menu Management'],
|
|
'url' => '/admin/menus',
|
|
'icon' => 'fas fa-bars',
|
|
'parent_id' => null,
|
|
'order' => 6,
|
|
'is_active' => true,
|
|
],
|
|
[
|
|
'slug' => 'admin-users',
|
|
'name' => ['ko' => '사용자 관리', 'en' => 'User Management'],
|
|
'url' => '/admin/users',
|
|
'icon' => 'fas fa-users',
|
|
'parent_id' => null,
|
|
'order' => 7,
|
|
'is_active' => true,
|
|
],
|
|
[
|
|
'slug' => 'admin-roles',
|
|
'name' => ['ko' => '권한 관리', 'en' => 'Permission Management'],
|
|
'url' => '/admin/roles',
|
|
'icon' => 'fas fa-lock',
|
|
'parent_id' => null,
|
|
'order' => 8,
|
|
'is_active' => true,
|
|
],
|
|
[
|
|
'slug' => 'admin-modules',
|
|
'name' => ['ko' => '모듈 관리', 'en' => 'Module Management'],
|
|
'url' => '/admin/modules',
|
|
'icon' => 'fas fa-cube',
|
|
'parent_id' => null,
|
|
'order' => 9,
|
|
'is_active' => true,
|
|
],
|
|
[
|
|
'slug' => 'admin-plugins',
|
|
'name' => ['ko' => '플러그인 관리', 'en' => 'Plugin Management'],
|
|
'url' => '/admin/plugins',
|
|
'icon' => 'fas fa-puzzle-piece',
|
|
'parent_id' => null,
|
|
'order' => 10,
|
|
'is_active' => true,
|
|
],
|
|
[
|
|
'slug' => 'admin-templates',
|
|
'name' => ['ko' => '템플릿 관리', 'en' => 'Template Management'],
|
|
'url' => '/admin/templates',
|
|
'icon' => 'fas fa-palette',
|
|
'parent_id' => null,
|
|
'order' => 11,
|
|
'is_active' => true,
|
|
],
|
|
[
|
|
'slug' => 'admin-schedules',
|
|
'name' => ['ko' => '스케쥴 관리', 'en' => 'Schedule Management'],
|
|
'url' => '/admin/schedules',
|
|
'icon' => 'fas fa-clock',
|
|
'parent_id' => null,
|
|
'order' => 12,
|
|
'is_active' => false,
|
|
],
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 코어 알림 정의
|
|
|--------------------------------------------------------------------------
|
|
| NotificationDefinitionSeeder 와 NotificationTemplateService 가 SSoT 로 사용합니다.
|
|
| NotificationSyncHelper 가 upsert 하며, 운영자가 관리자 UI 에서 수정한 필드는
|
|
| user_overrides JSON 에 보존되어 재시딩 시 덮어써지지 않습니다.
|
|
| 모듈/플러그인 자체 알림은 각자의 AbstractModule/AbstractPlugin::getNotificationDefinitions()
|
|
| 에서 선언하며 ModuleManager/PluginManager 가 자동 동기화합니다.
|
|
*/
|
|
'notification_definitions' => [
|
|
'welcome' => [
|
|
'hook_prefix' => 'core.auth',
|
|
'name' => ['ko' => '회원가입 환영', 'en' => 'Welcome'],
|
|
'description' => ['ko' => '회원가입 완료 시 발송되는 환영 알림', 'en' => 'Welcome notification sent upon registration'],
|
|
'channels' => ['mail', 'database'],
|
|
'hooks' => ['core.auth.after_register'],
|
|
'variables' => [
|
|
['key' => 'name', 'description' => '수신자 이름'],
|
|
['key' => 'app_name', 'description' => '사이트명'],
|
|
['key' => 'action_url', 'description' => '로그인 페이지 URL'],
|
|
['key' => 'site_url', 'description' => '사이트 URL'],
|
|
],
|
|
'templates' => [
|
|
[
|
|
'channel' => 'mail',
|
|
'recipients' => [['type' => 'trigger_user']],
|
|
'subject' => [
|
|
'ko' => '[{app_name}] 회원가입을 환영합니다',
|
|
'en' => '[{app_name}] Welcome to Our Service',
|
|
],
|
|
'body' => [
|
|
'ko' => '<h1>{name}님, 환영합니다!</h1>'
|
|
.'<p>{app_name}에 가입해 주셔서 감사합니다.</p>'
|
|
.'<p>이제 모든 서비스를 이용하실 수 있습니다. 아래 버튼을 클릭하여 로그인해 주세요.</p>'
|
|
.'<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin: 24px 0;"><tr><td align="center"><a href="{action_url}" style="display: inline-block; padding: 12px 32px; background-color: #2d3748; color: #ffffff; text-decoration: none; border-radius: 4px; font-weight: 600; font-size: 14px;">로그인하기</a></td></tr></table>'
|
|
.'<p>감사합니다,<br><a href="{site_url}">{app_name}</a></p>',
|
|
'en' => '<h1>Welcome, {name}!</h1>'
|
|
.'<p>Thank you for joining {app_name}.</p>'
|
|
.'<p>You now have access to all our services. Click the button below to log in.</p>'
|
|
.'<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin: 24px 0;"><tr><td align="center"><a href="{action_url}" style="display: inline-block; padding: 12px 32px; background-color: #2d3748; color: #ffffff; text-decoration: none; border-radius: 4px; font-weight: 600; font-size: 14px;">Log In</a></td></tr></table>'
|
|
.'<p>Thank you,<br><a href="{site_url}">{app_name}</a></p>',
|
|
],
|
|
],
|
|
[
|
|
'channel' => 'database',
|
|
'recipients' => [['type' => 'trigger_user']],
|
|
'subject' => ['ko' => '회원가입을 환영합니다', 'en' => 'Welcome to our service'],
|
|
'body' => ['ko' => '{name}님, {app_name}에 가입해 주셔서 감사합니다.', 'en' => 'Welcome {name}, thank you for joining {app_name}.'],
|
|
'click_url' => '/mypage',
|
|
],
|
|
],
|
|
],
|
|
'reset_password' => [
|
|
'hook_prefix' => 'core.auth',
|
|
'name' => ['ko' => '비밀번호 재설정', 'en' => 'Password Reset'],
|
|
'description' => ['ko' => '비밀번호 재설정 요청 시 발송되는 알림', 'en' => 'Notification sent when password reset is requested'],
|
|
'channels' => ['mail', 'database'],
|
|
'hooks' => ['core.auth.after_reset_password_request'],
|
|
'variables' => [
|
|
['key' => 'name', 'description' => '수신자 이름'],
|
|
['key' => 'app_name', 'description' => '사이트명'],
|
|
['key' => 'action_url', 'description' => '비밀번호 재설정 URL'],
|
|
['key' => 'expire_minutes', 'description' => '링크 만료 시간(분)'],
|
|
['key' => 'site_url', 'description' => '사이트 URL'],
|
|
],
|
|
'templates' => [
|
|
[
|
|
'channel' => 'mail',
|
|
'recipients' => [['type' => 'trigger_user']],
|
|
'subject' => [
|
|
'ko' => '[{app_name}] 비밀번호 재설정 안내',
|
|
'en' => '[{app_name}] Password Reset Request',
|
|
],
|
|
'body' => [
|
|
'ko' => '<h1>{name}님, 안녕하세요.</h1>'
|
|
.'<p>비밀번호 재설정 요청을 받았습니다. 아래 버튼을 클릭하여 비밀번호를 재설정해 주세요.</p>'
|
|
.'<p>이 링크는 <strong>{expire_minutes}분</strong> 후 만료됩니다.</p>'
|
|
.'<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin: 24px 0;"><tr><td align="center"><a href="{action_url}" style="display: inline-block; padding: 12px 32px; background-color: #2d3748; color: #ffffff; text-decoration: none; border-radius: 4px; font-weight: 600; font-size: 14px;">비밀번호 재설정</a></td></tr></table>'
|
|
.'<p>비밀번호 재설정을 요청하지 않으셨다면, 이 이메일을 무시해 주세요.</p>'
|
|
.'<p>감사합니다,<br><a href="{site_url}">{app_name}</a></p>',
|
|
'en' => '<h1>Hello, {name}.</h1>'
|
|
.'<p>We received a request to reset your password. Click the button below to set a new password.</p>'
|
|
.'<p>This link will expire in <strong>{expire_minutes} minutes</strong>.</p>'
|
|
.'<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin: 24px 0;"><tr><td align="center"><a href="{action_url}" style="display: inline-block; padding: 12px 32px; background-color: #2d3748; color: #ffffff; text-decoration: none; border-radius: 4px; font-weight: 600; font-size: 14px;">Reset Password</a></td></tr></table>'
|
|
.'<p>If you did not request a password reset, please ignore this email.</p>'
|
|
.'<p>Thank you,<br><a href="{site_url}">{app_name}</a></p>',
|
|
],
|
|
],
|
|
[
|
|
'channel' => 'database',
|
|
'recipients' => [['type' => 'trigger_user']],
|
|
'subject' => ['ko' => '비밀번호 재설정 안내', 'en' => 'Password Reset Request'],
|
|
'body' => ['ko' => '{name}님, 비밀번호 재설정이 요청되었습니다. 본인이 요청하지 않았다면 이 알림을 무시해 주세요.', 'en' => '{name}, a password reset has been requested. If you did not request this, please ignore this notification.'],
|
|
'click_url' => '{action_url}',
|
|
],
|
|
],
|
|
],
|
|
'password_changed' => [
|
|
'hook_prefix' => 'core.auth',
|
|
'name' => ['ko' => '비밀번호 변경', 'en' => 'Password Changed'],
|
|
'description' => ['ko' => '비밀번호 변경 완료 시 발송되는 알림', 'en' => 'Notification sent when password is changed'],
|
|
'channels' => ['mail', 'database'],
|
|
'hooks' => ['core.auth.after_password_changed'],
|
|
'variables' => [
|
|
['key' => 'name', 'description' => '수신자 이름'],
|
|
['key' => 'app_name', 'description' => '사이트명'],
|
|
['key' => 'action_url', 'description' => '로그인 페이지 URL'],
|
|
['key' => 'site_url', 'description' => '사이트 URL'],
|
|
],
|
|
'templates' => [
|
|
[
|
|
'channel' => 'mail',
|
|
'recipients' => [['type' => 'trigger_user']],
|
|
'subject' => [
|
|
'ko' => '[{app_name}] 비밀번호가 변경되었습니다',
|
|
'en' => '[{app_name}] Your Password Has Been Changed',
|
|
],
|
|
'body' => [
|
|
'ko' => '<h1>{name}님, 안녕하세요.</h1>'
|
|
.'<p>계정의 비밀번호가 성공적으로 변경되었습니다.</p>'
|
|
.'<p><strong>본인이 변경하지 않았다면, 즉시 고객 지원팀에 문의하시기 바랍니다.</strong></p>'
|
|
.'<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin: 24px 0;"><tr><td align="center"><a href="{action_url}" style="display: inline-block; padding: 12px 32px; background-color: #2d3748; color: #ffffff; text-decoration: none; border-radius: 4px; font-weight: 600; font-size: 14px;">로그인하기</a></td></tr></table>'
|
|
.'<p>감사합니다,<br><a href="{site_url}">{app_name}</a></p>',
|
|
'en' => '<h1>Hello, {name}.</h1>'
|
|
.'<p>Your account password has been successfully changed.</p>'
|
|
.'<p><strong>If you did not make this change, please contact our support team immediately.</strong></p>'
|
|
.'<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin: 24px 0;"><tr><td align="center"><a href="{action_url}" style="display: inline-block; padding: 12px 32px; background-color: #2d3748; color: #ffffff; text-decoration: none; border-radius: 4px; font-weight: 600; font-size: 14px;">Log In</a></td></tr></table>'
|
|
.'<p>Thank you,<br><a href="{site_url}">{app_name}</a></p>',
|
|
],
|
|
],
|
|
[
|
|
'channel' => 'database',
|
|
'recipients' => [['type' => 'trigger_user']],
|
|
'subject' => ['ko' => '비밀번호가 변경되었습니다', 'en' => 'Your password has been changed'],
|
|
'body' => ['ko' => '{name}님, 비밀번호가 변경되었습니다. 본인이 변경하지 않았다면 즉시 고객 지원팀에 문의하시기 바랍니다.', 'en' => '{name}, your password has been changed. If you did not make this change, please contact support immediately.'],
|
|
'click_url' => '/mypage/change-password',
|
|
],
|
|
],
|
|
],
|
|
|
|
// 사이트맵 재생성 완료 — 관리자 수동 재생성(SEO 탭 "지금 생성")에 한해, 실행한 관리자에게 발송.
|
|
// 스케줄러/증분/봇 재생성은 SeoNotificationDataListener 가 context.skip 으로 제외.
|
|
// 기본 활성 채널은 database(앱 내 알림)만. mail 템플릿은 존재하되 비활성(운영자가 필요 시 활성화).
|
|
'sitemap_regenerated' => [
|
|
'hook_prefix' => 'core.seo',
|
|
'name' => ['ko' => '사이트맵 재생성 완료', 'en' => 'Sitemap Regeneration Complete'],
|
|
'description' => ['ko' => '관리자가 수동으로 실행한 사이트맵 재생성이 완료되면 실행한 관리자에게 발송되는 알림', 'en' => 'Notification sent to the admin who manually triggered sitemap regeneration when it completes'],
|
|
'channels' => ['database'],
|
|
'hooks' => ['core.seo.sitemap.after_regenerate'],
|
|
'variables' => [
|
|
['key' => 'app_name', 'description' => '사이트명'],
|
|
['key' => 'url_count', 'description' => '생성된 총 URL 수'],
|
|
['key' => 'child_count', 'description' => '생성된 사이트맵 파일 수'],
|
|
['key' => 'action_url', 'description' => 'SEO 설정 페이지 URL'],
|
|
['key' => 'site_url', 'description' => '사이트 URL'],
|
|
],
|
|
'templates' => [
|
|
[
|
|
'channel' => 'mail',
|
|
'is_active' => false,
|
|
'recipients' => [['type' => 'trigger_user']],
|
|
'subject' => [
|
|
'ko' => '[{app_name}] 사이트맵 재생성이 완료되었습니다',
|
|
'en' => '[{app_name}] Sitemap Regeneration Complete',
|
|
],
|
|
'body' => [
|
|
'ko' => '<h1>사이트맵 재생성 완료</h1>'
|
|
.'<p>요청하신 사이트맵 재생성이 완료되었습니다.</p>'
|
|
.'<p>총 <strong>{url_count}</strong>개의 URL 이 <strong>{child_count}</strong>개의 파일로 생성되었습니다.</p>'
|
|
.'<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin: 24px 0;"><tr><td align="center"><a href="{action_url}" style="display: inline-block; padding: 12px 32px; background-color: #2d3748; color: #ffffff; text-decoration: none; border-radius: 4px; font-weight: 600; font-size: 14px;">SEO 설정 보기</a></td></tr></table>'
|
|
.'<p>감사합니다,<br><a href="{site_url}">{app_name}</a></p>',
|
|
'en' => '<h1>Sitemap Regeneration Complete</h1>'
|
|
.'<p>The sitemap regeneration you requested has completed.</p>'
|
|
.'<p>A total of <strong>{url_count}</strong> URLs were generated across <strong>{child_count}</strong> file(s).</p>'
|
|
.'<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin: 24px 0;"><tr><td align="center"><a href="{action_url}" style="display: inline-block; padding: 12px 32px; background-color: #2d3748; color: #ffffff; text-decoration: none; border-radius: 4px; font-weight: 600; font-size: 14px;">View SEO Settings</a></td></tr></table>'
|
|
.'<p>Thank you,<br><a href="{site_url}">{app_name}</a></p>',
|
|
],
|
|
],
|
|
[
|
|
'channel' => 'database',
|
|
'recipients' => [['type' => 'trigger_user']],
|
|
'subject' => ['ko' => '사이트맵 재생성 완료', 'en' => 'Sitemap regeneration complete'],
|
|
'body' => ['ko' => '요청하신 사이트맵 재생성이 완료되었습니다. 총 {url_count}개 URL, {child_count}개 파일이 생성되었습니다.', 'en' => 'Your sitemap regeneration is complete. {url_count} URLs across {child_count} file(s) were generated.'],
|
|
'click_url' => '/admin/settings?tab=seo',
|
|
],
|
|
],
|
|
],
|
|
|
|
// 사이트맵 재생성 실패 — 관리자 수동 재생성이 실패하면 실행한 관리자에게 발송.
|
|
'sitemap_regenerate_failed' => [
|
|
'hook_prefix' => 'core.seo',
|
|
'name' => ['ko' => '사이트맵 재생성 실패', 'en' => 'Sitemap Regeneration Failed'],
|
|
'description' => ['ko' => '관리자가 수동으로 실행한 사이트맵 재생성이 최종 실패(재시도 소진)하면 실행한 관리자에게 발송되는 알림', 'en' => 'Notification sent to the admin who manually triggered sitemap regeneration when it finally fails after retries'],
|
|
'channels' => ['database'],
|
|
// 재시도 소진 시 1회만 발화하는 최종 실패 훅에 구독 (매 시도 발화하는 after_regenerate_failed 아님)
|
|
'hooks' => ['core.seo.sitemap.regenerate_failed_final'],
|
|
'variables' => [
|
|
['key' => 'app_name', 'description' => '사이트명'],
|
|
['key' => 'error', 'description' => '실패 사유 메시지'],
|
|
['key' => 'action_url', 'description' => 'SEO 설정 페이지 URL'],
|
|
['key' => 'site_url', 'description' => '사이트 URL'],
|
|
],
|
|
'templates' => [
|
|
[
|
|
'channel' => 'mail',
|
|
'is_active' => false,
|
|
'recipients' => [['type' => 'trigger_user']],
|
|
'subject' => [
|
|
'ko' => '[{app_name}] 사이트맵 재생성이 실패했습니다',
|
|
'en' => '[{app_name}] Sitemap Regeneration Failed',
|
|
],
|
|
'body' => [
|
|
'ko' => '<h1>사이트맵 재생성 실패</h1>'
|
|
.'<p>요청하신 사이트맵 재생성이 실패했습니다.</p>'
|
|
.'<p>오류: <strong>{error}</strong></p>'
|
|
.'<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin: 24px 0;"><tr><td align="center"><a href="{action_url}" style="display: inline-block; padding: 12px 32px; background-color: #2d3748; color: #ffffff; text-decoration: none; border-radius: 4px; font-weight: 600; font-size: 14px;">SEO 설정 보기</a></td></tr></table>'
|
|
.'<p>감사합니다,<br><a href="{site_url}">{app_name}</a></p>',
|
|
'en' => '<h1>Sitemap Regeneration Failed</h1>'
|
|
.'<p>The sitemap regeneration you requested has failed.</p>'
|
|
.'<p>Error: <strong>{error}</strong></p>'
|
|
.'<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin: 24px 0;"><tr><td align="center"><a href="{action_url}" style="display: inline-block; padding: 12px 32px; background-color: #2d3748; color: #ffffff; text-decoration: none; border-radius: 4px; font-weight: 600; font-size: 14px;">View SEO Settings</a></td></tr></table>'
|
|
.'<p>Thank you,<br><a href="{site_url}">{app_name}</a></p>',
|
|
],
|
|
],
|
|
[
|
|
'channel' => 'database',
|
|
'recipients' => [['type' => 'trigger_user']],
|
|
'subject' => ['ko' => '사이트맵 재생성 실패', 'en' => 'Sitemap regeneration failed'],
|
|
'body' => ['ko' => '요청하신 사이트맵 재생성이 실패했습니다. 오류: {error}', 'en' => 'Your sitemap regeneration failed. Error: {error}'],
|
|
'click_url' => '/admin/settings?tab=seo',
|
|
],
|
|
],
|
|
],
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 코어 본인인증(IDV) 정책
|
|
|--------------------------------------------------------------------------
|
|
| IdentityPolicySeeder 에서 사용됩니다. IdentityPolicySyncHelper 가 upsert 하며,
|
|
| 운영자가 S1d UI 에서 수정한 필드는 user_overrides JSON 에 기록되어 재시딩 시 보존됩니다.
|
|
*/
|
|
'identity_policies' => [
|
|
// 회원가입 — Mode B (route 단계, verification_token 검증 필수)
|
|
'core.auth.signup_before_submit' => [
|
|
'scope' => 'route',
|
|
'target' => 'api.auth.register',
|
|
'purpose' => 'signup',
|
|
'provider_id' => null,
|
|
'grace_minutes' => 0,
|
|
'enabled' => false,
|
|
'applies_to' => 'self',
|
|
'fail_mode' => 'block',
|
|
'priority' => 110,
|
|
'conditions' => ['signup_stage' => 'before_submit', 'http_method' => ['POST']],
|
|
],
|
|
|
|
// 회원가입 — Mode C (hook 단계, after_register 시 challenge 발행 + PendingVerification)
|
|
'core.auth.signup_after_create' => [
|
|
'scope' => 'hook',
|
|
'target' => 'core.auth.after_register',
|
|
'purpose' => 'signup',
|
|
'provider_id' => null,
|
|
'grace_minutes' => 0,
|
|
'enabled' => false,
|
|
'applies_to' => 'self',
|
|
'fail_mode' => 'block',
|
|
'priority' => 100,
|
|
'conditions' => ['signup_stage' => 'after_create'],
|
|
],
|
|
|
|
// 비밀번호 재설정 — 기존 forgotPassword 흐름이 IDV 인프라 경유 (안전 기본 OFF)
|
|
'core.auth.password_reset' => [
|
|
'scope' => 'hook',
|
|
'target' => 'core.auth.before_reset_password',
|
|
'purpose' => 'password_reset',
|
|
'provider_id' => null,
|
|
'grace_minutes' => 0,
|
|
'enabled' => false,
|
|
'applies_to' => 'both',
|
|
'fail_mode' => 'block',
|
|
'priority' => 100,
|
|
],
|
|
|
|
// 비밀번호 변경 (로그인 상태 자기 변경)
|
|
'core.profile.password_change' => [
|
|
'scope' => 'route',
|
|
'target' => 'api.me.password',
|
|
'purpose' => 'sensitive_action',
|
|
'grace_minutes' => 5,
|
|
'enabled' => true,
|
|
'applies_to' => 'self',
|
|
'fail_mode' => 'block',
|
|
],
|
|
|
|
// 민감 정보 변경 (이메일/전화 변경)
|
|
'core.profile.contact_change' => [
|
|
'scope' => 'hook',
|
|
'target' => 'core.user.before_update',
|
|
'purpose' => 'sensitive_action',
|
|
'grace_minutes' => 5,
|
|
'enabled' => true,
|
|
'applies_to' => 'self',
|
|
'fail_mode' => 'block',
|
|
'conditions' => ['changed_fields' => ['email', 'phone', 'mobile']],
|
|
],
|
|
|
|
// 계정 탈퇴
|
|
'core.account.withdraw' => [
|
|
'scope' => 'route',
|
|
'target' => 'api.me.destroy',
|
|
'purpose' => 'sensitive_action',
|
|
'grace_minutes' => 0,
|
|
'enabled' => true,
|
|
'applies_to' => 'self',
|
|
'fail_mode' => 'block',
|
|
],
|
|
|
|
// 관리자: App Key 재생성
|
|
'core.admin.app_key_regenerate' => [
|
|
'scope' => 'route',
|
|
'target' => 'api.admin.settings.regenerate-app-key',
|
|
'purpose' => 'sensitive_action',
|
|
'grace_minutes' => 0,
|
|
'enabled' => false,
|
|
'applies_to' => 'admin',
|
|
'fail_mode' => 'block',
|
|
],
|
|
|
|
// 관리자: 사용자 삭제
|
|
'core.admin.user_delete' => [
|
|
'scope' => 'hook',
|
|
'target' => 'core.user.before_delete',
|
|
'purpose' => 'sensitive_action',
|
|
'grace_minutes' => 0,
|
|
'enabled' => false,
|
|
'applies_to' => 'admin',
|
|
'fail_mode' => 'block',
|
|
],
|
|
|
|
// 관리자: 모듈/플러그인 제거
|
|
'core.admin.extension_uninstall' => [
|
|
'scope' => 'route',
|
|
'target' => 'api.admin.{modules,plugins}.uninstall',
|
|
'purpose' => 'sensitive_action',
|
|
'grace_minutes' => 0,
|
|
'enabled' => false,
|
|
'applies_to' => 'admin',
|
|
'fail_mode' => 'block',
|
|
],
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 코어 본인인증(IDV) 메시지 정의/템플릿
|
|
|--------------------------------------------------------------------------
|
|
| IdentityMessageDefinitionSeeder 에서 사용됩니다. IdentityMessageSyncHelper 가
|
|
| upsert 하며, 운영자가 관리자 UI 에서 수정한 필드는 user_overrides JSON 에 보존됩니다.
|
|
| 모듈/플러그인 자체 IDV 메시지는 각자의 getIdentityMessages() 에서 선언합니다.
|
|
|
|
|
| 각 항목 키는 사람이 읽기 쉬운 식별자이며, 실제 unique 키는 (provider_id, scope_type, scope_value) 조합입니다.
|
|
| scope_type: 'provider_default' | 'purpose' | 'policy'
|
|
|
|
|
| 코어 변수(commonVariables) 는 모든 mail 정의 공통:
|
|
| code, action_url, expire_minutes, purpose_label, app_name, site_url, recipient_email
|
|
*/
|
|
'identity_messages' => [
|
|
'mail.provider_default' => [
|
|
'provider_id' => 'g7:core.mail',
|
|
'scope_type' => 'provider_default',
|
|
'scope_value' => '',
|
|
'name' => ['ko' => '메일 본인 확인 (기본)', 'en' => 'Mail Verification (default)'],
|
|
'description' => [
|
|
'ko' => '특정 목적이 매칭되지 않을 때 사용되는 기본 메일 템플릿',
|
|
'en' => 'Fallback mail template used when no specific purpose matches',
|
|
],
|
|
'channels' => ['mail'],
|
|
'variables' => '__common__',
|
|
'templates' => [
|
|
[
|
|
'channel' => 'mail',
|
|
'subject' => [
|
|
'ko' => '[{app_name}] 본인 확인 인증 코드',
|
|
'en' => '[{app_name}] Verification Code',
|
|
],
|
|
'body' => [
|
|
'ko' => '<p>안녕하세요.</p>'
|
|
.'<p>아래 인증 코드를 입력해 본인 확인을 완료해 주세요.</p>'
|
|
.'<p style="font-size:24px; font-weight:bold; letter-spacing:4px; text-align:center; padding:16px; background:#f4f6f8; border-radius:6px;">{code}</p>'
|
|
.'<p>이 코드는 <strong>{expire_minutes}분</strong> 후 만료됩니다.</p>'
|
|
.'<p>본인이 요청하지 않았다면 이 메일을 무시해 주세요.</p>'
|
|
.'<p>감사합니다,<br><a href="{site_url}">{app_name}</a></p>',
|
|
'en' => '<p>Hello.</p>'
|
|
.'<p>Please enter the verification code below to confirm your identity.</p>'
|
|
.'<p style="font-size:24px; font-weight:bold; letter-spacing:4px; text-align:center; padding:16px; background:#f4f6f8; border-radius:6px;">{code}</p>'
|
|
.'<p>This code will expire in <strong>{expire_minutes} minutes</strong>.</p>'
|
|
.'<p>If you did not request this, please ignore this email.</p>'
|
|
.'<p>Thank you,<br><a href="{site_url}">{app_name}</a></p>',
|
|
],
|
|
],
|
|
],
|
|
],
|
|
|
|
'mail.purpose.signup' => [
|
|
'provider_id' => 'g7:core.mail',
|
|
'scope_type' => 'purpose',
|
|
'scope_value' => 'signup',
|
|
'name' => ['ko' => '회원가입 인증', 'en' => 'Signup Verification'],
|
|
'description' => [
|
|
'ko' => '회원가입 단계에서 이메일 본인 확인 시 발송',
|
|
'en' => 'Sent when verifying email during signup',
|
|
],
|
|
'channels' => ['mail'],
|
|
'variables' => '__common__',
|
|
'templates' => [
|
|
[
|
|
'channel' => 'mail',
|
|
'subject' => [
|
|
'ko' => '[{app_name}] 회원가입 인증 코드',
|
|
'en' => '[{app_name}] Signup Verification Code',
|
|
],
|
|
'body' => [
|
|
'ko' => '<h1>{app_name} 회원가입 인증</h1>'
|
|
.'<p>아래 인증 코드를 회원가입 화면에 입력해 주세요.</p>'
|
|
.'<p style="font-size:28px; font-weight:bold; letter-spacing:4px; text-align:center; padding:16px; background:#f4f6f8; border-radius:6px;">{code}</p>'
|
|
.'<p>이 코드는 <strong>{expire_minutes}분</strong> 후 만료됩니다.</p>'
|
|
.'<p>본인이 가입을 시도하지 않았다면 이 메일을 무시해 주세요.</p>'
|
|
.'<p>감사합니다,<br><a href="{site_url}">{app_name}</a></p>',
|
|
'en' => '<h1>{app_name} Signup Verification</h1>'
|
|
.'<p>Please enter the verification code below on the signup screen.</p>'
|
|
.'<p style="font-size:28px; font-weight:bold; letter-spacing:4px; text-align:center; padding:16px; background:#f4f6f8; border-radius:6px;">{code}</p>'
|
|
.'<p>This code will expire in <strong>{expire_minutes} minutes</strong>.</p>'
|
|
.'<p>If you did not request this, please ignore this email.</p>'
|
|
.'<p>Thank you,<br><a href="{site_url}">{app_name}</a></p>',
|
|
],
|
|
],
|
|
],
|
|
],
|
|
|
|
'mail.purpose.password_reset' => [
|
|
'provider_id' => 'g7:core.mail',
|
|
'scope_type' => 'purpose',
|
|
'scope_value' => 'password_reset',
|
|
'name' => ['ko' => '비밀번호 재설정 링크', 'en' => 'Password Reset Link'],
|
|
'description' => [
|
|
'ko' => '비밀번호 재설정 요청 시 발송되는 서명 링크 메일 (link 흐름)',
|
|
'en' => 'Signed-link email sent on password reset request (link flow)',
|
|
],
|
|
'channels' => ['mail'],
|
|
'variables' => '__common__',
|
|
'templates' => [
|
|
[
|
|
'channel' => 'mail',
|
|
'subject' => [
|
|
'ko' => '[{app_name}] 비밀번호 재설정 안내',
|
|
'en' => '[{app_name}] Password Reset Request',
|
|
],
|
|
'body' => [
|
|
'ko' => '<h1>비밀번호 재설정</h1>'
|
|
.'<p>비밀번호 재설정 요청을 받았습니다. 아래 버튼을 클릭하여 비밀번호를 재설정해 주세요.</p>'
|
|
.'<p>이 링크는 <strong>{expire_minutes}분</strong> 후 만료됩니다.</p>'
|
|
.'<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:24px 0;"><tr><td align="center"><a href="{action_url}" style="display:inline-block; padding:12px 32px; background-color:#2d3748; color:#ffffff; text-decoration:none; border-radius:4px; font-weight:600; font-size:14px;">비밀번호 재설정</a></td></tr></table>'
|
|
.'<p>본인이 요청하지 않았다면 이 메일을 무시해 주세요.</p>'
|
|
.'<p>감사합니다,<br><a href="{site_url}">{app_name}</a></p>',
|
|
'en' => '<h1>Password Reset</h1>'
|
|
.'<p>We received a password reset request. Click the button below to set a new password.</p>'
|
|
.'<p>This link will expire in <strong>{expire_minutes} minutes</strong>.</p>'
|
|
.'<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:24px 0;"><tr><td align="center"><a href="{action_url}" style="display:inline-block; padding:12px 32px; background-color:#2d3748; color:#ffffff; text-decoration:none; border-radius:4px; font-weight:600; font-size:14px;">Reset Password</a></td></tr></table>'
|
|
.'<p>If you did not request this, please ignore this email.</p>'
|
|
.'<p>Thank you,<br><a href="{site_url}">{app_name}</a></p>',
|
|
],
|
|
],
|
|
],
|
|
],
|
|
|
|
'mail.purpose.self_update' => [
|
|
'provider_id' => 'g7:core.mail',
|
|
'scope_type' => 'purpose',
|
|
'scope_value' => 'self_update',
|
|
'name' => ['ko' => '계정 정보 변경 인증', 'en' => 'Self-update Verification'],
|
|
'description' => [
|
|
'ko' => '계정 정보 변경 시 본인 확인 코드 메일',
|
|
'en' => 'Verification code mail when updating account info',
|
|
],
|
|
'channels' => ['mail'],
|
|
'variables' => '__common__',
|
|
'templates' => [
|
|
[
|
|
'channel' => 'mail',
|
|
'subject' => [
|
|
'ko' => '[{app_name}] 계정 정보 변경 인증 코드',
|
|
'en' => '[{app_name}] Account Update Verification Code',
|
|
],
|
|
'body' => [
|
|
'ko' => '<h1>계정 정보 변경 인증</h1>'
|
|
.'<p>{purpose_label} 처리를 위해 아래 인증 코드를 입력해 주세요.</p>'
|
|
.'<p style="font-size:28px; font-weight:bold; letter-spacing:4px; text-align:center; padding:16px; background:#f4f6f8; border-radius:6px;">{code}</p>'
|
|
.'<p>이 코드는 <strong>{expire_minutes}분</strong> 후 만료됩니다.</p>'
|
|
.'<p><strong>본인이 변경을 시도하지 않았다면 즉시 비밀번호를 변경하시기 바랍니다.</strong></p>'
|
|
.'<p>감사합니다,<br><a href="{site_url}">{app_name}</a></p>',
|
|
'en' => '<h1>Account Update Verification</h1>'
|
|
.'<p>Please enter the code below to proceed with {purpose_label}.</p>'
|
|
.'<p style="font-size:28px; font-weight:bold; letter-spacing:4px; text-align:center; padding:16px; background:#f4f6f8; border-radius:6px;">{code}</p>'
|
|
.'<p>This code will expire in <strong>{expire_minutes} minutes</strong>.</p>'
|
|
.'<p><strong>If you did not initiate this change, please change your password immediately.</strong></p>'
|
|
.'<p>Thank you,<br><a href="{site_url}">{app_name}</a></p>',
|
|
],
|
|
],
|
|
],
|
|
],
|
|
|
|
'mail.purpose.sensitive_action' => [
|
|
'provider_id' => 'g7:core.mail',
|
|
'scope_type' => 'purpose',
|
|
'scope_value' => 'sensitive_action',
|
|
'name' => ['ko' => '중요 작업 인증', 'en' => 'Sensitive Action Verification'],
|
|
'description' => [
|
|
'ko' => '결제, 주요 설정 변경 등 중요 작업 시 본인 확인 코드 메일',
|
|
'en' => 'Verification code mail for sensitive actions (payment, critical settings)',
|
|
],
|
|
'channels' => ['mail'],
|
|
'variables' => '__common__',
|
|
'templates' => [
|
|
[
|
|
'channel' => 'mail',
|
|
'subject' => [
|
|
'ko' => '[{app_name}] 중요 작업 인증 코드',
|
|
'en' => '[{app_name}] Sensitive Action Verification Code',
|
|
],
|
|
'body' => [
|
|
'ko' => '<h1>중요 작업 인증</h1>'
|
|
.'<p>{purpose_label} 진행을 위해 아래 인증 코드를 입력해 주세요.</p>'
|
|
.'<p style="font-size:28px; font-weight:bold; letter-spacing:4px; text-align:center; padding:16px; background:#f4f6f8; border-radius:6px;">{code}</p>'
|
|
.'<p>이 코드는 <strong>{expire_minutes}분</strong> 후 만료됩니다.</p>'
|
|
.'<p><strong>본인이 시도하지 않았다면 즉시 고객 지원팀에 문의해 주세요.</strong></p>'
|
|
.'<p>감사합니다,<br><a href="{site_url}">{app_name}</a></p>',
|
|
'en' => '<h1>Sensitive Action Verification</h1>'
|
|
.'<p>Please enter the code below to proceed with {purpose_label}.</p>'
|
|
.'<p style="font-size:28px; font-weight:bold; letter-spacing:4px; text-align:center; padding:16px; background:#f4f6f8; border-radius:6px;">{code}</p>'
|
|
.'<p>This code will expire in <strong>{expire_minutes} minutes</strong>.</p>'
|
|
.'<p><strong>If you did not initiate this action, please contact support immediately.</strong></p>'
|
|
.'<p>Thank you,<br><a href="{site_url}">{app_name}</a></p>',
|
|
],
|
|
],
|
|
],
|
|
],
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 코어 본인인증(IDV) 목적 (purpose) 메타데이터
|
|
|--------------------------------------------------------------------------
|
|
| App\Enums\IdentityVerificationPurpose enum 이 코드 계약(타입 안전) 을 담당하고,
|
|
| 본 블록은 라벨/설명/기본 provider/허용 채널 등 운영 메타데이터의 SSoT 입니다.
|
|
| IdentityVerificationManager::registerCorePurposes() 가 부팅 시 본 블록을 로드하며,
|
|
| 모듈/플러그인은 각자의 getIdentityPurposes() 에서 추가 purpose 를 선언합니다.
|
|
| 라벨은 lang/{locale}/identity.php::purposes.{key}.label 가 우선 (다국어 정합).
|
|
*/
|
|
'identity_purposes' => [
|
|
'signup' => [
|
|
// 'label' / 'description' 의 값은 i18n 키 문자열. resolvePurposeText 가 __() 로 풀이.
|
|
// (`label_key` / `description_key` 명명은 controller 쪽이 'label'/'description' 만
|
|
// 인식해 동작하지 않으므로 표준 명명으로 정렬)
|
|
'label' => 'identity.purposes.signup.label',
|
|
'description' => 'identity.purposes.signup.description',
|
|
'default_provider' => 'g7:core.mail',
|
|
'allowed_channels' => ['mail', 'sms'],
|
|
],
|
|
'password_reset' => [
|
|
'label' => 'identity.purposes.password_reset.label',
|
|
'description' => 'identity.purposes.password_reset.description',
|
|
'default_provider' => 'g7:core.mail',
|
|
'allowed_channels' => ['mail', 'sms'],
|
|
],
|
|
'self_update' => [
|
|
'label' => 'identity.purposes.self_update.label',
|
|
'description' => 'identity.purposes.self_update.description',
|
|
'default_provider' => 'g7:core.mail',
|
|
'allowed_channels' => ['mail', 'sms'],
|
|
],
|
|
'sensitive_action' => [
|
|
'label' => 'identity.purposes.sensitive_action.label',
|
|
'description' => 'identity.purposes.sensitive_action.description',
|
|
'default_provider' => 'g7:core.mail',
|
|
'allowed_channels' => ['mail', 'sms'],
|
|
],
|
|
],
|
|
|
|
];
|