fix(shard): restrict staff in-game location to admins/moderators
The public "Staff online" list on the Shard page exposed each staff member's in-game location (map + coordinates) to everyone, including logged-in players and unauthenticated visitors. Location is now privileged data: - Server: getOnline inspects the caller's role via getUserFromRequest (the same non-rejecting helper siteMode uses on public routes) and only includes map/x/y/z for admin/moderator callers. For everyone else the fields are omitted from the JSON entirely, so they can't be read from the network tab. serial + name (online status) still shown. - Client: Shard.jsx gates the location span on the viewer's role from useAuth() (same pattern as RoleGate); non-privileged viewers see who is online but no location field is rendered. Tests: publicShardOnline.test.js covers admin + moderator (location included), player + unauthenticated + editor (location omitted). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XmHdsbnLzDMAVQkAoTQSBe
This commit is contained in:
@@ -8,6 +8,7 @@ import { describe } from '../../lib/shardEvents.js'
|
||||
import { ago } from '../../lib/format.js'
|
||||
import { api } from '../../api/client.js'
|
||||
import PlayersOnline from '../../components/PlayersOnline.jsx'
|
||||
import { useAuth } from '../../contexts/AuthContext.jsx'
|
||||
|
||||
// ── Gold-supply sparkline ───────────────────────────────────────────────────
|
||||
function Sparkline({ series }) {
|
||||
@@ -54,6 +55,11 @@ export default function Shard() {
|
||||
]).then(([status, idoc, economy, online]) => ({ status, idoc, economy, online })),
|
||||
)
|
||||
const { events, connected } = useShardFeed({ max: 30 })
|
||||
const { user } = useAuth()
|
||||
// Staff in-game location is privileged: only admins/moderators see it. Players
|
||||
// and the public see that staff are online but not where. The server enforces
|
||||
// this too (it omits the location fields entirely for non-privileged callers).
|
||||
const canSeeLocation = user?.role === 'admin' || user?.role === 'moderator'
|
||||
|
||||
const status = data?.status
|
||||
const online = status?.pluginConnected
|
||||
@@ -119,7 +125,7 @@ export default function Shard() {
|
||||
<PlayersOnline />
|
||||
</div>
|
||||
|
||||
{/* Staff online — linked staff accounts only, with location */}
|
||||
{/* Staff online — linked staff accounts only; location is admin/mod-only */}
|
||||
<section className="panel" style={{ padding: 20, marginBottom: 24 }}>
|
||||
<div className="sans" style={{ color: 'var(--accent)', fontSize: '0.7rem', letterSpacing: '0.12em', textTransform: 'uppercase', marginBottom: 12 }}>
|
||||
Staff online
|
||||
@@ -134,9 +140,11 @@ export default function Shard() {
|
||||
<span style={{ flex: 'none', width: 8, height: 8, borderRadius: '50%', background: '#7fd0a4' }} />
|
||||
{p.name || p.serial}
|
||||
</span>
|
||||
<span className="dim" style={{ flex: 'none', fontSize: '0.78rem' }}>
|
||||
{p.map || '—'}{p.x != null ? ` (${p.x}, ${p.y})` : ''}
|
||||
</span>
|
||||
{canSeeLocation && (
|
||||
<span className="dim" style={{ flex: 'none', fontSize: '0.78rem' }}>
|
||||
{p.map || '—'}{p.x != null ? ` (${p.x}, ${p.y})` : ''}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user