-
Notifications
You must be signed in to change notification settings - Fork 204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add support for postgres wallet type #699
Changes from 5 commits
004a7ed
2ed6194
085f966
3907d17
58cab5a
d4586d9
d5c5ccf
fc3c7ab
a29b72a
c7122c3
e1749ca
b33231c
f81d5bd
e41be46
dfa7487
d8077f3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,34 @@ | ||
import type { WalletStorageConfig } from 'indy-sdk' | ||
|
||
export interface FileSystem { | ||
readonly basePath: string | ||
|
||
exists(path: string): Promise<boolean> | ||
write(path: string, data: string): Promise<void> | ||
read(path: string): Promise<string> | ||
downloadToFile(url: string, path: string): Promise<void> | ||
loadPostgresPlugin?(storageConfig: WalletStorageConfig, storageCreds: WalletStorageCreds): Promise<boolean> | ||
} | ||
|
||
export enum WalletScheme { | ||
DatabasePerWallet = 'DatabasePerWallet', | ||
MultiWalletSingleTable = 'MultiWalletSingleTable', | ||
MultiWalletSingleTableSharedPool = 'MultiWalletSingleTableSharedPool', | ||
} | ||
|
||
export interface StorageConfig { | ||
url: string | ||
wallet_scheme?: WalletScheme | ||
path?: string | undefined | ||
} | ||
|
||
export interface WalletStorageCreds { | ||
[key: string]: unknown | ||
} | ||
|
||
export interface StorageCreds { | ||
account: string | ||
password: string | ||
admin_account: string | ||
admin_password: string | ||
} | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,8 @@ import type { DidCommService } from './modules/dids/domain/service/DidCommServic | |
import type { IndyPoolConfig } from './modules/ledger/IndyPool' | ||
import type { AutoAcceptProof } from './modules/proofs' | ||
import type { MediatorPickupStrategy } from './modules/routing' | ||
import type { WalletStorageCreds } from './storage/FileSystem' | ||
import type { WalletStorageConfig } from 'indy-sdk' | ||
|
||
export const enum KeyDerivationMethod { | ||
/** default value in indy-sdk. Will be used when no value is provided */ | ||
|
@@ -20,6 +22,9 @@ export interface WalletConfig { | |
id: string | ||
key: string | ||
keyDerivationMethod?: KeyDerivationMethod | ||
storageType?: WalletStorageType | ||
storageConfig?: WalletStorageConfig | ||
storageCreds?: WalletStorageCreds | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we can chamge the interface of this to be not 100% what indy expects, but more how we would want the user of the framework to provide the values. Mainly with the addition of other wallet types I'm not sure if this approach fits nicely. I was thinking about something like this: // defined in types.ts
export interface WalletConfig {
id: string
key: string
keyDerivationMethod?: KeyDerivationMethod
storage?: {
type: string
[key: string]: unknown
}
}
// defined in @aries-framework/node package
interface IndyPostgresStorageConfig {
type: 'postgres',
config: {
// .. parameters needed for postgres plugin
},
credentials: {
// .. parameters needed for postgres plugin
}
} It can then be used like this: import { IndyPostgresStorageConfig } from '@aries-framework/core'
const storageConfig: IndyPostgresStorageConfig = {
type: 'postgres',
// other required fields
}
await loadPostgresPlugin()
const agent = new Agent({
walletConfig: {
// other required fields
storage: storageConfig
}
})
await agent.initialize() |
||
} | ||
|
||
export interface WalletConfigRekey { | ||
|
@@ -47,6 +52,11 @@ export enum DidCommMimeType { | |
V1 = 'application/didcomm-envelope-enc', | ||
} | ||
|
||
export enum WalletStorageType { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can also be removed now |
||
Default = 'default', | ||
Postgres = 'postgres_storage', | ||
} | ||
|
||
export interface InitConfig { | ||
endpoints?: string[] | ||
label: string | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ import { Lifecycle, scoped } from 'tsyringe' | |
|
||
import { AgentConfig } from '../agent/AgentConfig' | ||
import { AriesFrameworkError } from '../error' | ||
import { WalletStorageType } from '../types' | ||
import { JsonEncoder } from '../utils/JsonEncoder' | ||
import { isIndyError } from '../utils/indyError' | ||
|
||
|
@@ -33,6 +34,14 @@ export class IndyWallet implements Wallet { | |
public constructor(agentConfig: AgentConfig) { | ||
this.logger = agentConfig.logger | ||
this.indy = agentConfig.agentDependencies.indy | ||
|
||
const { walletConfig, fileSystem } = agentConfig | ||
|
||
if (walletConfig?.storageType === WalletStorageType.Postgres) { | ||
if (walletConfig.storageConfig && walletConfig.storageCreds && fileSystem?.loadPostgresPlugin) { | ||
fileSystem?.loadPostgresPlugin(walletConfig.storageConfig, walletConfig.storageCreds) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the wallet storage is set to postgres, I think we should throw an error if the required parameters are not set. I think we should also throw an error if |
||
} | ||
} | ||
|
||
public get isProvisioned() { | ||
|
@@ -67,6 +76,41 @@ export class IndyWallet implements Wallet { | |
return this.walletConfig.id | ||
} | ||
|
||
private walletStorageConfig(walletConfig: WalletConfig): Indy.WalletConfig { | ||
const walletStorageConfig: Indy.WalletConfig = { | ||
id: walletConfig.id, | ||
storage_type: walletConfig.storageType, | ||
} | ||
|
||
if (walletConfig.storageConfig) { | ||
walletStorageConfig.storage_config = walletConfig.storageConfig | ||
} | ||
|
||
return walletStorageConfig | ||
} | ||
|
||
private walletCredentials( | ||
walletConfig: WalletConfig, | ||
rekey?: string, | ||
rekeyDerivation?: KeyDerivationMethod | ||
): Indy.OpenWalletCredentials { | ||
const walletCredentials: Indy.OpenWalletCredentials = { | ||
key: walletConfig.key, | ||
key_derivation_method: walletConfig.keyDerivationMethod, | ||
} | ||
if (rekey) { | ||
walletCredentials.rekey = rekey | ||
} | ||
if (rekeyDerivation) { | ||
walletCredentials.rekey_derivation_method = rekeyDerivation | ||
} | ||
if (walletConfig.storageCreds) { | ||
walletCredentials.storage_credentials = walletConfig.storageCreds | ||
} | ||
|
||
return walletCredentials | ||
} | ||
|
||
/** | ||
* @throws {WalletDuplicateError} if the wallet already exists | ||
* @throws {WalletError} if another error occurs | ||
|
@@ -84,11 +128,7 @@ export class IndyWallet implements Wallet { | |
this.logger.debug(`Creating wallet '${walletConfig.id}' using SQLite storage`) | ||
|
||
try { | ||
await this.indy.createWallet( | ||
{ id: walletConfig.id }, | ||
{ key: walletConfig.key, key_derivation_method: walletConfig.keyDerivationMethod } | ||
) | ||
|
||
await this.indy.createWallet(this.walletStorageConfig(walletConfig), this.walletCredentials(walletConfig)) | ||
this.walletConfig = walletConfig | ||
|
||
// We usually want to create master secret only once, therefore, we can to do so when creating a wallet. | ||
|
@@ -139,7 +179,11 @@ export class IndyWallet implements Wallet { | |
throw new WalletError('Wallet rekey undefined!. Please specify the new wallet key') | ||
} | ||
await this._open( | ||
{ id: walletConfig.id, key: walletConfig.key, keyDerivationMethod: walletConfig.keyDerivationMethod }, | ||
{ | ||
id: walletConfig.id, | ||
key: walletConfig.key, | ||
keyDerivationMethod: walletConfig.keyDerivationMethod, | ||
}, | ||
walletConfig.rekey, | ||
walletConfig.rekeyDerivationMethod | ||
) | ||
|
@@ -162,13 +206,8 @@ export class IndyWallet implements Wallet { | |
|
||
try { | ||
this.walletHandle = await this.indy.openWallet( | ||
{ id: walletConfig.id }, | ||
{ | ||
key: walletConfig.key, | ||
rekey: rekey, | ||
key_derivation_method: walletConfig.keyDerivationMethod, | ||
rekey_derivation_method: rekeyDerivation, | ||
} | ||
this.walletStorageConfig(walletConfig), | ||
this.walletCredentials(walletConfig, rekey, rekeyDerivation) | ||
) | ||
if (rekey) { | ||
this.walletConfig = { ...walletConfig, key: rekey, keyDerivationMethod: rekeyDerivation } | ||
|
@@ -224,8 +263,8 @@ export class IndyWallet implements Wallet { | |
|
||
try { | ||
await this.indy.deleteWallet( | ||
{ id: this.walletConfig.id }, | ||
{ key: this.walletConfig.key, key_derivation_method: this.walletConfig.keyDerivationMethod } | ||
this.walletStorageConfig(this.walletConfig), | ||
this.walletCredentials(this.walletConfig) | ||
) | ||
} catch (error) { | ||
if (isIndyError(error, 'WalletNotFoundError')) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,14 +7,20 @@ import { SubjectInboundTransport } from '../../../tests/transport/SubjectInbound | |
import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' | ||
import { Agent } from '../src/agent/Agent' | ||
|
||
import { waitForBasicMessage, getBaseConfig } from './helpers' | ||
import { waitForBasicMessage, getBaseConfig, getBasePostgresConfig } from './helpers' | ||
|
||
const aliceConfig = getBaseConfig('Agents Alice', { | ||
endpoints: ['rxjs:alice'], | ||
}) | ||
const bobConfig = getBaseConfig('Agents Bob', { | ||
endpoints: ['rxjs:bob'], | ||
}) | ||
const alicePostgresConfig = getBasePostgresConfig('AgentsAlice', { | ||
endpoints: ['rxjs:alice'], | ||
}) | ||
const bobPostgresConfig = getBasePostgresConfig('AgentsBob', { | ||
endpoints: ['rxjs:bob'], | ||
}) | ||
|
||
describe('agents', () => { | ||
let aliceAgent: Agent | ||
|
@@ -77,3 +83,65 @@ describe('agents', () => { | |
expect(aliceAgent.isInitialized).toBe(true) | ||
}) | ||
}) | ||
|
||
describe('postgres agents', () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we can extract this to a separate postgres.test.ts? |
||
let aliceAgent: Agent | ||
let bobAgent: Agent | ||
let aliceConnection: ConnectionRecord | ||
let bobConnection: ConnectionRecord | ||
|
||
afterAll(async () => { | ||
await bobAgent.shutdown() | ||
await bobAgent.wallet.delete() | ||
await aliceAgent.shutdown() | ||
await aliceAgent.wallet.delete() | ||
}) | ||
|
||
test('make a connection between agents', async () => { | ||
const aliceMessages = new Subject<SubjectMessage>() | ||
const bobMessages = new Subject<SubjectMessage>() | ||
|
||
const subjectMap = { | ||
'rxjs:alice': aliceMessages, | ||
'rxjs:bob': bobMessages, | ||
} | ||
|
||
aliceAgent = new Agent(alicePostgresConfig.config, alicePostgresConfig.agentDependencies) | ||
aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) | ||
aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) | ||
await aliceAgent.initialize() | ||
|
||
bobAgent = new Agent(bobPostgresConfig.config, bobPostgresConfig.agentDependencies) | ||
bobAgent.registerInboundTransport(new SubjectInboundTransport(bobMessages)) | ||
bobAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) | ||
await bobAgent.initialize() | ||
|
||
const aliceConnectionAtAliceBob = await aliceAgent.connections.createConnection() | ||
const bobConnectionAtBobAlice = await bobAgent.connections.receiveInvitation(aliceConnectionAtAliceBob.invitation) | ||
|
||
aliceConnection = await aliceAgent.connections.returnWhenIsConnected(aliceConnectionAtAliceBob.connectionRecord.id) | ||
bobConnection = await bobAgent.connections.returnWhenIsConnected(bobConnectionAtBobAlice.id) | ||
|
||
expect(aliceConnection).toBeConnectedWith(bobConnection) | ||
expect(bobConnection).toBeConnectedWith(aliceConnection) | ||
}) | ||
|
||
test('send a message to connection', async () => { | ||
const message = 'hello, world' | ||
await aliceAgent.basicMessages.sendMessage(aliceConnection.id, message) | ||
|
||
const basicMessage = await waitForBasicMessage(bobAgent, { | ||
content: message, | ||
}) | ||
|
||
expect(basicMessage.content).toBe(message) | ||
}) | ||
|
||
test('can shutdown and re-initialize the same agent', async () => { | ||
expect(aliceAgent.isInitialized).toBe(true) | ||
await aliceAgent.shutdown() | ||
expect(aliceAgent.isInitialized).toBe(false) | ||
await aliceAgent.initialize() | ||
expect(aliceAgent.isInitialized).toBe(true) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -30,12 +30,16 @@ | |||
"dependencies": { | ||||
"@aries-framework/core": "0.1.0", | ||||
"express": "^4.17.1", | ||||
"ffi-napi": "^4.0.3", | ||||
"indy-sdk": "^1.16.0-dev-1636", | ||||
"node-fetch": "^2.6.1", | ||||
"ws": "^7.5.3" | ||||
"ref-napi": "^3.0.3", | ||||
"ws": "^7.5.3", | ||||
"os": "^0.1.2" | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a node core module, we don't need to add it to the dependencies
Suggested change
|
||||
}, | ||||
"devDependencies": { | ||||
"@types/express": "^4.17.13", | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing |
||||
"@types/ffi-napi": "^4.0.5", | ||||
"@types/node": "^15.14.4", | ||||
"@types/node-fetch": "^2.5.10", | ||||
"@types/ws": "^7.4.6", | ||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The postgres plugin is related to the environment and the wallet type (in this case indy-sdk). I'm not sure the file system is the best place to add this, mainly because the filesystem stays the same if we support something different than indy-sdk.
Maybe we can keep it a bit more generic. Maybe later we can integrate more, but for now maybe can approach it like this: