@appwarden/middleware
Version:
Instantly disable all user interaction with your app deployed on Cloudflare or Vercel
218 lines (214 loc) • 6.27 kB
JavaScript
import {
applyContentSecurityPolicyToResponse,
isResponseLike
} from "../chunk-NWGDCGLB.js";
import "../chunk-CRDJAOJ7.js";
import {
getNowMs,
logElapsed
} from "../chunk-G6BMPIYD.js";
import {
checkLockStatus
} from "../chunk-SFJU4R6J.js";
import {
TEMPORARY_REDIRECT_STATUS,
buildLockPageUrl,
createHeartbeatConfigError,
createRedirect,
debug,
handleHeartbeatRequest,
isHTMLRequest,
isHeartbeatRequest,
isOnLockPage,
parseMergedConfig,
printMessage,
sanitizeConfigErrors
} from "../chunk-Q7ZBW3WZ.js";
import {
AppwardenApiHostnameSchema,
AppwardenApiTokenSchema,
BooleanSchema,
HEARTBEAT_SERVICES,
UseCSPInputSchema,
ValidLockPageSlugSchema
} from "../chunk-3OXAKJPA.js";
// src/adapters/astro-cloudflare.ts
import { env as cloudflareEnv, waitUntil } from "cloudflare:workers";
import { ZodError } from "zod";
// src/schemas/astro-cloudflare.ts
import { z } from "zod";
var AstroCloudflareConfigSchema = z.object({
/** The slug/path of the lock page to redirect to when the site is locked */
lockPageSlug: ValidLockPageSlugSchema,
/** The Appwarden API token for authentication */
appwardenApiToken: AppwardenApiTokenSchema,
/** Optional custom API hostname (defaults to https://api.appwarden.io) */
appwardenApiHostname: AppwardenApiHostnameSchema.optional(),
/** Enable debug logging */
debug: BooleanSchema.default(false),
/** Optional Content Security Policy configuration */
contentSecurityPolicy: z.lazy(() => UseCSPInputSchema).optional()
});
// src/adapters/astro-cloudflare.ts
function getAppwardenConfiguration(generatedConfig, config) {
return parseMergedConfig(
generatedConfig,
config,
AstroCloudflareConfigSchema.parse
);
}
var createAstroHeartbeatResponse = (request, runtime, configFn) => {
if (!runtime) {
return handleHeartbeatRequest(
request,
HEARTBEAT_SERVICES.CLOUDFLARE_ASTRO,
[
createHeartbeatConfigError(
["runtime"],
"custom",
"Cloudflare runtime unavailable"
)
]
);
}
try {
const validationResult = AstroCloudflareConfigSchema.safeParse(
configFn(runtime)
);
return handleHeartbeatRequest(
request,
HEARTBEAT_SERVICES.CLOUDFLARE_ASTRO,
validationResult.success ? [] : sanitizeConfigErrors(validationResult.error)
);
} catch (error) {
return handleHeartbeatRequest(
request,
HEARTBEAT_SERVICES.CLOUDFLARE_ASTRO,
error instanceof ZodError ? sanitizeConfigErrors(error) : [
createHeartbeatConfigError(
["config"],
"custom",
"Appwarden config evaluation failed"
)
]
);
}
};
var getAstroCloudflareRuntime = (locals) => {
if (!locals.cfContext) {
return void 0;
}
return {
env: cloudflareEnv,
caches,
ctx: locals.cfContext
};
};
function createAppwardenMiddleware(configFn) {
return async (context, next) => {
const startTime = getNowMs();
const { request } = context;
let config;
let debugFn;
const requestUrl = new URL(request.url);
const applyCspToResponse = async (response2) => {
if (!config.contentSecurityPolicy || !isResponseLike(response2)) {
return response2;
}
try {
return await applyContentSecurityPolicyToResponse({
request,
response: response2,
hostname: requestUrl.hostname,
waitUntil,
debug: debugFn,
contentSecurityPolicy: config.contentSecurityPolicy
});
} catch (error) {
console.error(
printMessage(
`Failed to apply content security policy: ${error instanceof Error ? error.message : String(error)}`
)
);
return response2;
}
};
const locals = context.locals;
const runtime = getAstroCloudflareRuntime(locals);
if (isHeartbeatRequest(request, requestUrl)) {
return createAstroHeartbeatResponse(request, runtime, configFn);
}
try {
if (!runtime) {
console.error(
printMessage(
"Cloudflare context not found. Ensure @astrojs/cloudflare adapter is configured."
)
);
return next();
}
const rawConfig = configFn(runtime);
const validationResult = AstroCloudflareConfigSchema.safeParse(rawConfig);
if (!validationResult.success) {
console.error(
printMessage(
`Config validation failed: ${validationResult.error.message}`
)
);
return next();
}
config = validationResult.data;
debugFn = debug(config.debug);
const isHTML = isHTMLRequest(request);
debugFn(
`Appwarden middleware invoked for ${requestUrl.pathname}`,
`isHTML: ${isHTML}`
);
if (!isHTML) {
return next();
}
if (isOnLockPage(config.lockPageSlug, request.url)) {
debugFn("Already on lock page - skipping lock status check");
} else {
const result = await checkLockStatus({
request,
appwardenApiToken: config.appwardenApiToken,
appwardenApiHostname: config.appwardenApiHostname,
debug: config.debug,
lockPageSlug: config.lockPageSlug,
waitUntil
});
if (result.isLocked) {
const lockPageUrl = buildLockPageUrl(config.lockPageSlug, request.url);
debugFn(`Website is locked - redirecting to ${lockPageUrl.pathname}`);
if (context.redirect) {
return context.redirect(
lockPageUrl.toString(),
TEMPORARY_REDIRECT_STATUS
);
}
return createRedirect(lockPageUrl);
}
debugFn("Website is unlocked");
}
} catch (error) {
if (isResponseLike(error)) {
throw error;
}
console.error(
printMessage(
`Unhandled error: ${error instanceof Error ? error.message : String(error)}`
)
);
return next();
}
const response = await next();
const finalResponse = await applyCspToResponse(response);
logElapsed(debugFn, startTime);
return finalResponse;
};
}
export {
createAppwardenMiddleware,
getAppwardenConfiguration
};