fix(og): rich social previews for share-token + slideshow URLs (#699)
Two SSR-OG injection bugs reported by @alexvaltchev. Both made his link
previews fall back to the brand logo + site-wide tagline instead of the
event-specific name/photo, even though the bot UA was hitting our
already-existing OG handler. He compensated with a Cloudflare Worker as
SSR middleware — which then created bug 3 below (og:image at the
auth-gated /api/.../hero/ path, not the public /og/.../cover one), so
Instagram never rendered the image either.
## Bug A — slideshow URLs miss the OG handler entirely
`/gallery/<slug>/show/<token>` has 3 segments after `/gallery/`. The OG
route was wired only at `/gallery/:slug/:token?` (1-2 segments), so
slideshow links fell through to the SPA-catchall `/gallery/*` and never
invoked the OG handler at all. Added a second route handler for the
3-segment slideshow shape, sharing the same intercept middleware so a
recognised social crawler still gets the rich preview.
## Bug B — share-token-only URLs resolve to nothing
`/gallery/<32-char-share-token>` (the form produced when migration 525's
short-URLs option strips the event slug) routes to the OG handler with
`slug=<token>`. resolveSlug then queries `events.slug = <token>`, which
never matches because the token is in a separate `share_token` column.
Result: falls through to the "no event found" branch and serves the
generic site-wide OG.
Fix: when the slug shape matches a 32-char hex AND the slug lookup
missed AND no redirect rule applies, try `events.share_token = slug` as
a final fallback. Real slugs are kebab/dot/underscore mixes, never pure
32-hex, so the extra DB roundtrip is gated to only fire for the
token-shaped URL.
## Tests
3 new tests in galleryOgService.shareImage.test.js using non-entropy
32-hex fixtures (deliberately zero-padded to avoid tripping
GitGuardian's Generic High Entropy Secret detector while still
matching the route's /^[a-f0-9]{32}$/i shape check):
- share-token slug resolves via the share_token column (alex's case)
- malformed/expired 32-hex token returns the site-wide fallback (no leak)
- non-hex slugs skip the share_token query entirely (hot-path cost guarded)
All 14 tests in the file pass.
## Out of scope here (separate follow-up)
- Issue 2 (Instagram og:image) — alex-side CF Worker bug pointing
og:image at /api/gallery/<slug>/hero/<id>, which requires gallery
auth. PicPeak already has the right unauthenticated path
(/og/gallery/<slug>/cover) gated by events.og_image_share_enabled
per-event opt-in (#474). Documented in the issue reply.
- Issue 3 (URL shortener with custom names) — real feature request,
meaningfully different from the existing #525 short-URLs option that
just strips the slug. Designing separately.
This commit is contained in:
+11
-2
@@ -769,12 +769,21 @@ try {
|
||||
// SPA fallback for admin + gallery routes. For gallery URLs we intercept
|
||||
// social-crawler User-Agents and serve OG/Twitter-card metadata so link
|
||||
// previews show the event name + branding instead of the SPA stub.
|
||||
app.get('/gallery/:slug/:token?', (req, res, next) => {
|
||||
//
|
||||
// Two route shapes — 1-2 segments (`/gallery/:slug/:token?`) and the
|
||||
// 3-segment slideshow form (`/gallery/:slug/show/:token`). The slideshow
|
||||
// shape was previously falling through to the SPA-catchall below and
|
||||
// skipping OG injection entirely (#699). Both patterns route to the
|
||||
// same handler — buildOgMetadata only looks at `slug`, so the extra
|
||||
// /show/ segment is harmless.
|
||||
const ogIntercept = (req, res, next) => {
|
||||
if (isSocialCrawler(req.get('user-agent'))) {
|
||||
return handleGalleryOgRequest(req, res);
|
||||
}
|
||||
return next();
|
||||
}, (req, res) => res.sendFile(indexPath));
|
||||
};
|
||||
app.get('/gallery/:slug/:token?', ogIntercept, (req, res) => res.sendFile(indexPath));
|
||||
app.get('/gallery/:slug/show/:token', ogIntercept, (req, res) => res.sendFile(indexPath));
|
||||
|
||||
app.get(['/admin', '/admin/*', '/gallery/*'], (req, res) => {
|
||||
res.sendFile(indexPath);
|
||||
|
||||
Reference in New Issue
Block a user