@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { HttpServerRequest } from '../../http/server/index.js';
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
2
|
+
import type { Record } from '../../types/index.js';
|
|
3
|
+
import type { PasswordResetToken, RefreshToken, Token } from '../models/index.js';
|
|
4
4
|
/**
|
|
5
5
|
* Tries to get the authorization token string from a request.
|
|
6
6
|
* @param request The request to get the token from.
|
|
@@ -13,42 +13,42 @@ export declare function tryGetAuthorizationTokenStringFromRequest(request: HttpS
|
|
|
13
13
|
* Tries to get a token from a request.
|
|
14
14
|
* @param request The request to get the token from.
|
|
15
15
|
* @param tokenVersion The version of the token to get.
|
|
16
|
-
* @param
|
|
16
|
+
* @param key The CryptoKey to use for validation.
|
|
17
17
|
* @returns The token or undefined if not found.
|
|
18
18
|
* @throws {InvalidTokenError} If the token is invalid.
|
|
19
19
|
*/
|
|
20
|
-
export declare function tryGetTokenFromRequest<AdditionalTokenPayload extends Record = Record<never>>(request: HttpServerRequest, tokenVersion: number,
|
|
20
|
+
export declare function tryGetTokenFromRequest<AdditionalTokenPayload extends Record = Record<never>>(request: HttpServerRequest, tokenVersion: number, key: CryptoKey): Promise<Token<AdditionalTokenPayload> | undefined>;
|
|
21
21
|
/**
|
|
22
22
|
* Gets a token from a request.
|
|
23
23
|
* @param request The request to get the token from.
|
|
24
24
|
* @param tokenVersion The version of the token to get.
|
|
25
|
-
* @param
|
|
25
|
+
* @param key The CryptoKey to use for validation.
|
|
26
26
|
* @returns The token.
|
|
27
27
|
* @throws {InvalidTokenError} If the token is invalid or not found.
|
|
28
28
|
*/
|
|
29
|
-
export declare function getTokenFromRequest<AdditionalTokenPayload extends Record = Record<never>>(request: HttpServerRequest, tokenVersion: number,
|
|
29
|
+
export declare function getTokenFromRequest<AdditionalTokenPayload extends Record = Record<never>>(request: HttpServerRequest, tokenVersion: number, key: CryptoKey): Promise<Token<AdditionalTokenPayload>>;
|
|
30
30
|
/**
|
|
31
31
|
* Gets a token from a token string.
|
|
32
32
|
* @param tokenString The token string to get the token from.
|
|
33
33
|
* @param tokenVersion The version of the token to get.
|
|
34
|
-
* @param
|
|
34
|
+
* @param key The CryptoKey to use for validation.
|
|
35
35
|
* @returns The token.
|
|
36
36
|
* @throws {InvalidTokenError} If the token is invalid.
|
|
37
37
|
*/
|
|
38
|
-
export declare function getTokenFromString<AdditionalTokenPayload extends Record = Record<never>>(tokenString: string, tokenVersion: number,
|
|
38
|
+
export declare function getTokenFromString<AdditionalTokenPayload extends Record = Record<never>>(tokenString: string, tokenVersion: number, key: CryptoKey): Promise<Token<AdditionalTokenPayload>>;
|
|
39
39
|
/**
|
|
40
40
|
* Gets a refresh token from a token string.
|
|
41
41
|
* @param tokenString The token string to get the refresh token from.
|
|
42
|
-
* @param
|
|
42
|
+
* @param key The CryptoKey to use for validation.
|
|
43
43
|
* @returns The refresh token.
|
|
44
44
|
* @throws {InvalidTokenError} If the refresh token is invalid.
|
|
45
45
|
*/
|
|
46
|
-
export declare function getRefreshTokenFromString(tokenString: string,
|
|
46
|
+
export declare function getRefreshTokenFromString(tokenString: string, key: CryptoKey): Promise<RefreshToken>;
|
|
47
47
|
/**
|
|
48
|
-
* Gets a
|
|
49
|
-
* @param tokenString The token string to get the
|
|
50
|
-
* @param
|
|
51
|
-
* @returns The
|
|
52
|
-
* @throws {InvalidTokenError} If the
|
|
48
|
+
* Gets a password reset token from a token string.
|
|
49
|
+
* @param tokenString The token string to get the password reset token from.
|
|
50
|
+
* @param key The CryptoKey to use for validation.
|
|
51
|
+
* @returns The password reset token.
|
|
52
|
+
* @throws {InvalidTokenError} If the password reset token is invalid.
|
|
53
53
|
*/
|
|
54
|
-
export declare function
|
|
54
|
+
export declare function getPasswordResetTokenFromString(tokenString: string, key: CryptoKey): Promise<PasswordResetToken>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { parseAndValidateJwtTokenString } from '../../cryptography/index.js';
|
|
1
2
|
import { BadRequestError } from '../../errors/bad-request.error.js';
|
|
2
3
|
import { InvalidTokenError } from '../../errors/invalid-token.error.js';
|
|
3
4
|
import { currentTimestampSeconds } from '../../utils/date-time.js';
|
|
4
|
-
import { parseAndValidateJwtTokenString } from '../../utils/jwt.js';
|
|
5
5
|
import { isArray, isDefined, isUndefined } from '../../utils/type-guards.js';
|
|
6
6
|
/**
|
|
7
7
|
* Tries to get the authorization token string from a request.
|
|
@@ -10,8 +10,8 @@ import { isArray, isDefined, isUndefined } from '../../utils/type-guards.js';
|
|
|
10
10
|
* @param fromCookieOnly Whether to only get the token from the cookie.
|
|
11
11
|
* @returns The token string or undefined if not found.
|
|
12
12
|
*/
|
|
13
|
-
export function tryGetAuthorizationTokenStringFromRequest(request, cookieName = '
|
|
14
|
-
const headerName = (cookieName.toLocaleLowerCase() == '
|
|
13
|
+
export function tryGetAuthorizationTokenStringFromRequest(request, cookieName = 'token', fromCookieOnly = false) {
|
|
14
|
+
const headerName = (cookieName.toLocaleLowerCase() == 'token')
|
|
15
15
|
? 'Authorization'
|
|
16
16
|
: (cookieName.toLocaleLowerCase() == 'refreshtoken')
|
|
17
17
|
? 'X-Refresh-Token'
|
|
@@ -19,47 +19,46 @@ export function tryGetAuthorizationTokenStringFromRequest(request, cookieName =
|
|
|
19
19
|
? 'X-Impersonator-Refresh-Token'
|
|
20
20
|
: undefined;
|
|
21
21
|
const headerValue = (fromCookieOnly || isUndefined(headerName)) ? undefined : request.headers.tryGet(headerName);
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const spaceIndex = authorizationString.indexOf(' ');
|
|
22
|
+
const headerTokenString = (isArray(headerValue) ? headerValue[0] : headerValue);
|
|
23
|
+
if (isDefined(headerTokenString)) {
|
|
24
|
+
const spaceIndex = headerTokenString.indexOf(' ');
|
|
26
25
|
if (spaceIndex == -1) {
|
|
27
|
-
|
|
26
|
+
throw new BadRequestError('Missing authorization scheme.');
|
|
28
27
|
}
|
|
29
|
-
const authorizationScheme =
|
|
28
|
+
const authorizationScheme = headerTokenString.slice(0, spaceIndex).trim().toLowerCase();
|
|
30
29
|
if (authorizationScheme != 'bearer') {
|
|
31
30
|
throw new BadRequestError(`Unsupported authorization scheme "${authorizationScheme}".`);
|
|
32
31
|
}
|
|
33
|
-
const authorization =
|
|
32
|
+
const authorization = headerTokenString.slice(spaceIndex).trim();
|
|
34
33
|
return authorization;
|
|
35
34
|
}
|
|
36
|
-
return
|
|
35
|
+
return request.cookies.tryGet(cookieName);
|
|
37
36
|
}
|
|
38
37
|
/**
|
|
39
38
|
* Tries to get a token from a request.
|
|
40
39
|
* @param request The request to get the token from.
|
|
41
40
|
* @param tokenVersion The version of the token to get.
|
|
42
|
-
* @param
|
|
41
|
+
* @param key The CryptoKey to use for validation.
|
|
43
42
|
* @returns The token or undefined if not found.
|
|
44
43
|
* @throws {InvalidTokenError} If the token is invalid.
|
|
45
44
|
*/
|
|
46
|
-
export async function tryGetTokenFromRequest(request, tokenVersion,
|
|
45
|
+
export async function tryGetTokenFromRequest(request, tokenVersion, key) {
|
|
47
46
|
const tokenString = tryGetAuthorizationTokenStringFromRequest(request);
|
|
48
47
|
if (isUndefined(tokenString)) {
|
|
49
48
|
return undefined;
|
|
50
49
|
}
|
|
51
|
-
return await getTokenFromString(tokenString, tokenVersion,
|
|
50
|
+
return await getTokenFromString(tokenString, tokenVersion, key);
|
|
52
51
|
}
|
|
53
52
|
/**
|
|
54
53
|
* Gets a token from a request.
|
|
55
54
|
* @param request The request to get the token from.
|
|
56
55
|
* @param tokenVersion The version of the token to get.
|
|
57
|
-
* @param
|
|
56
|
+
* @param key The CryptoKey to use for validation.
|
|
58
57
|
* @returns The token.
|
|
59
58
|
* @throws {InvalidTokenError} If the token is invalid or not found.
|
|
60
59
|
*/
|
|
61
|
-
export async function getTokenFromRequest(request, tokenVersion,
|
|
62
|
-
const token = await tryGetTokenFromRequest(request, tokenVersion,
|
|
60
|
+
export async function getTokenFromRequest(request, tokenVersion, key) {
|
|
61
|
+
const token = await tryGetTokenFromRequest(request, tokenVersion, key);
|
|
63
62
|
if (isUndefined(token)) {
|
|
64
63
|
throw new InvalidTokenError('Missing authorization token');
|
|
65
64
|
}
|
|
@@ -69,15 +68,15 @@ export async function getTokenFromRequest(request, tokenVersion, secret) {
|
|
|
69
68
|
* Gets a token from a token string.
|
|
70
69
|
* @param tokenString The token string to get the token from.
|
|
71
70
|
* @param tokenVersion The version of the token to get.
|
|
72
|
-
* @param
|
|
71
|
+
* @param key The CryptoKey to use for validation.
|
|
73
72
|
* @returns The token.
|
|
74
73
|
* @throws {InvalidTokenError} If the token is invalid.
|
|
75
74
|
*/
|
|
76
|
-
export async function getTokenFromString(tokenString, tokenVersion,
|
|
75
|
+
export async function getTokenFromString(tokenString, tokenVersion, key) {
|
|
77
76
|
if (isUndefined(tokenString)) {
|
|
78
77
|
throw new InvalidTokenError('Missing authorization token');
|
|
79
78
|
}
|
|
80
|
-
const validatedToken = await parseAndValidateJwtTokenString(tokenString, '
|
|
79
|
+
const validatedToken = await parseAndValidateJwtTokenString(tokenString, 'KMAC256', key);
|
|
81
80
|
if (validatedToken.header.v != tokenVersion) {
|
|
82
81
|
throw new InvalidTokenError('Invalid token version');
|
|
83
82
|
}
|
|
@@ -89,34 +88,34 @@ export async function getTokenFromString(tokenString, tokenVersion, secret) {
|
|
|
89
88
|
/**
|
|
90
89
|
* Gets a refresh token from a token string.
|
|
91
90
|
* @param tokenString The token string to get the refresh token from.
|
|
92
|
-
* @param
|
|
91
|
+
* @param key The CryptoKey to use for validation.
|
|
93
92
|
* @returns The refresh token.
|
|
94
93
|
* @throws {InvalidTokenError} If the refresh token is invalid.
|
|
95
94
|
*/
|
|
96
|
-
export async function getRefreshTokenFromString(tokenString,
|
|
95
|
+
export async function getRefreshTokenFromString(tokenString, key) {
|
|
97
96
|
if (isUndefined(tokenString)) {
|
|
98
97
|
throw new InvalidTokenError('Missing refresh token');
|
|
99
98
|
}
|
|
100
|
-
const validatedRefreshToken = await parseAndValidateJwtTokenString(tokenString, '
|
|
99
|
+
const validatedRefreshToken = await parseAndValidateJwtTokenString(tokenString, 'KMAC256', key);
|
|
101
100
|
if (validatedRefreshToken.payload.exp <= currentTimestampSeconds()) {
|
|
102
101
|
throw new InvalidTokenError('Refresh token expired.');
|
|
103
102
|
}
|
|
104
103
|
return validatedRefreshToken;
|
|
105
104
|
}
|
|
106
105
|
/**
|
|
107
|
-
* Gets a
|
|
108
|
-
* @param tokenString The token string to get the
|
|
109
|
-
* @param
|
|
110
|
-
* @returns The
|
|
111
|
-
* @throws {InvalidTokenError} If the
|
|
106
|
+
* Gets a password reset token from a token string.
|
|
107
|
+
* @param tokenString The token string to get the password reset token from.
|
|
108
|
+
* @param key The CryptoKey to use for validation.
|
|
109
|
+
* @returns The password reset token.
|
|
110
|
+
* @throws {InvalidTokenError} If the password reset token is invalid.
|
|
112
111
|
*/
|
|
113
|
-
export async function
|
|
112
|
+
export async function getPasswordResetTokenFromString(tokenString, key) {
|
|
114
113
|
if (isUndefined(tokenString)) {
|
|
115
|
-
throw new InvalidTokenError('Missing
|
|
114
|
+
throw new InvalidTokenError('Missing password reset token');
|
|
116
115
|
}
|
|
117
|
-
const
|
|
118
|
-
if (
|
|
119
|
-
throw new InvalidTokenError('
|
|
116
|
+
const validatedPasswordResetToken = await parseAndValidateJwtTokenString(tokenString, 'KMAC256', key);
|
|
117
|
+
if (validatedPasswordResetToken.payload.exp <= currentTimestampSeconds()) {
|
|
118
|
+
throw new InvalidTokenError('Password reset token expired.');
|
|
120
119
|
}
|
|
121
|
-
return
|
|
120
|
+
return validatedPasswordResetToken;
|
|
122
121
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from './authentication-ancillary.service.js';
|
|
2
2
|
export * from './authentication-api-request-token.provider.js';
|
|
3
|
-
export * from './authentication-
|
|
3
|
+
export * from './authentication-password-requirements.validator.js';
|
|
4
4
|
export * from './authentication.api-controller.js';
|
|
5
5
|
export * from './authentication.service.js';
|
|
6
6
|
export * from './helper.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from './authentication-ancillary.service.js';
|
|
2
2
|
export * from './authentication-api-request-token.provider.js';
|
|
3
|
-
export * from './authentication-
|
|
3
|
+
export * from './authentication-password-requirements.validator.js';
|
|
4
4
|
export * from './authentication.api-controller.js';
|
|
5
5
|
export * from './authentication.service.js';
|
|
6
6
|
export * from './helper.js';
|
|
@@ -16,7 +16,7 @@ export declare class AuthenticationModuleConfig {
|
|
|
16
16
|
/**
|
|
17
17
|
* Options for {@link AuthenticationService}.
|
|
18
18
|
*/
|
|
19
|
-
serviceOptions
|
|
19
|
+
serviceOptions?: AuthenticationServiceOptions | Provider<AuthenticationServiceOptions>;
|
|
20
20
|
/**
|
|
21
21
|
* Override default {@link AuthenticationService}.
|
|
22
22
|
*/
|
|
@@ -35,7 +35,7 @@ export declare class AuthenticationModuleConfig {
|
|
|
35
35
|
* Configures authentication server services.
|
|
36
36
|
* @param config Configuration.
|
|
37
37
|
*/
|
|
38
|
-
export declare function configureAuthenticationServer({ injector, ...config }
|
|
38
|
+
export declare function configureAuthenticationServer({ injector, ...config }?: AuthenticationModuleConfig & {
|
|
39
39
|
injector?: Injector;
|
|
40
40
|
}): void;
|
|
41
41
|
/**
|
|
@@ -38,11 +38,13 @@ export class AuthenticationModuleConfig {
|
|
|
38
38
|
* Configures authentication server services.
|
|
39
39
|
* @param config Configuration.
|
|
40
40
|
*/
|
|
41
|
-
export function configureAuthenticationServer({ injector, ...config }) {
|
|
41
|
+
export function configureAuthenticationServer({ injector, ...config } = {}) {
|
|
42
42
|
const targetInjector = injector ?? Injector;
|
|
43
43
|
targetInjector.register(AuthenticationModuleConfig, { useValue: config });
|
|
44
|
-
targetInjector.register(AuthenticationServiceOptions, isProvider(config.serviceOptions) ? config.serviceOptions : { useValue: config.serviceOptions });
|
|
45
44
|
targetInjector.register(ApiRequestTokenProvider, { useToken: AuthenticationApiRequestTokenProvider });
|
|
45
|
+
if (isDefined(config.serviceOptions)) {
|
|
46
|
+
targetInjector.register(AuthenticationServiceOptions, isProvider(config.serviceOptions) ? config.serviceOptions : { useValue: config.serviceOptions });
|
|
47
|
+
}
|
|
46
48
|
if (isDefined(config.authenticationService)) {
|
|
47
49
|
targetInjector.registerSingleton(AuthenticationService, { useToken: config.authenticationService });
|
|
48
50
|
}
|
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AuthenticationPassword, AuthenticationSession, AuthenticationTotp, AuthenticationTotpRecoveryCode, AuthenticationUsedTotpToken, ServiceAccount, Subject, SystemAccount, User } from '../models/index.js';
|
|
2
2
|
export declare const authenticationSchema: import("../../orm/server/index.js").DatabaseSchema<"authentication">;
|
|
3
|
+
export declare const subjectStatus: import("../../orm/enums.js").PgEnumFromEnumeration<{
|
|
4
|
+
readonly Active: "active";
|
|
5
|
+
readonly Suspended: "suspended";
|
|
6
|
+
}>;
|
|
3
7
|
export declare const subjectType: import("../../orm/enums.js").PgEnumFromEnumeration<{
|
|
4
8
|
readonly System: "system";
|
|
5
9
|
readonly User: "user";
|
|
6
10
|
readonly ServiceAccount: "service-account";
|
|
7
11
|
}>;
|
|
8
|
-
export declare const
|
|
12
|
+
export declare const totpStatus: import("../../orm/enums.js").PgEnumFromEnumeration<{
|
|
13
|
+
readonly Pending: "pending";
|
|
9
14
|
readonly Active: "active";
|
|
10
|
-
readonly Inactive: "inactive";
|
|
11
|
-
readonly Suspended: "suspended";
|
|
12
|
-
readonly PendingApproval: "pending-approval";
|
|
13
|
-
readonly Invited: "invited";
|
|
14
15
|
}>;
|
|
15
|
-
export declare const
|
|
16
|
+
export declare const authenticationPasswords: import("../../orm/server/types.js").PgTableFromType<typeof AuthenticationPassword, "authentication">;
|
|
16
17
|
export declare const authenticationSession: import("../../orm/server/types.js").PgTableFromType<typeof AuthenticationSession, "authentication">;
|
|
18
|
+
export declare const authenticationTotp: import("../../orm/server/types.js").PgTableFromType<typeof AuthenticationTotp, "authentication">;
|
|
19
|
+
export declare const authenticationTotpRecoveryCode: import("../../orm/server/types.js").PgTableFromType<typeof AuthenticationTotpRecoveryCode, "authentication">;
|
|
20
|
+
export declare const authenticationUsedTotpToken: import("../../orm/server/types.js").PgTableFromType<typeof AuthenticationUsedTotpToken, "authentication">;
|
|
17
21
|
export declare const serviceAccount: import("../../orm/server/types.js").PgTableFromType<typeof ServiceAccount, "authentication">;
|
|
18
22
|
export declare const subject: import("../../orm/server/types.js").PgTableFromType<typeof Subject, "authentication">;
|
|
19
23
|
export declare const systemAccount: import("../../orm/server/types.js").PgTableFromType<typeof SystemAccount, "authentication">;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { databaseSchema } from '../../orm/server/index.js';
|
|
2
|
-
import {
|
|
2
|
+
import { AuthenticationPassword, AuthenticationSession, AuthenticationTotp, AuthenticationTotpRecoveryCode, AuthenticationUsedTotpToken, ServiceAccount, Subject, SubjectStatus, SubjectType, SystemAccount, TotpStatus, User } from '../models/index.js';
|
|
3
3
|
export const authenticationSchema = databaseSchema('authentication');
|
|
4
|
-
export const subjectType = authenticationSchema.getEnum(SubjectType);
|
|
5
4
|
export const subjectStatus = authenticationSchema.getEnum(SubjectStatus);
|
|
6
|
-
export const
|
|
5
|
+
export const subjectType = authenticationSchema.getEnum(SubjectType);
|
|
6
|
+
export const totpStatus = authenticationSchema.getEnum(TotpStatus);
|
|
7
|
+
export const authenticationPasswords = authenticationSchema.getTable(AuthenticationPassword);
|
|
7
8
|
export const authenticationSession = authenticationSchema.getTable(AuthenticationSession);
|
|
9
|
+
export const authenticationTotp = authenticationSchema.getTable(AuthenticationTotp);
|
|
10
|
+
export const authenticationTotpRecoveryCode = authenticationSchema.getTable(AuthenticationTotpRecoveryCode);
|
|
11
|
+
export const authenticationUsedTotpToken = authenticationSchema.getTable(AuthenticationUsedTotpToken);
|
|
8
12
|
export const serviceAccount = authenticationSchema.getTable(ServiceAccount);
|
|
9
13
|
export const subject = authenticationSchema.getTable(Subject);
|
|
10
14
|
export const systemAccount = authenticationSchema.getTable(SystemAccount);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { PasswordRequirementsError } from '../errors/password-requirements.error.js';
|
|
3
|
+
import { DefaultAuthenticationPasswordRequirementsValidator } from '../server/authentication-password-requirements.validator.js';
|
|
4
|
+
describe('DefaultAuthenticationPasswordRequirementsValidator', () => {
|
|
5
|
+
const validator = new DefaultAuthenticationPasswordRequirementsValidator();
|
|
6
|
+
it('should return success when password is strong and not pwned', async () => {
|
|
7
|
+
// A very long random string is unlikely to be pwned and will be strong
|
|
8
|
+
const result = await validator.testPasswordRequirements('Very-Strong-And-Long-Password-2026!@#$%^&*()');
|
|
9
|
+
expect(result.success).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
it('should return failure when password is pwned', async () => {
|
|
12
|
+
// "password" is definitely pwned
|
|
13
|
+
const result = await validator.testPasswordRequirements('password');
|
|
14
|
+
expect(result.success).toBe(false);
|
|
15
|
+
expect(result.reason).toContain('exposed in data breach');
|
|
16
|
+
});
|
|
17
|
+
it('should return failure when password is too weak', async () => {
|
|
18
|
+
// "abc" is too weak (and likely pwned)
|
|
19
|
+
const result = await validator.testPasswordRequirements('abc');
|
|
20
|
+
expect(result.success).toBe(false);
|
|
21
|
+
expect(result.reason).toBeDefined();
|
|
22
|
+
});
|
|
23
|
+
it('should throw PasswordRequirementsError on validation failure', async () => {
|
|
24
|
+
await expect(validator.validatePasswordRequirements('abc')).rejects.toThrow(PasswordRequirementsError);
|
|
25
|
+
});
|
|
26
|
+
it('should not throw on validation success', async () => {
|
|
27
|
+
await expect(validator.validatePasswordRequirements('Very-Strong-And-Long-Password-2026!@#$%^&*()')).resolves.not.toThrow();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { afterAll, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { Auditor } from '../../audit/index.js';
|
|
2
3
|
import { AuthenticationClientService } from '../../authentication/client/index.js';
|
|
3
4
|
import { AuthenticationAncillaryService, AuthenticationService as AuthenticationServerService } from '../../authentication/server/index.js';
|
|
4
5
|
import { HttpClientOptions } from '../../http/client/index.js';
|
|
5
6
|
import { HttpServer } from '../../http/server/index.js';
|
|
6
7
|
import { runInInjectionContext } from '../../injector/index.js';
|
|
7
8
|
import { clearTenantData, setupIntegrationTest } from '../../testing/index.js';
|
|
9
|
+
import { currentTimestampSeconds } from '../../utils/date-time.js';
|
|
8
10
|
import { SubjectService } from '../server/subject.service.js';
|
|
9
11
|
import { DefaultAuthenticationAncillaryService } from './authentication.test-ancillary-service.js';
|
|
10
12
|
describe('AuthenticationApiController Integration', () => {
|
|
@@ -26,7 +28,9 @@ describe('AuthenticationApiController Integration', () => {
|
|
|
26
28
|
};
|
|
27
29
|
({ injector, database } = await setupIntegrationTest({
|
|
28
30
|
modules: { authentication: true, audit: true, keyValueStore: true, signals: true, api: true, webServer: true },
|
|
29
|
-
|
|
31
|
+
authentication: {
|
|
32
|
+
ancillaryService: DefaultAuthenticationAncillaryService,
|
|
33
|
+
},
|
|
30
34
|
}));
|
|
31
35
|
await runInInjectionContext(injector, async () => {
|
|
32
36
|
server = injector.resolve(HttpServer);
|
|
@@ -44,12 +48,12 @@ describe('AuthenticationApiController Integration', () => {
|
|
|
44
48
|
});
|
|
45
49
|
beforeEach(async () => {
|
|
46
50
|
globalThis.localStorage?.clear();
|
|
47
|
-
await clearTenantData(database, schema, ['
|
|
51
|
+
await clearTenantData(database, schema, ['used_totp_tokens', 'totp_recovery_code', 'totp', 'password', 'session', 'user', 'service_account', 'system_account', 'subject'], tenantId);
|
|
48
52
|
});
|
|
49
53
|
test('login should work via API client', async () => {
|
|
50
54
|
await runInInjectionContext(injector, async () => {
|
|
51
55
|
const user = await subjectService.createUser({ tenantId, email: 'api-login@example.com', firstName: 'A', lastName: 'L' });
|
|
52
|
-
await serverService.
|
|
56
|
+
await serverService.setPassword(user, 'Strong-Password-2026!');
|
|
53
57
|
await service.login({ tenantId, subject: user.id }, 'Strong-Password-2026!');
|
|
54
58
|
expect(service.isLoggedIn()).toBe(true);
|
|
55
59
|
expect(service.subjectId()).toBe(user.id);
|
|
@@ -58,9 +62,9 @@ describe('AuthenticationApiController Integration', () => {
|
|
|
58
62
|
test('login should work even if an expired token is present in the Authorization header', async () => {
|
|
59
63
|
await runInInjectionContext(injector, async () => {
|
|
60
64
|
const user = await subjectService.createUser({ tenantId, email: 'expired-token-login@example.com', firstName: 'E', lastName: 'L' });
|
|
61
|
-
await serverService.
|
|
65
|
+
await serverService.setPassword(user, 'Strong-Password-2026!');
|
|
62
66
|
// Create an expired token
|
|
63
|
-
const now =
|
|
67
|
+
const now = currentTimestampSeconds();
|
|
64
68
|
const expiredTokenResult = await serverService.createToken({
|
|
65
69
|
subject: user,
|
|
66
70
|
sessionId: crypto.randomUUID(),
|
|
@@ -72,21 +76,21 @@ describe('AuthenticationApiController Integration', () => {
|
|
|
72
76
|
timestamp: (now - 7200) * 1000,
|
|
73
77
|
});
|
|
74
78
|
// Inject the expired token into the client service
|
|
75
|
-
service.updateRawTokens(expiredTokenResult.token);
|
|
79
|
+
service.updateRawTokens(`Bearer ${expiredTokenResult.token}`);
|
|
76
80
|
// Now try to login
|
|
77
81
|
await service.login({ tenantId, subject: user.id }, 'Strong-Password-2026!');
|
|
78
82
|
expect(service.isLoggedIn()).toBe(true);
|
|
79
83
|
expect(service.subjectId()).toBe(user.id);
|
|
80
84
|
});
|
|
81
85
|
});
|
|
82
|
-
test('
|
|
83
|
-
const result = await service.
|
|
86
|
+
test('checkPassword should work via API client', async () => {
|
|
87
|
+
const result = await service.checkPassword('abc');
|
|
84
88
|
expect(result.strength).toBeLessThan(2);
|
|
85
89
|
});
|
|
86
90
|
test('refresh should work via API client', async () => {
|
|
87
91
|
await runInInjectionContext(injector, async () => {
|
|
88
92
|
const user = await subjectService.createUser({ tenantId, email: 'api-refresh@example.com', firstName: 'A', lastName: 'L' });
|
|
89
|
-
await serverService.
|
|
93
|
+
await serverService.setPassword(user, 'Strong-Password-2026!');
|
|
90
94
|
await service.login({ tenantId, subject: user.id }, 'Strong-Password-2026!');
|
|
91
95
|
const initialToken = service.token()?.jti;
|
|
92
96
|
await service.refresh();
|
|
@@ -97,7 +101,7 @@ describe('AuthenticationApiController Integration', () => {
|
|
|
97
101
|
await runInInjectionContext(injector, async () => {
|
|
98
102
|
const admin = await subjectService.createUser({ tenantId, email: 'api-admin@example.com', firstName: 'A', lastName: 'D' });
|
|
99
103
|
const user = await subjectService.createUser({ tenantId, email: 'api-user@example.com', firstName: 'U', lastName: 'S' });
|
|
100
|
-
await serverService.
|
|
104
|
+
await serverService.setPassword(admin, 'Admin-Pass-123!');
|
|
101
105
|
expect(await subjectService.exists(tenantId, admin.id)).toBe(true);
|
|
102
106
|
expect(await subjectService.exists(tenantId, user.id)).toBe(true);
|
|
103
107
|
await service.login({ tenantId, subject: admin.id }, 'Admin-Pass-123!');
|
|
@@ -108,15 +112,45 @@ describe('AuthenticationApiController Integration', () => {
|
|
|
108
112
|
expect(service.subjectId()).toBe(admin.id);
|
|
109
113
|
});
|
|
110
114
|
});
|
|
111
|
-
test('
|
|
115
|
+
test('password reset should work via API client', async () => {
|
|
112
116
|
await runInInjectionContext(injector, async () => {
|
|
113
117
|
const user = await subjectService.createUser({ tenantId, email: 'api-reset@example.com', firstName: 'A', lastName: 'L' });
|
|
114
|
-
const ancillaryService = injector.
|
|
115
|
-
await service.
|
|
116
|
-
expect(ancillaryService.lastResetData).toBeDefined();
|
|
117
|
-
await service.
|
|
118
|
+
const ancillaryService = await injector.resolveAsync(AuthenticationAncillaryService);
|
|
119
|
+
await service.initPasswordReset({ tenantId, subject: user.id }, undefined);
|
|
120
|
+
await vi.waitFor(() => expect(ancillaryService.lastResetData).toBeDefined(), { timeout: 5000, interval: 50 });
|
|
121
|
+
await service.resetPassword(ancillaryService.lastResetData.token, 'New-API-Pass-123!');
|
|
118
122
|
await service.login({ tenantId, subject: user.id }, 'New-API-Pass-123!');
|
|
119
123
|
expect(service.isLoggedIn()).toBe(true);
|
|
120
124
|
});
|
|
121
125
|
});
|
|
126
|
+
test('listSessions and invalidateAllOtherSessions should work via API client', async () => {
|
|
127
|
+
await runInInjectionContext(injector, async () => {
|
|
128
|
+
const user = await subjectService.createUser({ tenantId, email: 'api-sessions@example.com', firstName: 'S', lastName: 'E' });
|
|
129
|
+
await serverService.setPassword(user, 'Strong-Password-2026!');
|
|
130
|
+
// Create two sessions
|
|
131
|
+
const auditor = injector.resolve(Auditor);
|
|
132
|
+
await serverService.login({ tenantId, subject: user.id }, 'Strong-Password-2026!', undefined, auditor);
|
|
133
|
+
await service.login({ tenantId, subject: user.id }, 'Strong-Password-2026!');
|
|
134
|
+
const sessions = await service.listSessions();
|
|
135
|
+
expect(sessions.length).toBe(2);
|
|
136
|
+
expect(sessions[0]).toHaveProperty('id');
|
|
137
|
+
expect(sessions[0]).toHaveProperty('begin');
|
|
138
|
+
expect(sessions[0]).toHaveProperty('end');
|
|
139
|
+
await service.invalidateAllOtherSessions();
|
|
140
|
+
const updatedSessions = await service.listSessions();
|
|
141
|
+
// Only the current session should remain
|
|
142
|
+
expect(updatedSessions.length).toBe(1);
|
|
143
|
+
expect(updatedSessions[0].id).toBe(service.sessionId());
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
test('changePassword should work via API client', async () => {
|
|
147
|
+
await runInInjectionContext(injector, async () => {
|
|
148
|
+
const user = await subjectService.createUser({ tenantId, email: 'api-change-secret@example.com', firstName: 'C', lastName: 'S' });
|
|
149
|
+
await serverService.setPassword(user, 'Old-Strong-Password-2026!');
|
|
150
|
+
await service.login({ tenantId, subject: user.id }, 'Old-Strong-Password-2026!');
|
|
151
|
+
await service.changePassword('Old-Strong-Password-2026!', 'New-Strong-Password-2026!');
|
|
152
|
+
const authResult = await serverService.authenticateWithPassword({ tenantId, subject: user.id }, 'New-Strong-Password-2026!');
|
|
153
|
+
expect(authResult.success).toBe(true);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
122
156
|
});
|
|
@@ -14,6 +14,7 @@ import { Lock } from '../../lock/index.js';
|
|
|
14
14
|
import { Logger } from '../../logger/index.js';
|
|
15
15
|
import { MessageBus } from '../../message-bus/index.js';
|
|
16
16
|
import { configureDefaultSignalsImplementation } from '../../signals/implementation/configure.js';
|
|
17
|
+
import { currentTimestampSeconds } from '../../utils/date-time.js';
|
|
17
18
|
import { timeout } from '../../utils/timing.js';
|
|
18
19
|
describe('AuthenticationClientService Error Handling & Stuck States', () => {
|
|
19
20
|
let injector;
|
|
@@ -36,7 +37,7 @@ describe('AuthenticationClientService Error Handling & Stuck States', () => {
|
|
|
36
37
|
mockApiClient = {
|
|
37
38
|
login: vi.fn(),
|
|
38
39
|
refresh: vi.fn(),
|
|
39
|
-
timestamp: vi.fn().mockResolvedValue(
|
|
40
|
+
timestamp: vi.fn().mockResolvedValue(currentTimestampSeconds()),
|
|
40
41
|
endSession: vi.fn().mockResolvedValue(undefined),
|
|
41
42
|
};
|
|
42
43
|
mockLock = {
|
|
@@ -72,7 +73,7 @@ describe('AuthenticationClientService Error Handling & Stuck States', () => {
|
|
|
72
73
|
await injector.dispose();
|
|
73
74
|
});
|
|
74
75
|
function setupServiceWithToken(iatOffset = -10, expOffset = 5) {
|
|
75
|
-
const now =
|
|
76
|
+
const now = currentTimestampSeconds();
|
|
76
77
|
const initialToken = { iat: now + iatOffset, exp: now + expOffset, jti: 'initial' };
|
|
77
78
|
globalThis.localStorage.setItem('AuthenticationService:token', JSON.stringify(initialToken));
|
|
78
79
|
service = injector.resolve(AuthenticationClientService);
|
|
@@ -2,15 +2,15 @@ import { of } from 'rxjs';
|
|
|
2
2
|
import { describe, expect, test, vi } from 'vitest';
|
|
3
3
|
import { HttpClientRequest, HttpClientResponse, HttpError, HttpErrorReason } from '../../http/index.js';
|
|
4
4
|
import { dontWaitForValidToken } from '../authentication.api.js';
|
|
5
|
-
import { logoutOnUnauthorizedMiddleware,
|
|
6
|
-
describe('
|
|
5
|
+
import { logoutOnUnauthorizedMiddleware, waitForAuthenticationMiddleware } from '../client/http-client.middleware.js';
|
|
6
|
+
describe('waitForAuthenticationMiddleware', () => {
|
|
7
7
|
test('should wait for token and call next', async () => {
|
|
8
8
|
const authenticationServiceMock = {
|
|
9
9
|
token: vi.fn().mockReturnValue({ jti: 'test-token' }),
|
|
10
10
|
validToken$: of({ jti: 'test-token' }),
|
|
11
11
|
hasValidToken: true,
|
|
12
12
|
};
|
|
13
|
-
const middleware =
|
|
13
|
+
const middleware = waitForAuthenticationMiddleware(authenticationServiceMock);
|
|
14
14
|
const request = new HttpClientRequest('http://localhost');
|
|
15
15
|
request.context = {
|
|
16
16
|
endpoint: {
|
|
@@ -26,7 +26,7 @@ describe('waitForAuthenticationCredentialsMiddleware', () => {
|
|
|
26
26
|
isLoggedIn: vi.fn().mockReturnValue(true),
|
|
27
27
|
hasValidToken: false,
|
|
28
28
|
};
|
|
29
|
-
const middleware =
|
|
29
|
+
const middleware = waitForAuthenticationMiddleware(authenticationServiceMock);
|
|
30
30
|
const request = new HttpClientRequest('http://localhost');
|
|
31
31
|
request.context = {
|
|
32
32
|
endpoint: {
|
|
@@ -45,7 +45,7 @@ describe('waitForAuthenticationCredentialsMiddleware', () => {
|
|
|
45
45
|
isLoggedIn: vi.fn().mockReturnValue(true),
|
|
46
46
|
hasValidToken: false,
|
|
47
47
|
};
|
|
48
|
-
const middleware =
|
|
48
|
+
const middleware = waitForAuthenticationMiddleware(authenticationServiceMock);
|
|
49
49
|
const request = new HttpClientRequest('http://localhost');
|
|
50
50
|
request.context = {
|
|
51
51
|
endpoint: {
|
|
@@ -8,6 +8,7 @@ import { Lock } from '../../lock/index.js';
|
|
|
8
8
|
import { Logger } from '../../logger/index.js';
|
|
9
9
|
import { MessageBus } from '../../message-bus/index.js';
|
|
10
10
|
import { configureDefaultSignalsImplementation } from '../../signals/implementation/configure.js';
|
|
11
|
+
import { currentTimestampSeconds } from '../../utils/date-time.js';
|
|
11
12
|
describe('AuthenticationClientService Methods', () => {
|
|
12
13
|
let injector;
|
|
13
14
|
let service;
|
|
@@ -31,11 +32,13 @@ describe('AuthenticationClientService Methods', () => {
|
|
|
31
32
|
refresh: vi.fn(),
|
|
32
33
|
impersonate: vi.fn(),
|
|
33
34
|
unimpersonate: vi.fn(),
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
changePassword: vi.fn(),
|
|
36
|
+
initPasswordReset: vi.fn(),
|
|
37
|
+
resetPassword: vi.fn(),
|
|
38
|
+
checkPassword: vi.fn(),
|
|
39
|
+
listSessions: vi.fn(),
|
|
40
|
+
invalidateAllOtherSessions: vi.fn(),
|
|
41
|
+
timestamp: vi.fn().mockResolvedValue(currentTimestampSeconds()),
|
|
39
42
|
endSession: vi.fn().mockResolvedValue(undefined),
|
|
40
43
|
};
|
|
41
44
|
mockLock = {
|
|
@@ -106,17 +109,17 @@ describe('AuthenticationClientService Methods', () => {
|
|
|
106
109
|
expect(mockApiClient.unimpersonate).toHaveBeenCalled();
|
|
107
110
|
expect(service.token()).toEqual(token);
|
|
108
111
|
});
|
|
109
|
-
test('
|
|
110
|
-
await service.
|
|
111
|
-
expect(mockApiClient.
|
|
112
|
+
test('changePassword should call api', async () => {
|
|
113
|
+
await service.changePassword('old', 'new');
|
|
114
|
+
expect(mockApiClient.changePassword).toHaveBeenCalledWith({ currentPassword: 'old', newPassword: 'new' });
|
|
112
115
|
});
|
|
113
|
-
test('
|
|
114
|
-
await service.
|
|
115
|
-
expect(mockApiClient.
|
|
116
|
+
test('initPasswordReset should call api', async () => {
|
|
117
|
+
await service.initPasswordReset({ tenantId: 't', subject: 's' }, { some: 'data' });
|
|
118
|
+
expect(mockApiClient.initPasswordReset).toHaveBeenCalledWith({ tenantId: 't', subject: 's', data: { some: 'data' } });
|
|
116
119
|
});
|
|
117
|
-
test('
|
|
118
|
-
await service.
|
|
119
|
-
expect(mockApiClient.
|
|
120
|
+
test('resetPassword should call api', async () => {
|
|
121
|
+
await service.resetPassword('token', 'new-password');
|
|
122
|
+
expect(mockApiClient.resetPassword).toHaveBeenCalledWith({ token: 'token', newPassword: 'new-password' });
|
|
120
123
|
});
|
|
121
124
|
test('updateRawTokens should update signals and storage', async () => {
|
|
122
125
|
service.updateRawTokens('raw', 'refresh', 'impersonator');
|
|
@@ -160,4 +163,15 @@ describe('AuthenticationClientService Methods', () => {
|
|
|
160
163
|
expect(mockLogger.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to synchronize clock'));
|
|
161
164
|
expect(service.clockOffset).toBe(0);
|
|
162
165
|
});
|
|
166
|
+
test('listSessions should call api', async () => {
|
|
167
|
+
const sessions = [{ id: '1', begin: 100, end: 200 }];
|
|
168
|
+
mockApiClient.listSessions.mockResolvedValue(sessions);
|
|
169
|
+
const result = await service.listSessions();
|
|
170
|
+
expect(mockApiClient.listSessions).toHaveBeenCalled();
|
|
171
|
+
expect(result).toEqual(sessions);
|
|
172
|
+
});
|
|
173
|
+
test('invalidateAllOtherSessions should call api', async () => {
|
|
174
|
+
await service.invalidateAllOtherSessions();
|
|
175
|
+
expect(mockApiClient.invalidateAllOtherSessions).toHaveBeenCalled();
|
|
176
|
+
});
|
|
163
177
|
});
|