An Auto-Posting Tool You Restrict to Admins the Day You Ship It

The Top Anglers tab has a 'Kopie teks' / 'Ouver mo paz' share tool that copies a pre-written leaderboard post and opens the Facebook page — commit 61def53 restricted it to admins before it shipped further.

An Auto-Posting Tool You Restrict to Admins the Day You Ship It

Commit 61def53, "Restrict the Facebook share tool to admins," is one of the more recent commits in the fishing-tracker history, and it's a small, almost boring-looking diff. What makes it worth writing about is the order of operations: the share tool and the access restriction landed close together, not the restriction as an afterthought bolted on after the feature had been live and unrestricted for a while.

Auto-posting, restricted from day one
Auto-posting, restricted from day one

commit 61def53: access control shipped with the feature, not after.

What the tool actually does

It's not an API integration with Facebook's Graph API — there's no OAuth token, no app review, no permission scope to request. It's simpler and more constrained than that: the admin panel's Top Anglers tab generates a pre-formatted leaderboard post in Kreol Morisien from real data (this week's or this month's top contributors by trips logged, fish caught, fishing types used, baits used), puts it on the clipboard with a Kopie teks button, and opens facebook.com/people/nulapessapp/... — the app's own page — with an Ouver mo paz button, so the admin can paste it manually.

const copyLeaderboardText = () => {
  const text = formatLeaderboardPost(leaderboardData, period);
  navigator.clipboard.writeText(text);
};

const openFacebookPage = () => {
  window.open(process.env.REACT_APP_FACEBOOK_PAGE_URL || DEFAULT_PAGE_URL, '_blank');
};

No token exchange means no credential to leak, but it's still a tool that generates public-facing marketing copy referencing real usernames and real statistics pulled straight from the database — and that's a capability worth gating regardless of how it's implemented.

Why "admin only" isn't obvious from the mechanism alone

It would be easy to reason "there's no API call to Facebook here, no write access to anything external — what's the actual risk of any logged-in user seeing this button?" The answer is that the data is the sensitive part, not the Facebook integration. The leaderboard text is built from real user activity: usernames, trip counts, species counts. Surfacing a "generate a public post from live user data" button to every regular user — even one that only copies text to their own clipboard — extends a data-exposure surface that has nothing to do with whether Facebook's API is involved.

router.get('/admin/leaderboard/share-text', authMiddleware, adminMiddleware, shareController.getShareText);

adminMiddleware runs after authMiddleware, checking the authenticated user's role before the controller ever assembles that text — the same pattern used everywhere else in the admin API surface, so this route isn't a special case requiring its own review; it's compliance with an existing convention.

The lesson generalizes past this one feature

Any feature that formats and exposes aggregate data — a leaderboard, an export button, a "share your stats" link — carries the same shape of risk even when it has no external write path: it's a read surface over data the person clicking the button didn't necessarily have a reason to see in bulk. Restricting it to admins isn't about preventing a bad Facebook post; it's about not handing every user a formatted dump of everyone else's activity just because the button happened to be easy to build into the same tab.

Series: Fishing Tracker Pro. Next: why the auth system runs both Google OAuth and JWT, and what each one is actually responsible for.