@canton-network/wallet-gateway-remote 1.3.0 → 1.5.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.
Files changed (141) hide show
  1. package/README.md +1 -1
  2. package/dist/auth/jwt-auth-service.d.ts.map +1 -1
  3. package/dist/auth/jwt-auth-service.js +6 -5
  4. package/dist/auth/jwt-auth-service.test.d.ts +2 -0
  5. package/dist/auth/jwt-auth-service.test.d.ts.map +1 -0
  6. package/dist/auth/jwt-auth-service.test.js +259 -0
  7. package/dist/auth/jwt-unsafe-auth-service.test.d.ts +2 -0
  8. package/dist/auth/jwt-unsafe-auth-service.test.d.ts.map +1 -0
  9. package/dist/auth/jwt-unsafe-auth-service.test.js +120 -0
  10. package/dist/config/Config.d.ts +64 -8
  11. package/dist/config/Config.d.ts.map +1 -1
  12. package/dist/config/Config.js +1 -0
  13. package/dist/config/ConfigUtils.d.ts.map +1 -1
  14. package/dist/config/ConfigUtils.js +3 -0
  15. package/dist/dapp-api/controller.d.ts +1 -0
  16. package/dist/dapp-api/controller.d.ts.map +1 -1
  17. package/dist/dapp-api/controller.js +87 -26
  18. package/dist/dapp-api/controller.test.d.ts +2 -0
  19. package/dist/dapp-api/controller.test.d.ts.map +1 -0
  20. package/dist/dapp-api/controller.test.js +516 -0
  21. package/dist/dapp-api/rpc-gen/index.d.ts +3 -0
  22. package/dist/dapp-api/rpc-gen/index.d.ts.map +1 -1
  23. package/dist/dapp-api/rpc-gen/index.js +1 -0
  24. package/dist/dapp-api/rpc-gen/typings.d.ts +99 -14
  25. package/dist/dapp-api/rpc-gen/typings.d.ts.map +1 -1
  26. package/dist/dapp-api/server.d.ts.map +1 -1
  27. package/dist/dapp-api/server.js +5 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +10 -0
  30. package/dist/init.d.ts.map +1 -1
  31. package/dist/init.js +4 -6
  32. package/dist/ledger/transaction-service.d.ts +14 -11
  33. package/dist/ledger/transaction-service.d.ts.map +1 -1
  34. package/dist/ledger/transaction-service.js +95 -8
  35. package/dist/ledger/transaction-service.test.d.ts +2 -0
  36. package/dist/ledger/transaction-service.test.d.ts.map +1 -0
  37. package/dist/ledger/transaction-service.test.js +356 -0
  38. package/dist/ledger/wallet-allocation/signing-providers/fireblocks-wallet-allocator.d.ts +5 -2
  39. package/dist/ledger/wallet-allocation/signing-providers/fireblocks-wallet-allocator.d.ts.map +1 -1
  40. package/dist/ledger/wallet-allocation/signing-providers/fireblocks-wallet-allocator.js +7 -6
  41. package/dist/ledger/wallet-allocation/wallet-allocation-service.d.ts +11 -5
  42. package/dist/ledger/wallet-allocation/wallet-allocation-service.d.ts.map +1 -1
  43. package/dist/ledger/wallet-allocation/wallet-allocation-service.js +28 -14
  44. package/dist/ledger/wallet-allocation/wallet-allocation-service.test.js +550 -19
  45. package/dist/ledger/wallet-sync-service.js +3 -3
  46. package/dist/middleware/apiKeyAuth.d.ts +6 -0
  47. package/dist/middleware/apiKeyAuth.d.ts.map +1 -0
  48. package/dist/middleware/apiKeyAuth.js +88 -0
  49. package/dist/middleware/apiKeyAuth.test.d.ts +2 -0
  50. package/dist/middleware/apiKeyAuth.test.d.ts.map +1 -0
  51. package/dist/middleware/apiKeyAuth.test.js +126 -0
  52. package/dist/middleware/jsonRpcHandler.d.ts +11 -1
  53. package/dist/middleware/jsonRpcHandler.d.ts.map +1 -1
  54. package/dist/middleware/jsonRpcHandler.js +14 -15
  55. package/dist/middleware/jsonRpcHandler.test.d.ts +2 -0
  56. package/dist/middleware/jsonRpcHandler.test.d.ts.map +1 -0
  57. package/dist/middleware/jsonRpcHandler.test.js +254 -0
  58. package/dist/middleware/jwtAuth.d.ts.map +1 -1
  59. package/dist/middleware/jwtAuth.js +4 -0
  60. package/dist/middleware/jwtAuth.test.d.ts +2 -0
  61. package/dist/middleware/jwtAuth.test.d.ts.map +1 -0
  62. package/dist/middleware/jwtAuth.test.js +93 -0
  63. package/dist/middleware/rateLimit.js +2 -2
  64. package/dist/middleware/rateLimit.test.js +26 -0
  65. package/dist/middleware/sessionHandler.js +1 -1
  66. package/dist/middleware/sessionHandler.test.d.ts +2 -0
  67. package/dist/middleware/sessionHandler.test.d.ts.map +1 -0
  68. package/dist/middleware/sessionHandler.test.js +121 -0
  69. package/dist/notification/NotificationService.test.d.ts +2 -0
  70. package/dist/notification/NotificationService.test.d.ts.map +1 -0
  71. package/dist/notification/NotificationService.test.js +110 -0
  72. package/dist/signing/signing-drivers.d.ts +7 -0
  73. package/dist/signing/signing-drivers.d.ts.map +1 -0
  74. package/dist/signing/signing-drivers.js +3 -0
  75. package/dist/user-api/controller.d.ts +12 -4
  76. package/dist/user-api/controller.d.ts.map +1 -1
  77. package/dist/user-api/controller.js +330 -95
  78. package/dist/user-api/controller.test.d.ts +2 -0
  79. package/dist/user-api/controller.test.d.ts.map +1 -0
  80. package/dist/user-api/controller.test.js +862 -0
  81. package/dist/user-api/rpc-gen/index.d.ts +30 -0
  82. package/dist/user-api/rpc-gen/index.d.ts.map +1 -1
  83. package/dist/user-api/rpc-gen/index.js +10 -0
  84. package/dist/user-api/rpc-gen/typings.d.ts +170 -25
  85. package/dist/user-api/rpc-gen/typings.d.ts.map +1 -1
  86. package/dist/user-api/server.test.js +53 -0
  87. package/dist/utils.d.ts +19 -5
  88. package/dist/utils.d.ts.map +1 -1
  89. package/dist/utils.js +53 -2
  90. package/dist/utils.test.d.ts +2 -0
  91. package/dist/utils.test.d.ts.map +1 -0
  92. package/dist/utils.test.js +24 -0
  93. package/dist/web/frontend/404/index.html +2 -2
  94. package/dist/web/frontend/activities/index.html +3 -3
  95. package/dist/web/frontend/approve/index.html +4 -5
  96. package/dist/web/frontend/assets/404-MVLWUPwN.js +5 -0
  97. package/dist/web/frontend/assets/activities-DaWkmGqA.js +61 -0
  98. package/dist/web/frontend/assets/addIdentityProvider--rBb0ihb.js +28 -0
  99. package/dist/web/frontend/assets/addNetwork-C6UDsRkU.js +38 -0
  100. package/dist/web/frontend/assets/addParty-B3fFD1eM.js +45 -0
  101. package/dist/web/frontend/assets/approve-WbGL4E82.js +21 -0
  102. package/dist/web/frontend/assets/callback--tihEryB.js +1 -0
  103. package/dist/web/frontend/assets/identityProviders-Dra4qG6t.js +71 -0
  104. package/dist/web/frontend/assets/index-Bm76pXA_.js +91 -0
  105. package/dist/web/frontend/assets/index-D1dC4Cl9.js +4477 -0
  106. package/dist/web/frontend/assets/login-BC8a-kD_.js +10 -0
  107. package/dist/web/frontend/assets/networks-B3ztsogL.js +73 -0
  108. package/dist/web/frontend/assets/reviewIdentityProvider-Co420Dbk.js +43 -0
  109. package/dist/web/frontend/assets/reviewNetwork-B-Sh8pCH.js +43 -0
  110. package/dist/web/frontend/assets/{settings-B1ga2TK0.js → settings-DvyUjqLl.js} +1 -1
  111. package/dist/web/frontend/assets/state-Bq65GwdF.js +1 -0
  112. package/dist/web/frontend/assets/{utils-CT9Hzi7v.js → utils-B2iziW8V.js} +1 -1
  113. package/dist/web/frontend/callback/index.html +2 -2
  114. package/dist/web/frontend/identity-providers/add/index.html +3 -3
  115. package/dist/web/frontend/identity-providers/index.html +3 -3
  116. package/dist/web/frontend/identity-providers/review/index.html +3 -3
  117. package/dist/web/frontend/index.html +1 -1
  118. package/dist/web/frontend/login/index.html +3 -4
  119. package/dist/web/frontend/networks/add/index.html +3 -3
  120. package/dist/web/frontend/networks/index.html +3 -3
  121. package/dist/web/frontend/networks/review/index.html +3 -3
  122. package/dist/web/frontend/parties/add/index.html +5 -5
  123. package/dist/web/frontend/parties/index.html +4 -4
  124. package/dist/web/frontend/settings/index.html +3 -3
  125. package/package.json +40 -33
  126. package/dist/web/frontend/assets/404-B-o9ppJB.js +0 -5
  127. package/dist/web/frontend/assets/activities-CGjCIjUH.js +0 -61
  128. package/dist/web/frontend/assets/addIdentityProvider-CR4Wm9Tl.js +0 -28
  129. package/dist/web/frontend/assets/addNetwork-Dx0-SN4j.js +0 -38
  130. package/dist/web/frontend/assets/addParty-COhk_rFn.js +0 -41
  131. package/dist/web/frontend/assets/approve-B2w66l0J.js +0 -21
  132. package/dist/web/frontend/assets/callback-BTVon_yQ.js +0 -1
  133. package/dist/web/frontend/assets/identityProviders-CK8zSrd3.js +0 -71
  134. package/dist/web/frontend/assets/index-CF4BKzgl.js +0 -91
  135. package/dist/web/frontend/assets/index-DWz_3f3y.js +0 -1
  136. package/dist/web/frontend/assets/index-_pMHlJoE.js +0 -4042
  137. package/dist/web/frontend/assets/login-CVoPNVDw.js +0 -10
  138. package/dist/web/frontend/assets/networks-D1nPvUzM.js +0 -73
  139. package/dist/web/frontend/assets/reviewIdentityProvider-CNSf2qQv.js +0 -43
  140. package/dist/web/frontend/assets/reviewNetwork-DGLK-Ume.js +0 -43
  141. package/dist/web/frontend/assets/state-B2k3ak7d.js +0 -1
package/README.md CHANGED
@@ -59,7 +59,7 @@ Dfns provisions and activates Canton wallets through its validator integration,
59
59
 
60
60
  ## Fireblocks
61
61
 
62
- 1. Complete steps 1–3 from the instructions at https://github.com/canton-network/wallet-gateway/tree/main/core/signing-fireblocks
62
+ 1. Complete steps 1–3 from the instructions at https://github.com/canton-network/wallet/tree/main/core/signing-fireblocks
63
63
 
64
64
  2. set the environment variable `FIREBLOCKS_API_KEY` (get it from `API User (ID)` column in fireblocks api users table).
65
65
 
@@ -1 +1 @@
1
- {"version":3,"file":"jwt-auth-service.d.ts","sourceRoot":"","sources":["../../src/auth/jwt-auth-service.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAC9D,OAAO,EAAE,KAAK,EAAE,MAAM,mCAAmC,CAAA;AAEzD,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAE7B;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,KAAK,EAAE,QAAQ,MAAM,KAAG,WA8H5D,CAAA"}
1
+ {"version":3,"file":"jwt-auth-service.d.ts","sourceRoot":"","sources":["../../src/auth/jwt-auth-service.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAoB,MAAM,kCAAkC,CAAA;AAChF,OAAO,EAAE,KAAK,EAAE,MAAM,mCAAmC,CAAA;AAEzD,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAE7B;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,KAAK,EAAE,QAAQ,MAAM,KAAG,WAiI5D,CAAA"}
@@ -1,5 +1,6 @@
1
1
  // Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
+ import { resolveUserEmail } from '@canton-network/core-wallet-auth';
3
4
  import { createRemoteJWKSet, decodeJwt, jwtVerify } from 'jose';
4
5
  /**
5
6
  * Creates an AuthService that verifies JWT tokens using a remote JWK set.
@@ -38,7 +39,6 @@ export const jwtAuthService = (store, logger) => ({
38
39
  return undefined;
39
40
  }
40
41
  if (idp.type == 'self_signed') {
41
- logger.debug(idp, 'Using self-signed IDP');
42
42
  const sub = decoded.sub;
43
43
  if (!sub) {
44
44
  logger.warn('JWT does not contain a subject');
@@ -51,7 +51,7 @@ export const jwtAuthService = (store, logger) => ({
51
51
  ...(email ? { email } : {}),
52
52
  };
53
53
  }
54
- logger.debug(idp, 'Using IDP');
54
+ logger.debug({ idp }, 'Using IDP');
55
55
  const response = await fetch(idp.configUrl);
56
56
  const config = await response.json();
57
57
  const jwks = createRemoteJWKSet(new URL(config.jwks_uri));
@@ -91,12 +91,13 @@ export const jwtAuthService = (store, logger) => ({
91
91
  accessToken: jwt,
92
92
  email: getEmail(decoded.email),
93
93
  }, 'JWT verified');
94
- const email = getEmail(decoded.email);
95
- return {
94
+ const authContext = {
96
95
  userId: payload.sub,
97
96
  accessToken: jwt,
98
- ...(email ? { email } : {}),
99
97
  };
98
+ const email = getEmail(decoded.email) ??
99
+ (await resolveUserEmail(authContext, idp, logger));
100
+ return email ? { ...authContext, email } : authContext;
100
101
  }
101
102
  catch (error) {
102
103
  if (error instanceof Error) {
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=jwt-auth-service.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt-auth-service.test.d.ts","sourceRoot":"","sources":["../../src/auth/jwt-auth-service.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,259 @@
1
+ // Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
4
+ import { pino } from 'pino';
5
+ import { sink } from 'pino-test';
6
+ import { SignJWT } from 'jose';
7
+ import { StoreInternal } from '@canton-network/core-wallet-store-inmemory';
8
+ import { jwtAuthService } from './jwt-auth-service.js';
9
+ const mockJwtVerify = vi.hoisted(() => vi.fn());
10
+ const mockCreateRemoteJWKSet = vi.hoisted(() => vi.fn(() => 'jwks'));
11
+ const mockFetch = vi.hoisted(() => vi.fn());
12
+ vi.mock('jose', async (importOriginal) => {
13
+ const actual = await importOriginal();
14
+ return {
15
+ ...actual,
16
+ jwtVerify: mockJwtVerify,
17
+ createRemoteJWKSet: mockCreateRemoteJWKSet,
18
+ };
19
+ });
20
+ const authContext = {
21
+ userId: 'test-user-id',
22
+ accessToken: 'test-access-token',
23
+ };
24
+ const SELF_SIGNED_ISSUER = 'unsafe-auth';
25
+ const OAUTH_ISSUER = 'https://oauth.example.com';
26
+ async function bearerToken(claims) {
27
+ const jwt = await new SignJWT(claims)
28
+ .setProtectedHeader({ alg: 'HS256' })
29
+ .sign(new TextEncoder().encode('test-secret'));
30
+ return `Bearer ${jwt}`;
31
+ }
32
+ const createNetwork = (id, identityProviderId, audience = 'test-audience') => ({
33
+ id,
34
+ name: `Network ${id}`,
35
+ synchronizerId: `${id}-sync`,
36
+ identityProviderId,
37
+ description: `Test Network ${id}`,
38
+ ledgerApi: { baseUrl: `http://${id}` },
39
+ auth: {
40
+ method: 'authorization_code',
41
+ clientId: 'cid',
42
+ scope: 'openid',
43
+ audience,
44
+ },
45
+ });
46
+ describe('jwtAuthService', () => {
47
+ let mockLogger;
48
+ let store;
49
+ beforeEach(async () => {
50
+ mockLogger = pino(sink());
51
+ store = new StoreInternal({ idps: [], networks: [] }, mockLogger, authContext);
52
+ vi.stubGlobal('fetch', mockFetch);
53
+ mockFetch.mockReset();
54
+ mockJwtVerify.mockReset();
55
+ mockCreateRemoteJWKSet.mockClear();
56
+ });
57
+ afterEach(() => {
58
+ vi.unstubAllGlobals();
59
+ vi.restoreAllMocks();
60
+ });
61
+ it('returns undefined when access token is missing', async () => {
62
+ const service = jwtAuthService(store, mockLogger);
63
+ await expect(service.verifyToken(undefined)).resolves.toBeUndefined();
64
+ });
65
+ it('returns undefined when authorization header is not Bearer', async () => {
66
+ const service = jwtAuthService(store, mockLogger);
67
+ await expect(service.verifyToken('Basic abc')).resolves.toBeUndefined();
68
+ });
69
+ it('returns undefined when JWT has no issuer', async () => {
70
+ const service = jwtAuthService(store, mockLogger);
71
+ const token = await bearerToken({ sub: 'user-1', scope: 'openid' });
72
+ await expect(service.verifyToken(token)).resolves.toBeUndefined();
73
+ });
74
+ it('returns undefined when no identity provider matches issuer', async () => {
75
+ await store.addIdp({
76
+ id: 'idp-self',
77
+ type: 'self_signed',
78
+ issuer: SELF_SIGNED_ISSUER,
79
+ });
80
+ const service = jwtAuthService(store, mockLogger);
81
+ const token = await bearerToken({
82
+ iss: 'unknown-issuer',
83
+ sub: 'user-1',
84
+ scope: 'openid',
85
+ });
86
+ await expect(service.verifyToken(token)).resolves.toBeUndefined();
87
+ });
88
+ it('returns undefined when JWT has no scope or scp claim', async () => {
89
+ await store.addIdp({
90
+ id: 'idp-self',
91
+ type: 'self_signed',
92
+ issuer: SELF_SIGNED_ISSUER,
93
+ });
94
+ const service = jwtAuthService(store, mockLogger);
95
+ const token = await bearerToken({
96
+ iss: SELF_SIGNED_ISSUER,
97
+ sub: 'user-1',
98
+ });
99
+ await expect(service.verifyToken(token)).resolves.toBeUndefined();
100
+ });
101
+ describe('self_signed identity provider', () => {
102
+ beforeEach(async () => {
103
+ await store.addIdp({
104
+ id: 'idp-self',
105
+ type: 'self_signed',
106
+ issuer: SELF_SIGNED_ISSUER,
107
+ });
108
+ });
109
+ it('returns auth context for a valid self-signed token', async () => {
110
+ const service = jwtAuthService(store, mockLogger);
111
+ const token = await bearerToken({
112
+ iss: SELF_SIGNED_ISSUER,
113
+ sub: 'user-1',
114
+ scope: 'openid',
115
+ });
116
+ const result = await service.verifyToken(token);
117
+ expect(result).toEqual({
118
+ userId: 'user-1',
119
+ accessToken: token.split(' ')[1],
120
+ });
121
+ });
122
+ it('accepts scp claim instead of scope', async () => {
123
+ const service = jwtAuthService(store, mockLogger);
124
+ const token = await bearerToken({
125
+ iss: SELF_SIGNED_ISSUER,
126
+ sub: 'user-1',
127
+ scp: 'openid',
128
+ });
129
+ const result = await service.verifyToken(token);
130
+ expect(result?.userId).toBe('user-1');
131
+ });
132
+ it('includes email when present in token', async () => {
133
+ const service = jwtAuthService(store, mockLogger);
134
+ const token = await bearerToken({
135
+ iss: SELF_SIGNED_ISSUER,
136
+ sub: 'user-1',
137
+ scope: 'openid',
138
+ email: 'user@example.com',
139
+ });
140
+ const result = await service.verifyToken(token);
141
+ expect(result).toEqual({
142
+ userId: 'user-1',
143
+ accessToken: token.split(' ')[1],
144
+ email: 'user@example.com',
145
+ });
146
+ });
147
+ it('returns undefined when JWT has no subject', async () => {
148
+ const service = jwtAuthService(store, mockLogger);
149
+ const token = await bearerToken({
150
+ iss: SELF_SIGNED_ISSUER,
151
+ scope: 'openid',
152
+ });
153
+ await expect(service.verifyToken(token)).resolves.toBeUndefined();
154
+ });
155
+ });
156
+ describe('oauth identity provider', () => {
157
+ const configUrl = 'https://oauth.example.com/.well-known/openid-configuration';
158
+ beforeEach(async () => {
159
+ await store.addIdp({
160
+ id: 'idp-oauth',
161
+ type: 'oauth',
162
+ issuer: OAUTH_ISSUER,
163
+ configUrl,
164
+ });
165
+ mockFetch.mockResolvedValue({
166
+ json: async () => ({
167
+ jwks_uri: 'https://oauth.example.com/jwks',
168
+ }),
169
+ });
170
+ });
171
+ it('returns auth context when JWT verifies and audience matches', async () => {
172
+ await store.addNetwork(createNetwork('network-1', 'idp-oauth', 'ledger-audience'));
173
+ mockJwtVerify.mockResolvedValue({
174
+ payload: { sub: 'oauth-user', aud: 'ledger-audience' },
175
+ });
176
+ const service = jwtAuthService(store, mockLogger);
177
+ const token = await bearerToken({
178
+ iss: OAUTH_ISSUER,
179
+ sub: 'oauth-user',
180
+ scope: 'openid',
181
+ email: 'oauth@example.com',
182
+ });
183
+ const rawJwt = token.split(' ')[1];
184
+ const result = await service.verifyToken(token);
185
+ expect(mockFetch).toHaveBeenCalledWith(configUrl);
186
+ expect(mockCreateRemoteJWKSet).toHaveBeenCalledWith(new URL('https://oauth.example.com/jwks'));
187
+ expect(mockJwtVerify).toHaveBeenCalledWith(rawJwt, 'jwks', {
188
+ algorithms: ['RS256'],
189
+ });
190
+ expect(result).toEqual({
191
+ userId: 'oauth-user',
192
+ accessToken: rawJwt,
193
+ email: 'oauth@example.com',
194
+ });
195
+ });
196
+ it('returns undefined when no networks are configured for the IDP', async () => {
197
+ mockJwtVerify.mockResolvedValue({
198
+ payload: { sub: 'oauth-user', aud: 'ledger-audience' },
199
+ });
200
+ const service = jwtAuthService(store, mockLogger);
201
+ const token = await bearerToken({
202
+ iss: OAUTH_ISSUER,
203
+ sub: 'oauth-user',
204
+ scope: 'openid',
205
+ });
206
+ await expect(service.verifyToken(token)).resolves.toBeUndefined();
207
+ });
208
+ it('returns undefined when audience does not match configured networks', async () => {
209
+ await store.addNetwork(createNetwork('network-1', 'idp-oauth', 'expected-audience'));
210
+ mockJwtVerify.mockResolvedValue({
211
+ payload: { sub: 'oauth-user', aud: 'other-audience' },
212
+ });
213
+ const service = jwtAuthService(store, mockLogger);
214
+ const token = await bearerToken({
215
+ iss: OAUTH_ISSUER,
216
+ sub: 'oauth-user',
217
+ scope: 'openid',
218
+ });
219
+ await expect(service.verifyToken(token)).resolves.toBeUndefined();
220
+ });
221
+ it('returns undefined when verified JWT has no audience', async () => {
222
+ await store.addNetwork(createNetwork('network-1', 'idp-oauth', 'expected-audience'));
223
+ mockJwtVerify.mockResolvedValue({
224
+ payload: { sub: 'oauth-user' },
225
+ });
226
+ const service = jwtAuthService(store, mockLogger);
227
+ const token = await bearerToken({
228
+ iss: OAUTH_ISSUER,
229
+ sub: 'oauth-user',
230
+ scope: 'openid',
231
+ });
232
+ await expect(service.verifyToken(token)).resolves.toBeUndefined();
233
+ });
234
+ it('returns undefined when verified JWT has no subject', async () => {
235
+ await store.addNetwork(createNetwork('network-1', 'idp-oauth', 'expected-audience'));
236
+ mockJwtVerify.mockResolvedValue({
237
+ payload: { aud: 'expected-audience' },
238
+ });
239
+ const service = jwtAuthService(store, mockLogger);
240
+ const token = await bearerToken({
241
+ iss: OAUTH_ISSUER,
242
+ sub: 'oauth-user',
243
+ scope: 'openid',
244
+ });
245
+ await expect(service.verifyToken(token)).resolves.toBeUndefined();
246
+ });
247
+ it('returns undefined when JWT verification fails', async () => {
248
+ await store.addNetwork(createNetwork('network-1', 'idp-oauth', 'expected-audience'));
249
+ mockJwtVerify.mockRejectedValue(new Error('invalid signature'));
250
+ const service = jwtAuthService(store, mockLogger);
251
+ const token = await bearerToken({
252
+ iss: OAUTH_ISSUER,
253
+ sub: 'oauth-user',
254
+ scope: 'openid',
255
+ });
256
+ await expect(service.verifyToken(token)).resolves.toBeUndefined();
257
+ });
258
+ });
259
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=jwt-unsafe-auth-service.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt-unsafe-auth-service.test.d.ts","sourceRoot":"","sources":["../../src/auth/jwt-unsafe-auth-service.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,120 @@
1
+ // Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { describe, it, expect, beforeEach } from 'vitest';
4
+ import { pino } from 'pino';
5
+ import { sink } from 'pino-test';
6
+ import { SignJWT } from 'jose';
7
+ import { StoreInternal } from '@canton-network/core-wallet-store-inmemory';
8
+ import { jwtAuthService as jwtUnsafeAuthService } from './jwt-unsafe-auth-service.js';
9
+ const authContext = {
10
+ userId: 'test-user-id',
11
+ accessToken: 'test-access-token',
12
+ };
13
+ const SELF_SIGNED_ISSUER = 'unsafe-auth';
14
+ async function bearerToken(claims) {
15
+ const jwt = await new SignJWT(claims)
16
+ .setProtectedHeader({ alg: 'HS256' })
17
+ .sign(new TextEncoder().encode('test-secret'));
18
+ return `Bearer ${jwt}`;
19
+ }
20
+ describe('jwtAuthService (unsafe)', () => {
21
+ let mockLogger;
22
+ let store;
23
+ beforeEach(async () => {
24
+ mockLogger = pino(sink());
25
+ store = new StoreInternal({ idps: [], networks: [] }, mockLogger, authContext);
26
+ await store.addIdp({
27
+ id: 'idp-self',
28
+ type: 'self_signed',
29
+ issuer: SELF_SIGNED_ISSUER,
30
+ });
31
+ });
32
+ it('returns undefined when access token is missing', async () => {
33
+ const service = jwtUnsafeAuthService(store, mockLogger);
34
+ await expect(service.verifyToken(undefined)).resolves.toBeUndefined();
35
+ });
36
+ it('returns undefined when authorization header is not Bearer', async () => {
37
+ const service = jwtUnsafeAuthService(store, mockLogger);
38
+ await expect(service.verifyToken('NotBearer abc')).resolves.toBeUndefined();
39
+ });
40
+ it('returns undefined when JWT has no issuer', async () => {
41
+ const service = jwtUnsafeAuthService(store, mockLogger);
42
+ const token = await bearerToken({
43
+ sub: 'user-1',
44
+ scope: 'openid',
45
+ });
46
+ await expect(service.verifyToken(token)).resolves.toBeUndefined();
47
+ });
48
+ it('returns undefined when JWT has no subject', async () => {
49
+ const service = jwtUnsafeAuthService(store, mockLogger);
50
+ const token = await bearerToken({
51
+ iss: SELF_SIGNED_ISSUER,
52
+ scope: 'openid',
53
+ });
54
+ await expect(service.verifyToken(token)).resolves.toBeUndefined();
55
+ });
56
+ it('returns undefined when JWT has no scope claim', async () => {
57
+ const service = jwtUnsafeAuthService(store, mockLogger);
58
+ const token = await bearerToken({
59
+ iss: SELF_SIGNED_ISSUER,
60
+ sub: 'user-1',
61
+ });
62
+ await expect(service.verifyToken(token)).resolves.toBeUndefined();
63
+ });
64
+ it('returns undefined when no identity provider matches issuer', async () => {
65
+ const service = jwtUnsafeAuthService(store, mockLogger);
66
+ const token = await bearerToken({
67
+ iss: 'unknown-issuer',
68
+ sub: 'user-1',
69
+ scope: 'openid',
70
+ });
71
+ await expect(service.verifyToken(token)).resolves.toBeUndefined();
72
+ });
73
+ it('returns undefined for oauth identity providers', async () => {
74
+ await store.addIdp({
75
+ id: 'idp-oauth',
76
+ type: 'oauth',
77
+ issuer: 'https://oauth.example.com',
78
+ configUrl: 'https://oauth.example.com/.well-known/openid-configuration',
79
+ });
80
+ const service = jwtUnsafeAuthService(store, mockLogger);
81
+ const token = await bearerToken({
82
+ iss: 'https://oauth.example.com',
83
+ sub: 'user-1',
84
+ scope: 'openid',
85
+ });
86
+ await expect(service.verifyToken(token)).resolves.toBeUndefined();
87
+ });
88
+ it('returns auth context for a valid self-signed token', async () => {
89
+ const service = jwtUnsafeAuthService(store, mockLogger);
90
+ const token = await bearerToken({
91
+ iss: SELF_SIGNED_ISSUER,
92
+ sub: 'user-1',
93
+ scope: 'openid',
94
+ });
95
+ const result = await service.verifyToken(token);
96
+ expect(result).toEqual({
97
+ userId: 'user-1',
98
+ accessToken: token.split(' ')[1],
99
+ });
100
+ });
101
+ it('includes email when present in token', async () => {
102
+ const service = jwtUnsafeAuthService(store, mockLogger);
103
+ const token = await bearerToken({
104
+ iss: SELF_SIGNED_ISSUER,
105
+ sub: 'user-1',
106
+ scope: 'openid',
107
+ email: 'user@example.com',
108
+ });
109
+ const result = await service.verifyToken(token);
110
+ expect(result).toEqual({
111
+ userId: 'user-1',
112
+ accessToken: token.split(' ')[1],
113
+ email: 'user@example.com',
114
+ });
115
+ });
116
+ it('returns undefined for malformed JWT', async () => {
117
+ const service = jwtUnsafeAuthService(store, mockLogger);
118
+ await expect(service.verifyToken('Bearer not-a-jwt')).resolves.toBeUndefined();
119
+ });
120
+ });
@@ -22,7 +22,7 @@ export declare const rawConfigSchema: z.ZodObject<{
22
22
  publicUrl: z.ZodOptional<z.ZodString>;
23
23
  clientType: z.ZodUnion<readonly [z.ZodLiteral<"browser">, z.ZodLiteral<"desktop">, z.ZodLiteral<"mobile">, z.ZodLiteral<"remote">]>;
24
24
  }, z.core.$strip>;
25
- server: z.ZodPipe<z.ZodTransform<{}, unknown>, z.ZodObject<{
25
+ server: z.ZodPreprocess<z.ZodObject<{
26
26
  port: z.ZodDefault<z.ZodNumber>;
27
27
  dappPath: z.ZodDefault<z.ZodString>;
28
28
  userPath: z.ZodDefault<z.ZodString>;
@@ -34,7 +34,7 @@ export declare const rawConfigSchema: z.ZodObject<{
34
34
  trustProxy: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodNumber, z.ZodString]>>;
35
35
  admin: z.ZodOptional<z.ZodString>;
36
36
  }, z.core.$strip>>;
37
- logging: z.ZodOptional<z.ZodPipe<z.ZodTransform<{}, unknown>, z.ZodObject<{
37
+ logging: z.ZodOptional<z.ZodPreprocess<z.ZodObject<{
38
38
  level: z.ZodOptional<z.ZodEnum<{
39
39
  error: "error";
40
40
  trace: "trace";
@@ -61,7 +61,7 @@ export declare const rawConfigSchema: z.ZodObject<{
61
61
  user: z.ZodString;
62
62
  password: z.ZodString;
63
63
  database: z.ZodString;
64
- }, z.core.$strip>], "type">;
64
+ }, z.core.$loose>], "type">;
65
65
  }, z.core.$strip>;
66
66
  signingStore: z.ZodObject<{
67
67
  connection: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -76,7 +76,7 @@ export declare const rawConfigSchema: z.ZodObject<{
76
76
  user: z.ZodString;
77
77
  password: z.ZodString;
78
78
  database: z.ZodString;
79
- }, z.core.$strip>], "type">;
79
+ }, z.core.$loose>], "type">;
80
80
  }, z.core.$strip>;
81
81
  bootstrap: z.ZodObject<{
82
82
  idps: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -172,6 +172,43 @@ export declare const rawConfigSchema: z.ZodObject<{
172
172
  clientId: z.ZodString;
173
173
  clientSecretEnv: z.ZodString;
174
174
  }, z.core.$strip>], "method">]>>;
175
+ serviceAccountAuth: z.ZodOptional<z.ZodUnion<readonly [z.ZodDiscriminatedUnion<[z.ZodObject<{
176
+ method: z.ZodLiteral<"authorization_code">;
177
+ audience: z.ZodString;
178
+ scope: z.ZodString;
179
+ clientId: z.ZodString;
180
+ }, z.core.$strip>, z.ZodObject<{
181
+ method: z.ZodLiteral<"client_credentials">;
182
+ audience: z.ZodString;
183
+ scope: z.ZodString;
184
+ clientId: z.ZodString;
185
+ clientSecret: z.ZodString;
186
+ }, z.core.$strip>, z.ZodObject<{
187
+ method: z.ZodLiteral<"self_signed">;
188
+ issuer: z.ZodString;
189
+ audience: z.ZodString;
190
+ scope: z.ZodString;
191
+ clientId: z.ZodString;
192
+ clientSecret: z.ZodString;
193
+ }, z.core.$strip>], "method">, z.ZodDiscriminatedUnion<[z.ZodObject<{
194
+ method: z.ZodLiteral<"authorization_code">;
195
+ audience: z.ZodString;
196
+ scope: z.ZodString;
197
+ clientId: z.ZodString;
198
+ }, z.core.$strip>, z.ZodObject<{
199
+ method: z.ZodLiteral<"client_credentials">;
200
+ audience: z.ZodString;
201
+ scope: z.ZodString;
202
+ clientId: z.ZodString;
203
+ clientSecretEnv: z.ZodString;
204
+ }, z.core.$strip>, z.ZodObject<{
205
+ method: z.ZodLiteral<"self_signed">;
206
+ issuer: z.ZodString;
207
+ audience: z.ZodString;
208
+ scope: z.ZodString;
209
+ clientId: z.ZodString;
210
+ clientSecretEnv: z.ZodString;
211
+ }, z.core.$strip>], "method">]>>;
175
212
  }, z.core.$strip>>;
176
213
  }, z.core.$strip>;
177
214
  }, z.core.$strip>;
@@ -181,7 +218,7 @@ export declare const configSchema: z.ZodObject<{
181
218
  publicUrl: z.ZodOptional<z.ZodString>;
182
219
  clientType: z.ZodUnion<readonly [z.ZodLiteral<"browser">, z.ZodLiteral<"desktop">, z.ZodLiteral<"mobile">, z.ZodLiteral<"remote">]>;
183
220
  }, z.core.$strip>;
184
- server: z.ZodPipe<z.ZodTransform<{}, unknown>, z.ZodObject<{
221
+ server: z.ZodPreprocess<z.ZodObject<{
185
222
  port: z.ZodDefault<z.ZodNumber>;
186
223
  dappPath: z.ZodDefault<z.ZodString>;
187
224
  userPath: z.ZodDefault<z.ZodString>;
@@ -193,7 +230,7 @@ export declare const configSchema: z.ZodObject<{
193
230
  trustProxy: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodNumber, z.ZodString]>>;
194
231
  admin: z.ZodOptional<z.ZodString>;
195
232
  }, z.core.$strip>>;
196
- logging: z.ZodOptional<z.ZodPipe<z.ZodTransform<{}, unknown>, z.ZodObject<{
233
+ logging: z.ZodOptional<z.ZodPreprocess<z.ZodObject<{
197
234
  level: z.ZodOptional<z.ZodEnum<{
198
235
  error: "error";
199
236
  trace: "trace";
@@ -220,7 +257,7 @@ export declare const configSchema: z.ZodObject<{
220
257
  user: z.ZodString;
221
258
  password: z.ZodString;
222
259
  database: z.ZodString;
223
- }, z.core.$strip>], "type">;
260
+ }, z.core.$loose>], "type">;
224
261
  }, z.core.$strip>;
225
262
  signingStore: z.ZodObject<{
226
263
  connection: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -235,7 +272,7 @@ export declare const configSchema: z.ZodObject<{
235
272
  user: z.ZodString;
236
273
  password: z.ZodString;
237
274
  database: z.ZodString;
238
- }, z.core.$strip>], "type">;
275
+ }, z.core.$loose>], "type">;
239
276
  }, z.core.$strip>;
240
277
  bootstrap: z.ZodObject<{
241
278
  idps: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -295,6 +332,25 @@ export declare const configSchema: z.ZodObject<{
295
332
  clientId: z.ZodString;
296
333
  clientSecret: z.ZodString;
297
334
  }, z.core.$strip>], "method">>;
335
+ serviceAccountAuth: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
336
+ method: z.ZodLiteral<"authorization_code">;
337
+ audience: z.ZodString;
338
+ scope: z.ZodString;
339
+ clientId: z.ZodString;
340
+ }, z.core.$strip>, z.ZodObject<{
341
+ method: z.ZodLiteral<"client_credentials">;
342
+ audience: z.ZodString;
343
+ scope: z.ZodString;
344
+ clientId: z.ZodString;
345
+ clientSecret: z.ZodString;
346
+ }, z.core.$strip>, z.ZodObject<{
347
+ method: z.ZodLiteral<"self_signed">;
348
+ issuer: z.ZodString;
349
+ audience: z.ZodString;
350
+ scope: z.ZodString;
351
+ clientId: z.ZodString;
352
+ clientSecret: z.ZodString;
353
+ }, z.core.$strip>], "method">>;
298
354
  }, z.core.$strip>>;
299
355
  }, z.core.$strip>;
300
356
  }, z.core.$strip>;
@@ -1 +1 @@
1
- {"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../../src/config/Config.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AASvB,eAAO,MAAM,gBAAgB;;;;iBAY3B,CAAA;AAEF,eAAO,MAAM,kBAAkB;;;;;;;;;;;iBAiD7B,CAAA;AAiCF,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAO1B,CAAA;AAEF,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAOvB,CAAA;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAA;AACzD,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAC7D,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AACvD,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAA"}
1
+ {"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../../src/config/Config.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AASvB,eAAO,MAAM,gBAAgB;;;;iBAY3B,CAAA;AAEF,eAAO,MAAM,kBAAkB;;;;;;;;;;;iBAiD7B,CAAA;AAkCF,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAO1B,CAAA;AAEF,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAOvB,CAAA;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAA;AACzD,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAC7D,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AACvD,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAA"}
@@ -78,6 +78,7 @@ const bootstrapFromEnv = bootstrapConfigSchema.extend({
78
78
  networks: z.array(networkSchema.extend({
79
79
  auth: authFromEnvOrConfig,
80
80
  adminAuth: authFromEnvOrConfig.optional(),
81
+ serviceAccountAuth: authFromEnvOrConfig.optional(),
81
82
  })),
82
83
  });
83
84
  // Includes secrets for networks as env vars, rather than defined explicitly
@@ -1 +1 @@
1
- {"version":3,"file":"ConfigUtils.d.ts","sourceRoot":"","sources":["../../src/config/ConfigUtils.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAA8B,MAAM,aAAa,CAAA;AAGhE,qBAAa,WAAW;IACpB,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;CAsDlD;AA+FD,UAAU,IAAI;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;CACrB;AAOD,eAAO,MAAM,UAAU,GAAI,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,IAa1D,CAAA"}
1
+ {"version":3,"file":"ConfigUtils.d.ts","sourceRoot":"","sources":["../../src/config/ConfigUtils.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAA8B,MAAM,aAAa,CAAA;AAGhE,qBAAa,WAAW;IACpB,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;CAsDlD;AAkGD,UAAU,IAAI;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;CACrB;AAOD,eAAO,MAAM,UAAU,GAAI,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,IAa1D,CAAA"}
@@ -67,6 +67,9 @@ function resolveRawConfig(rawConfig) {
67
67
  adminAuth: n.adminAuth
68
68
  ? resolveRawNetworkAuth(n.adminAuth)
69
69
  : undefined,
70
+ serviceAccountAuth: n.serviceAccountAuth
71
+ ? resolveRawNetworkAuth(n.serviceAccountAuth)
72
+ : undefined,
70
73
  };
71
74
  });
72
75
  return {
@@ -18,5 +18,6 @@ export declare const dappController: (kernelInfo: KernelInfoConfig, dappUrl: str
18
18
  getPrimaryAccount: import("./rpc-gen/typings.js").GetPrimaryAccount;
19
19
  listAccounts: import("./rpc-gen/typings.js").ListAccounts;
20
20
  txChanged: import("./rpc-gen/typings.js").TxChanged;
21
+ messageSignature: import("./rpc-gen/typings.js").MessageSignature;
21
22
  };
22
23
  //# sourceMappingURL=controller.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/dapp-api/controller.ts"],"names":[],"mappings":"AAGA,OAAO,EAEH,WAAW,EAEd,MAAM,kCAAkC,CAAA;AAYzC,OAAO,EAAE,KAAK,EAAe,MAAM,mCAAmC,CAAA;AAQtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAC5E,OAAO,EAAE,UAAU,IAAI,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACpE,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAI7B,eAAO,MAAM,cAAc,GACvB,YAAY,gBAAgB,EAC5B,SAAS,MAAM,EACf,SAAS,MAAM,EACf,OAAO,KAAK,EACZ,qBAAqB,mBAAmB,EACxC,SAAS,MAAM,EACf,QAAQ,MAAM,GAAG,IAAI,EACrB,UAAU,WAAW;;;;;;;;;;;;;;;CAmTxB,CAAA"}
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/dapp-api/controller.ts"],"names":[],"mappings":"AAGA,OAAO,EAEH,WAAW,EAEd,MAAM,kCAAkC,CAAA;AAczC,OAAO,EAAE,KAAK,EAAe,MAAM,mCAAmC,CAAA;AAQtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAC5E,OAAO,EAAE,UAAU,IAAI,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACpE,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAK7B,eAAO,MAAM,cAAc,GACvB,YAAY,gBAAgB,EAC5B,SAAS,MAAM,EACf,SAAS,MAAM,EACf,OAAO,KAAK,EACZ,qBAAqB,mBAAmB,EACxC,SAAS,MAAM,EACf,QAAQ,MAAM,GAAG,IAAI,EACrB,UAAU,WAAW;;;;;;;;;;;;;;;;CA0YxB,CAAA"}