UNPKG

@appwarden/middleware

Version:

Instantly disable all user interaction with your app deployed on Cloudflare or Vercel

218 lines (214 loc) 6.27 kB
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 };