Merge pull request #737 from PicPeak/fix/auth-access-control
fix(security): cross-event thumbnail leak, bulk-op ownership bypass + auth hardening
This commit is contained in:
@@ -137,18 +137,26 @@ export const AuthenticatedImage: React.FC<AuthenticatedImageProps> = ({
|
||||
throw new Error('No URL provided');
|
||||
}
|
||||
|
||||
// Build full URL for the image
|
||||
// Build full URL for the image. Only relative paths are app-owned;
|
||||
// an absolute URL is passed through untouched.
|
||||
const isRelative = rawUrl.startsWith('/');
|
||||
const fullImageUrl = rawUrl.startsWith('/admin')
|
||||
? buildResourceUrl(`/api${rawUrl}`)
|
||||
: rawUrl.startsWith('/')
|
||||
: isRelative
|
||||
? buildResourceUrl(rawUrl)
|
||||
: rawUrl;
|
||||
|
||||
const headers: Record<string, string> = {};
|
||||
const slugForRequest = resolveSlug(rawUrl);
|
||||
const token = getGalleryToken(slugForRequest);
|
||||
if (token) {
|
||||
headers.Authorization = `Bearer ${token}`;
|
||||
// Attach the gallery bearer token ONLY to relative (same-app) image
|
||||
// paths. Never send it to an absolute/external URL — that would leak
|
||||
// gallery credentials cross-origin. AuthenticatedImage does not
|
||||
// support external URLs by design.
|
||||
if (isRelative) {
|
||||
const slugForRequest = resolveSlug(rawUrl);
|
||||
const token = getGalleryToken(slugForRequest);
|
||||
if (token) {
|
||||
headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(fullImageUrl, {
|
||||
|
||||
@@ -56,11 +56,19 @@ api.interceptors.request.use(
|
||||
|
||||
const pathname = rawPath.startsWith('/') ? rawPath : `/${rawPath}`;
|
||||
|
||||
const isGalleryEndpoint = /^\/gallery\//.test(pathname)
|
||||
|| /^\/secure-images\//.test(pathname)
|
||||
|| /^\/auth\/gallery\//.test(pathname);
|
||||
// Never attach the gallery token to an absolute URL. Requests to the
|
||||
// app's own API use relative paths (axios prepends baseURL); an
|
||||
// absolute URL could point at any origin, and extracting its
|
||||
// `/gallery/...` pathname would otherwise match below and leak the
|
||||
// bearer token cross-origin.
|
||||
const isAbsoluteUrl = /^https?:\/\//i.test(config.url || '');
|
||||
|
||||
const isGallerySessionCheck = pathname === '/auth/session'
|
||||
const isGalleryEndpoint = !isAbsoluteUrl && (
|
||||
/^\/gallery\//.test(pathname)
|
||||
|| /^\/secure-images\//.test(pathname)
|
||||
|| /^\/auth\/gallery\//.test(pathname));
|
||||
|
||||
const isGallerySessionCheck = !isAbsoluteUrl && pathname === '/auth/session'
|
||||
&& (!!paramSlug || window.location.pathname.startsWith('/gallery/'));
|
||||
|
||||
if (isGalleryEndpoint || isGallerySessionCheck) {
|
||||
|
||||
Reference in New Issue
Block a user