diff --git a/src/commands/login.command.ts b/src/commands/login.command.ts index 461b8916cd..6c6e6f96d2 100644 --- a/src/commands/login.command.ts +++ b/src/commands/login.command.ts @@ -1,18 +1,22 @@ import * as program from 'commander'; import * as readline from 'readline-sync'; -import { AuthResult } from 'jslib/models/domain/authResult'; +import { TwoFactorProviderType } from 'jslib/enums/twoFactorProviderType'; +import { AuthResult } from 'jslib/models/domain/authResult'; +import { TwoFactorEmailRequest } from 'jslib/models/request/twoFactorEmailRequest'; + +import { ApiService } from 'jslib/abstractions/api.service'; import { AuthService } from 'jslib/abstractions/auth.service'; import { Response } from '../models/response'; export class LoginCommand { - constructor(private authService: AuthService) { } + constructor(private authService: AuthService, private apiService: ApiService) { } async run(email: string, password: string, cmd: program.Command) { if (email == null || email === '') { - email = readline.question('Email Address: '); + email = readline.question('Email address: '); } if (email == null || email.trim() === '') { return Response.badRequest('Email address is required.'); @@ -22,7 +26,7 @@ export class LoginCommand { } if (password == null || password === '') { - password = readline.question('Master Password: ', { + password = readline.question('Master password: ', { hideEchoBack: true, mask: '*', }); @@ -31,35 +35,70 @@ export class LoginCommand { return Response.badRequest('Master password is required.'); } + let twoFactorToken: string = cmd.code; + let twoFactorMethod: TwoFactorProviderType = null; try { - 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 (cmd.method != null) { + twoFactorMethod = parseInt(cmd.method, null); + } + } catch (e) { + return Response.error('Invalid two-step login method.'); + } - 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) { + try { + let response: AuthResult = null; + if (twoFactorToken != null && twoFactorMethod != null) { + response = await this.authService.logInComplete(email, password, twoFactorMethod, + twoFactorToken, false); + } else { + 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 (twoFactorMethod != null) { + try { + selectedProvider = twoFactorProviders.filter((p) => p.type === twoFactorMethod)[0]; + } catch (e) { + return Response.error('Invalid two-step login method.'); + } + } + + if (selectedProvider == null) { + 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]; + } + } + + if (twoFactorToken == null && response.twoFactorProviders.size > 1 && + selectedProvider.type === TwoFactorProviderType.Email) { + const emailReq = new TwoFactorEmailRequest(this.authService.email, + this.authService.masterPasswordHash); + await this.apiService.postTwoFactorEmail(emailReq); + } + + if (twoFactorToken == null) { + twoFactorToken = readline.question('Two-step login code for ' + selectedProvider.name + ': '); + if (twoFactorToken == null || twoFactorToken === '') { + return Response.badRequest('Code is required.'); + } + } + + const twoFactorResponse = await this.authService.logInTwoFactor(selectedProvider.type, + twoFactorToken, false); + if (twoFactorResponse.twoFactor) { 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(); diff --git a/src/program.ts b/src/program.ts index 0267ebc0ed..0506439ac7 100644 --- a/src/program.ts +++ b/src/program.ts @@ -29,12 +29,12 @@ export class Program { .option('--pretty', 'Format stdout.'); program - .command('login ') + .command('login [email] [password]') .description('Log into a Bitwarden user account.') - .option('-m, --method ', '2FA method.') - .option('-c, --code ', '2FA code.') + .option('-m, --method ', 'Two-step login method.') + .option('-c, --code ', 'Two-step login code.') .action(async (email: string, password: string, cmd: program.Command) => { - const command = new LoginCommand(this.main.authService); + const command = new LoginCommand(this.main.authService, this.main.apiService); const response = await command.run(email, password, cmd); this.processResponse(response, cmd); }); @@ -54,7 +54,7 @@ export class Program { }); program - .command('unlock ') + .command('unlock [password]') .description('Unlock the vault and obtain a new session token.') .action((cmd) => { // TODO