|
| 1 | +# 인증과 로그인 |
| 2 | + |
| 3 | +## 한 줄 설명 |
| 4 | + |
| 5 | +Harness Docs의 로그인은 `GitHub OAuth -> desktop authentication session -> workspace bootstrap` 순서로 이어진다. |
| 6 | + |
| 7 | +## 현재 범위 |
| 8 | + |
| 9 | +현재 구현 기준의 인증 범위는 다음과 같다. |
| 10 | + |
| 11 | +- 인증 provider는 `github_oauth` 하나다. |
| 12 | +- 인증 세션 상태는 `authenticated` 또는 `signed_out` 두 가지다. |
| 13 | +- desktop은 인증 세션을 복원한 뒤 workspace session bootstrap을 시도한다. |
| 14 | +- browser/mock 환경에서는 저장소 기반 mock 세션을 사용한다. |
| 15 | +- Tauri 환경에서는 네이티브 command를 통해 GitHub 인증 세션을 복원하거나 시작한다. |
| 16 | + |
| 17 | +관련 소스: |
| 18 | + |
| 19 | +- `apps/desktop/src/services/contracts.ts` |
| 20 | +- `apps/desktop/src/services/mockHarnessDocsServices.ts` |
| 21 | +- `apps/desktop/src/services/tauriHarnessDocsServices.ts` |
| 22 | +- `apps/desktop/src/services/rpcWorkspaceSession.ts` |
| 23 | +- `apps/api/src/server.ts` |
| 24 | + |
| 25 | +## 핵심 타입 |
| 26 | + |
| 27 | +인증 서비스 경계는 `apps/desktop/src/services/contracts.ts`에 있다. |
| 28 | + |
| 29 | +### `AuthenticationProviderDescriptor` |
| 30 | + |
| 31 | +- `id` |
| 32 | +- `label` |
| 33 | +- `kind` |
| 34 | +- `loginCtaLabel` |
| 35 | + |
| 36 | +현재 값: |
| 37 | + |
| 38 | +- `id = github_oauth` |
| 39 | +- `kind = oauth` |
| 40 | + |
| 41 | +### `AuthenticationSessionSnapshot` |
| 42 | + |
| 43 | +union: |
| 44 | + |
| 45 | +- `AuthenticatedSessionSnapshot` |
| 46 | +- `SignedOutSessionSnapshot` |
| 47 | + |
| 48 | +즉, 데스크톱 앱은 로그인 여부를 복잡한 중간 상태보다 명시적인 두 상태로 본다. |
| 49 | + |
| 50 | +### `AppSessionSnapshot` |
| 51 | + |
| 52 | +union: |
| 53 | + |
| 54 | +- signed out: |
| 55 | + `authentication = signed_out`, `workspace = null` |
| 56 | +- authenticated: |
| 57 | + `authentication = authenticated`, `workspace = WorkspaceSessionSnapshot` |
| 58 | + |
| 59 | +이 구조 때문에 workspace bootstrap은 로그인 이후에만 수행된다. |
| 60 | + |
| 61 | +## 로그인 흐름 |
| 62 | + |
| 63 | +```mermaid |
| 64 | +sequenceDiagram |
| 65 | + actor User as 사용자 |
| 66 | + participant Desktop as Desktop App |
| 67 | + participant Auth as AuthenticationService |
| 68 | + participant Workspace as WorkspaceSessionService |
| 69 | + participant API as Harness Docs API |
| 70 | +
|
| 71 | + User->>Desktop: 앱 진입 |
| 72 | + Desktop->>Auth: restoreSession() |
| 73 | +
|
| 74 | + alt authenticated |
| 75 | + Auth-->>Desktop: AuthenticationSessionSnapshot(authenticated) |
| 76 | + Desktop->>Workspace: getSnapshot(session) |
| 77 | + Workspace->>API: GET /api/session/bootstrap |
| 78 | + API-->>Workspace: BootstrapSessionDto |
| 79 | + Workspace-->>Desktop: WorkspaceSessionSnapshot |
| 80 | + else signed_out |
| 81 | + Auth-->>Desktop: AuthenticationSessionSnapshot(signed_out) |
| 82 | + Desktop-->>User: 로그인 화면 표시 |
| 83 | + end |
| 84 | +``` |
| 85 | + |
| 86 | +## Browser / Mock 환경 |
| 87 | + |
| 88 | +출처: |
| 89 | + |
| 90 | +- `apps/desktop/src/services/mockHarnessDocsServices.ts` |
| 91 | + |
| 92 | +browser/mock 환경의 동작은 다음과 같다. |
| 93 | + |
| 94 | +### provider |
| 95 | + |
| 96 | +- provider는 여전히 `github_oauth`로 고정한다. |
| 97 | + |
| 98 | +### 세션 저장 |
| 99 | + |
| 100 | +- storage key: `harness-docs/mock-auth-session` |
| 101 | +- 값이 `signed_out`이면 로그아웃 상태 |
| 102 | +- 그 외에는 `authenticated` |
| 103 | + |
| 104 | +### `restoreSession()` |
| 105 | + |
| 106 | +- local storage에서 상태를 읽는다. |
| 107 | +- authenticated면 `mockSession.user`를 반환한다. |
| 108 | +- signed out이면 `user = null`을 반환한다. |
| 109 | + |
| 110 | +### `startSignIn()` |
| 111 | + |
| 112 | +- provider가 `github_oauth`인지 확인한다. |
| 113 | +- storage를 `authenticated`로 쓴다. |
| 114 | +- mock user 기반 인증 세션을 반환한다. |
| 115 | + |
| 116 | +### `signOut()` |
| 117 | + |
| 118 | +- storage를 `signed_out`으로 쓴다. |
| 119 | +- signed out 세션을 반환한다. |
| 120 | + |
| 121 | +## Tauri / GitHub OAuth 환경 |
| 122 | + |
| 123 | +출처: |
| 124 | + |
| 125 | +- `apps/desktop/src/services/tauriHarnessDocsServices.ts` |
| 126 | + |
| 127 | +Tauri 환경에서는 인증 상태를 desktop app이 직접 계산하지 않고 command layer를 통해 받는다. |
| 128 | + |
| 129 | +### 사용 command |
| 130 | + |
| 131 | +- `get_github_authentication_session` |
| 132 | +- `start_github_sign_in` |
| 133 | +- `sign_out_github` |
| 134 | + |
| 135 | +### `restoreSession()` |
| 136 | + |
| 137 | +- runtime이 `tauri`가 아니면 signed out 반환 |
| 138 | +- runtime이 `tauri`면 `get_github_authentication_session` command 호출 |
| 139 | +- 반환된 GitHub identity를 앱의 `SessionUser` shape로 변환 |
| 140 | + |
| 141 | +### `startSignIn()` |
| 142 | + |
| 143 | +- provider가 `github_oauth`인지 검증 |
| 144 | +- tauri runtime이 아니면 에러 |
| 145 | +- `start_github_sign_in` command 호출 |
| 146 | +- GitHub identity를 앱 세션으로 변환 |
| 147 | + |
| 148 | +### `signOut()` |
| 149 | + |
| 150 | +- tauri runtime이면 `sign_out_github` command 호출 |
| 151 | +- 결과를 signed out 또는 authenticated 세션으로 다시 매핑 |
| 152 | + |
| 153 | +## GitHub identity -> 앱 사용자 매핑 |
| 154 | + |
| 155 | +Tauri 서비스는 raw GitHub user를 앱의 `SessionUser`로 바꾼다. |
| 156 | + |
| 157 | +매핑 규칙: |
| 158 | + |
| 159 | +- `handle = @${githubLogin}` |
| 160 | +- `avatarInitials = name 또는 login 기반 이니셜` |
| 161 | +- `primaryEmail = GitHub email이 없으면 mock fallback` |
| 162 | + |
| 163 | +즉, 인증 provider는 GitHub지만 desktop 내부 세션은 제품 고유 타입으로 유지한다. |
| 164 | + |
| 165 | +## Workspace bootstrap 연결 |
| 166 | + |
| 167 | +출처: |
| 168 | + |
| 169 | +- `apps/desktop/src/services/rpcWorkspaceSession.ts` |
| 170 | + |
| 171 | +인증이 성공했다고 바로 workspace 데이터가 생기는 것은 아니다. |
| 172 | + |
| 173 | +다음 단계가 필요하다. |
| 174 | + |
| 175 | +1. `AuthenticationSessionSnapshot.status === authenticated` |
| 176 | +2. `WorkspaceSessionService.getSnapshot(session)` 호출 |
| 177 | +3. RPC client가 `GET /api/session/bootstrap` 호출 |
| 178 | +4. `BootstrapSessionDto`를 `WorkspaceSessionSnapshot`으로 매핑 |
| 179 | + |
| 180 | +fallback 규칙: |
| 181 | + |
| 182 | +- RPC bootstrap 실패 시 mock snapshot 사용 |
| 183 | +- 단 user는 현재 authentication session의 user로 덮어쓴다 |
| 184 | + |
| 185 | +이 구조 덕분에 인증과 workspace bootstrap이 분리된다. |
| 186 | + |
| 187 | +## 인증과 API의 관계 |
| 188 | + |
| 189 | +현재 `apps/api/src/server.ts`는 인증 provider를 직접 소유하지 않는다. |
| 190 | + |
| 191 | +현재 API가 하는 일: |
| 192 | + |
| 193 | +- Hono server 시작 |
| 194 | +- datasource 선택 |
| 195 | +- publish governance adapter 연결 |
| 196 | + |
| 197 | +즉, 현재 로그인 구조는 desktop 주도형이다. |
| 198 | + |
| 199 | +- desktop이 인증 세션을 복원 |
| 200 | +- desktop이 bootstrap 호출 |
| 201 | +- API는 bootstrap 응답 제공 |
| 202 | + |
| 203 | +장기적으로 서버 측 session 검증이나 token 검증이 붙더라도, 현재 문서 기준 source of truth는 desktop authentication service 쪽에 있다. |
| 204 | + |
| 205 | +## 현재 구현의 한계 |
| 206 | + |
| 207 | +### provider 다양성 없음 |
| 208 | + |
| 209 | +- 현재는 `github_oauth`만 지원한다. |
| 210 | + |
| 211 | +### token/credential 문서화 부족 |
| 212 | + |
| 213 | +- access token 저장 위치 |
| 214 | +- refresh 규칙 |
| 215 | +- 만료 처리 |
| 216 | +- API 요청 인증 헤더 규칙 |
| 217 | + |
| 218 | +은 아직 현재 문서 범위에 없다. |
| 219 | + |
| 220 | +### server-side auth 정책 부족 |
| 221 | + |
| 222 | +- API가 지금은 bootstrap과 도메인 응답을 중심으로 동작한다. |
| 223 | +- 실제 사용자 권한 검증과 GitHub identity 검증은 더 강화되어야 한다. |
| 224 | + |
| 225 | +### 로그인 화면과 route 문서 부족 |
| 226 | + |
| 227 | +- 현재 file-based route 리팩터링 중이라 로그인 route 문서는 안정화 이후 보강 필요 |
| 228 | + |
| 229 | +## Mermaid 구조도 |
| 230 | + |
| 231 | +```mermaid |
| 232 | +flowchart TD |
| 233 | + A["User opens desktop app"] --> B["AuthenticationService.restoreSession()"] |
| 234 | + B --> C{"authenticated?"} |
| 235 | + C -->|no| D["SignedOutAppSessionSnapshot"] |
| 236 | + C -->|yes| E["WorkspaceSessionService.getSnapshot(session)"] |
| 237 | + E --> F["GET /api/session/bootstrap"] |
| 238 | + F --> G["WorkspaceSessionSnapshot"] |
| 239 | + G --> H["AuthenticatedAppSessionSnapshot"] |
| 240 | +``` |
| 241 | + |
| 242 | +## 구현 파일별 책임 |
| 243 | + |
| 244 | +### `apps/desktop/src/services/contracts.ts` |
| 245 | + |
| 246 | +- authentication 관련 public service contract |
| 247 | +- session snapshot union 정의 |
| 248 | + |
| 249 | +### `apps/desktop/src/services/mockHarnessDocsServices.ts` |
| 250 | + |
| 251 | +- browser/mock 인증 상태 복원 |
| 252 | +- mock sign-in/sign-out |
| 253 | +- local storage 상태 관리 |
| 254 | + |
| 255 | +### `apps/desktop/src/services/tauriHarnessDocsServices.ts` |
| 256 | + |
| 257 | +- Tauri command 기반 GitHub OAuth 연동 |
| 258 | +- raw GitHub identity -> 앱 세션 매핑 |
| 259 | + |
| 260 | +### `apps/desktop/src/services/rpcWorkspaceSession.ts` |
| 261 | + |
| 262 | +- 인증 이후 workspace bootstrap RPC 호출 |
| 263 | +- bootstrap fallback 관리 |
| 264 | + |
| 265 | +### `apps/api/src/server.ts` |
| 266 | + |
| 267 | +- API startup |
| 268 | +- 현재 인증 provider 로직은 직접 소유하지 않음 |
| 269 | + |
| 270 | +## 지금 추천되는 다음 문서 |
| 271 | + |
| 272 | +이 문서 다음으로 분리하면 좋은 주제는 두 가지다. |
| 273 | + |
| 274 | +1. `desktop route auth flow` |
| 275 | +2. `server-side auth and authorization policy` |
0 commit comments