From 531464ecc9470d8931ae2c01fca2b29eb878f279 Mon Sep 17 00:00:00 2001 From: mkuhn Date: Mon, 23 Mar 2026 11:14:33 +0100 Subject: [PATCH 1/4] feat: allow non-password-protected tokens --- .../FillDeviceOnboardingTokenWithNewDevice.ts | 5 +-- .../runtime/test/anonymous/tokens.test.ts | 6 +-- .../src/modules/tokens/TokenController.ts | 43 +++++++++++-------- .../src/modules/tokens/local/EmptyToken.ts | 8 ++-- .../tokens/local/SendEmptyTokenParameters.ts | 5 +++ .../local/UpdateTokenContentParameters.ts | 4 +- .../tokens/AnonymousTokenController.test.ts | 4 +- 7 files changed, 43 insertions(+), 32 deletions(-) diff --git a/packages/runtime/src/useCases/transport/devices/FillDeviceOnboardingTokenWithNewDevice.ts b/packages/runtime/src/useCases/transport/devices/FillDeviceOnboardingTokenWithNewDevice.ts index 4ef390dac..53ac3fada 100644 --- a/packages/runtime/src/useCases/transport/devices/FillDeviceOnboardingTokenWithNewDevice.ts +++ b/packages/runtime/src/useCases/transport/devices/FillDeviceOnboardingTokenWithNewDevice.ts @@ -30,9 +30,6 @@ export class FillDeviceOnboardingTokenWithNewDeviceUseCase extends UseCase> { const reference = TokenReference.from(request.reference); - const passwordProtection = reference.passwordProtection; - if (!passwordProtection?.password) throw RuntimeErrors.devices.referenceNotPointingToAnEmptyToken(); - const isEmptyToken = await this.tokenController.isEmptyToken(reference); if (!isEmptyToken) throw RuntimeErrors.devices.referenceNotPointingToAnEmptyToken(); @@ -46,7 +43,7 @@ export class FillDeviceOnboardingTokenWithNewDeviceUseCase extends UseCase { expect(result).toBeSuccessful(); const token = result.value; - expect(token.passwordProtection.password).toBeDefined(); - expect(token.passwordProtection.passwordIsPin).toBeUndefined(); - expect(token.passwordProtection.passwordLocationIndicator).toBeUndefined(); + expect(token.passwordProtection?.password).toBeDefined(); + expect(token.passwordProtection?.passwordIsPin).toBeUndefined(); + expect(token.passwordProtection?.passwordLocationIndicator).toBeUndefined(); }); test("should get a proper error when trying to load an empty token", async () => { diff --git a/packages/transport/src/modules/tokens/TokenController.ts b/packages/transport/src/modules/tokens/TokenController.ts index 042fa6963..81a6977ce 100644 --- a/packages/transport/src/modules/tokens/TokenController.ts +++ b/packages/transport/src/modules/tokens/TokenController.ts @@ -93,10 +93,9 @@ export class TokenController extends TransportController { 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 = (await CoreCrypto.deriveHashOutOfPassword(password, salt)).toBase64(); - const passwordProtection = PasswordProtection.from({ password, passwordType: "pw", salt }); + const { hashedPassword, passwordProtection } = input.usePasswordProtection + ? await this.generatePasswordProtection() + : { hashedPassword: undefined, passwordProtection: undefined }; const response = (await this.client.createEmptyToken({ password: hashedPassword, expiresAt: input.expiresAt.toISOString() })).value; @@ -114,6 +113,15 @@ export class TokenController extends TransportController { return token; } + private async generatePasswordProtection(): Promise<{ hashedPassword: string; passwordProtection: PasswordProtection }> { + const password = await Random.string(16, RandomCharacterRange.Alphanumeric + RandomCharacterRange.SpecialCharacters); + const salt = await CoreCrypto.random(16); + const hashedPassword = (await CoreCrypto.deriveHashOutOfPassword(password, salt)).toBase64(); + const passwordProtection = PasswordProtection.from({ password, passwordType: "pw", salt }); + + return { hashedPassword, passwordProtection }; + } + @log() public async setTokenMetadata(idOrToken: CoreId | Token, metadata: ISerializable): Promise { const id = idOrToken instanceof CoreId ? idOrToken.toString() : idOrToken.id.toString(); @@ -233,19 +241,20 @@ export class TokenController extends TransportController { const cipher = await CoreCrypto.encrypt(serializedTokenBuffer, input.secretKey); - const password = parameters.passwordProtection.password; - if (!password) throw TransportCoreErrors.general.noPasswordProvided(); - - const hashedPassword = (await CoreCrypto.deriveHashOutOfPassword(password, input.passwordProtection.salt)).toBase64(); + const hashedPassword = input.passwordProtection?.password + ? (await CoreCrypto.deriveHashOutOfPassword(input.passwordProtection.password, input.passwordProtection.salt)).toBase64() + : undefined; const response = (await this.client.updateTokenContent({ id: parameters.id.toString(), newContent: cipher.toBase64(), password: hashedPassword })).value; - const passwordProtection = PasswordProtection.from({ - password, - passwordType: parameters.passwordProtection.passwordType, - salt: parameters.passwordProtection.salt, - passwordLocationIndicator: parameters.passwordProtection.passwordLocationIndicator - }); + const passwordProtection = input.passwordProtection?.password + ? PasswordProtection.from({ + password: input.passwordProtection.password, + passwordType: input.passwordProtection.passwordType, + salt: input.passwordProtection.salt, + passwordLocationIndicator: input.passwordProtection.passwordLocationIndicator + }) + : undefined; const token = Token.from({ id: CoreId.from(response.id), @@ -263,9 +272,9 @@ export class TokenController extends TransportController { } public async isEmptyToken(reference: TokenReference): Promise { - if (!reference.passwordProtection?.password) throw TransportCoreErrors.general.noPasswordProvided(); - - const hashedPassword = (await CoreCrypto.deriveHashOutOfPassword(reference.passwordProtection.password, reference.passwordProtection.salt)).toBase64(); + const hashedPassword = reference.passwordProtection?.password + ? (await CoreCrypto.deriveHashOutOfPassword(reference.passwordProtection.password, reference.passwordProtection.salt)).toBase64() + : undefined; const response = (await this.client.getToken(reference.id.toString(), hashedPassword)).value; return !response.content; diff --git a/packages/transport/src/modules/tokens/local/EmptyToken.ts b/packages/transport/src/modules/tokens/local/EmptyToken.ts index 0a3616da5..f746d6b3c 100644 --- a/packages/transport/src/modules/tokens/local/EmptyToken.ts +++ b/packages/transport/src/modules/tokens/local/EmptyToken.ts @@ -9,7 +9,7 @@ import { TokenReference } from "../transmission/TokenReference"; export interface IEmptyToken extends ICoreSynchronizable { secretKey: ICryptoSecretKey; expiresAt: ICoreDate; - passwordProtection: IPasswordProtection; + passwordProtection?: IPasswordProtection; } @type("EmptyToken") @@ -25,9 +25,9 @@ export class EmptyToken extends CoreSynchronizable implements IEmptyToken { @serialize() public expiresAt: CoreDate; - @validate() + @validate({ nullable: true }) @serialize() - public passwordProtection: PasswordProtection; + public passwordProtection?: PasswordProtection; public static from(value: IEmptyToken): EmptyToken { return this.fromAny(value); @@ -38,7 +38,7 @@ export class EmptyToken extends CoreSynchronizable implements IEmptyToken { id: this.id, backboneBaseUrl, key: this.secretKey, - passwordProtection: this.passwordProtection.toSharedPasswordProtection(true) + passwordProtection: this.passwordProtection?.toSharedPasswordProtection(true) }); } } diff --git a/packages/transport/src/modules/tokens/local/SendEmptyTokenParameters.ts b/packages/transport/src/modules/tokens/local/SendEmptyTokenParameters.ts index f09cea641..9fcea53f8 100644 --- a/packages/transport/src/modules/tokens/local/SendEmptyTokenParameters.ts +++ b/packages/transport/src/modules/tokens/local/SendEmptyTokenParameters.ts @@ -4,6 +4,7 @@ import { CoreDate, ICoreDate } from "@nmshd/core-types"; export interface ISendEmptyTokenParameters extends ISerializable { expiresAt: ICoreDate; ephemeral: boolean; + usePasswordProtection?: boolean; } @type("SendEmptyTokenParameters") @@ -16,6 +17,10 @@ export class SendEmptyTokenParameters extends Serializable implements ISendEmpty @serialize() public ephemeral: boolean; + @validate({ nullable: true }) + @serialize() + public usePasswordProtection?: boolean; + public static from(value: ISendEmptyTokenParameters): SendEmptyTokenParameters { return this.fromAny(value); } diff --git a/packages/transport/src/modules/tokens/local/UpdateTokenContentParameters.ts b/packages/transport/src/modules/tokens/local/UpdateTokenContentParameters.ts index 8a4551501..f587ba2bb 100644 --- a/packages/transport/src/modules/tokens/local/UpdateTokenContentParameters.ts +++ b/packages/transport/src/modules/tokens/local/UpdateTokenContentParameters.ts @@ -6,7 +6,7 @@ export interface IUpdateTokenContentParameters extends ISerializable { id: ICoreId; secretKey: ICryptoSecretKey; content: ISerializable; - passwordProtection: ISharedPasswordProtection; + passwordProtection?: ISharedPasswordProtection; } @type("UpdateTokenContentParameters") @@ -25,7 +25,7 @@ export class UpdateTokenContentParameters extends Serializable implements IUpdat @validate({ nullable: true }) @serialize() - public passwordProtection: SharedPasswordProtection; + public passwordProtection?: SharedPasswordProtection; public static from(value: IUpdateTokenContentParameters): UpdateTokenContentParameters { return this.fromAny(value); diff --git a/packages/transport/test/modules/tokens/AnonymousTokenController.test.ts b/packages/transport/test/modules/tokens/AnonymousTokenController.test.ts index c3712f04f..20660ed68 100644 --- a/packages/transport/test/modules/tokens/AnonymousTokenController.test.ts +++ b/packages/transport/test/modules/tokens/AnonymousTokenController.test.ts @@ -133,8 +133,8 @@ describe("AnonymousTokenController", function () { test("should create an empty token", async () => { const token = await anonymousTokenController.createEmptyToken(); - expect(token.passwordProtection.password).toBeDefined(); - expect(token.passwordProtection.passwordLocationIndicator).toBeUndefined(); + expect(token.passwordProtection?.password).toBeDefined(); + expect(token.passwordProtection?.passwordLocationIndicator).toBeUndefined(); }); test("should get a proper error when trying to load an empty token", async () => { From 403d4278b0ea7ee4ad28f2e51b534ea38b9160f6 Mon Sep 17 00:00:00 2001 From: mkuhn Date: Mon, 23 Mar 2026 11:29:31 +0100 Subject: [PATCH 2/4] fix: adapt emptyTokenDto --- packages/runtime-types/src/dtos/transport/EmptyTokenDTO.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime-types/src/dtos/transport/EmptyTokenDTO.ts b/packages/runtime-types/src/dtos/transport/EmptyTokenDTO.ts index 162646e86..4d69ff58e 100644 --- a/packages/runtime-types/src/dtos/transport/EmptyTokenDTO.ts +++ b/packages/runtime-types/src/dtos/transport/EmptyTokenDTO.ts @@ -3,7 +3,7 @@ import { PasswordProtectionDTO } from "./PasswordProtectionDTO"; export interface EmptyTokenDTO { id: string; expiresAt: string; - passwordProtection: PasswordProtectionDTO; + passwordProtection?: PasswordProtectionDTO; reference: { truncated: string; url: string; From 3f57cfffca54a68807884f78f3e22bdfe50a02ba Mon Sep 17 00:00:00 2001 From: mkuhn Date: Wed, 25 Mar 2026 09:42:57 +0100 Subject: [PATCH 3/4] feat: remove password protection from empty tokens --- .../src/SharedPasswordProtection.ts | 17 ++----- .../src/dtos/transport/EmptyTokenDTO.ts | 3 -- .../FillDeviceOnboardingTokenWithNewDevice.ts | 3 +- .../useCases/transport/tokens/TokenMapper.ts | 1 - .../runtime/test/anonymous/tokens.test.ts | 5 -- .../src/core/types/PasswordProtection.ts | 5 +- .../tokens/AnonymousTokenController.ts | 16 ++----- .../src/modules/tokens/TokenController.ts | 47 ++++--------------- .../src/modules/tokens/local/EmptyToken.ts | 10 +--- .../tokens/local/SendEmptyTokenParameters.ts | 5 -- .../local/UpdateTokenContentParameters.ts | 7 +-- .../tokens/AnonymousTokenController.test.ts | 3 +- 12 files changed, 25 insertions(+), 97 deletions(-) diff --git a/packages/core-types/src/SharedPasswordProtection.ts b/packages/core-types/src/SharedPasswordProtection.ts index 289968f5b..2e1dfcc23 100644 --- a/packages/core-types/src/SharedPasswordProtection.ts +++ b/packages/core-types/src/SharedPasswordProtection.ts @@ -6,7 +6,6 @@ export interface ISharedPasswordProtection extends ISerializable { passwordType: "pw" | `pin${number}`; salt: ICoreBuffer; passwordLocationIndicator?: number; - password?: string; } export class SharedPasswordProtection extends Serializable implements ISharedPasswordProtection { @@ -22,10 +21,6 @@ export class SharedPasswordProtection extends Serializable implements ISharedPas @serialize({ any: true }) public passwordLocationIndicator?: number; - @validate({ nullable: true }) - @serialize() - public password?: string; - public static from(value: ISharedPasswordProtection): SharedPasswordProtection { return this.fromAny(value); } @@ -34,8 +29,8 @@ export class SharedPasswordProtection extends Serializable implements ISharedPas if (value === undefined || value === "") return undefined; const splittedPasswordParts = value.split("&"); - if (![2, 3, 4].includes(splittedPasswordParts.length)) { - throw new CoreError("error.core-types.invalidTruncatedReference", "The password part of a TruncatedReference must consist of 2, 3 or 4 components."); + if (![2, 3].includes(splittedPasswordParts.length)) { + throw new CoreError("error.core-types.invalidTruncatedReference", "The password part of a TruncatedReference must consist of 2 or 3 components."); } const passwordType = splittedPasswordParts[0] as "pw" | `pin${number}`; @@ -43,9 +38,7 @@ export class SharedPasswordProtection extends Serializable implements ISharedPas const salt = this.parseSalt(splittedPasswordParts[1]); - const password = splittedPasswordParts.length > 3 && splittedPasswordParts[3] ? splittedPasswordParts[3] : undefined; - - return SharedPasswordProtection.from({ passwordType, salt, passwordLocationIndicator, password }); + return SharedPasswordProtection.from({ passwordType, salt, passwordLocationIndicator }); } private static parseSalt(value: string): CoreBuffer { @@ -60,8 +53,8 @@ export class SharedPasswordProtection extends Serializable implements ISharedPas public truncate(): string { const base = `${this.passwordType}&${this.salt.toBase64()}`; - if (this.passwordLocationIndicator === undefined && this.password === undefined) return base; + if (this.passwordLocationIndicator === undefined) return base; - return `${base}&${this.passwordLocationIndicator ?? ""}${this.password ? `&${this.password}` : ""}`; + return `${base}&${this.passwordLocationIndicator ?? ""}`; } } diff --git a/packages/runtime-types/src/dtos/transport/EmptyTokenDTO.ts b/packages/runtime-types/src/dtos/transport/EmptyTokenDTO.ts index 4d69ff58e..660f0dd35 100644 --- a/packages/runtime-types/src/dtos/transport/EmptyTokenDTO.ts +++ b/packages/runtime-types/src/dtos/transport/EmptyTokenDTO.ts @@ -1,9 +1,6 @@ -import { PasswordProtectionDTO } from "./PasswordProtectionDTO"; - export interface EmptyTokenDTO { id: string; expiresAt: string; - passwordProtection?: PasswordProtectionDTO; reference: { truncated: string; url: string; diff --git a/packages/runtime/src/useCases/transport/devices/FillDeviceOnboardingTokenWithNewDevice.ts b/packages/runtime/src/useCases/transport/devices/FillDeviceOnboardingTokenWithNewDevice.ts index 53ac3fada..240a3ced6 100644 --- a/packages/runtime/src/useCases/transport/devices/FillDeviceOnboardingTokenWithNewDevice.ts +++ b/packages/runtime/src/useCases/transport/devices/FillDeviceOnboardingTokenWithNewDevice.ts @@ -42,8 +42,7 @@ export class FillDeviceOnboardingTokenWithNewDeviceUseCase extends UseCase { test("should create an empty token", async () => { const result = await noLoginRuntime.anonymousServices.tokens.createEmptyToken(); expect(result).toBeSuccessful(); - - const token = result.value; - expect(token.passwordProtection?.password).toBeDefined(); - expect(token.passwordProtection?.passwordIsPin).toBeUndefined(); - expect(token.passwordProtection?.passwordLocationIndicator).toBeUndefined(); }); test("should get a proper error when trying to load an empty token", async () => { diff --git a/packages/transport/src/core/types/PasswordProtection.ts b/packages/transport/src/core/types/PasswordProtection.ts index d9c29224d..290409cbd 100644 --- a/packages/transport/src/core/types/PasswordProtection.ts +++ b/packages/transport/src/core/types/PasswordProtection.ts @@ -31,12 +31,11 @@ export class PasswordProtection extends Serializable implements IPasswordProtect return this.fromAny(value); } - public toSharedPasswordProtection(includeCleartextPassword?: boolean): SharedPasswordProtection { + public toSharedPasswordProtection(): SharedPasswordProtection { return SharedPasswordProtection.from({ passwordType: this.passwordType, salt: this.salt, - passwordLocationIndicator: this.passwordLocationIndicator, - password: includeCleartextPassword ? this.password : undefined + passwordLocationIndicator: this.passwordLocationIndicator }); } diff --git a/packages/transport/src/modules/tokens/AnonymousTokenController.ts b/packages/transport/src/modules/tokens/AnonymousTokenController.ts index fd439b6dd..c03bd8465 100644 --- a/packages/transport/src/modules/tokens/AnonymousTokenController.ts +++ b/packages/transport/src/modules/tokens/AnonymousTokenController.ts @@ -1,5 +1,5 @@ import { Serializable } from "@js-soft/ts-serval"; -import { CoreAddress, CoreDate, CoreId, Random, RandomCharacterRange } from "@nmshd/core-types"; +import { CoreAddress, CoreDate, CoreId } from "@nmshd/core-types"; import { CryptoCipher, CryptoSecretKey } from "@nmshd/crypto"; import { CoreCrypto, IConfig, ICorrelator, TransportCoreErrors } from "../../core"; import { PasswordProtection } from "../../core/types/PasswordProtection"; @@ -16,27 +16,21 @@ export class AnonymousTokenController { public async createEmptyToken(): Promise { const secretKey = await CoreCrypto.generateSecretKey(); - const password = await Random.string(16, RandomCharacterRange.Alphanumeric + RandomCharacterRange.SpecialCharacters); - - const salt = await CoreCrypto.random(16); - const passwordProtection = PasswordProtection.from({ password, passwordType: "pw", salt }); - const expiresAt = CoreDate.utc().add({ minutes: 2 }); - const hashedPassword = (await CoreCrypto.deriveHashOutOfPassword(password, salt)).toBase64(); - const response = (await this.client.createToken({ password: hashedPassword, expiresAt: expiresAt.toISOString() })).value; + const response = (await this.client.createToken({ expiresAt: expiresAt.toISOString() })).value; - return EmptyToken.from({ id: CoreId.from(response.id), secretKey: secretKey, expiresAt, passwordProtection }); + return EmptyToken.from({ id: CoreId.from(response.id), secretKey: secretKey, expiresAt }); } public async loadPeerTokenByReference(reference: TokenReference, password?: string): Promise { - if (reference.passwordProtection && !reference.passwordProtection.password && !password) throw TransportCoreErrors.general.noPasswordProvided(); + if (reference.passwordProtection && !password) throw TransportCoreErrors.general.noPasswordProvided(); const passwordProtection = reference.passwordProtection ? PasswordProtection.from({ salt: reference.passwordProtection.salt, passwordType: reference.passwordProtection.passwordType, - password: (password ?? reference.passwordProtection.password)!, + password: password!, passwordLocationIndicator: reference.passwordProtection.passwordLocationIndicator }) : undefined; diff --git a/packages/transport/src/modules/tokens/TokenController.ts b/packages/transport/src/modules/tokens/TokenController.ts index 81a6977ce..fa273f2c4 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, Random, RandomCharacterRange } from "@nmshd/core-types"; +import { CoreAddress, CoreDate, CoreId } from "@nmshd/core-types"; import { CoreBuffer, CryptoCipher, CryptoSecretKey } from "@nmshd/crypto"; import { CoreCrypto, TransportCoreErrors } from "../../core"; import { DbCollectionName } from "../../core/DbCollectionName"; @@ -93,17 +93,12 @@ export class TokenController extends TransportController { const input = SendEmptyTokenParameters.from(parameters); const secretKey = await CoreCrypto.generateSecretKey(); - const { hashedPassword, passwordProtection } = input.usePasswordProtection - ? await this.generatePasswordProtection() - : { hashedPassword: undefined, passwordProtection: undefined }; - - const response = (await this.client.createEmptyToken({ password: hashedPassword, expiresAt: input.expiresAt.toISOString() })).value; + const response = (await this.client.createEmptyToken({ expiresAt: input.expiresAt.toISOString() })).value; const token = EmptyToken.from({ id: CoreId.from(response.id), secretKey: secretKey, - expiresAt: input.expiresAt, - passwordProtection + expiresAt: input.expiresAt }); if (!input.ephemeral) { @@ -113,15 +108,6 @@ export class TokenController extends TransportController { return token; } - private async generatePasswordProtection(): Promise<{ hashedPassword: string; passwordProtection: PasswordProtection }> { - const password = await Random.string(16, RandomCharacterRange.Alphanumeric + RandomCharacterRange.SpecialCharacters); - const salt = await CoreCrypto.random(16); - const hashedPassword = (await CoreCrypto.deriveHashOutOfPassword(password, salt)).toBase64(); - const passwordProtection = PasswordProtection.from({ password, passwordType: "pw", salt }); - - return { hashedPassword, passwordProtection }; - } - @log() public async setTokenMetadata(idOrToken: CoreId | Token, metadata: ISerializable): Promise { const id = idOrToken instanceof CoreId ? idOrToken.toString() : idOrToken.id.toString(); @@ -143,12 +129,12 @@ export class TokenController extends TransportController { } public async loadPeerTokenByReference(reference: TokenReference, ephemeral: boolean, password?: string): Promise { - if (reference.passwordProtection && !reference.passwordProtection.password && !password) throw TransportCoreErrors.general.noPasswordProvided(); + if (reference.passwordProtection && !password) throw TransportCoreErrors.general.noPasswordProvided(); const passwordProtection = reference.passwordProtection ? PasswordProtection.from({ salt: reference.passwordProtection.salt, passwordType: reference.passwordProtection.passwordType, - password: (password ?? reference.passwordProtection.password)!, + password: password!, passwordLocationIndicator: reference.passwordProtection.passwordLocationIndicator }) : undefined; @@ -241,26 +227,12 @@ export class TokenController extends TransportController { const cipher = await CoreCrypto.encrypt(serializedTokenBuffer, input.secretKey); - const hashedPassword = input.passwordProtection?.password - ? (await CoreCrypto.deriveHashOutOfPassword(input.passwordProtection.password, input.passwordProtection.salt)).toBase64() - : undefined; - - const response = (await this.client.updateTokenContent({ id: parameters.id.toString(), newContent: cipher.toBase64(), password: hashedPassword })).value; - - const passwordProtection = input.passwordProtection?.password - ? PasswordProtection.from({ - password: input.passwordProtection.password, - passwordType: input.passwordProtection.passwordType, - salt: input.passwordProtection.salt, - passwordLocationIndicator: input.passwordProtection.passwordLocationIndicator - }) - : undefined; + const response = (await this.client.updateTokenContent({ id: parameters.id.toString(), newContent: cipher.toBase64() })).value; const token = Token.from({ id: CoreId.from(response.id), secretKey: input.secretKey, isOwn: true, - passwordProtection, createdAt: CoreDate.from(response.createdAt), expiresAt: CoreDate.from(response.expiresAt), createdBy: this.parent.identity.address, @@ -272,10 +244,9 @@ export class TokenController extends TransportController { } public async isEmptyToken(reference: TokenReference): Promise { - const hashedPassword = reference.passwordProtection?.password - ? (await CoreCrypto.deriveHashOutOfPassword(reference.passwordProtection.password, reference.passwordProtection.salt)).toBase64() - : undefined; - const response = (await this.client.getToken(reference.id.toString(), hashedPassword)).value; + if (reference.passwordProtection) return false; + + const response = (await this.client.getToken(reference.id.toString())).value; return !response.content; } diff --git a/packages/transport/src/modules/tokens/local/EmptyToken.ts b/packages/transport/src/modules/tokens/local/EmptyToken.ts index f746d6b3c..2ce588f88 100644 --- a/packages/transport/src/modules/tokens/local/EmptyToken.ts +++ b/packages/transport/src/modules/tokens/local/EmptyToken.ts @@ -3,19 +3,16 @@ import { CoreDate, ICoreDate } from "@nmshd/core-types"; import { CryptoSecretKey, ICryptoSecretKey } from "@nmshd/crypto"; import { nameof } from "ts-simple-nameof"; import { CoreSynchronizable, ICoreSynchronizable } from "../../../core"; -import { IPasswordProtection, PasswordProtection } from "../../../core/types/PasswordProtection"; import { TokenReference } from "../transmission/TokenReference"; export interface IEmptyToken extends ICoreSynchronizable { secretKey: ICryptoSecretKey; expiresAt: ICoreDate; - passwordProtection?: IPasswordProtection; } @type("EmptyToken") export class EmptyToken extends CoreSynchronizable implements IEmptyToken { public override readonly technicalProperties = ["@type", "@context", nameof((r) => r.secretKey), nameof((r) => r.expiresAt)]; - public override readonly userdataProperties = [nameof((r) => r.passwordProtection)]; @validate() @serialize() @@ -25,10 +22,6 @@ export class EmptyToken extends CoreSynchronizable implements IEmptyToken { @serialize() public expiresAt: CoreDate; - @validate({ nullable: true }) - @serialize() - public passwordProtection?: PasswordProtection; - public static from(value: IEmptyToken): EmptyToken { return this.fromAny(value); } @@ -37,8 +30,7 @@ export class EmptyToken extends CoreSynchronizable implements IEmptyToken { return TokenReference.from({ id: this.id, backboneBaseUrl, - key: this.secretKey, - passwordProtection: this.passwordProtection?.toSharedPasswordProtection(true) + key: this.secretKey }); } } diff --git a/packages/transport/src/modules/tokens/local/SendEmptyTokenParameters.ts b/packages/transport/src/modules/tokens/local/SendEmptyTokenParameters.ts index 9fcea53f8..f09cea641 100644 --- a/packages/transport/src/modules/tokens/local/SendEmptyTokenParameters.ts +++ b/packages/transport/src/modules/tokens/local/SendEmptyTokenParameters.ts @@ -4,7 +4,6 @@ import { CoreDate, ICoreDate } from "@nmshd/core-types"; export interface ISendEmptyTokenParameters extends ISerializable { expiresAt: ICoreDate; ephemeral: boolean; - usePasswordProtection?: boolean; } @type("SendEmptyTokenParameters") @@ -17,10 +16,6 @@ export class SendEmptyTokenParameters extends Serializable implements ISendEmpty @serialize() public ephemeral: boolean; - @validate({ nullable: true }) - @serialize() - public usePasswordProtection?: boolean; - public static from(value: ISendEmptyTokenParameters): SendEmptyTokenParameters { return this.fromAny(value); } diff --git a/packages/transport/src/modules/tokens/local/UpdateTokenContentParameters.ts b/packages/transport/src/modules/tokens/local/UpdateTokenContentParameters.ts index f587ba2bb..62330ef03 100644 --- a/packages/transport/src/modules/tokens/local/UpdateTokenContentParameters.ts +++ b/packages/transport/src/modules/tokens/local/UpdateTokenContentParameters.ts @@ -1,12 +1,11 @@ import { ISerializable, Serializable, serialize, type, validate } from "@js-soft/ts-serval"; -import { CoreId, ICoreId, ISharedPasswordProtection, SharedPasswordProtection } from "@nmshd/core-types"; +import { CoreId, ICoreId } from "@nmshd/core-types"; import { CryptoSecretKey, ICryptoSecretKey } from "@nmshd/crypto"; export interface IUpdateTokenContentParameters extends ISerializable { id: ICoreId; secretKey: ICryptoSecretKey; content: ISerializable; - passwordProtection?: ISharedPasswordProtection; } @type("UpdateTokenContentParameters") @@ -23,10 +22,6 @@ export class UpdateTokenContentParameters extends Serializable implements IUpdat @serialize() public content: Serializable; - @validate({ nullable: true }) - @serialize() - public passwordProtection?: SharedPasswordProtection; - public static from(value: IUpdateTokenContentParameters): UpdateTokenContentParameters { return this.fromAny(value); } diff --git a/packages/transport/test/modules/tokens/AnonymousTokenController.test.ts b/packages/transport/test/modules/tokens/AnonymousTokenController.test.ts index 20660ed68..87a7be7d4 100644 --- a/packages/transport/test/modules/tokens/AnonymousTokenController.test.ts +++ b/packages/transport/test/modules/tokens/AnonymousTokenController.test.ts @@ -133,8 +133,7 @@ describe("AnonymousTokenController", function () { test("should create an empty token", async () => { const token = await anonymousTokenController.createEmptyToken(); - expect(token.passwordProtection?.password).toBeDefined(); - expect(token.passwordProtection?.passwordLocationIndicator).toBeUndefined(); + expect(token).toBeDefined(); }); test("should get a proper error when trying to load an empty token", async () => { From 176ec6535137ce95385ef94a52e591f8458ee34b Mon Sep 17 00:00:00 2001 From: mkuhn Date: Wed, 25 Mar 2026 09:54:55 +0100 Subject: [PATCH 4/4] test: adapt tests --- .../test/references/Reference.test.ts | 22 +------------------ .../modules/tokens/TokenController.test.ts | 1 - 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/packages/core-types/test/references/Reference.test.ts b/packages/core-types/test/references/Reference.test.ts index d806209c1..bf55ac2e8 100644 --- a/packages/core-types/test/references/Reference.test.ts +++ b/packages/core-types/test/references/Reference.test.ts @@ -50,27 +50,7 @@ describe("Reference", () => { ); }); - test("toUrl with a cleartext password in the passwordProtection reference fields used", () => { - const reference = Reference.from({ - id: CoreId.from("ANID1234"), - backboneBaseUrl: "https://backbone.example.com", - key: CryptoSecretKey.from({ - secretKey: CoreBuffer.from("lerJyX8ydJDEXowq2PMMntRXXA27wgHJYA_BjnFx55Y"), - algorithm: CryptoEncryptionAlgorithm.XCHACHA20_POLY1305 - }), - passwordProtection: SharedPasswordProtection.from({ - passwordType: "pw", - salt: CoreBuffer.fromUtf8("a16byteslongsalt"), - password: "aPassword" - }) - }); - - expect(reference.toUrl()).toBe( - "https://backbone.example.com/r/ANID1234#M3xsZXJKeVg4eWRKREVYb3dxMlBNTW50UlhYQTI3d2dISllBX0JqbkZ4NTVZfHxwdyZZVEUyWW5sMFpYTnNiMjVuYzJGc2RBPT0mJmFQYXNzd29yZA" - ); - }); - - test("toUrl without a cleartext password but with a passwordLocationIndicator in the passwordProtection reference fields used", () => { + test("toUrl with a passwordLocationIndicator in the passwordProtection reference fields used", () => { const reference = Reference.from({ id: CoreId.from("ANID1234"), backboneBaseUrl: "https://backbone.example.com", diff --git a/packages/transport/test/modules/tokens/TokenController.test.ts b/packages/transport/test/modules/tokens/TokenController.test.ts index 5eb0ed425..7b93f0b83 100644 --- a/packages/transport/test/modules/tokens/TokenController.test.ts +++ b/packages/transport/test/modules/tokens/TokenController.test.ts @@ -389,7 +389,6 @@ describe("TokenController", function () { const updatedSentToken = await sender.tokens.updateTokenContent({ content: content, id: reference.id, - passwordProtection: reference.passwordProtection!, secretKey: reference.key });