@dwn-protocol/id-sdk
Version:
SDK for accessing the features and capabilities
244 lines (201 loc) • 8.46 kB
text/typescript
//@ts-nocheck
import { Convert } from '../common/index.js';
import type { IDManagedAgent } from './types/agent.js';
import type { ManagedIdentity } from './identity-manager.js';
import type { RecordsWriteMessage } from '@dwn-protocol/id';
export interface ManagedIdentityStore {
deleteIdentity(options: { did: string, agent?: IDManagedAgent, context?: string }): Promise<boolean>
getIdentity(options: { did: string, agent?: IDManagedAgent, context?: string }): Promise<ManagedIdentity | undefined>
importIdentity(options: { identity: ManagedIdentity, agent?: IDManagedAgent, context?: string }): Promise<void>
listIdentities(options?: { agent?: IDManagedAgent, context?: string }): Promise<ManagedIdentity[]>
}
/**
*
*/
export class IdentityStoreDwn implements ManagedIdentityStore {
private _identityRecordProperties = {
dataFormat : 'application/json',
schema : 'https://identity.foundation/schemas/dwn/managed-identity'
};
async deleteIdentity(options: {
agent: IDManagedAgent,
context?: string,
did: string
}): Promise<boolean> {
const { agent, context, did } = options;
// Determine which DID to use to author DWN messages.
const authorDid = await this.getAuthor({ agent, context, did });
// Query the DWN for all stored Identity objects.
const { reply: queryReply} = await agent.dwnManager.processRequest({
author : authorDid,
target : authorDid,
messageType : 'RecordsQuery',
messageOptions : {
filter: { ...this._identityRecordProperties }
}
});
// Loop through all of the entries and try to find a match.
let matchingRecordId: string | undefined;
for (const record of queryReply.entries ?? []) {
if (record.encodedData) {
const storedIdentity = Convert.base64Url(record.encodedData).toObject() as ManagedIdentity;
if (storedIdentity && storedIdentity.did === did) {
matchingRecordId = (record as RecordsWriteMessage).recordId ;
break;
}
}
}
// Return undefined if the specified Identity was not found in the store.
if (!matchingRecordId) return false;
// If a record for the specified Identity was found, attempt to delete it.
const { reply: { status } } = await agent.dwnManager.processRequest({
author : authorDid,
target : authorDid,
messageType : 'RecordsDelete',
messageOptions : {
recordId: matchingRecordId
}
});
// If the Identity was successfully deleted, return true;
if (status.code === 202) return true;
// If the Identity could not be deleted, return false;
return false;
}
async getIdentity(options: {
agent: IDManagedAgent,
context?: string,
did: string
}): Promise<ManagedIdentity | undefined> {
const { agent, context, did } = options;
// Determine which DID to use to author DWN messages.
const authorDid = await this.getAuthor({ agent, context, did });
// Query the DWN for all stored Identity objects.
const { reply: queryReply} = await agent.dwnManager.processRequest({
author : authorDid,
target : authorDid,
messageType : 'RecordsQuery',
messageOptions : { filter: { ...this._identityRecordProperties } }
});
// Loop through all of the entries and return a match, if found.
for (const record of queryReply.entries ?? []) {
if (record.encodedData) {
const storedIdentity = Convert.base64Url(record.encodedData).toObject() as ManagedIdentity;
if (storedIdentity && storedIdentity.did === did) return storedIdentity;
}
}
// Return undefined if no matches were found.
return undefined;
}
async importIdentity(options: {
agent: IDManagedAgent,
context?: string,
identity: ManagedIdentity
}) {
const { agent, context, identity } = options;
// Determine which DID to use to author DWN messages.
const authorDid = await this.getAuthor({ agent, context, did: identity.did });
// Check if the Identity being imported is already present in the store.
const duplicateFound = await this.getIdentity({ agent, context, did: identity.did });
if (duplicateFound) {
throw new Error(`IdentityStoreDwn: Identity with DID already exists: '${identity.did}'`);
}
// Encode the ManagedIdentity as bytes.
const identityU8A = Convert.object(identity).toUint8Array();
const { reply: { status } } = await agent.dwnManager.processRequest({
author : authorDid,
target : authorDid,
messageType : 'RecordsWrite',
messageOptions : { ...this._identityRecordProperties },
dataStream : new Blob([identityU8A])
});
// If the write fails, throw an error.
if (status.code !== 202) {
throw new Error('IdentityStoreDwn: Failed to write imported identity to store.');
}
}
async listIdentities(options: {
agent: IDManagedAgent,
context?: string
}): Promise<ManagedIdentity[]> {
const { agent, context } = options;
// Determine which DID to use to author DWN messages.
const authorDid = await this.getAuthor({ agent, context });
// Query the DWN for all stored Identity objects.
const { reply: queryReply} = await agent.dwnManager.processRequest({
author : authorDid,
target : authorDid,
messageType : 'RecordsQuery',
messageOptions : {
filter: { ...this._identityRecordProperties }
}
});
// Loop through all of the entries and accumulate the Identity objects.
let storedIdentities: ManagedIdentity[] = [];
for (const record of queryReply.entries ?? []) {
if (record.encodedData) {
const storedIdentity = Convert.base64Url(record.encodedData).toObject() as ManagedIdentity;
storedIdentities.push(storedIdentity);
}
}
return storedIdentities;
}
private async getAuthor(options: {
context?: string,
did?: string,
agent: IDManagedAgent
}): Promise<string> {
const { context, did, agent } = options;
// If `context` is specified, DWN messages will be signed by this DID.
if (context) return context;
// If Agent has an agentDid, use it to sign DWN messages.
if (agent.agentDid) return agent.agentDid;
// If `context`, `agent.agentDid`, and `did` are undefined, throw error.
if (!did) {
throw new Error(`DidStoreDwn: Agent property 'agentDid' is undefined.`);
}
/** Lacking a context and agentDid DID, check whether KeyManager has
* a key pair for the given `did` value.*/
const signingKeyId = await agent.didManager.getDefaultSigningKey({ did });
const keyPair = (signingKeyId)
? await agent.keyManager.getKey({ keyRef: signingKeyId })
: undefined;
// If a key pair is found, use the `did` to sign messages.
if (keyPair) return did;
// If all else fails, throw an error.
throw new Error(`IdentityStoreDwn: Agent property 'agentDid' is undefined and no keys were found for: '${did}'`);
}
}
/**
*
*/
export class IdentityStoreMemory implements ManagedIdentityStore {
/**
* A private field that contains the Map used as the in-memory key-value store.
*/
private store: Map<string, ManagedIdentity> = new Map();
async deleteIdentity({ did }: { did: string; }): Promise<boolean> {
if (this.store.has(did)) {
// Identity with given DID exists so proceed with delete.
this.store.delete(did);
return true;
}
// Identity with given DID not present so delete operation not possible.
return false;
}
async getIdentity({ did }: { did: string; }): Promise<ManagedIdentity | undefined> {
return this.store.get(did);
}
async importIdentity(options: { identity: ManagedIdentity }) {
const { identity } = options;
if (this.store.has(identity.did)) {
// Identity with given identifier already exists so import operation cannot proceed.
throw new Error(`IdentityStoreMemory: Identity with DID already exists: '${identity.did}'`);
}
// Make a deep copy of the Identity so that the object stored does not share the same references as the input.
const clonedIdentity = structuredClone(identity);
this.store.set(identity.did, clonedIdentity);
}
async listIdentities(): Promise<ManagedIdentity[]> {
return Array.from(this.store.values());
}
}