botbook/node_modules/@azure/static-web-apps-cli/dist/msha/handlers/function.handler.js
Rodrigo Rodriguez 6ae15fe3e5 Updated.
2024-09-04 13:13:15 -03:00

132 lines
No EOL
6 KiB
JavaScript

import chalk from "chalk";
import httpProxy from "http-proxy";
import fetch from "node-fetch";
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