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 { SendProgram } from "./send.program";
import { VaultProgram } from "./vault.program"; import { VaultProgram } from "./vault.program";
import { Account, AccountFactory } from "jslib-common/models/domain/account";
// Polyfills // Polyfills
(global as any).DOMParser = new jsdom.JSDOM().window.DOMParser; (global as any).DOMParser = new jsdom.JSDOM().window.DOMParser;
@ -136,7 +138,8 @@ export class Main {
this.storageService, this.storageService,
this.secureStorageService, this.secureStorageService,
this.logService, this.logService,
this.stateMigrationService this.stateMigrationService,
new AccountFactory(Account)
); );
this.cryptoService = new CryptoService( this.cryptoService = new CryptoService(
@ -328,6 +331,9 @@ export class Main {
} }
async logout() { async logout() {
this.authService.logOut(() => {
/* Do nothing */
});
const userId = await this.stateService.getUserId(); const userId = await this.stateService.getUserId();
await Promise.all([ await Promise.all([
this.syncService.setLastSync(new Date(0)), 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 program from "commander";
import * as inquirer from "inquirer";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service"; import { AuthService } from "jslib-common/abstractions/auth.service";
@ -27,7 +26,6 @@ export class LoginCommand extends BaseLoginCommand {
authService: AuthService, authService: AuthService,
apiService: ApiService, apiService: ApiService,
cryptoFunctionService: CryptoFunctionService, cryptoFunctionService: CryptoFunctionService,
syncService: SyncService,
i18nService: I18nService, i18nService: I18nService,
environmentService: EnvironmentService, environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService, passwordGenerationService: PasswordGenerationService,
@ -35,7 +33,8 @@ export class LoginCommand extends BaseLoginCommand {
stateService: StateService, stateService: StateService,
cryptoService: CryptoService, cryptoService: CryptoService,
policyService: PolicyService, policyService: PolicyService,
keyConnectorService: KeyConnectorService, private syncService: SyncService,
private keyConnectorService: KeyConnectorService,
private logoutCallback: () => Promise<void> private logoutCallback: () => Promise<void>
) { ) {
super( super(
@ -49,9 +48,7 @@ export class LoginCommand extends BaseLoginCommand {
stateService, stateService,
cryptoService, cryptoService,
policyService, policyService,
"cli", "cli"
syncService,
keyConnectorService
); );
this.logout = this.logoutCallback; this.logout = this.logoutCallback;
this.validatedParams = async () => { this.validatedParams = async () => {
@ -59,6 +56,8 @@ export class LoginCommand extends BaseLoginCommand {
process.env.BW_SESSION = Utils.fromBufferToB64(key); process.env.BW_SESSION = Utils.fromBufferToB64(key);
}; };
this.success = async () => { this.success = async () => {
await this.syncService.fullSync(true);
const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector(); const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
if ( if (

View File

@ -107,7 +107,11 @@ export class ServeCommand {
this.main.stateService, this.main.stateService,
this.main.cryptoFunctionService, this.main.cryptoFunctionService,
this.main.apiService, 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( this.sendCreateCommand = new SendCreateCommand(

View File

@ -3,7 +3,10 @@ import * as inquirer from "inquirer";
import { ApiService } from "jslib-common/abstractions/api.service"; import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service"; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.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 { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { Response } from "jslib-node/cli/models/response"; import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from "jslib-node/cli/models/response/messageResponse"; 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 { NodeUtils } from "jslib-common/misc/nodeUtils";
import { ConsoleLogService } from "jslib-common/services/consoleLog.service"; import { ConsoleLogService } from "jslib-common/services/consoleLog.service";
import { ConvertToKeyConnectorCommand } from "./convertToKeyConnector.command";
export class UnlockCommand { export class UnlockCommand {
constructor( constructor(
private cryptoService: CryptoService, private cryptoService: CryptoService,
private stateService: StateService, private stateService: StateService,
private cryptoFunctionService: CryptoFunctionService, private cryptoFunctionService: CryptoFunctionService,
private apiService: ApiService, 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>) { async run(password: string, cmdOptions: Record<string, any>) {
@ -92,6 +101,33 @@ export class UnlockCommand {
if (passwordValid) { if (passwordValid) {
await this.cryptoService.setKey(key); await this.cryptoService.setKey(key);
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.");
}
}
private async setNewSessionKey() {
const key = await this.cryptoFunctionService.randomBytes(64);
process.env.BW_SESSION = Utils.fromBufferToB64(key);
}
private async successResponse() {
const res = new MessageResponse( const res = new MessageResponse(
"Your vault is now unlocked!", "Your vault is now unlocked!",
"\n" + "\n" +
@ -108,14 +144,6 @@ export class UnlockCommand {
); );
res.raw = process.env.BW_SESSION; res.raw = process.env.BW_SESSION;
return Response.success(res); 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);
} }
} }

View File

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