1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 08:13:42 +00:00

Merge branch 'master' into EC-598-beeep-properly-store-passkeys-in-bitwarden

This commit is contained in:
Andreas Coroiu
2023-01-25 15:24:42 +01:00
1208 changed files with 94078 additions and 16018 deletions

View File

@@ -0,0 +1,8 @@
import { Observable } from "rxjs";
import { ProfileResponse } from "../../models/response/profile.response";
export abstract class AvatarUpdateService {
avatarUpdate$ = new Observable<string | null>();
abstract pushUpdate(color: string): Promise<ProfileResponse | void>;
abstract loadColorFromState(): Promise<string | null>;
}

View File

@@ -11,6 +11,7 @@ import { CipherCreateRequest } from "../models/request/cipher-create.request";
import { CipherPartialRequest } from "../models/request/cipher-partial.request";
import { CipherShareRequest } from "../models/request/cipher-share.request";
import { CipherRequest } from "../models/request/cipher.request";
import { CollectionBulkDeleteRequest } from "../models/request/collection-bulk-delete.request";
import { CollectionRequest } from "../models/request/collection.request";
import { DeleteRecoverRequest } from "../models/request/delete-recover.request";
import { DeviceVerificationRequest } from "../models/request/device-verification.request";
@@ -22,7 +23,6 @@ import { EmergencyAccessInviteRequest } from "../models/request/emergency-access
import { EmergencyAccessPasswordRequest } from "../models/request/emergency-access-password.request";
import { EmergencyAccessUpdateRequest } from "../models/request/emergency-access-update.request";
import { EventRequest } from "../models/request/event.request";
import { GroupRequest } from "../models/request/group.request";
import { IapCheckRequest } from "../models/request/iap-check.request";
import { PasswordTokenRequest } from "../models/request/identity-token/password-token.request";
import { SsoTokenRequest } from "../models/request/identity-token/sso-token.request";
@@ -34,15 +34,6 @@ import { KeyConnectorUserKeyRequest } from "../models/request/key-connector-user
import { KeysRequest } from "../models/request/keys.request";
import { OrganizationConnectionRequest } from "../models/request/organization-connection.request";
import { OrganizationImportRequest } from "../models/request/organization-import.request";
import { OrganizationUserAcceptRequest } from "../models/request/organization-user-accept.request";
import { OrganizationUserBulkConfirmRequest } from "../models/request/organization-user-bulk-confirm.request";
import { OrganizationUserBulkRequest } from "../models/request/organization-user-bulk.request";
import { OrganizationUserConfirmRequest } from "../models/request/organization-user-confirm.request";
import { OrganizationUserInviteRequest } from "../models/request/organization-user-invite.request";
import { OrganizationUserResetPasswordEnrollmentRequest } from "../models/request/organization-user-reset-password-enrollment.request";
import { OrganizationUserResetPasswordRequest } from "../models/request/organization-user-reset-password.request";
import { OrganizationUserUpdateGroupsRequest } from "../models/request/organization-user-update-groups.request";
import { OrganizationUserUpdateRequest } from "../models/request/organization-user-update.request";
import { OrganizationSponsorshipCreateRequest } from "../models/request/organization/organization-sponsorship-create.request";
import { OrganizationSponsorshipRedeemRequest } from "../models/request/organization/organization-sponsorship-redeem.request";
import { PasswordHintRequest } from "../models/request/password-hint.request";
@@ -71,6 +62,7 @@ import { TaxInfoUpdateRequest } from "../models/request/tax-info-update.request"
import { TwoFactorEmailRequest } from "../models/request/two-factor-email.request";
import { TwoFactorProviderRequest } from "../models/request/two-factor-provider.request";
import { TwoFactorRecoveryRequest } from "../models/request/two-factor-recovery.request";
import { UpdateAvatarRequest } from "../models/request/update-avatar.request";
import { UpdateDomainsRequest } from "../models/request/update-domains.request";
import { UpdateKeyRequest } from "../models/request/update-key.request";
import { UpdateProfileRequest } from "../models/request/update-profile.request";
@@ -93,7 +85,7 @@ import { BillingPaymentResponse } from "../models/response/billing-payment.respo
import { BreachAccountResponse } from "../models/response/breach-account.response";
import { CipherResponse } from "../models/response/cipher.response";
import {
CollectionGroupDetailsResponse,
CollectionAccessDetailsResponse,
CollectionResponse,
} from "../models/response/collection.response";
import { DeviceVerificationResponse } from "../models/response/device-verification.response";
@@ -105,7 +97,6 @@ import {
EmergencyAccessViewResponse,
} from "../models/response/emergency-access.response";
import { EventResponse } from "../models/response/event.response";
import { GroupDetailsResponse, GroupResponse } from "../models/response/group.response";
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";
@@ -117,13 +108,6 @@ import {
} from "../models/response/organization-connection.response";
import { OrganizationExportResponse } from "../models/response/organization-export.response";
import { OrganizationSponsorshipSyncStatusResponse } from "../models/response/organization-sponsorship-sync-status.response";
import { OrganizationUserBulkPublicKeyResponse } from "../models/response/organization-user-bulk-public-key.response";
import { OrganizationUserBulkResponse } from "../models/response/organization-user-bulk.response";
import {
OrganizationUserDetailsResponse,
OrganizationUserUserDetailsResponse,
OrganizationUserResetPasswordDetailsReponse,
} from "../models/response/organization-user.response";
import { PaymentResponse } from "../models/response/payment.response";
import { PlanResponse } from "../models/response/plan.response";
import { PolicyResponse } from "../models/response/policy.response";
@@ -136,8 +120,8 @@ import {
import { ProviderUserBulkPublicKeyResponse } from "../models/response/provider/provider-user-bulk-public-key.response";
import { ProviderUserBulkResponse } from "../models/response/provider/provider-user-bulk.response";
import {
ProviderUserUserDetailsResponse,
ProviderUserResponse,
ProviderUserUserDetailsResponse,
} from "../models/response/provider/provider-user.response";
import { ProviderResponse } from "../models/response/provider/provider.response";
import { SelectionReadOnlyResponse } from "../models/response/selection-read-only.response";
@@ -156,13 +140,18 @@ import { TwoFactorEmailResponse } from "../models/response/two-factor-email.resp
import { TwoFactorProviderResponse } from "../models/response/two-factor-provider.response";
import { TwoFactorRecoverResponse } from "../models/response/two-factor-recover.response";
import {
TwoFactorWebAuthnResponse,
ChallengeResponse,
TwoFactorWebAuthnResponse,
} from "../models/response/two-factor-web-authn.response";
import { TwoFactorYubiKeyResponse } from "../models/response/two-factor-yubi-key.response";
import { UserKeyResponse } from "../models/response/user-key.response";
import { SendAccessView } from "../models/view/send-access.view";
/**
* @deprecated The `ApiService` class is deprecated and calls should be extracted into individual
* api services. The `send` method is still allowed to be used within api services. For background
* of this decision please read https://contributing.bitwarden.com/architecture/adr/refactor-api-service.
*/
export abstract class ApiService {
send: (
method: "GET" | "POST" | "PUT" | "DELETE",
@@ -183,6 +172,7 @@ export abstract class ApiService {
getUserSubscription: () => Promise<SubscriptionResponse>;
getTaxInfo: () => Promise<TaxInfoResponse>;
putProfile: (request: UpdateProfileRequest) => Promise<ProfileResponse>;
putAvatar: (request: UpdateAvatarRequest) => Promise<ProfileResponse>;
putTaxInfo: (request: TaxInfoUpdateRequest) => Promise<any>;
postPrelogin: (request: PreloginRequest) => Promise<PreloginResponse>;
postEmailToken: (request: EmailTokenRequest) => Promise<any>;
@@ -313,13 +303,16 @@ export abstract class ApiService {
) => Promise<AttachmentUploadDataResponse>;
postAttachmentFile: (id: string, attachmentId: string, data: FormData) => Promise<any>;
getCollectionDetails: (
organizationId: string,
id: string
) => Promise<CollectionGroupDetailsResponse>;
getUserCollections: () => Promise<ListResponse<CollectionResponse>>;
getCollections: (organizationId: string) => Promise<ListResponse<CollectionResponse>>;
getCollectionUsers: (organizationId: string, id: string) => Promise<SelectionReadOnlyResponse[]>;
getCollectionAccessDetails: (
organizationId: string,
id: string
) => Promise<CollectionAccessDetailsResponse>;
getManyCollectionsWithAccessDetails: (
orgId: string
) => Promise<ListResponse<CollectionAccessDetailsResponse>>;
postCollection: (
organizationId: string,
request: CollectionRequest
@@ -335,97 +328,17 @@ export abstract class ApiService {
request: CollectionRequest
) => Promise<CollectionResponse>;
deleteCollection: (organizationId: string, id: string) => Promise<any>;
deleteManyCollections: (request: CollectionBulkDeleteRequest) => Promise<any>;
deleteCollectionUser: (
organizationId: string,
id: string,
organizationUserId: string
) => Promise<any>;
getGroupDetails: (organizationId: string, id: string) => Promise<GroupDetailsResponse>;
getGroups: (organizationId: string) => Promise<ListResponse<GroupResponse>>;
getGroupUsers: (organizationId: string, id: string) => Promise<string[]>;
postGroup: (organizationId: string, request: GroupRequest) => Promise<GroupResponse>;
putGroup: (organizationId: string, id: string, request: GroupRequest) => Promise<GroupResponse>;
putGroupUsers: (organizationId: string, id: string, request: string[]) => Promise<any>;
deleteGroup: (organizationId: string, id: string) => Promise<any>;
deleteGroupUser: (organizationId: string, id: string, organizationUserId: string) => Promise<any>;
getOrganizationUser: (
organizationId: string,
id: string
) => Promise<OrganizationUserDetailsResponse>;
getOrganizationUserGroups: (organizationId: string, id: string) => Promise<string[]>;
getOrganizationUsers: (
organizationId: string
) => Promise<ListResponse<OrganizationUserUserDetailsResponse>>;
getOrganizationUserResetPasswordDetails: (
organizationId: string,
id: string
) => Promise<OrganizationUserResetPasswordDetailsReponse>;
postOrganizationUserInvite: (
organizationId: string,
request: OrganizationUserInviteRequest
) => Promise<any>;
postOrganizationUserReinvite: (organizationId: string, id: string) => Promise<any>;
postManyOrganizationUserReinvite: (
organizationId: string,
request: OrganizationUserBulkRequest
) => Promise<ListResponse<OrganizationUserBulkResponse>>;
postOrganizationUserAccept: (
organizationId: string,
id: string,
request: OrganizationUserAcceptRequest
) => Promise<any>;
postOrganizationUserConfirm: (
organizationId: string,
id: string,
request: OrganizationUserConfirmRequest
) => Promise<any>;
postOrganizationUsersPublicKey: (
organizationId: string,
request: OrganizationUserBulkRequest
) => Promise<ListResponse<OrganizationUserBulkPublicKeyResponse>>;
postOrganizationUserBulkConfirm: (
organizationId: string,
request: OrganizationUserBulkConfirmRequest
) => Promise<ListResponse<OrganizationUserBulkResponse>>;
putOrganizationUser: (
organizationId: string,
id: string,
request: OrganizationUserUpdateRequest
) => Promise<any>;
putOrganizationUserGroups: (
organizationId: string,
id: string,
request: OrganizationUserUpdateGroupsRequest
) => Promise<any>;
putOrganizationUserResetPasswordEnrollment: (
organizationId: string,
userId: string,
request: OrganizationUserResetPasswordEnrollmentRequest
) => Promise<void>;
putOrganizationUserResetPassword: (
organizationId: string,
id: string,
request: OrganizationUserResetPasswordRequest
) => Promise<any>;
deleteOrganizationUser: (organizationId: string, id: string) => Promise<any>;
deleteManyOrganizationUsers: (
organizationId: string,
request: OrganizationUserBulkRequest
) => Promise<ListResponse<OrganizationUserBulkResponse>>;
revokeOrganizationUser: (organizationId: string, id: string) => Promise<any>;
revokeManyOrganizationUsers: (
organizationId: string,
request: OrganizationUserBulkRequest
) => Promise<ListResponse<OrganizationUserBulkResponse>>;
restoreOrganizationUser: (organizationId: string, id: string) => Promise<any>;
restoreManyOrganizationUsers: (
organizationId: string,
request: OrganizationUserBulkRequest
) => Promise<ListResponse<OrganizationUserBulkResponse>>;
getSync: () => Promise<SyncResponse>;
postPublicImportDirectory: (request: OrganizationImportRequest) => Promise<any>;

View File

@@ -66,8 +66,8 @@ export abstract class CipherService {
deleteManyWithServer: (ids: string[]) => Promise<any>;
deleteAttachment: (id: string, attachmentId: string) => Promise<void>;
deleteAttachmentWithServer: (id: string, attachmentId: string) => Promise<void>;
sortCiphersByLastUsed: (a: any, b: any) => number;
sortCiphersByLastUsedThenName: (a: any, b: any) => number;
sortCiphersByLastUsed: (a: CipherView, b: CipherView) => number;
sortCiphersByLastUsedThenName: (a: CipherView, b: CipherView) => number;
getLocaleSortingFunction: () => (a: CipherView, b: CipherView) => number;
softDelete: (id: string | string[]) => Promise<any>;
softDeleteWithServer: (id: string) => Promise<any>;

View File

@@ -1,12 +0,0 @@
import { EventType } from "../enums/eventType";
export abstract class EventService {
collect: (
eventType: EventType,
cipherId?: string,
uploadImmediately?: boolean,
organizationId?: string
) => Promise<any>;
uploadEvents: (userId?: string) => Promise<any>;
clearEvents: (userId?: string) => Promise<any>;
}

View File

@@ -0,0 +1,10 @@
import { EventType } from "../../enums/eventType";
export abstract class EventCollectionService {
collect: (
eventType: EventType,
cipherId?: string,
uploadImmediately?: boolean,
organizationId?: string
) => Promise<any>;
}

View File

@@ -0,0 +1,3 @@
export abstract class EventUploadService {
uploadEvents: (userId?: string) => Promise<void>;
}

View File

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

View File

@@ -6,6 +6,6 @@ export abstract class I18nService {
translationLocale: string;
collator: Intl.Collator;
localeNames: Map<string, string>;
t: (id: string, p1?: string, p2?: string, p3?: string) => string;
t: (id: string, p1?: string | number, p2?: string | number, p3?: string | number) => string;
translate: (id: string, p1?: string, p2?: string, p3?: string) => string;
}

View File

@@ -4,4 +4,5 @@ export abstract class LoginService {
setEmail: (value: string) => void;
setRememberEmail: (value: boolean) => void;
clearValues: () => void;
saveEmailSettings: () => Promise<void>;
}

View File

@@ -0,0 +1,242 @@
import { ListResponse } from "../../models/response/list.response";
import {
OrganizationUserAcceptRequest,
OrganizationUserBulkConfirmRequest,
OrganizationUserConfirmRequest,
OrganizationUserInviteRequest,
OrganizationUserResetPasswordEnrollmentRequest,
OrganizationUserResetPasswordRequest,
OrganizationUserUpdateGroupsRequest,
OrganizationUserUpdateRequest,
} from "./requests";
import {
OrganizationUserBulkPublicKeyResponse,
OrganizationUserBulkResponse,
OrganizationUserDetailsResponse,
OrganizationUserResetPasswordDetailsReponse,
OrganizationUserUserDetailsResponse,
} from "./responses";
/**
* Service for interacting with Organization Users via the API
*/
export abstract class OrganizationUserService {
/**
* Retrieve a single organization user by Id
* @param organizationId - Identifier for the user's organization
* @param id - Organization user identifier
* @param options - Options for the request
*/
abstract getOrganizationUser(
organizationId: string,
id: string,
options?: {
includeGroups?: boolean;
}
): Promise<OrganizationUserDetailsResponse>;
/**
* Retrieve a list of groups Ids the specified organization user belongs to
* @param organizationId - Identifier for the user's organization
* @param id - Organization user identifier
*/
abstract getOrganizationUserGroups(organizationId: string, id: string): Promise<string[]>;
/**
* Retrieve a list of all users that belong to the specified organization
* @param organizationId - Identifier for the organization
* @param options - Options for the request
*/
abstract getAllUsers(
organizationId: string,
options?: {
includeCollections?: boolean;
includeGroups?: boolean;
}
): Promise<ListResponse<OrganizationUserUserDetailsResponse>>;
/**
* Retrieve reset password details for the specified organization user
* @param organizationId - Identifier for the user's organization
* @param id - Organization user identifier
*/
abstract getOrganizationUserResetPasswordDetails(
organizationId: string,
id: string
): Promise<OrganizationUserResetPasswordDetailsReponse>;
/**
* Create new organization user invite(s) for the specified organization
* @param organizationId - Identifier for the organization
* @param request - New user invitation request details
*/
abstract postOrganizationUserInvite(
organizationId: string,
request: OrganizationUserInviteRequest
): Promise<void>;
/**
* Re-invite the specified organization user
* @param organizationId - Identifier for the user's organization
* @param id - Organization user identifier
*/
abstract postOrganizationUserReinvite(organizationId: string, id: string): Promise<any>;
/**
* Re-invite many organization users for the specified organization
* @param organizationId - Identifier for the organization
* @param ids - A list of organization user identifiers
* @return List of user ids, including both those that were successfully re-invited and those that had an error
*/
abstract postManyOrganizationUserReinvite(
organizationId: string,
ids: string[]
): Promise<ListResponse<OrganizationUserBulkResponse>>;
/**
* Accept an organization user invitation
* @param organizationId - Identifier for the organization to accept
* @param id - Organization user identifier
* @param request - Request details for accepting the invitation
*/
abstract postOrganizationUserAccept(
organizationId: string,
id: string,
request: OrganizationUserAcceptRequest
): Promise<void>;
/**
* Confirm an organization user that has accepted their invitation
* @param organizationId - Identifier for the organization to confirm
* @param id - Organization user identifier
* @param request - Request details for confirming the user
*/
abstract postOrganizationUserConfirm(
organizationId: string,
id: string,
request: OrganizationUserConfirmRequest
): Promise<void>;
/**
* Retrieve a list of the specified users' public keys
* @param organizationId - Identifier for the organization to accept
* @param ids - A list of organization user identifiers to retrieve public keys for
*/
abstract postOrganizationUsersPublicKey(
organizationId: string,
ids: string[]
): Promise<ListResponse<OrganizationUserBulkPublicKeyResponse>>;
/**
* Confirm many organization users that have accepted their invitations
* @param organizationId - Identifier for the organization to confirm users
* @param request - Bulk request details for confirming the user
*/
abstract postOrganizationUserBulkConfirm(
organizationId: string,
request: OrganizationUserBulkConfirmRequest
): Promise<ListResponse<OrganizationUserBulkResponse>>;
/**
* Update an organization users
* @param organizationId - Identifier for the organization the user belongs to
* @param id - Organization user identifier
* @param request - Request details for updating the user
*/
abstract putOrganizationUser(
organizationId: string,
id: string,
request: OrganizationUserUpdateRequest
): Promise<void>;
/**
* Update an organization user's groups
* @param organizationId - Identifier for the organization the user belongs to
* @param id - Organization user identifier
* @param groupIds - List of group ids to associate the user with
*/
abstract putOrganizationUserGroups(
organizationId: string,
id: string,
groupIds: OrganizationUserUpdateGroupsRequest
): Promise<void>;
/**
* Update an organization user's reset password enrollment
* @param organizationId - Identifier for the organization the user belongs to
* @param userId - Organization user identifier
* @param request - Reset password enrollment details
*/
abstract putOrganizationUserResetPasswordEnrollment(
organizationId: string,
userId: string,
request: OrganizationUserResetPasswordEnrollmentRequest
): Promise<void>;
/**
* Reset an organization user's password
* @param organizationId - Identifier for the organization the user belongs to
* @param id - Organization user identifier
* @param request - Reset password details
*/
abstract putOrganizationUserResetPassword(
organizationId: string,
id: string,
request: OrganizationUserResetPasswordRequest
): Promise<void>;
/**
* Delete an organization user
* @param organizationId - Identifier for the organization the user belongs to
* @param id - Organization user identifier
*/
abstract deleteOrganizationUser(organizationId: string, id: string): Promise<void>;
/**
* Delete many organization users
* @param organizationId - Identifier for the organization the users belongs to
* @param ids - List of organization user identifiers to delete
* @return List of user ids, including both those that were successfully deleted and those that had an error
*/
abstract deleteManyOrganizationUsers(
organizationId: string,
ids: string[]
): Promise<ListResponse<OrganizationUserBulkResponse>>;
/**
* Revoke an organization user's access to the organization
* @param organizationId - Identifier for the organization the user belongs to
* @param id - Organization user identifier
*/
abstract revokeOrganizationUser(organizationId: string, id: string): Promise<void>;
/**
* Revoke many organization users' access to the organization
* @param organizationId - Identifier for the organization the users belongs to
* @param ids - List of organization user identifiers to revoke
* @return List of user ids, including both those that were successfully revoked and those that had an error
*/
abstract revokeManyOrganizationUsers(
organizationId: string,
ids: string[]
): Promise<ListResponse<OrganizationUserBulkResponse>>;
/**
* Restore an organization user's access to the organization
* @param organizationId - Identifier for the organization the user belongs to
* @param id - Organization user identifier
*/
abstract restoreOrganizationUser(organizationId: string, id: string): Promise<void>;
/**
* Restore many organization users' access to the organization
* @param organizationId - Identifier for the organization the users belongs to
* @param ids - List of organization user identifiers to restore
* @return List of user ids, including both those that were successfully restored and those that had an error
*/
abstract restoreManyOrganizationUsers(
organizationId: string,
ids: string[]
): Promise<ListResponse<OrganizationUserBulkResponse>>;
}

View File

@@ -0,0 +1,8 @@
export * from "./organization-user-accept.request";
export * from "./organization-user-bulk-confirm.request";
export * from "./organization-user-confirm.request";
export * from "./organization-user-invite.request";
export * from "./organization-user-reset-password.request";
export * from "./organization-user-reset-password-enrollment.request";
export * from "./organization-user-update.request";
export * from "./organization-user-update-groups.request";

View File

@@ -0,0 +1,12 @@
import { OrganizationUserType } from "../../../enums/organizationUserType";
import { PermissionsApi } from "../../../models/api/permissions.api";
import { SelectionReadOnlyRequest } from "../../../models/request/selection-read-only.request";
export class OrganizationUserInviteRequest {
emails: string[] = [];
type: OrganizationUserType;
accessAll: boolean;
collections: SelectionReadOnlyRequest[] = [];
groups: string[];
permissions: PermissionsApi;
}

View File

@@ -1,4 +1,4 @@
import { SecretVerificationRequest } from "./secret-verification.request";
import { SecretVerificationRequest } from "../../../models/request/secret-verification.request";
export class OrganizationUserResetPasswordEnrollmentRequest extends SecretVerificationRequest {
resetPasswordKey: string;

View File

@@ -0,0 +1,11 @@
import { OrganizationUserType } from "../../../enums/organizationUserType";
import { PermissionsApi } from "../../../models/api/permissions.api";
import { SelectionReadOnlyRequest } from "../../../models/request/selection-read-only.request";
export class OrganizationUserUpdateRequest {
type: OrganizationUserType;
accessAll: boolean;
collections: SelectionReadOnlyRequest[] = [];
groups: string[] = [];
permissions: PermissionsApi;
}

View File

@@ -0,0 +1,3 @@
export * from "./organization-user.response";
export * from "./organization-user-bulk.response";
export * from "./organization-user-bulk-public-key.response";

View File

@@ -1,4 +1,4 @@
import { BaseResponse } from "./base.response";
import { BaseResponse } from "../../../models/response/base.response";
export class OrganizationUserBulkPublicKeyResponse extends BaseResponse {
id: string;

View File

@@ -1,4 +1,4 @@
import { BaseResponse } from "./base.response";
import { BaseResponse } from "../../../models/response/base.response";
export class OrganizationUserBulkResponse extends BaseResponse {
id: string;

View File

@@ -1,10 +1,9 @@
import { KdfType } from "../../enums/kdfType";
import { OrganizationUserStatusType } from "../../enums/organizationUserStatusType";
import { OrganizationUserType } from "../../enums/organizationUserType";
import { PermissionsApi } from "../api/permissions.api";
import { BaseResponse } from "./base.response";
import { SelectionReadOnlyResponse } from "./selection-read-only.response";
import { KdfType } from "../../../enums/kdfType";
import { OrganizationUserStatusType } from "../../../enums/organizationUserStatusType";
import { OrganizationUserType } from "../../../enums/organizationUserType";
import { PermissionsApi } from "../../../models/api/permissions.api";
import { BaseResponse } from "../../../models/response/base.response";
import { SelectionReadOnlyResponse } from "../../../models/response/selection-read-only.response";
export class OrganizationUserResponse extends BaseResponse {
id: string;
@@ -14,6 +13,8 @@ export class OrganizationUserResponse extends BaseResponse {
accessAll: boolean;
permissions: PermissionsApi;
resetPasswordEnrolled: boolean;
collections: SelectionReadOnlyResponse[] = [];
groups: string[] = [];
constructor(response: any) {
super(response);
@@ -24,6 +25,15 @@ export class OrganizationUserResponse extends BaseResponse {
this.permissions = new PermissionsApi(this.getResponseProperty("Permissions"));
this.accessAll = this.getResponseProperty("AccessAll");
this.resetPasswordEnrolled = this.getResponseProperty("ResetPasswordEnrolled");
const collections = this.getResponseProperty("Collections");
if (collections != null) {
this.collections = collections.map((c: any) => new SelectionReadOnlyResponse(c));
}
const groups = this.getResponseProperty("Groups");
if (groups != null) {
this.groups = groups;
}
}
}
@@ -43,14 +53,8 @@ export class OrganizationUserUserDetailsResponse extends OrganizationUserRespons
}
export class OrganizationUserDetailsResponse extends OrganizationUserResponse {
collections: SelectionReadOnlyResponse[] = [];
constructor(response: any) {
super(response);
const collections = this.getResponseProperty("Collections");
if (collections != null) {
this.collections = collections.map((c: any) => new SelectionReadOnlyResponse(c));
}
}
}

View File

@@ -1,11 +1,12 @@
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";
export function canAccessVaultTab(org: Organization): boolean {
return org.isManager;
return org.canViewAssignedCollections || org.canViewAllCollections || org.canManageGroups;
}
export function canAccessSettingsTab(org: Organization): boolean {
@@ -34,19 +35,6 @@ export function canAccessBillingTab(org: Organization): boolean {
return org.canManageBilling;
}
export function canManageCollections(org: Organization): boolean {
return (
org.canCreateNewCollections ||
org.canEditAnyCollection ||
org.canDeleteAnyCollection ||
org.canViewAssignedCollections
);
}
export function canAccessManageTab(org: Organization): boolean {
return canAccessMembersTab(org) || canAccessGroupsTab(org) || canManageCollections(org);
}
export function canAccessOrgAdmin(org: Organization): boolean {
return (
canAccessMembersTab(org) ||
@@ -54,8 +42,7 @@ export function canAccessOrgAdmin(org: Organization): boolean {
canAccessReportingTab(org) ||
canAccessBillingTab(org) ||
canAccessSettingsTab(org) ||
canAccessVaultTab(org) ||
canAccessManageTab(org)
canAccessVaultTab(org)
);
}
@@ -69,6 +56,10 @@ export function canAccessAdmin(i18nService: I18nService) {
);
}
export function isNotProviderUser(org: Organization): boolean {
return !org.isProviderUser;
}
export abstract class OrganizationService {
organizations$: Observable<Organization[]>;
@@ -83,3 +74,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

@@ -19,6 +19,7 @@ export abstract class PlatformUtilsService {
isViewOpen: () => Promise<boolean>;
launchUri: (uri: string, options?: any) => void;
getApplicationVersion: () => Promise<string>;
getApplicationVersionNumber: () => Promise<string>;
supportsWebAuthn: (win: Window) => boolean;
supportsDuo: () => boolean;
showToast: (
@@ -33,7 +34,8 @@ export abstract class PlatformUtilsService {
confirmText?: string,
cancelText?: string,
type?: string,
bodyIsHtml?: boolean
bodyIsHtml?: boolean,
target?: string
) => Promise<boolean>;
isDev: () => boolean;
isSelfHost: () => boolean;

View File

@@ -349,4 +349,7 @@ export abstract class StateService<T extends Account = Account> {
* @deprecated Do not call this directly, use ConfigService
*/
setServerConfig: (value: ServerConfigData, options?: StorageOptions) => Promise<void>;
getAvatarColor: (options?: StorageOptions) => Promise<string | null | undefined>;
setAvatarColor: (value: string, options?: StorageOptions) => Promise<void>;
}

View File

@@ -7,10 +7,11 @@ export abstract class AbstractStorageService {
abstract remove(key: string, options?: StorageOptions): Promise<void>;
}
export abstract class AbstractCachedStorageService extends AbstractStorageService {
export abstract class AbstractMemoryStorageService extends AbstractStorageService {
// Used to identify the service in the session sync decorator framework
static readonly TYPE = "MemoryStorageService";
readonly type = AbstractMemoryStorageService.TYPE;
abstract get<T>(key: string, options?: MemoryStorageOptions<T>): Promise<T>;
abstract getBypassCache<T>(key: string, options?: MemoryStorageOptions<T>): Promise<T>;
}
export interface MemoryStorageServiceInterface {
get<T>(key: string, options?: MemoryStorageOptions<T>): Promise<T>;
}

View File

@@ -1,7 +1,7 @@
import { ApiService } from "../abstractions/api.service";
import { Forwarder } from "./forwarder";
import { ForwarderOptions } from "./forwarderOptions";
import { ForwarderOptions } from "./forwarder-options";
export class AnonAddyForwarder implements Forwarder {
async generate(apiService: ApiService, options: ForwarderOptions): Promise<string> {

View File

@@ -1,7 +1,7 @@
import { ApiService } from "../abstractions/api.service";
import { Forwarder } from "./forwarder";
import { ForwarderOptions } from "./forwarderOptions";
import { ForwarderOptions } from "./forwarder-options";
export class DuckDuckGoForwarder implements Forwarder {
async generate(apiService: ApiService, options: ForwarderOptions): Promise<string> {

View File

@@ -1,7 +1,7 @@
import { ApiService } from "../abstractions/api.service";
import { Forwarder } from "./forwarder";
import { ForwarderOptions } from "./forwarderOptions";
import { ForwarderOptions } from "./forwarder-options";
export class FastmailForwarder implements Forwarder {
async generate(apiService: ApiService, options: ForwarderOptions): Promise<string> {

View File

@@ -1,7 +1,7 @@
import { ApiService } from "../abstractions/api.service";
import { Forwarder } from "./forwarder";
import { ForwarderOptions } from "./forwarderOptions";
import { ForwarderOptions } from "./forwarder-options";
export class FirefoxRelayForwarder implements Forwarder {
async generate(apiService: ApiService, options: ForwarderOptions): Promise<string> {

View File

@@ -1,6 +1,6 @@
import { ApiService } from "../abstractions/api.service";
import { ForwarderOptions } from "./forwarderOptions";
import { ForwarderOptions } from "./forwarder-options";
export interface Forwarder {
generate(apiService: ApiService, options: ForwarderOptions): Promise<string>;

View File

@@ -1,7 +1,7 @@
import { ApiService } from "../abstractions/api.service";
import { Forwarder } from "./forwarder";
import { ForwarderOptions } from "./forwarderOptions";
import { ForwarderOptions } from "./forwarder-options";
export class SimpleLoginForwarder implements Forwarder {
async generate(apiService: ApiService, options: ForwarderOptions): Promise<string> {

View File

@@ -67,6 +67,7 @@ export const regularImportOptions = [
{ id: "encryptrcsv", name: "Encryptr (csv)" },
{ id: "yoticsv", name: "Yoti (csv)" },
{ id: "nordpasscsv", name: "Nordpass (csv)" },
{ id: "passkyjson", name: "Passky (json)" },
] as const;
export type ImportType =

View File

@@ -3,5 +3,5 @@ export enum KdfType {
}
export const DEFAULT_KDF_TYPE = KdfType.PBKDF2_SHA256;
export const DEFAULT_KDF_ITERATIONS = 100000;
export const DEFAULT_KDF_ITERATIONS = 600000;
export const SEND_KDF_ITERATIONS = 100000;

View File

@@ -137,8 +137,7 @@ export class DashlaneCsvImporter extends BaseImporter implements Importer {
cipher.card.number = row.cc_number;
cipher.card.brand = this.getCardBrand(cipher.card.number);
cipher.card.code = row.code;
cipher.card.expMonth = row.expiration_month;
cipher.card.expYear = row.expiration_year.substring(2, 4);
this.setCardExpiration(cipher, `${row.expiration_month}/${row.expiration_year}`);
// If you add more mapped fields please extend this
mappedValues = [

View File

@@ -1,11 +1,10 @@
import { CipherType } from "../enums/cipherType";
import { SecureNoteType } from "../enums/secureNoteType";
import { ImportResult } from "../models/domain/import-result";
import { CardView } from "../models/view/card.view";
import { SecureNoteView } from "../models/view/secure-note.view";
import { BaseImporter } from "./base-importer";
import { Importer } from "./importer";
import { CipherType } from "../../enums/cipherType";
import { SecureNoteType } from "../../enums/secureNoteType";
import { ImportResult } from "../../models/domain/import-result";
import { CardView } from "../../models/view/card.view";
import { SecureNoteView } from "../../models/view/secure-note.view";
import { BaseImporter } from "../base-importer";
import { Importer } from "../importer";
export class EnpassCsvImporter extends BaseImporter implements Importer {
parse(data: string): Promise<ImportResult> {

View File

@@ -1,17 +1,21 @@
import { CipherType } from "../enums/cipherType";
import { FieldType } from "../enums/fieldType";
import { ImportResult } from "../models/domain/import-result";
import { CardView } from "../models/view/card.view";
import { CipherView } from "../models/view/cipher.view";
import { FolderView } from "../models/view/folder.view";
import { CipherType } from "../../enums/cipherType";
import { FieldType } from "../../enums/fieldType";
import { ImportResult } from "../../models/domain/import-result";
import { CardView } from "../../models/view/card.view";
import { CipherView } from "../../models/view/cipher.view";
import { FolderView } from "../../models/view/folder.view";
import { BaseImporter } from "../base-importer";
import { Importer } from "../importer";
import { BaseImporter } from "./base-importer";
import { Importer } from "./importer";
import { EnpassJsonFile, EnpassFolder, EnpassField } from "./types/enpass-json-type";
type EnpassFolderTreeItem = EnpassFolder & { children: EnpassFolderTreeItem[] };
const androidUrlRegex = new RegExp("androidapp://.*==@", "g");
export class EnpassJsonImporter extends BaseImporter implements Importer {
parse(data: string): Promise<ImportResult> {
const result = new ImportResult();
const results = JSON.parse(data);
const results: EnpassJsonFile = JSON.parse(data);
if (results == null || results.items == null || results.items.length === 0) {
result.success = false;
return Promise.resolve(result);
@@ -28,7 +32,7 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
result.folders.push(f);
});
results.items.forEach((item: any) => {
results.items.forEach((item) => {
if (item.folders != null && item.folders.length > 0 && foldersIndexMap.has(item.folders[0])) {
result.folderRelationships.push([
result.ciphers.length,
@@ -50,7 +54,7 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
this.processCard(cipher, item.fields);
} else if (
item.template_type.indexOf("identity.") < 0 &&
item.fields.some((f: any) => f.type === "password" && !this.isNullOrWhitespace(f.value))
item.fields.some((f) => f.type === "password" && !this.isNullOrWhitespace(f.value))
) {
this.processLogin(cipher, item.fields);
} else {
@@ -68,9 +72,9 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
return Promise.resolve(result);
}
private processLogin(cipher: CipherView, fields: any[]) {
private processLogin(cipher: CipherView, fields: EnpassField[]) {
const urls: string[] = [];
fields.forEach((field: any) => {
fields.forEach((field) => {
if (this.isNullOrWhitespace(field.value) || field.type === "section") {
return;
}
@@ -86,6 +90,13 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
cipher.login.totp = field.value;
} else if (field.type === "url") {
urls.push(field.value);
} else if (field.type === ".Android#") {
let cleanedValue = field.value.startsWith("androidapp://")
? field.value
: "androidapp://" + field.value;
cleanedValue = cleanedValue.replace("android://", "");
cleanedValue = cleanedValue.replace(androidUrlRegex, "androidapp://");
urls.push(cleanedValue);
} else {
this.processKvp(
cipher,
@@ -98,10 +109,10 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
cipher.login.uris = this.makeUriArray(urls);
}
private processCard(cipher: CipherView, fields: any[]) {
private processCard(cipher: CipherView, fields: EnpassField[]) {
cipher.card = new CardView();
cipher.type = CipherType.Card;
fields.forEach((field: any) => {
fields.forEach((field) => {
if (
this.isNullOrWhitespace(field.value) ||
field.type === "section" ||
@@ -137,8 +148,8 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
});
}
private processNote(cipher: CipherView, fields: any[]) {
fields.forEach((field: any) => {
private processNote(cipher: CipherView, fields: EnpassField[]) {
fields.forEach((field) => {
if (this.isNullOrWhitespace(field.value) || field.type === "section") {
return;
}
@@ -151,17 +162,17 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
});
}
private buildFolderTree(folders: any[]): any[] {
private buildFolderTree(folders: EnpassFolder[]): EnpassFolderTreeItem[] {
if (folders == null) {
return [];
}
const folderTree: any[] = [];
const map = new Map<string, any>([]);
folders.forEach((obj: any) => {
const folderTree: EnpassFolderTreeItem[] = [];
const map = new Map<string, EnpassFolderTreeItem>([]);
folders.forEach((obj: EnpassFolderTreeItem) => {
map.set(obj.uuid, obj);
obj.children = [];
});
folders.forEach((obj: any) => {
folders.forEach((obj: EnpassFolderTreeItem) => {
if (obj.parent_uuid != null && obj.parent_uuid !== "" && map.has(obj.parent_uuid)) {
map.get(obj.parent_uuid).children.push(obj);
} else {
@@ -171,11 +182,15 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
return folderTree;
}
private flattenFolderTree(titlePrefix: string, tree: any[], map: Map<string, string>) {
private flattenFolderTree(
titlePrefix: string,
tree: EnpassFolderTreeItem[],
map: Map<string, string>
) {
if (tree == null) {
return;
}
tree.forEach((f: any) => {
tree.forEach((f) => {
if (f.title != null && f.title.trim() !== "") {
let title = f.title.trim();
if (titlePrefix != null && titlePrefix.trim() !== "") {

View File

@@ -0,0 +1,79 @@
type Login = "login.default";
type CreditCard = "creditcard.default";
type Identity = "identity.default";
type Note = "note.default";
type Password = "password.default";
type Finance =
| "finance.stock"
| "finance.bankaccount"
| "finance.loan"
| "finance.mutualfund"
| "finance.insurance"
| "finance.other";
type License = "license.driving" | "license.hunting" | "license.software" | "license.other";
type Travel =
| "travel.passport"
| "travel.flightdetails"
| "travel.hotelreservation"
| "travel.visa"
| "travel.freqflyer"
| "travel.other";
type Computer =
| "computer.database"
| "computer.emailaccount"
| "computer.ftp"
| "computer.messaging"
| "computer.internetprovider"
| "computer.server"
| "computer.wifi"
| "computer.hosting"
| "computer.other";
type Misc =
| "misc.Aadhar"
| "misc.address"
| "misc.library"
| "misc.rewardprogram"
| "misc.lens"
| "misc.service"
| "misc.vehicleinfo"
| "misc.itic"
| "misc.itz"
| "misc.propertyinfo"
| "misc.clothsize"
| "misc.contact"
| "misc.membership"
| "misc.cellphone"
| "misc.emergencyno"
| "misc.pan"
| "misc.identity"
| "misc.regcode"
| "misc.prescription"
| "misc.serial"
| "misc.socialsecurityno"
| "misc.isic"
| "misc.calling"
| "misc.voicemail"
| "misc.voter"
| "misc.combilock"
| "misc.other";
export type EnpassItemTemplate =
| Login
| CreditCard
| Identity
| Note
| Password
| Finance
| License
| Travel
| Computer
| Misc;

View File

@@ -0,0 +1,85 @@
import { EnpassItemTemplate } from "./enpass-item-templates";
export type EnpassJsonFile = {
folders: EnpassFolder[];
items: EnpassItem[];
};
export type EnpassFolder = {
icon: string;
parent_uuid: string;
title: string;
updated_at: number;
uuid: string;
};
export type EnpassItem = {
archived: number;
auto_submit: number;
category: string;
createdAt: number;
favorite: number;
fields?: EnpassField[];
icon: Icon;
note: string;
subtitle: string;
template_type: EnpassItemTemplate;
title: string;
trashed: number;
updated_at: number;
uuid: string;
folders?: string[];
};
export type EnpassFieldType =
| "text"
| "password"
| "pin"
| "numeric"
| "date"
| "email"
| "url"
| "phone"
| "username"
| "totp"
| "multiline"
| "ccName"
| "ccNumber"
| "ccCvc"
| "ccPin"
| "ccExpiry"
| "ccBankname"
| "ccTxnpassword"
| "ccType"
| "ccValidfrom"
| "section"
| ".Android#";
export type EnpassField = {
deleted: number;
history?: History[];
label: string;
order: number;
sensitive: number;
type: EnpassFieldType;
uid: number;
updated_at: number;
value: string;
value_updated_at: number;
};
export type History = {
updated_at: number;
value: string;
};
export type Icon = {
fav: string;
image: Image;
type: number;
uuid: string;
};
export type Image = {
file: string;
};

View File

@@ -1,59 +0,0 @@
import { CipherType } from "../enums/cipherType";
import { ImportResult } from "../models/domain/import-result";
import { CardView } from "../models/view/card.view";
import { BaseImporter } from "./base-importer";
import { Importer } from "./importer";
export class FSecureFskImporter extends BaseImporter implements Importer {
parse(data: string): Promise<ImportResult> {
const result = new ImportResult();
const results = JSON.parse(data);
if (results == null || results.data == null) {
result.success = false;
return Promise.resolve(result);
}
for (const key in results.data) {
// eslint-disable-next-line
if (!results.data.hasOwnProperty(key)) {
continue;
}
const value = results.data[key];
const cipher = this.initLoginCipher();
cipher.name = this.getValueOrDefault(value.service);
cipher.notes = this.getValueOrDefault(value.notes);
if (value.style === "website" || value.style === "globe") {
cipher.login.username = this.getValueOrDefault(value.username);
cipher.login.password = this.getValueOrDefault(value.password);
cipher.login.uris = this.makeUriArray(value.url);
} else if (value.style === "creditcard") {
cipher.type = CipherType.Card;
cipher.card = new CardView();
cipher.card.cardholderName = this.getValueOrDefault(value.username);
cipher.card.number = this.getValueOrDefault(value.creditNumber);
cipher.card.brand = this.getCardBrand(cipher.card.number);
cipher.card.code = this.getValueOrDefault(value.creditCvv);
if (!this.isNullOrWhitespace(value.creditExpiry)) {
if (!this.setCardExpiration(cipher, value.creditExpiry)) {
this.processKvp(cipher, "Expiration", value.creditExpiry);
}
}
if (!this.isNullOrWhitespace(value.password)) {
this.processKvp(cipher, "PIN", value.password);
}
} else {
continue;
}
this.convertToNoteIfNeeded(cipher);
this.cleanupCipher(cipher);
result.ciphers.push(cipher);
}
result.success = true;
return Promise.resolve(result);
}
}

View File

@@ -0,0 +1,52 @@
import { CipherType } from "../../enums/cipherType";
import { FSecureFskImporter as Importer } from "./fsecure-fsk-importer";
import { CreditCardTestEntry, LoginTestEntry } from "./fsk-test-data";
describe("FSecure FSK Importer", () => {
it("should import data of type login", async () => {
const importer = new Importer();
const LoginTestEntryStringified = JSON.stringify(LoginTestEntry);
const result = await importer.parse(LoginTestEntryStringified);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.name).toEqual("example.com");
expect(cipher.favorite).toBe(true);
expect(cipher.notes).toEqual("some note for example.com");
expect(cipher.type).toBe(CipherType.Login);
expect(cipher.login.username).toEqual("jdoe");
expect(cipher.login.password).toEqual("somePassword");
expect(cipher.login.uris.length).toEqual(1);
const uriView = cipher.login.uris.shift();
expect(uriView.uri).toEqual("https://www.example.com");
});
it("should import data of type creditCard", async () => {
const importer = new Importer();
const CreditCardTestEntryStringified = JSON.stringify(CreditCardTestEntry);
const result = await importer.parse(CreditCardTestEntryStringified);
expect(result != null).toBe(true);
const cipher = result.ciphers.shift();
expect(cipher.name).toEqual("My credit card");
expect(cipher.favorite).toBe(false);
expect(cipher.notes).toEqual("some notes to my card");
expect(cipher.type).toBe(CipherType.Card);
expect(cipher.card.cardholderName).toEqual("John Doe");
expect(cipher.card.number).toEqual("4242424242424242");
expect(cipher.card.code).toEqual("123");
expect(cipher.fields.length).toBe(2);
expect(cipher.fields[0].name).toEqual("Expiration");
expect(cipher.fields[0].value).toEqual("22.10.2026");
expect(cipher.fields[1].name).toEqual("PIN");
expect(cipher.fields[1].value).toEqual("1234");
});
});

View File

@@ -0,0 +1,79 @@
import { CipherType } from "../../enums/cipherType";
import { ImportResult } from "../../models/domain/import-result";
import { CardView } from "../../models/view/card.view";
import { CipherView } from "../../models/view/cipher.view";
import { BaseImporter } from "../base-importer";
import { Importer } from "../importer";
import { FskEntry, FskEntryTypesEnum, FskFile } from "./fsecure-fsk-types";
export class FSecureFskImporter extends BaseImporter implements Importer {
parse(data: string): Promise<ImportResult> {
const result = new ImportResult();
const results: FskFile = JSON.parse(data);
if (results == null || results.data == null) {
result.success = false;
return Promise.resolve(result);
}
for (const key in results.data) {
// eslint-disable-next-line
if (!results.data.hasOwnProperty(key)) {
continue;
}
const value = results.data[key];
const cipher = this.parseEntry(value);
result.ciphers.push(cipher);
}
result.success = true;
return Promise.resolve(result);
}
private parseEntry(entry: FskEntry): CipherView {
const cipher = this.initLoginCipher();
cipher.name = this.getValueOrDefault(entry.service);
cipher.notes = this.getValueOrDefault(entry.notes);
cipher.favorite = entry.favorite > 0;
switch (entry.type) {
case FskEntryTypesEnum.Login:
this.handleLoginEntry(entry, cipher);
break;
case FskEntryTypesEnum.CreditCard:
this.handleCreditCardEntry(entry, cipher);
break;
default:
return;
break;
}
this.convertToNoteIfNeeded(cipher);
this.cleanupCipher(cipher);
return cipher;
}
private handleLoginEntry(entry: FskEntry, cipher: CipherView) {
cipher.login.username = this.getValueOrDefault(entry.username);
cipher.login.password = this.getValueOrDefault(entry.password);
cipher.login.uris = this.makeUriArray(entry.url);
}
private handleCreditCardEntry(entry: FskEntry, cipher: CipherView) {
cipher.type = CipherType.Card;
cipher.card = new CardView();
cipher.card.cardholderName = this.getValueOrDefault(entry.username);
cipher.card.number = this.getValueOrDefault(entry.creditNumber);
cipher.card.brand = this.getCardBrand(cipher.card.number);
cipher.card.code = this.getValueOrDefault(entry.creditCvv);
if (!this.isNullOrWhitespace(entry.creditExpiry)) {
if (!this.setCardExpiration(cipher, entry.creditExpiry)) {
this.processKvp(cipher, "Expiration", entry.creditExpiry);
}
}
if (!this.isNullOrWhitespace(entry.password)) {
this.processKvp(cipher, "PIN", entry.password);
}
}
}

View File

@@ -0,0 +1,37 @@
export interface FskFile {
data: Data;
}
export interface Data {
[key: string]: FskEntry;
}
export enum FskEntryTypesEnum {
Login = 1,
CreditCard = 2,
}
export interface FskEntry {
color: string;
creditCvv: string;
creditExpiry: string;
creditNumber: string;
favorite: number; // UNIX timestamp
notes: string;
password: string;
passwordList: PasswordList[];
passwordModifiedDate: number; // UNIX timestamp
rev: string | number;
service: string;
style: string;
type: FskEntryTypesEnum;
url: string;
username: string;
createdDate: number; // UNIX timestamp
modifiedDate: number; // UNIX timestamp
}
export interface PasswordList {
changedate: string;
password: string;
}

View File

@@ -0,0 +1,49 @@
import { FskFile } from "./fsecure-fsk-types";
export const LoginTestEntry: FskFile = {
data: {
"1c3a2e31dcaa8459edd70a9d895ce298": {
color: "#00A34D",
createdDate: 0,
creditCvv: "",
creditExpiry: "",
creditNumber: "",
favorite: 1666440874,
modifiedDate: 0,
notes: "some note for example.com",
password: "somePassword",
passwordList: [],
passwordModifiedDate: 0,
rev: 1,
service: "example.com",
style: "website",
type: 1,
url: "https://www.example.com",
username: "jdoe",
},
},
};
export const CreditCardTestEntry: FskFile = {
data: {
"156498a46a3254f16035cbbbd09c2b8f": {
color: "#00baff",
createdDate: 1666438977,
creditCvv: "123",
creditExpiry: "22.10.2026",
creditNumber: "4242424242424242",
favorite: 0,
modifiedDate: 1666438977,
notes: "some notes to my card",
password: "1234",
passwordList: [],
passwordModifiedDate: 1666438977,
rev: 1,
service: "My credit card",
style: "creditcard",
type: 2,
url: "mybank",
username: "John Doe",
},
},
};

View File

@@ -15,7 +15,23 @@ export class KeePass2XmlImporter extends BaseImporter implements Importer {
return Promise.resolve(this.result);
}
const rootGroup = doc.querySelector("KeePassFile > Root > Group");
//Note: The doc.querySelector("KeePassFile > Root > Group") no longers works on node and we have to breakdown the query by nodes
const KeePassFileNode = doc.querySelector("KeePassFile");
if (KeePassFileNode == null) {
this.result.errorMessage = "Missing `KeePassFile` node.";
this.result.success = false;
return Promise.resolve(this.result);
}
const RootNode = KeePassFileNode.querySelector("Root");
if (RootNode == null) {
this.result.errorMessage = "Missing `KeePassFile > Root` node.";
this.result.success = false;
return Promise.resolve(this.result);
}
const rootGroup = RootNode.querySelector("Group");
if (rootGroup == null) {
this.result.errorMessage = "Missing `KeePassFile > Root > Group` node.";
this.result.success = false;

View File

@@ -18,7 +18,12 @@ export class KeeperCsvImporter extends BaseImporter implements Importer {
this.processFolder(result, value[0]);
const cipher = this.initLoginCipher();
cipher.notes = this.getValueOrDefault(value[5]) + "\n";
const notes = this.getValueOrDefault(value[5]);
if (notes) {
cipher.notes = `${notes}\n`;
}
cipher.name = this.getValueOrDefault(value[1], "--");
cipher.login.username = this.getValueOrDefault(value[2]);
cipher.login.password = this.getValueOrDefault(value[3]);
@@ -27,7 +32,11 @@ export class KeeperCsvImporter extends BaseImporter implements Importer {
if (value.length > 7) {
// we have some custom fields.
for (let i = 7; i < value.length; i = i + 2) {
this.processKvp(cipher, value[i], value[i + 1]);
if (value[i] == "TFC:Keeper") {
cipher.login.totp = value[i + 1];
} else {
this.processKvp(cipher, value[i], value[i + 1]);
}
}
}

View File

@@ -0,0 +1,43 @@
import { ImportResult } from "../../models/domain/import-result";
import { BaseImporter } from "../base-importer";
import { Importer } from "../importer";
import { PasskyJsonExport } from "./passky-json-types";
export class PasskyJsonImporter extends BaseImporter implements Importer {
parse(data: string): Promise<ImportResult> {
const result = new ImportResult();
const passkyExport: PasskyJsonExport = JSON.parse(data);
if (
passkyExport == null ||
passkyExport.passwords == null ||
passkyExport.passwords.length === 0
) {
result.success = false;
return Promise.resolve(result);
}
if (passkyExport.encrypted == true) {
result.success = false;
result.errorMessage = "Unable to import an encrypted passky backup.";
return Promise.resolve(result);
}
passkyExport.passwords.forEach((record) => {
const cipher = this.initLoginCipher();
cipher.name = record.website;
cipher.login.username = record.username;
cipher.login.password = record.password;
cipher.login.uris = this.makeUriArray(record.website);
cipher.notes = record.message;
this.convertToNoteIfNeeded(cipher);
this.cleanupCipher(cipher);
result.ciphers.push(cipher);
});
result.success = true;
return Promise.resolve(result);
}
}

View File

@@ -0,0 +1,11 @@
export interface PasskyJsonExport {
encrypted: boolean;
passwords: LoginEntry[];
}
export interface LoginEntry {
website: string;
username: string;
password: string;
message: string;
}

View File

@@ -50,6 +50,9 @@ export abstract class LogInStrategy {
| PasswordlessLogInCredentials
): Promise<AuthResult>;
// The user key comes from different sources depending on the login strategy
protected abstract setUserKey(response: IdentityTokenResponse): Promise<void>;
async logInTwoFactor(
twoFactor: TokenTwoFactorRequest,
captchaResponse: string = null
@@ -74,11 +77,6 @@ export abstract class LogInStrategy {
throw new Error("Invalid response object.");
}
protected onSuccessfulLogin(response: IdentityTokenResponse): Promise<void> {
// Implemented in subclass if required
return null;
}
protected async buildDeviceRequest() {
const appId = await this.appIdService.getAppId();
return new DeviceRequest(appId, this.platformUtilsService);
@@ -134,6 +132,9 @@ export abstract class LogInStrategy {
await this.tokenService.setTwoFactorToken(response);
}
await this.setUserKey(response);
// Must come after the user Key is set, otherwise createKeyPairForOldAccount will fail
const newSsoUser = response.key == null;
if (!newSsoUser) {
await this.cryptoService.setEncKey(response.key);
@@ -142,8 +143,6 @@ export abstract class LogInStrategy {
);
}
await this.onSuccessfulLogin(response);
this.messagingService.send("loggedIn");
return result;

View File

@@ -56,7 +56,7 @@ export class PasswordLogInStrategy extends LogInStrategy {
);
}
async onSuccessfulLogin() {
async setUserKey() {
await this.cryptoService.setKey(this.key);
await this.cryptoService.setKeyHash(this.localHashedPassword);
}

View File

@@ -56,7 +56,7 @@ export class PasswordlessLogInStrategy extends LogInStrategy {
);
}
async onSuccessfulLogin() {
async setUserKey() {
await this.cryptoService.setKey(this.passwordlessCredentials.decKey);
await this.cryptoService.setKeyHash(this.passwordlessCredentials.localPasswordHash);
}

View File

@@ -43,7 +43,7 @@ export class SsoLogInStrategy extends LogInStrategy {
);
}
async onSuccessfulLogin(tokenResponse: IdentityTokenResponse) {
async setUserKey(tokenResponse: IdentityTokenResponse) {
const newSsoUser = tokenResponse.key == null;
if (tokenResponse.keyConnectorUrl != null) {

View File

@@ -44,7 +44,7 @@ export class UserApiLogInStrategy extends LogInStrategy {
);
}
async onSuccessfulLogin(tokenResponse: IdentityTokenResponse) {
async setUserKey(tokenResponse: IdentityTokenResponse) {
if (tokenResponse.apiUseKeyConnector) {
const keyConnectorUrl = this.environmentService.getKeyConnectorUrl();
await this.keyConnectorService.getAndSetKey(keyConnectorUrl);

View File

@@ -0,0 +1,72 @@
import { ITreeNodeObject, TreeNode } from "../models/domain/tree-node";
import { ServiceUtils } from "./serviceUtils";
type FakeObject = { id: string; name: string };
describe("serviceUtils", () => {
let nodeTree: TreeNode<FakeObject>[];
beforeEach(() => {
nodeTree = [
createTreeNode({ id: "1", name: "1" }, [
createTreeNode({ id: "1.1", name: "1.1" }, [
createTreeNode({ id: "1.1.1", name: "1.1.1" }),
]),
createTreeNode({ id: "1.2", name: "1.2" }),
])(null),
createTreeNode({ id: "2", name: "2" }, [createTreeNode({ id: "2.1", name: "2.1" })])(null),
createTreeNode({ id: "3", name: "3" }, [])(null),
];
});
describe("nestedTraverse", () => {
it("should traverse a tree and add a node at the correct position given a valid path", () => {
const nodeToBeAdded: FakeObject = { id: "1.2.1", name: "1.2.1" };
const path = ["1", "1.2", "1.2.1"];
ServiceUtils.nestedTraverse(nodeTree, 0, path, nodeToBeAdded, null, "/");
expect(nodeTree[0].children[1].children[0].node).toEqual(nodeToBeAdded);
});
it("should combine the path for missing nodes and use as the added node name given an invalid path", () => {
const nodeToBeAdded: FakeObject = { id: "blank", name: "blank" };
const path = ["3", "3.1", "3.1.1"];
ServiceUtils.nestedTraverse(nodeTree, 0, path, nodeToBeAdded, null, "/");
expect(nodeTree[2].children[0].node.name).toEqual("3.1/3.1.1");
});
});
describe("getTreeNodeObject", () => {
it("should return a matching node given a single tree branch and a valid id", () => {
const id = "1.1.1";
const given = ServiceUtils.getTreeNodeObject(nodeTree[0], id);
expect(given.node.id).toEqual(id);
});
});
describe("getTreeNodeObjectFromList", () => {
it("should return a matching node given a list of branches and a valid id", () => {
const id = "1.1.1";
const given = ServiceUtils.getTreeNodeObjectFromList(nodeTree, id);
expect(given.node.id).toEqual(id);
});
});
});
type TreeNodeFactory<T extends ITreeNodeObject> = (
obj: T,
children?: TreeNodeFactoryWithoutParent<T>[]
) => TreeNodeFactoryWithoutParent<T>;
type TreeNodeFactoryWithoutParent<T extends ITreeNodeObject> = (
parent?: TreeNode<T>
) => TreeNode<T>;
const createTreeNode: TreeNodeFactory<FakeObject> =
(obj, children = []) =>
(parent) => {
const node = new TreeNode<FakeObject>(obj, parent, obj.name, obj.id);
node.children = children.map((childFunc) => childFunc(node));
return node;
};

View File

@@ -1,47 +1,62 @@
import { ITreeNodeObject, TreeNode } from "../models/domain/tree-node";
export class ServiceUtils {
/**
* Recursively adds a node to nodeTree
* @param {TreeNode<ITreeNodeObject>[]} nodeTree - An array of TreeNodes that the node will be added to
* @param {number} partIndex - Index of the `parts` array that is being processed
* @param {string[]} parts - Array of strings that represent the path to the `obj` node
* @param {ITreeNodeObject} obj - The node to be added to the tree
* @param {ITreeNodeObject} parent - The parent node of the `obj` node
* @param {string} delimiter - The delimiter used to split the path string, will be used to combine the path for missing nodes
*/
static nestedTraverse(
nodeTree: TreeNode<ITreeNodeObject>[],
partIndex: number,
parts: string[],
obj: ITreeNodeObject,
parent: ITreeNodeObject,
parent: TreeNode<ITreeNodeObject> | undefined,
delimiter: string
) {
if (parts.length <= partIndex) {
return;
}
const end = partIndex === parts.length - 1;
const partName = parts[partIndex];
const end: boolean = partIndex === parts.length - 1;
const partName: string = parts[partIndex];
for (let i = 0; i < nodeTree.length; i++) {
if (nodeTree[i].node.name !== parts[partIndex]) {
if (nodeTree[i].node.name !== partName) {
continue;
}
if (end && nodeTree[i].node.id !== obj.id) {
// Another node with the same name.
nodeTree.push(new TreeNode(obj, partName, parent));
// Another node exists with the same name as the node being added
nodeTree.push(new TreeNode(obj, parent, partName));
return;
}
// Move down the tree to the next level
ServiceUtils.nestedTraverse(
nodeTree[i].children,
partIndex + 1,
parts,
obj,
nodeTree[i].node,
nodeTree[i],
delimiter
);
return;
}
// If there's no node here with the same name...
if (nodeTree.filter((n) => n.node.name === partName).length === 0) {
// And we're at the end of the path given, add the node
if (end) {
nodeTree.push(new TreeNode(obj, partName, parent));
nodeTree.push(new TreeNode(obj, parent, partName));
return;
}
const newPartName = parts[partIndex] + delimiter + parts[partIndex + 1];
// And we're not at the end of the path, combine the current name with the next name
// 1, *1.2, 1.2.1 becomes
// 1, *1.2/1.2.1
const newPartName = partName + delimiter + parts[partIndex + 1];
ServiceUtils.nestedTraverse(
nodeTree,
0,
@@ -53,7 +68,37 @@ export class ServiceUtils {
}
}
/**
* Searches a tree for a node with a matching `id`
* @param {TreeNode<ITreeNodeObject>} nodeTree - A single TreeNode branch that will be searched
* @param {string} id - The id of the node to be found
* @returns {TreeNode<ITreeNodeObject>} The node with a matching `id`
*/
static getTreeNodeObject(
nodeTree: TreeNode<ITreeNodeObject>,
id: string
): TreeNode<ITreeNodeObject> {
if (nodeTree.node.id === id) {
return nodeTree;
}
for (let i = 0; i < nodeTree.children.length; i++) {
if (nodeTree.children[i].children != null) {
const node = ServiceUtils.getTreeNodeObject(nodeTree.children[i], id);
if (node !== null) {
return node;
}
}
}
return null;
}
/**
* Searches an array of tree nodes for a node with a matching `id`
* @param {TreeNode<ITreeNodeObject>} nodeTree - An array of TreeNode branches that will be searched
* @param {string} id - The id of the node to be found
* @returns {TreeNode<ITreeNodeObject>} The node with a matching `id`
*/
static getTreeNodeObjectFromList(
nodeTree: TreeNode<ITreeNodeObject>[],
id: string
): TreeNode<ITreeNodeObject> {
@@ -61,7 +106,7 @@ export class ServiceUtils {
if (nodeTree[i].node.id === id) {
return nodeTree[i];
} else if (nodeTree[i].children != null) {
const node = ServiceUtils.getTreeNodeObject(nodeTree[i].children, id);
const node = ServiceUtils.getTreeNodeObjectFromList(nodeTree[i].children, id);
if (node !== null) {
return node;
}

View File

@@ -1,5 +1,6 @@
/* eslint-disable no-useless-escape */
import { getHostname, parse } from "tldts";
import { Merge } from "type-fest";
import { CryptoService } from "../abstractions/crypto.service";
import { EncryptService } from "../abstractions/encrypt.service";
@@ -55,6 +56,10 @@ export class Utils {
}
static fromB64ToArray(str: string): Uint8Array {
if (str == null) {
return null;
}
if (Utils.isNode) {
return new Uint8Array(Buffer.from(str, "base64"));
} else {
@@ -108,6 +113,9 @@ export class Utils {
}
static fromBufferToB64(buffer: ArrayBuffer): string {
if (buffer == null) {
return null;
}
if (Utils.isNode) {
return Buffer.from(buffer).toString("base64");
} else {
@@ -423,6 +431,73 @@ export class Utils {
return this.global.bitwardenContainerService;
}
static validateHexColor(color: string) {
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(color);
}
/**
* Converts map to a Record<string, V> with the same data. Inverse of recordToMap
* Useful in toJSON methods, since Maps are not serializable
* @param map
* @returns
*/
static mapToRecord<K extends string | number, V>(map: Map<K, V>): Record<string, V> {
if (map == null) {
return null;
}
if (!(map instanceof Map)) {
return map;
}
return Object.fromEntries(map);
}
/**
* Converts record to a Map<string, V> with the same data. Inverse of mapToRecord
* Useful in fromJSON methods, since Maps are not serializable
*
* Warning: If the record has string keys that are numbers, they will be converted to numbers in the map
* @param record
* @returns
*/
static recordToMap<K extends string | number, V>(record: Record<K, V>): Map<K, V> {
if (record == null) {
return null;
} else if (record instanceof Map) {
return record;
}
const entries = Object.entries(record);
if (entries.length === 0) {
return new Map();
}
if (isNaN(Number(entries[0][0]))) {
return new Map(entries) as Map<K, V>;
} else {
return new Map(entries.map((e) => [Number(e[0]), e[1]])) as Map<K, V>;
}
}
/** Applies Object.assign, but converts the type nicely using Type-Fest Merge<Destination, Source> */
static merge<Destination, Source>(
destination: Destination,
source: Source
): Merge<Destination, Source> {
return Object.assign(destination, source) as unknown as Merge<Destination, Source>;
}
/**
* encodeURIComponent escapes all characters except the following:
* alphabetic, decimal digits, - _ . ! ~ * ' ( )
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#encoding_for_rfc3986
*/
static encodeRFC3986URIComponent(str: string): string {
return encodeURIComponent(str).replace(
/[!'()*]/g,
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
);
}
private static isMobile(win: Window) {
let mobile = false;
((a) => {
@@ -440,6 +515,10 @@ export class Utils {
return mobile || win.navigator.userAgent.match(/iPad/i) != null;
}
static delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
private static isAppleMobile(win: Window) {
return (
win.navigator.userAgent.match(/iPhone/i) != null ||

View File

@@ -4,19 +4,9 @@ export class PermissionsApi extends BaseResponse {
accessEventLogs: boolean;
accessImportExport: boolean;
accessReports: boolean;
/**
* @deprecated Sep 29 2021: This permission has been split out to `createNewCollections`, `editAnyCollection`, and
* `deleteAnyCollection`. It exists here for backwards compatibility with Server versions <= 1.43.0
*/
manageAllCollections: boolean;
createNewCollections: boolean;
editAnyCollection: boolean;
deleteAnyCollection: boolean;
/**
* @deprecated Sep 29 2021: This permission has been split out to `editAssignedCollections` and
* `deleteAssignedCollections`. It exists here for backwards compatibility with Server versions <= 1.43.0
*/
manageAssignedCollections: boolean;
editAssignedCollections: boolean;
deleteAssignedCollections: boolean;
manageCiphers: boolean;
@@ -36,10 +26,6 @@ export class PermissionsApi extends BaseResponse {
this.accessImportExport = this.getResponseProperty("AccessImportExport");
this.accessReports = this.getResponseProperty("AccessReports");
// For backwards compatibility with Server <= 1.43.0
this.manageAllCollections = this.getResponseProperty("ManageAllCollections");
this.manageAssignedCollections = this.getResponseProperty("ManageAssignedCollections");
this.createNewCollections = this.getResponseProperty("CreateNewCollections");
this.editAnyCollection = this.getResponseProperty("EditAnyCollection");
this.deleteAnyCollection = this.getResponseProperty("DeleteAnyCollection");

View File

@@ -20,7 +20,9 @@ export class OrganizationData {
useSso: boolean;
useKeyConnector: boolean;
useScim: boolean;
useCustomPermissions: boolean;
useResetPassword: boolean;
useSecretsManager: boolean;
selfHost: boolean;
usersGetPremium: boolean;
seats: number;
@@ -60,7 +62,9 @@ export class OrganizationData {
this.useSso = response.useSso;
this.useKeyConnector = response.useKeyConnector;
this.useScim = response.useScim;
this.useCustomPermissions = response.useCustomPermissions;
this.useResetPassword = response.useResetPassword;
this.useSecretsManager = response.useSecretsManager;
this.selfHost = response.selfHost;
this.usersGetPremium = response.usersGetPremium;
this.seats = response.seats;

View File

@@ -1,4 +1,4 @@
import { Except, Jsonify } from "type-fest";
import { Jsonify } from "type-fest";
import { AuthenticationStatus } from "../../enums/authenticationStatus";
import { KdfType } from "../../enums/kdfType";
@@ -40,7 +40,7 @@ export class EncryptionPair<TEncrypted, TDecrypted> {
}
static fromJSON<TEncrypted, TDecrypted>(
obj: Jsonify<EncryptionPair<Jsonify<TEncrypted>, Jsonify<TDecrypted>>>,
obj: { encrypted?: Jsonify<TEncrypted>; decrypted?: string | Jsonify<TDecrypted> },
decryptedFromJson?: (decObj: Jsonify<TDecrypted> | string) => TDecrypted,
encryptedFromJson?: (encObj: Jsonify<TEncrypted>) => TEncrypted
) {
@@ -123,7 +123,7 @@ export class AccountKeys {
apiKeyClientSecret?: string;
toJSON() {
return Object.assign(this as Except<AccountKeys, "publicKey">, {
return Utils.merge(this, {
publicKey: Utils.fromBufferToByteString(this.publicKey),
});
}
@@ -233,6 +233,7 @@ export class AccountSettings {
vaultTimeout?: number;
vaultTimeoutAction?: string = "lock";
serverConfig?: ServerConfigData;
avatarColor?: string;
static fromJSON(obj: Jsonify<AccountSettings>): AccountSettings {
if (obj == null) {
@@ -251,7 +252,7 @@ export class AccountSettings {
}
export type AccountSettingsSettings = {
equivalentDomains?: { [id: string]: any };
equivalentDomains?: string[][];
};
export class AccountTokens {

View File

@@ -1,3 +1,5 @@
import { Jsonify } from "type-fest";
import { OrganizationUserStatusType } from "../../enums/organizationUserStatusType";
import { OrganizationUserType } from "../../enums/organizationUserType";
import { ProductType } from "../../enums/productType";
@@ -20,7 +22,9 @@ export class Organization {
useSso: boolean;
useKeyConnector: boolean;
useScim: boolean;
useCustomPermissions: boolean;
useResetPassword: boolean;
useSecretsManager: boolean;
selfHost: boolean;
usersGetPremium: boolean;
seats: number;
@@ -64,7 +68,9 @@ export class Organization {
this.useSso = obj.useSso;
this.useKeyConnector = obj.useKeyConnector;
this.useScim = obj.useScim;
this.useCustomPermissions = obj.useCustomPermissions;
this.useResetPassword = obj.useResetPassword;
this.useSecretsManager = obj.useSecretsManager;
this.selfHost = obj.selfHost;
this.usersGetPremium = obj.usersGetPremium;
this.seats = obj.seats;
@@ -125,23 +131,19 @@ export class Organization {
}
get canCreateNewCollections() {
return (
this.isManager ||
(this.permissions.createNewCollections ?? this.permissions.manageAllCollections)
);
return this.isManager || this.permissions.createNewCollections;
}
get canEditAnyCollection() {
return (
this.isAdmin || (this.permissions.editAnyCollection ?? this.permissions.manageAllCollections)
);
return this.isAdmin || this.permissions.editAnyCollection;
}
get canUseAdminCollections() {
return this.canEditAnyCollection;
}
get canDeleteAnyCollection() {
return (
this.isAdmin ||
(this.permissions.deleteAnyCollection ?? this.permissions.manageAllCollections)
);
return this.isAdmin || this.permissions.deleteAnyCollection;
}
get canViewAllCollections() {
@@ -149,17 +151,11 @@ export class Organization {
}
get canEditAssignedCollections() {
return (
this.isManager ||
(this.permissions.editAssignedCollections ?? this.permissions.manageAssignedCollections)
);
return this.isManager || this.permissions.editAssignedCollections;
}
get canDeleteAssignedCollections() {
return (
this.isManager ||
(this.permissions.deleteAssignedCollections ?? this.permissions.manageAssignedCollections)
);
return this.isManager || this.permissions.deleteAssignedCollections;
}
get canViewAssignedCollections() {
@@ -201,4 +197,19 @@ export class Organization {
get hasProvider() {
return this.providerId != null || this.providerName != null;
}
get canAccessSecretsManager() {
return this.useSecretsManager;
}
static fromJSON(json: Jsonify<Organization>) {
if (json == null) {
return null;
}
return Object.assign(new Organization(), json, {
familySponsorshipLastSyncDate: new Date(json.familySponsorshipLastSyncDate),
familySponsorshipValidUntil: new Date(json.familySponsorshipValidUntil),
});
}
}

View File

@@ -4,22 +4,25 @@ import { State } from "./state";
describe("state", () => {
describe("fromJSON", () => {
it("should deserialize to an instance of itself", () => {
expect(State.fromJSON({})).toBeInstanceOf(State);
expect(State.fromJSON({}, () => new Account({}))).toBeInstanceOf(State);
});
it("should always assign an object to accounts", () => {
const state = State.fromJSON({});
const state = State.fromJSON({}, () => new Account({}));
expect(state.accounts).not.toBeNull();
expect(state.accounts).toEqual({});
});
it("should build an account map", () => {
const accountsSpy = jest.spyOn(Account, "fromJSON");
const state = State.fromJSON({
accounts: {
userId: {},
const state = State.fromJSON(
{
accounts: {
userId: {},
},
},
});
Account.fromJSON
);
expect(state.accounts["userId"]).toBeInstanceOf(Account);
expect(accountsSpy).toHaveBeenCalled();

View File

@@ -19,26 +19,28 @@ export class State<
// TODO, make Jsonify<State,TGlobalState,TAccount> work. It currently doesn't because Globals doesn't implement Jsonify.
static fromJSON<TGlobalState extends GlobalState, TAccount extends Account>(
obj: any
obj: any,
accountDeserializer: (json: Jsonify<TAccount>) => TAccount
): State<TGlobalState, TAccount> {
if (obj == null) {
return null;
}
return Object.assign(new State(null), obj, {
accounts: State.buildAccountMapFromJSON(obj?.accounts),
accounts: State.buildAccountMapFromJSON(obj?.accounts, accountDeserializer),
});
}
private static buildAccountMapFromJSON(
jsonAccounts: Jsonify<{ [userId: string]: Jsonify<Account> }>
private static buildAccountMapFromJSON<TAccount extends Account>(
jsonAccounts: { [userId: string]: Jsonify<TAccount> },
accountDeserializer: (json: Jsonify<TAccount>) => TAccount
) {
if (!jsonAccounts) {
return {};
}
const accounts: { [userId: string]: Account } = {};
const accounts: { [userId: string]: TAccount } = {};
for (const userId in jsonAccounts) {
accounts[userId] = Account.fromJSON(jsonAccounts[userId]);
accounts[userId] = accountDeserializer(jsonAccounts[userId]);
}
return accounts;
}

View File

@@ -1,12 +1,17 @@
export class TreeNode<T extends ITreeNodeObject> {
parent: T;
node: T;
parent: TreeNode<T>;
children: TreeNode<T>[] = [];
constructor(node: T, name: string, parent: T) {
constructor(node: T, parent: TreeNode<T>, name?: string, id?: string) {
this.parent = parent;
this.node = node;
this.node.name = name;
if (name) {
this.node.name = name;
}
if (id) {
this.node.id = id;
}
}
}

View File

@@ -7,4 +7,5 @@ export class WindowState {
displayBounds: any;
x?: number;
y?: number;
zoomFactor?: number;
}

View File

@@ -0,0 +1,9 @@
export class CollectionBulkDeleteRequest {
ids: string[];
organizationId: string;
constructor(ids: string[], organizationId?: string) {
this.ids = ids == null ? [] : ids;
this.organizationId = organizationId;
}
}

View File

@@ -6,6 +6,7 @@ export class CollectionRequest {
name: string;
externalId: string;
groups: SelectionReadOnlyRequest[] = [];
users: SelectionReadOnlyRequest[] = [];
constructor(collection?: Collection) {
if (collection == null) {

View File

@@ -1,8 +0,0 @@
import { SelectionReadOnlyRequest } from "./selection-read-only.request";
export class GroupRequest {
name: string;
accessAll: boolean;
externalId: string;
collections: SelectionReadOnlyRequest[] = [];
}

View File

@@ -1,12 +0,0 @@
import { OrganizationUserType } from "../../enums/organizationUserType";
import { PermissionsApi } from "../api/permissions.api";
import { SelectionReadOnlyRequest } from "./selection-read-only.request";
export class OrganizationUserInviteRequest {
emails: string[] = [];
type: OrganizationUserType;
accessAll: boolean;
collections: SelectionReadOnlyRequest[] = [];
permissions: PermissionsApi;
}

View File

@@ -1,11 +0,0 @@
import { OrganizationUserType } from "../../enums/organizationUserType";
import { PermissionsApi } from "../api/permissions.api";
import { SelectionReadOnlyRequest } from "./selection-read-only.request";
export class OrganizationUserUpdateRequest {
type: OrganizationUserType;
accessAll: boolean;
collections: SelectionReadOnlyRequest[] = [];
permissions: PermissionsApi;
}

View File

@@ -0,0 +1,7 @@
export class UpdateAvatarRequest {
avatarColor: string;
constructor(avatarColor: string) {
this.avatarColor = avatarColor;
}
}

View File

@@ -1,4 +1,4 @@
import { OrganizationUserResetPasswordRequest } from "./organization-user-reset-password.request";
import { OrganizationUserResetPasswordRequest } from "../../abstractions/organization-user/requests";
export class UpdateTempPasswordRequest extends OrganizationUserResetPasswordRequest {
masterPasswordHint: string;

View File

@@ -25,14 +25,27 @@ export class CollectionDetailsResponse extends CollectionResponse {
}
}
export class CollectionGroupDetailsResponse extends CollectionResponse {
export class CollectionAccessDetailsResponse extends CollectionResponse {
groups: SelectionReadOnlyResponse[] = [];
users: SelectionReadOnlyResponse[] = [];
/**
* Flag indicating the user has been explicitly assigned to this Collection
*/
assigned: boolean;
constructor(response: any) {
super(response);
this.assigned = this.getResponseProperty("Assigned") || false;
const groups = this.getResponseProperty("Groups");
if (groups != null) {
this.groups = groups.map((g: any) => new SelectionReadOnlyResponse(g));
}
const users = this.getResponseProperty("Users");
if (users != null) {
this.users = users.map((g: any) => new SelectionReadOnlyResponse(g));
}
}
}

View File

@@ -14,6 +14,7 @@ export class EmergencyAccessGranteeDetailsResponse extends BaseResponse {
status: EmergencyAccessStatusType;
waitTimeDays: number;
creationDate: string;
avatarColor: string;
constructor(response: any) {
super(response);
@@ -25,6 +26,7 @@ export class EmergencyAccessGranteeDetailsResponse extends BaseResponse {
this.status = this.getResponseProperty("Status");
this.waitTimeDays = this.getResponseProperty("WaitTimeDays");
this.creationDate = this.getResponseProperty("CreationDate");
this.avatarColor = this.getResponseProperty("AvatarColor");
}
}
@@ -37,6 +39,7 @@ export class EmergencyAccessGrantorDetailsResponse extends BaseResponse {
status: EmergencyAccessStatusType;
waitTimeDays: number;
creationDate: string;
avatarColor: string;
constructor(response: any) {
super(response);
@@ -48,6 +51,7 @@ export class EmergencyAccessGrantorDetailsResponse extends BaseResponse {
this.status = this.getResponseProperty("Status");
this.waitTimeDays = this.getResponseProperty("WaitTimeDays");
this.creationDate = this.getResponseProperty("CreationDate");
this.avatarColor = this.getResponseProperty("AvatarColor");
}
}

View File

@@ -1,31 +0,0 @@
import { BaseResponse } from "./base.response";
import { SelectionReadOnlyResponse } from "./selection-read-only.response";
export class GroupResponse extends BaseResponse {
id: string;
organizationId: string;
name: string;
accessAll: boolean;
externalId: string;
constructor(response: any) {
super(response);
this.id = this.getResponseProperty("Id");
this.organizationId = this.getResponseProperty("OrganizationId");
this.name = this.getResponseProperty("Name");
this.accessAll = this.getResponseProperty("AccessAll");
this.externalId = this.getResponseProperty("ExternalId");
}
}
export class GroupDetailsResponse extends GroupResponse {
collections: SelectionReadOnlyResponse[] = [];
constructor(response: any) {
super(response);
const collections = this.getResponseProperty("Collections");
if (collections != null) {
this.collections = collections.map((c: any) => new SelectionReadOnlyResponse(c));
}
}
}

View File

@@ -18,7 +18,9 @@ export class ProfileOrganizationResponse extends BaseResponse {
useSso: boolean;
useKeyConnector: boolean;
useScim: boolean;
useCustomPermissions: boolean;
useResetPassword: boolean;
useSecretsManager: boolean;
selfHost: boolean;
usersGetPremium: boolean;
seats: number;
@@ -59,7 +61,9 @@ export class ProfileOrganizationResponse extends BaseResponse {
this.useSso = this.getResponseProperty("UseSso");
this.useKeyConnector = this.getResponseProperty("UseKeyConnector") ?? false;
this.useScim = this.getResponseProperty("UseScim") ?? false;
this.useCustomPermissions = this.getResponseProperty("UseCustomPermissions") ?? false;
this.useResetPassword = this.getResponseProperty("UseResetPassword");
this.useSecretsManager = this.getResponseProperty("UseSecretsManager");
this.selfHost = this.getResponseProperty("SelfHost");
this.usersGetPremium = this.getResponseProperty("UsersGetPremium");
this.seats = this.getResponseProperty("Seats");

View File

@@ -14,6 +14,7 @@ export class ProfileResponse extends BaseResponse {
culture: string;
twoFactorEnabled: boolean;
key: string;
avatarColor: string;
privateKey: string;
securityStamp: string;
forcePasswordReset: boolean;
@@ -34,6 +35,7 @@ export class ProfileResponse extends BaseResponse {
this.culture = this.getResponseProperty("Culture");
this.twoFactorEnabled = this.getResponseProperty("TwoFactorEnabled");
this.key = this.getResponseProperty("Key");
this.avatarColor = this.getResponseProperty("AvatarColor");
this.privateKey = this.getResponseProperty("PrivateKey");
this.securityStamp = this.getResponseProperty("SecurityStamp");
this.forcePasswordReset = this.getResponseProperty("ForcePasswordReset") ?? false;

View File

@@ -1,3 +1,3 @@
import { OrganizationUserBulkPublicKeyResponse } from "../organization-user-bulk-public-key.response";
import { OrganizationUserBulkPublicKeyResponse } from "../../../abstractions/organization-user/responses";
export class ProviderUserBulkPublicKeyResponse extends OrganizationUserBulkPublicKeyResponse {}

View File

@@ -1,6 +1,6 @@
import { Collection } from "../domain/collection";
import { ITreeNodeObject } from "../domain/tree-node";
import { CollectionGroupDetailsResponse } from "../response/collection.response";
import { CollectionAccessDetailsResponse } from "../response/collection.response";
import { View } from "./view";
@@ -12,7 +12,7 @@ export class CollectionView implements View, ITreeNodeObject {
readOnly: boolean = null;
hidePasswords: boolean = null;
constructor(c?: Collection | CollectionGroupDetailsResponse) {
constructor(c?: Collection | CollectionAccessDetailsResponse) {
if (!c) {
return;
}

View File

@@ -1,3 +1,4 @@
import { DeepJsonify } from "../../types/deep-jsonify";
import { SendFile } from "../domain/send-file";
import { View } from "./view";
@@ -28,4 +29,12 @@ export class SendFileView implements View {
}
return 0;
}
static fromJSON(json: DeepJsonify<SendFileView>) {
if (json == null) {
return null;
}
return Object.assign(new SendFileView(), json);
}
}

View File

@@ -1,3 +1,4 @@
import { DeepJsonify } from "../../types/deep-jsonify";
import { SendText } from "../domain/send-text";
import { View } from "./view";
@@ -17,4 +18,12 @@ export class SendTextView implements View {
get maskedText(): string {
return this.text != null ? "••••••••" : null;
}
static fromJSON(json: DeepJsonify<SendTextView>) {
if (json == null) {
return null;
}
return Object.assign(new SendTextView(), json);
}
}

View File

@@ -1,5 +1,6 @@
import { SendType } from "../../enums/sendType";
import { Utils } from "../../misc/utils";
import { DeepJsonify } from "../../types/deep-jsonify";
import { Send } from "../domain/send";
import { SymmetricCryptoKey } from "../domain/symmetric-crypto-key";
@@ -65,4 +66,26 @@ export class SendView implements View {
get pendingDelete(): boolean {
return this.deletionDate <= new Date();
}
toJSON() {
return Utils.merge(this, {
key: Utils.fromBufferToB64(this.key),
});
}
static fromJSON(json: DeepJsonify<SendView>) {
if (json == null) {
return null;
}
return Object.assign(new SendView(), json, {
key: Utils.fromB64ToArray(json.key)?.buffer,
cryptoKey: SymmetricCryptoKey.fromJSON(json.cryptoKey),
text: SendTextView.fromJSON(json.text),
file: SendFileView.fromJSON(json.file),
revisionDate: json.revisionDate == null ? null : new Date(json.revisionDate),
deletionDate: json.deletionDate == null ? null : new Date(json.deletionDate),
expirationDate: json.expirationDate == null ? null : new Date(json.expirationDate),
});
}
}

View File

@@ -0,0 +1,30 @@
import { BehaviorSubject, Observable } from "rxjs";
import { AvatarUpdateService as AvatarUpdateServiceAbstraction } from "../../abstractions/account/avatar-update.service";
import { ApiService } from "../../abstractions/api.service";
import { StateService } from "../../abstractions/state.service";
import { UpdateAvatarRequest } from "../../models/request/update-avatar.request";
import { ProfileResponse } from "../../models/response/profile.response";
export class AvatarUpdateService implements AvatarUpdateServiceAbstraction {
private _avatarUpdate$ = new BehaviorSubject<string | null>(null);
avatarUpdate$: Observable<string | null> = this._avatarUpdate$.asObservable();
constructor(private apiService: ApiService, private stateService: StateService) {
this.loadColorFromState();
}
loadColorFromState(): Promise<string | null> {
return this.stateService.getAvatarColor().then((color) => {
this._avatarUpdate$.next(color);
return color;
});
}
pushUpdate(color: string | null): Promise<ProfileResponse | void> {
return this.apiService.putAvatar(new UpdateAvatarRequest(color)).then((response) => {
this.stateService.setAvatarColor(response.avatarColor);
this._avatarUpdate$.next(response.avatarColor);
});
}
}

View File

@@ -17,6 +17,7 @@ import { CipherCreateRequest } from "../models/request/cipher-create.request";
import { CipherPartialRequest } from "../models/request/cipher-partial.request";
import { CipherShareRequest } from "../models/request/cipher-share.request";
import { CipherRequest } from "../models/request/cipher.request";
import { CollectionBulkDeleteRequest } from "../models/request/collection-bulk-delete.request";
import { CollectionRequest } from "../models/request/collection.request";
import { DeleteRecoverRequest } from "../models/request/delete-recover.request";
import { DeviceVerificationRequest } from "../models/request/device-verification.request";
@@ -29,7 +30,6 @@ import { EmergencyAccessInviteRequest } from "../models/request/emergency-access
import { EmergencyAccessPasswordRequest } from "../models/request/emergency-access-password.request";
import { EmergencyAccessUpdateRequest } from "../models/request/emergency-access-update.request";
import { EventRequest } from "../models/request/event.request";
import { GroupRequest } from "../models/request/group.request";
import { IapCheckRequest } from "../models/request/iap-check.request";
import { PasswordTokenRequest } from "../models/request/identity-token/password-token.request";
import { SsoTokenRequest } from "../models/request/identity-token/sso-token.request";
@@ -42,15 +42,6 @@ import { KeyConnectorUserKeyRequest } from "../models/request/key-connector-user
import { KeysRequest } from "../models/request/keys.request";
import { OrganizationConnectionRequest } from "../models/request/organization-connection.request";
import { OrganizationImportRequest } from "../models/request/organization-import.request";
import { OrganizationUserAcceptRequest } from "../models/request/organization-user-accept.request";
import { OrganizationUserBulkConfirmRequest } from "../models/request/organization-user-bulk-confirm.request";
import { OrganizationUserBulkRequest } from "../models/request/organization-user-bulk.request";
import { OrganizationUserConfirmRequest } from "../models/request/organization-user-confirm.request";
import { OrganizationUserInviteRequest } from "../models/request/organization-user-invite.request";
import { OrganizationUserResetPasswordEnrollmentRequest } from "../models/request/organization-user-reset-password-enrollment.request";
import { OrganizationUserResetPasswordRequest } from "../models/request/organization-user-reset-password.request";
import { OrganizationUserUpdateGroupsRequest } from "../models/request/organization-user-update-groups.request";
import { OrganizationUserUpdateRequest } from "../models/request/organization-user-update.request";
import { OrganizationSponsorshipCreateRequest } from "../models/request/organization/organization-sponsorship-create.request";
import { OrganizationSponsorshipRedeemRequest } from "../models/request/organization/organization-sponsorship-redeem.request";
import { PasswordHintRequest } from "../models/request/password-hint.request";
@@ -79,6 +70,7 @@ import { TaxInfoUpdateRequest } from "../models/request/tax-info-update.request"
import { TwoFactorEmailRequest } from "../models/request/two-factor-email.request";
import { TwoFactorProviderRequest } from "../models/request/two-factor-provider.request";
import { TwoFactorRecoveryRequest } from "../models/request/two-factor-recovery.request";
import { UpdateAvatarRequest } from "../models/request/update-avatar.request";
import { UpdateDomainsRequest } from "../models/request/update-domains.request";
import { UpdateKeyRequest } from "../models/request/update-key.request";
import { UpdateProfileRequest } from "../models/request/update-profile.request";
@@ -101,7 +93,7 @@ import { BillingPaymentResponse } from "../models/response/billing-payment.respo
import { BreachAccountResponse } from "../models/response/breach-account.response";
import { CipherResponse } from "../models/response/cipher.response";
import {
CollectionGroupDetailsResponse,
CollectionAccessDetailsResponse,
CollectionResponse,
} from "../models/response/collection.response";
import { DeviceVerificationResponse } from "../models/response/device-verification.response";
@@ -114,7 +106,6 @@ import {
} from "../models/response/emergency-access.response";
import { ErrorResponse } from "../models/response/error.response";
import { EventResponse } from "../models/response/event.response";
import { GroupDetailsResponse, GroupResponse } from "../models/response/group.response";
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";
@@ -126,13 +117,6 @@ import {
} from "../models/response/organization-connection.response";
import { OrganizationExportResponse } from "../models/response/organization-export.response";
import { OrganizationSponsorshipSyncStatusResponse } from "../models/response/organization-sponsorship-sync-status.response";
import { OrganizationUserBulkPublicKeyResponse } from "../models/response/organization-user-bulk-public-key.response";
import { OrganizationUserBulkResponse } from "../models/response/organization-user-bulk.response";
import {
OrganizationUserDetailsResponse,
OrganizationUserUserDetailsResponse,
OrganizationUserResetPasswordDetailsReponse,
} from "../models/response/organization-user.response";
import { PaymentResponse } from "../models/response/payment.response";
import { PlanResponse } from "../models/response/plan.response";
import { PolicyResponse } from "../models/response/policy.response";
@@ -165,13 +149,18 @@ import { TwoFactorEmailResponse } from "../models/response/two-factor-email.resp
import { TwoFactorProviderResponse } from "../models/response/two-factor-provider.response";
import { TwoFactorRecoverResponse } from "../models/response/two-factor-recover.response";
import {
TwoFactorWebAuthnResponse,
ChallengeResponse,
TwoFactorWebAuthnResponse,
} from "../models/response/two-factor-web-authn.response";
import { TwoFactorYubiKeyResponse } from "../models/response/two-factor-yubi-key.response";
import { UserKeyResponse } from "../models/response/user-key.response";
import { SendAccessView } from "../models/view/send-access.view";
/**
* @deprecated The `ApiService` class is deprecated and calls should be extracted into individual
* api services. The `send` method is still allowed to be used within api services. For background
* of this decision please read https://contributing.bitwarden.com/architecture/adr/refactor-api-service.
*/
export class ApiService implements ApiServiceAbstraction {
private device: DeviceType;
private deviceType: string;
@@ -301,6 +290,11 @@ export class ApiService implements ApiServiceAbstraction {
return new ProfileResponse(r);
}
async putAvatar(request: UpdateAvatarRequest): Promise<ProfileResponse> {
const r = await this.send("PUT", "/accounts/avatar", request, true, true);
return new ProfileResponse(r);
}
putTaxInfo(request: TaxInfoUpdateRequest): Promise<any> {
return this.send("PUT", "/accounts/tax", request, true, false);
}
@@ -809,10 +803,10 @@ export class ApiService implements ApiServiceAbstraction {
// Collections APIs
async getCollectionDetails(
async getCollectionAccessDetails(
organizationId: string,
id: string
): Promise<CollectionGroupDetailsResponse> {
): Promise<CollectionAccessDetailsResponse> {
const r = await this.send(
"GET",
"/organizations/" + organizationId + "/collections/" + id + "/details",
@@ -820,7 +814,7 @@ export class ApiService implements ApiServiceAbstraction {
true,
true
);
return new CollectionGroupDetailsResponse(r);
return new CollectionAccessDetailsResponse(r);
}
async getUserCollections(): Promise<ListResponse<CollectionResponse>> {
@@ -839,6 +833,19 @@ export class ApiService implements ApiServiceAbstraction {
return new ListResponse(r, CollectionResponse);
}
async getManyCollectionsWithAccessDetails(
organizationId: string
): Promise<ListResponse<CollectionAccessDetailsResponse>> {
const r = await this.send(
"GET",
"/organizations/" + organizationId + "/collections/details",
null,
true,
true
);
return new ListResponse(r, CollectionAccessDetailsResponse);
}
async getCollectionUsers(
organizationId: string,
id: string
@@ -906,6 +913,16 @@ export class ApiService implements ApiServiceAbstraction {
);
}
deleteManyCollections(request: CollectionBulkDeleteRequest): Promise<any> {
return this.send(
"DELETE",
"/organizations/" + request.organizationId + "/collections",
request,
true,
false
);
}
deleteCollectionUser(
organizationId: string,
id: string,
@@ -922,28 +939,6 @@ export class ApiService implements ApiServiceAbstraction {
// Groups APIs
async getGroupDetails(organizationId: string, id: string): Promise<GroupDetailsResponse> {
const r = await this.send(
"GET",
"/organizations/" + organizationId + "/groups/" + id + "/details",
null,
true,
true
);
return new GroupDetailsResponse(r);
}
async getGroups(organizationId: string): Promise<ListResponse<GroupResponse>> {
const r = await this.send(
"GET",
"/organizations/" + organizationId + "/groups",
null,
true,
true
);
return new ListResponse(r, GroupResponse);
}
async getGroupUsers(organizationId: string, id: string): Promise<string[]> {
const r = await this.send(
"GET",
@@ -955,32 +950,6 @@ export class ApiService implements ApiServiceAbstraction {
return r;
}
async postGroup(organizationId: string, request: GroupRequest): Promise<GroupResponse> {
const r = await this.send(
"POST",
"/organizations/" + organizationId + "/groups",
request,
true,
true
);
return new GroupResponse(r);
}
async putGroup(
organizationId: string,
id: string,
request: GroupRequest
): Promise<GroupResponse> {
const r = await this.send(
"PUT",
"/organizations/" + organizationId + "/groups/" + id,
request,
true,
true
);
return new GroupResponse(r);
}
async putGroupUsers(organizationId: string, id: string, request: string[]): Promise<any> {
await this.send(
"PUT",
@@ -991,16 +960,6 @@ export class ApiService implements ApiServiceAbstraction {
);
}
deleteGroup(organizationId: string, id: string): Promise<any> {
return this.send(
"DELETE",
"/organizations/" + organizationId + "/groups/" + id,
null,
true,
false
);
}
deleteGroupUser(organizationId: string, id: string, organizationUserId: string): Promise<any> {
return this.send(
"DELETE",
@@ -1011,281 +970,6 @@ export class ApiService implements ApiServiceAbstraction {
);
}
// Organization User APIs
async getOrganizationUser(
organizationId: string,
id: string
): Promise<OrganizationUserDetailsResponse> {
const r = await this.send(
"GET",
"/organizations/" + organizationId + "/users/" + id,
null,
true,
true
);
return new OrganizationUserDetailsResponse(r);
}
async getOrganizationUserGroups(organizationId: string, id: string): Promise<string[]> {
const r = await this.send(
"GET",
"/organizations/" + organizationId + "/users/" + id + "/groups",
null,
true,
true
);
return r;
}
async getOrganizationUsers(
organizationId: string
): Promise<ListResponse<OrganizationUserUserDetailsResponse>> {
const r = await this.send(
"GET",
"/organizations/" + organizationId + "/users",
null,
true,
true
);
return new ListResponse(r, OrganizationUserUserDetailsResponse);
}
async getOrganizationUserResetPasswordDetails(
organizationId: string,
id: string
): Promise<OrganizationUserResetPasswordDetailsReponse> {
const r = await this.send(
"GET",
"/organizations/" + organizationId + "/users/" + id + "/reset-password-details",
null,
true,
true
);
return new OrganizationUserResetPasswordDetailsReponse(r);
}
postOrganizationUserInvite(
organizationId: string,
request: OrganizationUserInviteRequest
): Promise<any> {
return this.send(
"POST",
"/organizations/" + organizationId + "/users/invite",
request,
true,
false
);
}
postOrganizationUserReinvite(organizationId: string, id: string): Promise<any> {
return this.send(
"POST",
"/organizations/" + organizationId + "/users/" + id + "/reinvite",
null,
true,
false
);
}
async postManyOrganizationUserReinvite(
organizationId: string,
request: OrganizationUserBulkRequest
): Promise<ListResponse<OrganizationUserBulkResponse>> {
const r = await this.send(
"POST",
"/organizations/" + organizationId + "/users/reinvite",
request,
true,
true
);
return new ListResponse(r, OrganizationUserBulkResponse);
}
postOrganizationUserAccept(
organizationId: string,
id: string,
request: OrganizationUserAcceptRequest
): Promise<any> {
return this.send(
"POST",
"/organizations/" + organizationId + "/users/" + id + "/accept",
request,
true,
false
);
}
postOrganizationUserConfirm(
organizationId: string,
id: string,
request: OrganizationUserConfirmRequest
): Promise<any> {
return this.send(
"POST",
"/organizations/" + organizationId + "/users/" + id + "/confirm",
request,
true,
false
);
}
async postOrganizationUsersPublicKey(
organizationId: string,
request: OrganizationUserBulkRequest
): Promise<ListResponse<OrganizationUserBulkPublicKeyResponse>> {
const r = await this.send(
"POST",
"/organizations/" + organizationId + "/users/public-keys",
request,
true,
true
);
return new ListResponse(r, OrganizationUserBulkPublicKeyResponse);
}
async postOrganizationUserBulkConfirm(
organizationId: string,
request: OrganizationUserBulkConfirmRequest
): Promise<ListResponse<OrganizationUserBulkResponse>> {
const r = await this.send(
"POST",
"/organizations/" + organizationId + "/users/confirm",
request,
true,
true
);
return new ListResponse(r, OrganizationUserBulkResponse);
}
putOrganizationUser(
organizationId: string,
id: string,
request: OrganizationUserUpdateRequest
): Promise<any> {
return this.send(
"PUT",
"/organizations/" + organizationId + "/users/" + id,
request,
true,
false
);
}
putOrganizationUserGroups(
organizationId: string,
id: string,
request: OrganizationUserUpdateGroupsRequest
): Promise<any> {
return this.send(
"PUT",
"/organizations/" + organizationId + "/users/" + id + "/groups",
request,
true,
false
);
}
putOrganizationUserResetPasswordEnrollment(
organizationId: string,
userId: string,
request: OrganizationUserResetPasswordEnrollmentRequest
): Promise<void> {
return this.send(
"PUT",
"/organizations/" + organizationId + "/users/" + userId + "/reset-password-enrollment",
request,
true,
false
);
}
putOrganizationUserResetPassword(
organizationId: string,
id: string,
request: OrganizationUserResetPasswordRequest
): Promise<any> {
return this.send(
"PUT",
"/organizations/" + organizationId + "/users/" + id + "/reset-password",
request,
true,
false
);
}
deleteOrganizationUser(organizationId: string, id: string): Promise<any> {
return this.send(
"DELETE",
"/organizations/" + organizationId + "/users/" + id,
null,
true,
false
);
}
async deleteManyOrganizationUsers(
organizationId: string,
request: OrganizationUserBulkRequest
): Promise<ListResponse<OrganizationUserBulkResponse>> {
const r = await this.send(
"DELETE",
"/organizations/" + organizationId + "/users",
request,
true,
true
);
return new ListResponse(r, OrganizationUserBulkResponse);
}
revokeOrganizationUser(organizationId: string, id: string): Promise<any> {
return this.send(
"PUT",
"/organizations/" + organizationId + "/users/" + id + "/revoke",
null,
true,
false
);
}
async revokeManyOrganizationUsers(
organizationId: string,
request: OrganizationUserBulkRequest
): Promise<ListResponse<OrganizationUserBulkResponse>> {
const r = await this.send(
"PUT",
"/organizations/" + organizationId + "/users/revoke",
request,
true,
true
);
return new ListResponse(r, OrganizationUserBulkResponse);
}
restoreOrganizationUser(organizationId: string, id: string): Promise<any> {
return this.send(
"PUT",
"/organizations/" + organizationId + "/users/" + id + "/restore",
null,
true,
false
);
}
async restoreManyOrganizationUsers(
organizationId: string,
request: OrganizationUserBulkRequest
): Promise<ListResponse<OrganizationUserBulkResponse>> {
const r = await this.send(
"PUT",
"/organizations/" + organizationId + "/users/restore",
request,
true,
true
);
return new ListResponse(r, OrganizationUserBulkResponse);
}
// Plan APIs
async getPlans(): Promise<ListResponse<PlanResponse>> {
@@ -2081,7 +1765,7 @@ export class ApiService implements ApiServiceAbstraction {
request.headers.set("Bitwarden-Client-Name", this.platformUtilsService.getClientType());
request.headers.set(
"Bitwarden-Client-Version",
await this.platformUtilsService.getApplicationVersion()
await this.platformUtilsService.getApplicationVersionNumber()
);
return this.nativeFetch(request);
}

View File

@@ -413,7 +413,7 @@ export class CipherService implements CipherServiceAbstraction {
: firstValueFrom(this.settingsService.settings$).then(
(settings: AccountSettingsSettings) => {
let matches: any[] = [];
settings.equivalentDomains?.forEach((eqDomain: any) => {
settings?.equivalentDomains?.forEach((eqDomain: any) => {
if (eqDomain.length && eqDomain.indexOf(domain) >= 0) {
matches = matches.concat(eqDomain);
}

View File

@@ -106,9 +106,13 @@ export class CollectionService implements CollectionServiceAbstraction {
return nodes;
}
/**
* @deprecated August 30 2022: Moved to new Vault Filter Service
* Remove when Desktop and Browser are updated
*/
async getNested(id: string): Promise<TreeNode<CollectionView>> {
const collections = await this.getAllNested();
return ServiceUtils.getTreeNodeObject(collections, id) as TreeNode<CollectionView>;
return ServiceUtils.getTreeNodeObjectFromList(collections, id) as TreeNode<CollectionView>;
}
async upsert(collection: CollectionData | CollectionData[]): Promise<any> {

View File

@@ -1,107 +0,0 @@
import { ApiService } from "../abstractions/api.service";
import { CipherService } from "../abstractions/cipher.service";
import { EventService as EventServiceAbstraction } from "../abstractions/event.service";
import { LogService } from "../abstractions/log.service";
import { OrganizationService } from "../abstractions/organization/organization.service.abstraction";
import { StateService } from "../abstractions/state.service";
import { EventType } from "../enums/eventType";
import { EventData } from "../models/data/event.data";
import { EventRequest } from "../models/request/event.request";
export class EventService implements EventServiceAbstraction {
private inited = false;
constructor(
private apiService: ApiService,
private cipherService: CipherService,
private stateService: StateService,
private logService: LogService,
private organizationService: OrganizationService
) {}
init(checkOnInterval: boolean) {
if (this.inited) {
return;
}
this.inited = true;
if (checkOnInterval) {
this.uploadEvents();
setInterval(() => this.uploadEvents(), 60 * 1000); // check every 60 seconds
}
}
async collect(
eventType: EventType,
cipherId: string = null,
uploadImmediately = false,
organizationId: string = null
): Promise<any> {
const authed = await this.stateService.getIsAuthenticated();
if (!authed) {
return;
}
const organizations = await this.organizationService.getAll();
if (organizations == null) {
return;
}
const orgIds = new Set<string>(organizations.filter((o) => o.useEvents).map((o) => o.id));
if (orgIds.size === 0) {
return;
}
if (cipherId != null) {
const cipher = await this.cipherService.get(cipherId);
if (cipher == null || cipher.organizationId == null || !orgIds.has(cipher.organizationId)) {
return;
}
}
if (organizationId != null) {
if (!orgIds.has(organizationId)) {
return;
}
}
let eventCollection = await this.stateService.getEventCollection();
if (eventCollection == null) {
eventCollection = [];
}
const event = new EventData();
event.type = eventType;
event.cipherId = cipherId;
event.date = new Date().toISOString();
event.organizationId = organizationId;
eventCollection.push(event);
await this.stateService.setEventCollection(eventCollection);
if (uploadImmediately) {
await this.uploadEvents();
}
}
async uploadEvents(userId?: string): Promise<any> {
const authed = await this.stateService.getIsAuthenticated({ userId: userId });
if (!authed) {
return;
}
const eventCollection = await this.stateService.getEventCollection({ userId: userId });
if (eventCollection == null || eventCollection.length === 0) {
return;
}
const request = eventCollection.map((e) => {
const req = new EventRequest();
req.type = e.type;
req.cipherId = e.cipherId;
req.date = e.date;
req.organizationId = e.organizationId;
return req;
});
try {
await this.apiService.postEventsCollect(request);
this.clearEvents(userId);
} catch (e) {
this.logService.error(e);
}
}
async clearEvents(userId?: string): Promise<any> {
await this.stateService.setEventCollection(null, { userId: userId });
}
}

View File

@@ -0,0 +1,61 @@
import { CipherService } from "../../abstractions/cipher.service";
import { EventCollectionService as EventCollectionServiceAbstraction } from "../../abstractions/event/event-collection.service";
import { EventUploadService } from "../../abstractions/event/event-upload.service";
import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction";
import { StateService } from "../../abstractions/state.service";
import { EventType } from "../../enums/eventType";
import { EventData } from "../../models/data/event.data";
export class EventCollectionService implements EventCollectionServiceAbstraction {
constructor(
private cipherService: CipherService,
private stateService: StateService,
private organizationService: OrganizationService,
private eventUploadService: EventUploadService
) {}
async collect(
eventType: EventType,
cipherId: string = null,
uploadImmediately = false,
organizationId: string = null
): Promise<any> {
const authed = await this.stateService.getIsAuthenticated();
if (!authed) {
return;
}
const organizations = await this.organizationService.getAll();
if (organizations == null) {
return;
}
const orgIds = new Set<string>(organizations.filter((o) => o.useEvents).map((o) => o.id));
if (orgIds.size === 0) {
return;
}
if (cipherId != null) {
const cipher = await this.cipherService.get(cipherId);
if (cipher == null || cipher.organizationId == null || !orgIds.has(cipher.organizationId)) {
return;
}
}
if (organizationId != null) {
if (!orgIds.has(organizationId)) {
return;
}
}
let eventCollection = await this.stateService.getEventCollection();
if (eventCollection == null) {
eventCollection = [];
}
const event = new EventData();
event.type = eventType;
event.cipherId = cipherId;
event.date = new Date().toISOString();
event.organizationId = organizationId;
eventCollection.push(event);
await this.stateService.setEventCollection(eventCollection);
if (uploadImmediately) {
await this.eventUploadService.uploadEvents();
}
}
}

View File

@@ -0,0 +1,55 @@
import { ApiService } from "../../abstractions/api.service";
import { EventUploadService as EventUploadServiceAbstraction } from "../../abstractions/event/event-upload.service";
import { LogService } from "../../abstractions/log.service";
import { StateService } from "../../abstractions/state.service";
import { EventRequest } from "../../models/request/event.request";
export class EventUploadService implements EventUploadServiceAbstraction {
private inited = false;
constructor(
private apiService: ApiService,
private stateService: StateService,
private logService: LogService
) {}
init(checkOnInterval: boolean) {
if (this.inited) {
return;
}
this.inited = true;
if (checkOnInterval) {
this.uploadEvents();
setInterval(() => this.uploadEvents(), 60 * 1000); // check every 60 seconds
}
}
async uploadEvents(userId?: string): Promise<void> {
const authed = await this.stateService.getIsAuthenticated({ userId: userId });
if (!authed) {
return;
}
const eventCollection = await this.stateService.getEventCollection({ userId: userId });
if (eventCollection == null || eventCollection.length === 0) {
return;
}
const request = eventCollection.map((e) => {
const req = new EventRequest();
req.type = e.type;
req.cipherId = e.cipherId;
req.date = e.date;
req.organizationId = e.organizationId;
return req;
});
try {
await this.apiService.postEventsCollect(request);
this.clearEvents(userId);
} catch (e) {
this.logService.error(e);
}
}
private async clearEvents(userId?: string): Promise<any> {
await this.stateService.setEventCollection(null, { userId: userId });
}
}

View File

@@ -1,5 +1,4 @@
import * as papa from "papaparse";
import { firstValueFrom } from "rxjs";
import { ApiService } from "../abstractions/api.service";
import { CipherService } from "../abstractions/cipher.service";
@@ -116,7 +115,7 @@ export class ExportService implements ExportServiceAbstraction {
const promises = [];
promises.push(
firstValueFrom(this.folderService.folderViews$).then((folders) => {
this.folderService.getAllDecryptedFromState().then((folders) => {
decFolders = folders;
})
);
@@ -192,7 +191,7 @@ export class ExportService implements ExportServiceAbstraction {
const promises = [];
promises.push(
firstValueFrom(this.folderService.folders$).then((f) => {
this.folderService.getAllFromState().then((f) => {
folders = f;
})
);

View File

@@ -64,6 +64,18 @@ export class FolderService implements InternalFolderServiceAbstraction {
return folders.find((folder) => folder.id === id);
}
async getAllFromState(): Promise<Folder[]> {
const folders = await this.stateService.getEncryptedFolders();
const response: Folder[] = [];
for (const id in folders) {
// eslint-disable-next-line
if (folders.hasOwnProperty(id)) {
response.push(new Folder(folders[id]));
}
}
return response;
}
/**
* @deprecated For the CLI only
* @param id id of the folder

View File

@@ -7,6 +7,7 @@ export class I18nService implements I18nServiceAbstraction {
locale$: Observable<string> = this._locale.asObservable();
// First locale is the default (English)
supportedTranslationLocales: string[] = ["en"];
defaultLocale = "en";
translationLocale: string;
collator: Intl.Collator;
localeNames = new Map<string, string>([
@@ -106,14 +107,14 @@ export class I18nService implements I18nServiceAbstraction {
this.translationLocale = this.translationLocale.slice(0, 2);
if (this.supportedTranslationLocales.indexOf(this.translationLocale) === -1) {
this.translationLocale = this.supportedTranslationLocales[0];
this.translationLocale = this.defaultLocale;
}
}
if (this.localesDirectory != null) {
await this.loadMessages(this.translationLocale, this.localeMessages);
if (this.translationLocale !== this.supportedTranslationLocales[0]) {
await this.loadMessages(this.supportedTranslationLocales[0], this.defaultMessages);
if (this.translationLocale !== this.defaultLocale) {
await this.loadMessages(this.defaultLocale, this.defaultMessages);
}
}
}
@@ -122,7 +123,7 @@ export class I18nService implements I18nServiceAbstraction {
return this.translate(id, p1, p2, p3);
}
translate(id: string, p1?: string, p2?: string, p3?: string): string {
translate(id: string, p1?: string | number, p2?: string | number, p3?: string | number): string {
let result: string;
// eslint-disable-next-line
if (this.localeMessages.hasOwnProperty(id) && this.localeMessages[id]) {
@@ -136,13 +137,13 @@ export class I18nService implements I18nServiceAbstraction {
if (result !== "") {
if (p1 != null) {
result = result.split("__$1__").join(p1);
result = result.split("__$1__").join(p1.toString());
}
if (p2 != null) {
result = result.split("__$2__").join(p2);
result = result.split("__$2__").join(p2.toString());
}
if (p3 != null) {
result = result.split("__$3__").join(p3);
result = result.split("__$3__").join(p3.toString());
}
}

View File

@@ -28,10 +28,10 @@ import { CodebookCsvImporter } from "../importers/codebook-csv-importer";
import { DashlaneCsvImporter } from "../importers/dashlane/dashlane-csv-importer";
import { DashlaneJsonImporter } from "../importers/dashlane/dashlane-json-importer";
import { EncryptrCsvImporter } from "../importers/encryptr-csv-importer";
import { EnpassCsvImporter } from "../importers/enpass-csv-importer";
import { EnpassJsonImporter } from "../importers/enpass-json-importer";
import { EnpassCsvImporter } from "../importers/enpass/enpass-csv-importer";
import { EnpassJsonImporter } from "../importers/enpass/enpass-json-importer";
import { FirefoxCsvImporter } from "../importers/firefox-csv-importer";
import { FSecureFskImporter } from "../importers/fsecure-fsk-importer";
import { FSecureFskImporter } from "../importers/fsecure/fsecure-fsk-importer";
import { GnomeJsonImporter } from "../importers/gnome-json-importer";
import { ImportError } from "../importers/import-error";
import { Importer } from "../importers/importer";
@@ -51,6 +51,7 @@ import { OnePasswordMacCsvImporter } from "../importers/onepassword/onepassword-
import { OnePasswordWinCsvImporter } from "../importers/onepassword/onepassword-win-csv-importer";
import { PadlockCsvImporter } from "../importers/padlock-csv-importer";
import { PassKeepCsvImporter } from "../importers/passkeep-csv-importer";
import { PasskyJsonImporter } from "../importers/passky/passky-json-importer";
import { PassmanJsonImporter } from "../importers/passman-json-importer";
import { PasspackCsvImporter } from "../importers/passpack-csv-importer";
import { PasswordAgentCsvImporter } from "../importers/passwordagent-csv-importer";
@@ -279,6 +280,8 @@ export class ImportService implements ImportServiceAbstraction {
return new YotiCsvImporter();
case "nordpasscsv":
return new NordPassCsvImporter();
case "passkyjson":
return new PasskyJsonImporter();
default:
return null;
}

View File

@@ -1,9 +1,12 @@
import { LoginService as LoginServiceAbstraction } from "../abstractions/login.service";
import { StateService } from "../abstractions/state.service";
export class LoginService implements LoginServiceAbstraction {
private _email: string;
private _rememberEmail: boolean;
constructor(private stateService: StateService) {}
getEmail() {
return this._email;
}
@@ -24,4 +27,9 @@ export class LoginService implements LoginServiceAbstraction {
this._email = null;
this._rememberEmail = null;
}
async saveEmailSettings() {
await this.stateService.setRememberedEmail(this._rememberEmail ? this._email : null);
this.clearValues();
}
}

View File

@@ -1,12 +1,6 @@
import {
AbstractStorageService,
MemoryStorageServiceInterface,
} from "../abstractions/storage.service";
import { AbstractMemoryStorageService } from "../abstractions/storage.service";
export class MemoryStorageService
extends AbstractStorageService
implements MemoryStorageServiceInterface
{
export class MemoryStorageService extends AbstractMemoryStorageService {
private store = new Map<string, any>();
get<T>(key: string): Promise<T> {
@@ -18,7 +12,7 @@ export class MemoryStorageService
}
async has(key: string): Promise<boolean> {
return this.get(key) != null;
return (await this.get(key)) != null;
}
save(key: string, obj: any): Promise<any> {
@@ -33,4 +27,8 @@ export class MemoryStorageService
this.store.delete(key);
return Promise.resolve();
}
getBypassCache<T>(key: string): Promise<T> {
return this.get<T>(key);
}
}

View File

@@ -0,0 +1,321 @@
import { ApiService } from "../../abstractions/api.service";
import { OrganizationUserService } from "../../abstractions/organization-user/organization-user.service";
import {
OrganizationUserAcceptRequest,
OrganizationUserBulkConfirmRequest,
OrganizationUserConfirmRequest,
OrganizationUserInviteRequest,
OrganizationUserResetPasswordEnrollmentRequest,
OrganizationUserResetPasswordRequest,
OrganizationUserUpdateGroupsRequest,
OrganizationUserUpdateRequest,
} from "../../abstractions/organization-user/requests";
import {
OrganizationUserBulkPublicKeyResponse,
OrganizationUserBulkResponse,
OrganizationUserDetailsResponse,
OrganizationUserResetPasswordDetailsReponse,
OrganizationUserUserDetailsResponse,
} from "../../abstractions/organization-user/responses";
import { ListResponse } from "../../models/response/list.response";
import { OrganizationUserBulkRequest } from "./requests";
export class OrganizationUserServiceImplementation implements OrganizationUserService {
constructor(private apiService: ApiService) {}
async getOrganizationUser(
organizationId: string,
id: string,
options?: {
includeGroups?: boolean;
}
): Promise<OrganizationUserDetailsResponse> {
const params = new URLSearchParams();
if (options?.includeGroups) {
params.set("includeGroups", "true");
}
const r = await this.apiService.send(
"GET",
`/organizations/${organizationId}/users/${id}?${params.toString()}`,
null,
true,
true
);
return new OrganizationUserDetailsResponse(r);
}
async getOrganizationUserGroups(organizationId: string, id: string): Promise<string[]> {
const r = await this.apiService.send(
"GET",
"/organizations/" + organizationId + "/users/" + id + "/groups",
null,
true,
true
);
return r;
}
async getAllUsers(
organizationId: string,
options?: {
includeCollections?: boolean;
includeGroups?: boolean;
}
): Promise<ListResponse<OrganizationUserUserDetailsResponse>> {
const params = new URLSearchParams();
if (options?.includeCollections) {
params.set("includeCollections", "true");
}
if (options?.includeGroups) {
params.set("includeGroups", "true");
}
const r = await this.apiService.send(
"GET",
`/organizations/${organizationId}/users?${params.toString()}`,
null,
true,
true
);
return new ListResponse(r, OrganizationUserUserDetailsResponse);
}
async getOrganizationUserResetPasswordDetails(
organizationId: string,
id: string
): Promise<OrganizationUserResetPasswordDetailsReponse> {
const r = await this.apiService.send(
"GET",
"/organizations/" + organizationId + "/users/" + id + "/reset-password-details",
null,
true,
true
);
return new OrganizationUserResetPasswordDetailsReponse(r);
}
postOrganizationUserInvite(
organizationId: string,
request: OrganizationUserInviteRequest
): Promise<void> {
return this.apiService.send(
"POST",
"/organizations/" + organizationId + "/users/invite",
request,
true,
false
);
}
postOrganizationUserReinvite(organizationId: string, id: string): Promise<any> {
return this.apiService.send(
"POST",
"/organizations/" + organizationId + "/users/" + id + "/reinvite",
null,
true,
false
);
}
async postManyOrganizationUserReinvite(
organizationId: string,
ids: string[]
): Promise<ListResponse<OrganizationUserBulkResponse>> {
const r = await this.apiService.send(
"POST",
"/organizations/" + organizationId + "/users/reinvite",
new OrganizationUserBulkRequest(ids),
true,
true
);
return new ListResponse(r, OrganizationUserBulkResponse);
}
postOrganizationUserAccept(
organizationId: string,
id: string,
request: OrganizationUserAcceptRequest
): Promise<void> {
return this.apiService.send(
"POST",
"/organizations/" + organizationId + "/users/" + id + "/accept",
request,
true,
false
);
}
postOrganizationUserConfirm(
organizationId: string,
id: string,
request: OrganizationUserConfirmRequest
): Promise<void> {
return this.apiService.send(
"POST",
"/organizations/" + organizationId + "/users/" + id + "/confirm",
request,
true,
false
);
}
async postOrganizationUsersPublicKey(
organizationId: string,
ids: string[]
): Promise<ListResponse<OrganizationUserBulkPublicKeyResponse>> {
const r = await this.apiService.send(
"POST",
"/organizations/" + organizationId + "/users/public-keys",
new OrganizationUserBulkRequest(ids),
true,
true
);
return new ListResponse(r, OrganizationUserBulkPublicKeyResponse);
}
async postOrganizationUserBulkConfirm(
organizationId: string,
request: OrganizationUserBulkConfirmRequest
): Promise<ListResponse<OrganizationUserBulkResponse>> {
const r = await this.apiService.send(
"POST",
"/organizations/" + organizationId + "/users/confirm",
request,
true,
true
);
return new ListResponse(r, OrganizationUserBulkResponse);
}
putOrganizationUser(
organizationId: string,
id: string,
request: OrganizationUserUpdateRequest
): Promise<void> {
return this.apiService.send(
"PUT",
"/organizations/" + organizationId + "/users/" + id,
request,
true,
false
);
}
putOrganizationUserGroups(
organizationId: string,
id: string,
request: OrganizationUserUpdateGroupsRequest
): Promise<void> {
return this.apiService.send(
"PUT",
"/organizations/" + organizationId + "/users/" + id + "/groups",
request,
true,
false
);
}
putOrganizationUserResetPasswordEnrollment(
organizationId: string,
userId: string,
request: OrganizationUserResetPasswordEnrollmentRequest
): Promise<void> {
return this.apiService.send(
"PUT",
"/organizations/" + organizationId + "/users/" + userId + "/reset-password-enrollment",
request,
true,
false
);
}
putOrganizationUserResetPassword(
organizationId: string,
id: string,
request: OrganizationUserResetPasswordRequest
): Promise<void> {
return this.apiService.send(
"PUT",
"/organizations/" + organizationId + "/users/" + id + "/reset-password",
request,
true,
false
);
}
deleteOrganizationUser(organizationId: string, id: string): Promise<any> {
return this.apiService.send(
"DELETE",
"/organizations/" + organizationId + "/users/" + id,
null,
true,
false
);
}
async deleteManyOrganizationUsers(
organizationId: string,
ids: string[]
): Promise<ListResponse<OrganizationUserBulkResponse>> {
const r = await this.apiService.send(
"DELETE",
"/organizations/" + organizationId + "/users",
new OrganizationUserBulkRequest(ids),
true,
true
);
return new ListResponse(r, OrganizationUserBulkResponse);
}
revokeOrganizationUser(organizationId: string, id: string): Promise<void> {
return this.apiService.send(
"PUT",
"/organizations/" + organizationId + "/users/" + id + "/revoke",
null,
true,
false
);
}
async revokeManyOrganizationUsers(
organizationId: string,
ids: string[]
): Promise<ListResponse<OrganizationUserBulkResponse>> {
const r = await this.apiService.send(
"PUT",
"/organizations/" + organizationId + "/users/revoke",
new OrganizationUserBulkRequest(ids),
true,
true
);
return new ListResponse(r, OrganizationUserBulkResponse);
}
restoreOrganizationUser(organizationId: string, id: string): Promise<void> {
return this.apiService.send(
"PUT",
"/organizations/" + organizationId + "/users/" + id + "/restore",
null,
true,
false
);
}
async restoreManyOrganizationUsers(
organizationId: string,
ids: string[]
): Promise<ListResponse<OrganizationUserBulkResponse>> {
const r = await this.apiService.send(
"PUT",
"/organizations/" + organizationId + "/users/restore",
new OrganizationUserBulkRequest(ids),
true,
true
);
return new ListResponse(r, OrganizationUserBulkResponse);
}
}

View File

@@ -0,0 +1 @@
export * from "./organization-user-bulk.request";

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 {
private _organizations = new BehaviorSubject<Organization[]>([]);
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 }) {

Some files were not shown because too many files have changed in this diff Show More