@tstdl/base 0.93.178 → 0.93.180

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 (207) hide show
  1. package/api/response.js +4 -3
  2. package/api/server/gateway.js +9 -3
  3. package/audit/auditor.d.ts +1 -2
  4. package/audit/drizzle/{0000_lumpy_thunderball.sql → 0000_shallow_elektra.sql} +1 -1
  5. package/audit/drizzle/meta/0000_snapshot.json +2 -2
  6. package/audit/drizzle/meta/_journal.json +2 -2
  7. package/authentication/README.md +87 -42
  8. package/authentication/authentication.api.d.ts +392 -53
  9. package/authentication/authentication.api.js +133 -28
  10. package/authentication/client/api.client.d.ts +3 -3
  11. package/authentication/client/api.client.js +4 -4
  12. package/authentication/client/authentication.service.d.ts +93 -23
  13. package/authentication/client/authentication.service.js +113 -28
  14. package/authentication/client/http-client.middleware.d.ts +1 -1
  15. package/authentication/client/http-client.middleware.js +5 -4
  16. package/authentication/client/module.d.ts +1 -1
  17. package/authentication/client/module.js +2 -2
  18. package/authentication/errors/index.d.ts +1 -1
  19. package/authentication/errors/index.js +1 -1
  20. package/authentication/errors/password-requirements.error.d.ts +5 -0
  21. package/authentication/errors/{secret-requirements.error.js → password-requirements.error.js} +2 -2
  22. package/authentication/models/authentication-password.model.d.ts +8 -0
  23. package/authentication/models/{authentication-credentials.model.js → authentication-password.model.js} +11 -17
  24. package/authentication/models/authentication-session.model.d.ts +0 -2
  25. package/authentication/models/authentication-session.model.js +1 -7
  26. package/authentication/models/authentication-totp-recovery-code.model.d.ts +6 -0
  27. package/authentication/models/authentication-totp-recovery-code.model.js +34 -0
  28. package/authentication/models/authentication-totp.model.d.ts +19 -0
  29. package/authentication/models/authentication-totp.model.js +51 -0
  30. package/authentication/models/authentication-used-totp-token.model.d.ts +5 -0
  31. package/authentication/models/authentication-used-totp-token.model.js +32 -0
  32. package/authentication/models/index.d.ts +6 -3
  33. package/authentication/models/index.js +6 -3
  34. package/authentication/models/{init-secret-reset-data.model.d.ts → init-password-reset-data.model.d.ts} +3 -3
  35. package/authentication/models/{init-secret-reset-data.model.js → init-password-reset-data.model.js} +5 -5
  36. package/authentication/models/password-check-result.model.d.ts +3 -0
  37. package/authentication/models/{secret-check-result.model.js → password-check-result.model.js} +6 -6
  38. package/authentication/models/subject.model.d.ts +0 -6
  39. package/authentication/models/subject.model.js +0 -6
  40. package/authentication/models/token.model.d.ts +16 -2
  41. package/authentication/server/authentication-ancillary.service.d.ts +6 -6
  42. package/authentication/server/authentication-ancillary.service.js +1 -1
  43. package/authentication/server/authentication-password-requirements.validator.d.ts +55 -0
  44. package/authentication/server/{authentication-secret-requirements.validator.js → authentication-password-requirements.validator.js} +22 -22
  45. package/authentication/server/authentication.api-controller.d.ts +55 -27
  46. package/authentication/server/authentication.api-controller.js +214 -39
  47. package/authentication/server/authentication.audit.d.ts +42 -5
  48. package/authentication/server/authentication.service.d.ts +182 -93
  49. package/authentication/server/authentication.service.js +628 -206
  50. package/authentication/server/drizzle/{0000_soft_tag.sql → 0000_odd_echo.sql} +59 -13
  51. package/authentication/server/drizzle/meta/0000_snapshot.json +345 -32
  52. package/authentication/server/drizzle/meta/_journal.json +2 -2
  53. package/authentication/server/helper.d.ts +16 -16
  54. package/authentication/server/helper.js +33 -34
  55. package/authentication/server/index.d.ts +1 -1
  56. package/authentication/server/index.js +1 -1
  57. package/authentication/server/module.d.ts +2 -2
  58. package/authentication/server/module.js +4 -2
  59. package/authentication/server/schemas.d.ts +11 -7
  60. package/authentication/server/schemas.js +7 -3
  61. package/authentication/tests/authentication-password-requirements.validator.test.js +29 -0
  62. package/authentication/tests/authentication.api-controller.test.js +49 -15
  63. package/authentication/tests/authentication.client-error-handling.test.js +3 -2
  64. package/authentication/tests/authentication.client-middleware.test.js +5 -5
  65. package/authentication/tests/authentication.client-service-methods.test.js +28 -14
  66. package/authentication/tests/authentication.client-service-refresh.test.js +7 -6
  67. package/authentication/tests/authentication.client-service.test.js +10 -8
  68. package/authentication/tests/authentication.service.test.js +37 -29
  69. package/authentication/tests/authentication.test-ancillary-service.d.ts +1 -1
  70. package/authentication/tests/authentication.test-ancillary-service.js +1 -1
  71. package/authentication/tests/brute-force-protection.test.js +211 -0
  72. package/authentication/tests/helper.test.js +25 -21
  73. package/authentication/tests/password-requirements.error.test.js +14 -0
  74. package/authentication/tests/remember.api.test.js +22 -14
  75. package/authentication/tests/remember.service.test.js +23 -16
  76. package/authentication/tests/subject.service.test.js +2 -2
  77. package/authentication/tests/suspended-subject.test.d.ts +1 -0
  78. package/authentication/tests/suspended-subject.test.js +120 -0
  79. package/authentication/tests/totp.enrollment.test.d.ts +1 -0
  80. package/authentication/tests/totp.enrollment.test.js +123 -0
  81. package/authentication/tests/totp.login.test.d.ts +1 -0
  82. package/authentication/tests/totp.login.test.js +213 -0
  83. package/authentication/tests/totp.recovery-codes.test.d.ts +1 -0
  84. package/authentication/tests/totp.recovery-codes.test.js +97 -0
  85. package/authentication/tests/totp.status.test.d.ts +1 -0
  86. package/authentication/tests/totp.status.test.js +72 -0
  87. package/circuit-breaker/postgres/drizzle/{0000_cooing_korath.sql → 0000_same_captain_cross.sql} +1 -1
  88. package/circuit-breaker/postgres/drizzle/meta/0000_snapshot.json +2 -2
  89. package/circuit-breaker/postgres/drizzle/meta/_journal.json +2 -2
  90. package/cryptography/cryptography.d.ts +336 -0
  91. package/cryptography/cryptography.js +328 -0
  92. package/cryptography/index.d.ts +4 -0
  93. package/cryptography/index.js +4 -0
  94. package/{utils → cryptography}/jwt.d.ts +22 -4
  95. package/{utils → cryptography}/jwt.js +36 -18
  96. package/cryptography/module.d.ts +35 -0
  97. package/cryptography/module.js +148 -0
  98. package/cryptography/tests/cryptography.test.d.ts +1 -0
  99. package/cryptography/tests/cryptography.test.js +175 -0
  100. package/cryptography/tests/jwt.test.d.ts +1 -0
  101. package/cryptography/tests/jwt.test.js +54 -0
  102. package/cryptography/tests/modern.test.d.ts +1 -0
  103. package/cryptography/tests/modern.test.js +105 -0
  104. package/cryptography/tests/module.test.d.ts +1 -0
  105. package/cryptography/tests/module.test.js +100 -0
  106. package/cryptography/tests/totp.test.d.ts +1 -0
  107. package/cryptography/tests/totp.test.js +108 -0
  108. package/cryptography/totp.d.ts +96 -0
  109. package/cryptography/totp.js +123 -0
  110. package/document-management/server/drizzle/{0000_curious_nighthawk.sql → 0000_sharp_scream.sql} +21 -21
  111. package/document-management/server/drizzle/meta/0000_snapshot.json +22 -22
  112. package/document-management/server/drizzle/meta/_journal.json +2 -2
  113. package/document-management/server/services/document-file.service.js +1 -1
  114. package/errors/errors.localization.d.ts +2 -2
  115. package/errors/errors.localization.js +2 -2
  116. package/errors/index.d.ts +1 -0
  117. package/errors/index.js +1 -0
  118. package/errors/too-many-requests.error.d.ts +5 -0
  119. package/errors/too-many-requests.error.js +7 -0
  120. package/examples/api/authentication.js +5 -5
  121. package/examples/api/custom-authentication.js +4 -3
  122. package/file/server/mime-type.js +1 -1
  123. package/http/http-body.d.ts +1 -0
  124. package/http/http-body.js +3 -0
  125. package/image-service/imgproxy/imgproxy-image-service.d.ts +0 -1
  126. package/image-service/imgproxy/imgproxy-image-service.js +9 -27
  127. package/key-value-store/postgres/drizzle/{0000_shocking_slipstream.sql → 0000_moaning_calypso.sql} +1 -1
  128. package/key-value-store/postgres/drizzle/meta/0000_snapshot.json +2 -2
  129. package/key-value-store/postgres/drizzle/meta/_journal.json +2 -2
  130. package/lock/postgres/drizzle/{0000_busy_tattoo.sql → 0000_nappy_wraith.sql} +1 -1
  131. package/lock/postgres/drizzle/meta/0000_snapshot.json +2 -2
  132. package/lock/postgres/drizzle/meta/_journal.json +2 -2
  133. package/logger/formatters/json.js +1 -1
  134. package/logger/formatters/pretty-print.js +1 -1
  135. package/mail/drizzle/{0000_numerous_the_watchers.sql → 0000_cultured_quicksilver.sql} +2 -2
  136. package/mail/drizzle/meta/0000_snapshot.json +4 -4
  137. package/mail/drizzle/meta/_journal.json +2 -9
  138. package/notification/server/drizzle/{0000_wise_pyro.sql → 0000_new_tenebrous.sql} +6 -6
  139. package/notification/server/drizzle/meta/0000_snapshot.json +7 -7
  140. package/notification/server/drizzle/meta/_journal.json +2 -2
  141. package/notification/tests/notification-flow.test.js +1 -8
  142. package/notification/tests/notification-type.service.test.js +3 -3
  143. package/openid-connect/oidc.service.js +2 -3
  144. package/orm/data-types/common.js +1 -1
  145. package/orm/server/drizzle/schema-converter.js +9 -4
  146. package/orm/server/encryption.js +1 -1
  147. package/orm/server/module.d.ts +0 -1
  148. package/orm/server/module.js +0 -4
  149. package/orm/server/repository.d.ts +2 -1
  150. package/orm/server/repository.js +7 -10
  151. package/orm/tests/encryption.test.js +4 -6
  152. package/orm/tests/repository-extra-coverage.test.js +0 -2
  153. package/orm/tests/repository-regression.test.js +0 -3
  154. package/package.json +9 -8
  155. package/password/README.md +1 -1
  156. package/password/have-i-been-pwned.js +1 -1
  157. package/rate-limit/postgres/drizzle/{0000_watery_rage.sql → 0000_serious_sauron.sql} +1 -1
  158. package/rate-limit/postgres/drizzle/meta/0000_snapshot.json +2 -2
  159. package/rate-limit/postgres/drizzle/meta/_journal.json +2 -2
  160. package/rate-limit/postgres/postgres-rate-limiter.d.ts +1 -1
  161. package/rate-limit/postgres/postgres-rate-limiter.js +1 -1
  162. package/rate-limit/rate-limiter.d.ts +1 -1
  163. package/rpc/tests/rpc.integration.test.js +25 -31
  164. package/supports.d.ts +1 -0
  165. package/supports.js +1 -0
  166. package/task-queue/postgres/drizzle/{0000_faithful_daimon_hellstrom.sql → 0000_dark_ronan.sql} +5 -5
  167. package/task-queue/postgres/drizzle/meta/0000_snapshot.json +10 -10
  168. package/task-queue/postgres/drizzle/meta/_journal.json +2 -9
  169. package/task-queue/postgres/task-queue.js +2 -2
  170. package/task-queue/tests/coverage-enhancement.test.js +2 -2
  171. package/test/drizzle/{0000_natural_cannonball.sql → 0000_organic_gamora.sql} +2 -2
  172. package/test/drizzle/meta/0000_snapshot.json +3 -4
  173. package/test/drizzle/meta/_journal.json +2 -9
  174. package/testing/integration-setup.d.ts +7 -3
  175. package/testing/integration-setup.js +119 -96
  176. package/utils/alphabet.d.ts +1 -0
  177. package/utils/alphabet.js +1 -0
  178. package/utils/base32.d.ts +4 -0
  179. package/utils/base32.js +49 -0
  180. package/utils/base64.d.ts +0 -2
  181. package/utils/base64.js +6 -70
  182. package/utils/equals.d.ts +13 -3
  183. package/utils/equals.js +29 -9
  184. package/utils/index.d.ts +1 -2
  185. package/utils/index.js +1 -2
  186. package/utils/random.d.ts +1 -0
  187. package/utils/random.js +14 -8
  188. package/authentication/errors/secret-requirements.error.d.ts +0 -5
  189. package/authentication/models/authentication-credentials.model.d.ts +0 -10
  190. package/authentication/models/secret-check-result.model.d.ts +0 -3
  191. package/authentication/server/authentication-secret-requirements.validator.d.ts +0 -55
  192. package/authentication/tests/authentication-ancillary.service.test.js +0 -13
  193. package/authentication/tests/authentication-secret-requirements.validator.test.js +0 -29
  194. package/authentication/tests/secret-requirements.error.test.js +0 -14
  195. package/mail/drizzle/0001_married_tarantula.sql +0 -12
  196. package/mail/drizzle/meta/0001_snapshot.json +0 -69
  197. package/orm/server/tokens.d.ts +0 -1
  198. package/orm/server/tokens.js +0 -2
  199. package/task-queue/postgres/drizzle/0001_rapid_infant_terrible.sql +0 -16
  200. package/task-queue/postgres/drizzle/meta/0001_snapshot.json +0 -753
  201. package/test/drizzle/0001_closed_the_captain.sql +0 -2
  202. package/test/drizzle/meta/0001_snapshot.json +0 -117
  203. package/utils/cryptography.d.ts +0 -137
  204. package/utils/cryptography.js +0 -201
  205. /package/authentication/tests/{authentication-ancillary.service.test.d.ts → authentication-password-requirements.validator.test.d.ts} +0 -0
  206. /package/authentication/tests/{authentication-secret-requirements.validator.test.d.ts → brute-force-protection.test.d.ts} +0 -0
  207. /package/authentication/tests/{secret-requirements.error.test.d.ts → password-requirements.error.test.d.ts} +0 -0
@@ -59,6 +59,7 @@ import { AuthenticationApiClient } from '../authentication/client/api.client.js'
59
59
  import { configureAuthenticationClient } from '../authentication/client/index.js';
60
60
  import { AuthenticationApiController, configureAuthenticationServer } from '../authentication/server/index.js';
61
61
  import { configurePostgresCircuitBreaker } from '../circuit-breaker/postgres/module.js';
62
+ import { configureSecrets } from '../cryptography/module.js';
62
63
  import { configureDocumentManagement } from '../document-management/server/index.js';
63
64
  import { configureUndiciHttpClientAdapter } from '../http/client/adapters/undici.adapter.js';
64
65
  import { configureHttpClient } from '../http/client/index.js';
@@ -128,118 +129,140 @@ export async function setupIsolatedIntegrationTest(options = {}) {
128
129
  database: configParser.string('DATABASE_NAME', 'tstdl'),
129
130
  ...options.dbConfig,
130
131
  };
132
+ const schema = options.orm?.schema ?? 'test';
133
+ configureSecrets({ key: 'tstdl-unit-tests' });
131
134
  // 4. Configure ORM
132
135
  // We disable autoMigrate here because APPLICATION_INITIALIZER is not used in integration tests
133
136
  // We manually run migrations via bootstrapOrm below
134
137
  configureOrm({
135
- repositoryConfig: { schema: options.orm?.schema ?? 'test' },
138
+ repositoryConfig: { schema },
136
139
  connection: dbConfig,
137
- encryptionSecret: options.orm?.encryptionSecret,
138
140
  injector,
139
141
  });
140
142
  // 5. Database Resolution
141
143
  // We resolve the DB here to allow for immediate schema operations below
142
144
  const database = injector.resolve(Database);
143
- await runMigrationSafely(database, async () => {
144
- // 6. Schema Setup Helper
145
- // Automatically create the schema if a specific one was requested
145
+ for (const provider of options.providers ?? []) {
146
+ injector.register(provider.provide, provider, { multi: provider.multi });
147
+ }
148
+ // 7. Optional Modules
149
+ if (options.modules?.messageBus ?? options.modules?.taskQueue ?? options.modules?.authentication ?? options.modules?.notification) {
150
+ configureLocalMessageBus({ injector });
151
+ }
152
+ if (options.modules?.taskQueue) {
153
+ configurePostgresTaskQueue({ injector });
154
+ }
155
+ if (options.modules?.circuitBreaker ?? options.modules?.taskQueue) {
156
+ configurePostgresCircuitBreaker({ injector });
157
+ }
158
+ if (options.modules?.rateLimiter ?? options.modules?.taskQueue ?? options.modules?.authentication) {
159
+ configurePostgresRateLimiter({ injector });
160
+ }
161
+ if (options.modules?.keyValueStore ?? options.modules?.authentication) {
162
+ configurePostgresKeyValueStore({ injector });
163
+ }
164
+ if (options.modules?.lock ?? options.modules?.authentication) {
165
+ configurePostgresLock({ injector });
166
+ }
167
+ if (options.modules?.signals ?? options.modules?.authentication ?? options.modules?.notification) {
168
+ configureDefaultSignalsImplementation();
169
+ }
170
+ if (options.modules?.audit ?? options.modules?.authentication) {
171
+ configureAudit({ injector });
172
+ }
173
+ if (options.modules?.authentication) {
174
+ configureSecrets({ key: 'test-secret', injector });
175
+ const fastHashDeriveOptions = {
176
+ algorithm: {
177
+ name: 'Argon2id',
178
+ memory: 1024, // 1 MiB
179
+ parallelism: 1,
180
+ passes: 1,
181
+ },
182
+ };
183
+ const fastTotpHashingOptions = {
184
+ codeHashAlgorithm: 'SHA-1',
185
+ recoveryCodeHashOptions: {
186
+ algorithm: fastHashDeriveOptions.algorithm,
187
+ length: 64,
188
+ },
189
+ };
190
+ configureAuthenticationServer({
191
+ serviceOptions: {
192
+ passwordHashing: fastHashDeriveOptions,
193
+ totp: fastTotpHashingOptions,
194
+ ...options.authentication?.options,
195
+ },
196
+ authenticationAncillaryService: options.authentication?.ancillaryService,
197
+ injector,
198
+ });
199
+ }
200
+ if (options.modules?.notification) {
201
+ configureNotification({ injector });
202
+ }
203
+ if (options.modules?.documentManagement) {
204
+ configureDocumentManagement({
205
+ ancillaryService: undefined, // Should be overridden by test if needed
206
+ authorizationService: undefined, // Should be overridden by test if needed
207
+ fileObjectStorageModule: 'docs',
208
+ fileUploadObjectStorageModule: 'uploads',
209
+ filePreviewObjectStorageModule: 'previews',
210
+ skipAi: true,
211
+ injector,
212
+ });
213
+ }
214
+ if (options.modules?.objectStorage) {
215
+ const bucketPerModule = options.s3?.bucketPerModule ?? configParser.boolean('S3_BUCKET_PER_MODULE', true);
216
+ configureS3ObjectStorage({
217
+ endpoint: options.s3?.endpoint ?? configParser.string('S3_ENDPOINT', 'http://127.0.0.1:19552'),
218
+ accessKey: options.s3?.accessKey ?? configParser.string('S3_ACCESS_KEY', 'tstdl-dev'),
219
+ secretKey: options.s3?.secretKey ?? configParser.string('S3_SECRET_KEY', 'tstdl-dev'),
220
+ bucket: bucketPerModule ? undefined : (options.s3?.bucket ?? configParser.string('S3_BUCKET', 'test-bucket')),
221
+ bucketPerModule,
222
+ region: options.s3?.region ?? configParser.string('S3_REGION', 'us-east-1'),
223
+ forcePathStyle: options.s3?.forcePathStyle ?? configParser.boolean('S3_FORCE_PATH_STYLE', true),
224
+ injector,
225
+ });
226
+ }
227
+ if (options.modules?.api ?? options.modules?.authentication) {
228
+ configureNodeHttpServer({ trustedProxiesCount: 0, injector });
229
+ configureApiServer({ controllers: [AuthenticationApiController], injector });
230
+ configureUndiciHttpClientAdapter({ injector });
231
+ configureAuthenticationClient({
232
+ authenticationApiClient: AuthenticationApiClient,
233
+ registerMiddleware: true,
234
+ }, injector);
235
+ if (options.modules.webServer ?? options.modules.api ?? options.modules.authentication) {
236
+ const port = options.api?.port ?? 0;
237
+ configureWebServerModule({ port, injector });
238
+ }
239
+ }
240
+ await runMigrationSafely(database, schema, async () => {
146
241
  if (isDefined(options.orm?.schema)) {
147
242
  await database.execute(sql `CREATE SCHEMA IF NOT EXISTS ${sql.identifier(options.orm.schema)}`);
148
243
  }
149
- // 7. Optional Modules
150
- if (options.modules?.messageBus ?? options.modules?.taskQueue ?? options.modules?.authentication ?? options.modules?.notification) {
151
- configureLocalMessageBus({ injector });
152
- }
153
- if (options.modules?.taskQueue) {
154
- configurePostgresTaskQueue({ injector });
155
- }
156
- if (options.modules?.circuitBreaker ?? options.modules?.taskQueue) {
157
- configurePostgresCircuitBreaker({ injector });
158
- }
159
- if (options.modules?.rateLimiter ?? options.modules?.taskQueue) {
160
- configurePostgresRateLimiter({ injector });
161
- }
162
- if (options.modules?.keyValueStore ?? options.modules?.authentication) {
163
- configurePostgresKeyValueStore({ injector });
164
- }
165
- if (options.modules?.lock ?? options.modules?.authentication) {
166
- configurePostgresLock({ injector });
167
- }
168
- if (options.modules?.signals ?? options.modules?.authentication ?? options.modules?.notification) {
169
- configureDefaultSignalsImplementation();
170
- }
171
- if (options.modules?.audit ?? options.modules?.authentication) {
172
- configureAudit({ injector });
173
- }
174
- if (options.modules?.authentication) {
175
- configureAuthenticationServer({
176
- serviceOptions: {
177
- secret: 'test-secret',
178
- hashIterations: 10,
179
- signingSecretsDerivationIterations: 10,
180
- },
181
- authenticationAncillaryService: options.authenticationAncillaryService,
182
- injector,
183
- });
184
- }
185
- if (options.modules?.notification) {
186
- configureNotification({ injector });
187
- }
188
- if (options.modules?.documentManagement) {
189
- configureDocumentManagement({
190
- ancillaryService: undefined, // Should be overridden by test if needed
191
- authorizationService: undefined, // Should be overridden by test if needed
192
- fileObjectStorageModule: 'docs',
193
- fileUploadObjectStorageModule: 'uploads',
194
- filePreviewObjectStorageModule: 'previews',
195
- skipAi: true,
196
- injector,
197
- });
198
- }
199
244
  await runInInjectionContext(injector, bootstrapOrm);
200
- if (options.modules?.objectStorage) {
201
- const bucketPerModule = options.s3?.bucketPerModule ?? configParser.boolean('S3_BUCKET_PER_MODULE', true);
202
- configureS3ObjectStorage({
203
- endpoint: options.s3?.endpoint ?? configParser.string('S3_ENDPOINT', 'http://127.0.0.1:19552'),
204
- accessKey: options.s3?.accessKey ?? configParser.string('S3_ACCESS_KEY', 'tstdl-dev'),
205
- secretKey: options.s3?.secretKey ?? configParser.string('S3_SECRET_KEY', 'tstdl-dev'),
206
- bucket: bucketPerModule ? undefined : (options.s3?.bucket ?? configParser.string('S3_BUCKET', 'test-bucket')),
207
- bucketPerModule,
208
- region: options.s3?.region ?? configParser.string('S3_REGION', 'us-east-1'),
209
- forcePathStyle: options.s3?.forcePathStyle ?? configParser.boolean('S3_FORCE_PATH_STYLE', true),
210
- injector,
211
- });
212
- }
213
- if (options.modules?.api ?? options.modules?.authentication) {
214
- configureNodeHttpServer({ trustedProxiesCount: 0, injector });
215
- configureApiServer({ controllers: [AuthenticationApiController], injector });
216
- configureUndiciHttpClientAdapter({ injector });
217
- configureAuthenticationClient({
218
- authenticationApiClient: AuthenticationApiClient,
219
- registerMiddleware: true,
220
- }, injector);
221
- if (options.modules.webServer ?? options.modules.api ?? options.modules.authentication) {
222
- const port = options.api?.port ?? 0;
223
- configureWebServerModule({ port, injector });
224
- const webServerModule = await injector.resolveAsync(WebServerModule);
225
- const httpServer = await injector.resolveAsync(HttpServer);
226
- void webServerModule.run();
227
- // Wait for server to be listening
228
- while (true) {
229
- try {
230
- if (isNotNull(httpServer.port)) {
231
- break;
232
- }
233
- }
234
- catch {
235
- // ignore
245
+ });
246
+ if (options.modules?.api ?? options.modules?.authentication) {
247
+ if (options.modules.webServer ?? options.modules.api ?? options.modules.authentication) {
248
+ const webServerModule = await injector.resolveAsync(WebServerModule);
249
+ const httpServer = await injector.resolveAsync(HttpServer);
250
+ void webServerModule.run();
251
+ // Wait for server to be listening
252
+ while (true) {
253
+ try {
254
+ if (isNotNull(httpServer.port)) {
255
+ break;
236
256
  }
237
- await new Promise((resolve) => setTimeout(resolve, 10));
238
257
  }
239
- configureHttpClient({ baseUrl: options.api?.baseUrl ?? `http://localhost:${httpServer.port}`, injector });
258
+ catch {
259
+ // ignore
260
+ }
261
+ await new Promise((resolve) => setTimeout(resolve, 10));
240
262
  }
263
+ configureHttpClient({ baseUrl: options.api?.baseUrl ?? `http://localhost:${httpServer.port}`, injector });
241
264
  }
242
- });
265
+ }
243
266
  return { injector, database };
244
267
  }
245
268
  export const integrationTest = baseTest.extend({
@@ -358,8 +381,8 @@ export async function dropTables(database, schema, tables) {
358
381
  /**
359
382
  * Helper to run a migration safely with a database advisory lock to prevent race conditions.
360
383
  */
361
- async function runMigrationSafely(database, migration) {
362
- const lockName = 'tstdl:migration';
384
+ async function runMigrationSafely(database, schema, migration) {
385
+ const lockName = `tstdl:migration:${schema}`;
363
386
  await runWithAdvisoryLock(database, lockName, migration);
364
387
  }
365
388
  async function runWithAdvisoryLock(database, lockName, fn) {
@@ -8,6 +8,7 @@ export declare const Alphabet: {
8
8
  readonly UpperCaseNumbers: "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
9
9
  readonly LowerUpperCaseNumbers: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
10
10
  readonly ZBase32: "ybndrfg8ejkmcpqxot1uwisza345h769";
11
+ readonly Base32: "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
11
12
  readonly LowerCaseHex: "0123456789abcdef";
12
13
  readonly UpperCaseHex: "0123456789ABCDEF";
13
14
  };
package/utils/alphabet.js CHANGED
@@ -8,6 +8,7 @@ export const Alphabet = defineEnum('Alphabet', {
8
8
  UpperCaseNumbers: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
9
9
  LowerUpperCaseNumbers: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
10
10
  ZBase32: 'ybndrfg8ejkmcpqxot1uwisza345h769',
11
+ Base32: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
11
12
  LowerCaseHex: '0123456789abcdef',
12
13
  UpperCaseHex: '0123456789ABCDEF',
13
14
  });
@@ -0,0 +1,4 @@
1
+ /** biome-ignore-all lint/suspicious/noBitwiseOperators: ok */
2
+ import type { BinaryData } from '../types/index.js';
3
+ export declare function encodeBase32(buffer: BinaryData, padding?: boolean): string;
4
+ export declare function decodeBase32(input: string): Uint8Array<ArrayBuffer>;
@@ -0,0 +1,49 @@
1
+ /** biome-ignore-all lint/suspicious/noBitwiseOperators: ok */
2
+ import { Alphabet } from './alphabet.js';
3
+ import { toUint8Array } from './binary.js';
4
+ import { assertDefined } from './type-guards.js';
5
+ const alphabet = Alphabet.Base32;
6
+ const charValueMap = new Map(alphabet.split('').map((char, index) => [char, index]));
7
+ export function encodeBase32(buffer, padding = true) {
8
+ const byteView = toUint8Array(buffer);
9
+ let result = '';
10
+ let bits = 0;
11
+ let value = 0;
12
+ for (let i = 0; i < byteView.byteLength; i++) {
13
+ value = (value << 8) | byteView[i];
14
+ bits += 8;
15
+ while (bits >= 5) {
16
+ const quantum = (value >>> (bits - 5)) & 31;
17
+ result += alphabet[quantum];
18
+ bits -= 5;
19
+ }
20
+ }
21
+ if (bits > 0) {
22
+ result += alphabet[(value << (5 - bits)) & 31];
23
+ }
24
+ if (padding) {
25
+ while (result.length % 8 !== 0) {
26
+ result += '=';
27
+ }
28
+ }
29
+ return result;
30
+ }
31
+ export function decodeBase32(input) {
32
+ const cleanedInput = input.replace(/=/g, '').toUpperCase();
33
+ const bytes = new Uint8Array((cleanedInput.length * 5 / 8) | 0);
34
+ let bits = 0;
35
+ let value = 0;
36
+ let byteIndex = 0;
37
+ for (let i = 0; i < cleanedInput.length; i++) {
38
+ const char = cleanedInput[i];
39
+ const charValue = charValueMap.get(char);
40
+ assertDefined(charValue, `Invalid base32 character at index ${i}.`);
41
+ value = (value << 5) | charValue;
42
+ bits += 5;
43
+ if (bits >= 8) {
44
+ bytes[byteIndex++] = (value >>> (bits - 8)) & 255;
45
+ bits -= 8;
46
+ }
47
+ }
48
+ return bytes;
49
+ }
package/utils/base64.d.ts CHANGED
@@ -12,5 +12,3 @@ export declare function encodeBase64Url(array: BinaryData, bytesOffset?: number,
12
12
  export declare function decodeBase64Url(base64Url: string): Uint8Array<ArrayBuffer>;
13
13
  export declare function base64ToBase64Url(input: string): string;
14
14
  export declare function base64UrlToBase64(input: string): string;
15
- export declare function utf8ArrayToString(bytes: Uint8Array): string;
16
- export declare function stringToUtf8Array(string: string): Uint8Array<ArrayBuffer>;
package/utils/base64.js CHANGED
@@ -44,14 +44,14 @@ export function decodeBase64Url(base64Url) {
44
44
  }
45
45
  export function base64ToBase64Url(input) {
46
46
  return input
47
- .replace(/=/ug, '')
48
- .replace(/\+/ug, '-')
49
- .replace(/\//ug, '_');
47
+ .replace(/=/g, '')
48
+ .replace(/\+/g, '-')
49
+ .replace(/\//g, '_');
50
50
  }
51
51
  export function base64UrlToBase64(input) {
52
52
  return input.padEnd(input.length + ((4 - (input.length % 4)) % 4), '=')
53
- .replace(/-/ug, '+')
54
- .replace(/_/ug, '/');
53
+ .replace(/-/g, '+')
54
+ .replace(/_/g, '/');
55
55
  }
56
56
  function encodeBase64Fallback(data) {
57
57
  const bytes = toUint8Array(data);
@@ -69,7 +69,7 @@ function encodeBase64Fallback(data) {
69
69
  return base64.substring(0, base64.length - 2 + nMod3) + (nMod3 == 2 ? '' : nMod3 == 1 ? '=' : '==');
70
70
  }
71
71
  function decodeBase64Fallback(base64, blockSize) {
72
- const clearedBase64 = base64.replace(/[^A-Za-z0-9+/]/ug, '');
72
+ const clearedBase64 = base64.replace(/[^A-Za-z0-9+/]/g, '');
73
73
  const inputLength = clearedBase64.length;
74
74
  const outputLength = isDefined(blockSize) ? Math.ceil(((inputLength * 3) + 1 >> 2) / blockSize) * blockSize : (inputLength * 3) + 1 >> 2;
75
75
  const bytes = new Uint8Array(outputLength);
@@ -87,70 +87,6 @@ function decodeBase64Fallback(base64, blockSize) {
87
87
  }
88
88
  return bytes;
89
89
  }
90
- export function utf8ArrayToString(bytes) {
91
- let string = '';
92
- for (let index = 0; index < bytes.length; index++) {
93
- const byte = bytes[index];
94
- const charCode = byte > 251 && byte < 254 && index + 5 < bytes.length
95
- ? ((byte - 252) * 1073741824) + (bytes[++index] - 128 << 24) + (bytes[++index] - 128 << 18) + (bytes[++index] - 128 << 12) + (bytes[++index] - 128 << 6) + bytes[++index] - 128
96
- : byte > 247 && byte < 252 && index + 4 < bytes.length
97
- ? (byte - 248 << 24) + (bytes[++index] - 128 << 18) + (bytes[++index] - 128 << 12) + (bytes[++index] - 128 << 6) + bytes[++index] - 128
98
- : byte > 239 && byte < 248 && index + 3 < bytes.length
99
- ? (byte - 240 << 18) + (bytes[++index] - 128 << 12) + (bytes[++index] - 128 << 6) + bytes[++index] - 128
100
- : byte > 223 && byte < 240 && index + 2 < bytes.length
101
- ? (byte - 224 << 12) + (bytes[++index] - 128 << 6) + bytes[++index] - 128
102
- : byte > 191 && byte < 224 && index + 1 < bytes.length
103
- ? (byte - 192 << 6) + bytes[++index] - 128
104
- : byte;
105
- string += String.fromCharCode(charCode);
106
- }
107
- return string;
108
- }
109
- export function stringToUtf8Array(string) {
110
- let bytesLength = 0;
111
- for (let i = 0; i < string.length; i++) {
112
- const char = string.charCodeAt(i);
113
- bytesLength += char < 0x80 ? 1 : char < 0x800 ? 2 : char < 0x10000 ? 3 : char < 0x200000 ? 4 : char < 0x4000000 ? 5 : 6;
114
- }
115
- const bytes = new Uint8Array(bytesLength);
116
- for (let byteIndex = 0, charIndex = 0; byteIndex < bytesLength; charIndex++) {
117
- const char = string.charCodeAt(charIndex);
118
- if (char < 128) {
119
- bytes[byteIndex++] = char;
120
- }
121
- else if (char < 0x800) {
122
- bytes[byteIndex++] = 192 + (char >>> 6);
123
- bytes[byteIndex++] = 128 + (char & 63);
124
- }
125
- else if (char < 0x10000) {
126
- bytes[byteIndex++] = 224 + (char >>> 12);
127
- bytes[byteIndex++] = 128 + ((char >>> 6) & 63);
128
- bytes[byteIndex++] = 128 + (char & 63);
129
- }
130
- else if (char < 0x200000) {
131
- bytes[byteIndex++] = 240 + (char >>> 18);
132
- bytes[byteIndex++] = 128 + ((char >>> 12) & 63);
133
- bytes[byteIndex++] = 128 + ((char >>> 6) & 63);
134
- bytes[byteIndex++] = 128 + (char & 63);
135
- }
136
- else if (char < 0x4000000) {
137
- bytes[byteIndex++] = 248 + (char >>> 24);
138
- bytes[byteIndex++] = 128 + ((char >>> 18) & 63);
139
- bytes[byteIndex++] = 128 + ((char >>> 12) & 63);
140
- bytes[byteIndex++] = 128 + ((char >>> 6) & 63);
141
- bytes[byteIndex++] = 128 + (char & 63);
142
- }
143
- else {
144
- bytes[byteIndex++] = 252 + (char >>> 30);
145
- bytes[byteIndex++] = 128 + ((char >>> 24) & 63);
146
- bytes[byteIndex++] = 128 + ((char >>> 18) & 63);
147
- bytes[byteIndex++] = 128 + ((char >>> 12) & 63);
148
- bytes[byteIndex++] = 128 + ((char >>> 6) & 63);
149
- bytes[byteIndex++] = 128 + (char & 63);
150
- }
151
- }
152
- return bytes;
153
- }
154
90
  function uint6ToBase64(nUint6) {
155
91
  return nUint6 < 26
156
92
  ? nUint6 + 65
package/utils/equals.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /** biome-ignore-all lint/suspicious/noBitwiseOperators: low level */
1
2
  import type { BinaryData } from '../types/index.js';
2
3
  import type { Comparator } from './sort.js';
3
4
  export interface Equals<T = unknown> {
@@ -29,9 +30,18 @@ export declare function equals(a: any, b: any, options?: EqualsOptions, __intern
29
30
  export declare function binaryEquals(bufferA: BinaryData, bufferB: BinaryData): boolean;
30
31
  /**
31
32
  * Compares two buffers in a way that is resistant to timing attacks.
32
- * @param untrusted The first buffer to compare. It's length is used to determine the iterations of the comparison. This should be the untrusted input to minimize information leakage.
33
- * @param trusted The second buffer to compare. This should be the secret.
33
+ * It short-circuits on length mismatch. So length is not a secret.
34
+ * @param a The first buffer to compare.
35
+ * @param b The second buffer to compare.
34
36
  * @returns True if the buffers are equal, false otherwise.
35
37
  */
36
- export declare function timingSafeBinaryEquals(untrusted: BinaryData, trusted: BinaryData): boolean;
38
+ export declare function timingSafeBinaryEquals(a: BinaryData, b: BinaryData): boolean;
39
+ /**
40
+ * Compares two strings in a way that is resistant to timing attacks.
41
+ * It short-circuits on length mismatch. So length is not a secret.
42
+ * @param a The first string to compare. Its length is used to determine the iterations of the comparison.
43
+ * @param b The second string to compare. This should be the secret.
44
+ * @returns True if the strings are equal, false otherwise.
45
+ */
46
+ export declare function timingSafeStringEquals(a: string, b: string): boolean;
37
47
  export {};
package/utils/equals.js CHANGED
@@ -1,6 +1,8 @@
1
+ /** biome-ignore-all lint/suspicious/noBitwiseOperators: low level */
1
2
  import { toArray } from './array/array.js';
2
3
  import { toUint8Array } from './binary.js';
3
4
  import { compareByValue } from './comparison.js';
5
+ import { encodeUtf8 } from './encoding.js';
4
6
  import { sort } from './iterable-helpers/sort.js';
5
7
  import { objectKeys } from './object/object.js';
6
8
  import { isArray, isDefined, isNotNull, isNull } from './type-guards.js';
@@ -116,17 +118,35 @@ export function binaryEquals(bufferA, bufferB) {
116
118
  }
117
119
  /**
118
120
  * Compares two buffers in a way that is resistant to timing attacks.
119
- * @param untrusted The first buffer to compare. It's length is used to determine the iterations of the comparison. This should be the untrusted input to minimize information leakage.
120
- * @param trusted The second buffer to compare. This should be the secret.
121
+ * It short-circuits on length mismatch. So length is not a secret.
122
+ * @param a The first buffer to compare.
123
+ * @param b The second buffer to compare.
121
124
  * @returns True if the buffers are equal, false otherwise.
122
125
  */
123
- export function timingSafeBinaryEquals(untrusted, trusted) {
124
- const a = toUint8Array(untrusted, false);
125
- const b = toUint8Array(trusted, false);
126
- const compareTarget = (a.length > b.length) ? a : b;
127
- let diff = a.length ^ b.length;
128
- for (let i = 0; i < a.length; i++) {
129
- diff |= a[i] ^ compareTarget[i];
126
+ export function timingSafeBinaryEquals(a, b) {
127
+ const aArray = toUint8Array(a, false);
128
+ const bArray = toUint8Array(b, false);
129
+ if (aArray.length != bArray.length) {
130
+ return false;
131
+ }
132
+ let diff = 0;
133
+ for (let i = 0; i < aArray.length; i++) {
134
+ diff |= aArray[i] ^ bArray[i];
130
135
  }
131
136
  return diff == 0;
132
137
  }
138
+ /**
139
+ * Compares two strings in a way that is resistant to timing attacks.
140
+ * It short-circuits on length mismatch. So length is not a secret.
141
+ * @param a The first string to compare. Its length is used to determine the iterations of the comparison.
142
+ * @param b The second string to compare. This should be the secret.
143
+ * @returns True if the strings are equal, false otherwise.
144
+ */
145
+ export function timingSafeStringEquals(a, b) {
146
+ if (a.length != b.length) {
147
+ return false;
148
+ }
149
+ const aBytes = encodeUtf8(a);
150
+ const bBytes = encodeUtf8(b);
151
+ return timingSafeBinaryEquals(aBytes, bBytes);
152
+ }
package/utils/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export * from './alphabet.js';
2
2
  export * from './any-iterable-iterator.js';
3
3
  export * from './async-iterator-iterable-iterator.js';
4
4
  export * from './backoff.js';
5
+ export * from './base32.js';
5
6
  export * from './base64.js';
6
7
  export * from './benchmark.js';
7
8
  export * from './binary-search.js';
@@ -11,7 +12,6 @@ export * from './comparison.js';
11
12
  export * from './compression.js';
12
13
  export * from './config-parser.js';
13
14
  export * from './crc32.js';
14
- export * from './cryptography.js';
15
15
  export * from './date-time.js';
16
16
  export * from './encoding.js';
17
17
  export * from './enum.js';
@@ -23,7 +23,6 @@ export * from './format.js';
23
23
  export * from './helpers.js';
24
24
  export * from './html-link-utils.js';
25
25
  export * from './image.js';
26
- export * from './jwt.js';
27
26
  export * from './map.js';
28
27
  export * from './math.js';
29
28
  export * from './merge.js';
package/utils/index.js CHANGED
@@ -2,6 +2,7 @@ export * from './alphabet.js';
2
2
  export * from './any-iterable-iterator.js';
3
3
  export * from './async-iterator-iterable-iterator.js';
4
4
  export * from './backoff.js';
5
+ export * from './base32.js';
5
6
  export * from './base64.js';
6
7
  export * from './benchmark.js';
7
8
  export * from './binary-search.js';
@@ -11,7 +12,6 @@ export * from './comparison.js';
11
12
  export * from './compression.js';
12
13
  export * from './config-parser.js';
13
14
  export * from './crc32.js';
14
- export * from './cryptography.js';
15
15
  export * from './date-time.js';
16
16
  export * from './encoding.js';
17
17
  export * from './enum.js';
@@ -23,7 +23,6 @@ export * from './format.js';
23
23
  export * from './helpers.js';
24
24
  export * from './html-link-utils.js';
25
25
  export * from './image.js';
26
- export * from './jwt.js';
27
26
  export * from './map.js';
28
27
  export * from './math.js';
29
28
  export * from './merge.js';
package/utils/random.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /** biome-ignore-all lint/suspicious/noBitwiseOperators: low level */
1
2
  /**
2
3
  * Generate cryptographically secure random bytes
3
4
  *
package/utils/random.js CHANGED
@@ -1,3 +1,4 @@
1
+ /** biome-ignore-all lint/suspicious/noBitwiseOperators: low level */
1
2
  import { Alphabet } from './alphabet.js';
2
3
  const bufferSize = 20480;
3
4
  const bufferBypassThreshold = (bufferSize / 2) + 1;
@@ -31,16 +32,21 @@ export function getRandomBytes(count, allowUnsafe = false) {
31
32
  * @param alphabet alphabet to choose characters from. Defaults to {@link Alphabet.LowerUpperCaseNumbers}
32
33
  */
33
34
  export function getRandomString(length, alphabet = Alphabet.LowerUpperCaseNumbers) {
34
- let result = '';
35
35
  if (length < 1) {
36
- return result;
36
+ return '';
37
37
  }
38
- const bytes = getRandomBytes(length * Uint16Array.BYTES_PER_ELEMENT, true);
39
- const values = new Uint16Array(bytes.buffer, bytes.byteOffset, length);
40
- const factor = alphabet.length / (2 ** (Uint16Array.BYTES_PER_ELEMENT * 8));
41
- for (let i = 0; i < length; i++) {
42
- const index = Math.floor(values[i] * factor);
43
- result += alphabet[index];
38
+ const alphabetLength = alphabet.length;
39
+ const mask = (2 << (31 - Math.clz32(alphabetLength - 1))) - 1;
40
+ const step = Math.ceil((1.6 * mask * length) / alphabetLength);
41
+ let result = '';
42
+ while (result.length < length) {
43
+ const bytes = getRandomBytes(step, true);
44
+ for (let i = 0; i < bytes.length && result.length < length; i++) {
45
+ const byte = bytes[i] & mask;
46
+ if (byte < alphabetLength) {
47
+ result += alphabet[byte];
48
+ }
49
+ }
44
50
  }
45
51
  return result;
46
52
  }
@@ -1,5 +0,0 @@
1
- import { CustomError } from '../../errors/custom.error.js';
2
- export declare class SecretRequirementsError extends CustomError {
3
- static readonly errorName = "SecretRequirementsError";
4
- constructor(message?: string);
5
- }
@@ -1,10 +0,0 @@
1
- import { TenantEntity, type Uuid } from '../../orm/index.js';
2
- export declare class AuthenticationCredentials extends TenantEntity {
3
- subjectId: Uuid;
4
- /** The version of the hash algorithm used. */
5
- hashVersion: number;
6
- /** The salt used to hash the secret. */
7
- salt: Uint8Array<ArrayBuffer>;
8
- /** The hashed secret. */
9
- hash: Uint8Array<ArrayBuffer>;
10
- }
@@ -1,3 +0,0 @@
1
- import { PasswordCheckResult } from '../../password/password-check-result.model.js';
2
- export declare class SecretCheckResult extends PasswordCheckResult {
3
- }