From 7415a4720e4233de4e121df7638ae0a66a3badb4 Mon Sep 17 00:00:00 2001 From: Timo Notheisen Date: Thu, 12 Feb 2026 15:45:02 +0100 Subject: [PATCH 01/11] test. add first test --- .../test/modules/tokens/TokenController.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/transport/test/modules/tokens/TokenController.test.ts b/packages/transport/test/modules/tokens/TokenController.test.ts index 82c6886b3..07d76f933 100644 --- a/packages/transport/test/modules/tokens/TokenController.test.ts +++ b/packages/transport/test/modules/tokens/TokenController.test.ts @@ -67,6 +67,23 @@ describe("TokenController", function () { expect((receivedToken.content as any).content).toBe((sentToken.content as any).content); }); + + test("should send a token without content", async function () { + tempDate = CoreDate.utc().subtract(TestUtil.tempDateThreshold); + const expiresAt = CoreDate.utc().add({ hours: 1 }); + const sentToken = await sender.tokens.sendToken({ + expiresAt, + ephemeral: false + }); + + expect(sentToken.expiresAt.toISOString()).toBe(expiresAt.toISOString()); + expect(sentToken.content).toBeInstanceOf(Serializable); + expect(receivedToken.content).toBeInstanceOf(JSONWrapper); + expect((sentToken.content.toJSON() as any).content).toBe("TestToken"); + expect((receivedToken.content as any).content).toBe((sentToken.content as any).content); + }); + + test("should get the stored token", async function () { const sentToken = await sender.tokens.getToken(tempId1); const receivedToken = await recipient.tokens.getToken(tempId1); From cecf07b46af301c7d9540e326fd203a0d66c998b Mon Sep 17 00:00:00 2001 From: Timo Notheisen Date: Fri, 13 Feb 2026 13:17:54 +0100 Subject: [PATCH 02/11] chore: upgrade backbone to 7.1.3 --- .dev/compose.backbone.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.dev/compose.backbone.yml b/.dev/compose.backbone.yml index 9d5131aa4..c8fb0e1d7 100644 --- a/.dev/compose.backbone.yml +++ b/.dev/compose.backbone.yml @@ -2,7 +2,7 @@ name: local-test-backbone services: consumer-api: - image: ghcr.io/nmshd/backbone-consumer-api:7.1.2@sha256:9948ad32a5c0d280df0049934f14d5f2e572b78167a2c8f62054cd0db1920ac7 + image: ghcr.io/nmshd/backbone-consumer-api:7.1.3@sha256:9948ad32a5c0d280df0049934f14d5f2e572b78167a2c8f62054cd0db1920ac7 container_name: consumer-api hostname: consumer-api environment: @@ -25,7 +25,7 @@ services: target: app/appsettings.override.json event-handler-service: - image: ghcr.io/nmshd/backbone-event-handler:7.1.2@sha256:48b3dccc918d2bd1cdbf85058d9b2638a5175b6afb5ada60a744657a35fa2531 + image: ghcr.io/nmshd/backbone-event-handler:7.1.3@sha256:48b3dccc918d2bd1cdbf85058d9b2638a5175b6afb5ada60a744657a35fa2531 container_name: event-handler-service depends_on: database: @@ -39,7 +39,7 @@ services: target: app/appsettings.override.json sse-server: - image: ghcr.io/nmshd/backbone-sse-server:7.1.2@sha256:e702c6af79e8f04d5cfd86d38fb0b103b753c20f64b13d5b5a9b9b4e09e8a21a + image: ghcr.io/nmshd/backbone-sse-server:7.1.3@sha256:e702c6af79e8f04d5cfd86d38fb0b103b753c20f64b13d5b5a9b9b4e09e8a21a container_name: sse-server hostname: sse-server ports: @@ -52,7 +52,7 @@ services: target: app/appsettings.override.json admin-ui: - image: ghcr.io/nmshd/backbone-admin-ui:7.1.2@sha256:3d59df401a471e8f2606cfd342f598136f12794f4e75a2f5eeb1b3bce47a959a + image: ghcr.io/nmshd/backbone-admin-ui:7.1.3@sha256:3d59df401a471e8f2606cfd342f598136f12794f4e75a2f5eeb1b3bce47a959a container_name: admin-ui hostname: admin-ui ports: @@ -72,7 +72,7 @@ services: database-migrator: container_name: database-migrator - image: ghcr.io/nmshd/backbone-database-migrator:7.1.2@sha256:7f5781feec57c4e60032cf6d3ba47fc9ab9da5e13f530d8d326d58685f174609 + image: ghcr.io/nmshd/backbone-database-migrator:7.1.3@sha256:7f5781feec57c4e60032cf6d3ba47fc9ab9da5e13f530d8d326d58685f174609 environment: Infrastructure__SqlDatabase__Provider: Postgres Infrastructure__SqlDatabase__ConnectionString: "Server=postgres;Database=enmeshed;User Id=postgres;Password=Passw0rd;Port=5432" @@ -115,7 +115,7 @@ services: seed-client: container_name: seed-client - image: ghcr.io/nmshd/backbone-admin-cli:7.1.2@sha256:df2e51e181f98f666e5054d86005e015c3f126d8673a389ecc0fe508e22046ce + image: ghcr.io/nmshd/backbone-admin-cli:7.1.3@sha256:df2e51e181f98f666e5054d86005e015c3f126d8673a389ecc0fe508e22046ce depends_on: consumer-api: condition: service_healthy From a125b60f6f85f02bda34ec512523513499fb223d Mon Sep 17 00:00:00 2001 From: Timo Notheisen Date: Fri, 13 Feb 2026 13:18:41 +0100 Subject: [PATCH 03/11] test: update test --- .../test/modules/tokens/TokenController.test.ts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/packages/transport/test/modules/tokens/TokenController.test.ts b/packages/transport/test/modules/tokens/TokenController.test.ts index 07d76f933..b73767ed8 100644 --- a/packages/transport/test/modules/tokens/TokenController.test.ts +++ b/packages/transport/test/modules/tokens/TokenController.test.ts @@ -67,23 +67,16 @@ describe("TokenController", function () { expect((receivedToken.content as any).content).toBe((sentToken.content as any).content); }); - - test("should send a token without content", async function () { + test("should send an empty token", async function () { tempDate = CoreDate.utc().subtract(TestUtil.tempDateThreshold); const expiresAt = CoreDate.utc().add({ hours: 1 }); - const sentToken = await sender.tokens.sendToken({ - expiresAt, - ephemeral: false + const sentToken = await sender.tokens.sendEmptyToken({ + expiresAt }); - expect(sentToken.expiresAt.toISOString()).toBe(expiresAt.toISOString()); - expect(sentToken.content).toBeInstanceOf(Serializable); - expect(receivedToken.content).toBeInstanceOf(JSONWrapper); - expect((sentToken.content.toJSON() as any).content).toBe("TestToken"); - expect((receivedToken.content as any).content).toBe((sentToken.content as any).content); + expect(sentToken).toBeDefined(); }); - test("should get the stored token", async function () { const sentToken = await sender.tokens.getToken(tempId1); const receivedToken = await recipient.tokens.getToken(tempId1); From ec3f2f24fec607608bf03eeb6a5ac83fa5aae461 Mon Sep 17 00:00:00 2001 From: Timo Notheisen Date: Fri, 13 Feb 2026 13:18:52 +0100 Subject: [PATCH 04/11] feat: allow creation of empty tokens with authenticated user --- .../src/modules/tokens/TokenController.ts | 25 ++++++++++++++++++- .../modules/tokens/backbone/TokenClient.ts | 4 +++ .../tokens/local/SendEmptyTokenParameters.ts | 17 +++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 packages/transport/src/modules/tokens/local/SendEmptyTokenParameters.ts diff --git a/packages/transport/src/modules/tokens/TokenController.ts b/packages/transport/src/modules/tokens/TokenController.ts index 26dd347a2..997d553cd 100644 --- a/packages/transport/src/modules/tokens/TokenController.ts +++ b/packages/transport/src/modules/tokens/TokenController.ts @@ -1,6 +1,6 @@ import { ISerializable, Serializable } from "@js-soft/ts-serval"; import { log } from "@js-soft/ts-utils"; -import { CoreAddress, CoreDate, CoreId } from "@nmshd/core-types"; +import { CoreAddress, CoreDate, CoreId, Random, RandomCharacterRange } from "@nmshd/core-types"; import { CoreBuffer, CryptoCipher, CryptoSecretKey } from "@nmshd/crypto"; import { CoreCrypto, TransportCoreErrors } from "../../core"; import { DbCollectionName } from "../../core/DbCollectionName"; @@ -10,6 +10,8 @@ import { AccountController } from "../accounts/AccountController"; import { SynchronizedCollection } from "../sync/SynchronizedCollection"; import { BackboneGetTokensResponse } from "./backbone/BackboneGetTokens"; import { TokenClient } from "./backbone/TokenClient"; +import { EmptyToken } from "./local/EmptyToken"; +import { ISendEmptyTokenParameters, SendEmptyTokenParameters } from "./local/SendEmptyTokenParameters"; import { ISendTokenParameters, SendTokenParameters } from "./local/SendTokenParameters"; import { Token } from "./local/Token"; import { IUpdateTokenContentParameters, UpdateTokenContentParameters } from "./local/UpdateTokenContentParameters"; @@ -87,6 +89,27 @@ export class TokenController extends TransportController { return token; } + public async sendEmptyToken(parameters: ISendEmptyTokenParameters): Promise { + const input = SendEmptyTokenParameters.from(parameters); + const secretKey = await CoreCrypto.generateSecretKey(); + + const password = await Random.string(16, RandomCharacterRange.Alphanumeric + RandomCharacterRange.SpecialCharacters); + const salt = await CoreCrypto.random(16); + const hashedPassword = password ? (await CoreCrypto.deriveHashOutOfPassword(password, salt)).toBase64() : undefined; + const passwordProtection = PasswordProtection.from({ password, passwordType: "pw", salt }); + + const response = (await this.client.createEmptyToken({ password: hashedPassword, expiresAt: input.expiresAt.toISOString() })).value; + + const token = EmptyToken.from({ + id: CoreId.from(response.id), + secretKey: secretKey, + expiresAt: input.expiresAt, + passwordProtection + }); + + return token; + } + @log() public async setTokenMetadata(idOrToken: CoreId | Token, metadata: ISerializable): Promise { const id = idOrToken instanceof CoreId ? idOrToken.toString() : idOrToken.id.toString(); diff --git a/packages/transport/src/modules/tokens/backbone/TokenClient.ts b/packages/transport/src/modules/tokens/backbone/TokenClient.ts index 11d1b2bc0..77ba189d6 100644 --- a/packages/transport/src/modules/tokens/backbone/TokenClient.ts +++ b/packages/transport/src/modules/tokens/backbone/TokenClient.ts @@ -9,6 +9,10 @@ export class TokenClient extends RESTClientAuthenticate { return await this.post("/api/v2/Tokens", token); } + public async createEmptyToken(request: Omit): Promise> { + return await this.post("/api/v2/Tokens", request); + } + public async updateTokenContent(request: BackboneUpdateTokenContentRequest): Promise> { return await this.post(`/api/v2/Tokens/${request.id}/UpdateContent`, request); } diff --git a/packages/transport/src/modules/tokens/local/SendEmptyTokenParameters.ts b/packages/transport/src/modules/tokens/local/SendEmptyTokenParameters.ts new file mode 100644 index 000000000..282b6d1d2 --- /dev/null +++ b/packages/transport/src/modules/tokens/local/SendEmptyTokenParameters.ts @@ -0,0 +1,17 @@ +import { ISerializable, Serializable, serialize, type, validate } from "@js-soft/ts-serval"; +import { CoreDate, ICoreDate } from "@nmshd/core-types"; + +export interface ISendEmptyTokenParameters extends ISerializable { + expiresAt: ICoreDate; +} + +@type("SendEmptyTokenParameters") +export class SendEmptyTokenParameters extends Serializable implements ISendEmptyTokenParameters { + @validate() + @serialize() + public expiresAt: CoreDate; + + public static from(value: ISendEmptyTokenParameters): SendEmptyTokenParameters { + return this.fromAny(value); + } +} From fc985e7afa70dd90922e93c7453ddb65e5b13916 Mon Sep 17 00:00:00 2001 From: Timo Notheisen Date: Wed, 11 Feb 2026 12:49:31 +0100 Subject: [PATCH 05/11] refactor: use optional chain --- packages/runtime/src/dataViews/DataViewExpander.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime/src/dataViews/DataViewExpander.ts b/packages/runtime/src/dataViews/DataViewExpander.ts index 5bbefe665..ccfbd6b35 100644 --- a/packages/runtime/src/dataViews/DataViewExpander.ts +++ b/packages/runtime/src/dataViews/DataViewExpander.ts @@ -538,7 +538,7 @@ export class DataViewExpander { } let proposedValueOverruled = false; - if (responseItemDVO && responseItemDVO.result === ResponseItemResult.Accepted) { + if (responseItemDVO?.result === ResponseItemResult.Accepted) { if (responseItemDVO.type === "AttributeSuccessionAcceptResponseItemDVO") { const attributeSuccessionResponseItem = responseItemDVO as AttributeSuccessionAcceptResponseItemDVO; proposedValueOverruled = !_.isEqual(attributeSuccessionResponseItem.successor.content.value, proposeAttributeRequestItem.attribute.value); From 7e06620ef4b14dd7252c1f833003018314184dbf Mon Sep 17 00:00:00 2001 From: Timo Notheisen Date: Wed, 11 Feb 2026 12:50:06 +0100 Subject: [PATCH 06/11] fix: use correct @type annotation --- .../src/modules/tokens/local/UpdateTokenContentParameters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/transport/src/modules/tokens/local/UpdateTokenContentParameters.ts b/packages/transport/src/modules/tokens/local/UpdateTokenContentParameters.ts index 576ce4d0f..8a4551501 100644 --- a/packages/transport/src/modules/tokens/local/UpdateTokenContentParameters.ts +++ b/packages/transport/src/modules/tokens/local/UpdateTokenContentParameters.ts @@ -9,7 +9,7 @@ export interface IUpdateTokenContentParameters extends ISerializable { passwordProtection: ISharedPasswordProtection; } -@type("SendTokenParameters") +@type("UpdateTokenContentParameters") export class UpdateTokenContentParameters extends Serializable implements IUpdateTokenContentParameters { @validate() @serialize() From 293e1cccb2c71ef8ea0b25a6306da7d750dc462f Mon Sep 17 00:00:00 2001 From: Timo Notheisen Date: Fri, 13 Feb 2026 13:22:29 +0100 Subject: [PATCH 07/11] chore: update backbone shas --- .dev/compose.backbone.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.dev/compose.backbone.yml b/.dev/compose.backbone.yml index c8fb0e1d7..f38048e91 100644 --- a/.dev/compose.backbone.yml +++ b/.dev/compose.backbone.yml @@ -2,7 +2,7 @@ name: local-test-backbone services: consumer-api: - image: ghcr.io/nmshd/backbone-consumer-api:7.1.3@sha256:9948ad32a5c0d280df0049934f14d5f2e572b78167a2c8f62054cd0db1920ac7 + image: ghcr.io/nmshd/backbone-consumer-api:7.1.3@sha256:25c09fd48f6380ca6b953dccacdac06a16c28ebfd4945c81a0b7ee5bbef829b5 container_name: consumer-api hostname: consumer-api environment: @@ -25,7 +25,7 @@ services: target: app/appsettings.override.json event-handler-service: - image: ghcr.io/nmshd/backbone-event-handler:7.1.3@sha256:48b3dccc918d2bd1cdbf85058d9b2638a5175b6afb5ada60a744657a35fa2531 + image: ghcr.io/nmshd/backbone-event-handler:7.1.3@sha256:c9f9d57f484b8f678d1a3a84895b59d4f952f00e7ab02a55d15e59a7eae2fb11 container_name: event-handler-service depends_on: database: @@ -39,7 +39,7 @@ services: target: app/appsettings.override.json sse-server: - image: ghcr.io/nmshd/backbone-sse-server:7.1.3@sha256:e702c6af79e8f04d5cfd86d38fb0b103b753c20f64b13d5b5a9b9b4e09e8a21a + image: ghcr.io/nmshd/backbone-sse-server:7.1.3@sha256:5840d7cfa54187fa59154b8099c6586833b90965b5f92f26a38bdcefbc8fb4ec container_name: sse-server hostname: sse-server ports: @@ -52,7 +52,7 @@ services: target: app/appsettings.override.json admin-ui: - image: ghcr.io/nmshd/backbone-admin-ui:7.1.3@sha256:3d59df401a471e8f2606cfd342f598136f12794f4e75a2f5eeb1b3bce47a959a + image: ghcr.io/nmshd/backbone-admin-ui:7.1.3@sha256:637d671ed7a5aafd71807a4febabb0da8f8f05394da59f59a53b6a013b8a232d container_name: admin-ui hostname: admin-ui ports: @@ -72,7 +72,7 @@ services: database-migrator: container_name: database-migrator - image: ghcr.io/nmshd/backbone-database-migrator:7.1.3@sha256:7f5781feec57c4e60032cf6d3ba47fc9ab9da5e13f530d8d326d58685f174609 + image: ghcr.io/nmshd/backbone-database-migrator:7.1.3@sha256:120b724eeddb58bb5f8c4aa2d9107e322bb6eea1707498daf932e6328464af58 environment: Infrastructure__SqlDatabase__Provider: Postgres Infrastructure__SqlDatabase__ConnectionString: "Server=postgres;Database=enmeshed;User Id=postgres;Password=Passw0rd;Port=5432" @@ -115,7 +115,7 @@ services: seed-client: container_name: seed-client - image: ghcr.io/nmshd/backbone-admin-cli:7.1.3@sha256:df2e51e181f98f666e5054d86005e015c3f126d8673a389ecc0fe508e22046ce + image: ghcr.io/nmshd/backbone-admin-cli:7.1.3@sha256:619aa192e3ce925c22342fa7bb3dd82437ad5fe6281c7f9fb84dad33d5dd3646 depends_on: consumer-api: condition: service_healthy From 960e4b224197849abc3132147ceb3daeff5f0722 Mon Sep 17 00:00:00 2001 From: Timo Notheisen Date: Fri, 13 Feb 2026 13:28:44 +0100 Subject: [PATCH 08/11] test: delete unnecessary assignment from test --- packages/transport/test/modules/tokens/TokenController.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/transport/test/modules/tokens/TokenController.test.ts b/packages/transport/test/modules/tokens/TokenController.test.ts index b73767ed8..a635cfbbc 100644 --- a/packages/transport/test/modules/tokens/TokenController.test.ts +++ b/packages/transport/test/modules/tokens/TokenController.test.ts @@ -68,7 +68,6 @@ describe("TokenController", function () { }); test("should send an empty token", async function () { - tempDate = CoreDate.utc().subtract(TestUtil.tempDateThreshold); const expiresAt = CoreDate.utc().add({ hours: 1 }); const sentToken = await sender.tokens.sendEmptyToken({ expiresAt From 126abbc12726c649467246cef8006d961db581d0 Mon Sep 17 00:00:00 2001 From: Timo Notheisen Date: Fri, 13 Feb 2026 13:58:25 +0100 Subject: [PATCH 09/11] chore: downgrade supercharge/mongodb-github-action --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 10787ea28..097d53c27 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -50,7 +50,7 @@ jobs: node-version-file: .nvmrc - run: npm ci - name: Start MongoDB - uses: supercharge/mongodb-github-action@v1 + uses: supercharge/mongodb-github-action@1.12.1 - run: npm run build:node - run: npm run test:ci:mongodb --workspace=@nmshd/consumption env: @@ -122,7 +122,7 @@ jobs: - uses: actions/setup-node@v6 with: node-version-file: .nvmrc - - uses: supercharge/mongodb-github-action@v1 + - uses: supercharge/mongodb-github-action@1.12.1 - run: npm ci - run: npm run build:node - run: npm run build:schemas --workspace=@nmshd/runtime @@ -172,7 +172,7 @@ jobs: - run: npm ci - run: npm run build:node - name: Start MongoDB - uses: supercharge/mongodb-github-action@v1 + uses: supercharge/mongodb-github-action@1.12.1 - run: npm run test:ci:mongodb --workspace=@nmshd/transport env: CONNECTION_STRING: mongodb://127.0.0.1:27017 From 1a7789fb19c39bba4264cd04509d4dd8b3f7c1c9 Mon Sep 17 00:00:00 2001 From: Timo Notheisen Date: Fri, 13 Feb 2026 15:06:38 +0100 Subject: [PATCH 10/11] fix: add SendEmptyTokenParameters to index.ts --- packages/transport/src/modules/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/transport/src/modules/index.ts b/packages/transport/src/modules/index.ts index 17a5f5d36..ea5556ef3 100644 --- a/packages/transport/src/modules/index.ts +++ b/packages/transport/src/modules/index.ts @@ -103,6 +103,7 @@ export * from "./tokens/backbone/BackboneGetTokens"; export * from "./tokens/backbone/BackbonePostTokens"; export * from "./tokens/backbone/TokenClient"; export * from "./tokens/local/EmptyToken"; +export * from "./tokens/local/SendEmptyTokenParameters"; export * from "./tokens/local/SendTokenParameters"; export * from "./tokens/local/Token"; export * from "./tokens/TokenController"; From 066470f417d69e189c0bf438c3efb20e26ea3301 Mon Sep 17 00:00:00 2001 From: Timo Notheisen Date: Fri, 13 Feb 2026 15:07:05 +0100 Subject: [PATCH 11/11] chore: remove unnecessary ternary check --- packages/transport/src/modules/tokens/TokenController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/transport/src/modules/tokens/TokenController.ts b/packages/transport/src/modules/tokens/TokenController.ts index 997d553cd..29aa473de 100644 --- a/packages/transport/src/modules/tokens/TokenController.ts +++ b/packages/transport/src/modules/tokens/TokenController.ts @@ -95,7 +95,7 @@ export class TokenController extends TransportController { const password = await Random.string(16, RandomCharacterRange.Alphanumeric + RandomCharacterRange.SpecialCharacters); const salt = await CoreCrypto.random(16); - const hashedPassword = password ? (await CoreCrypto.deriveHashOutOfPassword(password, salt)).toBase64() : undefined; + const hashedPassword = (await CoreCrypto.deriveHashOutOfPassword(password, salt)).toBase64(); const passwordProtection = PasswordProtection.from({ password, passwordType: "pw", salt }); const response = (await this.client.createEmptyToken({ password: hashedPassword, expiresAt: input.expiresAt.toISOString() })).value;