@anthusai/papyrus 1.0.0-next.28 → 1.0.0-next.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/staging-preview-object.ts +21 -5
- package/middleware.ts +3 -3
- package/package.json +1 -1
- package/routes.manifest.json +1 -1
|
@@ -8,6 +8,7 @@ export const PREVIEW_KEY_PREFIX = "preview/";
|
|
|
8
8
|
|
|
9
9
|
const PRESIGN_SECONDS = 60;
|
|
10
10
|
const NOT_FOUND_ERROR_NAMES = ["NotFound", "NoSuchKey"];
|
|
11
|
+
const FORBIDDEN_ERROR_NAMES = ["Forbidden", "AccessDenied"];
|
|
11
12
|
const PREVIEW_EXCLUDED_PREFIXES = ["/newsroom", "/api", "/_next", PREVIEW_ROUTE_PREFIX];
|
|
12
13
|
|
|
13
14
|
export function isStaticPreviewEnabled(environment: Record<string, string | undefined> = process.env): boolean {
|
|
@@ -32,13 +33,14 @@ export function previewCandidateKeys(requestPath: string): string[] | null {
|
|
|
32
33
|
return [base, `${base}.html`, `${base}/index.html`];
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
export function shouldRewriteToPreview(pathname: string): boolean {
|
|
36
|
+
export function shouldRewriteToPreview(pathname: string, newsroomAtRoot: boolean = false): boolean {
|
|
36
37
|
if (pathname === "/favicon.ico" || pathname === "/robots.txt" || pathname.startsWith("/icon")) return false;
|
|
37
|
-
|
|
38
|
+
if (PREVIEW_EXCLUDED_PREFIXES.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`))) return false;
|
|
39
|
+
return !newsroomAtRoot || pathname.endsWith(".html");
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
export function isPreviewGatedPath(pathname: string): boolean {
|
|
41
|
-
return shouldRewriteToPreview(pathname) || pathname === PREVIEW_ROUTE_PREFIX || pathname.startsWith(`${PREVIEW_ROUTE_PREFIX}/`);
|
|
42
|
+
export function isPreviewGatedPath(pathname: string, newsroomAtRoot: boolean = false): boolean {
|
|
43
|
+
return shouldRewriteToPreview(pathname, newsroomAtRoot) || pathname === PREVIEW_ROUTE_PREFIX || pathname.startsWith(`${PREVIEW_ROUTE_PREFIX}/`);
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
export function previewRewritePathname(pathname: string): string {
|
|
@@ -54,6 +56,12 @@ function isNotFoundError(error: unknown): boolean {
|
|
|
54
56
|
return NOT_FOUND_ERROR_NAMES.includes(name);
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
function isForbiddenError(error: unknown): boolean {
|
|
60
|
+
const candidate = error as { name?: string; $metadata?: { httpStatusCode?: number }; metadata?: { httpStatusCode?: number } } | null;
|
|
61
|
+
const status = candidate?.$metadata?.httpStatusCode ?? candidate?.metadata?.httpStatusCode;
|
|
62
|
+
return status === 403 || FORBIDDEN_ERROR_NAMES.includes(candidate?.name ?? "");
|
|
63
|
+
}
|
|
64
|
+
|
|
57
65
|
async function presignFirstExistingKey(candidateKeys: string[]): Promise<URL | null> {
|
|
58
66
|
const { runWithAmplifyServerContext } = getAmplifyServerRuntime();
|
|
59
67
|
return runWithAmplifyServerContext({
|
|
@@ -83,7 +91,15 @@ export async function streamPreviewObject(pathname: string): Promise<Response> {
|
|
|
83
91
|
if (!isStaticPreviewEnabled()) return notFound();
|
|
84
92
|
const candidateKeys = previewCandidateKeys(previewRequestPath(pathname));
|
|
85
93
|
if (!candidateKeys) return notFound();
|
|
86
|
-
|
|
94
|
+
let presigned: URL | null;
|
|
95
|
+
try {
|
|
96
|
+
presigned = await presignFirstExistingKey(candidateKeys);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (isForbiddenError(error)) {
|
|
99
|
+
return new Response("Staging preview is limited to editors and admins.", { status: 403, headers: { "Cache-Control": "private, no-store" } });
|
|
100
|
+
}
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
87
103
|
if (!presigned) return notFound();
|
|
88
104
|
const upstream = await fetch(presigned);
|
|
89
105
|
if (!upstream.ok || !upstream.body) return notFound();
|
package/middleware.ts
CHANGED
|
@@ -23,7 +23,7 @@ function isStagingGatedPathForBrand(pathname: string): boolean {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function routeRequest(request: NextRequest): NextResponse {
|
|
26
|
-
if (isStaticPreviewEnabled() && shouldRewriteToPreview(request.nextUrl.pathname)) {
|
|
26
|
+
if (isStaticPreviewEnabled() && shouldRewriteToPreview(request.nextUrl.pathname, usesNewsroomRootPaths())) {
|
|
27
27
|
const rewriteUrl = request.nextUrl.clone();
|
|
28
28
|
rewriteUrl.pathname = previewRewritePathname(request.nextUrl.pathname);
|
|
29
29
|
return NextResponse.rewrite(rewriteUrl);
|
|
@@ -61,8 +61,8 @@ function routeRequest(request: NextRequest): NextResponse {
|
|
|
61
61
|
export async function middleware(request: NextRequest): Promise<NextResponse> {
|
|
62
62
|
let sessionResponse: NextResponse | null = null;
|
|
63
63
|
const { pathname } = request.nextUrl;
|
|
64
|
-
const gated = isStaticPreviewEnabled()
|
|
65
|
-
? isPreviewGatedPath(pathname)
|
|
64
|
+
const gated = isStaticPreviewEnabled()
|
|
65
|
+
? isPreviewGatedPath(pathname, usesNewsroomRootPaths())
|
|
66
66
|
: isStagingGatedPathForBrand(pathname);
|
|
67
67
|
if (getSiteEnv() === "staging" && gated) {
|
|
68
68
|
sessionResponse = NextResponse.next();
|
package/package.json
CHANGED
package/routes.manifest.json
CHANGED