-
Notifications
You must be signed in to change notification settings - Fork 2
feat: guild admin authentication & authorization #73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 45 commits
Commits
Show all changes
53 commits
Select commit
Hold shift + click to select a range
86ad3a4
feat: add guild admin authentication and authorization
BillChirico 38feff0
fix: address PR #73 review feedback
BillChirico 8114c01
fix: move Discord access token from JWT to server-side session store
BillChirico 0657948
fix: add interval-based cleanup for OAuth state map
BillChirico e037302
fix: cache guild fetches to avoid Discord API call on every request
BillChirico 2ea7ea4
fix: address PR #73 review comments
BillChirico fe7a652
fix: consolidate auth cleanup intervals
BillChirico 6f31174
fix: add algorithm HS256 to all jwt.sign calls in tests
BillChirico d287621
fix: use static import for jsonwebtoken in auth middleware test
BillChirico 5c1dafe
fix: add ip and path to mock req in oauth middleware test
BillChirico 51fc321
fix: add cleanup method to SessionStore and remove direct entry itera…
BillChirico d9dd49e
fix: use nullish coalescing for owners fallback in permissions
BillChirico 0b41aaa
fix: add periodic cleanup for expired guildCache entries
BillChirico 8ba3f53
fix: correct OAuth redirect URI in .env.example
BillChirico 81f238f
refactor: extract shared JWT verification helper
BillChirico 22ddbc4
refactor: extract fetchUserGuilds to shared utility module
BillChirico bf9a30e
fix: prevent TypeError if bot leaves guild during list operation
BillChirico 32a8084
test: clear shared guild cache in auth route tests
BillChirico 9058e80
test: add happy-path OAuth2 callback test with state seeding helper
BillChirico 82b4a8a
fix: address remaining PR #73 review comments
BillChirico 8ae3cfc
refactor: export shared Discord API base URL constant
BillChirico dc6da3e
refactor: consume shared Discord API constant in auth routes
BillChirico 4927aa4
fix: validate Discord OAuth callback payloads
BillChirico c1b9ae1
test: cover OAuth callback failure paths
BillChirico 0687145
fix: require explicit bot owner configuration
BillChirico 733b6e6
fix: add requireGuildAdmin to guild info endpoint, fix OAuth state ex…
BillChirico 7892460
refactor: delegate SessionStore.has() to get() to avoid duplicated TT…
BillChirico 74cebb2
fix: add explicit NaN guard for guild permissions in isOAuthGuildAdmin
BillChirico 61c1e81
fix: stop leaking Discord API error status in thrown error messages
BillChirico efe7303
fix: log guild-fetch failures in /me endpoint instead of silent catch
BillChirico e8e3b10
refactor: remove unreachable null guard after filter in guild list en…
BillChirico b375d6b
fix: run requireGuildAdmin before validateGuild to prevent guild-pres…
BillChirico 5c54a4a
fix: validate Discord guild response is an array before caching
BillChirico c51105b
refactor: use mocked PermissionFlagsBits.ManageGuild constant in test
BillChirico 4db3706
fix: set req.authMethod in requireOAuth middleware
BillChirico 15ca9f7
fix: address unresolved review comments on PR #73
BillChirico 66f9f97
fix: address remaining review warnings for guild admin auth
BillChirico ef62f98
fix: address all 24 unresolved PR #73 review threads
BillChirico 33591fd
fix: address all 25 unresolved PR #73 review threads
BillChirico 92ff4fb
fix: preserve moderator role fallback in isModerator
BillChirico 29a9af9
fix(api): include userId in guild permission error log
BillChirico 958487f
refactor(errors): centralize DiscordApiError in shared module
BillChirico a610284
docs(config): document moderatorRoleId permission setting
BillChirico 68f688a
test(auth): remove unnecessary async from sync cases
BillChirico a22697d
test(oauth): assert authMethod is set for valid jwt
BillChirico 519a05c
fix: honor permission toggles in config and modlog commands
BillChirico 8fd3516
refactor: share oauth jwt middleware verification flow
BillChirico 1df4565
fix: add oauth bot-owner bypass and guild access metadata
BillChirico 160d4f6
fix: hard-cap oauth guild cache under burst inserts
BillChirico 038e0e1
test: simplify config command permission mocks
BillChirico caa09b5
test: simplify modlog mocks and fix missing-config setup
BillChirico a77f0c6
refactor: route admin permission checks through isGuildAdmin
BillChirico e9737f5
docs: clarify modlog default moderator permission
BillChirico File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| /** | ||
| * OAuth2 JWT Middleware | ||
| * Verifies JWT tokens from Discord OAuth2 sessions | ||
| */ | ||
|
|
||
| import { error } from '../../logger.js'; | ||
| import { verifyJwtToken } from './verifyJwt.js'; | ||
|
|
||
| /** | ||
| * Creates middleware that verifies a JWT Bearer token from the Authorization header. | ||
| * Attaches the decoded user payload to req.user on success. | ||
| * | ||
| * @returns {import('express').RequestHandler} Express middleware function | ||
| */ | ||
| export function requireOAuth() { | ||
| return (req, res, next) => { | ||
| const authHeader = req.headers.authorization; | ||
|
|
||
| if (!authHeader?.startsWith('Bearer ')) { | ||
| return res.status(401).json({ error: 'No token provided' }); | ||
| } | ||
|
|
||
| const token = authHeader.slice(7); | ||
| const result = verifyJwtToken(token); | ||
| if (result.error) { | ||
| if (result.status === 500) { | ||
| error('SESSION_SECRET not configured — cannot verify OAuth token', { | ||
| ip: req.ip, | ||
| path: req.path, | ||
| }); | ||
| } | ||
| return res.status(result.status).json({ error: result.error }); | ||
| } | ||
| req.authMethod = 'oauth'; | ||
| req.user = result.user; | ||
BillChirico marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return next(); | ||
BillChirico marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }; | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
BillChirico marked this conversation as resolved.
Show resolved
Hide resolved
BillChirico marked this conversation as resolved.
Show resolved
Hide resolved
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| /** | ||
| * JWT Verification Helper | ||
| * Shared JWT verification logic used by both requireAuth and requireOAuth middleware | ||
| */ | ||
|
|
||
| import jwt from 'jsonwebtoken'; | ||
| import { getSessionToken } from '../utils/sessionStore.js'; | ||
|
|
||
| /** | ||
| * Lazily cached SESSION_SECRET — read from env on first call, then reused. | ||
| * Avoids per-request env lookup while remaining compatible with test stubs | ||
| * (vi.stubEnv sets process.env before the first call within each test). | ||
| * Call `_resetSecretCache()` in test teardown if needed. | ||
| */ | ||
| let _cachedSecret; | ||
|
|
||
| /** @internal Reset the cached secret (for test teardown). */ | ||
| export function _resetSecretCache() { | ||
| _cachedSecret = undefined; | ||
| } | ||
|
|
||
| function getSecret() { | ||
| if (_cachedSecret === undefined) { | ||
| _cachedSecret = process.env.SESSION_SECRET || ''; | ||
| } | ||
| return _cachedSecret; | ||
| } | ||
|
|
||
| /** | ||
| * Verify a JWT token and validate the associated server-side session. | ||
| * | ||
| * @param {string} token - The JWT Bearer token to verify | ||
| * @returns {{ user: Object } | { error: string, status: number }} | ||
| * On success: `{ user }` with the decoded JWT payload. | ||
| * On failure: `{ error, status }` with an error message and HTTP status code. | ||
| */ | ||
| export function verifyJwtToken(token) { | ||
BillChirico marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const secret = getSecret(); | ||
| if (!secret) return { error: 'Session not configured', status: 500 }; | ||
|
|
||
| try { | ||
| const decoded = jwt.verify(token, secret, { algorithms: ['HS256'] }); | ||
| if (!getSessionToken(decoded.userId)) { | ||
| return { error: 'Session expired or revoked', status: 401 }; | ||
| } | ||
| return { user: decoded }; | ||
| } catch { | ||
| return { error: 'Invalid or expired token', status: 401 }; | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.