mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-12 01:46:08 +00:00
fix(invitations): throttle unauthenticated invitation lookups and harden redirects
The invitation preview surfaces (/invitations/accept and /register?invitationToken=) are the
only unauthenticated reads of invitation data, and each render costs two database queries.
They are now rate limited before the lookup can touch the database: a generous per-IP bucket
that bounds enumeration across tokens, plus a tight per-IP+token bucket that stops repeated
probing of a single invitation. Tokens are hashed before they reach the rate_limits table.
A throttled lookup says so ("we couldn't check this invitation right now") instead of claiming
the invitation is invalid, and signed-in acceptance is not gated by it.
The callback sanitizer also checked only the origin, which is not enough: an attacker can
smuggle a host into the path of an otherwise same-origin URL — new URL('https://app//evil.com')
keeps our origin but yields a pathname of //evil.com, which navigation sinks resolve as
protocol-relative and follow off-site. Paths are now required to be rooted at a single slash,
and the login redirect re-checks at the sink.
getClientIp is split so server components that only have `await headers()` resolve the client
IP through the same trusted-proxy logic as route handlers.
This commit is contained in:
@@ -9,7 +9,11 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { getSafeCallbackUrl, isInvitationCallbackUrl } from '@/lib/safe-redirect';
|
||||
import {
|
||||
getSafeCallbackUrl,
|
||||
isInvitationCallbackUrl,
|
||||
isSafeRelativePath,
|
||||
} from '@/lib/safe-redirect';
|
||||
|
||||
/**
|
||||
* Sign-up link that carries the pending destination — and, when that destination is an
|
||||
@@ -88,8 +92,10 @@ function LoginFormInner({ googleEnabled, githubEnabled }: LoginFormInnerProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
// `result.url` is whatever next-auth resolved, so it is sanitized again here — and
|
||||
// re-checked at the sink, because `router.push` happily leaves the origin.
|
||||
const destination = getSafeCallbackUrl(result?.url || callbackUrl);
|
||||
router.push(destination);
|
||||
router.push(isSafeRelativePath(destination) ? destination : '/dashboard');
|
||||
router.refresh();
|
||||
} catch {
|
||||
setError('Something went wrong. Please try again.');
|
||||
|
||||
Reference in New Issue
Block a user