Skip to content

fix: align wallet role storage with server responses#1513

Merged
simo6529 merged 2 commits intomainfrom
071025-1
Oct 7, 2025
Merged

fix: align wallet role storage with server responses#1513
simo6529 merged 2 commits intomainfrom
071025-1

Conversation

@simo6529
Copy link
Copy Markdown
Collaborator

@simo6529 simo6529 commented Oct 7, 2025

Summary by CodeRabbit

  • New Features

    • Added server-driven role synchronization and explicit role validation to ensure token and server role consistency.
    • Per-wallet-address role storage introduced so roles persist and are scoped to the active wallet.
  • Bug Fixes

    • Roles now sync reliably with the server and are cleaned up when absent or on logout, preventing stale or mismatched permissions.
    • Wallet addresses normalized to avoid case-related issues.
  • Tests

    • Added and updated tests covering synchronization, per-address storage, and role validation.

Signed-off-by: Simo <simo@6529.io>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 7, 2025

Walkthrough

Adds per-address role storage and a server-authoritative synchronization path; introduces exported validateJwtRole and syncWalletRoleWithServer functions; set/remove JWT now manage both global and per-address role localStorage keys and normalize wallet addresses to lowercase. Tests updated accordingly.

Changes

Cohort / File(s) Summary
Auth utils implementation
services/auth/auth.utils.ts
- Added getAddressRoleStorageKey(address) to compute per-address key and normalize addresses to lowercase.
- setAuthJwt: stores/removes global auth-role and per-address auth-role-${addr} when role present/absent; lowercases address when storing address-specific key.
- removeAuthJwt: clears per-address role entry if an address is available and also clears global role.
- syncWalletRoleWithServer(serverRole, walletAddress): new export that sets or removes both global and per-address role keys based on server-provided role; lowercases address first.
- validateJwtRole(freshJwt, walletRole, requestedRole?): new exported function performing server-driven JWT role validation with explicit error cases.
Unit tests for auth utils
__tests__/services/auth.utils.test.ts
- Tests updated to import and exercise syncWalletRoleWithServer for present and null server roles; verifies localStorage.setItem/removeItem calls for both global and per-address keys.
- setAuthJwt/removeAuthJwt tests extended to expect per-address auth-role-${addr} behavior and normalized (lowercased) address handling.
- Tests cover validateJwtRole behavior and error signaling where applicable.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant App as Client
  participant Auth as auth.utils
  participant LS as localStorage
  participant Srv as Server

  rect rgb(235,245,255)
  note right of Auth: setAuthJwt / removeAuthJwt
  User->>App: authenticate
  App->>Auth: setAuthJwt(jwt, walletAddr, role?)
  Auth->>Auth: addr := lowercase(walletAddr)
  alt role provided
    Auth->>LS: setItem("auth-role", role)
    Auth->>LS: setItem("auth-role-"+addr, role)
  else no role
    Auth->>LS: removeItem("auth-role")
    Auth->>LS: removeItem("auth-role-"+addr)
  end
  App->>Auth: removeAuthJwt()
  Auth->>Auth: read stored addr? -> lowercase
  Auth->>LS: removeItem("auth-role-"+addr)
  Auth->>LS: removeItem("auth-role")
  end

  rect rgb(240,255,240)
  note right of Auth: syncWalletRoleWithServer (server-authoritative)
  App->>Srv: fetch wallet role for addr
  Srv-->>App: serverRole (string|null)
  App->>Auth: syncWalletRoleWithServer(serverRole, walletAddr)
  Auth->>Auth: addr := lowercase(walletAddr)
  alt serverRole != null
    Auth->>LS: setItem("auth-role", serverRole)
    Auth->>LS: setItem("auth-role-"+addr, serverRole)
  else serverRole == null
    Auth->>LS: removeItem("auth-role")
    Auth->>LS: removeItem("auth-role-"+addr)
  end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I lower each address, tuck roles in rows,
Server taps my paw — I sync what it shows.
Per-burrow keys, neat in a line,
I hop, clear caches, and make them align.
Carrots safe, the ledger hums — all fine. 🥕🐇

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly summarizes the main change — aligning wallet role storage with server responses. It directly reflects the new syncWalletRoleWithServer behavior and associated updates to localStorage in setAuthJwt and removeAuthJwt. It avoids generic phrasing and clearly communicates the PR’s focus.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 071025-1

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb20bb4 and 4378a6b.

📒 Files selected for processing (1)
  • services/auth/auth.utils.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript for implementation code

Files:

  • services/auth/auth.utils.ts
🧬 Code graph analysis (1)
services/auth/auth.utils.ts (1)
helpers/safeLocalStorage.ts (1)
  • safeLocalStorage (3-11)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
services/auth/auth.utils.ts (1)

72-80: Extract per-address key construction to reduce duplication.

The per-address storage key construction auth-role-${normalizedAddress} is duplicated in setAuthJwt, removeAuthJwt, and syncWalletRoleWithServer.

Consider extracting a helper function:

+const getAddressRoleStorageKey = (address: string): string => {
+  return `auth-role-${address.toLowerCase()}`;
+};
+
 export const setAuthJwt = (
   address: string,
   jwt: string,
   refreshToken: string,
   role?: string
 ) => {
   const jwtExpiration = getJwtExpiration(jwt);
   const now = Math.floor(Date.now() / 1000);
   const expiresInSeconds = jwtExpiration - now;
   const expiresInDays = expiresInSeconds / 86400;

   Cookies.set(WALLET_AUTH_COOKIE, jwt, {
     ...COOKIE_OPTIONS,
     expires: expiresInDays,
   });

   safeLocalStorage.setItem(WALLET_ADDRESS_STORAGE_KEY, address);
   safeLocalStorage.setItem(WALLET_REFRESH_TOKEN_STORAGE_KEY, refreshToken);
-  const normalizedAddress = address.toLowerCase();
-  const addressRoleStorageKey = `auth-role-${normalizedAddress}`;
+  const addressRoleStorageKey = getAddressRoleStorageKey(address);
   if (role) {
     safeLocalStorage.setItem(WALLET_ROLE_STORAGE_KEY, role);
     safeLocalStorage.setItem(addressRoleStorageKey, role);
   } else {
     safeLocalStorage.removeItem(WALLET_ROLE_STORAGE_KEY);
     safeLocalStorage.removeItem(addressRoleStorageKey);
   }
 };

Then apply similar changes to removeAuthJwt and syncWalletRoleWithServer.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2018a27 and eb20bb4.

📒 Files selected for processing (2)
  • __tests__/services/auth.utils.test.ts (4 hunks)
  • services/auth/auth.utils.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript for implementation code

Files:

  • __tests__/services/auth.utils.test.ts
  • services/auth/auth.utils.ts
**/{__tests__/**/*.{ts,tsx},*.test.tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/{__tests__/**/*.{ts,tsx},*.test.tsx}: Place tests in tests directories or alongside components as ComponentName.test.tsx
Mock external dependencies and APIs in tests

Files:

  • __tests__/services/auth.utils.test.ts
__tests__/**

📄 CodeRabbit inference engine (tests/AGENTS.md)

Place Jest test suites under the __tests__ directory mirroring source folders (e.g., components, contexts, hooks, utils)

Files:

  • __tests__/services/auth.utils.test.ts
🧬 Code graph analysis (2)
__tests__/services/auth.utils.test.ts (2)
helpers/safeLocalStorage.ts (1)
  • safeLocalStorage (3-11)
services/auth/auth.utils.ts (2)
  • setAuthJwt (53-81)
  • syncWalletRoleWithServer (124-138)
services/auth/auth.utils.ts (1)
helpers/safeLocalStorage.ts (1)
  • safeLocalStorage (3-11)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (10)
__tests__/services/auth.utils.test.ts (7)

11-11: LGTM!

The import correctly adds the new function to the test suite.


54-58: LGTM!

The test correctly verifies that per-address role storage is created with the normalized (lowercase) address.


60-70: LGTM!

The test correctly verifies that both global and per-address role keys are removed when no role is provided, and correctly expects the lowercase normalized address in the storage key despite passing a mixed-case address.


72-82: LGTM!

The test correctly verifies the new syncWalletRoleWithServer function, including address normalization to lowercase.


84-92: LGTM!

The test correctly verifies role removal when server role is null, with proper address normalization.


147-147: LGTM!

The mock return value update enables testing of address normalization in the removal logic.


162-164: LGTM!

The test correctly verifies that per-address role storage is removed when clearing auth, with proper address normalization from the mocked "Addr" to "auth-role-addr".

services/auth/auth.utils.ts (3)

122-122: Review comment against coding guidelines.

The coding guidelines state "Do not include any comments in the code" for TypeScript files. However, this is a JSDoc comment documenting a public API function.

Please clarify whether JSDoc comments for public APIs are exempt from the "no comments" guideline, or if this comment should be removed.


128-137: LGTM!

The implementation correctly normalizes the address and manages both global and per-address role storage keys consistently with setAuthJwt.


110-118: Per-address role keys are cleaned up in removeAuthJwt
removeAuthJwt is the only code path that removes WALLET_ADDRESS_STORAGE_KEY and it also deletes the matching auth-role-${address}, so orphaned keys cannot occur.

Signed-off-by: Simo <simo@6529.io>
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Oct 7, 2025

@simo6529 simo6529 merged commit 81662c0 into main Oct 7, 2025
8 checks passed
@simo6529 simo6529 deleted the 071025-1 branch October 7, 2025 10:29
This was referenced Feb 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants