@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
JavaScript
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