The frontend component of PuffPastry, meant to interface with the backend, which in turn interfaces with the smart contract.
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

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.