summaryrefslogtreecommitdiff
path: root/apps/web/lib
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-07 05:41:07 -0800
committerFuwn <[email protected]>2026-02-07 05:41:07 -0800
commita1a405e56a0907ed44bfaba721e0ea632e051141 (patch)
tree99621f7c1407eed732eeb5742fc7458c14ee6d48 /apps/web/lib
parentsecurity: remove unsafe-eval CSP, fix host header injection, harden API routes (diff)
downloadasa.news-a1a405e56a0907ed44bfaba721e0ea632e051141.tar.xz
asa.news-a1a405e56a0907ed44bfaba721e0ea632e051141.zip
fix: resolve 6 pre-ship audit bugs
- Webhook entry identifier: use entry GUID instead of feed identifier - Optimistic rollback: add previousTimeline snapshot and onError handler to both useToggleEntryReadState and useToggleEntrySavedState - Rate limiter memory leak: delete Map entries when window expires, use else-if to avoid re-setting after delete - Entries API limit param: use Number.isFinite guard instead of falsy coercion that treats 0 as default - PWA manifest: add PNG raster icon routes (192x192, 512x512) for devices that don't support SVG icons - Billing webhook: throw on DB errors and return 500 so Stripe retries failed events instead of silently losing them
Diffstat (limited to 'apps/web/lib')
-rw-r--r--apps/web/lib/queries/use-entry-state-mutations.ts20
-rw-r--r--apps/web/lib/rate-limit.ts4
2 files changed, 23 insertions, 1 deletions
diff --git a/apps/web/lib/queries/use-entry-state-mutations.ts b/apps/web/lib/queries/use-entry-state-mutations.ts
index a8c72d0..80bab79 100644
--- a/apps/web/lib/queries/use-entry-state-mutations.ts
+++ b/apps/web/lib/queries/use-entry-state-mutations.ts
@@ -65,6 +65,13 @@ export function useToggleEntryReadState() {
return { previousTimeline }
},
+ onError: (_error, _variables, context) => {
+ if (context?.previousTimeline) {
+ for (const [queryKey, queryData] of context.previousTimeline) {
+ queryClient.setQueryData(queryKey, queryData)
+ }
+ }
+ },
onSettled: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.timeline.all })
queryClient.invalidateQueries({ queryKey: queryKeys.savedEntries.all })
@@ -107,6 +114,10 @@ export function useToggleEntrySavedState() {
onMutate: async ({ entryIdentifier, isSaved }) => {
await queryClient.cancelQueries({ queryKey: queryKeys.timeline.all })
+ const previousTimeline = queryClient.getQueriesData<
+ InfiniteData<TimelineEntry[]>
+ >({ queryKey: queryKeys.timeline.all })
+
queryClient.setQueriesData<InfiniteData<TimelineEntry[]>>(
{ queryKey: queryKeys.timeline.all },
(existingData) => {
@@ -124,6 +135,15 @@ export function useToggleEntrySavedState() {
}
}
)
+
+ return { previousTimeline }
+ },
+ onError: (_error, _variables, context) => {
+ if (context?.previousTimeline) {
+ for (const [queryKey, queryData] of context.previousTimeline) {
+ queryClient.setQueryData(queryKey, queryData)
+ }
+ }
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.timeline.all })
diff --git a/apps/web/lib/rate-limit.ts b/apps/web/lib/rate-limit.ts
index 4016781..506511d 100644
--- a/apps/web/lib/rate-limit.ts
+++ b/apps/web/lib/rate-limit.ts
@@ -12,7 +12,9 @@ export function rateLimit(
(timestamp) => timestamp > windowStart
)
- if (recentTimestamps.length >= limit) {
+ if (recentTimestamps.length === 0) {
+ requestTimestamps.delete(identifier)
+ } else if (recentTimestamps.length >= limit) {
requestTimestamps.set(identifier, recentTimestamps)
return { success: false, remaining: 0 }
}