@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.
- package/api/response.js +4 -3
- package/api/server/gateway.js +9 -3
- package/audit/auditor.d.ts +1 -2
- package/audit/drizzle/{0000_lumpy_thunderball.sql → 0000_shallow_elektra.sql} +1 -1
- package/audit/drizzle/meta/0000_snapshot.json +2 -2
- package/audit/drizzle/meta/_journal.json +2 -2
- package/authentication/README.md +87 -42
- package/authentication/authentication.api.d.ts +392 -53
- package/authentication/authentication.api.js +133 -28
- package/authentication/client/api.client.d.ts +3 -3
- package/authentication/client/api.client.js +4 -4
- package/authentication/client/authentication.service.d.ts +93 -23
- package/authentication/client/authentication.service.js +113 -28
- package/authentication/client/http-client.middleware.d.ts +1 -1
- package/authentication/client/http-client.middleware.js +5 -4
- package/authentication/client/module.d.ts +1 -1
- package/authentication/client/module.js +2 -2
- package/authentication/errors/index.d.ts +1 -1
- package/authentication/errors/index.js +1 -1
- package/authentication/errors/password-requirements.error.d.ts +5 -0
- package/authentication/errors/{secret-requirements.error.js → password-requirements.error.js} +2 -2
- package/authentication/models/authentication-password.model.d.ts +8 -0
- package/authentication/models/{authentication-credentials.model.js → authentication-password.model.js} +11 -17
- package/authentication/models/authentication-session.model.d.ts +0 -2
- package/authentication/models/authentication-session.model.js +1 -7
- package/authentication/models/authentication-totp-recovery-code.model.d.ts +6 -0
- package/authentication/models/authentication-totp-recovery-code.model.js +34 -0
- package/authentication/models/authentication-totp.model.d.ts +19 -0
- package/authentication/models/authentication-totp.model.js +51 -0
- package/authentication/models/authentication-used-totp-token.model.d.ts +5 -0
- package/authentication/models/authentication-used-totp-token.model.js +32 -0
- package/authentication/models/index.d.ts +6 -3
- package/authentication/models/index.js +6 -3
- package/authentication/models/{init-secret-reset-data.model.d.ts → init-password-reset-data.model.d.ts} +3 -3
- package/authentication/models/{init-secret-reset-data.model.js → init-password-reset-data.model.js} +5 -5
- package/authentication/models/password-check-result.model.d.ts +3 -0
- package/authentication/models/{secret-check-result.model.js → password-check-result.model.js} +6 -6
- package/authentication/models/subject.model.d.ts +0 -6
- package/authentication/models/subject.model.js +0 -6
- package/authentication/models/token.model.d.ts +16 -2
- package/authentication/server/authentication-ancillary.service.d.ts +6 -6
- package/authentication/server/authentication-ancillary.service.js +1 -1
- package/authentication/server/authentication-password-requirements.validator.d.ts +55 -0
- package/authentication/server/{authentication-secret-requirements.validator.js → authentication-password-requirements.validator.js} +22 -22
- package/authentication/server/authentication.api-controller.d.ts +55 -27
- package/authentication/server/authentication.api-controller.js +214 -39
- package/authentication/server/authentication.audit.d.ts +42 -5
- package/authentication/server/authentication.service.d.ts +182 -93
- package/authentication/server/authentication.service.js +628 -206
- package/authentication/server/drizzle/{0000_soft_tag.sql → 0000_odd_echo.sql} +59 -13
- package/authentication/server/drizzle/meta/0000_snapshot.json +345 -32
- package/authentication/server/drizzle/meta/_journal.json +2 -2
- package/authentication/server/helper.d.ts +16 -16
- package/authentication/server/helper.js +33 -34
- package/authentication/server/index.d.ts +1 -1
- package/authentication/server/index.js +1 -1
- package/authentication/server/module.d.ts +2 -2
- package/authentication/server/module.js +4 -2
- package/authentication/server/schemas.d.ts +11 -7
- package/authentication/server/schemas.js +7 -3
- package/authentication/tests/authentication-password-requirements.validator.test.js +29 -0
- package/authentication/tests/authentication.api-controller.test.js +49 -15
- package/authentication/tests/authentication.client-error-handling.test.js +3 -2
- package/authentication/tests/authentication.client-middleware.test.js +5 -5
- package/authentication/tests/authentication.client-service-methods.test.js +28 -14
- package/authentication/tests/authentication.client-service-refresh.test.js +7 -6
- package/authentication/tests/authentication.client-service.test.js +10 -8
- package/authentication/tests/authentication.service.test.js +37 -29
- package/authentication/tests/authentication.test-ancillary-service.d.ts +1 -1
- package/authentication/tests/authentication.test-ancillary-service.js +1 -1
- package/authentication/tests/brute-force-protection.test.js +211 -0
- package/authentication/tests/helper.test.js +25 -21
- package/authentication/tests/password-requirements.error.test.js +14 -0
- package/authentication/tests/remember.api.test.js +22 -14
- package/authentication/tests/remember.service.test.js +23 -16
- package/authentication/tests/subject.service.test.js +2 -2
- package/authentication/tests/suspended-subject.test.d.ts +1 -0
- package/authentication/tests/suspended-subject.test.js +120 -0
- package/authentication/tests/totp.enrollment.test.d.ts +1 -0
- package/authentication/tests/totp.enrollment.test.js +123 -0
- package/authentication/tests/totp.login.test.d.ts +1 -0
- package/authentication/tests/totp.login.test.js +213 -0
- package/authentication/tests/totp.recovery-codes.test.d.ts +1 -0
- package/authentication/tests/totp.recovery-codes.test.js +97 -0
- package/authentication/tests/totp.status.test.d.ts +1 -0
- package/authentication/tests/totp.status.test.js +72 -0
- package/circuit-breaker/postgres/drizzle/{0000_cooing_korath.sql → 0000_same_captain_cross.sql} +1 -1
- package/circuit-breaker/postgres/drizzle/meta/0000_snapshot.json +2 -2
- package/circuit-breaker/postgres/drizzle/meta/_journal.json +2 -2
- package/cryptography/cryptography.d.ts +336 -0
- package/cryptography/cryptography.js +328 -0
- package/cryptography/index.d.ts +4 -0
- package/cryptography/index.js +4 -0
- package/{utils → cryptography}/jwt.d.ts +22 -4
- package/{utils → cryptography}/jwt.js +36 -18
- package/cryptography/module.d.ts +35 -0
- package/cryptography/module.js +148 -0
- package/cryptography/tests/cryptography.test.d.ts +1 -0
- package/cryptography/tests/cryptography.test.js +175 -0
- package/cryptography/tests/jwt.test.d.ts +1 -0
- package/cryptography/tests/jwt.test.js +54 -0
- package/cryptography/tests/modern.test.d.ts +1 -0
- package/cryptography/tests/modern.test.js +105 -0
- package/cryptography/tests/module.test.d.ts +1 -0
- package/cryptography/tests/module.test.js +100 -0
- package/cryptography/tests/totp.test.d.ts +1 -0
- package/cryptography/tests/totp.test.js +108 -0
- package/cryptography/totp.d.ts +96 -0
- package/cryptography/totp.js +123 -0
- package/document-management/server/drizzle/{0000_curious_nighthawk.sql → 0000_sharp_scream.sql} +21 -21
- package/document-management/server/drizzle/meta/0000_snapshot.json +22 -22
- package/document-management/server/drizzle/meta/_journal.json +2 -2
- package/document-management/server/services/document-file.service.js +1 -1
- package/errors/errors.localization.d.ts +2 -2
- package/errors/errors.localization.js +2 -2
- package/errors/index.d.ts +1 -0
- package/errors/index.js +1 -0
- package/errors/too-many-requests.error.d.ts +5 -0
- package/errors/too-many-requests.error.js +7 -0
- package/examples/api/authentication.js +5 -5
- package/examples/api/custom-authentication.js +4 -3
- package/file/server/mime-type.js +1 -1
- package/http/http-body.d.ts +1 -0
- package/http/http-body.js +3 -0
- package/image-service/imgproxy/imgproxy-image-service.d.ts +0 -1
- package/image-service/imgproxy/imgproxy-image-service.js +9 -27
- package/key-value-store/postgres/drizzle/{0000_shocking_slipstream.sql → 0000_moaning_calypso.sql} +1 -1
- package/key-value-store/postgres/drizzle/meta/0000_snapshot.json +2 -2
- package/key-value-store/postgres/drizzle/meta/_journal.json +2 -2
- package/lock/postgres/drizzle/{0000_busy_tattoo.sql → 0000_nappy_wraith.sql} +1 -1
- package/lock/postgres/drizzle/meta/0000_snapshot.json +2 -2
- package/lock/postgres/drizzle/meta/_journal.json +2 -2
- package/logger/formatters/json.js +1 -1
- package/logger/formatters/pretty-print.js +1 -1
- package/mail/drizzle/{0000_numerous_the_watchers.sql → 0000_cultured_quicksilver.sql} +2 -2
- package/mail/drizzle/meta/0000_snapshot.json +4 -4
- package/mail/drizzle/meta/_journal.json +2 -9
- package/notification/server/drizzle/{0000_wise_pyro.sql → 0000_new_tenebrous.sql} +6 -6
- package/notification/server/drizzle/meta/0000_snapshot.json +7 -7
- package/notification/server/drizzle/meta/_journal.json +2 -2
- package/notification/tests/notification-flow.test.js +1 -8
- package/notification/tests/notification-type.service.test.js +3 -3
- package/openid-connect/oidc.service.js +2 -3
- package/orm/data-types/common.js +1 -1
- package/orm/server/drizzle/schema-converter.js +9 -4
- package/orm/server/encryption.js +1 -1
- package/orm/server/module.d.ts +0 -1
- package/orm/server/module.js +0 -4
- package/orm/server/repository.d.ts +2 -1
- package/orm/server/repository.js +7 -10
- package/orm/tests/encryption.test.js +4 -6
- package/orm/tests/repository-extra-coverage.test.js +0 -2
- package/orm/tests/repository-regression.test.js +0 -3
- package/package.json +9 -8
- package/password/README.md +1 -1
- package/password/have-i-been-pwned.js +1 -1
- package/rate-limit/postgres/drizzle/{0000_watery_rage.sql → 0000_serious_sauron.sql} +1 -1
- package/rate-limit/postgres/drizzle/meta/0000_snapshot.json +2 -2
- package/rate-limit/postgres/drizzle/meta/_journal.json +2 -2
- package/rate-limit/postgres/postgres-rate-limiter.d.ts +1 -1
- package/rate-limit/postgres/postgres-rate-limiter.js +1 -1
- package/rate-limit/rate-limiter.d.ts +1 -1
- package/rpc/tests/rpc.integration.test.js +25 -31
- package/supports.d.ts +1 -0
- package/supports.js +1 -0
- package/task-queue/postgres/drizzle/{0000_faithful_daimon_hellstrom.sql → 0000_dark_ronan.sql} +5 -5
- package/task-queue/postgres/drizzle/meta/0000_snapshot.json +10 -10
- package/task-queue/postgres/drizzle/meta/_journal.json +2 -9
- package/task-queue/postgres/task-queue.js +2 -2
- package/task-queue/tests/coverage-enhancement.test.js +2 -2
- package/test/drizzle/{0000_natural_cannonball.sql → 0000_organic_gamora.sql} +2 -2
- package/test/drizzle/meta/0000_snapshot.json +3 -4
- package/test/drizzle/meta/_journal.json +2 -9
- package/testing/integration-setup.d.ts +7 -3
- package/testing/integration-setup.js +119 -96
- package/utils/alphabet.d.ts +1 -0
- package/utils/alphabet.js +1 -0
- package/utils/base32.d.ts +4 -0
- package/utils/base32.js +49 -0
- package/utils/base64.d.ts +0 -2
- package/utils/base64.js +6 -70
- package/utils/equals.d.ts +13 -3
- package/utils/equals.js +29 -9
- package/utils/index.d.ts +1 -2
- package/utils/index.js +1 -2
- package/utils/random.d.ts +1 -0
- package/utils/random.js +14 -8
- package/authentication/errors/secret-requirements.error.d.ts +0 -5
- package/authentication/models/authentication-credentials.model.d.ts +0 -10
- package/authentication/models/secret-check-result.model.d.ts +0 -3
- package/authentication/server/authentication-secret-requirements.validator.d.ts +0 -55
- package/authentication/tests/authentication-ancillary.service.test.js +0 -13
- package/authentication/tests/authentication-secret-requirements.validator.test.js +0 -29
- package/authentication/tests/secret-requirements.error.test.js +0 -14
- package/mail/drizzle/0001_married_tarantula.sql +0 -12
- package/mail/drizzle/meta/0001_snapshot.json +0 -69
- package/orm/server/tokens.d.ts +0 -1
- package/orm/server/tokens.js +0 -2
- package/task-queue/postgres/drizzle/0001_rapid_infant_terrible.sql +0 -16
- package/task-queue/postgres/drizzle/meta/0001_snapshot.json +0 -753
- package/test/drizzle/0001_closed_the_captain.sql +0 -2
- package/test/drizzle/meta/0001_snapshot.json +0 -117
- package/utils/cryptography.d.ts +0 -137
- package/utils/cryptography.js +0 -201
- /package/authentication/tests/{authentication-ancillary.service.test.d.ts → authentication-password-requirements.validator.test.d.ts} +0 -0
- /package/authentication/tests/{authentication-secret-requirements.validator.test.d.ts → brute-force-protection.test.d.ts} +0 -0
- /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 {
|
|
12
|
-
export class
|
|
11
|
+
import { PasswordRequirementsError } from '../errors/password-requirements.error.js';
|
|
12
|
+
export class AuthenticationPasswordRequirementsValidator {
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
* Default validator for
|
|
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
|
|
21
|
+
let DefaultAuthenticationPasswordRequirementsValidator = class DefaultAuthenticationPasswordRequirementsValidator extends AuthenticationPasswordRequirementsValidator {
|
|
22
22
|
/**
|
|
23
|
-
* Checks the
|
|
24
|
-
* @param
|
|
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
|
|
28
|
-
return await checkPassword(
|
|
27
|
+
async checkPasswordRequirements(password) {
|
|
28
|
+
return await checkPassword(password, { checkForPwned: true });
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
* Tests the
|
|
32
|
-
* @param
|
|
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
|
|
36
|
-
const result = await this.
|
|
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
|
|
47
|
-
* @param
|
|
48
|
-
* @throws {
|
|
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
|
|
51
|
-
const result = await this.
|
|
50
|
+
async validatePasswordRequirements(password) {
|
|
51
|
+
const result = await this.testPasswordRequirements(password);
|
|
52
52
|
if (!result.success) {
|
|
53
|
-
throw new
|
|
53
|
+
throw new PasswordRequirementsError(result.reason);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
};
|
|
57
|
-
|
|
58
|
-
Singleton({ alias:
|
|
59
|
-
],
|
|
60
|
-
export {
|
|
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
|
|
16
|
+
* @template AdditionalInitPasswordResetData Type of additional password reset data
|
|
15
17
|
*/
|
|
16
|
-
export declare class AuthenticationApiController<AdditionalTokenPayload extends Record, AuthenticationData,
|
|
17
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
51
|
-
|
|
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
|
|
67
|
+
* Initialize a password reset.
|
|
54
68
|
* @param parameters The parameters for the request.
|
|
55
|
-
* @returns 'ok' if the
|
|
69
|
+
* @returns 'ok' if the password reset was initialized.
|
|
56
70
|
*/
|
|
57
|
-
|
|
71
|
+
initPasswordReset({ request, parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'initPasswordReset'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'initPasswordReset'>>;
|
|
58
72
|
/**
|
|
59
|
-
* Reset a
|
|
73
|
+
* Reset a password.
|
|
60
74
|
* @param parameters The parameters for the request.
|
|
61
|
-
* @returns 'ok' if the
|
|
75
|
+
* @returns 'ok' if the password was reset.
|
|
62
76
|
*/
|
|
63
|
-
|
|
77
|
+
resetPassword({ request, parameters, getAuditor }: ApiRequestContext<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'resetPassword'>): Promise<ApiServerResult<AuthenticationApiDefinition<AdditionalTokenPayload, AuthenticationData, AdditionalInitPasswordResetData>, 'resetPassword'>>;
|
|
64
78
|
/**
|
|
65
|
-
* Check a
|
|
79
|
+
* Check a password.
|
|
66
80
|
* @param parameters The parameters for the request.
|
|
67
|
-
* @returns The result of the
|
|
81
|
+
* @returns The result of the password check.
|
|
68
82
|
*/
|
|
69
|
-
|
|
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,
|
|
75
|
-
|
|
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
|
|
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
|
|
113
|
+
* @template AdditionalInitPasswordResetData Type of additional password reset data.
|
|
86
114
|
*/
|
|
87
|
-
export declare function getAuthenticationApiController<AdditionalTokenPayload extends Record, AuthenticationData,
|
|
88
|
-
additionalTokenPayloadSchema: ObjectSchemaOrType<AdditionalTokenPayload>, authenticationDataSchema: SchemaTestable<AuthenticationData>,
|
|
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
|
|
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
|
|
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
|
|
34
|
-
|
|
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
|
|
45
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
|
108
|
-
|
|
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
|
|
205
|
+
* Initialize a password reset.
|
|
113
206
|
* @param parameters The parameters for the request.
|
|
114
|
-
* @returns 'ok' if the
|
|
207
|
+
* @returns 'ok' if the password reset was initialized.
|
|
115
208
|
*/
|
|
116
|
-
async
|
|
117
|
-
|
|
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
|
|
226
|
+
* Reset a password.
|
|
122
227
|
* @param parameters The parameters for the request.
|
|
123
|
-
* @returns 'ok' if the
|
|
228
|
+
* @returns 'ok' if the password was reset.
|
|
124
229
|
*/
|
|
125
|
-
async
|
|
126
|
-
|
|
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
|
|
238
|
+
* Check a password.
|
|
131
239
|
* @param parameters The parameters for the request.
|
|
132
|
-
* @returns The result of the
|
|
240
|
+
* @returns The result of the password check.
|
|
133
241
|
*/
|
|
134
|
-
async
|
|
135
|
-
return await this.authenticationService.
|
|
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
|
-
|
|
145
|
-
|
|
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
|
-
|
|
153
|
-
value:
|
|
317
|
+
token: {
|
|
318
|
+
value: token,
|
|
154
319
|
...cookieBaseOptions,
|
|
155
320
|
expires: remember ? (jsonToken.payload.exp * 1000) : undefined,
|
|
156
321
|
},
|
|
157
322
|
refreshToken: {
|
|
158
|
-
value:
|
|
323
|
+
value: refreshToken,
|
|
159
324
|
...cookieBaseOptions,
|
|
160
325
|
expires: remember ? (jsonToken.payload.refreshTokenExp * 1000) : undefined,
|
|
161
326
|
},
|
|
162
327
|
},
|
|
163
328
|
body: {
|
|
164
|
-
json:
|
|
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
|
|
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
|
|
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,
|
|
197
|
-
const apiDefinition = getAuthenticationApiDefinition(additionalTokenPayloadSchema, authenticationDataSchema,
|
|
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([
|