@contember/engine-tenant-api 2.2.0-alpha.7 → 2.2.0-alpha.8
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/dist/development/src/TenantContainer.cjs +5 -2
- package/dist/development/src/TenantContainer.cjs.map +1 -1
- package/dist/development/src/TenantContainer.js +5 -2
- package/dist/development/src/TenantContainer.js.map +1 -1
- package/dist/development/src/model/commands/apiKey/ProlongApiKeyCommand.cjs +5 -8
- package/dist/development/src/model/commands/apiKey/ProlongApiKeyCommand.cjs.map +1 -1
- package/dist/development/src/model/commands/apiKey/ProlongApiKeyCommand.js +5 -8
- package/dist/development/src/model/commands/apiKey/ProlongApiKeyCommand.js.map +1 -1
- package/dist/development/src/model/commands/authLog/CreateAuthLogEntryCommand.cjs.map +1 -1
- package/dist/development/src/model/commands/authLog/CreateAuthLogEntryCommand.js.map +1 -1
- package/dist/development/src/model/service/IDPSignInManager.cjs +23 -15
- package/dist/development/src/model/service/IDPSignInManager.cjs.map +1 -1
- package/dist/development/src/model/service/IDPSignInManager.js +23 -15
- package/dist/development/src/model/service/IDPSignInManager.js.map +1 -1
- package/dist/development/src/model/service/idp/IdpSessionRevalidator.cjs +42 -31
- package/dist/development/src/model/service/idp/IdpSessionRevalidator.cjs.map +1 -1
- package/dist/development/src/model/service/idp/IdpSessionRevalidator.js +42 -31
- package/dist/development/src/model/service/idp/IdpSessionRevalidator.js.map +1 -1
- package/dist/production/src/TenantContainer.cjs +5 -2
- package/dist/production/src/TenantContainer.cjs.map +1 -1
- package/dist/production/src/TenantContainer.js +5 -2
- package/dist/production/src/TenantContainer.js.map +1 -1
- package/dist/production/src/model/commands/apiKey/ProlongApiKeyCommand.cjs +5 -8
- package/dist/production/src/model/commands/apiKey/ProlongApiKeyCommand.cjs.map +1 -1
- package/dist/production/src/model/commands/apiKey/ProlongApiKeyCommand.js +5 -8
- package/dist/production/src/model/commands/apiKey/ProlongApiKeyCommand.js.map +1 -1
- package/dist/production/src/model/commands/authLog/CreateAuthLogEntryCommand.cjs.map +1 -1
- package/dist/production/src/model/commands/authLog/CreateAuthLogEntryCommand.js.map +1 -1
- package/dist/production/src/model/service/IDPSignInManager.cjs +23 -15
- package/dist/production/src/model/service/IDPSignInManager.cjs.map +1 -1
- package/dist/production/src/model/service/IDPSignInManager.js +23 -15
- package/dist/production/src/model/service/IDPSignInManager.js.map +1 -1
- package/dist/production/src/model/service/idp/IdpSessionRevalidator.cjs +42 -31
- package/dist/production/src/model/service/idp/IdpSessionRevalidator.cjs.map +1 -1
- package/dist/production/src/model/service/idp/IdpSessionRevalidator.js +42 -31
- package/dist/production/src/model/service/idp/IdpSessionRevalidator.js.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/sqlTransaction.d.ts +5 -0
- package/dist/tests/cases/integration/mocked/sql/sqlTransaction.d.ts.map +1 -1
- package/dist/tests/src/IdPMock.d.ts.map +1 -1
- package/dist/tests/src/testTenant.d.ts.map +1 -1
- package/dist/tests/tsconfig.tsbuildinfo +1 -1
- package/dist/types/TenantContainer.d.ts +2 -0
- package/dist/types/TenantContainer.d.ts.map +1 -1
- package/dist/types/model/commands/apiKey/ProlongApiKeyCommand.d.ts +5 -3
- package/dist/types/model/commands/apiKey/ProlongApiKeyCommand.d.ts.map +1 -1
- package/dist/types/model/commands/authLog/CreateAuthLogEntryCommand.d.ts +1 -0
- package/dist/types/model/commands/authLog/CreateAuthLogEntryCommand.d.ts.map +1 -1
- package/dist/types/model/service/IDPSignInManager.d.ts +3 -1
- package/dist/types/model/service/IDPSignInManager.d.ts.map +1 -1
- package/dist/types/model/service/idp/IdpSessionRevalidator.d.ts +3 -1
- package/dist/types/model/service/idp/IdpSessionRevalidator.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -12
- package/src/TenantContainer.ts +7 -2
- package/src/model/commands/apiKey/ProlongApiKeyCommand.ts +10 -11
- package/src/model/commands/authLog/CreateAuthLogEntryCommand.ts +1 -0
- package/src/model/service/IDPSignInManager.ts +26 -14
- package/src/model/service/idp/IdpSessionRevalidator.ts +43 -28
- package/tests/cases/integration/mocked/signInIdpMutation.test.ts +100 -1
- package/tests/cases/integration/mocked/sql/sqlTransaction.ts +16 -0
- package/tests/cases/unit/apiKey/prolongApiKeyCommand.test.ts +19 -9
- package/tests/cases/unit/idp/idpSessionRevalidator.test.ts +37 -4
- package/tests/src/IdPMock.ts +2 -0
- package/tests/src/testTenant.ts +1 -0
|
@@ -9,6 +9,7 @@ import { CreateAuthLogEntryCommand } from '../../../../src/model/commands/authLo
|
|
|
9
9
|
import { ProjectBySlugQuery, ProjectMembershipByIdentityQuery } from '../../../../src/model/queries/index.js'
|
|
10
10
|
import { Schema } from '@contember/schema'
|
|
11
11
|
import { emptySchema } from '@contember/schema-utils'
|
|
12
|
+
import { createLogger, Logger, TestLoggerHandler } from '@contember/logger'
|
|
12
13
|
|
|
13
14
|
const NOW = new Date('2026-05-26T12:00:00Z')
|
|
14
15
|
const t = (iso: string) => new Date(`2026-05-26T${iso}Z`)
|
|
@@ -20,8 +21,8 @@ const t = (iso: string) => new Date(`2026-05-26T${iso}Z`)
|
|
|
20
21
|
// happy-path claim tests below grant the plain `editor` role on the existing `demo` project, so they need a
|
|
21
22
|
// resolvable schema that defines that role (otherwise the apply-time backstop would drop the grant).
|
|
22
23
|
const claimSyncSchema: Schema = { ...emptySchema, acl: { roles: { editor: { stages: '*', entities: {}, variables: {} } } } }
|
|
23
|
-
const makeRevalidator = (registry: IDPHandlerRegistry) =>
|
|
24
|
-
new IdpSessionRevalidator(registry, new IDPClaimSyncService({ getSchema: () => Promise.resolve(claimSyncSchema) }))
|
|
24
|
+
const makeRevalidator = (registry: IDPHandlerRegistry, logger: Logger = createLogger(new TestLoggerHandler())) =>
|
|
25
|
+
new IdpSessionRevalidator(registry, new IDPClaimSyncService({ getSchema: () => Promise.resolve(claimSyncSchema) }), logger)
|
|
25
26
|
|
|
26
27
|
const apiKeyRow: any = {
|
|
27
28
|
id: 'api-key-1',
|
|
@@ -48,7 +49,7 @@ const baseRow = (overrides: Partial<IdpSessionRow> = {}): IdpSessionRow => ({
|
|
|
48
49
|
...overrides,
|
|
49
50
|
})
|
|
50
51
|
|
|
51
|
-
const createHarness = (row: IdpSessionRow | null, opts: { claim?: boolean } = {}) => {
|
|
52
|
+
const createHarness = (row: IdpSessionRow | null, opts: { claim?: boolean; failAudit?: boolean } = {}) => {
|
|
52
53
|
const executed: any[] = []
|
|
53
54
|
const commandBus = {
|
|
54
55
|
execute: async (command: any) => {
|
|
@@ -56,6 +57,9 @@ const createHarness = (row: IdpSessionRow | null, opts: { claim?: boolean } = {}
|
|
|
56
57
|
if (command instanceof ClaimIdpRevalidationCommand) {
|
|
57
58
|
return opts.claim ?? true
|
|
58
59
|
}
|
|
60
|
+
if (opts.failAudit && command instanceof CreateAuthLogEntryCommand) {
|
|
61
|
+
throw new Error('audit insert failed')
|
|
62
|
+
}
|
|
59
63
|
if (command instanceof DisableApiKeyCommand) {
|
|
60
64
|
return true
|
|
61
65
|
}
|
|
@@ -155,7 +159,7 @@ describe('IdpSessionRevalidator — lifetime-driven phases', () => {
|
|
|
155
159
|
expect((log as any).data.success).toBe(false)
|
|
156
160
|
expect((log as any).data.errorCode).toBe('invalid_grant')
|
|
157
161
|
// the reason is the action payload → event_data (where the rest of the audit system reads it)
|
|
158
|
-
expect((log as any).data.eventData).toEqual({ reason: 'invalid_grant' })
|
|
162
|
+
expect((log as any).data.eventData).toEqual({ reason: 'invalid_grant', apiKeyId: 'api-key-1' })
|
|
159
163
|
expect((log as any).data.metadata).toBeUndefined()
|
|
160
164
|
})
|
|
161
165
|
|
|
@@ -269,6 +273,8 @@ describe('IdpSessionRevalidator — audit logging', () => {
|
|
|
269
273
|
expect(log).toBeDefined()
|
|
270
274
|
expect((log as any).data.type).toBe('idp_session_revalidated')
|
|
271
275
|
expect((log as any).data.success).toBe(true)
|
|
276
|
+
expect((log as any).data.personTokenId).toBeUndefined()
|
|
277
|
+
expect((log as any).data.eventData).toEqual({ apiKeyId: 'api-key-1' })
|
|
272
278
|
})
|
|
273
279
|
|
|
274
280
|
test('records the originating request IP / user agent on both revoke and revalidated entries', async () => {
|
|
@@ -280,6 +286,8 @@ describe('IdpSessionRevalidator — audit logging', () => {
|
|
|
280
286
|
const revokeLog = revoked.executed.find(c => c instanceof CreateAuthLogEntryCommand) as any
|
|
281
287
|
expect(revokeLog.data.ipAddress).toBe('203.0.113.7')
|
|
282
288
|
expect(revokeLog.data.userAgent).toBe('Mozilla/5.0 test')
|
|
289
|
+
expect(revokeLog.data.personTokenId).toBeUndefined()
|
|
290
|
+
expect(revokeLog.data.eventData).toEqual({ reason: 'invalid_grant', apiKeyId: 'api-key-1' })
|
|
283
291
|
|
|
284
292
|
const rotated = createHarness(baseRow({ session: { tokens: { refresh_token: 'r' }, expiresAt: t('11:30:00') } }))
|
|
285
293
|
await makeRevalidator(
|
|
@@ -304,6 +312,8 @@ describe('IdpSessionRevalidator — audit logging', () => {
|
|
|
304
312
|
expect(log.data.errorCode).toBe('revalidation_error')
|
|
305
313
|
expect(log.data.success).toBe(true) // fail-open marker, not a security failure
|
|
306
314
|
expect(log.data.ipAddress).toBe('203.0.113.7')
|
|
315
|
+
expect(log.data.personTokenId).toBeUndefined()
|
|
316
|
+
expect(log.data.eventData).toEqual({ apiKeyId: 'api-key-1' })
|
|
307
317
|
})
|
|
308
318
|
|
|
309
319
|
test('corrupt stored config (fail open) is audited as idp_session_revalidation_failed / config_invalid', async () => {
|
|
@@ -352,6 +362,27 @@ describe('IdpSessionRevalidator — audit logging', () => {
|
|
|
352
362
|
expect(out).toBe('valid')
|
|
353
363
|
})
|
|
354
364
|
|
|
365
|
+
// person_auth_log.person_token_id references person_token(id), so an api_key id there fails the FK
|
|
366
|
+
// and the insert throws. The key is already disabled at that point: the request must still be
|
|
367
|
+
// answered as revoked, not surface the audit failure (#964).
|
|
368
|
+
for (
|
|
369
|
+
const [name, row] of [
|
|
370
|
+
['blocking revocation', baseRow({ session: { tokens: { refresh_token: 'r' }, expiresAt: t('11:30:00') } })],
|
|
371
|
+
['disabled provider', baseRow({ providerDisabledAt: t('11:30:00') })],
|
|
372
|
+
] as const
|
|
373
|
+
) {
|
|
374
|
+
test(`a failing audit write on ${name} still returns revoked and logs the error`, async () => {
|
|
375
|
+
const h = createHarness(row, { failAudit: true })
|
|
376
|
+
const logs = new TestLoggerHandler()
|
|
377
|
+
const out = await makeRevalidator(registryWith(async () => ({ status: 'revoked', reason: 'invalid_grant' })), createLogger(logs))
|
|
378
|
+
.revalidate(h.dbContext, h.readDbContext, apiKeyRow)
|
|
379
|
+
expect(out).toBe('revoked')
|
|
380
|
+
expect(h.executed.some(c => c instanceof DisableApiKeyCommand)).toBe(true)
|
|
381
|
+
expect(h.executed.some(c => c instanceof CreateAuthLogEntryCommand && (c as any).data.type === 'idp_session_revoked')).toBe(true)
|
|
382
|
+
expect(logs.messages.some(m => m.level.name === 'error' && m.error instanceof Error && m.error.message === 'audit insert failed')).toBe(true)
|
|
383
|
+
})
|
|
384
|
+
}
|
|
385
|
+
|
|
355
386
|
test('a no-op probe (valid, no rotated session) is NOT logged', async () => {
|
|
356
387
|
// userinfo / introspection return valid without an idpSession; logging every such tick
|
|
357
388
|
// (every minInterval) would flood the audit log, so it must stay silent.
|
|
@@ -461,6 +492,7 @@ describe('IdpSessionRevalidator — A09 claim mapping on refresh', () => {
|
|
|
461
492
|
expect(log).toBeDefined()
|
|
462
493
|
expect(log.data.success).toBe(true)
|
|
463
494
|
expect(log.data.eventData.after.memberships).toEqual([{ project: 'demo', role: 'editor', variables: [] }])
|
|
495
|
+
expect(log.data.personTokenId).toBeUndefined()
|
|
464
496
|
})
|
|
465
497
|
|
|
466
498
|
test('sticky mapping is skipped on refresh (always-only) — no membership write, no audit', async () => {
|
|
@@ -484,6 +516,7 @@ describe('IdpSessionRevalidator — A09 claim mapping on refresh', () => {
|
|
|
484
516
|
const log = h.executed.find(c => c instanceof CreateAuthLogEntryCommand && (c as any).data.type === 'idp_role_mapping_failed') as any
|
|
485
517
|
expect(log).toBeDefined()
|
|
486
518
|
expect(log.data.success).toBe(true)
|
|
519
|
+
expect(log.data.personTokenId).toBeUndefined()
|
|
487
520
|
})
|
|
488
521
|
|
|
489
522
|
test('refresh without fresh claims does not run claim sync', async () => {
|
package/tests/src/IdPMock.ts
CHANGED
|
@@ -17,6 +17,8 @@ export class IdPMock implements IdentityProviderHandler<any> {
|
|
|
17
17
|
// mirroring how real providers spread raw OIDC claims into the IDPResponse — lets tests
|
|
18
18
|
// drive claim-mapping (A09). `claimMapping` itself lives on the IdP config, not here.
|
|
19
19
|
...(configuration.claims ?? {}),
|
|
20
|
+
// Lets tests drive the federated-session branch of sign-in (re-validation state).
|
|
21
|
+
...(configuration.idpSession ? { idpSession: configuration.idpSession } : {}),
|
|
20
22
|
})
|
|
21
23
|
}
|
|
22
24
|
|
package/tests/src/testTenant.ts
CHANGED
|
@@ -143,6 +143,7 @@ export const executeTenantTest = async (test: Test) => {
|
|
|
143
143
|
connection,
|
|
144
144
|
readConnection: connection,
|
|
145
145
|
cryptoProviders: providers,
|
|
146
|
+
logger: createLogger(new JsonStreamLoggerHandler(process.stderr)),
|
|
146
147
|
})
|
|
147
148
|
.replaceService('mailer', () => mailer)
|
|
148
149
|
.setupService('idpRegistry', reg => {
|