Discord OAuth + session for BOT_ADMINS #545
Labels
No labels
blocked
duplicate
high priority
needs
approval
needs
criteria
needs
estimate
needs
tests
question
step
doing
step
review
step
testing
step
todo
step
uat
type
admin
type
alert
type
bug
type
change
type
defect
type
dependencies
type
development
type/documentation
type
epic
type
idea
type
incident
type
investigation
type
spike
type
story
type
tests
wontfix
No project
No assignees
1 participant
Notifications
Total time spent: 2 minutes 43 seconds
Due date
Smithy-bot
2 minutes 43 seconds
No due date set.
Depends on
#544 Management API foundation
External/card-drop
Reference
External/card-drop#545
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Epic: #543
Story Points: 5
SO THAT only Discord users in
BOT_ADMINScan access the management UI and API.Implement Discord OAuth login and session handling for the Management API/UI. Reject non-admins after OAuth.
Scope
Pairs with #544: provides the admin session that
requireManagementAuthconsumes. Public auth routes mount on the public router from #544; protected/api/*routes require a valid session whose Discord user ID is inBOT_ADMINS.In scope: Discord OAuth2 (authorization code), server-side session, login/logout/callback routes, session cookie, post-OAuth
BOT_ADMINSgate, integration with management auth middleware.Out of scope: React login page polish (#546 may link to login); multi-role RBAC (#551); serving SPA static assets (#546).
Acceptance criteria
GIVEN required env vars are set (
DISCORD_OAUTH_CLIENT_ID,DISCORD_OAUTH_CLIENT_SECRET,SESSION_SECRET, and a public base URL such asMANAGEMENT_PUBLIC_URLfor OAuth redirect construction)WHEN the bot process starts
THEN OAuth routes are registered on the public router (not behind
requireManagementAuth).GIVEN an unauthenticated browser
WHEN they
GET /auth/discord(login entry)THEN they are redirected to Discord OAuth with correct
client_id,redirect_uri, and scopes sufficient to read the user's Discord ID (identifyminimum).GIVEN Discord returns an authorization code to
GET /auth/discord/callbackWHEN the server exchanges the code and loads the Discord user profile
THEN a server-side session is created containing at least
discordUserId(string snowflake).GIVEN the authenticated Discord user ID is listed in
BOT_ADMINSWHEN OAuth completes
THEN the session is kept, the user is redirected to the management UI entry (or SPA root path), and subsequent requests include a session cookie accepted by #544 middleware.
GIVEN the Discord user ID is not in
BOT_ADMINSWHEN OAuth completes
THEN the session is destroyed or marked invalid, the user sees a clear forbidden/denied response, and protected
/api/*routes return 401 or 403.GIVEN an admin session exists
WHEN they call
POST /auth/logoutTHEN the session is cleared and protected routes no longer accept the old cookie.
GIVEN production deployment behind HTTPS (reverse proxy)
WHEN sessions are issued
THEN cookies are
HttpOnly, use an appropriateSameSitefor OAuth (e.g. Lax), andSecurein production; the session secret is not logged.GIVEN automated tests for auth
WHEN CI runs
THEN tests cover: OAuth callback creates session for a
BOT_ADMINSuser; non-admin rejected; logout clears session; session shape is compatible with #544requireManagementAuthintegration tests.Subtasks
/auth/discord,/auth/discord/callback)discordUserId; optional display name/avatar for UI)BOT_ADMINSmembership check after OAuth; deny non-adminsPOST /auth/logoutand session destructionrequireManagementAuthfrom #544 (shared session reader / types)Notes
BOT_ADMINS/api/reload-dbStory point estimate: 5
Secure/SameSiteBOT_ADMINSgate + logoutrequireManagementAuthComparable scope to #544 (also 5). Eight subtasks already split implementation, test, and minimal docs work.
→ Moving to
needs/tests.Planning — needs/tests → needs/approval
QA scripts mapped to the acceptance criteria in the issue description. Run on a branch with this story and #544 on stage; automated rows via
yarn testlocally/CI.Preconditions
EXPRESS_PORT/ same entry as #544).DISCORD_OAUTH_CLIENT_ID,DISCORD_OAUTH_CLIENT_SECRET,SESSION_SECRET,MANAGEMENT_PUBLIC_URL(or equivalent public base for redirect URI).BOT_ADMINSincludes at least one Discord user ID for “admin” OAuth tests.BOT_ADMINSfor denial tests.{MANAGEMENT_PUBLIC_URL}/auth/discord/callback(or implemented path).yarn testfor automated suite.AC1 — OAuth routes on public router when env is configured
/auth/*routes mount on public router from #544, not behindrequireManagementAuthGET /auth/discordwithout sessionAC2 — Login entry redirects to Discord OAuth
GET /auth/discord(browser or follow redirects disabled)discord.comOAuth authorize URLclient_idmatches env;redirect_urimatches configured callback; scopes includeidentify(minimum)AC3 — Callback exchanges code and creates session
GET /auth/discord/callback?code=…succeedsdiscordUserId(snowflake string)codeAC4 —
BOT_ADMINSmember completes login successfullyBOT_ADMINSPOST /api/reload-db(or other protected/api/*from #544) with session cookieAC5 — Non-admin rejected after OAuth
BOT_ADMINSPOST /api/reload-dbwith post-denial cookieAC6 — Logout clears session
POST /auth/logoutGET /auth/discordafter logoutAC7 — Production cookie security
Set-CookieincludesHttpOnly;Securewhen HTTPS;SameSiteappropriate for OAuth (e.g. Lax)SESSION_SECRETand client secret not loggedAC8 — Automated tests in CI
yarn test— OAuth/session auth testsBOT_ADMINSuserSubtasks / documentation / regression
/auth/*routes/apiprotection before this story is live on target envSign-off checklist
Moving to needs/approval for your review before implementation. @Vylpes