1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-20 10:13:31 +00:00

Merge branch 'master' of https://github.com/bitwarden/bitwarden into PS-1854-split-split-services-between-backgrond-and-visualizations

This commit is contained in:
Daniel James Smith
2022-11-21 16:53:38 +01:00
125 changed files with 1797 additions and 1658 deletions

View File

@@ -12,13 +12,13 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { TokenService } from "@bitwarden/common/abstractions/token.service";
import { TwoFactorService } from "@bitwarden/common/abstractions/twoFactor.service";
import { ApiLogInStrategy } from "@bitwarden/common/misc/logInStrategies/apiLogin.strategy";
import { UserApiLogInStrategy } from "@bitwarden/common/misc/logInStrategies/user-api-login.strategy";
import { Utils } from "@bitwarden/common/misc/utils";
import { ApiLogInCredentials } from "@bitwarden/common/models/domain/log-in-credentials";
import { UserApiLogInCredentials } from "@bitwarden/common/models/domain/log-in-credentials";
import { identityTokenResponseFactory } from "./logIn.strategy.spec";
describe("ApiLogInStrategy", () => {
describe("UserApiLogInStrategy", () => {
let cryptoService: SubstituteOf<CryptoService>;
let apiService: SubstituteOf<ApiService>;
let tokenService: SubstituteOf<TokenService>;
@@ -31,8 +31,8 @@ describe("ApiLogInStrategy", () => {
let stateService: SubstituteOf<StateService>;
let twoFactorService: SubstituteOf<TwoFactorService>;
let apiLogInStrategy: ApiLogInStrategy;
let credentials: ApiLogInCredentials;
let apiLogInStrategy: UserApiLogInStrategy;
let credentials: UserApiLogInCredentials;
const deviceId = Utils.newGuid();
const keyConnectorUrl = "KEY_CONNECTOR_URL";
@@ -55,7 +55,7 @@ describe("ApiLogInStrategy", () => {
appIdService.getAppId().resolves(deviceId);
tokenService.getTwoFactorToken().resolves(null);
apiLogInStrategy = new ApiLogInStrategy(
apiLogInStrategy = new UserApiLogInStrategy(
cryptoService,
apiService,
tokenService,
@@ -69,7 +69,7 @@ describe("ApiLogInStrategy", () => {
keyConnectorService
);
credentials = new ApiLogInCredentials(apiClientId, apiClientSecret);
credentials = new UserApiLogInCredentials(apiClientId, apiClientSecret);
});
it("sends api key credentials to the server", async () => {

View File

@@ -1,12 +1,9 @@
import { MockProxy, mock, any, mockClear, matches } from "jest-mock-extended";
import { BehaviorSubject, firstValueFrom, Subject } from "rxjs";
import { MockProxy, mock, any, mockClear } from "jest-mock-extended";
import { BehaviorSubject, firstValueFrom } from "rxjs";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { SyncNotifierService } from "@bitwarden/common/abstractions/sync/syncNotifier.service.abstraction";
import { OrganizationData } from "@bitwarden/common/models/data/organization.data";
import { SyncResponse } from "@bitwarden/common/models/response/sync.response";
import { OrganizationService } from "@bitwarden/common/services/organization/organization.service";
import { SyncEventArgs } from "@bitwarden/common/types/syncEventArgs";
describe("Organization Service", () => {
let organizationService: OrganizationService;
@@ -14,8 +11,6 @@ describe("Organization Service", () => {
let stateService: MockProxy<StateService>;
let activeAccount: BehaviorSubject<string>;
let activeAccountUnlocked: BehaviorSubject<boolean>;
let syncNotifierService: MockProxy<SyncNotifierService>;
let sync: Subject<SyncEventArgs>;
const resetStateService = async (
customizeStateService: (stateService: MockProxy<StateService>) => void
@@ -25,7 +20,7 @@ describe("Organization Service", () => {
stateService.activeAccount$ = activeAccount;
stateService.activeAccountUnlocked$ = activeAccountUnlocked;
customizeStateService(stateService);
organizationService = new OrganizationService(stateService, syncNotifierService);
organizationService = new OrganizationService(stateService);
await new Promise((r) => setTimeout(r, 50));
};
@@ -41,12 +36,7 @@ describe("Organization Service", () => {
"1": organizationData("1", "Test Org"),
});
sync = new Subject<SyncEventArgs>();
syncNotifierService = mock<SyncNotifierService>();
syncNotifierService.sync$ = sync;
organizationService = new OrganizationService(stateService, syncNotifierService);
organizationService = new OrganizationService(stateService);
});
afterEach(() => {
@@ -169,36 +159,6 @@ describe("Organization Service", () => {
});
});
describe("syncEvent works", () => {
it("Complete event updates data", async () => {
sync.next({
status: "Completed",
successfully: true,
data: new SyncResponse({
profile: {
organizations: [
{
id: "1",
name: "Updated Name",
},
],
},
}),
});
await new Promise((r) => setTimeout(r, 500));
expect(stateService.setOrganizations).toHaveBeenCalledTimes(1);
expect(stateService.setOrganizations).toHaveBeenLastCalledWith(
matches((organizationData: { [id: string]: OrganizationData }) => {
const organization = organizationData["1"];
return organization?.name === "Updated Name";
})
);
});
});
function organizationData(id: string, name: string) {
const data = new OrganizationData({} as any);
data.id = id;

View File

@@ -24,9 +24,9 @@ import { EmergencyAccessUpdateRequest } from "../models/request/emergency-access
import { EventRequest } from "../models/request/event.request";
import { GroupRequest } from "../models/request/group.request";
import { IapCheckRequest } from "../models/request/iap-check.request";
import { ApiTokenRequest } from "../models/request/identity-token/api-token.request";
import { PasswordTokenRequest } from "../models/request/identity-token/password-token.request";
import { SsoTokenRequest } from "../models/request/identity-token/sso-token.request";
import { UserApiTokenRequest } from "../models/request/identity-token/user-api-token.request";
import { ImportCiphersRequest } from "../models/request/import-ciphers.request";
import { ImportOrganizationCiphersRequest } from "../models/request/import-organization-ciphers.request";
import { KdfRequest } from "../models/request/kdf.request";
@@ -175,7 +175,7 @@ export abstract class ApiService {
) => Promise<any>;
postIdentityToken: (
request: PasswordTokenRequest | SsoTokenRequest | ApiTokenRequest
request: PasswordTokenRequest | SsoTokenRequest | UserApiTokenRequest
) => Promise<IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse>;
refreshIdentityToken: () => Promise<any>;

View File

@@ -3,7 +3,7 @@ import { Observable } from "rxjs";
import { AuthenticationStatus } from "../enums/authenticationStatus";
import { AuthResult } from "../models/domain/auth-result";
import {
ApiLogInCredentials,
UserApiLogInCredentials,
PasswordLogInCredentials,
SsoLogInCredentials,
PasswordlessLogInCredentials,
@@ -20,7 +20,7 @@ export abstract class AuthService {
logIn: (
credentials:
| ApiLogInCredentials
| UserApiLogInCredentials
| PasswordLogInCredentials
| SsoLogInCredentials
| PasswordlessLogInCredentials
@@ -31,7 +31,7 @@ export abstract class AuthService {
) => Promise<AuthResult>;
logOut: (callback: () => void) => void;
makePreloginKey: (masterPassword: string, email: string) => Promise<SymmetricCryptoKey>;
authingWithApiKey: () => boolean;
authingWithUserApiKey: () => boolean;
authingWithSso: () => boolean;
authingWithPassword: () => boolean;
authingWithPasswordless: () => boolean;

View File

@@ -12,6 +12,10 @@ export abstract class FolderService {
clearCache: () => Promise<void>;
encrypt: (model: FolderView, key?: SymmetricCryptoKey) => Promise<Folder>;
get: (id: string) => Promise<Folder>;
/**
* @deprecated Only use in CLI!
*/
getFromState: (id: string) => Promise<Folder>;
/**
* @deprecated Only use in CLI!
*/

View File

@@ -1,6 +1,7 @@
import { map, Observable } from "rxjs";
import { Utils } from "../../misc/utils";
import { OrganizationData } from "../../models/data/organization.data";
import { Organization } from "../../models/domain/organization";
import { I18nService } from "../i18n.service";
@@ -83,3 +84,7 @@ export abstract class OrganizationService {
canManageSponsorships: () => Promise<boolean>;
hasOrganizations: () => boolean;
}
export abstract class InternalOrganizationService extends OrganizationService {
replace: (organizations: { [id: string]: OrganizationData }) => Promise<void>;
}

View File

@@ -1,6 +1,6 @@
export enum AuthenticationType {
Password = 0,
Sso = 1,
Api = 2,
UserApi = 2,
Passwordless = 3,
}

View File

@@ -11,23 +11,23 @@ import { TwoFactorProviderType } from "../../enums/twoFactorProviderType";
import { Account, AccountProfile, AccountTokens } from "../../models/domain/account";
import { AuthResult } from "../../models/domain/auth-result";
import {
ApiLogInCredentials,
UserApiLogInCredentials,
PasswordLogInCredentials,
SsoLogInCredentials,
PasswordlessLogInCredentials,
} from "../../models/domain/log-in-credentials";
import { DeviceRequest } from "../../models/request/device.request";
import { ApiTokenRequest } from "../../models/request/identity-token/api-token.request";
import { PasswordTokenRequest } from "../../models/request/identity-token/password-token.request";
import { SsoTokenRequest } from "../../models/request/identity-token/sso-token.request";
import { TokenTwoFactorRequest } from "../../models/request/identity-token/token-two-factor.request";
import { UserApiTokenRequest } from "../../models/request/identity-token/user-api-token.request";
import { KeysRequest } from "../../models/request/keys.request";
import { IdentityCaptchaResponse } from "../../models/response/identity-captcha.response";
import { IdentityTokenResponse } from "../../models/response/identity-token.response";
import { IdentityTwoFactorResponse } from "../../models/response/identity-two-factor.response";
export abstract class LogInStrategy {
protected abstract tokenRequest: ApiTokenRequest | PasswordTokenRequest | SsoTokenRequest;
protected abstract tokenRequest: UserApiTokenRequest | PasswordTokenRequest | SsoTokenRequest;
protected captchaBypassToken: string = null;
constructor(
@@ -44,7 +44,7 @@ export abstract class LogInStrategy {
abstract logIn(
credentials:
| ApiLogInCredentials
| UserApiLogInCredentials
| PasswordLogInCredentials
| SsoLogInCredentials
| PasswordlessLogInCredentials

View File

@@ -9,14 +9,14 @@ import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
import { StateService } from "../../abstractions/state.service";
import { TokenService } from "../../abstractions/token.service";
import { TwoFactorService } from "../../abstractions/twoFactor.service";
import { ApiLogInCredentials } from "../../models/domain/log-in-credentials";
import { ApiTokenRequest } from "../../models/request/identity-token/api-token.request";
import { UserApiLogInCredentials } from "../../models/domain/log-in-credentials";
import { UserApiTokenRequest } from "../../models/request/identity-token/user-api-token.request";
import { IdentityTokenResponse } from "../../models/response/identity-token.response";
import { LogInStrategy } from "./logIn.strategy";
export class ApiLogInStrategy extends LogInStrategy {
tokenRequest: ApiTokenRequest;
export class UserApiLogInStrategy extends LogInStrategy {
tokenRequest: UserApiTokenRequest;
constructor(
cryptoService: CryptoService,
@@ -51,8 +51,8 @@ export class ApiLogInStrategy extends LogInStrategy {
}
}
async logIn(credentials: ApiLogInCredentials) {
this.tokenRequest = new ApiTokenRequest(
async logIn(credentials: UserApiLogInCredentials) {
this.tokenRequest = new UserApiTokenRequest(
credentials.clientId,
credentials.clientSecret,
await this.buildTwoFactor(),

View File

@@ -26,8 +26,8 @@ export class SsoLogInCredentials {
) {}
}
export class ApiLogInCredentials {
readonly type = AuthenticationType.Api;
export class UserApiLogInCredentials {
readonly type = AuthenticationType.UserApi;
constructor(public clientId: string, public clientSecret: string) {}
}

View File

@@ -42,10 +42,12 @@ export abstract class TokenRequest {
obj.authRequest = this.passwordlessAuthRequest;
}
if (this.twoFactor.token && this.twoFactor.provider != null) {
obj.twoFactorToken = this.twoFactor.token;
obj.twoFactorProvider = this.twoFactor.provider;
obj.twoFactorRemember = this.twoFactor.remember ? "1" : "0";
if (this.twoFactor) {
if (this.twoFactor.token && this.twoFactor.provider != null) {
obj.twoFactorToken = this.twoFactor.token;
obj.twoFactorProvider = this.twoFactor.provider;
obj.twoFactorRemember = this.twoFactor.remember ? "1" : "0";
}
}
return obj;

View File

@@ -3,7 +3,7 @@ import { DeviceRequest } from "../device.request";
import { TokenTwoFactorRequest } from "./token-two-factor.request";
import { TokenRequest } from "./token.request";
export class ApiTokenRequest extends TokenRequest {
export class UserApiTokenRequest extends TokenRequest {
constructor(
public clientId: string,
public clientSecret: string,

View File

@@ -31,10 +31,10 @@ import { EmergencyAccessUpdateRequest } from "../models/request/emergency-access
import { EventRequest } from "../models/request/event.request";
import { GroupRequest } from "../models/request/group.request";
import { IapCheckRequest } from "../models/request/iap-check.request";
import { ApiTokenRequest } from "../models/request/identity-token/api-token.request";
import { PasswordTokenRequest } from "../models/request/identity-token/password-token.request";
import { SsoTokenRequest } from "../models/request/identity-token/sso-token.request";
import { TokenTwoFactorRequest } from "../models/request/identity-token/token-two-factor.request";
import { UserApiTokenRequest } from "../models/request/identity-token/user-api-token.request";
import { ImportCiphersRequest } from "../models/request/import-ciphers.request";
import { ImportOrganizationCiphersRequest } from "../models/request/import-organization-ciphers.request";
import { KdfRequest } from "../models/request/kdf.request";
@@ -206,7 +206,7 @@ export class ApiService implements ApiServiceAbstraction {
// Auth APIs
async postIdentityToken(
request: ApiTokenRequest | PasswordTokenRequest | SsoTokenRequest
request: UserApiTokenRequest | PasswordTokenRequest | SsoTokenRequest
): Promise<IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse> {
const headers = new Headers({
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
@@ -219,7 +219,7 @@ export class ApiService implements ApiServiceAbstraction {
request.alterIdentityTokenHeaders(headers);
const identityToken =
request instanceof ApiTokenRequest
request instanceof UserApiTokenRequest
? request.toIdentityToken()
: request.toIdentityToken(this.platformUtilsService.getClientType());
@@ -2271,8 +2271,7 @@ export class ApiService implements ApiServiceAbstraction {
const appId = await this.appIdService.getAppId();
const deviceRequest = new DeviceRequest(appId, this.platformUtilsService);
const tokenRequest = new ApiTokenRequest(
const tokenRequest = new UserApiTokenRequest(
clientId,
clientSecret,
new TokenTwoFactorRequest(),

View File

@@ -17,13 +17,13 @@ import { AuthenticationStatus } from "../enums/authenticationStatus";
import { AuthenticationType } from "../enums/authenticationType";
import { KdfType } from "../enums/kdfType";
import { KeySuffixOptions } from "../enums/keySuffixOptions";
import { ApiLogInStrategy } from "../misc/logInStrategies/apiLogin.strategy";
import { PasswordLogInStrategy } from "../misc/logInStrategies/passwordLogin.strategy";
import { PasswordlessLogInStrategy } from "../misc/logInStrategies/passwordlessLogin.strategy";
import { SsoLogInStrategy } from "../misc/logInStrategies/ssoLogin.strategy";
import { UserApiLogInStrategy } from "../misc/logInStrategies/user-api-login.strategy";
import { AuthResult } from "../models/domain/auth-result";
import {
ApiLogInCredentials,
UserApiLogInCredentials,
PasswordLogInCredentials,
SsoLogInCredentials,
PasswordlessLogInCredentials,
@@ -67,7 +67,7 @@ export class AuthService implements AuthServiceAbstraction {
}
private logInStrategy:
| ApiLogInStrategy
| UserApiLogInStrategy
| PasswordLogInStrategy
| SsoLogInStrategy
| PasswordlessLogInStrategy;
@@ -92,7 +92,7 @@ export class AuthService implements AuthServiceAbstraction {
async logIn(
credentials:
| ApiLogInCredentials
| UserApiLogInCredentials
| PasswordLogInCredentials
| SsoLogInCredentials
| PasswordlessLogInCredentials
@@ -100,7 +100,7 @@ export class AuthService implements AuthServiceAbstraction {
this.clearState();
let strategy:
| ApiLogInStrategy
| UserApiLogInStrategy
| PasswordLogInStrategy
| SsoLogInStrategy
| PasswordlessLogInStrategy;
@@ -134,8 +134,8 @@ export class AuthService implements AuthServiceAbstraction {
this.keyConnectorService
);
break;
case AuthenticationType.Api:
strategy = new ApiLogInStrategy(
case AuthenticationType.UserApi:
strategy = new UserApiLogInStrategy(
this.cryptoService,
this.apiService,
this.tokenService,
@@ -203,8 +203,8 @@ export class AuthService implements AuthServiceAbstraction {
this.messagingService.send("loggedOut");
}
authingWithApiKey(): boolean {
return this.logInStrategy instanceof ApiLogInStrategy;
authingWithUserApiKey(): boolean {
return this.logInStrategy instanceof UserApiLogInStrategy;
}
authingWithSso(): boolean {
@@ -272,7 +272,7 @@ export class AuthService implements AuthServiceAbstraction {
private saveState(
strategy:
| ApiLogInStrategy
| UserApiLogInStrategy
| PasswordLogInStrategy
| SsoLogInStrategy
| PasswordlessLogInStrategy

View File

@@ -64,6 +64,20 @@ export class FolderService implements InternalFolderServiceAbstraction {
return folders.find((folder) => folder.id === id);
}
/**
* @deprecated For the CLI only
* @param id id of the folder
*/
async getFromState(id: string): Promise<Folder> {
const foldersMap = await this.stateService.getEncryptedFolders();
const folder = foldersMap[id];
if (folder == null) {
return null;
}
return new Folder(folder);
}
/**
* @deprecated Only use in CLI!
*/

View File

@@ -1,21 +1,16 @@
import { BehaviorSubject, concatMap, filter } from "rxjs";
import { BehaviorSubject, concatMap } from "rxjs";
import { OrganizationService as OrganizationServiceAbstraction } from "../../abstractions/organization/organization.service.abstraction";
import { InternalOrganizationService as InternalOrganizationServiceAbstraction } from "../../abstractions/organization/organization.service.abstraction";
import { StateService } from "../../abstractions/state.service";
import { SyncNotifierService } from "../../abstractions/sync/syncNotifier.service.abstraction";
import { OrganizationData } from "../../models/data/organization.data";
import { Organization } from "../../models/domain/organization";
import { isSuccessfullyCompleted } from "../../types/syncEventArgs";
export class OrganizationService implements OrganizationServiceAbstraction {
export class OrganizationService implements InternalOrganizationServiceAbstraction {
protected _organizations = new BehaviorSubject<Organization[]>([]);
organizations$ = this._organizations.asObservable();
constructor(
private stateService: StateService,
private syncNotifierService: SyncNotifierService
) {
constructor(private stateService: StateService) {
this.stateService.activeAccountUnlocked$
.pipe(
concatMap(async (unlocked) => {
@@ -29,28 +24,6 @@ export class OrganizationService implements OrganizationServiceAbstraction {
})
)
.subscribe();
this.syncNotifierService.sync$
.pipe(
filter(isSuccessfullyCompleted),
concatMap(async ({ data }) => {
const { profile } = data;
const organizations: { [id: string]: OrganizationData } = {};
profile.organizations.forEach((o) => {
organizations[o.id] = new OrganizationData(o);
});
profile.providerOrganizations.forEach((o) => {
if (organizations[o.id] == null) {
organizations[o.id] = new OrganizationData(o);
organizations[o.id].isProviderUser = true;
}
});
await this.updateStateAndObservables(organizations);
})
)
.subscribe();
}
async getAll(userId?: string): Promise<Organization[]> {
@@ -78,7 +51,7 @@ export class OrganizationService implements OrganizationServiceAbstraction {
organizations[organization.id] = organization;
await this.updateStateAndObservables(organizations);
await this.replace(organizations);
}
async delete(id: string): Promise<void> {
@@ -92,7 +65,7 @@ export class OrganizationService implements OrganizationServiceAbstraction {
}
delete organizations[id];
await this.updateStateAndObservables(organizations);
await this.replace(organizations);
}
get(id: string): Organization {
@@ -121,9 +94,9 @@ export class OrganizationService implements OrganizationServiceAbstraction {
return organizations.find((organization) => organization.identifier === identifier);
}
private async updateStateAndObservables(organizationsMap: { [id: string]: OrganizationData }) {
await this.stateService.setOrganizations(organizationsMap);
this.updateObservables(organizationsMap);
async replace(organizations: { [id: string]: OrganizationData }) {
await this.stateService.setOrganizations(organizations);
this.updateObservables(organizations);
}
private updateObservables(organizationsMap: { [id: string]: OrganizationData }) {

View File

@@ -7,17 +7,18 @@ import { InternalFolderService } from "../../abstractions/folder/folder.service.
import { KeyConnectorService } from "../../abstractions/keyConnector.service";
import { LogService } from "../../abstractions/log.service";
import { MessagingService } from "../../abstractions/messaging.service";
import { InternalOrganizationService } from "../../abstractions/organization/organization.service.abstraction";
import { InternalPolicyService } from "../../abstractions/policy/policy.service.abstraction";
import { ProviderService } from "../../abstractions/provider.service";
import { SendService } from "../../abstractions/send.service";
import { SettingsService } from "../../abstractions/settings.service";
import { StateService } from "../../abstractions/state.service";
import { SyncService as SyncServiceAbstraction } from "../../abstractions/sync/sync.service.abstraction";
import { SyncNotifierService } from "../../abstractions/sync/syncNotifier.service.abstraction";
import { sequentialize } from "../../misc/sequentialize";
import { CipherData } from "../../models/data/cipher.data";
import { CollectionData } from "../../models/data/collection.data";
import { FolderData } from "../../models/data/folder.data";
import { OrganizationData } from "../../models/data/organization.data";
import { PolicyData } from "../../models/data/policy.data";
import { ProviderData } from "../../models/data/provider.data";
import { SendData } from "../../models/data/send.data";
@@ -52,7 +53,7 @@ export class SyncService implements SyncServiceAbstraction {
private stateService: StateService,
private providerService: ProviderService,
private folderApiService: FolderApiServiceAbstraction,
private syncNotifierService: SyncNotifierService,
private organizationService: InternalOrganizationService,
private logoutCallback: (expired: boolean) => Promise<void>
) {}
@@ -76,10 +77,8 @@ export class SyncService implements SyncServiceAbstraction {
@sequentialize(() => "fullSync")
async fullSync(forceSync: boolean, allowThrowOnError = false): Promise<boolean> {
this.syncStarted();
this.syncNotifierService.next({ status: "Started" });
const isAuthenticated = await this.stateService.getIsAuthenticated();
if (!isAuthenticated) {
this.syncNotifierService.next({ status: "Completed", successfully: false });
return this.syncCompleted(false);
}
@@ -95,7 +94,6 @@ export class SyncService implements SyncServiceAbstraction {
if (!needsSync) {
await this.setLastSync(now);
this.syncNotifierService.next({ status: "Completed", successfully: false });
return this.syncCompleted(false);
}
@@ -112,13 +110,11 @@ export class SyncService implements SyncServiceAbstraction {
await this.syncPolicies(response.policies);
await this.setLastSync(now);
this.syncNotifierService.next({ status: "Completed", successfully: true, data: response });
return this.syncCompleted(true);
} catch (e) {
if (allowThrowOnError) {
throw e;
} else {
this.syncNotifierService.next({ status: "Completed", successfully: false });
return this.syncCompleted(false);
}
}
@@ -315,11 +311,24 @@ export class SyncService implements SyncServiceAbstraction {
await this.stateService.setForcePasswordReset(response.forcePasswordReset);
await this.keyConnectorService.setUsesKeyConnector(response.usesKeyConnector);
const organizations: { [id: string]: OrganizationData } = {};
response.organizations.forEach((o) => {
organizations[o.id] = new OrganizationData(o);
});
const providers: { [id: string]: ProviderData } = {};
response.providers.forEach((p) => {
providers[p.id] = new ProviderData(p);
});
response.providerOrganizations.forEach((o) => {
if (organizations[o.id] == null) {
organizations[o.id] = new OrganizationData(o);
organizations[o.id].isProviderUser = true;
}
});
await this.organizationService.replace(organizations);
await this.providerService.save(providers);
if (await this.keyConnectorService.userNeedsMigration()) {