parse two step login options

This commit is contained in:
Kyle Spearrin 2018-05-15 17:17:47 -04:00
parent e3eea736ed
commit e8a3325ec9
2 changed files with 73 additions and 34 deletions

View File

@ -1,18 +1,22 @@
import * as program from 'commander'; import * as program from 'commander';
import * as readline from 'readline-sync'; 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 { AuthService } from 'jslib/abstractions/auth.service';
import { Response } from '../models/response'; import { Response } from '../models/response';
export class LoginCommand { export class LoginCommand {
constructor(private authService: AuthService) { } constructor(private authService: AuthService, private apiService: ApiService) { }
async run(email: string, password: string, cmd: program.Command) { async run(email: string, password: string, cmd: program.Command) {
if (email == null || email === '') { if (email == null || email === '') {
email = readline.question('Email Address: '); email = readline.question('Email address: ');
} }
if (email == null || email.trim() === '') { if (email == null || email.trim() === '') {
return Response.badRequest('Email address is required.'); return Response.badRequest('Email address is required.');
@ -22,7 +26,7 @@ export class LoginCommand {
} }
if (password == null || password === '') { if (password == null || password === '') {
password = readline.question('Master Password: ', { password = readline.question('Master password: ', {
hideEchoBack: true, hideEchoBack: true,
mask: '*', mask: '*',
}); });
@ -31,8 +35,23 @@ export class LoginCommand {
return Response.badRequest('Master password is required.'); return Response.badRequest('Master password is required.');
} }
let twoFactorToken: string = cmd.code;
let twoFactorMethod: TwoFactorProviderType = null;
try { try {
const response = await this.authService.logIn(email, password); if (cmd.method != null) {
twoFactorMethod = parseInt(cmd.method, null);
}
} catch (e) {
return Response.error('Invalid two-step login method.');
}
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) { if (response.twoFactor) {
let selectedProvider: any = null; let selectedProvider: any = null;
const twoFactorProviders = this.authService.getSupportedTwoFactorProviders(null); const twoFactorProviders = this.authService.getSupportedTwoFactorProviders(null);
@ -40,6 +59,15 @@ export class LoginCommand {
return Response.badRequest('No providers available for this client.'); 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) { if (twoFactorProviders.length === 1) {
selectedProvider = twoFactorProviders[0]; selectedProvider = twoFactorProviders[0];
} else { } else {
@ -50,10 +78,20 @@ export class LoginCommand {
} }
selectedProvider = twoFactorProviders[i]; selectedProvider = twoFactorProviders[i];
} }
}
const twoFactorToken = readline.question('Two-step login token for ' + selectedProvider.name + ': '); 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 === '') { if (twoFactorToken == null || twoFactorToken === '') {
return Response.badRequest('Token is required.'); return Response.badRequest('Code is required.');
}
} }
const twoFactorResponse = await this.authService.logInTwoFactor(selectedProvider.type, const twoFactorResponse = await this.authService.logInTwoFactor(selectedProvider.type,
@ -62,6 +100,7 @@ export class LoginCommand {
return Response.error('Login failed.'); return Response.error('Login failed.');
} }
} }
}
return Response.success(); return Response.success();
} catch (e) { } catch (e) {
return Response.error(e); return Response.error(e);

View File

@ -29,12 +29,12 @@ export class Program {
.option('--pretty', 'Format stdout.'); .option('--pretty', 'Format stdout.');
program program
.command('login <email> <password>') .command('login [email] [password]')
.description('Log into a Bitwarden user account.') .description('Log into a Bitwarden user account.')
.option('-m, --method <method>', '2FA method.') .option('-m, --method <method>', 'Two-step login method.')
.option('-c, --code <code>', '2FA code.') .option('-c, --code <code>', 'Two-step login code.')
.action(async (email: string, password: string, cmd: program.Command) => { .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); const response = await command.run(email, password, cmd);
this.processResponse(response, cmd); this.processResponse(response, cmd);
}); });
@ -54,7 +54,7 @@ export class Program {
}); });
program program
.command('unlock <password>') .command('unlock [password]')
.description('Unlock the vault and obtain a new session token.') .description('Unlock the vault and obtain a new session token.')
.action((cmd) => { .action((cmd) => {
// TODO // TODO