@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
@@ -8,32 +8,32 @@ import { Singleton } from '../../injector/index.js';
8
8
  import { PasswordStrength } from '../../password/password-check-result.model.js';
9
9
  import { checkPassword } from '../../password/password-check.js';
10
10
  import { isNumber } from '../../utils/type-guards.js';
11
- import { SecretRequirementsError } from '../errors/secret-requirements.error.js';
12
- export class AuthenticationSecretRequirementsValidator {
11
+ import { PasswordRequirementsError } from '../errors/password-requirements.error.js';
12
+ export class AuthenticationPasswordRequirementsValidator {
13
13
  }
14
14
  /**
15
- * Default validator for secret requirements.
15
+ * Default validator for password requirements.
16
16
  *
17
17
  * Checks for pwned passwords and password strength.
18
18
  * - Pwned passwords are not allowed.
19
19
  * - Password strength must be at least 'medium'.
20
20
  */
21
- let DefaultAuthenticationSecretRequirementsValidator = class DefaultAuthenticationSecretRequirementsValidator extends AuthenticationSecretRequirementsValidator {
21
+ let DefaultAuthenticationPasswordRequirementsValidator = class DefaultAuthenticationPasswordRequirementsValidator extends AuthenticationPasswordRequirementsValidator {
22
22
  /**
23
- * Checks the secret against the requirements.
24
- * @param secret The secret to check.
23
+ * Checks the password against the requirements.
24
+ * @param password The password to check.
25
25
  * @returns The result of the check.
26
26
  */
27
- async checkSecretRequirements(secret) {
28
- return await checkPassword(secret, { checkForPwned: true });
27
+ async checkPasswordRequirements(password) {
28
+ return await checkPassword(password, { checkForPwned: true });
29
29
  }
30
30
  /**
31
- * Tests the secret against the requirements.
32
- * @param secret The secret to test.
31
+ * Tests the password against the requirements.
32
+ * @param password The password to test.
33
33
  * @returns The result of the test.
34
34
  */
35
- async testSecretRequirements(secret) {
36
- const result = await this.checkSecretRequirements(secret);
35
+ async testPasswordRequirements(password) {
36
+ const result = await this.checkPasswordRequirements(password);
37
37
  if (isNumber(result.pwned) && (result.pwned > 0)) {
38
38
  return { success: false, reason: 'Password is exposed in data breach (https://haveibeenpwned.com/passwords).' };
39
39
  }
@@ -43,18 +43,18 @@ let DefaultAuthenticationSecretRequirementsValidator = class DefaultAuthenticati
43
43
  return { success: true };
44
44
  }
45
45
  /**
46
- * Validates the secret against the requirements. Throws an error if the requirements are not met.
47
- * @param secret The secret to validate.
48
- * @throws {SecretRequirementsError} If the secret does not meet the requirements.
46
+ * Validates the password against the requirements. Throws an error if the requirements are not met.
47
+ * @param password The password to validate.
48
+ * @throws {PasswordRequirementsError} If the password does not meet the requirements.
49
49
  */
50
- async validateSecretRequirements(secret) {
51
- const result = await this.testSecretRequirements(secret);
50
+ async validatePasswordRequirements(password) {
51
+ const result = await this.testPasswordRequirements(password);
52
52
  if (!result.success) {
53
- throw new SecretRequirementsError(result.reason);
53
+ throw new PasswordRequirementsError(result.reason);
54
54
  }
55
55
  }
56
56
  };
57
- DefaultAuthenticationSecretRequirementsValidator = __decorate([
58
- Singleton({ alias: AuthenticationSecretRequirementsValidator })
59
- ], DefaultAuthenticationSecretRequirementsValidator);
60
- export { DefaultAuthenticationSecretRequirementsValidator };
57
+ DefaultAuthenticationPasswordRequirementsValidator = __decorate([
58
+ Singleton({ alias: AuthenticationPasswordRequirementsValidator })
59
+ ], DefaultAuthenticationPasswordRequirementsValidator);
60
+ export { DefaultAuthenticationPasswordRequirementsValidator };
@@ -1,88 +1,116 @@
1
1
  /** biome-ignore-all lint/nursery/noExcessiveClassesPerFile: <explanation> */
2
2
  import type { ApiController, ApiRequestContext, ApiServerResult } from '../../api/types.js';
3
+ import { type Auditor } from '../../audit/index.js';
3
4
  import { HttpServerResponse } from '../../http/server/index.js';
5
+ import { RateLimiter } from '../../rate-limit/index.js';
4
6
  import type { ObjectSchemaOrType, SchemaTestable } from '../../schema/index.js';
5
7
  import type { Record, Type } from '../../types/index.js';
6
8
  import type { AuthenticationApiDefinition } from '../authentication.api.js';
7
- import type { TokenResult } from './authentication.service.js';
8
- import { AuthenticationService } from './authentication.service.js';
9
+ import type { LoginResult, TokenResult } from './authentication.service.js';
10
+ import { AuthenticationService, AuthenticationServiceOptions } from './authentication.service.js';
9
11
  /**
10
12
  * API controller for authentication.
11
13
  *
12
14
  * @template AdditionalTokenPayload Type of additional token payload
13
15
  * @template AuthenticationData Type of additional authentication data
14
- * @template AdditionalInitSecretResetData Type of additional secret reset data
16
+ * @template AdditionalInitPasswordResetData Type of additional password reset data
15
17
  */
16
- export declare class AuthenticationApiController<AdditionalTokenPayload extends Record, AuthenticationData, AdditionalInitSecretResetData = void> implements ApiController<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>> {
17
- protected readonly authenticationService: AuthenticationService<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>;
18
+ export declare class AuthenticationApiController<AdditionalTokenPayload extends Record, AuthenticationData, AdditionalInitPasswordResetData = void> implements ApiController<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>> {
19
+ #private;
20
+ protected readonly authenticationService: AuthenticationService<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>;
21
+ protected readonly options: AuthenticationServiceOptions;
22
+ protected readonly subjectRateLimiter: RateLimiter;
23
+ protected readonly ipRateLimiter: RateLimiter;
18
24
  /**
19
- * Get a token for a subject and secret.
25
+ * Get a token for a subject and password.
20
26
  * @param parameters The parameters for the request.
21
27
  * @returns The token result.
22
28
  */
23
- login({ parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'login'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'login'>>;
29
+ login({ request, parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'login'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'login'>>;
30
+ loginVerifyTotp({ request, parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'loginVerifyTotp'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'loginVerifyTotp'>>;
31
+ loginRecovery({ request, parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'loginRecovery'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'loginRecovery'>>;
32
+ initEnrollTotp({ getToken, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'initEnrollTotp'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'initEnrollTotp'>>;
33
+ completeEnrollTotp({ request, getToken, parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'completeEnrollTotp'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'completeEnrollTotp'>>;
34
+ disableTotp({ request, getToken, parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'disableTotp'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'disableTotp'>>;
35
+ disableTotpWithRecoveryCode({ request, getToken, parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'disableTotpWithRecoveryCode'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'disableTotpWithRecoveryCode'>>;
36
+ regenerateRecoveryCodes({ request, getToken, parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'regenerateRecoveryCodes'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'regenerateRecoveryCodes'>>;
37
+ getTotpStatus({ getToken }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'getTotpStatus'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'getTotpStatus'>>;
24
38
  /**
25
39
  * Refresh a token.
26
40
  * @param request The request context.
27
41
  * @param parameters The parameters for the request.
28
42
  * @returns The token result.
29
43
  */
30
- refresh({ request, parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'refresh'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'refresh'>>;
44
+ refresh({ request, parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'refresh'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'refresh'>>;
31
45
  /**
32
46
  * Impersonate a subject.
33
47
  * @param request The request context.
34
48
  * @param parameters The parameters for the request.
35
49
  * @returns The token result.
36
50
  */
37
- impersonate({ request, parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'impersonate'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'impersonate'>>;
51
+ impersonate({ request, parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'impersonate'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'impersonate'>>;
38
52
  /**
39
53
  * Unimpersonate a subject.
40
54
  * @param request The request context.
41
55
  * @param parameters The parameters for the request.
42
56
  * @returns The token result.
43
57
  */
44
- unimpersonate({ request, parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'unimpersonate'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'unimpersonate'>>;
58
+ unimpersonate({ request, parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'unimpersonate'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'unimpersonate'>>;
45
59
  /**
46
60
  * End a session.
47
61
  * @param request The request context.
48
62
  * @returns 'ok'
49
63
  */
50
- endSession({ request, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'endSession'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'endSession'>>;
51
- changeSecret({ parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'changeSecret'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'changeSecret'>>;
64
+ endSession({ request, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'endSession'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'endSession'>>;
65
+ changePassword({ request, parameters, getToken, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'changePassword'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'changePassword'>>;
52
66
  /**
53
- * Initialize a secret reset.
67
+ * Initialize a password reset.
54
68
  * @param parameters The parameters for the request.
55
- * @returns 'ok' if the secret reset was initialized.
69
+ * @returns 'ok' if the password reset was initialized.
56
70
  */
57
- initSecretReset({ parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'initSecretReset'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'initSecretReset'>>;
71
+ initPasswordReset({ request, parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'initPasswordReset'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'initPasswordReset'>>;
58
72
  /**
59
- * Reset a secret.
73
+ * Reset a password.
60
74
  * @param parameters The parameters for the request.
61
- * @returns 'ok' if the secret was reset.
75
+ * @returns 'ok' if the password was reset.
62
76
  */
63
- resetSecret({ parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'resetSecret'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'resetSecret'>>;
77
+ resetPassword({ request, parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'resetPassword'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'resetPassword'>>;
64
78
  /**
65
- * Check a secret.
79
+ * Check a password.
66
80
  * @param parameters The parameters for the request.
67
- * @returns The result of the secret check.
81
+ * @returns The result of the password check.
68
82
  */
69
- checkSecret({ parameters }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'checkSecret'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'checkSecret'>>;
83
+ checkPassword({ parameters }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'checkPassword'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'checkPassword'>>;
70
84
  /**
71
85
  * Get the current server timestamp.
72
86
  * @returns The current server timestamp in seconds.
73
87
  */
74
- timestamp(): ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>, 'timestamp'>;
75
- protected getTokenResponse({ token, jsonToken, refreshToken, remember, omitImpersonatorRefreshToken, impersonatorRefreshToken, impersonatorRefreshTokenExpiration }: TokenResult<AdditionalTokenPayload>): HttpServerResponse;
88
+ timestamp(): ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'timestamp'>;
89
+ /**
90
+ * List all active sessions for the current user.
91
+ * @param context The request context.
92
+ * @returns List of sessions.
93
+ */
94
+ listSessions({ getToken }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'listSessions'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'listSessions'>>;
95
+ /**
96
+ * Invalidate all active sessions for the current user except the current one.
97
+ * @param context The request context.
98
+ * @returns 'ok' if all other sessions were invalidated.
99
+ */
100
+ invalidateAllOtherSessions({ getToken, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'invalidateAllOtherSessions'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'invalidateAllOtherSessions'>>;
101
+ protected enforceRateLimit(ip: string, subjectResource: string, auditor: Auditor, targetId: string, action: string): Promise<void>;
102
+ protected getTokenResponse<T>(result: TokenResult<AdditionalTokenPayload>, body: NoInfer<T>): HttpServerResponse;
103
+ protected getLoginResponse(result: LoginResult<AdditionalTokenPayload>): ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'login'>;
76
104
  }
77
105
  /**
78
106
  * Get an authentication API controller.
79
107
  * @param additionalTokenPayloadSchema Schema for additional token payload.
80
108
  * @param authenticationDataSchema Schema for additional authentication data.
81
- * @param additionalInitSecretResetData Schema for additional secret reset data.
109
+ * @param additionalInitPasswordResetData Schema for additional password reset data.
82
110
  * @returns An authentication API controller.
83
111
  * @template AdditionalTokenPayload Type of additional token payload.
84
112
  * @template AuthenticationData Type of additional authentication data.
85
- * @template AdditionalInitSecretResetData Type of additional secret reset data.
113
+ * @template AdditionalInitPasswordResetData Type of additional password reset data.
86
114
  */
87
- export declare function getAuthenticationApiController<AdditionalTokenPayload extends Record, AuthenticationData, AdditionalInitSecretResetData = void>(// eslint-disable-line @typescript-eslint/explicit-function-return-type
88
- additionalTokenPayloadSchema: ObjectSchemaOrType<AdditionalTokenPayload>, authenticationDataSchema: SchemaTestable<AuthenticationData>, additionalInitSecretResetData: SchemaTestable<AdditionalInitSecretResetData>): Type<AuthenticationApiController<AdditionalTokenPayload, AuthenticationData, AdditionalInitSecretResetData>>;
115
+ export declare function getAuthenticationApiController<AdditionalTokenPayload extends Record, AuthenticationData, AdditionalInitPasswordResetData = void>(// eslint-disable-line @typescript-eslint/explicit-function-return-type
116
+ additionalTokenPayloadSchema: ObjectSchemaOrType<AdditionalTokenPayload>, authenticationDataSchema: SchemaTestable<AuthenticationData>, additionalInitPasswordResetData: SchemaTestable<AdditionalInitPasswordResetData>): Type<AuthenticationApiController<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>>;
@@ -5,13 +5,21 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
6
  return c > 3 && r && Object.defineProperty(target, key, r), r;
7
7
  };
8
+ var _a;
9
+ var AuthenticationApiController_1;
8
10
  import { apiController } from '../../api/server/index.js';
11
+ import { ActorType } from '../../audit/index.js';
12
+ import { NIL_UUID } from '../../constants.js';
13
+ import { TooManyRequestsError } from '../../errors/index.js';
9
14
  import { HttpServerResponse } from '../../http/server/index.js';
10
15
  import { inject } from '../../injector/index.js';
16
+ import { Logger } from '../../logger/logger.js';
17
+ import { RateLimiter } from '../../rate-limit/index.js';
11
18
  import { currentTimestampSeconds } from '../../utils/date-time.js';
12
19
  import { isDefined } from '../../utils/type-guards.js';
20
+ import { millisecondsPerMinute } from '../../utils/units.js';
13
21
  import { authenticationApiDefinition, getAuthenticationApiDefinition } from '../authentication.api.js';
14
- import { AuthenticationService } from './authentication.service.js';
22
+ import { AuthenticationService, AuthenticationServiceOptions } from './authentication.service.js';
15
23
  import { tryGetAuthorizationTokenStringFromRequest } from './helper.js';
16
24
  const cookieBaseOptions = { path: '/', httpOnly: true, secure: true, sameSite: 'strict' };
17
25
  const deleteCookie = { value: '', ...cookieBaseOptions, maxAge: -1 };
@@ -20,18 +28,91 @@ const deleteCookie = { value: '', ...cookieBaseOptions, maxAge: -1 };
20
28
  *
21
29
  * @template AdditionalTokenPayload Type of additional token payload
22
30
  * @template AuthenticationData Type of additional authentication data
23
- * @template AdditionalInitSecretResetData Type of additional secret reset data
31
+ * @template AdditionalInitPasswordResetData Type of additional password reset data
24
32
  */
25
- let AuthenticationApiController = class AuthenticationApiController {
33
+ let AuthenticationApiController = AuthenticationApiController_1 = class AuthenticationApiController {
34
+ #logger = inject(Logger, AuthenticationApiController_1.name);
26
35
  authenticationService = inject((AuthenticationService));
36
+ options = inject(AuthenticationServiceOptions, undefined, { optional: true }) ?? {};
37
+ subjectRateLimiter = inject(RateLimiter, {
38
+ resource: 'authentication:subject',
39
+ burstCapacity: this.options.bruteForceProtection?.subjectBurstCapacity ?? 10,
40
+ refillInterval: this.options.bruteForceProtection?.subjectRefillInterval ?? (30 * millisecondsPerMinute),
41
+ });
42
+ ipRateLimiter = inject(RateLimiter, {
43
+ resource: 'authentication:ip',
44
+ burstCapacity: this.options.bruteForceProtection?.ipBurstCapacity ?? 20,
45
+ refillInterval: this.options.bruteForceProtection?.ipRefillInterval ?? (5 * millisecondsPerMinute),
46
+ });
27
47
  /**
28
- * Get a token for a subject and secret.
48
+ * Get a token for a subject and password.
29
49
  * @param parameters The parameters for the request.
30
50
  * @returns The token result.
31
51
  */
32
- async login({ parameters, getAuditor }) {
33
- const result = await this.authenticationService.login({ tenantId: parameters.tenantId, subject: parameters.subject }, parameters.secret, parameters.data, await getAuditor(), parameters.remember);
34
- return this.getTokenResponse(result);
52
+ async login({ request, parameters, getAuditor }) {
53
+ const auditor = await getAuditor();
54
+ const subjectResource = `${parameters.tenantId ?? NIL_UUID}:${parameters.subject}`;
55
+ const actualSubject = await this.authenticationService.tryResolveSubject({ tenantId: parameters.tenantId, subject: parameters.subject });
56
+ await this.enforceRateLimit(request.ip, subjectResource, auditor, actualSubject?.id ?? NIL_UUID, 'login');
57
+ const result = await this.authenticationService.login({ tenantId: parameters.tenantId, subject: parameters.subject }, parameters.password, parameters.data, auditor, parameters.remember);
58
+ return this.getLoginResponse(result);
59
+ }
60
+ async loginVerifyTotp({ request, parameters, getAuditor }) {
61
+ const auditor = await getAuditor();
62
+ const validatedChallengeToken = await this.authenticationService.validateTotpChallengeToken(parameters.challengeToken);
63
+ const subjectResource = `${validatedChallengeToken.payload.tenant}:${validatedChallengeToken.payload.subject}`;
64
+ await this.enforceRateLimit(request.ip, subjectResource, auditor, validatedChallengeToken.payload.subject, 'login-verify-totp');
65
+ const result = await this.authenticationService.loginVerifyTotp(parameters.challengeToken, parameters.token, auditor);
66
+ return this.getLoginResponse(result);
67
+ }
68
+ async loginRecovery({ request, parameters, getAuditor }) {
69
+ const auditor = await getAuditor();
70
+ const validatedChallengeToken = await this.authenticationService.validateTotpChallengeToken(parameters.challengeToken);
71
+ const subjectResource = `${validatedChallengeToken.payload.tenant}:${validatedChallengeToken.payload.subject}`;
72
+ await this.enforceRateLimit(request.ip, subjectResource, auditor, validatedChallengeToken.payload.subject, 'login-recovery');
73
+ const result = await this.authenticationService.loginRecovery(parameters.challengeToken, parameters.recoveryCode, auditor);
74
+ return this.getLoginResponse(result);
75
+ }
76
+ async initEnrollTotp({ getToken, getAuditor }) {
77
+ const token = await getToken();
78
+ const auditor = await getAuditor();
79
+ return await this.authenticationService.initEnrollTotp(token.payload.tenant, token.payload.subject, auditor);
80
+ }
81
+ async completeEnrollTotp({ request, getToken, parameters, getAuditor }) {
82
+ const token = await getToken();
83
+ const auditor = await getAuditor();
84
+ const subjectResource = `${token.payload.tenant}:${token.payload.subject}`;
85
+ await this.enforceRateLimit(request.ip, subjectResource, auditor, token.payload.subject, 'complete-enroll-totp');
86
+ const result = await this.authenticationService.completeEnrollTotp(token.payload.tenant, token.payload.subject, parameters.token, auditor);
87
+ return result;
88
+ }
89
+ async disableTotp({ request, getToken, parameters, getAuditor }) {
90
+ const token = await getToken();
91
+ const auditor = await getAuditor();
92
+ const subjectResource = `${token.payload.tenant}:${token.payload.subject}`;
93
+ await this.enforceRateLimit(request.ip, subjectResource, auditor, token.payload.subject, 'disable-totp');
94
+ await this.authenticationService.disableTotp(token.payload.tenant, token.payload.subject, parameters.token, auditor);
95
+ return 'ok';
96
+ }
97
+ async disableTotpWithRecoveryCode({ request, getToken, parameters, getAuditor }) {
98
+ const token = await getToken();
99
+ const auditor = await getAuditor();
100
+ const subjectResource = `${token.payload.tenant}:${token.payload.subject}`;
101
+ await this.enforceRateLimit(request.ip, subjectResource, auditor, token.payload.subject, 'disable-totp-recovery');
102
+ await this.authenticationService.disableTotpWithRecoveryCode(token.payload.tenant, token.payload.subject, parameters.recoveryCode, auditor);
103
+ return 'ok';
104
+ }
105
+ async regenerateRecoveryCodes({ request, getToken, parameters, getAuditor }) {
106
+ const token = await getToken();
107
+ const auditor = await getAuditor();
108
+ const subjectResource = `${token.payload.tenant}:${token.payload.subject}`;
109
+ await this.enforceRateLimit(request.ip, subjectResource, auditor, token.payload.subject, 'regenerate-recovery-codes');
110
+ const result = await this.authenticationService.regenerateRecoveryCodes(token.payload.tenant, token.payload.subject, parameters.token, auditor, { invalidateOtherSessions: parameters.invalidateOtherSessions });
111
+ return result;
112
+ }
113
+ async getTotpStatus({ getToken }) {
114
+ const token = await getToken();
115
+ return await this.authenticationService.getTotpStatus(token.payload.tenant, token.payload.subject);
35
116
  }
36
117
  /**
37
118
  * Refresh a token.
@@ -40,9 +121,13 @@ let AuthenticationApiController = class AuthenticationApiController {
40
121
  * @returns The token result.
41
122
  */
42
123
  async refresh({ request, parameters, getAuditor }) {
124
+ const auditor = await getAuditor();
43
125
  const refreshTokenString = tryGetAuthorizationTokenStringFromRequest(request, 'refreshToken') ?? '';
44
- const result = await this.authenticationService.refresh(refreshTokenString, parameters.data, undefined, await getAuditor());
45
- return this.getTokenResponse(result);
126
+ const validatedToken = await this.authenticationService.validateRefreshToken(refreshTokenString);
127
+ const subjectResource = `${validatedToken.payload.tenant}:${validatedToken.payload.subject}`;
128
+ await this.enforceRateLimit(request.ip, subjectResource, auditor, validatedToken.payload.subject, 'refresh');
129
+ const result = await this.authenticationService.refreshAlreadyValidatedToken(validatedToken, parameters.data, undefined, auditor);
130
+ return this.getTokenResponse(result, result.jsonToken.payload);
46
131
  }
47
132
  /**
48
133
  * Impersonate a subject.
@@ -51,10 +136,11 @@ let AuthenticationApiController = class AuthenticationApiController {
51
136
  * @returns The token result.
52
137
  */
53
138
  async impersonate({ request, parameters, getAuditor }) {
139
+ const auditor = await getAuditor();
54
140
  const tokenString = tryGetAuthorizationTokenStringFromRequest(request) ?? '';
55
141
  const refreshTokenString = tryGetAuthorizationTokenStringFromRequest(request, 'refreshToken') ?? '';
56
- const impersonatorResult = await this.authenticationService.impersonate(tokenString, refreshTokenString, parameters.subject, parameters.data, await getAuditor());
57
- return this.getTokenResponse(impersonatorResult);
142
+ const impersonatorResult = await this.authenticationService.impersonate(tokenString, refreshTokenString, parameters.subject, parameters.data, auditor);
143
+ return this.getTokenResponse(impersonatorResult, impersonatorResult.jsonToken.payload);
58
144
  }
59
145
  /**
60
146
  * Unimpersonate a subject.
@@ -63,9 +149,11 @@ let AuthenticationApiController = class AuthenticationApiController {
63
149
  * @returns The token result.
64
150
  */
65
151
  async unimpersonate({ request, parameters, getAuditor }) {
152
+ const auditor = await getAuditor();
153
+ const tokenString = tryGetAuthorizationTokenStringFromRequest(request) ?? '';
66
154
  const impersonatorRefreshTokenString = tryGetAuthorizationTokenStringFromRequest(request, 'impersonatorRefreshToken') ?? '';
67
- const result = await this.authenticationService.unimpersonate(impersonatorRefreshTokenString, parameters.data, await getAuditor());
68
- return this.getTokenResponse(result);
155
+ const result = await this.authenticationService.unimpersonate(impersonatorRefreshTokenString, tokenString, parameters.data, auditor);
156
+ return this.getTokenResponse(result, result.jsonToken.payload);
69
157
  }
70
158
  /**
71
159
  * End a session.
@@ -90,7 +178,8 @@ let AuthenticationApiController = class AuthenticationApiController {
90
178
  }
91
179
  }
92
180
  if (isDefined(sessionId)) {
93
- await this.authenticationService.endSession(sessionId, await getAuditor());
181
+ const auditor = await getAuditor();
182
+ await this.authenticationService.endSession(sessionId, auditor);
94
183
  }
95
184
  const result = 'ok';
96
185
  return new HttpServerResponse({
@@ -104,35 +193,54 @@ let AuthenticationApiController = class AuthenticationApiController {
104
193
  },
105
194
  });
106
195
  }
107
- async changeSecret({ parameters, getAuditor }) {
108
- await this.authenticationService.changeSecret({ tenantId: parameters.tenantId, subject: parameters.subject }, parameters.currentSecret, parameters.newSecret, await getAuditor());
196
+ async changePassword({ request, parameters, getToken, getAuditor }) {
197
+ const token = await getToken();
198
+ const auditor = await getAuditor();
199
+ const subjectResource = `${token.payload.tenant}:${token.payload.subject}`;
200
+ await this.enforceRateLimit(request.ip, subjectResource, auditor, token.payload.subject, 'change-password');
201
+ await this.authenticationService.changePassword({ tenantId: token.payload.tenant, subject: token.payload.subject }, parameters.currentPassword, parameters.newPassword, auditor);
109
202
  return 'ok';
110
203
  }
111
204
  /**
112
- * Initialize a secret reset.
205
+ * Initialize a password reset.
113
206
  * @param parameters The parameters for the request.
114
- * @returns 'ok' if the secret reset was initialized.
207
+ * @returns 'ok' if the password reset was initialized.
115
208
  */
116
- async initSecretReset({ parameters, getAuditor }) {
117
- await this.authenticationService.initSecretReset({ tenantId: parameters.tenantId, subject: parameters.subject }, parameters.data, await getAuditor());
209
+ async initPasswordReset({ request, parameters, getAuditor }) {
210
+ const auditor = await getAuditor();
211
+ const subjectResource = `${parameters.tenantId ?? NIL_UUID}:${parameters.subject}`;
212
+ const actualSubject = await this.authenticationService.tryResolveSubject({ tenantId: parameters.tenantId, subject: parameters.subject });
213
+ await this.enforceRateLimit(request.ip, subjectResource, auditor, actualSubject?.id ?? NIL_UUID, 'init-password-reset');
214
+ void (async () => {
215
+ // we are intentionally not awaiting the initPasswordReset call here to not leak information through timing about whether the subject exists or not.
216
+ try {
217
+ await this.authenticationService.initPasswordReset({ tenantId: parameters.tenantId, subject: parameters.subject }, parameters.data, auditor);
218
+ }
219
+ catch (error) {
220
+ this.#logger.error(error);
221
+ }
222
+ })();
118
223
  return 'ok';
119
224
  }
120
225
  /**
121
- * Reset a secret.
226
+ * Reset a password.
122
227
  * @param parameters The parameters for the request.
123
- * @returns 'ok' if the secret was reset.
228
+ * @returns 'ok' if the password was reset.
124
229
  */
125
- async resetSecret({ parameters, getAuditor }) {
126
- await this.authenticationService.resetSecret(parameters.token, parameters.newSecret, await getAuditor());
230
+ async resetPassword({ request, parameters, getAuditor }) {
231
+ const subjectResource = parameters.token;
232
+ const auditor = await getAuditor();
233
+ await this.enforceRateLimit(request.ip, subjectResource, auditor, NIL_UUID, 'reset-password');
234
+ await this.authenticationService.resetPassword(parameters.token, parameters.newPassword, auditor);
127
235
  return 'ok';
128
236
  }
129
237
  /**
130
- * Check a secret.
238
+ * Check a password.
131
239
  * @param parameters The parameters for the request.
132
- * @returns The result of the secret check.
240
+ * @returns The result of the password check.
133
241
  */
134
- async checkSecret({ parameters }) {
135
- return await this.authenticationService.checkSecret(parameters.secret);
242
+ async checkPassword({ parameters }) {
243
+ return await this.authenticationService.checkPassword(parameters.password);
136
244
  }
137
245
  /**
138
246
  * Get the current server timestamp.
@@ -141,27 +249,84 @@ let AuthenticationApiController = class AuthenticationApiController {
141
249
  timestamp() {
142
250
  return currentTimestampSeconds();
143
251
  }
144
- getTokenResponse({ token, jsonToken, refreshToken, remember, omitImpersonatorRefreshToken, impersonatorRefreshToken, impersonatorRefreshTokenExpiration }) {
145
- const result = jsonToken.payload;
252
+ /**
253
+ * List all active sessions for the current user.
254
+ * @param context The request context.
255
+ * @returns List of sessions.
256
+ */
257
+ async listSessions({ getToken }) {
258
+ const token = await getToken();
259
+ const sessions = await this.authenticationService.listSessions(token.payload.tenant, token.payload.subject);
260
+ return sessions.map((session) => ({
261
+ id: session.id,
262
+ begin: session.begin,
263
+ end: session.end,
264
+ }));
265
+ }
266
+ /**
267
+ * Invalidate all active sessions for the current user except the current one.
268
+ * @param context The request context.
269
+ * @returns 'ok' if all other sessions were invalidated.
270
+ */
271
+ async invalidateAllOtherSessions({ getToken, getAuditor }) {
272
+ const token = await getToken();
273
+ const auditor = await getAuditor();
274
+ await this.authenticationService.invalidateAllOtherSessions(token.payload.tenant, token.payload.subject, token.payload.session, auditor);
275
+ return 'ok';
276
+ }
277
+ async enforceRateLimit(ip, subjectResource, auditor, targetId, action) {
278
+ const authAuditor = auditor.fork('authentication');
279
+ const ipAcquired = await this.ipRateLimiter.tryAcquire(ip);
280
+ if (!ipAcquired) {
281
+ await authAuditor.warn('rate-limit-exceeded', {
282
+ actorType: ActorType.Anonymous,
283
+ targetType: 'Subject',
284
+ targetId,
285
+ details: {
286
+ type: 'ip',
287
+ resource: ip,
288
+ action,
289
+ },
290
+ });
291
+ throw new TooManyRequestsError();
292
+ }
293
+ const subjectAcquired = await this.subjectRateLimiter.tryAcquire(subjectResource);
294
+ if (!subjectAcquired) {
295
+ await authAuditor.warn('rate-limit-exceeded', {
296
+ actorType: ActorType.Anonymous,
297
+ targetType: 'Subject',
298
+ targetId,
299
+ details: {
300
+ type: 'subject',
301
+ resource: subjectResource,
302
+ action,
303
+ },
304
+ });
305
+ throw new TooManyRequestsError();
306
+ }
307
+ }
308
+ getTokenResponse(result, body) {
309
+ const { token, jsonToken, refreshToken, remember, omitImpersonatorRefreshToken, impersonatorRefreshToken, impersonatorRefreshTokenExpiration } = result;
146
310
  const options = {
311
+ statusCode: 200,
147
312
  headers: {
148
313
  'X-Authorization': `Bearer ${token}`,
149
314
  'X-Refresh-Token': `Bearer ${refreshToken}`,
150
315
  },
151
316
  cookies: {
152
- authorization: {
153
- value: `Bearer ${token}`,
317
+ token: {
318
+ value: token,
154
319
  ...cookieBaseOptions,
155
320
  expires: remember ? (jsonToken.payload.exp * 1000) : undefined,
156
321
  },
157
322
  refreshToken: {
158
- value: `Bearer ${refreshToken}`,
323
+ value: refreshToken,
159
324
  ...cookieBaseOptions,
160
325
  expires: remember ? (jsonToken.payload.refreshTokenExp * 1000) : undefined,
161
326
  },
162
327
  },
163
328
  body: {
164
- json: result,
329
+ json: body,
165
330
  },
166
331
  };
167
332
  if (isDefined(impersonatorRefreshToken)) {
@@ -177,8 +342,18 @@ let AuthenticationApiController = class AuthenticationApiController {
177
342
  }
178
343
  return new HttpServerResponse(options);
179
344
  }
345
+ getLoginResponse(result) {
346
+ if (result.type == 'success') {
347
+ return this.getTokenResponse(result.result, {
348
+ type: 'success',
349
+ result: result.result.jsonToken.payload,
350
+ lowRecoveryCodesWarning: result.lowRecoveryCodesWarning,
351
+ });
352
+ }
353
+ return result;
354
+ }
180
355
  };
181
- AuthenticationApiController = __decorate([
356
+ AuthenticationApiController = AuthenticationApiController_1 = __decorate([
182
357
  apiController(authenticationApiDefinition)
183
358
  ], AuthenticationApiController);
184
359
  export { AuthenticationApiController };
@@ -186,15 +361,15 @@ export { AuthenticationApiController };
186
361
  * Get an authentication API controller.
187
362
  * @param additionalTokenPayloadSchema Schema for additional token payload.
188
363
  * @param authenticationDataSchema Schema for additional authentication data.
189
- * @param additionalInitSecretResetData Schema for additional secret reset data.
364
+ * @param additionalInitPasswordResetData Schema for additional password reset data.
190
365
  * @returns An authentication API controller.
191
366
  * @template AdditionalTokenPayload Type of additional token payload.
192
367
  * @template AuthenticationData Type of additional authentication data.
193
- * @template AdditionalInitSecretResetData Type of additional secret reset data.
368
+ * @template AdditionalInitPasswordResetData Type of additional password reset data.
194
369
  */
195
370
  export function getAuthenticationApiController(// eslint-disable-line @typescript-eslint/explicit-function-return-type
196
- additionalTokenPayloadSchema, authenticationDataSchema, additionalInitSecretResetData) {
197
- const apiDefinition = getAuthenticationApiDefinition(additionalTokenPayloadSchema, authenticationDataSchema, additionalInitSecretResetData);
371
+ additionalTokenPayloadSchema, authenticationDataSchema, additionalInitPasswordResetData) {
372
+ const apiDefinition = getAuthenticationApiDefinition(additionalTokenPayloadSchema, authenticationDataSchema, additionalInitPasswordResetData);
198
373
  let AuthenticationApi = class AuthenticationApi extends AuthenticationApiController {
199
374
  };
200
375
  AuthenticationApi = __decorate([