UNPKG

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

Version:

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

424 lines (419 loc) • 16.9 kB
var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); // src/statuslist-management-api-server.ts import { copyGlobalAuthToEndpoint } from "@sphereon/ssi-express-support"; import { agentContext } from "@sphereon/ssi-sdk.core"; import express from "express"; // src/api-functions.ts import { checkAuth, sendErrorResponse } from "@sphereon/ssi-express-support"; import { checkStatusIndexFromStatusListCredential, updateStatusIndexFromStatusListCredential } from "@sphereon/ssi-sdk.vc-status-list"; import { getDriver } from "@sphereon/ssi-sdk.vc-status-list-issuer-drivers"; import Debug from "debug"; // 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 import { StatusListType } from "@sphereon/ssi-types"; var debug = Debug("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, checkAuth(opts?.endpoint), async (request, response) => { try { const statusListArgs = request.body.statusList; if (!statusListArgs) { return 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 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, checkAuth(opts?.endpoint), async (request, response) => { try { const correlationId = request.query.correlationId?.toString(); const driver = await 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 sendErrorResponse(response, 500, e.message, e); } }); } __name(getStatusListCredentialEndpoint, "getStatusListCredentialEndpoint"); 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, 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 sendErrorResponse(response, 400, `Please provide a proper statusListIndex`); } } catch (error) { return 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 getDriver({ ...statusListCorrelationId ? { correlationId: statusListCorrelationId } : { id: statusListId }, dbName: opts.dbName }); const details = await driver.getStatusList(); if (statusListIndex && statusListIndex > details.length) { return sendErrorResponse(response, 400, `Please provide a proper statusListIndex`); } let entry = await driver.getStatusListEntryByIndex({ statusListId: details.id, ...entityCorrelationId ? { correlationId: entityCorrelationId } : { statusListIndex }, errorOnNotFound: false }); const type = details.type === StatusListType.StatusList2021 ? "StatusList2021Entry" : details.type; const resultStatusIndex = entry?.statusListIndex ?? statusListIndex ?? 0; const status = await checkStatusIndexFromStatusListCredential({ statusListCredential: details.statusListCredential, ...details.type === StatusListType.StatusList2021 ? { statusPurpose: details.statusList2021?.statusPurpose } : {}, type, id: details.id, statusListIndex: resultStatusIndex }); if (!entry) { entry = { statusListId: details.id, value: "0", statusListIndex: resultStatusIndex }; } response.statusCode = 200; return response.json({ ...entry, status }); } catch (e) { return 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, checkAuth(opts?.endpoint), async (request, response) => { try { const statusListIndexStr = request.params.statusListIndex; let statusListIndex; try { statusListIndex = Number.parseInt(statusListIndexStr); } catch (error) { return sendErrorResponse(response, 400, `Please provide a proper statusListIndex`); } if (!statusListIndex || statusListIndex < 0) { return sendErrorResponse(response, 400, `Please provide a proper statusListIndex`); } const statusListCorrelationId = request.query.correlationId?.toString(); const driver = await getDriver({ ...statusListCorrelationId ? { correlationId: statusListCorrelationId } : { id: buildStatusListId(request) }, dbName: opts.dbName }); const details = await driver.getStatusList(); if (statusListIndex > details.length) { return 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 === StatusListType.StatusList2021 ? "StatusList2021Entry" : details.type; const status = await checkStatusIndexFromStatusListCredential({ statusListCredential: details.statusListCredential, ...details.type === StatusListType.StatusList2021 ? { statusPurpose: details.statusList2021?.statusPurpose } : {}, type, id: details.id, statusListIndex }); if (!entry) { entry = { statusListId: details.id, value: "0", statusListIndex }; } response.statusCode = 200; return response.json({ ...entry, status }); } catch (e) { return 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", 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 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 sendErrorResponse(response, 400, "No statusList updates supplied"); } const driver = await 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 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 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 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 === StatusListType.StatusList2021) { return sendErrorResponse(response, 400, `Invalid 'status' value in the credentialStatus array: ${updateItem.status}`); } else if (parseInt(updateItem.status) < 0 || parseInt(updateItem.status) > 255) { return sendErrorResponse(response, 400, `Invalid 'status' value in the credentialStatus array: ${updateItem.status}`); } value = `${parseInt(updateItem.status)}`; } const updStatusListId = statusListId ?? statusListEntry.statusList?.id; if (!updStatusListId) { return sendErrorResponse(response, 400, "statuslist id could not be determined"); } await driver.updateStatusListEntry({ ...statusListEntry, statusListId: updStatusListId, value }); statusListResult = await updateStatusIndexFromStatusListCredential({ statusListCredential, statusListIndex: statusListEntry.statusListIndex, value: parseInt(value), keyRef: opts.keyRef }, context); statusListResult = await driver.updateStatusList({ statusListCredential: statusListResult.statusListCredential }); } return sendStatuslistResponse(statusListResult, statusListResult.statusListCredential, response); } catch (e) { return 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) { copyGlobalAuthToEndpoint({ opts, key: "vcApiCredentialStatus" }); copyGlobalAuthToEndpoint({ opts, key: "createStatusList" }); copyGlobalAuthToEndpoint({ opts, key: "getStatusList" }); } this._opts = opts; this._express = args.expressSupport.express; this._router = express.Router(); const context = 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; } }; export { EntryIdType, StatusListIdType, StatuslistManagementApiServer, createNewStatusListEndpoint, getStatusListCredentialEndpoint, getStatusListCredentialIndexStatusEndpoint, getStatusListCredentialIndexStatusEndpointLegacy, updateStatusEndpoint }; //# sourceMappingURL=index.js.map