@canton-network/wallet-gateway-remote 0.17.3 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/dist/config/Config.d.ts +19 -15
- package/dist/config/Config.d.ts.map +1 -1
- package/dist/config/Config.js +7 -3
- package/dist/config/Config.test.js +8 -8
- package/dist/config/ConfigUtils.js +6 -6
- package/dist/dapp-api/controller.d.ts +7 -6
- package/dist/dapp-api/controller.d.ts.map +1 -1
- package/dist/dapp-api/controller.js +75 -50
- package/dist/dapp-api/rpc-gen/index.d.ts +21 -18
- package/dist/dapp-api/rpc-gen/index.d.ts.map +1 -1
- package/dist/dapp-api/rpc-gen/index.js +7 -6
- package/dist/dapp-api/rpc-gen/typings.d.ts +69 -101
- package/dist/dapp-api/rpc-gen/typings.d.ts.map +1 -1
- package/dist/dapp-api/server.d.ts.map +1 -1
- package/dist/dapp-api/server.js +52 -45
- package/dist/dapp-api/server.test.js +1 -5
- package/dist/example-config.d.ts +3 -0
- package/dist/example-config.d.ts.map +1 -1
- package/dist/example-config.js +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +52 -5
- package/dist/ledger/wallet-sync-service.d.ts.map +1 -1
- package/dist/ledger/wallet-sync-service.js +30 -20
- package/dist/ledger/wallet-sync-service.test.js +339 -0
- package/dist/middleware/jwtAuth.d.ts.map +1 -1
- package/dist/middleware/jwtAuth.js +3 -1
- package/dist/user-api/controller.d.ts +3 -1
- package/dist/user-api/controller.d.ts.map +1 -1
- package/dist/user-api/controller.js +128 -47
- package/dist/user-api/rpc-gen/index.d.ts +6 -0
- package/dist/user-api/rpc-gen/index.d.ts.map +1 -1
- package/dist/user-api/rpc-gen/index.js +2 -0
- package/dist/user-api/rpc-gen/typings.d.ts +37 -24
- package/dist/user-api/rpc-gen/typings.d.ts.map +1 -1
- package/dist/user-api/server.d.ts +1 -1
- package/dist/user-api/server.d.ts.map +1 -1
- package/dist/user-api/server.js +2 -2
- package/dist/user-api/server.test.js +1 -1
- package/dist/utils.d.ts +0 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/web/frontend/404/index.html +2 -3
- package/dist/web/frontend/approve/index.html +4 -5
- package/dist/web/frontend/assets/404-BQsvObfu.js +8 -0
- package/dist/web/frontend/assets/approve-BLORPbEM.js +17 -0
- package/dist/web/frontend/assets/{callback-B-wmBVDs.js → callback-C7kRcm3U.js} +1 -1
- package/dist/web/frontend/assets/index-DPbaEEZi.js +1722 -0
- package/dist/web/frontend/assets/{decode-CLJkuAIr.js → index-NP2zGQqX.js} +1 -1
- package/dist/web/frontend/assets/login-p5H_f9In.js +7 -0
- package/dist/web/frontend/assets/settings-89wZp5vZ.js +37 -0
- package/dist/web/frontend/assets/{state-CziDYJOu.js → state-TjGUBiUh.js} +1 -1
- package/dist/web/frontend/assets/transactions-BnROcA2_.js +28 -0
- package/dist/web/frontend/assets/wallets-TPeR2lS2.js +63 -0
- package/dist/web/frontend/callback/index.html +2 -3
- package/dist/web/frontend/index.html +1 -2
- package/dist/web/frontend/login/index.html +3 -4
- package/dist/web/frontend/settings/index.html +3 -4
- package/dist/web/frontend/transactions/index.html +4 -5
- package/dist/web/frontend/wallets/index.html +3 -4
- package/package.json +26 -21
- package/dist/web/frontend/assets/404-C5sXhYIJ.js +0 -16
- package/dist/web/frontend/assets/approve-DLsPdBvo.js +0 -227
- package/dist/web/frontend/assets/index-CxDOwxiY.css +0 -1
- package/dist/web/frontend/assets/index-Dfw8gEND.js +0 -1158
- package/dist/web/frontend/assets/login-BiJNU2kh.js +0 -186
- package/dist/web/frontend/assets/settings-B8ayu_0I.js +0 -28
- package/dist/web/frontend/assets/transactions-D2Yx5LYD.js +0 -140
- package/dist/web/frontend/assets/wallets-nCed0fPk.js +0 -266
package/dist/init.js
CHANGED
|
@@ -21,6 +21,7 @@ import path from 'path';
|
|
|
21
21
|
import { GATEWAY_VERSION } from './version.js';
|
|
22
22
|
import { sessionHandler } from './middleware/sessionHandler.js';
|
|
23
23
|
import { NotificationService } from './notification/NotificationService.js';
|
|
24
|
+
import { sql } from 'kysely';
|
|
24
25
|
let isReady = false;
|
|
25
26
|
async function initializeDatabase(config, logger) {
|
|
26
27
|
logger.info('Checking for database migrations...');
|
|
@@ -28,6 +29,25 @@ async function initializeDatabase(config, logger) {
|
|
|
28
29
|
if (config.store.connection.type === 'sqlite') {
|
|
29
30
|
exists = existsSync(config.store.connection.database);
|
|
30
31
|
}
|
|
32
|
+
if (config.store.connection.type === 'postgres') {
|
|
33
|
+
const db = connection({
|
|
34
|
+
...config.store,
|
|
35
|
+
connection: { ...config.store.connection, database: 'postgres' },
|
|
36
|
+
});
|
|
37
|
+
const result = await sql
|
|
38
|
+
.raw(`select 1 from pg_database where datname='${config.store.connection.database}';`)
|
|
39
|
+
.execute(db);
|
|
40
|
+
const databaseExist = result.rows.length > 0;
|
|
41
|
+
if (!databaseExist) {
|
|
42
|
+
// Ignore error because postgres does not support `create database if nor exists` clause
|
|
43
|
+
await sql
|
|
44
|
+
.raw(`create database ${config.store.connection.database};`)
|
|
45
|
+
.execute(db)
|
|
46
|
+
.catch(() => { });
|
|
47
|
+
exists = false;
|
|
48
|
+
}
|
|
49
|
+
await db.destroy();
|
|
50
|
+
}
|
|
31
51
|
const db = connection(config.store);
|
|
32
52
|
const umzug = migrator(db);
|
|
33
53
|
const pending = await umzug.pending();
|
|
@@ -42,7 +62,7 @@ async function initializeDatabase(config, logger) {
|
|
|
42
62
|
// bootstrap database from config file if it did not exist before
|
|
43
63
|
if (!exists) {
|
|
44
64
|
logger.info('Bootstrapping database from config...');
|
|
45
|
-
await bootstrap(db, config.
|
|
65
|
+
await bootstrap(db, config.bootstrap, logger);
|
|
46
66
|
}
|
|
47
67
|
return new StoreSql(db, logger);
|
|
48
68
|
}
|
|
@@ -52,6 +72,28 @@ async function initializeSigningDatabase(config, logger) {
|
|
|
52
72
|
if (config.signingStore.connection.type === 'sqlite') {
|
|
53
73
|
exists = existsSync(config.signingStore.connection.database);
|
|
54
74
|
}
|
|
75
|
+
if (config.signingStore.connection.type === 'postgres') {
|
|
76
|
+
const db = signingConnection({
|
|
77
|
+
...config.signingStore,
|
|
78
|
+
connection: {
|
|
79
|
+
...config.signingStore.connection,
|
|
80
|
+
database: 'postgres',
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
const result = await sql
|
|
84
|
+
.raw(`select 1 from pg_database where datname='${config.signingStore.connection.database}';`)
|
|
85
|
+
.execute(db);
|
|
86
|
+
const databaseExist = result.rows.length > 0;
|
|
87
|
+
if (!databaseExist) {
|
|
88
|
+
// Ignore error because postgres does not support `create database if nor exists` clause
|
|
89
|
+
await sql
|
|
90
|
+
.raw(`create database ${config.signingStore.connection.database};`)
|
|
91
|
+
.execute(db)
|
|
92
|
+
.catch(() => { });
|
|
93
|
+
exists = false;
|
|
94
|
+
}
|
|
95
|
+
await db.destroy();
|
|
96
|
+
}
|
|
55
97
|
const db = signingConnection(config.signingStore);
|
|
56
98
|
const umzug = signingMigrator(db);
|
|
57
99
|
const pending = await umzug.pending();
|
|
@@ -65,8 +107,8 @@ async function initializeSigningDatabase(config, logger) {
|
|
|
65
107
|
}
|
|
66
108
|
// bootstrap database from config file if it did not exist before
|
|
67
109
|
if (!exists) {
|
|
68
|
-
logger.info('Bootstrapping database from config...');
|
|
69
|
-
await signingBootstrap(db, config.
|
|
110
|
+
logger.info('Bootstrapping signing database from config...');
|
|
111
|
+
await signingBootstrap(db, config.signingStore, logger);
|
|
70
112
|
}
|
|
71
113
|
return new SigningStoreSql(db, logger);
|
|
72
114
|
}
|
|
@@ -128,7 +170,12 @@ export async function initialize(opts, logger) {
|
|
|
128
170
|
};
|
|
129
171
|
const allowedPaths = {
|
|
130
172
|
[config.server.dappPath]: ['*'],
|
|
131
|
-
[config.server.userPath]: [
|
|
173
|
+
[config.server.userPath]: [
|
|
174
|
+
'addSession',
|
|
175
|
+
'listNetworks',
|
|
176
|
+
'listIdps',
|
|
177
|
+
'getUser',
|
|
178
|
+
],
|
|
132
179
|
};
|
|
133
180
|
app.use('/api/*splat', express.json());
|
|
134
181
|
app.use('/api/*splat', rpcRateLimit);
|
|
@@ -138,7 +185,7 @@ export async function initialize(opts, logger) {
|
|
|
138
185
|
// register dapp API handlers
|
|
139
186
|
dapp(config.server.dappPath, app, logger, server, kernelInfo, dappApiUrl, publicUrl, config.server, notificationService, authService, store);
|
|
140
187
|
// register user API handlers
|
|
141
|
-
user(config.server.userPath, app, logger, kernelInfo, publicUrl, notificationService, drivers, store);
|
|
188
|
+
user(config.server.userPath, app, logger, kernelInfo, publicUrl, notificationService, drivers, store, config.server.admin);
|
|
142
189
|
// register web handler
|
|
143
190
|
web(app, server, userApiUrl);
|
|
144
191
|
isReady = true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wallet-sync-service.d.ts","sourceRoot":"","sources":["../../src/ledger/wallet-sync-service.ts"],"names":[],"mappings":"AAGA,OAAO,EACH,YAAY,EAEf,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAC9D,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAA;AACjE,OAAO,EACH,sBAAsB,EACtB,eAAe,EAClB,MAAM,kCAAkC,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA;AAEtE,MAAM,MAAM,gBAAgB,GAAG;IAC3B,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,EAAE,MAAM,EAAE,CAAA;CACpB,CAAA;AAED,eAAO,MAAM,sBAAsB;;CAElC,CAAA;AAED,qBAAa,iBAAiB;IAEtB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,iBAAiB;IACzB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,cAAc;IAGtB,OAAO,CAAC,cAAc;gBARd,KAAK,EAAE,KAAK,EACZ,YAAY,EAAE,YAAY,EAC1B,iBAAiB,EAAE,YAAY,EAC/B,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,OAAO,CAC3B,MAAM,CAAC,eAAe,EAAE,sBAAsB,CAAC,CAClD,YAAK,EACE,cAAc,EAAE,sBAAsB;IAG5C,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;cAU3B,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAC5D;QACI,iBAAiB,EAAE,eAAe,CAAC,WAAW,CAAA;QAC9C,OAAO,EAAE,OAAO,CAAA;KACnB,GACD;QACI,iBAAiB,EAAE,OAAO,CACtB,eAAe,EACf,eAAe,CAAC,WAAW,CAC9B,CAAA;QACD,SAAS,EAAE,MAAM,CAAA;QACjB,OAAO,EAAE,OAAO,CAAA;KACnB,CACN;YA6Ha,mBAAmB;IAqC3B,kBAAkB,IAAI,OAAO,CAAC,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"wallet-sync-service.d.ts","sourceRoot":"","sources":["../../src/ledger/wallet-sync-service.ts"],"names":[],"mappings":"AAGA,OAAO,EACH,YAAY,EAEf,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAC9D,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAA;AACjE,OAAO,EACH,sBAAsB,EACtB,eAAe,EAClB,MAAM,kCAAkC,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA;AAEtE,MAAM,MAAM,gBAAgB,GAAG;IAC3B,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,EAAE,MAAM,EAAE,CAAA;CACpB,CAAA;AAED,eAAO,MAAM,sBAAsB;;CAElC,CAAA;AAED,qBAAa,iBAAiB;IAEtB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,iBAAiB;IACzB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,cAAc;IAGtB,OAAO,CAAC,cAAc;gBARd,KAAK,EAAE,KAAK,EACZ,YAAY,EAAE,YAAY,EAC1B,iBAAiB,EAAE,YAAY,EAC/B,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,OAAO,CAC3B,MAAM,CAAC,eAAe,EAAE,sBAAsB,CAAC,CAClD,YAAK,EACE,cAAc,EAAE,sBAAsB;IAG5C,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;cAU3B,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAC5D;QACI,iBAAiB,EAAE,eAAe,CAAC,WAAW,CAAA;QAC9C,OAAO,EAAE,OAAO,CAAA;KACnB,GACD;QACI,iBAAiB,EAAE,OAAO,CACtB,eAAe,EACf,eAAe,CAAC,WAAW,CAC9B,CAAA;QACD,SAAS,EAAE,MAAM,CAAA;QACjB,OAAO,EAAE,OAAO,CAAA;KACnB,CACN;YA6Ha,mBAAmB;IAqC3B,kBAAkB,IAAI,OAAO,CAAC,OAAO,CAAC;IA+BtC,WAAW,IAAI,OAAO,CAAC,gBAAgB,CAAC;CAsJjD"}
|
|
@@ -144,21 +144,19 @@ export class WalletSyncService {
|
|
|
144
144
|
}
|
|
145
145
|
async isWalletSyncNeeded() {
|
|
146
146
|
try {
|
|
147
|
+
const network = await this.store.getCurrentNetwork();
|
|
147
148
|
const existingWallets = await this.store.getWallets();
|
|
148
|
-
// Check if there are any disabled wallets
|
|
149
149
|
const hasDisabledWallets = existingWallets.some((w) => w.disabled);
|
|
150
150
|
if (hasDisabledWallets) {
|
|
151
151
|
return true;
|
|
152
152
|
}
|
|
153
|
-
// Check if there are parties on ledger that aren't in store
|
|
154
153
|
const partiesWithRights = await this.getPartiesRightsMap();
|
|
155
154
|
// Treat disabled wallets as if they don't exist, so they can be re-synced
|
|
156
155
|
const enabledWallets = existingWallets.filter((w) => !w.disabled);
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
.some((party) => !existingPartyIds.has(party));
|
|
156
|
+
// Track by (partyId, networkId) combination to handle multi-hosted parties
|
|
157
|
+
const existingPartyNetworkPairs = new Set(enabledWallets.map((w) => `${w.partyId}:${w.networkId}`));
|
|
158
|
+
// Check if there are parties on ledger that aren't in store for this network
|
|
159
|
+
return Array.from(partiesWithRights.keys()).some((party) => !existingPartyNetworkPairs.has(`${party}:${network.id}`));
|
|
162
160
|
}
|
|
163
161
|
catch (err) {
|
|
164
162
|
this.logger.error({ err }, 'Error checking if sync is needed');
|
|
@@ -171,17 +169,25 @@ export class WalletSyncService {
|
|
|
171
169
|
try {
|
|
172
170
|
const network = await this.store.getCurrentNetwork();
|
|
173
171
|
this.logger.info(network, 'Current network');
|
|
174
|
-
// Get existing parties from participant
|
|
175
172
|
const partiesWithRights = await this.getPartiesRightsMap();
|
|
176
173
|
// Add new Wallets given the found parties
|
|
174
|
+
// Only check wallets in the current network
|
|
177
175
|
const existingWallets = await this.store.getWallets();
|
|
178
176
|
this.logger.info(existingWallets, 'Existing wallets');
|
|
179
177
|
// Treat disabled wallets as if they don't exist, so they can be re-synced
|
|
180
178
|
const enabledWallets = existingWallets.filter((w) => !w.disabled);
|
|
181
|
-
|
|
182
|
-
const
|
|
179
|
+
// Track by (partyId, networkId) combination
|
|
180
|
+
const existingPartyNetworkToSigningProvider = new Map(enabledWallets.map((w) => [
|
|
181
|
+
`${w.partyId}:${w.networkId}`,
|
|
182
|
+
w.signingProviderId,
|
|
183
|
+
]));
|
|
184
|
+
// Track disabled wallets by (partyId, networkId) combination
|
|
185
|
+
const disabledPartyNetworkPairs = new Set(existingWallets
|
|
186
|
+
.filter((w) => w.disabled)
|
|
187
|
+
.map((w) => `${w.partyId}:${w.networkId}`));
|
|
183
188
|
// Resolve signing providers for all new parties
|
|
184
|
-
|
|
189
|
+
// Check if (partyId, networkId) combination already exists
|
|
190
|
+
const newParties = Array.from(partiesWithRights.keys()).filter((party) => !existingPartyNetworkToSigningProvider.has(`${party}:${network.id}`)
|
|
185
191
|
// todo: filter on idp id
|
|
186
192
|
);
|
|
187
193
|
this.logger.info({ newParties }, 'Found new parties to sync with Wallet Gateway');
|
|
@@ -217,10 +223,14 @@ export class WalletSyncService {
|
|
|
217
223
|
return wallet;
|
|
218
224
|
}));
|
|
219
225
|
// Remove disabled wallets that are being re-synced before adding them back
|
|
226
|
+
// Filter by (partyId, networkId) combination
|
|
220
227
|
await Promise.all(newParticipantWallets
|
|
221
|
-
.filter((wallet) =>
|
|
228
|
+
.filter((wallet) => disabledPartyNetworkPairs.has(`${wallet.partyId}:${wallet.networkId}`))
|
|
222
229
|
.map((wallet) => {
|
|
223
|
-
this.logger.info({
|
|
230
|
+
this.logger.info({
|
|
231
|
+
partyId: wallet.partyId,
|
|
232
|
+
networkId: wallet.networkId,
|
|
233
|
+
}, 'Removing disabled wallet for re-sync');
|
|
224
234
|
return this.store.removeWallet(wallet.partyId);
|
|
225
235
|
}));
|
|
226
236
|
await Promise.all(newParticipantWallets.map((wallet) => this.store.addWallet(wallet)));
|
|
@@ -231,14 +241,14 @@ export class WalletSyncService {
|
|
|
231
241
|
disabled: newParticipantWallets.filter((w) => w.disabled)
|
|
232
242
|
.length,
|
|
233
243
|
}, 'Wallet sync summary');
|
|
234
|
-
// Set primary wallet if none exists
|
|
235
|
-
const
|
|
236
|
-
const hasPrimary =
|
|
237
|
-
if (!hasPrimary &&
|
|
238
|
-
this.store.setPrimaryWallet(
|
|
239
|
-
this.logger.info(`Set ${
|
|
244
|
+
// Set primary wallet if none exists in current network
|
|
245
|
+
const networkWallets = await this.store.getWallets();
|
|
246
|
+
const hasPrimary = networkWallets.some((w) => w.primary);
|
|
247
|
+
if (!hasPrimary && networkWallets.length > 0) {
|
|
248
|
+
this.store.setPrimaryWallet(networkWallets[0].partyId);
|
|
249
|
+
this.logger.info(`Set ${networkWallets[0].partyId} as primary wallet in network ${network.id}`);
|
|
240
250
|
}
|
|
241
|
-
this.logger.debug(wallets, 'Wallet sync completed.');
|
|
251
|
+
this.logger.debug({ wallets: newParticipantWallets }, 'Wallet sync completed.');
|
|
242
252
|
return {
|
|
243
253
|
added: newParticipantWallets,
|
|
244
254
|
removed: [],
|
|
@@ -7,6 +7,7 @@ import { SigningProvider, } from '@canton-network/core-signing-lib';
|
|
|
7
7
|
import { InternalSigningDriver } from '@canton-network/core-signing-internal';
|
|
8
8
|
import { ParticipantSigningDriver } from '@canton-network/core-signing-participant';
|
|
9
9
|
import { StoreSql, connection, migrator, } from '@canton-network/core-signing-store-sql';
|
|
10
|
+
import { StoreInternal } from '@canton-network/core-wallet-store-inmemory';
|
|
10
11
|
import { WalletSyncService } from './wallet-sync-service.js';
|
|
11
12
|
import { PartyAllocationService } from './party-allocation-service.js';
|
|
12
13
|
const mockLedgerGet = jest.fn();
|
|
@@ -103,6 +104,9 @@ describe('WalletSyncService - resolveSigningProvider', () => {
|
|
|
103
104
|
const internalDriver = service['signingDrivers'][SigningProvider.WALLET_KERNEL];
|
|
104
105
|
const controller = internalDriver.controller(authContext.userId);
|
|
105
106
|
const key = await controller.createKey({ name: 'test-key' });
|
|
107
|
+
if ('error' in key) {
|
|
108
|
+
throw new Error(`Failed to create key in test: ${key.error_description}`);
|
|
109
|
+
}
|
|
106
110
|
const namespace = partyAllocator.createFingerprintFromKey(key.publicKey);
|
|
107
111
|
mockLedgerGet.mockResolvedValueOnce({
|
|
108
112
|
participantId: 'participant1::different-participant-namespace',
|
|
@@ -165,3 +169,338 @@ describe('WalletSyncService - resolveSigningProvider', () => {
|
|
|
165
169
|
});
|
|
166
170
|
});
|
|
167
171
|
});
|
|
172
|
+
describe('WalletSyncService - multi-network features', () => {
|
|
173
|
+
const authContext = {
|
|
174
|
+
userId: 'test-user-id',
|
|
175
|
+
accessToken: 'test-access-token',
|
|
176
|
+
};
|
|
177
|
+
let mockLogger;
|
|
178
|
+
let store;
|
|
179
|
+
let mockLedgerClient;
|
|
180
|
+
let mockAdminLedgerClient;
|
|
181
|
+
let partyAllocator;
|
|
182
|
+
const createNetwork = (id) => ({
|
|
183
|
+
id,
|
|
184
|
+
name: `Network ${id}`,
|
|
185
|
+
synchronizerId: `${id}-sync`,
|
|
186
|
+
identityProviderId: 'idp1',
|
|
187
|
+
description: `Test Network ${id}`,
|
|
188
|
+
ledgerApi: { baseUrl: `http://${id}` },
|
|
189
|
+
auth: {
|
|
190
|
+
method: 'authorization_code',
|
|
191
|
+
clientId: 'cid',
|
|
192
|
+
scope: 'scope',
|
|
193
|
+
audience: 'aud',
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
const createWallet = (partyId, networkId, disabled = false) => ({
|
|
197
|
+
primary: false,
|
|
198
|
+
partyId,
|
|
199
|
+
status: 'allocated',
|
|
200
|
+
hint: partyId.split('::')[0],
|
|
201
|
+
signingProviderId: 'internal',
|
|
202
|
+
publicKey: 'publicKey',
|
|
203
|
+
namespace: 'namespace',
|
|
204
|
+
networkId,
|
|
205
|
+
disabled,
|
|
206
|
+
});
|
|
207
|
+
const setSession = async (networkId) => {
|
|
208
|
+
await store.setSession({
|
|
209
|
+
id: `sess-${networkId}`,
|
|
210
|
+
network: networkId,
|
|
211
|
+
accessToken: 'token',
|
|
212
|
+
});
|
|
213
|
+
};
|
|
214
|
+
beforeEach(async () => {
|
|
215
|
+
mockLogger = pino(sink());
|
|
216
|
+
store = new StoreInternal({
|
|
217
|
+
idps: [],
|
|
218
|
+
networks: [],
|
|
219
|
+
}, mockLogger, authContext);
|
|
220
|
+
// Add a default IdP that tests can use (use updateIdp to avoid errors if it already exists)
|
|
221
|
+
try {
|
|
222
|
+
await store.addIdp({
|
|
223
|
+
id: 'idp1',
|
|
224
|
+
type: 'oauth',
|
|
225
|
+
issuer: 'http://auth',
|
|
226
|
+
configUrl: 'http://auth/.well-known/openid-configuration',
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// IdP might already exist from previous test, use updateIdp instead
|
|
231
|
+
await store.updateIdp({
|
|
232
|
+
id: 'idp1',
|
|
233
|
+
type: 'oauth',
|
|
234
|
+
issuer: 'http://auth',
|
|
235
|
+
configUrl: 'http://auth/.well-known/openid-configuration',
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
partyAllocator = new PartyAllocationService({
|
|
239
|
+
synchronizerId: 'test-sync-id',
|
|
240
|
+
accessTokenProvider: {
|
|
241
|
+
getUserAccessToken: async () => 'user.jwt',
|
|
242
|
+
getAdminAccessToken: async () => 'admin.jwt',
|
|
243
|
+
},
|
|
244
|
+
httpLedgerUrl: 'http://test',
|
|
245
|
+
logger: mockLogger,
|
|
246
|
+
});
|
|
247
|
+
const ledgerModule = await import('@canton-network/core-ledger-client');
|
|
248
|
+
mockLedgerClient = new ledgerModule.LedgerClient({
|
|
249
|
+
baseUrl: new URL('http://test'),
|
|
250
|
+
logger: mockLogger,
|
|
251
|
+
accessTokenProvider: {
|
|
252
|
+
getUserAccessToken: async () => 'token',
|
|
253
|
+
getAdminAccessToken: async () => 'token',
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
mockAdminLedgerClient = new ledgerModule.LedgerClient({
|
|
257
|
+
baseUrl: new URL('http://test'),
|
|
258
|
+
logger: mockLogger,
|
|
259
|
+
isAdmin: true,
|
|
260
|
+
accessTokenProvider: {
|
|
261
|
+
getUserAccessToken: async () => 'token',
|
|
262
|
+
getAdminAccessToken: async () => 'token',
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
afterEach(() => {
|
|
267
|
+
jest.restoreAllMocks();
|
|
268
|
+
mockLedgerGet.mockClear();
|
|
269
|
+
});
|
|
270
|
+
it('isWalletSyncNeeded should filter by current network', async () => {
|
|
271
|
+
const network1 = createNetwork('network1');
|
|
272
|
+
await store.addNetwork(network1);
|
|
273
|
+
await setSession('network1');
|
|
274
|
+
await store.addWallet(createWallet('party1::namespace', 'network1'));
|
|
275
|
+
await store.addWallet(createWallet('party2::namespace', 'network2'));
|
|
276
|
+
mockLedgerGet.mockResolvedValueOnce({
|
|
277
|
+
rights: [
|
|
278
|
+
{
|
|
279
|
+
kind: {
|
|
280
|
+
CanActAs: {
|
|
281
|
+
value: {
|
|
282
|
+
party: 'party1::namespace',
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
});
|
|
289
|
+
const service = new WalletSyncService(store, mockLedgerClient, mockAdminLedgerClient, authContext, mockLogger, {}, partyAllocator);
|
|
290
|
+
const syncNeeded = await service.isWalletSyncNeeded();
|
|
291
|
+
// Should return false because party1 already exists in network1
|
|
292
|
+
expect(syncNeeded).toBe(false);
|
|
293
|
+
});
|
|
294
|
+
it('isWalletSyncNeeded should detect new parties for current network only', async () => {
|
|
295
|
+
const network1 = createNetwork('network1');
|
|
296
|
+
await store.addNetwork(network1);
|
|
297
|
+
await setSession('network1');
|
|
298
|
+
mockLedgerGet.mockResolvedValueOnce({
|
|
299
|
+
rights: [
|
|
300
|
+
{
|
|
301
|
+
kind: {
|
|
302
|
+
CanActAs: {
|
|
303
|
+
value: {
|
|
304
|
+
party: 'party1::namespace',
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
});
|
|
311
|
+
const service = new WalletSyncService(store, mockLedgerClient, mockAdminLedgerClient, authContext, mockLogger, {}, partyAllocator);
|
|
312
|
+
const syncNeeded = await service.isWalletSyncNeeded();
|
|
313
|
+
// Should return true because party1 exists on ledger but not in store for network1
|
|
314
|
+
expect(syncNeeded).toBe(true);
|
|
315
|
+
});
|
|
316
|
+
it('syncWallets should only sync wallets for current network', async () => {
|
|
317
|
+
const network1 = createNetwork('network1');
|
|
318
|
+
await store.addNetwork(network1);
|
|
319
|
+
await setSession('network1');
|
|
320
|
+
await store.addWallet(createWallet('party1::namespace', 'network1'));
|
|
321
|
+
const addWalletSpy = jest.spyOn(store, 'addWallet');
|
|
322
|
+
mockLedgerGet
|
|
323
|
+
.mockResolvedValueOnce({
|
|
324
|
+
rights: [
|
|
325
|
+
{
|
|
326
|
+
kind: {
|
|
327
|
+
CanActAs: {
|
|
328
|
+
value: {
|
|
329
|
+
party: 'party1::namespace',
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
kind: {
|
|
336
|
+
CanActAs: {
|
|
337
|
+
value: {
|
|
338
|
+
party: 'party3::namespace',
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
})
|
|
345
|
+
.mockResolvedValueOnce({
|
|
346
|
+
participantId: 'participant1::namespace',
|
|
347
|
+
});
|
|
348
|
+
const service = new WalletSyncService(store, mockLedgerClient, mockAdminLedgerClient, authContext, mockLogger, {}, partyAllocator);
|
|
349
|
+
await service.syncWallets();
|
|
350
|
+
// Should only add wallet for party3 (party1 already exists)
|
|
351
|
+
const wallets = await store.getAllWallets({ networkIds: ['network1'] });
|
|
352
|
+
expect(wallets.some((w) => w.partyId === 'party3::namespace')).toBe(true);
|
|
353
|
+
expect(addWalletSpy).toHaveBeenCalled();
|
|
354
|
+
});
|
|
355
|
+
it('syncWallets should handle same party ID across different networks', async () => {
|
|
356
|
+
const network1 = createNetwork('network1');
|
|
357
|
+
await store.addNetwork(network1);
|
|
358
|
+
await setSession('network1');
|
|
359
|
+
// Mock ledger client to return rights for party1
|
|
360
|
+
mockLedgerGet
|
|
361
|
+
.mockResolvedValueOnce({
|
|
362
|
+
rights: [
|
|
363
|
+
{
|
|
364
|
+
kind: {
|
|
365
|
+
CanActAs: {
|
|
366
|
+
value: {
|
|
367
|
+
party: 'party1::namespace',
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
],
|
|
373
|
+
})
|
|
374
|
+
.mockResolvedValueOnce({
|
|
375
|
+
participantId: 'participant1::namespace',
|
|
376
|
+
});
|
|
377
|
+
const service = new WalletSyncService(store, mockLedgerClient, mockAdminLedgerClient, authContext, mockLogger, {}, partyAllocator);
|
|
378
|
+
await service.syncWallets();
|
|
379
|
+
// Should add party1 for network1
|
|
380
|
+
const wallets = await store.getAllWallets({ networkIds: ['network1'] });
|
|
381
|
+
expect(wallets.some((w) => w.partyId === 'party1::namespace')).toBe(true);
|
|
382
|
+
});
|
|
383
|
+
it('isWalletSyncNeeded should detect multi-hosted party on different network', async () => {
|
|
384
|
+
const network1 = createNetwork('network1');
|
|
385
|
+
const network2 = createNetwork('network2');
|
|
386
|
+
await store.addNetwork(network1);
|
|
387
|
+
await store.addNetwork(network2);
|
|
388
|
+
await store.addWallet(createWallet('party1::namespace', 'network1'));
|
|
389
|
+
const service = new WalletSyncService(store, mockLedgerClient, mockAdminLedgerClient, authContext, mockLogger, {}, partyAllocator);
|
|
390
|
+
// Mock ledger client to return rights for party1 (multi-hosted party) for network1 check
|
|
391
|
+
mockLedgerGet.mockResolvedValueOnce({
|
|
392
|
+
rights: [
|
|
393
|
+
{
|
|
394
|
+
kind: {
|
|
395
|
+
CanActAs: {
|
|
396
|
+
value: {
|
|
397
|
+
party: 'party1::namespace',
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
],
|
|
403
|
+
});
|
|
404
|
+
await setSession('network1');
|
|
405
|
+
// Check sync needed for network1 (party already exists)
|
|
406
|
+
const syncNeeded1 = await service.isWalletSyncNeeded();
|
|
407
|
+
expect(syncNeeded1).toBe(false);
|
|
408
|
+
// Mock ledger client to return rights for party1 for network2 check
|
|
409
|
+
mockLedgerGet.mockResolvedValueOnce({
|
|
410
|
+
rights: [
|
|
411
|
+
{
|
|
412
|
+
kind: {
|
|
413
|
+
CanActAs: {
|
|
414
|
+
value: {
|
|
415
|
+
party: 'party1::namespace',
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
],
|
|
421
|
+
});
|
|
422
|
+
await setSession('network2');
|
|
423
|
+
// Check sync needed for network2 (party doesn't exist yet)
|
|
424
|
+
const syncNeeded2 = await service.isWalletSyncNeeded();
|
|
425
|
+
expect(syncNeeded2).toBe(true);
|
|
426
|
+
});
|
|
427
|
+
it('syncWallets should handle multi-hosted party across networks', async () => {
|
|
428
|
+
const network1 = createNetwork('network1');
|
|
429
|
+
const network2 = createNetwork('network2');
|
|
430
|
+
await store.addNetwork(network1);
|
|
431
|
+
await store.addNetwork(network2);
|
|
432
|
+
// Add wallet to network1 (simulating it was synced there previously)
|
|
433
|
+
await setSession('network1');
|
|
434
|
+
await store.addWallet(createWallet('party1::namespace', 'network1'));
|
|
435
|
+
const service = new WalletSyncService(store, mockLedgerClient, mockAdminLedgerClient, authContext, mockLogger, {}, partyAllocator);
|
|
436
|
+
// Sync on network1 (party already exists, should not add)
|
|
437
|
+
await setSession('network1');
|
|
438
|
+
// Only need one mock since resolveSigningProvider won't be called if party already exists
|
|
439
|
+
mockLedgerGet.mockResolvedValueOnce({
|
|
440
|
+
rights: [
|
|
441
|
+
{
|
|
442
|
+
kind: {
|
|
443
|
+
CanActAs: {
|
|
444
|
+
value: {
|
|
445
|
+
party: 'party1::namespace',
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
],
|
|
451
|
+
});
|
|
452
|
+
const syncResult1 = await service.syncWallets();
|
|
453
|
+
expect(syncResult1.added.length).toBe(0); // Should not add, already exists
|
|
454
|
+
// Sync on network2 (party doesn't exist, should add)
|
|
455
|
+
await setSession('network2');
|
|
456
|
+
// Verify no wallets exist for network2 before sync
|
|
457
|
+
const walletsBeforeSync = await store.getAllWallets({
|
|
458
|
+
networkIds: ['network2'],
|
|
459
|
+
});
|
|
460
|
+
expect(walletsBeforeSync.length).toBe(0);
|
|
461
|
+
mockLedgerGet.mockClear();
|
|
462
|
+
// First mock: getPartiesRightsMap calls ledgerClient.getWithRetry('/v2/users/{user-id}/rights')
|
|
463
|
+
mockLedgerGet.mockResolvedValueOnce({
|
|
464
|
+
rights: [
|
|
465
|
+
{
|
|
466
|
+
kind: {
|
|
467
|
+
CanActAs: {
|
|
468
|
+
value: {
|
|
469
|
+
party: 'party1::namespace',
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
],
|
|
475
|
+
});
|
|
476
|
+
// Second mock: resolveSigningProvider calls adminLedgerClient.getWithRetry('/v2/parties/participant-id')
|
|
477
|
+
mockLedgerGet.mockResolvedValueOnce({
|
|
478
|
+
participantId: 'participant1::namespace',
|
|
479
|
+
});
|
|
480
|
+
expect(mockLedgerGet).toHaveBeenCalledTimes(0);
|
|
481
|
+
const syncResult = await service.syncWallets();
|
|
482
|
+
expect(mockLedgerGet).toHaveBeenCalledTimes(2); // Once for rights, once for participantId
|
|
483
|
+
expect(syncResult.added.length).toBe(1);
|
|
484
|
+
expect(syncResult.added[0].partyId).toBe('party1::namespace');
|
|
485
|
+
expect(syncResult.added[0].networkId).toBe('network2');
|
|
486
|
+
expect(syncResult.added[0].disabled).toBe(false);
|
|
487
|
+
const network2Wallets = await store.getAllWallets({
|
|
488
|
+
networkIds: ['network2'],
|
|
489
|
+
});
|
|
490
|
+
const party1Wallet = network2Wallets.find((w) => w.partyId === 'party1::namespace');
|
|
491
|
+
expect(party1Wallet).toBeDefined();
|
|
492
|
+
expect(party1Wallet?.networkId).toBe('network2');
|
|
493
|
+
expect(party1Wallet?.disabled).toBe(false);
|
|
494
|
+
});
|
|
495
|
+
// TODO maybe now that we never block sync button don't consider disabled wallet as sync is needed?
|
|
496
|
+
it('isWalletSyncNeeded should return true when disabled wallets exist', async () => {
|
|
497
|
+
const network1 = createNetwork('network1');
|
|
498
|
+
await store.addNetwork(network1);
|
|
499
|
+
await setSession('network1');
|
|
500
|
+
await store.addWallet(createWallet('party1::namespace', 'network1', true));
|
|
501
|
+
const service = new WalletSyncService(store, mockLedgerClient, mockAdminLedgerClient, authContext, mockLogger, {}, partyAllocator);
|
|
502
|
+
const syncNeeded = await service.isWalletSyncNeeded();
|
|
503
|
+
// Should return true because there's a disabled wallet
|
|
504
|
+
expect(syncNeeded).toBe(true);
|
|
505
|
+
});
|
|
506
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwtAuth.d.ts","sourceRoot":"","sources":["../../src/middleware/jwtAuth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAE7B,wBAAgB,OAAO,CAAC,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,IAC9C,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,
|
|
1
|
+
{"version":3,"file":"jwtAuth.d.ts","sourceRoot":"","sources":["../../src/middleware/jwtAuth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAE7B,wBAAgB,OAAO,CAAC,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,IAC9C,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,mBAkBhE"}
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
export function jwtAuth(authService, logger) {
|
|
4
4
|
return async (req, res, next) => {
|
|
5
|
-
|
|
5
|
+
// Support both Authorization header and token query parameter (for EventSource)
|
|
6
|
+
const authHeader = req.headers.authorization ||
|
|
7
|
+
(req.query.token ? `Bearer ${req.query.token}` : undefined);
|
|
6
8
|
try {
|
|
7
9
|
const context = await authService.verifyToken(authHeader);
|
|
8
10
|
req.authContext = context;
|
|
@@ -5,7 +5,7 @@ import { AuthContext } from '@canton-network/core-wallet-auth';
|
|
|
5
5
|
import { KernelInfo } from '../config/Config.js';
|
|
6
6
|
import { SigningDriverInterface, SigningProvider } from '@canton-network/core-signing-lib';
|
|
7
7
|
type AvailableSigningDrivers = Partial<Record<SigningProvider, SigningDriverInterface>>;
|
|
8
|
-
export declare const userController: (kernelInfo: KernelInfo, userUrl: string, store: Store, notificationService: NotificationService, authContext: AuthContext | undefined, drivers: AvailableSigningDrivers, _logger: Logger) => {
|
|
8
|
+
export declare const userController: (kernelInfo: KernelInfo, userUrl: string, store: Store, notificationService: NotificationService, authContext: AuthContext | undefined, drivers: AvailableSigningDrivers, _logger: Logger, adminUserId?: string) => {
|
|
9
9
|
addNetwork: import("./rpc-gen/typings.js").AddNetwork;
|
|
10
10
|
removeNetwork: import("./rpc-gen/typings.js").RemoveNetwork;
|
|
11
11
|
listNetworks: import("./rpc-gen/typings.js").ListNetworks;
|
|
@@ -25,6 +25,8 @@ export declare const userController: (kernelInfo: KernelInfo, userUrl: string, s
|
|
|
25
25
|
listSessions: import("./rpc-gen/typings.js").ListSessions;
|
|
26
26
|
getTransaction: import("./rpc-gen/typings.js").GetTransaction;
|
|
27
27
|
listTransactions: import("./rpc-gen/typings.js").ListTransactions;
|
|
28
|
+
deleteTransaction: import("./rpc-gen/typings.js").DeleteTransaction;
|
|
29
|
+
getUser: import("./rpc-gen/typings.js").GetUser;
|
|
28
30
|
};
|
|
29
31
|
export {};
|
|
30
32
|
//# sourceMappingURL=controller.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/user-api/controller.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/user-api/controller.ts"],"names":[],"mappings":"AA4BA,OAAO,EACH,KAAK,EAIR,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAC7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAC5E,OAAO,EAGH,WAAW,EAId,MAAM,kCAAkC,CAAA;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EACH,sBAAsB,EACtB,eAAe,EAElB,MAAM,kCAAkC,CAAA;AAazC,KAAK,uBAAuB,GAAG,OAAO,CAClC,MAAM,CAAC,eAAe,EAAE,sBAAsB,CAAC,CAClD,CAAA;AAED,eAAO,MAAM,cAAc,GACvB,YAAY,UAAU,EACtB,SAAS,MAAM,EACf,OAAO,KAAK,EACZ,qBAAqB,mBAAmB,EACxC,aAAa,WAAW,GAAG,SAAS,EACpC,SAAS,uBAAuB,EAChC,SAAS,MAAM,EACf,cAAc,MAAM;;;;;;;;;;;;;;;;;;;;;;CA4iCvB,CAAA"}
|