Files
HeuJung 4e1ec6e001 fix(core,admin_basic,board,ecommerce,page,gdpr,verification): 설정 미러·자동 정리·탈퇴 원자성·일괄 선택 범위 정비
공개이슈 ··· 및 그 전수 해소분.

- 설정 저장·복원·초기화가 상주 프로세스 미러를 갱신하도록 단일 소유자화
- 만료 데이터 자동 정리 예약 12종 신설, 파기는 기본키 배치로 분할
- 회원 탈퇴를 전 경로 원자화하고 관리자 상태변경 경로를 정식 탈퇴로 통일
- 일괄 처리 목록의 선택을 화면에 보이는 행으로 한정(selectionScope)
- 비밀번호 정책 문구를 도메인 중립 라벨로 정정
- 정적 검사 2종·검증 스킬 설계 정합성 축 신설
2026-08-14 11:06:25 +09:00

98 lines
5.9 KiB
YAML

# audit:allow test-scenario-coverage reason: |
# cross product 자동 전개는 audit 실행 환경의 fallback YAML 파서가 nested axes 를 읽지 못해
# 검출되지 않는다(다른 시나리오와 동일 한계). 본 변경은 백엔드 검증 규칙 + 관리자 설정 화면이며
# PHPUnit Feature 테스트와 Vitest 레이아웃 테스트가 축 조합을 커버하고 green 이다.
feature: 비밀번호 정책 설정 강제 (C4)
description: |
관리자 보안 설정의 비밀번호 정책이 비밀번호를 받는 모든 경로에서 동일하게 적용되도록 한다.
배경:
- `security.password_min_length` · `security.password_require_special` 설정이 화면에는
있었지만 어떤 검증 규칙도 읽지 않는 고아 설정이었다.
- 비밀번호 길이 제한은 `LoginRequest` 에만 `min:6` 으로 하드코딩되어 있었다. 로그인은
기존 비밀번호를 대조하는 자리라 정책을 걸 자리가 아니고(정책 강화 시 기존 사용자가
로그인 불가), 정작 비밀번호를 **정하는** 경로에는 정책이 없었다.
적용 지점:
- 비밀번호를 새로 정하는 6개 경로가 `PasswordPolicy::rule()` 하나를 공유한다
(회원가입 · 비밀번호 재설정 · 비밀번호 변경 · 관리자 회원 생성 · 관리자 회원 수정 · 프로필 수정).
- 로그인은 길이 규칙을 갖지 않는다.
axes:
entry_point:
- register
- reset_password
- change_password
- verify_password
- admin_create_user
- admin_update_user
- update_profile
min_length_setting: [default, raised, lowered]
special_char_setting: [required, not_required]
input_state: [satisfies_policy, violates_length, violates_special]
# 오류 문구의 :attribute 치환 라벨 축 (공개이슈 #113)
# 비밀번호 외 범용 키(ids)도 같은 결함군이라 함께 고정한다 —
# 전역 사전 하나를 도메인 문구로 덮으면 그 키를 쓰는 모든 화면이 오염된다.
attribute_label_scope: [generic_password, smtp_password, generic_ids, user_scoped_ids]
locale: [ko, en]
exclusions:
- { special_char_setting: not_required, input_state: violates_special, reason: "특수문자 미요구 설정에서는 위반 상태가 성립하지 않음" }
- { attribute_label_scope: smtp_password, entry_point: register, reason: "SMTP 라벨은 메일 설정 화면 전용 — 비밀번호를 정하는 경로에는 나타나지 않아야 한다" }
- { attribute_label_scope: smtp_password, entry_point: reset_password, reason: "동일" }
- { attribute_label_scope: smtp_password, entry_point: change_password, reason: "동일" }
- { attribute_label_scope: smtp_password, entry_point: admin_create_user, reason: "동일" }
- { attribute_label_scope: smtp_password, entry_point: admin_update_user, reason: "동일" }
- { attribute_label_scope: smtp_password, entry_point: update_profile, reason: "동일" }
- { attribute_label_scope: generic_ids, entry_point: register, reason: "ids 라벨은 목록 대상 일괄 처리 화면 전용 — 비밀번호를 정하는 경로에는 나타나지 않는다" }
- { attribute_label_scope: generic_ids, entry_point: reset_password, reason: "동일" }
- { attribute_label_scope: generic_ids, entry_point: change_password, reason: "동일" }
- { attribute_label_scope: generic_ids, entry_point: verify_password, reason: "동일" }
- { attribute_label_scope: generic_ids, entry_point: admin_create_user, reason: "동일" }
- { attribute_label_scope: generic_ids, entry_point: admin_update_user, reason: "동일" }
- { attribute_label_scope: generic_ids, entry_point: update_profile, reason: "동일" }
- { attribute_label_scope: user_scoped_ids, entry_point: register, reason: "동일" }
- { attribute_label_scope: user_scoped_ids, entry_point: reset_password, reason: "동일" }
- { attribute_label_scope: user_scoped_ids, entry_point: change_password, reason: "동일" }
- { attribute_label_scope: user_scoped_ids, entry_point: verify_password, reason: "동일" }
- { attribute_label_scope: user_scoped_ids, entry_point: admin_create_user, reason: "동일" }
- { attribute_label_scope: user_scoped_ids, entry_point: admin_update_user, reason: "동일" }
- { attribute_label_scope: user_scoped_ids, entry_point: update_profile, reason: "동일" }
- { attribute_label_scope: smtp_password, entry_point: verify_password, reason: "동일" }
effects:
- policy_rule_reads_min_length_from_settings
- policy_rule_reads_special_requirement_from_settings
- password_shorter_than_setting_is_rejected
- password_meeting_setting_is_accepted
- special_char_requirement_is_enforced_when_enabled
- login_request_has_no_length_rule
- all_password_setting_paths_share_one_rule
- empty_security_settings_fall_back_to_defaults
- saved_security_settings_are_visible_on_reload
- admin_screen_min_length_bounds_follow_settings_limits
# 라벨 축 (공개이슈 #113)
- policy_message_attribute_uses_generic_password_label
- smtp_label_never_appears_in_password_setting_paths
- smtp_screens_keep_their_domain_labels
- generic_labels_hold_in_every_locale
- bulk_status_screen_keeps_user_scoped_ids_label
- generic_ids_label_stays_generic_on_other_screens
- generic_attribute_keys_carry_no_domain_prefix
test_files:
- tests/Feature/Auth/PasswordPolicyTest.php
- tests/Feature/Settings/SettingsBoundaryContractTest.php
- tests/Feature/Validation/GlobalAttributeLabelTest.php
- templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-settings-security-password-policy.test.ts
rules_layer_coverage:
- rule: formrequest-numeric-boundary-drift
coverage: 최소 길이 경계는 config('core.settings_limits.security_password_min_length_*') 단일 출처
- rule: layout-input-boundary-hardcoded
coverage: 보안 탭 입력 min/max 는 `_meta.limits` 바인딩 (리터럴 없음)
- rule: i18n-throw-hardcoded-korean
coverage: 정책 위반 메시지는 lang/{ko,en}/validation.php 의 password 키