diff --git a/package-lock.json b/package-lock.json index ade46dfaf6..b8bc759c96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,12 @@ "integrity": "sha512-MFFKFv2X4iZy/NFl1m1E8uwE1CR96SGwJjgHma09PLtqOWoj3nqeJHMG+P/EuJGVLvC2I6MdQRQsr4TcRduIow==", "dev": true }, + "@types/readline-sync": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@types/readline-sync/-/readline-sync-1.4.3.tgz", + "integrity": "sha512-YP9NVli96E+qQLAF2db+VjnAUEeZcFVg4YnMgr8kpDUFwQBnj31rPLOVHmazbKQhaIkJ9cMHsZhpKdzUeL0KTg==", + "dev": true + }, "acorn": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", @@ -3186,6 +3192,11 @@ "set-immediate-shim": "1.0.1" } }, + "readline-sync": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.9.tgz", + "integrity": "sha1-PtqOZfI80qF+YTAbHwADOWr17No=" + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", diff --git a/package.json b/package.json index cba9d4971c..5e07f81ca1 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "devDependencies": { "@types/commander": "^2.12.2", "@types/node": "^10.0.8", + "@types/readline-sync": "^1.4.3", "clean-webpack-plugin": "^0.1.17", "copy-webpack-plugin": "^4.2.0", "cross-env": "^5.1.4", @@ -49,6 +50,7 @@ "commander": "2.15.1", "node-fetch": "2.1.2", "node-forge": "0.7.1", - "node-localstorage": "1.3.1" + "node-localstorage": "1.3.1", + "readline-sync": "1.4.9" } } diff --git a/src/commands/login.command.ts b/src/commands/login.command.ts index 8938aa6b90..461b8916cd 100644 --- a/src/commands/login.command.ts +++ b/src/commands/login.command.ts @@ -1,4 +1,5 @@ import * as program from 'commander'; +import * as readline from 'readline-sync'; import { AuthResult } from 'jslib/models/domain/authResult'; @@ -10,9 +11,57 @@ export class LoginCommand { constructor(private authService: AuthService) { } async run(email: string, password: string, cmd: program.Command) { + if (email == null || email === '') { + email = readline.question('Email Address: '); + } + if (email == null || email.trim() === '') { + return Response.badRequest('Email address is required.'); + } + if (email.indexOf('@') === -1) { + return Response.badRequest('Email address is invalid.'); + } + + if (password == null || password === '') { + password = readline.question('Master Password: ', { + hideEchoBack: true, + mask: '*', + }); + } + if (password == null || password === '') { + return Response.badRequest('Master password is required.'); + } + try { - const result = await this.authService.logIn(email, password); - // TODO: 2FA + const response = await this.authService.logIn(email, password); + if (response.twoFactor) { + let selectedProvider: any = null; + const twoFactorProviders = this.authService.getSupportedTwoFactorProviders(null); + if (twoFactorProviders.length === 0) { + return Response.badRequest('No providers available for this client.'); + } + + if (twoFactorProviders.length === 1) { + selectedProvider = twoFactorProviders[0]; + } else { + const options = twoFactorProviders.map((p) => p.name); + const i = readline.keyInSelect(options, 'Two-step login method: ', { cancel: 'Cancel' }); + if (i < 0) { + return Response.error('Login failed.'); + } + selectedProvider = twoFactorProviders[i]; + } + + const twoFactorToken = readline.question('Two-step login token for ' + selectedProvider.name + ': '); + if (twoFactorToken == null || twoFactorToken === '') { + return Response.badRequest('Token is required.'); + } + + const twoFactorResponse = await this.authService.logInTwoFactor(selectedProvider.type, + twoFactorToken, false); + if (twoFactorResponse.twoFactor) { + return Response.error('Login failed.'); + } + } return Response.success(); } catch (e) { return Response.error(e); diff --git a/src/program.ts b/src/program.ts index dfff131d3f..0267ebc0ed 100644 --- a/src/program.ts +++ b/src/program.ts @@ -46,6 +46,20 @@ export class Program { // TODO }); + program + .command('lock') + .description('Lock the vault and destroy the current session token.') + .action((cmd) => { + // TODO + }); + + program + .command('unlock ') + .description('Unlock the vault and obtain a new session token.') + .action((cmd) => { + // TODO + }); + program .command('sync') .description('Sync user\'s vault from server.') @@ -117,23 +131,24 @@ export class Program { } private processResponse(response: Response, cmd: program.Command) { - if (response.success) { - if (response.data != null) { - if (response.data.object === 'string') { - process.stdout.write((response.data as StringResponse).data); - } else if (response.data.object === 'list') { - this.printJson((response.data as ListResponse).data, cmd); - } else if (response.data.object === 'template') { - this.printJson((response.data as TemplateResponse).template, cmd); - } else { - this.printJson(response.data, cmd); - } - } - process.exit(); - } else { + if (!response.success) { process.stdout.write(chalk.redBright(response.message)); process.exit(1); + return; } + + if (response.data != null) { + if (response.data.object === 'string') { + process.stdout.write((response.data as StringResponse).data); + } else if (response.data.object === 'list') { + this.printJson((response.data as ListResponse).data, cmd); + } else if (response.data.object === 'template') { + this.printJson((response.data as TemplateResponse).template, cmd); + } else { + this.printJson(response.data, cmd); + } + } + process.exit(); } private printJson(obj: any, cmd: program.Command) {