UNPKG

serverless-offline

Version:

Emulate AWS λ and API Gateway locally when developing your Serverless project

141 lines (119 loc) 4.04 kB
import { Buffer } from "node:buffer" import crypto from "node:crypto" import { env } from "node:process" import jsEscapeString from "js-string-escape" import { decodeJwt } from "jose" import { isPlainObject, jsonPath, parseHeaders } from "../../../utils/index.js" const { parse, stringify } = JSON const { assign, entries, fromEntries } = Object function escapeJavaScript(x) { if (typeof x === "string") { return jsEscapeString(x).replaceAll(String.raw`\n`, "\n") // See #26, } if (isPlainObject(x)) { const result = fromEntries( entries(x).map(([key, value]) => [key, jsEscapeString(value)]), ) return stringify(result) // Is this really how APIG does it? } if (typeof x.toString === "function") { return escapeJavaScript(x.toString()) } return x } /* Returns a context object that mocks APIG mapping template reference http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html */ export default class VelocityContext { #path = null #payload = null #request = null #stage = null constructor(request, stage, payload, path) { this.#path = path this.#payload = payload this.#request = request this.#stage = stage } getContext() { const path = (x) => jsonPath(this.#payload, x) const authPrincipalId = this.#request.auth && this.#request.auth.credentials && this.#request.auth.credentials.principalId let authorizer = this.#request.auth && this.#request.auth.credentials && this.#request.auth.credentials.authorizer // NOTE FIXME request.raw.req.rawHeaders can only be null for testing (hapi shot inject()) const headers = parseHeaders(this.#request.raw.req.rawHeaders || []) let token = headers && (headers.Authorization || headers.authorization) if (token && token.split(" ")[0] === "Bearer") { ;[, token] = token.split(" ") } if (!authorizer) authorizer = {} authorizer.principalId = authPrincipalId || env.PRINCIPAL_ID || "offlineContext_authorizer_principalId" // See #24 if (token) { try { assign(authorizer, { claims: decodeJwt(token) }) } catch { // Nothing } } return { context: { apiId: "offlineContext_apiId", authorizer, httpMethod: this.#request.method.toUpperCase(), identity: { accountId: "offlineContext_accountId", apiKey: "offlineContext_apiKey", apiKeyId: "offlineContext_apiKeyId", caller: "offlineContext_caller", cognitoAuthenticationProvider: "offlineContext_cognitoAuthenticationProvider", cognitoAuthenticationType: "offlineContext_cognitoAuthenticationType", sourceIp: this.#request.info.remoteAddress, user: "offlineContext_user", userAgent: this.#request.headers["user-agent"] || "", userArn: "offlineContext_userArn", }, requestId: crypto.randomUUID(), resourceId: "offlineContext_resourceId", resourcePath: this.#path, stage: this.#stage, }, input: { body: this.#payload, // Not a string yet, todo json: (x) => stringify(path(x)), params: (x) => typeof x === "string" ? this.#request.params[x] || this.#request.query[x] || headers[x] : { header: headers, path: { ...this.#request.params, }, querystring: { ...this.#request.query, }, }, path, }, util: { base64Decode: (x) => Buffer.from(x.toString(), "base64").toString("binary"), base64Encode: (x) => Buffer.from(x.toString(), "binary").toString("base64"), escapeJavaScript, parseJson: parse, urlDecode: (x) => decodeURIComponent(x.replaceAll("+", " ")), urlEncode: encodeURI, }, } } }