Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable secret recovery request. #263

Merged
merged 2 commits into from
May 22, 2024
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
51 changes: 44 additions & 7 deletions packages/client-node/integration/Secrets.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,67 @@
import { ClosedIdentityLoc } from "@logion/client";
import { ClosedIdentityLoc, CreateSecretRecoveryRequest } from "@logion/client";
import { State } from "./Utils";
import { UUID } from "@logion/node-api";

const secretToKeep = "Key";

export async function recoverableSecrets(state: State) {
const requesterIdentityLocId = await addSecrets(state);
await createRecoveryRequest(state, requesterIdentityLocId);
}

async function addSecrets(state: State): Promise<UUID> {
const { requesterAccount } = state;

const client = state.client.withCurrentAccount(requesterAccount);
let locsState = await client.locsState();

let closedIdentityLoc = locsState.closedLocs.Identity[0] as ClosedIdentityLoc;
expect(closedIdentityLoc).toBeInstanceOf(ClosedIdentityLoc);
const name = "Key";
const value = "Encrypted key";

closedIdentityLoc = await closedIdentityLoc.addSecret({
name,
name: secretToKeep,
value,
});
const secretToRemove = "secret-to-remove";
closedIdentityLoc = await closedIdentityLoc.addSecret({
name: secretToRemove,
value,
});

expect(closedIdentityLoc).toBeInstanceOf(ClosedIdentityLoc);

let data = closedIdentityLoc.data();
expect(data.secrets.length).toBe(2);

closedIdentityLoc = await closedIdentityLoc.removeSecret(secretToRemove);

data = closedIdentityLoc.data();
expect(data.secrets.length).toBe(1);
expect(data.secrets[0].name).toBe(name);
expect(data.secrets[0].name).toBe(secretToKeep);
expect(data.secrets[0].value).toBe(value);

closedIdentityLoc = await closedIdentityLoc.removeSecret(name);
return closedIdentityLoc.locId;
}

data = closedIdentityLoc.data();
expect(data.secrets.length).toBe(0);
async function createRecoveryRequest(state: State, requesterIdentityLocId: UUID) {
const request: CreateSecretRecoveryRequest = {
requesterIdentityLocId,
secretName: secretToKeep,
challenge: "my-personal-challenge",
userIdentity: {
email: "[email protected]",
firstName: "John",
lastName: "Doe",
phoneNumber: "+1234",
},
userPostalAddress: {
line1: "Line1",
line2: "Line2",
postalCode: "PostalCode",
city: "City",
country: "Country",
}
}
await state.client.secretRecovery.createSecretRecoveryRequest(request);
}
2 changes: 1 addition & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@logion/client",
"version": "0.45.0-7",
"version": "0.45.0-8",
"description": "logion SDK for client applications",
"main": "dist/index.js",
"packageManager": "[email protected]",
Expand Down
25 changes: 17 additions & 8 deletions packages/client/src/LogionClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ import { VoterApi } from "./Voter.js";
import { SponsorshipState, SponsorshipApi } from "./Sponsorship.js";
import { requireDefined } from "./assertions.js";
import { InvitedContributorApi } from "./InvitedContributor.js";
import { SecretRecoveryApi } from "./SecretRecovery.js";

/**
* An instance of LogionClient is connected to a Logion network and
* interacts with all its components (including the blockchain).
*
*
* It features:
* - Access to LGNT balance, transactions and transfer
* - LOC management
Expand All @@ -35,7 +36,7 @@ export class LogionClient {

/**
* Instantiates a connected client.
*
*
* @param config Parameters of a connection to the Logion network.
* @returns A connected client.
*/
Expand Down Expand Up @@ -71,6 +72,7 @@ export class LogionClient {
this._public = new PublicApi({ sharedState });
this._voter = new VoterApi({ sharedState, logionClient: this });
this._invitedContributor = new InvitedContributorApi({ sharedState, logionClient: this })
this._secretRecovery = new SecretRecoveryApi({ sharedState });
}

private sharedState: SharedState;
Expand All @@ -81,6 +83,8 @@ export class LogionClient {

private readonly _invitedContributor: InvitedContributorApi;

private readonly _secretRecovery: SecretRecoveryApi;

/**
* The configuration of this client.
*/
Expand Down Expand Up @@ -143,7 +147,7 @@ export class LogionClient {

/**
* Overrides current tokens.
*
*
* @param tokens The new tokens.
* @returns A copy of this client, but using the new tokens.
*/
Expand All @@ -165,7 +169,7 @@ export class LogionClient {

/**
* Postpones the expiration of valid (see {@link isTokenValid}) JWT tokens.
*
*
* @param now Current time, used to check if tokens are still valid or not.
* @param threshold If at least one token's expiration falls between now and (now + threshold), then tokens are refreshed. Otherwise, they are not.
* @returns An authenticated client using refreshed tokens or this if no refresh occured.
Expand Down Expand Up @@ -206,7 +210,7 @@ export class LogionClient {

/**
* Sets current account.
*
*
* @param currentAccount The account to use as current.
* @returns A client instance with provided current account.
*/
Expand Down Expand Up @@ -295,11 +299,11 @@ export class LogionClient {
* a JWT token for each address. A valid JWT token is sent back by a Logion node
* if the client was able to sign a random challenge, hence proving that provided
* signer is indeed able to sign using provided addresses.
*
*
* Note that the signer may be able to sign for more addresses than the once provided.
* A call to this method will merge the retrieved tokens with the ones already available.
* Older tokens are replaced.
*
*
* @param accounts The addresses for which an authentication token must be retrieved.
* @param signer The signer that will sign the challenge.
* @returns An instance of client with retrived JWT tokens.
Expand Down Expand Up @@ -503,9 +507,14 @@ export class LogionClient {
return this._invitedContributor;
}

get secretRecovery(): SecretRecoveryApi {
this.ensureConnected();
return this._secretRecovery;
}

/**
* Disconnects the client from the Logion blockchain.
* @returns
* @returns
*/
async disconnect() {
if(this.sharedState.nodeApi.polkadot.isConnected) {
Expand Down
15 changes: 15 additions & 0 deletions packages/client/src/SecretRecovery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { SharedState } from "./SharedClient.js";
import { CreateSecretRecoveryRequest, SecretRecoveryClient } from "./SecretRecoveryClient.js";

export class SecretRecoveryApi {

constructor(args: { sharedState: SharedState }) {
this.client = new SecretRecoveryClient(args);
}

private readonly client: SecretRecoveryClient;

async createSecretRecoveryRequest(params: CreateSecretRecoveryRequest): Promise<void> {
await this.client.createSecretRecoveryRequest(params);
}
}
49 changes: 49 additions & 0 deletions packages/client/src/SecretRecoveryClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { UserIdentity, PostalAddress } from "./Types";
import { LocMultiClient, FetchParameters } from "./LocClient.js";
import { requireDefined } from "./assertions.js";
import { SharedState } from "./SharedClient.js";
import { AxiosInstance } from "axios";
import { UUID } from "@logion/node-api";
import { newBackendError } from "./Error.js";

export interface CreateSecretRecoveryRequest {
requesterIdentityLocId: UUID;
secretName: string;
challenge: string;
userIdentity: UserIdentity;
userPostalAddress: PostalAddress;
}

export class SecretRecoveryClient {

constructor(args: { sharedState: SharedState }) {
this.sharedState = args.sharedState;
}

private sharedState: SharedState;

async createSecretRecoveryRequest(params: CreateSecretRecoveryRequest): Promise<void> {
const { secretName, challenge, userIdentity, userPostalAddress, requesterIdentityLocId } = params;
const axios = await this.backend({ locId: requesterIdentityLocId });
try {
await axios.post("/api/secret-recovery", {
requesterIdentityLocId: requesterIdentityLocId.toString(),
secretName,
challenge,
userIdentity,
userPostalAddress,
});
} catch (e) {
throw newBackendError(e);
}
}

private async backend(params: FetchParameters): Promise<AxiosInstance> {
const loc = await LocMultiClient.getLoc({
...params,
api: this.sharedState.nodeApi,
});
const legalOfficer = requireDefined(this.sharedState.legalOfficers.find(lo => lo.account.equals(loc.owner)));
return legalOfficer.buildAxiosToNode();
}
}
4 changes: 3 additions & 1 deletion packages/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Exposes tools enabling interaction with a Logion network.
* An instance of {@link LogionClient} must be created.
* Most features of the client require authentication.
*
*
* @module
*/
import { ISubmittableResult } from '@polkadot/types/types';
Expand Down Expand Up @@ -33,6 +33,8 @@ export * from './Polling.js';
export * from './Public.js';
export * from './Recovery.js';
export { LegalOfficerDecision, LoRecoveryClient, ProtectionRequest, ProtectionRequestStatus, UpdateParameters, UserActionParameters, CreateProtectionRequest } from './RecoveryClient.js';
export * from './SecretRecovery.js';
export * from './SecretRecoveryClient.js';
export * from './SharedClient.js';
export * from './Signer.js';
export * from './State.js';
Expand Down
9 changes: 4 additions & 5 deletions packages/client/test/AuthenticationClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
ALICE,
buildAliceTokens,
buildSimpleNodeApi,
buildValidPolkadotAccountId,
DIRECTORY_ENDPOINT
} from "./Utils.js";
import { ValidAccountId } from "@logion/node-api";
Expand Down Expand Up @@ -78,7 +77,7 @@ async function testAuthentication(legalOfficers: LegalOfficer[], expectedEndpoin
.returns(axiosInstance.object());
}

const addresses = [ buildValidPolkadotAccountId("5GWqG9UVMx4o9fHHx6K4JT8kcw9UnKNozLtHRg7g4aojMf1i")! ];
const addresses = [ ValidAccountId.polkadot("5GWqG9UVMx4o9fHHx6K4JT8kcw9UnKNozLtHRg7g4aojMf1i")! ];
const sessionId = "session-id";
setupSignIn(axiosInstance, addresses, sessionId);

Expand Down Expand Up @@ -159,9 +158,9 @@ describe("AccountTokens", () => {

const now = DateTime.now();

const ADDRESS_WITH_VALID_TOKEN = buildValidPolkadotAccountId("5FhQTfi1CxGAeNmbZj5bRLhnBpydKnMuMnk1wZQAqUUQ3kwE")!;
const ADDRESS_WITH_VALID_TOKEN = ValidAccountId.polkadot("5FhQTfi1CxGAeNmbZj5bRLhnBpydKnMuMnk1wZQAqUUQ3kwE")!;

const ADDRESS_WITH_EXPIRED_TOKEN = buildValidPolkadotAccountId("5FS47HBMnYYav1qGz4m5suiAF8zCuMreDPoKuYCxCyzMDtRv")!;
const ADDRESS_WITH_EXPIRED_TOKEN = ValidAccountId.polkadot("5FS47HBMnYYav1qGz4m5suiAF8zCuMreDPoKuYCxCyzMDtRv")!;

const addresses = [
ADDRESS_WITH_VALID_TOKEN,
Expand All @@ -179,7 +178,7 @@ describe("AccountTokens", () => {
}
};

const OTHER_ADDRESS = buildValidPolkadotAccountId("5FxbV4wTw4PRjKWHQrjwb43ZZUqjPWWZ3RXRb1NdWsi7T4Xn")!;
const OTHER_ADDRESS = ValidAccountId.polkadot("5FxbV4wTw4PRjKWHQrjwb43ZZUqjPWWZ3RXRb1NdWsi7T4Xn")!;

const otherTokensRecord: Record<string, Token> = {
[OTHER_ADDRESS.toKey()]: {
Expand Down
3 changes: 1 addition & 2 deletions packages/client/test/Balance.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
BOB,
buildTestAuthenticatedSharedSate,
SUCCESSFUL_SUBMISSION,
buildValidPolkadotAccountId,
buildSimpleNodeApi
} from "./Utils.js";
import { AccountTokens, LogionClient, Transaction, AxiosFactory, BalanceState, Signer, LegalOfficerClass, BackendTransaction } from "../src/index.js";
Expand Down Expand Up @@ -323,7 +322,7 @@ describe("Balance", () => {
})
})

const REQUESTER_ADDRESS = buildValidPolkadotAccountId("5ERRWWYABvYjyUG2oLCNifkmcCQT44ijPpQNxtwZZFj86Jjd")!;
const REQUESTER_ADDRESS = ValidAccountId.polkadot("5ERRWWYABvYjyUG2oLCNifkmcCQT44ijPpQNxtwZZFj86Jjd")!;

function setupFetchTransactions(axiosFactory: Mock<AxiosFactory>, transactions: BackendTransaction[], address: string) {
const axios = new Mock<AxiosInstance>();
Expand Down
3 changes: 1 addition & 2 deletions packages/client/test/Loc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ import {
REQUESTER,
SUCCESSFUL_SUBMISSION,
buildSimpleNodeApi,
buildValidPolkadotAccountId,
ItIsUuid,
mockCodecWithToString,
MOCK_FILE,
Expand Down Expand Up @@ -1293,7 +1292,7 @@ async function getVoidedCollectionLoc() {
function expectDataToMatch(data: LocData, request: LocRequest) {
expect(data.id.toString()).toBe(request.id.toString());
expect(data.ownerAccountId.address).toBe(request.ownerAddress);
expect(data.requesterAccountId).toEqual(buildValidPolkadotAccountId(request.requesterAddress?.address));
expect(data.requesterAccountId?.equals(request.requesterAddress)).toBeTrue();
expect(data.requesterLocId?.toString()).toBe(request.requesterIdentityLoc ? request.requesterIdentityLoc : undefined);
expect(data.description).toBe(request.description);
expect(data.locType).toBe(request.locType);
Expand Down
26 changes: 21 additions & 5 deletions packages/client/test/LocUtils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
import { CollectionItem, Hash, LegalOfficerCase, Lgnt, LocBatch, LocType, UUID, ValidAccountId, VerifiedIssuerType, VoidInfo } from "@logion/node-api";
import {
CollectionItem,
Hash,
LegalOfficerCase,
Lgnt,
LocBatch,
LocType,
UUID,
ValidAccountId,
VerifiedIssuerType,
VoidInfo
} from "@logion/node-api";
import { DateTime } from "luxon";
import { Mock } from "moq.ts";

import { HashString, LocFile, LocLink, LocRequest, LocRequestStatus, OffchainCollectionItem, UploadableItemFile } from "../src/index.js";
import {
REQUESTER,
buildValidPolkadotAccountId
} from "./Utils.js";
HashString,
LocFile,
LocLink,
LocRequest,
LocRequestStatus,
OffchainCollectionItem,
UploadableItemFile
} from "../src/index.js";
import { REQUESTER, } from "./Utils.js";

export const EXISTING_FILE_HASH = Hash.fromHex("0xa4d9f9f1a02baae960d1a7c4cedb25940a414ae4c545bf2f14ab24691fec09a5");

Expand Down
1 change: 0 additions & 1 deletion packages/client/test/Recovery.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import {
REQUESTER,
RECOVERED_ADDRESS,
buildSimpleNodeApi,
buildValidPolkadotAccountId,
REQUESTER_ID_LOC_ALICE,
REQUESTER_ID_LOC_BOB,
} from './Utils.js';
Expand Down
Loading
Loading