@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,117 +0,0 @@
|
|
|
1
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'vitest';
|
|
2
|
-
import { Auditor } from '../../audit/index.js';
|
|
3
|
-
import { HttpHeaders } from '../../http/index.js';
|
|
4
|
-
import { HttpServerResponse } from '../../http/server/index.js';
|
|
5
|
-
import { clearTenantData, setupIntegrationTest } from '../../testing/index.js';
|
|
6
|
-
import { toArray } from '../../utils/array/array.js';
|
|
7
|
-
import { AuthenticationApiController } from '../server/authentication.api-controller.js';
|
|
8
|
-
import { AuthenticationService } from '../server/authentication.service.js';
|
|
9
|
-
import { SubjectService } from '../server/subject.service.js';
|
|
10
|
-
import { DefaultAuthenticationAncillaryService } from './authentication.test-ancillary-service.js';
|
|
11
|
-
describe('AuthenticationApiController Remember Functionality', () => {
|
|
12
|
-
let injector;
|
|
13
|
-
let database;
|
|
14
|
-
let controller;
|
|
15
|
-
let authenticationService;
|
|
16
|
-
let subjectService;
|
|
17
|
-
let auditor;
|
|
18
|
-
const schema = 'authentication';
|
|
19
|
-
const tenantId = crypto.randomUUID();
|
|
20
|
-
beforeAll(async () => {
|
|
21
|
-
({ injector, database } = await setupIntegrationTest({
|
|
22
|
-
modules: { authentication: true, audit: true, keyValueStore: true, signals: true, api: true },
|
|
23
|
-
authentication: {
|
|
24
|
-
ancillaryService: DefaultAuthenticationAncillaryService,
|
|
25
|
-
},
|
|
26
|
-
}));
|
|
27
|
-
authenticationService = await injector.resolveAsync(AuthenticationService);
|
|
28
|
-
subjectService = await injector.resolveAsync(SubjectService);
|
|
29
|
-
auditor = injector.resolve(Auditor);
|
|
30
|
-
controller = injector.resolve(AuthenticationApiController);
|
|
31
|
-
});
|
|
32
|
-
afterAll(async () => {
|
|
33
|
-
await injector?.dispose();
|
|
34
|
-
});
|
|
35
|
-
beforeEach(async () => {
|
|
36
|
-
await clearTenantData(database, schema, ['used_totp_tokens', 'totp_recovery_code', 'totp', 'password', 'session', 'user', 'service_account', 'system_account', 'subject'], tenantId);
|
|
37
|
-
});
|
|
38
|
-
test('login with remember: true should have Expires in cookies', async () => {
|
|
39
|
-
const user = await subjectService.createUser({ tenantId, email: 'api-rem@example.com', firstName: 'A', lastName: 'L' });
|
|
40
|
-
await authenticationService.setPassword(user, 'Pass-R3m3mb3r-2026!');
|
|
41
|
-
const context = {
|
|
42
|
-
request: { ip: '127.0.0.1' },
|
|
43
|
-
parameters: { tenantId, subject: user.id, password: 'Pass-R3m3mb3r-2026!', remember: true, data: undefined },
|
|
44
|
-
getAuditor: async () => auditor,
|
|
45
|
-
};
|
|
46
|
-
const response = await controller.login(context);
|
|
47
|
-
expect(response).toBeInstanceOf(HttpServerResponse);
|
|
48
|
-
const setCookieHeaders = toArray(response.headers.tryGet('Set-Cookie') ?? []);
|
|
49
|
-
expect(setCookieHeaders).toHaveLength(2); // authorization and refreshToken
|
|
50
|
-
for (const cookie of setCookieHeaders) {
|
|
51
|
-
expect(cookie).toMatch(/Expires=/);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
test('login with remember: false should NOT have Expires in cookies', async () => {
|
|
55
|
-
const user = await subjectService.createUser({ tenantId, email: 'api-no-rem@example.com', firstName: 'A', lastName: 'L' });
|
|
56
|
-
await authenticationService.setPassword(user, 'Pass-R3m3mb3r-2026!');
|
|
57
|
-
const context = {
|
|
58
|
-
request: { ip: '127.0.0.1' },
|
|
59
|
-
parameters: { tenantId, subject: user.id, password: 'Pass-R3m3mb3r-2026!', remember: false, data: undefined },
|
|
60
|
-
getAuditor: async () => auditor,
|
|
61
|
-
};
|
|
62
|
-
const response = await controller.login(context);
|
|
63
|
-
const setCookieHeaders = toArray(response.headers.tryGet('Set-Cookie') ?? []);
|
|
64
|
-
for (const cookie of setCookieHeaders) {
|
|
65
|
-
expect(cookie).not.toMatch(/Expires=/);
|
|
66
|
-
expect(cookie).not.toMatch(/Max-Age=/);
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
test('refresh should propagate remember status to cookies', async () => {
|
|
70
|
-
const user = await subjectService.createUser({ tenantId, email: 'api-refresh-rem@example.com', firstName: 'A', lastName: 'L' });
|
|
71
|
-
await authenticationService.setPassword(user, 'Pass-R3m3mb3r-2026!');
|
|
72
|
-
// 1. Login with remember: true
|
|
73
|
-
const loginResult = await authenticationService.login({ tenantId, subject: user.id }, 'Pass-R3m3mb3r-2026!', undefined, auditor, true);
|
|
74
|
-
expect(loginResult.type).toBe('success');
|
|
75
|
-
// 2. Refresh
|
|
76
|
-
const context = {
|
|
77
|
-
request: {
|
|
78
|
-
ip: '127.0.0.1',
|
|
79
|
-
headers: new HttpHeaders({
|
|
80
|
-
'X-Refresh-Token': `Bearer ${loginResult.result.refreshToken}`,
|
|
81
|
-
}),
|
|
82
|
-
cookies: {
|
|
83
|
-
tryGet: () => undefined,
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
parameters: { data: undefined },
|
|
87
|
-
getAuditor: async () => auditor,
|
|
88
|
-
};
|
|
89
|
-
const response = await controller.refresh(context);
|
|
90
|
-
const setCookieHeaders = toArray(response.headers.tryGet('Set-Cookie') ?? []);
|
|
91
|
-
for (const cookie of setCookieHeaders) {
|
|
92
|
-
expect(cookie).toMatch(/Expires=/);
|
|
93
|
-
}
|
|
94
|
-
// 3. Login with remember: false
|
|
95
|
-
const loginResultNoRem = await authenticationService.login({ tenantId, subject: user.id }, 'Pass-R3m3mb3r-2026!', undefined, auditor, false);
|
|
96
|
-
expect(loginResultNoRem.type).toBe('success');
|
|
97
|
-
// 4. Refresh
|
|
98
|
-
const contextNoRem = {
|
|
99
|
-
request: {
|
|
100
|
-
ip: '127.0.0.1',
|
|
101
|
-
headers: new HttpHeaders({
|
|
102
|
-
'X-Refresh-Token': `Bearer ${loginResultNoRem.result.refreshToken}`,
|
|
103
|
-
}),
|
|
104
|
-
cookies: {
|
|
105
|
-
tryGet: () => undefined,
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
parameters: { data: undefined },
|
|
109
|
-
getAuditor: async () => auditor,
|
|
110
|
-
};
|
|
111
|
-
const responseNoRem = await controller.refresh(contextNoRem);
|
|
112
|
-
const setCookieHeadersNoRem = toArray(responseNoRem.headers.tryGet('Set-Cookie') ?? []);
|
|
113
|
-
for (const cookie of setCookieHeadersNoRem) {
|
|
114
|
-
expect(cookie).not.toMatch(/Expires=/);
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'vitest';
|
|
2
|
-
import { Auditor } from '../../audit/index.js';
|
|
3
|
-
import { clearTenantData, setupIntegrationTest } from '../../testing/index.js';
|
|
4
|
-
import { AuthenticationService } from '../server/authentication.service.js';
|
|
5
|
-
import { SubjectService } from '../server/subject.service.js';
|
|
6
|
-
import { DefaultAuthenticationAncillaryService } from './authentication.test-ancillary-service.js';
|
|
7
|
-
describe('AuthenticationService Remember Functionality', () => {
|
|
8
|
-
let injector;
|
|
9
|
-
let database;
|
|
10
|
-
let authenticationService;
|
|
11
|
-
let subjectService;
|
|
12
|
-
let auditor;
|
|
13
|
-
const schema = 'authentication';
|
|
14
|
-
const tenantId = crypto.randomUUID();
|
|
15
|
-
beforeAll(async () => {
|
|
16
|
-
({ injector, database } = await setupIntegrationTest({
|
|
17
|
-
modules: { authentication: true },
|
|
18
|
-
authentication: {
|
|
19
|
-
ancillaryService: DefaultAuthenticationAncillaryService,
|
|
20
|
-
},
|
|
21
|
-
}));
|
|
22
|
-
authenticationService = await injector.resolveAsync(AuthenticationService);
|
|
23
|
-
subjectService = await injector.resolveAsync(SubjectService);
|
|
24
|
-
auditor = injector.resolve(Auditor);
|
|
25
|
-
});
|
|
26
|
-
afterAll(async () => {
|
|
27
|
-
await injector?.dispose();
|
|
28
|
-
});
|
|
29
|
-
beforeEach(async () => {
|
|
30
|
-
await clearTenantData(database, schema, ['password', 'session', 'user', 'service_account', 'system_account', 'subject'], tenantId);
|
|
31
|
-
});
|
|
32
|
-
test('RefreshToken should contain remember flag', async () => {
|
|
33
|
-
const user = await subjectService.createUser({
|
|
34
|
-
tenantId,
|
|
35
|
-
email: 'remember@example.com',
|
|
36
|
-
firstName: 'Rem',
|
|
37
|
-
lastName: 'Ember',
|
|
38
|
-
});
|
|
39
|
-
const tokenResult = await authenticationService.getToken(user, undefined, { remember: true });
|
|
40
|
-
const refreshToken = await authenticationService.validateRefreshToken(tokenResult.refreshToken);
|
|
41
|
-
expect(refreshToken.payload).toHaveProperty('remember', true);
|
|
42
|
-
expect(tokenResult.remember).toBe(true);
|
|
43
|
-
});
|
|
44
|
-
test('RefreshToken should respect remember flag for expiration', async () => {
|
|
45
|
-
const user = await subjectService.createUser({ tenantId, email: 'ttl@example.com', firstName: 'T', lastName: 'L' });
|
|
46
|
-
const normalResult = await authenticationService.getToken(user, undefined, { remember: false });
|
|
47
|
-
const rememberResult = await authenticationService.getToken(user, undefined, { remember: true });
|
|
48
|
-
const normalToken = await authenticationService.validateRefreshToken(normalResult.refreshToken);
|
|
49
|
-
const rememberToken = await authenticationService.validateRefreshToken(rememberResult.refreshToken);
|
|
50
|
-
expect(rememberToken.payload.exp).toBeGreaterThan(normalToken.payload.exp);
|
|
51
|
-
});
|
|
52
|
-
test('login should pass remember flag to getToken', async () => {
|
|
53
|
-
const user = await subjectService.createUser({ tenantId, email: 'login-rem@example.com', firstName: 'L', lastName: 'R' });
|
|
54
|
-
await authenticationService.setPassword(user, 'Strong-Password-2026!');
|
|
55
|
-
const result = await authenticationService.login({ tenantId, subject: user.id }, 'Strong-Password-2026!', undefined, auditor, true);
|
|
56
|
-
expect(result.type).toBe('success');
|
|
57
|
-
const tokenResult = result.result;
|
|
58
|
-
expect(tokenResult.remember).toBe(true);
|
|
59
|
-
const refreshToken = await authenticationService.validateRefreshToken(tokenResult.refreshToken);
|
|
60
|
-
expect(refreshToken.payload.remember).toBe(true);
|
|
61
|
-
});
|
|
62
|
-
test('refresh should propagate remember flag', async () => {
|
|
63
|
-
const user = await subjectService.createUser({ tenantId, email: 'refresh-rem@example.com', firstName: 'R', lastName: 'R' });
|
|
64
|
-
await authenticationService.setPassword(user, 'Strong-Password-2026!');
|
|
65
|
-
const loginResult = await authenticationService.login({ tenantId, subject: user.id }, 'Strong-Password-2026!', undefined, auditor, true);
|
|
66
|
-
expect(loginResult.type).toBe('success');
|
|
67
|
-
const loginSuccessResult = loginResult.result;
|
|
68
|
-
expect(loginSuccessResult.remember).toBe(true);
|
|
69
|
-
const refreshResult = await authenticationService.refresh(loginSuccessResult.refreshToken, undefined, {}, auditor);
|
|
70
|
-
expect(refreshResult.remember).toBe(true);
|
|
71
|
-
const newRefreshToken = await authenticationService.validateRefreshToken(refreshResult.refreshToken);
|
|
72
|
-
expect(newRefreshToken.payload.remember).toBe(true);
|
|
73
|
-
// Verify it also works when not remembered
|
|
74
|
-
const loginResultNoRem = await authenticationService.login({ tenantId, subject: user.id }, 'Strong-Password-2026!', undefined, auditor, false);
|
|
75
|
-
expect(loginResultNoRem.type).toBe('success');
|
|
76
|
-
const loginSuccessResultNoRem = loginResultNoRem.result;
|
|
77
|
-
expect(loginSuccessResultNoRem.remember).toBe(false);
|
|
78
|
-
const refreshResultNoRem = await authenticationService.refresh(loginSuccessResultNoRem.refreshToken, undefined, {}, auditor);
|
|
79
|
-
expect(refreshResultNoRem.remember).toBe(false);
|
|
80
|
-
const newRefreshTokenNoRem = await authenticationService.validateRefreshToken(refreshResultNoRem.refreshToken);
|
|
81
|
-
expect(newRefreshTokenNoRem.payload.remember).toBe(false);
|
|
82
|
-
});
|
|
83
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { beforeAll, beforeEach, describe, expect, test } from 'vitest';
|
|
2
|
-
import { NIL_UUID } from '../../constants.js';
|
|
3
|
-
import { runInInjectionContext } from '../../injector/index.js';
|
|
4
|
-
import { clearTenantData, setupIntegrationTest } from '../../testing/index.js';
|
|
5
|
-
import { SubjectStatus } from '../models/index.js';
|
|
6
|
-
import { SubjectService } from '../server/subject.service.js';
|
|
7
|
-
describe('SubjectService', () => {
|
|
8
|
-
let injector;
|
|
9
|
-
let database;
|
|
10
|
-
let subjectService;
|
|
11
|
-
const schema = 'authentication';
|
|
12
|
-
const tenantId = crypto.randomUUID();
|
|
13
|
-
beforeAll(async () => {
|
|
14
|
-
({ injector, database } = await setupIntegrationTest({ modules: { authentication: true } }));
|
|
15
|
-
subjectService = injector.resolve(SubjectService);
|
|
16
|
-
});
|
|
17
|
-
beforeEach(async () => {
|
|
18
|
-
await clearTenantData(database, schema, ['user', 'service_account', 'system_account', 'subject'], tenantId);
|
|
19
|
-
});
|
|
20
|
-
test('createUser should create a user', async () => {
|
|
21
|
-
await runInInjectionContext(injector, async () => {
|
|
22
|
-
const user = await subjectService.createUser({
|
|
23
|
-
tenantId,
|
|
24
|
-
email: 'test@example.com',
|
|
25
|
-
firstName: 'John',
|
|
26
|
-
lastName: 'Doe',
|
|
27
|
-
});
|
|
28
|
-
expect(user.id).toBeDefined();
|
|
29
|
-
expect(user.email).toBe('test@example.com');
|
|
30
|
-
expect(user.status).toBe(SubjectStatus.Active);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
test('exists should return true if subject exists', async () => {
|
|
34
|
-
await runInInjectionContext(injector, async () => {
|
|
35
|
-
const user = await subjectService.createUser({
|
|
36
|
-
tenantId,
|
|
37
|
-
email: 'test@example.com',
|
|
38
|
-
firstName: 'John',
|
|
39
|
-
lastName: 'Doe',
|
|
40
|
-
});
|
|
41
|
-
expect(await subjectService.exists(tenantId, user.id)).toBe(true);
|
|
42
|
-
expect(await subjectService.exists(tenantId, NIL_UUID)).toBe(false);
|
|
43
|
-
const found = await subjectService.hasUserByEmail(tenantId, 'test@example.com');
|
|
44
|
-
const notFound = await subjectService.hasUserByEmail(tenantId, 'missing@example.com');
|
|
45
|
-
expect(found).toBe(true);
|
|
46
|
-
expect(notFound).toBe(false);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
test('deleteUser should soft delete a user', async () => {
|
|
50
|
-
await runInInjectionContext(injector, async () => {
|
|
51
|
-
const user = await subjectService.createUser({
|
|
52
|
-
tenantId,
|
|
53
|
-
email: 'test@example.com',
|
|
54
|
-
firstName: 'John',
|
|
55
|
-
lastName: 'Doe',
|
|
56
|
-
});
|
|
57
|
-
expect(await subjectService.exists(tenantId, user.id)).toBe(true);
|
|
58
|
-
await subjectService.deleteUser(tenantId, user.id);
|
|
59
|
-
const deletedUser = await subjectService.getSubject(user.id, { withDeleted: true });
|
|
60
|
-
expect(deletedUser.metadata.deleteTimestamp).not.toBeNull();
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
test('listUsers should list all non-deleted users in a tenant', async () => {
|
|
64
|
-
await runInInjectionContext(injector, async () => {
|
|
65
|
-
await subjectService.createUser({
|
|
66
|
-
tenantId,
|
|
67
|
-
email: 'user1@example.com',
|
|
68
|
-
firstName: 'User',
|
|
69
|
-
lastName: 'One',
|
|
70
|
-
});
|
|
71
|
-
const user2 = await subjectService.createUser({
|
|
72
|
-
tenantId,
|
|
73
|
-
email: 'user2@example.com',
|
|
74
|
-
firstName: 'User',
|
|
75
|
-
lastName: 'Two',
|
|
76
|
-
});
|
|
77
|
-
await subjectService.deleteUser(tenantId, user2.id);
|
|
78
|
-
const users = await subjectService.listUsers(tenantId);
|
|
79
|
-
expect(users).toHaveLength(1);
|
|
80
|
-
expect(users[0]?.email).toBe('user1@example.com');
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
test('getSystemAccountSubject should create or return system account', async () => {
|
|
84
|
-
await runInInjectionContext(injector, async () => {
|
|
85
|
-
const sa1 = await subjectService.getSystemAccount(tenantId, 'test-system');
|
|
86
|
-
expect(sa1.id).toBeDefined();
|
|
87
|
-
expect(sa1.identifier).toBe('test-system');
|
|
88
|
-
const sa2 = await subjectService.getSystemAccount(tenantId, 'test-system');
|
|
89
|
-
expect(sa2.id).toBe(sa1.id);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
test('updateUser should update user details', async () => {
|
|
93
|
-
await runInInjectionContext(injector, async () => {
|
|
94
|
-
const user = await subjectService.createUser({ tenantId, email: 'update@example.com', firstName: 'Old', lastName: 'Name' });
|
|
95
|
-
await subjectService.updateUser(tenantId, user.id, { firstName: 'New', status: SubjectStatus.Suspended });
|
|
96
|
-
const updated = await subjectService.getUser(tenantId, user.id);
|
|
97
|
-
expect(updated.firstName).toBe('New');
|
|
98
|
-
expect(updated.lastName).toBe('Name');
|
|
99
|
-
expect(updated.status).toBe(SubjectStatus.Suspended);
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
test('updateUserEmail should update user email', async () => {
|
|
103
|
-
await runInInjectionContext(injector, async () => {
|
|
104
|
-
const user = await subjectService.createUser({ tenantId, email: 'email@example.com', firstName: 'E', lastName: 'M' });
|
|
105
|
-
await subjectService.updateUserEmail(tenantId, user.id, 'new-email@example.com');
|
|
106
|
-
const updated = await subjectService.getUserByEmail(tenantId, 'new-email@example.com');
|
|
107
|
-
expect(updated.id).toBe(user.id);
|
|
108
|
-
await expect(subjectService.updateUserEmail(tenantId, user.id, 'invalid-email')).rejects.toThrow();
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
test('service accounts should be manageable', async () => {
|
|
112
|
-
await runInInjectionContext(injector, async () => {
|
|
113
|
-
const sa = await subjectService.createServiceAccount({ tenantId, displayName: 'SA', description: 'Desc', parent: null });
|
|
114
|
-
expect(sa.displayName).toBe('SA');
|
|
115
|
-
await subjectService.updateServiceAccount(tenantId, sa.id, { displayName: 'New SA' });
|
|
116
|
-
const updated = await subjectService.getServiceAccount(tenantId, sa.id);
|
|
117
|
-
expect(updated.displayName).toBe('New SA');
|
|
118
|
-
const saBySubject = await subjectService.getServiceAccountBySubject(updated);
|
|
119
|
-
expect(saBySubject.id).toBe(sa.id);
|
|
120
|
-
const list = await subjectService.listServiceAccounts(tenantId);
|
|
121
|
-
expect(list).toHaveLength(1);
|
|
122
|
-
await subjectService.deleteServiceAccount(tenantId, sa.id);
|
|
123
|
-
expect(await subjectService.exists(tenantId, sa.id)).toBe(false);
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
test('deleteUser should remove user', async () => {
|
|
127
|
-
await runInInjectionContext(injector, async () => {
|
|
128
|
-
const user = await subjectService.createUser({ tenantId, email: 'delete@example.com', firstName: 'D', lastName: 'E' });
|
|
129
|
-
await subjectService.deleteUser(tenantId, user.id);
|
|
130
|
-
expect(await subjectService.exists(tenantId, user.id)).toBe(false);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
test('getSystemAccountSubject should load or create system account', async () => {
|
|
134
|
-
await runInInjectionContext(injector, async () => {
|
|
135
|
-
const sa1 = await subjectService.getSystemAccount(tenantId, 'sys1');
|
|
136
|
-
const sa2 = await subjectService.getSystemAccount(tenantId, 'sys1');
|
|
137
|
-
expect(sa1.id).toBe(sa2.id);
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'vitest';
|
|
2
|
-
import { ActorType, Auditor } from '../../audit/index.js';
|
|
3
|
-
import { clearTenantData, setupIntegrationTest } from '../../testing/index.js';
|
|
4
|
-
import { SubjectStatus } from '../models/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('Suspended Subject Authentication', () => {
|
|
10
|
-
let injector;
|
|
11
|
-
let database;
|
|
12
|
-
let authenticationService;
|
|
13
|
-
let subjectService;
|
|
14
|
-
let auditor;
|
|
15
|
-
const schema = 'authentication';
|
|
16
|
-
const tenantId = crypto.randomUUID();
|
|
17
|
-
beforeAll(async () => {
|
|
18
|
-
({ injector, database } = await setupIntegrationTest({
|
|
19
|
-
modules: { authentication: true },
|
|
20
|
-
authentication: {
|
|
21
|
-
ancillaryService: DefaultAuthenticationAncillaryService,
|
|
22
|
-
},
|
|
23
|
-
}));
|
|
24
|
-
authenticationService = await injector.resolveAsync(AuthenticationService);
|
|
25
|
-
subjectService = await injector.resolveAsync(SubjectService);
|
|
26
|
-
auditor = injector.resolve(Auditor);
|
|
27
|
-
});
|
|
28
|
-
afterAll(async () => {
|
|
29
|
-
await injector?.dispose();
|
|
30
|
-
});
|
|
31
|
-
beforeEach(async () => {
|
|
32
|
-
await clearTenantData(database, schema, ['totp_recovery_code', 'totp', 'password', 'session', 'user', 'service_account', 'system_account', 'subject'], tenantId);
|
|
33
|
-
});
|
|
34
|
-
test('login should FAIL for suspended user', async () => {
|
|
35
|
-
const user = await subjectService.createUser({
|
|
36
|
-
tenantId,
|
|
37
|
-
email: 'suspended@example.com',
|
|
38
|
-
firstName: 'Suspended',
|
|
39
|
-
lastName: 'User',
|
|
40
|
-
status: SubjectStatus.Suspended,
|
|
41
|
-
});
|
|
42
|
-
await authenticationService.setPassword(user, 'Strong-Password-2026!');
|
|
43
|
-
const loginPromise = authenticationService.login({ tenantId, subject: user.id }, 'Strong-Password-2026!', undefined, auditor.with({ actor: user.id, actorType: ActorType.Subject }));
|
|
44
|
-
await expect(loginPromise).rejects.toThrow('Invalid credentials.');
|
|
45
|
-
});
|
|
46
|
-
test('refresh should FAIL for suspended user', async () => {
|
|
47
|
-
const user = await subjectService.createUser({
|
|
48
|
-
tenantId,
|
|
49
|
-
email: 'suspended-refresh@example.com',
|
|
50
|
-
firstName: 'Suspended',
|
|
51
|
-
lastName: 'Refresh',
|
|
52
|
-
status: SubjectStatus.Active, // Start active to get a token
|
|
53
|
-
});
|
|
54
|
-
await authenticationService.setPassword(user, 'Strong-Password-2026!');
|
|
55
|
-
const userAuditor = auditor.with({ actor: user.id, actorType: ActorType.Subject });
|
|
56
|
-
const loginResult = await authenticationService.login({ tenantId, subject: user.id }, 'Strong-Password-2026!', undefined, userAuditor);
|
|
57
|
-
expect(loginResult.type).toBe('success');
|
|
58
|
-
// Now suspend the user
|
|
59
|
-
await subjectService.updateUser(tenantId, user.id, { status: SubjectStatus.Suspended });
|
|
60
|
-
const refreshPromise = authenticationService.refresh(loginResult.result.refreshToken, undefined, {}, userAuditor);
|
|
61
|
-
await expect(refreshPromise).rejects.toThrow('Subject is suspended.');
|
|
62
|
-
});
|
|
63
|
-
test('impersonate should FAIL if target is suspended', async () => {
|
|
64
|
-
const admin = await subjectService.createUser({ tenantId, email: 'admin@example.com', firstName: 'A', lastName: 'D' });
|
|
65
|
-
const user = await subjectService.createUser({
|
|
66
|
-
tenantId,
|
|
67
|
-
email: 'suspended-target@example.com',
|
|
68
|
-
firstName: 'S',
|
|
69
|
-
lastName: 'T',
|
|
70
|
-
status: SubjectStatus.Suspended,
|
|
71
|
-
});
|
|
72
|
-
const adminAuditor = auditor.with({ actor: admin.id, actorType: ActorType.Subject });
|
|
73
|
-
const adminToken = await authenticationService.getToken(admin, undefined);
|
|
74
|
-
const impersonatePromise = authenticationService.impersonate(adminToken.token, adminToken.refreshToken, user.id, undefined, adminAuditor);
|
|
75
|
-
await expect(impersonatePromise).rejects.toThrow('Subject is suspended.');
|
|
76
|
-
});
|
|
77
|
-
test('changePassword should FAIL for suspended user', async () => {
|
|
78
|
-
const user = await subjectService.createUser({
|
|
79
|
-
tenantId,
|
|
80
|
-
email: 'suspended-change@example.com',
|
|
81
|
-
firstName: 'S',
|
|
82
|
-
lastName: 'C',
|
|
83
|
-
status: SubjectStatus.Suspended,
|
|
84
|
-
});
|
|
85
|
-
await authenticationService.setPassword(user, 'Old-Password-2026!');
|
|
86
|
-
const userAuditor = auditor.with({ actor: user.id, actorType: ActorType.Subject });
|
|
87
|
-
const changePasswordPromise = authenticationService.changePassword({ tenantId, subject: user.id }, 'Old-Password-2026!', 'New-Password-2026!', userAuditor);
|
|
88
|
-
await expect(changePasswordPromise).rejects.toThrow('Invalid credentials.');
|
|
89
|
-
});
|
|
90
|
-
test('resetPassword should FAIL for suspended user', async () => {
|
|
91
|
-
const user = await subjectService.createUser({
|
|
92
|
-
tenantId,
|
|
93
|
-
email: 'suspended-reset@example.com',
|
|
94
|
-
firstName: 'S',
|
|
95
|
-
lastName: 'R',
|
|
96
|
-
status: SubjectStatus.Active,
|
|
97
|
-
});
|
|
98
|
-
const userAuditor = auditor.with({ actor: user.id, actorType: ActorType.Subject });
|
|
99
|
-
// @ts-ignore - access private for test or use public init
|
|
100
|
-
const passwordResetToken = await authenticationService.createPasswordResetToken(user, Date.now() + 100000);
|
|
101
|
-
// Suspend after token issue
|
|
102
|
-
await subjectService.updateUser(tenantId, user.id, { status: SubjectStatus.Suspended });
|
|
103
|
-
const resetPasswordPromise = authenticationService.resetPassword(passwordResetToken.token, 'New-Password-Reset-2026!', userAuditor);
|
|
104
|
-
await expect(resetPasswordPromise).rejects.toThrow('Subject is suspended.');
|
|
105
|
-
});
|
|
106
|
-
test('initPasswordReset should SILENTLY IGNORE suspended user', async () => {
|
|
107
|
-
const user = await subjectService.createUser({
|
|
108
|
-
tenantId,
|
|
109
|
-
email: 'suspended-init-reset@example.com',
|
|
110
|
-
firstName: 'S',
|
|
111
|
-
lastName: 'IR',
|
|
112
|
-
status: SubjectStatus.Suspended,
|
|
113
|
-
});
|
|
114
|
-
const userAuditor = auditor.with({ actor: user.id, actorType: ActorType.Subject });
|
|
115
|
-
const ancillaryService = await injector.resolveAsync(AuthenticationAncillaryService);
|
|
116
|
-
ancillaryService.lastResetData = undefined;
|
|
117
|
-
await authenticationService.initPasswordReset({ tenantId, subject: user.id }, undefined, userAuditor);
|
|
118
|
-
expect(ancillaryService.lastResetData).toBeUndefined();
|
|
119
|
-
});
|
|
120
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'vitest';
|
|
2
|
-
import { ActorType, Auditor } from '../../audit/index.js';
|
|
3
|
-
import { importHmacKey, importKey } from '../../cryptography/index.js';
|
|
4
|
-
import { generateTotpToken } from '../../cryptography/totp.js';
|
|
5
|
-
import { clearTenantData, setupIntegrationTest } from '../../testing/index.js';
|
|
6
|
-
import { currentTimestampSeconds } from '../../utils/date-time.js';
|
|
7
|
-
import { TotpStatus } from '../models/authentication-totp.model.js';
|
|
8
|
-
import { AuthenticationService } from '../server/authentication.service.js';
|
|
9
|
-
import { SubjectService } from '../server/subject.service.js';
|
|
10
|
-
import { DefaultAuthenticationAncillaryService } from './authentication.test-ancillary-service.js';
|
|
11
|
-
describe('TOTP Enrollment', () => {
|
|
12
|
-
let injector;
|
|
13
|
-
let database;
|
|
14
|
-
let authenticationService;
|
|
15
|
-
let subjectService;
|
|
16
|
-
let auditor;
|
|
17
|
-
const schema = 'authentication';
|
|
18
|
-
const tenantId = crypto.randomUUID();
|
|
19
|
-
beforeAll(async () => {
|
|
20
|
-
({ injector, database } = await setupIntegrationTest({
|
|
21
|
-
modules: { authentication: true },
|
|
22
|
-
authentication: {
|
|
23
|
-
ancillaryService: DefaultAuthenticationAncillaryService,
|
|
24
|
-
},
|
|
25
|
-
}));
|
|
26
|
-
authenticationService = await injector.resolveAsync(AuthenticationService);
|
|
27
|
-
subjectService = await injector.resolveAsync(SubjectService);
|
|
28
|
-
auditor = injector.resolve(Auditor);
|
|
29
|
-
});
|
|
30
|
-
afterAll(async () => {
|
|
31
|
-
await injector?.dispose();
|
|
32
|
-
});
|
|
33
|
-
beforeEach(async () => {
|
|
34
|
-
await clearTenantData(database, schema, ['used_totp_tokens', 'totp_recovery_code', 'totp', 'password', 'session', 'user', 'service_account', 'system_account', 'subject'], tenantId);
|
|
35
|
-
});
|
|
36
|
-
test('initEnrollTotp should initiate enrollment', async () => {
|
|
37
|
-
const user = await subjectService.createUser({
|
|
38
|
-
tenantId,
|
|
39
|
-
email: 'totp-test@example.com',
|
|
40
|
-
firstName: 'Totp',
|
|
41
|
-
lastName: 'User',
|
|
42
|
-
});
|
|
43
|
-
const userAuditor = auditor.with({ actor: user.id, actorType: ActorType.Subject });
|
|
44
|
-
const result = await authenticationService.initEnrollTotp(tenantId, user.id, userAuditor);
|
|
45
|
-
expect(result.secret).toBeDefined();
|
|
46
|
-
expect(result.uri).toContain('otpauth://totp/');
|
|
47
|
-
expect(result.uri).toContain('secret=' + result.secret);
|
|
48
|
-
const totpEntry = await authenticationService.tryGetTotp(tenantId, user.id);
|
|
49
|
-
expect(totpEntry).toBeDefined();
|
|
50
|
-
expect(totpEntry.status).toBe(TotpStatus.Pending);
|
|
51
|
-
});
|
|
52
|
-
test('completeEnrollTotp should activate TOTP after verification', async () => {
|
|
53
|
-
const user = await subjectService.createUser({
|
|
54
|
-
tenantId,
|
|
55
|
-
email: 'totp-test-complete@example.com',
|
|
56
|
-
firstName: 'Totp',
|
|
57
|
-
lastName: 'Complete',
|
|
58
|
-
});
|
|
59
|
-
const userAuditor = auditor.with({ actor: user.id, actorType: ActorType.Subject });
|
|
60
|
-
const { secret } = await authenticationService.initEnrollTotp(tenantId, user.id, userAuditor);
|
|
61
|
-
// We need to convert base32 secret back to Uint8Array for generateTotpToken if it's base32
|
|
62
|
-
// But the utility probably expects Uint8Array or we use the utility's own secret generation
|
|
63
|
-
// Actually initEnrollTotp returns base32 secret for the user to enter in app.
|
|
64
|
-
// For testing, let's assume we can get the raw secret from the DB or similar.
|
|
65
|
-
const totpEntry = await authenticationService.tryGetTotp(tenantId, user.id);
|
|
66
|
-
const secretKey = await importKey('raw-secret', totpEntry.secret, { name: 'HMAC', hash: authenticationService.getTotpOptions().codeHashAlgorithm }, false, ['sign']);
|
|
67
|
-
const token = await generateTotpToken(secretKey, authenticationService.getTotpOptions());
|
|
68
|
-
const completeResult = await authenticationService.completeEnrollTotp(tenantId, user.id, token, userAuditor);
|
|
69
|
-
expect(completeResult.recoveryCodes).toHaveLength(10);
|
|
70
|
-
const activeTotpEntry = await authenticationService.tryGetTotp(tenantId, user.id);
|
|
71
|
-
expect(activeTotpEntry.status).toBe(TotpStatus.Active);
|
|
72
|
-
});
|
|
73
|
-
test('disableTotp should deactivate TOTP', async () => {
|
|
74
|
-
const user = await subjectService.createUser({
|
|
75
|
-
tenantId,
|
|
76
|
-
email: 'totp-test-disable@example.com',
|
|
77
|
-
firstName: 'Totp',
|
|
78
|
-
lastName: 'Disable',
|
|
79
|
-
});
|
|
80
|
-
const userAuditor = auditor.with({ actor: user.id, actorType: ActorType.Subject });
|
|
81
|
-
await authenticationService.initEnrollTotp(tenantId, user.id, userAuditor);
|
|
82
|
-
const totpEntry = await authenticationService.tryGetTotp(tenantId, user.id);
|
|
83
|
-
const secretKey = await importKey('raw-secret', totpEntry.secret, { name: 'HMAC', hash: authenticationService.getTotpOptions().codeHashAlgorithm }, false, ['sign']);
|
|
84
|
-
const token = await generateTotpToken(secretKey, { ...authenticationService.getTotpOptions(), timestamp: currentTimestampSeconds() - 30 });
|
|
85
|
-
await authenticationService.completeEnrollTotp(tenantId, user.id, token, userAuditor);
|
|
86
|
-
const token2 = await generateTotpToken(secretKey, authenticationService.getTotpOptions());
|
|
87
|
-
await authenticationService.disableTotp(tenantId, user.id, token2, userAuditor);
|
|
88
|
-
const disabledTotpEntry = await authenticationService.tryGetTotp(tenantId, user.id);
|
|
89
|
-
expect(disabledTotpEntry).toBeUndefined();
|
|
90
|
-
});
|
|
91
|
-
test('disableTotp should fail with recovery code (decoupled)', async () => {
|
|
92
|
-
const user = await subjectService.createUser({
|
|
93
|
-
tenantId,
|
|
94
|
-
email: 'totp-test-disable-fail@example.com',
|
|
95
|
-
firstName: 'Totp',
|
|
96
|
-
lastName: 'DisableFail',
|
|
97
|
-
});
|
|
98
|
-
const userAuditor = auditor.with({ actor: user.id, actorType: ActorType.Subject });
|
|
99
|
-
await authenticationService.initEnrollTotp(tenantId, user.id, userAuditor);
|
|
100
|
-
const totpEntry = (await authenticationService.tryGetTotp(tenantId, user.id));
|
|
101
|
-
const secretKey = await importHmacKey('raw-secret', authenticationService.getTotpOptions().codeHashAlgorithm, totpEntry.secret, false);
|
|
102
|
-
const token = await generateTotpToken(secretKey, { ...authenticationService.getTotpOptions(), timestamp: currentTimestampSeconds() - 30 });
|
|
103
|
-
const { recoveryCodes } = await authenticationService.completeEnrollTotp(tenantId, user.id, token, userAuditor);
|
|
104
|
-
await expect(authenticationService.disableTotp(tenantId, user.id, recoveryCodes[0], userAuditor)).rejects.toThrow();
|
|
105
|
-
});
|
|
106
|
-
test('disableTotpWithRecoveryCode should deactivate TOTP', async () => {
|
|
107
|
-
const user = await subjectService.createUser({
|
|
108
|
-
tenantId,
|
|
109
|
-
email: 'totp-test-disable-recovery@example.com',
|
|
110
|
-
firstName: 'Totp',
|
|
111
|
-
lastName: 'DisableRecovery',
|
|
112
|
-
});
|
|
113
|
-
const userAuditor = auditor.with({ actor: user.id, actorType: ActorType.Subject });
|
|
114
|
-
await authenticationService.initEnrollTotp(tenantId, user.id, userAuditor);
|
|
115
|
-
const totpEntry = (await authenticationService.tryGetTotp(tenantId, user.id));
|
|
116
|
-
const secretKey = await importHmacKey('raw-secret', authenticationService.getTotpOptions().codeHashAlgorithm, totpEntry.secret, false);
|
|
117
|
-
const token = await generateTotpToken(secretKey, { ...authenticationService.getTotpOptions(), timestamp: currentTimestampSeconds() - 30 });
|
|
118
|
-
const { recoveryCodes } = await authenticationService.completeEnrollTotp(tenantId, user.id, token, userAuditor);
|
|
119
|
-
await authenticationService.disableTotpWithRecoveryCode(tenantId, user.id, recoveryCodes[0], userAuditor);
|
|
120
|
-
const disabledTotpEntry = await authenticationService.tryGetTotp(tenantId, user.id);
|
|
121
|
-
expect(disabledTotpEntry).toBeUndefined();
|
|
122
|
-
});
|
|
123
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|