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