Skip to content

Commit 11f58ea

Browse files
committed
fix(frontend): resolve all security and code quality issues
1 parent b35ca4a commit 11f58ea

4 files changed

Lines changed: 32 additions & 13 deletions

File tree

frontend/src/components/claims/__tests__/claims-board.property.test.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -247,11 +247,8 @@ describe("Property 9: No authentication-dependent UI rendered without JWT", () =
247247
}),
248248
);
249249
const text = container.textContent ?? "";
250-
const html = container.innerHTML;
251250
// "Needs my vote" text must be completely absent from the DOM
252-
return (
253-
!text.includes("Needs my vote") && !html.includes("Needs my vote")
254-
);
251+
return !text.includes("Needs my vote");
255252
},
256253
),
257254
{ numRuns: 100 },

frontend/src/lib/api/claim.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { IpfsUploadResponse } from '../types/claim';
2+
import { getConfig } from '@/config/env';
23

3-
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
4+
const { apiUrl: API_BASE_URL } = getConfig();
45

56
export interface Claim {
67
id: number;
@@ -49,13 +50,13 @@ export class ClaimAPI {
4950
return this.handleResponse<BuildClaimTransactionResponse>(response);
5051
}
5152

52-
static async submitTransaction(transactionXdr: string): Promise<any> {
53+
static async submitTransaction(transactionXdr: string): Promise<{ claimId: number; transactionHash: string }> {
5354
const response = await fetch(`${API_BASE_URL}/api/claims/submit`, {
5455
method: 'POST',
5556
headers: { 'Content-Type': 'application/json' },
5657
body: JSON.stringify({ transactionXdr }),
5758
});
58-
return this.handleResponse<any>(response);
59+
return this.handleResponse<{ claimId: number; transactionHash: string }>(response);
5960
}
6061

6162
static async getClaim(claimId: number): Promise<Claim> {

frontend/src/lib/api/support.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
1+
import { getConfig } from '@/config/env';
2+
3+
const { apiUrl: API_BASE_URL } = getConfig();
24

35
export interface TicketPayload {
46
email: string;

frontend/src/lib/hooks/useRealtimeTallies.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
"use client";
22

3-
import { useEffect, useRef, useCallback } from "react";
3+
import { useEffect, useRef } from "react";
4+
import { z } from "zod";
45
import type { TallyUpdate } from "@/components/claims/types";
56

7+
// Schema to validate untrusted SSE / polling payloads before use
8+
const TallyUpdateSchema = z.object({
9+
claimId: z.string(),
10+
approveVotes: z.number().int().nonnegative(),
11+
rejectVotes: z.number().int().nonnegative(),
12+
quorumThreshold: z.number().int().positive(),
13+
deadlineTimestamp: z.string(),
14+
})
15+
16+
function parseTallyUpdate(raw: unknown): TallyUpdate | null {
17+
const result = TallyUpdateSchema.safeParse(raw)
18+
return result.success ? (result.data as TallyUpdate) : null
19+
}
20+
621
// ---------------------------------------------------------------------------
722
// Constants
823
// ---------------------------------------------------------------------------
@@ -125,9 +140,12 @@ export function useRealtimeTallies(
125140
throw new Error(`Polling failed: ${response.status}`);
126141
}
127142

128-
const updates: TallyUpdate[] = await response.json();
143+
const updates = (await response.json() as unknown[]);
129144
if (!unmounted) {
130-
updates.forEach((u) => onUpdateRef.current(u));
145+
updates.forEach((raw) => {
146+
const u = parseTallyUpdate(raw);
147+
if (u) onUpdateRef.current(u);
148+
});
131149
failureCount = 0; // reset on success
132150
scheduleNextPoll(false);
133151
}
@@ -182,8 +200,9 @@ export function useRealtimeTallies(
182200
eventSource.onmessage = (event: MessageEvent) => {
183201
if (unmounted) return;
184202
try {
185-
const update: TallyUpdate = JSON.parse(event.data as string);
186-
onUpdateRef.current(update);
203+
const raw: unknown = JSON.parse(event.data as string);
204+
const update = parseTallyUpdate(raw);
205+
if (update) onUpdateRef.current(update);
187206
} catch {
188207
// Ignore malformed messages.
189208
}

0 commit comments

Comments
 (0)