mirror of
https://github.com/bitwarden/cli
synced 2026-01-03 16:53:14 +00:00
normalize options parsing and error messages
This commit is contained in:
@@ -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<Response> {
|
||||
async run(object: string, id: string, cmdOptions: Record<string, any>): Promise<Response> {
|
||||
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 <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<string, any>): Options {
|
||||
const typedOptions = new Options();
|
||||
typedOptions.organizationId = passedOptions.organizationid || passedOptions.organizationId;
|
||||
return typedOptions;
|
||||
}
|
||||
}
|
||||
|
||||
class Options {
|
||||
organizationId: string;
|
||||
}
|
||||
|
||||
@@ -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<string, any>,
|
||||
additionalData: any = null): Promise<Response> {
|
||||
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 <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 <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 <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 <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<string, any>): 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;
|
||||
}
|
||||
|
||||
@@ -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<Response> {
|
||||
async run(object: string, id: string, cmdOptions: Record<string, any>): Promise<Response> {
|
||||
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 <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 <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<string, any>): 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;
|
||||
}
|
||||
|
||||
@@ -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<Response> {
|
||||
async run(object: string, id: string, requestJson: any, cmdOptions: Record<string, any>): Promise<Response> {
|
||||
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 <id> 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 <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 <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<string, any>): Options {
|
||||
const typedOptions = new Options();
|
||||
typedOptions.organizationId = passedOptions.organizationid || passedOptions.organizationId;
|
||||
return typedOptions;
|
||||
}
|
||||
}
|
||||
|
||||
class Options {
|
||||
organizationId: string;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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<Response> {
|
||||
async run(cmdOptions: Record<string, any>): Promise<Response> {
|
||||
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<string, any>): 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;
|
||||
}
|
||||
|
||||
@@ -170,4 +170,8 @@ export class CliUtils {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
static convertBooleanOption(optionValue: any) {
|
||||
return optionValue || optionValue === '' ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user