@gleif-it/vlei-verifier-workflows
Version:
Workflows for vLEI users and vLEI credentials for the vLEI-verifier service
574 lines (573 loc) • 24.1 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getRootOfTrust = exports.sendAdmitMessage = exports.sendGrantMessage = exports.getOrCreateRegistry = exports.waitOperation = exports.waitForNotifications = exports.waitAndMarkNotification = exports.waitForCredential = exports.resolveOobi = exports.markNotification = exports.markAndRemoveNotification = exports.getReceivedCredential = exports.deleteOperations = exports.warnNotifications = exports.hasEndRole = exports.getStates = exports.revokeCredential = exports.getOrIssueCredential = exports.getOrCreateIdentifier = exports.getOrCreateContact = exports.getOrCreateClients = exports.getOrCreateClient = exports.getOrCreateAID = exports.getIssuedCredential = exports.getGrantedCredential = exports.getEndRoles = exports.createTimestamp = exports.createAID = exports.createAid = exports.assertNotifications = exports.assertOperations = exports.admitSinglesig = exports.sleep = void 0;
const assert_1 = require("assert");
const signify_ts_1 = require("signify-ts");
const retry_js_1 = require("./retry.js");
const resolve_env_js_1 = require("./resolve-env.js");
const workflow_state_js_1 = require("../workflow-state.js");
const handle_json_config_js_1 = require("./handle-json-config.js");
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
exports.sleep = sleep;
function admitSinglesig(client, aidName, recipientAid) {
return __awaiter(this, void 0, void 0, function* () {
const grantMsgSaid = yield waitAndMarkNotification(client, '/exn/ipex/grant');
const [admit, sigs, aend] = yield client.ipex().admit({
senderName: aidName,
message: '',
grantSaid: grantMsgSaid,
recipient: recipientAid.prefix,
});
yield client
.ipex()
.submitAdmit(aidName, admit, sigs, aend, [recipientAid.prefix]);
});
}
exports.admitSinglesig = admitSinglesig;
/**
* Assert that all operations were waited for.
* <p>This is a postcondition check to make sure all long-running operations have been waited for
* @see waitOperation
*/
function assertOperations(...clients) {
return __awaiter(this, void 0, void 0, function* () {
for (const client of clients) {
const operations = yield client.operations().list();
(0, assert_1.default)(operations.length === 0);
}
});
}
exports.assertOperations = assertOperations;
/**
* Assert that all notifications were handled.
* <p>This is a postcondition check to make sure all notifications have been handled
* @see markNotification
* @see markAndRemoveNotification
*/
function assertNotifications(...clients) {
return __awaiter(this, void 0, void 0, function* () {
for (const client of clients) {
const res = yield client.notifications().list();
const notes = res.notes.filter((i) => i.r === false);
(0, assert_1.default)(notes.length === 0);
}
});
}
exports.assertNotifications = assertNotifications;
function createAid(client, name) {
return __awaiter(this, void 0, void 0, function* () {
const [prefix, oobi] = yield getOrCreateIdentifier(client, name);
return { prefix, oobi, name };
});
}
exports.createAid = createAid;
function createAID(client, name) {
return __awaiter(this, void 0, void 0, function* () {
yield getOrCreateIdentifier(client, name);
const aid = yield client.identifiers().get(name);
console.log(name, 'AID:', aid.prefix);
return aid;
});
}
exports.createAID = createAID;
function createTimestamp() {
return new Date().toISOString().replace('Z', '000+00:00');
}
exports.createTimestamp = createTimestamp;
/**
* Get list of end role authorizations for a Keri idenfitier
*/
function getEndRoles(client, alias, role) {
return __awaiter(this, void 0, void 0, function* () {
const path = role !== undefined
? `/identifiers/${alias}/endroles/${role}`
: `/identifiers/${alias}/endroles`;
const response = yield client.fetch(path, 'GET', null);
if (!response.ok)
throw new Error(yield response.text());
const result = yield response.json();
// console.log("getEndRoles", result);
return result;
});
}
exports.getEndRoles = getEndRoles;
function getGrantedCredential(client, credId) {
return __awaiter(this, void 0, void 0, function* () {
const credentialList = yield client.credentials().list({
filter: { '-d': credId },
});
let credential;
if (credentialList.length > 0) {
(0, assert_1.default)(credentialList.length === 1);
credential = credentialList[0];
}
return credential;
});
}
exports.getGrantedCredential = getGrantedCredential;
function getIssuedCredential(issuerClient, issuerAID, recipientAID, schemaSAID) {
return __awaiter(this, void 0, void 0, function* () {
const credentialList = yield issuerClient.credentials().list({
filter: {
'-i': issuerAID.prefix,
'-s': schemaSAID,
'-a-i': recipientAID.prefix,
},
});
(0, assert_1.default)(credentialList.length <= 1);
return credentialList[0];
});
}
exports.getIssuedCredential = getIssuedCredential;
function getOrCreateAID(client, name, kargs) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
if (!client) {
throw new Error("getOrCreateAID: client doesn't exist");
}
try {
return yield client.identifiers().get(name);
}
catch (_c) {
console.log('Creating AID', name, ': ', kargs);
const result = yield client
.identifiers()
.create(name, kargs);
yield waitOperation(client, yield result.op());
const aid = yield client.identifiers().get(name);
const op = yield client
.identifiers()
.addEndRole(name, 'agent', (_b = (_a = client === null || client === void 0 ? void 0 : client.agent) === null || _a === void 0 ? void 0 : _a.pre) !== null && _b !== void 0 ? _b : undefined);
yield waitOperation(client, yield op.op());
console.log(name, 'AID:', aid.prefix);
return aid;
}
});
}
exports.getOrCreateAID = getOrCreateAID;
/**
* Connect or boot a SignifyClient instance
*/
function getOrCreateClient() {
return __awaiter(this, arguments, void 0, function* (bran = undefined, getOnly = false) {
var _a;
const env = (0, resolve_env_js_1.resolveEnvironment)();
yield signify_ts_1.default.ready();
bran !== null && bran !== void 0 ? bran : (bran = signify_ts_1.default.randomPasscode());
bran = bran.padEnd(21, '_');
const client = new signify_ts_1.default.SignifyClient(env.url, bran, signify_ts_1.default.Tier.low, env.bootUrl);
try {
yield client.connect();
}
catch (e) {
if (!getOnly) {
const res = yield client.boot();
if (!res.ok)
throw new Error();
yield client.connect();
}
else {
throw new Error('Could not connect to client w/ bran ' + bran + e.message);
}
}
console.log('client', {
agent: (_a = client.agent) === null || _a === void 0 ? void 0 : _a.pre,
controller: client.controller.pre,
});
return client;
});
}
exports.getOrCreateClient = getOrCreateClient;
/**
* Connect or boot a number of SignifyClient instances
* @example
* <caption>Create two clients with random secrets</caption>
* let client1: SignifyClient, client2: SignifyClient;
* beforeAll(async () => {
* [client1, client2] = await getOrCreateClients(2);
* });
* @example
* <caption>Launch jest from shell with pre-defined secrets</caption>
*/
function getOrCreateClients(count_1) {
return __awaiter(this, arguments, void 0, function* (count, brans = undefined, getOnly = false) {
var _a;
const tasks = [];
for (let i = 0; i < count; i++) {
tasks.push(getOrCreateClient((_a = brans === null || brans === void 0 ? void 0 : brans.at(i)) !== null && _a !== void 0 ? _a : undefined, getOnly));
}
const clients = yield Promise.all(tasks);
console.log(`secrets="${clients.map((i) => i.bran).join(',')}"`);
return clients;
});
}
exports.getOrCreateClients = getOrCreateClients;
/**
* Get or resolve a Keri contact
* @example
* <caption>Create a Keri contact before running tests</caption>
* let contact1_id: string;
* beforeAll(async () => {
* contact1_id = await getOrCreateContact(client2, "contact1", name1_oobi);
* });
*/
function getOrCreateContact(client, name, oobi) {
return __awaiter(this, void 0, void 0, function* () {
const list = yield client.contacts().list(undefined, 'alias', `^${name}$`);
// console.log("contacts.list", list);
if (list.length > 0) {
const contact = list[0];
if (contact.oobi === oobi) {
// console.log("contacts.id", contact.id);
return contact.id;
}
}
let op = yield client.oobis().resolve(oobi, name);
op = yield waitOperation(client, op);
return op.response.i;
});
}
exports.getOrCreateContact = getOrCreateContact;
/**
* Get or create a Keri identifier. Uses default witness config from `resolveEnvironment`
* @example
* <caption>Create a Keri identifier before running tests</caption>
* let name1_id: string, name1_oobi: string;
* beforeAll(async () => {
* [name1_id, name1_oobi] = await getOrCreateIdentifier(client1, "name1");
* });
* @see resolveEnvironment
*/
function getOrCreateIdentifier(client_1, name_1) {
return __awaiter(this, arguments, void 0, function* (client, name, kargs = undefined) {
var _a, _b;
let id = undefined;
try {
const identfier = yield client.identifiers().get(name);
// console.log("identifiers.get", identfier);
id = identfier.prefix;
}
catch (_c) {
const env = (0, resolve_env_js_1.resolveEnvironment)();
kargs !== null && kargs !== void 0 ? kargs : (kargs = env.witnessIds.length > 0
? { toad: env.witnessIds.length, wits: env.witnessIds }
: {});
const result = yield client
.identifiers()
.create(name, kargs);
let op = yield result.op();
op = yield waitOperation(client, op);
// console.log("identifiers.create", op);
id = op.response.i;
}
const eid = (_b = (_a = client.agent) === null || _a === void 0 ? void 0 : _a.pre) !== null && _b !== void 0 ? _b : ''; // considering this used to be a non-null assertion, presumably it will never end up being ''
if (!(yield hasEndRole(client, name, 'agent', eid))) {
const result = yield client
.identifiers()
.addEndRole(name, 'agent', eid);
let op = yield result.op();
op = yield waitOperation(client, op);
console.log('identifiers.addEndRole', op);
}
const oobi = yield client.oobis().get(name, 'agent');
const result = [id, oobi.oobis[0]];
console.log(name, result);
return result;
});
}
exports.getOrCreateIdentifier = getOrCreateIdentifier;
function getOrIssueCredential(issuerClient_1, issuerAid_1, recipientAid_1, issuerRegistry_1, credData_1, schema_1, rules_1, source_1) {
return __awaiter(this, arguments, void 0, function* (issuerClient, issuerAid, recipientAid, issuerRegistry, credData, schema, rules, source, privacy = false) {
const credentialList = yield issuerClient.credentials().list();
if (credentialList.length > 0) {
const credential = credentialList.find((cred) => cred.sad.s === schema &&
cred.sad.i === issuerAid.prefix &&
cred.sad.a.i === recipientAid.prefix &&
cred.sad.a.AID === credData.AID &&
cred.status.et != 'rev');
if (credential)
return credential;
}
const issResult = yield issuerClient.credentials().issue(issuerAid.name, {
ri: issuerRegistry.regk,
s: schema,
u: privacy ? new signify_ts_1.default.Salter({}).qb64 : undefined,
a: Object.assign({ i: recipientAid.prefix, u: privacy ? new signify_ts_1.default.Salter({}).qb64 : undefined }, credData),
r: rules,
e: source,
});
yield waitOperation(issuerClient, issResult.op);
const credential = yield issuerClient.credentials().get(issResult.acdc.ked.d);
return credential;
});
}
exports.getOrIssueCredential = getOrIssueCredential;
function revokeCredential(issuerClient, issuerAid, credentialSaid) {
return __awaiter(this, void 0, void 0, function* () {
const revResult = yield issuerClient
.credentials()
.revoke(issuerAid.name, credentialSaid);
yield waitOperation(issuerClient, revResult.op);
const credential = yield issuerClient.credentials().get(credentialSaid);
return credential;
});
}
exports.revokeCredential = revokeCredential;
function getStates(client, prefixes) {
return __awaiter(this, void 0, void 0, function* () {
const participantStates = yield Promise.all(prefixes.map((p) => client.keyStates().get(p)));
return participantStates.map((s) => s[0]);
});
}
exports.getStates = getStates;
/**
* Test if end role is authorized for a Keri identifier
*/
function hasEndRole(client, alias, role, eid) {
return __awaiter(this, void 0, void 0, function* () {
const list = yield getEndRoles(client, alias, role);
for (const i of list) {
if (i.role === role && i.eid === eid) {
return true;
}
}
return false;
});
}
exports.hasEndRole = hasEndRole;
/**
* Logs a warning for each un-handled notification.
* <p>Replace warnNotifications with assertNotifications when test handles all notifications
* @see assertNotifications
*/
function warnNotifications(...clients) {
return __awaiter(this, void 0, void 0, function* () {
let count = 0;
for (const client of clients) {
const res = yield client.notifications().list();
const notes = res.notes.filter((i) => i.r === false);
if (notes.length > 0) {
count += notes.length;
console.warn('notifications', notes);
}
}
(0, assert_1.default)(count > 0);
});
}
exports.warnNotifications = warnNotifications;
function deleteOperations(client, op) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
if ((_a = op.metadata) === null || _a === void 0 ? void 0 : _a.depends) {
yield deleteOperations(client, op.metadata.depends);
}
yield client.operations().delete(op.name);
});
}
exports.deleteOperations = deleteOperations;
function getReceivedCredential(client, credId) {
return __awaiter(this, void 0, void 0, function* () {
const credentialList = yield client.credentials().list({
filter: {
'-d': credId,
},
});
let credential;
if (credentialList.length > 0) {
(0, assert_1.default)(credentialList.length === 1);
credential = credentialList[0];
}
return credential;
});
}
exports.getReceivedCredential = getReceivedCredential;
/**
* Mark and remove notification.
*/
function markAndRemoveNotification(client, note) {
return __awaiter(this, void 0, void 0, function* () {
try {
yield client.notifications().mark(note.i);
}
finally {
yield client.notifications().delete(note.i);
}
});
}
exports.markAndRemoveNotification = markAndRemoveNotification;
/**
* Mark notification as read.
*/
function markNotification(client, note) {
return __awaiter(this, void 0, void 0, function* () {
yield client.notifications().mark(note.i);
});
}
exports.markNotification = markNotification;
function resolveOobi(client, oobi, alias) {
return __awaiter(this, void 0, void 0, function* () {
const op = yield client.oobis().resolve(oobi, alias);
yield waitOperation(client, op);
});
}
exports.resolveOobi = resolveOobi;
function waitForCredential(client_1, credSAID_1) {
return __awaiter(this, arguments, void 0, function* (client, credSAID, MAX_RETRIES = 10) {
let retryCount = 0;
while (retryCount < MAX_RETRIES) {
const cred = yield getReceivedCredential(client, credSAID);
if (cred)
return cred;
yield new Promise((resolve) => setTimeout(resolve, 1000));
console.log(` retry-${retryCount}: No credentials yet...`);
retryCount = retryCount + 1;
}
throw Error('Credential SAID: ' + credSAID + ' has not been received');
});
}
exports.waitForCredential = waitForCredential;
function waitAndMarkNotification(client, route) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const notes = yield waitForNotifications(client, route);
yield Promise.all(notes.map((note) => {
client.notifications().mark(note.i);
}));
return (_b = (_a = notes[notes.length - 1]) === null || _a === void 0 ? void 0 : _a.a.d) !== null && _b !== void 0 ? _b : '';
});
}
exports.waitAndMarkNotification = waitAndMarkNotification;
function waitForNotifications(client_1, route_1) {
return __awaiter(this, arguments, void 0, function* (client, route, options = {}) {
return (0, retry_js_1.retry)(() => __awaiter(this, void 0, void 0, function* () {
const response = yield client
.notifications()
.list();
const notes = response.notes.filter((note) => note.a.r === route && note.r === false);
if (!notes.length) {
throw new Error(`No notifications with route ${route}`);
}
return notes;
}), options);
});
}
exports.waitForNotifications = waitForNotifications;
/**
* Poll for operation to become completed.
* Removes completed operation
*/
function waitOperation(client, op, signal) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof op === 'string') {
op = yield client.operations().get(op);
}
op = yield client
.operations()
.wait(op, { signal: signal !== null && signal !== void 0 ? signal : AbortSignal.timeout(60000) });
yield deleteOperations(client, op);
return op;
});
}
exports.waitOperation = waitOperation;
function getOrCreateRegistry(client, aid, registryName) {
return __awaiter(this, void 0, void 0, function* () {
let registries = yield client.registries().list(aid.name);
registries = registries.filter((reg) => reg.name == registryName);
if (registries.length > 0) {
(0, assert_1.default)(registries.length === 1);
}
else {
const regResult = yield client
.registries()
.create({ name: aid.name, registryName: registryName });
yield waitOperation(client, yield regResult.op());
registries = yield client.registries().list(aid.name);
registries = registries.filter((reg) => reg.name == registryName);
}
console.log(registries);
return registries[0];
});
}
exports.getOrCreateRegistry = getOrCreateRegistry;
function sendGrantMessage(senderClient, senderAid, recipientAid, credential) {
return __awaiter(this, void 0, void 0, function* () {
const [grant, gsigs, gend] = yield senderClient.ipex().grant({
senderName: senderAid.name,
acdc: new signify_ts_1.default.Serder(credential.sad),
anc: new signify_ts_1.default.Serder(credential.anc),
iss: new signify_ts_1.default.Serder(credential.iss),
ancAttachment: credential.ancAttachment,
recipient: recipientAid.prefix,
datetime: createTimestamp(),
});
const op = yield senderClient
.ipex()
.submitGrant(senderAid.name, grant, gsigs, gend, [recipientAid.prefix]);
yield waitOperation(senderClient, op);
});
}
exports.sendGrantMessage = sendGrantMessage;
function sendAdmitMessage(senderClient, senderAid, recipientAid) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
const notifications = yield waitForNotifications(senderClient, '/exn/ipex/grant');
(0, assert_1.default)(notifications.length > 0);
const grantNotification = notifications[0];
const [admit, sigs, aend] = yield senderClient.ipex().admit({
senderName: senderAid.name,
message: '',
grantSaid: (_a = grantNotification.a.d) !== null && _a !== void 0 ? _a : '', // presumably, since this was originally a non-null assertion, it will never be ''
recipient: recipientAid.prefix,
datetime: createTimestamp(),
});
const op = yield senderClient
.ipex()
.submitAdmit(senderAid.name, admit, sigs, aend, [recipientAid.prefix]);
yield waitOperation(senderClient, op);
yield markAndRemoveNotification(senderClient, grantNotification);
});
}
exports.sendAdmitMessage = sendAdmitMessage;
function getRootOfTrust(configJson, rot_aid, rot_member_aid) {
return __awaiter(this, void 0, void 0, function* () {
const workflow_state = workflow_state_js_1.WorkflowState.getInstance();
// Use the rot_member_aid if provided, otherwise fall back to rot_aid
const identifierToUse = rot_member_aid || rot_aid;
const identifierData = (0, handle_json_config_js_1.getIdentifierData)(configJson, identifierToUse);
const client = workflow_state.clients.get(identifierData.agent.name);
if (!client) {
throw new Error(`Failed to initialize client for identifier: ${identifierToUse}`);
}
const rootOfTrustIdentifierName = rot_aid;
const rootOfTrustAid = yield client
.identifiers()
.get(rootOfTrustIdentifierName);
const oobi = yield client.oobis().get(rootOfTrustIdentifierName);
let oobiUrl = oobi.oobis[0];
console.log(`Root of trust OOBI: ${oobiUrl}`);
const url = new URL(oobiUrl);
if (url.hostname === 'keria')
oobiUrl = oobiUrl.replace('keria', 'localhost');
const oobiResp = yield fetch(oobiUrl);
const oobiRespBody = yield oobiResp.text();
return {
vlei: oobiRespBody,
aid: rootOfTrustAid.prefix,
oobi: oobiUrl,
};
});
}
exports.getRootOfTrust = getRootOfTrust;