diff --git a/jslib b/jslib index 799c90af17..e6fde2e92b 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 799c90af1702c681874cb8fce8f8adcf1049d0c8 +Subproject commit e6fde2e92be4be472933234daa7bae242ee17794 diff --git a/src/bw.ts b/src/bw.ts index c6e47d5b36..343cf2c25d 100644 --- a/src/bw.ts +++ b/src/bw.ts @@ -78,8 +78,7 @@ export class Main { this.collectionService = new CollectionService(this.cryptoService, this.userService, this.storageService, this.i18nService); this.lockService = new LockService(this.cipherService, this.folderService, this.collectionService, - this.cryptoService, this.platformUtilsService, this.storageService, this.messagingService, - () => { /* do nothing */ }); + this.cryptoService, this.platformUtilsService, this.storageService, this.messagingService, null); this.syncService = new SyncService(this.userService, this.apiService, this.settingsService, this.folderService, this.cipherService, this.cryptoService, this.collectionService, this.storageService, this.messagingService, async (expired: boolean) => await this.logout()); diff --git a/src/commands/lock.command.ts b/src/commands/lock.command.ts new file mode 100644 index 0000000000..259d591c86 --- /dev/null +++ b/src/commands/lock.command.ts @@ -0,0 +1,17 @@ +import * as program from 'commander'; + +import { LockService } from 'jslib/abstractions/lock.service'; + +import { Response } from '../models/response'; +import { MessageResponse } from '../models/response/messageResponse'; + +export class LockCommand { + constructor(private lockService: LockService) { } + + async run(cmd: program.Command) { + await this.lockService.lock(); + process.env.BW_SESSION = null; + const res = new MessageResponse('Your vault is locked.', null); + return Response.success(res); + } +} diff --git a/src/commands/unlock.command.ts b/src/commands/unlock.command.ts new file mode 100644 index 0000000000..4856cc002c --- /dev/null +++ b/src/commands/unlock.command.ts @@ -0,0 +1,51 @@ +import * as program from 'commander'; +import * as readline from 'readline-sync'; + +import { CryptoService } from 'jslib/abstractions/crypto.service'; +import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service'; +import { UserService } from 'jslib/abstractions/user.service'; + +import { Response } from '../models/response'; +import { MessageResponse } from '../models/response/messageResponse'; + +import { Utils } from 'jslib/misc/utils'; + +export class UnlockCommand { + constructor(private cryptoService: CryptoService, private userService: UserService, + private cryptoFunctionService: CryptoFunctionService) { } + + async run(password: string, cmd: program.Command) { + if (password == null || password === '') { + password = readline.question('Master password: ', { + hideEchoBack: true, + mask: '*', + }); + } + if (password == null || password === '') { + return Response.badRequest('Master password is required.'); + } + + this.setNewSessionKey(); + const email = await this.userService.getEmail(); + const key = await this.cryptoService.makeKey(password, email); + const keyHash = await this.cryptoService.hashPassword(password, key); + const storedKeyHash = await this.cryptoService.getKeyHash(); + if (storedKeyHash != null && keyHash != null && storedKeyHash === keyHash) { + await this.cryptoService.setKey(key); + const res = new MessageResponse('Your vault is now unlocked!', '\n' + + 'To unlock your vault, set your session key to the `BW_SESSION` environment variable. ex:\n' + + '$ export BW_SESSION="' + process.env.BW_SESSION + '"\n\n' + + 'You can also pass the session key to any command with the `--session` option. ex:\n' + + '$ bw get items --session ' + process.env.BW_SESSION); + res.raw = process.env.BW_SESSION; + return Response.success(res); + } else { + return Response.error('Invalid master password.'); + } + } + + private async setNewSessionKey() { + const key = await this.cryptoFunctionService.randomBytes(64); + process.env.BW_SESSION = Utils.fromBufferToB64(key); + } +} diff --git a/src/program.ts b/src/program.ts index e10885f966..b4404e40b7 100644 --- a/src/program.ts +++ b/src/program.ts @@ -9,9 +9,11 @@ import { EditCommand } from './commands/edit.command'; import { EncodeCommand } from './commands/encode.command'; import { GetCommand } from './commands/get.command'; import { ListCommand } from './commands/list.command'; +import { LockCommand } from './commands/lock.command'; import { LoginCommand } from './commands/login.command'; import { LogoutCommand } from './commands/logout.command'; import { SyncCommand } from './commands/sync.command'; +import { UnlockCommand } from './commands/unlock.command'; import { Response } from './models/response'; import { ListResponse } from './models/response/listResponse'; @@ -53,7 +55,7 @@ export class Program { const command = new LoginCommand(this.main.authService, this.main.apiService, this.main.cryptoFunctionService); const response = await command.run(email, password, cmd); - this.processResponse(response, cmd); + this.processResponse(response); }); program @@ -63,7 +65,7 @@ export class Program { await this.exitIfNotAuthed(); const command = new LogoutCommand(this.main.authService, async () => await this.main.logout()); const response = await command.run(cmd); - this.processResponse(response, cmd); + this.processResponse(response); }); program @@ -71,15 +73,20 @@ export class Program { .description('Lock the vault and destroy the current session token.') .action(async (cmd) => { await this.exitIfNotAuthed(); - // TODO + const command = new LockCommand(this.main.lockService); + const response = await command.run(cmd); + this.processResponse(response); }); program .command('unlock [password]') .description('Unlock the vault and obtain a new session token.') - .action(async (cmd) => { + .action(async (password, cmd) => { await this.exitIfNotAuthed(); - // TODO + const command = new UnlockCommand(this.main.cryptoService, this.main.userService, + this.main.cryptoFunctionService); + const response = await command.run(password, cmd); + this.processResponse(response); }); program @@ -90,7 +97,7 @@ export class Program { await this.exitIfLocked(); const command = new SyncCommand(this.main.syncService); const response = await command.run(cmd); - this.processResponse(response, cmd); + this.processResponse(response); }); program @@ -101,7 +108,7 @@ export class Program { const command = new ListCommand(this.main.cipherService, this.main.folderService, this.main.collectionService); const response = await command.run(object, cmd); - this.processResponse(response, cmd); + this.processResponse(response); }); program @@ -118,7 +125,7 @@ export class Program { this.main.collectionService, this.main.totpService, this.main.syncService, this.main.passwordGenerationService); const response = await command.run(object, id, cmd); - this.processResponse(response, cmd); + this.processResponse(response); }); program @@ -128,7 +135,7 @@ export class Program { await this.exitIfLocked(); const command = new CreateCommand(this.main.cipherService, this.main.folderService); const response = await command.run(object, encodedData, cmd); - this.processResponse(response, cmd); + this.processResponse(response); }); program @@ -138,7 +145,7 @@ export class Program { await this.exitIfLocked(); const command = new EditCommand(this.main.cipherService, this.main.folderService); const response = await command.run(object, id, encodedData, cmd); - this.processResponse(response, cmd); + this.processResponse(response); }); program @@ -148,7 +155,7 @@ export class Program { await this.exitIfLocked(); const command = new DeleteCommand(this.main.cipherService, this.main.folderService); const response = await command.run(object, id, cmd); - this.processResponse(response, cmd); + this.processResponse(response); }); program @@ -157,14 +164,21 @@ export class Program { .action(async (object, id, cmd) => { const command = new EncodeCommand(); const response = await command.run(cmd); - this.processResponse(response, cmd); + this.processResponse(response); + }); + + program + .command('update') + .description('Check for updates.') + .action(async (object, id, cmd) => { + console.log('Checking...'); }); program .parse(process.argv); } - private processResponse(response: Response, cmd: program.Command) { + private processResponse(response: Response) { if (!response.success) { process.stdout.write(chalk.redBright(response.message)); process.exit(1); @@ -179,13 +193,13 @@ export class Program { out = data; } } else if (response.data.object === 'list') { - out = this.getJson((response.data as ListResponse).data, cmd); + out = this.getJson((response.data as ListResponse).data); } else if (response.data.object === 'template') { - out = this.getJson((response.data as TemplateResponse).template, cmd); + out = this.getJson((response.data as TemplateResponse).template); } else if (response.data.object === 'message') { out = this.getMessage(response); } else { - out = this.getJson(response.data, cmd); + out = this.getJson(response.data); } if (out != null) { @@ -195,7 +209,7 @@ export class Program { process.exit(); } - private getJson(obj: any, cmd: program.Command): string { + private getJson(obj: any): string { if (process.env.BW_PRETTY === 'true') { return JSON.stringify(obj, null, ' '); } else {