인터뷰에서 방향만 정하고 남겨 뒀던 후속 5건을 구현하고, 커밋 전 종합 점검에서 발견한 결함 2건을 함께 고쳤다. 기능 도입: - 업로드 이미지 축소 — `upload.image_max_width` / `image_max_height` / `image_quality` 는 저장·검증만 되고 리사이즈 코드가 0건이었다. 비율 유지, 상한 이내면 재인코딩하지 않음(불필요한 화질 손실 방지), 한계 미설정이면 무동작(기존 사이트 이미지 보호). PNG·GIF·WebP 투명도 보존. 실패해도 업로드는 계속되고 원본이 저장된다. - 로그인 2단계 인증 — `security.two_factor_auth` 는 설정 항목만 있고 구현도 화면 컨트롤도 없었다. 코어 IDV 인프라를 재사용해 purpose `login` 을 추가하고, 비밀번호 확인 통과 후 challenge 만 발행한다(토큰 미발급 + logout). `POST /api/auth/login/two-factor` 가 코드를 확인해야 토큰이 나가며, 로그인과 같은 요청 제한을 적용한다. challenge 의 purpose 를 대조해 다른 흐름의 challenge 로 로그인하는 것을 막는다. 코드 발송이 실패하면 실패로 알린다 — 2단계를 건너뛰고 로그인시키면 통제가 조용히 열린다. 결함 수정: - 게시글 수정 시 사용자 경로가 `attachment_ids` 를 Service 로 넘기지 않아, 검증과 권한은 통과하고 200 을 받는데 첨부만 조용히 연결되지 않았다. 관리자 경로는 넘기고 있어 같은 요청이 화면에 따라 다르게 동작했다. - 설정 화면 라디오가 `pointer-events-none` + Label click 으로만 상태를 바꿔, 키보드 방향키(change) 조작 시 화면 표시와 저장될 값이 어긋났다. 동일 패턴 4개 레이아웃 12개 라디오에 change 액션을 부여하고 params 를 click 과 동일하게 고정한다. - 주문완료 화면이 결제수단 확인보다 영수증 조회를 앞세워, PG 를 거치지 않는 주문에서도 매번 404 요청이 나갔다. - 개별 게시판 폼의 숫자 항목에 경계값 바인딩이 없어 환경설정 화면과 안내가 달랐다. 커밋 전 점검에서 발견: - 2단계 인증이 반쪽으로 들어가 있었다. enum 에 case 만 추가하고 코어 purpose 레지스트리와 ko/en 라벨을 빠뜨려, 목적 목록 API 에서 통째로 빠지고 `hasPurpose` 가 false 였으며 라벨 대신 i18n 키 원문이 반환됐다. 관리자가 이 목적의 메시지 템플릿·정책을 만들 수 없는 상태였다. 세 곳을 enum 기준으로 전수 대조하는 회귀 테스트를 함께 둔다. - 업로드 이미지 축소가 적용되지 않은 8번째 서비스(레이아웃 편집기 첨부)가 있었다. 열거형 커버리지 테스트는 내 조사 결과를 green 으로 고정할 뿐 전수를 증명하지 못한다 — 같은 규약을 조건으로 표현한 audit 룰이 첫 실행에서 검출했다. 재발 방지: - audit 룰 `upload-service-image-resize` (신규 업로드 서비스의 축소 누락 검출) - 트러블슈팅 사례 20 (코어 설정이 일부 경로에만 적용되는 증상의 진단 절차) - `sirsoft-ckeditor5` 의 코어 최소 요구 버전을 7.0.6 으로 상향 (신설 클래스 의존) 공개 이슈: gnuboard/g7
119 lines
4.6 KiB
PHP
119 lines
4.6 KiB
PHP
<?php
|
|
|
|
return [
|
|
'providers' => [
|
|
'mail' => [
|
|
'label' => '이메일',
|
|
'settings' => [
|
|
'code_length' => '인증 코드 길이',
|
|
'code_length_help' => '발송되는 숫자 코드의 자릿수 (기본 6, 최소 4, 최대 10).',
|
|
'from_address' => '발신자 주소',
|
|
'from_address_help' => '비어 있으면 시스템 기본 발신자를 사용합니다.',
|
|
],
|
|
],
|
|
],
|
|
|
|
'errors' => [
|
|
'verification_required' => '본인 확인이 필요합니다.',
|
|
'challenge_not_found' => '유효하지 않은 인증 요청입니다.',
|
|
'wrong_provider' => '이 인증 요청은 다른 프로바이더에서 처리해야 합니다.',
|
|
'invalid_state' => '이미 처리된 인증 요청입니다.',
|
|
'expired' => '인증 시간이 만료되었습니다. 다시 시도해주세요.',
|
|
'max_attempts' => '시도 횟수를 초과했습니다. 다시 요청해주세요.',
|
|
'invalid_code' => '인증 코드가 올바르지 않습니다.',
|
|
'invalid_verification_token' => '유효하지 않은 본인인증 토큰입니다.',
|
|
'missing_target' => '인증 대상(이메일·전화번호)이 필요합니다.',
|
|
'target_mismatch' => '인증한 대상과 요청한 대상이 일치하지 않습니다.',
|
|
'purpose_not_supported' => '선택된 프로바이더는 이 목적을 지원하지 않습니다.',
|
|
'provider_unavailable' => '본인인증 프로바이더를 사용할 수 없습니다.',
|
|
'generic' => '본인인증에 실패했습니다.',
|
|
'missing_scope_or_target' => '정책 조회를 위해 scope 와 target 이 모두 필요합니다.',
|
|
'admin_policy_has_no_default' => '관리자가 직접 생성한 정책에는 선언 기본값이 없습니다.',
|
|
'reset_field_failed' => '선언 기본값 복원에 실패했습니다. 필드가 유효한지 확인하세요.',
|
|
],
|
|
|
|
'messages' => [
|
|
'challenge_requested' => '본인인증 코드를 발송했습니다.',
|
|
'challenge_verified' => '본인 확인이 완료되었습니다.',
|
|
'challenge_cancelled' => '본인인증 요청이 취소되었습니다.',
|
|
],
|
|
|
|
'logs' => [
|
|
'activity' => [
|
|
'requested' => ':email 로 본인인증 코드를 발송했습니다.',
|
|
'verified' => '본인 확인이 완료되었습니다.',
|
|
'failed' => '본인인증에 실패했습니다.',
|
|
'expired' => '본인인증 시간이 만료되었습니다.',
|
|
'cancelled' => '본인인증 요청을 취소했습니다.',
|
|
],
|
|
],
|
|
|
|
'purposes' => [
|
|
'signup' => [
|
|
'label' => '회원가입 인증',
|
|
'description' => '신규 가입자의 이메일/전화번호 소유 확인.',
|
|
],
|
|
'password_reset' => [
|
|
'label' => '비밀번호 재설정',
|
|
'description' => '비밀번호를 잊은 사용자가 본인 확인 후 재설정.',
|
|
],
|
|
'self_update' => [
|
|
'label' => '자기 정보 변경',
|
|
'description' => '로그인 사용자가 이메일/전화 등 본인 정보를 변경할 때.',
|
|
],
|
|
'sensitive_action' => [
|
|
'label' => '민감 작업',
|
|
'description' => '계정 탈퇴·관리자 작업 등 재인증이 필요한 시점.',
|
|
],
|
|
'login' => [
|
|
'label' => '로그인 2단계 인증',
|
|
'description' => '2단계 인증을 켰을 때 비밀번호 확인 뒤 한 단계를 더 요구.',
|
|
],
|
|
],
|
|
|
|
'channels' => [
|
|
'email' => '이메일',
|
|
],
|
|
|
|
'origin_types' => [
|
|
'route' => '라우트',
|
|
'hook' => '훅',
|
|
'policy' => '정책',
|
|
'middleware' => '미들웨어',
|
|
'api' => 'API 직접 호출',
|
|
'custom' => '커스텀',
|
|
'system' => '시스템',
|
|
],
|
|
|
|
'policy' => [
|
|
'scope' => [
|
|
'route' => '라우트',
|
|
'hook' => '훅',
|
|
'custom' => '커스텀',
|
|
],
|
|
'fail_mode' => [
|
|
'block' => '차단(HTTP 428)',
|
|
'log_only' => '로그만 기록',
|
|
],
|
|
'applies_to' => [
|
|
'self' => '본인',
|
|
'admin' => '관리자',
|
|
'both' => '모두',
|
|
],
|
|
'source_type' => [
|
|
'core' => '코어',
|
|
'module' => '모듈',
|
|
'plugin' => '플러그인',
|
|
'admin' => '관리자',
|
|
],
|
|
],
|
|
|
|
'message' => [
|
|
'scope_type' => [
|
|
'provider_default' => 'Provider 기본',
|
|
'purpose' => 'Purpose 별',
|
|
'policy' => 'Policy 별',
|
|
],
|
|
],
|
|
];
|