Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/transport/src/modules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
25 changes: 24 additions & 1 deletion packages/transport/src/modules/tokens/TokenController.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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";
Expand Down Expand Up @@ -87,6 +89,27 @@ export class TokenController extends TransportController {
return token;
}

public async sendEmptyToken(parameters: ISendEmptyTokenParameters): Promise<EmptyToken> {
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 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<Token> {
const id = idOrToken instanceof CoreId ? idOrToken.toString() : idOrToken.id.toString();
Expand Down
4 changes: 4 additions & 0 deletions packages/transport/src/modules/tokens/backbone/TokenClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export class TokenClient extends RESTClientAuthenticate {
return await this.post<BackbonePostTokensResponse>("/api/v2/Tokens", token);
}

public async createEmptyToken(request: Omit<BackbonePostTokensRequest, "content">): Promise<ClientResult<BackbonePostTokensResponse>> {
return await this.post<BackbonePostTokensResponse>("/api/v2/Tokens", request);
}

public async updateTokenContent(request: BackboneUpdateTokenContentRequest): Promise<ClientResult<BackboneUpdateTokenContentResponse>> {
return await this.post<BackboneUpdateTokenContentResponse>(`/api/v2/Tokens/${request.id}/UpdateContent`, request);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface IUpdateTokenContentParameters extends ISerializable {
passwordProtection: ISharedPasswordProtection;
}

@type("SendTokenParameters")
@type("UpdateTokenContentParameters")
export class UpdateTokenContentParameters extends Serializable implements IUpdateTokenContentParameters {
@validate()
@serialize()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ describe("TokenController", function () {
expect((receivedToken.content as any).content).toBe((sentToken.content as any).content);
});

test("should send an empty token", async function () {
const expiresAt = CoreDate.utc().add({ hours: 1 });
const sentToken = await sender.tokens.sendEmptyToken({
expiresAt
});

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);
Expand Down