Files
ichni_Official/.omo/plans/api-auth-fix.md
Developer ebd5dafa2d update
2026-06-18 10:06:49 +08:00

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 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<T> has PascalCase Code/Message/Datasilent deserialization failure
  • Server runs on http://localhost:5308 (launchSettings.json) but Unity client has http://localhost:60887all 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<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.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

  • 1. Fix ApiResponse.cs + ApiClient.cs — GlobalResponse camelCase + BaseUrl port

    What to do:

    • In ApiResponse.cs: Rename field Codecode, Messagemessage, Datadata
    • In ApiClient.cs:
      • Change BaseUrl default from "http://localhost:60887" to "http://localhost:5308"
      • Update all references to GlobalResponse<T> fields from PascalCase to camelCase:
        • response.Coderesponse.code
        • response.Messageresponse.message
        • response.Dataresponse.data
        • errorResponse.CodeerrorResponse.code
        • errorResponse.MessageerrorResponse.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<T> 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<T> or PostAsync<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 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<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.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<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

  • 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<LoginResponse>.Ok(null, "Account not bound")
    • ApiResponse.cs ApiResult<T> 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 Auditoracle 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 Checkdeep 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<T> 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<GlobalResponse<string>> 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