diff --git a/src/commands/confirm.command.ts b/src/commands/confirm.command.ts index 42ab4dc..4d84159 100644 --- a/src/commands/confirm.command.ts +++ b/src/commands/confirm.command.ts @@ -1,5 +1,3 @@ -import * as program from 'commander'; - import { ApiService } from 'jslib-common/abstractions/api.service'; import { CryptoService } from 'jslib-common/abstractions/crypto.service'; @@ -12,35 +10,36 @@ import { Utils } from 'jslib-common/misc/utils'; export class ConfirmCommand { constructor(private apiService: ApiService, private cryptoService: CryptoService) { } - async run(object: string, id: string, cmd: program.Command | any): Promise { + async run(object: string, id: string, cmdOptions: Record): Promise { if (id != null) { id = id.toLowerCase(); } + const normalizedOptions = this.normalizeOptions(cmdOptions); switch (object.toLowerCase()) { case 'org-member': - return await this.confirmOrganizationMember(id, cmd); + return await this.confirmOrganizationMember(id, normalizedOptions); default: return Response.badRequest('Unknown object.'); } } - private async confirmOrganizationMember(id: string, options: program.OptionValues) { - if (options.organizationid == null || options.organizationid === '') { - return Response.badRequest('--organizationid required.'); + private async confirmOrganizationMember(id: string, options: Options) { + if (options.organizationId == null || options.organizationId === '') { + return Response.badRequest('`organizationid` option is required.'); } if (!Utils.isGuid(id)) { - return Response.error('`' + id + '` is not a GUID.'); + return Response.badRequest('`' + id + '` is not a GUID.'); } - if (!Utils.isGuid(options.organizationid)) { - return Response.error('`' + options.organizationid + '` is not a GUID.'); + if (!Utils.isGuid(options.organizationId)) { + return Response.badRequest('`' + options.organizationId + '` is not a GUID.'); } try { - const orgKey = await this.cryptoService.getOrgKey(options.organizationid); + const orgKey = await this.cryptoService.getOrgKey(options.organizationId); if (orgKey == null) { throw new Error('No encryption key for this organization.'); } - const orgUser = await this.apiService.getOrganizationUser(options.organizationid, id); + const orgUser = await this.apiService.getOrganizationUser(options.organizationId, id); if (orgUser == null) { throw new Error('Member id does not exist for this organization.'); } @@ -49,10 +48,20 @@ export class ConfirmCommand { const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer); const req = new OrganizationUserConfirmRequest(); req.key = key.encryptedString; - await this.apiService.postOrganizationUserConfirm(options.organizationid, id, req); + await this.apiService.postOrganizationUserConfirm(options.organizationId, id, req); return Response.success(); } catch (e) { return Response.error(e); } } + + private normalizeOptions(passedOptions: Record): Options { + const typedOptions = new Options(); + typedOptions.organizationId = passedOptions.organizationid || passedOptions.organizationId; + return typedOptions; + } +} + +class Options { + organizationId: string; } diff --git a/src/commands/create.command.ts b/src/commands/create.command.ts index bc59b9b..99fdc20 100644 --- a/src/commands/create.command.ts +++ b/src/commands/create.command.ts @@ -1,4 +1,3 @@ -import * as program from 'commander'; import * as fs from 'fs'; import * as path from 'path'; @@ -32,9 +31,10 @@ export class CreateCommand { private userService: UserService, private cryptoService: CryptoService, private apiService: ApiService) { } - async run(object: string, requestJson: any, cmd: program.Command | any, + async run(object: string, requestJson: any, cmdOptions: Record, additionalData: any = null): Promise { let req: any = null; + if (object !== 'attachment') { if (requestJson == null || requestJson === '') { requestJson = await CliUtils.readStdin(); @@ -56,15 +56,16 @@ export class CreateCommand { } } + const normalizedOptions = this.normalizeOptions(cmdOptions); switch (object.toLowerCase()) { case 'item': return await this.createCipher(req); case 'attachment': - return await this.createAttachment(cmd, additionalData); + return await this.createAttachment(normalizedOptions, additionalData); case 'folder': return await this.createFolder(req); case 'org-collection': - return await this.createOrganizationCollection(req, cmd); + return await this.createOrganizationCollection(req, normalizedOptions); default: return Response.badRequest('Unknown object.'); } @@ -83,9 +84,9 @@ export class CreateCommand { } } - private async createAttachment(options: program.OptionValues, additionalData: any) { - if (options.itemid == null || options.itemid === '') { - return Response.badRequest('--itemid required.'); + private async createAttachment(options: Options, additionalData: any) { + if (options.itemId == null || options.itemId === '') { + return Response.badRequest('`itemid` option is required.'); } let fileBuf: Buffer = null; let fileName: string = null; @@ -94,7 +95,7 @@ export class CreateCommand { fileName = additionalData.fileName; } else { if (options.file == null || options.file === '') { - return Response.badRequest('--file required.'); + return Response.badRequest('`file` option is required.'); } const filePath = path.resolve(options.file); if (!fs.existsSync(options.file)) { @@ -111,7 +112,7 @@ export class CreateCommand { return Response.badRequest('File name not provided.'); } - const itemId = options.itemid.toLowerCase(); + const itemId = options.itemId.toLowerCase(); const cipher = await this.cipherService.get(itemId); if (cipher == null) { return Response.notFound(); @@ -151,15 +152,15 @@ export class CreateCommand { } } - private async createOrganizationCollection(req: OrganizationCollectionRequest, options: program.OptionValues) { - if (options.organizationid == null || options.organizationid === '') { - return Response.badRequest('--organizationid required.'); + private async createOrganizationCollection(req: OrganizationCollectionRequest, options: Options) { + if (options.organizationId == null || options.organizationId === '') { + return Response.badRequest('`organizationid` option is required.'); } - if (!Utils.isGuid(options.organizationid)) { - return Response.error('`' + options.organizationid + '` is not a GUID.'); + if (!Utils.isGuid(options.organizationId)) { + return Response.badRequest('`' + options.organizationId + '` is not a GUID.'); } - if (options.organizationid !== req.organizationId) { - return Response.error('--organizationid does not match request object.'); + if (options.organizationId !== req.organizationId) { + return Response.badRequest('`organizationid` option does not match request object.'); } try { const orgKey = await this.cryptoService.getOrgKey(req.organizationId); @@ -182,4 +183,18 @@ export class CreateCommand { return Response.error(e); } } + + private normalizeOptions(passedOptions: Record): Options { + const typedOptions = new Options(); + typedOptions.organizationId = passedOptions.organizationid || passedOptions.organizationId; + typedOptions.itemId = passedOptions.itemid || passedOptions.itemId; + typedOptions.file = passedOptions.file; + return typedOptions; + } +} + +class Options { + itemId: string; + organizationId: string; + file: string; } diff --git a/src/commands/delete.command.ts b/src/commands/delete.command.ts index f4abc48..e25b29d 100644 --- a/src/commands/delete.command.ts +++ b/src/commands/delete.command.ts @@ -1,5 +1,3 @@ -import * as program from 'commander'; - import { ApiService } from 'jslib-common/abstractions/api.service'; import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { FolderService } from 'jslib-common/abstractions/folder.service'; @@ -9,30 +7,33 @@ import { Response } from 'jslib-node/cli/models/response'; import { Utils } from 'jslib-common/misc/utils'; +import { CliUtils } from '../utils'; + export class DeleteCommand { constructor(private cipherService: CipherService, private folderService: FolderService, private userService: UserService, private apiService: ApiService) { } - async run(object: string, id: string, cmd: program.Command | any): Promise { + async run(object: string, id: string, cmdOptions: Record): Promise { if (id != null) { id = id.toLowerCase(); } + const normalizedOptions = this.normalizeOptions(cmdOptions); switch (object.toLowerCase()) { case 'item': - return await this.deleteCipher(id, cmd); + return await this.deleteCipher(id, normalizedOptions); case 'attachment': - return await this.deleteAttachment(id, cmd); + return await this.deleteAttachment(id, normalizedOptions); case 'folder': return await this.deleteFolder(id); case 'org-collection': - return await this.deleteOrganizationCollection(id, cmd); + return await this.deleteOrganizationCollection(id, normalizedOptions); default: return Response.badRequest('Unknown object.'); } } - private async deleteCipher(id: string, options: program.OptionValues) { + private async deleteCipher(id: string, options: Options) { const cipher = await this.cipherService.get(id); if (cipher == null) { return Response.notFound(); @@ -50,12 +51,12 @@ export class DeleteCommand { } } - private async deleteAttachment(id: string, options: program.OptionValues) { - if (options.itemid == null || options.itemid === '') { - return Response.badRequest('--itemid required.'); + private async deleteAttachment(id: string, options: Options) { + if (options.itemId == null || options.itemId === '') { + return Response.badRequest('`itemid` option is required.'); } - const itemId = options.itemid.toLowerCase(); + const itemId = options.itemId.toLowerCase(); const cipher = await this.cipherService.get(itemId); if (cipher == null) { return Response.notFound(); @@ -96,21 +97,35 @@ export class DeleteCommand { } } - private async deleteOrganizationCollection(id: string, options: program.OptionValues) { - if (options.organizationid == null || options.organizationid === '') { - return Response.badRequest('--organizationid required.'); + private async deleteOrganizationCollection(id: string, options: Options) { + if (options.organizationId == null || options.organizationId === '') { + return Response.badRequest('`organizationid` option is required.'); } if (!Utils.isGuid(id)) { - return Response.error('`' + id + '` is not a GUID.'); + return Response.badRequest('`' + id + '` is not a GUID.'); } - if (!Utils.isGuid(options.organizationid)) { - return Response.error('`' + options.organizationid + '` is not a GUID.'); + if (!Utils.isGuid(options.organizationId)) { + return Response.badRequest('`' + options.organizationId + '` is not a GUID.'); } try { - await this.apiService.deleteCollection(options.organizationid, id); + await this.apiService.deleteCollection(options.organizationId, id); return Response.success(); } catch (e) { return Response.error(e); } } + + private normalizeOptions(passedOptions: Record): Options { + const typedOptions = new Options(); + typedOptions.organizationId = passedOptions.organizationid || passedOptions.organizationId; + typedOptions.itemId = passedOptions.itemid || passedOptions.itemId; + typedOptions.permanent = CliUtils.convertBooleanOption(passedOptions.permanent); + return typedOptions; + } +} + +class Options { + itemId: string; + organizationId: string; + permanent: boolean; } diff --git a/src/commands/edit.command.ts b/src/commands/edit.command.ts index 3427954..07b072b 100644 --- a/src/commands/edit.command.ts +++ b/src/commands/edit.command.ts @@ -28,7 +28,7 @@ export class EditCommand { constructor(private cipherService: CipherService, private folderService: FolderService, private cryptoService: CryptoService, private apiService: ApiService) { } - async run(object: string, id: string, requestJson: any, cmd: program.Command | any): Promise { + async run(object: string, id: string, requestJson: any, cmdOptions: Record): Promise { if (requestJson == null || requestJson === '') { requestJson = await CliUtils.readStdin(); } @@ -53,6 +53,7 @@ export class EditCommand { id = id.toLowerCase(); } + const normalizedOptions = this.normalizeOptions(cmdOptions); switch (object.toLowerCase()) { case 'item': return await this.editCipher(id, req); @@ -61,7 +62,7 @@ export class EditCommand { case 'folder': return await this.editFolder(id, req); case 'org-collection': - return await this.editOrganizationCollection(id, req, cmd); + return await this.editOrganizationCollection(id, req, normalizedOptions); default: return Response.badRequest('Unknown object.'); } @@ -75,7 +76,7 @@ export class EditCommand { let cipherView = await cipher.decrypt(); if (cipherView.isDeleted) { - return Response.badRequest('You may not edit a deleted cipher. Use restore item command first.'); + return Response.badRequest('You may not edit a deleted item. Use the restore command first.'); } cipherView = Cipher.toView(req, cipherView); const encCipher = await this.cipherService.encrypt(cipherView); @@ -96,7 +97,7 @@ export class EditCommand { return Response.notFound(); } if (cipher.organizationId == null) { - return Response.badRequest('Item does not belong to an organization. Consider sharing it first.'); + return Response.badRequest('Item does not belong to an organization. Consider moving it first.'); } cipher.collectionIds = req; @@ -131,18 +132,18 @@ export class EditCommand { } } - private async editOrganizationCollection(id: string, req: OrganizationCollectionRequest, options: program.OptionValues) { - if (options.organizationid == null || options.organizationid === '') { - return Response.badRequest('--organizationid required.'); + private async editOrganizationCollection(id: string, req: OrganizationCollectionRequest, options: Options) { + if (options.organizationId == null || options.organizationId === '') { + return Response.badRequest('`organizationid` option is required.'); } if (!Utils.isGuid(id)) { - return Response.error('`' + id + '` is not a GUID.'); + return Response.badRequest('`' + id + '` is not a GUID.'); } - if (!Utils.isGuid(options.organizationid)) { - return Response.error('`' + options.organizationid + '` is not a GUID.'); + if (!Utils.isGuid(options.organizationId)) { + return Response.badRequest('`' + options.organizationId + '` is not a GUID.'); } - if (options.organizationid !== req.organizationId) { - return Response.error('--organizationid does not match request object.'); + if (options.organizationId !== req.organizationId) { + return Response.badRequest('`organizationid` option does not match request object.'); } try { const orgKey = await this.cryptoService.getOrgKey(req.organizationId); @@ -165,4 +166,14 @@ export class EditCommand { return Response.error(e); } } + + private normalizeOptions(passedOptions: Record): Options { + const typedOptions = new Options(); + typedOptions.organizationId = passedOptions.organizationid || passedOptions.organizationId; + return typedOptions; + } +} + +class Options { + organizationId: string; } diff --git a/src/commands/encode.command.ts b/src/commands/encode.command.ts index 885700e..42eefe6 100644 --- a/src/commands/encode.command.ts +++ b/src/commands/encode.command.ts @@ -1,5 +1,3 @@ -import * as program from 'commander'; - import { Response } from 'jslib-node/cli/models/response'; import { StringResponse } from 'jslib-node/cli/models/response/stringResponse'; diff --git a/src/commands/generate.command.ts b/src/commands/generate.command.ts index a4a2e52..aab3a7a 100644 --- a/src/commands/generate.command.ts +++ b/src/commands/generate.command.ts @@ -1,43 +1,70 @@ -import * as program from 'commander'; - import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { Response } from 'jslib-node/cli/models/response'; import { StringResponse } from 'jslib-node/cli/models/response/stringResponse'; +import { CliUtils } from '../utils'; + export class GenerateCommand { constructor(private passwordGenerationService: PasswordGenerationService) { } - async run(cmdOptions: program.OptionValues | any): Promise { + async run(cmdOptions: Record): Promise { + const normalizedOptions = this.normalizeOptions(cmdOptions); const options = { - uppercase: cmdOptions.uppercase || false, - lowercase: cmdOptions.lowercase || false, - number: cmdOptions.number || false, - special: cmdOptions.special || false, - length: cmdOptions.length || 14, - type: cmdOptions.passphrase ? 'passphrase' : 'password', - wordSeparator: cmdOptions.separator == null ? '-' : cmdOptions.separator, - numWords: cmdOptions.words || 3, + uppercase: normalizedOptions.uppercase, + lowercase: normalizedOptions.lowercase, + number: normalizedOptions.number, + special: normalizedOptions.special, + length: normalizedOptions.length, + type: normalizedOptions.type, + wordSeparator: normalizedOptions.separator, + numWords: normalizedOptions.words, }; - if (!options.uppercase && !options.lowercase && !options.special && !options.number) { - options.lowercase = true; - options.uppercase = true; - options.number = true; - } - if (options.length < 5) { - options.length = 5; - } - if (options.numWords < 3) { - options.numWords = 3; - } - if (options.wordSeparator === 'space') { - options.wordSeparator = ' '; - } else if (options.wordSeparator != null && options.wordSeparator.length > 1) { - options.wordSeparator = options.wordSeparator[0]; - } const enforcedOptions = await this.passwordGenerationService.enforcePasswordGeneratorPoliciesOnOptions(options); const password = await this.passwordGenerationService.generatePassword(enforcedOptions[0]); const res = new StringResponse(password); return Response.success(res); } + + private normalizeOptions(passedOptions: Record): Options { + const typedOptions = new Options(); + typedOptions.uppercase = CliUtils.convertBooleanOption(passedOptions.uppercase); + typedOptions.lowercase = CliUtils.convertBooleanOption(passedOptions.lowercase); + typedOptions.number = CliUtils.convertBooleanOption(passedOptions.number); + typedOptions.special = CliUtils.convertBooleanOption(passedOptions.special); + typedOptions.length = passedOptions.length != null ? parseInt(passedOptions.length, null) : 14; + typedOptions.type = passedOptions.passphrase ? 'passphrase' : 'password'; + typedOptions.separator = passedOptions.separator == null ? '-' : passedOptions.separator + ''; + typedOptions.words = passedOptions.words != null ? parseInt(passedOptions.words, null) : 3; + + if (!typedOptions.uppercase && !typedOptions.lowercase && !typedOptions.special && !typedOptions.number) { + typedOptions.lowercase = true; + typedOptions.uppercase = true; + typedOptions.number = true; + } + if (typedOptions.length < 5) { + typedOptions.length = 5; + } + if (typedOptions.words < 3) { + typedOptions.words = 3; + } + if (typedOptions.separator === 'space') { + typedOptions.separator = ' '; + } else if (typedOptions.separator != null && typedOptions.separator.length > 1) { + typedOptions.separator = typedOptions.separator[0]; + } + + return typedOptions; + } +} + +class Options { + uppercase: boolean; + lowercase: boolean; + number: boolean; + special: boolean; + length: number; + type: 'passphrase' | 'password'; + separator: string; + words: number; } diff --git a/src/utils.ts b/src/utils.ts index c9ef1ce..2582dc1 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -170,4 +170,8 @@ export class CliUtils { return false; }); } + + static convertBooleanOption(optionValue: any) { + return optionValue || optionValue === '' ? true : false; + } }