14 KiB
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 correctedAuthService.cs— null check added for third-party login responseEstimated 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.Jsonwith camelCase policy → JSON keys:code,message,data - Unity client uses
JsonUtility.FromJsonwhich is case-sensitive → C# field names must exactly match JSON keys - Current
GlobalResponse<T>has PascalCaseCode/Message/Data→ silent deserialization failure - Server runs on
http://localhost:5308(launchSettings.json) but Unity client hashttp://localhost:60887→ all requests fail - Third-party login can return
data: null(unbound account) → NullReferenceException when accessingresult.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 correctedAssets/Scripts/Online/Network/ApiClient.cs— BaseUrl port fixed, field access updatedAssets/Scripts/Online/Logic/AuthService.cs— null-safe third-party login handling
Definition of Done
IchniOnlineApiClient.Instance.BaseUrlreturnshttp://localhost:5308JsonUtility.FromJsoncorrectly populatesGlobalResponse<T>.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<T>field names to match JSON camelCase - Add null check for
result.Datain 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-executeto run C# test snippets that directly verifyJsonUtility.FromJsondeserialization 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
-
1. Fix ApiResponse.cs + ApiClient.cs — GlobalResponse camelCase + BaseUrl port
What to do:
- In
ApiResponse.cs: Rename fieldCode→code,Message→message,Data→data - In
ApiClient.cs:- Change
BaseUrldefault from"http://localhost:60887"to"http://localhost:5308" - Update all references to
GlobalResponse<T>fields from PascalCase to camelCase:response.Code→response.coderesponse.Message→response.messageresponse.Data→response.dataerrorResponse.Code→errorResponse.codeerrorResponse.Message→errorResponse.message
- Change
- Verify:
ResponseCodeenum 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
ResponseCodeenum values or names - Do not change
ApiResult<T>class structure - Do not add
[JsonProperty]or other attributes - Do not change the
BuildUrl,AddAuthHeader, orSendAsyncmethods - Do not change the method signatures of
GetAsync<T>orPostAsync<T>
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 siteAssets/Scripts/Online/Network/ApiClient.cs— Field usage site + BaseUrl- Server
GlobalResponse.csat/mnt/d/Projects/IchniOnline/IchniOnline.Server/Models/Responses/GlobalResponse.cs— Reference (PascalCase with System.Text.Json camelCase policy) - Backend
launchSettings.jsonat/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.csfield declarations use camelCase:code,message,dataApiClient.csfield references use camelCase:response.code,response.message,response.dataApiClient.csBaseUrl line 20:"http://localhost:5308"script-executeconfirmsJsonUtility.FromJson<GlobalResponse<string>>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<GlobalResponse<string>>(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<GlobalResponseBase>(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.txtEvidence 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<T> 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
- In
-
2. Fix AuthService.cs — third-party login null check
What to do:
- In
CompleteTapTapLoginAsyncmethod (~line 109): Add null check forresult.Databefore 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: fireOnLoginFailedwith a clear message about account not being bound/linked
- If
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.csat/mnt/d/Projects/IchniOnline/IchniOnline.Server/Controller/AuthController.cslines 70-77 — Shows theThirdPartyLogincan returnGlobalResponse<LoginResponse>.Ok(null, "Account not bound") ApiResponse.csApiResult<T>class — For understandingIsSuccess,Dataproperties
Acceptance Criteria:
CompleteTapTapLoginAsyncmethod has null guard beforeresult.Data.tokenaccess- When
result.IsSuccess && result.Data == null: callsOnLoginFailedwith message containing "not bound" or "unbound" - When
result.IsSuccess && result.Data != null: saves auth session, sets JWT, firesOnLoginSuccess - 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.txtEvidence 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
- In
Final Verification Wave
-
F1. Plan Compliance Audit —
oracleRead 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 —
deepFor 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<T> fields to camelCase, correct BaseUrl port to 5308- Files:
ApiResponse.cs,ApiClient.cs
- Files:
- 2:
fix(auth): add null check for third-party login response data- Files:
AuthService.cs
- Files:
Success Criteria
Verification Scenarios
script-executeconfirmsJsonUtility.FromJson<GlobalResponse<string>>correctly parses{"code":10000,"message":"OK","data":"test"}ApiClient.csline 20 showsBaseUrl = "http://localhost:5308"AuthService.cshas null guard beforeresult.Data.tokenaccess
Final Checklist
- All "Must Have" present
- All "Must NOT Have" absent
- All 3 files modified correctly