The frontend component of PuffPastry, meant to interface with the backend, which in turn interfaces with the smart contract.
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

add-vote-amendment-parameters.md 7.2 KiB

​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. ---
  2. sessionId: session-261005-174554-ealk
  3. ---
  4. # Requirements
  5. ### Overview & Goals
  6. The objective is to dynamically resolve and read the current user's ID from their JWT authentication token when voting on amendments in `src/components/AmendmentCarousel.vue`. This replaces hardcoded placeholder user IDs (`userId: 1`) with genuine authenticated user context to ensure reliable vote attribution and system integrity.
  7. ### Scope
  8. - **In Scope**:
  9. - Implement a dedicated JWT decoding and auth helper utility (`src/utils/auth.ts`) to extract payload claims (specifically `userId`, `user_id`, or `sub`) from stored tokens in `localStorage` (`authToken` / `token`).
  10. - Update `confirm` and `reject` voting methods in `src/components/AmendmentCarousel.vue` to dynamically retrieve the authenticated user ID.
  11. - Handle edge cases gracefully when the JWT is absent, expired, or missing user identification.
  12. - **Out of Scope**:
  13. - Backend token signing or signature verification algorithms (handled on the server).
  14. - Redesigning unrelated UI flows or authentication modals.
  15. ### User Stories
  16. - As a logged-in user, I want my votes on amendments to be recorded under my actual account ID decoded from my session JWT so that my voting contributions are correctly counted.
  17. - As an unauthenticated or expired user, I want voting attempts to fail gracefully with informative feedback rather than submitting fraudulent or invalid IDs.
  18. ### Functional Requirements
  19. - **JWT Claim Extraction**: Safely decode Base64Url-encoded JWT tokens stored in `localStorage` (`authToken` and `token` fallbacks) without third-party runtime bloat.
  20. - **User ID Resolution**: Extract the numerical user identifier from `userId`, `user_id`, or `sub` claims in the token payload.
  21. - **Dynamic Vote Submission**: In `AmendmentCarousel.vue`, invoke `AmendmentService.voteAmendment` with the decoded `userId`.
  22. - **Missing Token Handling**: If no valid user ID can be resolved, log a clear warning or error and prevent submitting invalid voting requests.
  23. # Technical Design
  24. ### Current Implementation
  25. In `src/components/BrandBar.vue`, upon Freighter authentication, the token is stored:
  26. ```typescript
  27. const jwt = await AuthService.loginFreighter({ stellarAddress: addr.address });
  28. localStorage.setItem("authToken", jwt.token as string);
  29. ```
  30. In `src/components/AmendmentCarousel.vue`, `confirm` and `reject` currently use a hardcoded `userId: 1`:
  31. ```typescript
  32. const confirm = async (amendmentId: number) => {
  33. try {
  34. await AmendmentService.voteAmendment({
  35. amendmentId,
  36. inFavor: true,
  37. userId: 1, // Hardcoded placeholder
  38. });
  39. await getAmendments();
  40. } catch (e) {
  41. console.error("Failed to confirm amendment:", e);
  42. }
  43. };
  44. ```
  45. ### Key Decisions
  46. - **Lightweight Client-Side JWT Parser**: Implement a lightweight decoding utility in `src/utils/auth.ts` using native Base64Url decoding (`atob` / `decodeURIComponent`) to minimize dependencies while ensuring high performance.
  47. - **Flexible Token Key Resolution**: Check `localStorage.getItem("authToken")` with fallback to `localStorage.getItem("token")` to ensure seamless compatibility across all authentication paths.
  48. - **Graceful Error Handling**: If no valid user ID is present in the token, log an error / set component error state and avoid making a malformed API call.
  49. ### Proposed Changes
  50. 1. **`src/utils/auth.ts`**:
  51. - Create utility functions:
  52. ```typescript
  53. export interface JwtPayload {
  54. userId?: number;
  55. user_id?: number;
  56. sub?: string | number;
  57. stellarAddress?: string;
  58. exp?: number;
  59. [key: string]: unknown;
  60. }
  61. export function parseJwt(token: string): JwtPayload | null {
  62. try {
  63. const base64Url = token.split('.')[1];
  64. if (!base64Url) return null;
  65. const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
  66. const jsonPayload = decodeURIComponent(
  67. atob(base64)
  68. .split('')
  69. .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
  70. .join('')
  71. );
  72. return JSON.parse(jsonPayload);
  73. } catch {
  74. return null;
  75. }
  76. }
  77. export function getAuthToken(): string | null {
  78. try {
  79. return localStorage.getItem('authToken') || localStorage.getItem('token');
  80. } catch {
  81. return null;
  82. }
  83. }
  84. export function getUserIdFromJwt(): number | undefined {
  85. const token = getAuthToken();
  86. if (!token) return undefined;
  87. const payload = parseJwt(token);
  88. if (!payload) return undefined;
  89. const rawId = payload.userId ?? payload.user_id ?? payload.sub;
  90. const parsedId = Number(rawId);
  91. return !isNaN(parsedId) && parsedId > 0 ? parsedId : undefined;
  92. }
  93. ```
  94. 2. **`src/components/AmendmentCarousel.vue`**:
  95. - Import `getUserIdFromJwt` from `../utils/auth.ts`.
  96. - Update `confirm` and `reject`:
  97. ```typescript
  98. const confirm = async (amendmentId: number) => {
  99. const userId = getUserIdFromJwt();
  100. if (!userId) {
  101. console.error("User is not authenticated or user ID is missing from JWT.");
  102. error.value = "Please connect your wallet to vote on amendments.";
  103. return;
  104. }
  105. try {
  106. await AmendmentService.voteAmendment({
  107. amendmentId,
  108. inFavor: true,
  109. userId,
  110. });
  111. await getAmendments();
  112. } catch (e) {
  113. console.error("Failed to confirm amendment:", e);
  114. }
  115. };
  116. ```
  117. - Apply matching logic for `reject(amendmentId: number)`.
  118. ### File Structure
  119. - **New File**: `src/utils/auth.ts` (JWT extraction and user identification helpers)
  120. - **Modified File**: `src/components/AmendmentCarousel.vue` (Dynamic user ID in voting handlers)
  121. # Testing
  122. ### Validation Approach
  123. - Execute TypeScript diagnostics to ensure strict type compliance.
  124. - Verify JWT decoding behavior against standard token formats, invalid tokens, and missing tokens.
  125. ### Key Scenarios
  126. 1. **Valid JWT Present**: When `authToken` contains a valid JWT with `userId: 42`, `confirm` and `reject` pass `userId: 42` to `AmendmentService.voteAmendment`.
  127. 2. **Alternative Claims**: When JWT contains `user_id` or `sub`, the parser accurately extracts the numeric user ID.
  128. 3. **No Token / Invalid Token**: When no token exists or the token is malformed, voting is blocked gracefully with appropriate user/console error messaging.
  129. # Delivery Steps
  130. ### ✓ Step 1: Implement JWT decoding and auth helper utility
  131. Create robust utility functions to read and decode JWT claims from localStorage.
  132. - Create `src/utils/auth.ts` with `parseJwt`, `getAuthToken`, and `getUserIdFromJwt`.
  133. - Support standard Base64Url decoding with UTF-8 character safety.
  134. - Extract `userId`, `user_id`, and `sub` claims, validating numeric output.
  135. ### ✓ Step 2: Connect dynamic user ID resolution in AmendmentCarousel
  136. Update vote submission handlers to use the decoded user ID from the active JWT session.
  137. - Import `getUserIdFromJwt` in `src/components/AmendmentCarousel.vue`.
  138. - Update `confirm` to obtain the active user ID and pass it in `VoteAmendmentRequest`.
  139. - Update `reject` to obtain the active user ID and pass it in `VoteAmendmentRequest`.
  140. - Add validation and user feedback when the user is not authenticated.