@tstdl/base 0.93.87 → 0.93.89
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/ai/genkit/helpers.d.ts +3 -1
- package/ai/genkit/helpers.js +3 -3
- package/api/server/gateway.d.ts +3 -0
- package/api/server/gateway.js +15 -4
- package/api/server/middlewares/catch-error.middleware.js +2 -4
- package/api/server/middlewares/cors.middleware.js +2 -3
- package/api/server/middlewares/csrf.middleware.d.ts +41 -0
- package/api/server/middlewares/csrf.middleware.js +108 -0
- package/api/server/middlewares/index.d.ts +1 -0
- package/api/server/middlewares/index.js +1 -0
- package/api/server/module.d.ts +8 -2
- package/api/server/module.js +14 -8
- package/api/server/tests/csrf.middleware.test.js +91 -0
- package/audit/drizzle/{0000_bored_stick.sql → 0000_lumpy_thunderball.sql} +3 -3
- package/audit/drizzle/meta/0000_snapshot.json +4 -4
- package/audit/drizzle/meta/_journal.json +2 -9
- package/audit/module.d.ts +4 -1
- package/audit/module.js +3 -2
- package/audit/schemas.d.ts +1 -1
- package/audit/types.d.ts +1 -1
- package/audit/types.js +1 -1
- package/authentication/client/authentication.service.d.ts +14 -1
- package/authentication/client/authentication.service.js +82 -23
- package/authentication/client/http-client.middleware.d.ts +6 -0
- package/authentication/client/http-client.middleware.js +36 -0
- package/authentication/client/module.js +8 -2
- package/authentication/models/service-account.model.d.ts +2 -2
- package/authentication/models/service-account.model.js +10 -5
- package/authentication/models/subject.model.d.ts +19 -5
- package/authentication/models/subject.model.js +25 -29
- package/authentication/models/system-account.model.d.ts +3 -2
- package/authentication/models/system-account.model.js +11 -5
- package/authentication/models/user.model.d.ts +2 -11
- package/authentication/models/user.model.js +5 -16
- package/authentication/server/authentication-api-request-token.provider.d.ts +0 -2
- package/authentication/server/authentication-api-request-token.provider.js +3 -11
- package/authentication/server/authentication.api-controller.d.ts +1 -2
- package/authentication/server/authentication.api-controller.js +8 -9
- package/authentication/server/authentication.audit.d.ts +3 -2
- package/authentication/server/authentication.service.d.ts +27 -1
- package/authentication/server/authentication.service.js +67 -18
- package/authentication/server/drizzle/{0000_normal_paper_doll.sql → 0000_soft_tag.sql} +25 -32
- package/authentication/server/drizzle/meta/0000_snapshot.json +180 -205
- package/authentication/server/drizzle/meta/_journal.json +2 -2
- package/authentication/server/helper.js +9 -2
- package/authentication/server/module.d.ts +4 -1
- package/authentication/server/module.js +9 -5
- package/authentication/server/schemas.d.ts +2 -1
- package/authentication/server/schemas.js +2 -2
- package/authentication/server/subject.service.d.ts +14 -8
- package/authentication/server/subject.service.js +86 -84
- package/authentication/tests/authentication-ancillary.service.test.d.ts +1 -0
- package/authentication/tests/authentication-ancillary.service.test.js +13 -0
- package/authentication/tests/authentication-secret-requirements.validator.test.d.ts +1 -0
- package/authentication/tests/authentication-secret-requirements.validator.test.js +29 -0
- package/authentication/tests/authentication.api-controller.test.d.ts +1 -0
- package/authentication/tests/authentication.api-controller.test.js +88 -0
- package/authentication/tests/authentication.api-request-token.provider.test.d.ts +1 -0
- package/authentication/tests/authentication.api-request-token.provider.test.js +48 -0
- package/authentication/tests/authentication.client-middleware.test.d.ts +1 -0
- package/authentication/tests/authentication.client-middleware.test.js +23 -0
- package/authentication/tests/authentication.client-service.test.d.ts +1 -0
- package/authentication/tests/authentication.client-service.test.js +70 -0
- package/authentication/tests/authentication.service.test.d.ts +1 -0
- package/authentication/tests/authentication.service.test.js +186 -0
- package/authentication/tests/authentication.test-ancillary-service.d.ts +9 -0
- package/authentication/tests/authentication.test-ancillary-service.js +27 -0
- package/authentication/tests/helper.test.d.ts +1 -0
- package/authentication/tests/helper.test.js +107 -0
- package/authentication/tests/secret-requirements.error.test.d.ts +1 -0
- package/authentication/tests/secret-requirements.error.test.js +14 -0
- package/authentication/tests/subject.service.test.d.ts +1 -0
- package/authentication/tests/subject.service.test.js +140 -0
- package/circuit-breaker/postgres/drizzle/meta/0000_snapshot.json +1 -1
- package/circuit-breaker/postgres/drizzle/meta/_journal.json +2 -2
- package/circuit-breaker/postgres/module.d.ts +7 -1
- package/circuit-breaker/postgres/module.js +8 -6
- package/circuit-breaker/tests/circuit-breaker.test.js +2 -22
- package/document-management/api/document-management.api.js +2 -6
- package/document-management/server/services/document-validation.service.js +6 -5
- package/document-management/server/services/document-workflow.service.js +5 -5
- package/document-management/service-models/document-folders.view-model.d.ts +5 -2
- package/document-management/service-models/document-folders.view-model.js +42 -9
- package/document-management/service-models/enriched/enriched-document-management-data.view.js +1 -1
- package/examples/document-management/main.js +4 -4
- package/http/client/adapters/undici.adapter.d.ts +7 -5
- package/http/client/adapters/undici.adapter.js +13 -10
- package/http/client/module.d.ts +3 -1
- package/http/client/module.js +8 -9
- package/http/server/http-server.d.ts +2 -0
- package/http/server/node/module.d.ts +6 -2
- package/http/server/node/module.js +6 -4
- package/http/server/node/node-http-server.d.ts +2 -0
- package/http/server/node/node-http-server.js +7 -0
- package/http/types.d.ts +1 -1
- package/key-value-store/postgres/module.d.ts +7 -1
- package/key-value-store/postgres/module.js +7 -3
- package/lock/postgres/lock.js +0 -1
- package/lock/postgres/module.d.ts +7 -1
- package/lock/postgres/module.js +9 -5
- package/logger/formatter.d.ts +2 -0
- package/logger/formatters/json.js +2 -2
- package/logger/formatters/pretty-print.js +8 -10
- package/logger/logger.d.ts +1 -1
- package/logger/logger.js +15 -12
- package/message-bus/local/module.d.ts +5 -2
- package/message-bus/local/module.js +5 -4
- package/module/module.d.ts +2 -1
- package/module/module.js +3 -0
- package/module/modules/web-server.module.d.ts +11 -6
- package/module/modules/web-server.module.js +15 -10
- package/orm/decorators.d.ts +24 -1
- package/orm/decorators.js +40 -4
- package/orm/query/base.d.ts +17 -17
- package/orm/query/base.js +1 -1
- package/orm/repository.types.d.ts +45 -1
- package/orm/schemas/tsvector.js +1 -1
- package/orm/server/drizzle/schema-converter.d.ts +3 -1
- package/orm/server/drizzle/schema-converter.js +120 -14
- package/orm/server/index.d.ts +1 -0
- package/orm/server/index.js +1 -0
- package/orm/server/module.d.ts +4 -2
- package/orm/server/module.js +6 -5
- package/orm/server/query-converter.d.ts +6 -3
- package/orm/server/query-converter.js +32 -20
- package/orm/server/repository-config.d.ts +8 -0
- package/orm/server/repository-config.js +8 -0
- package/orm/server/repository.d.ts +117 -43
- package/orm/server/repository.js +757 -253
- package/orm/server/transaction.d.ts +4 -2
- package/orm/server/transaction.js +14 -5
- package/orm/server/transactional.d.ts +6 -2
- package/orm/server/transactional.js +39 -9
- package/orm/server/types.d.ts +2 -0
- package/orm/sqls/case-when.d.ts +3 -3
- package/orm/sqls/case-when.js +2 -2
- package/orm/sqls/sqls.d.ts +31 -5
- package/orm/sqls/sqls.js +69 -6
- package/orm/tests/data-types.test.d.ts +1 -0
- package/orm/tests/data-types.test.js +39 -0
- package/orm/tests/decorators.test.d.ts +1 -0
- package/orm/tests/decorators.test.js +77 -0
- package/orm/tests/encryption.test.d.ts +1 -0
- package/orm/tests/encryption.test.js +34 -0
- package/orm/tests/query-complex.test.d.ts +1 -0
- package/orm/tests/query-complex.test.js +203 -0
- package/orm/tests/query-converter-complex.test.d.ts +1 -0
- package/orm/tests/query-converter-complex.test.js +126 -0
- package/orm/tests/query-converter.test.d.ts +1 -0
- package/orm/tests/query-converter.test.js +123 -0
- package/orm/tests/repository-advanced.test.d.ts +1 -0
- package/orm/tests/repository-advanced.test.js +232 -0
- package/orm/tests/repository-attributes.test.d.ts +1 -0
- package/orm/tests/repository-attributes.test.js +99 -0
- package/orm/tests/repository-comprehensive.test.d.ts +1 -0
- package/orm/tests/repository-comprehensive.test.js +187 -0
- package/orm/tests/repository-coverage.test.d.ts +1 -0
- package/orm/tests/repository-coverage.test.js +303 -0
- package/orm/tests/repository-cti-complex.test.d.ts +1 -0
- package/orm/tests/repository-cti-complex.test.js +170 -0
- package/orm/tests/repository-cti-embedded.test.d.ts +1 -0
- package/orm/tests/repository-cti-embedded.test.js +188 -0
- package/orm/tests/repository-cti-extensive.test.d.ts +1 -0
- package/orm/tests/repository-cti-extensive.test.js +308 -0
- package/orm/tests/repository-cti-mapping.test.d.ts +1 -0
- package/orm/tests/repository-cti-mapping.test.js +121 -0
- package/orm/tests/repository-cti-search.test.d.ts +1 -0
- package/orm/tests/repository-cti-search.test.js +152 -0
- package/orm/tests/repository-cti-soft-delete.test.d.ts +1 -0
- package/orm/tests/repository-cti-soft-delete.test.js +115 -0
- package/orm/tests/repository-cti-transactions.test.d.ts +1 -0
- package/orm/tests/repository-cti-transactions.test.js +126 -0
- package/orm/tests/repository-cti-upsert-many.test.d.ts +1 -0
- package/orm/tests/repository-cti-upsert-many.test.js +127 -0
- package/orm/tests/repository-cti.test.d.ts +1 -0
- package/orm/tests/repository-cti.test.js +456 -0
- package/orm/tests/repository-edge-cases.test.d.ts +1 -0
- package/orm/tests/repository-edge-cases.test.js +216 -0
- package/orm/tests/repository-expiration.test.d.ts +1 -0
- package/orm/tests/repository-expiration.test.js +153 -0
- package/orm/tests/repository-extra-coverage.test.d.ts +1 -0
- package/orm/tests/repository-extra-coverage.test.js +546 -0
- package/orm/tests/repository-mapping.test.d.ts +1 -0
- package/orm/tests/repository-mapping.test.js +71 -0
- package/orm/tests/repository-regression.test.d.ts +1 -0
- package/orm/tests/repository-regression.test.js +330 -0
- package/orm/tests/repository-search-coverage.test.d.ts +1 -0
- package/orm/tests/repository-search-coverage.test.js +129 -0
- package/orm/tests/repository-search.test.d.ts +1 -0
- package/orm/tests/repository-search.test.js +116 -0
- package/orm/tests/repository-soft-delete.test.d.ts +1 -0
- package/orm/tests/repository-soft-delete.test.js +143 -0
- package/orm/tests/repository-transactions-nested.test.d.ts +1 -0
- package/orm/tests/repository-transactions-nested.test.js +202 -0
- package/orm/tests/repository-types.test.d.ts +1 -0
- package/orm/tests/repository-types.test.js +218 -0
- package/orm/tests/schema-converter.test.d.ts +1 -0
- package/orm/tests/schema-converter.test.js +81 -0
- package/orm/tests/schema-generation.test.d.ts +1 -0
- package/orm/tests/schema-generation.test.js +127 -0
- package/orm/tests/sql-helpers.test.d.ts +1 -0
- package/orm/tests/sql-helpers.test.js +67 -0
- package/orm/tests/transaction-safety.test.d.ts +1 -0
- package/orm/tests/transaction-safety.test.js +81 -0
- package/orm/tests/transactional.test.d.ts +1 -0
- package/orm/tests/transactional.test.js +224 -0
- package/orm/tests/utils.test.d.ts +1 -0
- package/orm/tests/utils.test.js +70 -0
- package/orm/utils.d.ts +7 -0
- package/orm/utils.js +26 -6
- package/package.json +12 -7
- package/pool/pool.js +1 -1
- package/rate-limit/index.d.ts +2 -0
- package/rate-limit/index.js +2 -0
- package/rate-limit/postgres/drizzle/0000_watery_rage.sql +7 -0
- package/{queue → rate-limit}/postgres/drizzle/meta/0000_snapshot.json +14 -39
- package/rate-limit/postgres/drizzle/meta/_journal.json +13 -0
- package/{queue → rate-limit}/postgres/drizzle.config.js +1 -1
- package/rate-limit/postgres/index.d.ts +4 -0
- package/rate-limit/postgres/index.js +4 -0
- package/rate-limit/postgres/module.d.ts +12 -0
- package/rate-limit/postgres/module.js +28 -0
- package/rate-limit/postgres/postgres-rate-limiter.d.ts +9 -0
- package/rate-limit/postgres/postgres-rate-limiter.js +56 -0
- package/rate-limit/postgres/rate-limit.model.d.ts +8 -0
- package/rate-limit/postgres/rate-limit.model.js +35 -0
- package/rate-limit/postgres/rate-limiter.provider.d.ts +6 -0
- package/rate-limit/postgres/rate-limiter.provider.js +21 -0
- package/rate-limit/postgres/schemas.d.ts +3 -0
- package/rate-limit/postgres/schemas.js +4 -0
- package/rate-limit/provider.d.ts +9 -0
- package/rate-limit/provider.js +2 -0
- package/rate-limit/rate-limiter.d.ts +35 -0
- package/rate-limit/rate-limiter.js +3 -0
- package/rate-limit/tests/postgres-rate-limiter.test.d.ts +1 -0
- package/rate-limit/tests/postgres-rate-limiter.test.js +92 -0
- package/signals/implementation/configure.d.ts +3 -0
- package/signals/implementation/configure.js +3 -0
- package/sse/data-stream-source.d.ts +1 -1
- package/sse/data-stream-source.js +6 -6
- package/task-queue/enqueue-batch.d.ts +17 -0
- package/task-queue/enqueue-batch.js +24 -0
- package/{queue → task-queue}/index.d.ts +1 -1
- package/{queue → task-queue}/index.js +1 -1
- package/task-queue/postgres/drizzle/0000_thin_black_panther.sql +74 -0
- package/task-queue/postgres/drizzle/meta/0000_snapshot.json +592 -0
- package/task-queue/postgres/drizzle/meta/_journal.json +13 -0
- package/task-queue/postgres/drizzle.config.d.ts +2 -0
- package/task-queue/postgres/drizzle.config.js +11 -0
- package/task-queue/postgres/index.d.ts +4 -0
- package/task-queue/postgres/index.js +4 -0
- package/task-queue/postgres/module.d.ts +12 -0
- package/task-queue/postgres/module.js +28 -0
- package/task-queue/postgres/schemas.d.ts +16 -0
- package/task-queue/postgres/schemas.js +8 -0
- package/task-queue/postgres/task-queue.d.ts +83 -0
- package/task-queue/postgres/task-queue.js +1054 -0
- package/task-queue/postgres/task-queue.provider.d.ts +7 -0
- package/{queue/postgres/queue.provider.js → task-queue/postgres/task-queue.provider.js} +8 -8
- package/task-queue/postgres/task.model.d.ts +39 -0
- package/task-queue/postgres/task.model.js +178 -0
- package/{queue → task-queue}/provider.d.ts +3 -3
- package/task-queue/provider.js +2 -0
- package/{queue → task-queue}/task-context.d.ts +7 -7
- package/{queue → task-queue}/task-context.js +8 -8
- package/{queue/queue.d.ts → task-queue/task-queue.d.ts} +128 -59
- package/task-queue/task-queue.js +200 -0
- package/task-queue/tests/complex.test.d.ts +1 -0
- package/task-queue/tests/complex.test.js +299 -0
- package/task-queue/tests/dependencies.test.d.ts +1 -0
- package/task-queue/tests/dependencies.test.js +174 -0
- package/task-queue/tests/queue.test.d.ts +1 -0
- package/task-queue/tests/queue.test.js +334 -0
- package/task-queue/tests/worker.test.d.ts +1 -0
- package/task-queue/tests/worker.test.js +163 -0
- package/test1.js +1 -1
- package/test4.js +2 -2
- package/unit-test/index.d.ts +1 -0
- package/unit-test/index.js +1 -0
- package/unit-test/integration-setup.d.ts +55 -0
- package/unit-test/integration-setup.js +182 -0
- package/utils/patterns.d.ts +3 -0
- package/utils/patterns.js +6 -1
- package/audit/drizzle/0001_previous_network.sql +0 -2
- package/audit/drizzle/meta/0001_snapshot.json +0 -195
- package/queue/enqueue-batch.d.ts +0 -17
- package/queue/enqueue-batch.js +0 -18
- package/queue/postgres/drizzle/0000_zippy_moondragon.sql +0 -11
- package/queue/postgres/drizzle/0001_certain_wild_pack.sql +0 -2
- package/queue/postgres/drizzle/0002_dear_meggan.sql +0 -2
- package/queue/postgres/drizzle/0003_tricky_venom.sql +0 -30
- package/queue/postgres/drizzle/meta/0001_snapshot.json +0 -103
- package/queue/postgres/drizzle/meta/0002_snapshot.json +0 -90
- package/queue/postgres/drizzle/meta/0003_snapshot.json +0 -288
- package/queue/postgres/drizzle/meta/_journal.json +0 -34
- package/queue/postgres/index.d.ts +0 -4
- package/queue/postgres/index.js +0 -4
- package/queue/postgres/module.d.ts +0 -9
- package/queue/postgres/module.js +0 -29
- package/queue/postgres/queue.d.ts +0 -60
- package/queue/postgres/queue.js +0 -681
- package/queue/postgres/queue.provider.d.ts +0 -7
- package/queue/postgres/schemas.d.ts +0 -14
- package/queue/postgres/schemas.js +0 -6
- package/queue/postgres/task.model.d.ts +0 -24
- package/queue/postgres/task.model.js +0 -115
- package/queue/provider.js +0 -2
- package/queue/queue.js +0 -131
- package/queue/tests/queue.test.js +0 -623
- package/test3.d.ts +0 -1
- package/test3.js +0 -47
- /package/{queue/tests/queue.test.d.ts → api/server/tests/csrf.middleware.test.d.ts} +0 -0
- /package/circuit-breaker/postgres/drizzle/{0000_hard_shocker.sql → 0000_cooing_korath.sql} +0 -0
- /package/{queue → rate-limit}/postgres/drizzle.config.d.ts +0 -0
|
@@ -0,0 +1,186 @@
|
|
|
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 { runInInjectionContext } from '../../injector/index.js';
|
|
5
|
+
import { clearTenantData, setupIntegrationTest } from '../../unit-test/index.js';
|
|
6
|
+
import { AuthenticationAncillaryService } from '../server/authentication-ancillary.service.js';
|
|
7
|
+
import { AuthenticationService } from '../server/authentication.service.js';
|
|
8
|
+
import { SubjectService } from '../server/subject.service.js';
|
|
9
|
+
import { DefaultAuthenticationAncillaryService } from './authentication.test-ancillary-service.js';
|
|
10
|
+
describe('AuthenticationService', () => {
|
|
11
|
+
let injector;
|
|
12
|
+
let database;
|
|
13
|
+
let authenticationService;
|
|
14
|
+
let subjectService;
|
|
15
|
+
let auditor;
|
|
16
|
+
let ancillaryService;
|
|
17
|
+
const schema = 'authentication';
|
|
18
|
+
const tenantId = crypto.randomUUID();
|
|
19
|
+
beforeAll(async () => {
|
|
20
|
+
({ injector, database } = await setupIntegrationTest({
|
|
21
|
+
modules: { authentication: true },
|
|
22
|
+
authenticationAncillaryService: DefaultAuthenticationAncillaryService,
|
|
23
|
+
}));
|
|
24
|
+
authenticationService = await injector.resolveAsync(AuthenticationService);
|
|
25
|
+
subjectService = await injector.resolveAsync(SubjectService);
|
|
26
|
+
auditor = injector.resolve(Auditor);
|
|
27
|
+
ancillaryService = await injector.resolveAsync(AuthenticationAncillaryService);
|
|
28
|
+
});
|
|
29
|
+
afterAll(async () => {
|
|
30
|
+
await injector?.dispose();
|
|
31
|
+
});
|
|
32
|
+
beforeEach(async () => {
|
|
33
|
+
await clearTenantData(database, schema, ['credentials', 'session', 'user', 'service_account', 'system_account', 'subject'], tenantId);
|
|
34
|
+
});
|
|
35
|
+
test('login should create a session and listSessions should return it', async () => {
|
|
36
|
+
await runInInjectionContext(injector, async () => {
|
|
37
|
+
const user = await subjectService.createUser({
|
|
38
|
+
tenantId,
|
|
39
|
+
email: 'test@example.com',
|
|
40
|
+
firstName: 'John',
|
|
41
|
+
lastName: 'Doe',
|
|
42
|
+
});
|
|
43
|
+
await authenticationService.setCredentials(user, 'Strong-Password-2026!');
|
|
44
|
+
const tokenResult = await authenticationService.login({ tenantId, subject: user.id }, 'Strong-Password-2026!', undefined, auditor.with({ actor: user.id, actorType: ActorType.Subject }));
|
|
45
|
+
expect(tokenResult.token).toBeDefined();
|
|
46
|
+
const sessions = await authenticationService.listSessions(tenantId, user.id);
|
|
47
|
+
expect(sessions).toHaveLength(1);
|
|
48
|
+
expect(sessions[0]?.id).toBe(tokenResult.jsonToken.payload.session);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
test('invalidateAllSessions should end all active sessions', async () => {
|
|
52
|
+
await runInInjectionContext(injector, async () => {
|
|
53
|
+
const user = await subjectService.createUser({
|
|
54
|
+
tenantId,
|
|
55
|
+
email: 'test@example.com',
|
|
56
|
+
firstName: 'John',
|
|
57
|
+
lastName: 'Doe',
|
|
58
|
+
});
|
|
59
|
+
await authenticationService.setCredentials(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.every((s) => s.end <= now + 1000)).toBe(true); // small buffer for test execution time
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
test('getSession and tryGetSession', async () => {
|
|
73
|
+
await runInInjectionContext(injector, async () => {
|
|
74
|
+
const user = await subjectService.createUser({
|
|
75
|
+
tenantId,
|
|
76
|
+
email: 'test@example.com',
|
|
77
|
+
firstName: 'John',
|
|
78
|
+
lastName: 'Doe',
|
|
79
|
+
});
|
|
80
|
+
await authenticationService.setCredentials(user, 'Strong-Password-2026!');
|
|
81
|
+
const userAuditor = auditor.with({ actor: user.id, actorType: ActorType.Subject });
|
|
82
|
+
const tokenResult = await authenticationService.login({ tenantId, subject: user.id }, 'Strong-Password-2026!', undefined, userAuditor);
|
|
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
|
+
});
|
|
92
|
+
test('refresh should issue new token and session', async () => {
|
|
93
|
+
await runInInjectionContext(injector, async () => {
|
|
94
|
+
const user = await subjectService.createUser({ tenantId, email: 'refresh@example.com', firstName: 'R', lastName: 'F' });
|
|
95
|
+
await authenticationService.setCredentials(user, 'Strong-Password-2026!');
|
|
96
|
+
const userAuditor = auditor.with({ actor: user.id, actorType: ActorType.Subject });
|
|
97
|
+
const loginResult = await authenticationService.login({ tenantId, subject: user.id }, 'Strong-Password-2026!', undefined, userAuditor);
|
|
98
|
+
const refreshResult = await authenticationService.refresh(loginResult.refreshToken, undefined, {}, userAuditor);
|
|
99
|
+
expect(refreshResult.token).toBeDefined();
|
|
100
|
+
expect(refreshResult.refreshToken).not.toBe(loginResult.refreshToken);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
test('changeSecret should update credentials', async () => {
|
|
104
|
+
await runInInjectionContext(injector, async () => {
|
|
105
|
+
const user = await subjectService.createUser({ tenantId, email: 'change@example.com', firstName: 'C', lastName: 'S' });
|
|
106
|
+
await authenticationService.setCredentials(user, 'Old-Password-2026!');
|
|
107
|
+
const userAuditor = auditor.with({ actor: user.id, actorType: ActorType.Subject });
|
|
108
|
+
await authenticationService.changeSecret({ tenantId, subject: user.id }, 'Old-Password-2026!', 'New-Password-2026!', userAuditor);
|
|
109
|
+
const authResult = await authenticationService.authenticate({ tenantId, subject: user.id }, 'New-Password-2026!');
|
|
110
|
+
expect(authResult.success).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
test('checkSecret, testSecret, and validateSecret', async () => {
|
|
114
|
+
await runInInjectionContext(injector, async () => {
|
|
115
|
+
const weak = 'abc';
|
|
116
|
+
const strong = 'Very-Strong-Password-2026-!@#$';
|
|
117
|
+
expect((await authenticationService.checkSecret(weak)).strength).toBeLessThan(2);
|
|
118
|
+
expect((await authenticationService.checkSecret(strong)).strength).toBeGreaterThanOrEqual(2);
|
|
119
|
+
expect((await authenticationService.testSecret(weak)).success).toBe(false);
|
|
120
|
+
expect((await authenticationService.testSecret(strong)).success).toBe(true);
|
|
121
|
+
await expect(authenticationService.validateSecret(weak)).rejects.toThrow();
|
|
122
|
+
await expect(authenticationService.validateSecret(strong)).resolves.not.toThrow();
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
test('tryResolveSubject and resolveSubject', async () => {
|
|
126
|
+
await runInInjectionContext(injector, async () => {
|
|
127
|
+
const user = await subjectService.createUser({ tenantId, email: 'resolve@example.com', firstName: 'R', lastName: 'S' });
|
|
128
|
+
const resolved = await authenticationService.tryResolveSubject({ tenantId, subject: user.id });
|
|
129
|
+
expect(resolved?.id).toBe(user.id);
|
|
130
|
+
const resolvedByEmail = await authenticationService.resolveSubject({ tenantId, subject: 'resolve@example.com' });
|
|
131
|
+
expect(resolvedByEmail.id).toBe(user.id);
|
|
132
|
+
await expect(authenticationService.resolveSubject({ tenantId, subject: 'missing' })).rejects.toThrow();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
test('impersonation and unimpersonation', async () => {
|
|
136
|
+
await runInInjectionContext(injector, async () => {
|
|
137
|
+
const admin = await subjectService.createUser({ tenantId, email: 'admin@example.com', firstName: 'A', lastName: 'D' });
|
|
138
|
+
const user = await subjectService.createUser({ tenantId, email: 'user@example.com', firstName: 'U', lastName: 'S' });
|
|
139
|
+
const adminAuditor = auditor.with({ actor: admin.id, actorType: ActorType.Subject });
|
|
140
|
+
const adminToken = await authenticationService.getToken(admin, undefined);
|
|
141
|
+
const impersonated = await authenticationService.impersonate(adminToken.token, adminToken.refreshToken, user.id, undefined, adminAuditor);
|
|
142
|
+
expect(impersonated.jsonToken.payload.subject).toBe(user.id);
|
|
143
|
+
expect(impersonated.jsonToken.payload.impersonator).toBe(admin.id);
|
|
144
|
+
const unimpersonated = await authenticationService.unimpersonate(impersonated.impersonatorRefreshToken, undefined, adminAuditor);
|
|
145
|
+
expect(unimpersonated.jsonToken.payload.subject).toBe(admin.id);
|
|
146
|
+
expect(unimpersonated.jsonToken.payload.impersonator).toBeUndefined();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
test('refresh should throw on invalid token', async () => {
|
|
150
|
+
await runInInjectionContext(injector, async () => {
|
|
151
|
+
await expect(authenticationService.refresh('invalid', undefined, {}, auditor)).rejects.toThrow();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
test('login should throw on invalid credentials', async () => {
|
|
155
|
+
await runInInjectionContext(injector, async () => {
|
|
156
|
+
const user = await subjectService.createUser({ tenantId, email: 'fail@example.com', firstName: 'F', lastName: 'L' });
|
|
157
|
+
await authenticationService.setCredentials(user, 'Very-Strong-Password-2026!');
|
|
158
|
+
await expect(authenticationService.login({ tenantId, subject: user.id }, 'wrong', undefined, auditor)).rejects.toThrow();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
test('endSession should handle non-existent session gracefully', async () => {
|
|
162
|
+
await runInInjectionContext(injector, async () => {
|
|
163
|
+
await expect(authenticationService.endSession(NIL_UUID, auditor)).resolves.not.toThrow();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
test('resolveSubject should throw if not found', async () => {
|
|
167
|
+
await runInInjectionContext(injector, async () => {
|
|
168
|
+
await expect(authenticationService.resolveSubject({ tenantId, subject: 'missing' })).rejects.toThrow();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
test('secret reset flow', async () => {
|
|
172
|
+
await runInInjectionContext(injector, async () => {
|
|
173
|
+
const user = await subjectService.createUser({ tenantId, email: 'reset@example.com', firstName: 'R', lastName: 'E' });
|
|
174
|
+
const userAuditor = auditor.with({ actor: user.id, actorType: ActorType.Subject });
|
|
175
|
+
await authenticationService.initSecretReset({ tenantId, subject: user.id }, undefined, userAuditor);
|
|
176
|
+
expect(ancillaryService.lastResetData).toBeDefined();
|
|
177
|
+
await authenticationService.resetSecret(ancillaryService.lastResetData.token, 'New-Password-Reset-2026!', userAuditor);
|
|
178
|
+
const authResult = await authenticationService.authenticate({ tenantId, subject: user.id }, 'New-Password-Reset-2026!');
|
|
179
|
+
expect(authResult.success).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
test('deriveSigningSecrets should work', async () => {
|
|
183
|
+
// This is mostly covered by initialize, but we can test it implicitly by ensuring service works after init
|
|
184
|
+
expect(authenticationService).toBeDefined();
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
handleInitSecretReset(data: any): Promise<void>;
|
|
7
|
+
canImpersonate(_token: any, _subject: any, _data: any): Promise<boolean>;
|
|
8
|
+
resolveSubjects(data: any): Promise<import("../index.js").Subject[]>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
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 handleInitSecretReset(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 };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { BadRequestError } from '../../errors/bad-request.error.js';
|
|
3
|
+
import { InvalidTokenError } from '../../errors/invalid-token.error.js';
|
|
4
|
+
import { currentTimestampSeconds } from '../../utils/date-time.js';
|
|
5
|
+
import { createJwtTokenString } from '../../utils/jwt.js';
|
|
6
|
+
import { getRefreshTokenFromString, getSecretResetTokenFromString, getTokenFromRequest, getTokenFromString, tryGetAuthorizationTokenStringFromRequest, tryGetTokenFromRequest } from '../server/helper.js';
|
|
7
|
+
describe('authentication helper', () => {
|
|
8
|
+
const secret = 'test-secret';
|
|
9
|
+
test('tryGetAuthorizationTokenStringFromRequest should extract bearer token from header', () => {
|
|
10
|
+
const request = {
|
|
11
|
+
headers: {
|
|
12
|
+
tryGet: (name) => name == 'Authorization' ? 'Bearer my-token' : undefined,
|
|
13
|
+
},
|
|
14
|
+
cookies: {
|
|
15
|
+
tryGet: () => undefined,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
expect(tryGetAuthorizationTokenStringFromRequest(request)).toBe('my-token');
|
|
19
|
+
});
|
|
20
|
+
test('tryGetAuthorizationTokenStringFromRequest should extract bearer token from cookie', () => {
|
|
21
|
+
const request = {
|
|
22
|
+
headers: {
|
|
23
|
+
tryGet: () => undefined,
|
|
24
|
+
},
|
|
25
|
+
cookies: {
|
|
26
|
+
tryGet: (name) => name == 'authorization' ? 'Bearer my-token' : undefined,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
expect(tryGetAuthorizationTokenStringFromRequest(request)).toBe('my-token');
|
|
30
|
+
});
|
|
31
|
+
test('tryGetAuthorizationTokenStringFromRequest should throw on unsupported scheme', () => {
|
|
32
|
+
const request = {
|
|
33
|
+
headers: {
|
|
34
|
+
tryGet: (name) => name == 'Authorization' ? 'Basic my-token' : undefined,
|
|
35
|
+
},
|
|
36
|
+
cookies: {
|
|
37
|
+
tryGet: () => undefined,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
expect(() => tryGetAuthorizationTokenStringFromRequest(request)).toThrow(BadRequestError);
|
|
41
|
+
});
|
|
42
|
+
test('tryGetAuthorizationTokenStringFromRequest should return undefined if no token found', () => {
|
|
43
|
+
const request = {
|
|
44
|
+
headers: {
|
|
45
|
+
tryGet: () => undefined,
|
|
46
|
+
},
|
|
47
|
+
cookies: {
|
|
48
|
+
tryGet: () => undefined,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
expect(tryGetAuthorizationTokenStringFromRequest(request)).toBeUndefined();
|
|
52
|
+
});
|
|
53
|
+
test('getTokenFromString should validate token', async () => {
|
|
54
|
+
const payload = { exp: currentTimestampSeconds() + 60, tenant: 't', subject: 's', jti: 'j' };
|
|
55
|
+
const token = await createJwtTokenString({ header: { v: 1, alg: 'HS256', typ: 'JWT' }, payload }, secret);
|
|
56
|
+
const validated = await getTokenFromString(token, 1, secret);
|
|
57
|
+
expect(validated.payload).toEqual(payload);
|
|
58
|
+
});
|
|
59
|
+
test('getTokenFromString should throw on version mismatch', async () => {
|
|
60
|
+
const payload = { exp: currentTimestampSeconds() + 60, tenant: 't', subject: 's', jti: 'j' };
|
|
61
|
+
const token = await createJwtTokenString({ header: { v: 2, alg: 'HS256', typ: 'JWT' }, payload }, secret);
|
|
62
|
+
await expect(getTokenFromString(token, 1, secret)).rejects.toThrow(InvalidTokenError);
|
|
63
|
+
});
|
|
64
|
+
test('getTokenFromString should throw on expired token', async () => {
|
|
65
|
+
const payload = { exp: currentTimestampSeconds() - 60, tenant: 't', subject: 's', jti: 'j' };
|
|
66
|
+
const token = await createJwtTokenString({ header: { v: 1, alg: 'HS256', typ: 'JWT' }, payload }, secret);
|
|
67
|
+
await expect(getTokenFromString(token, 1, secret)).rejects.toThrow('Token expired');
|
|
68
|
+
});
|
|
69
|
+
test('getRefreshTokenFromString should validate refresh token', async () => {
|
|
70
|
+
const payload = { exp: currentTimestampSeconds() + 60, tenant: 't', subject: 's', session: 'sess', secret: 'sec' };
|
|
71
|
+
const token = await createJwtTokenString({ header: { alg: 'HS256', typ: 'JWT' }, payload }, secret);
|
|
72
|
+
const validated = await getRefreshTokenFromString(token, secret);
|
|
73
|
+
expect(validated.payload).toEqual(payload);
|
|
74
|
+
});
|
|
75
|
+
test('getSecretResetTokenFromString should validate secret reset token', async () => {
|
|
76
|
+
const payload = { iat: currentTimestampSeconds(), exp: currentTimestampSeconds() + 60, tenant: 't', subject: 's' };
|
|
77
|
+
const token = await createJwtTokenString({ header: { alg: 'HS256', typ: 'JWT' }, payload }, secret);
|
|
78
|
+
const validated = await getSecretResetTokenFromString(token, secret);
|
|
79
|
+
expect(validated.payload).toEqual(payload);
|
|
80
|
+
});
|
|
81
|
+
test('getTokenFromRequest should extract and validate token', async () => {
|
|
82
|
+
const payload = { exp: currentTimestampSeconds() + 60, tenant: 't', subject: 's', jti: 'j' };
|
|
83
|
+
const token = await createJwtTokenString({ header: { v: 1, alg: 'HS256', typ: 'JWT' }, payload }, secret);
|
|
84
|
+
const request = {
|
|
85
|
+
headers: {
|
|
86
|
+
tryGet: (name) => name == 'Authorization' ? `Bearer ${token}` : undefined,
|
|
87
|
+
},
|
|
88
|
+
cookies: {
|
|
89
|
+
tryGet: () => undefined,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
const validated = await getTokenFromRequest(request, 1, secret);
|
|
93
|
+
expect(validated.payload).toEqual(payload);
|
|
94
|
+
});
|
|
95
|
+
test('tryGetTokenFromRequest should return undefined if no token in request', async () => {
|
|
96
|
+
const request = {
|
|
97
|
+
headers: {
|
|
98
|
+
tryGet: () => undefined,
|
|
99
|
+
},
|
|
100
|
+
cookies: {
|
|
101
|
+
tryGet: () => undefined,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
const validated = await tryGetTokenFromRequest(request, 1, secret);
|
|
105
|
+
expect(validated).toBeUndefined();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { SecretRequirementsError } from '../errors/secret-requirements.error.js';
|
|
3
|
+
describe('SecretRequirementsError', () => {
|
|
4
|
+
it('should create an error with the given message', () => {
|
|
5
|
+
const message = 'Password is too weak.';
|
|
6
|
+
const error = new SecretRequirementsError(message);
|
|
7
|
+
expect(error.message).toBe(message);
|
|
8
|
+
expect(error.name).toBe('SecretRequirementsError');
|
|
9
|
+
});
|
|
10
|
+
it('should have the correct name', () => {
|
|
11
|
+
const error = new SecretRequirementsError('any message');
|
|
12
|
+
expect(error.name).toBe('SecretRequirementsError');
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,140 @@
|
|
|
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 '../../unit-test/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.Inactive });
|
|
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.Inactive);
|
|
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,6 +1,12 @@
|
|
|
1
|
+
import { Injector } from '../../injector/index.js';
|
|
1
2
|
import { type DatabaseConfig } from '../../orm/server/index.js';
|
|
2
3
|
export declare class PostgresCircuitBreakerModuleConfig {
|
|
3
4
|
database?: DatabaseConfig;
|
|
4
5
|
}
|
|
5
|
-
|
|
6
|
+
/**
|
|
7
|
+
* configure circuit breaker module
|
|
8
|
+
*/
|
|
9
|
+
export declare function configurePostgresCircuitBreaker({ injector, ...config }?: PostgresCircuitBreakerModuleConfig & {
|
|
10
|
+
injector?: Injector;
|
|
11
|
+
}): void;
|
|
6
12
|
export declare function migratePostgresCircuitBreaker(): Promise<void>;
|
|
@@ -7,12 +7,14 @@ import { PostgresCircuitBreakerProvider } from './provider.js';
|
|
|
7
7
|
export class PostgresCircuitBreakerModuleConfig {
|
|
8
8
|
database;
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
10
|
+
/**
|
|
11
|
+
* configure circuit breaker module
|
|
12
|
+
*/
|
|
13
|
+
export function configurePostgresCircuitBreaker({ injector, ...config } = {}) {
|
|
14
|
+
const targetInjector = injector ?? Injector;
|
|
15
|
+
targetInjector.register(PostgresCircuitBreakerModuleConfig, { useValue: config });
|
|
16
|
+
targetInjector.registerSingleton(CircuitBreakerProvider, { useToken: PostgresCircuitBreakerProvider });
|
|
17
|
+
targetInjector.registerSingleton(CircuitBreaker, { useToken: PostgresCircuitBreakerService });
|
|
16
18
|
}
|
|
17
19
|
export async function migratePostgresCircuitBreaker() {
|
|
18
20
|
const connection = inject(PostgresCircuitBreakerModuleConfig, undefined, { optional: true })?.database?.connection;
|
|
@@ -1,33 +1,13 @@
|
|
|
1
1
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
2
2
|
import { CircuitBreakerState } from '../../circuit-breaker/index.js';
|
|
3
3
|
import { CircuitBreakerProvider } from '../../circuit-breaker/provider.js';
|
|
4
|
-
import {
|
|
5
|
-
import { configureOrm } from '../../orm/server/index.js';
|
|
6
|
-
import * as configParser from '../../utils/config-parser.js';
|
|
4
|
+
import { setupIntegrationTest } from '../../unit-test/index.js';
|
|
7
5
|
import { timeout } from '../../utils/timing.js';
|
|
8
|
-
import { configurePostgresCircuitBreaker, migratePostgresCircuitBreaker } from '../postgres/module.js';
|
|
9
|
-
async function setupIntegrationTest() {
|
|
10
|
-
const injector = new Injector('TestInjector');
|
|
11
|
-
const dbConfig = {
|
|
12
|
-
host: configParser.string('DATABASE_HOST', '127.0.0.1'),
|
|
13
|
-
port: configParser.positiveInteger('DATABASE_PORT', 5432),
|
|
14
|
-
user: configParser.string('DATABASE_USER', 'tstdl'),
|
|
15
|
-
password: configParser.string('DATABASE_PASS', 'wf7rq6glrk5jykne'),
|
|
16
|
-
database: configParser.string('DATABASE_NAME', 'tstdl'),
|
|
17
|
-
};
|
|
18
|
-
configureOrm({
|
|
19
|
-
repositoryConfig: { schema: 'test' },
|
|
20
|
-
connection: dbConfig,
|
|
21
|
-
});
|
|
22
|
-
configurePostgresCircuitBreaker({ database: { connection: dbConfig } });
|
|
23
|
-
await runInInjectionContext(injector, migratePostgresCircuitBreaker);
|
|
24
|
-
return injector;
|
|
25
|
-
}
|
|
26
6
|
describe('Circuit Breaker (Standalone) Tests', () => {
|
|
27
7
|
let injector;
|
|
28
8
|
let provider;
|
|
29
9
|
beforeAll(async () => {
|
|
30
|
-
injector = await setupIntegrationTest();
|
|
10
|
+
({ injector } = (await setupIntegrationTest({ modules: { circuitBreaker: true } })));
|
|
31
11
|
provider = injector.resolve(CircuitBreakerProvider);
|
|
32
12
|
});
|
|
33
13
|
afterAll(async () => {
|
|
@@ -30,12 +30,8 @@ export const documentManagementApiDefinition = defineApi({
|
|
|
30
30
|
parameters: loadDataParametersSchema,
|
|
31
31
|
result: (DataStream),
|
|
32
32
|
credentials: true,
|
|
33
|
-
data: {
|
|
34
|
-
|
|
35
|
-
},
|
|
36
|
-
cors: {
|
|
37
|
-
accessControlAllowHeaders: `${defaultAccessControlAllowHeaders}, Cache-Control`,
|
|
38
|
-
},
|
|
33
|
+
data: { [bustCache]: true },
|
|
34
|
+
cors: { accessControlAllowHeaders: `${defaultAccessControlAllowHeaders}, Cache-Control` },
|
|
39
35
|
},
|
|
40
36
|
loadDocumentRequestsTemplateData: {
|
|
41
37
|
resource: 'views/document-requests-template-data',
|