UNPKG

@azure/static-web-apps-cli

Version:
132 lines 6.2 kB
import chalk from "chalk"; import httpProxy from "http-proxy"; import { fetchWithProxy as fetch } from "../../core/utils/fetch-proxy.js"; import { decodeCookie, validateCookie } from "../../core/utils/cookie.js"; import { registerProcessExit } from "../../core/utils/cli.js"; import { logger, logRequest } from "../../core/utils/logger.js"; import { parseUrl } from "../../core/utils/net.js"; import { HAS_API, SWA_CLI_API_URI } from "../../core/constants.js"; import { onConnectionLost } from "../middlewares/request.middleware.js"; const proxyApi = httpProxy.createProxyServer({ autoRewrite: true }); registerProcessExit(() => { logger.silly(`killing SWA CLI`); proxyApi.close(() => logger.log("Api proxy stopped.")); process.exit(0); }); function injectHeaders(req, host) { logger.silly(`injecting headers to Functions request:`); if (!req.getHeader("x-ms-original-url")) { req.setHeader("x-ms-original-url", encodeURI(new URL(req.path, host).toString())); logger.silly(` - x-ms-original-url: ${chalk.yellow(req.getHeader("x-ms-original-url"))}`); } // generate a fake correlation ID req.setHeader("x-ms-request-id", `SWA-CLI-${Math.random().toString(36).substring(2).toUpperCase()}`); logger.silly(` - x-ms-request-id: ${chalk.yellow(req.getHeader("x-ms-request-id"))}`); } function injectClientPrincipalCookies(req) { logger.silly(`injecting client principal to Functions request:`); const cookie = req.getHeader("cookie"); if (cookie && validateCookie(cookie)) { const user = decodeCookie(cookie); // Remove claims from client principal to match SWA behaviour. See https://github.com/MicrosoftDocs/azure-docs/issues/86803. // The following property deletion can be removed depending on outcome of the above issue. if (user) { delete user["claims"]; } const buff = Buffer.from(JSON.stringify(user), "utf-8"); const token = buff.toString("base64"); req.setHeader("X-MS-CLIENT-PRINCIPAL", token); logger.silly(` - X-MS-CLIENT-PRINCIPAL: ${chalk.yellow(req.getHeader("X-MS-CLIENT-PRINCIPAL"))}`); // locally, we set the JWT bearer token to be the same as the cookie value because we are not using the real auth flow. // Note: on production, SWA uses a valid encrypted JWT token! if (!req.getHeader("authorization")) { req.setHeader("authorization", `Bearer ${token}`); logger.silly(` - Authorization: ${chalk.yellow(req.getHeader("authorization"))}`); } } else { logger.silly(` - no valid cookie found`); } } async function resolveLocalhostByFetching(url) { const { protocol, port } = parseUrl(url); const fetchOneOfUrl = [`${protocol}//127.0.0.1:${port}`, `${protocol}//[::1]:${port}`]; let promises = fetchOneOfUrl.map((Url) => { return fetch(Url) .then((response) => { if (response.ok || response.redirected) { logger.silly(`Fetch ${Url} successfully`); } else { logger.warn(`Fetch ${Url} with status ${response.status} ${response.statusText}`); } return Url; }) .catch((err) => { logger.silly(`Could not fetch ${Url}`); throw err; }); }); try { const availableUrl = await Promise.any(promises); return availableUrl; } catch { throw new Error('Error: "localhost" can not be resolved to either IPv4 or IPv6. Please check your network settings.'); } } export function handleFunctionRequest(req, res) { let cliApiUri = SWA_CLI_API_URI(); const { protocol, hostname, port } = parseUrl(cliApiUri); const target = hostname === "localhost" ? `${protocol}//127.0.0.1:${port}` : cliApiUri; if (HAS_API) { logger.silly(`function request detected. Proxying to Azure Functions emulator`); logger.silly(` - target: ${chalk.yellow(target)}`); } else { logger.log(`***************************************************************************`); logger.log(`** Functions request detected but no endpoint configuration was found. **`); logger.log(`** Please use the --api-location option to configure a function endpoint.**`); logger.log(`***************************************************************************`); } proxyApi.web(req, res, { target, }, onConnectionLost(req, res, target, "↳")); proxyApi.once("proxyReq", (proxyReq) => { injectHeaders(proxyReq, target); injectClientPrincipalCookies(proxyReq); }); proxyApi.once("proxyRes", (proxyRes) => { logger.silly(`getting response from remote host`); logRequest(req, "", proxyRes.statusCode); }); logRequest(req, target); } export function isFunctionRequest(req, rewritePath) { let path = rewritePath || req.url; return Boolean(path?.toLowerCase().startsWith(`/api/`)); } export async function validateFunctionTriggers(url) { const { hostname } = parseUrl(url); if (hostname === "localhost") { try { url = await resolveLocalhostByFetching(url); } catch (error) { logger.error(error?.message, true); } } try { const functionsResponse = await fetch(`${url}/admin/functions`); const functions = (await functionsResponse.json()); const triggers = functions.map((f) => f.config.bindings.find((b) => /trigger$/i.test(b.type))).map((b) => b.type); if (triggers.some((t) => !/^httptrigger$/i.test(t))) { logger.error("\nFunction app contains non-HTTP triggered functions. Azure Static Web Apps managed functions only support HTTP functions. To use this function app with Static Web Apps, see 'Bring your own function app'.\n"); } } catch (error) { logger.warn("Unable to query functions trigger types from local function app. Skipping."); logger.warn(`Note: Only Http trigger functions are supported. See https://docs.microsoft.com/azure/static-web-apps/apis`); } } //# sourceMappingURL=function.handler.js.map