fixes; locked down api (allegedly, idk, i let claude vibe it) and vandilised pedophiles profiles
This commit is contained in:
+47
-4
@@ -63,6 +63,9 @@ const siteGateEnabled = String(import.meta.env.VITE_SITE_GATE || 'false').toLowe
|
|||||||
const staticDataBase = (import.meta.env.VITE_STATIC_DATA_BASE || '/data').replace(/\/+$/, '')
|
const staticDataBase = (import.meta.env.VITE_STATIC_DATA_BASE || '/data').replace(/\/+$/, '')
|
||||||
const staticDataEnabled = String(import.meta.env.VITE_STATIC_DATA || 'false').toLowerCase() === 'true'
|
const staticDataEnabled = String(import.meta.env.VITE_STATIC_DATA || 'false').toLowerCase() === 'true'
|
||||||
const missingStaticDataPaths = new Set()
|
const missingStaticDataPaths = new Set()
|
||||||
|
|
||||||
|
const BLOCKED_PLAYER_UIDS = new Set(['165569402', '86157459', '33536334', '41808996', '3651161'])
|
||||||
|
const BLOCKED_TEAM_NAMES = new Set(['TPC'])
|
||||||
const blogPostFiles = import.meta.glob('./blog/posts/*.md', {
|
const blogPostFiles = import.meta.glob('./blog/posts/*.md', {
|
||||||
eager: true,
|
eager: true,
|
||||||
import: 'default',
|
import: 'default',
|
||||||
@@ -1393,11 +1396,11 @@ function AppContent() {
|
|||||||
games: { status: 'idle', data: null, error: null },
|
games: { status: 'idle', data: null, error: null },
|
||||||
})
|
})
|
||||||
const teams = useMemo(
|
const teams = useMemo(
|
||||||
() => leaderboard.data?.teams || leaderboard.data?.squadrons || [],
|
() => (leaderboard.data?.teams || leaderboard.data?.squadrons || []).filter((t) => !BLOCKED_TEAM_NAMES.has(bestTeamName(t))),
|
||||||
[leaderboard.data],
|
[leaderboard.data],
|
||||||
)
|
)
|
||||||
const players = useMemo(
|
const players = useMemo(
|
||||||
() => playerLeaderboard.data?.players || [],
|
() => (playerLeaderboard.data?.players || []).filter((p) => !BLOCKED_PLAYER_UIDS.has(String(p?.uid || ''))),
|
||||||
[playerLeaderboard.data],
|
[playerLeaderboard.data],
|
||||||
)
|
)
|
||||||
const teamsToWatch = useMemo(
|
const teamsToWatch = useMemo(
|
||||||
@@ -1645,10 +1648,14 @@ function AppContent() {
|
|||||||
if (controller.signal.aborted) return
|
if (controller.signal.aborted) return
|
||||||
|
|
||||||
const teamResults = teamResult.status === 'fulfilled'
|
const teamResults = teamResult.status === 'fulfilled'
|
||||||
? (teamResult.value.teams || teamResult.value.results || []).map(teamSearchResult)
|
? (teamResult.value.teams || teamResult.value.results || [])
|
||||||
|
.filter((team) => !BLOCKED_TEAM_NAMES.has(bestTeamName(team)))
|
||||||
|
.map(teamSearchResult)
|
||||||
: []
|
: []
|
||||||
const playerResults = playerResult.status === 'fulfilled'
|
const playerResults = playerResult.status === 'fulfilled'
|
||||||
? (playerResult.value.players || []).map(playerSearchResult)
|
? (playerResult.value.players || [])
|
||||||
|
.filter((player) => !BLOCKED_PLAYER_UIDS.has(String(player?.uid || '')))
|
||||||
|
.map(playerSearchResult)
|
||||||
: []
|
: []
|
||||||
const results = dedupeSearchResults([...teamResults, ...playerResults]).slice(0, 10)
|
const results = dedupeSearchResults([...teamResults, ...playerResults]).slice(0, 10)
|
||||||
|
|
||||||
@@ -2663,6 +2670,26 @@ function PlayerStatCard({ label, value }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function BlockedPage({ onBack }) {
|
||||||
|
return (
|
||||||
|
<section className="mx-auto max-w-4xl pb-12 pt-24 sm:pt-28">
|
||||||
|
<div className="mt-8 rounded-xl border border-border bg-fury-white p-10 text-center">
|
||||||
|
<p className="text-7xl">🤡🤡🤡🤡</p>
|
||||||
|
<p className="mt-4 text-text-soft">user affiliated with pedophiles, blocked~</p>
|
||||||
|
{onBack ? (
|
||||||
|
<button
|
||||||
|
className="mt-6 text-sm font-semibold text-fury-cyan transition hover:text-text"
|
||||||
|
onClick={onBack}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Go back
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function PlayerPage({ uid, navigate }) {
|
function PlayerPage({ uid, navigate }) {
|
||||||
const [state, setState] = useState({ status: 'loading', data: null, error: '' })
|
const [state, setState] = useState({ status: 'loading', data: null, error: '' })
|
||||||
|
|
||||||
@@ -2671,6 +2698,10 @@ function PlayerPage({ uid, navigate }) {
|
|||||||
setState({ status: 'error', data: null, error: 'No player specified.' })
|
setState({ status: 'error', data: null, error: 'No player specified.' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (BLOCKED_PLAYER_UIDS.has(String(uid))) {
|
||||||
|
setState({ status: 'blocked', data: null, error: '' })
|
||||||
|
return
|
||||||
|
}
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
setState({ status: 'loading', data: null, error: '' })
|
setState({ status: 'loading', data: null, error: '' })
|
||||||
fetchPublicJson(publicDataSources.player(uid))
|
fetchPublicJson(publicDataSources.player(uid))
|
||||||
@@ -2687,6 +2718,12 @@ function PlayerPage({ uid, navigate }) {
|
|||||||
|
|
||||||
const { status, data, error } = state
|
const { status, data, error } = state
|
||||||
|
|
||||||
|
if (status === 'blocked') {
|
||||||
|
return (
|
||||||
|
<BlockedPage onBack={() => navigate('/players')} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="mx-auto max-w-4xl pb-12 pt-24 sm:pt-28">
|
<section className="mx-auto max-w-4xl pb-12 pt-24 sm:pt-28">
|
||||||
<div className="border-b border-border pb-6">
|
<div className="border-b border-border pb-6">
|
||||||
@@ -4241,6 +4278,12 @@ function TeamProfilePage({ navigate, profile, requestedTeam, teams }) {
|
|||||||
const leaderboardTeam = teams.find((team) => bestTeamName(team) === requestedTeam)
|
const leaderboardTeam = teams.find((team) => bestTeamName(team) === requestedTeam)
|
||||||
const displayName = detail?.name || bestTeamName(leaderboardTeam) || requestedTeam
|
const displayName = detail?.name || bestTeamName(leaderboardTeam) || requestedTeam
|
||||||
|
|
||||||
|
if (BLOCKED_TEAM_NAMES.has(requestedTeam)) {
|
||||||
|
return (
|
||||||
|
<BlockedPage onBack={() => navigate('/teams')} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="space-y-6 pt-24 sm:pt-28">
|
<section className="space-y-6 pt-24 sm:pt-28">
|
||||||
<button
|
<button
|
||||||
|
|||||||
+21
-3
@@ -1944,6 +1944,8 @@ function isRateLimited(req) {
|
|||||||
const current = rateLimits.get(ip)
|
const current = rateLimits.get(ip)
|
||||||
|
|
||||||
if (!current || current.resetAt <= now) {
|
if (!current || current.resetAt <= now) {
|
||||||
|
// When the map is at capacity, deny new IPs rather than clearing existing limits.
|
||||||
|
if (!current && rateLimits.size >= MAX_RATE_LIMIT_KEYS) return true
|
||||||
rateLimits.set(ip, { count: 1, resetAt: now + API_RATE_LIMIT_WINDOW_MS })
|
rateLimits.set(ip, { count: 1, resetAt: now + API_RATE_LIMIT_WINDOW_MS })
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -1956,11 +1958,17 @@ function pruneMaps() {
|
|||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
|
|
||||||
for (const [key, value] of apiCache) {
|
for (const [key, value] of apiCache) {
|
||||||
if (value.expiresAt <= now || apiCache.size > MAX_CACHE_ENTRIES) apiCache.delete(key)
|
if (value.expiresAt <= now) apiCache.delete(key)
|
||||||
|
}
|
||||||
|
if (apiCache.size > MAX_CACHE_ENTRIES) {
|
||||||
|
const sorted = [...apiCache.entries()].sort((a, b) => a[1].expiresAt - b[1].expiresAt)
|
||||||
|
for (const [key] of sorted.slice(0, apiCache.size - MAX_CACHE_ENTRIES)) {
|
||||||
|
apiCache.delete(key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [key, value] of rateLimits) {
|
for (const [key, value] of rateLimits) {
|
||||||
if (value.resetAt <= now || rateLimits.size > MAX_RATE_LIMIT_KEYS) rateLimits.delete(key)
|
if (value.resetAt <= now) rateLimits.delete(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2156,6 +2164,8 @@ function proxyRequest(req, res) {
|
|||||||
delete headers['access-control-allow-methods']
|
delete headers['access-control-allow-methods']
|
||||||
delete headers['access-control-allow-headers']
|
delete headers['access-control-allow-headers']
|
||||||
delete headers['access-control-expose-headers']
|
delete headers['access-control-expose-headers']
|
||||||
|
delete headers['server']
|
||||||
|
delete headers['x-powered-by']
|
||||||
|
|
||||||
res.writeHead(statusCode, headers)
|
res.writeHead(statusCode, headers)
|
||||||
|
|
||||||
@@ -2759,7 +2769,11 @@ const server = http.createServer((req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (req.url === '/health') {
|
if (req.url === '/health') {
|
||||||
sendJson(res, 200, { ok: true, public_data: publicDataStartupStatus })
|
if (isRateLimited(req)) {
|
||||||
|
sendJson(res, 429, { error: 'Too many requests' }, { 'retry-after': String(Math.ceil(API_RATE_LIMIT_WINDOW_MS / 1000)) })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sendJson(res, 200, { ok: true })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2790,6 +2804,10 @@ const server = http.createServer((req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (req.method === 'GET' && req.url === '/api/viewers') {
|
if (req.method === 'GET' && req.url === '/api/viewers') {
|
||||||
|
if (!isSameOriginRequest(req)) {
|
||||||
|
sendJson(res, 403, { error: 'Viewer analytics are restricted to this site' })
|
||||||
|
return
|
||||||
|
}
|
||||||
if (isRateLimited(req)) {
|
if (isRateLimited(req)) {
|
||||||
sendJson(res, 429, { error: 'Too many requests' }, { 'retry-after': String(Math.ceil(API_RATE_LIMIT_WINDOW_MS / 1000)) })
|
sendJson(res, 429, { error: 'Too many requests' }, { 'retry-after': String(Math.ceil(API_RATE_LIMIT_WINDOW_MS / 1000)) })
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user