mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 22:03:36 +00:00
[PM-18017] Show key connector domain in remove password page (#14695)
* Passed in userId on RemovePasswordComponent. * Added userId on other references to KeyConnectorService methods * remove password component refactor, test coverage, enabled strict * explicit user id provided to key connector service * redirect to / instead when user not logged in or not managing organization * key connector service explicit user id * key connector service no longer requires account service * key connector service missing null type * cli convert to key connector unit tests * remove unnecessary SyncService * error toast not showing on ErrorResponse * bad import due to merge conflict * bad import due to merge conflict * missing loading in remove password component for browser extension * error handling in remove password component * organization observable race condition in key-connector * usesKeyConnector always returns boolean * unit test coverage * key connector reactive * reactive key connector service * introducing convertAccountRequired$ * cli build fix * moving message sending side effect to sync * key connector service unit tests * fix unit tests * move key connector components to KM team ownership * new unit tests in wrong place * key connector domain shown in remove password component * type safety improvements * convert to key connector command localization * key connector domain in convert to key connector command * convert to key connector command unit tests with prompt assert * organization name placement change in the remove password component * unit test update * key connector url required to be provided when migrating user * unit tests in wrong place after KM code ownership move * infinite page reload * failing unit tests * failing unit tests --------- Co-authored-by: Todd Martin <tmartin@bitwarden.com>
This commit is contained in:
@@ -3014,14 +3014,14 @@
|
|||||||
"copyCustomFieldNameNotUnique": {
|
"copyCustomFieldNameNotUnique": {
|
||||||
"message": "No unique identifier found."
|
"message": "No unique identifier found."
|
||||||
},
|
},
|
||||||
"convertOrganizationEncryptionDesc": {
|
"removeMasterPasswordForOrganizationUserKeyConnector": {
|
||||||
"message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.",
|
"message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator."
|
||||||
"placeholders": {
|
},
|
||||||
"organization": {
|
"organizationName": {
|
||||||
"content": "$1",
|
"message": "Organization name"
|
||||||
"example": "My Org Name"
|
},
|
||||||
}
|
"keyConnectorDomain": {
|
||||||
}
|
"message": "Key Connector domain"
|
||||||
},
|
},
|
||||||
"leaveOrganization": {
|
"leaveOrganization": {
|
||||||
"message": "Leave organization"
|
"message": "Leave organization"
|
||||||
|
|||||||
@@ -15,7 +15,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="box-content" *ngIf="!loading">
|
<div class="box-content" *ngIf="!loading">
|
||||||
<div class="box-content-row" appBoxRow>
|
<div class="box-content-row" appBoxRow>
|
||||||
<p>{{ "convertOrganizationEncryptionDesc" | i18n: organization.name }}</p>
|
<p>{{ "removeMasterPasswordForOrganizationUserKeyConnector" | i18n }}</p>
|
||||||
|
<p class="tw-mb-0">{{ "organizationName" | i18n }}:</p>
|
||||||
|
<p class="tw-text-muted tw-mb-6">{{ organization.name }}</p>
|
||||||
|
<p class="tw-mb-0">{{ "keyConnectorDomain" | i18n }}:</p>
|
||||||
|
<p class="tw-text-muted tw-mb-6">{{ organization.keyConnectorUrl }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="box-content-row">
|
<div class="box-content-row">
|
||||||
<button type="button" class="btn block primary" (click)="convert()" [disabled]="action">
|
<button type="button" class="btn block primary" (click)="convert()" [disabled]="action">
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { KeyService } from "@bitwarden/key-management";
|
|||||||
import { ConvertToKeyConnectorCommand } from "../../key-management/convert-to-key-connector.command";
|
import { ConvertToKeyConnectorCommand } from "../../key-management/convert-to-key-connector.command";
|
||||||
import { Response } from "../../models/response";
|
import { Response } from "../../models/response";
|
||||||
import { MessageResponse } from "../../models/response/message.response";
|
import { MessageResponse } from "../../models/response/message.response";
|
||||||
|
import { I18nService } from "../../platform/services/i18n.service";
|
||||||
import { CliUtils } from "../../utils";
|
import { CliUtils } from "../../utils";
|
||||||
|
|
||||||
export class UnlockCommand {
|
export class UnlockCommand {
|
||||||
@@ -33,6 +34,7 @@ export class UnlockCommand {
|
|||||||
private environmentService: EnvironmentService,
|
private environmentService: EnvironmentService,
|
||||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||||
private logout: () => Promise<void>,
|
private logout: () => Promise<void>,
|
||||||
|
private i18nService: I18nService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async run(password: string, cmdOptions: Record<string, any>) {
|
async run(password: string, cmdOptions: Record<string, any>) {
|
||||||
@@ -78,6 +80,7 @@ export class UnlockCommand {
|
|||||||
this.environmentService,
|
this.environmentService,
|
||||||
this.organizationApiService,
|
this.organizationApiService,
|
||||||
this.logout,
|
this.logout,
|
||||||
|
this.i18nService,
|
||||||
);
|
);
|
||||||
const convertResponse = await convertToKeyConnectorCommand.run();
|
const convertResponse = await convertToKeyConnectorCommand.run();
|
||||||
if (!convertResponse.success) {
|
if (!convertResponse.success) {
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ export abstract class BaseProgram {
|
|||||||
this.serviceContainer.environmentService,
|
this.serviceContainer.environmentService,
|
||||||
this.serviceContainer.organizationApiService,
|
this.serviceContainer.organizationApiService,
|
||||||
this.serviceContainer.logout,
|
this.serviceContainer.logout,
|
||||||
|
this.serviceContainer.i18nService,
|
||||||
);
|
);
|
||||||
const response = await command.run(null, null);
|
const response = await command.run(null, null);
|
||||||
if (!response.success) {
|
if (!response.success) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { UserId } from "@bitwarden/common/types/guid";
|
|||||||
|
|
||||||
import { Response } from "../models/response";
|
import { Response } from "../models/response";
|
||||||
import { MessageResponse } from "../models/response/message.response";
|
import { MessageResponse } from "../models/response/message.response";
|
||||||
|
import { I18nService } from "../platform/services/i18n.service";
|
||||||
|
|
||||||
import { ConvertToKeyConnectorCommand } from "./convert-to-key-connector.command";
|
import { ConvertToKeyConnectorCommand } from "./convert-to-key-connector.command";
|
||||||
|
|
||||||
@@ -38,6 +39,7 @@ describe("ConvertToKeyConnectorCommand", () => {
|
|||||||
const environmentService = mock<EnvironmentService>();
|
const environmentService = mock<EnvironmentService>();
|
||||||
const organizationApiService = mock<OrganizationApiServiceAbstraction>();
|
const organizationApiService = mock<OrganizationApiServiceAbstraction>();
|
||||||
const logout = jest.fn();
|
const logout = jest.fn();
|
||||||
|
const i18nService = mock<I18nService>();
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
command = new ConvertToKeyConnectorCommand(
|
command = new ConvertToKeyConnectorCommand(
|
||||||
@@ -46,7 +48,27 @@ describe("ConvertToKeyConnectorCommand", () => {
|
|||||||
environmentService,
|
environmentService,
|
||||||
organizationApiService,
|
organizationApiService,
|
||||||
logout,
|
logout,
|
||||||
|
i18nService,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
i18nService.t.mockImplementation((key: string) => {
|
||||||
|
switch (key) {
|
||||||
|
case "removeMasterPasswordForOrganizationUserKeyConnector":
|
||||||
|
return "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator. Organization name: Test Organization. Key Connector domain: https://keyconnector.example.com";
|
||||||
|
case "removeMasterPasswordAndUnlock":
|
||||||
|
return "Remove master password and unlock";
|
||||||
|
case "leaveOrganizationAndUnlock":
|
||||||
|
return "Leave organization and unlock";
|
||||||
|
case "logOut":
|
||||||
|
return "Log out";
|
||||||
|
case "youHaveBeenLoggedOut":
|
||||||
|
return "You have been logged out.";
|
||||||
|
case "organizationUsingKeyConnectorOptInLoggedOut":
|
||||||
|
return "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.";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("run", () => {
|
describe("run", () => {
|
||||||
@@ -73,7 +95,10 @@ describe("ConvertToKeyConnectorCommand", () => {
|
|||||||
keyConnectorService.getManagingOrganization.mockResolvedValue(organization);
|
keyConnectorService.getManagingOrganization.mockResolvedValue(organization);
|
||||||
|
|
||||||
(createPromptModule as jest.Mock).mockImplementation(() =>
|
(createPromptModule as jest.Mock).mockImplementation(() =>
|
||||||
jest.fn(() => Promise.resolve({ convert: "exit" })),
|
jest.fn((prompt) => {
|
||||||
|
assertPrompt(prompt);
|
||||||
|
return Promise.resolve({ convert: "exit" });
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const response = await command.run();
|
const response = await command.run();
|
||||||
@@ -95,14 +120,20 @@ describe("ConvertToKeyConnectorCommand", () => {
|
|||||||
} as Environment);
|
} as Environment);
|
||||||
|
|
||||||
(createPromptModule as jest.Mock).mockImplementation(() =>
|
(createPromptModule as jest.Mock).mockImplementation(() =>
|
||||||
jest.fn(() => Promise.resolve({ convert: "remove" })),
|
jest.fn((prompt) => {
|
||||||
|
assertPrompt(prompt);
|
||||||
|
return Promise.resolve({ convert: "remove" });
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const response = await command.run();
|
const response = await command.run();
|
||||||
|
|
||||||
expect(response).not.toBeNull();
|
expect(response).not.toBeNull();
|
||||||
expect(response.success).toEqual(true);
|
expect(response.success).toEqual(true);
|
||||||
expect(keyConnectorService.migrateUser).toHaveBeenCalledWith(userId);
|
expect(keyConnectorService.migrateUser).toHaveBeenCalledWith(
|
||||||
|
organization.keyConnectorUrl,
|
||||||
|
userId,
|
||||||
|
);
|
||||||
expect(environmentService.setEnvironment).toHaveBeenCalledWith(Region.SelfHosted, {
|
expect(environmentService.setEnvironment).toHaveBeenCalledWith(Region.SelfHosted, {
|
||||||
keyConnector: organization.keyConnectorUrl,
|
keyConnector: organization.keyConnectorUrl,
|
||||||
} as Urls);
|
} as Urls);
|
||||||
@@ -113,7 +144,10 @@ describe("ConvertToKeyConnectorCommand", () => {
|
|||||||
keyConnectorService.getManagingOrganization.mockResolvedValue(organization);
|
keyConnectorService.getManagingOrganization.mockResolvedValue(organization);
|
||||||
|
|
||||||
(createPromptModule as jest.Mock).mockImplementation(() =>
|
(createPromptModule as jest.Mock).mockImplementation(() =>
|
||||||
jest.fn(() => Promise.resolve({ convert: "remove" })),
|
jest.fn((prompt) => {
|
||||||
|
assertPrompt(prompt);
|
||||||
|
return Promise.resolve({ convert: "remove" });
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
keyConnectorService.migrateUser.mockRejectedValue(new Error("Migration failed"));
|
keyConnectorService.migrateUser.mockRejectedValue(new Error("Migration failed"));
|
||||||
@@ -127,7 +161,10 @@ describe("ConvertToKeyConnectorCommand", () => {
|
|||||||
keyConnectorService.getManagingOrganization.mockResolvedValue(organization);
|
keyConnectorService.getManagingOrganization.mockResolvedValue(organization);
|
||||||
|
|
||||||
(createPromptModule as jest.Mock).mockImplementation(() =>
|
(createPromptModule as jest.Mock).mockImplementation(() =>
|
||||||
jest.fn(() => Promise.resolve({ convert: "leave" })),
|
jest.fn((prompt) => {
|
||||||
|
assertPrompt(prompt);
|
||||||
|
return Promise.resolve({ convert: "leave" });
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const response = await command.run();
|
const response = await command.run();
|
||||||
@@ -136,5 +173,34 @@ describe("ConvertToKeyConnectorCommand", () => {
|
|||||||
expect(response.success).toEqual(true);
|
expect(response.success).toEqual(true);
|
||||||
expect(organizationApiService.leave).toHaveBeenCalledWith(organization.id);
|
expect(organizationApiService.leave).toHaveBeenCalledWith(organization.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function assertPrompt(prompt: unknown) {
|
||||||
|
expect(typeof prompt).toEqual("object");
|
||||||
|
expect(prompt).toHaveProperty("type");
|
||||||
|
expect(prompt).toHaveProperty("name");
|
||||||
|
expect(prompt).toHaveProperty("message");
|
||||||
|
expect(prompt).toHaveProperty("choices");
|
||||||
|
const promptObj = prompt as Record<string, unknown>;
|
||||||
|
expect(promptObj["type"]).toEqual("list");
|
||||||
|
expect(promptObj["name"]).toEqual("convert");
|
||||||
|
expect(promptObj["message"]).toEqual(
|
||||||
|
`A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator. Organization name: ${organization.name}. Key Connector domain: ${organization.keyConnectorUrl}`,
|
||||||
|
);
|
||||||
|
expect(promptObj["choices"]).toBeInstanceOf(Array);
|
||||||
|
const choices = promptObj["choices"] as Array<Record<string, unknown>>;
|
||||||
|
expect(choices).toHaveLength(3);
|
||||||
|
expect(choices[0]).toEqual({
|
||||||
|
name: "Remove master password and unlock",
|
||||||
|
value: "remove",
|
||||||
|
});
|
||||||
|
expect(choices[1]).toEqual({
|
||||||
|
name: "Leave organization and unlock",
|
||||||
|
value: "leave",
|
||||||
|
});
|
||||||
|
expect(choices[2]).toEqual({
|
||||||
|
name: "Log out",
|
||||||
|
value: "exit",
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { UserId } from "@bitwarden/common/types/guid";
|
|||||||
|
|
||||||
import { Response } from "../models/response";
|
import { Response } from "../models/response";
|
||||||
import { MessageResponse } from "../models/response/message.response";
|
import { MessageResponse } from "../models/response/message.response";
|
||||||
|
import { I18nService } from "../platform/services/i18n.service";
|
||||||
|
|
||||||
export class ConvertToKeyConnectorCommand {
|
export class ConvertToKeyConnectorCommand {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -19,6 +20,7 @@ export class ConvertToKeyConnectorCommand {
|
|||||||
private environmentService: EnvironmentService,
|
private environmentService: EnvironmentService,
|
||||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||||
private logout: () => Promise<void>,
|
private logout: () => Promise<void>,
|
||||||
|
private i18nService: I18nService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async run(): Promise<Response> {
|
async run(): Promise<Response> {
|
||||||
@@ -28,8 +30,7 @@ export class ConvertToKeyConnectorCommand {
|
|||||||
await this.logout();
|
await this.logout();
|
||||||
return Response.error(
|
return Response.error(
|
||||||
new MessageResponse(
|
new MessageResponse(
|
||||||
"An organization you are a member of is using Key Connector. " +
|
this.i18nService.t("organizationUsingKeyConnectorOptInLoggedOut"),
|
||||||
"In order to access the vault, you must opt-in to Key Connector now via the web vault. You have been logged out.",
|
|
||||||
null,
|
null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -40,20 +41,22 @@ export class ConvertToKeyConnectorCommand {
|
|||||||
const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({
|
const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({
|
||||||
type: "list",
|
type: "list",
|
||||||
name: "convert",
|
name: "convert",
|
||||||
message:
|
message: this.i18nService.t(
|
||||||
organization.name +
|
"removeMasterPasswordForOrganizationUserKeyConnector",
|
||||||
" is using a self-hosted key server. A master password is no longer required to log in for members of this organization. ",
|
organization.name,
|
||||||
|
organization.keyConnectorUrl,
|
||||||
|
),
|
||||||
choices: [
|
choices: [
|
||||||
{
|
{
|
||||||
name: "Remove master password and unlock",
|
name: this.i18nService.t("removeMasterPasswordAndUnlock"),
|
||||||
value: "remove",
|
value: "remove",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Leave organization and unlock",
|
name: this.i18nService.t("leaveOrganizationAndUnlock"),
|
||||||
value: "leave",
|
value: "leave",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Log out",
|
name: this.i18nService.t("logOut"),
|
||||||
value: "exit",
|
value: "exit",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -61,7 +64,7 @@ export class ConvertToKeyConnectorCommand {
|
|||||||
|
|
||||||
if (answer.convert === "remove") {
|
if (answer.convert === "remove") {
|
||||||
try {
|
try {
|
||||||
await this.keyConnectorService.migrateUser(this.userId);
|
await this.keyConnectorService.migrateUser(organization.keyConnectorUrl, this.userId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await this.logout();
|
await this.logout();
|
||||||
throw e;
|
throw e;
|
||||||
@@ -79,7 +82,7 @@ export class ConvertToKeyConnectorCommand {
|
|||||||
return Response.success();
|
return Response.success();
|
||||||
} else {
|
} else {
|
||||||
await this.logout();
|
await this.logout();
|
||||||
return Response.error("You have been logged out.");
|
return Response.error(this.i18nService.t("youHaveBeenLoggedOut"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,5 +184,33 @@
|
|||||||
"example": "JustTrust.us"
|
"example": "JustTrust.us"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"organizationUsingKeyConnectorOptInLoggedOut": {
|
||||||
|
"message": "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."
|
||||||
|
},
|
||||||
|
"removeMasterPasswordForOrganizationUserKeyConnector": {
|
||||||
|
"message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator. Organization name: $ORGANIZATION$. Key Connector domain: $KEYCONNECTORDOMAIN$",
|
||||||
|
"placeholders": {
|
||||||
|
"organization": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "My Org Name"
|
||||||
|
},
|
||||||
|
"keyConnectorDomain": {
|
||||||
|
"content": "$2",
|
||||||
|
"example": "Key Connector domain"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"removeMasterPasswordAndUnlock": {
|
||||||
|
"message": "Remove master password and unlock"
|
||||||
|
},
|
||||||
|
"leaveOrganizationAndUnlock": {
|
||||||
|
"message": "Leave organization and unlock"
|
||||||
|
},
|
||||||
|
"logOut": {
|
||||||
|
"message": "Log out"
|
||||||
|
},
|
||||||
|
"youHaveBeenLoggedOut": {
|
||||||
|
"message": "You have been logged out."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ export class OssServeConfigurator {
|
|||||||
this.serviceContainer.environmentService,
|
this.serviceContainer.environmentService,
|
||||||
this.serviceContainer.organizationApiService,
|
this.serviceContainer.organizationApiService,
|
||||||
async () => await this.serviceContainer.logout(),
|
async () => await this.serviceContainer.logout(),
|
||||||
|
this.serviceContainer.i18nService,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.sendCreateCommand = new SendCreateCommand(
|
this.sendCreateCommand = new SendCreateCommand(
|
||||||
|
|||||||
@@ -283,6 +283,7 @@ export class Program extends BaseProgram {
|
|||||||
this.serviceContainer.environmentService,
|
this.serviceContainer.environmentService,
|
||||||
this.serviceContainer.organizationApiService,
|
this.serviceContainer.organizationApiService,
|
||||||
async () => await this.serviceContainer.logout(),
|
async () => await this.serviceContainer.logout(),
|
||||||
|
this.serviceContainer.i18nService,
|
||||||
);
|
);
|
||||||
const response = await command.run(password, cmd);
|
const response = await command.run(password, cmd);
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
<div id="remove-password-page" *ngIf="!loading">
|
<div id="remove-password-page" *ngIf="!loading">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<h1>{{ "removeMasterPassword" | i18n }}</h1>
|
<h1>{{ "removeMasterPassword" | i18n }}</h1>
|
||||||
<p>{{ "convertOrganizationEncryptionDesc" | i18n: organization.name }}</p>
|
<p>{{ "removeMasterPasswordForOrganizationUserKeyConnector" | i18n }}</p>
|
||||||
|
<p class="tw-mb-0">{{ "organizationName" | i18n }}:</p>
|
||||||
|
<p class="tw-text-muted tw-mb-6">{{ organization.name }}</p>
|
||||||
|
<p class="tw-mb-0">{{ "keyConnectorDomain" | i18n }}:</p>
|
||||||
|
<p class="tw-text-muted tw-mb-6">{{ organization.keyConnectorUrl }}</p>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button type="submit" class="btn primary block" [disabled]="action" (click)="convert()">
|
<button type="submit" class="btn primary block" [disabled]="action" (click)="convert()">
|
||||||
<b [hidden]="continuing">{{ "removeMasterPassword" | i18n }}</b>
|
<b [hidden]="continuing">{{ "removeMasterPassword" | i18n }}</b>
|
||||||
|
|||||||
@@ -2512,14 +2512,14 @@
|
|||||||
"removedMasterPassword": {
|
"removedMasterPassword": {
|
||||||
"message": "Master password removed"
|
"message": "Master password removed"
|
||||||
},
|
},
|
||||||
"convertOrganizationEncryptionDesc": {
|
"removeMasterPasswordForOrganizationUserKeyConnector": {
|
||||||
"message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.",
|
"message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator."
|
||||||
"placeholders": {
|
},
|
||||||
"organization": {
|
"organizationName": {
|
||||||
"content": "$1",
|
"message": "Organization name"
|
||||||
"example": "My Org Name"
|
},
|
||||||
}
|
"keyConnectorDomain": {
|
||||||
}
|
"message": "Key Connector domain"
|
||||||
},
|
},
|
||||||
"leaveOrganization": {
|
"leaveOrganization": {
|
||||||
"message": "Leave organization"
|
"message": "Leave organization"
|
||||||
|
|||||||
@@ -8,7 +8,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="!loading">
|
<div *ngIf="!loading">
|
||||||
<p>{{ "convertOrganizationEncryptionDesc" | i18n: organization.name }}</p>
|
<p>{{ "removeMasterPasswordForOrganizationUserKeyConnector" | i18n }}</p>
|
||||||
|
<p class="tw-mb-0">{{ "organizationName" | i18n }}:</p>
|
||||||
|
<p class="tw-text-muted tw-mb-6">{{ organization.name }}</p>
|
||||||
|
<p class="tw-mb-0">{{ "keyConnectorDomain" | i18n }}:</p>
|
||||||
|
<p class="tw-text-muted tw-mb-6">{{ organization.keyConnectorUrl }}</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
bitButton
|
bitButton
|
||||||
|
|||||||
@@ -6477,14 +6477,11 @@
|
|||||||
"invalidVerificationCode": {
|
"invalidVerificationCode": {
|
||||||
"message": "Invalid verification code"
|
"message": "Invalid verification code"
|
||||||
},
|
},
|
||||||
"convertOrganizationEncryptionDesc": {
|
"removeMasterPasswordForOrganizationUserKeyConnector": {
|
||||||
"message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.",
|
"message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator."
|
||||||
"placeholders": {
|
},
|
||||||
"organization": {
|
"keyConnectorDomain": {
|
||||||
"content": "$1",
|
"message": "Key Connector domain"
|
||||||
"example": "My Org Name"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"leaveOrganization": {
|
"leaveOrganization": {
|
||||||
"message": "Leave organization"
|
"message": "Leave organization"
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ import { IdentityTokenResponse } from "../../../auth/models/response/identity-to
|
|||||||
import { UserId } from "../../../types/guid";
|
import { UserId } from "../../../types/guid";
|
||||||
|
|
||||||
export abstract class KeyConnectorService {
|
export abstract class KeyConnectorService {
|
||||||
abstract setMasterKeyFromUrl(url: string, userId: UserId): Promise<void>;
|
abstract setMasterKeyFromUrl(keyConnectorUrl: string, userId: UserId): Promise<void>;
|
||||||
|
|
||||||
abstract getManagingOrganization(userId: UserId): Promise<Organization>;
|
abstract getManagingOrganization(userId: UserId): Promise<Organization>;
|
||||||
|
|
||||||
abstract getUsesKeyConnector(userId: UserId): Promise<boolean>;
|
abstract getUsesKeyConnector(userId: UserId): Promise<boolean>;
|
||||||
|
|
||||||
abstract migrateUser(userId: UserId): Promise<void>;
|
abstract migrateUser(keyConnectorUrl: string, userId: UserId): Promise<void>;
|
||||||
|
|
||||||
abstract convertNewSsoUserToKeyConnector(
|
abstract convertNewSsoUserToKeyConnector(
|
||||||
tokenResponse: IdentityTokenResponse,
|
tokenResponse: IdentityTokenResponse,
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ describe("KeyConnectorService", () => {
|
|||||||
key: "eO9nVlVl3I3sU6O+CyK0kEkpGtl/auT84Hig2WTXmZtDTqYtKpDvUPfjhgMOHf+KQzx++TVS2AOLYq856Caa7w==",
|
key: "eO9nVlVl3I3sU6O+CyK0kEkpGtl/auT84Hig2WTXmZtDTqYtKpDvUPfjhgMOHf+KQzx++TVS2AOLYq856Caa7w==",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const keyConnectorUrl = "https://key-connector-url.com";
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
@@ -124,27 +126,9 @@ describe("KeyConnectorService", () => {
|
|||||||
it("should return the managing organization with key connector enabled", async () => {
|
it("should return the managing organization with key connector enabled", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const orgs = [
|
const orgs = [
|
||||||
organizationData(
|
organizationData(true, true, keyConnectorUrl, OrganizationUserType.User, false),
|
||||||
true,
|
organizationData(false, true, keyConnectorUrl, OrganizationUserType.User, false),
|
||||||
true,
|
organizationData(true, false, keyConnectorUrl, OrganizationUserType.User, false),
|
||||||
"https://key-connector-url.com",
|
|
||||||
OrganizationUserType.User,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
organizationData(
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
"https://key-connector-url.com",
|
|
||||||
OrganizationUserType.User,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
organizationData(
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
"https://key-connector-url.com",
|
|
||||||
OrganizationUserType.User,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
organizationData(true, true, "https://other-url.com", OrganizationUserType.User, false),
|
organizationData(true, true, "https://other-url.com", OrganizationUserType.User, false),
|
||||||
];
|
];
|
||||||
organizationService.organizations$.mockReturnValue(of(orgs));
|
organizationService.organizations$.mockReturnValue(of(orgs));
|
||||||
@@ -159,20 +143,8 @@ describe("KeyConnectorService", () => {
|
|||||||
it("should return undefined if no managing organization with key connector enabled is found", async () => {
|
it("should return undefined if no managing organization with key connector enabled is found", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const orgs = [
|
const orgs = [
|
||||||
organizationData(
|
organizationData(true, false, keyConnectorUrl, OrganizationUserType.User, false),
|
||||||
true,
|
organizationData(false, false, keyConnectorUrl, OrganizationUserType.User, false),
|
||||||
false,
|
|
||||||
"https://key-connector-url.com",
|
|
||||||
OrganizationUserType.User,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
organizationData(
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
"https://key-connector-url.com",
|
|
||||||
OrganizationUserType.User,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
organizationService.organizations$.mockReturnValue(of(orgs));
|
organizationService.organizations$.mockReturnValue(of(orgs));
|
||||||
|
|
||||||
@@ -186,8 +158,8 @@ describe("KeyConnectorService", () => {
|
|||||||
it("should return undefined if user is Owner or Admin", async () => {
|
it("should return undefined if user is Owner or Admin", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const orgs = [
|
const orgs = [
|
||||||
organizationData(true, true, "https://key-connector-url.com", 0, false),
|
organizationData(true, true, keyConnectorUrl, 0, false),
|
||||||
organizationData(true, true, "https://key-connector-url.com", 1, false),
|
organizationData(true, true, keyConnectorUrl, 1, false),
|
||||||
];
|
];
|
||||||
organizationService.organizations$.mockReturnValue(of(orgs));
|
organizationService.organizations$.mockReturnValue(of(orgs));
|
||||||
|
|
||||||
@@ -201,20 +173,8 @@ describe("KeyConnectorService", () => {
|
|||||||
it("should return undefined if user is a Provider", async () => {
|
it("should return undefined if user is a Provider", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const orgs = [
|
const orgs = [
|
||||||
organizationData(
|
organizationData(true, true, keyConnectorUrl, OrganizationUserType.User, true),
|
||||||
true,
|
organizationData(false, true, keyConnectorUrl, OrganizationUserType.User, true),
|
||||||
true,
|
|
||||||
"https://key-connector-url.com",
|
|
||||||
OrganizationUserType.User,
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
organizationData(
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
"https://key-connector-url.com",
|
|
||||||
OrganizationUserType.User,
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
organizationService.organizations$.mockReturnValue(of(orgs));
|
organizationService.organizations$.mockReturnValue(of(orgs));
|
||||||
|
|
||||||
@@ -229,7 +189,7 @@ describe("KeyConnectorService", () => {
|
|||||||
describe("setMasterKeyFromUrl", () => {
|
describe("setMasterKeyFromUrl", () => {
|
||||||
it("should set the master key from the provided URL", async () => {
|
it("should set the master key from the provided URL", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const url = "https://key-connector-url.com";
|
const url = keyConnectorUrl;
|
||||||
|
|
||||||
apiService.getMasterKeyFromKeyConnector.mockResolvedValue(mockMasterKeyResponse);
|
apiService.getMasterKeyFromKeyConnector.mockResolvedValue(mockMasterKeyResponse);
|
||||||
|
|
||||||
@@ -247,7 +207,7 @@ describe("KeyConnectorService", () => {
|
|||||||
|
|
||||||
it("should handle errors thrown during the process", async () => {
|
it("should handle errors thrown during the process", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const url = "https://key-connector-url.com";
|
const url = keyConnectorUrl;
|
||||||
|
|
||||||
const error = new Error("Failed to get master key");
|
const error = new Error("Failed to get master key");
|
||||||
apiService.getMasterKeyFromKeyConnector.mockRejectedValue(error);
|
apiService.getMasterKeyFromKeyConnector.mockRejectedValue(error);
|
||||||
@@ -267,29 +227,20 @@ describe("KeyConnectorService", () => {
|
|||||||
describe("migrateUser", () => {
|
describe("migrateUser", () => {
|
||||||
it("should migrate the user to the key connector", async () => {
|
it("should migrate the user to the key connector", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const organization = organizationData(
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
"https://key-connector-url.com",
|
|
||||||
OrganizationUserType.User,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
const masterKey = getMockMasterKey();
|
const masterKey = getMockMasterKey();
|
||||||
masterPasswordService.masterKeySubject.next(masterKey);
|
masterPasswordService.masterKeySubject.next(masterKey);
|
||||||
const keyConnectorRequest = new KeyConnectorUserKeyRequest(
|
const keyConnectorRequest = new KeyConnectorUserKeyRequest(
|
||||||
Utils.fromBufferToB64(masterKey.inner().encryptionKey),
|
Utils.fromBufferToB64(masterKey.inner().encryptionKey),
|
||||||
);
|
);
|
||||||
|
|
||||||
jest.spyOn(keyConnectorService, "getManagingOrganization").mockResolvedValue(organization);
|
|
||||||
jest.spyOn(apiService, "postUserKeyToKeyConnector").mockResolvedValue();
|
jest.spyOn(apiService, "postUserKeyToKeyConnector").mockResolvedValue();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await keyConnectorService.migrateUser(mockUserId);
|
await keyConnectorService.migrateUser(keyConnectorUrl, mockUserId);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(keyConnectorService.getManagingOrganization).toHaveBeenCalled();
|
|
||||||
expect(apiService.postUserKeyToKeyConnector).toHaveBeenCalledWith(
|
expect(apiService.postUserKeyToKeyConnector).toHaveBeenCalledWith(
|
||||||
organization.keyConnectorUrl,
|
keyConnectorUrl,
|
||||||
keyConnectorRequest,
|
keyConnectorRequest,
|
||||||
);
|
);
|
||||||
expect(apiService.postConvertToKeyConnector).toHaveBeenCalled();
|
expect(apiService.postConvertToKeyConnector).toHaveBeenCalled();
|
||||||
@@ -297,34 +248,23 @@ describe("KeyConnectorService", () => {
|
|||||||
|
|
||||||
it("should handle errors thrown during migration", async () => {
|
it("should handle errors thrown during migration", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const organization = organizationData(
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
"https://key-connector-url.com",
|
|
||||||
OrganizationUserType.User,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
const masterKey = getMockMasterKey();
|
const masterKey = getMockMasterKey();
|
||||||
const keyConnectorRequest = new KeyConnectorUserKeyRequest(
|
const keyConnectorRequest = new KeyConnectorUserKeyRequest(
|
||||||
Utils.fromBufferToB64(masterKey.inner().encryptionKey),
|
Utils.fromBufferToB64(masterKey.inner().encryptionKey),
|
||||||
);
|
);
|
||||||
const error = new Error("Failed to post user key to key connector");
|
|
||||||
organizationService.organizations$.mockReturnValue(of([organization]));
|
|
||||||
|
|
||||||
masterPasswordService.masterKeySubject.next(masterKey);
|
masterPasswordService.masterKeySubject.next(masterKey);
|
||||||
jest.spyOn(keyConnectorService, "getManagingOrganization").mockResolvedValue(organization);
|
const error = new Error("Failed to post user key to key connector");
|
||||||
jest.spyOn(apiService, "postUserKeyToKeyConnector").mockRejectedValue(error);
|
jest.spyOn(apiService, "postUserKeyToKeyConnector").mockRejectedValue(error);
|
||||||
jest.spyOn(logService, "error");
|
jest.spyOn(logService, "error");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Act
|
// Act
|
||||||
await keyConnectorService.migrateUser(mockUserId);
|
await keyConnectorService.migrateUser(keyConnectorUrl, mockUserId);
|
||||||
} catch {
|
} catch {
|
||||||
// Assert
|
// Assert
|
||||||
expect(logService.error).toHaveBeenCalledWith(error);
|
expect(logService.error).toHaveBeenCalledWith(error);
|
||||||
expect(keyConnectorService.getManagingOrganization).toHaveBeenCalled();
|
|
||||||
expect(apiService.postUserKeyToKeyConnector).toHaveBeenCalledWith(
|
expect(apiService.postUserKeyToKeyConnector).toHaveBeenCalledWith(
|
||||||
organization.keyConnectorUrl,
|
keyConnectorUrl,
|
||||||
keyConnectorRequest,
|
keyConnectorRequest,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -336,7 +276,7 @@ describe("KeyConnectorService", () => {
|
|||||||
const organization = organizationData(
|
const organization = organizationData(
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
"https://key-connector-url.com",
|
keyConnectorUrl,
|
||||||
OrganizationUserType.User,
|
OrganizationUserType.User,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
@@ -364,7 +304,7 @@ describe("KeyConnectorService", () => {
|
|||||||
const organization = organizationData(
|
const organization = organizationData(
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
"https://key-connector-url.com",
|
keyConnectorUrl,
|
||||||
OrganizationUserType.User,
|
OrganizationUserType.User,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
@@ -379,7 +319,7 @@ describe("KeyConnectorService", () => {
|
|||||||
const organization = organizationData(
|
const organization = organizationData(
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
"https://key-connector-url.com",
|
keyConnectorUrl,
|
||||||
OrganizationUserType.Admin,
|
OrganizationUserType.Admin,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
@@ -394,7 +334,7 @@ describe("KeyConnectorService", () => {
|
|||||||
const organization = organizationData(
|
const organization = organizationData(
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
"https://key-connector-url.com",
|
keyConnectorUrl,
|
||||||
OrganizationUserType.Owner,
|
OrganizationUserType.Owner,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
@@ -409,7 +349,7 @@ describe("KeyConnectorService", () => {
|
|||||||
const organization = organizationData(
|
const organization = organizationData(
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
"https://key-connector-url.com",
|
keyConnectorUrl,
|
||||||
OrganizationUserType.User,
|
OrganizationUserType.User,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -90,18 +90,14 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async migrateUser(userId: UserId) {
|
async migrateUser(keyConnectorUrl: string, userId: UserId) {
|
||||||
const organization = await this.getManagingOrganization(userId);
|
|
||||||
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||||
const keyConnectorRequest = new KeyConnectorUserKeyRequest(
|
const keyConnectorRequest = new KeyConnectorUserKeyRequest(
|
||||||
Utils.fromBufferToB64(masterKey.inner().encryptionKey),
|
Utils.fromBufferToB64(masterKey.inner().encryptionKey),
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.apiService.postUserKeyToKeyConnector(
|
await this.apiService.postUserKeyToKeyConnector(keyConnectorUrl, keyConnectorRequest);
|
||||||
organization.keyConnectorUrl,
|
|
||||||
keyConnectorRequest,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.handleKeyConnectorError(e);
|
this.handleKeyConnectorError(e);
|
||||||
}
|
}
|
||||||
@@ -112,9 +108,9 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: UserKey should be renamed to MasterKey and typed accordingly
|
// TODO: UserKey should be renamed to MasterKey and typed accordingly
|
||||||
async setMasterKeyFromUrl(url: string, userId: UserId) {
|
async setMasterKeyFromUrl(keyConnectorUrl: string, userId: UserId) {
|
||||||
try {
|
try {
|
||||||
const masterKeyResponse = await this.apiService.getMasterKeyFromKeyConnector(url);
|
const masterKeyResponse = await this.apiService.getMasterKeyFromKeyConnector(keyConnectorUrl);
|
||||||
const keyArr = Utils.fromB64ToArray(masterKeyResponse.key);
|
const keyArr = Utils.fromB64ToArray(masterKeyResponse.key);
|
||||||
const masterKey = new SymmetricCryptoKey(keyArr) as MasterKey;
|
const masterKey = new SymmetricCryptoKey(keyArr) as MasterKey;
|
||||||
await this.masterPasswordService.setMasterKey(masterKey, userId);
|
await this.masterPasswordService.setMasterKey(masterKey, userId);
|
||||||
@@ -192,7 +188,7 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
|
|||||||
throw new Error("Key Connector error");
|
throw new Error("Key Connector error");
|
||||||
}
|
}
|
||||||
|
|
||||||
private findManagingOrganization(organizations: Organization[]) {
|
private findManagingOrganization(organizations: Organization[]): Organization | undefined {
|
||||||
return organizations.find(
|
return organizations.find(
|
||||||
(o) =>
|
(o) =>
|
||||||
o.keyConnectorEnabled &&
|
o.keyConnectorEnabled &&
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ describe("RemovePasswordComponent", () => {
|
|||||||
const organization = {
|
const organization = {
|
||||||
id: "test-organization-id",
|
id: "test-organization-id",
|
||||||
name: "test-organization-name",
|
name: "test-organization-name",
|
||||||
|
keyConnectorUrl: "https://key-connector-url.com",
|
||||||
} as Organization;
|
} as Organization;
|
||||||
|
|
||||||
const accountService = mockAccountServiceWith(userId);
|
const accountService = mockAccountServiceWith(userId);
|
||||||
@@ -124,7 +125,10 @@ describe("RemovePasswordComponent", () => {
|
|||||||
await component.convert();
|
await component.convert();
|
||||||
|
|
||||||
expect(component.continuing).toBe(true);
|
expect(component.continuing).toBe(true);
|
||||||
expect(mockKeyConnectorService.migrateUser).toHaveBeenCalledWith(userId);
|
expect(mockKeyConnectorService.migrateUser).toHaveBeenCalledWith(
|
||||||
|
organization.keyConnectorUrl,
|
||||||
|
userId,
|
||||||
|
);
|
||||||
expect(mockToastService.showToast).toHaveBeenCalledWith({
|
expect(mockToastService.showToast).toHaveBeenCalledWith({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
message: "removed master password",
|
message: "removed master password",
|
||||||
@@ -140,7 +144,10 @@ describe("RemovePasswordComponent", () => {
|
|||||||
await component.convert();
|
await component.convert();
|
||||||
|
|
||||||
expect(component.continuing).toBe(false);
|
expect(component.continuing).toBe(false);
|
||||||
expect(mockKeyConnectorService.migrateUser).toHaveBeenCalledWith(userId);
|
expect(mockKeyConnectorService.migrateUser).toHaveBeenCalledWith(
|
||||||
|
organization.keyConnectorUrl,
|
||||||
|
userId,
|
||||||
|
);
|
||||||
expect(mockToastService.showToast).toHaveBeenCalledWith({
|
expect(mockToastService.showToast).toHaveBeenCalledWith({
|
||||||
variant: "error",
|
variant: "error",
|
||||||
title: "error occurred",
|
title: "error occurred",
|
||||||
@@ -164,7 +171,10 @@ describe("RemovePasswordComponent", () => {
|
|||||||
await component.convert();
|
await component.convert();
|
||||||
|
|
||||||
expect(component.continuing).toBe(false);
|
expect(component.continuing).toBe(false);
|
||||||
expect(mockKeyConnectorService.migrateUser).toHaveBeenCalledWith(userId);
|
expect(mockKeyConnectorService.migrateUser).toHaveBeenCalledWith(
|
||||||
|
organization.keyConnectorUrl,
|
||||||
|
userId,
|
||||||
|
);
|
||||||
expect(mockToastService.showToast).toHaveBeenCalledWith({
|
expect(mockToastService.showToast).toHaveBeenCalledWith({
|
||||||
variant: "error",
|
variant: "error",
|
||||||
title: "error occurred",
|
title: "error occurred",
|
||||||
|
|||||||
@@ -66,7 +66,10 @@ export class RemovePasswordComponent implements OnInit {
|
|||||||
this.continuing = true;
|
this.continuing = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.keyConnectorService.migrateUser(this.activeUserId);
|
await this.keyConnectorService.migrateUser(
|
||||||
|
this.organization.keyConnectorUrl,
|
||||||
|
this.activeUserId,
|
||||||
|
);
|
||||||
|
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
|
|||||||
Reference in New Issue
Block a user