-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cli): support
config:forger:bip39
command (#418)
* support `config:forger:bip39` command * style: resolve style guide violations --------- Co-authored-by: oXtxNt9U <[email protected]>
- Loading branch information
Showing
6 changed files
with
393 additions
and
98 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { Console, describe } from "@mainsail/test-framework"; | ||
import { ensureDirSync, writeJSONSync } from "fs-extra"; | ||
import prompts from "prompts"; | ||
import { dirSync, setGracefulCleanup } from "tmp"; | ||
|
||
import { Command } from "./config-forger-bip38"; | ||
import { Command as BIP39Command } from "./config-forger-bip39"; | ||
|
||
describe<{ | ||
cli: Console; | ||
}>("ConfigForgerBIP38Command", ({ beforeEach, afterAll, it, assert }) => { | ||
const bip39 = "clay harbor enemy utility margin pretty hub comic piece aerobic umbrella acquire"; | ||
const bip39Flags = "venue below waste gather spin cruise title still boost mother flash tuna"; | ||
const bip39Prompt = "craft imitate step mixture patch forest volcano business charge around girl confirm"; | ||
|
||
beforeEach((context) => { | ||
process.env.CORE_PATH_CONFIG = dirSync().name; | ||
|
||
ensureDirSync(`${process.env.CORE_PATH_CONFIG}/mainsail/`); | ||
writeJSONSync(`${process.env.CORE_PATH_CONFIG}/mainsail/validators.json`, {}); | ||
|
||
context.cli = new Console(); | ||
}); | ||
|
||
afterAll(() => setGracefulCleanup()); | ||
|
||
it("should configure from flags", async ({ cli }) => { | ||
await cli.withFlags({ bip39: bip39Flags, password: "password" }).execute(Command); | ||
|
||
assert.equal(require(`${process.env.CORE_PATH_CONFIG}/mainsail/validators.json`), { secrets: [] }); | ||
}); | ||
|
||
it("should configure from a prompt if it receives a valid bip39 and confirmation", async ({ cli }) => { | ||
prompts.inject([bip39Prompt, "password", "password"]); | ||
|
||
await cli.execute(Command); | ||
|
||
assert.equal(require(`${process.env.CORE_PATH_CONFIG}/mainsail/validators.json`), { secrets: [] }); | ||
}); | ||
|
||
it("should fail to configure from a prompt if it receives an invalid bip39", async ({ cli }) => { | ||
await cli.withFlags({ bip39 }).execute(BIP39Command); | ||
|
||
prompts.inject(["random-string", "password", "password"]); | ||
|
||
await assert.rejects(() => cli.execute(Command), "Failed to verify the given passphrase as BIP39 compliant."); | ||
|
||
assert.equal(require(`${process.env.CORE_PATH_CONFIG}/mainsail/validators.json`), { secrets: [bip39] }); | ||
}); | ||
|
||
it("should configure from a prompt if it receives an invalid bip39 and skipValidation flag is set", async ({ | ||
cli, | ||
}) => { | ||
await cli.withFlags({ bip39 }).execute(BIP39Command); | ||
|
||
prompts.inject(["random-string", "password", "password"]); | ||
|
||
await cli.withFlags({ skipValidation: true }).execute(Command); | ||
|
||
assert.equal(require(`${process.env.CORE_PATH_CONFIG}/mainsail/validators.json`), { | ||
secrets: [], | ||
}); | ||
}); | ||
|
||
it("should fail to configure from a prompt if it doesn't receive a bip39", async ({ cli }) => { | ||
prompts.inject([null, true]); | ||
|
||
await assert.rejects(() => cli.execute(Command), "Failed to verify the given passphrase as BIP39 compliant."); | ||
}); | ||
|
||
it("should fail to configure from a prompt if it doesn't receive a valid bip39", async ({ cli }) => { | ||
await assert.rejects( | ||
() => cli.withFlags({ bip39: "random-string" }).execute(Command), | ||
"Failed to verify the given passphrase as BIP39 compliant.", | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { Commands, Contracts } from "@mainsail/cli"; | ||
import { injectable } from "@mainsail/container"; | ||
import { validateMnemonic } from "bip39"; | ||
import { writeJSONSync } from "fs-extra"; | ||
import Joi from "joi"; | ||
|
||
@injectable() | ||
export class Command extends Commands.Command { | ||
public signature = "config:forger:bip38"; | ||
|
||
public description = "Configure the forging validator (BIP38)."; | ||
|
||
public isHidden = true; | ||
|
||
public configure(): void { | ||
this.definition | ||
.setFlag("token", "The name of the token.", Joi.string()) | ||
.setFlag("network", "The name of the network.", Joi.string()) | ||
.setFlag("bip39", "A validator plain text passphrase. Referred to as BIP39.", Joi.string()) | ||
.setFlag("password", "A custom password that encrypts the BIP39. Referred to as BIP38.", Joi.string()) | ||
.setFlag("skipValidation", "Skip BIP39 mnemonic validation", Joi.boolean().default(false)); | ||
} | ||
|
||
public async execute(): Promise<void> { | ||
if (this.hasFlag("bip39") && this.hasFlag("password")) { | ||
return this.performConfiguration(this.getFlags()); | ||
} | ||
|
||
const response = await this.components.prompt([ | ||
{ | ||
message: "Please enter your validator plain text passphrase. Referred to as BIP39.", | ||
name: "bip39", | ||
type: "password", | ||
validate: (value) => | ||
!validateMnemonic(value) && !this.getFlag("skipValidation") | ||
? `Failed to verify the given passphrase as BIP39 compliant.` | ||
: true, | ||
}, | ||
{ | ||
message: "Please enter your custom password that encrypts the BIP39. Referred to as BIP38.", | ||
name: "password", | ||
type: "password", | ||
validate: (value) => (typeof value !== "string" ? "The BIP38 password has to be a string." : true), | ||
}, | ||
]); | ||
|
||
await this.components.prompt([ | ||
{ | ||
message: "Confirm custom password that encrypts the BIP39. Referred to as BIP38.", | ||
name: "passwordConfirmation", | ||
type: "password", | ||
validate: (value) => | ||
value !== response.password ? "Confirm password does not match BIP38 password." : true, | ||
}, | ||
]); | ||
|
||
if (!response.bip39) { | ||
throw new Error("Failed to verify the given passphrase as BIP39 compliant."); | ||
} | ||
|
||
if (!response.password) { | ||
throw new Error("The BIP38 password has to be a string."); | ||
} | ||
|
||
return this.performConfiguration({ ...this.getFlags(), ...response }); | ||
} | ||
|
||
private async performConfiguration(flags: Contracts.AnyObject): Promise<void> { | ||
//let decodedWIF; | ||
|
||
await this.components.taskList([ | ||
{ | ||
task: () => { | ||
if (!flags.bip39 || (!validateMnemonic(flags.bip39) && !flags.skipValidation)) { | ||
throw new Error(`Failed to verify the given passphrase as BIP39 compliant.`); | ||
} | ||
}, | ||
title: "Validating passphrase is BIP39 compliant.", | ||
}, | ||
{ | ||
task: () => { | ||
// decodedWIF = wif.decode(Identities.WIF.fromPassphrase(flags.bip39)); | ||
}, | ||
title: "Loading private key.", | ||
}, | ||
{ | ||
task: () => { | ||
const validatorsConfig = this.app.getCorePath("config", "validators.json"); | ||
|
||
const validators: Record<string, string | string[]> = require(validatorsConfig); | ||
// validators.bip38 = bip38.encrypt( | ||
// decodedWIF.privateKey, | ||
// decodedWIF.compressed, | ||
// flags.password, | ||
// ); | ||
validators.secrets = []; | ||
|
||
writeJSONSync(validatorsConfig, validators); | ||
}, | ||
title: "Writing BIP39 passphrase to configuration.", | ||
}, | ||
]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { Console, describe } from "@mainsail/test-framework"; | ||
import { ensureDirSync, writeJSONSync } from "fs-extra"; | ||
import prompts from "prompts"; | ||
import { dirSync, setGracefulCleanup } from "tmp"; | ||
|
||
import { Command } from "./config-forger-bip39"; | ||
|
||
describe<{ | ||
cli: Console; | ||
}>("ConfigForgerBIP39Command", ({ beforeEach, afterAll, it, assert }) => { | ||
const bip39 = "clay harbor enemy utility margin pretty hub comic piece aerobic umbrella acquire"; | ||
const bip39Flags = "venue below waste gather spin cruise title still boost mother flash tuna"; | ||
const bip39Prompt = "craft imitate step mixture patch forest volcano business charge around girl confirm"; | ||
|
||
beforeEach((context) => { | ||
process.env.CORE_PATH_CONFIG = dirSync().name; | ||
|
||
ensureDirSync(`${process.env.CORE_PATH_CONFIG}/mainsail/`); | ||
writeJSONSync(`${process.env.CORE_PATH_CONFIG}/mainsail/validators.json`, {}); | ||
|
||
context.cli = new Console(); | ||
}); | ||
|
||
afterAll(() => setGracefulCleanup()); | ||
|
||
it("should configure from flags", async ({ cli }) => { | ||
await cli.withFlags({ bip39: bip39Flags }).execute(Command); | ||
|
||
assert.equal(require(`${process.env.CORE_PATH_CONFIG}/mainsail/validators.json`), { secrets: [bip39Flags] }); | ||
}); | ||
|
||
it("should configure from a prompt if it receives a valid bip39 and confirmation", async ({ cli }) => { | ||
prompts.inject([bip39Prompt, true]); | ||
|
||
await cli.execute(Command); | ||
|
||
assert.equal(require(`${process.env.CORE_PATH_CONFIG}/mainsail/validators.json`), { secrets: [bip39Prompt] }); | ||
}); | ||
|
||
it("should fail to configure from a prompt if it receives a valid bip39 and but no confirmation", async ({ | ||
cli, | ||
}) => { | ||
await cli.withFlags({ bip39 }).execute(Command); | ||
|
||
prompts.inject([bip39Prompt, false]); | ||
|
||
await cli.execute(Command); | ||
|
||
assert.equal(require(`${process.env.CORE_PATH_CONFIG}/mainsail/validators.json`), { secrets: [bip39] }); | ||
}); | ||
|
||
it("should fail to configure from a prompt if it receives an invalid bip39", async ({ cli }) => { | ||
await cli.withFlags({ bip39 }).execute(Command); | ||
|
||
prompts.inject(["random-string", true]); | ||
|
||
await assert.rejects(() => cli.execute(Command), "Failed to verify the given passphrase as BIP39 compliant."); | ||
|
||
assert.equal(require(`${process.env.CORE_PATH_CONFIG}/mainsail/validators.json`), { secrets: [bip39] }); | ||
}); | ||
|
||
it("should configure from a prompt if it receives an invalid bip39 and skipValidation flag is set", async ({ | ||
cli, | ||
}) => { | ||
await cli.withFlags({ bip39 }).execute(Command); | ||
|
||
prompts.inject(["random-string", true]); | ||
|
||
await cli.withFlags({ skipValidation: true }).execute(Command); | ||
|
||
assert.equal(require(`${process.env.CORE_PATH_CONFIG}/mainsail/validators.json`), { | ||
secrets: ["random-string"], | ||
}); | ||
}); | ||
|
||
it("should fail to configure from a prompt if it doesn't receive a bip39", async ({ cli }) => { | ||
prompts.inject([null, true]); | ||
|
||
await assert.rejects(() => cli.execute(Command), "Failed to verify the given passphrase as BIP39 compliant."); | ||
}); | ||
|
||
it("should fail to configure from a prompt if it doesn't receive a valid bip39", async ({ cli }) => { | ||
await assert.rejects( | ||
() => cli.withFlags({ bip39: "random-string" }).execute(Command), | ||
"Failed to verify the given passphrase as BIP39 compliant.", | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { Commands, Contracts } from "@mainsail/cli"; | ||
import { injectable } from "@mainsail/container"; | ||
import { validateMnemonic } from "bip39"; | ||
import { writeJSONSync } from "fs-extra"; | ||
import Joi from "joi"; | ||
|
||
@injectable() | ||
export class Command extends Commands.Command { | ||
public signature = "config:forger:bip39"; | ||
|
||
public description = "Configure the forging validator (BIP39)."; | ||
|
||
public configure(): void { | ||
this.definition | ||
.setFlag("token", "The name of the token.", Joi.string()) | ||
.setFlag("network", "The name of the network.", Joi.string()) | ||
.setFlag("bip39", "A validator plain text passphrase. Referred to as BIP39.", Joi.string()) | ||
.setFlag("skipValidation", "Skip BIP39 mnemonic validation", Joi.boolean().default(false)); | ||
} | ||
|
||
public async execute(): Promise<void> { | ||
if (this.hasFlag("bip39")) { | ||
return this.performConfiguration(this.getFlags()); | ||
} | ||
|
||
const response = await this.components.prompt([ | ||
{ | ||
message: "Please enter your validator plain text passphrase. Referred to as BIP39.", | ||
name: "bip39", | ||
type: "password", | ||
validate: (value) => | ||
!validateMnemonic(value) && !this.getFlag("skipValidation") | ||
? `Failed to verify the given passphrase as BIP39 compliant.` | ||
: true, | ||
}, | ||
{ | ||
message: "Can you confirm?", | ||
name: "confirm", | ||
type: "confirm", | ||
}, | ||
]); | ||
|
||
if (response.confirm) { | ||
return this.performConfiguration({ ...this.getFlags(), ...response }); | ||
} | ||
} | ||
|
||
private async performConfiguration(flags: Contracts.AnyObject): Promise<void> { | ||
await this.components.taskList([ | ||
{ | ||
task: () => { | ||
if (!flags.bip39 || (!validateMnemonic(flags.bip39) && !flags.skipValidation)) { | ||
throw new Error(`Failed to verify the given passphrase as BIP39 compliant.`); | ||
} | ||
}, | ||
title: "Validating passphrase is BIP39 compliant.", | ||
}, | ||
{ | ||
task: () => { | ||
const validatorsConfig = this.app.getCorePath("config", "validators.json"); | ||
|
||
const validators: Record<string, string | string[]> = require(validatorsConfig); | ||
validators.secrets = [flags.bip39]; | ||
|
||
writeJSONSync(validatorsConfig, validators); | ||
}, | ||
title: "Writing BIP39 passphrase to configuration.", | ||
}, | ||
]); | ||
} | ||
} |
Oops, something went wrong.