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

Add identity deletion to the CLI #308

Open
wants to merge 62 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
231043a
feat: convert connector to cli
sebbi08 Oct 17, 2024
72c4227
chore: revert eslint
sebbi08 Oct 17, 2024
dc527b3
chore: remove cli tests
sebbi08 Oct 17, 2024
203c04b
chore: update readmes
sebbi08 Oct 17, 2024
c214887
chore: provide cli in docker image
sebbi08 Oct 17, 2024
b6ad842
chore: revert eslint
sebbi08 Oct 17, 2024
c1c4eb7
chore: revert unneeded changes
sebbi08 Oct 17, 2024
38851ad
chore: fix security
sebbi08 Oct 17, 2024
19e0134
Merge branch 'main' into feature/convert_connector_to_cli
sebbi08 Oct 28, 2024
75acf8c
chore: add identity deletion and identity status to cli
sebbi08 Oct 28, 2024
933abfb
chore: pr comments
sebbi08 Oct 28, 2024
1fea68d
chore: pr comments
sebbi08 Oct 28, 2024
d47323b
chore: local connector without local link
sebbi08 Oct 28, 2024
5828a7c
chore: pr comments
sebbi08 Oct 28, 2024
3bf99f3
chore: new lines
sebbi08 Oct 28, 2024
9566a7d
chore: move create connector config to seperate file
sebbi08 Oct 28, 2024
0112991
chore: move create connector config to seperate file
sebbi08 Oct 28, 2024
ef54b1f
chore: update path
sebbi08 Oct 29, 2024
7c38655
chore: remvoe cli readme
sebbi08 Oct 29, 2024
7c56b43
chore: remove cli
sebbi08 Oct 29, 2024
4a75d79
Merge branch 'main' into feature/convert_connector_to_cli
sebbi08 Oct 29, 2024
20d4ff2
Merge branch 'feature/convert_connector_to_cli' into feature/identity…
sebbi08 Oct 29, 2024
54ea35e
feat: enable api for identity deletion in debug mode
sebbi08 Oct 29, 2024
e6ebc42
Merge branch 'main' into feature/identity_deletion
sebbi08 Oct 31, 2024
365dac0
Merge branch 'main' into feature/identity_deletion
sebbi08 Nov 7, 2024
e5cc6bf
chore: remove newline
sebbi08 Nov 7, 2024
f754c76
chore: remove newline
sebbi08 Nov 7, 2024
dc57dc9
chore: remove duplicate start command implementation
sebbi08 Nov 7, 2024
c3e42d1
chore: remove unneded function
sebbi08 Nov 7, 2024
67bee04
Merge branch 'main' into feature/identity_deletion
mergify[bot] Nov 8, 2024
cf250af
chore: remove unneded function
sebbi08 Nov 7, 2024
292bc2d
test: add tests for cli
sebbi08 Nov 8, 2024
adfaf88
chore: use connector mode
sebbi08 Nov 8, 2024
6131486
chore: use connector mode
sebbi08 Nov 8, 2024
f78f173
Merge branch 'main' into feature/identity_deletion
mergify[bot] Nov 8, 2024
55e34da
chore: remove identity deletion controller
sebbi08 Nov 11, 2024
e2390d4
Merge branch 'main' into feature/identity_deletion
mergify[bot] Nov 11, 2024
54bd244
Merge branch 'main' into feature/identity_deletion
mergify[bot] Nov 12, 2024
b916516
Merge branch 'main' into feature/identity_deletion
mergify[bot] Nov 12, 2024
dd38931
Merge branch 'main' into feature/identity_deletion
mergify[bot] Nov 12, 2024
2c55743
Merge branch 'main' into feature/identity_deletion
mergify[bot] Nov 13, 2024
015f5f5
Merge branch 'main' into feature/identity_deletion
mergify[bot] Nov 14, 2024
d4f960e
Merge branch 'main' into feature/identity_deletion
mergify[bot] Nov 19, 2024
4254679
Merge branch 'main' into feature/identity_deletion
mergify[bot] Nov 19, 2024
786b932
Merge branch 'main' into feature/identity_deletion
mergify[bot] Nov 19, 2024
f61d130
Merge branch 'main' into feature/identity_deletion
mergify[bot] Nov 19, 2024
d37e441
Merge branch 'main' into feature/identity_deletion
mergify[bot] Nov 26, 2024
98c8482
Merge branch 'main' into feature/identity_deletion
mergify[bot] Nov 26, 2024
f626bc9
Merge branch 'main' into feature/identity_deletion
mergify[bot] Nov 26, 2024
70be950
chore: pr comments
sebbi08 Nov 29, 2024
269a6e7
chore: remove waiting for approval as it is not posiible for connector
sebbi08 Dec 2, 2024
e4abf7a
Merge branch 'main' into feature/identity_deletion
mergify[bot] Dec 2, 2024
d77593b
Merge branch 'main' into feature/identity_deletion
mergify[bot] Dec 3, 2024
609d1e9
Merge branch 'main' into feature/identity_deletion
mergify[bot] Dec 6, 2024
1c43fed
Merge branch 'main' into feature/identity_deletion
mergify[bot] Dec 10, 2024
12b2993
Merge branch 'main' into feature/identity_deletion
mergify[bot] Dec 10, 2024
270d879
Merge branch 'main' into feature/identity_deletion
mergify[bot] Dec 16, 2024
7d1a8ae
Merge branch 'main' into feature/identity_deletion
mergify[bot] Dec 18, 2024
a377061
Merge branch 'main' into feature/identity_deletion
mergify[bot] Dec 18, 2024
e92b6c9
Merge branch 'main' into feature/identity_deletion
mergify[bot] Dec 19, 2024
28d582a
Merge branch 'main' into feature/identity_deletion
mergify[bot] Dec 19, 2024
521a3cd
Merge branch 'main' into feature/identity_deletion
mergify[bot] Dec 23, 2024
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
2 changes: 1 addition & 1 deletion src/ConnectorRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ export class ConnectorRuntime extends Runtime<ConnectorRuntimeConfig> {
}
}

protected async stop(): Promise<void> {
public async stop(): Promise<void> {
if (this.isStarted) {
try {
await super.stop();
Expand Down
49 changes: 48 additions & 1 deletion src/cli/BaseCommand.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import yargs from "yargs";
import { ConnectorRuntime } from "../ConnectorRuntime";
import { ConnectorRuntimeConfig } from "../ConnectorRuntimeConfig";
import { createConnectorConfig } from "../CreateConnectorConfig";

export interface ConfigFileOptions {
config: string | undefined;
config?: string;
}

export const configOptionBuilder = (yargs: yargs.Argv<{}>): yargs.Argv<ConfigFileOptions> => {
Expand All @@ -13,3 +16,47 @@ Can also be set via the CUSTOM_CONFIG_LOCATION env variable`,
demandOption: false
});
};

export abstract class BaseCommand {
private connectorConfig?: ConnectorRuntimeConfig;
protected cliRuntime?: ConnectorRuntime;
protected log = console;

public async run(configPath: string | undefined): Promise<any> {
if (configPath) {
process.env.CUSTOM_CONFIG_LOCATION = configPath;
}

try {
this.connectorConfig = createConnectorConfig();
this.connectorConfig.infrastructure.httpServer.enabled = false;
this.connectorConfig.modules.coreHttpApi.enabled = false;
this.connectorConfig.logging = {
appenders: {
console: { type: "console" }
},
categories: {
default: { appenders: ["console"], level: "OFF" }
}
};
return await this.runInternal(this.connectorConfig);
} catch (error: any) {
this.log.log("Error creating identity: ", error);
} finally {
if (this.cliRuntime) {
await this.cliRuntime.stop();
}
}
}

protected async createRuntime(): Promise<void> {
if (this.cliRuntime) {
return;
}
if (!this.connectorConfig) throw new Error("Connector config not initialized");
this.cliRuntime = await ConnectorRuntime.create(this.connectorConfig);
await this.cliRuntime.start();
}

protected abstract runInternal(connectorConfig: ConnectorRuntimeConfig): Promise<void>;
}
40 changes: 40 additions & 0 deletions src/cli/commands/identity/status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { DateTime } from "luxon";
import { CommandModule } from "yargs";
import { BaseCommand, ConfigFileOptions, configOptionBuilder } from "../../BaseCommand";

export const identityStatusHandler = async ({ config }: ConfigFileOptions): Promise<void> => {
await new IdentityStatus().run(config);
};
export const yargsIdentityStatusCommand: CommandModule<{}, ConfigFileOptions> = {
sebbi08 marked this conversation as resolved.
Show resolved Hide resolved
command: "status",
describe: "show the status of the identity",
sebbi08 marked this conversation as resolved.
Show resolved Hide resolved
handler: identityStatusHandler,
builder: configOptionBuilder
};

export class IdentityStatus extends BaseCommand {
britsta marked this conversation as resolved.
Show resolved Hide resolved
protected async runInternal(): Promise<void> {
await this.createRuntime();
if (!this.cliRuntime) {
throw new Error("Failed to inizitialize runtime");
sebbi08 marked this conversation as resolved.
Show resolved Hide resolved
}

try {
const identityInfoResult = await this.cliRuntime.getServices().transportServices.account.getIdentityInfo();
const identityDeletionProcessesResult = await this.cliRuntime.getServices().transportServices.identityDeletionProcesses.getActiveIdentityDeletionProcess();
sebbi08 marked this conversation as resolved.
Show resolved Hide resolved

const identityInfo = identityInfoResult.value;
let message = `Id: ${identityInfo.address}`;

if (identityDeletionProcessesResult.isSuccess) {
const identityDeletionProcesses = identityDeletionProcessesResult.value;
sebbi08 marked this conversation as resolved.
Show resolved Hide resolved
message += `\nIdentity deletion status: ${identityDeletionProcesses.status}`;
message += `\nEnd of approval period: ${DateTime.fromISO(identityDeletionProcesses.approvalPeriodEndsAt ?? "").toLocaleString()}`;
sebbi08 marked this conversation as resolved.
Show resolved Hide resolved
sebbi08 marked this conversation as resolved.
Show resolved Hide resolved
message += `\nEnd of grace period: ${DateTime.fromISO(identityDeletionProcesses.gracePeriodEndsAt ?? "").toLocaleString()}`;
sebbi08 marked this conversation as resolved.
Show resolved Hide resolved
}
this.log.log(message);
} catch (e: any) {
this.log.error(e);
}
}
}
29 changes: 29 additions & 0 deletions src/cli/commands/identityDeletion/cancel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { CommandModule } from "yargs";
import { BaseCommand, ConfigFileOptions, configOptionBuilder } from "../../BaseCommand";

export const identityDeletionCancelHandler = async ({ config }: ConfigFileOptions): Promise<void> => {
await new CancelIdentityDeletion().run(config);
};
export const yargsIdentityDeletionCancelCommand: CommandModule<{}, ConfigFileOptions> = {
sebbi08 marked this conversation as resolved.
Show resolved Hide resolved
command: "cancel",
describe: "cancel the identity deletion",
sebbi08 marked this conversation as resolved.
Show resolved Hide resolved
handler: identityDeletionCancelHandler,
builder: configOptionBuilder
};

export default class CancelIdentityDeletion extends BaseCommand {
protected async runInternal(): Promise<void> {
await this.createRuntime();
if (!this.cliRuntime) {
throw new Error("Failed to initialize runtime");
}

const identityDeletionCancelationResult = await this.cliRuntime.getServices().transportServices.identityDeletionProcesses.cancelIdentityDeletionProcess();

if (identityDeletionCancelationResult.isSuccess) {
this.log.log("Identity deletion canceled");
sebbi08 marked this conversation as resolved.
Show resolved Hide resolved
return;
}
this.log.log(identityDeletionCancelationResult.error.toString());
}
}
30 changes: 30 additions & 0 deletions src/cli/commands/identityDeletion/init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { CommandModule } from "yargs";
import { BaseCommand, ConfigFileOptions, configOptionBuilder } from "../../BaseCommand";

export const identityDeletionInitHandler = async ({ config }: ConfigFileOptions): Promise<void> => {
const command = new InitIdentityDeletion();
await command.run(config);
};
export const yargsIdentityDeletionInitCommand: CommandModule<{}, ConfigFileOptions> = {
sebbi08 marked this conversation as resolved.
Show resolved Hide resolved
command: "init",
describe: "initialize the identity deletion",
sebbi08 marked this conversation as resolved.
Show resolved Hide resolved
handler: identityDeletionInitHandler,
builder: configOptionBuilder
};

export default class InitIdentityDeletion extends BaseCommand {
protected async runInternal(): Promise<void> {
await this.createRuntime();
if (!this.cliRuntime) {
throw new Error("Failed to initialize runtime");
}

const identityDeletionInitResult = await this.cliRuntime.getServices().transportServices.identityDeletionProcesses.initiateIdentityDeletionProcess();

if (identityDeletionInitResult.isSuccess) {
this.log.log("Identity deletion initiated");
return;
}
this.log.error(identityDeletionInitResult.error.toString());
}
}
3 changes: 3 additions & 0 deletions src/cli/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export * from "./identity/status";
export * from "./identityDeletion/cancel";
export * from "./identityDeletion/init";
export * from "./startConnector";
22 changes: 21 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,29 @@

import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { startConnectorCommand } from "./cli/commands";
import { startConnectorCommand, yargsIdentityDeletionCancelCommand, yargsIdentityDeletionInitCommand, yargsIdentityStatusCommand } from "./cli/commands";

yargs(hideBin(process.argv))
.command({
command: "identity [command]",
describe: "Identity related commands",
builder: (yargs) => {
return yargs.command(yargsIdentityStatusCommand);
},
handler: () => {
yargs.showHelp("log");
}
})
.command({
command: "identityDeletion [command]",
describe: "Identity deletion related commands",
builder: (yargs) => {
return yargs.command(yargsIdentityDeletionInitCommand).command(yargsIdentityDeletionCancelCommand);
},
handler: () => {
yargs.showHelp("log");
}
})
.command(startConnectorCommand)
.demandCommand(1, 1, "Please specify a command")
.scriptName("")
Expand Down
37 changes: 37 additions & 0 deletions test/modules/cli/identity/identityStatus.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { sleep } from "@js-soft/ts-utils";
import { identityDeletionInitHandler, identityStatusHandler } from "../../../../dist/cli/commands";
import { resetDB, setupEnvironment } from "../setup";

describe("identity status", () => {
sebbi08 marked this conversation as resolved.
Show resolved Hide resolved
const identityStatusPattern = /Id: did:e:((([A-Za-z0-9]+(-[A-Za-z0-9]+)*)\.)+[a-z]{2,}|localhost):dids:[0-9a-f]{22}/;
beforeAll(() => {
sebbi08 marked this conversation as resolved.
Show resolved Hide resolved
setupEnvironment();
});

afterAll(async () => {
await resetDB();
});

afterEach(() => {
jest.resetAllMocks();
});

test("show identity status", async () => {
const consoleSpy = jest.spyOn(console, "log");
await identityStatusHandler({});
expect(consoleSpy).toHaveBeenCalledTimes(1);
expect(consoleSpy.mock.lastCall![0]).toMatch(identityStatusPattern);

await identityDeletionInitHandler({});
await sleep(1000);
expect(consoleSpy).toHaveBeenCalledWith("Identity deletion initiated");
expect(consoleSpy).toHaveBeenCalledTimes(2);

await identityStatusHandler({});
expect(consoleSpy).toHaveBeenCalledTimes(3);
expect(consoleSpy.mock.lastCall![0]).toMatch(identityStatusPattern);
expect(consoleSpy.mock.lastCall![0]).toContain("Identity deletion status: Approved");
expect(consoleSpy.mock.lastCall![0]).toMatch(/End of approval period:/);
expect(consoleSpy.mock.lastCall![0]).toMatch(/End of grace period:/);
});
});
30 changes: 30 additions & 0 deletions test/modules/cli/identityDeletion/identityDeletion.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { identityDeletionCancelHandler, identityDeletionInitHandler } from "../../../../dist/cli/commands";
import { resetDB, setupEnvironment } from "../setup";

describe("identity deletion", () => {
sebbi08 marked this conversation as resolved.
Show resolved Hide resolved
beforeAll(() => {
setupEnvironment();
});

afterAll(async () => {
await resetDB();
});

beforeEach(() => {
jest.resetAllMocks();
});

test("initiate identity deletion", async () => {
const consoleSpy = jest.spyOn(console, "log");
await identityDeletionInitHandler({});
expect(consoleSpy).toHaveBeenCalledWith("Identity deletion initiated");
expect(consoleSpy).toHaveBeenCalledTimes(1);
});

test("cancel identity deletion", async () => {
const consoleSpy = jest.spyOn(console, "log");
await identityDeletionCancelHandler({});
expect(consoleSpy).toHaveBeenCalledWith("Identity deletion canceled");
sebbi08 marked this conversation as resolved.
Show resolved Hide resolved
expect(consoleSpy).toHaveBeenCalledTimes(1);
});
});
26 changes: 26 additions & 0 deletions test/modules/cli/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { rm } from "fs/promises";
import { join } from "path";
import getPort from "../../lib/getPort";

export function setupEnvironment(): void {
process.env.database = JSON.stringify({
driver: "lokijs",
folder: "./",
dbName: `default${process.pid}`,
dbNamePrefix: "test-"
});
process.env.NODE_CONFIG_ENV = "test";
process.env.API_KEY = "test";
process.env["infrastructure:httpServer:port"] = getPort().toString();

process.env["transportLibrary:baseUrl"] = process.env["NMSHD_TEST_BASEURL"];
process.env["transportLibrary:platformClientId"] = process.env["NMSHD_TEST_CLIENTID"];
process.env["transportLibrary:platformClientSecret"] = process.env["NMSHD_TEST_CLIENTSECRET"];
}
export async function resetDB(): Promise<void> {
sebbi08 marked this conversation as resolved.
Show resolved Hide resolved
try {
await rm(join(__dirname, `../../../test-default${process.pid}.db`));
} catch (_e) {
// Ignore
sebbi08 marked this conversation as resolved.
Show resolved Hide resolved
}
}
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"target": "es2021",
"module": "commonjs",
"sourceMap": true,
"declarationMap": true,
"declaration": true,
"outDir": "dist",
"strict": true,
Expand Down