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