Skip to content

fix(invite): route /invite through the backend and unify the permissions - #1893

Merged
LucasSantana-Dev merged 3 commits into
mainfrom
fix/invite-redirect-nginx
Jul 27, 2026
Merged

LucasSantana-Dev merged 3 commits into
mainfrom
fix/invite-redirect-nginx

Conversation

@LucasSantana-Dev

@LucasSantana-Dev LucasSantana-Dev commented Jul 27, 2026 •

Copy link
Copy Markdown
Owner

Follow-up to #1889 / #1888. That PR put the /invite redirect in vercel.json, which does not apply to production.

Why the first fix missed

lucky.lucassantana.tech does not go through Vercel. It routes through the Cloudflare tunnel to the homelab nginx:

ingress:
  - hostname: lucky.lucassantana.tech
    service: http://nginx:80

So the canonical invite URL still served the SPA, whose catch-all bounces unknown paths to the landing page. Verified against production after the v2.37.2 deploy:

$ curl -o /dev/null -w '%{http_code} %{redirect_url}' https://lucky.lucassantana.tech/invite
200          # no Location header

That URL is what the Top.gg listing body, the README CTA and every tracked campaign link in .agents/tracking-urls.md point at, so every invite click was swallowed and no utm_source ever recorded an install.

Change

Adds the redirect to nginx/nginx.conf, where production actually serves it. vercel.json keeps its copy for preview deploys.

  • location = /invite (exact match) so no other path is caught.
  • 302, not 301: browsers cache 301 permanently, which would make a wrong client_id or permission set unfixable for anyone who had already clicked.
  • permissions=3165184 matches getBotInviteUrl() in lib/discord.ts. Not Administrator, per the public listing claim.

Verification

Config validated with nginx -t against nginxinc/nginx-unprivileged:1.31-alpine, the same base Dockerfile.nginx builds from:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Will re-verify against production once deployed.

Blocks

The Top.gg resubmission. The listing description links /invite, so a reviewer clicking it would land on the homepage rather than an invite prompt.


Summary by cubic

Route /invite through the backend to preserve UTM attribution and redirect to Discord OAuth with the minimal permissions. Fixes invite links (Top.gg, README, campaigns) being swallowed by the SPA and stops attribution loss.

  • Bug Fixes
    • nginx/nginx.conf: exact-match location = /invite now proxies to the backend so UTM params are logged before redirect; SPA no longer catches it; added notes on why internal is not used (Semgrep false positive).
    • Centralized the invite URL/permissions in @lucky/shared (BOT_CLIENT_ID, BOT_INVITE_PERMISSIONS=3165184, buildBotInviteUrl()), updated frontend/backend to use it with tests to pin the bits and scope; vercel.json now points /invite to the backend for previews.

Written for commit 7c2c907. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added a dedicated /invite link that redirects visitors to the configured Discord authorization page.
    • Ensured consistent invite behavior across frontend and preview deployments.

#1889 added the /invite redirect to vercel.json, but production does not
go through Vercel: lucky.lucassantana.tech routes through the Cloudflare
tunnel to the homelab nginx (config-lucky.yml ingress -> http://nginx:80).
So the canonical invite URL still returned 200 with the SPA, which then
bounced to the landing page.

Verified against production after the v2.37.2 deploy:

  curl -o /dev/null -w '%{http_code} %{redirect_url}' \
    https://lucky.lucassantana.tech/invite
  -> 200, no Location header

That URL is what the Top.gg listing, the README CTA and every tracked
campaign link point at, so every invite click was being swallowed and no
utm_source ever recorded an install.

Adds the rule where production actually serves it. vercel.json keeps its
copy for preview deploys. Exact match (location = /invite) so nothing
else is caught, and 302 rather than 301 because browsers cache 301
permanently, which would make a wrong client_id or permission set
unfixable for anyone who had already clicked.

Permission set matches getBotInviteUrl() in lib/discord.ts: 3165184, not
Administrator.

Config validated with `nginx -t` against nginxinc/nginx-unprivileged:1.31
-alpine, the same image Dockerfile.nginx builds from.
@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The nginx configuration adds an exact-match /invite route that returns a 302 redirect to the Discord OAuth2 authorization URL with the configured client ID, permissions, and scope.

Changes

Invite redirect

Layer / File(s) Summary
Add the invite redirect route
nginx/nginx.conf
Adds an exact-match /invite location that redirects requests to the Discord OAuth2 authorize URL with the configured authorization parameters.

Estimated code review effort: 1 (Trivial) | ~2 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: adding a /invite redirect and aligning the invite permissions.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/invite-redirect-nginx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

Failed to generate code suggestions for PR

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 1 file

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread nginx/nginx.conf Outdated
cubic was right on #1893: redirecting at the edge fixes the click but
drops the attribution. backend/src/routes/invite.ts already existed and
logs the utm_* parameters before redirecting, but nginx only proxies
/api, so /invite never reached it and fell through to the SPA catch-all.

nginx now proxies the exact path to the backend instead of returning a
redirect itself, so tracked links keep emitting `[invite] click`.

Chasing that turned up a third permission value. The codebase shipped:

  backend invite.ts       36970496  Manage Messages, Connect, Speak
  frontend discord.ts      3165184  View Channels, Send Messages,
                                    Embed Links, Connect, Speak
  docs page (fixed #1889)         8  Administrator

The backend one is the worst of the three: it asks for Manage Messages,
which is message deletion, while omitting View Channels and Send
Messages, so it is over-scoped and non-functional at the same time. It is
also the value that every tracked link resolved to.

Single source of truth is now BOT_INVITE_PERMISSIONS in shared, consumed
by the backend redirect and the frontend helper. Test pins the bitfield:
no Administrator, no Manage Messages, exactly the five needed bits, and
the applications.commands scope whose absence caused #1885.

vercel.json points at the backend too, so preview deploys log the same.

nginx -t clean against nginxinc/nginx-unprivileged:1.31-alpine. Suites
green: shared 1398, frontend 1019, backend 1350.
@LucasSantana-Dev

Copy link
Copy Markdown
Owner Author

Good catch, and it was more than a P2. Fixed in 834c4537.

You were right that backend/src/routes/invite.ts already logs [invite] click with the utm parameters, and that a return 302 at the edge would have bypassed it. I had checked the frontend router and vercel.json and concluded no /invite handler existed anywhere — I never grepped the backend. The real bug was narrower than I described: the handler was fine, but nginx only proxies /api, so nothing ever reached it.

nginx now proxies location = /invite to the backend rather than redirecting itself, so tracked links keep their attribution.

What that turned up. Following your pointer to the backend handler surfaced a third permission value:

source value permissions
backend/src/routes/invite.ts 36970496 Manage Messages, Connect, Speak
frontend/src/lib/discord.ts 3165184 View Channels, Send Messages, Embed Links, Connect, Speak
docs page (fixed in #1889) 8 Administrator

The backend value is the worst of the three: it requests Manage Messages (message deletion) while omitting View Channels and Send Messages, so it is simultaneously over-scoped and non-functional. And since it is the handler every tracked link resolves to, it is what real users were being asked to approve — while the Top.gg listing says "No admin permission" and implies a minimal set.

BOT_INVITE_PERMISSIONS now lives in packages/shared/src/constants/invite.ts, consumed by both the backend redirect and the frontend helper. The test pins the bitfield rather than the string: no Administrator, no Manage Messages, exactly the five bits the bot needs, plus the applications.commands scope whose absence caused #1885.

vercel.json points at the backend too, so preview deploys log identically.

nginx -t clean against nginxinc/nginx-unprivileged:1.31-alpine. Suites green: shared 1398, frontend 1019, backend 1350.

@github-actions github-actions Bot added size/m and removed size/s labels Jul 27, 2026
@LucasSantana-Dev LucasSantana-Dev changed the title fix(nginx): redirect /invite to discord at the edge fix(invite): route /invite through the backend and unify the permissions Jul 27, 2026
Comment thread nginx/nginx.conf Fixed
@github-actions

github-actions Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Warnings
⚠️

User-facing change without a CHANGELOG.md update. Add a line under ## [Unreleased] if this should appear in release notes. (Or apply the skip-changelog label if this PR does not affect end users.)

Generated by 🚫 dangerJS against 7c2c907

@github-actions

Copy link
Copy Markdown

Size Change: +287 B (+0.06%)

Total Size: 498 kB

📦 View Changed
Filename Size Change
packages/frontend/dist/assets/AddStyledRoleForm-Cx5-0Q3m.js 3.11 kB +3.11 kB (new file) 🆕
packages/frontend/dist/assets/AddStyledRoleForm-WTP6Gz5U.js 0 B -3.11 kB (removed) 🏆
packages/frontend/dist/assets/Admin-BCIQgwjr.js 0 B -2.3 kB (removed) 🏆
packages/frontend/dist/assets/Admin-DZLLrm6x.js 2.3 kB +2.3 kB (new file) 🆕
packages/frontend/dist/assets/AdminSupport-DgyxlPzL.js 1.6 kB +1.6 kB (new file) 🆕
packages/frontend/dist/assets/AdminSupport-DUkrvBJZ.js 0 B -1.6 kB (removed) 🏆
packages/frontend/dist/assets/api-BBv75RPO.js 3.76 kB +3.76 kB (new file) 🆕
packages/frontend/dist/assets/api-CtLilru1.js 0 B -4 kB (removed) 🏆
packages/frontend/dist/assets/AutoMessages-473vnycD.js 0 B -2.65 kB (removed) 🏆
packages/frontend/dist/assets/AutoMessages-DlVrpPHG.js 2.65 kB +2.65 kB (new file) 🆕
packages/frontend/dist/assets/AutoMod-MCm_cQQo.js 4.19 kB +4.19 kB (new file) 🆕
packages/frontend/dist/assets/AutoMod-WEkv8PdC.js 0 B -4.19 kB (removed) 🏆
packages/frontend/dist/assets/badge-BGWESMjZ.js 0 B -504 B (removed) 🏆
packages/frontend/dist/assets/badge-TAC-ZGip.js 503 B +503 B (new file) 🆕
packages/frontend/dist/assets/BatchJobs-C3yAs_DG.js 0 B -3.72 kB (removed) 🏆
packages/frontend/dist/assets/BatchJobs-CoKkhP3-.js 3.71 kB +3.71 kB (new file) 🆕
packages/frontend/dist/assets/Card-B5vAvluV.js 0 B -506 B (removed) 🏆
packages/frontend/dist/assets/Card-C0atL-ph.js 504 B +504 B (new file) 🆕
packages/frontend/dist/assets/Changelog-BaMgxdMG.js 0 B -59.3 kB (removed) 🏆
packages/frontend/dist/assets/Changelog-hEaAGyfG.js 59.4 kB +59.4 kB (new file) 🆕
packages/frontend/dist/assets/CommandsConfig-Bm0QUqz0.js 1.5 kB +1.5 kB (new file) 🆕
packages/frontend/dist/assets/CommandsConfig-DxNph5Pg.js 0 B -1.5 kB (removed) 🏆
packages/frontend/dist/assets/Config-B9wKGogq.js 1.93 kB +1.93 kB (new file) 🆕
packages/frontend/dist/assets/Config-DJhGos1G.js 0 B -1.91 kB (removed) 🏆
packages/frontend/dist/assets/constants-DfwHQtAY.js 605 B +605 B (new file) 🆕
packages/frontend/dist/assets/CustomCommands-B352YMXW.js 0 B -2.12 kB (removed) 🏆
packages/frontend/dist/assets/CustomCommands-DOWmY7B_.js 2.12 kB +2.12 kB (new file) 🆕
packages/frontend/dist/assets/DashboardOverview-B2zJi2mL.js 3.95 kB +3.95 kB (new file) 🆕
packages/frontend/dist/assets/DashboardOverview-CPkhMrwy.js 0 B -3.95 kB (removed) 🏆
packages/frontend/dist/assets/dialog-HVcphePk.js 0 B -957 B (removed) 🏆
packages/frontend/dist/assets/dialog-ZitVxPLb.js 958 B +958 B (new file) 🆕
packages/frontend/dist/assets/Docs-B4kA-Cub.js 17.5 kB +17.5 kB (new file) 🆕
packages/frontend/dist/assets/Docs-CVB1bplF.js 0 B -17.5 kB (removed) 🏆
packages/frontend/dist/assets/DocsShell-JLgT1FOO.js 1.42 kB +1.42 kB (new file) 🆕
packages/frontend/dist/assets/DocsShell-zlrIvdz7.js 0 B -1.42 kB (removed) 🏆
packages/frontend/dist/assets/EmbedBuilder-cEvrffCy.js 0 B -3.27 kB (removed) 🏆
packages/frontend/dist/assets/EmbedBuilder-DoC80Tdf.js 3.27 kB +3.27 kB (new file) 🆕
packages/frontend/dist/assets/Features-D2k4z8Uh.js 755 B +755 B (new file) 🆕
packages/frontend/dist/assets/Features-Dvej7fUv.js 0 B -754 B (removed) 🏆
packages/frontend/dist/assets/GuildAutomation-B9v2AEr0.js 2.89 kB +2.89 kB (new file) 🆕
packages/frontend/dist/assets/GuildAutomation-U0Dc6190.js 0 B -2.88 kB (removed) 🏆
packages/frontend/dist/assets/index-C27g5p70.js 0 B -71.2 kB (removed) 🏆
packages/frontend/dist/assets/index-ohxQCBUd.js 71.2 kB +71.2 kB (new file) 🆕
packages/frontend/dist/assets/input-3-my5BYL.js 464 B +464 B (new file) 🆕
packages/frontend/dist/assets/input-5jRg2SqR.js 0 B -463 B (removed) 🏆
packages/frontend/dist/assets/label-6p8zFgpg.js 475 B +475 B (new file) 🆕
packages/frontend/dist/assets/label-DnPvG-Mn.js 0 B -475 B (removed) 🏆
packages/frontend/dist/assets/Landing-Biam55R8.js 5.1 kB +5.1 kB (new file) 🆕
packages/frontend/dist/assets/Landing-Df8P0xIT.js 0 B -5.1 kB (removed) 🏆
packages/frontend/dist/assets/LastFm-CDT5LTWz.js 0 B -1.74 kB (removed) 🏆
packages/frontend/dist/assets/LastFm-CpSgwFon.js 1.74 kB +1.74 kB (new file) 🆕
packages/frontend/dist/assets/Levels-1eRxg5hG.js 0 B -2.27 kB (removed) 🏆
packages/frontend/dist/assets/Levels-BR4jbqpq.js 2.27 kB +2.27 kB (new file) 🆕
packages/frontend/dist/assets/Login-C6IOXzfh.js 2.49 kB +2.49 kB (new file) 🆕
packages/frontend/dist/assets/Login-CM8YICUR.js 0 B -2.49 kB (removed) 🏆
packages/frontend/dist/assets/Lyrics-BbxeX4ha.js 1.34 kB +1.34 kB (new file) 🆕
packages/frontend/dist/assets/Lyrics-NvKYkdbH.js 0 B -1.34 kB (removed) 🏆
packages/frontend/dist/assets/Moderation-C6TK-HaL.js 0 B -3.78 kB (removed) 🏆
packages/frontend/dist/assets/Moderation-CvvWewpE.js 3.77 kB +3.77 kB (new file) 🆕
packages/frontend/dist/assets/Music-7BLvnNW4.js 0 B -5.96 kB (removed) 🏆
packages/frontend/dist/assets/Music-BIDBO6_N.js 5.96 kB +5.96 kB (new file) 🆕
packages/frontend/dist/assets/MusicConfig-CnQ1eWSO.js 1.68 kB +1.68 kB (new file) 🆕
packages/frontend/dist/assets/MusicConfig-fxiprJqp.js 0 B -1.68 kB (removed) 🏆
packages/frontend/dist/assets/PreferredArtists-aMvlOD-w.js 0 B -3.72 kB (removed) 🏆
packages/frontend/dist/assets/PreferredArtists-DtokK6wm.js 3.72 kB +3.72 kB (new file) 🆕
packages/frontend/dist/assets/PrivacyPolicy-BDIH6CTo.js 0 B -1.77 kB (removed) 🏆
packages/frontend/dist/assets/PrivacyPolicy-DJm0Pgmt.js 1.77 kB +1.77 kB (new file) 🆕
packages/frontend/dist/assets/ReactionRoles-CbULDKQ5.js 0 B -7.04 kB (removed) 🏆
packages/frontend/dist/assets/ReactionRoles-e-kjVidu.js 7.04 kB +7.04 kB (new file) 🆕
packages/frontend/dist/assets/RoleGroups-IbPgyUQn.js 0 B -2.24 kB (removed) 🏆
packages/frontend/dist/assets/RoleGroups-yrimpHN6.js 2.23 kB +2.23 kB (new file) 🆕
packages/frontend/dist/assets/Roles-BiXdSxwx.js 3.33 kB +3.33 kB (new file) 🆕
packages/frontend/dist/assets/Roles-Bv8VZzVO.js 0 B -3.34 kB (removed) 🏆
packages/frontend/dist/assets/SectionHeader-BWKyaqhx.js 0 B -894 B (removed) 🏆
packages/frontend/dist/assets/SectionHeader-C5YrOAyc.js 894 B +894 B (new file) 🆕
packages/frontend/dist/assets/select-BX4ehF8C.js 1.23 kB +1.23 kB (new file) 🆕
packages/frontend/dist/assets/select-DJwfpZPY.js 0 B -1.23 kB (removed) 🏆
packages/frontend/dist/assets/ServerLogs-CY-j9Y9O.js 3.04 kB +3.04 kB (new file) 🆕
packages/frontend/dist/assets/ServerLogs-qxtSvaIA.js 0 B -3.04 kB (removed) 🏆
packages/frontend/dist/assets/ServerSettings-Bcs7VepD.js 3.98 kB +3.98 kB (new file) 🆕
packages/frontend/dist/assets/ServerSettings-XCRCFlc3.js 0 B -3.98 kB (removed) 🏆
packages/frontend/dist/assets/ServersPage-CB5oT-3S.js 0 B -3.03 kB (removed) 🏆
packages/frontend/dist/assets/ServersPage-hrVN_Drr.js 3.03 kB +3.03 kB (new file) 🆕
packages/frontend/dist/assets/Skeleton-CwhEVbdv.js 234 B +234 B (new file) 🆕
packages/frontend/dist/assets/Skeleton-DSPrr4pu.js 0 B -234 B (removed) 🏆
packages/frontend/dist/assets/Spotify-CxQDG1Qg.js 0 B -1.75 kB (removed) 🏆
packages/frontend/dist/assets/Spotify-eMc798XN.js 1.75 kB +1.75 kB (new file) 🆕
packages/frontend/dist/assets/Starboard-C0Q2ywZN.js 1.82 kB +1.82 kB (new file) 🆕
packages/frontend/dist/assets/Starboard-CMbVC34b.js 0 B -1.82 kB (removed) 🏆
packages/frontend/dist/assets/StatTile-CTqHT69J.js 639 B +639 B (new file) 🆕
packages/frontend/dist/assets/StatTile-u_z4PpmL.js 0 B -638 B (removed) 🏆
packages/frontend/dist/assets/Support-BwWYpBs9.js 0 B -1.56 kB (removed) 🏆
packages/frontend/dist/assets/Support-EBQoutZw.js 1.56 kB +1.56 kB (new file) 🆕
packages/frontend/dist/assets/switch-CLH1z5_I.js 0 B -542 B (removed) 🏆
packages/frontend/dist/assets/switch-CxWdv9bJ.js 542 B +542 B (new file) 🆕
packages/frontend/dist/assets/TermsOfService-BMZ0DIlg.js 1.6 kB +1.6 kB (new file) 🆕
packages/frontend/dist/assets/TermsOfService-CDiDB9NL.js 0 B -1.59 kB (removed) 🏆
packages/frontend/dist/assets/TrackHistory-DKyuQKjM.js 2.31 kB +2.31 kB (new file) 🆕
packages/frontend/dist/assets/TrackHistory-DQGbs6p-.js 0 B -2.31 kB (removed) 🏆
packages/frontend/dist/assets/TwitchNotifications-B-MZTFk9.js 2.44 kB +2.44 kB (new file) 🆕
packages/frontend/dist/assets/TwitchNotifications-Ci3lU6gp.js 0 B -2.44 kB (removed) 🏆
packages/frontend/dist/assets/useActiveHeading-2mi69a5h.js 0 B -1.35 kB (removed) 🏆
packages/frontend/dist/assets/useActiveHeading-DrMGTy0j.js 1.36 kB +1.36 kB (new file) 🆕
packages/frontend/dist/assets/useFeatures-4a-VVDLr.js 2.06 kB +2.06 kB (new file) 🆕
packages/frontend/dist/assets/useFeatures-DspcZwJA.js 0 B -2.06 kB (removed) 🏆
ℹ️ View Unchanged
Filename Size
packages/frontend/dist/assets/index-DY6JagVQ.css 17.6 kB
packages/frontend/dist/assets/legalNav-B6k3CWsW.js 274 B
packages/frontend/dist/assets/rolldown-runtime-Cyuzqnbw.js 471 B
packages/frontend/dist/assets/routeMeta-BZjtwMbs.js 595 B
packages/frontend/dist/assets/sentry-DhXOA89y.js 3.76 kB
packages/frontend/dist/assets/usePageMetadata-DTv-6eVb.js 327 B
packages/frontend/dist/assets/vendor-forms-C-bof8GF.js 25.9 kB
packages/frontend/dist/assets/vendor-radix-qkfmDH9H.js 39.9 kB
packages/frontend/dist/assets/vendor-react-B7C34xnu.js 55.7 kB
packages/frontend/dist/assets/vendor-state-Kvbn3gqw.js 25.2 kB
packages/frontend/dist/assets/vendor-ui-BBN61NBD.js 66.2 kB

compressed-size-action

Semgrep's missing-internal rule fires on the new location block. It is a
false positive here and worth recording so it is not re-litigated.

`internal;` makes a location unreachable from outside, which would break
the invite link entirely — this is a public entry point by design, same
as the /api and / blocks, neither of which uses it either.

The rule's SSRF concern needs request-derived input in the proxy target.
The upstream here is a fixed literal, so a client cannot influence where
the proxy connects.
@LucasSantana-Dev

Copy link
Copy Markdown
Owner Author

False positive, annotated with nosemgrep and a comment explaining why (7c2c9077).

internal; makes a location reachable only from internal redirects. Applying it here would make the invite link return 404 for every real visitor, which is the exact failure this PR exists to fix. It is a public entry point by design — the existing /api and / blocks are the same shape and neither uses internal either.

The rule's SSRF concern requires request-derived input in the proxy target, so that a client can steer where the proxy connects. Here the upstream is a fixed literal:

set $invite_upstream http://backend:3000;
proxy_pass $invite_upstream;

No part of the request reaches that value. The set-then-proxy_pass indirection is this repo's existing convention for deferring DNS resolution to the runtime resolver, not a place variables enter the URL.

@sonarqubecloud

Copy link
Copy Markdown

@LucasSantana-Dev
LucasSantana-Dev merged commit 9372961 into main Jul 27, 2026
47 checks passed
@LucasSantana-Dev
LucasSantana-Dev deleted the fix/invite-redirect-nginx branch July 27, 2026 13:22
LucasSantana-Dev added a commit that referenced this pull request Jul 27, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>2.37.3</summary>

##
[2.37.3](v2.37.2...v2.37.3)
(2026-07-27)


### Bug Fixes

* **invite:** route /invite through the backend and unify the
permissions
([#1893](#1893))
([9372961](9372961))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Release 2.37.3 fixes /invite handling by routing it through the backend
and unifying permission checks. This makes invite scopes consistent and
reduces the risk of incorrect permissions.

<sup>Written for commit 35099e8.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/LucasSantana-Dev/Lucky/pull/1894?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
LucasSantana-Dev added a commit that referenced this pull request Jul 27, 2026
I fixed this twice in the wrong layer. lucky.lucassantana.tech is served
by Cloudflare Pages (project lucky-webapp, deploy-frontend-cf.yml), not
by Vercel and not by the homelab nginx behind the tunnel. So neither the
vercel.json redirect (#1889) nor the nginx location block (#1893) applied
to the public site, and /invite kept returning 200 with the SPA.

What gave it away: the live response carries a CSP allowing
static.cloudflareinsights.com, which appears only in
packages/frontend/public/_headers. The nginx config serves a different
CSP, so the request was never reaching it.

Meanwhile lucky-api.lucassantana.tech DOES go through the tunnel, and
already returns the correct 302 with permissions=3165184, so the backend
handler and the nginx work from #1893 are both fine and stay.

_redirects rules are evaluated top to bottom, so /invite is placed above
the SPA catch-all, which is what was swallowing it. Points at the backend
rather than Discord directly so the utm_* attribution logging still runs.

Verified the file lands in packages/frontend/dist after a build, which is
the directory `wrangler pages deploy` uploads.
LucasSantana-Dev added a commit that referenced this pull request Jul 27, 2026
lucky.lucassantana.tech is served by Cloudflare Pages (project
lucky-webapp), not by Vercel and not by the homelab nginx behind the
tunnel. So neither the vercel.json redirect (#1889) nor the nginx
location block (#1893) applied to the public site, and /invite kept
returning 200 with the SPA, whose catch-all bounces to the landing page.

_redirects rules are evaluated top to bottom, so /invite now sits above
the SPA catch-all that was swallowing it. It points at the backend rather
than Discord directly, so the utm_* attribution logging still runs.

The nginx block and shared BOT_INVITE_PERMISSIONS from #1893 stay: they
are what make lucky-api.lucassantana.tech/invite return the correct 302.

Closes #1888.
LucasSantana-Dev added a commit that referenced this pull request Jul 30, 2026
#1893 changed the invite permission set without reading
decisions/2026-06-18-invite-permission-scope.md, landing on 3165184
(music only). Its prerequisite #1498 is closed, so the ADR's curated set
should already have been live.

Adopts 3173504: music + ManageMessages (auto-mod cleanup) + ViewAuditLog,
which auditHandler needs to attribute moderation cases — without it that
attribution was silently degraded on every fresh install.

Tracing the call sites found a fifth copy: GuildService.generateBotInviteUrl
hardcoded permissions='8' (Administrator), backing the dashboard's "add
Lucky to this server" button. cubic then found a sixth in the e2e
fixtures, which mocked an Administrator response and so could not have
caught either regression. All six now derive from the shared constant.

Also corrects docs/TOP_GG_SUBMISSION.md, which specified 36970496 with a
breakdown summing to a different number entirely, and drops the vanity
and zero-incident claims removed from the README in #1912.

Tests pin the bitfield to the ADR and were verified to fail when the
value is reverted.

Closes #1923.
LucasSantana-Dev added a commit that referenced this pull request Sep 8, 2026
## Summary
Nothing in the repo said which of the three independent hosting layers
(Cloudflare Pages, Cloudflare Tunnel + homelab nginx, legacy Vercel
preview) serves which host, or which config file governs each. Fixing
the /invite redirect took three PRs (#1889, #1893, #1895) before landing
in the right file.

- Added a "Deployment & hosting" section to docs/ARCHITECTURE.md: the
host/layer/config table, the CSP trick for identifying which layer
answered a request, and the nginx.conf/_redirects gotchas that cost time
before.
- Added a one-line pointer to that section at the top of nginx.conf and
_redirects.
- vercel.json and _headers intentionally left untouched: vercel.json is
strict JSON with no safe comment syntax, and _headers' Cloudflare Pages
comment support isn't proven in this repo the way _redirects' is (its
existing comment block already works in production - _headers has none
to point to as precedent). Not worth guessing on a live config file for
a one-line pointer when docs/ARCHITECTURE.md already names both files.

## Test plan
- [x] Read-only doc/comment change, no code paths touched
- [x] nginx.conf comment uses `#`, the format nginx already uses
throughout this file
- [x] _redirects comment extends the file's own pre-existing `#` comment
block, proven safe since it's already live in production

Closes #1924

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Documents which of the three hosting layers (Cloudflare Pages,
Cloudflare Tunnel + homelab nginx, legacy Vercel preview) serves which
host and which config file governs each, so fixes like `/invite` land in
the right file instead of taking three PRs.

- Adds a Deployment & hosting section to `docs/ARCHITECTURE.md` with the
host/layer/config table, the CSP trick for identifying which layer
answered, the `nginx.conf` `/api`-proxying rule plus its `/invite`,
`/webhook/`, `/webhooks/` exceptions, and the `_redirects` top-to-bottom
gotcha.
- Adds pointers to that section at the top of `nginx.conf` and
`_redirects`.
- Read-only change; leaves `vercel.json` and `_headers` untouched.
Closes #1924.

<sup>Written for commit 4b401a0.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/LucasSantana-Dev/Lucky/pull/2253?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
This was referenced Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants