From 0635ed4bbe913058db9c55fb8d3f26ef4ba10820 Mon Sep 17 00:00:00 2001 From: nhartner Date: Thu, 10 Sep 2020 16:09:28 -0600 Subject: [PATCH] change the `sign` command to clear unsigned addresses by default add an option `-k, --keep-addresses` to override default and retain unsigned addresses --- README.md | 8 +++-- src/commands/Command.ts | 9 ++++-- src/commands/payid-sign.ts | 58 ++++++++++++++++++++++++++++++------- test/unit/signPayId.test.ts | 50 ++++++++++++++++++++++++++++++++ 4 files changed, 111 insertions(+), 14 deletions(-) create mode 100644 test/unit/signPayId.test.ts diff --git a/README.md b/README.md index 61584c9..7a29e96 100644 --- a/README.md +++ b/README.md @@ -142,8 +142,12 @@ Before you sign an PayID, you must either load the PayID using the `load` comman Once a PayID has been initialized or loaded, you can sign it using an [identity key](#identity-keys). You must either generate a new key, or load an existing one. Once your PayID has been loaded or initialized, and your identity key has been generated or loaded, you can sign the PayID using `sign` command. The `sign` command signs each of your PayID address -mappings using the loaded identity keys, and outputs the resulting PayID with a `verifiedAddress` field. Run the `save` -command to save your PayID, with signed addresses, to file. +mappings using the loaded identity keys, and outputs the resulting PayID with a `verifiedAddress` field. + +By default, the sign command clears the unsigned `addresses` from the results. If you wish to +retain unsigned addresses after signing, use `sign --keep-addresses` or `sign -k` instead. + +Finally, run the `save` command to save your PayID, with signed addresses, to file. ### Inspect a Verified PayID diff --git a/src/commands/Command.ts b/src/commands/Command.ts index c531d01..eaf55fd 100644 --- a/src/commands/Command.ts +++ b/src/commands/Command.ts @@ -30,10 +30,15 @@ abstract class Command { this.vorpal = vorpal } - public setup(): void { + /** + * Sets up and registers the vorpal command. + * + * @returns The registered command. + */ + public setup(): Vorpal.Command { // Register the concrete command to Vorpal. // Execute the concrete action inside a try/catch wrapper - this.vorpal.command(this.command(), this.description()).action( + return this.vorpal.command(this.command(), this.description()).action( async (args: Args): Promise => { await this.action(args).catch((error) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- error has any type diff --git a/src/commands/payid-sign.ts b/src/commands/payid-sign.ts index a8bfe85..76b3c19 100644 --- a/src/commands/payid-sign.ts +++ b/src/commands/payid-sign.ts @@ -3,8 +3,10 @@ import { signWithKeys, IdentityKeySigningParams, toKey, + PaymentInformation, } from '@payid-org/utils' import { JWKECKey, JWKOctKey, JWKOKPKey, JWKRSAKey } from 'jose' +import * as Vorpal from 'vorpal' import Command from './Command' @@ -12,7 +14,24 @@ import Command from './Command' * Signs the currently loaded PayID PaymentInformation using the loaded signings keys. */ export default class SignPayIdCommand extends Command { - protected async action(): Promise { + /** + * @override + */ + public setup(): Vorpal.Command { + return super + .setup() + .option( + '-k, --keep-addresses', + 'Keep the unverified addresses section after signing.', + ) + } + + /** + * @override + */ + protected async action(args: Vorpal.Args): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- Vorpal.options isn't typed + const isKeepAddresses: boolean = args.options['keep-addresses'] ?? false const info = this.getPaymentInfo() const payId = info.payId if (!payId) { @@ -28,15 +47,7 @@ export default class SignPayIdCommand extends Command { return } - const updatedAddresses = info.addresses.map((address) => { - const jws = signWithKeys(payId, address, signingKeys) - return convertToVerifiedAddress(jws) - }) - const updated = { - payId: info.payId, - addresses: info.addresses, - verifiedAddresses: updatedAddresses, - } + const updated = signPayId(info, signingKeys, isKeepAddresses) this.localStorage.setPaymentInfo(updated) this.logPaymentInfo(updated) @@ -70,6 +81,33 @@ export default class SignPayIdCommand extends Command { } } +/** + * Signs all the addresses for the given payment information and returns + * with verified address. + * + * @param info - The payment information to sign. + * @param signingKeys - The keys to sign with. + * @param isKeepAddresses - If true, the unverified addresses property will be retained instead of cleared. + * @returns A copy of the PaymentInformation but with verified addresses. + */ +export function signPayId( + info: PaymentInformation, + signingKeys: IdentityKeySigningParams[], + isKeepAddresses: boolean, +): PaymentInformation { + const payId = info.payId + const updatedAddresses = info.addresses.map((address) => { + const jws = signWithKeys(payId, address, signingKeys) + return convertToVerifiedAddress(jws) + }) + const updated = { + payId: info.payId, + addresses: isKeepAddresses ? info.addresses : [], + verifiedAddresses: updatedAddresses, + } + return updated +} + /** * Returns the default algorithm to use to sign with the given jwk. * diff --git a/test/unit/signPayId.test.ts b/test/unit/signPayId.test.ts new file mode 100644 index 0000000..5aa77c6 --- /dev/null +++ b/test/unit/signPayId.test.ts @@ -0,0 +1,50 @@ +import 'mocha' +import { + AddressDetailsType, + IdentityKeySigningParams, + PaymentInformation, +} from '@payid-org/utils' +import { assert } from 'chai' +import { JWK } from 'jose' + +import { signPayId } from '../../src/commands/payid-sign' + +const info: PaymentInformation = { + payId: 'boaty$mcboatface.com', + addresses: [ + { + paymentNetwork: 'boatcoin', + environment: 'seanet', + addressDetailsType: AddressDetailsType.CryptoAddress, + addressDetails: { + address: 'xyz12345', + }, + }, + ], + verifiedAddresses: [], +} + +describe('when signPayId()', function (): void { + let signingKey: IdentityKeySigningParams + + beforeEach('create key', async function (): Promise { + const key = await JWK.generate('EC', 'P-256') + signingKey = new IdentityKeySigningParams(key, 'ES256') + }) + + it('called with keepAddresses=true, then addresses property is retained', async function (): Promise< + void + > { + const result = signPayId(info, [signingKey], true) + assert.equal(result.addresses, info.addresses) + assert.lengthOf(result.verifiedAddresses, 1) + }) + + it('called with keepAddresses=false, then addresses property is cleared', async function (): Promise< + void + > { + const result = signPayId(info, [signingKey], false) + assert.isEmpty(result.addresses) + assert.lengthOf(result.verifiedAddresses, 1) + }) +})