UNPKG

@cerbos/embedded

Version:

Client library for interacting with embedded Cerbos policy decision points generated by Cerbos Hub from server-side Node.js and browser-based applications

166 lines 6.35 kB
import { fromJsonString, toJson } from "@bufbuild/protobuf"; import { MetadataSchema } from "@cerbos/api/cerbos/cloud/epdp/v1/epdp_pb"; import { CheckResourcesRequestSchema } from "@cerbos/api/cerbos/request/v1/request_pb"; import { CheckResourcesResponseSchema } from "@cerbos/api/cerbos/response/v1/response_pb"; import { NotOK } from "@cerbos/core"; import { valuesFromProtobuf } from "@cerbos/core/~internal"; import { cancelBody } from "./response.js"; import { Slice } from "./slice.js"; export class Bundle { etag; exports; decodeJWTPayload; logger; url; static async from(source, url, logger, userAgent, { decodeJWTPayload = cannotDecodeJWTPayload, defaultPolicyVersion, globals, lenientScopeSearch, now = Date.now, }) { if (typeof source === "string" || source instanceof URL) { source = await download(source, userAgent); } else { source = await source; } const etag = source instanceof Response ? (source.headers.get("ETag") ?? undefined) : undefined; const exports = await instantiate(source, { env: { now: () => secondsSinceUnixEpoch(now()), }, }); if (defaultPolicyVersion) { const slice = Slice.ofString(exports, defaultPolicyVersion); exports.set_default_policy_version(slice.offset, slice.length); } if (globals) { const slice = Slice.ofJSON(exports, globals); try { exports.set_globals(slice.offset, slice.length); } finally { slice.deallocate(); } } if (lenientScopeSearch) { exports.set_lenient_scope_search(1); } if (!url && source instanceof Response && source.url) { url = source.url; } return new Bundle(etag, exports, decodeJWTPayload, logger, url); } _metadata; constructor(etag, exports, decodeJWTPayload, logger, url) { this.etag = etag; this.exports = exports; this.decodeJWTPayload = decodeJWTPayload; this.logger = logger; this.url = url; } get metadata() { if (!this._metadata) { const { commitHash, version, buildTimestamp, policies, // eslint-disable-line @typescript-eslint/no-deprecated sourceAttributes, } = fromJsonString(MetadataSchema, Slice.from(this.exports, this.exports.metadata()).text(), { ignoreUnknownFields: true }); this._metadata = { url: this.url, commit: commitHash || version, builtAt: new Date(Number(buildTimestamp) * 1000), policies, sourceAttributes: Object.fromEntries(Object.entries(sourceAttributes).map(([id, { attributes }]) => [ id, valuesFromProtobuf(attributes), ])), }; } return this._metadata; } async checkResources(request, headers) { let response = undefined; let auxData = undefined; let error = undefined; try { const requestJSON = toJson(CheckResourcesRequestSchema, request); if (requestJSON.auxData?.jwt) { const jwt = requestJSON.auxData.jwt; if (!jwt.keySetId) { delete jwt.keySetId; } auxData = { jwt: await this.decodeJWTPayload(jwt) }; requestJSON.auxData = auxData; } const requestSlice = Slice.ofJSON(this.exports, requestJSON); let responseSlice; try { responseSlice = Slice.from(this.exports, this.exports.check(requestSlice.offset, requestSlice.length)); } finally { requestSlice.deallocate(); } let responseText; try { responseText = responseSlice.text(); } finally { responseSlice.deallocate(); } try { response = fromJsonString(CheckResourcesResponseSchema, responseText, { ignoreUnknownFields: true, }); } catch { throw NotOK.fromJSON(responseText); } return response; } catch (caught) { error = caught; throw caught; } finally { if (this.logger) { await this.logger.logCheckResources(request, auxData, headers, response, this.metadata, error); } if (response && !request.includeMeta) { for (const result of response.results) { result.meta = undefined; } } } } } function cannotDecodeJWTPayload() { throw new Error("To decode JWTs from auxiliary data, you must provide a `decodeJWTPayload` function"); } function secondsSinceUnixEpoch(date) { const millisecondsSinceUnixEpoch = date instanceof Date ? date.getTime() : date; return BigInt(Math.floor(millisecondsSinceUnixEpoch / 1000)); } export async function download(url, userAgent, request) { const headers = new Headers(request?.headers); headers.set("User-Agent", userAgent); try { return await fetch(url, { ...request, headers }); } catch (error) { const message = `Failed to download from ${url.toString()}`; throw new Error(error instanceof Error ? `${message}: ${error.message}` : message, { cause: error }); } } async function instantiate(source, imports) { if (source instanceof Response) { return await instantiateStreaming(source, imports); } return instantiated(await WebAssembly.instantiate(source, imports)); } async function instantiateStreaming(response, imports) { if (!response.ok) { cancelBody(response); throw new Error(`Failed to download from ${response.url}: HTTP ${response.status}`); } return instantiated(await WebAssembly.instantiateStreaming(response, imports)); } function instantiated(result) { const { exports } = result instanceof WebAssembly.Instance ? result : result.instance; return exports; } //# sourceMappingURL=bundle.js.map