aboutsummaryrefslogtreecommitdiffstats
path: root/frontend/src/components
diff options
context:
space:
mode:
authorAdam Mathes <adam@adammathes.com>2026-02-14 09:09:10 -0800
committerAdam Mathes <adam@adammathes.com>2026-02-14 09:09:10 -0800
commitca1418fc0135d52a009ab218d6e24187fb355a3c (patch)
tree95f54977609ec401f8439a30e3a158c36a5526bf /frontend/src/components
parenta39dfd30529330e3eea44bce865093158eaf2f1b (diff)
downloadneko-ca1418fc0135d52a009ab218d6e24187fb355a3c.tar.gz
neko-ca1418fc0135d52a009ab218d6e24187fb355a3c.tar.bz2
neko-ca1418fc0135d52a009ab218d6e24187fb355a3c.zip
security: implement CSRF protection and improve session cookie security (fixing NK-gfh33y)
Diffstat (limited to 'frontend/src/components')
-rw-r--r--frontend/src/components/FeedItem.tsx4
-rw-r--r--frontend/src/components/FeedItems.test.tsx10
-rw-r--r--frontend/src/components/FeedItems.tsx7
-rw-r--r--frontend/src/components/FeedList.tsx5
-rw-r--r--frontend/src/components/Login.tsx4
-rw-r--r--frontend/src/components/Settings.tsx7
-rw-r--r--frontend/src/components/TagView.test.tsx4
7 files changed, 24 insertions, 17 deletions
diff --git a/frontend/src/components/FeedItem.tsx b/frontend/src/components/FeedItem.tsx
index 9b40114..ae4462a 100644
--- a/frontend/src/components/FeedItem.tsx
+++ b/frontend/src/components/FeedItem.tsx
@@ -2,6 +2,8 @@ import { useState, useEffect } from 'react';
import type { Item } from '../types';
import './FeedItem.css';
+import { apiFetch } from '../utils';
+
interface FeedItemProps {
item: Item;
}
@@ -24,7 +26,7 @@ export default function FeedItem({ item: initialItem }: FeedItemProps) {
const previousItem = item;
setItem(newItem);
- fetch(`/api/item/${newItem._id}`, {
+ apiFetch(`/api/item/${newItem._id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
diff --git a/frontend/src/components/FeedItems.test.tsx b/frontend/src/components/FeedItems.test.tsx
index 4d96da9..ca0dc98 100644
--- a/frontend/src/components/FeedItems.test.tsx
+++ b/frontend/src/components/FeedItems.test.tsx
@@ -21,7 +21,7 @@ describe('FeedItems Component', () => {
});
it('renders loading state', () => {
- (global.fetch as any).mockImplementation(() => new Promise(() => {}));
+ (global.fetch as any).mockImplementation(() => new Promise(() => { }));
render(
<MemoryRouter initialEntries={['/feed/1']}>
<Routes>
@@ -70,7 +70,7 @@ describe('FeedItems Component', () => {
const params = new URLSearchParams();
params.append('feed_id', '1');
params.append('read_filter', 'unread');
- expect(global.fetch).toHaveBeenCalledWith(`/api/stream?${params.toString()}`);
+ expect(global.fetch).toHaveBeenCalledWith(`/api/stream?${params.toString()}`, expect.anything());
});
it('handles keyboard shortcuts', async () => {
@@ -138,7 +138,7 @@ describe('FeedItems Component', () => {
});
// Capture the callback
- let observerCallback: IntersectionObserverCallback = () => {};
+ let observerCallback: IntersectionObserverCallback = () => { };
// Override the mock to capture callback
class MockIntersectionObserver {
@@ -199,7 +199,7 @@ describe('FeedItems Component', () => {
.mockResolvedValueOnce({ ok: true, json: async () => initialItems })
.mockResolvedValueOnce({ ok: true, json: async () => moreItems });
- let observerCallback: IntersectionObserverCallback = () => {};
+ let observerCallback: IntersectionObserverCallback = () => { };
class MockIntersectionObserver {
constructor(callback: IntersectionObserverCallback) {
observerCallback = callback;
@@ -240,7 +240,7 @@ describe('FeedItems Component', () => {
const params = new URLSearchParams();
params.append('max_id', '101');
params.append('read_filter', 'unread');
- expect(global.fetch).toHaveBeenCalledWith(`/api/stream?${params.toString()}`);
+ expect(global.fetch).toHaveBeenCalledWith(`/api/stream?${params.toString()}`, expect.anything());
});
});
});
diff --git a/frontend/src/components/FeedItems.tsx b/frontend/src/components/FeedItems.tsx
index 81c9139..b497e9d 100644
--- a/frontend/src/components/FeedItems.tsx
+++ b/frontend/src/components/FeedItems.tsx
@@ -3,6 +3,7 @@ import { useParams, useSearchParams } from 'react-router-dom';
import type { Item } from '../types';
import FeedItem from './FeedItem';
import './FeedItems.css';
+import { apiFetch } from '../utils';
export default function FeedItems() {
const { feedId, tagName } = useParams<{ feedId: string; tagName: string }>();
@@ -61,7 +62,7 @@ export default function FeedItems() {
url += `?${queryString}`;
}
- fetch(url)
+ apiFetch(url)
.then((res) => {
if (!res.ok) {
throw new Error('Failed to fetch items');
@@ -103,7 +104,7 @@ export default function FeedItems() {
// Optimistic update
setItems((prevItems) => prevItems.map((i) => (i._id === item._id ? updatedItem : i)));
- fetch(`/api/item/${item._id}`, {
+ apiFetch(`/api/item/${item._id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ read: true, starred: item.starred }),
@@ -115,7 +116,7 @@ export default function FeedItems() {
// Optimistic update
setItems((prevItems) => prevItems.map((i) => (i._id === item._id ? updatedItem : i)));
- fetch(`/api/item/${item._id}`, {
+ apiFetch(`/api/item/${item._id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ read: item.read, starred: !item.starred }),
diff --git a/frontend/src/components/FeedList.tsx b/frontend/src/components/FeedList.tsx
index 497baf8..d2dc8e1 100644
--- a/frontend/src/components/FeedList.tsx
+++ b/frontend/src/components/FeedList.tsx
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
import { Link, useNavigate, useSearchParams, useLocation, useParams } from 'react-router-dom';
import type { Feed, Category } from '../types';
import './FeedList.css';
+import { apiFetch } from '../utils';
export default function FeedList({
theme,
@@ -38,11 +39,11 @@ export default function FeedList({
useEffect(() => {
Promise.all([
- fetch('/api/feed/').then((res) => {
+ apiFetch('/api/feed/').then((res) => {
if (!res.ok) throw new Error('Failed to fetch feeds');
return res.json();
}),
- fetch('/api/tag').then((res) => {
+ apiFetch('/api/tag').then((res) => {
if (!res.ok) throw new Error('Failed to fetch tags');
return res.json();
}),
diff --git a/frontend/src/components/Login.tsx b/frontend/src/components/Login.tsx
index 5f63248..ba2cd96 100644
--- a/frontend/src/components/Login.tsx
+++ b/frontend/src/components/Login.tsx
@@ -2,6 +2,8 @@ import { useState, type FormEvent } from 'react';
import { useNavigate } from 'react-router-dom';
import './Login.css';
+import { apiFetch } from '../utils';
+
export default function Login() {
const [password, setPassword] = useState('');
const [error, setError] = useState('');
@@ -16,7 +18,7 @@ export default function Login() {
const params = new URLSearchParams();
params.append('password', password);
- const res = await fetch('/api/login', {
+ const res = await apiFetch('/api/login', {
method: 'POST',
body: params,
});
diff --git a/frontend/src/components/Settings.tsx b/frontend/src/components/Settings.tsx
index b4f6a3b..3f508e9 100644
--- a/frontend/src/components/Settings.tsx
+++ b/frontend/src/components/Settings.tsx
@@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react';
import type { Feed } from '../types';
import './Settings.css';
+import { apiFetch } from '../utils';
export default function Settings() {
const [feeds, setFeeds] = useState<Feed[]>([]);
@@ -10,7 +11,7 @@ export default function Settings() {
const fetchFeeds = () => {
setLoading(true);
- fetch('/api/feed/')
+ apiFetch('/api/feed/')
.then((res) => {
if (!res.ok) throw new Error('Failed to fetch feeds');
return res.json();
@@ -36,7 +37,7 @@ export default function Settings() {
if (!newFeedUrl) return;
setLoading(true);
- fetch('/api/feed/', {
+ apiFetch('/api/feed/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: newFeedUrl }),
@@ -59,7 +60,7 @@ export default function Settings() {
if (!globalThis.confirm('Are you sure you want to delete this feed?')) return;
setLoading(true);
- fetch(`/api/feed/${id}`, {
+ apiFetch(`/api/feed/${id}`, {
method: 'DELETE',
})
.then((res) => {
diff --git a/frontend/src/components/TagView.test.tsx b/frontend/src/components/TagView.test.tsx
index 10872bc..8f7eb86 100644
--- a/frontend/src/components/TagView.test.tsx
+++ b/frontend/src/components/TagView.test.tsx
@@ -35,7 +35,7 @@ describe('Tag View Integration', () => {
render(
<MemoryRouter>
- <FeedList />
+ <FeedList theme="light" setTheme={() => { }} />
</MemoryRouter>
);
@@ -81,6 +81,6 @@ describe('Tag View Integration', () => {
const params = new URLSearchParams();
params.append('tag', 'Tech');
params.append('read_filter', 'unread');
- expect(global.fetch).toHaveBeenCalledWith(`/api/stream?${params.toString()}`);
+ expect(global.fetch).toHaveBeenCalledWith(`/api/stream?${params.toString()}`, expect.anything());
});
});