@sphereon/ssi-sdk.oid4vci-issuer-store
Version:
350 lines (313 loc) • 13.4 kB
text/typescript
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}`
}
}