UNPKG

@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
"use strict"; 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;