Fix migration to Key Connector (#452)

* Move Key Connector check to subclass

* Move authService.logout call to main program

* Move Key Connector migration check to unlock command

* Use get/setConvertAccountRequired flag

* Move Key Connector convert to own command, set usesKeyConnector after conversion

* Remove KC conversion check from syncCommand, fix callback

* Make class service private

* Fix naming convention

* Update jslib and deps
This commit is contained in:
Thomas Rittson 2022-01-21 06:03:37 +10:00 committed by GitHub
parent 922cd1dc54
commit 8b650666c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 159 additions and 29 deletions

2
jslib

@ -1 +1 @@
Subproject commit cc285e5ea729795812d8f1f66f622b67c11efb4a
Subproject commit 11e7133aefa4443e07febde9b3add25539ec2287

View File

@ -50,6 +50,8 @@ import { Program } from "./program";
import { SendProgram } from "./send.program";
import { VaultProgram } from "./vault.program";
import { Account, AccountFactory } from "jslib-common/models/domain/account";
// Polyfills
(global as any).DOMParser = new jsdom.JSDOM().window.DOMParser;
@ -136,7 +138,8 @@ export class Main {
this.storageService,
this.secureStorageService,
this.logService,
this.stateMigrationService
this.stateMigrationService,
new AccountFactory(Account)
);
this.cryptoService = new CryptoService(
@ -328,6 +331,9 @@ export class Main {
}
async logout() {
this.authService.logOut(() => {
/* Do nothing */
});
const userId = await this.stateService.getUserId();
await Promise.all([
this.syncService.setLastSync(new Date(0)),

View File

@ -0,0 +1,85 @@
import * as inquirer from "inquirer";
import { ApiService } from "jslib-common/abstractions/api.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
export class ConvertToKeyConnectorCommand {
constructor(
private apiService: ApiService,
private keyConnectorService: KeyConnectorService,
private environmentService: EnvironmentService,
private syncService: SyncService,
private logout: () => Promise<void>
) {}
async run(): Promise<Response> {
// If no interaction available, alert user to use web vault
const canInteract = process.env.BW_NOINTERACTION !== "true";
if (!canInteract) {
await this.logout();
return Response.error(
new MessageResponse(
"An organization you are a member of is using Key Connector. " +
"In order to access the vault, you must opt-in to Key Connector now via the web vault. You have been logged out.",
null
)
);
}
const organization = await this.keyConnectorService.getManagingOrganization();
const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({
type: "list",
name: "convert",
message:
organization.name +
" is using a self-hosted key server. A master password is no longer required to log in for members of this organization. ",
choices: [
{
name: "Remove master password and unlock",
value: "remove",
},
{
name: "Leave organization and unlock",
value: "leave",
},
{
name: "Log out",
value: "exit",
},
],
});
if (answer.convert === "remove") {
try {
await this.keyConnectorService.migrateUser();
} catch (e) {
await this.logout();
throw e;
}
await this.keyConnectorService.removeConvertAccountRequired();
await this.keyConnectorService.setUsesKeyConnector(true);
// Update environment URL - required for api key login
const urls = this.environmentService.getUrls();
urls.keyConnector = organization.keyConnectorUrl;
await this.environmentService.setUrls(urls, true);
return Response.success();
} else if (answer.convert === "leave") {
await this.apiService.postLeaveOrganization(organization.id);
await this.keyConnectorService.removeConvertAccountRequired();
await this.syncService.fullSync(true);
return Response.success();
} else {
await this.logout();
return Response.error("You have been logged out.");
}
}
}

View File

@ -1,5 +1,4 @@
import * as program from "commander";
import * as inquirer from "inquirer";
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
@ -27,7 +26,6 @@ export class LoginCommand extends BaseLoginCommand {
authService: AuthService,
apiService: ApiService,
cryptoFunctionService: CryptoFunctionService,
syncService: SyncService,
i18nService: I18nService,
environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService,
@ -35,7 +33,8 @@ export class LoginCommand extends BaseLoginCommand {
stateService: StateService,
cryptoService: CryptoService,
policyService: PolicyService,
keyConnectorService: KeyConnectorService,
private syncService: SyncService,
private keyConnectorService: KeyConnectorService,
private logoutCallback: () => Promise<void>
) {
super(
@ -49,9 +48,7 @@ export class LoginCommand extends BaseLoginCommand {
stateService,
cryptoService,
policyService,
"cli",
syncService,
keyConnectorService
"cli"
);
this.logout = this.logoutCallback;
this.validatedParams = async () => {
@ -59,6 +56,8 @@ export class LoginCommand extends BaseLoginCommand {
process.env.BW_SESSION = Utils.fromBufferToB64(key);
};
this.success = async () => {
await this.syncService.fullSync(true);
const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
if (

View File

@ -107,7 +107,11 @@ export class ServeCommand {
this.main.stateService,
this.main.cryptoFunctionService,
this.main.apiService,
this.main.logService
this.main.logService,
this.main.keyConnectorService,
this.main.environmentService,
this.main.syncService,
async () => await this.main.logout()
);
this.sendCreateCommand = new SendCreateCommand(

View File

@ -3,7 +3,10 @@ import * as inquirer from "inquirer";
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
@ -16,13 +19,19 @@ import { HashPurpose } from "jslib-common/enums/hashPurpose";
import { NodeUtils } from "jslib-common/misc/nodeUtils";
import { ConsoleLogService } from "jslib-common/services/consoleLog.service";
import { ConvertToKeyConnectorCommand } from "./convertToKeyConnector.command";
export class UnlockCommand {
constructor(
private cryptoService: CryptoService,
private stateService: StateService,
private cryptoFunctionService: CryptoFunctionService,
private apiService: ApiService,
private logService: ConsoleLogService
private logService: ConsoleLogService,
private keyConnectorService: KeyConnectorService,
private environmentService: EnvironmentService,
private syncService: SyncService,
private logout: () => Promise<void>
) {}
async run(password: string, cmdOptions: Record<string, any>) {
@ -92,22 +101,22 @@ export class UnlockCommand {
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" +
'$ 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 Response.success(res);
if (await this.keyConnectorService.getConvertAccountRequired()) {
const convertToKeyConnectorCommand = new ConvertToKeyConnectorCommand(
this.apiService,
this.keyConnectorService,
this.environmentService,
this.syncService,
this.logout
);
const convertResponse = await convertToKeyConnectorCommand.run();
if (!convertResponse.success) {
return convertResponse;
}
}
return this.successResponse();
} else {
return Response.error("Invalid master password.");
}
@ -117,6 +126,25 @@ export class UnlockCommand {
const key = await this.cryptoFunctionService.randomBytes(64);
process.env.BW_SESSION = Utils.fromBufferToB64(key);
}
private async successResponse() {
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' +
'> $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 Response.success(res);
}
}
class Options {

View File

@ -149,7 +149,6 @@ export class Program extends BaseProgram {
this.main.authService,
this.main.apiService,
this.main.cryptoFunctionService,
this.main.syncService,
this.main.i18nService,
this.main.environmentService,
this.main.passwordGenerationService,
@ -157,6 +156,7 @@ export class Program extends BaseProgram {
this.main.stateService,
this.main.cryptoService,
this.main.policyService,
this.main.syncService,
this.main.keyConnectorService,
async () => await this.main.logout()
);
@ -257,7 +257,11 @@ export class Program extends BaseProgram {
this.main.stateService,
this.main.cryptoFunctionService,
this.main.apiService,
this.main.logService
this.main.logService,
this.main.keyConnectorService,
this.main.environmentService,
this.main.syncService,
async () => await this.main.logout()
);
const response = await command.run(password, cmd);
this.processResponse(response);
@ -511,7 +515,11 @@ export class Program extends BaseProgram {
this.main.stateService,
this.main.cryptoFunctionService,
this.main.apiService,
this.main.logService
this.main.logService,
this.main.keyConnectorService,
this.main.environmentService,
this.main.syncService,
this.main.logout
);
const response = await command.run(null, null);
if (!response.success) {