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