UNPKG

@ceramicnetwork/3id-did-resolver

Version:
175 lines • 6.55 kB
import { TileDocument } from '@ceramicnetwork/stream-tile'; import { LegacyResolver } from './legacyResolver.js'; import * as u8a from 'uint8arrays'; import { StreamID, CommitID } from '@ceramicnetwork/streamid'; import { CID } from 'multiformats/cid'; import { errorRepresentation, withResolutionError } from './error-representation.js'; import { EventType } from '@ceramicnetwork/common'; const DID_LD_JSON = 'application/did+ld+json'; const DID_JSON = 'application/did+json'; function isLegacyDid(didId) { try { CID.parse(didId); return true; } catch (e) { return false; } } const formatTime = (timestamp) => { return new Date(timestamp * 1000).toISOString().split('.')[0] + 'Z'; }; export function wrapDocument(content, did) { if (!(content && content.publicKeys)) return null; const startDoc = { id: did, verificationMethod: [], authentication: [], keyAgreement: [], }; return Object.entries(content.publicKeys).reduce((diddoc, [keyName, keyValue]) => { const keyBuf = u8a.fromString(keyValue.slice(1), 'base58btc'); const entry = { id: `${did}#${keyName}`, type: '', controller: did, publicKeyBase58: u8a.toString(keyBuf.slice(2), 'base58btc'), }; if (keyBuf[0] === 0xe7) { entry.type = 'EcdsaSecp256k1Signature2019'; diddoc.verificationMethod.push(entry); diddoc.authentication.push(entry); } else if (keyBuf[0] === 0xec) { entry.type = 'X25519KeyAgreementKey2019'; diddoc.verificationMethod.push(entry); diddoc.keyAgreement.push(entry); } return diddoc; }, startDoc); } function lastAnchorOrGenesisEntry(log) { for (let index = log.length - 1; index >= 0; index--) { const entry = log[index]; if (entry.type === EventType.TIME) { return entry; } } return log[0]; } function extractMetadata(requestedVersionState, latestVersionState) { const metadata = {}; const { timestamp: updated, cid: versionId } = lastAnchorOrGenesisEntry(requestedVersionState.log); const { timestamp: nextUpdate, cid: nextVersionId } = latestVersionState.log.find(({ timestamp }) => { return timestamp && ((updated && timestamp > updated) || !updated); }) || {}; if (updated) { metadata.updated = formatTime(updated); } if (nextUpdate) { metadata.nextUpdate = formatTime(nextUpdate); } if (versionId) { metadata.versionId = requestedVersionState.log.length === 1 ? '0' : versionId?.toString(); } if (nextVersionId) { metadata.nextVersionId = nextVersionId.toString(); } return metadata; } function getVersionInfo(query = '') { const asSearchParams = new URLSearchParams(query); const versionId = asSearchParams.get('versionId') || asSearchParams.get('version-id') || undefined; const versionTime = asSearchParams.get('versionTime'); return { commit: versionId, timestamp: versionTime ? Math.floor(new Date(versionTime).getTime() / 1000) : undefined, }; } async function legacyResolve(ceramic, didId, verNfo) { const legacyPublicKeys = await LegacyResolver(didId); const metadata = { controllers: [legacyPublicKeys.keyDid], family: '3id', deterministic: true }; const streamid = (await TileDocument.create(ceramic, null, metadata, { anchor: false, publish: false })).id; const didResult = await resolve(ceramic, streamid.toString(), verNfo, didId); if (didResult.didDocument === null) { didResult.didDocument = wrapDocument(legacyPublicKeys, `did:3:${didId}`); } return didResult; } async function resolve(ceramic, didId, verNfo, v03ID) { const streamId = StreamID.fromString(didId); let commitId; const query = [{ streamId }]; if (verNfo.commit) { commitId = CommitID.make(streamId, verNfo.commit); query.push({ streamId: commitId }); } else if (verNfo.timestamp) { query.push({ streamId, opts: { atTime: verNfo.timestamp }, }); } const resp = await ceramic.multiQuery(query); const latest = resp[didId]; if (!latest) { throw new Error(`Failed to properly resolve 3ID, stream ${didId} not found in response.`); } const latestVersionState = latest.state; const commitIdStr = commitId?.toString() || Object.keys(resp).find((k) => k !== didId); const requestedVersionState = (commitIdStr && resp[commitIdStr]?.state) || latestVersionState; const metadata = extractMetadata(requestedVersionState, latestVersionState); let tile; if (commitIdStr) { const found = resp[commitIdStr]; if (found) { tile = found; } else { throw new Error(`No resolution for commit ${commitIdStr}`); } } else { tile = latest; } const content = tile.state.content; const document = wrapDocument(content, `did:3:${v03ID || didId}`); return { didResolutionMetadata: { contentType: DID_JSON }, didDocument: document, didDocumentMetadata: metadata, }; } export function getResolver(ceramic) { return { '3': (_did, parsed, _resolver, options) => { return withResolutionError(async () => { const contentType = options.accept || DID_JSON; const verNfo = getVersionInfo(parsed.query); const id = parsed.id; let resolution; if (isLegacyDid(id)) { resolution = await legacyResolve(ceramic, id, verNfo); } else { resolution = await resolve(ceramic, id, verNfo); } switch (contentType) { case DID_JSON: return resolution; case DID_LD_JSON: { if (resolution.didDocument) { resolution.didDocument['@context'] = 'https://www.w3.org/ns/did/v1'; } resolution.didResolutionMetadata.contentType = DID_LD_JSON; return resolution; } default: return errorRepresentation({ error: 'representationNotSupported' }); } }); }, }; } //# sourceMappingURL=index.js.map