@web5/agent
Version:
322 lines • 15.7 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());
});
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { PermissionsProtocol } from '@tbd54566975/dwn-sdk-js';
import { DwnInterface, DwnPermissionGrant, DwnPermissionRequest } from './types/dwn.js';
import { Convert, TtlCache } from '@web5/common';
import { isRecordsType } from './dwn-api.js';
export class AgentPermissionsApi {
get agent() {
if (!this._agent) {
throw new Error('AgentPermissionsApi: Agent is not set');
}
return this._agent;
}
set agent(agent) {
this._agent = agent;
}
constructor({ agent } = {}) {
/** cache for fetching a permission {@link PermissionGrant}, keyed by a specific MessageType and protocol */
this._cachedPermissions = new TtlCache({ ttl: 60 * 1000 });
this._agent = agent;
}
getPermissionForRequest({ connectedDid, delegateDid, delegate, messageType, protocol, cached = false }) {
return __awaiter(this, void 0, void 0, function* () {
// Currently we only support finding grants based on protocols
// A different approach may be necessary when we introduce `protocolPath` and `contextId` specific impersonation
const cacheKey = [connectedDid, delegateDid, messageType, protocol].join('~');
const cachedGrant = cached ? this._cachedPermissions.get(cacheKey) : undefined;
if (cachedGrant) {
return cachedGrant;
}
const permissionGrants = yield this.fetchGrants({
author: delegateDid,
target: delegateDid,
grantor: connectedDid,
grantee: delegateDid,
});
// get the delegate grants that match the messageParams and are associated with the connectedDid as the grantor
const grant = yield AgentPermissionsApi.matchGrantFromArray(connectedDid, delegateDid, { messageType, protocol }, permissionGrants, delegate);
if (!grant) {
throw new Error(`CachedPermissions: No permissions found for ${messageType}: ${protocol}`);
}
this._cachedPermissions.set(cacheKey, grant);
return grant;
});
}
fetchGrants({ author, target, grantee, grantor, protocol, remote = false }) {
return __awaiter(this, void 0, void 0, function* () {
// filter by a protocol using tags if provided
const tags = protocol ? { protocol } : undefined;
const params = {
author: author,
target: target,
messageType: DwnInterface.RecordsQuery,
messageParams: {
filter: {
author: grantor,
recipient: grantee,
protocol: PermissionsProtocol.uri,
protocolPath: PermissionsProtocol.grantPath,
tags
}
}
};
const { reply } = remote ? yield this.agent.sendDwnRequest(params) : yield this.agent.processDwnRequest(params);
if (reply.status.code !== 200) {
throw new Error(`PermissionsApi: Failed to fetch grants: ${reply.status.detail}`);
}
const grants = [];
for (const entry of reply.entries) {
// TODO: Check for revocation status based on a request parameter and filter out revoked grants
const grant = yield DwnPermissionGrant.parse(entry);
grants.push({ grant, message: entry });
}
return grants;
});
}
fetchRequests({ author, target, protocol, remote = false }) {
return __awaiter(this, void 0, void 0, function* () {
// filter by a protocol using tags if provided
const tags = protocol ? { protocol } : undefined;
const params = {
author: author,
target: target,
messageType: DwnInterface.RecordsQuery,
messageParams: {
filter: {
protocol: PermissionsProtocol.uri,
protocolPath: PermissionsProtocol.requestPath,
tags
}
}
};
const { reply } = remote ? yield this.agent.sendDwnRequest(params) : yield this.agent.processDwnRequest(params);
if (reply.status.code !== 200) {
throw new Error(`PermissionsApi: Failed to fetch requests: ${reply.status.detail}`);
}
const requests = [];
for (const entry of reply.entries) {
const request = yield DwnPermissionRequest.parse(entry);
requests.push({ request, message: entry });
}
return requests;
});
}
isGrantRevoked({ author, target, grantRecordId, remote = false }) {
return __awaiter(this, void 0, void 0, function* () {
const params = {
author,
target,
messageType: DwnInterface.RecordsRead,
messageParams: {
filter: {
parentId: grantRecordId,
protocol: PermissionsProtocol.uri,
protocolPath: PermissionsProtocol.revocationPath,
}
}
};
const { reply: revocationReply } = remote ? yield this.agent.sendDwnRequest(params) : yield this.agent.processDwnRequest(params);
if (revocationReply.status.code === 404) {
// no revocation found, the grant is not revoked
return false;
}
else if (revocationReply.status.code === 200) {
// a revocation was found, the grant is revoked
return true;
}
throw new Error(`PermissionsApi: Failed to check if grant is revoked: ${revocationReply.status.detail}`);
});
}
createGrant(params) {
return __awaiter(this, void 0, void 0, function* () {
const { author, store = false, delegated = false } = params, createGrantParams = __rest(params, ["author", "store", "delegated"]);
let tags = undefined;
if (PermissionsProtocol.hasProtocolScope(createGrantParams.scope)) {
tags = { protocol: createGrantParams.scope.protocol };
}
const permissionGrantData = {
dateExpires: createGrantParams.dateExpires,
requestId: createGrantParams.requestId,
description: createGrantParams.description,
delegated,
scope: createGrantParams.scope
};
const permissionsGrantBytes = Convert.object(permissionGrantData).toUint8Array();
const messageParams = {
recipient: createGrantParams.grantedTo,
protocol: PermissionsProtocol.uri,
protocolPath: PermissionsProtocol.grantPath,
dataFormat: 'application/json',
tags
};
const { reply, message } = yield this.agent.processDwnRequest({
store,
author,
target: author,
messageType: DwnInterface.RecordsWrite,
messageParams,
dataStream: new Blob([permissionsGrantBytes])
});
if (reply.status.code !== 202) {
throw new Error(`PermissionsApi: Failed to create grant: ${reply.status.detail}`);
}
const dataEncodedMessage = Object.assign(Object.assign({}, message), { encodedData: Convert.uint8Array(permissionsGrantBytes).toBase64Url() });
const grant = yield DwnPermissionGrant.parse(dataEncodedMessage);
return { grant, message: dataEncodedMessage };
});
}
createRequest(params) {
return __awaiter(this, void 0, void 0, function* () {
const { author, store = false, delegated = false } = params, createGrantParams = __rest(params, ["author", "store", "delegated"]);
let tags = undefined;
if (PermissionsProtocol.hasProtocolScope(createGrantParams.scope)) {
tags = { protocol: createGrantParams.scope.protocol };
}
const permissionRequestData = {
description: createGrantParams.description,
delegated,
scope: createGrantParams.scope
};
const permissionRequestBytes = Convert.object(permissionRequestData).toUint8Array();
const messageParams = {
protocol: PermissionsProtocol.uri,
protocolPath: PermissionsProtocol.requestPath,
dataFormat: 'application/json',
tags
};
const { reply, message } = yield this.agent.processDwnRequest({
store,
author,
target: author,
messageType: DwnInterface.RecordsWrite,
messageParams,
dataStream: new Blob([permissionRequestBytes])
});
if (reply.status.code !== 202) {
throw new Error(`PermissionsApi: Failed to create request: ${reply.status.detail}`);
}
const dataEncodedMessage = Object.assign(Object.assign({}, message), { encodedData: Convert.uint8Array(permissionRequestBytes).toBase64Url() });
const request = yield DwnPermissionRequest.parse(dataEncodedMessage);
return { request, message: dataEncodedMessage };
});
}
createRevocation(params) {
return __awaiter(this, void 0, void 0, function* () {
const { author, store = false, grant, description } = params;
const revokeData = { description };
const permissionRevocationBytes = Convert.object(revokeData).toUint8Array();
let tags = undefined;
if (PermissionsProtocol.hasProtocolScope(grant.scope)) {
tags = { protocol: grant.scope.protocol };
}
const messageParams = {
parentContextId: grant.id,
protocol: PermissionsProtocol.uri,
protocolPath: PermissionsProtocol.revocationPath,
dataFormat: 'application/json',
tags
};
const { reply, message } = yield this.agent.processDwnRequest({
store,
author,
target: author,
messageType: DwnInterface.RecordsWrite,
messageParams,
dataStream: new Blob([permissionRevocationBytes])
});
if (reply.status.code !== 202) {
throw new Error(`PermissionsApi: Failed to create revocation: ${reply.status.detail}`);
}
const dataEncodedMessage = Object.assign(Object.assign({}, message), { encodedData: Convert.uint8Array(permissionRevocationBytes).toBase64Url() });
return { message: dataEncodedMessage };
});
}
clear() {
return __awaiter(this, void 0, void 0, function* () {
this._cachedPermissions.clear();
});
}
/**
* Matches the appropriate grant from an array of grants based on the provided parameters.
*
* @param delegated if true, only delegated grants are turned, if false all grants are returned including delegated ones.
*/
static matchGrantFromArray(grantor, grantee, messageParams, grants, delegated = false) {
return __awaiter(this, void 0, void 0, function* () {
for (const entry of grants) {
const { grant, message } = entry;
if (delegated === true && grant.delegated !== true) {
continue;
}
const { messageType, protocol, protocolPath, contextId } = messageParams;
if (this.matchScopeFromGrant(grantor, grantee, messageType, grant, protocol, protocolPath, contextId)) {
return { grant, message };
}
}
});
}
static matchScopeFromGrant(grantor, grantee, messageType, grant, protocol, protocolPath, contextId) {
// Check if the grant matches the provided parameters
if (grant.grantee !== grantee || grant.grantor !== grantor) {
return false;
}
const scope = grant.scope;
const scopeMessageType = scope.interface + scope.method;
if (scopeMessageType === messageType) {
if (isRecordsType(messageType)) {
const recordScope = scope;
if (recordScope.protocol !== protocol) {
return false;
}
// If the grant scope is not restricted to a specific context or protocol path, it is unrestricted and can be used
if (this.isUnrestrictedProtocolScope(recordScope)) {
return true;
}
// protocolPath and contextId are mutually exclusive
// If the permission is scoped to a protocolPath and the permissionParams matches that path, this grant can be used
if (recordScope.protocolPath !== undefined && recordScope.protocolPath === protocolPath) {
return true;
}
// If the permission is scoped to a contextId and the permissionParams starts with that contextId, this grant can be used
if (recordScope.contextId !== undefined && (contextId === null || contextId === void 0 ? void 0 : contextId.startsWith(recordScope.contextId))) {
return true;
}
}
else {
const messagesScope = scope;
// Checks for unrestricted protocol scope, if no protocol is defined in the scope it is unrestricted
if (messagesScope.protocol === undefined) {
return true;
}
if (messagesScope.protocol !== protocol) {
return false;
}
return this.isUnrestrictedProtocolScope(messagesScope);
}
}
return false;
}
static isUnrestrictedProtocolScope(scope) {
return scope.contextId === undefined && scope.protocolPath === undefined;
}
}
//# sourceMappingURL=permissions-api.js.map