@tstdl/base 0.93.182 → 0.93.184
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/server/api-request-token.provider.js +1 -1
- package/api/server/gateway.js +6 -1
- package/authentication/authentication.api.d.ts +13 -40
- package/authentication/authentication.api.js +5 -14
- package/authentication/client/authentication.service.d.ts +6 -14
- package/authentication/client/authentication.service.js +22 -4
- package/authentication/client/module.d.ts +1 -1
- package/authentication/client/module.js +4 -4
- package/authentication/models/index.d.ts +1 -0
- package/authentication/models/index.js +1 -0
- package/authentication/models/totp-results.model.d.ts +11 -0
- package/authentication/models/totp-results.model.js +37 -0
- package/authentication/server/authentication.api-controller.d.ts +3 -3
- package/authentication/server/authentication.api-controller.js +31 -4
- package/authentication/server/authentication.service.d.ts +5 -14
- package/authentication/server/authentication.service.js +6 -4
- package/core.d.ts +0 -5
- package/core.js +0 -8
- package/document-management/api/document-management.api.d.ts +2 -2
- package/document-management/service-models/document.service-model.d.ts +1 -1
- package/examples/config.d.ts +25 -0
- package/examples/config.js +26 -0
- package/notification/server/module.d.ts +1 -1
- package/notification/server/module.js +1 -1
- package/package.json +5 -5
- package/signals/api.d.ts +5 -1
- package/signals/api.js +3 -1
- package/signals/implementation/api.d.ts +10 -1
- package/signals/implementation/api.js +7 -1
- package/signals/implementation/asserts.d.ts +2 -2
- package/signals/implementation/asserts.js +3 -3
- package/signals/implementation/computed.d.ts +7 -34
- package/signals/implementation/computed.js +14 -83
- package/signals/implementation/configure.js +6 -2
- package/signals/implementation/effect.d.ts +65 -46
- package/signals/implementation/effect.js +97 -62
- package/signals/implementation/index.d.ts +2 -4
- package/signals/implementation/index.js +2 -4
- package/signals/implementation/linked_signal.d.ts +36 -0
- package/signals/implementation/linked_signal.js +34 -0
- package/signals/implementation/primitive/computed.d.ts +55 -0
- package/signals/implementation/primitive/computed.js +107 -0
- package/signals/implementation/primitive/effect.d.ts +26 -0
- package/signals/implementation/primitive/effect.js +31 -0
- package/signals/implementation/{equality.d.ts → primitive/equality.d.ts} +1 -1
- package/signals/implementation/{equality.js → primitive/equality.js} +1 -1
- package/signals/implementation/primitive/errors.d.ts +10 -0
- package/signals/implementation/{errors.js → primitive/errors.js} +3 -4
- package/signals/implementation/primitive/formatter.d.ts +19 -0
- package/signals/implementation/primitive/formatter.js +136 -0
- package/signals/implementation/{graph.d.ts → primitive/graph.d.ts} +68 -36
- package/signals/implementation/primitive/graph.js +386 -0
- package/signals/implementation/primitive/linked_signal.d.ts +46 -0
- package/signals/implementation/primitive/linked_signal.js +110 -0
- package/signals/implementation/primitive/signal.d.ts +31 -0
- package/signals/implementation/primitive/signal.js +80 -0
- package/signals/implementation/primitive/untracked.d.ts +12 -0
- package/signals/implementation/primitive/untracked.js +23 -0
- package/signals/implementation/{watch.d.ts → primitive/watch.d.ts} +1 -2
- package/signals/implementation/{watch.js → primitive/watch.js} +22 -16
- package/signals/implementation/resource/api.d.ts +275 -0
- package/signals/implementation/resource/api.js +26 -0
- package/signals/implementation/resource/debounce.d.ts +13 -0
- package/signals/implementation/resource/debounce.js +113 -0
- package/signals/implementation/resource/from_snapshots.d.ts +16 -0
- package/signals/implementation/resource/from_snapshots.js +44 -0
- package/signals/implementation/resource/index.d.ts +11 -0
- package/signals/implementation/resource/index.js +11 -0
- package/signals/implementation/resource/resource.d.ts +110 -0
- package/signals/implementation/resource/resource.js +402 -0
- package/signals/implementation/root_effect_scheduler.d.ts +50 -0
- package/signals/implementation/root_effect_scheduler.js +66 -0
- package/signals/implementation/signal.d.ts +42 -18
- package/signals/implementation/signal.js +29 -49
- package/signals/implementation/to-observable.d.ts +12 -5
- package/signals/implementation/to-observable.js +12 -2
- package/signals/implementation/to-signal.d.ts +9 -18
- package/signals/implementation/to-signal.js +46 -13
- package/signals/implementation/untracked.d.ts +1 -1
- package/signals/implementation/untracked.js +3 -11
- package/signals/operators/debounce.d.ts +8 -0
- package/signals/operators/debounce.js +19 -0
- package/signals/operators/derive-async.js +43 -15
- package/signals/operators/index.d.ts +2 -0
- package/signals/operators/index.js +2 -0
- package/signals/operators/throttle.d.ts +8 -0
- package/signals/operators/throttle.js +31 -0
- package/ai/genkit/tests/multi-region.test.d.ts +0 -2
- package/ai/genkit/tests/multi-region.test.js +0 -179
- package/ai/genkit/tests/token-limit-fallback.test.d.ts +0 -2
- package/ai/genkit/tests/token-limit-fallback.test.js +0 -209
- package/ai/prompts/tests/prompt-builder.test.d.ts +0 -1
- package/ai/prompts/tests/prompt-builder.test.js +0 -22
- package/ai/tests/instructions-formatter.test.d.ts +0 -1
- package/ai/tests/instructions-formatter.test.js +0 -116
- package/ai/tests/steering.test.d.ts +0 -1
- package/ai/tests/steering.test.js +0 -37
- package/api/client/tests/api-client.test.d.ts +0 -1
- package/api/client/tests/api-client.test.js +0 -194
- package/api/server/tests/csrf.middleware.test.d.ts +0 -1
- package/api/server/tests/csrf.middleware.test.js +0 -91
- package/authentication/tests/authentication-password-requirements.validator.test.d.ts +0 -1
- package/authentication/tests/authentication-password-requirements.validator.test.js +0 -29
- package/authentication/tests/authentication.api-controller.test.d.ts +0 -1
- package/authentication/tests/authentication.api-controller.test.js +0 -156
- package/authentication/tests/authentication.api-request-token.provider.test.d.ts +0 -1
- package/authentication/tests/authentication.api-request-token.provider.test.js +0 -48
- package/authentication/tests/authentication.client-error-handling.test.d.ts +0 -1
- package/authentication/tests/authentication.client-error-handling.test.js +0 -123
- package/authentication/tests/authentication.client-middleware.test.d.ts +0 -1
- package/authentication/tests/authentication.client-middleware.test.js +0 -118
- package/authentication/tests/authentication.client-service-methods.test.d.ts +0 -1
- package/authentication/tests/authentication.client-service-methods.test.js +0 -177
- package/authentication/tests/authentication.client-service-refresh.test.d.ts +0 -1
- package/authentication/tests/authentication.client-service-refresh.test.js +0 -153
- package/authentication/tests/authentication.client-service.test.d.ts +0 -1
- package/authentication/tests/authentication.client-service.test.js +0 -76
- package/authentication/tests/authentication.refresh-busy-loop.test.d.ts +0 -1
- package/authentication/tests/authentication.refresh-busy-loop.test.js +0 -84
- package/authentication/tests/authentication.service.test.d.ts +0 -1
- package/authentication/tests/authentication.service.test.js +0 -167
- package/authentication/tests/authentication.test-ancillary-service.d.ts +0 -9
- package/authentication/tests/authentication.test-ancillary-service.js +0 -27
- package/authentication/tests/brute-force-protection.test.d.ts +0 -1
- package/authentication/tests/brute-force-protection.test.js +0 -211
- package/authentication/tests/helper.test.d.ts +0 -1
- package/authentication/tests/helper.test.js +0 -122
- package/authentication/tests/password-requirements.error.test.d.ts +0 -1
- package/authentication/tests/password-requirements.error.test.js +0 -14
- package/authentication/tests/remember.api.test.d.ts +0 -1
- package/authentication/tests/remember.api.test.js +0 -117
- package/authentication/tests/remember.service.test.d.ts +0 -1
- package/authentication/tests/remember.service.test.js +0 -83
- package/authentication/tests/subject.service.test.d.ts +0 -1
- package/authentication/tests/subject.service.test.js +0 -140
- package/authentication/tests/suspended-subject.test.d.ts +0 -1
- package/authentication/tests/suspended-subject.test.js +0 -120
- package/authentication/tests/totp.enrollment.test.d.ts +0 -1
- package/authentication/tests/totp.enrollment.test.js +0 -123
- package/authentication/tests/totp.login.test.d.ts +0 -1
- package/authentication/tests/totp.login.test.js +0 -213
- package/authentication/tests/totp.recovery-codes.test.d.ts +0 -1
- package/authentication/tests/totp.recovery-codes.test.js +0 -97
- package/authentication/tests/totp.status.test.d.ts +0 -1
- package/authentication/tests/totp.status.test.js +0 -72
- package/cancellation/tests/coverage.test.d.ts +0 -1
- package/cancellation/tests/coverage.test.js +0 -49
- package/cancellation/tests/leak.test.d.ts +0 -1
- package/cancellation/tests/leak.test.js +0 -35
- package/cancellation/tests/token.test.d.ts +0 -1
- package/cancellation/tests/token.test.js +0 -136
- package/circuit-breaker/tests/circuit-breaker.test.d.ts +0 -1
- package/circuit-breaker/tests/circuit-breaker.test.js +0 -116
- package/cryptography/tests/cryptography.test.d.ts +0 -1
- package/cryptography/tests/cryptography.test.js +0 -175
- package/cryptography/tests/jwt.test.d.ts +0 -1
- package/cryptography/tests/jwt.test.js +0 -54
- package/cryptography/tests/modern.test.d.ts +0 -1
- package/cryptography/tests/modern.test.js +0 -105
- package/cryptography/tests/module.test.d.ts +0 -1
- package/cryptography/tests/module.test.js +0 -100
- package/cryptography/tests/totp.test.d.ts +0 -1
- package/cryptography/tests/totp.test.js +0 -108
- package/document-management/tests/ai-config-hierarchy.test.d.ts +0 -1
- package/document-management/tests/ai-config-hierarchy.test.js +0 -59
- package/document-management/tests/ai-config-integration.test.d.ts +0 -1
- package/document-management/tests/ai-config-integration.test.js +0 -125
- package/document-management/tests/ai-config-merge.test.d.ts +0 -1
- package/document-management/tests/ai-config-merge.test.js +0 -46
- package/document-management/tests/document-management-ai-overrides.test.d.ts +0 -1
- package/document-management/tests/document-management-ai-overrides.test.js +0 -63
- package/document-management/tests/document-management-core.test.d.ts +0 -1
- package/document-management/tests/document-management-core.test.js +0 -157
- package/document-management/tests/document-management.api.test.d.ts +0 -1
- package/document-management/tests/document-management.api.test.js +0 -101
- package/document-management/tests/document-statistics.service.test.d.ts +0 -1
- package/document-management/tests/document-statistics.service.test.js +0 -498
- package/document-management/tests/document-validation-ai-overrides.test.d.ts +0 -1
- package/document-management/tests/document-validation-ai-overrides.test.js +0 -87
- package/document-management/tests/document.service.test.d.ts +0 -1
- package/document-management/tests/document.service.test.js +0 -143
- package/document-management/tests/enum-helpers.test.d.ts +0 -1
- package/document-management/tests/enum-helpers.test.js +0 -452
- package/document-management/tests/helper.d.ts +0 -24
- package/document-management/tests/helper.js +0 -39
- package/errors/tests/format.test.d.ts +0 -1
- package/errors/tests/format.test.js +0 -84
- package/http/tests/server-timing.test.d.ts +0 -1
- package/http/tests/server-timing.test.js +0 -42
- package/injector/tests/advanced.test.d.ts +0 -1
- package/injector/tests/advanced.test.js +0 -116
- package/injector/tests/async-init.test.d.ts +0 -1
- package/injector/tests/async-init.test.js +0 -77
- package/injector/tests/basic.test.d.ts +0 -1
- package/injector/tests/basic.test.js +0 -114
- package/injector/tests/hierarchical.test.d.ts +0 -1
- package/injector/tests/hierarchical.test.js +0 -59
- package/injector/tests/leak.test.d.ts +0 -1
- package/injector/tests/leak.test.js +0 -45
- package/injector/tests/lifecycles.test.d.ts +0 -1
- package/injector/tests/lifecycles.test.js +0 -109
- package/logger/tests/pretty-print.test.d.ts +0 -1
- package/logger/tests/pretty-print.test.js +0 -60
- package/notification/tests/notification-api.test.d.ts +0 -1
- package/notification/tests/notification-api.test.js +0 -124
- package/notification/tests/notification-client.test.d.ts +0 -1
- package/notification/tests/notification-client.test.js +0 -101
- package/notification/tests/notification-flow.test.d.ts +0 -1
- package/notification/tests/notification-flow.test.js +0 -296
- package/notification/tests/notification-sse.service.test.d.ts +0 -1
- package/notification/tests/notification-sse.service.test.js +0 -43
- package/notification/tests/notification-type.service.test.d.ts +0 -1
- package/notification/tests/notification-type.service.test.js +0 -41
- package/object-storage/s3/tests/s3.object-storage.integration.test.d.ts +0 -1
- package/object-storage/s3/tests/s3.object-storage.integration.test.js +0 -303
- package/orm/tests/build-jsonb.test.d.ts +0 -1
- package/orm/tests/build-jsonb.test.js +0 -39
- package/orm/tests/data-types.test.d.ts +0 -1
- package/orm/tests/data-types.test.js +0 -39
- package/orm/tests/database-extension.test.d.ts +0 -1
- package/orm/tests/database-extension.test.js +0 -63
- package/orm/tests/database-migration.test.d.ts +0 -1
- package/orm/tests/database-migration.test.js +0 -83
- package/orm/tests/decorators.test.d.ts +0 -1
- package/orm/tests/decorators.test.js +0 -77
- package/orm/tests/encryption.test.d.ts +0 -1
- package/orm/tests/encryption.test.js +0 -31
- package/orm/tests/query-complex.test.d.ts +0 -1
- package/orm/tests/query-complex.test.js +0 -172
- package/orm/tests/query-converter-complex.test.d.ts +0 -1
- package/orm/tests/query-converter-complex.test.js +0 -131
- package/orm/tests/query-converter.test.d.ts +0 -1
- package/orm/tests/query-converter.test.js +0 -123
- package/orm/tests/repository-advanced.test.d.ts +0 -1
- package/orm/tests/repository-advanced.test.js +0 -189
- package/orm/tests/repository-attributes.test.d.ts +0 -1
- package/orm/tests/repository-attributes.test.js +0 -83
- package/orm/tests/repository-compound-primary-key.test.d.ts +0 -2
- package/orm/tests/repository-compound-primary-key.test.js +0 -226
- package/orm/tests/repository-comprehensive.test.d.ts +0 -1
- package/orm/tests/repository-comprehensive.test.js +0 -162
- package/orm/tests/repository-coverage.test.d.ts +0 -2
- package/orm/tests/repository-coverage.test.js +0 -242
- package/orm/tests/repository-cti-complex.test.d.ts +0 -1
- package/orm/tests/repository-cti-complex.test.js +0 -151
- package/orm/tests/repository-cti-embedded.test.d.ts +0 -1
- package/orm/tests/repository-cti-embedded.test.js +0 -178
- package/orm/tests/repository-cti-extensive.test.d.ts +0 -2
- package/orm/tests/repository-cti-extensive.test.js +0 -279
- package/orm/tests/repository-cti-mapping.test.d.ts +0 -2
- package/orm/tests/repository-cti-mapping.test.js +0 -108
- package/orm/tests/repository-cti-search.test.d.ts +0 -1
- package/orm/tests/repository-cti-search.test.js +0 -141
- package/orm/tests/repository-cti-soft-delete.test.d.ts +0 -2
- package/orm/tests/repository-cti-soft-delete.test.js +0 -103
- package/orm/tests/repository-cti-transactions.test.d.ts +0 -1
- package/orm/tests/repository-cti-transactions.test.js +0 -112
- package/orm/tests/repository-cti-upsert-many.test.d.ts +0 -2
- package/orm/tests/repository-cti-upsert-many.test.js +0 -115
- package/orm/tests/repository-cti.test.d.ts +0 -2
- package/orm/tests/repository-cti.test.js +0 -390
- package/orm/tests/repository-edge-cases.test.d.ts +0 -1
- package/orm/tests/repository-edge-cases.test.js +0 -178
- package/orm/tests/repository-expiration.test.d.ts +0 -2
- package/orm/tests/repository-expiration.test.js +0 -140
- package/orm/tests/repository-extra-coverage.test.d.ts +0 -2
- package/orm/tests/repository-extra-coverage.test.js +0 -402
- package/orm/tests/repository-mapping.test.d.ts +0 -2
- package/orm/tests/repository-mapping.test.js +0 -65
- package/orm/tests/repository-regression.test.d.ts +0 -1
- package/orm/tests/repository-regression.test.js +0 -288
- package/orm/tests/repository-search-coverage.test.d.ts +0 -1
- package/orm/tests/repository-search-coverage.test.js +0 -107
- package/orm/tests/repository-search.test.d.ts +0 -1
- package/orm/tests/repository-search.test.js +0 -105
- package/orm/tests/repository-soft-delete.test.d.ts +0 -1
- package/orm/tests/repository-soft-delete.test.js +0 -118
- package/orm/tests/repository-transactions-nested.test.d.ts +0 -1
- package/orm/tests/repository-transactions-nested.test.js +0 -178
- package/orm/tests/repository-types.test.d.ts +0 -1
- package/orm/tests/repository-types.test.js +0 -184
- package/orm/tests/repository-undelete.test.d.ts +0 -2
- package/orm/tests/repository-undelete.test.js +0 -201
- package/orm/tests/schema-converter.test.d.ts +0 -1
- package/orm/tests/schema-converter.test.js +0 -82
- package/orm/tests/schema-generation.test.d.ts +0 -2
- package/orm/tests/schema-generation.test.js +0 -174
- package/orm/tests/sql-helpers.test.d.ts +0 -1
- package/orm/tests/sql-helpers.test.js +0 -67
- package/orm/tests/transaction-safety.test.d.ts +0 -1
- package/orm/tests/transaction-safety.test.js +0 -81
- package/orm/tests/transactional.test.d.ts +0 -1
- package/orm/tests/transactional.test.js +0 -215
- package/orm/tests/utils.test.d.ts +0 -1
- package/orm/tests/utils.test.js +0 -70
- package/pdf/tests/utils.test.d.ts +0 -1
- package/pdf/tests/utils.test.js +0 -187
- package/process/tests/spawn.test.d.ts +0 -1
- package/process/tests/spawn.test.js +0 -182
- package/rate-limit/tests/postgres-rate-limiter.test.d.ts +0 -1
- package/rate-limit/tests/postgres-rate-limiter.test.js +0 -84
- package/renderer/tests/renderer.test.d.ts +0 -1
- package/renderer/tests/renderer.test.js +0 -88
- package/rpc/tests/rpc.integration.test.d.ts +0 -1
- package/rpc/tests/rpc.integration.test.js +0 -615
- package/signals/implementation/errors.d.ts +0 -2
- package/signals/implementation/graph.js +0 -312
- package/signals/implementation/writable-signal.d.ts +0 -48
- package/signals/implementation/writable-signal.js +0 -32
- package/task-queue/tests/coverage-branch.test.d.ts +0 -1
- package/task-queue/tests/coverage-branch.test.js +0 -395
- package/task-queue/tests/coverage-enhancement.test.d.ts +0 -1
- package/task-queue/tests/coverage-enhancement.test.js +0 -150
- package/task-queue/tests/dag.test.d.ts +0 -1
- package/task-queue/tests/dag.test.js +0 -188
- package/task-queue/tests/dependencies.test.d.ts +0 -1
- package/task-queue/tests/dependencies.test.js +0 -296
- package/task-queue/tests/enqueue-batch.test.d.ts +0 -1
- package/task-queue/tests/enqueue-batch.test.js +0 -125
- package/task-queue/tests/enqueue-item.test.d.ts +0 -1
- package/task-queue/tests/enqueue-item.test.js +0 -12
- package/task-queue/tests/fan-out-spawning.test.d.ts +0 -1
- package/task-queue/tests/fan-out-spawning.test.js +0 -94
- package/task-queue/tests/idempotent-replacement.test.d.ts +0 -1
- package/task-queue/tests/idempotent-replacement.test.js +0 -114
- package/task-queue/tests/missing-idempotent-tasks.test.d.ts +0 -1
- package/task-queue/tests/missing-idempotent-tasks.test.js +0 -39
- package/task-queue/tests/optimization-edge-cases.test.d.ts +0 -1
- package/task-queue/tests/optimization-edge-cases.test.js +0 -124
- package/task-queue/tests/queue-generic.test.d.ts +0 -1
- package/task-queue/tests/queue-generic.test.js +0 -8
- package/task-queue/tests/queue.test.d.ts +0 -1
- package/task-queue/tests/queue.test.js +0 -756
- package/task-queue/tests/shutdown.test.d.ts +0 -1
- package/task-queue/tests/shutdown.test.js +0 -41
- package/task-queue/tests/task-context.test.d.ts +0 -1
- package/task-queue/tests/task-context.test.js +0 -7
- package/task-queue/tests/task-union.test.d.ts +0 -1
- package/task-queue/tests/task-union.test.js +0 -18
- package/task-queue/tests/transactions.test.d.ts +0 -1
- package/task-queue/tests/transactions.test.js +0 -47
- package/task-queue/tests/typing.test.d.ts +0 -1
- package/task-queue/tests/typing.test.js +0 -9
- package/task-queue/tests/worker.test.d.ts +0 -1
- package/task-queue/tests/worker.test.js +0 -258
- package/task-queue/tests/zombie-parent.test.d.ts +0 -1
- package/task-queue/tests/zombie-parent.test.js +0 -45
- package/task-queue/tests/zombie-recovery.test.d.ts +0 -1
- package/task-queue/tests/zombie-recovery.test.js +0 -51
- package/utils/tests/backoff.test.d.ts +0 -1
- package/utils/tests/backoff.test.js +0 -41
- package/utils/tests/retry-with-backoff.test.d.ts +0 -1
- package/utils/tests/retry-with-backoff.test.js +0 -49
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'vitest';
|
|
2
|
-
import { ActorType, Auditor } from '../../audit/index.js';
|
|
3
|
-
import { NIL_UUID } from '../../constants.js';
|
|
4
|
-
import { clearTenantData, setupIntegrationTest } from '../../testing/index.js';
|
|
5
|
-
import { AuthenticationAncillaryService } from '../server/authentication-ancillary.service.js';
|
|
6
|
-
import { AuthenticationService } from '../server/authentication.service.js';
|
|
7
|
-
import { SubjectService } from '../server/subject.service.js';
|
|
8
|
-
import { DefaultAuthenticationAncillaryService } from './authentication.test-ancillary-service.js';
|
|
9
|
-
describe('AuthenticationService', () => {
|
|
10
|
-
let injector;
|
|
11
|
-
let database;
|
|
12
|
-
let authenticationService;
|
|
13
|
-
let subjectService;
|
|
14
|
-
let auditor;
|
|
15
|
-
let ancillaryService;
|
|
16
|
-
const schema = 'authentication';
|
|
17
|
-
const tenantId = crypto.randomUUID();
|
|
18
|
-
beforeAll(async () => {
|
|
19
|
-
({ injector, database } = await setupIntegrationTest({
|
|
20
|
-
modules: { authentication: true },
|
|
21
|
-
authentication: {
|
|
22
|
-
ancillaryService: DefaultAuthenticationAncillaryService,
|
|
23
|
-
},
|
|
24
|
-
}));
|
|
25
|
-
authenticationService = await injector.resolveAsync(AuthenticationService);
|
|
26
|
-
subjectService = await injector.resolveAsync(SubjectService);
|
|
27
|
-
auditor = injector.resolve(Auditor);
|
|
28
|
-
ancillaryService = await injector.resolveAsync(AuthenticationAncillaryService);
|
|
29
|
-
});
|
|
30
|
-
afterAll(async () => {
|
|
31
|
-
await injector?.dispose();
|
|
32
|
-
});
|
|
33
|
-
beforeEach(async () => {
|
|
34
|
-
await clearTenantData(database, schema, ['password', 'session', 'user', 'service_account', 'system_account', 'subject'], tenantId);
|
|
35
|
-
});
|
|
36
|
-
test('login should create a session and listSessions should return it', async () => {
|
|
37
|
-
const user = await subjectService.createUser({
|
|
38
|
-
tenantId,
|
|
39
|
-
email: 'test@example.com',
|
|
40
|
-
firstName: 'John',
|
|
41
|
-
lastName: 'Doe',
|
|
42
|
-
});
|
|
43
|
-
await authenticationService.setPassword(user, 'Strong-Password-2026!');
|
|
44
|
-
const loginResult = await authenticationService.login({ tenantId, subject: user.id }, 'Strong-Password-2026!', undefined, auditor.with({ actor: user.id, actorType: ActorType.Subject }));
|
|
45
|
-
expect(loginResult.type).toBe('success');
|
|
46
|
-
const tokenResult = loginResult.result;
|
|
47
|
-
expect(tokenResult.token).toBeDefined();
|
|
48
|
-
const sessions = await authenticationService.listSessions(tenantId, user.id);
|
|
49
|
-
expect(sessions).toHaveLength(1);
|
|
50
|
-
expect(sessions[0]?.id).toBe(tokenResult.jsonToken.payload.session);
|
|
51
|
-
});
|
|
52
|
-
test('invalidateAllSessions should end all active sessions', async () => {
|
|
53
|
-
const user = await subjectService.createUser({
|
|
54
|
-
tenantId,
|
|
55
|
-
email: 'test@example.com',
|
|
56
|
-
firstName: 'John',
|
|
57
|
-
lastName: 'Doe',
|
|
58
|
-
});
|
|
59
|
-
await authenticationService.setPassword(user, 'Strong-Password-2026!');
|
|
60
|
-
const userAuditor = auditor.with({ actor: user.id, actorType: ActorType.Subject });
|
|
61
|
-
await authenticationService.login({ tenantId, subject: user.id }, 'Strong-Password-2026!', undefined, userAuditor);
|
|
62
|
-
await authenticationService.login({ tenantId, subject: user.id }, 'Strong-Password-2026!', undefined, userAuditor);
|
|
63
|
-
let sessions = await authenticationService.listSessions(tenantId, user.id);
|
|
64
|
-
expect(sessions).toHaveLength(2);
|
|
65
|
-
const now = Date.now();
|
|
66
|
-
expect(sessions.every((s) => s.end > now)).toBe(true);
|
|
67
|
-
await authenticationService.invalidateAllSessions(tenantId, user.id, userAuditor);
|
|
68
|
-
sessions = await authenticationService.listSessions(tenantId, user.id);
|
|
69
|
-
expect(sessions).toHaveLength(0);
|
|
70
|
-
});
|
|
71
|
-
test('getSession and tryGetSession', async () => {
|
|
72
|
-
const user = await subjectService.createUser({
|
|
73
|
-
tenantId,
|
|
74
|
-
email: 'test@example.com',
|
|
75
|
-
firstName: 'John',
|
|
76
|
-
lastName: 'Doe',
|
|
77
|
-
});
|
|
78
|
-
await authenticationService.setPassword(user, 'Strong-Password-2026!');
|
|
79
|
-
const userAuditor = auditor.with({ actor: user.id, actorType: ActorType.Subject });
|
|
80
|
-
const loginResult = await authenticationService.login({ tenantId, subject: user.id }, 'Strong-Password-2026!', undefined, userAuditor);
|
|
81
|
-
expect(loginResult.type).toBe('success');
|
|
82
|
-
const tokenResult = loginResult.result;
|
|
83
|
-
const sessionId = tokenResult.jsonToken.payload.session;
|
|
84
|
-
const session = await authenticationService.getSession(sessionId);
|
|
85
|
-
expect(session.id).toBe(sessionId);
|
|
86
|
-
const triedSession = await authenticationService.tryGetSession(sessionId);
|
|
87
|
-
expect(triedSession?.id).toBe(sessionId);
|
|
88
|
-
const nonExistent = await authenticationService.tryGetSession(NIL_UUID);
|
|
89
|
-
expect(nonExistent).toBeUndefined();
|
|
90
|
-
});
|
|
91
|
-
test('refresh should issue new token and session', async () => {
|
|
92
|
-
const user = await subjectService.createUser({ tenantId, email: 'refresh@example.com', firstName: 'R', lastName: 'F' });
|
|
93
|
-
await authenticationService.setPassword(user, 'Strong-Password-2026!');
|
|
94
|
-
const userAuditor = auditor.with({ actor: user.id, actorType: ActorType.Subject });
|
|
95
|
-
const loginResult = await authenticationService.login({ tenantId, subject: user.id }, 'Strong-Password-2026!', undefined, userAuditor);
|
|
96
|
-
expect(loginResult.type).toBe('success');
|
|
97
|
-
const loginSuccessResult = loginResult.result;
|
|
98
|
-
const refreshResult = await authenticationService.refresh(loginSuccessResult.refreshToken, undefined, {}, userAuditor);
|
|
99
|
-
expect(refreshResult.token).toBeDefined();
|
|
100
|
-
expect(refreshResult.refreshToken).not.toBe(loginSuccessResult.refreshToken);
|
|
101
|
-
});
|
|
102
|
-
test('changePassword should update password', async () => {
|
|
103
|
-
const user = await subjectService.createUser({ tenantId, email: 'change@example.com', firstName: 'C', lastName: 'S' });
|
|
104
|
-
await authenticationService.setPassword(user, 'Old-Password-2026!');
|
|
105
|
-
const userAuditor = auditor.with({ actor: user.id, actorType: ActorType.Subject });
|
|
106
|
-
await authenticationService.changePassword({ tenantId, subject: user.id }, 'Old-Password-2026!', 'New-Password-2026!', userAuditor);
|
|
107
|
-
const authResult = await authenticationService.authenticateWithPassword({ tenantId, subject: user.id }, 'New-Password-2026!');
|
|
108
|
-
expect(authResult.success).toBe(true);
|
|
109
|
-
});
|
|
110
|
-
test('checkPassword, testPassword, and validatePassword', async () => {
|
|
111
|
-
const weak = 'abc';
|
|
112
|
-
const strong = 'Very-Strong-Password-2026-!@#$';
|
|
113
|
-
expect((await authenticationService.checkPassword(weak)).strength).toBeLessThan(2);
|
|
114
|
-
expect((await authenticationService.checkPassword(strong)).strength).toBeGreaterThanOrEqual(2);
|
|
115
|
-
expect((await authenticationService.testPassword(weak)).success).toBe(false);
|
|
116
|
-
expect((await authenticationService.testPassword(strong)).success).toBe(true);
|
|
117
|
-
await expect(authenticationService.validatePassword(weak)).rejects.toThrow();
|
|
118
|
-
await expect(authenticationService.validatePassword(strong)).resolves.not.toThrow();
|
|
119
|
-
});
|
|
120
|
-
test('tryResolveSubject and resolveSubject', async () => {
|
|
121
|
-
const user = await subjectService.createUser({ tenantId, email: 'resolve@example.com', firstName: 'R', lastName: 'S' });
|
|
122
|
-
const resolved = await authenticationService.tryResolveSubject({ tenantId, subject: user.id });
|
|
123
|
-
expect(resolved?.id).toBe(user.id);
|
|
124
|
-
const resolvedByEmail = await authenticationService.resolveSubject({ tenantId, subject: 'resolve@example.com' });
|
|
125
|
-
expect(resolvedByEmail.id).toBe(user.id);
|
|
126
|
-
await expect(authenticationService.resolveSubject({ tenantId, subject: 'missing' })).rejects.toThrow();
|
|
127
|
-
});
|
|
128
|
-
test('impersonation and unimpersonation', async () => {
|
|
129
|
-
const admin = await subjectService.createUser({ tenantId, email: 'admin@example.com', firstName: 'A', lastName: 'D' });
|
|
130
|
-
const user = await subjectService.createUser({ tenantId, email: 'user@example.com', firstName: 'U', lastName: 'S' });
|
|
131
|
-
const adminAuditor = auditor.with({ actor: admin.id, actorType: ActorType.Subject });
|
|
132
|
-
const adminToken = await authenticationService.getToken(admin, undefined);
|
|
133
|
-
const impersonated = await authenticationService.impersonate(adminToken.token, adminToken.refreshToken, user.id, undefined, adminAuditor);
|
|
134
|
-
expect(impersonated.jsonToken.payload.subject).toBe(user.id);
|
|
135
|
-
expect(impersonated.jsonToken.payload.impersonator).toBe(admin.id);
|
|
136
|
-
const unimpersonated = await authenticationService.unimpersonate(impersonated.impersonatorRefreshToken, impersonated.token, undefined, adminAuditor);
|
|
137
|
-
expect(unimpersonated.jsonToken.payload.subject).toBe(admin.id);
|
|
138
|
-
expect(unimpersonated.jsonToken.payload.impersonator).toBeUndefined();
|
|
139
|
-
});
|
|
140
|
-
test('refresh should throw on invalid token', async () => {
|
|
141
|
-
await expect(authenticationService.refresh('invalid', undefined, {}, auditor)).rejects.toThrow();
|
|
142
|
-
});
|
|
143
|
-
test('login should throw on invalid password', async () => {
|
|
144
|
-
const user = await subjectService.createUser({ tenantId, email: 'fail@example.com', firstName: 'F', lastName: 'L' });
|
|
145
|
-
await authenticationService.setPassword(user, 'Very-Strong-Password-2026!');
|
|
146
|
-
await expect(authenticationService.login({ tenantId, subject: user.id }, 'wrong', undefined, auditor)).rejects.toThrow();
|
|
147
|
-
});
|
|
148
|
-
test('endSession should handle non-existent session gracefully', async () => {
|
|
149
|
-
await expect(authenticationService.endSession(NIL_UUID, auditor)).resolves.not.toThrow();
|
|
150
|
-
});
|
|
151
|
-
test('resolveSubject should throw if not found', async () => {
|
|
152
|
-
await expect(authenticationService.resolveSubject({ tenantId, subject: 'missing' })).rejects.toThrow();
|
|
153
|
-
});
|
|
154
|
-
test('password reset flow', async () => {
|
|
155
|
-
const user = await subjectService.createUser({ tenantId, email: 'reset@example.com', firstName: 'R', lastName: 'E' });
|
|
156
|
-
const userAuditor = auditor.with({ actor: user.id, actorType: ActorType.Subject });
|
|
157
|
-
await authenticationService.initPasswordReset({ tenantId, subject: user.id }, undefined, userAuditor);
|
|
158
|
-
expect(ancillaryService.lastResetData).toBeDefined();
|
|
159
|
-
await authenticationService.resetPassword(ancillaryService.lastResetData.token, 'New-Password-Reset-2026!', userAuditor);
|
|
160
|
-
const authResult = await authenticationService.authenticateWithPassword({ tenantId, subject: user.id }, 'New-Password-Reset-2026!');
|
|
161
|
-
expect(authResult.success).toBe(true);
|
|
162
|
-
});
|
|
163
|
-
test('deriveSigningSecrets should work', async () => {
|
|
164
|
-
// This is mostly covered by initialize, but we can test it implicitly by ensuring service works after init
|
|
165
|
-
expect(authenticationService).toBeDefined();
|
|
166
|
-
});
|
|
167
|
-
});
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { AuthenticationAncillaryService } from '../server/index.js';
|
|
2
|
-
export declare class DefaultAuthenticationAncillaryService extends AuthenticationAncillaryService<any, any, any> {
|
|
3
|
-
#private;
|
|
4
|
-
lastResetData: any;
|
|
5
|
-
getTokenPayload(): Promise<{}>;
|
|
6
|
-
handleInitPasswordReset(data: any): Promise<void>;
|
|
7
|
-
canImpersonate(_token: any, _subject: any, _data: any): Promise<boolean>;
|
|
8
|
-
resolveSubjects(data: any): Promise<import("../index.js").Subject[]>;
|
|
9
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
-
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;
|
|
5
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
-
};
|
|
7
|
-
import { inject, Injector, Singleton } from '../../injector/index.js';
|
|
8
|
-
import { isUndefined } from '../../utils/type-guards.js';
|
|
9
|
-
import { AuthenticationAncillaryService, AuthenticationService } from '../server/index.js';
|
|
10
|
-
let DefaultAuthenticationAncillaryService = class DefaultAuthenticationAncillaryService extends AuthenticationAncillaryService {
|
|
11
|
-
#injector = inject(Injector);
|
|
12
|
-
#authenticationService;
|
|
13
|
-
lastResetData;
|
|
14
|
-
async getTokenPayload() { return {}; }
|
|
15
|
-
async handleInitPasswordReset(data) { this.lastResetData = data; }
|
|
16
|
-
async canImpersonate(_token, _subject, _data) { return true; }
|
|
17
|
-
async resolveSubjects(data) {
|
|
18
|
-
if (isUndefined(this.#authenticationService)) {
|
|
19
|
-
this.#authenticationService = await this.#injector.resolveAsync(AuthenticationService);
|
|
20
|
-
}
|
|
21
|
-
return await this.#authenticationService.defaultResolveSubjects(data);
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
DefaultAuthenticationAncillaryService = __decorate([
|
|
25
|
-
Singleton()
|
|
26
|
-
], DefaultAuthenticationAncillaryService);
|
|
27
|
-
export { DefaultAuthenticationAncillaryService };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'vitest';
|
|
2
|
-
import { ApiGateway } from '../../api/server/gateway.js';
|
|
3
|
-
import { Auditor } from '../../audit/index.js';
|
|
4
|
-
import { importKey } from '../../cryptography/index.js';
|
|
5
|
-
import { generateTotpToken } from '../../cryptography/totp.js';
|
|
6
|
-
import { HttpBody } from '../../http/http-body.js';
|
|
7
|
-
import { HttpHeaders } from '../../http/http-headers.js';
|
|
8
|
-
import { HttpQuery } from '../../http/http-query.js';
|
|
9
|
-
import { HttpServerRequest } from '../../http/server/http-server-request.js';
|
|
10
|
-
import { clearTenantData, setupIntegrationTest } from '../../testing/index.js';
|
|
11
|
-
import { currentTimestampSeconds } from '../../utils/date-time.js';
|
|
12
|
-
import { assert } from '../../utils/type-guards.js';
|
|
13
|
-
import { AuthenticationService } from '../server/authentication.service.js';
|
|
14
|
-
import { SubjectService } from '../server/subject.service.js';
|
|
15
|
-
import { DefaultAuthenticationAncillaryService } from './authentication.test-ancillary-service.js';
|
|
16
|
-
describe('Authentication Brute Force Protection (API Level)', () => {
|
|
17
|
-
let injector;
|
|
18
|
-
let database;
|
|
19
|
-
let gateway;
|
|
20
|
-
let subjectService;
|
|
21
|
-
let authenticationService;
|
|
22
|
-
let auditor;
|
|
23
|
-
const schema = 'authentication';
|
|
24
|
-
const tenantId = crypto.randomUUID();
|
|
25
|
-
beforeAll(async () => {
|
|
26
|
-
({ injector, database } = await setupIntegrationTest({
|
|
27
|
-
modules: { authentication: true, rateLimiter: true, api: true },
|
|
28
|
-
authentication: {
|
|
29
|
-
ancillaryService: DefaultAuthenticationAncillaryService,
|
|
30
|
-
options: {
|
|
31
|
-
bruteForceProtection: {
|
|
32
|
-
subjectBurstCapacity: 2,
|
|
33
|
-
subjectRefillInterval: 10000,
|
|
34
|
-
ipBurstCapacity: 2,
|
|
35
|
-
ipRefillInterval: 10000,
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
}));
|
|
40
|
-
gateway = injector.resolve(ApiGateway);
|
|
41
|
-
subjectService = await injector.resolveAsync(SubjectService);
|
|
42
|
-
authenticationService = await injector.resolveAsync(AuthenticationService);
|
|
43
|
-
auditor = injector.resolve(Auditor);
|
|
44
|
-
});
|
|
45
|
-
afterAll(async () => {
|
|
46
|
-
await injector?.dispose();
|
|
47
|
-
});
|
|
48
|
-
beforeEach(async () => {
|
|
49
|
-
await clearTenantData(database, schema, ['used_totp_tokens', 'totp_recovery_code', 'totp', 'password', 'session', 'user', 'service_account', 'system_account', 'subject'], tenantId);
|
|
50
|
-
});
|
|
51
|
-
async function callLogin(ip, body) {
|
|
52
|
-
const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
|
|
53
|
-
const encodedBody = new TextEncoder().encode(JSON.stringify(body));
|
|
54
|
-
const request = new HttpServerRequest({
|
|
55
|
-
url: new URL('http://localhost/api/v1/auth/token'),
|
|
56
|
-
method: 'POST',
|
|
57
|
-
headers,
|
|
58
|
-
query: new HttpQuery(),
|
|
59
|
-
ip,
|
|
60
|
-
body: HttpBody.from(encodedBody, headers),
|
|
61
|
-
});
|
|
62
|
-
let capturedResponse;
|
|
63
|
-
const respond = async (response) => { capturedResponse = response; };
|
|
64
|
-
const close = async () => { };
|
|
65
|
-
const abortSignal = new AbortController().signal;
|
|
66
|
-
await gateway.handleHttpServerRequestContext({ request, respond, close, abortSignal, context: {} });
|
|
67
|
-
return capturedResponse;
|
|
68
|
-
}
|
|
69
|
-
async function callDisableTotp(ip, token, body) {
|
|
70
|
-
const headers = new HttpHeaders({
|
|
71
|
-
'Content-Type': 'application/json',
|
|
72
|
-
'Authorization': `Bearer ${token}`
|
|
73
|
-
});
|
|
74
|
-
const encodedBody = new TextEncoder().encode(JSON.stringify(body));
|
|
75
|
-
const request = new HttpServerRequest({
|
|
76
|
-
url: new URL('http://localhost/api/v1/auth/totp/disable'),
|
|
77
|
-
method: 'POST',
|
|
78
|
-
headers,
|
|
79
|
-
query: new HttpQuery(),
|
|
80
|
-
ip,
|
|
81
|
-
body: HttpBody.from(encodedBody, headers),
|
|
82
|
-
});
|
|
83
|
-
let capturedResponse;
|
|
84
|
-
const respond = async (response) => { capturedResponse = response; };
|
|
85
|
-
const close = async () => { };
|
|
86
|
-
const abortSignal = new AbortController().signal;
|
|
87
|
-
await gateway.handleHttpServerRequestContext({ request, respond, close, abortSignal, context: {} });
|
|
88
|
-
return capturedResponse;
|
|
89
|
-
}
|
|
90
|
-
test('should limit login attempts by user', async () => {
|
|
91
|
-
const user = await subjectService.createUser({ tenantId, email: 'user-limit@example.com', firstName: 'John', lastName: 'Doe' });
|
|
92
|
-
await authenticationService.setPassword(user, 'Strong-Password-2026!');
|
|
93
|
-
const body = { tenantId, subject: user.id, password: 'wrong' };
|
|
94
|
-
// 1st attempt - Fail (InvalidCredentials)
|
|
95
|
-
let res = await callLogin('1.1.1.1', body);
|
|
96
|
-
expect(res.statusCode).toBe(401);
|
|
97
|
-
// 2nd attempt - Fail (InvalidCredentials)
|
|
98
|
-
res = await callLogin('1.1.1.2', body);
|
|
99
|
-
expect(res.statusCode).toBe(401);
|
|
100
|
-
// 3rd attempt - Should be throttled (429)
|
|
101
|
-
res = await callLogin('1.1.1.3', body);
|
|
102
|
-
expect(res.statusCode).toBe(429);
|
|
103
|
-
expect(res.body.json.error.name).toBe('TooManyRequestsError');
|
|
104
|
-
});
|
|
105
|
-
test('should limit login attempts by IP', async () => {
|
|
106
|
-
const ip = '2.2.2.2';
|
|
107
|
-
const user1 = await subjectService.createUser({ tenantId, email: 'ip-limit-1@example.com', firstName: 'A', lastName: 'B' });
|
|
108
|
-
const user2 = await subjectService.createUser({ tenantId, email: 'ip-limit-2@example.com', firstName: 'C', lastName: 'D' });
|
|
109
|
-
await authenticationService.setPassword(user1, 'Strong-Password-2026!');
|
|
110
|
-
await authenticationService.setPassword(user2, 'Strong-Password-2026!');
|
|
111
|
-
// Fail for user1
|
|
112
|
-
let res = await callLogin(ip, { tenantId, subject: user1.id, password: 'wrong' });
|
|
113
|
-
expect(res.statusCode).toBe(401);
|
|
114
|
-
// Fail for user2
|
|
115
|
-
res = await callLogin(ip, { tenantId, subject: user2.id, password: 'wrong' });
|
|
116
|
-
expect(res.statusCode).toBe(401);
|
|
117
|
-
// 3rd attempt from same IP - Should be throttled (429)
|
|
118
|
-
res = await callLogin(ip, { tenantId, subject: 'any', password: 'any' });
|
|
119
|
-
expect(res.statusCode).toBe(429);
|
|
120
|
-
});
|
|
121
|
-
test('should work for multiple successful logins', async () => {
|
|
122
|
-
const user1 = await subjectService.createUser({ tenantId, email: 'success1@example.com', firstName: 'E', lastName: 'F' });
|
|
123
|
-
const user2 = await subjectService.createUser({ tenantId, email: 'success2@example.com', firstName: 'E', lastName: 'F' });
|
|
124
|
-
const user3 = await subjectService.createUser({ tenantId, email: 'success3@example.com', firstName: 'E', lastName: 'F' });
|
|
125
|
-
await authenticationService.setPassword(user1, 'Strong-Password-2026!');
|
|
126
|
-
await authenticationService.setPassword(user2, 'Strong-Password-2026!');
|
|
127
|
-
await authenticationService.setPassword(user3, 'Strong-Password-2026!');
|
|
128
|
-
// 1st attempt
|
|
129
|
-
let res = await callLogin('4.4.4.1', { tenantId, subject: user1.id, password: 'Strong-Password-2026!' });
|
|
130
|
-
expect(res.statusCode).toBe(200);
|
|
131
|
-
// 2nd attempt
|
|
132
|
-
res = await callLogin('4.4.4.2', { tenantId, subject: user2.id, password: 'Strong-Password-2026!' });
|
|
133
|
-
expect(res.statusCode).toBe(200);
|
|
134
|
-
// 3rd attempt
|
|
135
|
-
res = await callLogin('4.4.4.3', { tenantId, subject: user3.id, password: 'Strong-Password-2026!' });
|
|
136
|
-
expect(res.statusCode).toBe(200);
|
|
137
|
-
});
|
|
138
|
-
test('should limit disableTotp attempts by user', async () => {
|
|
139
|
-
const password = 'Strong-Password-2026!';
|
|
140
|
-
const user = await subjectService.createUser({ tenantId, email: 'totp-limit@example.com', firstName: 'John', lastName: 'Doe' });
|
|
141
|
-
await authenticationService.setPassword(user, password);
|
|
142
|
-
// Enable TOTP for the user
|
|
143
|
-
await authenticationService.initEnrollTotp(tenantId, user.id, auditor);
|
|
144
|
-
const totpEntry = await authenticationService.tryGetTotp(tenantId, user.id);
|
|
145
|
-
const secret = await importKey('raw-secret', totpEntry.secret, { name: 'HMAC', hash: authenticationService.getTotpOptions().codeHashAlgorithm }, false, ['sign']);
|
|
146
|
-
const setupToken = await generateTotpToken(secret, { ...authenticationService.getTotpOptions(), timestamp: currentTimestampSeconds() - 30 });
|
|
147
|
-
await authenticationService.completeEnrollTotp(tenantId, user.id, setupToken, auditor);
|
|
148
|
-
// Login to get a session token
|
|
149
|
-
const loginResult = await authenticationService.login({ tenantId, subject: user.id }, password, undefined, auditor);
|
|
150
|
-
expect(loginResult.type).toBe('totp');
|
|
151
|
-
assert(loginResult.type == 'totp', 'Login should have triggered TOTP');
|
|
152
|
-
const verifyToken = await generateTotpToken(secret, authenticationService.getTotpOptions());
|
|
153
|
-
const verifyResult = await authenticationService.loginVerifyTotp(loginResult.challengeToken, verifyToken, auditor);
|
|
154
|
-
expect(verifyResult.type).toBe('success');
|
|
155
|
-
assert(verifyResult.type == 'success', 'TOTP verification failed');
|
|
156
|
-
const sessionToken = verifyResult.result.token;
|
|
157
|
-
// 1st attempt - Fail (Invalid TOTP token)
|
|
158
|
-
let res = await callDisableTotp('1.1.1.1', sessionToken, { token: '000000' });
|
|
159
|
-
expect(res.statusCode).toBe(403);
|
|
160
|
-
// 2nd attempt - Fail (Invalid TOTP token)
|
|
161
|
-
res = await callDisableTotp('1.1.1.2', sessionToken, { token: '000001' });
|
|
162
|
-
expect(res.statusCode).toBe(403);
|
|
163
|
-
// 3rd attempt - Should be throttled (429)
|
|
164
|
-
res = await callDisableTotp('1.1.1.3', sessionToken, { token: '000002' });
|
|
165
|
-
expect(res.statusCode).toBe(429);
|
|
166
|
-
expect(res.body.json.error.name).toBe('TooManyRequestsError');
|
|
167
|
-
});
|
|
168
|
-
test('should limit disableTotp attempts by IP', async () => {
|
|
169
|
-
const password = 'Strong-Password-2026!';
|
|
170
|
-
const ip = '3.3.3.3';
|
|
171
|
-
// Create two users with TOTP enabled
|
|
172
|
-
const user1 = await subjectService.createUser({ tenantId, email: 'totp-ip-1@example.com', firstName: 'A', lastName: 'B' });
|
|
173
|
-
await authenticationService.setPassword(user1, password);
|
|
174
|
-
await authenticationService.initEnrollTotp(tenantId, user1.id, auditor);
|
|
175
|
-
const totpEntry1 = await authenticationService.tryGetTotp(tenantId, user1.id);
|
|
176
|
-
const secret1 = await importKey('raw-secret', totpEntry1.secret, { name: 'HMAC', hash: authenticationService.getTotpOptions().codeHashAlgorithm }, false, ['sign']);
|
|
177
|
-
const setupToken1 = await generateTotpToken(secret1, { ...authenticationService.getTotpOptions(), timestamp: currentTimestampSeconds() - 30 });
|
|
178
|
-
await authenticationService.completeEnrollTotp(tenantId, user1.id, setupToken1, auditor);
|
|
179
|
-
const user2 = await subjectService.createUser({ tenantId, email: 'totp-ip-2@example.com', firstName: 'C', lastName: 'D' });
|
|
180
|
-
await authenticationService.setPassword(user2, password);
|
|
181
|
-
await authenticationService.initEnrollTotp(tenantId, user2.id, auditor);
|
|
182
|
-
const totpEntry2 = await authenticationService.tryGetTotp(tenantId, user2.id);
|
|
183
|
-
const secret2 = await importKey('raw-secret', totpEntry2.secret, { name: 'HMAC', hash: authenticationService.getTotpOptions().codeHashAlgorithm }, false, ['sign']);
|
|
184
|
-
const setupToken2 = await generateTotpToken(secret2, { ...authenticationService.getTotpOptions(), timestamp: currentTimestampSeconds() - 30 });
|
|
185
|
-
await authenticationService.completeEnrollTotp(tenantId, user2.id, setupToken2, auditor);
|
|
186
|
-
// Login both users
|
|
187
|
-
const loginResult1 = await authenticationService.login({ tenantId, subject: user1.id }, password, undefined, auditor);
|
|
188
|
-
const loginResult2 = await authenticationService.login({ tenantId, subject: user2.id }, password, undefined, auditor);
|
|
189
|
-
expect(loginResult1.type).toBe('totp');
|
|
190
|
-
expect(loginResult2.type).toBe('totp');
|
|
191
|
-
assert(loginResult1.type == 'totp' && loginResult2.type == 'totp', 'Login should have triggered TOTP');
|
|
192
|
-
const verifyToken1 = await generateTotpToken(secret1, authenticationService.getTotpOptions());
|
|
193
|
-
const verifyToken2 = await generateTotpToken(secret2, authenticationService.getTotpOptions());
|
|
194
|
-
const verifyResult1 = await authenticationService.loginVerifyTotp(loginResult1.challengeToken, verifyToken1, auditor);
|
|
195
|
-
const verifyResult2 = await authenticationService.loginVerifyTotp(loginResult2.challengeToken, verifyToken2, auditor);
|
|
196
|
-
expect(verifyResult1.type).toBe('success');
|
|
197
|
-
expect(verifyResult2.type).toBe('success');
|
|
198
|
-
assert(verifyResult1.type == 'success' && verifyResult2.type == 'success', 'TOTP verification failed');
|
|
199
|
-
const sessionToken1 = verifyResult1.result.token;
|
|
200
|
-
const sessionToken2 = verifyResult2.result.token;
|
|
201
|
-
// Fail for user1 from IP
|
|
202
|
-
let res = await callDisableTotp(ip, sessionToken1, { token: '000000' });
|
|
203
|
-
expect(res.statusCode).toBe(403);
|
|
204
|
-
// Fail for user2 from same IP
|
|
205
|
-
res = await callDisableTotp(ip, sessionToken2, { token: '000000' });
|
|
206
|
-
expect(res.statusCode).toBe(403);
|
|
207
|
-
// 3rd attempt from same IP - Should be throttled (429)
|
|
208
|
-
res = await callDisableTotp(ip, sessionToken1, { token: 'any' });
|
|
209
|
-
expect(res.statusCode).toBe(429);
|
|
210
|
-
});
|
|
211
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { beforeAll, describe, expect, test } from 'vitest';
|
|
2
|
-
import { createJwtTokenString, importKmacKey } from '../../cryptography/index.js';
|
|
3
|
-
import { BadRequestError } from '../../errors/bad-request.error.js';
|
|
4
|
-
import { InvalidTokenError } from '../../errors/invalid-token.error.js';
|
|
5
|
-
import { currentTimestampSeconds } from '../../utils/date-time.js';
|
|
6
|
-
import { encodeUtf8 } from '../../utils/encoding.js';
|
|
7
|
-
import { getPasswordResetTokenFromString, getRefreshTokenFromString, getTokenFromRequest, getTokenFromString, tryGetAuthorizationTokenStringFromRequest, tryGetTokenFromRequest } from '../server/helper.js';
|
|
8
|
-
describe('authentication helper', () => {
|
|
9
|
-
let signingKey;
|
|
10
|
-
beforeAll(async () => {
|
|
11
|
-
signingKey = await importKmacKey('raw-secret', 'KMAC256', encodeUtf8('test-secret-with-enough-length-32'));
|
|
12
|
-
});
|
|
13
|
-
test('tryGetAuthorizationTokenStringFromRequest should extract bearer token from header', () => {
|
|
14
|
-
const request = {
|
|
15
|
-
headers: {
|
|
16
|
-
tryGet: (name) => name == 'Authorization' ? 'Bearer my-token' : undefined,
|
|
17
|
-
},
|
|
18
|
-
cookies: {
|
|
19
|
-
tryGet: () => undefined,
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
expect(tryGetAuthorizationTokenStringFromRequest(request)).toBe('my-token');
|
|
23
|
-
});
|
|
24
|
-
test('tryGetAuthorizationTokenStringFromRequest should throw if token without scheme from header', () => {
|
|
25
|
-
const request = {
|
|
26
|
-
headers: {
|
|
27
|
-
tryGet: (name) => name == 'Authorization' ? 'my-token' : undefined,
|
|
28
|
-
},
|
|
29
|
-
cookies: {
|
|
30
|
-
tryGet: () => undefined,
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
expect(() => tryGetAuthorizationTokenStringFromRequest(request)).toThrow(BadRequestError);
|
|
34
|
-
});
|
|
35
|
-
test('tryGetAuthorizationTokenStringFromRequest should extract bearer token from cookie', () => {
|
|
36
|
-
const request = {
|
|
37
|
-
headers: {
|
|
38
|
-
tryGet: () => undefined,
|
|
39
|
-
},
|
|
40
|
-
cookies: {
|
|
41
|
-
tryGet: (name) => name == 'token' ? 'my-token' : undefined,
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
expect(tryGetAuthorizationTokenStringFromRequest(request)).toBe('my-token');
|
|
45
|
-
});
|
|
46
|
-
test('tryGetAuthorizationTokenStringFromRequest should throw on unsupported scheme', () => {
|
|
47
|
-
const request = {
|
|
48
|
-
headers: {
|
|
49
|
-
tryGet: (name) => name == 'Authorization' ? 'Basic my-token' : undefined,
|
|
50
|
-
},
|
|
51
|
-
cookies: {
|
|
52
|
-
tryGet: () => undefined,
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
expect(() => tryGetAuthorizationTokenStringFromRequest(request)).toThrow(BadRequestError);
|
|
56
|
-
});
|
|
57
|
-
test('tryGetAuthorizationTokenStringFromRequest should return undefined if no token found', () => {
|
|
58
|
-
const request = {
|
|
59
|
-
headers: {
|
|
60
|
-
tryGet: () => undefined,
|
|
61
|
-
},
|
|
62
|
-
cookies: {
|
|
63
|
-
tryGet: () => undefined,
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
expect(tryGetAuthorizationTokenStringFromRequest(request)).toBeUndefined();
|
|
67
|
-
});
|
|
68
|
-
test('getTokenFromString should validate token', async () => {
|
|
69
|
-
const payload = { exp: currentTimestampSeconds() + 60, tenant: 't', subject: 's', jti: 'j' };
|
|
70
|
-
const token = await createJwtTokenString({ header: { v: 1, alg: 'KMAC256', typ: 'JWT' }, payload }, signingKey);
|
|
71
|
-
const validated = await getTokenFromString(token, 1, signingKey);
|
|
72
|
-
expect(validated.payload).toEqual(payload);
|
|
73
|
-
});
|
|
74
|
-
test('getTokenFromString should throw on version mismatch', async () => {
|
|
75
|
-
const payload = { exp: currentTimestampSeconds() + 60, tenant: 't', subject: 's', jti: 'j' };
|
|
76
|
-
const token = await createJwtTokenString({ header: { v: 2, alg: 'KMAC256', typ: 'JWT' }, payload }, signingKey);
|
|
77
|
-
await expect(getTokenFromString(token, 1, signingKey)).rejects.toThrow(InvalidTokenError);
|
|
78
|
-
});
|
|
79
|
-
test('getTokenFromString should throw on expired token', async () => {
|
|
80
|
-
const payload = { exp: currentTimestampSeconds() - 60, tenant: 't', subject: 's', jti: 'j' };
|
|
81
|
-
const token = await createJwtTokenString({ header: { v: 1, alg: 'KMAC256', typ: 'JWT' }, payload }, signingKey);
|
|
82
|
-
await expect(getTokenFromString(token, 1, signingKey)).rejects.toThrow('Token expired');
|
|
83
|
-
});
|
|
84
|
-
test('getRefreshTokenFromString should validate refresh token', async () => {
|
|
85
|
-
const payload = { exp: currentTimestampSeconds() + 60, tenant: 't', subject: 's', session: 'sess', secret: 'sec' };
|
|
86
|
-
const token = await createJwtTokenString({ header: { alg: 'KMAC256', typ: 'JWT' }, payload }, signingKey);
|
|
87
|
-
const validated = await getRefreshTokenFromString(token, signingKey);
|
|
88
|
-
expect(validated.payload).toEqual(payload);
|
|
89
|
-
});
|
|
90
|
-
test('getPasswordResetTokenFromString should validate password reset token', async () => {
|
|
91
|
-
const payload = { iat: currentTimestampSeconds(), exp: currentTimestampSeconds() + 60, tenant: 't', subject: 's' };
|
|
92
|
-
const token = await createJwtTokenString({ header: { alg: 'KMAC256', typ: 'JWT' }, payload }, signingKey);
|
|
93
|
-
const validated = await getPasswordResetTokenFromString(token, signingKey);
|
|
94
|
-
expect(validated.payload).toEqual(payload);
|
|
95
|
-
});
|
|
96
|
-
test('getTokenFromRequest should extract and validate token', async () => {
|
|
97
|
-
const payload = { exp: currentTimestampSeconds() + 60, tenant: 't', subject: 's', jti: 'j' };
|
|
98
|
-
const token = await createJwtTokenString({ header: { v: 1, alg: 'KMAC256', typ: 'JWT' }, payload }, signingKey);
|
|
99
|
-
const request = {
|
|
100
|
-
headers: {
|
|
101
|
-
tryGet: (name) => name == 'Authorization' ? `Bearer ${token}` : undefined,
|
|
102
|
-
},
|
|
103
|
-
cookies: {
|
|
104
|
-
tryGet: () => undefined,
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
const validated = await getTokenFromRequest(request, 1, signingKey);
|
|
108
|
-
expect(validated.payload).toEqual(payload);
|
|
109
|
-
});
|
|
110
|
-
test('tryGetTokenFromRequest should return undefined if no token in request', async () => {
|
|
111
|
-
const request = {
|
|
112
|
-
headers: {
|
|
113
|
-
tryGet: () => undefined,
|
|
114
|
-
},
|
|
115
|
-
cookies: {
|
|
116
|
-
tryGet: () => undefined,
|
|
117
|
-
},
|
|
118
|
-
};
|
|
119
|
-
const validated = await tryGetTokenFromRequest(request, 1, signingKey);
|
|
120
|
-
expect(validated).toBeUndefined();
|
|
121
|
-
});
|
|
122
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { PasswordRequirementsError } from '../errors/password-requirements.error.js';
|
|
3
|
-
describe('PasswordRequirementsError', () => {
|
|
4
|
-
it('should create an error with the given message', () => {
|
|
5
|
-
const message = 'Password is too weak.';
|
|
6
|
-
const error = new PasswordRequirementsError(message);
|
|
7
|
-
expect(error.message).toBe(message);
|
|
8
|
-
expect(error.name).toBe('PasswordRequirementsError');
|
|
9
|
-
});
|
|
10
|
-
it('should have the correct name', () => {
|
|
11
|
-
const error = new PasswordRequirementsError('any message');
|
|
12
|
-
expect(error.name).toBe('PasswordRequirementsError');
|
|
13
|
-
});
|
|
14
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|