@appwarden/middleware
Version:
Instantly disable all user interaction with your app deployed on Cloudflare or Vercel
244 lines (235 loc) • 6.93 kB
JavaScript
import {
UseAppwardenInputSchema,
lockPageSlugRefinement
} from "./chunk-AP6A6BXL.js";
import {
useContentSecurityPolicy
} from "./chunk-CRDJAOJ7.js";
import {
checkLockStatus
} from "./chunk-SFJU4R6J.js";
import {
buildLockPageUrl,
createHeartbeatConfigError,
createRedirect,
debug,
handleHeartbeatRequest,
isHTMLRequest,
isHeartbeatRequest,
isOnLockPage,
parseMergedConfig,
printMessage,
sanitizeConfigErrors
} from "./chunk-Q7ZBW3WZ.js";
import {
HEARTBEAT_SERVICES,
getErrors
} from "./chunk-3OXAKJPA.js";
// src/runners/appwarden-on-cloudflare.ts
import { ZodError } from "zod";
// src/schemas/cloudflare.ts
import { z } from "zod";
var ConfigFnInputSchema = z.function().args(z.custom()).returns(z.lazy(() => lockPageSlugRefinement(UseAppwardenInputSchema)));
// src/utils/middleware.ts
var usePipeline = (...initMiddlewares) => {
const stack = [...initMiddlewares];
const execute = async (context) => {
const runner = async (prevIndex, index) => {
if (index === prevIndex) {
throw new Error("next() called multiple times");
}
if (index >= stack.length) {
return;
}
const middleware = stack[index];
const next = async () => runner(index, index + 1);
await middleware(context, next);
};
await runner(-1, 0);
};
return {
execute
};
};
// src/utils/cloudflare/insert-errors-logs.ts
function htmlEscape(str) {
return str.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
}
var insertErrorLogs = async (context, error) => {
const errors = getErrors(error);
for (const err of errors) {
console.log(printMessage(err));
}
const safeErrors = JSON.stringify(
errors.map((err) => printMessage(err))
);
const escapedErrors = htmlEscape(safeErrors);
return new HTMLRewriter().on("body", {
element: (elem) => {
elem.setAttribute("data-appwarden-logs", escapedErrors);
elem.append(
`<script>const logs=JSON.parse(document.body.getAttribute("data-appwarden-logs"));for(const log of logs){console.error(log)}</script>`,
{ html: true }
);
}
}).transform(await fetch(context.request.clone()));
};
// src/middlewares/use-appwarden.ts
var useAppwarden = (input) => async (context, next) => {
const { request } = context;
let shouldCallNext = true;
try {
const requestUrl = new URL(request.url);
if (request.method.toUpperCase() === "OPTIONS") {
return;
}
if (!isHTMLRequest(request)) {
return;
}
const lockPageSlug = input.multidomainConfig?.[requestUrl.hostname]?.lockPageSlug ?? input.lockPageSlug;
if (!lockPageSlug) {
return;
}
if (isOnLockPage(lockPageSlug, request.url)) {
return;
}
const result = await checkLockStatus({
request,
appwardenApiToken: input.appwardenApiToken,
appwardenApiHostname: input.appwardenApiHostname,
debug: input.debug,
lockPageSlug,
waitUntil: (fn) => context.waitUntil(fn)
});
if (result.isLocked) {
const lockPageUrl = buildLockPageUrl(lockPageSlug, request.url);
context.response = createRedirect(lockPageUrl);
shouldCallNext = false;
return;
}
} catch (e) {
const message = "Appwarden encountered an unknown error. Please contact Appwarden support at https://appwarden.io/join-community.";
console.error(
printMessage(
e instanceof Error ? `${message} - ${e.message}` : message
)
);
} finally {
if (shouldCallNext) {
await next();
}
}
};
// src/middlewares/use-fetch-origin.ts
var useFetchOrigin = () => async (context, next) => {
context.response = await fetch(new Request(context.request));
await next();
};
// src/runners/appwarden-on-cloudflare.ts
var RefinedUseAppwardenInputSchema = lockPageSlugRefinement(
UseAppwardenInputSchema
);
function getAppwardenConfiguration(generatedConfig, config) {
return parseMergedConfig(
generatedConfig,
config,
RefinedUseAppwardenInputSchema.parse
);
}
var appwardenOnCloudflare = (inputFn) => async (request, env, ctx) => {
ctx.passThroughOnException();
const requestUrl = new URL(request.url);
const requestContext = {
env,
ctx
};
const parsedInput = ConfigFnInputSchema.safeParse(inputFn);
if (isHeartbeatRequest(request, requestUrl)) {
let configErrors = parsedInput.success ? [] : sanitizeConfigErrors(parsedInput.error);
if (parsedInput.success) {
try {
parsedInput.data(requestContext);
} catch (error) {
if (error instanceof ZodError) {
configErrors = sanitizeConfigErrors(error);
} else {
configErrors = [
createHeartbeatConfigError(
["config"],
"custom",
"Appwarden config evaluation failed"
)
];
}
}
}
return handleHeartbeatRequest(
request,
HEARTBEAT_SERVICES.CLOUDFLARE,
configErrors
);
}
if (!parsedInput.success) {
const tempContext = {
request,
hostname: requestUrl.hostname,
response: new Response("Unhandled response"),
waitUntil: (fn) => ctx.waitUntil(fn),
debug: () => {
}
// no-op debug for error case
};
return insertErrorLogs(tempContext, parsedInput.error);
}
let input;
try {
input = parsedInput.data(requestContext);
} catch (error) {
if (error instanceof ZodError) {
const tempContext = {
request,
hostname: requestUrl.hostname,
response: new Response("Unhandled response"),
waitUntil: (fn) => ctx.waitUntil(fn),
debug: () => {
}
// no-op debug for error case
};
return insertErrorLogs(tempContext, error);
}
throw error;
}
const domainDebug = input.multidomainConfig?.[requestUrl.hostname]?.debug ?? input.debug ?? false;
const context = {
request,
hostname: requestUrl.hostname,
response: new Response("Unhandled response"),
// https://developers.cloudflare.com/workers/observability/errors/#illegal-invocation-errors
waitUntil: (fn) => ctx.waitUntil(fn),
debug: debug(domainDebug)
};
try {
const pipeline = [
useAppwarden({ ...input, debug: domainDebug }),
useFetchOrigin()
];
const cspConfig = input.multidomainConfig?.[requestUrl.hostname]?.contentSecurityPolicy ?? input.contentSecurityPolicy;
if (cspConfig) {
pipeline.push(useContentSecurityPolicy(cspConfig));
}
await usePipeline(...pipeline).execute(context);
} catch (error) {
if (error instanceof ZodError) {
return insertErrorLogs(context, error);
}
throw error;
}
return context.response;
};
// src/bundles/cloudflare.ts
var createAppwardenMiddleware = appwardenOnCloudflare;
export {
createAppwardenMiddleware,
getAppwardenConfiguration,
useContentSecurityPolicy
};