diff --git a/jslib b/jslib index 57ace40845..b1dcb3949f 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 57ace4084556758fdc2989cf1a8cf6a5d1736a29 +Subproject commit b1dcb3949f585576bd0a6d24596e384c26a89ffe diff --git a/package-lock.json b/package-lock.json index 937a8b91a7..45a95f12d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/cli", - "version": "1.10.0", + "version": "1.11.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -3232,6 +3232,11 @@ } } }, + "is-docker": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz", + "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==" + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -4068,6 +4073,25 @@ "mimic-fn": "^1.0.0" } }, + "open": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-7.1.0.tgz", + "integrity": "sha512-lLPI5KgOwEYCDKXf4np7y1PBEkj7HYIyP2DY8mVDRnx0VIIu6bNrRB0R66TuO7Mack6EnTNLm4uvcl1UoklTpA==", + "requires": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "dependencies": { + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "requires": { + "is-docker": "^2.0.0" + } + } + } + }, "optionator": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", diff --git a/package.json b/package.json index 72bac3a1a6..d1bc1b0212 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "lunr": "2.3.3", "node-fetch": "2.2.0", "node-forge": "0.7.6", + "open": "7.1.0", "papaparse": "4.6.0", "tldjs": "2.3.1", "zxcvbn": "4.4.2" diff --git a/src/bw.ts b/src/bw.ts index 7362590ff9..f6df74c517 100644 --- a/src/bw.ts +++ b/src/bw.ts @@ -130,7 +130,8 @@ export class Main { this.i18nService, this.collectionService); this.exportService = new ExportService(this.folderService, this.cipherService, this.apiService); this.authService = new AuthService(this.cryptoService, this.apiService, this.userService, this.tokenService, - this.appIdService, this.i18nService, this.platformUtilsService, this.messagingService, true); + this.appIdService, this.i18nService, this.platformUtilsService, this.messagingService, + this.vaultTimeoutService, true); this.auditService = new AuditService(this.cryptoFunctionService, this.apiService); this.program = new Program(this); } diff --git a/src/commands/login.command.ts b/src/commands/login.command.ts index 0a08402d55..86d28786ca 100644 --- a/src/commands/login.command.ts +++ b/src/commands/login.command.ts @@ -1,7 +1,11 @@ +import * as program from 'commander'; + import { ApiService } from 'jslib/abstractions/api.service'; import { AuthService } from 'jslib/abstractions/auth.service'; import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service'; +import { EnvironmentService } from 'jslib/abstractions/environment.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; +import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service'; import { SyncService } from 'jslib/abstractions/sync.service'; import { MessageResponse } from 'jslib/cli/models/response/messageResponse'; @@ -11,24 +15,41 @@ import { Utils } from 'jslib/misc/utils'; import { LoginCommand as BaseLoginCommand } from 'jslib/cli/commands/login.command'; export class LoginCommand extends BaseLoginCommand { + private cmd: program.Command; + constructor(authService: AuthService, apiService: ApiService, cryptoFunctionService: CryptoFunctionService, syncService: SyncService, - i18nService: I18nService) { - super(authService, apiService, i18nService); + i18nService: I18nService, environmentService: EnvironmentService, + passwordGenerationService: PasswordGenerationService) { + super(authService, apiService, i18nService, environmentService, passwordGenerationService, + cryptoFunctionService); + this.clientId = 'cli'; this.validatedParams = async () => { const key = await cryptoFunctionService.randomBytes(64); process.env.BW_SESSION = Utils.fromBufferToB64(key); }; this.success = async () => { await syncService.fullSync(true); - 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' + - '$ export BW_SESSION="' + process.env.BW_SESSION + '"\n' + - '> $env: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 list items --session ' + process.env.BW_SESSION); - res.raw = process.env.BW_SESSION; - return res; + if (this.cmd.sso != null && this.canInteract) { + const res = new MessageResponse('You are logged in!', '\n' + + 'To unlock your vault, use the `unlock` command. ex:\n' + + '$ bw unlock'); + return res; + } else { + 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' + + '$ export BW_SESSION="' + process.env.BW_SESSION + '"\n' + + '> $env: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 list items --session ' + process.env.BW_SESSION); + res.raw = process.env.BW_SESSION; + return res; + } }; } + + run(email: string, password: string, cmd: program.Command) { + this.cmd = cmd; + return super.run(email, password, cmd); + } } diff --git a/src/commands/unlock.command.ts b/src/commands/unlock.command.ts index 6d7669ef6c..d175d8397a 100644 --- a/src/commands/unlock.command.ts +++ b/src/commands/unlock.command.ts @@ -1,6 +1,7 @@ import * as program from 'commander'; import * as inquirer from 'inquirer'; +import { ApiService } from 'jslib/abstractions/api.service'; import { CryptoService } from 'jslib/abstractions/crypto.service'; import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service'; import { UserService } from 'jslib/abstractions/user.service'; @@ -8,11 +9,13 @@ import { UserService } from 'jslib/abstractions/user.service'; import { Response } from 'jslib/cli/models/response'; import { MessageResponse } from 'jslib/cli/models/response/messageResponse'; +import { PasswordVerificationRequest } from 'jslib/models/request/passwordVerificationRequest'; + import { Utils } from 'jslib/misc/utils'; export class UnlockCommand { constructor(private cryptoService: CryptoService, private userService: UserService, - private cryptoFunctionService: CryptoFunctionService) { } + private cryptoFunctionService: CryptoFunctionService, private apiService: ApiService) { } async run(password: string, cmd: program.Command) { const canInteract = process.env.BW_NOINTERACTION !== 'true'; @@ -34,8 +37,24 @@ export class UnlockCommand { const kdfIterations = await this.userService.getKdfIterations(); const key = await this.cryptoService.makeKey(password, email, kdf, kdfIterations); const keyHash = await this.cryptoService.hashPassword(password, key); - const storedKeyHash = await this.cryptoService.getKeyHash(); - if (storedKeyHash != null && keyHash != null && storedKeyHash === keyHash) { + + let passwordValid = false; + if (keyHash != null) { + const storedKeyHash = await this.cryptoService.getKeyHash(); + if (storedKeyHash != null) { + passwordValid = storedKeyHash === keyHash; + } else { + const request = new PasswordVerificationRequest(); + request.masterPasswordHash = keyHash; + try { + await this.apiService.postAccountVerifyPassword(request); + passwordValid = true; + await this.cryptoService.setKeyHash(keyHash); + } catch { } + } + } + + if (passwordValid) { 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' + diff --git a/src/program.ts b/src/program.ts index 0a5392272f..1dac0a6e99 100644 --- a/src/program.ts +++ b/src/program.ts @@ -107,6 +107,7 @@ export class Program extends BaseProgram { .description('Log into a user account.') .option('--method ', 'Two-step login method.') .option('--code ', 'Two-step login code.') + .option('--sso', 'Log in with Single-Sign On.') .option('--check', 'Check login status.', async () => { const authed = await this.main.userService.isAuthenticated(); if (authed) { @@ -127,13 +128,15 @@ export class Program extends BaseProgram { writeLn(' bw login'); writeLn(' bw login john@example.com myPassword321 --raw'); writeLn(' bw login john@example.com myPassword321 --method 1 --code 249213'); + writeLn(' bw login --sso'); writeLn('', true); }) .action(async (email: string, password: string, cmd: program.Command) => { if (!cmd.check) { await this.exitIfAuthed(); const command = new LoginCommand(this.main.authService, this.main.apiService, - this.main.cryptoFunctionService, this.main.syncService, this.main.i18nService); + this.main.cryptoFunctionService, this.main.syncService, this.main.i18nService, + this.main.environmentService, this.main.passwordGenerationService); const response = await command.run(email, password, cmd); this.processResponse(response); } @@ -201,7 +204,7 @@ export class Program extends BaseProgram { if (!cmd.check) { await this.exitIfNotAuthed(); const command = new UnlockCommand(this.main.cryptoService, this.main.userService, - this.main.cryptoFunctionService); + this.main.cryptoFunctionService, this.main.apiService); const response = await command.run(password, cmd); this.processResponse(response); } @@ -741,7 +744,7 @@ export class Program extends BaseProgram { const canInteract = process.env.BW_NOINTERACTION !== 'true'; if (canInteract) { const command = new UnlockCommand(this.main.cryptoService, this.main.userService, - this.main.cryptoFunctionService); + this.main.cryptoFunctionService, this.main.apiService); const response = await command.run(null, null); if (!response.success) { this.processResponse(response, true);