mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-12 01:46:08 +00:00
fix: centralize trusted same-origin validation for watch session and upload token routes
This commit is contained in:
@@ -2,6 +2,7 @@ import { NextRequest } from 'next/server';
|
|||||||
import { auth, checkProjectAccess } from '@/lib/auth';
|
import { auth, checkProjectAccess } from '@/lib/auth';
|
||||||
import { db } from '@/lib/db';
|
import { db } from '@/lib/db';
|
||||||
import { rateLimit } from '@/lib/rate-limit';
|
import { rateLimit } from '@/lib/rate-limit';
|
||||||
|
import { isTrustedSameOriginRequest } from '@/lib/request-origin';
|
||||||
import { validateShareLinkAccess } from '@/lib/share-links';
|
import { validateShareLinkAccess } from '@/lib/share-links';
|
||||||
import { getShareSessionFromRequest } from '@/lib/share-session';
|
import { getShareSessionFromRequest } from '@/lib/share-session';
|
||||||
import { apiErrors, successResponse, withCacheControl } from '@/lib/api-response';
|
import { apiErrors, successResponse, withCacheControl } from '@/lib/api-response';
|
||||||
@@ -20,7 +21,7 @@ function validateSameOriginRequest(request: NextRequest): Response | null {
|
|||||||
return apiErrors.forbidden('Missing Origin header');
|
return apiErrors.forbidden('Missing Origin header');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (origin !== request.nextUrl.origin) {
|
if (!isTrustedSameOriginRequest(request)) {
|
||||||
return apiErrors.forbidden('Cross-origin requests are not allowed');
|
return apiErrors.forbidden('Cross-origin requests are not allowed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createHash } from 'crypto';
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { db } from '@/lib/db';
|
import { db } from '@/lib/db';
|
||||||
import { checkRateLimit, getClientIp, rateLimit, rateLimitHeaders } from '@/lib/rate-limit';
|
import { checkRateLimit, getClientIp, rateLimit, rateLimitHeaders } from '@/lib/rate-limit';
|
||||||
|
import { isTrustedSameOriginRequest } from '@/lib/request-origin';
|
||||||
import { MAX_SHARE_PASSWORD_LENGTH, validateShareLinkAccess } from '@/lib/share-links';
|
import { MAX_SHARE_PASSWORD_LENGTH, validateShareLinkAccess } from '@/lib/share-links';
|
||||||
import {
|
import {
|
||||||
createPendingShareValue,
|
createPendingShareValue,
|
||||||
@@ -38,7 +39,7 @@ function validateSameOriginRequest(request: NextRequest): NextResponse | null {
|
|||||||
return NextResponse.json({ error: 'Missing Origin header' }, { status: 403 });
|
return NextResponse.json({ error: 'Missing Origin header' }, { status: 403 });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (origin !== request.nextUrl.origin) {
|
if (!isTrustedSameOriginRequest(request)) {
|
||||||
return NextResponse.json({ error: 'Cross-origin requests are not allowed' }, { status: 403 });
|
return NextResponse.json({ error: 'Cross-origin requests are not allowed' }, { status: 403 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import type { NextRequest } from 'next/server';
|
||||||
|
|
||||||
|
function normalizeOrigin(value: string): string | null {
|
||||||
|
try {
|
||||||
|
return new URL(value).origin;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConfiguredOrigins(): string[] {
|
||||||
|
const configured = [process.env.NEXT_PUBLIC_APP_URL, process.env.NEXTAUTH_URL];
|
||||||
|
return configured
|
||||||
|
.filter((value): value is string => typeof value === 'string' && value.trim().length > 0)
|
||||||
|
.map((value) => normalizeOrigin(/^https?:\/\//i.test(value) ? value : `https://${value}`))
|
||||||
|
.filter((value): value is string => value !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getForwardedOrigin(request: NextRequest): string | null {
|
||||||
|
const forwardedProto = request.headers.get('x-forwarded-proto')?.split(',')[0]?.trim();
|
||||||
|
const forwardedHost = request.headers.get('x-forwarded-host')?.split(',')[0]?.trim();
|
||||||
|
|
||||||
|
if (!forwardedProto || !forwardedHost) return null;
|
||||||
|
return normalizeOrigin(`${forwardedProto}://${forwardedHost}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHostHeaderOrigin(request: NextRequest): string | null {
|
||||||
|
const host = request.headers.get('host')?.split(',')[0]?.trim();
|
||||||
|
if (!host) return null;
|
||||||
|
|
||||||
|
const forwardedProto = request.headers.get('x-forwarded-proto')?.split(',')[0]?.trim();
|
||||||
|
const protocol = forwardedProto || request.nextUrl.protocol.replace(':', '');
|
||||||
|
if (!protocol) return null;
|
||||||
|
|
||||||
|
return normalizeOrigin(`${protocol}://${host}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAllowedRequestOrigins(request: NextRequest): Set<string> {
|
||||||
|
const origins = new Set<string>();
|
||||||
|
origins.add(request.nextUrl.origin);
|
||||||
|
|
||||||
|
const forwardedOrigin = getForwardedOrigin(request);
|
||||||
|
if (forwardedOrigin) origins.add(forwardedOrigin);
|
||||||
|
|
||||||
|
const hostHeaderOrigin = getHostHeaderOrigin(request);
|
||||||
|
if (hostHeaderOrigin) origins.add(hostHeaderOrigin);
|
||||||
|
|
||||||
|
for (const configuredOrigin of getConfiguredOrigins()) {
|
||||||
|
origins.add(configuredOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
return origins;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isTrustedSameOriginRequest(request: NextRequest): boolean {
|
||||||
|
const requestOrigin = request.headers.get('origin');
|
||||||
|
if (!requestOrigin) return false;
|
||||||
|
|
||||||
|
const normalizedRequestOrigin = normalizeOrigin(requestOrigin);
|
||||||
|
if (!normalizedRequestOrigin) return false;
|
||||||
|
|
||||||
|
return getAllowedRequestOrigins(request).has(normalizedRequestOrigin);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user