@contember/engine-tenant-api 2.2.0-alpha.6 → 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.
Files changed (77) hide show
  1. package/dist/development/src/TenantContainer.cjs +5 -2
  2. package/dist/development/src/TenantContainer.cjs.map +1 -1
  3. package/dist/development/src/TenantContainer.js +5 -2
  4. package/dist/development/src/TenantContainer.js.map +1 -1
  5. package/dist/development/src/model/commands/apiKey/ProlongApiKeyCommand.cjs +5 -8
  6. package/dist/development/src/model/commands/apiKey/ProlongApiKeyCommand.cjs.map +1 -1
  7. package/dist/development/src/model/commands/apiKey/ProlongApiKeyCommand.js +5 -8
  8. package/dist/development/src/model/commands/apiKey/ProlongApiKeyCommand.js.map +1 -1
  9. package/dist/development/src/model/commands/authLog/CreateAuthLogEntryCommand.cjs.map +1 -1
  10. package/dist/development/src/model/commands/authLog/CreateAuthLogEntryCommand.js.map +1 -1
  11. package/dist/development/src/model/commands/idp/ClaimIdpRevalidationCommand.cjs +3 -7
  12. package/dist/development/src/model/commands/idp/ClaimIdpRevalidationCommand.cjs.map +1 -1
  13. package/dist/development/src/model/commands/idp/ClaimIdpRevalidationCommand.js +3 -7
  14. package/dist/development/src/model/commands/idp/ClaimIdpRevalidationCommand.js.map +1 -1
  15. package/dist/development/src/model/service/IDPSignInManager.cjs +23 -15
  16. package/dist/development/src/model/service/IDPSignInManager.cjs.map +1 -1
  17. package/dist/development/src/model/service/IDPSignInManager.js +23 -15
  18. package/dist/development/src/model/service/IDPSignInManager.js.map +1 -1
  19. package/dist/development/src/model/service/idp/IdpSessionRevalidator.cjs +42 -31
  20. package/dist/development/src/model/service/idp/IdpSessionRevalidator.cjs.map +1 -1
  21. package/dist/development/src/model/service/idp/IdpSessionRevalidator.js +42 -31
  22. package/dist/development/src/model/service/idp/IdpSessionRevalidator.js.map +1 -1
  23. package/dist/production/src/TenantContainer.cjs +5 -2
  24. package/dist/production/src/TenantContainer.cjs.map +1 -1
  25. package/dist/production/src/TenantContainer.js +5 -2
  26. package/dist/production/src/TenantContainer.js.map +1 -1
  27. package/dist/production/src/model/commands/apiKey/ProlongApiKeyCommand.cjs +5 -8
  28. package/dist/production/src/model/commands/apiKey/ProlongApiKeyCommand.cjs.map +1 -1
  29. package/dist/production/src/model/commands/apiKey/ProlongApiKeyCommand.js +5 -8
  30. package/dist/production/src/model/commands/apiKey/ProlongApiKeyCommand.js.map +1 -1
  31. package/dist/production/src/model/commands/authLog/CreateAuthLogEntryCommand.cjs.map +1 -1
  32. package/dist/production/src/model/commands/authLog/CreateAuthLogEntryCommand.js.map +1 -1
  33. package/dist/production/src/model/commands/idp/ClaimIdpRevalidationCommand.cjs +3 -7
  34. package/dist/production/src/model/commands/idp/ClaimIdpRevalidationCommand.cjs.map +1 -1
  35. package/dist/production/src/model/commands/idp/ClaimIdpRevalidationCommand.js +3 -7
  36. package/dist/production/src/model/commands/idp/ClaimIdpRevalidationCommand.js.map +1 -1
  37. package/dist/production/src/model/service/IDPSignInManager.cjs +23 -15
  38. package/dist/production/src/model/service/IDPSignInManager.cjs.map +1 -1
  39. package/dist/production/src/model/service/IDPSignInManager.js +23 -15
  40. package/dist/production/src/model/service/IDPSignInManager.js.map +1 -1
  41. package/dist/production/src/model/service/idp/IdpSessionRevalidator.cjs +42 -31
  42. package/dist/production/src/model/service/idp/IdpSessionRevalidator.cjs.map +1 -1
  43. package/dist/production/src/model/service/idp/IdpSessionRevalidator.js +42 -31
  44. package/dist/production/src/model/service/idp/IdpSessionRevalidator.js.map +1 -1
  45. package/dist/tests/cases/integration/mocked/sql/sqlTransaction.d.ts +5 -0
  46. package/dist/tests/cases/integration/mocked/sql/sqlTransaction.d.ts.map +1 -1
  47. package/dist/tests/cases/unit/idp/claimIdpRevalidation.test.d.ts +2 -0
  48. package/dist/tests/cases/unit/idp/claimIdpRevalidation.test.d.ts.map +1 -0
  49. package/dist/tests/src/IdPMock.d.ts.map +1 -1
  50. package/dist/tests/src/testTenant.d.ts.map +1 -1
  51. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  52. package/dist/types/TenantContainer.d.ts +2 -0
  53. package/dist/types/TenantContainer.d.ts.map +1 -1
  54. package/dist/types/model/commands/apiKey/ProlongApiKeyCommand.d.ts +5 -3
  55. package/dist/types/model/commands/apiKey/ProlongApiKeyCommand.d.ts.map +1 -1
  56. package/dist/types/model/commands/authLog/CreateAuthLogEntryCommand.d.ts +1 -0
  57. package/dist/types/model/commands/authLog/CreateAuthLogEntryCommand.d.ts.map +1 -1
  58. package/dist/types/model/commands/idp/ClaimIdpRevalidationCommand.d.ts.map +1 -1
  59. package/dist/types/model/service/IDPSignInManager.d.ts +3 -1
  60. package/dist/types/model/service/IDPSignInManager.d.ts.map +1 -1
  61. package/dist/types/model/service/idp/IdpSessionRevalidator.d.ts +3 -1
  62. package/dist/types/model/service/idp/IdpSessionRevalidator.d.ts.map +1 -1
  63. package/dist/types/tsconfig.tsbuildinfo +1 -1
  64. package/package.json +12 -12
  65. package/src/TenantContainer.ts +7 -2
  66. package/src/model/commands/apiKey/ProlongApiKeyCommand.ts +10 -11
  67. package/src/model/commands/authLog/CreateAuthLogEntryCommand.ts +1 -0
  68. package/src/model/commands/idp/ClaimIdpRevalidationCommand.ts +8 -7
  69. package/src/model/service/IDPSignInManager.ts +26 -14
  70. package/src/model/service/idp/IdpSessionRevalidator.ts +43 -28
  71. package/tests/cases/integration/mocked/signInIdpMutation.test.ts +100 -1
  72. package/tests/cases/integration/mocked/sql/sqlTransaction.ts +16 -0
  73. package/tests/cases/unit/apiKey/prolongApiKeyCommand.test.ts +19 -9
  74. package/tests/cases/unit/idp/claimIdpRevalidation.test.ts +50 -0
  75. package/tests/cases/unit/idp/idpSessionRevalidator.test.ts +37 -4
  76. package/tests/src/IdPMock.ts +2 -0
  77. 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 () => {
@@ -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
 
@@ -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 => {