Skip to content

Commit

Permalink
feat(tenants): support for tenant storage migration (#1747)
Browse files Browse the repository at this point in the history
Signed-off-by: Timo Glastra <[email protected]>
  • Loading branch information
TimoGlastra authored Feb 14, 2024
1 parent 793527c commit 12c617e
Show file tree
Hide file tree
Showing 24 changed files with 477 additions and 67 deletions.
3 changes: 2 additions & 1 deletion packages/askar/src/wallet/AskarProfileWallet.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { WalletConfig } from '@credo-ts/core'

import {
WalletExportUnsupportedError,
WalletDuplicateError,
WalletNotFoundError,
InjectionSymbols,
Expand Down Expand Up @@ -151,7 +152,7 @@ export class AskarProfileWallet extends AskarBaseWallet {

public async export() {
// This PR should help with this: https://github.com/hyperledger/aries-askar/pull/159
throw new WalletError('Exporting a profile is not supported.')
throw new WalletExportUnsupportedError('Exporting a profile is not supported.')
}

public async import() {
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/agent/BaseAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { SdJwtVcApi } from '../modules/sd-jwt-vc'
import { W3cCredentialsApi } from '../modules/vc/W3cCredentialsApi'
import { StorageUpdateService } from '../storage'
import { UpdateAssistant } from '../storage/migration/UpdateAssistant'
import { DEFAULT_UPDATE_CONFIG } from '../storage/migration/updates'
import { WalletApi } from '../wallet'
import { WalletError } from '../wallet/error'

Expand Down Expand Up @@ -160,7 +159,7 @@ export abstract class BaseAgent<AgentModules extends ModulesMap = EmptyModuleMap
this.logger.info(`Agent storage is ${isStorageUpToDate ? '' : 'not '}up to date.`)

if (!isStorageUpToDate && this.agentConfig.autoUpdateStorageOnStartup) {
const updateAssistant = new UpdateAssistant(this, DEFAULT_UPDATE_CONFIG)
const updateAssistant = new UpdateAssistant(this)

await updateAssistant.initialize()
await updateAssistant.update({ backupBeforeStorageUpdate: this.agentConfig.backupBeforeStorageUpdate })
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export {
export * from './logger'
export * from './error'
export * from './wallet/error'
export { VersionString } from './utils/version'
export { parseMessageType, IsValidMessageType, replaceLegacyDidSovPrefix } from './utils/messageType'
export type { Constructor, Constructable } from './utils/mixins'
export * from './agent/Events'
Expand Down
37 changes: 6 additions & 31 deletions packages/core/src/modules/sd-jwt-vc/__tests__/sdJwtVc.test.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,18 @@
import type { Key } from '@credo-ts/core'

import { AskarModule } from '../../../../../askar/src'
import { askarModuleConfig } from '../../../../../askar/tests/helpers'
import { agentDependencies } from '../../../../tests'

import {
Agent,
DidKey,
DidsModule,
getJwkFromKey,
KeyDidRegistrar,
KeyDidResolver,
KeyType,
TypedArrayEncoder,
utils,
} from '@credo-ts/core'

const getAgent = (label: string) =>
new Agent({
config: { label, walletConfig: { id: utils.uuid(), key: utils.uuid() } },
modules: {
askar: new AskarModule(askarModuleConfig),
dids: new DidsModule({
resolvers: [new KeyDidResolver()],
registrars: [new KeyDidRegistrar()],
}),
},
dependencies: agentDependencies,
})
import { getInMemoryAgentOptions } from '../../../../tests'

import { Agent, DidKey, getJwkFromKey, KeyType, TypedArrayEncoder } from '@credo-ts/core'

describe('sd-jwt-vc end to end test', () => {
const issuer = getAgent('sdjwtvcissueragent')
const issuer = new Agent(getInMemoryAgentOptions('sd-jwt-vc-issuer-agent'))
let issuerKey: Key
let issuerDidUrl: string

const holder = getAgent('sdjwtvcholderagent')
const holder = new Agent(getInMemoryAgentOptions('sd-jwt-vc-holder-agent'))
let holderKey: Key

const verifier = getAgent('sdjwtvcverifieragent')
const verifier = new Agent(getInMemoryAgentOptions('sd-jwt-vc-verifier-agent'))
const verifierDid = 'did:key:zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y'

beforeAll(async () => {
Expand Down
15 changes: 4 additions & 11 deletions packages/core/src/storage/migration/StorageUpdateService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import type { VersionString } from '../../utils/version'
import { InjectionSymbols } from '../../constants'
import { Logger } from '../../logger'
import { injectable, inject } from '../../plugins'
import { isFirstVersionEqualToSecond, isFirstVersionHigherThanSecond, parseVersionString } from '../../utils/version'

import { isStorageUpToDate } from './isUpToDate'
import { StorageVersionRecord } from './repository/StorageVersionRecord'
import { StorageVersionRepository } from './repository/StorageVersionRepository'
import { CURRENT_FRAMEWORK_STORAGE_VERSION, INITIAL_STORAGE_VERSION } from './updates'
import { INITIAL_STORAGE_VERSION } from './updates'

@injectable()
export class StorageUpdateService {
Expand All @@ -27,15 +27,8 @@ export class StorageUpdateService {
}

public async isUpToDate(agentContext: AgentContext, updateToVersion?: UpdateToVersion) {
const currentStorageVersion = parseVersionString(await this.getCurrentStorageVersion(agentContext))

const compareToVersion = parseVersionString(updateToVersion ?? CURRENT_FRAMEWORK_STORAGE_VERSION)

const isUpToDate =
isFirstVersionEqualToSecond(currentStorageVersion, compareToVersion) ||
isFirstVersionHigherThanSecond(currentStorageVersion, compareToVersion)

return isUpToDate
const currentStorageVersion = await this.getCurrentStorageVersion(agentContext)
return isStorageUpToDate(currentStorageVersion, updateToVersion)
}

public async getCurrentStorageVersion(agentContext: AgentContext): Promise<VersionString> {
Expand Down
11 changes: 8 additions & 3 deletions packages/core/src/storage/migration/UpdateAssistant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import { WalletError } from '../../wallet/error/WalletError'

import { StorageUpdateService } from './StorageUpdateService'
import { StorageUpdateError } from './error/StorageUpdateError'
import { CURRENT_FRAMEWORK_STORAGE_VERSION, supportedUpdates } from './updates'
import { DEFAULT_UPDATE_CONFIG, CURRENT_FRAMEWORK_STORAGE_VERSION, supportedUpdates } from './updates'

export interface UpdateAssistantUpdateOptions {
updateToVersion?: UpdateToVersion
backupBeforeStorageUpdate?: boolean
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export class UpdateAssistant<Agent extends BaseAgent<any> = BaseAgent> {
Expand All @@ -20,7 +25,7 @@ export class UpdateAssistant<Agent extends BaseAgent<any> = BaseAgent> {
private updateConfig: UpdateConfig
private fileSystem: FileSystem

public constructor(agent: Agent, updateConfig: UpdateConfig) {
public constructor(agent: Agent, updateConfig: UpdateConfig = DEFAULT_UPDATE_CONFIG) {
this.agent = agent
this.updateConfig = updateConfig

Expand Down Expand Up @@ -107,7 +112,7 @@ export class UpdateAssistant<Agent extends BaseAgent<any> = BaseAgent> {
return neededUpdates
}

public async update(options?: { updateToVersion?: UpdateToVersion; backupBeforeStorageUpdate?: boolean }) {
public async update(options?: UpdateAssistantUpdateOptions) {
const updateIdentifier = Date.now().toString()
const updateToVersion = options?.updateToVersion

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/storage/migration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './repository/StorageVersionRepository'
export * from './StorageUpdateService'
export * from './UpdateAssistant'
export { Update } from './updates'
export * from './isUpToDate'
17 changes: 17 additions & 0 deletions packages/core/src/storage/migration/isUpToDate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { UpdateToVersion } from './updates'
import type { VersionString } from '../../utils/version'

import { isFirstVersionEqualToSecond, isFirstVersionHigherThanSecond, parseVersionString } from '../../utils/version'

import { CURRENT_FRAMEWORK_STORAGE_VERSION } from './updates'

export function isStorageUpToDate(storageVersion: VersionString, updateToVersion?: UpdateToVersion) {
const currentStorageVersion = parseVersionString(storageVersion)
const compareToVersion = parseVersionString(updateToVersion ?? CURRENT_FRAMEWORK_STORAGE_VERSION)

const isUpToDate =
isFirstVersionEqualToSecond(currentStorageVersion, compareToVersion) ||
isFirstVersionHigherThanSecond(currentStorageVersion, compareToVersion)

return isUpToDate
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const dependencyManager = {

const agentConfig = getAgentConfig('Migration W3cCredentialRecord 0.4-0.5')
const agentContext = getAgentContext({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
dependencyManager: dependencyManager as any,
})

Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/wallet/Wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ import type {
} from '../types'
import type { Buffer } from '../utils/buffer'

// Split up into WalletManager and Wallet instance
// WalletManager is responsible for:
// - create, open, delete, close, export, import
// Wallet is responsible for:
// - createKey, sign, verify, pack, unpack, generateNonce, generateWalletKey

// - Split storage initialization from wallet initialization, as storage and wallet are not required to be the same
// - wallet handles key management, signing, and encryption
// - storage handles record storage and retrieval

export interface Wallet extends Disposable {
isInitialized: boolean
isProvisioned: boolean
Expand Down
44 changes: 40 additions & 4 deletions packages/tenants/src/TenantsApi.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
import type { CreateTenantOptions, GetTenantAgentOptions, WithTenantAgentCallback } from './TenantsApiOptions'
import type {
CreateTenantOptions,
GetTenantAgentOptions,
UpdateTenantStorageOptions,
WithTenantAgentCallback,
} from './TenantsApiOptions'
import type { TenantRecord } from './repository'
import type { DefaultAgentModules, ModulesMap, Query } from '@credo-ts/core'

import { AgentContext, inject, InjectionSymbols, AgentContextProvider, injectable, Logger } from '@credo-ts/core'
import {
isStorageUpToDate,
AgentContext,
inject,
injectable,
InjectionSymbols,
Logger,
UpdateAssistant,
} from '@credo-ts/core'

import { TenantAgent } from './TenantAgent'
import { TenantAgentContextProvider } from './context/TenantAgentContextProvider'
import { TenantRecordService } from './services'

@injectable()
export class TenantsApi<AgentModules extends ModulesMap = DefaultAgentModules> {
public readonly rootAgentContext: AgentContext
private tenantRecordService: TenantRecordService
private agentContextProvider: AgentContextProvider
private agentContextProvider: TenantAgentContextProvider
private logger: Logger

public constructor(
tenantRecordService: TenantRecordService,
rootAgentContext: AgentContext,
@inject(InjectionSymbols.AgentContextProvider) agentContextProvider: AgentContextProvider,
@inject(InjectionSymbols.AgentContextProvider) agentContextProvider: TenantAgentContextProvider,
@inject(InjectionSymbols.Logger) logger: Logger
) {
this.tenantRecordService = tenantRecordService
Expand Down Expand Up @@ -105,4 +119,26 @@ export class TenantsApi<AgentModules extends ModulesMap = DefaultAgentModules> {
this.logger.debug('Getting all tenants')
return this.tenantRecordService.getAllTenants(this.rootAgentContext)
}

public async updateTenantStorage({ tenantId, updateOptions }: UpdateTenantStorageOptions) {
this.logger.debug(`Updating tenant storage for tenant '${tenantId}'`)
const tenantRecord = await this.tenantRecordService.getTenantById(this.rootAgentContext, tenantId)

if (isStorageUpToDate(tenantRecord.storageVersion)) {
this.logger.debug(`Tenant storage for tenant '${tenantId}' is already up to date. Skipping update`)
return
}

await this.agentContextProvider.updateTenantStorage(tenantRecord, updateOptions)
}

public async getTenantsWithOutdatedStorage() {
const outdatedTenants = await this.tenantRecordService.findTenantsByQuery(this.rootAgentContext, {
$not: {
storageVersion: UpdateAssistant.frameworkStorageVersion,
},
})

return outdatedTenants
}
}
7 changes: 6 additions & 1 deletion packages/tenants/src/TenantsApiOptions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { TenantAgent } from './TenantAgent'
import type { TenantConfig } from './models/TenantConfig'
import type { ModulesMap } from '@credo-ts/core'
import type { ModulesMap, UpdateAssistantUpdateOptions } from '@credo-ts/core'

export interface GetTenantAgentOptions {
tenantId: string
Expand All @@ -13,3 +13,8 @@ export type WithTenantAgentCallback<AgentModules extends ModulesMap> = (
export interface CreateTenantOptions {
config: Omit<TenantConfig, 'walletConfig'>
}

export interface UpdateTenantStorageOptions {
tenantId: string
updateOptions?: UpdateAssistantUpdateOptions
}
1 change: 1 addition & 0 deletions packages/tenants/src/__tests__/TenantsApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ describe('TenantsApi', () => {
key: 'Wallet: TenantsApi: tenant-id',
},
},
storageVersion: '0.5',
})

const tenantAgentMock = {
Expand Down
Loading

0 comments on commit 12c617e

Please sign in to comment.