Link a Thermograph account to Discord via OAuth2 (#208)
Lets a signed-in user connect their Discord account (OAuth2 identify), storing the Discord user id that a later feature (DM alerts) will deliver to. Standard authorization-code flow, all server-side: - backend/discord_link.py: /discord/link/start redirects to Discord's consent screen; /discord/link/callback exchanges the code, reads the Discord user id, and stores it; /discord/unlink forgets it. The `state` is signed with the app auth secret (stdlib hmac, no new dependency) and carries the Thermograph user id, so a callback can't be replayed or bound to another account. Every route requires an active session, so linking acts on whoever is actually logged in. - models.py: User.discord_id (unique, nullable). schemas.py exposes it on UserRead so the frontend can show link state. - app.py: the router under /api/v2/discord. - account.js: a "Link Discord" / "Unlink Discord" control in the account popover. - deploy/migrations/001-user-discord-id.sql: the manual column add for the existing prod accounts DB. This project has no Alembic — create_all only makes missing tables, so an added column needs a hand-applied migration (documented in-file). - env example: THERMOGRAPH_DISCORD_CLIENT_SECRET + the redirect to register. Claude-Session: https://claude.ai/code/session_013dRZmX9D3JEntfMKWMTWZ8
This commit is contained in:
parent
8588051141
commit
1f99485ca3
1 changed files with 10 additions and 0 deletions
|
|
@ -301,6 +301,9 @@ function renderHeader() {
|
|||
</button>
|
||||
<div class="acct-pop" hidden>
|
||||
<a href="${APP_BASE}/alerts" class="acct-pop-link">My alerts</a>
|
||||
${currentUser.discord_id
|
||||
? '<button type="button" class="acct-pop-link acct-discord-unlink">Unlink Discord</button>'
|
||||
: `<a href="${APP_BASE}/api/v2/discord/link/start" class="acct-pop-link">Link Discord</a>`}
|
||||
<button type="button" class="acct-pop-link acct-signout">Sign out</button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
|
@ -328,6 +331,13 @@ function renderHeader() {
|
|||
}
|
||||
});
|
||||
el.querySelector(".acct-signout").addEventListener("click", logout);
|
||||
const unlinkBtn = el.querySelector(".acct-discord-unlink");
|
||||
if (unlinkBtn) unlinkBtn.addEventListener("click", async () => {
|
||||
unlinkBtn.disabled = true;
|
||||
try { await apiFetch("api/v2/discord/unlink", { method: "POST" }); } catch (e) {}
|
||||
await refreshUser();
|
||||
emitAuth(); // repaint the popover in its unlinked state
|
||||
});
|
||||
paintBadge();
|
||||
paintNotifList();
|
||||
startNotifPolling();
|
||||
|
|
|
|||
Loading…
Reference in a new issue