@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
package/api/response.js CHANGED
@@ -1,7 +1,7 @@
1
- import { SecretRequirementsError } from '../authentication/errors/secret-requirements.error.js';
1
+ import { PasswordRequirementsError } from '../authentication/errors/password-requirements.error.js';
2
2
  import { formatError } from '../errors/index.js';
3
3
  import { SchemaError } from '../schema/schema.error.js';
4
- import { ApiError, BadRequestError, ForbiddenError, InvalidCredentialsError, InvalidTokenError, MaxBytesExceededError, MethodNotAllowedError, NotFoundError, NotImplementedError, NotSupportedError, UnauthorizedError, UnsupportedMediaTypeError } from '../errors/index.js';
4
+ import { ApiError, BadRequestError, ForbiddenError, InvalidCredentialsError, InvalidTokenError, MaxBytesExceededError, MethodNotAllowedError, NotFoundError, NotImplementedError, NotSupportedError, TooManyRequestsError, UnauthorizedError, UnsupportedMediaTypeError } from '../errors/index.js';
5
5
  import { assertString, isDefined, isFunction, isObject, isString } from '../utils/type-guards.js';
6
6
  import { deserializeSchemaError, serializeSchemaError } from './default-error-handlers.js';
7
7
  const errorHandlers = new Map();
@@ -99,7 +99,8 @@ registerErrorHandler(NotFoundError, 404, () => undefined, (_, error) => new NotF
99
99
  registerErrorHandler(NotImplementedError, 501, () => undefined, (_, error) => new NotImplementedError(error.message));
100
100
  registerErrorHandler(NotSupportedError, 400, () => undefined, (_, error) => new NotSupportedError(error.message));
101
101
  registerErrorHandler(SchemaError, 400, serializeSchemaError, (data, error) => deserializeSchemaError(error.message, data));
102
- registerErrorHandler(SecretRequirementsError, 403, () => undefined, (_, error) => new SecretRequirementsError(error.message));
102
+ registerErrorHandler(PasswordRequirementsError, 403, () => undefined, (_, error) => new PasswordRequirementsError(error.message));
103
+ registerErrorHandler(TooManyRequestsError, 429, () => undefined, (_, error) => new TooManyRequestsError(error.message));
103
104
  registerErrorHandler(UnauthorizedError, 401, () => undefined, (_, error) => new UnauthorizedError(error.message));
104
105
  registerErrorHandler(UnsupportedMediaTypeError, 415, () => undefined, (_, error) => new UnsupportedMediaTypeError(error.message));
105
106
  // biome-ignore-end lint/style/noMagicNumbers: http status codes
@@ -15,6 +15,7 @@ import 'urlpattern-polyfill';
15
15
  import { Auditor } from '../../audit/auditor.js';
16
16
  import { ActorType } from '../../audit/types.js';
17
17
  import { NIL_UUID } from '../../constants.js';
18
+ import { isDevMode } from '../../core.js';
18
19
  import { BadRequestError, InvalidTokenError, NotFoundError, NotImplementedError } from '../../errors/index.js';
19
20
  import { HttpServerResponse } from '../../http/server/index.js';
20
21
  import { inject, injectArgument, resolveArgumentType, Singleton } from '../../injector/index.js';
@@ -23,11 +24,12 @@ import { Schema } from '../../schema/index.js';
23
24
  import { DataStreamSource } from '../../sse/data-stream-source.js';
24
25
  import { DataStream } from '../../sse/data-stream.js';
25
26
  import { ServerSentEventsSource } from '../../sse/server-sent-events-source.js';
27
+ import { ServerSentEvents } from '../../sse/server-sent-events.js';
26
28
  import { toArray } from '../../utils/array/array.js';
27
29
  import { composeAsyncMiddleware } from '../../utils/middleware.js';
28
30
  import { mapObjectValues } from '../../utils/object/object.js';
29
31
  import { deferThrow } from '../../utils/throw.js';
30
- import { isArray, isBlob, isDefined, isNotNull, isNotNullOrUndefined, isNull, isNullOrUndefined, isObject, isReadableStream, isUint8Array, isUndefined } from '../../utils/type-guards.js';
32
+ import { isArray, isBlob, isDefined, isNotInstanceOf, isNotNull, isNotNullOrUndefined, isNull, isNullOrUndefined, isObject, isReadableStream, isUint8Array, isUndefined } from '../../utils/type-guards.js';
31
33
  import { mebibyte } from '../../utils/units.js';
32
34
  import { logAndGetErrorResponse } from '../response.js';
33
35
  import { normalizedApiDefinition, normalizedApiDefinitionEndpointsEntries } from '../types.js';
@@ -242,19 +244,23 @@ let ApiGateway = ApiGateway_1 = class ApiGateway {
242
244
  context.response.update(result);
243
245
  }
244
246
  else {
247
+ const endpointDefinitionResult = context.endpoint.definition.result;
248
+ if (isDevMode() && isDefined(endpointDefinitionResult) && isNotInstanceOf(endpointDefinitionResult, DataStream) && isNotInstanceOf(endpointDefinitionResult, ServerSentEvents)) {
249
+ Schema.assert(endpointDefinitionResult, result);
250
+ }
245
251
  context.response.body = match(result)
246
252
  .when(isUint8Array, (buffer) => ({ buffer }))
247
253
  .when(isBlob, (value) => ({ stream: value.stream() }))
248
254
  .when((isReadableStream), (stream) => ({ stream }))
249
255
  .when((value) => value instanceof ServerSentEventsSource, (events) => ({ events }))
250
- .when(() => (context.endpoint.definition.result == DataStream), (value) => {
256
+ .when(() => (endpointDefinitionResult == DataStream), (value) => {
251
257
  const errorFormatter = (error) => {
252
258
  const { errorResponse } = logAndGetErrorResponse(this.#logger, this.#supressedErrors, error);
253
259
  return errorResponse.error;
254
260
  };
255
261
  return ({ events: DataStreamSource.fromIterable(value, { errorFormatter, ...context.endpoint.definition.dataStream }).eventSource });
256
262
  })
257
- .when(() => (context.endpoint.definition.result == String), (text) => ({ text: text }))
263
+ .when(() => (endpointDefinitionResult == String), (text) => ({ text: text }))
258
264
  .otherwise((json) => ({ json }));
259
265
  }
260
266
  await next();
@@ -35,7 +35,7 @@ export type AuditorArgument = string | string[] | {
35
35
  */
36
36
  context?: Partial<AuditPayload>;
37
37
  };
38
- type AuditEvents = Record<string, UndefinableJsonObject>;
38
+ export type AuditEvents = Record<string, UndefinableJsonObject>;
39
39
  /**
40
40
  * A service for logging audit events.
41
41
  * It provides a structured way to record activities within the system.
@@ -124,4 +124,3 @@ export declare class Auditor<Events extends AuditEvents = AuditEvents> implement
124
124
  */
125
125
  critical<const E extends Extract<keyof Events, string>>(action: E, data?: AuditPayload<Events[E]>): Promise<void>;
126
126
  }
127
- export {};
@@ -2,7 +2,7 @@ CREATE TYPE "audit"."actor_type" AS ENUM('anonymous', 'system', 'api-key', 'subj
2
2
  CREATE TYPE "audit"."audit_outcome" AS ENUM('pending', 'success', 'cancelled', 'failure', 'denied');--> statement-breakpoint
3
3
  CREATE TYPE "audit"."audit_severity" AS ENUM('info', 'warn', 'error', 'critical');--> statement-breakpoint
4
4
  CREATE TABLE "audit"."event" (
5
- "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
5
+ "id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
6
6
  "timestamp" timestamp with time zone NOT NULL,
7
7
  "tenant_id" uuid,
8
8
  "correlation_id" uuid,
@@ -1,5 +1,5 @@
1
1
  {
2
- "id": "e6910cc2-1674-462e-be27-f94e3372393f",
2
+ "id": "ae3f33c2-9768-4d5d-8878-7779bf73b52a",
3
3
  "prevId": "00000000-0000-0000-0000-000000000000",
4
4
  "version": "7",
5
5
  "dialect": "postgresql",
@@ -13,7 +13,7 @@
13
13
  "type": "uuid",
14
14
  "primaryKey": true,
15
15
  "notNull": true,
16
- "default": "gen_random_uuid()"
16
+ "default": "uuidv7()"
17
17
  },
18
18
  "timestamp": {
19
19
  "name": "timestamp",
@@ -5,8 +5,8 @@
5
5
  {
6
6
  "idx": 0,
7
7
  "version": "7",
8
- "when": 1768666123825,
9
- "tag": "0000_lumpy_thunderball",
8
+ "when": 1774646420544,
9
+ "tag": "0000_shallow_elektra",
10
10
  "breakpoints": true
11
11
  }
12
12
  ]
@@ -1,6 +1,6 @@
1
1
  # @tstdl/base/authentication
2
2
 
3
- A comprehensive, secure, and type-safe authentication module providing JWT-based session management, credential handling, and extensible hooks for both server and client environments.
3
+ A comprehensive, secure, and type-safe authentication module providing JWT-based session management, password and passkey handling, and extensible hooks for both server and client environments.
4
4
 
5
5
  ## Table of Contents
6
6
 
@@ -16,7 +16,7 @@ A comprehensive, secure, and type-safe authentication module providing JWT-based
16
16
  - [🔧 Advanced Topics](#-advanced-topics)
17
17
  - [Custom Token Payloads & Authentication Data](#custom-token-payloads--authentication-data)
18
18
  - [Impersonation](#impersonation)
19
- - [Secret Validation](#secret-validation)
19
+ - [Password Validation](#password-validation)
20
20
  - [HTTP Client Middleware](#http-client-middleware)
21
21
  - [📚 API](#-api)
22
22
 
@@ -28,8 +28,9 @@ A comprehensive, secure, and type-safe authentication module providing JWT-based
28
28
  - **Reactive Client State**: The client service exposes authentication state (token, session, subject) via Signals and RxJS Observables.
29
29
  - **Impersonation**: Built-in support for administrators to securely log in as other users.
30
30
  - **Subject Diversity**: Supports `User`, `ServiceAccount`, and `SystemAccount` types out of the box.
31
- - **Secret Management**: Includes flows for changing passwords and secure, token-based password resets.
32
- - **Audit Logging**: Automatically logs security events (login success/failure, password changes) via the `@tstdl/base/audit` system.
31
+ - **Password Management**: Includes flows for changing passwords and secure, token-based password resets.
32
+ - **Audit Logging**: Automatically logs security events (login success/failure, password changes, TOTP enrollment/verification) via the `@tstdl/base/audit` system.
33
+ - **TOTP Multi-Factor Authentication**: Built-in support for Time-Based One-Time Passwords (TOTP) with authenticator apps, recovery codes, and rate-limited multi-step login flows.
33
34
  - **Extensible**: Abstract classes allow you to inject custom logic for subject resolution, token payload enrichment, and permission checks.
34
35
 
35
36
  ## Core Concepts
@@ -46,9 +47,9 @@ The module uses a polymorphic `Subject` system to represent different types of e
46
47
 
47
48
  The server component revolves around the `AuthenticationService`. It handles:
48
49
 
49
- - **Credential Storage**: Manages the `AuthenticationCredentials` entity (subject, hash, salt).
50
+ - **Password Storage**: Manages the `AuthenticationPassword` entity (subject, hash, salt).
50
51
  - **Session Tracking**: Manages the `AuthenticationSession` entity, allowing for server-side session revocation.
51
- - **Token Issuance**: Generates signed JWTs using configured secrets.
52
+ - **Token Issuance**: Generates signed JWTs using configured signing keys.
52
53
  - **Validation**: Verifies tokens and handles the refresh flow.
53
54
 
54
55
  ### Client-Side Architecture
@@ -57,6 +58,7 @@ The `AuthenticationClientService` manages the lifecycle in the browser:
57
58
 
58
59
  - **Storage**: Persists tokens securely in `localStorage`.
59
60
  - **Auto-Refresh**: Automatically refreshes the access token before expiration using a synchronized lock mechanism (works across tabs).
61
+ - **MFA Support**: Handles the multi-step login flow when TOTP is enabled.
60
62
  - **State**: Provides reactive signals like `isLoggedIn`, `token`, and `subjectId`.
61
63
 
62
64
  ### Extensibility Hooks
@@ -96,8 +98,8 @@ To integrate this module with your application, implement the `AuthenticationAnc
96
98
  return {};
97
99
  }
98
100
 
99
- // Handle secret reset (e.g., send email)
100
- override async handleInitSecretReset(data: any): Promise<void> {
101
+ // Handle password reset (e.g., send email)
102
+ override async handleInitPasswordReset(data: any): Promise<void> {
101
103
  console.log(`Send reset email to ${data.subject.id} with token ${data.token}`);
102
104
  }
103
105
 
@@ -172,6 +174,48 @@ To integrate this module with your application, implement the `AuthenticationAnc
172
174
 
173
175
  ## 🔧 Advanced Topics
174
176
 
177
+ ### TOTP Multi-Factor Authentication
178
+
179
+ The module supports a two-step login flow for accounts with TOTP enabled.
180
+
181
+ #### 1. Enrollment (with Proof of Possession)
182
+
183
+ Users must verify a code from their authenticator app before TOTP is activated.
184
+
185
+ ```typescript
186
+ // Client
187
+ const { secret, uri } = await authService.initEnrollTotp();
188
+ // ... Display QR code using the URI or manual entry using the secret ...
189
+
190
+ // Submit the first code to complete enrollment and get recovery codes
191
+ const { recoveryCodes } = await authService.completeEnrollTotp(userEnteredToken);
192
+ ```
193
+
194
+ #### 2. Multi-Step Login
195
+
196
+ When a user logs in with their password, the `login` method may return a TOTP challenge.
197
+
198
+ ```typescript
199
+ // Client
200
+ const result = await authService.login(email, password);
201
+
202
+ if (result?.type == 'totp') {
203
+ const challengeToken = result.challengeToken;
204
+ // ... Prompt user for 6-digit code or recovery code ...
205
+ await authService.verifyTotpLogin(challengeToken, userEnteredCodeOrRecoveryCode);
206
+ }
207
+
208
+ // User is now logged in
209
+ ```
210
+
211
+ #### 3. Disenrollment
212
+
213
+ Users can disable TOTP by providing a valid code.
214
+
215
+ ```typescript
216
+ await authService.disableTotp(userEnteredCodeOrRecoveryCode);
217
+ ```
218
+
175
219
  ### Custom Token Payloads & Authentication Data
176
220
 
177
221
  You can strongly type the extra data in your JWTs and the data passed during login.
@@ -217,23 +261,23 @@ if (authService.impersonated()) {
217
261
  await authService.unimpersonate();
218
262
  ```
219
263
 
220
- ### Secret Validation
264
+ ### Password Validation
221
265
 
222
- By default, the module checks for password strength and known data breaches (pwned passwords). You can override this by implementing `AuthenticationSecretRequirementsValidator`.
266
+ By default, the module checks for password strength and known data breaches (pwned passwords). You can override this by implementing `AuthenticationPasswordRequirementsValidator`.
223
267
 
224
268
  ```typescript
225
- import { AuthenticationSecretRequirementsValidator, type SecretCheckResult, type SecretTestResult } from '@tstdl/base/authentication/server';
269
+ import { AuthenticationPasswordRequirementsValidator, type PasswordCheckResult, type PasswordTestResult } from '@tstdl/base/authentication/server';
226
270
  import { Singleton } from '@tstdl/base/injector';
227
271
 
228
- @Singleton({ alias: AuthenticationSecretRequirementsValidator })
229
- export class MySecretValidator extends AuthenticationSecretRequirementsValidator {
230
- override async checkSecretRequirements(secret: string): Promise<SecretCheckResult> {
272
+ @Singleton({ alias: AuthenticationPasswordRequirementsValidator })
273
+ export class MyPasswordValidator extends AuthenticationPasswordRequirementsValidator {
274
+ override async checkPasswordRequirements(password: string): Promise<PasswordCheckResult> {
231
275
  // Custom logic (e.g. using zxcvbn or similar)
232
276
  return { strength: 4, pwned: 0, warnings: [], suggestions: [] };
233
277
  }
234
278
 
235
- override async testSecretRequirements(secret: string): Promise<SecretTestResult> {
236
- if (secret.length < 10) {
279
+ override async testPasswordRequirements(password: string): Promise<PasswordTestResult> {
280
+ if (password.length < 10) {
237
281
  return { success: false, reason: 'Too short' };
238
282
  }
239
283
  return { success: true };
@@ -243,7 +287,7 @@ export class MySecretValidator extends AuthenticationSecretRequirementsValidator
243
287
 
244
288
  ### HTTP Client Middleware
245
289
 
246
- If `registerMiddleware: true` is passed to `configureAuthenticationClient`, the `waitForAuthenticationCredentialsMiddleware` is registered.
290
+ If `registerMiddleware: true` is passed to `configureAuthenticationClient`, the `waitForAuthenticationPasswordMiddleware` is registered.
247
291
 
248
292
  This middleware intercepts all outgoing HTTP requests made via `@tstdl/base/http`. If the request endpoint requires credentials (as defined in the API definition), the middleware will:
249
293
 
@@ -255,34 +299,35 @@ This middleware intercepts all outgoing HTTP requests made via `@tstdl/base/http
255
299
 
256
300
  ### Server-Side (`@tstdl/base/authentication/server`)
257
301
 
258
- | Class/Function | Description |
259
- | :------------------------------------------ | :------------------------------------------------------------------------------------ |
260
- | `AuthenticationService` | Main service for credential verification, token issuance, and session management. |
261
- | `AuthenticationAncillaryService` | Abstract class for hooks (resolve subjects, payload generation, impersonation checks).|
262
- | `AuthenticationSecretRequirementsValidator` | Abstract class for validating password strength/requirements. |
263
- | `configureAuthenticationServer` | Configures the server module (secrets, options, ancillary service). |
264
- | `migrateAuthenticationSchema` | Runs database migrations for authentication tables. |
265
- | `AuthenticationApiController` | The API controller implementation (automatically registered). |
266
- | `SubjectService` | Service for managing subjects (User, ServiceAccount, SystemAccount). |
302
+ | Class/Function | Description |
303
+ | :-------------------------------------------- | :------------------------------------------------------------------------------------- |
304
+ | `AuthenticationService` | Main service for password verification, token issuance, and session management. |
305
+ | `AuthenticationAncillaryService` | Abstract class for hooks (resolve subjects, payload generation, impersonation checks). |
306
+ | `AuthenticationPasswordRequirementsValidator` | Abstract class for validating password strength/requirements. |
307
+ | `configureAuthenticationServer` | Configures the server module (signing keys, options, ancillary service). |
308
+ | `migrateAuthenticationSchema` | Runs database migrations for authentication tables. |
309
+ | `AuthenticationApiController` | The API controller implementation (automatically registered). |
310
+ | `SubjectService` | Service for managing subjects (User, ServiceAccount, SystemAccount). |
267
311
 
268
312
  ### Client-Side (`@tstdl/base/authentication`)
269
313
 
270
- | Class/Function | Description |
271
- | :------------------------------------------- | :---------------------------------------------------------------------- |
272
- | `AuthenticationClientService` | Main client service. Handles login, logout, refresh, and state signals. |
273
- | `configureAuthenticationClient` | Configures the client module and optional middleware. |
274
- | `waitForAuthenticationCredentialsMiddleware` | HTTP middleware that pauses requests until a valid token is available. |
314
+ | Class/Function | Description |
315
+ | :---------------------------------------- | :---------------------------------------------------------------------- |
316
+ | `AuthenticationClientService` | Main client service. Handles login, logout, refresh, and state signals. |
317
+ | `configureAuthenticationClient` | Configures the client module and optional middleware. |
318
+ | `waitForAuthenticationPasswordMiddleware` | HTTP middleware that pauses requests until a valid token is available. |
275
319
 
276
320
  ### Models & Types (`@tstdl/base/authentication`)
277
321
 
278
- | Type/Class | Description |
279
- | :-------------------------- | :------------------------------------------ |
280
- | `Subject` | Base entity for Users, ServiceAccounts, etc.|
281
- | `User` | Subject type representing a person. |
282
- | `ServiceAccount` | Subject type representing a non-human user. |
283
- | `SystemAccount` | Subject type representing a system user. |
284
- | `TokenPayload` | The structure of the JWT payload. |
285
- | `InitSecretResetData` | Data required to initiate a password reset. |
286
- | `SecretCheckResult` | Result of a password strength check. |
287
- | `AuthenticationCredentials` | Database entity for user credentials. |
288
- | `AuthenticationSession` | Database entity for active sessions. |
322
+ | Type/Class | Description |
323
+ | :----------------------- | :------------------------------------------- |
324
+ | `Subject` | Base entity for Users, ServiceAccounts, etc. |
325
+ | `User` | Subject type representing a person. |
326
+ | `ServiceAccount` | Subject type representing a non-human user. |
327
+ | `SystemAccount` | Subject type representing a system user. |
328
+ | `TokenPayload` | The structure of the JWT payload. |
329
+ | `InitPasswordResetData` | Data required to initiate a password reset. |
330
+ | `PasswordCheckResult` | Result of a password strength check. |
331
+ | `AuthenticationPassword` | Database entity for user passwords. |
332
+ | `AuthenticationSession` | Database entity for active sessions. |
333
+ | `AuthenticationTotp` | Database entity for TOTP configuration. |