@canton-network/wallet-gateway-remote 1.4.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 (116) hide show
  1. package/dist/auth/jwt-auth-service.d.ts.map +1 -1
  2. package/dist/auth/jwt-auth-service.js +6 -5
  3. package/dist/auth/jwt-auth-service.test.d.ts +2 -0
  4. package/dist/auth/jwt-auth-service.test.d.ts.map +1 -0
  5. package/dist/auth/jwt-auth-service.test.js +259 -0
  6. package/dist/auth/jwt-unsafe-auth-service.test.d.ts +2 -0
  7. package/dist/auth/jwt-unsafe-auth-service.test.d.ts.map +1 -0
  8. package/dist/auth/jwt-unsafe-auth-service.test.js +120 -0
  9. package/dist/config/Config.d.ts +60 -4
  10. package/dist/config/Config.d.ts.map +1 -1
  11. package/dist/config/Config.js +1 -0
  12. package/dist/config/ConfigUtils.d.ts.map +1 -1
  13. package/dist/config/ConfigUtils.js +3 -0
  14. package/dist/dapp-api/controller.d.ts.map +1 -1
  15. package/dist/dapp-api/controller.js +54 -24
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +10 -0
  18. package/dist/init.d.ts.map +1 -1
  19. package/dist/init.js +3 -6
  20. package/dist/ledger/transaction-service.d.ts +14 -11
  21. package/dist/ledger/transaction-service.d.ts.map +1 -1
  22. package/dist/ledger/transaction-service.js +95 -8
  23. package/dist/ledger/transaction-service.test.d.ts +2 -0
  24. package/dist/ledger/transaction-service.test.d.ts.map +1 -0
  25. package/dist/ledger/transaction-service.test.js +356 -0
  26. package/dist/ledger/wallet-allocation/signing-providers/fireblocks-wallet-allocator.d.ts +5 -2
  27. package/dist/ledger/wallet-allocation/signing-providers/fireblocks-wallet-allocator.d.ts.map +1 -1
  28. package/dist/ledger/wallet-allocation/signing-providers/fireblocks-wallet-allocator.js +7 -6
  29. package/dist/ledger/wallet-allocation/wallet-allocation-service.d.ts +11 -5
  30. package/dist/ledger/wallet-allocation/wallet-allocation-service.d.ts.map +1 -1
  31. package/dist/ledger/wallet-allocation/wallet-allocation-service.js +28 -14
  32. package/dist/ledger/wallet-allocation/wallet-allocation-service.test.js +550 -19
  33. package/dist/ledger/wallet-sync-service.js +3 -3
  34. package/dist/middleware/apiKeyAuth.d.ts +6 -0
  35. package/dist/middleware/apiKeyAuth.d.ts.map +1 -0
  36. package/dist/middleware/apiKeyAuth.js +88 -0
  37. package/dist/middleware/apiKeyAuth.test.d.ts +2 -0
  38. package/dist/middleware/apiKeyAuth.test.d.ts.map +1 -0
  39. package/dist/middleware/apiKeyAuth.test.js +126 -0
  40. package/dist/middleware/jsonRpcHandler.d.ts +11 -1
  41. package/dist/middleware/jsonRpcHandler.d.ts.map +1 -1
  42. package/dist/middleware/jsonRpcHandler.js +14 -15
  43. package/dist/middleware/jsonRpcHandler.test.d.ts +2 -0
  44. package/dist/middleware/jsonRpcHandler.test.d.ts.map +1 -0
  45. package/dist/middleware/jsonRpcHandler.test.js +254 -0
  46. package/dist/middleware/jwtAuth.d.ts.map +1 -1
  47. package/dist/middleware/jwtAuth.js +4 -0
  48. package/dist/middleware/jwtAuth.test.d.ts +2 -0
  49. package/dist/middleware/jwtAuth.test.d.ts.map +1 -0
  50. package/dist/middleware/jwtAuth.test.js +93 -0
  51. package/dist/middleware/rateLimit.test.js +12 -0
  52. package/dist/middleware/sessionHandler.js +1 -1
  53. package/dist/middleware/sessionHandler.test.d.ts +2 -0
  54. package/dist/middleware/sessionHandler.test.d.ts.map +1 -0
  55. package/dist/middleware/sessionHandler.test.js +121 -0
  56. package/dist/notification/NotificationService.test.d.ts +2 -0
  57. package/dist/notification/NotificationService.test.d.ts.map +1 -0
  58. package/dist/notification/NotificationService.test.js +110 -0
  59. package/dist/signing/signing-drivers.d.ts +7 -0
  60. package/dist/signing/signing-drivers.d.ts.map +1 -0
  61. package/dist/signing/signing-drivers.js +3 -0
  62. package/dist/user-api/controller.d.ts +6 -4
  63. package/dist/user-api/controller.d.ts.map +1 -1
  64. package/dist/user-api/controller.js +111 -94
  65. package/dist/user-api/controller.test.d.ts +2 -0
  66. package/dist/user-api/controller.test.d.ts.map +1 -0
  67. package/dist/user-api/controller.test.js +862 -0
  68. package/dist/user-api/rpc-gen/index.d.ts +12 -0
  69. package/dist/user-api/rpc-gen/index.d.ts.map +1 -1
  70. package/dist/user-api/rpc-gen/index.js +4 -0
  71. package/dist/user-api/rpc-gen/typings.d.ts +52 -3
  72. package/dist/user-api/rpc-gen/typings.d.ts.map +1 -1
  73. package/dist/utils.d.ts +17 -1
  74. package/dist/utils.d.ts.map +1 -1
  75. package/dist/utils.js +53 -2
  76. package/dist/utils.test.d.ts +2 -0
  77. package/dist/utils.test.d.ts.map +1 -0
  78. package/dist/utils.test.js +24 -0
  79. package/dist/web/frontend/404/index.html +2 -2
  80. package/dist/web/frontend/activities/index.html +3 -3
  81. package/dist/web/frontend/approve/index.html +4 -4
  82. package/dist/web/frontend/assets/404-MVLWUPwN.js +5 -0
  83. package/dist/web/frontend/assets/{activities-NY8oZAkH.js → activities-DaWkmGqA.js} +2 -2
  84. package/dist/web/frontend/assets/{addIdentityProvider-38ghz3RT.js → addIdentityProvider--rBb0ihb.js} +2 -2
  85. package/dist/web/frontend/assets/{addNetwork-BaDhbrZl.js → addNetwork-C6UDsRkU.js} +4 -4
  86. package/dist/web/frontend/assets/addParty-B3fFD1eM.js +45 -0
  87. package/dist/web/frontend/assets/approve-WbGL4E82.js +21 -0
  88. package/dist/web/frontend/assets/callback--tihEryB.js +1 -0
  89. package/dist/web/frontend/assets/{identityProviders-BZ9h_VOJ.js → identityProviders-Dra4qG6t.js} +2 -2
  90. package/dist/web/frontend/assets/{index-B-HiKugW.js → index-Bm76pXA_.js} +2 -2
  91. package/dist/web/frontend/assets/index-D1dC4Cl9.js +4477 -0
  92. package/dist/web/frontend/assets/{login-CqwMEG9J.js → login-BC8a-kD_.js} +2 -2
  93. package/dist/web/frontend/assets/{networks-BJvRi47M.js → networks-B3ztsogL.js} +2 -2
  94. package/dist/web/frontend/assets/{reviewIdentityProvider-ySlQnw5R.js → reviewIdentityProvider-Co420Dbk.js} +3 -3
  95. package/dist/web/frontend/assets/{reviewNetwork-DrVxeHd4.js → reviewNetwork-B-Sh8pCH.js} +3 -3
  96. package/dist/web/frontend/assets/{settings-BVNkaUyJ.js → settings-DvyUjqLl.js} +1 -1
  97. package/dist/web/frontend/assets/{state-qAsNw5qf.js → state-Bq65GwdF.js} +1 -1
  98. package/dist/web/frontend/assets/{utils-BDkHxi8V.js → utils-B2iziW8V.js} +1 -1
  99. package/dist/web/frontend/callback/index.html +2 -2
  100. package/dist/web/frontend/identity-providers/add/index.html +3 -3
  101. package/dist/web/frontend/identity-providers/index.html +3 -3
  102. package/dist/web/frontend/identity-providers/review/index.html +3 -3
  103. package/dist/web/frontend/index.html +1 -1
  104. package/dist/web/frontend/login/index.html +3 -3
  105. package/dist/web/frontend/networks/add/index.html +3 -3
  106. package/dist/web/frontend/networks/index.html +3 -3
  107. package/dist/web/frontend/networks/review/index.html +3 -3
  108. package/dist/web/frontend/parties/add/index.html +5 -5
  109. package/dist/web/frontend/parties/index.html +4 -4
  110. package/dist/web/frontend/settings/index.html +3 -3
  111. package/package.json +36 -32
  112. package/dist/web/frontend/assets/404-C7V2yl2z.js +0 -5
  113. package/dist/web/frontend/assets/addParty-j3JWZX5a.js +0 -41
  114. package/dist/web/frontend/assets/approve-43nVzAAC.js +0 -21
  115. package/dist/web/frontend/assets/callback-BBMiCjHR.js +0 -1
  116. package/dist/web/frontend/assets/index-DoNqknyE.js +0 -4042
@@ -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";
@@ -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";
@@ -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 {
@@ -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;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;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;;;;;;;;;;;;;;;;CAyVxB,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"}