@contember/engine-tenant-api 2.2.0-alpha.6 → 2.2.0-alpha.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contember/engine-tenant-api",
3
- "version": "2.2.0-alpha.6",
3
+ "version": "2.2.0-alpha.7",
4
4
  "license": "Apache-2.0",
5
5
  "main": "./dist/production/index.js",
6
6
  "typings": "./dist/types/index.d.ts",
@@ -26,16 +26,16 @@
26
26
  "generate": "gql-gen --config graphql.codegen.yml"
27
27
  },
28
28
  "dependencies": {
29
- "@contember/authorization": "2.2.0-alpha.6",
30
- "@contember/config-loader": "2.2.0-alpha.6",
31
- "@contember/database": "2.2.0-alpha.6",
32
- "@contember/database-migrations": "2.2.0-alpha.6",
33
- "@contember/dic": "2.2.0-alpha.6",
34
- "@contember/graphql-utils": "2.2.0-alpha.6",
35
- "@contember/logger": "2.2.0-alpha.6",
36
- "@contember/schema": "2.2.0-alpha.6",
37
- "@contember/schema-utils": "2.2.0-alpha.6",
38
- "@contember/typesafe": "2.2.0-alpha.6",
29
+ "@contember/authorization": "2.2.0-alpha.7",
30
+ "@contember/config-loader": "2.2.0-alpha.7",
31
+ "@contember/database": "2.2.0-alpha.7",
32
+ "@contember/database-migrations": "2.2.0-alpha.7",
33
+ "@contember/dic": "2.2.0-alpha.7",
34
+ "@contember/graphql-utils": "2.2.0-alpha.7",
35
+ "@contember/logger": "2.2.0-alpha.7",
36
+ "@contember/schema": "2.2.0-alpha.7",
37
+ "@contember/schema-utils": "2.2.0-alpha.7",
38
+ "@contember/typesafe": "2.2.0-alpha.7",
39
39
  "@graphql-tools/utils": "^11.0.0",
40
40
  "graphql-tag": "^2.12.6",
41
41
  "jose": "^6.2.1",
@@ -46,7 +46,7 @@
46
46
  "otpauth": "^9.5.0"
47
47
  },
48
48
  "devDependencies": {
49
- "@contember/database-tester": "2.2.0-alpha.6",
49
+ "@contember/database-tester": "2.2.0-alpha.7",
50
50
  "@graphql-tools/schema": "^10.0.31",
51
51
  "@types/mustache": "^4.2.6",
52
52
  "@types/node": "^24.12.0",
@@ -1,3 +1,4 @@
1
+ import { UpdateBuilder } from '@contember/database'
1
2
  import { Command } from '../Command.js'
2
3
 
3
4
  /**
@@ -17,12 +18,12 @@ export class ClaimIdpRevalidationCommand implements Command<boolean> {
17
18
  }
18
19
 
19
20
  async execute({ db }: Command.Args): Promise<boolean> {
20
- const result = await db.query(
21
- `UPDATE "idp_session"
22
- SET "last_validated_at" = now()
23
- WHERE "id" = ? AND "last_validated_at" <= now() - ?::interval`,
24
- [this.id, this.interval],
25
- )
26
- return (result.rowCount ?? 0) > 0
21
+ const qb = UpdateBuilder.create()
22
+ .table('idp_session')
23
+ .where({ id: this.id })
24
+ .where(expr => expr.raw('"last_validated_at" <= now() - ?::interval', this.interval))
25
+ .values({ last_validated_at: expr => expr.raw('now()') })
26
+
27
+ return (await qb.execute(db)) > 0
27
28
  }
28
29
  }
@@ -0,0 +1,50 @@
1
+ import { expect, test } from 'bun:test'
2
+ import { createConnectionMock, type ExpectedQuery } from '@contember/database-tester'
3
+ import { DatabaseContext, type Providers } from '../../../../src/index.js'
4
+ import { ClaimIdpRevalidationCommand } from '../../../../src/model/commands/idp/ClaimIdpRevalidationCommand.js'
5
+
6
+ const providers: Providers = {
7
+ bcrypt: async value => value,
8
+ bcryptCompare: async () => true,
9
+ now: () => new Date('2026-05-26T12:00:00Z'),
10
+ randomBytes: async length => Buffer.alloc(length),
11
+ uuid: () => '00000000-0000-0000-0000-000000000000',
12
+ decrypt: async value => ({ value, needsReEncrypt: false }),
13
+ encrypt: async value => ({ value, version: 1 }),
14
+ encryptionEnabled: true,
15
+ hash: value => Buffer.from(value.toString()),
16
+ }
17
+
18
+ const execute = async (expectedQuery: ExpectedQuery): Promise<boolean> => {
19
+ const expectedQueries = [expectedQuery]
20
+ const connection = createConnectionMock(expectedQueries)
21
+ const db = new DatabaseContext(connection.createClient('tenant', { module: 'tenant' }), providers)
22
+
23
+ const claimed = await db.commandBus.execute(new ClaimIdpRevalidationCommand('idp-session-1', '30 seconds'))
24
+
25
+ expect(expectedQueries).toHaveLength(0)
26
+ return claimed
27
+ }
28
+
29
+ // The table must be schema-qualified: the tenant schema is not on the connection's search_path,
30
+ // so an unqualified name makes the claim fail on every request, and the caller treats that as
31
+ // "somebody else holds the claim" — re-validation then never runs at all.
32
+ test('the claim is issued against the qualified tenant table', async () => {
33
+ const claimed = await execute({
34
+ sql: 'update "tenant"."idp_session" set "last_validated_at" = now() where "id" = ? and "last_validated_at" <= now() - ?::interval',
35
+ parameters: ['idp-session-1', '30 seconds'],
36
+ response: { rowCount: 1 },
37
+ })
38
+
39
+ expect(claimed).toBe(true)
40
+ })
41
+
42
+ test('a session still inside the throttle window is not claimed', async () => {
43
+ const claimed = await execute({
44
+ sql: 'update "tenant"."idp_session" set "last_validated_at" = now() where "id" = ? and "last_validated_at" <= now() - ?::interval',
45
+ parameters: ['idp-session-1', '30 seconds'],
46
+ response: { rowCount: 0 },
47
+ })
48
+
49
+ expect(claimed).toBe(false)
50
+ })