The frontend component of PuffPastry, meant to interface with the backend, which in turn interfaces with the smart contract.
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 
 
 

7.2 KiB

sessionId
session-261005-174554-ealk

Requirements

Overview & Goals

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.

Scope

  • In Scope:
    • 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).
    • Update confirm and reject voting methods in src/components/AmendmentCarousel.vue to dynamically retrieve the authenticated user ID.
    • Handle edge cases gracefully when the JWT is absent, expired, or missing user identification.
  • Out of Scope:
    • Backend token signing or signature verification algorithms (handled on the server).
    • Redesigning unrelated UI flows or authentication modals.

User Stories

  • 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.
  • As an unauthenticated or expired user, I want voting attempts to fail gracefully with informative feedback rather than submitting fraudulent or invalid IDs.

Functional Requirements

  • JWT Claim Extraction: Safely decode Base64Url-encoded JWT tokens stored in localStorage (authToken and token fallbacks) without third-party runtime bloat.
  • User ID Resolution: Extract the numerical user identifier from userId, user_id, or sub claims in the token payload.
  • Dynamic Vote Submission: In AmendmentCarousel.vue, invoke AmendmentService.voteAmendment with the decoded userId.
  • Missing Token Handling: If no valid user ID can be resolved, log a clear warning or error and prevent submitting invalid voting requests.

Technical Design

Current Implementation

In src/components/BrandBar.vue, upon Freighter authentication, the token is stored:

const jwt = await AuthService.loginFreighter({ stellarAddress: addr.address });
localStorage.setItem("authToken", jwt.token as string);

In src/components/AmendmentCarousel.vue, confirm and reject currently use a hardcoded userId: 1:

const confirm = async (amendmentId: number) => {
  try {
    await AmendmentService.voteAmendment({
      amendmentId,
      inFavor: true,
      userId: 1, // Hardcoded placeholder
    });
    await getAmendments();
  } catch (e) {
    console.error("Failed to confirm amendment:", e);
  }
};

Key Decisions

  • 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.
  • Flexible Token Key Resolution: Check localStorage.getItem("authToken") with fallback to localStorage.getItem("token") to ensure seamless compatibility across all authentication paths.
  • 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.

Proposed Changes

  1. src/utils/auth.ts:

    • Create utility functions:
      export interface JwtPayload {
        userId?: number;
        user_id?: number;
        sub?: string | number;
        stellarAddress?: string;
        exp?: number;
        [key: string]: unknown;
      }
      
      export function parseJwt(token: string): JwtPayload | null {
        try {
          const base64Url = token.split('.')[1];
          if (!base64Url) return null;
          const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
          const jsonPayload = decodeURIComponent(
            atob(base64)
              .split('')
              .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
              .join('')
          );
          return JSON.parse(jsonPayload);
        } catch {
          return null;
        }
      }
      
      export function getAuthToken(): string | null {
        try {
          return localStorage.getItem('authToken') || localStorage.getItem('token');
        } catch {
          return null;
        }
      }
      
      export function getUserIdFromJwt(): number | undefined {
        const token = getAuthToken();
        if (!token) return undefined;
        const payload = parseJwt(token);
        if (!payload) return undefined;
        const rawId = payload.userId ?? payload.user_id ?? payload.sub;
        const parsedId = Number(rawId);
        return !isNaN(parsedId) && parsedId > 0 ? parsedId : undefined;
      }
      
  2. src/components/AmendmentCarousel.vue:

    • Import getUserIdFromJwt from ../utils/auth.ts.
    • Update confirm and reject:
      const confirm = async (amendmentId: number) => {
        const userId = getUserIdFromJwt();
        if (!userId) {
          console.error("User is not authenticated or user ID is missing from JWT.");
          error.value = "Please connect your wallet to vote on amendments.";
          return;
        }
        try {
          await AmendmentService.voteAmendment({
            amendmentId,
            inFavor: true,
            userId,
          });
          await getAmendments();
        } catch (e) {
          console.error("Failed to confirm amendment:", e);
        }
      };
      
    • Apply matching logic for reject(amendmentId: number).

File Structure

  • New File: src/utils/auth.ts (JWT extraction and user identification helpers)
  • Modified File: src/components/AmendmentCarousel.vue (Dynamic user ID in voting handlers)

Testing

Validation Approach

  • Execute TypeScript diagnostics to ensure strict type compliance.
  • Verify JWT decoding behavior against standard token formats, invalid tokens, and missing tokens.

Key Scenarios

  1. Valid JWT Present: When authToken contains a valid JWT with userId: 42, confirm and reject pass userId: 42 to AmendmentService.voteAmendment.
  2. Alternative Claims: When JWT contains user_id or sub, the parser accurately extracts the numeric user ID.
  3. No Token / Invalid Token: When no token exists or the token is malformed, voting is blocked gracefully with appropriate user/console error messaging.

Delivery Steps

✓ Step 1: Implement JWT decoding and auth helper utility

Create robust utility functions to read and decode JWT claims from localStorage.

  • Create src/utils/auth.ts with parseJwt, getAuthToken, and getUserIdFromJwt.
  • Support standard Base64Url decoding with UTF-8 character safety.
  • Extract userId, user_id, and sub claims, validating numeric output.

✓ Step 2: Connect dynamic user ID resolution in AmendmentCarousel

Update vote submission handlers to use the decoded user ID from the active JWT session.

  • Import getUserIdFromJwt in src/components/AmendmentCarousel.vue.
  • Update confirm to obtain the active user ID and pass it in VoteAmendmentRequest.
  • Update reject to obtain the active user ID and pass it in VoteAmendmentRequest.
  • Add validation and user feedback when the user is not authenticated.