The frontend component of PuffPastry, meant to interface with the backend, which in turn interfaces with the smart contract.
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

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.