-
Notifications
You must be signed in to change notification settings - Fork 65
feat: defer most session/ban authority to kf-auth #3627
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
+652
−181
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
1e3a20e
feat: handle bans
tefkah 694cbc1
fix: pass through oauth prompt correctly
tefkah 8191a17
fix: sync session revoke
tefkah b93d6fb
fix: update session times
tefkah df16dd5
chore: format
tefkah da2fb8f
fix: env
tefkah f23ad97
fix: add sessions secrets
tefkah c2c7ef5
fix: remove platformbanguard, too expensive
tefkah d5c6673
fix: infinite duqduq redirect?
tefkah 8c043af
fix: trust proxy
tefkah 13b280a
fix: trust proxy, less?
tefkah be78bae
fix: add debug
tefkah 712d945
fix: circuit brake better
tefkah 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
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
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
Large diffs are not rendered by default.
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| import { User } from 'server/models'; | ||
| import { upsertSpamTag } from 'server/spamTag/userQueries'; | ||
| import { deleteSessionsByKfSessionId, deleteSessionsForUser } from 'server/utils/session'; | ||
|
|
||
| export async function handleUserUpdated(data: any, res: any) { | ||
| const { userId, givenName, familyName, displayName, email, image } = data; | ||
|
|
||
| if (!userId) { | ||
| return res.status(400).json({ error: 'userId is required' }); | ||
| } | ||
|
|
||
| const user = await User.findOne({ where: { id: userId } }); | ||
| if (!user) { | ||
| return res.status(404).json({ error: 'User not found' }); | ||
| } | ||
|
|
||
| const updates: Record<string, any> = {}; | ||
| if (displayName !== undefined) updates.fullName = displayName; | ||
| if (givenName !== undefined) updates.firstName = givenName; | ||
| if (familyName !== undefined) updates.lastName = familyName; | ||
| if (email !== undefined) updates.email = email.toLowerCase(); | ||
| if (image !== undefined) updates.avatar = image; | ||
|
|
||
| if (givenName !== undefined || familyName !== undefined || displayName !== undefined) { | ||
| const first = givenName ?? user.firstName ?? ''; | ||
| const last = familyName ?? user.lastName ?? ''; | ||
| if (first || last) { | ||
| updates.initials = `${first.charAt(0)}${last.charAt(0)}`.toUpperCase(); | ||
| } | ||
| } | ||
|
|
||
| if (Object.keys(updates).length > 0) { | ||
| await user.update(updates); | ||
| } | ||
|
|
||
| return res.status(200).json({ ok: true }); | ||
| } | ||
|
|
||
| export async function handleUserBanned(data: any, res: any) { | ||
| const { userId, banReason } = data; | ||
|
|
||
| if (!userId) { | ||
| return res.status(400).json({ error: 'userId is required' }); | ||
| } | ||
|
|
||
| const user = await User.findOne({ where: { id: userId } }); | ||
| if (!user) { | ||
| return res.status(404).json({ error: 'User not found' }); | ||
| } | ||
|
|
||
| await upsertSpamTag({ | ||
| userId, | ||
| status: 'confirmed-spam', | ||
| fields: { | ||
| manuallyMarkedBy: [ | ||
| { | ||
| userId: 'kf-auth', | ||
| userName: banReason ? `KF Auth: ${banReason}` : 'KF Auth (external ban)', | ||
| at: new Date().toISOString(), | ||
| }, | ||
| ], | ||
| }, | ||
| skipKfAuthSync: true, | ||
| }); | ||
|
|
||
| return res.status(200).json({ ok: true }); | ||
| } | ||
|
|
||
| export async function handleUserUnbanned(data: any, res: any) { | ||
| const { userId } = data; | ||
|
|
||
| if (!userId) { | ||
| return res.status(400).json({ error: 'userId is required' }); | ||
| } | ||
|
|
||
| const user = await User.findOne({ where: { id: userId } }); | ||
| if (!user) { | ||
| return res.status(404).json({ error: 'User not found' }); | ||
| } | ||
|
|
||
| await upsertSpamTag({ | ||
| userId, | ||
| status: 'confirmed-not-spam', | ||
| skipKfAuthSync: true, | ||
| }); | ||
|
|
||
| return res.status(200).json({ ok: true }); | ||
| } | ||
|
|
||
| export async function handleUserSessionsRevoked(data: any, res: any) { | ||
| const { userId } = data; | ||
|
|
||
| if (!userId) { | ||
| return res.status(400).json({ error: 'userId is required' }); | ||
| } | ||
|
|
||
| const user = await User.findOne({ where: { id: userId } }); | ||
| if (!user) { | ||
| return res.status(200).json({ ok: true, skipped: 'user not found' }); | ||
| } | ||
|
|
||
| if (user.email) { | ||
| await deleteSessionsForUser(user.email); | ||
| } | ||
|
|
||
| return res.status(200).json({ ok: true }); | ||
| } | ||
|
|
||
| /** | ||
| * A single kf-auth session was revoked (user revoked a device from the | ||
| * Security page, signed out, etc.). Delete exactly the local sessions | ||
| * that were minted from it — they're stamped with the kf-auth session | ||
| * id (the ID token's `sid` claim) at login. | ||
| */ | ||
| export async function handleSessionRevoked(data: any, res: any) { | ||
| const { sessionId } = data; | ||
|
|
||
| if (!sessionId) { | ||
| return res.status(400).json({ error: 'sessionId is required' }); | ||
| } | ||
|
|
||
| const deleted = await deleteSessionsByKfSessionId(sessionId); | ||
|
|
||
| return res.status(200).json({ ok: true, deleted }); | ||
| } |
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,43 @@ | ||
| import type { NextFunction, Request, Response } from 'express'; | ||
|
|
||
| const SKIP_PREFIXES = ['/api', '/auth', '/dist', '/static', '/service-worker', '/favicon']; | ||
|
|
||
| /** | ||
| * Detects "was logged in, session expired" and triggers silent re-auth | ||
| * via OIDC prompt=none. Only fires for browser page loads (GET requests | ||
| * to non-API, non-asset paths). | ||
| * | ||
| * Uses the `pp-lic` CDN cookie (set at login, 30-day maxAge) to detect | ||
| * that the user was previously authenticated. A `pp-renew-failed` cookie | ||
| * acts as a circuit breaker to prevent redirect loops when kf-auth's | ||
| * session is also expired. | ||
| */ | ||
| export const silentReauthMiddleware = () => { | ||
| return (req: Request, res: Response, next: NextFunction) => { | ||
| if (req.method !== 'GET') return next(); | ||
| if (SKIP_PREFIXES.some((p) => req.path.startsWith(p))) { | ||
| return next(); | ||
| } | ||
|
|
||
| if (req.user) return next(); | ||
|
|
||
| // After logout it's set to 'pp-lo' - renewing would resurrect the session the user just deliberately ended. | ||
| const lic = req.cookies?.['pp-lic']; | ||
| if (typeof lic !== 'string' || !lic.startsWith('pp-li-')) return next(); | ||
|
|
||
| // Circuit breaker: recently tried and failed - skip | ||
| if (req.cookies?.['pp-renew-failed']) return next(); | ||
|
|
||
| // This 302 carries no Set-Cookie, so Fastly would otherwise cache it | ||
| // under the per-`pp-lic` cache key (vcl_hash only mixes connect.sid in | ||
| // for /api routes). A cached "go reauth" redirect would then be served | ||
| // even after the user has a valid session again — an infinite loop the | ||
| // session cookie can't bust. Mark it private/no-store so the edge | ||
| // passes it through (Fastly return(pass)es on `Cache-Control ~ private`). | ||
| res.set('Cache-Control', 'private, no-store'); | ||
| res.set('Surrogate-Control', 'no-store'); | ||
|
|
||
| const returnTo = req.originalUrl; | ||
| return res.redirect(`/auth/login?renew=true&return_to=${encodeURIComponent(returnTo)}`); | ||
| }; | ||
| }; |
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
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.