2단계 인증은 7.0.6 에서 서버측이 갖춰졌지만 인증번호를 입력할 화면이 어느 버전에도
없었다. 그래서 그 설정을 켠 사이트는 관리자를 포함한 전원이 로그인할 수 없었다.
원인은 `POST /api/auth/login` 이 조건에 따라 **다른 형태의 200** 을 돌려준다는 것이다.
평소에는 `{token, user}` 지만 2단계 인증이 켜져 있으면 `{two_factor_required,
challenge_id, ...}` 를 돌려준다. 프론트는 앞의 형태만 선언하고 `response.data.user.language`
를 바로 읽었으므로 그 자리에서 TypeError 가 났고, 영문 원문이 로그인 화면에 그대로 노출됐다.
서버는 정상 응답했으므로 서버 로그에는 아무 흔적도 남지 않는다.
이어서 `setToken(undefined)` 가 `localStorage` 에 문자열 `"undefined"` 를 남겼다.
이 값은 truthy 라 이후 모든 요청이 `Bearer undefined` 로 나가 401 이 되고, 사용자에게는
「세션이 만료되었습니다」로 보인다. 관리자 로그인은 한발 더 나가 `null->isAdmin` 으로
500 이 되어, 설정을 되돌릴 수단까지 함께 사라졌다.
## 구현
- 로그인 응답을 판별 유니온(`LoginResult`)으로 표현하고, 형태를 판별한 뒤에 읽는다.
`ApiClient.setToken` 은 비어 있지 않은 문자열만 저장한다.
- 사용자·관리자 로그인 화면에 인증번호 입력 단계를 추가했다. 같은 카드 안에서 넘어가며
「인증번호 다시 받기」와 「처음부터」를 제공한다. 관리자 판정은 코드 확인에 성공한 뒤에
수행하고, 거부할 때는 그 직전에 발급된 토큰을 회수한다.
- 재발송(`login/two-factor/resend`)은 기존 challenge 를 취소하고 새로 발행한다. 유효한
코드를 여러 개 살려 두면 대입 시도의 표적이 넓어진다.
- 인증번호를 보내지 못하면 401 이 아니라 503 으로 답한다. 자격 증명은 올바른데 401 로
뭉개면 사용자는 비밀번호를 의심하며 같은 시도를 반복하고, 운영자는 메일 설정이 깨진
사실을 알 방법이 없다.
- 공개 본인인증 경로(`identity/verify`·`cancel`)가 로그인 목적의 challenge 를 소진하지
못하도록 403 게이트를 세웠다. 소진되면 그 challenge 로 영영 로그인할 수 없다.
- 로그인 시도 제한 429 응답이 다국어 문구를 싣도록 했다(종전에는 프레임워크 기본 영문).
- 다국어 파라미터에서 파이프 표현식이 평가되지 않아 「유효시간 까지」처럼 값이 빠지던
문제를 함께 고쳤다. 같은 결함이 문의 목록 화면에도 있었다.
## 이번 점검에서 함께 고친 것
- 계정 잠금(423)·발송 실패(503) 응답이 사용자·관리자 컨트롤러에 동일하게 복제돼 있었고
그 주석 자신은 "단일 지점에서 만든다" 고 적혀 있었다. 페이로드에 필드가 하나 추가되면
한쪽만 따라가 같은 실패를 두 화면이 다르게 안내하게 된다 — 트레이트로 통합했다.
- 테스트가 개발자 자신의 사이트 설정을 읽고 있었다. 2단계 인증을 켜 둔 환경에서는 로그인
성공을 전제한 테스트가 503 으로 깨지는데 실패 메시지가 원인을 가리키지도 않는다.
같은 결함군을 위해 이미 존재하던 단일 지점에 그 축을 추가했다.
## 버전
코어 7.0.11 · sirsoft-basic 1.1.4 · sirsoft-admin_basic 1.0.9 ·
번들 일본어팩 3종 · 템플릿 엔진 engine-v1.65.0.
그누보드7 문서 인덱스
그누보드7 오픈소스 CMS 플랫폼의 개발 가이드 문서입니다.
카테고리별 통계
| 카테고리 | 문서 수 | 링크 상태 |
|---|---|---|
| 백엔드 | 37개 | 정상 |
| 프론트엔드 | 46개 | 정상 |
| 확장 시스템 | 32개 | 정상 |
| 공통 | 20개 | 정상 |
| AI 도구 | - | 정상 |
API 레퍼런스
G7 이 제공하는 REST API 의 엔드포인트별 요청 파라미터·응답 필드·요청/응답 예시입니다.
템플릿의 data_sources 를 작성하거나 외부에서 G7 을 연동할 때 참고하세요.
| 문서 | 설명 |
|---|---|
| backend/api/README.md | API 레퍼런스 진입점 — 공통 규약(인증·응답 봉투·페이지네이션·에러) + 코어/확장 전체 목차 |
| backend/api-documentation.md | API 문서 작성·갱신 규정 (기여자용) |
작업 유형별 필수 문서
레이아웃 JSON 작성
| 순서 | 문서 | TL;DR 핵심 |
|---|---|---|
| 1 | 레이아웃 JSON 스키마 | HTML 태그 직접 사용 금지 → 기본 컴포넌트 사용 (Div, Button, Span) |
| 2 | 레이아웃 JSON - 컴포넌트 | if: 조건부 렌더링 (type: "conditional" 사용 금지!) |
| 3 | 레이아웃 JSON - 기능 | classMap: 조건부 CSS 클래스 (key → variants 매핑) |
| 4 | 레이아웃 JSON - 상속 | extends: 베이스 레이아웃 상속 (type: "slot" 위치에 삽입) |
| 5 | 컴포넌트 개발 규칙 | HTML 태그 직접 사용 금지 |
| 6 | 컴포넌트 Props 레퍼런스 | - |
| 7 | sirsoft-basic 컴포넌트 | 확장 소유 문서 (basic / composite / layout) |
| 8 | 데이터 바인딩 및 표현식 | API 데이터: {{user.name}}, URL 파라미터: {{route.id}} |
| 9 | 데이터 바인딩 - 다국어 처리 | - |
| 10 | 액션 핸들러 가이드 | 구조: type 또는 event(이벤트), handler(핸들러명), params(옵션) |
| 11 | 액션 핸들러 - 핸들러별 상세 사용법 | navigate: 페이지 이동 (path, query, mergeQuery 옵션) |
| 12 | 전역 상태 관리 | 전역 상태: _global.속성명 (앱 전체 공유, 페이지 이동 시 유지) |
| 13 | 데이터 소스 | data_sources 배열에 API 정의: id, endpoint, method |
| 14 | 다크 모드 지원 | Tailwind dark: variant 사용 |
sirsoft-admin_basic 컴포넌트 문서는 확장이 소유합니다 — templates/_bundled/sirsoft-admin_basic/docs/components.md 를 참고하세요.
컨트롤러 작성
| 순서 | 문서 | TL;DR 핵심 |
|---|---|---|
| 1 | 컨트롤러 계층 구조 | AdminBaseController / AuthBaseController / PublicBaseController |
| 2 | 라우트 네이밍 및 경로 | 모든 라우트는 name() 필수 |
| 3 | 검증 (Validation) | FormRequest 사용 필수 (Service에 검증 로직 금지) |
| 4 | API 응답 규칙 (ResponseHelper) | 모든 API 응답은 ResponseHelper 사용 |
Service/Repository 작성
| 순서 | 문서 | TL;DR 핵심 |
|---|---|---|
| 1 | Service-Repository 패턴 | RepositoryInterface 주입 필수 (구체 클래스 직접 주입 금지) |
| 2 | 훅 시스템 | Action 훅: doAction() - 부가 작업 (로그, 알림, 캐시) |
마이그레이션 작성
| 순서 | 문서 | TL;DR 핵심 |
|---|---|---|
| 1 | 데이터베이스 개발 가이드 | 마이그레이션: 한국어 comment 필수, down() 구현 필수 |
모듈 개발
| 순서 | 문서 | TL;DR 핵심 |
|---|---|---|
| 1 | 모듈 개발 기초 | 디렉토리: vendor-module (예: sirsoft-ecommerce) |
| 2 | 모듈 라우트 규칙 | URL prefix 자동: /api/admin/[vendor-module]/... |
| 3 | 모듈 레이아웃 시스템 | modules/_bundled/vendor-module/resources/layouts/ |
| 4 | 모듈 다국어 시스템 | 백엔드: /lang/{locale}/*.php |
| 5 | 훅 시스템 | Action 훅: doAction() |
테스트 작성
| 순서 | 문서 | TL;DR 핵심 |
|---|---|---|
| 1 | 테스트 가이드 | 테스트 통과 = 작업 완료 (작성만으로 불충분!) |
| 2 | 레이아웃 렌더링 테스트 가이드 | createLayoutTest()로 테스트 헬퍼 생성 |
API 리소스 작성
| 순서 | 문서 | TL;DR 핵심 |
|---|---|---|
| 1 | API 리소스 | BaseApiResource 상속 필수 |
| 2 | API 응답 규칙 (ResponseHelper) | 모든 API 응답은 ResponseHelper 사용 |
FormRequest 작성
| 순서 | 문서 | TL;DR 핵심 |
|---|---|---|
| 1 | 검증 (Validation) | FormRequest 사용 필수 (Service에 검증 로직 금지) |
| 2 | Custom Exception 다국어 처리 | 예외 메시지 하드코딩 금지 → __() 함수 필수 |
권한/메뉴 추가
| 순서 | 문서 | TL;DR 핵심 |
|---|---|---|
| 1 | 권한 시스템 | 구조: User → Role → Permission (기능 레벨) |
| 2 | 메뉴 시스템 | 구조: User → Role → role_menus 피벗 → Menu |
다국어 추가
| 순서 | 문서 | TL;DR 핵심 |
|---|---|---|
| 1 | 모듈 다국어 시스템 | 백엔드: /lang/{locale}/*.php |
| 2 | 데이터베이스 개발 가이드 | 마이그레이션: 한국어 comment 필수, down() 구현 필수 |
| 3 | 데이터 바인딩 - 다국어 처리 | - |
카테고리별 전체 문서 목록
백엔드 (37개)
| 문서 | 제목 |
|---|---|
| activity-log-hooks.md | 활동 로그 훅 레퍼런스 (Activity Log Hooks Reference) |
| activity-log.md | 활동 로그 시스템 (Activity Log System) |
| admin-settings-access.md | Admin 환경설정 값 접근 (g7_core_settings vs config()) |
| api-documentation.md | API 레퍼런스 문서 규정 (API Documentation) |
| api-resources.md | API 리소스 |
| authentication.md | 인증 및 세션 처리 |
| benchmark.md | 성능 계측 시스템 (Benchmark) |
| broadcasting.md | Broadcasting (실시간 이벤트) |
| console-confirm.md | 콘솔 yes/no 프롬프트 (ConsoleConfirm) |
| controllers.md | 컨트롤러 계층 구조 |
| core-config.md | 코어 설정 (config/core.php) |
| core-update-system.md | 코어 업데이트 시스템 (Core Update System) |
| data-sync-helpers.md | 데이터 동기화 Helper (Data Sync Helpers) |
| dto.md | DTO (Data Transfer Object) 사용 규칙 |
| enum.md | Enum 사용 규칙 |
| exceptions.md | Custom Exception 다국어 처리 |
| geoip.md | GeoIP 시스템 (MaxMind GeoLite2) |
| identity-messages.md | 본인인증 메시지 템플릿 시스템 (Identity Messages) |
| identity-policies.md | 본인인증 정책 시스템 (Identity Policies) |
| identity-providers.md | IDV Provider 작성 가이드 (Identity Verification Providers) |
| language-pack-service.md | LanguagePackService (백엔드 Service 레이어) |
| middleware.md | 미들웨어 등록 규칙 |
| notification-system.md | 알림 시스템 (Notification System) |
| pagination.md | 대용량 목록 페이지네이션 (Pagination) |
| README.md | 백엔드 개발 가이드 |
| response-helper.md | API 응답 규칙 (ResponseHelper) |
| reverse-proxy.md | 리버스 프록시 환경 (Reverse Proxy) |
| routing.md | 라우트 네이밍 및 경로 |
| search-system.md | Scout 검색 엔진 시스템 (Search System) |
| seo-system.md | SEO 페이지 생성기 시스템 (SEO Page Generator) |
| service-provider.md | 서비스 프로바이더 안전성 |
| service-repository.md | Service-Repository 패턴 |
| settings-multilingual-enrichment.md | Settings 카탈로그 다국어 자동 보강 |
| static-asset-publishing.md | 부트스트랩 리소스 정적 게시 (Static Asset Publishing) |
| translatable-seeders.md | 다국어 시더 인터페이스 (Translatable Seeders) |
| user-overrides.md | 사용자 수정 보존 (HasUserOverrides Trait) |
| validation.md | 검증 (Validation) |
프론트엔드 (46개)
| 문서 | 제목 |
|---|---|
| actions-g7core-api.md | 액션 시스템 - G7Core API (React 컴포넌트용) |
| actions-handlers-navigation.md | 액션 핸들러 - 네비게이션 |
| actions-handlers-state.md | 액션 핸들러 - 상태 관리 |
| actions-handlers-ui.md | 액션 핸들러 - UI 인터랙션 |
| actions-handlers.md | 액션 핸들러 - 핸들러별 상세 사용법 |
| actions.md | 액션 핸들러 가이드 |
| auth-system.md | 인증 시스템 (AuthManager) |
| component-props-composite.md | 컴포넌트 Props 레퍼런스 - Composite |
| component-props.md | 컴포넌트 Props 레퍼런스 |
| components-advanced.md | 컴포넌트 고급 기능 |
| components-patterns.md | 컴포넌트 패턴 및 다국어 |
| components-types.md | 컴포넌트 타입별 개발 규칙 |
| components.md | 컴포넌트 개발 규칙 |
| dark-mode.md | 다크 모드 지원 (engine-v1.1.0+) |
| data-binding-i18n.md | 데이터 바인딩 - 다국어 처리 |
| data-binding.md | 데이터 바인딩 및 표현식 |
| data-sources-advanced.md | 데이터 소스 - 고급 기능 |
| data-sources.md | 데이터 소스 (Data Sources) |
| editors.md | 에디터 컴포넌트 가이드 |
| g7core-api-advanced.md | G7Core 전역 API 레퍼런스 - 고급 |
| g7core-api.md | G7Core 전역 API 레퍼런스 |
| g7core-helpers.md | G7Core 헬퍼 API |
| identity-guard-interceptor.md | IdentityGuardInterceptor — 코어 본인인증 인터셉터 레퍼런스 |
| identity-verification-ui.md | 본인인증(IDV) 공통 UI 가이드 |
| layout-json-components-loading.md | 레이아웃 JSON - 데이터 로딩 및 생명주기 |
| layout-json-components-rendering.md | 레이아웃 JSON - 조건부/반복 렌더링 |
| layout-json-components-slots.md | 레이아웃 JSON - 슬롯 시스템 |
| layout-json-components.md | 레이아웃 JSON - 컴포넌트 (반복 렌더링, Blur, 생명주기, 슬롯) |
| layout-json-features-actions.md | 레이아웃 JSON - 초기화, 모달, 액션, 스크립트 |
| layout-json-features-error.md | 레이아웃 JSON - 에러 핸들링 |
| layout-json-features-styling.md | 레이아웃 JSON - 스타일 및 계산된 값 |
| layout-json-features.md | 레이아웃 JSON - 기능 (에러 핸들링, 초기화, 모달, 액션) |
| layout-json-inheritance.md | 레이아웃 JSON - 상속 (Extends, Partial, 병합) |
| layout-json.md | 레이아웃 JSON 스키마 |
| layout-testing.md | 그누보드7 레이아웃 파일 렌더링 테스트 가이드 |
| modal-usage.md | Modal 컴포넌트 사용 가이드 |
| README.md | 그누보드7 프론트엔드 개발 가이드 |
| responsive-layout.md | 반응형 레이아웃 개발 (engine-v1.1.0+) |
| security.md | 보안 및 검증 |
| state-management-advanced.md | 상태 관리 - 고급 기능 |
| state-management-forms.md | 상태 관리 - 폼 자동 바인딩 및 setState |
| state-management.md | 전역 상태 관리 |
| tailwind-safelist.md | Tailwind Safelist 가이드 |
| template-development.md | 템플릿 개발 가이드라인 |
| template-handlers.md | 템플릿 전용 핸들러 |
| README.md | 템플릿별 컴포넌트·핸들러·레이아웃 문서 |
확장 시스템 (32개)
| 문서 | 제목 |
|---|---|
| cache-driver.md | 캐시 드라이버 시스템 (CacheInterface) |
| changelog-rules.md | Changelog 규칙 (Changelog Rules) |
| editor-spec.md | 편집기 스펙 (editor-spec.json) |
| extension-documentation.md | 확장 개발자 문서 (Extension Documentation) |
| extension-manager.md | ExtensionManager (확장 관리자) |
| extension-update-system.md | 확장 업데이트 시스템 (Extension Update System) |
| hooks.md | 훅 시스템 (Hook System) |
| language-packs.md | 언어팩 시스템 (Language Packs) |
| layout-extensions.md | 레이아웃 확장 시스템 (Layout Extensions) |
| menus.md | 메뉴 시스템 |
| module-assets.md | 모듈 프론트엔드 에셋 시스템 |
| module-basics.md | 모듈 개발 기초 |
| module-commands.md | 모듈 Artisan 커맨드 |
| module-i18n.md | 모듈 다국어 시스템 |
| module-identity-settings.md | 모듈/플러그인 본인인증(IDV) 설정 통합 가이드 |
| module-layouts.md | 모듈 레이아웃 시스템 |
| module-routing.md | 모듈 라우트 규칙 |
| module-settings.md | 모듈 환경설정 시스템 개발 가이드 |
| permissions.md | 권한 시스템 |
| plugin-development.md | 플러그인 개발 가이드 |
| README.md | 그누보드7 확장 시스템 개발 가이드 |
| sample-extensions.md | 학습용 샘플 확장 (Sample Extensions) |
| storage-driver.md | 스토리지 드라이버 시스템 (StorageInterface) |
| template-basics.md | 템플릿 시스템 기초 |
| template-caching.md | 템플릿 캐싱 전략 |
| template-commands.md | 템플릿 Artisan 커맨드 |
| template-idv-bootstrap.md | 템플릿 IDV launcher 등록 가이드 |
| template-routing.md | 템플릿 라우트/언어 파일 규칙 |
| template-security.md | 템플릿 보안 정책 |
| template-workflow.md | 템플릿 개발 워크플로우 |
| upgrade-step-guide.md | 업그레이드 스텝 작성 가이드 (Upgrade Step Guide) |
| vendor-bundle.md | Vendor 번들 시스템 (Vendor Bundle System) |
공통 (20개)
| 문서 | 제목 |
|---|---|
| README.md | 그누보드7 Multi-Agent System |
| README.md | 그누보드7 DevTools MCP 서버 |
| README.md | 그누보드7 AI 도구 |
| create-module.md | 모듈 스캐폴딩 (create-module) |
| create-plugin.md | 플러그인 스캐폴딩 (create-plugin) |
| create-template.md | 템플릿 스캐폴딩 (create-template) |
| extract-i18n-keys.md | 다국어 키 추출 (extract-i18n-keys) |
| run-tests.md | 테스트 실행 (run-tests) |
| validate-backend.md | 백엔드 코드 패턴 검증 (validate-backend) |
| validate-frontend.md | 프론트엔드 검증 (validate-frontend) |
| validate-hook.md | 훅 패턴 검증 (validate-hook) |
| validate-i18n.md | 다국어 검증 (validate-i18n) |
| validate-migration.md | 마이그레이션 검증 (validate-migration) |
| cheatsheet.md | 그누보드7 자주 쓰는 명령어 치트시트 |
| database-guide.md | 그누보드7 데이터베이스 개발 가이드 |
| README.md | 그누보드7 문서 인덱스 |
| requirements.md | 그누보드7 시스템 요구사항 (System Requirements) |
| SECURITY.md | 그누보드7 템플릿 엔진 보안 가이드 |
| testing-guide.md | 그누보드7 테스트 가이드 |
| e2e-testing.md | 그누보드7 Playwright E2E 테스트 가이드 |
AI 도구
| 문서 | 설명 |
|---|---|
| ai-tools/README.md | AI 도구 개요 |
| ai-tools/skills/ | AI 코딩 도구 스킬 |
| ai-tools/agents/ | 멀티에이전트 시스템 소스 |