Security audit improvements: authentication, path traversal protection, file validation, rate limiting, security headers
This commit is contained in:
@@ -151,8 +151,19 @@ export default function AdminPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to add auth headers to requests
|
||||
const getAuthHeaders = () => {
|
||||
const authToken = localStorage.getItem('hoerdle_admin_auth');
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
'x-admin-auth': authToken || ''
|
||||
};
|
||||
};
|
||||
|
||||
const fetchSongs = async () => {
|
||||
const res = await fetch('/api/songs');
|
||||
const res = await fetch('/api/songs', {
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setSongs(data);
|
||||
@@ -160,7 +171,9 @@ export default function AdminPage() {
|
||||
};
|
||||
|
||||
const fetchGenres = async () => {
|
||||
const res = await fetch('/api/genres');
|
||||
const res = await fetch('/api/genres', {
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setGenres(data);
|
||||
@@ -171,6 +184,7 @@ export default function AdminPage() {
|
||||
if (!newGenreName.trim()) return;
|
||||
const res = await fetch('/api/genres', {
|
||||
method: 'POST',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({ name: newGenreName, subtitle: newGenreSubtitle }),
|
||||
});
|
||||
if (res.ok) {
|
||||
@@ -192,7 +206,7 @@ export default function AdminPage() {
|
||||
if (editingGenreId === null) return;
|
||||
const res = await fetch('/api/genres', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({
|
||||
id: editingGenreId,
|
||||
name: editGenreName,
|
||||
@@ -209,7 +223,9 @@ export default function AdminPage() {
|
||||
|
||||
// Specials functions
|
||||
const fetchSpecials = async () => {
|
||||
const res = await fetch('/api/specials');
|
||||
const res = await fetch('/api/specials', {
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setSpecials(data);
|
||||
@@ -220,7 +236,7 @@ export default function AdminPage() {
|
||||
e.preventDefault();
|
||||
const res = await fetch('/api/specials', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({
|
||||
name: newSpecialName,
|
||||
subtitle: newSpecialSubtitle,
|
||||
@@ -249,7 +265,7 @@ export default function AdminPage() {
|
||||
if (!confirm('Delete this special?')) return;
|
||||
const res = await fetch('/api/specials', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({ id }),
|
||||
});
|
||||
if (res.ok) fetchSpecials();
|
||||
@@ -258,7 +274,9 @@ export default function AdminPage() {
|
||||
|
||||
// Daily Puzzles functions
|
||||
const fetchDailyPuzzles = async () => {
|
||||
const res = await fetch('/api/admin/daily-puzzles');
|
||||
const res = await fetch('/api/admin/daily-puzzles', {
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setDailyPuzzles(data);
|
||||
@@ -269,7 +287,7 @@ export default function AdminPage() {
|
||||
if (!confirm('Delete this daily puzzle? A new one will be generated automatically.')) return;
|
||||
const res = await fetch('/api/admin/daily-puzzles', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({ puzzleId }),
|
||||
});
|
||||
if (res.ok) {
|
||||
@@ -328,7 +346,7 @@ export default function AdminPage() {
|
||||
if (editingSpecialId === null) return;
|
||||
const res = await fetch('/api/specials', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({
|
||||
id: editingSpecialId,
|
||||
name: editSpecialName,
|
||||
@@ -357,6 +375,7 @@ export default function AdminPage() {
|
||||
if (!confirm('Delete this genre?')) return;
|
||||
const res = await fetch('/api/genres', {
|
||||
method: 'DELETE',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({ id }),
|
||||
});
|
||||
if (res.ok) {
|
||||
@@ -383,7 +402,7 @@ export default function AdminPage() {
|
||||
while (hasMore) {
|
||||
const res = await fetch('/api/categorize', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({ offset })
|
||||
});
|
||||
|
||||
@@ -453,6 +472,7 @@ export default function AdminPage() {
|
||||
|
||||
const res = await fetch('/api/songs', {
|
||||
method: 'POST',
|
||||
headers: { 'x-admin-auth': localStorage.getItem('hoerdle_admin_auth') || '' },
|
||||
body: formData,
|
||||
});
|
||||
|
||||
@@ -555,7 +575,7 @@ export default function AdminPage() {
|
||||
|
||||
const res = await fetch('/api/songs', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({
|
||||
id: uploadedSong.id,
|
||||
title: uploadedSong.title,
|
||||
@@ -596,7 +616,7 @@ export default function AdminPage() {
|
||||
const saveEditing = async (id: number) => {
|
||||
const res = await fetch('/api/songs', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({
|
||||
id,
|
||||
title: editTitle,
|
||||
@@ -623,7 +643,7 @@ export default function AdminPage() {
|
||||
|
||||
const res = await fetch('/api/songs', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({ id }),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user