Skip to content

[codex] Fix Discord dashboard login links - #287

Merged
michaelmwu merged 1 commit into
mainfrom
michaelmwu/fix-dashboard-login-404
May 16, 2026
Merged

[codex] Fix Discord dashboard login links#287
michaelmwu merged 1 commit into
mainfrom
michaelmwu/fix-dashboard-login-404

Conversation

@michaelmwu

@michaelmwumichaelmwu commented May 16, 2026

Copy link
Copy Markdown
Member

Summary

  • Change Discord dashboard login links so the initial GET /auth/discord/link/{token} renders a no-store browser page and does not consume the one-time token.
  • Add a JavaScript auto-POST to /auth/discord/link/{token}/consume, with a manual fallback button, so normal users continue without an extra click while Discord previews and basic link scanners do not burn the token.
  • Replace raw {"error":"link_not_found"} browser failures with a friendly expired/used-link page that tells users to run /dashboard-login again.
  • Add a local/dev/test-only fallback that trusts the Discord bot's gateway role context when the local people cache has no matching CRM-linked row; production still requires the normal CRM/people identity.
  • Clean up the host dev launcher banner so it no longer prints WORKER_API_BASE_URL as if the worker were listening on the API port.

Root Cause

The original one-time Discord dashboard link was consumed by a GET request. Link previews or security scanners could request the URL, follow the redirect without preserving the browser session cookie, and burn the token before the user opened it. Separately, local testing denied /dashboard-login when the local people cache lacked a CRM-linked row even if Discord roles were correct.

Validation

  • uv run pytest tests/unit/test_backend_api.py tests/unit/test_admin_login_cog.py -q
  • ./scripts/lint.sh
  • ./scripts/mypy.sh
  • python3 -m py_compile scripts/dev_mux.py

Summary by CodeRabbit

Release Notes

  • New Features

    • Discord dashboard login links now display a confirmation page before completing authentication, with improved handling for expired or unavailable tokens.
  • Documentation

    • Updated environment configuration documentation and authentication flow guides for Discord dashboard link generation and setup.

Review Change Stack

@coderabbitai

coderabbitaiBot commented May 16, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@michaelmwu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 52 minutes and 44 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 989b57e3-5e94-41b1-9188-ab57d9f30ebc

📥 Commits

Reviewing files that changed from the base of the PR and between 09ac241 and 45b72de.

📒 Files selected for processing (11)
  • README.md
  • apps/api/README.md
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/auth.py
  • apps/api/src/five08/backend/dashboard.py
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
  • apps/worker/README.md
  • scripts/dev_mux.py
  • tests/unit/test_admin_login_cog.py
  • tests/unit/test_backend_api.py
📝 Walkthrough

Walkthrough

This PR implements a two-stage Discord deep-link authentication flow for dashboard login. It adds role-based identity fallback for local/dev environments, splits link consumption into a GET confirmation page and POST consume endpoint, extends data models to carry Discord roles and display names, and includes comprehensive test coverage and documentation updates.

Changes

Discord Deep-Link Login with Local Dev Support

Layer / File(s)Summary
Discord Link Data Models
apps/api/src/five08/backend/auth.py, apps/api/src/five08/backend/api.py
DiscordLinkGrant and DiscordLinkCreateRequest models gain discord_roles and discord_display_name fields; Redis serialization updated to persist and retrieve these new fields.
Discord Link HTML Interstitials
apps/api/src/five08/backend/dashboard.py
New discord_link_continue_html() and discord_link_unavailable_html() page generators render one-time link flow; the continue page auto-submits a POST form with escaped token, avoiding token consumption on preview/scan.
Dev Role-Based Identity Resolution
apps/api/src/five08/backend/api.py
Helper function _dev_discord_link_identity_from_roles() synthesizes a DiscordAdminIdentity from caller's Discord roles in local/dev environments when standard identity lookup fails.
Discord Link Creation with Role Fallback
apps/api/src/five08/backend/api.py, apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
Backend's /auth/discord/links create handler conditionally stores discord_roles and discord_display_name when dev role fallback is used; bot cog now extracts invoking user's roles and display name from Discord interaction and sends them to backend.
Discord Link GET and POST Endpoints
apps/api/src/five08/backend/api.py
GET /auth/discord/link/{token} renders HTML interstitial (no token consumption) based on Accept header and includes Cache-Control: no-store; new POST /auth/discord/link/{token}/consume endpoint consumes the token, with fallback dev role-based identity resolution in non-OIDC mode, and sets auth session cookie before redirect.
Discord Link Flow Tests
tests/unit/test_admin_login_cog.py, tests/unit/test_backend_api.py
Bot cog test fixture sets deterministic display_name; existing redirect/consume tests migrated to POST endpoint; new tests validate local role fallback for both creation and consumption paths, asserting session creation with local display name and role-derived permissions.
Documentation and Logging
README.md, apps/api/README.md, apps/discord_bot/README.md, apps/worker/README.md, scripts/dev_mux.py
All service READMEs document DASHBOARD_PUBLIC_BASE_URL requirement, separate GET/POST endpoints, DISCORD_LINK_REQUIRE_OIDC_IDENTITY_CHECKS modes, and local trusted-bot-role exception; dev_mux.py listener labels clarified.

Sequence Diagram(s)

sequenceDiagram
participant Bot as Discord Bot
participant CreateHandler as CreateHandler
participant RedisStore as Redis
participant GetHandler as GetHandler
participant Browser as Browser
participant ConsumeHandler as ConsumeHandler
participant Verifier as Verifier
Bot->>CreateHandler: POST discord_user_id, roles, display_name
CreateHandler->>CreateHandler: Resolve user (not found in prod)
alt Local mode + roles provided
CreateHandler->>CreateHandler: Synthesize DiscordAdminIdentity
end
CreateHandler->>RedisStore: Save DiscordLinkGrant
CreateHandler-->>Bot: Return one-time token
Bot-->>Browser: Display token link
Browser->>GetHandler: GET /auth/discord/link/{token}
GetHandler->>RedisStore: Fetch DiscordLinkGrant
GetHandler->>GetHandler: Check Accept header
alt Accept: text/html
GetHandler-->>Browser: HTML form page (no-store)
else Accept: application/json
GetHandler-->>Browser: JSON response
end
Browser->>ConsumeHandler: POST /auth/discord/link/{token}/consume
ConsumeHandler->>RedisStore: Fetch and consume grant
ConsumeHandler->>Verifier: Resolve dashboard identity
alt Identity not found + local mode
ConsumeHandler->>ConsumeHandler: Use stored display_name & roles
end
ConsumeHandler->>ConsumeHandler: Create AuthSession
ConsumeHandler->>ConsumeHandler: Set auth session cookie
ConsumeHandler-->>Browser: 302 Redirect to dashboard
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • 508-dev/508-workflows#110: Modifies the Discord deep-link redirect flow and DISCORD_LINK_REQUIRE_OIDC_IDENTITY_CHECKS behavior in the same backend handlers.
  • 508-dev/508-workflows#272: Extends AuthSession and DiscordAdminIdentity with permission and identity fields used by this PR's Discord-based login flow.

Poem

🐰 One click to the dashboard, a token born anew,
Two stages of grace—first a peek, then we're through!
When the local cache sleeps and roles light the way,
Deep links grant passage to admins each day. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 35.71% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title '[codex] Fix Discord dashboard login links' directly addresses the main change: fixing the Discord dashboard login flow by implementing a no-store confirmation page and separate consume endpoint.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch michaelmwu/fix-dashboard-login-404

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 and usage tips.

CopilotAI 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.

Pull request overview

Fixes the Discord dashboard one-time login link being burned by link previews/scanners by splitting the flow into a non-consuming GET interstitial and a POST /consume endpoint, replaces raw JSON errors with a friendly HTML page for expired/used links, adds a local/dev/test-only fallback that authorizes via the bot-provided Discord role context when the local people cache lacks a CRM-linked row, and clarifies the dev launcher banner so it no longer implies the worker has an API port.

Changes:

  • Split /auth/discord/link/{token} into a no-store HTML interstitial (GET) plus a new POST /auth/discord/link/{token}/consume that performs token consumption, session creation, and cookie set; surfaces a friendly HTML page on missing/expired tokens with JSON kept for Accept: application/json.
  • Extend DiscordLinkGrant and the create-link API/Discord cog payload with discord_display_name and discord_roles, and add a _dev_discord_link_identity_from_roles fallback gated by _dashboard_dev_sensitive_access_enabled() for local/dev/test only.
  • Update READMEs and the dev_mux.py banner; add unit tests covering the GET interstitial, friendly 404 HTML (and JSON via Accept), POST consume, and the local role-based fallback for create + consume.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
apps/api/src/five08/backend/api.pySplits link handler into non-consuming GET + POST consume, adds dev-only role fallback identity, threads new payload fields through grant save, registers new POST route.
apps/api/src/five08/backend/auth.pyAdds discord_roles and discord_display_name to DiscordLinkGrant with safe deserialization.
apps/api/src/five08/backend/dashboard.pyNew discord_link_continue_html interstitial (auto-POST + manual button) and discord_link_unavailable_html friendly page.
apps/discord_bot/src/five08/discord_bot/cogs/admin_login.pySends invoking user's display name and Discord role names when creating the link.
scripts/dev_mux.pyBanner clarifies Web/API + bot health listeners and notes worker has no HTTP listener.
tests/unit/test_backend_api.pyAdds tests for local role fallback (create + consume), GET non-consumption, friendly/JSON 404 responses, and updates existing tests to POST /consume.
tests/unit/test_admin_login_cog.pyAsserts new discord_display_name and discord_roles arguments are forwarded.
apps/api/README.md, apps/worker/README.md, apps/discord_bot/README.md, README.mdDocuments the new GET/POST split, dev-only role fallback, and DASHBOARD_PUBLIC_BASE_URL guidance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:09ac241519

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/api/src/five08/backend/api.py

@coderabbitaicoderabbitaiBot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/src/five08/backend/api.py (1)

3992-4049: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make Discord link consumption atomic to preserve one-time semantics.

Line 3992 reads the grant and Line 4049 deletes it later, so concurrent consume requests can both succeed before deletion. Use an atomic pop (GETDEL) path for discord links instead of separate get/delete.

Suggested fix
# apps/api/src/five08/backend/auth.py+ async def pop_discord_link(self, token: str) -> DiscordLinkGrant | None:+ value = await self._pop_json(self._discord_link_key(token))+ if value is None:+ return None+ try:+ return DiscordLinkGrant(+ discord_user_id=str(value["discord_user_id"]),+ next_path=str(value["next_path"]),+ discord_roles=_to_string_list(value.get("discord_roles")),+ discord_display_name=_to_optional_str(value.get("discord_display_name")),+ )+ except Exception:+ logger.warning("Invalid discord-link payload in Redis")+ return None
# apps/api/src/five08/backend/api.py- grant = await store.get_discord_link(token)+ grant = await store.pop_discord_link(token)
if grant is None:
...
- ...- await store.delete_discord_link(token)+ ...
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/src/five08/backend/api.py` around lines 3992 - 4049, The code reads
the grant with store.get_discord_link and deletes it later with
store.delete_discord_link, which allows race conditions where two consumers both
see the grant before deletion; change this to an atomic pop operation on the
store (e.g. implement and call store.pop_discord_link or
store.get_and_delete_discord_link(token)) so the retrieval and removal happen in
one step, then use the returned grant for the existing logic (remove the later
store.delete_discord_link call). Keep the rest of the flow (identity resolution,
save_session via store.save_session, and session_id handling) unchanged but
ensure the new pop method returns the same grant structure or None to preserve
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@apps/api/src/five08/backend/api.py`:
- Around line 3992-4049: The code reads the grant with store.get_discord_link
and deletes it later with store.delete_discord_link, which allows race
conditions where two consumers both see the grant before deletion; change this
to an atomic pop operation on the store (e.g. implement and call
store.pop_discord_link or store.get_and_delete_discord_link(token)) so the
retrieval and removal happen in one step, then use the returned grant for the
existing logic (remove the later store.delete_discord_link call). Keep the rest
of the flow (identity resolution, save_session via store.save_session, and
session_id handling) unchanged but ensure the new pop method returns the same
grant structure or None to preserve behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bd8a82c6-377e-4ffb-8c39-bfe89e66ac54

📥 Commits

Reviewing files that changed from the base of the PR and between 656391f and 09ac241.

📒 Files selected for processing (11)
  • README.md
  • apps/api/README.md
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/auth.py
  • apps/api/src/five08/backend/dashboard.py
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
  • apps/worker/README.md
  • scripts/dev_mux.py
  • tests/unit/test_admin_login_cog.py
  • tests/unit/test_backend_api.py

@michaelmwu
michaelmwuforce-pushed the michaelmwu/fix-dashboard-login-404 branch from 09ac241 to 45b72deCompareMay 16, 2026 15:04
@michaelmwu
michaelmwu merged commit ed09bf0 into mainMay 16, 2026
7 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/fix-dashboard-login-404 branch May 16, 2026 15:15
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
[codex] Fix Discord dashboard login links by michaelmwu · Pull Request #287 · 508-dev/508-workflows · GitHub
Skip to content

[codex] Fix Discord dashboard login links - #287

Merged
michaelmwu merged 1 commit into
mainfrom
michaelmwu/fix-dashboard-login-404
May 16, 2026
Merged

[codex] Fix Discord dashboard login links#287
michaelmwu merged 1 commit into
mainfrom
michaelmwu/fix-dashboard-login-404

Conversation

@michaelmwu

@michaelmwumichaelmwu commented May 16, 2026

Copy link
Copy Markdown
Member

Summary

  • Change Discord dashboard login links so the initial GET /auth/discord/link/{token} renders a no-store browser page and does not consume the one-time token.
  • Add a JavaScript auto-POST to /auth/discord/link/{token}/consume, with a manual fallback button, so normal users continue without an extra click while Discord previews and basic link scanners do not burn the token.
  • Replace raw {"error":"link_not_found"} browser failures with a friendly expired/used-link page that tells users to run /dashboard-login again.
  • Add a local/dev/test-only fallback that trusts the Discord bot's gateway role context when the local people cache has no matching CRM-linked row; production still requires the normal CRM/people identity.
  • Clean up the host dev launcher banner so it no longer prints WORKER_API_BASE_URL as if the worker were listening on the API port.

Root Cause

The original one-time Discord dashboard link was consumed by a GET request. Link previews or security scanners could request the URL, follow the redirect without preserving the browser session cookie, and burn the token before the user opened it. Separately, local testing denied /dashboard-login when the local people cache lacked a CRM-linked row even if Discord roles were correct.

Validation

  • uv run pytest tests/unit/test_backend_api.py tests/unit/test_admin_login_cog.py -q
  • ./scripts/lint.sh
  • ./scripts/mypy.sh
  • python3 -m py_compile scripts/dev_mux.py

Summary by CodeRabbit

Release Notes

  • New Features

    • Discord dashboard login links now display a confirmation page before completing authentication, with improved handling for expired or unavailable tokens.
  • Documentation

    • Updated environment configuration documentation and authentication flow guides for Discord dashboard link generation and setup.

Review Change Stack

@coderabbitai

coderabbitaiBot commented May 16, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@michaelmwu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 52 minutes and 44 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 989b57e3-5e94-41b1-9188-ab57d9f30ebc

📥 Commits

Reviewing files that changed from the base of the PR and between 09ac241 and 45b72de.

📒 Files selected for processing (11)
  • README.md
  • apps/api/README.md
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/auth.py
  • apps/api/src/five08/backend/dashboard.py
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
  • apps/worker/README.md
  • scripts/dev_mux.py
  • tests/unit/test_admin_login_cog.py
  • tests/unit/test_backend_api.py
📝 Walkthrough

Walkthrough

This PR implements a two-stage Discord deep-link authentication flow for dashboard login. It adds role-based identity fallback for local/dev environments, splits link consumption into a GET confirmation page and POST consume endpoint, extends data models to carry Discord roles and display names, and includes comprehensive test coverage and documentation updates.

Changes

Discord Deep-Link Login with Local Dev Support

Layer / File(s)Summary
Discord Link Data Models
apps/api/src/five08/backend/auth.py, apps/api/src/five08/backend/api.py
DiscordLinkGrant and DiscordLinkCreateRequest models gain discord_roles and discord_display_name fields; Redis serialization updated to persist and retrieve these new fields.
Discord Link HTML Interstitials
apps/api/src/five08/backend/dashboard.py
New discord_link_continue_html() and discord_link_unavailable_html() page generators render one-time link flow; the continue page auto-submits a POST form with escaped token, avoiding token consumption on preview/scan.
Dev Role-Based Identity Resolution
apps/api/src/five08/backend/api.py
Helper function _dev_discord_link_identity_from_roles() synthesizes a DiscordAdminIdentity from caller's Discord roles in local/dev environments when standard identity lookup fails.
Discord Link Creation with Role Fallback
apps/api/src/five08/backend/api.py, apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
Backend's /auth/discord/links create handler conditionally stores discord_roles and discord_display_name when dev role fallback is used; bot cog now extracts invoking user's roles and display name from Discord interaction and sends them to backend.
Discord Link GET and POST Endpoints
apps/api/src/five08/backend/api.py
GET /auth/discord/link/{token} renders HTML interstitial (no token consumption) based on Accept header and includes Cache-Control: no-store; new POST /auth/discord/link/{token}/consume endpoint consumes the token, with fallback dev role-based identity resolution in non-OIDC mode, and sets auth session cookie before redirect.
Discord Link Flow Tests
tests/unit/test_admin_login_cog.py, tests/unit/test_backend_api.py
Bot cog test fixture sets deterministic display_name; existing redirect/consume tests migrated to POST endpoint; new tests validate local role fallback for both creation and consumption paths, asserting session creation with local display name and role-derived permissions.
Documentation and Logging
README.md, apps/api/README.md, apps/discord_bot/README.md, apps/worker/README.md, scripts/dev_mux.py
All service READMEs document DASHBOARD_PUBLIC_BASE_URL requirement, separate GET/POST endpoints, DISCORD_LINK_REQUIRE_OIDC_IDENTITY_CHECKS modes, and local trusted-bot-role exception; dev_mux.py listener labels clarified.

Sequence Diagram(s)

sequenceDiagram
participant Bot as Discord Bot
participant CreateHandler as CreateHandler
participant RedisStore as Redis
participant GetHandler as GetHandler
participant Browser as Browser
participant ConsumeHandler as ConsumeHandler
participant Verifier as Verifier
Bot->>CreateHandler: POST discord_user_id, roles, display_name
CreateHandler->>CreateHandler: Resolve user (not found in prod)
alt Local mode + roles provided
CreateHandler->>CreateHandler: Synthesize DiscordAdminIdentity
end
CreateHandler->>RedisStore: Save DiscordLinkGrant
CreateHandler-->>Bot: Return one-time token
Bot-->>Browser: Display token link
Browser->>GetHandler: GET /auth/discord/link/{token}
GetHandler->>RedisStore: Fetch DiscordLinkGrant
GetHandler->>GetHandler: Check Accept header
alt Accept: text/html
GetHandler-->>Browser: HTML form page (no-store)
else Accept: application/json
GetHandler-->>Browser: JSON response
end
Browser->>ConsumeHandler: POST /auth/discord/link/{token}/consume
ConsumeHandler->>RedisStore: Fetch and consume grant
ConsumeHandler->>Verifier: Resolve dashboard identity
alt Identity not found + local mode
ConsumeHandler->>ConsumeHandler: Use stored display_name & roles
end
ConsumeHandler->>ConsumeHandler: Create AuthSession
ConsumeHandler->>ConsumeHandler: Set auth session cookie
ConsumeHandler-->>Browser: 302 Redirect to dashboard
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • 508-dev/508-workflows#110: Modifies the Discord deep-link redirect flow and DISCORD_LINK_REQUIRE_OIDC_IDENTITY_CHECKS behavior in the same backend handlers.
  • 508-dev/508-workflows#272: Extends AuthSession and DiscordAdminIdentity with permission and identity fields used by this PR's Discord-based login flow.

Poem

🐰 One click to the dashboard, a token born anew,
Two stages of grace—first a peek, then we're through!
When the local cache sleeps and roles light the way,
Deep links grant passage to admins each day. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 35.71% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title '[codex] Fix Discord dashboard login links' directly addresses the main change: fixing the Discord dashboard login flow by implementing a no-store confirmation page and separate consume endpoint.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch michaelmwu/fix-dashboard-login-404

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 and usage tips.

CopilotAI 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.

Pull request overview

Fixes the Discord dashboard one-time login link being burned by link previews/scanners by splitting the flow into a non-consuming GET interstitial and a POST /consume endpoint, replaces raw JSON errors with a friendly HTML page for expired/used links, adds a local/dev/test-only fallback that authorizes via the bot-provided Discord role context when the local people cache lacks a CRM-linked row, and clarifies the dev launcher banner so it no longer implies the worker has an API port.

Changes:

  • Split /auth/discord/link/{token} into a no-store HTML interstitial (GET) plus a new POST /auth/discord/link/{token}/consume that performs token consumption, session creation, and cookie set; surfaces a friendly HTML page on missing/expired tokens with JSON kept for Accept: application/json.
  • Extend DiscordLinkGrant and the create-link API/Discord cog payload with discord_display_name and discord_roles, and add a _dev_discord_link_identity_from_roles fallback gated by _dashboard_dev_sensitive_access_enabled() for local/dev/test only.
  • Update READMEs and the dev_mux.py banner; add unit tests covering the GET interstitial, friendly 404 HTML (and JSON via Accept), POST consume, and the local role-based fallback for create + consume.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
apps/api/src/five08/backend/api.pySplits link handler into non-consuming GET + POST consume, adds dev-only role fallback identity, threads new payload fields through grant save, registers new POST route.
apps/api/src/five08/backend/auth.pyAdds discord_roles and discord_display_name to DiscordLinkGrant with safe deserialization.
apps/api/src/five08/backend/dashboard.pyNew discord_link_continue_html interstitial (auto-POST + manual button) and discord_link_unavailable_html friendly page.
apps/discord_bot/src/five08/discord_bot/cogs/admin_login.pySends invoking user's display name and Discord role names when creating the link.
scripts/dev_mux.pyBanner clarifies Web/API + bot health listeners and notes worker has no HTTP listener.
tests/unit/test_backend_api.pyAdds tests for local role fallback (create + consume), GET non-consumption, friendly/JSON 404 responses, and updates existing tests to POST /consume.
tests/unit/test_admin_login_cog.pyAsserts new discord_display_name and discord_roles arguments are forwarded.
apps/api/README.md, apps/worker/README.md, apps/discord_bot/README.md, README.mdDocuments the new GET/POST split, dev-only role fallback, and DASHBOARD_PUBLIC_BASE_URL guidance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:09ac241519

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/api/src/five08/backend/api.py

@coderabbitaicoderabbitaiBot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/src/five08/backend/api.py (1)

3992-4049: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make Discord link consumption atomic to preserve one-time semantics.

Line 3992 reads the grant and Line 4049 deletes it later, so concurrent consume requests can both succeed before deletion. Use an atomic pop (GETDEL) path for discord links instead of separate get/delete.

Suggested fix
# apps/api/src/five08/backend/auth.py+ async def pop_discord_link(self, token: str) -> DiscordLinkGrant | None:+ value = await self._pop_json(self._discord_link_key(token))+ if value is None:+ return None+ try:+ return DiscordLinkGrant(+ discord_user_id=str(value["discord_user_id"]),+ next_path=str(value["next_path"]),+ discord_roles=_to_string_list(value.get("discord_roles")),+ discord_display_name=_to_optional_str(value.get("discord_display_name")),+ )+ except Exception:+ logger.warning("Invalid discord-link payload in Redis")+ return None
# apps/api/src/five08/backend/api.py- grant = await store.get_discord_link(token)+ grant = await store.pop_discord_link(token)
if grant is None:
...
- ...- await store.delete_discord_link(token)+ ...
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/src/five08/backend/api.py` around lines 3992 - 4049, The code reads
the grant with store.get_discord_link and deletes it later with
store.delete_discord_link, which allows race conditions where two consumers both
see the grant before deletion; change this to an atomic pop operation on the
store (e.g. implement and call store.pop_discord_link or
store.get_and_delete_discord_link(token)) so the retrieval and removal happen in
one step, then use the returned grant for the existing logic (remove the later
store.delete_discord_link call). Keep the rest of the flow (identity resolution,
save_session via store.save_session, and session_id handling) unchanged but
ensure the new pop method returns the same grant structure or None to preserve
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@apps/api/src/five08/backend/api.py`:
- Around line 3992-4049: The code reads the grant with store.get_discord_link
and deletes it later with store.delete_discord_link, which allows race
conditions where two consumers both see the grant before deletion; change this
to an atomic pop operation on the store (e.g. implement and call
store.pop_discord_link or store.get_and_delete_discord_link(token)) so the
retrieval and removal happen in one step, then use the returned grant for the
existing logic (remove the later store.delete_discord_link call). Keep the rest
of the flow (identity resolution, save_session via store.save_session, and
session_id handling) unchanged but ensure the new pop method returns the same
grant structure or None to preserve behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bd8a82c6-377e-4ffb-8c39-bfe89e66ac54

📥 Commits

Reviewing files that changed from the base of the PR and between 656391f and 09ac241.

📒 Files selected for processing (11)
  • README.md
  • apps/api/README.md
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/auth.py
  • apps/api/src/five08/backend/dashboard.py
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
  • apps/worker/README.md
  • scripts/dev_mux.py
  • tests/unit/test_admin_login_cog.py
  • tests/unit/test_backend_api.py

@michaelmwu
michaelmwuforce-pushed the michaelmwu/fix-dashboard-login-404 branch from 09ac241 to 45b72deCompareMay 16, 2026 15:04
@michaelmwu
michaelmwu merged commit ed09bf0 into mainMay 16, 2026
7 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/fix-dashboard-login-404 branch May 16, 2026 15:15
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [codex] Fix Discord dashboard login links by michaelmwu · Pull Request #287 · 508-dev/508-workflows · GitHub
Skip to content

[codex] Fix Discord dashboard login links - #287

Merged
michaelmwu merged 1 commit into
mainfrom
michaelmwu/fix-dashboard-login-404
May 16, 2026
Merged

[codex] Fix Discord dashboard login links#287
michaelmwu merged 1 commit into
mainfrom
michaelmwu/fix-dashboard-login-404

Conversation

@michaelmwu

@michaelmwumichaelmwu commented May 16, 2026

Copy link
Copy Markdown
Member

Summary

  • Change Discord dashboard login links so the initial GET /auth/discord/link/{token} renders a no-store browser page and does not consume the one-time token.
  • Add a JavaScript auto-POST to /auth/discord/link/{token}/consume, with a manual fallback button, so normal users continue without an extra click while Discord previews and basic link scanners do not burn the token.
  • Replace raw {"error":"link_not_found"} browser failures with a friendly expired/used-link page that tells users to run /dashboard-login again.
  • Add a local/dev/test-only fallback that trusts the Discord bot's gateway role context when the local people cache has no matching CRM-linked row; production still requires the normal CRM/people identity.
  • Clean up the host dev launcher banner so it no longer prints WORKER_API_BASE_URL as if the worker were listening on the API port.

Root Cause

The original one-time Discord dashboard link was consumed by a GET request. Link previews or security scanners could request the URL, follow the redirect without preserving the browser session cookie, and burn the token before the user opened it. Separately, local testing denied /dashboard-login when the local people cache lacked a CRM-linked row even if Discord roles were correct.

Validation

  • uv run pytest tests/unit/test_backend_api.py tests/unit/test_admin_login_cog.py -q
  • ./scripts/lint.sh
  • ./scripts/mypy.sh
  • python3 -m py_compile scripts/dev_mux.py

Summary by CodeRabbit

Release Notes

  • New Features

    • Discord dashboard login links now display a confirmation page before completing authentication, with improved handling for expired or unavailable tokens.
  • Documentation

    • Updated environment configuration documentation and authentication flow guides for Discord dashboard link generation and setup.

Review Change Stack

@coderabbitai

coderabbitaiBot commented May 16, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@michaelmwu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 52 minutes and 44 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 989b57e3-5e94-41b1-9188-ab57d9f30ebc

📥 Commits

Reviewing files that changed from the base of the PR and between 09ac241 and 45b72de.

📒 Files selected for processing (11)
  • README.md
  • apps/api/README.md
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/auth.py
  • apps/api/src/five08/backend/dashboard.py
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
  • apps/worker/README.md
  • scripts/dev_mux.py
  • tests/unit/test_admin_login_cog.py
  • tests/unit/test_backend_api.py
📝 Walkthrough

Walkthrough

This PR implements a two-stage Discord deep-link authentication flow for dashboard login. It adds role-based identity fallback for local/dev environments, splits link consumption into a GET confirmation page and POST consume endpoint, extends data models to carry Discord roles and display names, and includes comprehensive test coverage and documentation updates.

Changes

Discord Deep-Link Login with Local Dev Support

Layer / File(s)Summary
Discord Link Data Models
apps/api/src/five08/backend/auth.py, apps/api/src/five08/backend/api.py
DiscordLinkGrant and DiscordLinkCreateRequest models gain discord_roles and discord_display_name fields; Redis serialization updated to persist and retrieve these new fields.
Discord Link HTML Interstitials
apps/api/src/five08/backend/dashboard.py
New discord_link_continue_html() and discord_link_unavailable_html() page generators render one-time link flow; the continue page auto-submits a POST form with escaped token, avoiding token consumption on preview/scan.
Dev Role-Based Identity Resolution
apps/api/src/five08/backend/api.py
Helper function _dev_discord_link_identity_from_roles() synthesizes a DiscordAdminIdentity from caller's Discord roles in local/dev environments when standard identity lookup fails.
Discord Link Creation with Role Fallback
apps/api/src/five08/backend/api.py, apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
Backend's /auth/discord/links create handler conditionally stores discord_roles and discord_display_name when dev role fallback is used; bot cog now extracts invoking user's roles and display name from Discord interaction and sends them to backend.
Discord Link GET and POST Endpoints
apps/api/src/five08/backend/api.py
GET /auth/discord/link/{token} renders HTML interstitial (no token consumption) based on Accept header and includes Cache-Control: no-store; new POST /auth/discord/link/{token}/consume endpoint consumes the token, with fallback dev role-based identity resolution in non-OIDC mode, and sets auth session cookie before redirect.
Discord Link Flow Tests
tests/unit/test_admin_login_cog.py, tests/unit/test_backend_api.py
Bot cog test fixture sets deterministic display_name; existing redirect/consume tests migrated to POST endpoint; new tests validate local role fallback for both creation and consumption paths, asserting session creation with local display name and role-derived permissions.
Documentation and Logging
README.md, apps/api/README.md, apps/discord_bot/README.md, apps/worker/README.md, scripts/dev_mux.py
All service READMEs document DASHBOARD_PUBLIC_BASE_URL requirement, separate GET/POST endpoints, DISCORD_LINK_REQUIRE_OIDC_IDENTITY_CHECKS modes, and local trusted-bot-role exception; dev_mux.py listener labels clarified.

Sequence Diagram(s)

sequenceDiagram
participant Bot as Discord Bot
participant CreateHandler as CreateHandler
participant RedisStore as Redis
participant GetHandler as GetHandler
participant Browser as Browser
participant ConsumeHandler as ConsumeHandler
participant Verifier as Verifier
Bot->>CreateHandler: POST discord_user_id, roles, display_name
CreateHandler->>CreateHandler: Resolve user (not found in prod)
alt Local mode + roles provided
CreateHandler->>CreateHandler: Synthesize DiscordAdminIdentity
end
CreateHandler->>RedisStore: Save DiscordLinkGrant
CreateHandler-->>Bot: Return one-time token
Bot-->>Browser: Display token link
Browser->>GetHandler: GET /auth/discord/link/{token}
GetHandler->>RedisStore: Fetch DiscordLinkGrant
GetHandler->>GetHandler: Check Accept header
alt Accept: text/html
GetHandler-->>Browser: HTML form page (no-store)
else Accept: application/json
GetHandler-->>Browser: JSON response
end
Browser->>ConsumeHandler: POST /auth/discord/link/{token}/consume
ConsumeHandler->>RedisStore: Fetch and consume grant
ConsumeHandler->>Verifier: Resolve dashboard identity
alt Identity not found + local mode
ConsumeHandler->>ConsumeHandler: Use stored display_name & roles
end
ConsumeHandler->>ConsumeHandler: Create AuthSession
ConsumeHandler->>ConsumeHandler: Set auth session cookie
ConsumeHandler-->>Browser: 302 Redirect to dashboard
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • 508-dev/508-workflows#110: Modifies the Discord deep-link redirect flow and DISCORD_LINK_REQUIRE_OIDC_IDENTITY_CHECKS behavior in the same backend handlers.
  • 508-dev/508-workflows#272: Extends AuthSession and DiscordAdminIdentity with permission and identity fields used by this PR's Discord-based login flow.

Poem

🐰 One click to the dashboard, a token born anew,
Two stages of grace—first a peek, then we're through!
When the local cache sleeps and roles light the way,
Deep links grant passage to admins each day. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 35.71% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title '[codex] Fix Discord dashboard login links' directly addresses the main change: fixing the Discord dashboard login flow by implementing a no-store confirmation page and separate consume endpoint.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch michaelmwu/fix-dashboard-login-404

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 and usage tips.

CopilotAI 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.

Pull request overview

Fixes the Discord dashboard one-time login link being burned by link previews/scanners by splitting the flow into a non-consuming GET interstitial and a POST /consume endpoint, replaces raw JSON errors with a friendly HTML page for expired/used links, adds a local/dev/test-only fallback that authorizes via the bot-provided Discord role context when the local people cache lacks a CRM-linked row, and clarifies the dev launcher banner so it no longer implies the worker has an API port.

Changes:

  • Split /auth/discord/link/{token} into a no-store HTML interstitial (GET) plus a new POST /auth/discord/link/{token}/consume that performs token consumption, session creation, and cookie set; surfaces a friendly HTML page on missing/expired tokens with JSON kept for Accept: application/json.
  • Extend DiscordLinkGrant and the create-link API/Discord cog payload with discord_display_name and discord_roles, and add a _dev_discord_link_identity_from_roles fallback gated by _dashboard_dev_sensitive_access_enabled() for local/dev/test only.
  • Update READMEs and the dev_mux.py banner; add unit tests covering the GET interstitial, friendly 404 HTML (and JSON via Accept), POST consume, and the local role-based fallback for create + consume.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
apps/api/src/five08/backend/api.pySplits link handler into non-consuming GET + POST consume, adds dev-only role fallback identity, threads new payload fields through grant save, registers new POST route.
apps/api/src/five08/backend/auth.pyAdds discord_roles and discord_display_name to DiscordLinkGrant with safe deserialization.
apps/api/src/five08/backend/dashboard.pyNew discord_link_continue_html interstitial (auto-POST + manual button) and discord_link_unavailable_html friendly page.
apps/discord_bot/src/five08/discord_bot/cogs/admin_login.pySends invoking user's display name and Discord role names when creating the link.
scripts/dev_mux.pyBanner clarifies Web/API + bot health listeners and notes worker has no HTTP listener.
tests/unit/test_backend_api.pyAdds tests for local role fallback (create + consume), GET non-consumption, friendly/JSON 404 responses, and updates existing tests to POST /consume.
tests/unit/test_admin_login_cog.pyAsserts new discord_display_name and discord_roles arguments are forwarded.
apps/api/README.md, apps/worker/README.md, apps/discord_bot/README.md, README.mdDocuments the new GET/POST split, dev-only role fallback, and DASHBOARD_PUBLIC_BASE_URL guidance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:09ac241519

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/api/src/five08/backend/api.py

@coderabbitaicoderabbitaiBot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/src/five08/backend/api.py (1)

3992-4049: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make Discord link consumption atomic to preserve one-time semantics.

Line 3992 reads the grant and Line 4049 deletes it later, so concurrent consume requests can both succeed before deletion. Use an atomic pop (GETDEL) path for discord links instead of separate get/delete.

Suggested fix
# apps/api/src/five08/backend/auth.py+ async def pop_discord_link(self, token: str) -> DiscordLinkGrant | None:+ value = await self._pop_json(self._discord_link_key(token))+ if value is None:+ return None+ try:+ return DiscordLinkGrant(+ discord_user_id=str(value["discord_user_id"]),+ next_path=str(value["next_path"]),+ discord_roles=_to_string_list(value.get("discord_roles")),+ discord_display_name=_to_optional_str(value.get("discord_display_name")),+ )+ except Exception:+ logger.warning("Invalid discord-link payload in Redis")+ return None
# apps/api/src/five08/backend/api.py- grant = await store.get_discord_link(token)+ grant = await store.pop_discord_link(token)
if grant is None:
...
- ...- await store.delete_discord_link(token)+ ...
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/src/five08/backend/api.py` around lines 3992 - 4049, The code reads
the grant with store.get_discord_link and deletes it later with
store.delete_discord_link, which allows race conditions where two consumers both
see the grant before deletion; change this to an atomic pop operation on the
store (e.g. implement and call store.pop_discord_link or
store.get_and_delete_discord_link(token)) so the retrieval and removal happen in
one step, then use the returned grant for the existing logic (remove the later
store.delete_discord_link call). Keep the rest of the flow (identity resolution,
save_session via store.save_session, and session_id handling) unchanged but
ensure the new pop method returns the same grant structure or None to preserve
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@apps/api/src/five08/backend/api.py`:
- Around line 3992-4049: The code reads the grant with store.get_discord_link
and deletes it later with store.delete_discord_link, which allows race
conditions where two consumers both see the grant before deletion; change this
to an atomic pop operation on the store (e.g. implement and call
store.pop_discord_link or store.get_and_delete_discord_link(token)) so the
retrieval and removal happen in one step, then use the returned grant for the
existing logic (remove the later store.delete_discord_link call). Keep the rest
of the flow (identity resolution, save_session via store.save_session, and
session_id handling) unchanged but ensure the new pop method returns the same
grant structure or None to preserve behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bd8a82c6-377e-4ffb-8c39-bfe89e66ac54

📥 Commits

Reviewing files that changed from the base of the PR and between 656391f and 09ac241.

📒 Files selected for processing (11)
  • README.md
  • apps/api/README.md
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/auth.py
  • apps/api/src/five08/backend/dashboard.py
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
  • apps/worker/README.md
  • scripts/dev_mux.py
  • tests/unit/test_admin_login_cog.py
  • tests/unit/test_backend_api.py

@michaelmwu
michaelmwuforce-pushed the michaelmwu/fix-dashboard-login-404 branch from 09ac241 to 45b72deCompareMay 16, 2026 15:04
@michaelmwu
michaelmwu merged commit ed09bf0 into mainMay 16, 2026
7 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/fix-dashboard-login-404 branch May 16, 2026 15:15
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [codex] Fix Discord dashboard login links by michaelmwu · Pull Request #287 · 508-dev/508-workflows · GitHub
Skip to content

[codex] Fix Discord dashboard login links - #287

Merged
michaelmwu merged 1 commit into
mainfrom
michaelmwu/fix-dashboard-login-404
May 16, 2026
Merged

[codex] Fix Discord dashboard login links#287
michaelmwu merged 1 commit into
mainfrom
michaelmwu/fix-dashboard-login-404

Conversation

@michaelmwu

@michaelmwumichaelmwu commented May 16, 2026

Copy link
Copy Markdown
Member

Summary

  • Change Discord dashboard login links so the initial GET /auth/discord/link/{token} renders a no-store browser page and does not consume the one-time token.
  • Add a JavaScript auto-POST to /auth/discord/link/{token}/consume, with a manual fallback button, so normal users continue without an extra click while Discord previews and basic link scanners do not burn the token.
  • Replace raw {"error":"link_not_found"} browser failures with a friendly expired/used-link page that tells users to run /dashboard-login again.
  • Add a local/dev/test-only fallback that trusts the Discord bot's gateway role context when the local people cache has no matching CRM-linked row; production still requires the normal CRM/people identity.
  • Clean up the host dev launcher banner so it no longer prints WORKER_API_BASE_URL as if the worker were listening on the API port.

Root Cause

The original one-time Discord dashboard link was consumed by a GET request. Link previews or security scanners could request the URL, follow the redirect without preserving the browser session cookie, and burn the token before the user opened it. Separately, local testing denied /dashboard-login when the local people cache lacked a CRM-linked row even if Discord roles were correct.

Validation

  • uv run pytest tests/unit/test_backend_api.py tests/unit/test_admin_login_cog.py -q
  • ./scripts/lint.sh
  • ./scripts/mypy.sh
  • python3 -m py_compile scripts/dev_mux.py

Summary by CodeRabbit

Release Notes

  • New Features

    • Discord dashboard login links now display a confirmation page before completing authentication, with improved handling for expired or unavailable tokens.
  • Documentation

    • Updated environment configuration documentation and authentication flow guides for Discord dashboard link generation and setup.

Review Change Stack

@coderabbitai

coderabbitaiBot commented May 16, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@michaelmwu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 52 minutes and 44 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 989b57e3-5e94-41b1-9188-ab57d9f30ebc

📥 Commits

Reviewing files that changed from the base of the PR and between 09ac241 and 45b72de.

📒 Files selected for processing (11)
  • README.md
  • apps/api/README.md
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/auth.py
  • apps/api/src/five08/backend/dashboard.py
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
  • apps/worker/README.md
  • scripts/dev_mux.py
  • tests/unit/test_admin_login_cog.py
  • tests/unit/test_backend_api.py
📝 Walkthrough

Walkthrough

This PR implements a two-stage Discord deep-link authentication flow for dashboard login. It adds role-based identity fallback for local/dev environments, splits link consumption into a GET confirmation page and POST consume endpoint, extends data models to carry Discord roles and display names, and includes comprehensive test coverage and documentation updates.

Changes

Discord Deep-Link Login with Local Dev Support

Layer / File(s)Summary
Discord Link Data Models
apps/api/src/five08/backend/auth.py, apps/api/src/five08/backend/api.py
DiscordLinkGrant and DiscordLinkCreateRequest models gain discord_roles and discord_display_name fields; Redis serialization updated to persist and retrieve these new fields.
Discord Link HTML Interstitials
apps/api/src/five08/backend/dashboard.py
New discord_link_continue_html() and discord_link_unavailable_html() page generators render one-time link flow; the continue page auto-submits a POST form with escaped token, avoiding token consumption on preview/scan.
Dev Role-Based Identity Resolution
apps/api/src/five08/backend/api.py
Helper function _dev_discord_link_identity_from_roles() synthesizes a DiscordAdminIdentity from caller's Discord roles in local/dev environments when standard identity lookup fails.
Discord Link Creation with Role Fallback
apps/api/src/five08/backend/api.py, apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
Backend's /auth/discord/links create handler conditionally stores discord_roles and discord_display_name when dev role fallback is used; bot cog now extracts invoking user's roles and display name from Discord interaction and sends them to backend.
Discord Link GET and POST Endpoints
apps/api/src/five08/backend/api.py
GET /auth/discord/link/{token} renders HTML interstitial (no token consumption) based on Accept header and includes Cache-Control: no-store; new POST /auth/discord/link/{token}/consume endpoint consumes the token, with fallback dev role-based identity resolution in non-OIDC mode, and sets auth session cookie before redirect.
Discord Link Flow Tests
tests/unit/test_admin_login_cog.py, tests/unit/test_backend_api.py
Bot cog test fixture sets deterministic display_name; existing redirect/consume tests migrated to POST endpoint; new tests validate local role fallback for both creation and consumption paths, asserting session creation with local display name and role-derived permissions.
Documentation and Logging
README.md, apps/api/README.md, apps/discord_bot/README.md, apps/worker/README.md, scripts/dev_mux.py
All service READMEs document DASHBOARD_PUBLIC_BASE_URL requirement, separate GET/POST endpoints, DISCORD_LINK_REQUIRE_OIDC_IDENTITY_CHECKS modes, and local trusted-bot-role exception; dev_mux.py listener labels clarified.

Sequence Diagram(s)

sequenceDiagram
participant Bot as Discord Bot
participant CreateHandler as CreateHandler
participant RedisStore as Redis
participant GetHandler as GetHandler
participant Browser as Browser
participant ConsumeHandler as ConsumeHandler
participant Verifier as Verifier
Bot->>CreateHandler: POST discord_user_id, roles, display_name
CreateHandler->>CreateHandler: Resolve user (not found in prod)
alt Local mode + roles provided
CreateHandler->>CreateHandler: Synthesize DiscordAdminIdentity
end
CreateHandler->>RedisStore: Save DiscordLinkGrant
CreateHandler-->>Bot: Return one-time token
Bot-->>Browser: Display token link
Browser->>GetHandler: GET /auth/discord/link/{token}
GetHandler->>RedisStore: Fetch DiscordLinkGrant
GetHandler->>GetHandler: Check Accept header
alt Accept: text/html
GetHandler-->>Browser: HTML form page (no-store)
else Accept: application/json
GetHandler-->>Browser: JSON response
end
Browser->>ConsumeHandler: POST /auth/discord/link/{token}/consume
ConsumeHandler->>RedisStore: Fetch and consume grant
ConsumeHandler->>Verifier: Resolve dashboard identity
alt Identity not found + local mode
ConsumeHandler->>ConsumeHandler: Use stored display_name & roles
end
ConsumeHandler->>ConsumeHandler: Create AuthSession
ConsumeHandler->>ConsumeHandler: Set auth session cookie
ConsumeHandler-->>Browser: 302 Redirect to dashboard
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • 508-dev/508-workflows#110: Modifies the Discord deep-link redirect flow and DISCORD_LINK_REQUIRE_OIDC_IDENTITY_CHECKS behavior in the same backend handlers.
  • 508-dev/508-workflows#272: Extends AuthSession and DiscordAdminIdentity with permission and identity fields used by this PR's Discord-based login flow.

Poem

🐰 One click to the dashboard, a token born anew,
Two stages of grace—first a peek, then we're through!
When the local cache sleeps and roles light the way,
Deep links grant passage to admins each day. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 35.71% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title '[codex] Fix Discord dashboard login links' directly addresses the main change: fixing the Discord dashboard login flow by implementing a no-store confirmation page and separate consume endpoint.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch michaelmwu/fix-dashboard-login-404

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 and usage tips.

CopilotAI 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.

Pull request overview

Fixes the Discord dashboard one-time login link being burned by link previews/scanners by splitting the flow into a non-consuming GET interstitial and a POST /consume endpoint, replaces raw JSON errors with a friendly HTML page for expired/used links, adds a local/dev/test-only fallback that authorizes via the bot-provided Discord role context when the local people cache lacks a CRM-linked row, and clarifies the dev launcher banner so it no longer implies the worker has an API port.

Changes:

  • Split /auth/discord/link/{token} into a no-store HTML interstitial (GET) plus a new POST /auth/discord/link/{token}/consume that performs token consumption, session creation, and cookie set; surfaces a friendly HTML page on missing/expired tokens with JSON kept for Accept: application/json.
  • Extend DiscordLinkGrant and the create-link API/Discord cog payload with discord_display_name and discord_roles, and add a _dev_discord_link_identity_from_roles fallback gated by _dashboard_dev_sensitive_access_enabled() for local/dev/test only.
  • Update READMEs and the dev_mux.py banner; add unit tests covering the GET interstitial, friendly 404 HTML (and JSON via Accept), POST consume, and the local role-based fallback for create + consume.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
apps/api/src/five08/backend/api.pySplits link handler into non-consuming GET + POST consume, adds dev-only role fallback identity, threads new payload fields through grant save, registers new POST route.
apps/api/src/five08/backend/auth.pyAdds discord_roles and discord_display_name to DiscordLinkGrant with safe deserialization.
apps/api/src/five08/backend/dashboard.pyNew discord_link_continue_html interstitial (auto-POST + manual button) and discord_link_unavailable_html friendly page.
apps/discord_bot/src/five08/discord_bot/cogs/admin_login.pySends invoking user's display name and Discord role names when creating the link.
scripts/dev_mux.pyBanner clarifies Web/API + bot health listeners and notes worker has no HTTP listener.
tests/unit/test_backend_api.pyAdds tests for local role fallback (create + consume), GET non-consumption, friendly/JSON 404 responses, and updates existing tests to POST /consume.
tests/unit/test_admin_login_cog.pyAsserts new discord_display_name and discord_roles arguments are forwarded.
apps/api/README.md, apps/worker/README.md, apps/discord_bot/README.md, README.mdDocuments the new GET/POST split, dev-only role fallback, and DASHBOARD_PUBLIC_BASE_URL guidance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:09ac241519

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/api/src/five08/backend/api.py

@coderabbitaicoderabbitaiBot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/src/five08/backend/api.py (1)

3992-4049: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make Discord link consumption atomic to preserve one-time semantics.

Line 3992 reads the grant and Line 4049 deletes it later, so concurrent consume requests can both succeed before deletion. Use an atomic pop (GETDEL) path for discord links instead of separate get/delete.

Suggested fix
# apps/api/src/five08/backend/auth.py+ async def pop_discord_link(self, token: str) -> DiscordLinkGrant | None:+ value = await self._pop_json(self._discord_link_key(token))+ if value is None:+ return None+ try:+ return DiscordLinkGrant(+ discord_user_id=str(value["discord_user_id"]),+ next_path=str(value["next_path"]),+ discord_roles=_to_string_list(value.get("discord_roles")),+ discord_display_name=_to_optional_str(value.get("discord_display_name")),+ )+ except Exception:+ logger.warning("Invalid discord-link payload in Redis")+ return None
# apps/api/src/five08/backend/api.py- grant = await store.get_discord_link(token)+ grant = await store.pop_discord_link(token)
if grant is None:
...
- ...- await store.delete_discord_link(token)+ ...
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/src/five08/backend/api.py` around lines 3992 - 4049, The code reads
the grant with store.get_discord_link and deletes it later with
store.delete_discord_link, which allows race conditions where two consumers both
see the grant before deletion; change this to an atomic pop operation on the
store (e.g. implement and call store.pop_discord_link or
store.get_and_delete_discord_link(token)) so the retrieval and removal happen in
one step, then use the returned grant for the existing logic (remove the later
store.delete_discord_link call). Keep the rest of the flow (identity resolution,
save_session via store.save_session, and session_id handling) unchanged but
ensure the new pop method returns the same grant structure or None to preserve
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@apps/api/src/five08/backend/api.py`:
- Around line 3992-4049: The code reads the grant with store.get_discord_link
and deletes it later with store.delete_discord_link, which allows race
conditions where two consumers both see the grant before deletion; change this
to an atomic pop operation on the store (e.g. implement and call
store.pop_discord_link or store.get_and_delete_discord_link(token)) so the
retrieval and removal happen in one step, then use the returned grant for the
existing logic (remove the later store.delete_discord_link call). Keep the rest
of the flow (identity resolution, save_session via store.save_session, and
session_id handling) unchanged but ensure the new pop method returns the same
grant structure or None to preserve behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bd8a82c6-377e-4ffb-8c39-bfe89e66ac54

📥 Commits

Reviewing files that changed from the base of the PR and between 656391f and 09ac241.

📒 Files selected for processing (11)
  • README.md
  • apps/api/README.md
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/auth.py
  • apps/api/src/five08/backend/dashboard.py
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
  • apps/worker/README.md
  • scripts/dev_mux.py
  • tests/unit/test_admin_login_cog.py
  • tests/unit/test_backend_api.py

@michaelmwu
michaelmwuforce-pushed the michaelmwu/fix-dashboard-login-404 branch from 09ac241 to 45b72deCompareMay 16, 2026 15:04
@michaelmwu
michaelmwu merged commit ed09bf0 into mainMay 16, 2026
7 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/fix-dashboard-login-404 branch May 16, 2026 15:15
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' [codex] Fix Discord dashboard login links by michaelmwu · Pull Request #287 · 508-dev/508-workflows · GitHub
Skip to content

[codex] Fix Discord dashboard login links - #287

Merged
michaelmwu merged 1 commit into
mainfrom
michaelmwu/fix-dashboard-login-404
May 16, 2026
Merged

[codex] Fix Discord dashboard login links#287
michaelmwu merged 1 commit into
mainfrom
michaelmwu/fix-dashboard-login-404

Conversation

@michaelmwu

@michaelmwumichaelmwu commented May 16, 2026

Copy link
Copy Markdown
Member

Summary

  • Change Discord dashboard login links so the initial GET /auth/discord/link/{token} renders a no-store browser page and does not consume the one-time token.
  • Add a JavaScript auto-POST to /auth/discord/link/{token}/consume, with a manual fallback button, so normal users continue without an extra click while Discord previews and basic link scanners do not burn the token.
  • Replace raw {"error":"link_not_found"} browser failures with a friendly expired/used-link page that tells users to run /dashboard-login again.
  • Add a local/dev/test-only fallback that trusts the Discord bot's gateway role context when the local people cache has no matching CRM-linked row; production still requires the normal CRM/people identity.
  • Clean up the host dev launcher banner so it no longer prints WORKER_API_BASE_URL as if the worker were listening on the API port.

Root Cause

The original one-time Discord dashboard link was consumed by a GET request. Link previews or security scanners could request the URL, follow the redirect without preserving the browser session cookie, and burn the token before the user opened it. Separately, local testing denied /dashboard-login when the local people cache lacked a CRM-linked row even if Discord roles were correct.

Validation

  • uv run pytest tests/unit/test_backend_api.py tests/unit/test_admin_login_cog.py -q
  • ./scripts/lint.sh
  • ./scripts/mypy.sh
  • python3 -m py_compile scripts/dev_mux.py

Summary by CodeRabbit

Release Notes

  • New Features

    • Discord dashboard login links now display a confirmation page before completing authentication, with improved handling for expired or unavailable tokens.
  • Documentation

    • Updated environment configuration documentation and authentication flow guides for Discord dashboard link generation and setup.

Review Change Stack

@coderabbitai

coderabbitaiBot commented May 16, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@michaelmwu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 52 minutes and 44 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 989b57e3-5e94-41b1-9188-ab57d9f30ebc

📥 Commits

Reviewing files that changed from the base of the PR and between 09ac241 and 45b72de.

📒 Files selected for processing (11)
  • README.md
  • apps/api/README.md
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/auth.py
  • apps/api/src/five08/backend/dashboard.py
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
  • apps/worker/README.md
  • scripts/dev_mux.py
  • tests/unit/test_admin_login_cog.py
  • tests/unit/test_backend_api.py
📝 Walkthrough

Walkthrough

This PR implements a two-stage Discord deep-link authentication flow for dashboard login. It adds role-based identity fallback for local/dev environments, splits link consumption into a GET confirmation page and POST consume endpoint, extends data models to carry Discord roles and display names, and includes comprehensive test coverage and documentation updates.

Changes

Discord Deep-Link Login with Local Dev Support

Layer / File(s)Summary
Discord Link Data Models
apps/api/src/five08/backend/auth.py, apps/api/src/five08/backend/api.py
DiscordLinkGrant and DiscordLinkCreateRequest models gain discord_roles and discord_display_name fields; Redis serialization updated to persist and retrieve these new fields.
Discord Link HTML Interstitials
apps/api/src/five08/backend/dashboard.py
New discord_link_continue_html() and discord_link_unavailable_html() page generators render one-time link flow; the continue page auto-submits a POST form with escaped token, avoiding token consumption on preview/scan.
Dev Role-Based Identity Resolution
apps/api/src/five08/backend/api.py
Helper function _dev_discord_link_identity_from_roles() synthesizes a DiscordAdminIdentity from caller's Discord roles in local/dev environments when standard identity lookup fails.
Discord Link Creation with Role Fallback
apps/api/src/five08/backend/api.py, apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
Backend's /auth/discord/links create handler conditionally stores discord_roles and discord_display_name when dev role fallback is used; bot cog now extracts invoking user's roles and display name from Discord interaction and sends them to backend.
Discord Link GET and POST Endpoints
apps/api/src/five08/backend/api.py
GET /auth/discord/link/{token} renders HTML interstitial (no token consumption) based on Accept header and includes Cache-Control: no-store; new POST /auth/discord/link/{token}/consume endpoint consumes the token, with fallback dev role-based identity resolution in non-OIDC mode, and sets auth session cookie before redirect.
Discord Link Flow Tests
tests/unit/test_admin_login_cog.py, tests/unit/test_backend_api.py
Bot cog test fixture sets deterministic display_name; existing redirect/consume tests migrated to POST endpoint; new tests validate local role fallback for both creation and consumption paths, asserting session creation with local display name and role-derived permissions.
Documentation and Logging
README.md, apps/api/README.md, apps/discord_bot/README.md, apps/worker/README.md, scripts/dev_mux.py
All service READMEs document DASHBOARD_PUBLIC_BASE_URL requirement, separate GET/POST endpoints, DISCORD_LINK_REQUIRE_OIDC_IDENTITY_CHECKS modes, and local trusted-bot-role exception; dev_mux.py listener labels clarified.

Sequence Diagram(s)

sequenceDiagram
participant Bot as Discord Bot
participant CreateHandler as CreateHandler
participant RedisStore as Redis
participant GetHandler as GetHandler
participant Browser as Browser
participant ConsumeHandler as ConsumeHandler
participant Verifier as Verifier
Bot->>CreateHandler: POST discord_user_id, roles, display_name
CreateHandler->>CreateHandler: Resolve user (not found in prod)
alt Local mode + roles provided
CreateHandler->>CreateHandler: Synthesize DiscordAdminIdentity
end
CreateHandler->>RedisStore: Save DiscordLinkGrant
CreateHandler-->>Bot: Return one-time token
Bot-->>Browser: Display token link
Browser->>GetHandler: GET /auth/discord/link/{token}
GetHandler->>RedisStore: Fetch DiscordLinkGrant
GetHandler->>GetHandler: Check Accept header
alt Accept: text/html
GetHandler-->>Browser: HTML form page (no-store)
else Accept: application/json
GetHandler-->>Browser: JSON response
end
Browser->>ConsumeHandler: POST /auth/discord/link/{token}/consume
ConsumeHandler->>RedisStore: Fetch and consume grant
ConsumeHandler->>Verifier: Resolve dashboard identity
alt Identity not found + local mode
ConsumeHandler->>ConsumeHandler: Use stored display_name & roles
end
ConsumeHandler->>ConsumeHandler: Create AuthSession
ConsumeHandler->>ConsumeHandler: Set auth session cookie
ConsumeHandler-->>Browser: 302 Redirect to dashboard
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • 508-dev/508-workflows#110: Modifies the Discord deep-link redirect flow and DISCORD_LINK_REQUIRE_OIDC_IDENTITY_CHECKS behavior in the same backend handlers.
  • 508-dev/508-workflows#272: Extends AuthSession and DiscordAdminIdentity with permission and identity fields used by this PR's Discord-based login flow.

Poem

🐰 One click to the dashboard, a token born anew,
Two stages of grace—first a peek, then we're through!
When the local cache sleeps and roles light the way,
Deep links grant passage to admins each day. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 35.71% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title '[codex] Fix Discord dashboard login links' directly addresses the main change: fixing the Discord dashboard login flow by implementing a no-store confirmation page and separate consume endpoint.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch michaelmwu/fix-dashboard-login-404

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 and usage tips.

CopilotAI 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.

Pull request overview

Fixes the Discord dashboard one-time login link being burned by link previews/scanners by splitting the flow into a non-consuming GET interstitial and a POST /consume endpoint, replaces raw JSON errors with a friendly HTML page for expired/used links, adds a local/dev/test-only fallback that authorizes via the bot-provided Discord role context when the local people cache lacks a CRM-linked row, and clarifies the dev launcher banner so it no longer implies the worker has an API port.

Changes:

  • Split /auth/discord/link/{token} into a no-store HTML interstitial (GET) plus a new POST /auth/discord/link/{token}/consume that performs token consumption, session creation, and cookie set; surfaces a friendly HTML page on missing/expired tokens with JSON kept for Accept: application/json.
  • Extend DiscordLinkGrant and the create-link API/Discord cog payload with discord_display_name and discord_roles, and add a _dev_discord_link_identity_from_roles fallback gated by _dashboard_dev_sensitive_access_enabled() for local/dev/test only.
  • Update READMEs and the dev_mux.py banner; add unit tests covering the GET interstitial, friendly 404 HTML (and JSON via Accept), POST consume, and the local role-based fallback for create + consume.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
apps/api/src/five08/backend/api.pySplits link handler into non-consuming GET + POST consume, adds dev-only role fallback identity, threads new payload fields through grant save, registers new POST route.
apps/api/src/five08/backend/auth.pyAdds discord_roles and discord_display_name to DiscordLinkGrant with safe deserialization.
apps/api/src/five08/backend/dashboard.pyNew discord_link_continue_html interstitial (auto-POST + manual button) and discord_link_unavailable_html friendly page.
apps/discord_bot/src/five08/discord_bot/cogs/admin_login.pySends invoking user's display name and Discord role names when creating the link.
scripts/dev_mux.pyBanner clarifies Web/API + bot health listeners and notes worker has no HTTP listener.
tests/unit/test_backend_api.pyAdds tests for local role fallback (create + consume), GET non-consumption, friendly/JSON 404 responses, and updates existing tests to POST /consume.
tests/unit/test_admin_login_cog.pyAsserts new discord_display_name and discord_roles arguments are forwarded.
apps/api/README.md, apps/worker/README.md, apps/discord_bot/README.md, README.mdDocuments the new GET/POST split, dev-only role fallback, and DASHBOARD_PUBLIC_BASE_URL guidance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:09ac241519

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/api/src/five08/backend/api.py

@coderabbitaicoderabbitaiBot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/src/five08/backend/api.py (1)

3992-4049: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make Discord link consumption atomic to preserve one-time semantics.

Line 3992 reads the grant and Line 4049 deletes it later, so concurrent consume requests can both succeed before deletion. Use an atomic pop (GETDEL) path for discord links instead of separate get/delete.

Suggested fix
# apps/api/src/five08/backend/auth.py+ async def pop_discord_link(self, token: str) -> DiscordLinkGrant | None:+ value = await self._pop_json(self._discord_link_key(token))+ if value is None:+ return None+ try:+ return DiscordLinkGrant(+ discord_user_id=str(value["discord_user_id"]),+ next_path=str(value["next_path"]),+ discord_roles=_to_string_list(value.get("discord_roles")),+ discord_display_name=_to_optional_str(value.get("discord_display_name")),+ )+ except Exception:+ logger.warning("Invalid discord-link payload in Redis")+ return None
# apps/api/src/five08/backend/api.py- grant = await store.get_discord_link(token)+ grant = await store.pop_discord_link(token)
if grant is None:
...
- ...- await store.delete_discord_link(token)+ ...
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/src/five08/backend/api.py` around lines 3992 - 4049, The code reads
the grant with store.get_discord_link and deletes it later with
store.delete_discord_link, which allows race conditions where two consumers both
see the grant before deletion; change this to an atomic pop operation on the
store (e.g. implement and call store.pop_discord_link or
store.get_and_delete_discord_link(token)) so the retrieval and removal happen in
one step, then use the returned grant for the existing logic (remove the later
store.delete_discord_link call). Keep the rest of the flow (identity resolution,
save_session via store.save_session, and session_id handling) unchanged but
ensure the new pop method returns the same grant structure or None to preserve
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@apps/api/src/five08/backend/api.py`:
- Around line 3992-4049: The code reads the grant with store.get_discord_link
and deletes it later with store.delete_discord_link, which allows race
conditions where two consumers both see the grant before deletion; change this
to an atomic pop operation on the store (e.g. implement and call
store.pop_discord_link or store.get_and_delete_discord_link(token)) so the
retrieval and removal happen in one step, then use the returned grant for the
existing logic (remove the later store.delete_discord_link call). Keep the rest
of the flow (identity resolution, save_session via store.save_session, and
session_id handling) unchanged but ensure the new pop method returns the same
grant structure or None to preserve behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bd8a82c6-377e-4ffb-8c39-bfe89e66ac54

📥 Commits

Reviewing files that changed from the base of the PR and between 656391f and 09ac241.

📒 Files selected for processing (11)
  • README.md
  • apps/api/README.md
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/auth.py
  • apps/api/src/five08/backend/dashboard.py
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
  • apps/worker/README.md
  • scripts/dev_mux.py
  • tests/unit/test_admin_login_cog.py
  • tests/unit/test_backend_api.py

@michaelmwu
michaelmwuforce-pushed the michaelmwu/fix-dashboard-login-404 branch from 09ac241 to 45b72deCompareMay 16, 2026 15:04
@michaelmwu
michaelmwu merged commit ed09bf0 into mainMay 16, 2026
7 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/fix-dashboard-login-404 branch May 16, 2026 15:15
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [codex] Fix Discord dashboard login links by michaelmwu · Pull Request #287 · 508-dev/508-workflows · GitHub
Skip to content

[codex] Fix Discord dashboard login links - #287

Merged
michaelmwu merged 1 commit into
mainfrom
michaelmwu/fix-dashboard-login-404
May 16, 2026
Merged

[codex] Fix Discord dashboard login links#287
michaelmwu merged 1 commit into
mainfrom
michaelmwu/fix-dashboard-login-404

Conversation

@michaelmwu

@michaelmwumichaelmwu commented May 16, 2026

Copy link
Copy Markdown
Member

Summary

  • Change Discord dashboard login links so the initial GET /auth/discord/link/{token} renders a no-store browser page and does not consume the one-time token.
  • Add a JavaScript auto-POST to /auth/discord/link/{token}/consume, with a manual fallback button, so normal users continue without an extra click while Discord previews and basic link scanners do not burn the token.
  • Replace raw {"error":"link_not_found"} browser failures with a friendly expired/used-link page that tells users to run /dashboard-login again.
  • Add a local/dev/test-only fallback that trusts the Discord bot's gateway role context when the local people cache has no matching CRM-linked row; production still requires the normal CRM/people identity.
  • Clean up the host dev launcher banner so it no longer prints WORKER_API_BASE_URL as if the worker were listening on the API port.

Root Cause

The original one-time Discord dashboard link was consumed by a GET request. Link previews or security scanners could request the URL, follow the redirect without preserving the browser session cookie, and burn the token before the user opened it. Separately, local testing denied /dashboard-login when the local people cache lacked a CRM-linked row even if Discord roles were correct.

Validation

  • uv run pytest tests/unit/test_backend_api.py tests/unit/test_admin_login_cog.py -q
  • ./scripts/lint.sh
  • ./scripts/mypy.sh
  • python3 -m py_compile scripts/dev_mux.py

Summary by CodeRabbit

Release Notes

  • New Features

    • Discord dashboard login links now display a confirmation page before completing authentication, with improved handling for expired or unavailable tokens.
  • Documentation

    • Updated environment configuration documentation and authentication flow guides for Discord dashboard link generation and setup.

Review Change Stack

@coderabbitai

coderabbitaiBot commented May 16, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@michaelmwu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 52 minutes and 44 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 989b57e3-5e94-41b1-9188-ab57d9f30ebc

📥 Commits

Reviewing files that changed from the base of the PR and between 09ac241 and 45b72de.

📒 Files selected for processing (11)
  • README.md
  • apps/api/README.md
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/auth.py
  • apps/api/src/five08/backend/dashboard.py
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
  • apps/worker/README.md
  • scripts/dev_mux.py
  • tests/unit/test_admin_login_cog.py
  • tests/unit/test_backend_api.py
📝 Walkthrough

Walkthrough

This PR implements a two-stage Discord deep-link authentication flow for dashboard login. It adds role-based identity fallback for local/dev environments, splits link consumption into a GET confirmation page and POST consume endpoint, extends data models to carry Discord roles and display names, and includes comprehensive test coverage and documentation updates.

Changes

Discord Deep-Link Login with Local Dev Support

Layer / File(s)Summary
Discord Link Data Models
apps/api/src/five08/backend/auth.py, apps/api/src/five08/backend/api.py
DiscordLinkGrant and DiscordLinkCreateRequest models gain discord_roles and discord_display_name fields; Redis serialization updated to persist and retrieve these new fields.
Discord Link HTML Interstitials
apps/api/src/five08/backend/dashboard.py
New discord_link_continue_html() and discord_link_unavailable_html() page generators render one-time link flow; the continue page auto-submits a POST form with escaped token, avoiding token consumption on preview/scan.
Dev Role-Based Identity Resolution
apps/api/src/five08/backend/api.py
Helper function _dev_discord_link_identity_from_roles() synthesizes a DiscordAdminIdentity from caller's Discord roles in local/dev environments when standard identity lookup fails.
Discord Link Creation with Role Fallback
apps/api/src/five08/backend/api.py, apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
Backend's /auth/discord/links create handler conditionally stores discord_roles and discord_display_name when dev role fallback is used; bot cog now extracts invoking user's roles and display name from Discord interaction and sends them to backend.
Discord Link GET and POST Endpoints
apps/api/src/five08/backend/api.py
GET /auth/discord/link/{token} renders HTML interstitial (no token consumption) based on Accept header and includes Cache-Control: no-store; new POST /auth/discord/link/{token}/consume endpoint consumes the token, with fallback dev role-based identity resolution in non-OIDC mode, and sets auth session cookie before redirect.
Discord Link Flow Tests
tests/unit/test_admin_login_cog.py, tests/unit/test_backend_api.py
Bot cog test fixture sets deterministic display_name; existing redirect/consume tests migrated to POST endpoint; new tests validate local role fallback for both creation and consumption paths, asserting session creation with local display name and role-derived permissions.
Documentation and Logging
README.md, apps/api/README.md, apps/discord_bot/README.md, apps/worker/README.md, scripts/dev_mux.py
All service READMEs document DASHBOARD_PUBLIC_BASE_URL requirement, separate GET/POST endpoints, DISCORD_LINK_REQUIRE_OIDC_IDENTITY_CHECKS modes, and local trusted-bot-role exception; dev_mux.py listener labels clarified.

Sequence Diagram(s)

sequenceDiagram
participant Bot as Discord Bot
participant CreateHandler as CreateHandler
participant RedisStore as Redis
participant GetHandler as GetHandler
participant Browser as Browser
participant ConsumeHandler as ConsumeHandler
participant Verifier as Verifier
Bot->>CreateHandler: POST discord_user_id, roles, display_name
CreateHandler->>CreateHandler: Resolve user (not found in prod)
alt Local mode + roles provided
CreateHandler->>CreateHandler: Synthesize DiscordAdminIdentity
end
CreateHandler->>RedisStore: Save DiscordLinkGrant
CreateHandler-->>Bot: Return one-time token
Bot-->>Browser: Display token link
Browser->>GetHandler: GET /auth/discord/link/{token}
GetHandler->>RedisStore: Fetch DiscordLinkGrant
GetHandler->>GetHandler: Check Accept header
alt Accept: text/html
GetHandler-->>Browser: HTML form page (no-store)
else Accept: application/json
GetHandler-->>Browser: JSON response
end
Browser->>ConsumeHandler: POST /auth/discord/link/{token}/consume
ConsumeHandler->>RedisStore: Fetch and consume grant
ConsumeHandler->>Verifier: Resolve dashboard identity
alt Identity not found + local mode
ConsumeHandler->>ConsumeHandler: Use stored display_name & roles
end
ConsumeHandler->>ConsumeHandler: Create AuthSession
ConsumeHandler->>ConsumeHandler: Set auth session cookie
ConsumeHandler-->>Browser: 302 Redirect to dashboard
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • 508-dev/508-workflows#110: Modifies the Discord deep-link redirect flow and DISCORD_LINK_REQUIRE_OIDC_IDENTITY_CHECKS behavior in the same backend handlers.
  • 508-dev/508-workflows#272: Extends AuthSession and DiscordAdminIdentity with permission and identity fields used by this PR's Discord-based login flow.

Poem

🐰 One click to the dashboard, a token born anew,
Two stages of grace—first a peek, then we're through!
When the local cache sleeps and roles light the way,
Deep links grant passage to admins each day. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 35.71% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title '[codex] Fix Discord dashboard login links' directly addresses the main change: fixing the Discord dashboard login flow by implementing a no-store confirmation page and separate consume endpoint.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch michaelmwu/fix-dashboard-login-404

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 and usage tips.

CopilotAI 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.

Pull request overview

Fixes the Discord dashboard one-time login link being burned by link previews/scanners by splitting the flow into a non-consuming GET interstitial and a POST /consume endpoint, replaces raw JSON errors with a friendly HTML page for expired/used links, adds a local/dev/test-only fallback that authorizes via the bot-provided Discord role context when the local people cache lacks a CRM-linked row, and clarifies the dev launcher banner so it no longer implies the worker has an API port.

Changes:

  • Split /auth/discord/link/{token} into a no-store HTML interstitial (GET) plus a new POST /auth/discord/link/{token}/consume that performs token consumption, session creation, and cookie set; surfaces a friendly HTML page on missing/expired tokens with JSON kept for Accept: application/json.
  • Extend DiscordLinkGrant and the create-link API/Discord cog payload with discord_display_name and discord_roles, and add a _dev_discord_link_identity_from_roles fallback gated by _dashboard_dev_sensitive_access_enabled() for local/dev/test only.
  • Update READMEs and the dev_mux.py banner; add unit tests covering the GET interstitial, friendly 404 HTML (and JSON via Accept), POST consume, and the local role-based fallback for create + consume.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
apps/api/src/five08/backend/api.pySplits link handler into non-consuming GET + POST consume, adds dev-only role fallback identity, threads new payload fields through grant save, registers new POST route.
apps/api/src/five08/backend/auth.pyAdds discord_roles and discord_display_name to DiscordLinkGrant with safe deserialization.
apps/api/src/five08/backend/dashboard.pyNew discord_link_continue_html interstitial (auto-POST + manual button) and discord_link_unavailable_html friendly page.
apps/discord_bot/src/five08/discord_bot/cogs/admin_login.pySends invoking user's display name and Discord role names when creating the link.
scripts/dev_mux.pyBanner clarifies Web/API + bot health listeners and notes worker has no HTTP listener.
tests/unit/test_backend_api.pyAdds tests for local role fallback (create + consume), GET non-consumption, friendly/JSON 404 responses, and updates existing tests to POST /consume.
tests/unit/test_admin_login_cog.pyAsserts new discord_display_name and discord_roles arguments are forwarded.
apps/api/README.md, apps/worker/README.md, apps/discord_bot/README.md, README.mdDocuments the new GET/POST split, dev-only role fallback, and DASHBOARD_PUBLIC_BASE_URL guidance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:09ac241519

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/api/src/five08/backend/api.py

@coderabbitaicoderabbitaiBot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/src/five08/backend/api.py (1)

3992-4049: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make Discord link consumption atomic to preserve one-time semantics.

Line 3992 reads the grant and Line 4049 deletes it later, so concurrent consume requests can both succeed before deletion. Use an atomic pop (GETDEL) path for discord links instead of separate get/delete.

Suggested fix
# apps/api/src/five08/backend/auth.py+ async def pop_discord_link(self, token: str) -> DiscordLinkGrant | None:+ value = await self._pop_json(self._discord_link_key(token))+ if value is None:+ return None+ try:+ return DiscordLinkGrant(+ discord_user_id=str(value["discord_user_id"]),+ next_path=str(value["next_path"]),+ discord_roles=_to_string_list(value.get("discord_roles")),+ discord_display_name=_to_optional_str(value.get("discord_display_name")),+ )+ except Exception:+ logger.warning("Invalid discord-link payload in Redis")+ return None
# apps/api/src/five08/backend/api.py- grant = await store.get_discord_link(token)+ grant = await store.pop_discord_link(token)
if grant is None:
...
- ...- await store.delete_discord_link(token)+ ...
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/src/five08/backend/api.py` around lines 3992 - 4049, The code reads
the grant with store.get_discord_link and deletes it later with
store.delete_discord_link, which allows race conditions where two consumers both
see the grant before deletion; change this to an atomic pop operation on the
store (e.g. implement and call store.pop_discord_link or
store.get_and_delete_discord_link(token)) so the retrieval and removal happen in
one step, then use the returned grant for the existing logic (remove the later
store.delete_discord_link call). Keep the rest of the flow (identity resolution,
save_session via store.save_session, and session_id handling) unchanged but
ensure the new pop method returns the same grant structure or None to preserve
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@apps/api/src/five08/backend/api.py`:
- Around line 3992-4049: The code reads the grant with store.get_discord_link
and deletes it later with store.delete_discord_link, which allows race
conditions where two consumers both see the grant before deletion; change this
to an atomic pop operation on the store (e.g. implement and call
store.pop_discord_link or store.get_and_delete_discord_link(token)) so the
retrieval and removal happen in one step, then use the returned grant for the
existing logic (remove the later store.delete_discord_link call). Keep the rest
of the flow (identity resolution, save_session via store.save_session, and
session_id handling) unchanged but ensure the new pop method returns the same
grant structure or None to preserve behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bd8a82c6-377e-4ffb-8c39-bfe89e66ac54

📥 Commits

Reviewing files that changed from the base of the PR and between 656391f and 09ac241.

📒 Files selected for processing (11)
  • README.md
  • apps/api/README.md
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/auth.py
  • apps/api/src/five08/backend/dashboard.py
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
  • apps/worker/README.md
  • scripts/dev_mux.py
  • tests/unit/test_admin_login_cog.py
  • tests/unit/test_backend_api.py

@michaelmwu
michaelmwuforce-pushed the michaelmwu/fix-dashboard-login-404 branch from 09ac241 to 45b72deCompareMay 16, 2026 15:04
@michaelmwu
michaelmwu merged commit ed09bf0 into mainMay 16, 2026
7 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/fix-dashboard-login-404 branch May 16, 2026 15:15
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [codex] Fix Discord dashboard login links by michaelmwu · Pull Request #287 · 508-dev/508-workflows · GitHub
Skip to content

[codex] Fix Discord dashboard login links - #287

Merged
michaelmwu merged 1 commit into
mainfrom
michaelmwu/fix-dashboard-login-404
May 16, 2026
Merged

[codex] Fix Discord dashboard login links#287
michaelmwu merged 1 commit into
mainfrom
michaelmwu/fix-dashboard-login-404

Conversation

@michaelmwu

@michaelmwumichaelmwu commented May 16, 2026

Copy link
Copy Markdown
Member

Summary

  • Change Discord dashboard login links so the initial GET /auth/discord/link/{token} renders a no-store browser page and does not consume the one-time token.
  • Add a JavaScript auto-POST to /auth/discord/link/{token}/consume, with a manual fallback button, so normal users continue without an extra click while Discord previews and basic link scanners do not burn the token.
  • Replace raw {"error":"link_not_found"} browser failures with a friendly expired/used-link page that tells users to run /dashboard-login again.
  • Add a local/dev/test-only fallback that trusts the Discord bot's gateway role context when the local people cache has no matching CRM-linked row; production still requires the normal CRM/people identity.
  • Clean up the host dev launcher banner so it no longer prints WORKER_API_BASE_URL as if the worker were listening on the API port.

Root Cause

The original one-time Discord dashboard link was consumed by a GET request. Link previews or security scanners could request the URL, follow the redirect without preserving the browser session cookie, and burn the token before the user opened it. Separately, local testing denied /dashboard-login when the local people cache lacked a CRM-linked row even if Discord roles were correct.

Validation

  • uv run pytest tests/unit/test_backend_api.py tests/unit/test_admin_login_cog.py -q
  • ./scripts/lint.sh
  • ./scripts/mypy.sh
  • python3 -m py_compile scripts/dev_mux.py

Summary by CodeRabbit

Release Notes

  • New Features

    • Discord dashboard login links now display a confirmation page before completing authentication, with improved handling for expired or unavailable tokens.
  • Documentation

    • Updated environment configuration documentation and authentication flow guides for Discord dashboard link generation and setup.

Review Change Stack

@coderabbitai

coderabbitaiBot commented May 16, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@michaelmwu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 52 minutes and 44 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 989b57e3-5e94-41b1-9188-ab57d9f30ebc

📥 Commits

Reviewing files that changed from the base of the PR and between 09ac241 and 45b72de.

📒 Files selected for processing (11)
  • README.md
  • apps/api/README.md
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/auth.py
  • apps/api/src/five08/backend/dashboard.py
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
  • apps/worker/README.md
  • scripts/dev_mux.py
  • tests/unit/test_admin_login_cog.py
  • tests/unit/test_backend_api.py
📝 Walkthrough

Walkthrough

This PR implements a two-stage Discord deep-link authentication flow for dashboard login. It adds role-based identity fallback for local/dev environments, splits link consumption into a GET confirmation page and POST consume endpoint, extends data models to carry Discord roles and display names, and includes comprehensive test coverage and documentation updates.

Changes

Discord Deep-Link Login with Local Dev Support

Layer / File(s)Summary
Discord Link Data Models
apps/api/src/five08/backend/auth.py, apps/api/src/five08/backend/api.py
DiscordLinkGrant and DiscordLinkCreateRequest models gain discord_roles and discord_display_name fields; Redis serialization updated to persist and retrieve these new fields.
Discord Link HTML Interstitials
apps/api/src/five08/backend/dashboard.py
New discord_link_continue_html() and discord_link_unavailable_html() page generators render one-time link flow; the continue page auto-submits a POST form with escaped token, avoiding token consumption on preview/scan.
Dev Role-Based Identity Resolution
apps/api/src/five08/backend/api.py
Helper function _dev_discord_link_identity_from_roles() synthesizes a DiscordAdminIdentity from caller's Discord roles in local/dev environments when standard identity lookup fails.
Discord Link Creation with Role Fallback
apps/api/src/five08/backend/api.py, apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
Backend's /auth/discord/links create handler conditionally stores discord_roles and discord_display_name when dev role fallback is used; bot cog now extracts invoking user's roles and display name from Discord interaction and sends them to backend.
Discord Link GET and POST Endpoints
apps/api/src/five08/backend/api.py
GET /auth/discord/link/{token} renders HTML interstitial (no token consumption) based on Accept header and includes Cache-Control: no-store; new POST /auth/discord/link/{token}/consume endpoint consumes the token, with fallback dev role-based identity resolution in non-OIDC mode, and sets auth session cookie before redirect.
Discord Link Flow Tests
tests/unit/test_admin_login_cog.py, tests/unit/test_backend_api.py
Bot cog test fixture sets deterministic display_name; existing redirect/consume tests migrated to POST endpoint; new tests validate local role fallback for both creation and consumption paths, asserting session creation with local display name and role-derived permissions.
Documentation and Logging
README.md, apps/api/README.md, apps/discord_bot/README.md, apps/worker/README.md, scripts/dev_mux.py
All service READMEs document DASHBOARD_PUBLIC_BASE_URL requirement, separate GET/POST endpoints, DISCORD_LINK_REQUIRE_OIDC_IDENTITY_CHECKS modes, and local trusted-bot-role exception; dev_mux.py listener labels clarified.

Sequence Diagram(s)

sequenceDiagram
participant Bot as Discord Bot
participant CreateHandler as CreateHandler
participant RedisStore as Redis
participant GetHandler as GetHandler
participant Browser as Browser
participant ConsumeHandler as ConsumeHandler
participant Verifier as Verifier
Bot->>CreateHandler: POST discord_user_id, roles, display_name
CreateHandler->>CreateHandler: Resolve user (not found in prod)
alt Local mode + roles provided
CreateHandler->>CreateHandler: Synthesize DiscordAdminIdentity
end
CreateHandler->>RedisStore: Save DiscordLinkGrant
CreateHandler-->>Bot: Return one-time token
Bot-->>Browser: Display token link
Browser->>GetHandler: GET /auth/discord/link/{token}
GetHandler->>RedisStore: Fetch DiscordLinkGrant
GetHandler->>GetHandler: Check Accept header
alt Accept: text/html
GetHandler-->>Browser: HTML form page (no-store)
else Accept: application/json
GetHandler-->>Browser: JSON response
end
Browser->>ConsumeHandler: POST /auth/discord/link/{token}/consume
ConsumeHandler->>RedisStore: Fetch and consume grant
ConsumeHandler->>Verifier: Resolve dashboard identity
alt Identity not found + local mode
ConsumeHandler->>ConsumeHandler: Use stored display_name & roles
end
ConsumeHandler->>ConsumeHandler: Create AuthSession
ConsumeHandler->>ConsumeHandler: Set auth session cookie
ConsumeHandler-->>Browser: 302 Redirect to dashboard
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • 508-dev/508-workflows#110: Modifies the Discord deep-link redirect flow and DISCORD_LINK_REQUIRE_OIDC_IDENTITY_CHECKS behavior in the same backend handlers.
  • 508-dev/508-workflows#272: Extends AuthSession and DiscordAdminIdentity with permission and identity fields used by this PR's Discord-based login flow.

Poem

🐰 One click to the dashboard, a token born anew,
Two stages of grace—first a peek, then we're through!
When the local cache sleeps and roles light the way,
Deep links grant passage to admins each day. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 35.71% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title '[codex] Fix Discord dashboard login links' directly addresses the main change: fixing the Discord dashboard login flow by implementing a no-store confirmation page and separate consume endpoint.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch michaelmwu/fix-dashboard-login-404

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 and usage tips.

CopilotAI 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.

Pull request overview

Fixes the Discord dashboard one-time login link being burned by link previews/scanners by splitting the flow into a non-consuming GET interstitial and a POST /consume endpoint, replaces raw JSON errors with a friendly HTML page for expired/used links, adds a local/dev/test-only fallback that authorizes via the bot-provided Discord role context when the local people cache lacks a CRM-linked row, and clarifies the dev launcher banner so it no longer implies the worker has an API port.

Changes:

  • Split /auth/discord/link/{token} into a no-store HTML interstitial (GET) plus a new POST /auth/discord/link/{token}/consume that performs token consumption, session creation, and cookie set; surfaces a friendly HTML page on missing/expired tokens with JSON kept for Accept: application/json.
  • Extend DiscordLinkGrant and the create-link API/Discord cog payload with discord_display_name and discord_roles, and add a _dev_discord_link_identity_from_roles fallback gated by _dashboard_dev_sensitive_access_enabled() for local/dev/test only.
  • Update READMEs and the dev_mux.py banner; add unit tests covering the GET interstitial, friendly 404 HTML (and JSON via Accept), POST consume, and the local role-based fallback for create + consume.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
apps/api/src/five08/backend/api.pySplits link handler into non-consuming GET + POST consume, adds dev-only role fallback identity, threads new payload fields through grant save, registers new POST route.
apps/api/src/five08/backend/auth.pyAdds discord_roles and discord_display_name to DiscordLinkGrant with safe deserialization.
apps/api/src/five08/backend/dashboard.pyNew discord_link_continue_html interstitial (auto-POST + manual button) and discord_link_unavailable_html friendly page.
apps/discord_bot/src/five08/discord_bot/cogs/admin_login.pySends invoking user's display name and Discord role names when creating the link.
scripts/dev_mux.pyBanner clarifies Web/API + bot health listeners and notes worker has no HTTP listener.
tests/unit/test_backend_api.pyAdds tests for local role fallback (create + consume), GET non-consumption, friendly/JSON 404 responses, and updates existing tests to POST /consume.
tests/unit/test_admin_login_cog.pyAsserts new discord_display_name and discord_roles arguments are forwarded.
apps/api/README.md, apps/worker/README.md, apps/discord_bot/README.md, README.mdDocuments the new GET/POST split, dev-only role fallback, and DASHBOARD_PUBLIC_BASE_URL guidance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:09ac241519

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/api/src/five08/backend/api.py

@coderabbitaicoderabbitaiBot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/src/five08/backend/api.py (1)

3992-4049: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make Discord link consumption atomic to preserve one-time semantics.

Line 3992 reads the grant and Line 4049 deletes it later, so concurrent consume requests can both succeed before deletion. Use an atomic pop (GETDEL) path for discord links instead of separate get/delete.

Suggested fix
# apps/api/src/five08/backend/auth.py+ async def pop_discord_link(self, token: str) -> DiscordLinkGrant | None:+ value = await self._pop_json(self._discord_link_key(token))+ if value is None:+ return None+ try:+ return DiscordLinkGrant(+ discord_user_id=str(value["discord_user_id"]),+ next_path=str(value["next_path"]),+ discord_roles=_to_string_list(value.get("discord_roles")),+ discord_display_name=_to_optional_str(value.get("discord_display_name")),+ )+ except Exception:+ logger.warning("Invalid discord-link payload in Redis")+ return None
# apps/api/src/five08/backend/api.py- grant = await store.get_discord_link(token)+ grant = await store.pop_discord_link(token)
if grant is None:
...
- ...- await store.delete_discord_link(token)+ ...
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/src/five08/backend/api.py` around lines 3992 - 4049, The code reads
the grant with store.get_discord_link and deletes it later with
store.delete_discord_link, which allows race conditions where two consumers both
see the grant before deletion; change this to an atomic pop operation on the
store (e.g. implement and call store.pop_discord_link or
store.get_and_delete_discord_link(token)) so the retrieval and removal happen in
one step, then use the returned grant for the existing logic (remove the later
store.delete_discord_link call). Keep the rest of the flow (identity resolution,
save_session via store.save_session, and session_id handling) unchanged but
ensure the new pop method returns the same grant structure or None to preserve
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@apps/api/src/five08/backend/api.py`:
- Around line 3992-4049: The code reads the grant with store.get_discord_link
and deletes it later with store.delete_discord_link, which allows race
conditions where two consumers both see the grant before deletion; change this
to an atomic pop operation on the store (e.g. implement and call
store.pop_discord_link or store.get_and_delete_discord_link(token)) so the
retrieval and removal happen in one step, then use the returned grant for the
existing logic (remove the later store.delete_discord_link call). Keep the rest
of the flow (identity resolution, save_session via store.save_session, and
session_id handling) unchanged but ensure the new pop method returns the same
grant structure or None to preserve behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bd8a82c6-377e-4ffb-8c39-bfe89e66ac54

📥 Commits

Reviewing files that changed from the base of the PR and between 656391f and 09ac241.

📒 Files selected for processing (11)
  • README.md
  • apps/api/README.md
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/auth.py
  • apps/api/src/five08/backend/dashboard.py
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
  • apps/worker/README.md
  • scripts/dev_mux.py
  • tests/unit/test_admin_login_cog.py
  • tests/unit/test_backend_api.py

@michaelmwu
michaelmwuforce-pushed the michaelmwu/fix-dashboard-login-404 branch from 09ac241 to 45b72deCompareMay 16, 2026 15:04
@michaelmwu
michaelmwu merged commit ed09bf0 into mainMay 16, 2026
7 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/fix-dashboard-login-404 branch May 16, 2026 15:15
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); [codex] Fix Discord dashboard login links by michaelmwu · Pull Request #287 · 508-dev/508-workflows · GitHub
Skip to content

[codex] Fix Discord dashboard login links - #287

Merged
michaelmwu merged 1 commit into
mainfrom
michaelmwu/fix-dashboard-login-404
May 16, 2026
Merged

[codex] Fix Discord dashboard login links#287
michaelmwu merged 1 commit into
mainfrom
michaelmwu/fix-dashboard-login-404

Conversation

@michaelmwu

@michaelmwumichaelmwu commented May 16, 2026

Copy link
Copy Markdown
Member

Summary

  • Change Discord dashboard login links so the initial GET /auth/discord/link/{token} renders a no-store browser page and does not consume the one-time token.
  • Add a JavaScript auto-POST to /auth/discord/link/{token}/consume, with a manual fallback button, so normal users continue without an extra click while Discord previews and basic link scanners do not burn the token.
  • Replace raw {"error":"link_not_found"} browser failures with a friendly expired/used-link page that tells users to run /dashboard-login again.
  • Add a local/dev/test-only fallback that trusts the Discord bot's gateway role context when the local people cache has no matching CRM-linked row; production still requires the normal CRM/people identity.
  • Clean up the host dev launcher banner so it no longer prints WORKER_API_BASE_URL as if the worker were listening on the API port.

Root Cause

The original one-time Discord dashboard link was consumed by a GET request. Link previews or security scanners could request the URL, follow the redirect without preserving the browser session cookie, and burn the token before the user opened it. Separately, local testing denied /dashboard-login when the local people cache lacked a CRM-linked row even if Discord roles were correct.

Validation

  • uv run pytest tests/unit/test_backend_api.py tests/unit/test_admin_login_cog.py -q
  • ./scripts/lint.sh
  • ./scripts/mypy.sh
  • python3 -m py_compile scripts/dev_mux.py

Summary by CodeRabbit

Release Notes

  • New Features

    • Discord dashboard login links now display a confirmation page before completing authentication, with improved handling for expired or unavailable tokens.
  • Documentation

    • Updated environment configuration documentation and authentication flow guides for Discord dashboard link generation and setup.

Review Change Stack

@coderabbitai

coderabbitaiBot commented May 16, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@michaelmwu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 52 minutes and 44 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 989b57e3-5e94-41b1-9188-ab57d9f30ebc

📥 Commits

Reviewing files that changed from the base of the PR and between 09ac241 and 45b72de.

📒 Files selected for processing (11)
  • README.md
  • apps/api/README.md
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/auth.py
  • apps/api/src/five08/backend/dashboard.py
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
  • apps/worker/README.md
  • scripts/dev_mux.py
  • tests/unit/test_admin_login_cog.py
  • tests/unit/test_backend_api.py
📝 Walkthrough

Walkthrough

This PR implements a two-stage Discord deep-link authentication flow for dashboard login. It adds role-based identity fallback for local/dev environments, splits link consumption into a GET confirmation page and POST consume endpoint, extends data models to carry Discord roles and display names, and includes comprehensive test coverage and documentation updates.

Changes

Discord Deep-Link Login with Local Dev Support

Layer / File(s)Summary
Discord Link Data Models
apps/api/src/five08/backend/auth.py, apps/api/src/five08/backend/api.py
DiscordLinkGrant and DiscordLinkCreateRequest models gain discord_roles and discord_display_name fields; Redis serialization updated to persist and retrieve these new fields.
Discord Link HTML Interstitials
apps/api/src/five08/backend/dashboard.py
New discord_link_continue_html() and discord_link_unavailable_html() page generators render one-time link flow; the continue page auto-submits a POST form with escaped token, avoiding token consumption on preview/scan.
Dev Role-Based Identity Resolution
apps/api/src/five08/backend/api.py
Helper function _dev_discord_link_identity_from_roles() synthesizes a DiscordAdminIdentity from caller's Discord roles in local/dev environments when standard identity lookup fails.
Discord Link Creation with Role Fallback
apps/api/src/five08/backend/api.py, apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
Backend's /auth/discord/links create handler conditionally stores discord_roles and discord_display_name when dev role fallback is used; bot cog now extracts invoking user's roles and display name from Discord interaction and sends them to backend.
Discord Link GET and POST Endpoints
apps/api/src/five08/backend/api.py
GET /auth/discord/link/{token} renders HTML interstitial (no token consumption) based on Accept header and includes Cache-Control: no-store; new POST /auth/discord/link/{token}/consume endpoint consumes the token, with fallback dev role-based identity resolution in non-OIDC mode, and sets auth session cookie before redirect.
Discord Link Flow Tests
tests/unit/test_admin_login_cog.py, tests/unit/test_backend_api.py
Bot cog test fixture sets deterministic display_name; existing redirect/consume tests migrated to POST endpoint; new tests validate local role fallback for both creation and consumption paths, asserting session creation with local display name and role-derived permissions.
Documentation and Logging
README.md, apps/api/README.md, apps/discord_bot/README.md, apps/worker/README.md, scripts/dev_mux.py
All service READMEs document DASHBOARD_PUBLIC_BASE_URL requirement, separate GET/POST endpoints, DISCORD_LINK_REQUIRE_OIDC_IDENTITY_CHECKS modes, and local trusted-bot-role exception; dev_mux.py listener labels clarified.

Sequence Diagram(s)

sequenceDiagram
participant Bot as Discord Bot
participant CreateHandler as CreateHandler
participant RedisStore as Redis
participant GetHandler as GetHandler
participant Browser as Browser
participant ConsumeHandler as ConsumeHandler
participant Verifier as Verifier
Bot->>CreateHandler: POST discord_user_id, roles, display_name
CreateHandler->>CreateHandler: Resolve user (not found in prod)
alt Local mode + roles provided
CreateHandler->>CreateHandler: Synthesize DiscordAdminIdentity
end
CreateHandler->>RedisStore: Save DiscordLinkGrant
CreateHandler-->>Bot: Return one-time token
Bot-->>Browser: Display token link
Browser->>GetHandler: GET /auth/discord/link/{token}
GetHandler->>RedisStore: Fetch DiscordLinkGrant
GetHandler->>GetHandler: Check Accept header
alt Accept: text/html
GetHandler-->>Browser: HTML form page (no-store)
else Accept: application/json
GetHandler-->>Browser: JSON response
end
Browser->>ConsumeHandler: POST /auth/discord/link/{token}/consume
ConsumeHandler->>RedisStore: Fetch and consume grant
ConsumeHandler->>Verifier: Resolve dashboard identity
alt Identity not found + local mode
ConsumeHandler->>ConsumeHandler: Use stored display_name & roles
end
ConsumeHandler->>ConsumeHandler: Create AuthSession
ConsumeHandler->>ConsumeHandler: Set auth session cookie
ConsumeHandler-->>Browser: 302 Redirect to dashboard
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • 508-dev/508-workflows#110: Modifies the Discord deep-link redirect flow and DISCORD_LINK_REQUIRE_OIDC_IDENTITY_CHECKS behavior in the same backend handlers.
  • 508-dev/508-workflows#272: Extends AuthSession and DiscordAdminIdentity with permission and identity fields used by this PR's Discord-based login flow.

Poem

🐰 One click to the dashboard, a token born anew,
Two stages of grace—first a peek, then we're through!
When the local cache sleeps and roles light the way,
Deep links grant passage to admins each day. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 35.71% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title '[codex] Fix Discord dashboard login links' directly addresses the main change: fixing the Discord dashboard login flow by implementing a no-store confirmation page and separate consume endpoint.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch michaelmwu/fix-dashboard-login-404

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 and usage tips.

CopilotAI 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.

Pull request overview

Fixes the Discord dashboard one-time login link being burned by link previews/scanners by splitting the flow into a non-consuming GET interstitial and a POST /consume endpoint, replaces raw JSON errors with a friendly HTML page for expired/used links, adds a local/dev/test-only fallback that authorizes via the bot-provided Discord role context when the local people cache lacks a CRM-linked row, and clarifies the dev launcher banner so it no longer implies the worker has an API port.

Changes:

  • Split /auth/discord/link/{token} into a no-store HTML interstitial (GET) plus a new POST /auth/discord/link/{token}/consume that performs token consumption, session creation, and cookie set; surfaces a friendly HTML page on missing/expired tokens with JSON kept for Accept: application/json.
  • Extend DiscordLinkGrant and the create-link API/Discord cog payload with discord_display_name and discord_roles, and add a _dev_discord_link_identity_from_roles fallback gated by _dashboard_dev_sensitive_access_enabled() for local/dev/test only.
  • Update READMEs and the dev_mux.py banner; add unit tests covering the GET interstitial, friendly 404 HTML (and JSON via Accept), POST consume, and the local role-based fallback for create + consume.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
apps/api/src/five08/backend/api.pySplits link handler into non-consuming GET + POST consume, adds dev-only role fallback identity, threads new payload fields through grant save, registers new POST route.
apps/api/src/five08/backend/auth.pyAdds discord_roles and discord_display_name to DiscordLinkGrant with safe deserialization.
apps/api/src/five08/backend/dashboard.pyNew discord_link_continue_html interstitial (auto-POST + manual button) and discord_link_unavailable_html friendly page.
apps/discord_bot/src/five08/discord_bot/cogs/admin_login.pySends invoking user's display name and Discord role names when creating the link.
scripts/dev_mux.pyBanner clarifies Web/API + bot health listeners and notes worker has no HTTP listener.
tests/unit/test_backend_api.pyAdds tests for local role fallback (create + consume), GET non-consumption, friendly/JSON 404 responses, and updates existing tests to POST /consume.
tests/unit/test_admin_login_cog.pyAsserts new discord_display_name and discord_roles arguments are forwarded.
apps/api/README.md, apps/worker/README.md, apps/discord_bot/README.md, README.mdDocuments the new GET/POST split, dev-only role fallback, and DASHBOARD_PUBLIC_BASE_URL guidance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:09ac241519

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/api/src/five08/backend/api.py

@coderabbitaicoderabbitaiBot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/src/five08/backend/api.py (1)

3992-4049: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make Discord link consumption atomic to preserve one-time semantics.

Line 3992 reads the grant and Line 4049 deletes it later, so concurrent consume requests can both succeed before deletion. Use an atomic pop (GETDEL) path for discord links instead of separate get/delete.

Suggested fix
# apps/api/src/five08/backend/auth.py+ async def pop_discord_link(self, token: str) -> DiscordLinkGrant | None:+ value = await self._pop_json(self._discord_link_key(token))+ if value is None:+ return None+ try:+ return DiscordLinkGrant(+ discord_user_id=str(value["discord_user_id"]),+ next_path=str(value["next_path"]),+ discord_roles=_to_string_list(value.get("discord_roles")),+ discord_display_name=_to_optional_str(value.get("discord_display_name")),+ )+ except Exception:+ logger.warning("Invalid discord-link payload in Redis")+ return None
# apps/api/src/five08/backend/api.py- grant = await store.get_discord_link(token)+ grant = await store.pop_discord_link(token)
if grant is None:
...
- ...- await store.delete_discord_link(token)+ ...
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/src/five08/backend/api.py` around lines 3992 - 4049, The code reads
the grant with store.get_discord_link and deletes it later with
store.delete_discord_link, which allows race conditions where two consumers both
see the grant before deletion; change this to an atomic pop operation on the
store (e.g. implement and call store.pop_discord_link or
store.get_and_delete_discord_link(token)) so the retrieval and removal happen in
one step, then use the returned grant for the existing logic (remove the later
store.delete_discord_link call). Keep the rest of the flow (identity resolution,
save_session via store.save_session, and session_id handling) unchanged but
ensure the new pop method returns the same grant structure or None to preserve
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@apps/api/src/five08/backend/api.py`:
- Around line 3992-4049: The code reads the grant with store.get_discord_link
and deletes it later with store.delete_discord_link, which allows race
conditions where two consumers both see the grant before deletion; change this
to an atomic pop operation on the store (e.g. implement and call
store.pop_discord_link or store.get_and_delete_discord_link(token)) so the
retrieval and removal happen in one step, then use the returned grant for the
existing logic (remove the later store.delete_discord_link call). Keep the rest
of the flow (identity resolution, save_session via store.save_session, and
session_id handling) unchanged but ensure the new pop method returns the same
grant structure or None to preserve behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bd8a82c6-377e-4ffb-8c39-bfe89e66ac54

📥 Commits

Reviewing files that changed from the base of the PR and between 656391f and 09ac241.

📒 Files selected for processing (11)
  • README.md
  • apps/api/README.md
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/auth.py
  • apps/api/src/five08/backend/dashboard.py
  • apps/discord_bot/README.md
  • apps/discord_bot/src/five08/discord_bot/cogs/admin_login.py
  • apps/worker/README.md
  • scripts/dev_mux.py
  • tests/unit/test_admin_login_cog.py
  • tests/unit/test_backend_api.py

@michaelmwu
michaelmwuforce-pushed the michaelmwu/fix-dashboard-login-404 branch from 09ac241 to 45b72deCompareMay 16, 2026 15:04
@michaelmwu
michaelmwu merged commit ed09bf0 into mainMay 16, 2026
7 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/fix-dashboard-login-404 branch May 16, 2026 15:15
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@michaelmwu