forked from baron/baron-sso
userfront 로그인 후 /dashboard로 이동하게 변경
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
# AGENTS 가이드 (Baron SSO)
|
||||
|
||||
## 버그 수정 절차 대원칙 (강제)
|
||||
- 버그 대응 시 **재현 테스트를 먼저 작성**합니다.
|
||||
- 재현 테스트가 실패하는 상태를 확인한 뒤에만 수정 작업을 시작합니다.
|
||||
- 수정 후에는 테스트를 반복 실행하여 재현 테스트가 안정적으로 통과할 때까지 계속 보완합니다.
|
||||
- 재현 테스트 없이 “감으로 수정”하거나, 실패 테스트를 남긴 채 성공으로 보고하지 않습니다.
|
||||
- 이슈 종료 전에는 최소 1회 이상 실제 사용자 경로(예: 로그인/새로고침/리다이렉트)를 확인합니다.
|
||||
- 테스트/원인/조치 내역은 문서(`docs/test-plan/*`, `docs/trouble-shooting/*`)에 반영합니다.
|
||||
|
||||
## 목적
|
||||
- 인증/인가 허브로서 **Backend + Ory Stack** 중심 아키텍처를 유지
|
||||
- 사용자 플로우(UserFront)와 관리 플로우(Admin/DevFront)를 명확히 분리
|
||||
|
||||
@@ -31,9 +31,12 @@
|
||||
| `backend/internal/handler/auth_handler_qr_test.go:107` | `TestScanQRLogin_Success` | 인증/OIDC 플로우 검증 |
|
||||
| `backend/internal/handler/auth_handler_qr_test.go:150` | `TestResolveConsentSubjects_TokenAndCookie` | 인증/OIDC 플로우 검증 |
|
||||
| `backend/internal/handler/auth_handler_qr_test.go:57` | `TestQRLoginFlow_Success` | 인증/OIDC 플로우 검증 |
|
||||
| `backend/internal/handler/auth_handler_test.go:20` | `TestCompletePasswordReset_MissingLoginID` | 오류/예외/거부 경로 검증 |
|
||||
| `backend/internal/handler/auth_handler_test.go:50` | `TestCompletePasswordReset_InvalidPasswordPolicy` | 오류/예외/거부 경로 검증 |
|
||||
| `backend/internal/handler/auth_handler_test.go:80` | `TestCompletePasswordReset_NilIDPProvider` | 인증/OIDC 플로우 검증 |
|
||||
| `backend/internal/handler/auth_handler_test.go:67` | `TestCompletePasswordReset_MissingLoginID` | 오류/예외/거부 경로 검증 |
|
||||
| `backend/internal/handler/auth_handler_test.go:97` | `TestCompletePasswordReset_InvalidPasswordPolicy` | 오류/예외/거부 경로 검증 |
|
||||
| `backend/internal/handler/auth_handler_test.go:127` | `TestCompletePasswordReset_NilIDPProvider` | 오류/예외/거부 경로 검증 |
|
||||
| `backend/internal/handler/auth_handler_test.go:157` | `TestCompletePasswordReset_TokenValueOverridesLoginIDQuery` | 비밀번호 재설정 토큰 우선 규칙 검증 |
|
||||
| `backend/internal/handler/auth_handler_test.go:209` | `TestCompletePasswordReset_InvalidTokenRejectedEvenWhenLoginIDExists` | 오류/예외/거부 경로 검증 |
|
||||
| `backend/internal/handler/auth_handler_test.go:249` | `TestProcessPasswordResetToken_EncodesLoginIDInRedirect` | 리다이렉트/쿼리 보존 규칙 검증 |
|
||||
| `backend/internal/handler/dev_handler_test.go:103` | `TestCreateClient_Success` | Hydra/RP 연동 검증 |
|
||||
| `backend/internal/handler/dev_handler_test.go:15` | `TestListClients_Success` | Hydra/RP 연동 검증 |
|
||||
| `backend/internal/handler/dev_handler_test.go:49` | `TestGetClient_Success` | Hydra/RP 연동 검증 |
|
||||
@@ -48,6 +51,7 @@
|
||||
| `backend/internal/idp/factory_test.go:123` | `TestChainedProviderMetadataUnion` | 회귀 방지 기본 동작 검증 |
|
||||
| `backend/internal/idp/factory_test.go:139` | `TestChainedProviderUpdateUserPasswordFallback` | 복구/격리/회복 탄력성 검증 |
|
||||
| `backend/internal/idp/factory_test.go:152` | `TestChainedProviderUpdateUserPasswordAllFail` | 인증/OIDC 플로우 검증 |
|
||||
| `backend/internal/logger/audit_logger_test.go:14` | `TestAuditLogEntry_RedactsSensitiveFields` | 감사 로그 민감정보 마스킹/비노출 검증 |
|
||||
| `backend/internal/middleware/audit_middleware_test.go:42` | `TestAuditMiddleware` | 회귀 방지 기본 동작 검증 |
|
||||
| `backend/internal/middleware/error_code_enricher_test.go:22` | `TestErrorCodeEnricher_AddsCodeToLegacyErrorResponse` | 오류/예외/거부 경로 검증 |
|
||||
| `backend/internal/middleware/error_code_enricher_test.go:50` | `TestErrorCodeEnricher_DoesNotOverrideExistingCode` | 오류/예외/거부 경로 검증 |
|
||||
|
||||
@@ -43,15 +43,22 @@
|
||||
| `userfront/test/login_challenge_resolver_test.dart` | `widget 값이 없으면 URI query에서 복구` | fallback/복구 경로 검증 |
|
||||
| `userfront/test/login_challenge_resolver_test.dart` | `widget 값이 있으면 최우선으로 사용` | 핵심 동작 회귀 방지 검증 |
|
||||
| `userfront/test/login_challenge_resolver_test.dart` | `값이 전부 없으면 missing` | fallback/복구 경로 검증 |
|
||||
| `userfront/test/null_check_recovery_test.dart` | `Null check 오류 + 루트(/)면 선호 로케일 signin으로 복구` | Null-check 예외 복구 경로 검증 |
|
||||
| `userfront/test/null_check_recovery_test.dart` | `Null check 오류 + /ko면 /ko/signin으로 복구` | Null-check 예외 복구 경로 검증 |
|
||||
| `userfront/test/null_check_recovery_test.dart` | `이미 /ko/signin이면 복구 이동하지 않음` | Null-check 예외 복구 경로 검증 |
|
||||
| `userfront/test/null_check_recovery_test.dart` | `Null check 오류여도 /ko/profile에서는 복구 이동하지 않음` | Null-check 예외 복구 경로 검증 |
|
||||
| `userfront/test/null_check_recovery_test.dart` | `다른 오류 메시지면 복구 이동하지 않음` | Null-check 예외 복구 경로 검증 |
|
||||
| `userfront/test/oidc_redirect_guard_test.dart` | `http/https 절대 URL만 허용` | 핵심 동작 회귀 방지 검증 |
|
||||
| `userfront/test/oidc_redirect_guard_test.dart` | `빈 문자열과 파싱 실패를 차단` | 핵심 동작 회귀 방지 검증 |
|
||||
| `userfront/test/password_login_flow_policy_test.dart` | `OIDC challenge가 없고 jwt가 있으면 로컬 로그인 완료로 진행한다` | 로그인 분기/라우팅 규칙 검증 |
|
||||
| `userfront/test/password_login_flow_policy_test.dart` | `OIDC challenge가 있고 redirectTo가 없으면 accept를 시도한다` | 로그인 분기/라우팅 규칙 검증 |
|
||||
| `userfront/test/password_login_flow_policy_test.dart` | `redirectTo/jwt 모두 없으면 invalid로 처리한다` | 로그인 분기/라우팅 규칙 검증 |
|
||||
| `userfront/test/password_login_flow_policy_test.dart` | `redirectTo가 있으면 OIDC redirect를 우선한다` | 로그인 분기/라우팅 규칙 검증 |
|
||||
| `userfront/test/router_redirect_widget_test.dart` | `루트 경로: /{locale} 로 접근 시 /{locale}/signin 으로 리다이렉트되어야 한다 (버그: 화면 렌더링 안됨)` | 로그인 분기/라우팅 규칙 검증 |
|
||||
| `userfront/test/router_redirect_widget_test.dart` | `/login: login_challenge와 redirect_uri를 전달` | 리다이렉트/쿼리 보존 규칙 검증 |
|
||||
| `userfront/test/router_redirect_widget_test.dart` | `로그인 상태: profile 접근 시 signin으로 리다이렉트하지 않음` | 로그인 분기/라우팅 규칙 검증 |
|
||||
| `userfront/test/router_redirect_widget_test.dart` | `로그인 후 같은 브라우저 새 창/팝업에서도 세션이 유지된다` | 로그인 세션 지속성(동일 브라우저) 검증 |
|
||||
| `userfront/test/router_redirect_widget_test.dart` | `비로그인: redirect_uri/login_challenge가 signin으로 전달` | 리다이렉트/쿼리 보존 규칙 검증 |
|
||||
| `userfront/test/router_redirect_widget_test.dart` | `비로그인: redirect_uri가 없으면 redirect_url을 전달` | 리다이렉트/쿼리 보존 규칙 검증 |
|
||||
| `userfront/test/widget_test.dart` | `BaronSSOApp builds` | 기본 앱 렌더링 스모크 검증 |
|
||||
| `userfront/test/dashboard_screen_smoke_test.dart` | `대시보드는 로그인 토큰이 있으면 크래시 없이 기본 프레임을 렌더링한다` | 대시보드 Null-check 회귀 방지 스모크 검증 |
|
||||
| `userfront/test/widget_test.dart` | `smoke test` | 기본 앱 렌더링 스모크 검증 |
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Issue #277/#302 트러블슈팅 기록: 로그인 후 공백 화면 + 새로고침 시 signin 회귀
|
||||
|
||||
## 기준 시점
|
||||
- 2026-02-23 KST
|
||||
- 재현 환경: `https://sss.hmac.kr` (WASM 배포)
|
||||
|
||||
## 증상
|
||||
- 로그인 직후 URL은 `/{locale}` 또는 `/{locale}/dashboard`로 보이지만 화면이 렌더링되지 않음
|
||||
- 이후 새로고침하면 `/{locale}/signin`으로 되돌아감
|
||||
- 콘솔/백엔드 수집 로그:
|
||||
- `Null check operator used on a null value`
|
||||
- `wasm-function[765]` 포함 스택 반복
|
||||
|
||||
## 스택 매핑 결과 (source-map + no-strip-wasm)
|
||||
- 매핑 커맨드:
|
||||
- `python3 scripts/map_wasm_stack.py --wasm userfront/build/web/main.dart.wasm --sourcemap userfront/build/web/main.dart.wasm.map --frame ...`
|
||||
- 핵심 프레임:
|
||||
- `wasm-function[765]` -> `_TypeError._throwNullCheckErrorWithCurrentStack`
|
||||
- 상위 프레임 -> Flutter `NavigatorState.didUpdateWidget/_updatePages` 경로
|
||||
- 결론:
|
||||
- 단일 위젯 null 접근보다, 라우트 갱신 타이밍/중복 네비게이션 경쟁에서 `Navigator` 내부에서 터지는 양상
|
||||
|
||||
## 지금까지 시행착오와 실패 내역
|
||||
1. `LocaleGate`, `LanguageSelector`의 `EasyLocalization.of(context)` null 방어만 적용
|
||||
- 결과: 동일 예외 재발
|
||||
- 이유: 루트 원인은 로케일 위젯 단일 null 접근이 아니라 네비게이션 경쟁 구간
|
||||
|
||||
2. `/ko` 루트에서 signin 강제 리다이렉트만 강화
|
||||
- 결과: 최초 진입은 일부 개선됐지만 로그인 직후/새로고침 회귀 지속
|
||||
- 이유: 로그인 성공 경로가 루트(`/{locale}`)와 엮이면서 라우트 재평가가 중첩
|
||||
|
||||
3. 로그인 화면에서 `AuthNotifier.notify()` + `context.go(...)` 동시 수행
|
||||
- 결과: 간헐적 경쟁 상태 유발 가능성 확인
|
||||
- 조치: 로컬 네비게이션 1회 가드 도입(`_goLocalizedHomeOnce`)
|
||||
|
||||
4. cookie 세션 승격이 토큰 저장 이후 덮어쓰는 경합
|
||||
- 결과: 일부 흐름에서 저장 상태 불안정 가능성
|
||||
- 조치: `cookie_session_policy` 추가, 토큰 존재 시 불필요한 cookie 승격 차단
|
||||
|
||||
5. `/:locale` 엔트리가 redirect 없이 매칭되는 구조
|
||||
- 결과: `/ko` 직접 진입 시 페이지 스택 재계산 과정에서 `NavigatorState.didUpdateWidget/_updatePages` 경로 null check 재발
|
||||
- 이유: `/ko`는 실질 화면이 아닌 분기 지점인데, 명시적 redirect 경로가 없으면 라우트 갱신 타이밍 경쟁에 취약
|
||||
- 조치: `/:locale`를 redirect 전용 엔트리로 확정(비로그인 `/{locale}/signin`, 로그인 `/{locale}/dashboard`)
|
||||
|
||||
## 최종 반영 방향 (이번 패치)
|
||||
1. 로그인 성공 기본 경로를 명시적으로 `/{locale}/dashboard`로 고정
|
||||
- `buildLocalizedHomePath()` 반환값을 `/{locale}/dashboard`로 변경
|
||||
- `/:locale` 엔트리는 `/:locale/dashboard`로 redirect 전용 처리
|
||||
|
||||
2. 라우터/화면 역할 분리
|
||||
- 보호 경로 검사는 router redirect에서 수행
|
||||
- 대시보드는 필요 시 cookie 세션 복구를 1회 시도 후 signin 이동
|
||||
|
||||
3. 중복 네비게이션 억제
|
||||
- 로그인 성공 시 내부 이동은 1회만 수행
|
||||
|
||||
## 검증
|
||||
- 추가 테스트:
|
||||
- `userfront/test/login_navigation_race_test.dart`
|
||||
- `userfront/test/cookie_session_policy_test.dart`
|
||||
- `userfront/test/router_redirect_widget_test.dart` (`/{locale}` 직접 진입 시 signin/dashboard 분기 검증)
|
||||
- 갱신 테스트:
|
||||
- `userfront/test/locale_utils_test.dart` (home path `/{locale}/dashboard` 기준)
|
||||
- 실행:
|
||||
- `flutter test`
|
||||
- `flutter test --platform chrome test/router_redirect_widget_test.dart test/login_navigation_race_test.dart test/cookie_session_policy_test.dart`
|
||||
|
||||
## 남은 리스크
|
||||
- 실제 브라우저 저장소 정책(localStorage 차단/쿠키 정책)에 따라 세션 판정이 달라질 수 있음
|
||||
- 운영 검증 시 네트워크/스토리지 상태를 함께 수집해야 원인 분리 가능
|
||||
|
||||
## 운영 확인 체크리스트
|
||||
1. 비로그인으로 `/{locale}` 접속 시 즉시 `/{locale}/signin` 이동
|
||||
2. 로그인 성공 시 `/{locale}/dashboard` 진입
|
||||
3. `/{locale}/dashboard`에서 새로고침 후 세션 유지 (동일 브라우저)
|
||||
4. 실패 시 `RECOVERY_NAV_NULL_CHECK`와 wasm frame 동시 수집
|
||||
Reference in New Issue
Block a user