2018-05-12 21:12:28 +02:00
|
|
|
import * as program from 'commander';
|
2018-05-23 17:16:23 +02:00
|
|
|
import * as inquirer from 'inquirer';
|
2018-05-12 21:12:28 +02:00
|
|
|
|
2018-05-15 23:17:47 +02:00
|
|
|
import { TwoFactorProviderType } from 'jslib/enums/twoFactorProviderType';
|
|
|
|
|
2018-05-12 21:12:28 +02:00
|
|
|
import { AuthResult } from 'jslib/models/domain/authResult';
|
2018-05-15 23:17:47 +02:00
|
|
|
import { TwoFactorEmailRequest } from 'jslib/models/request/twoFactorEmailRequest';
|
2018-05-12 21:12:28 +02:00
|
|
|
|
2018-05-15 23:17:47 +02:00
|
|
|
import { ApiService } from 'jslib/abstractions/api.service';
|
2018-05-12 21:12:28 +02:00
|
|
|
import { AuthService } from 'jslib/abstractions/auth.service';
|
2018-05-16 03:11:58 +02:00
|
|
|
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
|
2018-05-17 03:16:42 +02:00
|
|
|
import { SyncService } from 'jslib/abstractions/sync.service';
|
2018-05-12 21:12:28 +02:00
|
|
|
|
2018-05-14 20:54:19 +02:00
|
|
|
import { Response } from '../models/response';
|
2018-05-16 04:47:52 +02:00
|
|
|
import { MessageResponse } from '../models/response/messageResponse';
|
2018-05-13 03:24:28 +02:00
|
|
|
|
2018-05-16 03:11:58 +02:00
|
|
|
import { Utils } from 'jslib/misc/utils';
|
|
|
|
|
2018-05-14 20:54:19 +02:00
|
|
|
export class LoginCommand {
|
2018-05-16 03:11:58 +02:00
|
|
|
constructor(private authService: AuthService, private apiService: ApiService,
|
2018-05-17 03:16:42 +02:00
|
|
|
private cryptoFunctionService: CryptoFunctionService, private syncService: SyncService) { }
|
2018-05-12 21:12:28 +02:00
|
|
|
|
2018-05-13 06:19:14 +02:00
|
|
|
async run(email: string, password: string, cmd: program.Command) {
|
2018-05-15 20:21:42 +02:00
|
|
|
if (email == null || email === '') {
|
2018-06-19 04:07:45 +02:00
|
|
|
const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({
|
2018-05-23 17:16:23 +02:00
|
|
|
type: 'input',
|
|
|
|
name: 'email',
|
|
|
|
message: 'Email address:',
|
|
|
|
});
|
|
|
|
email = answer.email;
|
2018-05-15 20:21:42 +02:00
|
|
|
}
|
|
|
|
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 === '') {
|
2018-06-19 04:07:45 +02:00
|
|
|
const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({
|
2018-05-23 17:16:23 +02:00
|
|
|
type: 'password',
|
|
|
|
name: 'password',
|
|
|
|
message: 'Master password:',
|
2018-05-15 20:21:42 +02:00
|
|
|
});
|
2018-05-23 17:16:23 +02:00
|
|
|
password = answer.password;
|
2018-05-15 20:21:42 +02:00
|
|
|
}
|
|
|
|
if (password == null || password === '') {
|
|
|
|
return Response.badRequest('Master password is required.');
|
|
|
|
}
|
|
|
|
|
2018-05-15 23:17:47 +02:00
|
|
|
let twoFactorToken: string = cmd.code;
|
|
|
|
let twoFactorMethod: TwoFactorProviderType = null;
|
2018-05-13 06:19:14 +02:00
|
|
|
try {
|
2018-05-15 23:17:47 +02:00
|
|
|
if (cmd.method != null) {
|
|
|
|
twoFactorMethod = parseInt(cmd.method, null);
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
return Response.error('Invalid two-step login method.');
|
|
|
|
}
|
2018-05-15 20:21:42 +02:00
|
|
|
|
2018-05-15 23:17:47 +02:00
|
|
|
try {
|
2018-05-16 03:11:58 +02:00
|
|
|
await this.setNewSessionKey();
|
2018-05-15 23:17:47 +02:00
|
|
|
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.');
|
2018-05-15 20:21:42 +02:00
|
|
|
}
|
|
|
|
|
2018-05-15 23:17:47 +02:00
|
|
|
if (twoFactorMethod != null) {
|
|
|
|
try {
|
|
|
|
selectedProvider = twoFactorProviders.filter((p) => p.type === twoFactorMethod)[0];
|
|
|
|
} catch (e) {
|
|
|
|
return Response.error('Invalid two-step login method.');
|
|
|
|
}
|
|
|
|
}
|
2018-05-15 20:21:42 +02:00
|
|
|
|
2018-05-15 23:17:47 +02:00
|
|
|
if (selectedProvider == null) {
|
|
|
|
if (twoFactorProviders.length === 1) {
|
|
|
|
selectedProvider = twoFactorProviders[0];
|
|
|
|
} else {
|
|
|
|
const options = twoFactorProviders.map((p) => p.name);
|
2018-05-23 17:16:23 +02:00
|
|
|
options.push(new inquirer.Separator());
|
|
|
|
options.push('Cancel');
|
2018-06-19 04:11:05 +02:00
|
|
|
const answer: inquirer.Answers =
|
|
|
|
await inquirer.createPromptModule({ output: process.stderr })({
|
|
|
|
type: 'list',
|
|
|
|
name: 'method',
|
|
|
|
message: 'Two-step login method:',
|
|
|
|
choices: options,
|
|
|
|
});
|
2018-05-23 17:16:23 +02:00
|
|
|
const i = options.indexOf(answer.method);
|
|
|
|
if (i === (options.length - 1)) {
|
2018-05-15 23:17:47 +02:00
|
|
|
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) {
|
2018-06-19 04:11:05 +02:00
|
|
|
const answer: inquirer.Answers =
|
|
|
|
await inquirer.createPromptModule({ output: process.stderr })({
|
|
|
|
type: 'input',
|
|
|
|
name: 'token',
|
|
|
|
message: 'Two-step login code:',
|
|
|
|
});
|
2018-05-23 17:16:23 +02:00
|
|
|
twoFactorToken = answer.token;
|
2018-05-15 23:17:47 +02:00
|
|
|
if (twoFactorToken == null || twoFactorToken === '') {
|
|
|
|
return Response.badRequest('Code is required.');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-03 17:27:42 +02:00
|
|
|
response = await this.authService.logInTwoFactor(selectedProvider.type,
|
2018-05-15 23:17:47 +02:00
|
|
|
twoFactorToken, false);
|
2018-05-15 20:21:42 +02:00
|
|
|
}
|
|
|
|
}
|
2018-05-16 04:47:52 +02:00
|
|
|
|
2018-08-03 17:27:42 +02:00
|
|
|
if (response.twoFactor) {
|
|
|
|
return Response.error('Login failed.');
|
|
|
|
}
|
|
|
|
|
2018-05-17 03:16:42 +02:00
|
|
|
await this.syncService.fullSync(true);
|
2018-05-16 04:47:52 +02:00
|
|
|
const res = new MessageResponse('You are logged in!', '\n' +
|
|
|
|
'To unlock your vault, set your session key to the `BW_SESSION` environment variable. ex:\n' +
|
2018-05-17 05:27:47 +02:00
|
|
|
'$ export BW_SESSION="' + process.env.BW_SESSION + '"\n' +
|
|
|
|
'> $env:BW_SESSION="' + process.env.BW_SESSION + '"\n\n' +
|
2018-05-16 04:47:52 +02:00
|
|
|
'You can also pass the session key to any command with the `--session` option. ex:\n' +
|
2018-05-18 15:16:34 +02:00
|
|
|
'$ bw list items --session ' + process.env.BW_SESSION);
|
2018-05-16 04:47:52 +02:00
|
|
|
res.raw = process.env.BW_SESSION;
|
|
|
|
return Response.success(res);
|
2018-05-13 06:19:14 +02:00
|
|
|
} catch (e) {
|
2018-05-15 17:08:55 +02:00
|
|
|
return Response.error(e);
|
2018-05-13 06:19:14 +02:00
|
|
|
}
|
2018-05-12 21:12:28 +02:00
|
|
|
}
|
2018-05-16 03:11:58 +02:00
|
|
|
|
|
|
|
private async setNewSessionKey() {
|
|
|
|
const key = await this.cryptoFunctionService.randomBytes(64);
|
|
|
|
process.env.BW_SESSION = Utils.fromBufferToB64(key);
|
|
|
|
}
|
2018-05-12 21:12:28 +02:00
|
|
|
}
|