# API Auth Access Fix — Unity Client ## TL;DR > **Quick Summary**: Fix 3 critical bugs preventing the Unity client from successfully authenticating with the IchniOnline backend: wrong server port, GlobalResponse field name mismatch (PascalCase vs camelCase) causing silent deserialization failure, and null pointer in third-party login flow. > > **Deliverables**: > - `ApiResponse.cs` + `ApiClient.cs` — Atomic fix: fields renamed to camelCase, BaseUrl port corrected > - `AuthService.cs` — null check added for third-party login response > > **Estimated Effort**: Quick (3 files, targeted changes) > **Parallel Execution**: YES — all 3 tasks can run in parallel (independent files) > **Critical Path**: None (no dependencies between tasks) --- ## Context ### Original Request 用户要求结合后端工程和 Apifox API 文档来对 `Assets/Scripts/Online` 目录里的 API 访问进行检查和修复,仅处理认证相关接口。 ### Interview Summary **Key Discussions**: - Only auth-related endpoints in scope: login, register, session-key, third-party login - Ignore beatmap and other APIs for now - Only modify files within `Assets/Scripts/Online/` **Research Findings**: - Server (ASP.NET Core) uses `System.Text.Json` with camelCase policy → JSON keys: `code`, `message`, `data` - Unity client uses `JsonUtility.FromJson` which is **case-sensitive** → C# field names must exactly match JSON keys - Current `GlobalResponse` has PascalCase `Code`/`Message`/`Data` → **silent deserialization failure** - Server runs on `http://localhost:5308` (launchSettings.json) but Unity client has `http://localhost:60887` → **all requests fail** - Third-party login can return `data: null` (unbound account) → **NullReferenceException** when accessing `result.Data.token` ### Metis Review *Skipped — clear scope, direct bugs, user confirmed approach.* --- ## Work Objectives ### Core Objective Fix the Unity client's auth API access so that all 4 auth endpoints (session-key, login, register, third-party login) successfully communicate with the IchniOnline backend. ### Concrete Deliverables - `Assets/Scripts/Online/Network/Models/ApiResponse.cs` — GlobalResponse field names corrected - `Assets/Scripts/Online/Network/ApiClient.cs` — BaseUrl port fixed, field access updated - `Assets/Scripts/Online/Logic/AuthService.cs` — null-safe third-party login handling ### Definition of Done - `IchniOnlineApiClient.Instance.BaseUrl` returns `http://localhost:5308` - `JsonUtility.FromJson` correctly populates `GlobalResponse.code`/`message`/`data` - Third-party login with unbound account does not throw NullReferenceException - All 3 auth flows (password login, TapTap login, register) return correct `ApiResult` ### Must Have - Fix BaseUrl port from 60887 to 5308 - Fix `GlobalResponse` field names to match JSON camelCase - Add null check for `result.Data` in third-party login flow - All changes within `Assets/Scripts/Online/` only ### Must NOT Have (Guardrails) - Do NOT touch beatmap or non-auth API code - Do NOT modify files outside `Assets/Scripts/Online/` - Do NOT add Newtonsoft.Json or other new dependencies - Do NOT refactor architecture (stay minimal, targeted fixes) --- ## Verification Strategy (MANDATORY) > **ZERO HUMAN INTERVENTION** — ALL verification is agent-executed. ### Test Decision - **Infrastructure exists**: NO - **Automated tests**: None - **Framework**: N/A ### QA Policy Every task MUST include agent-executed QA scenarios. Evidence saved to `.omo/evidence/task-{N}-{scenario-slug}.{ext}`. - **Library/Module verification**: Use Bash (bun/node REPL style) with `script-execute` to run C# test snippets that directly verify `JsonUtility.FromJson` deserialization behavior - **Code review verification**: Read modified files and verify correctness by cross-referencing with server API contracts --- ## Execution Strategy ``` Wave 1 (Start Immediately): ├── Task 1: Fix ApiResponse.cs + ApiClient.cs — GlobalResponse camelCase + BaseUrl port [quick] └── Task 2: Fix AuthService.cs — third-party login null check [quick] Wave FINAL (After ALL tasks): ├── Task F1: QA verification (oracle) └── Task F2: Scope fidelity check (deep) Critical Path: Task 1 → F1, F2 (independent from Task 2) Parallel Speedup: ~50% faster than sequential (Task 1 and Task 2 run in parallel) Max Concurrent: 2 (both in Wave 1) ``` --- ## TODOs - [x] 1. Fix ApiResponse.cs + ApiClient.cs — GlobalResponse camelCase + BaseUrl port **What to do**: - In `ApiResponse.cs`: Rename field `Code` → `code`, `Message` → `message`, `Data` → `data` - In `ApiClient.cs`: - Change `BaseUrl` default from `"http://localhost:60887"` to `"http://localhost:5308"` - Update all references to `GlobalResponse` fields from PascalCase to camelCase: - `response.Code` → `response.code` - `response.Message` → `response.message` - `response.Data` → `response.data` - `errorResponse.Code` → `errorResponse.code` - `errorResponse.Message` → `errorResponse.message` - Verify: `ResponseCode` enum values remain unchanged (Ok=10000, etc.) - Verify no other PascalCase field access patterns exist in ApiClient.cs > **Rationale for merging**: ApiResponse.cs declares the fields, ApiClient.cs consumes them. Both files must be changed atomically — if committed separately, the codebase is broken regardless of order. A single agent handles both files in one task to guarantee correctness. **Must NOT do**: - Do not change `ResponseCode` enum values or names - Do not change `ApiResult` class structure - Do not add `[JsonProperty]` or other attributes - Do not change the `BuildUrl`, `AddAuthHeader`, or `SendAsync` methods - Do not change the method signatures of `GetAsync` or `PostAsync` **Recommended Agent Profile**: > - **Category**: `quick` > - Reason: Two files, simple field rename + port change, no logic changes > - **Skills**: none required > - **Skills Evaluated but Omitted**: N/A **Parallelization**: > - **Can Run In Parallel**: NO (merged from previous Tasks 1+2 due to compilation dependency) > - **Parallel Group**: Wave 1 (with Task 2) > - **Blocks**: None > - **Blocked By**: None (can start immediately) **References**: - `Assets/Scripts/Online/Network/Models/ApiResponse.cs` — Field declaration site - `Assets/Scripts/Online/Network/ApiClient.cs` — Field usage site + BaseUrl - Server `GlobalResponse.cs` at `/mnt/d/Projects/IchniOnline/IchniOnline.Server/Models/Responses/GlobalResponse.cs` — Reference (PascalCase with System.Text.Json camelCase policy) - Backend `launchSettings.json` at `/mnt/d/Projects/IchniOnline/IchniOnline.Server/Properties/launchSettings.json` — Confirms port 5308 - Current JSON wire format: `{"code":10000,"message":"Success","data":{...}}` — JSON keys are lowercase **Acceptance Criteria**: - `ApiResponse.cs` field declarations use camelCase: `code`, `message`, `data` - `ApiClient.cs` field references use camelCase: `response.code`, `response.message`, `response.data` - `ApiClient.cs` BaseUrl line 20: `"http://localhost:5308"` - `script-execute` confirms `JsonUtility.FromJson>` correctly deserializes `{"code":10000,"message":"OK","data":"test"}` **QA Scenarios (MANDATORY)**: ``` Scenario: Verify GlobalResponse deserialization works with camelCase JSON Tool: script-execute (C# Roslyn) Preconditions: Both files have been modified Steps: 1. Use script-execute to run: string json = "{\\\"code\\\":10000,\\\"message\\\":\\\"OK\\\",\\\"data\\\":\\\"test\\\"}"; 2. Deserialize: var obj = UnityEngine.JsonUtility.FromJson>(json); 3. Assert: obj.code == (ResponseCode)10000 4. Assert: obj.message == "OK" 5. Assert: obj.data == "test" Expected Result: All 3 fields are correctly populated from camelCase JSON keys Failure Indicators: Any field remains default (0/null) Evidence: .omo/evidence/task-1-globalresponse-deserialize.txt Scenario: Verify GlobalResponseBase error deserialization Tool: script-execute Preconditions: Both files have been modified Steps: 1. Use script-execute to run: string json = "{\\\"code\\\":10400,\\\"message\\\":\\\"Bad request\\\"}"; 2. Deserialize as base: var obj = UnityEngine.JsonUtility.FromJson(json); 3. Assert: obj.code == (ResponseCode)10400 4. Assert: obj.message == "Bad request" Expected Result: Error response fields are correctly populated Evidence: .omo/evidence/task-1-globalresponsebase-deserialize.txt Scenario: Verify BaseUrl port is corrected Tool: script-read Preconditions: ApiClient.cs has been updated Steps: 1. Read line 20 of ApiClient.cs 2. Assert: BaseUrl default value is "http://localhost:5308" Expected Result: Port is corrected to 5308 Evidence: .omo/evidence/task-1-baseurl.txt Scenario: Verify all GlobalResponse field references use camelCase in ApiClient.cs Tool: grep Preconditions: ApiClient.cs has been updated Steps: 1. Search for pattern: "response\.Code" in ApiClient.cs — should return 0 matches 2. Search for pattern: "response\.code" — should return 2+ matches 3. Search for pattern: "errorResponse\.Code" — should return 0 matches 4. Search for pattern: "errorResponse\.code" — should return 2+ matches Expected Result: All PascalCase field references replaced with camelCase Evidence: .omo/evidence/task-1-field-access.txt ``` **Evidence to Capture**: - [ ] script-execute output for deserialization test - [ ] Line 20 read output - [ ] grep results for field access patterns **Commit**: YES - Message: `fix(api): rename GlobalResponse fields to camelCase, correct BaseUrl port to 5308` - Files: `Assets/Scripts/Online/Network/Models/ApiResponse.cs`, `Assets/Scripts/Online/Network/ApiClient.cs` - Pre-commit: Verify build compiles --- - [x] 2. Fix AuthService.cs — third-party login null check **What to do**: - In `CompleteTapTapLoginAsync` method (~line 109): Add null check for `result.Data` before accessing `.token` - Handle the case where third-party login succeeds (code=10000) but data is null (unbound account): - If `result.IsSuccess && result.Data != null`: proceed normally (save session, set JWT, fire success) - If `result.IsSuccess && result.Data == null`: fire `OnLoginFailed` with a clear message about account not being bound/linked **Must NOT do**: - Do not change the password login flow (LoginWithPasswordAsync) - Do not change register flow - Do not change the encryption logic - Do not modify ThirdPartyServiceManager **Recommended Agent Profile**: > - **Category**: `quick` > - Reason: Single null check addition in one method > - **Skills**: none required > - **Skills Evaluated but Omitted**: N/A **Parallelization**: > - **Can Run In Parallel**: YES > - **Parallel Group**: Wave 1 (with Tasks 1, 2) > - **Blocks**: None > - **Blocked By**: None **References**: - `Assets/Scripts/Online/Logic/AuthService.cs` — File to edit - Server `AuthController.cs` at `/mnt/d/Projects/IchniOnline/IchniOnline.Server/Controller/AuthController.cs` lines 70-77 — Shows the `ThirdPartyLogin` can return `GlobalResponse.Ok(null, "Account not bound")` - `ApiResponse.cs` `ApiResult` class — For understanding `IsSuccess`, `Data` properties **Acceptance Criteria**: - `CompleteTapTapLoginAsync` method has null guard before `result.Data.token` access - When `result.IsSuccess && result.Data == null`: calls `OnLoginFailed` with message containing "not bound" or "unbound" - When `result.IsSuccess && result.Data != null`: saves auth session, sets JWT, fires `OnLoginSuccess` - Password login flow (`LoginWithPasswordAsync`) is unchanged **QA Scenarios (MANDATORY)**: ``` Scenario: Verify null data handling for third-party login Tool: script-read Preconditions: AuthService.cs has been updated Steps: 1. Read the CompleteTapTapLoginAsync method 2. Verify: there is a null check for result.Data before result.Data.token access 3. Verify: null case calls OnLoginFailed with appropriate message Expected Result: NullReferenceException is prevented Evidence: .omo/evidence/task-2-null-check.txt Scenario: Verify password login flow is unchanged Tool: grep Preconditions: AuthService.cs has been updated Steps: 1. Search for "result\.Data\.token" in AuthService.cs — should only appear in the TapTap completion method 2. Verify all occurrences have null guards Expected Result: No unprotected access to result.Data.token Evidence: .omo/evidence/task-2-password-flow.txt ``` **Evidence to Capture**: - [ ] Read output showing the null check code - [ ] grep results **Commit**: YES - Message: `fix(auth): add null check for third-party login response data` - Files: `Assets/Scripts/Online/Logic/AuthService.cs` - Pre-commit: Verify build compiles --- ## Final Verification Wave - [ ] F1. **Plan Compliance Audit** — `oracle` Read the plan end-to-end. For each "Must Have": verify implementation exists (read file, check values). For each "Must NOT Have": search codebase for forbidden patterns — reject with file:line if found. Check evidence files exist in .omo/evidence/. Compare deliverables against plan. Output: `Must Have [N/N] | Must NOT Have [N/N] | Tasks [N/N] | VERDICT: APPROVE/REJECT` - [ ] F2. **Scope Fidelity Check** — `deep` For each task: read "What to do", read actual diff (git log/diff). Verify 1:1 — everything in spec was built (no missing), nothing beyond spec was built (no creep). Check "Must NOT do" compliance. Detect cross-task contamination. Output: `Tasks [N/N compliant] | Contamination [CLEAN/N issues] | VERDICT` --- ## Commit Strategy - **1**: `fix(api): rename GlobalResponse fields to camelCase, correct BaseUrl port to 5308` - Files: `ApiResponse.cs`, `ApiClient.cs` - **2**: `fix(auth): add null check for third-party login response data` - Files: `AuthService.cs` --- ## Success Criteria ### Verification Scenarios 1. `script-execute` confirms `JsonUtility.FromJson>` correctly parses `{"code":10000,"message":"OK","data":"test"}` 2. `ApiClient.cs` line 20 shows `BaseUrl = "http://localhost:5308"` 3. `AuthService.cs` has null guard before `result.Data.token` access ### Final Checklist - [ ] All "Must Have" present - [ ] All "Must NOT Have" absent - [ ] All 3 files modified correctly