UNPKG

@sphereon/ssi-sdk.vc-status-list-issuer-rest-api

Version:

Sphereon SSI-SDK plugin for Status List management, like StatusList2021. Issuer drivers module

486 lines (480 loc) • 21.1 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { EntryIdType: () => EntryIdType, StatusListIdType: () => StatusListIdType, StatuslistManagementApiServer: () => StatuslistManagementApiServer, createNewStatusListEndpoint: () => createNewStatusListEndpoint, getStatusListCredentialEndpoint: () => getStatusListCredentialEndpoint, getStatusListCredentialIndexStatusEndpoint: () => getStatusListCredentialIndexStatusEndpoint, getStatusListCredentialIndexStatusEndpointLegacy: () => getStatusListCredentialIndexStatusEndpointLegacy, updateStatusEndpoint: () => updateStatusEndpoint }); module.exports = __toCommonJS(index_exports); // src/statuslist-management-api-server.ts var import_ssi_express_support2 = require("@sphereon/ssi-express-support"); var import_ssi_sdk3 = require("@sphereon/ssi-sdk.core"); var import_express = __toESM(require("express"), 1); // src/api-functions.ts var import_ssi_express_support = require("@sphereon/ssi-express-support"); var import_ssi_sdk = require("@sphereon/ssi-sdk.vc-status-list"); var import_ssi_sdk2 = require("@sphereon/ssi-sdk.vc-status-list-issuer-drivers"); var import_debug = __toESM(require("debug"), 1); // src/types.ts var StatusListIdType = /* @__PURE__ */ (function(StatusListIdType2) { StatusListIdType2["StatusListId"] = "StatusListId"; StatusListIdType2["StatusListCorrelationId"] = "StatusListCorrelationId"; return StatusListIdType2; })({}); var EntryIdType = /* @__PURE__ */ (function(EntryIdType2) { EntryIdType2["StatusListIndex"] = "StatusListIndex"; EntryIdType2["EntryCorrelationId"] = "StatusListCorrelationId"; return EntryIdType2; })({}); // src/api-functions.ts var import_ssi_types = require("@sphereon/ssi-types"); var debug = (0, import_debug.default)("sphereon:ssi-sdk:status-list"); function sendStatuslistResponse(details, statuslistPayload, response) { let payload; switch (details.proofFormat) { case "jwt": case "cbor": payload = Buffer.from(statuslistPayload, "ascii"); break; default: payload = statuslistPayload; } return response.status(200).setHeader("Content-Type", details.statuslistContentType).send(payload); } __name(sendStatuslistResponse, "sendStatuslistResponse"); function buildStatusListId(request) { const protocol = request.headers["x-forwarded-proto"]?.toString() ?? request.protocol; let host = request.headers["x-forwarded-host"]?.toString() ?? request.get("host"); const forwardedPort = request.headers["x-forwarded-port"]?.toString(); if (forwardedPort && !(protocol === "https" && forwardedPort === "443") && !(protocol === "http" && forwardedPort === "80")) { host += `:${forwardedPort}`; } const forwardedPrefix = request.headers["x-forwarded-prefix"]?.toString() ?? ""; return `${protocol}://${host}${forwardedPrefix}${request.originalUrl.split("?")[0].replace(/\/status\/index\/.*/, "")}`; } __name(buildStatusListId, "buildStatusListId"); function createNewStatusListEndpoint(router, context, opts) { if (opts?.enabled === false) { console.log(`Create new status list endpoint is disabled`); return; } const path = opts?.path ?? "/status-lists"; router.post(path, (0, import_ssi_express_support.checkAuth)(opts?.endpoint), async (request, response) => { try { const statusListArgs = request.body.statusList; if (!statusListArgs) { return (0, import_ssi_express_support.sendErrorResponse)(response, 400, "No statusList details supplied"); } const details = await context.agent.slCreateStatusList(statusListArgs); const statuslistPayload = details.statusListCredential; return sendStatuslistResponse(details, statuslistPayload, response); } catch (e) { return (0, import_ssi_express_support.sendErrorResponse)(response, 500, e.message, e); } }); } __name(createNewStatusListEndpoint, "createNewStatusListEndpoint"); function getStatusListCredentialEndpoint(router, context, opts) { if (opts?.enabled === false) { console.log(`Get statusList credential endpoint is disabled`); return; } const path = opts?.path ?? "/status-lists/:index"; router.get(path, (0, import_ssi_express_support.checkAuth)(opts?.endpoint), async (request, response) => { try { const correlationId = request.query.correlationId?.toString(); const driver = await (0, import_ssi_sdk2.getDriver)({ ...correlationId ? { correlationId } : { id: buildStatusListId(request) }, dbName: opts.dbName }); const details = await driver.getStatusList(); const statuslistPayload = details.statusListCredential; return sendStatuslistResponse(details, statuslistPayload, response); } catch (e) { return (0, import_ssi_express_support.sendErrorResponse)(response, 500, e.message, e); } }); } __name(getStatusListCredentialEndpoint, "getStatusListCredentialEndpoint"); function lookupType(details) { switch (details.type) { case import_ssi_types.StatusListType.StatusList2021: return "StatusList2021Entry"; case import_ssi_types.StatusListType.BitstringStatusList: return "BitstringStatusListEntry"; default: return details.type; } } __name(lookupType, "lookupType"); function getStatusListCredentialIndexStatusEndpoint(router, context, opts) { if (opts?.enabled === false) { console.log(`Get statusList credential index status endpoint is disabled`); return; } const path = opts?.path ?? "/status-lists/:statusListId/status/entry-by-id/:entryId"; router.get(path, (0, import_ssi_express_support.checkAuth)(opts?.endpoint), async (request, response) => { try { const statusListIdType = request.query.statusListIdType ?? StatusListIdType.StatusListId; const entryIdType = request.query.entryIdType ?? EntryIdType.StatusListIndex; let statusListIndex; let entityCorrelationId; let statusListId; let statusListCorrelationId; if (entryIdType === EntryIdType.StatusListIndex) { try { statusListIndex = Number.parseInt(request.params.entryId); if (!statusListIndex || statusListIndex < 0) { return (0, import_ssi_express_support.sendErrorResponse)(response, 400, `Please provide a proper statusListIndex`); } } catch (error) { return (0, import_ssi_express_support.sendErrorResponse)(response, 400, `Please provide a proper statusListIndex`); } } else { entityCorrelationId = request.params.entryId; } if (statusListIdType === StatusListIdType.StatusListId) { statusListId = request.params.statusListId; } else { statusListCorrelationId = request.params.statusListId; } const driver = await (0, import_ssi_sdk2.getDriver)({ ...statusListCorrelationId ? { correlationId: statusListCorrelationId } : { id: statusListId }, dbName: opts.dbName }); const details = await driver.getStatusList(); if (statusListIndex && statusListIndex > details.length) { return (0, import_ssi_express_support.sendErrorResponse)(response, 400, `Please provide a proper statusListIndex`); } let entry = await driver.getStatusListEntryByIndex({ statusListId: details.id, ...entityCorrelationId ? { correlationId: entityCorrelationId } : { statusListIndex }, errorOnNotFound: false }); const type = lookupType(details); const resultStatusIndex = entry?.statusListIndex ?? statusListIndex ?? 0; const status = await (0, import_ssi_sdk.checkStatusIndexFromStatusListCredential)({ statusListCredential: details.statusListCredential, type, id: details.id, statusListIndex: resultStatusIndex, ...details.type === import_ssi_types.StatusListType.StatusList2021 && { statusPurpose: details.statusList2021?.statusPurpose }, ...details.type === import_ssi_types.StatusListType.BitstringStatusList && { statusPurpose: details.bitstringStatusList?.statusPurpose, bitsPerStatus: details.bitstringStatusList?.bitsPerStatus } }); if (!entry) { entry = { statusListId: details.id, value: "0", statusListIndex: resultStatusIndex }; } response.statusCode = 200; const result = { ...entry, status }; delete result.statusList; return response.json(result); } catch (e) { return (0, import_ssi_express_support.sendErrorResponse)(response, 500, e.message, e); } }); } __name(getStatusListCredentialIndexStatusEndpoint, "getStatusListCredentialIndexStatusEndpoint"); function getStatusListCredentialIndexStatusEndpointLegacy(router, context, opts) { if (opts?.enabled === false) { console.log(`Get statusList credential index status endpoint is disabled`); return; } const path = opts?.path ?? "/status-lists/:index/status/index/:statusListIndex"; router.get(path, (0, import_ssi_express_support.checkAuth)(opts?.endpoint), async (request, response) => { try { const statusListIndexStr = request.params.statusListIndex; let statusListIndex; try { statusListIndex = Number.parseInt(statusListIndexStr); } catch (error) { return (0, import_ssi_express_support.sendErrorResponse)(response, 400, `Please provide a proper statusListIndex`); } if (!statusListIndex || statusListIndex < 0) { return (0, import_ssi_express_support.sendErrorResponse)(response, 400, `Please provide a proper statusListIndex`); } const statusListCorrelationId = request.query.correlationId?.toString(); const driver = await (0, import_ssi_sdk2.getDriver)({ ...statusListCorrelationId ? { correlationId: statusListCorrelationId } : { id: buildStatusListId(request) }, dbName: opts.dbName }); const details = await driver.getStatusList(); if (statusListIndex > details.length) { return (0, import_ssi_express_support.sendErrorResponse)(response, 400, `Please provide a proper statusListIndex`); } const entityCorrelationId = request.query.entityCorrelationId?.toString(); let entry = await driver.getStatusListEntryByIndex({ statusListId: details.id, ...entityCorrelationId ? { correlationId: entityCorrelationId } : { statusListIndex }, errorOnNotFound: false }); const type = details.type === import_ssi_types.StatusListType.StatusList2021 ? "StatusList2021Entry" : details.type; const status = await (0, import_ssi_sdk.checkStatusIndexFromStatusListCredential)({ statusListCredential: details.statusListCredential, ...details.type === import_ssi_types.StatusListType.StatusList2021 && { statusPurpose: details.statusList2021?.statusPurpose }, ...details.type === import_ssi_types.StatusListType.BitstringStatusList && { statusPurpose: details.bitstringStatusList?.statusPurpose, bitsPerStatus: details.bitstringStatusList?.bitsPerStatus }, type, id: details.id, statusListIndex }); if (!entry) { entry = { statusListId: details.id, value: "0", statusListIndex }; } response.statusCode = 200; const result = { ...entry, status }; delete result.statusList; return response.json(result); } catch (e) { return (0, import_ssi_express_support.sendErrorResponse)(response, 500, e.message, e); } }); } __name(getStatusListCredentialIndexStatusEndpointLegacy, "getStatusListCredentialIndexStatusEndpointLegacy"); function updateStatusEndpoint(router, context, opts) { if (opts?.enabled === false) { console.log(`Update credential status endpoint is disabled`); return; } router.post(opts?.path ?? "/credentials/status", (0, import_ssi_express_support.checkAuth)(opts?.endpoint), async (request, response) => { try { debug(JSON.stringify(request.body, null, 2)); const updateRequest = request.body; const statusListId = updateRequest.statusListId ?? request.query.statusListId?.toString() ?? opts.statusListId; const statusListCorrelationId = updateRequest.statusListCorrelationId ?? request.query.statusListrelationId?.toString() ?? opts.correlationId; const entryCorrelationId = updateRequest.entryCorrelationId ?? request.query.entryCorrelationId?.toString(); if (!statusListId && !statusListCorrelationId) { return (0, import_ssi_express_support.sendErrorResponse)(response, 400, "No statusList id or correlation Id provided or deduced for the API or in the request"); } else if (!updateRequest.credentialStatus || updateRequest.credentialStatus.length === 0) { return (0, import_ssi_express_support.sendErrorResponse)(response, 400, "No statusList updates supplied"); } const driver = await (0, import_ssi_sdk2.getDriver)({ ...statusListCorrelationId ? { correlationId: statusListCorrelationId } : { id: buildStatusListId(request) }, dbName: opts.dbName }); let statusListResult = await driver.getStatusList(); let statusListEntry; if ("credentialId" in updateRequest) { if (!updateRequest.credentialId) { return (0, import_ssi_express_support.sendErrorResponse)(response, 400, "No credentialId supplied"); } statusListEntry = await driver.getStatusListEntryByCredentialId({ statusListId, statusListCorrelationId, entryCorrelationId, credentialId: updateRequest.credentialId, errorOnNotFound: true }); } else { statusListEntry = await driver.getStatusListEntryByIndex({ ...statusListResult.id && { statusListId: statusListResult.id }, ...statusListResult.correlationId && { statusListCorrelationId: statusListResult.correlationId }, ...entryCorrelationId ? { entryCorrelationId } : { statusListIndex: updateRequest.statusListIndex }, errorOnNotFound: true }); } if (!statusListEntry) { const identifier = "credentialId" in updateRequest ? updateRequest.credentialId : `index ${updateRequest.statusListIndex}`; return (0, import_ssi_express_support.sendErrorResponse)(response, 404, `Status list entry for ${identifier} not found for ${statusListId}`); } let statusListCredential = statusListResult.statusListCredential; for (const updateItem of updateRequest.credentialStatus) { if (!updateItem.status) { return (0, import_ssi_express_support.sendErrorResponse)(response, 400, `Required 'status' value was missing in the credentialStatus array`); } let value = "1"; if (updateItem.status === "0" || updateItem.status.toLowerCase() === "false") { value = "0"; } else if (updateItem.status !== "1" && updateItem.status.toLowerCase() !== "true") { if (updateItem.type === import_ssi_types.StatusListType.StatusList2021) { return (0, import_ssi_express_support.sendErrorResponse)(response, 400, `Invalid 'status' value in the credentialStatus array: ${updateItem.status}`); } else if (parseInt(updateItem.status) < 0 || parseInt(updateItem.status) > 255) { return (0, import_ssi_express_support.sendErrorResponse)(response, 400, `Invalid 'status' value in the credentialStatus array: ${updateItem.status}`); } value = `${parseInt(updateItem.status)}`; } const updStatusListId = statusListId ?? ("statusList" in statusListEntry && statusListEntry.statusList?.id) ?? statusListEntry?.statusListId; if (!updStatusListId) { return (0, import_ssi_express_support.sendErrorResponse)(response, 400, "statuslist id could not be determined"); } await driver.updateStatusListEntry({ ...statusListEntry, statusListId: updStatusListId, value }); const bitsPerStatus = "bitsPerStatus" in statusListEntry && typeof statusListEntry.bitsPerStatus === "number" ? statusListEntry.bitsPerStatus : void 0; statusListResult = await (0, import_ssi_sdk.updateStatusIndexFromStatusListCredential)({ statusListCredential, statusListIndex: statusListEntry.statusListIndex, ...bitsPerStatus && { bitsPerStatus }, value: parseInt(value), keyRef: opts.keyRef }, context); if (!statusListResult.correlationId) { return Promise.reject(Error("Cannot update the statuslist, correlationId is missing from the status list result")); } statusListResult = await driver.updateStatusList({ correlationId: statusListResult.correlationId, statusListCredential: statusListResult.statusListCredential }); } return sendStatuslistResponse(statusListResult, statusListResult.statusListCredential, response); } catch (e) { return (0, import_ssi_express_support.sendErrorResponse)(response, 500, e.message, e); } }); } __name(updateStatusEndpoint, "updateStatusEndpoint"); // src/statuslist-management-api-server.ts var StatuslistManagementApiServer = class { static { __name(this, "StatuslistManagementApiServer"); } get router() { return this._router; } _express; _agent; _opts; _router; constructor(args) { const { agent, opts } = args; this._agent = agent; if (opts?.endpointOpts?.globalAuth) { (0, import_ssi_express_support2.copyGlobalAuthToEndpoint)({ opts, key: "vcApiCredentialStatus" }); (0, import_ssi_express_support2.copyGlobalAuthToEndpoint)({ opts, key: "createStatusList" }); (0, import_ssi_express_support2.copyGlobalAuthToEndpoint)({ opts, key: "getStatusList" }); } this._opts = opts; this._express = args.expressSupport.express; this._router = import_express.default.Router(); const context = (0, import_ssi_sdk3.agentContext)(agent); const features = opts?.enableFeatures ?? [ "status-list-management", "status-list-hosting", "w3c-vc-api-credential-status" ]; console.log(`Status List API enabled, with features: ${JSON.stringify(features)}`); if (features.includes("status-list-management")) { createNewStatusListEndpoint(this.router, context, opts.endpointOpts.createStatusList); } if (features.includes("status-list-hosting")) { getStatusListCredentialEndpoint(this.router, context, opts.endpointOpts.getStatusList); getStatusListCredentialIndexStatusEndpoint(this.router, context, opts.endpointOpts.getStatusList); getStatusListCredentialIndexStatusEndpointLegacy(this.router, context, opts.endpointOpts.getStatusList); } if (features.includes("w3c-vc-api-credential-status")) { updateStatusEndpoint(this.router, context, opts.endpointOpts.vcApiCredentialStatus); } this._express.use(opts?.endpointOpts?.basePath ?? "", this.router); } get agent() { return this._agent; } get opts() { return this._opts; } get express() { return this._express; } }; //# sourceMappingURL=index.cjs.map