UNPKG

@sphereon/ssi-sdk.oid4vci-issuer-store

Version:

350 lines (313 loc) • 13.4 kB
import { AuthorizationServerMetadata, IssuerMetadata } from '@sphereon/oid4vci-common' import { IKeyValueStore, IValueData, KeyValueStore, ValueStoreType } from '@sphereon/ssi-sdk.kv-store-temp' import { IAgentPlugin } from '@veramo/core' import { IIssuerDefaultOpts, IIssuerMetadataImportArgs, IIssuerOptions, IIssuerOptsImportArgs, IIssuerOptsPersistArgs, IMetadataImportArgs, IMetadataPersistArgs, Ioid4vciStoreClearArgs, Ioid4vciStoreExistsArgs, IOid4vciStoreGetArgs, IOid4vciStoreListArgs, IOID4VCIStoreOpts, Ioid4vciStoreRemoveArgs, OptionalIssuerMetadata, OptionalIssuerMetadataValue, } from '../index' import { IOID4VCIStore } from '../types/IOID4VCIStore' export class OID4VCIStore implements IAgentPlugin { get defaultOpts(): IIssuerDefaultOpts | undefined { return this._defaultOpts } set defaultOpts(value: IIssuerDefaultOpts | undefined) { this._defaultOpts = value } private readonly _issuerMetadataStores: Map<string, IKeyValueStore<IssuerMetadata>> private readonly _authorizationServerMetadataStores: Map<string, IKeyValueStore<AuthorizationServerMetadata>> private readonly _optionStores: Map<string, IKeyValueStore<IIssuerOptions>> private readonly defaultStoreId: string private readonly defaultNamespace: string private _defaultOpts?: IIssuerDefaultOpts readonly methods: IOID4VCIStore = { oid4vciStoreDefaultMetadata: this.oid4vciStoreDefaultMetadata.bind(this), oid4vciStoreDefaultIssuerOptions: this.oid4vciStoreIssuerOptions.bind(this), oid4vciStoreDefaultStoreId: this.oid4vciStoreDefaultStoreId.bind(this), oid4vciStoreDefaultNamespace: this.oid4vciStoreDefaultNamespace.bind(this), oid4vciStoreGetIssuerOpts: this.oid4vciStoreGetIssuerOpts.bind(this), oid4vciStoreHasIssuerOpts: this.oid4vciStoreHasIssuerOpts.bind(this), oid4vciStorePersistIssuerOpts: this.oid4vciStorePersistIssuerOpts.bind(this), oid4vciStoreRemoveIssuerOpts: this.oid4vciStoreRemoveIssuerOpts.bind(this), oid4vciStoreClearAllIssuerOpts: this.oid4vciStoreClearAllIssuerOpts.bind(this), oid4vciStoreGetMetadata: this.oid4vciStoreGetMetadata.bind(this), oid4vciStoreListMetadata: this.oid4vciStoreListMetadata.bind(this), oid4vciStoreHasMetadata: this.oid4vciStoreHasMetadata.bind(this), oid4vciStorePersistMetadata: this.oid4vciStorePersistMetadata.bind(this), oid4vciStoreRemoveMetadata: this.oid4vciStoreRemoveMetadata.bind(this), oid4vciStoreClearAllMetadata: this.oid4vciStoreClearAllMetadata.bind(this), } constructor(opts: IOID4VCIStoreOpts) { this.defaultStoreId = opts.defaultStore ?? '_default' this.defaultNamespace = opts.defaultNamespace ?? 'oid4vci' if (opts.defaultOpts) { this._defaultOpts = opts.defaultOpts } if (opts?.issuerMetadataStores && opts.issuerMetadataStores instanceof Map) { this._issuerMetadataStores = opts.issuerMetadataStores } else if (opts?.issuerMetadataStores) { this._issuerMetadataStores = new Map().set(this.defaultStoreId, opts.issuerMetadataStores) } else { this._issuerMetadataStores = new Map().set( this.defaultStoreId, new KeyValueStore({ namespace: this.defaultNamespace, store: new Map<string, IssuerMetadata>(), }), ) } if (opts?.authorizationServerMetadataStores && opts.authorizationServerMetadataStores instanceof Map) { this._authorizationServerMetadataStores = opts.authorizationServerMetadataStores } else if (opts?.authorizationServerMetadataStores) { this._authorizationServerMetadataStores = new Map().set(this.defaultStoreId, opts.authorizationServerMetadataStores) } else { this._authorizationServerMetadataStores = new Map().set( this.defaultStoreId, new KeyValueStore({ namespace: this.defaultNamespace, store: new Map<string, AuthorizationServerMetadata>(), }), ) } if (opts && Array.isArray(opts?.importMetadatas)) { opts.importMetadatas.forEach((metaImport: IMetadataImportArgs) => { const meta = metaImport as IIssuerMetadataImportArgs void this.oid4vciStorePersistMetadata({ metadataType: meta.metadataType, metadata: meta.metadata, storeId: meta.storeId ?? this.defaultStoreId, correlationId: meta.correlationId, namespace: meta.namespace ?? this.defaultNamespace, overwriteExisting: meta.overwriteExisting === undefined ? true : meta.overwriteExisting, }) }) } if (opts?.issuerOptsStores && opts.issuerOptsStores instanceof Map) { this._optionStores = opts.issuerOptsStores } else if (opts?.issuerOptsStores) { this._optionStores = new Map().set(this.defaultStoreId, opts.issuerOptsStores) } else { this._optionStores = new Map().set( this.defaultStoreId, new KeyValueStore({ namespace: this.defaultNamespace, store: new Map<string, IIssuerOptions>(), }), ) } if (opts && Array.isArray(opts?.importIssuerOpts)) { opts.importIssuerOpts.forEach((opt) => this.oid4vciStorePersistIssuerOpts(opt)) } } private async oid4vciStoreGetIssuerOpts({ correlationId, storeId, namespace }: IOid4vciStoreGetArgs): Promise<IIssuerOptions | undefined> { return ( (await this.store({ stores: this._optionStores, storeId }).get( this.prefix({ namespace, correlationId, }), )) ?? this.defaultOpts ) } public importIssuerOpts(importOpts: IIssuerOptsImportArgs[]) { importOpts.forEach((opt) => this.oid4vciStorePersistIssuerOpts(opt)) } private async oid4vciStoreHasIssuerOpts({ correlationId, storeId, namespace }: Ioid4vciStoreExistsArgs): Promise<boolean> { return this.store({ stores: this._optionStores, storeId }).has(this.prefix({ namespace, correlationId })) } private async oid4vciStorePersistIssuerOpts(args: IIssuerOptsPersistArgs): Promise<IValueData<IIssuerOptions>> { const storeId = this.storeIdStr(args) const namespace = this.namespaceStr(args) const { correlationId, issuerOpts, ttl } = args if (args?.validation !== false) { //todo } const existing = await this.store({ stores: this._optionStores, storeId }).getAsValueData( this.prefix({ namespace, correlationId, }), ) if (!existing.value || (existing.value && args.overwriteExisting !== false)) { return await this.store({ stores: this._optionStores, storeId }).set( this.prefix({ namespace, correlationId, }), issuerOpts, ttl, ) } return existing } private async oid4vciStoreRemoveIssuerOpts({ storeId, correlationId, namespace }: Ioid4vciStoreRemoveArgs): Promise<boolean> { return this.store({ stores: this._optionStores, storeId }).delete(this.prefix({ namespace, correlationId })) } private async oid4vciStoreClearAllIssuerOpts({ storeId }: Ioid4vciStoreClearArgs): Promise<boolean> { return await this.store({ stores: this._optionStores, storeId }) .clear() .then(() => true) } private async oid4vciStoreGetMetadata({ metadataType, correlationId, storeId, namespace, }: IOid4vciStoreGetArgs): Promise<IssuerMetadata | AuthorizationServerMetadata | undefined> { switch (metadataType) { case 'authorizationServer': return this.store<AuthorizationServerMetadata>({ stores: this._authorizationServerMetadataStores, storeId, }).get(this.prefix({ namespace, correlationId })) case 'issuer': return this.store<IssuerMetadata>({ stores: this._issuerMetadataStores, storeId, }).get(this.prefix({ namespace, correlationId })) } return undefined } private async oid4vciStoreListMetadata({ metadataType, storeId, namespace }: IOid4vciStoreListArgs): Promise<Array<OptionalIssuerMetadata>> { switch (metadataType) { case 'authorizationServer': return this.store<AuthorizationServerMetadata>({ stores: this._authorizationServerMetadataStores, storeId, }).getMany([`${this.namespaceStr({ namespace })}`]) case 'issuer': return this.store<IssuerMetadata>({ stores: this._issuerMetadataStores, storeId, }).getMany([`${this.namespaceStr({ namespace })}`]) } return [] } private async oid4vciStoreHasMetadata({ metadataType, correlationId, storeId, namespace }: Ioid4vciStoreExistsArgs): Promise<boolean> { switch (metadataType) { case 'authorizationServer': return this.store<AuthorizationServerMetadata>({ stores: this._authorizationServerMetadataStores, storeId, }).has(this.prefix({ namespace, correlationId })) case 'issuer': return this.store<IssuerMetadata>({ stores: this._issuerMetadataStores, storeId, }).has(this.prefix({ namespace, correlationId })) } return false } private async oid4vciStorePersistMetadata(args: IMetadataPersistArgs): Promise<OptionalIssuerMetadataValue> { const namespace = this.namespaceStr(args) const storeId = this.storeIdStr(args) const { correlationId, metadata, ttl, metadataType } = args if (args?.validation !== false) { //todo } switch (metadataType) { case 'authorizationServer': const existingAuth = await this.store<AuthorizationServerMetadata>({ stores: this._authorizationServerMetadataStores, storeId, }).getAsValueData(this.prefix({ namespace, correlationId })) if (!existingAuth.value || (existingAuth.value && args.overwriteExisting !== false)) { return await this.store<AuthorizationServerMetadata>({ stores: this._authorizationServerMetadataStores, storeId, }).set(this.prefix({ namespace, correlationId }), metadata as AuthorizationServerMetadata, ttl) } return existingAuth case 'issuer': const existingIssuer = await this.store<IssuerMetadata>({ stores: this._issuerMetadataStores, storeId, }).getAsValueData(this.prefix({ namespace, correlationId })) if (!existingIssuer.value || (existingIssuer.value && args.overwriteExisting !== false)) { return await this.store<IssuerMetadata>({ stores: this._issuerMetadataStores, storeId, }).set(this.prefix({ namespace, correlationId }), metadata as IssuerMetadata, ttl) } return existingIssuer } return undefined } private async oid4vciStoreRemoveMetadata(args: Ioid4vciStoreRemoveArgs): Promise<boolean> { const namespace = this.namespaceStr(args) const storeId = this.storeIdStr(args) switch (args.metadataType) { case 'authorizationServer': return this.store<AuthorizationServerMetadata>({ stores: this._authorizationServerMetadataStores, storeId, }).delete(this.prefix({ namespace, correlationId: args.correlationId })) case 'issuer': return this.store<IssuerMetadata>({ stores: this._issuerMetadataStores, storeId, }).delete(this.prefix({ namespace, correlationId: args.correlationId })) } return false } private async oid4vciStoreClearAllMetadata({ metadataType, storeId }: Ioid4vciStoreClearArgs): Promise<boolean> { switch (metadataType) { case 'authorizationServer': return await this.store<AuthorizationServerMetadata>({ stores: this._authorizationServerMetadataStores, storeId, }) .clear() .then(() => true) case 'issuer': return await this.store<IssuerMetadata>({ stores: this._issuerMetadataStores, storeId, }) .clear() .then(() => true) } return false } private oid4vciStoreIssuerOptions(): Promise<IKeyValueStore<IIssuerOptions>> { return Promise.resolve(this.store({ stores: this._optionStores, storeId: this.defaultStoreId })) } private oid4vciStoreDefaultMetadata(): Promise<IKeyValueStore<IssuerMetadata>> { return Promise.resolve(this.store({ stores: this._issuerMetadataStores, storeId: this.defaultStoreId })) } private oid4vciStoreDefaultStoreId(): Promise<string> { return Promise.resolve(this.defaultStoreId) } private oid4vciStoreDefaultNamespace(): Promise<string> { return Promise.resolve(this.defaultNamespace) } private store<T extends ValueStoreType>(args: { stores: Map<string, IKeyValueStore<T>>; storeId?: string }): IKeyValueStore<T> { const storeId = this.storeIdStr({ storeId: args.storeId }) const store = args.stores.get(storeId) if (!store) { throw Error(`Could not get issuer metadata store: ${storeId}`) } return store } private storeIdStr({ storeId }: { storeId?: string }): string { return storeId ?? this.defaultStoreId } private namespaceStr({ namespace }: { namespace?: string }): string { return namespace ?? this.defaultNamespace } private prefix({ namespace, correlationId }: { namespace?: string; correlationId: string }): string { return `${this.namespaceStr({ namespace })}:${correlationId}` } }