mirror of
https://github.com/bitwarden/browser
synced 2025-12-18 01:03:35 +00:00
Merge branch 'master' into EC-598-beeep-properly-store-passkeys-in-bitwarden
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
|
||||
import { FolderView } from "@bitwarden/common/models/view/folder.view";
|
||||
|
||||
import { DynamicTreeNode } from "../vault/vault-filter/models/dynamic-tree-node.model";
|
||||
|
||||
/**
|
||||
* @deprecated August 30 2022: Use new VaultFilterService with observables
|
||||
*/
|
||||
export abstract class DeprecatedVaultFilterService {
|
||||
buildOrganizations: () => Promise<Organization[]>;
|
||||
buildNestedFolders: (organizationId?: string) => Observable<DynamicTreeNode<FolderView>>;
|
||||
buildCollections: (organizationId?: string) => Promise<DynamicTreeNode<CollectionView>>;
|
||||
buildCollapsedFilterNodes: () => Promise<Set<string>>;
|
||||
storeCollapsedFilterNodes: (collapsedFilterNodes: Set<string>) => Promise<void>;
|
||||
checkForSingleOrganizationPolicy: () => Promise<boolean>;
|
||||
checkForPersonalOwnershipPolicy: () => Promise<boolean>;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CdkDragDrop, moveItemInArray } from "@angular/cdk/drag-drop";
|
||||
import { Directive, Input, OnChanges, SimpleChanges } from "@angular/core";
|
||||
|
||||
import { EventService } from "@bitwarden/common/abstractions/event.service";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { CipherType } from "@bitwarden/common/enums/cipherType";
|
||||
import { EventType } from "@bitwarden/common/enums/eventType";
|
||||
@@ -25,7 +25,10 @@ export class AddEditCustomFieldsComponent implements OnChanges {
|
||||
fieldType = FieldType;
|
||||
eventType = EventType;
|
||||
|
||||
constructor(private i18nService: I18nService, private eventService: EventService) {
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
private eventCollectionService: EventCollectionService
|
||||
) {
|
||||
this.addFieldTypeOptions = [
|
||||
{ name: i18nService.t("cfTypeText"), value: FieldType.Text },
|
||||
{ name: i18nService.t("cfTypeHidden"), value: FieldType.Hidden },
|
||||
@@ -74,7 +77,10 @@ export class AddEditCustomFieldsComponent implements OnChanges {
|
||||
const f = field as any;
|
||||
f.showValue = !f.showValue;
|
||||
if (this.editMode && f.showValue) {
|
||||
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipher.id);
|
||||
this.eventCollectionService.collect(
|
||||
EventType.Cipher_ClientToggledHiddenFieldVisible,
|
||||
this.cipher.id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,15 @@ import { Observable, Subject, takeUntil, concatMap } from "rxjs";
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
||||
import { EventService } from "@bitwarden/common/abstractions/event.service";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import {
|
||||
isNotProviderUser,
|
||||
OrganizationService,
|
||||
} from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||
@@ -23,6 +26,7 @@ import { SecureNoteType } from "@bitwarden/common/enums/secureNoteType";
|
||||
import { UriMatchType } from "@bitwarden/common/enums/uriMatchType";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { Cipher } from "@bitwarden/common/models/domain/cipher";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
import { CardView } from "@bitwarden/common/models/view/card.view";
|
||||
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
|
||||
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
|
||||
@@ -74,7 +78,9 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
allowPersonal = true;
|
||||
reprompt = false;
|
||||
canUseReprompt = true;
|
||||
organization: Organization;
|
||||
|
||||
protected componentName = "";
|
||||
protected destroy$ = new Subject<void>();
|
||||
protected writeableCollections: CollectionView[];
|
||||
private personalOwnershipPolicyAppliesToActiveUser: boolean;
|
||||
@@ -89,7 +95,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
protected stateService: StateService,
|
||||
protected collectionService: CollectionService,
|
||||
protected messagingService: MessagingService,
|
||||
protected eventService: EventService,
|
||||
protected eventCollectionService: EventCollectionService,
|
||||
protected policyService: PolicyService,
|
||||
private logService: LogService,
|
||||
protected passwordRepromptService: PasswordRepromptService,
|
||||
@@ -135,6 +141,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
{ name: i18nService.t("mr"), value: i18nService.t("mr") },
|
||||
{ name: i18nService.t("mrs"), value: i18nService.t("mrs") },
|
||||
{ name: i18nService.t("ms"), value: i18nService.t("ms") },
|
||||
{ name: i18nService.t("mx"), value: i18nService.t("mx") },
|
||||
{ name: i18nService.t("dr"), value: i18nService.t("dr") },
|
||||
];
|
||||
this.uriMatchOptions = [
|
||||
@@ -183,11 +190,14 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
const orgs = await this.organizationService.getAll();
|
||||
orgs.sort(Utils.getSortFunction(this.i18nService, "name")).forEach((o) => {
|
||||
if (o.enabled && o.status === OrganizationUserStatusType.Confirmed) {
|
||||
this.ownershipOptions.push({ name: o.name, value: o.id });
|
||||
}
|
||||
});
|
||||
orgs
|
||||
.filter(isNotProviderUser)
|
||||
.sort(Utils.getSortFunction(this.i18nService, "name"))
|
||||
.forEach((o) => {
|
||||
if (o.enabled && o.status === OrganizationUserStatusType.Confirmed) {
|
||||
this.ownershipOptions.push({ name: o.name, value: o.id });
|
||||
}
|
||||
});
|
||||
if (!this.allowPersonal) {
|
||||
this.organizationId = this.ownershipOptions[0].value;
|
||||
}
|
||||
@@ -264,7 +274,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
this.folders$ = this.folderService.folderViews$;
|
||||
|
||||
if (this.editMode && this.previousCipherId !== this.cipherId) {
|
||||
this.eventService.collect(EventType.Cipher_ClientViewed, this.cipherId);
|
||||
this.eventCollectionService.collect(EventType.Cipher_ClientViewed, this.cipherId);
|
||||
}
|
||||
this.previousCipherId = this.cipherId;
|
||||
this.reprompt = this.cipher.reprompt !== CipherRepromptType.None;
|
||||
@@ -395,7 +405,9 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
this.i18nService.t("deleteItem"),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
"warning",
|
||||
false,
|
||||
this.componentName != "" ? this.componentName + " .modal-content" : null
|
||||
);
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
@@ -487,14 +499,20 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
this.showPassword = !this.showPassword;
|
||||
document.getElementById("loginPassword").focus();
|
||||
if (this.editMode && this.showPassword) {
|
||||
this.eventService.collect(EventType.Cipher_ClientToggledPasswordVisible, this.cipherId);
|
||||
this.eventCollectionService.collect(
|
||||
EventType.Cipher_ClientToggledPasswordVisible,
|
||||
this.cipherId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async toggleCardNumber() {
|
||||
this.showCardNumber = !this.showCardNumber;
|
||||
if (this.showCardNumber) {
|
||||
this.eventService.collect(EventType.Cipher_ClientToggledCardNumberVisible, this.cipherId);
|
||||
this.eventCollectionService.collect(
|
||||
EventType.Cipher_ClientToggledCardNumberVisible,
|
||||
this.cipherId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,7 +520,10 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
this.showCardCode = !this.showCardCode;
|
||||
document.getElementById("cardCode").focus();
|
||||
if (this.editMode && this.showCardCode) {
|
||||
this.eventService.collect(EventType.Cipher_ClientToggledCardCodeVisible, this.cipherId);
|
||||
this.eventCollectionService.collect(
|
||||
EventType.Cipher_ClientToggledCardCodeVisible,
|
||||
this.cipherId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ export class AttachmentsComponent implements OnInit {
|
||||
deletePromises: { [id: string]: Promise<any> } = {};
|
||||
reuploadPromises: { [id: string]: Promise<any> } = {};
|
||||
emergencyAccessId?: string = null;
|
||||
protected componentName = "";
|
||||
|
||||
constructor(
|
||||
protected cipherService: CipherService,
|
||||
@@ -104,7 +105,9 @@ export class AttachmentsComponent implements OnInit {
|
||||
this.i18nService.t("deleteAttachment"),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
"warning",
|
||||
false,
|
||||
this.componentName != "" ? this.componentName + " .modal-content" : null
|
||||
);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
@@ -292,4 +295,8 @@ export class AttachmentsComponent implements OnInit {
|
||||
protected deleteCipherAttachment(attachmentId: string) {
|
||||
return this.cipherService.deleteAttachmentWithServer(this.cipher.id, attachmentId);
|
||||
}
|
||||
|
||||
protected async reupload(attachment: AttachmentView) {
|
||||
// TODO: This should be removed but is needed since we re-use the same template
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { UntypedFormBuilder, Validators } from "@angular/forms";
|
||||
import { merge, takeUntil, Subject, startWith } from "rxjs";
|
||||
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { EventService } from "@bitwarden/common/abstractions/event.service";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { ExportService } from "@bitwarden/common/abstractions/export.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
@@ -21,8 +21,6 @@ export class ExportComponent implements OnInit, OnDestroy {
|
||||
|
||||
formPromise: Promise<string>;
|
||||
disabledByPolicy = false;
|
||||
showFilePassword: boolean;
|
||||
showConfirmFilePassword: boolean;
|
||||
|
||||
exportForm = this.formBuilder.group({
|
||||
format: ["json"],
|
||||
@@ -45,7 +43,7 @@ export class ExportComponent implements OnInit, OnDestroy {
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected exportService: ExportService,
|
||||
protected eventService: EventService,
|
||||
protected eventCollectionService: EventCollectionService,
|
||||
private policyService: PolicyService,
|
||||
protected win: Window,
|
||||
private logService: LogService,
|
||||
@@ -180,7 +178,7 @@ export class ExportComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
protected async collectEvent(): Promise<void> {
|
||||
await this.eventService.collect(EventType.User_ClientExportedVault);
|
||||
await this.eventCollectionService.collect(EventType.User_ClientExportedVault);
|
||||
}
|
||||
|
||||
get format() {
|
||||
@@ -199,16 +197,6 @@ export class ExportComponent implements OnInit, OnDestroy {
|
||||
return this.exportForm.get("fileEncryptionType").value;
|
||||
}
|
||||
|
||||
toggleFilePassword() {
|
||||
this.showFilePassword = !this.showFilePassword;
|
||||
document.getElementById("filePassword").focus();
|
||||
}
|
||||
|
||||
toggleConfirmFilePassword() {
|
||||
this.showConfirmFilePassword = !this.showConfirmFilePassword;
|
||||
document.getElementById("confirmFilePassword").focus();
|
||||
}
|
||||
|
||||
adjustValidators() {
|
||||
this.exportForm.get("confirmFilePassword").reset();
|
||||
this.exportForm.get("filePassword").reset();
|
||||
|
||||
@@ -18,6 +18,7 @@ export class FolderAddEditComponent implements OnInit {
|
||||
title: string;
|
||||
formPromise: Promise<any>;
|
||||
deletePromise: Promise<any>;
|
||||
protected componentName = "";
|
||||
|
||||
constructor(
|
||||
protected folderService: FolderService,
|
||||
@@ -65,7 +66,9 @@ export class FolderAddEditComponent implements OnInit {
|
||||
this.i18nService.t("deleteFolder"),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
"warning",
|
||||
false,
|
||||
this.componentName != "" ? this.componentName + " .modal-content" : null
|
||||
);
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
|
||||
@@ -70,13 +70,7 @@ export class GeneratorComponent implements OnInit {
|
||||
];
|
||||
this.subaddressOptions = [{ name: i18nService.t("random"), value: "random" }];
|
||||
this.catchallOptions = [{ name: i18nService.t("random"), value: "random" }];
|
||||
this.forwardOptions = [
|
||||
{ name: "SimpleLogin", value: "simplelogin" },
|
||||
{ name: "AnonAddy", value: "anonaddy" },
|
||||
{ name: "Firefox Relay", value: "firefoxrelay" },
|
||||
{ name: "Fastmail", value: "fastmail" },
|
||||
{ name: "DuckDuckGo", value: "duckduckgo" },
|
||||
];
|
||||
this.initForwardOptions();
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -239,4 +233,24 @@ export class GeneratorComponent implements OnInit {
|
||||
this.enforcedPasswordPolicyOptions
|
||||
);
|
||||
}
|
||||
|
||||
private async initForwardOptions() {
|
||||
this.forwardOptions = [
|
||||
{ name: "AnonAddy", value: "anonaddy" },
|
||||
{ name: "DuckDuckGo", value: "duckduckgo" },
|
||||
{ name: "Fastmail", value: "fastmail" },
|
||||
{ name: "Firefox Relay", value: "firefoxrelay" },
|
||||
{ name: "SimpleLogin", value: "simplelogin" },
|
||||
];
|
||||
|
||||
this.usernameOptions = await this.usernameGenerationService.getOptions();
|
||||
if (
|
||||
this.usernameOptions.forwardedService == null ||
|
||||
this.usernameOptions.forwardedService === ""
|
||||
) {
|
||||
this.forwardOptions.push({ name: "", value: null });
|
||||
}
|
||||
|
||||
this.forwardOptions = this.forwardOptions.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
decoding="async"
|
||||
loading="lazy"
|
||||
/>
|
||||
<i class="bwi bwi-fw bwi-lg {{ icon }}" *ngIf="!imageEnabled || !image"></i>
|
||||
<i class="tw-text-muted bwi bwi-lg {{ icon }}" *ngIf="!imageEnabled || !image"></i>
|
||||
</div>
|
||||
|
||||
@@ -46,8 +46,6 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
|
||||
protected twoFactorRoute = "2fa";
|
||||
protected successRoute = "vault";
|
||||
protected forcePasswordResetRoute = "update-temp-password";
|
||||
protected alwaysRememberEmail = false;
|
||||
protected skipRememberEmail = false;
|
||||
|
||||
get loggedEmail() {
|
||||
return this.formGroup.value.email;
|
||||
@@ -85,6 +83,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
|
||||
const queryParamsEmail = params["email"];
|
||||
if (queryParamsEmail != null && queryParamsEmail.indexOf("@") > -1) {
|
||||
this.formGroup.get("email").setValue(queryParamsEmail);
|
||||
this.loginService.setEmail(queryParamsEmail);
|
||||
this.paramEmailSet = true;
|
||||
}
|
||||
}
|
||||
@@ -98,17 +97,11 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
|
||||
if (!this.paramEmailSet) {
|
||||
this.formGroup.get("email")?.setValue(email ?? "");
|
||||
}
|
||||
if (!this.alwaysRememberEmail) {
|
||||
let rememberEmail = this.loginService.getRememberEmail();
|
||||
if (rememberEmail == null) {
|
||||
rememberEmail = (await this.stateService.getRememberedEmail()) != null;
|
||||
}
|
||||
this.formGroup.get("rememberEmail")?.setValue(rememberEmail);
|
||||
}
|
||||
|
||||
if (email) {
|
||||
this.validateEmail();
|
||||
let rememberEmail = this.loginService.getRememberEmail();
|
||||
if (rememberEmail == null) {
|
||||
rememberEmail = (await this.stateService.getRememberedEmail()) != null;
|
||||
}
|
||||
this.formGroup.get("rememberEmail")?.setValue(rememberEmail);
|
||||
}
|
||||
|
||||
async submit(showToast = true) {
|
||||
@@ -140,11 +133,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
|
||||
this.formPromise = this.authService.logIn(credentials);
|
||||
const response = await this.formPromise;
|
||||
this.setFormValues();
|
||||
if (data.rememberEmail || this.alwaysRememberEmail) {
|
||||
await this.stateService.setRememberedEmail(data.email);
|
||||
} else {
|
||||
await this.stateService.setRememberedEmail(null);
|
||||
}
|
||||
await this.loginService.saveEmailSettings();
|
||||
if (this.handleCaptchaRequired(response)) {
|
||||
return;
|
||||
} else if (response.requiresTwoFactor) {
|
||||
@@ -162,7 +151,6 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
|
||||
} else {
|
||||
const disableFavicon = await this.stateService.getDisableFavicon();
|
||||
await this.stateService.setDisableFavicon(!!disableFavicon);
|
||||
this.loginService.clearValues();
|
||||
if (this.onSuccessfulLogin != null) {
|
||||
this.onSuccessfulLogin();
|
||||
}
|
||||
@@ -189,6 +177,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
|
||||
}
|
||||
|
||||
async launchSsoBrowser(clientId: string, ssoRedirectUri: string) {
|
||||
await this.saveEmailSettings();
|
||||
// Generate necessary sso params
|
||||
const passwordOptions: any = {
|
||||
type: "password",
|
||||
@@ -243,6 +232,11 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
|
||||
this.loginService.setRememberEmail(this.formGroup.value.rememberEmail);
|
||||
}
|
||||
|
||||
async saveEmailSettings() {
|
||||
this.setFormValues();
|
||||
await this.loginService.saveEmailSettings();
|
||||
}
|
||||
|
||||
private getErrorToastMessage() {
|
||||
const error: AllValidationErrors = this.formValidationErrorService
|
||||
.getFormValidationErrors(this.formGroup.controls)
|
||||
|
||||
@@ -3,6 +3,7 @@ import { AbstractControl, UntypedFormBuilder, ValidatorFn, Validators } from "@a
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
@@ -38,6 +39,8 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
|
||||
showTerms = true;
|
||||
showErrorSummary = false;
|
||||
passwordStrengthResult: any;
|
||||
characterMinimumMessage: string;
|
||||
minimumLength = 8;
|
||||
color: string;
|
||||
text: string;
|
||||
|
||||
@@ -45,8 +48,8 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
|
||||
{
|
||||
email: ["", [Validators.required, Validators.email]],
|
||||
name: [""],
|
||||
masterPassword: ["", [Validators.required, Validators.minLength(8)]],
|
||||
confirmMasterPassword: ["", [Validators.required, Validators.minLength(8)]],
|
||||
masterPassword: ["", [Validators.required, Validators.minLength(this.minimumLength)]],
|
||||
confirmMasterPassword: ["", [Validators.required, Validators.minLength(this.minimumLength)]],
|
||||
hint: [
|
||||
null,
|
||||
[
|
||||
@@ -56,6 +59,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
|
||||
),
|
||||
],
|
||||
],
|
||||
checkForBreaches: [false],
|
||||
acceptPolicies: [false, [this.acceptPoliciesValidation()]],
|
||||
},
|
||||
{
|
||||
@@ -85,10 +89,12 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
protected passwordGenerationService: PasswordGenerationService,
|
||||
environmentService: EnvironmentService,
|
||||
protected logService: LogService
|
||||
protected logService: LogService,
|
||||
protected auditService: AuditService
|
||||
) {
|
||||
super(environmentService, i18nService, platformUtilsService);
|
||||
this.showTerms = !platformUtilsService.isSelfHost();
|
||||
this.characterMinimumMessage = this.i18nService.t("characterMinimum", this.minimumLength);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -212,7 +218,24 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
|
||||
return { isValid: false };
|
||||
}
|
||||
|
||||
if (this.passwordStrengthResult != null && this.passwordStrengthResult.score < 3) {
|
||||
const passwordWeak =
|
||||
this.passwordStrengthResult != null && this.passwordStrengthResult.score < 3;
|
||||
const passwordLeak =
|
||||
this.formGroup.controls.checkForBreaches.value &&
|
||||
(await this.auditService.passwordLeaked(this.formGroup.controls.masterPassword.value)) > 0;
|
||||
|
||||
if (passwordWeak && passwordLeak) {
|
||||
const result = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("weakAndBreachedMasterPasswordDesc"),
|
||||
this.i18nService.t("weakAndExposedMasterPassword"),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
);
|
||||
if (!result) {
|
||||
return { isValid: false };
|
||||
}
|
||||
} else if (passwordWeak) {
|
||||
const result = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("weakMasterPasswordDesc"),
|
||||
this.i18nService.t("weakMasterPassword"),
|
||||
@@ -223,7 +246,19 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
|
||||
if (!result) {
|
||||
return { isValid: false };
|
||||
}
|
||||
} else if (passwordLeak) {
|
||||
const result = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("exposedMasterPasswordDesc"),
|
||||
this.i18nService.t("exposedMasterPassword"),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
);
|
||||
if (!result) {
|
||||
return { isValid: false };
|
||||
}
|
||||
}
|
||||
|
||||
return { isValid: true };
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
alertShown = false;
|
||||
showOptions = false;
|
||||
|
||||
protected componentName = "";
|
||||
private sendLinkBaseUrl: string;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
@@ -200,25 +201,14 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
this.onSavedSend.emit(this.send);
|
||||
if (this.copyLink && this.link != null) {
|
||||
const copySuccess = await this.copyLinkToClipboard(this.link);
|
||||
if (copySuccess ?? true) {
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t(this.editMode ? "editedSend" : "createdSend")
|
||||
);
|
||||
} else {
|
||||
await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t(this.editMode ? "editedSend" : "createdSend"),
|
||||
null,
|
||||
this.i18nService.t("ok"),
|
||||
null,
|
||||
"success",
|
||||
null
|
||||
);
|
||||
await this.copyLinkToClipboard(this.link);
|
||||
}
|
||||
await this.handleCopyLinkToClipboard();
|
||||
return;
|
||||
}
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t(this.editMode ? "editedSend" : "createdSend")
|
||||
);
|
||||
});
|
||||
try {
|
||||
await this.formPromise;
|
||||
@@ -242,7 +232,9 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
this.i18nService.t("deleteSend"),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
"warning",
|
||||
false,
|
||||
this.componentName != "" ? this.componentName + " .modal-content" : null
|
||||
);
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
@@ -305,4 +297,24 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
this.showPassword = !this.showPassword;
|
||||
document.getElementById("password").focus();
|
||||
}
|
||||
private async handleCopyLinkToClipboard() {
|
||||
const copySuccess = await this.copyLinkToClipboard(this.link);
|
||||
if (copySuccess ?? true) {
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t(this.editMode ? "editedSend" : "createdSend")
|
||||
);
|
||||
} else {
|
||||
await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t(this.editMode ? "editedSend" : "createdSend"),
|
||||
null,
|
||||
this.i18nService.t("ok"),
|
||||
null,
|
||||
"success",
|
||||
null
|
||||
);
|
||||
await this.copyLinkToClipboard(this.link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
|
||||
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/abstractions/organization-user/requests";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
@@ -19,7 +21,6 @@ import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { EncString } from "@bitwarden/common/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
|
||||
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
|
||||
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/models/request/organization-user-reset-password-enrollment.request";
|
||||
import { SetPasswordRequest } from "@bitwarden/common/models/request/set-password.request";
|
||||
|
||||
import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component";
|
||||
@@ -49,7 +50,8 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
|
||||
private syncService: SyncService,
|
||||
private route: ActivatedRoute,
|
||||
stateService: StateService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private organizationUserService: OrganizationUserService
|
||||
) {
|
||||
super(
|
||||
i18nService,
|
||||
@@ -136,7 +138,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
|
||||
resetRequest.masterPasswordHash = masterPasswordHash;
|
||||
resetRequest.resetPasswordKey = encryptedKey.encryptedString;
|
||||
|
||||
return this.apiService.putOrganizationUserResetPasswordEnrollment(
|
||||
return this.organizationUserService.putOrganizationUserResetPasswordEnrollment(
|
||||
this.orgId,
|
||||
userId,
|
||||
resetRequest
|
||||
|
||||
@@ -5,7 +5,10 @@ import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import {
|
||||
isNotProviderUser,
|
||||
OrganizationService,
|
||||
} from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
@@ -54,7 +57,10 @@ export class ShareComponent implements OnInit, OnDestroy {
|
||||
this.organizations$ = this.organizationService.organizations$.pipe(
|
||||
map((orgs) => {
|
||||
return orgs
|
||||
.filter((o) => o.enabled && o.status === OrganizationUserStatusType.Confirmed)
|
||||
.filter(
|
||||
(o) =>
|
||||
o.enabled && o.status === OrganizationUserStatusType.Confirmed && isNotProviderUser(o)
|
||||
)
|
||||
.sort(Utils.getSortFunction(this.i18nService, "name"));
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
<ng-container *ngIf="!usesKeyConnector">
|
||||
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||
<input
|
||||
id="masterPassword"
|
||||
type="password"
|
||||
name="MasterPasswordHash"
|
||||
class="form-control"
|
||||
[formControl]="secret"
|
||||
required
|
||||
appAutofocus
|
||||
appInputVerbatim
|
||||
/>
|
||||
<small class="form-text text-muted">{{ "confirmIdentity" | i18n }}</small>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="usesKeyConnector">
|
||||
<div class="form-group">
|
||||
<label class="d-block">{{ "sendVerificationCode" | i18n }}</label>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
(click)="requestOTP()"
|
||||
[disabled]="disableRequestOTP"
|
||||
>
|
||||
{{ "sendCode" | i18n }}
|
||||
</button>
|
||||
<span class="ml-2 text-success" role="alert" @sent *ngIf="sentCode">
|
||||
<i class="bwi bwi-check-circle" aria-hidden="true"></i>
|
||||
{{ "codeSent" | i18n }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="verificationCode">{{ "verificationCode" | i18n }}</label>
|
||||
<input
|
||||
id="verificationCode"
|
||||
type="input"
|
||||
name="verificationCode"
|
||||
class="form-control"
|
||||
[formControl]="secret"
|
||||
required
|
||||
appAutofocus
|
||||
appInputVerbatim
|
||||
/>
|
||||
<small class="form-text text-muted">{{ "confirmIdentity" | i18n }}</small>
|
||||
</div>
|
||||
</ng-container>
|
||||
@@ -1,6 +1,5 @@
|
||||
import { animate, style, transition, trigger } from "@angular/animations";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl } from "@angular/forms";
|
||||
import { Directive, OnInit } from "@angular/core";
|
||||
import { ControlValueAccessor, FormControl } from "@angular/forms";
|
||||
|
||||
import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
@@ -14,21 +13,8 @@ import { Verification } from "@bitwarden/common/types/verification";
|
||||
* This is exposed to the parent component via the ControlValueAccessor interface (e.g. bind it to a FormControl).
|
||||
* Use UserVerificationService to verify the user's input.
|
||||
*/
|
||||
@Component({
|
||||
@Directive({
|
||||
selector: "app-user-verification",
|
||||
templateUrl: "user-verification.component.html",
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
multi: true,
|
||||
useExisting: UserVerificationComponent,
|
||||
},
|
||||
],
|
||||
animations: [
|
||||
trigger("sent", [
|
||||
transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]),
|
||||
]),
|
||||
],
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class UserVerificationComponent implements ControlValueAccessor, OnInit {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Directive, EventEmitter, Input, Output } from "@angular/core";
|
||||
|
||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
|
||||
|
||||
@Directive()
|
||||
export class CiphersComponent {
|
||||
export class VaultItemsComponent {
|
||||
@Input() activeCipherId: string = null;
|
||||
@Output() onCipherClicked = new EventEmitter<CipherView>();
|
||||
@Output() onCipherRightClicked = new EventEmitter<CipherView>();
|
||||
@@ -13,19 +14,27 @@ export class CiphersComponent {
|
||||
|
||||
loaded = false;
|
||||
ciphers: CipherView[] = [];
|
||||
searchText: string;
|
||||
searchPlaceholder: string = null;
|
||||
filter: (cipher: CipherView) => boolean = null;
|
||||
deleted = false;
|
||||
organization: Organization;
|
||||
accessEvents = false;
|
||||
|
||||
protected searchPending = false;
|
||||
|
||||
private searchTimeout: any = null;
|
||||
private _searchText: string = null;
|
||||
get searchText() {
|
||||
return this._searchText;
|
||||
}
|
||||
set searchText(value: string) {
|
||||
this._searchText = value;
|
||||
}
|
||||
|
||||
constructor(protected searchService: SearchService) {}
|
||||
|
||||
async load(filter: (cipher: CipherView) => boolean = null, deleted = false) {
|
||||
this.deleted = deleted || false;
|
||||
this.deleted = deleted ?? false;
|
||||
await this.applyFilter(filter);
|
||||
this.loaded = true;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Directive, Input } from "@angular/core";
|
||||
|
||||
import { EventService } from "@bitwarden/common/abstractions/event.service";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { EventType } from "@bitwarden/common/enums/eventType";
|
||||
import { FieldType } from "@bitwarden/common/enums/fieldType";
|
||||
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
|
||||
@@ -14,7 +14,7 @@ export class ViewCustomFieldsComponent {
|
||||
|
||||
fieldType = FieldType;
|
||||
|
||||
constructor(private eventService: EventService) {}
|
||||
constructor(private eventCollectionService: EventCollectionService) {}
|
||||
|
||||
async toggleFieldValue(field: FieldView) {
|
||||
if (!(await this.promptPassword())) {
|
||||
@@ -25,7 +25,10 @@ export class ViewCustomFieldsComponent {
|
||||
f.showValue = !f.showValue;
|
||||
f.showCount = false;
|
||||
if (f.showValue) {
|
||||
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipher.id);
|
||||
this.eventCollectionService.collect(
|
||||
EventType.Cipher_ClientToggledHiddenFieldVisible,
|
||||
this.cipher.id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service";
|
||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { EventService } from "@bitwarden/common/abstractions/event.service";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
@@ -80,7 +80,7 @@ export class ViewComponent implements OnDestroy, OnInit {
|
||||
protected broadcasterService: BroadcasterService,
|
||||
protected ngZone: NgZone,
|
||||
protected changeDetectorRef: ChangeDetectorRef,
|
||||
protected eventService: EventService,
|
||||
protected eventCollectionService: EventCollectionService,
|
||||
protected apiService: ApiService,
|
||||
protected passwordRepromptService: PasswordRepromptService,
|
||||
private logService: LogService,
|
||||
@@ -138,7 +138,7 @@ export class ViewComponent implements OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
if (this.previousCipherId !== this.cipherId) {
|
||||
this.eventService.collect(EventType.Cipher_ClientViewed, this.cipherId);
|
||||
this.eventCollectionService.collect(EventType.Cipher_ClientViewed, this.cipherId);
|
||||
}
|
||||
this.previousCipherId = this.cipherId;
|
||||
}
|
||||
@@ -238,7 +238,10 @@ export class ViewComponent implements OnDestroy, OnInit {
|
||||
this.showPassword = !this.showPassword;
|
||||
this.showPasswordCount = false;
|
||||
if (this.showPassword) {
|
||||
this.eventService.collect(EventType.Cipher_ClientToggledPasswordVisible, this.cipherId);
|
||||
this.eventCollectionService.collect(
|
||||
EventType.Cipher_ClientToggledPasswordVisible,
|
||||
this.cipherId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,7 +260,10 @@ export class ViewComponent implements OnDestroy, OnInit {
|
||||
|
||||
this.showCardNumber = !this.showCardNumber;
|
||||
if (this.showCardNumber) {
|
||||
this.eventService.collect(EventType.Cipher_ClientToggledCardNumberVisible, this.cipherId);
|
||||
this.eventCollectionService.collect(
|
||||
EventType.Cipher_ClientToggledCardNumberVisible,
|
||||
this.cipherId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,7 +274,10 @@ export class ViewComponent implements OnDestroy, OnInit {
|
||||
|
||||
this.showCardCode = !this.showCardCode;
|
||||
if (this.showCardCode) {
|
||||
this.eventService.collect(EventType.Cipher_ClientToggledCardCodeVisible, this.cipherId);
|
||||
this.eventCollectionService.collect(
|
||||
EventType.Cipher_ClientToggledCardCodeVisible,
|
||||
this.cipherId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,11 +337,14 @@ export class ViewComponent implements OnDestroy, OnInit {
|
||||
);
|
||||
|
||||
if (typeI18nKey === "password") {
|
||||
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipherId);
|
||||
this.eventCollectionService.collect(
|
||||
EventType.Cipher_ClientToggledHiddenFieldVisible,
|
||||
this.cipherId
|
||||
);
|
||||
} else if (typeI18nKey === "securityCode") {
|
||||
this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, this.cipherId);
|
||||
this.eventCollectionService.collect(EventType.Cipher_ClientCopiedCardCode, this.cipherId);
|
||||
} else if (aType === "H_Field") {
|
||||
this.eventService.collect(EventType.Cipher_ClientCopiedHiddenField, this.cipherId);
|
||||
this.eventCollectionService.collect(EventType.Cipher_ClientCopiedHiddenField, this.cipherId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";
|
||||
],
|
||||
})
|
||||
export class TrueFalseValueDirective implements ControlValueAccessor {
|
||||
@Input() trueValue = true;
|
||||
@Input() falseValue = false;
|
||||
@Input() trueValue: boolean | string = true;
|
||||
@Input() falseValue: boolean | string = false;
|
||||
|
||||
constructor(private elementRef: ElementRef, private renderer: Renderer2) {}
|
||||
|
||||
|
||||
@@ -21,14 +21,13 @@ import { SelectCopyDirective } from "./directives/select-copy.directive";
|
||||
import { StopClickDirective } from "./directives/stop-click.directive";
|
||||
import { StopPropDirective } from "./directives/stop-prop.directive";
|
||||
import { TrueFalseValueDirective } from "./directives/true-false-value.directive";
|
||||
import { ColorPasswordCountPipe } from "./pipes/color-password-count.pipe";
|
||||
import { ColorPasswordPipe } from "./pipes/color-password.pipe";
|
||||
import { CreditCardNumberPipe } from "./pipes/credit-card-number.pipe";
|
||||
import { EllipsisPipe } from "./pipes/ellipsis.pipe";
|
||||
import { I18nPipe } from "./pipes/i18n.pipe";
|
||||
import { SearchCiphersPipe } from "./pipes/search-ciphers.pipe";
|
||||
import { SearchPipe } from "./pipes/search.pipe";
|
||||
import { UserNamePipe } from "./pipes/user-name.pipe";
|
||||
import { UserTypePipe } from "./pipes/user-type.pipe";
|
||||
import { PasswordStrengthComponent } from "./shared/components/password-strength/password-strength.component";
|
||||
|
||||
@NgModule({
|
||||
@@ -49,8 +48,6 @@ import { PasswordStrengthComponent } from "./shared/components/password-strength
|
||||
AutofocusDirective,
|
||||
BoxRowDirective,
|
||||
CalloutComponent,
|
||||
ColorPasswordCountPipe,
|
||||
ColorPasswordPipe,
|
||||
CreditCardNumberPipe,
|
||||
EllipsisPipe,
|
||||
ExportScopeCalloutComponent,
|
||||
@@ -70,6 +67,7 @@ import { PasswordStrengthComponent } from "./shared/components/password-strength
|
||||
LaunchClickDirective,
|
||||
UserNamePipe,
|
||||
PasswordStrengthComponent,
|
||||
UserTypePipe,
|
||||
],
|
||||
exports: [
|
||||
A11yInvalidDirective,
|
||||
@@ -79,8 +77,6 @@ import { PasswordStrengthComponent } from "./shared/components/password-strength
|
||||
BitwardenToastModule,
|
||||
BoxRowDirective,
|
||||
CalloutComponent,
|
||||
ColorPasswordCountPipe,
|
||||
ColorPasswordPipe,
|
||||
CreditCardNumberPipe,
|
||||
EllipsisPipe,
|
||||
ExportScopeCalloutComponent,
|
||||
@@ -100,7 +96,8 @@ import { PasswordStrengthComponent } from "./shared/components/password-strength
|
||||
LaunchClickDirective,
|
||||
UserNamePipe,
|
||||
PasswordStrengthComponent,
|
||||
UserTypePipe,
|
||||
],
|
||||
providers: [CreditCardNumberPipe, DatePipe, I18nPipe, SearchPipe, UserNamePipe],
|
||||
providers: [CreditCardNumberPipe, DatePipe, I18nPipe, SearchPipe, UserNamePipe, UserTypePipe],
|
||||
})
|
||||
export class JslibModule {}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
export class I18nPipe implements PipeTransform {
|
||||
constructor(private i18nService: I18nService) {}
|
||||
|
||||
transform(id: string, p1?: string, p2?: string, p3?: string): string {
|
||||
transform(id: string, p1?: string | number, p2?: string | number, p3?: string | number): string {
|
||||
return this.i18nService.t(id, p1, p2, p3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,32 @@
|
||||
import { Pipe, PipeTransform } from "@angular/core";
|
||||
|
||||
type PropertyValueFunction<T> = (item: T) => { toString: () => string };
|
||||
|
||||
@Pipe({
|
||||
name: "search",
|
||||
})
|
||||
export class SearchPipe implements PipeTransform {
|
||||
transform(
|
||||
items: any[],
|
||||
transform<T>(
|
||||
items: T[],
|
||||
searchText: string,
|
||||
prop1?: string,
|
||||
prop2?: string,
|
||||
prop3?: string
|
||||
): any[] {
|
||||
prop1?: keyof T,
|
||||
prop2?: keyof T,
|
||||
prop3?: keyof T
|
||||
): T[];
|
||||
transform<T>(
|
||||
items: T[],
|
||||
searchText: string,
|
||||
prop1?: PropertyValueFunction<T>,
|
||||
prop2?: PropertyValueFunction<T>,
|
||||
prop3?: PropertyValueFunction<T>
|
||||
): T[];
|
||||
transform<T>(
|
||||
items: T[],
|
||||
searchText: string,
|
||||
prop1?: keyof T | PropertyValueFunction<T>,
|
||||
prop2?: keyof T | PropertyValueFunction<T>,
|
||||
prop3?: keyof T | PropertyValueFunction<T>
|
||||
): T[] {
|
||||
if (items == null || items.length === 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -21,27 +37,30 @@ export class SearchPipe implements PipeTransform {
|
||||
|
||||
searchText = searchText.trim().toLowerCase();
|
||||
return items.filter((i) => {
|
||||
if (
|
||||
prop1 != null &&
|
||||
i[prop1] != null &&
|
||||
i[prop1].toString().toLowerCase().indexOf(searchText) > -1
|
||||
) {
|
||||
return true;
|
||||
if (prop1 != null) {
|
||||
const propValue = typeof prop1 === "function" ? prop1(i) : i[prop1];
|
||||
|
||||
if (propValue?.toString().toLowerCase().indexOf(searchText) > -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (
|
||||
prop2 != null &&
|
||||
i[prop2] != null &&
|
||||
i[prop2].toString().toLowerCase().indexOf(searchText) > -1
|
||||
) {
|
||||
return true;
|
||||
|
||||
if (prop2 != null) {
|
||||
const propValue = typeof prop2 === "function" ? prop2(i) : i[prop2];
|
||||
|
||||
if (propValue?.toString().toLowerCase().indexOf(searchText) > -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (
|
||||
prop3 != null &&
|
||||
i[prop3] != null &&
|
||||
i[prop3].toString().toLowerCase().indexOf(searchText) > -1
|
||||
) {
|
||||
return true;
|
||||
|
||||
if (prop3 != null) {
|
||||
const propValue = typeof prop3 === "function" ? prop3(i) : i[prop3];
|
||||
|
||||
if (propValue?.toString().toLowerCase().indexOf(searchText) > -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
29
libs/angular/src/pipes/user-type.pipe.ts
Normal file
29
libs/angular/src/pipes/user-type.pipe.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Pipe, PipeTransform } from "@angular/core";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType";
|
||||
|
||||
@Pipe({
|
||||
name: "userType",
|
||||
})
|
||||
export class UserTypePipe implements PipeTransform {
|
||||
constructor(private i18nService: I18nService) {}
|
||||
|
||||
transform(value?: OrganizationUserType): string {
|
||||
if (value == null) {
|
||||
return this.i18nService.t("unknown");
|
||||
}
|
||||
switch (value) {
|
||||
case OrganizationUserType.Owner:
|
||||
return this.i18nService.t("owner");
|
||||
case OrganizationUserType.Admin:
|
||||
return this.i18nService.t("admin");
|
||||
case OrganizationUserType.User:
|
||||
return this.i18nService.t("user");
|
||||
case OrganizationUserType.Manager:
|
||||
return this.i18nService.t("manager");
|
||||
case OrganizationUserType.Custom:
|
||||
return this.i18nService.t("custom");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
import { InjectionToken } from "@angular/core";
|
||||
|
||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import {
|
||||
AbstractMemoryStorageService,
|
||||
AbstractStorageService,
|
||||
} from "@bitwarden/common/abstractions/storage.service";
|
||||
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
|
||||
|
||||
export const WINDOW = new InjectionToken<Window>("WINDOW");
|
||||
export const MEMORY_STORAGE = new InjectionToken<AbstractStorageService>("MEMORY_STORAGE");
|
||||
export const MEMORY_STORAGE = new InjectionToken<AbstractMemoryStorageService>("MEMORY_STORAGE");
|
||||
export const SECURE_STORAGE = new InjectionToken<AbstractStorageService>("SECURE_STORAGE");
|
||||
export const STATE_FACTORY = new InjectionToken<StateFactory>("STATE_FACTORY");
|
||||
export const STATE_SERVICE_USE_CACHE = new InjectionToken<boolean>("STATE_SERVICE_USE_CACHE");
|
||||
|
||||
@@ -2,9 +2,10 @@ import { Injector, LOCALE_ID, NgModule } from "@angular/core";
|
||||
|
||||
import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/abstractions/account/account-api.service";
|
||||
import {
|
||||
InternalAccountService,
|
||||
AccountService as AccountServiceAbstraction,
|
||||
InternalAccountService,
|
||||
} from "@bitwarden/common/abstractions/account/account.service";
|
||||
import { AvatarUpdateService as AccountUpdateServiceAbstraction } from "@bitwarden/common/abstractions/account/avatar-update.service";
|
||||
import { AnonymousHubService as AnonymousHubServiceAbstraction } from "@bitwarden/common/abstractions/anonymousHub.service";
|
||||
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/abstractions/appId.service";
|
||||
@@ -19,7 +20,8 @@ import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abs
|
||||
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
||||
import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service";
|
||||
import { EnvironmentService as EnvironmentServiceAbstraction } from "@bitwarden/common/abstractions/environment.service";
|
||||
import { EventService as EventServiceAbstraction } from "@bitwarden/common/abstractions/event.service";
|
||||
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
|
||||
import { ExportService as ExportServiceAbstraction } from "@bitwarden/common/abstractions/export.service";
|
||||
import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/common/abstractions/fileUpload.service";
|
||||
import { FolderApiServiceAbstraction } from "@bitwarden/common/abstractions/folder/folder-api.service.abstraction";
|
||||
@@ -34,15 +36,19 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/abstractions/login.service";
|
||||
import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service";
|
||||
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationService as OrganizationServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import {
|
||||
InternalOrganizationService,
|
||||
OrganizationService as OrganizationServiceAbstraction,
|
||||
} from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@bitwarden/common/abstractions/passwordReprompt.service";
|
||||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy-api.service.abstraction";
|
||||
import {
|
||||
PolicyService as PolicyServiceAbstraction,
|
||||
InternalPolicyService,
|
||||
PolicyService as PolicyServiceAbstraction,
|
||||
} from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||
import { ProviderService as ProviderServiceAbstraction } from "@bitwarden/common/abstractions/provider.service";
|
||||
import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service";
|
||||
@@ -68,6 +74,7 @@ import { Account } from "@bitwarden/common/models/domain/account";
|
||||
import { GlobalState } from "@bitwarden/common/models/domain/global-state";
|
||||
import { AccountApiServiceImplementation } from "@bitwarden/common/services/account/account-api.service";
|
||||
import { AccountServiceImplementation } from "@bitwarden/common/services/account/account.service";
|
||||
import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-update.service";
|
||||
import { AnonymousHubService } from "@bitwarden/common/services/anonymousHub.service";
|
||||
import { ApiService } from "@bitwarden/common/services/api.service";
|
||||
import { AppIdService } from "@bitwarden/common/services/appId.service";
|
||||
@@ -82,7 +89,8 @@ import { CryptoService } from "@bitwarden/common/services/crypto.service";
|
||||
import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation";
|
||||
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/services/cryptography/multithread-encrypt.service.implementation";
|
||||
import { EnvironmentService } from "@bitwarden/common/services/environment.service";
|
||||
import { EventService } from "@bitwarden/common/services/event.service";
|
||||
import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service";
|
||||
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
|
||||
import { ExportService } from "@bitwarden/common/services/export.service";
|
||||
import { FileUploadService } from "@bitwarden/common/services/fileUpload.service";
|
||||
import { FolderApiService } from "@bitwarden/common/services/folder/folder-api.service";
|
||||
@@ -91,6 +99,7 @@ import { FormValidationErrorsService } from "@bitwarden/common/services/formVali
|
||||
import { KeyConnectorService } from "@bitwarden/common/services/keyConnector.service";
|
||||
import { LoginService } from "@bitwarden/common/services/login.service";
|
||||
import { NotificationsService } from "@bitwarden/common/services/notifications.service";
|
||||
import { OrganizationUserServiceImplementation } from "@bitwarden/common/services/organization-user/organization-user.service.implementation";
|
||||
import { OrganizationApiService } from "@bitwarden/common/services/organization/organization-api.service";
|
||||
import { OrganizationService } from "@bitwarden/common/services/organization/organization.service";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/services/passwordGeneration.service";
|
||||
@@ -121,16 +130,16 @@ import { UnauthGuard } from "../guards/unauth.guard";
|
||||
|
||||
import { BroadcasterService } from "./broadcaster.service";
|
||||
import {
|
||||
WINDOW,
|
||||
LOCALES_DIRECTORY,
|
||||
LOCKED_CALLBACK,
|
||||
LOG_MAC_FAILURES,
|
||||
LOGOUT_CALLBACK,
|
||||
MEMORY_STORAGE,
|
||||
SECURE_STORAGE,
|
||||
STATE_FACTORY,
|
||||
STATE_SERVICE_USE_CACHE,
|
||||
LOGOUT_CALLBACK,
|
||||
LOCKED_CALLBACK,
|
||||
LOCALES_DIRECTORY,
|
||||
SYSTEM_LANGUAGE,
|
||||
LOG_MAC_FAILURES,
|
||||
WINDOW,
|
||||
} from "./injection-tokens";
|
||||
import { ModalService } from "./modal.service";
|
||||
import { PasswordRepromptService } from "./passwordReprompt.service";
|
||||
@@ -284,6 +293,11 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
|
||||
provide: InternalAccountService,
|
||||
useExisting: AccountServiceAbstraction,
|
||||
},
|
||||
{
|
||||
provide: AccountUpdateServiceAbstraction,
|
||||
useClass: AvatarUpdateService,
|
||||
deps: [ApiServiceAbstraction, StateServiceAbstraction],
|
||||
},
|
||||
{ provide: LogService, useFactory: () => new ConsoleLogService(false) },
|
||||
{
|
||||
provide: CollectionServiceAbstraction,
|
||||
@@ -356,7 +370,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
|
||||
StateServiceAbstraction,
|
||||
ProviderServiceAbstraction,
|
||||
FolderApiServiceAbstraction,
|
||||
SyncNotifierServiceAbstraction,
|
||||
OrganizationServiceAbstraction,
|
||||
LOGOUT_CALLBACK,
|
||||
],
|
||||
},
|
||||
@@ -454,14 +468,18 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
|
||||
deps: [CryptoFunctionServiceAbstraction, LogService, LOG_MAC_FAILURES],
|
||||
},
|
||||
{
|
||||
provide: EventServiceAbstraction,
|
||||
useClass: EventService,
|
||||
provide: EventUploadServiceAbstraction,
|
||||
useClass: EventUploadService,
|
||||
deps: [ApiServiceAbstraction, StateServiceAbstraction, LogService],
|
||||
},
|
||||
{
|
||||
provide: EventCollectionServiceAbstraction,
|
||||
useClass: EventCollectionService,
|
||||
deps: [
|
||||
ApiServiceAbstraction,
|
||||
CipherServiceAbstraction,
|
||||
StateServiceAbstraction,
|
||||
LogService,
|
||||
OrganizationServiceAbstraction,
|
||||
EventUploadServiceAbstraction,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -476,12 +494,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
|
||||
{
|
||||
provide: PolicyApiServiceAbstraction,
|
||||
useClass: PolicyApiService,
|
||||
deps: [
|
||||
PolicyServiceAbstraction,
|
||||
ApiServiceAbstraction,
|
||||
StateServiceAbstraction,
|
||||
OrganizationServiceAbstraction,
|
||||
],
|
||||
deps: [PolicyServiceAbstraction, ApiServiceAbstraction, StateServiceAbstraction],
|
||||
},
|
||||
{
|
||||
provide: SendServiceAbstraction,
|
||||
@@ -506,6 +519,8 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
|
||||
LogService,
|
||||
OrganizationServiceAbstraction,
|
||||
CryptoFunctionServiceAbstraction,
|
||||
SyncNotifierServiceAbstraction,
|
||||
MessagingServiceAbstraction,
|
||||
LOGOUT_CALLBACK,
|
||||
],
|
||||
},
|
||||
@@ -522,7 +537,16 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
|
||||
{
|
||||
provide: OrganizationServiceAbstraction,
|
||||
useClass: OrganizationService,
|
||||
deps: [StateServiceAbstraction, SyncNotifierServiceAbstraction],
|
||||
deps: [StateServiceAbstraction],
|
||||
},
|
||||
{
|
||||
provide: InternalOrganizationService,
|
||||
useExisting: OrganizationServiceAbstraction,
|
||||
},
|
||||
{
|
||||
provide: OrganizationUserService,
|
||||
useClass: OrganizationUserServiceImplementation,
|
||||
deps: [ApiServiceAbstraction],
|
||||
},
|
||||
{
|
||||
provide: ProviderServiceAbstraction,
|
||||
@@ -583,6 +607,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
|
||||
{
|
||||
provide: LoginServiceAbstraction,
|
||||
useClass: LoginService,
|
||||
deps: [StateServiceAbstraction],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -6,10 +6,12 @@ import { ITreeNodeObject } from "@bitwarden/common/models/domain/tree-node";
|
||||
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
|
||||
import { FolderView } from "@bitwarden/common/models/view/folder.view";
|
||||
|
||||
import { DeprecatedVaultFilterService } from "../../../abstractions/deprecated-vault-filter.service";
|
||||
import { DynamicTreeNode } from "../models/dynamic-tree-node.model";
|
||||
import { VaultFilter } from "../models/vault-filter.model";
|
||||
import { VaultFilterService } from "../services/vault-filter.service";
|
||||
|
||||
// TODO: Replace with refactored web vault filter component
|
||||
// and refactor desktop/browser vault filters
|
||||
@Directive()
|
||||
export class VaultFilterComponent implements OnInit {
|
||||
@Input() activeFilter: VaultFilter = new VaultFilter();
|
||||
@@ -31,7 +33,7 @@ export class VaultFilterComponent implements OnInit {
|
||||
collections: DynamicTreeNode<CollectionView>;
|
||||
folders$: Observable<DynamicTreeNode<FolderView>>;
|
||||
|
||||
constructor(protected vaultFilterService: VaultFilterService) {}
|
||||
constructor(protected vaultFilterService: DeprecatedVaultFilterService) {}
|
||||
|
||||
get displayCollections() {
|
||||
return this.collections?.fullList != null && this.collections.fullList.length > 0;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { TreeNode } from "@bitwarden/common/models/domain/tree-node";
|
||||
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
|
||||
import { FolderView } from "@bitwarden/common/models/view/folder.view";
|
||||
import { ITreeNodeObject, TreeNode } from "@bitwarden/common/models/domain/tree-node";
|
||||
|
||||
export class DynamicTreeNode<T extends CollectionView | FolderView> {
|
||||
export class DynamicTreeNode<T extends ITreeNodeObject> {
|
||||
fullList: T[];
|
||||
nestedList: TreeNode<T>[];
|
||||
|
||||
|
||||
@@ -4,7 +4,10 @@ import { firstValueFrom, from, mergeMap, Observable } from "rxjs";
|
||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import {
|
||||
isNotProviderUser,
|
||||
OrganizationService,
|
||||
} from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { PolicyType } from "@bitwarden/common/enums/policyType";
|
||||
@@ -14,12 +17,13 @@ import { TreeNode } from "@bitwarden/common/models/domain/tree-node";
|
||||
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
|
||||
import { FolderView } from "@bitwarden/common/models/view/folder.view";
|
||||
|
||||
import { DeprecatedVaultFilterService as DeprecatedVaultFilterServiceAbstraction } from "../../../abstractions/deprecated-vault-filter.service";
|
||||
import { DynamicTreeNode } from "../models/dynamic-tree-node.model";
|
||||
|
||||
const NestingDelimiter = "/";
|
||||
|
||||
@Injectable()
|
||||
export class VaultFilterService {
|
||||
export class VaultFilterService implements DeprecatedVaultFilterServiceAbstraction {
|
||||
constructor(
|
||||
protected stateService: StateService,
|
||||
protected organizationService: OrganizationService,
|
||||
@@ -40,7 +44,9 @@ export class VaultFilterService {
|
||||
async buildOrganizations(): Promise<Organization[]> {
|
||||
let organizations = await this.organizationService.getAll();
|
||||
if (organizations != null) {
|
||||
organizations = organizations.sort((a, b) => a.name.localeCompare(b.name));
|
||||
organizations = organizations
|
||||
.filter(isNotProviderUser)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
return organizations;
|
||||
@@ -115,6 +121,6 @@ export class VaultFilterService {
|
||||
const folders = await this.getAllFoldersNested(
|
||||
await firstValueFrom(this.folderService.folderViews$)
|
||||
);
|
||||
return ServiceUtils.getTreeNodeObject(folders, id) as TreeNode<FolderView>;
|
||||
return ServiceUtils.getTreeNodeObjectFromList(folders, id) as TreeNode<FolderView>;
|
||||
}
|
||||
}
|
||||
|
||||
3
libs/angular/test-utils.ts
Normal file
3
libs/angular/test-utils.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export async function awaitAsync(ms = 0) {
|
||||
await new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
@@ -79,8 +79,8 @@ describe("Dashlane CSV Importer", () => {
|
||||
expect(cipher2.card.cardholderName).toBe("John Doe");
|
||||
expect(cipher2.card.number).toBe("41111111111111111");
|
||||
expect(cipher2.card.code).toBe("123");
|
||||
expect(cipher2.card.expMonth).toBe("01");
|
||||
expect(cipher2.card.expYear).toBe("23");
|
||||
expect(cipher2.card.expMonth).toBe("1");
|
||||
expect(cipher2.card.expYear).toBe("2023");
|
||||
|
||||
expect(cipher2.fields.length).toBe(2);
|
||||
|
||||
|
||||
133
libs/common/spec/importers/enpass/enpass-json-importer.spec.ts
Normal file
133
libs/common/spec/importers/enpass/enpass-json-importer.spec.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { CipherType } from "@bitwarden/common/enums/cipherType";
|
||||
import { EnpassJsonImporter as Importer } from "@bitwarden/common/importers/enpass/enpass-json-importer";
|
||||
import { FieldView } from "@bitwarden/common/models/view/field.view";
|
||||
|
||||
import { creditCard } from "./test-data/json/credit-card";
|
||||
import { folders } from "./test-data/json/folders";
|
||||
import { login } from "./test-data/json/login";
|
||||
import { loginAndroidUrl } from "./test-data/json/login-android-url";
|
||||
import { note } from "./test-data/json/note";
|
||||
|
||||
function validateCustomField(fields: FieldView[], fieldName: string, expectedValue: any) {
|
||||
expect(fields).toBeDefined();
|
||||
const customField = fields.find((f) => f.name === fieldName);
|
||||
expect(customField).toBeDefined();
|
||||
|
||||
expect(customField.value).toEqual(expectedValue);
|
||||
}
|
||||
|
||||
describe("Enpass JSON Importer", () => {
|
||||
it("should create folders/ nested folder and assignment", async () => {
|
||||
const importer = new Importer();
|
||||
const testDataString = JSON.stringify(folders);
|
||||
const result = await importer.parse(testDataString);
|
||||
expect(result != null).toBe(true);
|
||||
|
||||
expect(result.folders.length).toEqual(2);
|
||||
const folder1 = result.folders.shift();
|
||||
expect(folder1.name).toEqual("Social");
|
||||
|
||||
// Created 2 folders and Twitter is child of Social
|
||||
const folder2 = result.folders.shift();
|
||||
expect(folder2.name).toEqual("Social/Twitter");
|
||||
|
||||
// Expect that the single cipher item is assigned to sub-folder "Social/Twitter"
|
||||
const folderRelationship = result.folderRelationships.shift();
|
||||
expect(folderRelationship).toEqual([0, 1]);
|
||||
});
|
||||
|
||||
it("should parse login items", async () => {
|
||||
const importer = new Importer();
|
||||
const testDataString = JSON.stringify(login);
|
||||
const result = await importer.parse(testDataString);
|
||||
expect(result != null).toBe(true);
|
||||
|
||||
const cipher = result.ciphers.shift();
|
||||
expect(cipher.type).toEqual(CipherType.Login);
|
||||
expect(cipher.name).toEqual("Amazon");
|
||||
expect(cipher.subTitle).toEqual("emily@enpass.io");
|
||||
expect(cipher.favorite).toBe(true);
|
||||
expect(cipher.notes).toEqual("some notes on the login item");
|
||||
|
||||
expect(cipher.login.username).toEqual("emily@enpass.io");
|
||||
expect(cipher.login.password).toEqual("$&W:v@}4\\iRpUXVbjPdPKDGbD<xK>");
|
||||
expect(cipher.login.totp).toEqual("TOTP_SEED_VALUE");
|
||||
expect(cipher.login.uris.length).toEqual(1);
|
||||
const uriView = cipher.login.uris.shift();
|
||||
expect(uriView.uri).toEqual("https://www.amazon.com");
|
||||
|
||||
// remaining fields as custom fields
|
||||
expect(cipher.fields.length).toEqual(3);
|
||||
validateCustomField(cipher.fields, "Phone number", "12345678");
|
||||
validateCustomField(cipher.fields, "Security question", "SECURITY_QUESTION");
|
||||
validateCustomField(cipher.fields, "Security answer", "SECURITY_ANSWER");
|
||||
});
|
||||
|
||||
it("should parse login items with Android Autofill information", async () => {
|
||||
const importer = new Importer();
|
||||
const testDataString = JSON.stringify(loginAndroidUrl);
|
||||
const result = await importer.parse(testDataString);
|
||||
expect(result != null).toBe(true);
|
||||
|
||||
const cipher = result.ciphers.shift();
|
||||
expect(cipher.type).toEqual(CipherType.Login);
|
||||
expect(cipher.name).toEqual("Amazon");
|
||||
|
||||
expect(cipher.login.uris.length).toEqual(5);
|
||||
expect(cipher.login.uris[0].uri).toEqual("https://www.amazon.com");
|
||||
expect(cipher.login.uris[1].uri).toEqual("androidapp://com.amazon.0");
|
||||
expect(cipher.login.uris[2].uri).toEqual("androidapp://com.amazon.1");
|
||||
expect(cipher.login.uris[3].uri).toEqual("androidapp://com.amazon.2");
|
||||
expect(cipher.login.uris[4].uri).toEqual("androidapp://com.amazon.3");
|
||||
});
|
||||
|
||||
it("should parse credit card items", async () => {
|
||||
const importer = new Importer();
|
||||
const testDataString = JSON.stringify(creditCard);
|
||||
const result = await importer.parse(testDataString);
|
||||
expect(result != null).toBe(true);
|
||||
|
||||
const cipher = result.ciphers.shift();
|
||||
expect(cipher.type).toEqual(CipherType.Card);
|
||||
expect(cipher.name).toEqual("Emily Sample Credit Card");
|
||||
expect(cipher.subTitle).toEqual("Amex, *10005");
|
||||
expect(cipher.favorite).toBe(true);
|
||||
expect(cipher.notes).toEqual("some notes on the credit card");
|
||||
|
||||
expect(cipher.card.cardholderName).toEqual("Emily Sample");
|
||||
expect(cipher.card.number).toEqual("3782 822463 10005");
|
||||
expect(cipher.card.brand).toEqual("Amex");
|
||||
expect(cipher.card.code).toEqual("1234");
|
||||
expect(cipher.card.expMonth).toEqual("3");
|
||||
expect(cipher.card.expYear).toEqual("23");
|
||||
|
||||
// remaining fields as custom fields
|
||||
expect(cipher.fields.length).toEqual(9);
|
||||
validateCustomField(cipher.fields, "PIN", "9874");
|
||||
validateCustomField(cipher.fields, "Username", "Emily_ENP");
|
||||
validateCustomField(
|
||||
cipher.fields,
|
||||
"Login password",
|
||||
"nnn tug shoot selfish bon liars convent dusty minnow uncheck"
|
||||
);
|
||||
validateCustomField(cipher.fields, "Website", "http://global.americanexpress.com/");
|
||||
validateCustomField(cipher.fields, "Issuing bank", "American Express");
|
||||
validateCustomField(cipher.fields, "Credit limit", "100000");
|
||||
validateCustomField(cipher.fields, "Withdrawal limit", "50000");
|
||||
validateCustomField(cipher.fields, "Interest rate", "1.5");
|
||||
validateCustomField(cipher.fields, "If lost, call", "12345678");
|
||||
});
|
||||
|
||||
it("should parse notes", async () => {
|
||||
const importer = new Importer();
|
||||
const testDataString = JSON.stringify(note);
|
||||
const result = await importer.parse(testDataString);
|
||||
expect(result != null).toBe(true);
|
||||
|
||||
const cipher = result.ciphers.shift();
|
||||
expect(cipher.type).toEqual(CipherType.SecureNote);
|
||||
expect(cipher.name).toEqual("some secure note title");
|
||||
expect(cipher.favorite).toBe(false);
|
||||
expect(cipher.notes).toEqual("some secure note content");
|
||||
});
|
||||
});
|
||||
274
libs/common/spec/importers/enpass/test-data/json/credit-card.ts
Normal file
274
libs/common/spec/importers/enpass/test-data/json/credit-card.ts
Normal file
@@ -0,0 +1,274 @@
|
||||
import { EnpassJsonFile } from "@bitwarden/common/importers/enpass/types/enpass-json-type";
|
||||
|
||||
export const creditCard: EnpassJsonFile = {
|
||||
folders: [],
|
||||
items: [
|
||||
{
|
||||
archived: 0,
|
||||
auto_submit: 1,
|
||||
category: "creditcard",
|
||||
createdAt: 1666449561,
|
||||
favorite: 1,
|
||||
fields: [
|
||||
{
|
||||
deleted: 0,
|
||||
history: [
|
||||
{
|
||||
updated_at: 1534490234,
|
||||
value: "Wendy Apple Seed",
|
||||
},
|
||||
{
|
||||
updated_at: 1535521811,
|
||||
value: "Emma",
|
||||
},
|
||||
{
|
||||
updated_at: 1535522090,
|
||||
value: "Emily",
|
||||
},
|
||||
],
|
||||
label: "Cardholder",
|
||||
order: 1,
|
||||
sensitive: 0,
|
||||
type: "ccName",
|
||||
uid: 0,
|
||||
updated_at: 1666449561,
|
||||
value: "Emily Sample",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Type",
|
||||
order: 2,
|
||||
sensitive: 0,
|
||||
type: "ccType",
|
||||
uid: 17,
|
||||
updated_at: 1666449561,
|
||||
value: "American Express",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
history: [
|
||||
{
|
||||
updated_at: 1534490234,
|
||||
value: "1234 1234 5678 0000",
|
||||
},
|
||||
],
|
||||
label: "Number",
|
||||
order: 3,
|
||||
sensitive: 0,
|
||||
type: "ccNumber",
|
||||
uid: 1,
|
||||
updated_at: 1666449561,
|
||||
value: "3782 822463 10005",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "CVC",
|
||||
order: 4,
|
||||
sensitive: 1,
|
||||
type: "ccCvc",
|
||||
uid: 2,
|
||||
updated_at: 1666449561,
|
||||
value: "1234",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "PIN",
|
||||
order: 5,
|
||||
sensitive: 1,
|
||||
type: "ccPin",
|
||||
uid: 3,
|
||||
updated_at: 1666449561,
|
||||
value: "9874",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Expiry date",
|
||||
order: 6,
|
||||
sensitive: 0,
|
||||
type: "ccExpiry",
|
||||
uid: 4,
|
||||
updated_at: 1666449561,
|
||||
value: "03/23",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "INTERNET BANKING",
|
||||
order: 7,
|
||||
sensitive: 0,
|
||||
type: "section",
|
||||
uid: 103,
|
||||
updated_at: 1666449561,
|
||||
value: "",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
history: [
|
||||
{
|
||||
updated_at: 1534490234,
|
||||
value: "WendySeed",
|
||||
},
|
||||
{
|
||||
updated_at: 1535521811,
|
||||
value: "Emma1",
|
||||
},
|
||||
{
|
||||
updated_at: 1535522182,
|
||||
value: "Emily1",
|
||||
},
|
||||
],
|
||||
label: "Username",
|
||||
order: 8,
|
||||
sensitive: 0,
|
||||
type: "username",
|
||||
uid: 15,
|
||||
updated_at: 1666449561,
|
||||
value: "Emily_ENP",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Login password",
|
||||
order: 9,
|
||||
sensitive: 1,
|
||||
type: "password",
|
||||
uid: 16,
|
||||
updated_at: 1666449561,
|
||||
value: "nnn tug shoot selfish bon liars convent dusty minnow uncheck",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Transaction password",
|
||||
order: 10,
|
||||
sensitive: 1,
|
||||
type: "ccTxnpassword",
|
||||
uid: 9,
|
||||
updated_at: 1666449561,
|
||||
value: "",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Website",
|
||||
order: 11,
|
||||
sensitive: 0,
|
||||
type: "url",
|
||||
uid: 14,
|
||||
updated_at: 1666449561,
|
||||
value: "http://global.americanexpress.com/",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "ADDITIONAL DETAILS",
|
||||
order: 12,
|
||||
sensitive: 0,
|
||||
type: "section",
|
||||
uid: 104,
|
||||
updated_at: 1666449561,
|
||||
value: "",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Issuing bank",
|
||||
order: 13,
|
||||
sensitive: 0,
|
||||
type: "ccBankname",
|
||||
uid: 6,
|
||||
updated_at: 1666449561,
|
||||
value: "American Express",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Issued on",
|
||||
order: 14,
|
||||
sensitive: 0,
|
||||
type: "date",
|
||||
uid: 7,
|
||||
updated_at: 1666449561,
|
||||
value: "",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Valid from",
|
||||
order: 15,
|
||||
sensitive: 0,
|
||||
type: "ccValidfrom",
|
||||
uid: 18,
|
||||
updated_at: 1666449561,
|
||||
value: "",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Credit limit",
|
||||
order: 16,
|
||||
sensitive: 0,
|
||||
type: "numeric",
|
||||
uid: 10,
|
||||
updated_at: 1666449561,
|
||||
value: "100000",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Withdrawal limit",
|
||||
order: 17,
|
||||
sensitive: 0,
|
||||
type: "numeric",
|
||||
uid: 11,
|
||||
updated_at: 1666449561,
|
||||
value: "50000",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Interest rate",
|
||||
order: 18,
|
||||
sensitive: 0,
|
||||
type: "numeric",
|
||||
uid: 12,
|
||||
updated_at: 1666449561,
|
||||
value: "1.5",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "If lost, call",
|
||||
order: 19,
|
||||
sensitive: 0,
|
||||
type: "phone",
|
||||
uid: 8,
|
||||
updated_at: 1666449561,
|
||||
value: "12345678",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
],
|
||||
icon: {
|
||||
fav: "global.americanexpress.com",
|
||||
image: {
|
||||
file: "cc/american_express",
|
||||
},
|
||||
type: 2,
|
||||
uuid: "",
|
||||
},
|
||||
note: "some notes on the credit card",
|
||||
subtitle: "***** 0000",
|
||||
template_type: "creditcard.default",
|
||||
title: "Emily Sample Credit Card",
|
||||
trashed: 0,
|
||||
updated_at: 1666554351,
|
||||
uuid: "dbbc741b-81d6-491a-9660-92995fd8958c",
|
||||
},
|
||||
],
|
||||
};
|
||||
45
libs/common/spec/importers/enpass/test-data/json/folders.ts
Normal file
45
libs/common/spec/importers/enpass/test-data/json/folders.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { EnpassJsonFile } from "@bitwarden/common/importers/enpass/types/enpass-json-type";
|
||||
|
||||
export const folders: EnpassJsonFile = {
|
||||
folders: [
|
||||
{
|
||||
icon: "1008",
|
||||
parent_uuid: "",
|
||||
title: "Social",
|
||||
updated_at: 1666449561,
|
||||
uuid: "7b2ed0da-8cd9-445f-9a1a-490ca2b9ffbc",
|
||||
},
|
||||
{
|
||||
icon: "1008",
|
||||
parent_uuid: "7b2ed0da-8cd9-445f-9a1a-490ca2b9ffbc",
|
||||
title: "Twitter",
|
||||
updated_at: 1666450857,
|
||||
uuid: "7fe8a8bc-b848-4f9f-9870-c2936317e74d",
|
||||
},
|
||||
],
|
||||
items: [
|
||||
{
|
||||
archived: 0,
|
||||
auto_submit: 1,
|
||||
category: "note",
|
||||
createdAt: 1666554621,
|
||||
favorite: 0,
|
||||
folders: ["7fe8a8bc-b848-4f9f-9870-c2936317e74d"],
|
||||
icon: {
|
||||
fav: "",
|
||||
image: {
|
||||
file: "misc/secure_note",
|
||||
},
|
||||
type: 1,
|
||||
uuid: "",
|
||||
},
|
||||
note: "some secure note content",
|
||||
subtitle: "",
|
||||
template_type: "note.default",
|
||||
title: "some secure note title",
|
||||
trashed: 0,
|
||||
updated_at: 1666554621,
|
||||
uuid: "8b5ea2f6-f62b-4fec-a235-4a40946026b6",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
import { EnpassJsonFile } from "@bitwarden/common/importers/enpass/types/enpass-json-type";
|
||||
|
||||
import { login } from "./login";
|
||||
|
||||
export const loginAndroidUrl: EnpassJsonFile = {
|
||||
folders: [],
|
||||
items: [
|
||||
{
|
||||
archived: 0,
|
||||
auto_submit: 1,
|
||||
category: "login",
|
||||
createdAt: 1666449561,
|
||||
favorite: 1,
|
||||
fields: [
|
||||
...login.items[0].fields,
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Autofill Info",
|
||||
order: 9,
|
||||
sensitive: 0,
|
||||
type: ".Android#",
|
||||
uid: 7696,
|
||||
updated_at: 1666551057,
|
||||
value: "com.amazon.0",
|
||||
value_updated_at: 1666551057,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Autofill Info 1",
|
||||
order: 9,
|
||||
sensitive: 0,
|
||||
type: ".Android#",
|
||||
uid: 7696,
|
||||
updated_at: 1666551057,
|
||||
value:
|
||||
"android://pMUhLBalOhcc3yK-84sMiGc2U856FVVUhm8PZveoRfNFT3ocT1KWZlciAkF2ED--B5i_fMuNlC6JfPxcHk1AQg==@com.amazon.1",
|
||||
value_updated_at: 1666551057,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Autofill Info2 ",
|
||||
order: 9,
|
||||
sensitive: 0,
|
||||
type: ".Android#",
|
||||
uid: 7696,
|
||||
updated_at: 1666551057,
|
||||
value: "android://com.amazon.2",
|
||||
value_updated_at: 1666551057,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Autofill Info 3",
|
||||
order: 9,
|
||||
sensitive: 0,
|
||||
type: ".Android#",
|
||||
uid: 7696,
|
||||
updated_at: 1666551057,
|
||||
value: "androidapp://com.amazon.3",
|
||||
value_updated_at: 1666551057,
|
||||
},
|
||||
],
|
||||
icon: {
|
||||
fav: "www.amazon.com",
|
||||
image: {
|
||||
file: "web/amazon.com",
|
||||
},
|
||||
type: 1,
|
||||
uuid: "",
|
||||
},
|
||||
note: "some notes on the login item",
|
||||
subtitle: "emily@enpass.io",
|
||||
template_type: "login.default",
|
||||
title: "Amazon",
|
||||
trashed: 0,
|
||||
updated_at: 1666449561,
|
||||
uuid: "f717cb7c-6cce-4b24-b023-ec8a429cc992",
|
||||
},
|
||||
],
|
||||
};
|
||||
130
libs/common/spec/importers/enpass/test-data/json/login.ts
Normal file
130
libs/common/spec/importers/enpass/test-data/json/login.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { EnpassJsonFile } from "@bitwarden/common/importers/enpass/types/enpass-json-type";
|
||||
|
||||
export const login: EnpassJsonFile = {
|
||||
folders: [],
|
||||
items: [
|
||||
{
|
||||
archived: 0,
|
||||
auto_submit: 1,
|
||||
category: "login",
|
||||
createdAt: 1666449561,
|
||||
favorite: 1,
|
||||
fields: [
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Username",
|
||||
order: 1,
|
||||
sensitive: 0,
|
||||
type: "username",
|
||||
uid: 10,
|
||||
updated_at: 1666449561,
|
||||
value: "",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "E-mail",
|
||||
order: 2,
|
||||
sensitive: 0,
|
||||
type: "email",
|
||||
uid: 12,
|
||||
updated_at: 1666449561,
|
||||
value: "emily@enpass.io",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Password",
|
||||
order: 3,
|
||||
sensitive: 1,
|
||||
type: "password",
|
||||
uid: 11,
|
||||
updated_at: 1666449561,
|
||||
value: "$&W:v@}4\\iRpUXVbjPdPKDGbD<xK>",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Website",
|
||||
order: 4,
|
||||
sensitive: 0,
|
||||
type: "url",
|
||||
uid: 13,
|
||||
updated_at: 1666449561,
|
||||
value: "https://www.amazon.com",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "ADDITIONAL DETAILS",
|
||||
order: 5,
|
||||
sensitive: 0,
|
||||
type: "section",
|
||||
uid: 101,
|
||||
updated_at: 1666449561,
|
||||
value: "",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Phone number",
|
||||
order: 6,
|
||||
sensitive: 0,
|
||||
type: "phone",
|
||||
uid: 14,
|
||||
updated_at: 1666449561,
|
||||
value: "12345678",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "TOTP",
|
||||
order: 7,
|
||||
sensitive: 0,
|
||||
type: "totp",
|
||||
uid: 102,
|
||||
updated_at: 1666449561,
|
||||
value: "TOTP_SEED_VALUE",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Security question",
|
||||
order: 8,
|
||||
sensitive: 0,
|
||||
type: "text",
|
||||
uid: 15,
|
||||
updated_at: 1666449561,
|
||||
value: "SECURITY_QUESTION",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
{
|
||||
deleted: 0,
|
||||
label: "Security answer",
|
||||
order: 9,
|
||||
sensitive: 1,
|
||||
type: "text",
|
||||
uid: 16,
|
||||
updated_at: 1666449561,
|
||||
value: "SECURITY_ANSWER",
|
||||
value_updated_at: 1666449561,
|
||||
},
|
||||
],
|
||||
icon: {
|
||||
fav: "www.amazon.com",
|
||||
image: {
|
||||
file: "web/amazon.com",
|
||||
},
|
||||
type: 1,
|
||||
uuid: "",
|
||||
},
|
||||
note: "some notes on the login item",
|
||||
subtitle: "emily@enpass.io",
|
||||
template_type: "login.default",
|
||||
title: "Amazon",
|
||||
trashed: 0,
|
||||
updated_at: 1666449561,
|
||||
uuid: "f717cb7c-6cce-4b24-b023-ec8a429cc992",
|
||||
},
|
||||
],
|
||||
};
|
||||
29
libs/common/spec/importers/enpass/test-data/json/note.ts
Normal file
29
libs/common/spec/importers/enpass/test-data/json/note.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { EnpassJsonFile } from "@bitwarden/common/importers/enpass/types/enpass-json-type";
|
||||
|
||||
export const note: EnpassJsonFile = {
|
||||
folders: [],
|
||||
items: [
|
||||
{
|
||||
archived: 0,
|
||||
auto_submit: 1,
|
||||
category: "note",
|
||||
createdAt: 1666554621,
|
||||
favorite: 0,
|
||||
icon: {
|
||||
fav: "",
|
||||
image: {
|
||||
file: "misc/secure_note",
|
||||
},
|
||||
type: 1,
|
||||
uuid: "",
|
||||
},
|
||||
note: "some secure note content",
|
||||
subtitle: "",
|
||||
template_type: "note.default",
|
||||
title: "some secure note title",
|
||||
trashed: 0,
|
||||
updated_at: 1666554621,
|
||||
uuid: "8b5ea2f6-f62b-4fec-a235-4a40946026b6",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,77 +0,0 @@
|
||||
import { FSecureFskImporter as Importer } from "@bitwarden/common/importers/fsecure-fsk-importer";
|
||||
|
||||
const TestDataWithStyleSetToWebsite: string = JSON.stringify({
|
||||
data: {
|
||||
"8d58b5cf252dd06fbd98f5289e918ab1": {
|
||||
color: "#00baff",
|
||||
reatedDate: 1609302913,
|
||||
creditCvv: "",
|
||||
creditExpiry: "",
|
||||
creditNumber: "",
|
||||
favorite: 0,
|
||||
modifiedDate: 1609302913,
|
||||
notes: "note",
|
||||
password: "word",
|
||||
passwordList: [],
|
||||
passwordModifiedDate: 1609302913,
|
||||
rev: 1,
|
||||
service: "My first pass",
|
||||
style: "website",
|
||||
type: 1,
|
||||
url: "https://bitwarden.com",
|
||||
username: "pass",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const TestDataWithStyleSetToGlobe: string = JSON.stringify({
|
||||
data: {
|
||||
"8d58b5cf252dd06fbd98f5289e918ab1": {
|
||||
color: "#00baff",
|
||||
reatedDate: 1609302913,
|
||||
creditCvv: "",
|
||||
creditExpiry: "",
|
||||
creditNumber: "",
|
||||
favorite: 0,
|
||||
modifiedDate: 1609302913,
|
||||
notes: "note",
|
||||
password: "word",
|
||||
passwordList: [],
|
||||
passwordModifiedDate: 1609302913,
|
||||
rev: 1,
|
||||
service: "My first pass",
|
||||
style: "globe",
|
||||
type: 1,
|
||||
url: "https://bitwarden.com",
|
||||
username: "pass",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe("FSecure FSK Importer", () => {
|
||||
it("should parse data with style set to website", async () => {
|
||||
const importer = new Importer();
|
||||
const result = await importer.parse(TestDataWithStyleSetToWebsite);
|
||||
expect(result != null).toBe(true);
|
||||
|
||||
const cipher = result.ciphers.shift();
|
||||
expect(cipher.login.username).toEqual("pass");
|
||||
expect(cipher.login.password).toEqual("word");
|
||||
expect(cipher.login.uris.length).toEqual(1);
|
||||
const uriView = cipher.login.uris.shift();
|
||||
expect(uriView.uri).toEqual("https://bitwarden.com");
|
||||
});
|
||||
|
||||
it("should parse data with style set to globe", async () => {
|
||||
const importer = new Importer();
|
||||
const result = await importer.parse(TestDataWithStyleSetToGlobe);
|
||||
expect(result != null).toBe(true);
|
||||
|
||||
const cipher = result.ciphers.shift();
|
||||
expect(cipher.login.username).toEqual("pass");
|
||||
expect(cipher.login.password).toEqual("word");
|
||||
expect(cipher.login.uris.length).toEqual(1);
|
||||
const uriView = cipher.login.uris.shift();
|
||||
expect(uriView.uri).toEqual("https://bitwarden.com");
|
||||
});
|
||||
});
|
||||
533
libs/common/spec/importers/keepass2-xml-importer-testdata.ts
Normal file
533
libs/common/spec/importers/keepass2-xml-importer-testdata.ts
Normal file
@@ -0,0 +1,533 @@
|
||||
export const TestData = `<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<KeePassFile>
|
||||
<Meta>
|
||||
<Generator>KeePass</Generator>
|
||||
<DatabaseName />
|
||||
<DatabaseNameChanged>2016-12-31T21:33:52Z</DatabaseNameChanged>
|
||||
<DatabaseDescription />
|
||||
<DatabaseDescriptionChanged>2016-12-31T21:33:52Z</DatabaseDescriptionChanged>
|
||||
<DefaultUserName />
|
||||
<DefaultUserNameChanged>2016-12-31T21:33:52Z</DefaultUserNameChanged>
|
||||
<MaintenanceHistoryDays>365</MaintenanceHistoryDays>
|
||||
<Color />
|
||||
<MasterKeyChanged>2016-12-31T21:33:59Z</MasterKeyChanged>
|
||||
<MasterKeyChangeRec>-1</MasterKeyChangeRec>
|
||||
<MasterKeyChangeForce>-1</MasterKeyChangeForce>
|
||||
<MemoryProtection>
|
||||
<ProtectTitle>False</ProtectTitle>
|
||||
<ProtectUserName>False</ProtectUserName>
|
||||
<ProtectPassword>True</ProtectPassword>
|
||||
<ProtectURL>False</ProtectURL>
|
||||
<ProtectNotes>False</ProtectNotes>
|
||||
</MemoryProtection>
|
||||
<RecycleBinEnabled>True</RecycleBinEnabled>
|
||||
<RecycleBinUUID>AAAAAAAAAAAAAAAAAAAAAA==</RecycleBinUUID>
|
||||
<RecycleBinChanged>2016-12-31T21:33:52Z</RecycleBinChanged>
|
||||
<EntryTemplatesGroup>AAAAAAAAAAAAAAAAAAAAAA==</EntryTemplatesGroup>
|
||||
<EntryTemplatesGroupChanged>2016-12-31T21:33:52Z</EntryTemplatesGroupChanged>
|
||||
<HistoryMaxItems>10</HistoryMaxItems>
|
||||
<HistoryMaxSize>6291456</HistoryMaxSize>
|
||||
<LastSelectedGroup>AAAAAAAAAAAAAAAAAAAAAA==</LastSelectedGroup>
|
||||
<LastTopVisibleGroup>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleGroup>
|
||||
<Binaries />
|
||||
<CustomData />
|
||||
</Meta>
|
||||
<Root>
|
||||
<Group>
|
||||
<UUID>KvS57lVwl13AfGFLwkvq4Q==</UUID>
|
||||
<Name>Root</Name>
|
||||
<Notes />
|
||||
<IconID>48</IconID>
|
||||
<Times>
|
||||
<CreationTime>2016-12-31T21:33:52Z</CreationTime>
|
||||
<LastModificationTime>2016-12-31T21:33:52Z</LastModificationTime>
|
||||
<LastAccessTime>2017-01-01T22:58:00Z</LastAccessTime>
|
||||
<ExpiryTime>2016-12-31T21:33:52Z</ExpiryTime>
|
||||
<Expires>False</Expires>
|
||||
<UsageCount>1</UsageCount>
|
||||
<LocationChanged>2016-12-31T21:33:52Z</LocationChanged>
|
||||
</Times>
|
||||
<IsExpanded>True</IsExpanded>
|
||||
<DefaultAutoTypeSequence />
|
||||
<EnableAutoType>null</EnableAutoType>
|
||||
<EnableSearching>null</EnableSearching>
|
||||
<LastTopVisibleEntry>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleEntry>
|
||||
<Group>
|
||||
<UUID>P0ParXgGMBW6caOL2YrhqQ==</UUID>
|
||||
<Name>Folder2</Name>
|
||||
<Notes>a note about the folder</Notes>
|
||||
<IconID>48</IconID>
|
||||
<Times>
|
||||
<CreationTime>2016-12-31T21:43:30Z</CreationTime>
|
||||
<LastModificationTime>2016-12-31T21:43:43Z</LastModificationTime>
|
||||
<LastAccessTime>2017-01-01T22:58:00Z</LastAccessTime>
|
||||
<ExpiryTime>2016-12-31T21:43:30Z</ExpiryTime>
|
||||
<Expires>False</Expires>
|
||||
<UsageCount>1</UsageCount>
|
||||
<LocationChanged>2016-12-31T21:43:43Z</LocationChanged>
|
||||
</Times>
|
||||
<IsExpanded>True</IsExpanded>
|
||||
<DefaultAutoTypeSequence />
|
||||
<EnableAutoType>null</EnableAutoType>
|
||||
<EnableSearching>null</EnableSearching>
|
||||
<LastTopVisibleEntry>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleEntry>
|
||||
<Entry>
|
||||
<UUID>fAa543oYlgnJKkhKag5HLw==</UUID>
|
||||
<IconID>1</IconID>
|
||||
<ForegroundColor />
|
||||
<BackgroundColor />
|
||||
<OverrideURL />
|
||||
<Tags />
|
||||
<Times>
|
||||
<CreationTime>2016-12-31T21:34:13Z</CreationTime>
|
||||
<LastModificationTime>2016-12-31T21:40:23Z</LastModificationTime>
|
||||
<LastAccessTime>2016-12-31T21:40:23Z</LastAccessTime>
|
||||
<ExpiryTime>2016-12-31T21:34:13Z</ExpiryTime>
|
||||
<Expires>False</Expires>
|
||||
<UsageCount>0</UsageCount>
|
||||
<LocationChanged>2016-12-31T21:43:48Z</LocationChanged>
|
||||
</Times>
|
||||
<String>
|
||||
<Key>att2</Key>
|
||||
<Value>att2value</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>attr1</Key>
|
||||
<Value>att1value
|
||||
|
||||
line1
|
||||
line2</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>Notes</Key>
|
||||
<Value>This is a note!!!
|
||||
|
||||
line1
|
||||
line2</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>Password</Key>
|
||||
<Value ProtectInMemory="True">googpass</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>Title</Key>
|
||||
<Value>Google</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>URL</Key>
|
||||
<Value>google.com</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>UserName</Key>
|
||||
<Value>googleuser</Value>
|
||||
</String>
|
||||
<AutoType>
|
||||
<Enabled>True</Enabled>
|
||||
<DataTransferObfuscation>0</DataTransferObfuscation>
|
||||
</AutoType>
|
||||
<History>
|
||||
<Entry>
|
||||
<UUID>fAa543oYlgnJKkhKag5HLw==</UUID>
|
||||
<IconID>0</IconID>
|
||||
<ForegroundColor />
|
||||
<BackgroundColor />
|
||||
<OverrideURL />
|
||||
<Tags />
|
||||
<Times>
|
||||
<CreationTime>2016-12-31T21:34:13Z</CreationTime>
|
||||
<LastModificationTime>2016-12-31T21:34:40Z</LastModificationTime>
|
||||
<LastAccessTime>2016-12-31T21:34:40Z</LastAccessTime>
|
||||
<ExpiryTime>2016-12-31T21:34:13Z</ExpiryTime>
|
||||
<Expires>False</Expires>
|
||||
<UsageCount>0</UsageCount>
|
||||
<LocationChanged>2016-12-31T21:34:40Z</LocationChanged>
|
||||
</Times>
|
||||
<String>
|
||||
<Key>Notes</Key>
|
||||
<Value>This is a note!!!
|
||||
|
||||
line1
|
||||
line2</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>Password</Key>
|
||||
<Value ProtectInMemory="True">googpass</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>Title</Key>
|
||||
<Value>Google</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>URL</Key>
|
||||
<Value>google.com</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>UserName</Key>
|
||||
<Value>googleuser</Value>
|
||||
</String>
|
||||
<AutoType>
|
||||
<Enabled>True</Enabled>
|
||||
<DataTransferObfuscation>0</DataTransferObfuscation>
|
||||
</AutoType>
|
||||
</Entry>
|
||||
</History>
|
||||
</Entry>
|
||||
</Group>
|
||||
</Group>
|
||||
<DeletedObjects />
|
||||
</Root>
|
||||
</KeePassFile>`;
|
||||
export const TestData1 = `<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<KeePassFile>
|
||||
<Meta>
|
||||
<Generator>KeePass</Generator>
|
||||
<DatabaseName />
|
||||
<DatabaseNameChanged>2016-12-31T21:33:52Z</DatabaseNameChanged>
|
||||
<DatabaseDescription />
|
||||
<DatabaseDescriptionChanged>2016-12-31T21:33:52Z</DatabaseDescriptionChanged>
|
||||
<DefaultUserName />
|
||||
<DefaultUserNameChanged>2016-12-31T21:33:52Z</DefaultUserNameChanged>
|
||||
<MaintenanceHistoryDays>365</MaintenanceHistoryDays>
|
||||
<Color />
|
||||
<MasterKeyChanged>2016-12-31T21:33:59Z</MasterKeyChanged>
|
||||
<MasterKeyChangeRec>-1</MasterKeyChangeRec>
|
||||
<MasterKeyChangeForce>-1</MasterKeyChangeForce>
|
||||
<MemoryProtection>
|
||||
<ProtectTitle>False</ProtectTitle>
|
||||
<ProtectUserName>False</ProtectUserName>
|
||||
<ProtectPassword>True</ProtectPassword>
|
||||
<ProtectURL>False</ProtectURL>
|
||||
<ProtectNotes>False</ProtectNotes>
|
||||
</MemoryProtection>
|
||||
<RecycleBinEnabled>True</RecycleBinEnabled>
|
||||
<RecycleBinUUID>AAAAAAAAAAAAAAAAAAAAAA==</RecycleBinUUID>
|
||||
<RecycleBinChanged>2016-12-31T21:33:52Z</RecycleBinChanged>
|
||||
<EntryTemplatesGroup>AAAAAAAAAAAAAAAAAAAAAA==</EntryTemplatesGroup>
|
||||
<EntryTemplatesGroupChanged>2016-12-31T21:33:52Z</EntryTemplatesGroupChanged>
|
||||
<HistoryMaxItems>10</HistoryMaxItems>
|
||||
<HistoryMaxSize>6291456</HistoryMaxSize>
|
||||
<LastSelectedGroup>AAAAAAAAAAAAAAAAAAAAAA==</LastSelectedGroup>
|
||||
<LastTopVisibleGroup>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleGroup>
|
||||
<Binaries />
|
||||
<CustomData />
|
||||
</Meta>
|
||||
<Group>
|
||||
<UUID>KvS57lVwl13AfGFLwkvq4Q==</UUID>
|
||||
<Name>Root</Name>
|
||||
<Notes />
|
||||
<IconID>48</IconID>
|
||||
<Times>
|
||||
<CreationTime>2016-12-31T21:33:52Z</CreationTime>
|
||||
<LastModificationTime>2016-12-31T21:33:52Z</LastModificationTime>
|
||||
<LastAccessTime>2017-01-01T22:58:00Z</LastAccessTime>
|
||||
<ExpiryTime>2016-12-31T21:33:52Z</ExpiryTime>
|
||||
<Expires>False</Expires>
|
||||
<UsageCount>1</UsageCount>
|
||||
<LocationChanged>2016-12-31T21:33:52Z</LocationChanged>
|
||||
</Times>
|
||||
<IsExpanded>True</IsExpanded>
|
||||
<DefaultAutoTypeSequence />
|
||||
<EnableAutoType>null</EnableAutoType>
|
||||
<EnableSearching>null</EnableSearching>
|
||||
<LastTopVisibleEntry>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleEntry>
|
||||
<Group>
|
||||
<UUID>P0ParXgGMBW6caOL2YrhqQ==</UUID>
|
||||
<Name>Folder2</Name>
|
||||
<Notes>a note about the folder</Notes>
|
||||
<IconID>48</IconID>
|
||||
<Times>
|
||||
<CreationTime>2016-12-31T21:43:30Z</CreationTime>
|
||||
<LastModificationTime>2016-12-31T21:43:43Z</LastModificationTime>
|
||||
<LastAccessTime>2017-01-01T22:58:00Z</LastAccessTime>
|
||||
<ExpiryTime>2016-12-31T21:43:30Z</ExpiryTime>
|
||||
<Expires>False</Expires>
|
||||
<UsageCount>1</UsageCount>
|
||||
<LocationChanged>2016-12-31T21:43:43Z</LocationChanged>
|
||||
</Times>
|
||||
<IsExpanded>True</IsExpanded>
|
||||
<DefaultAutoTypeSequence />
|
||||
<EnableAutoType>null</EnableAutoType>
|
||||
<EnableSearching>null</EnableSearching>
|
||||
<LastTopVisibleEntry>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleEntry>
|
||||
<Entry>
|
||||
<UUID>fAa543oYlgnJKkhKag5HLw==</UUID>
|
||||
<IconID>1</IconID>
|
||||
<ForegroundColor />
|
||||
<BackgroundColor />
|
||||
<OverrideURL />
|
||||
<Tags />
|
||||
<Times>
|
||||
<CreationTime>2016-12-31T21:34:13Z</CreationTime>
|
||||
<LastModificationTime>2016-12-31T21:40:23Z</LastModificationTime>
|
||||
<LastAccessTime>2016-12-31T21:40:23Z</LastAccessTime>
|
||||
<ExpiryTime>2016-12-31T21:34:13Z</ExpiryTime>
|
||||
<Expires>False</Expires>
|
||||
<UsageCount>0</UsageCount>
|
||||
<LocationChanged>2016-12-31T21:43:48Z</LocationChanged>
|
||||
</Times>
|
||||
<String>
|
||||
<Key>att2</Key>
|
||||
<Value>att2value</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>attr1</Key>
|
||||
<Value>att1value
|
||||
|
||||
line1
|
||||
line2</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>Notes</Key>
|
||||
<Value>This is a note!!!
|
||||
|
||||
line1
|
||||
line2</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>Password</Key>
|
||||
<Value ProtectInMemory="True">googpass</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>Title</Key>
|
||||
<Value>Google</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>URL</Key>
|
||||
<Value>google.com</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>UserName</Key>
|
||||
<Value>googleuser</Value>
|
||||
</String>
|
||||
<AutoType>
|
||||
<Enabled>True</Enabled>
|
||||
<DataTransferObfuscation>0</DataTransferObfuscation>
|
||||
</AutoType>
|
||||
<History>
|
||||
<Entry>
|
||||
<UUID>fAa543oYlgnJKkhKag5HLw==</UUID>
|
||||
<IconID>0</IconID>
|
||||
<ForegroundColor />
|
||||
<BackgroundColor />
|
||||
<OverrideURL />
|
||||
<Tags />
|
||||
<Times>
|
||||
<CreationTime>2016-12-31T21:34:13Z</CreationTime>
|
||||
<LastModificationTime>2016-12-31T21:34:40Z</LastModificationTime>
|
||||
<LastAccessTime>2016-12-31T21:34:40Z</LastAccessTime>
|
||||
<ExpiryTime>2016-12-31T21:34:13Z</ExpiryTime>
|
||||
<Expires>False</Expires>
|
||||
<UsageCount>0</UsageCount>
|
||||
<LocationChanged>2016-12-31T21:34:40Z</LocationChanged>
|
||||
</Times>
|
||||
<String>
|
||||
<Key>Notes</Key>
|
||||
<Value>This is a note!!!
|
||||
|
||||
line1
|
||||
line2</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>Password</Key>
|
||||
<Value ProtectInMemory="True">googpass</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>Title</Key>
|
||||
<Value>Google</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>URL</Key>
|
||||
<Value>google.com</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>UserName</Key>
|
||||
<Value>googleuser</Value>
|
||||
</String>
|
||||
<AutoType>
|
||||
<Enabled>True</Enabled>
|
||||
<DataTransferObfuscation>0</DataTransferObfuscation>
|
||||
</AutoType>
|
||||
</Entry>
|
||||
</History>
|
||||
</Entry>
|
||||
</Group>
|
||||
</Group>
|
||||
<DeletedObjects />
|
||||
</KeePassFile>`;
|
||||
export const TestData2 = `<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<Meta>
|
||||
<Generator>KeePass</Generator>
|
||||
<DatabaseName />
|
||||
<DatabaseNameChanged>2016-12-31T21:33:52Z</DatabaseNameChanged>
|
||||
<DatabaseDescription />
|
||||
<DatabaseDescriptionChanged>2016-12-31T21:33:52Z</DatabaseDescriptionChanged>
|
||||
<DefaultUserName />
|
||||
<DefaultUserNameChanged>2016-12-31T21:33:52Z</DefaultUserNameChanged>
|
||||
<MaintenanceHistoryDays>365</MaintenanceHistoryDays>
|
||||
<Color />
|
||||
<MasterKeyChanged>2016-12-31T21:33:59Z</MasterKeyChanged>
|
||||
<MasterKeyChangeRec>-1</MasterKeyChangeRec>
|
||||
<MasterKeyChangeForce>-1</MasterKeyChangeForce>
|
||||
<MemoryProtection>
|
||||
<ProtectTitle>False</ProtectTitle>
|
||||
<ProtectUserName>False</ProtectUserName>
|
||||
<ProtectPassword>True</ProtectPassword>
|
||||
<ProtectURL>False</ProtectURL>
|
||||
<ProtectNotes>False</ProtectNotes>
|
||||
</MemoryProtection>
|
||||
<RecycleBinEnabled>True</RecycleBinEnabled>
|
||||
<RecycleBinUUID>AAAAAAAAAAAAAAAAAAAAAA==</RecycleBinUUID>
|
||||
<RecycleBinChanged>2016-12-31T21:33:52Z</RecycleBinChanged>
|
||||
<EntryTemplatesGroup>AAAAAAAAAAAAAAAAAAAAAA==</EntryTemplatesGroup>
|
||||
<EntryTemplatesGroupChanged>2016-12-31T21:33:52Z</EntryTemplatesGroupChanged>
|
||||
<HistoryMaxItems>10</HistoryMaxItems>
|
||||
<HistoryMaxSize>6291456</HistoryMaxSize>
|
||||
<LastSelectedGroup>AAAAAAAAAAAAAAAAAAAAAA==</LastSelectedGroup>
|
||||
<LastTopVisibleGroup>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleGroup>
|
||||
<Binaries />
|
||||
<CustomData />
|
||||
</Meta>
|
||||
<Root>
|
||||
<Group>
|
||||
<UUID>KvS57lVwl13AfGFLwkvq4Q==</UUID>
|
||||
<Name>Root</Name>
|
||||
<Notes />
|
||||
<IconID>48</IconID>
|
||||
<Times>
|
||||
<CreationTime>2016-12-31T21:33:52Z</CreationTime>
|
||||
<LastModificationTime>2016-12-31T21:33:52Z</LastModificationTime>
|
||||
<LastAccessTime>2017-01-01T22:58:00Z</LastAccessTime>
|
||||
<ExpiryTime>2016-12-31T21:33:52Z</ExpiryTime>
|
||||
<Expires>False</Expires>
|
||||
<UsageCount>1</UsageCount>
|
||||
<LocationChanged>2016-12-31T21:33:52Z</LocationChanged>
|
||||
</Times>
|
||||
<IsExpanded>True</IsExpanded>
|
||||
<DefaultAutoTypeSequence />
|
||||
<EnableAutoType>null</EnableAutoType>
|
||||
<EnableSearching>null</EnableSearching>
|
||||
<LastTopVisibleEntry>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleEntry>
|
||||
<Group>
|
||||
<UUID>P0ParXgGMBW6caOL2YrhqQ==</UUID>
|
||||
<Name>Folder2</Name>
|
||||
<Notes>a note about the folder</Notes>
|
||||
<IconID>48</IconID>
|
||||
<Times>
|
||||
<CreationTime>2016-12-31T21:43:30Z</CreationTime>
|
||||
<LastModificationTime>2016-12-31T21:43:43Z</LastModificationTime>
|
||||
<LastAccessTime>2017-01-01T22:58:00Z</LastAccessTime>
|
||||
<ExpiryTime>2016-12-31T21:43:30Z</ExpiryTime>
|
||||
<Expires>False</Expires>
|
||||
<UsageCount>1</UsageCount>
|
||||
<LocationChanged>2016-12-31T21:43:43Z</LocationChanged>
|
||||
</Times>
|
||||
<IsExpanded>True</IsExpanded>
|
||||
<DefaultAutoTypeSequence />
|
||||
<EnableAutoType>null</EnableAutoType>
|
||||
<EnableSearching>null</EnableSearching>
|
||||
<LastTopVisibleEntry>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleEntry>
|
||||
<Entry>
|
||||
<UUID>fAa543oYlgnJKkhKag5HLw==</UUID>
|
||||
<IconID>1</IconID>
|
||||
<ForegroundColor />
|
||||
<BackgroundColor />
|
||||
<OverrideURL />
|
||||
<Tags />
|
||||
<Times>
|
||||
<CreationTime>2016-12-31T21:34:13Z</CreationTime>
|
||||
<LastModificationTime>2016-12-31T21:40:23Z</LastModificationTime>
|
||||
<LastAccessTime>2016-12-31T21:40:23Z</LastAccessTime>
|
||||
<ExpiryTime>2016-12-31T21:34:13Z</ExpiryTime>
|
||||
<Expires>False</Expires>
|
||||
<UsageCount>0</UsageCount>
|
||||
<LocationChanged>2016-12-31T21:43:48Z</LocationChanged>
|
||||
</Times>
|
||||
<String>
|
||||
<Key>att2</Key>
|
||||
<Value>att2value</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>attr1</Key>
|
||||
<Value>att1value
|
||||
|
||||
line1
|
||||
line2</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>Notes</Key>
|
||||
<Value>This is a note!!!
|
||||
|
||||
line1
|
||||
line2</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>Password</Key>
|
||||
<Value ProtectInMemory="True">googpass</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>Title</Key>
|
||||
<Value>Google</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>URL</Key>
|
||||
<Value>google.com</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>UserName</Key>
|
||||
<Value>googleuser</Value>
|
||||
</String>
|
||||
<AutoType>
|
||||
<Enabled>True</Enabled>
|
||||
<DataTransferObfuscation>0</DataTransferObfuscation>
|
||||
</AutoType>
|
||||
<History>
|
||||
<Entry>
|
||||
<UUID>fAa543oYlgnJKkhKag5HLw==</UUID>
|
||||
<IconID>0</IconID>
|
||||
<ForegroundColor />
|
||||
<BackgroundColor />
|
||||
<OverrideURL />
|
||||
<Tags />
|
||||
<Times>
|
||||
<CreationTime>2016-12-31T21:34:13Z</CreationTime>
|
||||
<LastModificationTime>2016-12-31T21:34:40Z</LastModificationTime>
|
||||
<LastAccessTime>2016-12-31T21:34:40Z</LastAccessTime>
|
||||
<ExpiryTime>2016-12-31T21:34:13Z</ExpiryTime>
|
||||
<Expires>False</Expires>
|
||||
<UsageCount>0</UsageCount>
|
||||
<LocationChanged>2016-12-31T21:34:40Z</LocationChanged>
|
||||
</Times>
|
||||
<String>
|
||||
<Key>Notes</Key>
|
||||
<Value>This is a note!!!
|
||||
|
||||
line1
|
||||
line2</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>Password</Key>
|
||||
<Value ProtectInMemory="True">googpass</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>Title</Key>
|
||||
<Value>Google</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>URL</Key>
|
||||
<Value>google.com</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>UserName</Key>
|
||||
<Value>googleuser</Value>
|
||||
</String>
|
||||
<AutoType>
|
||||
<Enabled>True</Enabled>
|
||||
<DataTransferObfuscation>0</DataTransferObfuscation>
|
||||
</AutoType>
|
||||
</Entry>
|
||||
</History>
|
||||
</Entry>
|
||||
</Group>
|
||||
</Group>
|
||||
<DeletedObjects />
|
||||
</Root>`;
|
||||
@@ -1,184 +1,7 @@
|
||||
import { KeePass2XmlImporter as Importer } from "@bitwarden/common/importers/keepass2-xml-importer";
|
||||
import { FolderView } from "@bitwarden/common/models/view/folder.view";
|
||||
|
||||
const TestData = `<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<KeePassFile>
|
||||
<Meta>
|
||||
<Generator>KeePass</Generator>
|
||||
<DatabaseName />
|
||||
<DatabaseNameChanged>2016-12-31T21:33:52Z</DatabaseNameChanged>
|
||||
<DatabaseDescription />
|
||||
<DatabaseDescriptionChanged>2016-12-31T21:33:52Z</DatabaseDescriptionChanged>
|
||||
<DefaultUserName />
|
||||
<DefaultUserNameChanged>2016-12-31T21:33:52Z</DefaultUserNameChanged>
|
||||
<MaintenanceHistoryDays>365</MaintenanceHistoryDays>
|
||||
<Color />
|
||||
<MasterKeyChanged>2016-12-31T21:33:59Z</MasterKeyChanged>
|
||||
<MasterKeyChangeRec>-1</MasterKeyChangeRec>
|
||||
<MasterKeyChangeForce>-1</MasterKeyChangeForce>
|
||||
<MemoryProtection>
|
||||
<ProtectTitle>False</ProtectTitle>
|
||||
<ProtectUserName>False</ProtectUserName>
|
||||
<ProtectPassword>True</ProtectPassword>
|
||||
<ProtectURL>False</ProtectURL>
|
||||
<ProtectNotes>False</ProtectNotes>
|
||||
</MemoryProtection>
|
||||
<RecycleBinEnabled>True</RecycleBinEnabled>
|
||||
<RecycleBinUUID>AAAAAAAAAAAAAAAAAAAAAA==</RecycleBinUUID>
|
||||
<RecycleBinChanged>2016-12-31T21:33:52Z</RecycleBinChanged>
|
||||
<EntryTemplatesGroup>AAAAAAAAAAAAAAAAAAAAAA==</EntryTemplatesGroup>
|
||||
<EntryTemplatesGroupChanged>2016-12-31T21:33:52Z</EntryTemplatesGroupChanged>
|
||||
<HistoryMaxItems>10</HistoryMaxItems>
|
||||
<HistoryMaxSize>6291456</HistoryMaxSize>
|
||||
<LastSelectedGroup>AAAAAAAAAAAAAAAAAAAAAA==</LastSelectedGroup>
|
||||
<LastTopVisibleGroup>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleGroup>
|
||||
<Binaries />
|
||||
<CustomData />
|
||||
</Meta>
|
||||
<Root>
|
||||
<Group>
|
||||
<UUID>KvS57lVwl13AfGFLwkvq4Q==</UUID>
|
||||
<Name>Root</Name>
|
||||
<Notes />
|
||||
<IconID>48</IconID>
|
||||
<Times>
|
||||
<CreationTime>2016-12-31T21:33:52Z</CreationTime>
|
||||
<LastModificationTime>2016-12-31T21:33:52Z</LastModificationTime>
|
||||
<LastAccessTime>2017-01-01T22:58:00Z</LastAccessTime>
|
||||
<ExpiryTime>2016-12-31T21:33:52Z</ExpiryTime>
|
||||
<Expires>False</Expires>
|
||||
<UsageCount>1</UsageCount>
|
||||
<LocationChanged>2016-12-31T21:33:52Z</LocationChanged>
|
||||
</Times>
|
||||
<IsExpanded>True</IsExpanded>
|
||||
<DefaultAutoTypeSequence />
|
||||
<EnableAutoType>null</EnableAutoType>
|
||||
<EnableSearching>null</EnableSearching>
|
||||
<LastTopVisibleEntry>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleEntry>
|
||||
<Group>
|
||||
<UUID>P0ParXgGMBW6caOL2YrhqQ==</UUID>
|
||||
<Name>Folder2</Name>
|
||||
<Notes>a note about the folder</Notes>
|
||||
<IconID>48</IconID>
|
||||
<Times>
|
||||
<CreationTime>2016-12-31T21:43:30Z</CreationTime>
|
||||
<LastModificationTime>2016-12-31T21:43:43Z</LastModificationTime>
|
||||
<LastAccessTime>2017-01-01T22:58:00Z</LastAccessTime>
|
||||
<ExpiryTime>2016-12-31T21:43:30Z</ExpiryTime>
|
||||
<Expires>False</Expires>
|
||||
<UsageCount>1</UsageCount>
|
||||
<LocationChanged>2016-12-31T21:43:43Z</LocationChanged>
|
||||
</Times>
|
||||
<IsExpanded>True</IsExpanded>
|
||||
<DefaultAutoTypeSequence />
|
||||
<EnableAutoType>null</EnableAutoType>
|
||||
<EnableSearching>null</EnableSearching>
|
||||
<LastTopVisibleEntry>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleEntry>
|
||||
<Entry>
|
||||
<UUID>fAa543oYlgnJKkhKag5HLw==</UUID>
|
||||
<IconID>1</IconID>
|
||||
<ForegroundColor />
|
||||
<BackgroundColor />
|
||||
<OverrideURL />
|
||||
<Tags />
|
||||
<Times>
|
||||
<CreationTime>2016-12-31T21:34:13Z</CreationTime>
|
||||
<LastModificationTime>2016-12-31T21:40:23Z</LastModificationTime>
|
||||
<LastAccessTime>2016-12-31T21:40:23Z</LastAccessTime>
|
||||
<ExpiryTime>2016-12-31T21:34:13Z</ExpiryTime>
|
||||
<Expires>False</Expires>
|
||||
<UsageCount>0</UsageCount>
|
||||
<LocationChanged>2016-12-31T21:43:48Z</LocationChanged>
|
||||
</Times>
|
||||
<String>
|
||||
<Key>att2</Key>
|
||||
<Value>att2value</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>attr1</Key>
|
||||
<Value>att1value
|
||||
|
||||
line1
|
||||
line2</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>Notes</Key>
|
||||
<Value>This is a note!!!
|
||||
|
||||
line1
|
||||
line2</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>Password</Key>
|
||||
<Value ProtectInMemory="True">googpass</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>Title</Key>
|
||||
<Value>Google</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>URL</Key>
|
||||
<Value>google.com</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>UserName</Key>
|
||||
<Value>googleuser</Value>
|
||||
</String>
|
||||
<AutoType>
|
||||
<Enabled>True</Enabled>
|
||||
<DataTransferObfuscation>0</DataTransferObfuscation>
|
||||
</AutoType>
|
||||
<History>
|
||||
<Entry>
|
||||
<UUID>fAa543oYlgnJKkhKag5HLw==</UUID>
|
||||
<IconID>0</IconID>
|
||||
<ForegroundColor />
|
||||
<BackgroundColor />
|
||||
<OverrideURL />
|
||||
<Tags />
|
||||
<Times>
|
||||
<CreationTime>2016-12-31T21:34:13Z</CreationTime>
|
||||
<LastModificationTime>2016-12-31T21:34:40Z</LastModificationTime>
|
||||
<LastAccessTime>2016-12-31T21:34:40Z</LastAccessTime>
|
||||
<ExpiryTime>2016-12-31T21:34:13Z</ExpiryTime>
|
||||
<Expires>False</Expires>
|
||||
<UsageCount>0</UsageCount>
|
||||
<LocationChanged>2016-12-31T21:34:40Z</LocationChanged>
|
||||
</Times>
|
||||
<String>
|
||||
<Key>Notes</Key>
|
||||
<Value>This is a note!!!
|
||||
|
||||
line1
|
||||
line2</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>Password</Key>
|
||||
<Value ProtectInMemory="True">googpass</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>Title</Key>
|
||||
<Value>Google</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>URL</Key>
|
||||
<Value>google.com</Value>
|
||||
</String>
|
||||
<String>
|
||||
<Key>UserName</Key>
|
||||
<Value>googleuser</Value>
|
||||
</String>
|
||||
<AutoType>
|
||||
<Enabled>True</Enabled>
|
||||
<DataTransferObfuscation>0</DataTransferObfuscation>
|
||||
</AutoType>
|
||||
</Entry>
|
||||
</History>
|
||||
</Entry>
|
||||
</Group>
|
||||
</Group>
|
||||
<DeletedObjects />
|
||||
</Root>
|
||||
</KeePassFile>`;
|
||||
import { TestData, TestData1, TestData2 } from "./keepass2-xml-importer-testdata";
|
||||
|
||||
describe("KeePass2 Xml Importer", () => {
|
||||
it("should parse XML data", async () => {
|
||||
@@ -186,4 +9,34 @@ describe("KeePass2 Xml Importer", () => {
|
||||
const result = await importer.parse(TestData);
|
||||
expect(result != null).toBe(true);
|
||||
});
|
||||
|
||||
it("parse XML should contains folders", async () => {
|
||||
const importer = new Importer();
|
||||
const folder = new FolderView();
|
||||
folder.name = "Folder2";
|
||||
const actual = [folder];
|
||||
|
||||
const result = await importer.parse(TestData);
|
||||
expect(result.folders).toEqual(actual);
|
||||
});
|
||||
|
||||
it("parse XML should contains login details", async () => {
|
||||
const importer = new Importer();
|
||||
const result = await importer.parse(TestData);
|
||||
expect(result.ciphers[0].login.uri != null).toBe(true);
|
||||
expect(result.ciphers[0].login.username != null).toBe(true);
|
||||
expect(result.ciphers[0].login.password != null).toBe(true);
|
||||
});
|
||||
|
||||
it("should return error with missing root tag", async () => {
|
||||
const importer = new Importer();
|
||||
const result = await importer.parse(TestData1);
|
||||
expect(result.errorMessage).toBe("Missing `KeePassFile > Root` node.");
|
||||
});
|
||||
|
||||
it("should return error with missing KeePassFile tag", async () => {
|
||||
const importer = new Importer();
|
||||
const result = await importer.parse(TestData2);
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
77
libs/common/spec/importers/keeper-csv-importer.spec.ts
Normal file
77
libs/common/spec/importers/keeper-csv-importer.spec.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { KeeperCsvImporter as Importer } from "@bitwarden/common/importers/keeper/keeper-csv-importer";
|
||||
|
||||
import { testData as TestData } from "./test-data/keeper-csv/testdata.csv";
|
||||
|
||||
describe("Keeper CSV Importer", () => {
|
||||
let importer: Importer;
|
||||
beforeEach(() => {
|
||||
importer = new Importer();
|
||||
});
|
||||
|
||||
it("should parse login data", async () => {
|
||||
const result = await importer.parse(TestData);
|
||||
expect(result != null).toBe(true);
|
||||
|
||||
const cipher = result.ciphers.shift();
|
||||
expect(cipher.name).toEqual("Bar");
|
||||
expect(cipher.login.username).toEqual("john.doe@example.com");
|
||||
expect(cipher.login.password).toEqual("1234567890abcdef");
|
||||
expect(cipher.login.uris.length).toEqual(1);
|
||||
const uriView = cipher.login.uris.shift();
|
||||
expect(uriView.uri).toEqual("https://example.com/");
|
||||
expect(cipher.notes).toEqual("These are some notes.");
|
||||
|
||||
const cipher2 = result.ciphers.shift();
|
||||
expect(cipher2.name).toEqual("Bar 1");
|
||||
expect(cipher2.login.username).toEqual("john.doe1@example.com");
|
||||
expect(cipher2.login.password).toEqual("234567890abcdef1");
|
||||
expect(cipher2.login.uris.length).toEqual(1);
|
||||
const uriView2 = cipher2.login.uris.shift();
|
||||
expect(uriView2.uri).toEqual("https://an.example.com/");
|
||||
expect(cipher2.notes).toBeNull();
|
||||
|
||||
const cipher3 = result.ciphers.shift();
|
||||
expect(cipher3.name).toEqual("Bar 2");
|
||||
expect(cipher3.login.username).toEqual("john.doe2@example.com");
|
||||
expect(cipher3.login.password).toEqual("34567890abcdef12");
|
||||
expect(cipher3.notes).toBeNull();
|
||||
expect(cipher3.login.uris.length).toEqual(1);
|
||||
const uriView3 = cipher3.login.uris.shift();
|
||||
expect(uriView3.uri).toEqual("https://another.example.com/");
|
||||
});
|
||||
|
||||
it("should import TOTP when present", async () => {
|
||||
const result = await importer.parse(TestData);
|
||||
expect(result != null).toBe(true);
|
||||
|
||||
const cipher = result.ciphers.shift();
|
||||
expect(cipher.login.totp).toBeNull();
|
||||
|
||||
const cipher2 = result.ciphers.shift();
|
||||
expect(cipher2.login.totp).toBeNull();
|
||||
|
||||
const cipher3 = result.ciphers.shift();
|
||||
expect(cipher3.login.totp).toEqual(
|
||||
"otpauth://totp/Amazon:me@company.com?secret=JBSWY3DPEHPK3PXP&issuer=Amazon&algorithm=SHA1&digits=6&period=30"
|
||||
);
|
||||
});
|
||||
|
||||
it("should parse custom fields", async () => {
|
||||
const result = await importer.parse(TestData);
|
||||
expect(result != null).toBe(true);
|
||||
|
||||
const cipher = result.ciphers.shift();
|
||||
expect(cipher.fields).toBeNull();
|
||||
|
||||
const cipher2 = result.ciphers.shift();
|
||||
expect(cipher2.fields.length).toBe(2);
|
||||
expect(cipher2.fields[0].name).toEqual("Account ID");
|
||||
expect(cipher2.fields[0].value).toEqual("12345");
|
||||
expect(cipher2.fields[1].name).toEqual("Org ID");
|
||||
expect(cipher2.fields[1].value).toEqual("54321");
|
||||
|
||||
const cipher3 = result.ciphers.shift();
|
||||
expect(cipher3.fields[0].name).toEqual("Account ID");
|
||||
expect(cipher3.fields[0].value).toEqual("23456");
|
||||
});
|
||||
});
|
||||
34
libs/common/spec/importers/passky-json-importer.spec.ts
Normal file
34
libs/common/spec/importers/passky-json-importer.spec.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { PasskyJsonImporter as Importer } from "@bitwarden/common/importers/passky/passky-json-importer";
|
||||
|
||||
import { testData as EncryptedData } from "./test-data/passky-json/passky-encrypted.json";
|
||||
import { testData as UnencryptedData } from "./test-data/passky-json/passky-unencrypted.json";
|
||||
|
||||
describe("Passky Json Importer", () => {
|
||||
let importer: Importer;
|
||||
beforeEach(() => {
|
||||
importer = new Importer();
|
||||
});
|
||||
|
||||
it("should not import encrypted backups", async () => {
|
||||
const testDataJson = JSON.stringify(EncryptedData);
|
||||
const result = await importer.parse(testDataJson);
|
||||
expect(result != null).toBe(true);
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.errorMessage).toBe("Unable to import an encrypted passky backup.");
|
||||
});
|
||||
|
||||
it("should parse login data", async () => {
|
||||
const testDataJson = JSON.stringify(UnencryptedData);
|
||||
const result = await importer.parse(testDataJson);
|
||||
expect(result != null).toBe(true);
|
||||
|
||||
const cipher = result.ciphers.shift();
|
||||
expect(cipher.name).toEqual("https://bitwarden.com/");
|
||||
expect(cipher.login.username).toEqual("testUser");
|
||||
expect(cipher.login.password).toEqual("testPassword");
|
||||
expect(cipher.login.uris.length).toEqual(1);
|
||||
const uriView = cipher.login.uris.shift();
|
||||
expect(uriView.uri).toEqual("https://bitwarden.com/");
|
||||
expect(cipher.notes).toEqual("my notes");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
export const testData = `"Foo","Bar","john.doe@example.com","1234567890abcdef","https://example.com/","These are some notes.",""
|
||||
"Foo","Bar 1","john.doe1@example.com","234567890abcdef1","https://an.example.com/","","","Account ID","12345","Org ID","54321"
|
||||
"Foo\\Baz","Bar 2","john.doe2@example.com","34567890abcdef12","https://another.example.com/","","","Account ID","23456","TFC:Keeper","otpauth://totp/Amazon:me@company.com?secret=JBSWY3DPEHPK3PXP&issuer=Amazon&algorithm=SHA1&digits=6&period=30"
|
||||
`;
|
||||
@@ -0,0 +1,15 @@
|
||||
import { PasskyJsonExport } from "@bitwarden/common/importers/passky/passky-json-types";
|
||||
|
||||
export const testData: PasskyJsonExport = {
|
||||
encrypted: true,
|
||||
passwords: [
|
||||
{
|
||||
website:
|
||||
"w68uw6nCjUI3w7MNYsK7w6xqwqHDlXLCpsOEw4/Dq8KbIMK3w6fCvQJFFcOECsOlwprCqUAawqnDvsKbwrLCsCXCtcOlw4dp",
|
||||
username: "bMKyUC0VPTx5woHCr8K9wpvDgGrClFAKw6VfJTgob8KVwqNoN8KIEA==",
|
||||
password: "XcKxO2FjwqIJPkoHwqrDvcKtXcORw6TDlMOlw7TDvMORfmlNdMKOwq7DocO+",
|
||||
message:
|
||||
"w5jCrWTCgAV1RcO+DsOzw5zCvD5CwqLCtcKtw6sPwpbCmcOxwrfDlcOQw4h1wqomEhNtUkRgwrzCkxrClFBSHsO5wrfCrg==",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import { PasskyJsonExport } from "@bitwarden/common/importers/passky/passky-json-types";
|
||||
|
||||
export const testData: PasskyJsonExport = {
|
||||
encrypted: false,
|
||||
passwords: [
|
||||
{
|
||||
website: "https://bitwarden.com/",
|
||||
username: "testUser",
|
||||
password: "testPassword",
|
||||
message: "my notes",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AppIdService } from "@bitwarden/common/abstractions/appId.service";
|
||||
@@ -67,33 +66,34 @@ export function identityTokenResponseFactory() {
|
||||
}
|
||||
|
||||
describe("LogInStrategy", () => {
|
||||
let cryptoService: SubstituteOf<CryptoService>;
|
||||
let apiService: SubstituteOf<ApiService>;
|
||||
let tokenService: SubstituteOf<TokenService>;
|
||||
let appIdService: SubstituteOf<AppIdService>;
|
||||
let platformUtilsService: SubstituteOf<PlatformUtilsService>;
|
||||
let messagingService: SubstituteOf<MessagingService>;
|
||||
let logService: SubstituteOf<LogService>;
|
||||
let stateService: SubstituteOf<StateService>;
|
||||
let twoFactorService: SubstituteOf<TwoFactorService>;
|
||||
let authService: SubstituteOf<AuthService>;
|
||||
let cryptoService: MockProxy<CryptoService>;
|
||||
let apiService: MockProxy<ApiService>;
|
||||
let tokenService: MockProxy<TokenService>;
|
||||
let appIdService: MockProxy<AppIdService>;
|
||||
let platformUtilsService: MockProxy<PlatformUtilsService>;
|
||||
let messagingService: MockProxy<MessagingService>;
|
||||
let logService: MockProxy<LogService>;
|
||||
let stateService: MockProxy<StateService>;
|
||||
let twoFactorService: MockProxy<TwoFactorService>;
|
||||
let authService: MockProxy<AuthService>;
|
||||
|
||||
let passwordLogInStrategy: PasswordLogInStrategy;
|
||||
let credentials: PasswordLogInCredentials;
|
||||
|
||||
beforeEach(async () => {
|
||||
cryptoService = Substitute.for<CryptoService>();
|
||||
apiService = Substitute.for<ApiService>();
|
||||
tokenService = Substitute.for<TokenService>();
|
||||
appIdService = Substitute.for<AppIdService>();
|
||||
platformUtilsService = Substitute.for<PlatformUtilsService>();
|
||||
messagingService = Substitute.for<MessagingService>();
|
||||
logService = Substitute.for<LogService>();
|
||||
stateService = Substitute.for<StateService>();
|
||||
twoFactorService = Substitute.for<TwoFactorService>();
|
||||
authService = Substitute.for<AuthService>();
|
||||
cryptoService = mock<CryptoService>();
|
||||
apiService = mock<ApiService>();
|
||||
tokenService = mock<TokenService>();
|
||||
appIdService = mock<AppIdService>();
|
||||
platformUtilsService = mock<PlatformUtilsService>();
|
||||
messagingService = mock<MessagingService>();
|
||||
logService = mock<LogService>();
|
||||
stateService = mock<StateService>();
|
||||
twoFactorService = mock<TwoFactorService>();
|
||||
authService = mock<AuthService>();
|
||||
|
||||
appIdService.getAppId().resolves(deviceId);
|
||||
appIdService.getAppId.mockResolvedValue(deviceId);
|
||||
tokenService.decodeToken.calledWith(accessToken).mockResolvedValue(decodedToken);
|
||||
|
||||
// The base class is abstract so we test it via PasswordLogInStrategy
|
||||
passwordLogInStrategy = new PasswordLogInStrategy(
|
||||
@@ -113,12 +113,11 @@ describe("LogInStrategy", () => {
|
||||
|
||||
describe("base class", () => {
|
||||
it("sets the local environment after a successful login", async () => {
|
||||
apiService.postIdentityToken(Arg.any()).resolves(identityTokenResponseFactory());
|
||||
tokenService.decodeToken(accessToken).resolves(decodedToken);
|
||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||
|
||||
await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
stateService.received(1).addAccount(
|
||||
expect(stateService.addAccount).toHaveBeenCalledWith(
|
||||
new Account({
|
||||
profile: {
|
||||
...new AccountProfile(),
|
||||
@@ -140,10 +139,9 @@ describe("LogInStrategy", () => {
|
||||
},
|
||||
})
|
||||
);
|
||||
cryptoService.received(1).setEncKey(encKey);
|
||||
cryptoService.received(1).setEncPrivateKey(privateKey);
|
||||
|
||||
messagingService.received(1).send("loggedIn");
|
||||
expect(cryptoService.setEncKey).toHaveBeenCalledWith(encKey);
|
||||
expect(cryptoService.setEncPrivateKey).toHaveBeenCalledWith(privateKey);
|
||||
expect(messagingService.send).toHaveBeenCalledWith("loggedIn");
|
||||
});
|
||||
|
||||
it("builds AuthResult", async () => {
|
||||
@@ -151,16 +149,16 @@ describe("LogInStrategy", () => {
|
||||
tokenResponse.forcePasswordReset = true;
|
||||
tokenResponse.resetMasterPassword = true;
|
||||
|
||||
apiService.postIdentityToken(Arg.any()).resolves(tokenResponse);
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
|
||||
const result = await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
const expected = new AuthResult();
|
||||
expected.forcePasswordReset = true;
|
||||
expected.resetMasterPassword = true;
|
||||
expected.twoFactorProviders = null;
|
||||
expected.captchaSiteKey = "";
|
||||
expect(result).toEqual(expected);
|
||||
expect(result).toEqual({
|
||||
forcePasswordReset: true,
|
||||
resetMasterPassword: true,
|
||||
twoFactorProviders: null,
|
||||
captchaSiteKey: "",
|
||||
} as AuthResult);
|
||||
});
|
||||
|
||||
it("rejects login if CAPTCHA is required", async () => {
|
||||
@@ -171,12 +169,12 @@ describe("LogInStrategy", () => {
|
||||
HCaptcha_SiteKey: captchaSiteKey,
|
||||
});
|
||||
|
||||
apiService.postIdentityToken(Arg.any()).resolves(tokenResponse);
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
|
||||
const result = await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
stateService.didNotReceive().addAccount(Arg.any());
|
||||
messagingService.didNotReceive().send(Arg.any());
|
||||
expect(stateService.addAccount).not.toHaveBeenCalled();
|
||||
expect(messagingService.send).not.toHaveBeenCalled();
|
||||
|
||||
const expected = new AuthResult();
|
||||
expected.captchaSiteKey = captchaSiteKey;
|
||||
@@ -186,13 +184,20 @@ describe("LogInStrategy", () => {
|
||||
it("makes a new public and private key for an old account", async () => {
|
||||
const tokenResponse = identityTokenResponseFactory();
|
||||
tokenResponse.privateKey = null;
|
||||
cryptoService.makeKeyPair(Arg.any()).resolves(["PUBLIC_KEY", new EncString("PRIVATE_KEY")]);
|
||||
cryptoService.makeKeyPair.mockResolvedValue(["PUBLIC_KEY", new EncString("PRIVATE_KEY")]);
|
||||
|
||||
apiService.postIdentityToken(Arg.any()).resolves(tokenResponse);
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
|
||||
await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
apiService.received(1).postAccountKeys(Arg.any());
|
||||
// User key must be set before the new RSA keypair is generated, otherwise we can't decrypt the EncKey
|
||||
expect(cryptoService.setKey).toHaveBeenCalled();
|
||||
expect(cryptoService.makeKeyPair).toHaveBeenCalled();
|
||||
expect(cryptoService.setKey.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
cryptoService.makeKeyPair.mock.invocationCallOrder[0]
|
||||
);
|
||||
|
||||
expect(apiService.postAccountKeys).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -206,12 +211,12 @@ describe("LogInStrategy", () => {
|
||||
error_description: "Two factor required.",
|
||||
});
|
||||
|
||||
apiService.postIdentityToken(Arg.any()).resolves(tokenResponse);
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
|
||||
const result = await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
stateService.didNotReceive().addAccount(Arg.any());
|
||||
messagingService.didNotReceive().send(Arg.any());
|
||||
expect(stateService.addAccount).not.toHaveBeenCalled();
|
||||
expect(messagingService.send).not.toHaveBeenCalled();
|
||||
|
||||
const expected = new AuthResult();
|
||||
expected.twoFactorProviders = new Map<TwoFactorProviderType, { [key: string]: string }>();
|
||||
@@ -220,26 +225,25 @@ describe("LogInStrategy", () => {
|
||||
});
|
||||
|
||||
it("sends stored 2FA token to server", async () => {
|
||||
tokenService.getTwoFactorToken().resolves(twoFactorToken);
|
||||
apiService.postIdentityToken(Arg.any()).resolves(identityTokenResponseFactory());
|
||||
tokenService.getTwoFactorToken.mockResolvedValue(twoFactorToken);
|
||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||
|
||||
await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
apiService.received(1).postIdentityToken(
|
||||
Arg.is((actual) => {
|
||||
const passwordTokenRequest = actual as any;
|
||||
return (
|
||||
passwordTokenRequest.twoFactor.provider === TwoFactorProviderType.Remember &&
|
||||
passwordTokenRequest.twoFactor.token === twoFactorToken &&
|
||||
passwordTokenRequest.twoFactor.remember === false
|
||||
);
|
||||
expect(apiService.postIdentityToken).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
twoFactor: {
|
||||
provider: TwoFactorProviderType.Remember,
|
||||
token: twoFactorToken,
|
||||
remember: false,
|
||||
} as TokenTwoFactorRequest,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("sends 2FA token provided by user to server (single step)", async () => {
|
||||
// This occurs if the user enters the 2FA code as an argument in the CLI
|
||||
apiService.postIdentityToken(Arg.any()).resolves(identityTokenResponseFactory());
|
||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||
credentials.twoFactor = new TokenTwoFactorRequest(
|
||||
twoFactorProviderType,
|
||||
twoFactorToken,
|
||||
@@ -248,14 +252,13 @@ describe("LogInStrategy", () => {
|
||||
|
||||
await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
apiService.received(1).postIdentityToken(
|
||||
Arg.is((actual) => {
|
||||
const passwordTokenRequest = actual as any;
|
||||
return (
|
||||
passwordTokenRequest.twoFactor.provider === twoFactorProviderType &&
|
||||
passwordTokenRequest.twoFactor.token === twoFactorToken &&
|
||||
passwordTokenRequest.twoFactor.remember === twoFactorRemember
|
||||
);
|
||||
expect(apiService.postIdentityToken).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
twoFactor: {
|
||||
provider: twoFactorProviderType,
|
||||
token: twoFactorToken,
|
||||
remember: twoFactorRemember,
|
||||
} as TokenTwoFactorRequest,
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -269,21 +272,20 @@ describe("LogInStrategy", () => {
|
||||
null
|
||||
);
|
||||
|
||||
apiService.postIdentityToken(Arg.any()).resolves(identityTokenResponseFactory());
|
||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||
|
||||
await passwordLogInStrategy.logInTwoFactor(
|
||||
new TokenTwoFactorRequest(twoFactorProviderType, twoFactorToken, twoFactorRemember),
|
||||
null
|
||||
);
|
||||
|
||||
apiService.received(1).postIdentityToken(
|
||||
Arg.is((actual) => {
|
||||
const passwordTokenRequest = actual as any;
|
||||
return (
|
||||
passwordTokenRequest.twoFactor.provider === twoFactorProviderType &&
|
||||
passwordTokenRequest.twoFactor.token === twoFactorToken &&
|
||||
passwordTokenRequest.twoFactor.remember === twoFactorRemember
|
||||
);
|
||||
expect(apiService.postIdentityToken).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
twoFactor: {
|
||||
provider: twoFactorProviderType,
|
||||
token: twoFactorToken,
|
||||
remember: twoFactorRemember,
|
||||
} as TokenTwoFactorRequest,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AppIdService } from "@bitwarden/common/abstractions/appId.service";
|
||||
@@ -31,41 +30,43 @@ const preloginKey = new SymmetricCryptoKey(
|
||||
const deviceId = Utils.newGuid();
|
||||
|
||||
describe("PasswordLogInStrategy", () => {
|
||||
let cryptoService: SubstituteOf<CryptoService>;
|
||||
let apiService: SubstituteOf<ApiService>;
|
||||
let tokenService: SubstituteOf<TokenService>;
|
||||
let appIdService: SubstituteOf<AppIdService>;
|
||||
let platformUtilsService: SubstituteOf<PlatformUtilsService>;
|
||||
let messagingService: SubstituteOf<MessagingService>;
|
||||
let logService: SubstituteOf<LogService>;
|
||||
let stateService: SubstituteOf<StateService>;
|
||||
let twoFactorService: SubstituteOf<TwoFactorService>;
|
||||
let authService: SubstituteOf<AuthService>;
|
||||
let cryptoService: MockProxy<CryptoService>;
|
||||
let apiService: MockProxy<ApiService>;
|
||||
let tokenService: MockProxy<TokenService>;
|
||||
let appIdService: MockProxy<AppIdService>;
|
||||
let platformUtilsService: MockProxy<PlatformUtilsService>;
|
||||
let messagingService: MockProxy<MessagingService>;
|
||||
let logService: MockProxy<LogService>;
|
||||
let stateService: MockProxy<StateService>;
|
||||
let twoFactorService: MockProxy<TwoFactorService>;
|
||||
let authService: MockProxy<AuthService>;
|
||||
|
||||
let passwordLogInStrategy: PasswordLogInStrategy;
|
||||
let credentials: PasswordLogInCredentials;
|
||||
|
||||
beforeEach(async () => {
|
||||
cryptoService = Substitute.for<CryptoService>();
|
||||
apiService = Substitute.for<ApiService>();
|
||||
tokenService = Substitute.for<TokenService>();
|
||||
appIdService = Substitute.for<AppIdService>();
|
||||
platformUtilsService = Substitute.for<PlatformUtilsService>();
|
||||
messagingService = Substitute.for<MessagingService>();
|
||||
logService = Substitute.for<LogService>();
|
||||
stateService = Substitute.for<StateService>();
|
||||
twoFactorService = Substitute.for<TwoFactorService>();
|
||||
authService = Substitute.for<AuthService>();
|
||||
cryptoService = mock<CryptoService>();
|
||||
apiService = mock<ApiService>();
|
||||
tokenService = mock<TokenService>();
|
||||
appIdService = mock<AppIdService>();
|
||||
platformUtilsService = mock<PlatformUtilsService>();
|
||||
messagingService = mock<MessagingService>();
|
||||
logService = mock<LogService>();
|
||||
stateService = mock<StateService>();
|
||||
twoFactorService = mock<TwoFactorService>();
|
||||
authService = mock<AuthService>();
|
||||
|
||||
appIdService.getAppId().resolves(deviceId);
|
||||
tokenService.getTwoFactorToken().resolves(null);
|
||||
appIdService.getAppId.mockResolvedValue(deviceId);
|
||||
tokenService.decodeToken.mockResolvedValue({});
|
||||
|
||||
authService.makePreloginKey(Arg.any(), Arg.any()).resolves(preloginKey);
|
||||
authService.makePreloginKey.mockResolvedValue(preloginKey);
|
||||
|
||||
cryptoService.hashPassword(masterPassword, Arg.any()).resolves(hashedPassword);
|
||||
cryptoService
|
||||
.hashPassword(masterPassword, Arg.any(), HashPurpose.LocalAuthorization)
|
||||
.resolves(localHashedPassword);
|
||||
cryptoService.hashPassword
|
||||
.calledWith(masterPassword, expect.anything(), undefined)
|
||||
.mockResolvedValue(hashedPassword);
|
||||
cryptoService.hashPassword
|
||||
.calledWith(masterPassword, expect.anything(), HashPurpose.LocalAuthorization)
|
||||
.mockResolvedValue(localHashedPassword);
|
||||
|
||||
passwordLogInStrategy = new PasswordLogInStrategy(
|
||||
cryptoService,
|
||||
@@ -81,23 +82,24 @@ describe("PasswordLogInStrategy", () => {
|
||||
);
|
||||
credentials = new PasswordLogInCredentials(email, masterPassword);
|
||||
|
||||
apiService.postIdentityToken(Arg.any()).resolves(identityTokenResponseFactory());
|
||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||
});
|
||||
|
||||
it("sends master password credentials to the server", async () => {
|
||||
await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
apiService.received(1).postIdentityToken(
|
||||
Arg.is((actual) => {
|
||||
const passwordTokenRequest = actual as any; // Need to access private fields
|
||||
return (
|
||||
passwordTokenRequest.email === email &&
|
||||
passwordTokenRequest.masterPasswordHash === hashedPassword &&
|
||||
passwordTokenRequest.device.identifier === deviceId &&
|
||||
passwordTokenRequest.twoFactor.provider == null &&
|
||||
passwordTokenRequest.twoFactor.token == null &&
|
||||
passwordTokenRequest.captchaResponse == null
|
||||
);
|
||||
expect(apiService.postIdentityToken).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
email: email,
|
||||
masterPasswordHash: hashedPassword,
|
||||
device: expect.objectContaining({
|
||||
identifier: deviceId,
|
||||
}),
|
||||
twoFactor: expect.objectContaining({
|
||||
provider: null,
|
||||
token: null,
|
||||
}),
|
||||
captchaResponse: undefined,
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -105,7 +107,7 @@ describe("PasswordLogInStrategy", () => {
|
||||
it("sets the local environment after a successful login", async () => {
|
||||
await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
cryptoService.received(1).setKey(preloginKey);
|
||||
cryptoService.received(1).setKeyHash(localHashedPassword);
|
||||
expect(cryptoService.setKey).toHaveBeenCalledWith(preloginKey);
|
||||
expect(cryptoService.setKeyHash).toHaveBeenCalledWith(localHashedPassword);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AppIdService } from "@bitwarden/common/abstractions/appId.service";
|
||||
@@ -18,23 +17,21 @@ import { SsoLogInCredentials } from "@bitwarden/common/models/domain/log-in-cred
|
||||
import { identityTokenResponseFactory } from "./logIn.strategy.spec";
|
||||
|
||||
describe("SsoLogInStrategy", () => {
|
||||
let cryptoService: SubstituteOf<CryptoService>;
|
||||
let apiService: SubstituteOf<ApiService>;
|
||||
let tokenService: SubstituteOf<TokenService>;
|
||||
let appIdService: SubstituteOf<AppIdService>;
|
||||
let platformUtilsService: SubstituteOf<PlatformUtilsService>;
|
||||
let messagingService: SubstituteOf<MessagingService>;
|
||||
let logService: SubstituteOf<LogService>;
|
||||
let keyConnectorService: SubstituteOf<KeyConnectorService>;
|
||||
let stateService: SubstituteOf<StateService>;
|
||||
let twoFactorService: SubstituteOf<TwoFactorService>;
|
||||
let cryptoService: MockProxy<CryptoService>;
|
||||
let apiService: MockProxy<ApiService>;
|
||||
let tokenService: MockProxy<TokenService>;
|
||||
let appIdService: MockProxy<AppIdService>;
|
||||
let platformUtilsService: MockProxy<PlatformUtilsService>;
|
||||
let messagingService: MockProxy<MessagingService>;
|
||||
let logService: MockProxy<LogService>;
|
||||
let stateService: MockProxy<StateService>;
|
||||
let twoFactorService: MockProxy<TwoFactorService>;
|
||||
let keyConnectorService: MockProxy<KeyConnectorService>;
|
||||
|
||||
let ssoLogInStrategy: SsoLogInStrategy;
|
||||
let credentials: SsoLogInCredentials;
|
||||
|
||||
const deviceId = Utils.newGuid();
|
||||
const encKey = "ENC_KEY";
|
||||
const privateKey = "PRIVATE_KEY";
|
||||
const keyConnectorUrl = "KEY_CONNECTOR_URL";
|
||||
|
||||
const ssoCode = "SSO_CODE";
|
||||
@@ -43,19 +40,20 @@ describe("SsoLogInStrategy", () => {
|
||||
const ssoOrgId = "SSO_ORG_ID";
|
||||
|
||||
beforeEach(async () => {
|
||||
cryptoService = Substitute.for<CryptoService>();
|
||||
apiService = Substitute.for<ApiService>();
|
||||
tokenService = Substitute.for<TokenService>();
|
||||
appIdService = Substitute.for<AppIdService>();
|
||||
platformUtilsService = Substitute.for<PlatformUtilsService>();
|
||||
messagingService = Substitute.for<MessagingService>();
|
||||
logService = Substitute.for<LogService>();
|
||||
stateService = Substitute.for<StateService>();
|
||||
keyConnectorService = Substitute.for<KeyConnectorService>();
|
||||
twoFactorService = Substitute.for<TwoFactorService>();
|
||||
cryptoService = mock<CryptoService>();
|
||||
apiService = mock<ApiService>();
|
||||
tokenService = mock<TokenService>();
|
||||
appIdService = mock<AppIdService>();
|
||||
platformUtilsService = mock<PlatformUtilsService>();
|
||||
messagingService = mock<MessagingService>();
|
||||
logService = mock<LogService>();
|
||||
stateService = mock<StateService>();
|
||||
twoFactorService = mock<TwoFactorService>();
|
||||
keyConnectorService = mock<KeyConnectorService>();
|
||||
|
||||
tokenService.getTwoFactorToken().resolves(null);
|
||||
appIdService.getAppId().resolves(deviceId);
|
||||
tokenService.getTwoFactorToken.mockResolvedValue(null);
|
||||
appIdService.getAppId.mockResolvedValue(deviceId);
|
||||
tokenService.decodeToken.mockResolvedValue({});
|
||||
|
||||
ssoLogInStrategy = new SsoLogInStrategy(
|
||||
cryptoService,
|
||||
@@ -73,21 +71,22 @@ describe("SsoLogInStrategy", () => {
|
||||
});
|
||||
|
||||
it("sends SSO information to server", async () => {
|
||||
apiService.postIdentityToken(Arg.any()).resolves(identityTokenResponseFactory());
|
||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||
|
||||
await ssoLogInStrategy.logIn(credentials);
|
||||
|
||||
apiService.received(1).postIdentityToken(
|
||||
Arg.is((actual) => {
|
||||
const ssoTokenRequest = actual as any;
|
||||
return (
|
||||
ssoTokenRequest.code === ssoCode &&
|
||||
ssoTokenRequest.codeVerifier === ssoCodeVerifier &&
|
||||
ssoTokenRequest.redirectUri === ssoRedirectUrl &&
|
||||
ssoTokenRequest.device.identifier === deviceId &&
|
||||
ssoTokenRequest.twoFactor.provider == null &&
|
||||
ssoTokenRequest.twoFactor.token == null
|
||||
);
|
||||
expect(apiService.postIdentityToken).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
code: ssoCode,
|
||||
codeVerifier: ssoCodeVerifier,
|
||||
redirectUri: ssoRedirectUrl,
|
||||
device: expect.objectContaining({
|
||||
identifier: deviceId,
|
||||
}),
|
||||
twoFactor: expect.objectContaining({
|
||||
provider: null,
|
||||
token: null,
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -95,23 +94,23 @@ describe("SsoLogInStrategy", () => {
|
||||
it("does not set keys for new SSO user flow", async () => {
|
||||
const tokenResponse = identityTokenResponseFactory();
|
||||
tokenResponse.key = null;
|
||||
apiService.postIdentityToken(Arg.any()).resolves(tokenResponse);
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
|
||||
await ssoLogInStrategy.logIn(credentials);
|
||||
|
||||
cryptoService.didNotReceive().setEncPrivateKey(privateKey);
|
||||
cryptoService.didNotReceive().setEncKey(encKey);
|
||||
expect(cryptoService.setEncPrivateKey).not.toHaveBeenCalled();
|
||||
expect(cryptoService.setEncKey).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("gets and sets KeyConnector key for enrolled user", async () => {
|
||||
const tokenResponse = identityTokenResponseFactory();
|
||||
tokenResponse.keyConnectorUrl = keyConnectorUrl;
|
||||
|
||||
apiService.postIdentityToken(Arg.any()).resolves(tokenResponse);
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
|
||||
await ssoLogInStrategy.logIn(credentials);
|
||||
|
||||
keyConnectorService.received(1).getAndSetKey(keyConnectorUrl);
|
||||
expect(keyConnectorService.getAndSetKey).toHaveBeenCalledWith(keyConnectorUrl);
|
||||
});
|
||||
|
||||
it("converts new SSO user to Key Connector on first login", async () => {
|
||||
@@ -119,10 +118,13 @@ describe("SsoLogInStrategy", () => {
|
||||
tokenResponse.keyConnectorUrl = keyConnectorUrl;
|
||||
tokenResponse.key = null;
|
||||
|
||||
apiService.postIdentityToken(Arg.any()).resolves(tokenResponse);
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
|
||||
await ssoLogInStrategy.logIn(credentials);
|
||||
|
||||
keyConnectorService.received(1).convertNewSsoUserToKeyConnector(tokenResponse, ssoOrgId);
|
||||
expect(keyConnectorService.convertNewSsoUserToKeyConnector).toHaveBeenCalledWith(
|
||||
tokenResponse,
|
||||
ssoOrgId
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AppIdService } from "@bitwarden/common/abstractions/appId.service";
|
||||
@@ -19,17 +18,17 @@ import { UserApiLogInCredentials } from "@bitwarden/common/models/domain/log-in-
|
||||
import { identityTokenResponseFactory } from "./logIn.strategy.spec";
|
||||
|
||||
describe("UserApiLogInStrategy", () => {
|
||||
let cryptoService: SubstituteOf<CryptoService>;
|
||||
let apiService: SubstituteOf<ApiService>;
|
||||
let tokenService: SubstituteOf<TokenService>;
|
||||
let appIdService: SubstituteOf<AppIdService>;
|
||||
let platformUtilsService: SubstituteOf<PlatformUtilsService>;
|
||||
let messagingService: SubstituteOf<MessagingService>;
|
||||
let logService: SubstituteOf<LogService>;
|
||||
let environmentService: SubstituteOf<EnvironmentService>;
|
||||
let keyConnectorService: SubstituteOf<KeyConnectorService>;
|
||||
let stateService: SubstituteOf<StateService>;
|
||||
let twoFactorService: SubstituteOf<TwoFactorService>;
|
||||
let cryptoService: MockProxy<CryptoService>;
|
||||
let apiService: MockProxy<ApiService>;
|
||||
let tokenService: MockProxy<TokenService>;
|
||||
let appIdService: MockProxy<AppIdService>;
|
||||
let platformUtilsService: MockProxy<PlatformUtilsService>;
|
||||
let messagingService: MockProxy<MessagingService>;
|
||||
let logService: MockProxy<LogService>;
|
||||
let stateService: MockProxy<StateService>;
|
||||
let twoFactorService: MockProxy<TwoFactorService>;
|
||||
let keyConnectorService: MockProxy<KeyConnectorService>;
|
||||
let environmentService: MockProxy<EnvironmentService>;
|
||||
|
||||
let apiLogInStrategy: UserApiLogInStrategy;
|
||||
let credentials: UserApiLogInCredentials;
|
||||
@@ -40,20 +39,21 @@ describe("UserApiLogInStrategy", () => {
|
||||
const apiClientSecret = "API_CLIENT_SECRET";
|
||||
|
||||
beforeEach(async () => {
|
||||
cryptoService = Substitute.for<CryptoService>();
|
||||
apiService = Substitute.for<ApiService>();
|
||||
tokenService = Substitute.for<TokenService>();
|
||||
appIdService = Substitute.for<AppIdService>();
|
||||
platformUtilsService = Substitute.for<PlatformUtilsService>();
|
||||
messagingService = Substitute.for<MessagingService>();
|
||||
logService = Substitute.for<LogService>();
|
||||
environmentService = Substitute.for<EnvironmentService>();
|
||||
stateService = Substitute.for<StateService>();
|
||||
keyConnectorService = Substitute.for<KeyConnectorService>();
|
||||
twoFactorService = Substitute.for<TwoFactorService>();
|
||||
cryptoService = mock<CryptoService>();
|
||||
apiService = mock<ApiService>();
|
||||
tokenService = mock<TokenService>();
|
||||
appIdService = mock<AppIdService>();
|
||||
platformUtilsService = mock<PlatformUtilsService>();
|
||||
messagingService = mock<MessagingService>();
|
||||
logService = mock<LogService>();
|
||||
stateService = mock<StateService>();
|
||||
twoFactorService = mock<TwoFactorService>();
|
||||
keyConnectorService = mock<KeyConnectorService>();
|
||||
environmentService = mock<EnvironmentService>();
|
||||
|
||||
appIdService.getAppId().resolves(deviceId);
|
||||
tokenService.getTwoFactorToken().resolves(null);
|
||||
appIdService.getAppId.mockResolvedValue(deviceId);
|
||||
tokenService.getTwoFactorToken.mockResolvedValue(null);
|
||||
tokenService.decodeToken.mockResolvedValue({});
|
||||
|
||||
apiLogInStrategy = new UserApiLogInStrategy(
|
||||
cryptoService,
|
||||
@@ -73,43 +73,43 @@ describe("UserApiLogInStrategy", () => {
|
||||
});
|
||||
|
||||
it("sends api key credentials to the server", async () => {
|
||||
apiService.postIdentityToken(Arg.any()).resolves(identityTokenResponseFactory());
|
||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||
await apiLogInStrategy.logIn(credentials);
|
||||
|
||||
apiService.received(1).postIdentityToken(
|
||||
Arg.is((actual) => {
|
||||
const apiTokenRequest = actual as any;
|
||||
return (
|
||||
apiTokenRequest.clientId === apiClientId &&
|
||||
apiTokenRequest.clientSecret === apiClientSecret &&
|
||||
apiTokenRequest.device.identifier === deviceId &&
|
||||
apiTokenRequest.twoFactor.provider == null &&
|
||||
apiTokenRequest.twoFactor.token == null &&
|
||||
apiTokenRequest.captchaResponse == null
|
||||
);
|
||||
expect(apiService.postIdentityToken).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
clientId: apiClientId,
|
||||
clientSecret: apiClientSecret,
|
||||
device: expect.objectContaining({
|
||||
identifier: deviceId,
|
||||
}),
|
||||
twoFactor: expect.objectContaining({
|
||||
provider: null,
|
||||
token: null,
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("sets the local environment after a successful login", async () => {
|
||||
apiService.postIdentityToken(Arg.any()).resolves(identityTokenResponseFactory());
|
||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||
|
||||
await apiLogInStrategy.logIn(credentials);
|
||||
|
||||
stateService.received(1).setApiKeyClientId(apiClientId);
|
||||
stateService.received(1).setApiKeyClientSecret(apiClientSecret);
|
||||
stateService.received(1).addAccount(Arg.any());
|
||||
expect(stateService.setApiKeyClientId).toHaveBeenCalledWith(apiClientId);
|
||||
expect(stateService.setApiKeyClientSecret).toHaveBeenCalledWith(apiClientSecret);
|
||||
expect(stateService.addAccount).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("gets and sets the Key Connector key from environmentUrl", async () => {
|
||||
const tokenResponse = identityTokenResponseFactory();
|
||||
tokenResponse.apiUseKeyConnector = true;
|
||||
|
||||
apiService.postIdentityToken(Arg.any()).resolves(tokenResponse);
|
||||
environmentService.getKeyConnectorUrl().returns(keyConnectorUrl);
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
environmentService.getKeyConnectorUrl.mockReturnValue(keyConnectorUrl);
|
||||
|
||||
await apiLogInStrategy.logIn(credentials);
|
||||
|
||||
keyConnectorService.received(1).getAndSetKey(keyConnectorUrl);
|
||||
expect(keyConnectorService.getAndSetKey).toHaveBeenCalledWith(keyConnectorUrl);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -241,4 +241,89 @@ describe("Utils Service", () => {
|
||||
expect(Utils.fromByteStringToArray(null)).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapToRecord", () => {
|
||||
it("should handle null", () => {
|
||||
expect(Utils.mapToRecord(null)).toEqual(null);
|
||||
});
|
||||
|
||||
it("should handle empty map", () => {
|
||||
expect(Utils.mapToRecord(new Map())).toEqual({});
|
||||
});
|
||||
|
||||
it("should handle convert a Map to a Record", () => {
|
||||
const map = new Map([
|
||||
["key1", "value1"],
|
||||
["key2", "value2"],
|
||||
]);
|
||||
expect(Utils.mapToRecord(map)).toEqual({ key1: "value1", key2: "value2" });
|
||||
});
|
||||
|
||||
it("should handle convert a Map to a Record with non-string keys", () => {
|
||||
const map = new Map([
|
||||
[1, "value1"],
|
||||
[2, "value2"],
|
||||
]);
|
||||
const result = Utils.mapToRecord(map);
|
||||
expect(result).toEqual({ 1: "value1", 2: "value2" });
|
||||
expect(Utils.recordToMap(result)).toEqual(map);
|
||||
});
|
||||
|
||||
it("should not convert an object if it's not a map", () => {
|
||||
const obj = { key1: "value1", key2: "value2" };
|
||||
expect(Utils.mapToRecord(obj as any)).toEqual(obj);
|
||||
});
|
||||
});
|
||||
|
||||
describe("recordToMap", () => {
|
||||
it("should handle null", () => {
|
||||
expect(Utils.recordToMap(null)).toEqual(null);
|
||||
});
|
||||
|
||||
it("should handle empty record", () => {
|
||||
expect(Utils.recordToMap({})).toEqual(new Map());
|
||||
});
|
||||
|
||||
it("should handle convert a Record to a Map", () => {
|
||||
const record = { key1: "value1", key2: "value2" };
|
||||
expect(Utils.recordToMap(record)).toEqual(new Map(Object.entries(record)));
|
||||
});
|
||||
|
||||
it("should handle convert a Record to a Map with non-string keys", () => {
|
||||
const record = { 1: "value1", 2: "value2" };
|
||||
const result = Utils.recordToMap(record);
|
||||
expect(result).toEqual(
|
||||
new Map([
|
||||
[1, "value1"],
|
||||
[2, "value2"],
|
||||
])
|
||||
);
|
||||
expect(Utils.mapToRecord(result)).toEqual(record);
|
||||
});
|
||||
|
||||
it("should not convert an object if already a map", () => {
|
||||
const map = new Map([
|
||||
["key1", "value1"],
|
||||
["key2", "value2"],
|
||||
]);
|
||||
expect(Utils.recordToMap(map as any)).toEqual(map);
|
||||
});
|
||||
});
|
||||
|
||||
describe("encodeRFC3986URIComponent", () => {
|
||||
it("returns input string with expected encoded chars", () => {
|
||||
expect(Utils.encodeRFC3986URIComponent("test'user@example.com")).toBe(
|
||||
"test%27user%40example.com"
|
||||
);
|
||||
expect(Utils.encodeRFC3986URIComponent("(test)user@example.com")).toBe(
|
||||
"%28test%29user%40example.com"
|
||||
);
|
||||
expect(Utils.encodeRFC3986URIComponent("testuser!@example.com")).toBe(
|
||||
"testuser%21%40example.com"
|
||||
);
|
||||
expect(Utils.encodeRFC3986URIComponent("Test*User@example.com")).toBe(
|
||||
"Test%2AUser%40example.com"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||
@@ -8,13 +7,15 @@ import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
|
||||
import { CipherType } from "@bitwarden/common/enums/cipherType";
|
||||
import { KdfType } from "@bitwarden/common/enums/kdfType";
|
||||
import { KdfType, DEFAULT_KDF_ITERATIONS } from "@bitwarden/common/enums/kdfType";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { Cipher } from "@bitwarden/common/models/domain/cipher";
|
||||
import { EncString } from "@bitwarden/common/models/domain/enc-string";
|
||||
import { Folder } from "@bitwarden/common/models/domain/folder";
|
||||
import { Login } from "@bitwarden/common/models/domain/login";
|
||||
import { CipherWithIdExport as CipherExport } from "@bitwarden/common/models/export/cipher-with-ids.export";
|
||||
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
|
||||
import { FolderView } from "@bitwarden/common/models/view/folder.view";
|
||||
import { LoginView } from "@bitwarden/common/models/view/login.view";
|
||||
import { ExportService } from "@bitwarden/common/services/export.service";
|
||||
|
||||
@@ -32,6 +33,10 @@ const UserCipherDomains = [
|
||||
generateCipherDomain(true),
|
||||
];
|
||||
|
||||
const UserFolderViews = [generateFolderView(), generateFolderView()];
|
||||
|
||||
const UserFolders = [generateFolder(), generateFolder()];
|
||||
|
||||
function generateCipherView(deleted: boolean) {
|
||||
return BuildTestObject(
|
||||
{
|
||||
@@ -72,6 +77,26 @@ function generateCipherDomain(deleted: boolean) {
|
||||
);
|
||||
}
|
||||
|
||||
function generateFolderView() {
|
||||
return BuildTestObject(
|
||||
{
|
||||
id: GetUniqueString("id"),
|
||||
name: GetUniqueString("name"),
|
||||
revisionDate: new Date(),
|
||||
},
|
||||
FolderView
|
||||
);
|
||||
}
|
||||
|
||||
function generateFolder() {
|
||||
const actual = Folder.fromJSON({
|
||||
revisionDate: new Date("2022-08-04T01:06:40.441Z").toISOString(),
|
||||
name: "name",
|
||||
id: "id",
|
||||
});
|
||||
return actual;
|
||||
}
|
||||
|
||||
function expectEqualCiphers(ciphers: CipherView[] | Cipher[], jsonResult: string) {
|
||||
const actual = JSON.stringify(JSON.parse(jsonResult).items);
|
||||
const items: CipherExport[] = [];
|
||||
@@ -84,6 +109,34 @@ function expectEqualCiphers(ciphers: CipherView[] | Cipher[], jsonResult: string
|
||||
expect(actual).toEqual(JSON.stringify(items));
|
||||
}
|
||||
|
||||
function expectEqualFolderViews(folderviews: FolderView[] | Folder[], jsonResult: string) {
|
||||
const actual = JSON.stringify(JSON.parse(jsonResult).folders);
|
||||
const folders: FolderResponse[] = [];
|
||||
folderviews.forEach((c) => {
|
||||
const folder = new FolderResponse();
|
||||
folder.id = c.id;
|
||||
folder.name = c.name.toString();
|
||||
folders.push(folder);
|
||||
});
|
||||
|
||||
expect(actual.length).toBeGreaterThan(0);
|
||||
expect(actual).toEqual(JSON.stringify(folders));
|
||||
}
|
||||
|
||||
function expectEqualFolders(folders: Folder[], jsonResult: string) {
|
||||
const actual = JSON.stringify(JSON.parse(jsonResult).folders);
|
||||
const items: Folder[] = [];
|
||||
folders.forEach((c) => {
|
||||
const item = new Folder();
|
||||
item.id = c.id;
|
||||
item.name = c.name;
|
||||
items.push(item);
|
||||
});
|
||||
|
||||
expect(actual.length).toBeGreaterThan(0);
|
||||
expect(actual).toEqual(JSON.stringify(items));
|
||||
}
|
||||
|
||||
describe("ExportService", () => {
|
||||
let exportService: ExportService;
|
||||
let apiService: SubstituteOf<ApiService>;
|
||||
@@ -99,8 +152,8 @@ describe("ExportService", () => {
|
||||
folderService = Substitute.for<FolderService>();
|
||||
cryptoService = Substitute.for<CryptoService>();
|
||||
|
||||
folderService.folderViews$.returns(new BehaviorSubject([]));
|
||||
folderService.folders$.returns(new BehaviorSubject([]));
|
||||
folderService.getAllDecryptedFromState().resolves(UserFolderViews);
|
||||
folderService.getAllFromState().resolves(UserFolders);
|
||||
|
||||
exportService = new ExportService(
|
||||
folderService,
|
||||
@@ -179,7 +232,7 @@ describe("ExportService", () => {
|
||||
});
|
||||
|
||||
it("specifies kdfIterations", () => {
|
||||
expect(exportObject.kdfIterations).toEqual(100000);
|
||||
expect(exportObject.kdfIterations).toEqual(DEFAULT_KDF_ITERATIONS);
|
||||
});
|
||||
|
||||
it("has kdfType", () => {
|
||||
@@ -208,4 +261,25 @@ describe("ExportService", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("exported unencrypted object contains folders", async () => {
|
||||
cipherService.getAllDecrypted().resolves(UserCipherViews.slice(0, 1));
|
||||
await folderService.getAllDecryptedFromState();
|
||||
const actual = await exportService.getExport("json");
|
||||
|
||||
expectEqualFolderViews(UserFolderViews, actual);
|
||||
});
|
||||
|
||||
it("exported encrypted json contains folders", async () => {
|
||||
cipherService.getAll().resolves(UserCipherDomains.slice(0, 1));
|
||||
await folderService.getAllFromState();
|
||||
const actual = await exportService.getExport("encrypted_json");
|
||||
|
||||
expectEqualFolders(UserFolders, actual);
|
||||
});
|
||||
});
|
||||
|
||||
export class FolderResponse {
|
||||
id: string = null;
|
||||
name: string = null;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { MockProxy, mock, any, mockClear, matches } from "jest-mock-extended";
|
||||
import { BehaviorSubject, firstValueFrom, Subject } from "rxjs";
|
||||
import { MockProxy, mock, any, mockClear } from "jest-mock-extended";
|
||||
import { BehaviorSubject, firstValueFrom } from "rxjs";
|
||||
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { SyncNotifierService } from "@bitwarden/common/abstractions/sync/syncNotifier.service.abstraction";
|
||||
import { OrganizationData } from "@bitwarden/common/models/data/organization.data";
|
||||
import { SyncResponse } from "@bitwarden/common/models/response/sync.response";
|
||||
import { OrganizationService } from "@bitwarden/common/services/organization/organization.service";
|
||||
import { SyncEventArgs } from "@bitwarden/common/types/syncEventArgs";
|
||||
|
||||
describe("Organization Service", () => {
|
||||
let organizationService: OrganizationService;
|
||||
@@ -14,8 +11,6 @@ describe("Organization Service", () => {
|
||||
let stateService: MockProxy<StateService>;
|
||||
let activeAccount: BehaviorSubject<string>;
|
||||
let activeAccountUnlocked: BehaviorSubject<boolean>;
|
||||
let syncNotifierService: MockProxy<SyncNotifierService>;
|
||||
let sync: Subject<SyncEventArgs>;
|
||||
|
||||
const resetStateService = async (
|
||||
customizeStateService: (stateService: MockProxy<StateService>) => void
|
||||
@@ -25,7 +20,7 @@ describe("Organization Service", () => {
|
||||
stateService.activeAccount$ = activeAccount;
|
||||
stateService.activeAccountUnlocked$ = activeAccountUnlocked;
|
||||
customizeStateService(stateService);
|
||||
organizationService = new OrganizationService(stateService, syncNotifierService);
|
||||
organizationService = new OrganizationService(stateService);
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
};
|
||||
|
||||
@@ -41,12 +36,7 @@ describe("Organization Service", () => {
|
||||
"1": organizationData("1", "Test Org"),
|
||||
});
|
||||
|
||||
sync = new Subject<SyncEventArgs>();
|
||||
|
||||
syncNotifierService = mock<SyncNotifierService>();
|
||||
syncNotifierService.sync$ = sync;
|
||||
|
||||
organizationService = new OrganizationService(stateService, syncNotifierService);
|
||||
organizationService = new OrganizationService(stateService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -169,36 +159,6 @@ describe("Organization Service", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("syncEvent works", () => {
|
||||
it("Complete event updates data", async () => {
|
||||
sync.next({
|
||||
status: "Completed",
|
||||
successfully: true,
|
||||
data: new SyncResponse({
|
||||
profile: {
|
||||
organizations: [
|
||||
{
|
||||
id: "1",
|
||||
name: "Updated Name",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
|
||||
expect(stateService.setOrganizations).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(stateService.setOrganizations).toHaveBeenLastCalledWith(
|
||||
matches((organizationData: { [id: string]: OrganizationData }) => {
|
||||
const organization = organizationData["1"];
|
||||
return organization?.name === "Updated Name";
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function organizationData(id: string, name: string) {
|
||||
const data = new OrganizationData({} as any);
|
||||
data.id = id;
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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>;
|
||||
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { EventType } from "../../enums/eventType";
|
||||
|
||||
export abstract class EventCollectionService {
|
||||
collect: (
|
||||
eventType: EventType,
|
||||
cipherId?: string,
|
||||
uploadImmediately?: boolean,
|
||||
organizationId?: string
|
||||
) => Promise<any>;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export abstract class EventUploadService {
|
||||
uploadEvents: (userId?: string) => Promise<void>;
|
||||
}
|
||||
@@ -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!
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -4,4 +4,5 @@ export abstract class LoginService {
|
||||
setEmail: (value: string) => void;
|
||||
setRememberEmail: (value: boolean) => void;
|
||||
clearValues: () => void;
|
||||
saveEmailSettings: () => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -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>>;
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./organization-user.response";
|
||||
export * from "./organization-user-bulk.response";
|
||||
export * from "./organization-user-bulk-public-key.response";
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BaseResponse } from "./base.response";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class OrganizationUserBulkPublicKeyResponse extends BaseResponse {
|
||||
id: string;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BaseResponse } from "./base.response";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class OrganizationUserBulkResponse extends BaseResponse {
|
||||
id: string;
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
@@ -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> {
|
||||
@@ -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> {
|
||||
@@ -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> {
|
||||
@@ -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>;
|
||||
@@ -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> {
|
||||
@@ -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 =
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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> {
|
||||
@@ -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() !== "") {
|
||||
@@ -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;
|
||||
85
libs/common/src/importers/enpass/types/enpass-json-type.ts
Normal file
85
libs/common/src/importers/enpass/types/enpass-json-type.ts
Normal 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;
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
79
libs/common/src/importers/fsecure/fsecure-fsk-importer.ts
Normal file
79
libs/common/src/importers/fsecure/fsecure-fsk-importer.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
libs/common/src/importers/fsecure/fsecure-fsk-types.ts
Normal file
37
libs/common/src/importers/fsecure/fsecure-fsk-types.ts
Normal 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;
|
||||
}
|
||||
49
libs/common/src/importers/fsecure/fsk-test-data.ts
Normal file
49
libs/common/src/importers/fsecure/fsk-test-data.ts
Normal 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",
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
43
libs/common/src/importers/passky/passky-json-importer.ts
Normal file
43
libs/common/src/importers/passky/passky-json-importer.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user