mirror of
https://github.com/bitwarden/jslib
synced 2026-01-07 02:53:13 +00:00
Merge branch 'master' of https://github.com/bitwarden/jslib into improve-hostname-and-domain-retrieval
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
import { PolicyType } from "../enums/policyType";
|
||||
import { SetKeyConnectorKeyRequest } from "../models/request/account/setKeyConnectorKeyRequest";
|
||||
import { VerifyOTPRequest } from "../models/request/account/verifyOTPRequest";
|
||||
|
||||
import { AttachmentRequest } from "../models/request/attachmentRequest";
|
||||
|
||||
import { BitPayInvoiceRequest } from "../models/request/bitPayInvoiceRequest";
|
||||
import { CipherBulkDeleteRequest } from "../models/request/cipherBulkDeleteRequest";
|
||||
import { CipherBulkMoveRequest } from "../models/request/cipherBulkMoveRequest";
|
||||
@@ -26,6 +24,9 @@ import { EventRequest } from "../models/request/eventRequest";
|
||||
import { FolderRequest } from "../models/request/folderRequest";
|
||||
import { GroupRequest } from "../models/request/groupRequest";
|
||||
import { IapCheckRequest } from "../models/request/iapCheckRequest";
|
||||
import { ApiTokenRequest } from "../models/request/identityToken/apiTokenRequest";
|
||||
import { PasswordTokenRequest } from "../models/request/identityToken/passwordTokenRequest";
|
||||
import { SsoTokenRequest } from "../models/request/identityToken/ssoTokenRequest";
|
||||
import { ImportCiphersRequest } from "../models/request/importCiphersRequest";
|
||||
import { ImportDirectoryRequest } from "../models/request/importDirectoryRequest";
|
||||
import { ImportOrganizationCiphersRequest } from "../models/request/importOrganizationCiphersRequest";
|
||||
@@ -75,7 +76,6 @@ import { SendRequest } from "../models/request/sendRequest";
|
||||
import { SetPasswordRequest } from "../models/request/setPasswordRequest";
|
||||
import { StorageRequest } from "../models/request/storageRequest";
|
||||
import { TaxInfoUpdateRequest } from "../models/request/taxInfoUpdateRequest";
|
||||
import { TokenRequest } from "../models/request/tokenRequest";
|
||||
import { TwoFactorEmailRequest } from "../models/request/twoFactorEmailRequest";
|
||||
import { TwoFactorProviderRequest } from "../models/request/twoFactorProviderRequest";
|
||||
import { TwoFactorRecoveryRequest } from "../models/request/twoFactorRecoveryRequest";
|
||||
@@ -92,7 +92,6 @@ import { UpdateTwoFactorYubioOtpRequest } from "../models/request/updateTwoFacto
|
||||
import { VerifyBankRequest } from "../models/request/verifyBankRequest";
|
||||
import { VerifyDeleteRecoverRequest } from "../models/request/verifyDeleteRecoverRequest";
|
||||
import { VerifyEmailRequest } from "../models/request/verifyEmailRequest";
|
||||
|
||||
import { ApiKeyResponse } from "../models/response/apiKeyResponse";
|
||||
import { AttachmentResponse } from "../models/response/attachmentResponse";
|
||||
import { AttachmentUploadDataResponse } from "../models/response/attachmentUploadDataResponse";
|
||||
@@ -166,12 +165,11 @@ import {
|
||||
} from "../models/response/twoFactorWebAuthnResponse";
|
||||
import { TwoFactorYubiKeyResponse } from "../models/response/twoFactorYubiKeyResponse";
|
||||
import { UserKeyResponse } from "../models/response/userKeyResponse";
|
||||
|
||||
import { SendAccessView } from "../models/view/sendAccessView";
|
||||
|
||||
export abstract class ApiService {
|
||||
postIdentityToken: (
|
||||
request: TokenRequest
|
||||
request: PasswordTokenRequest | SsoTokenRequest | ApiTokenRequest
|
||||
) => Promise<IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse>;
|
||||
refreshIdentityToken: () => Promise<any>;
|
||||
|
||||
@@ -355,6 +353,10 @@ export abstract class ApiService {
|
||||
email: string,
|
||||
organizationUserId: string
|
||||
) => Promise<ListResponse<PolicyResponse>>;
|
||||
getPoliciesByInvitedUser: (
|
||||
organizationId: string,
|
||||
userId: string
|
||||
) => Promise<ListResponse<PolicyResponse>>;
|
||||
putPolicy: (
|
||||
organizationId: string,
|
||||
type: PolicyType,
|
||||
|
||||
@@ -1,58 +1,23 @@
|
||||
import { TwoFactorProviderType } from "../enums/twoFactorProviderType";
|
||||
|
||||
import { AuthResult } from "../models/domain/authResult";
|
||||
import {
|
||||
ApiLogInCredentials,
|
||||
PasswordLogInCredentials,
|
||||
SsoLogInCredentials,
|
||||
} from "../models/domain/logInCredentials";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
||||
import { TokenRequestTwoFactor } from "../models/request/identityToken/tokenRequest";
|
||||
|
||||
export abstract class AuthService {
|
||||
email: string;
|
||||
masterPasswordHash: string;
|
||||
code: string;
|
||||
codeVerifier: string;
|
||||
ssoRedirectUrl: string;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
twoFactorProvidersData: Map<TwoFactorProviderType, { [key: string]: string }>;
|
||||
selectedTwoFactorProviderType: TwoFactorProviderType;
|
||||
|
||||
logIn: (email: string, masterPassword: string, captchaToken?: string) => Promise<AuthResult>;
|
||||
logInSso: (
|
||||
code: string,
|
||||
codeVerifier: string,
|
||||
redirectUrl: string,
|
||||
orgId: string
|
||||
email: string;
|
||||
logIn: (
|
||||
credentials: ApiLogInCredentials | PasswordLogInCredentials | SsoLogInCredentials
|
||||
) => Promise<AuthResult>;
|
||||
logInApiKey: (clientId: string, clientSecret: string) => Promise<AuthResult>;
|
||||
logInTwoFactor: (
|
||||
twoFactorProvider: TwoFactorProviderType,
|
||||
twoFactorToken: string,
|
||||
remember?: boolean
|
||||
twoFactor: TokenRequestTwoFactor,
|
||||
captchaResponse: string
|
||||
) => Promise<AuthResult>;
|
||||
logInComplete: (
|
||||
email: string,
|
||||
masterPassword: string,
|
||||
twoFactorProvider: TwoFactorProviderType,
|
||||
twoFactorToken: string,
|
||||
remember?: boolean,
|
||||
captchaToken?: string
|
||||
) => Promise<AuthResult>;
|
||||
logInSsoComplete: (
|
||||
code: string,
|
||||
codeVerifier: string,
|
||||
redirectUrl: string,
|
||||
twoFactorProvider: TwoFactorProviderType,
|
||||
twoFactorToken: string,
|
||||
remember?: boolean
|
||||
) => Promise<AuthResult>;
|
||||
logInApiKeyComplete: (
|
||||
clientId: string,
|
||||
clientSecret: string,
|
||||
twoFactorProvider: TwoFactorProviderType,
|
||||
twoFactorToken: string,
|
||||
remember?: boolean
|
||||
) => Promise<AuthResult>;
|
||||
logOut: (callback: Function) => void;
|
||||
getSupportedTwoFactorProviders: (win: Window) => any[];
|
||||
getDefaultTwoFactorProvider: (webAuthnSupported: boolean) => TwoFactorProviderType;
|
||||
logOut: (callback: () => void) => void;
|
||||
makePreloginKey: (masterPassword: string, email: string) => Promise<SymmetricCryptoKey>;
|
||||
authingWithApiKey: () => boolean;
|
||||
authingWithSso: () => boolean;
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { UriMatchType } from "../enums/uriMatchType";
|
||||
|
||||
import { CipherData } from "../models/data/cipherData";
|
||||
|
||||
import { Cipher } from "../models/domain/cipher";
|
||||
import { Field } from "../models/domain/field";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
||||
|
||||
import { CipherView } from "../models/view/cipherView";
|
||||
import { FieldView } from "../models/view/fieldView";
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { CollectionData } from "../models/data/collectionData";
|
||||
|
||||
import { Collection } from "../models/domain/collection";
|
||||
import { TreeNode } from "../models/domain/treeNode";
|
||||
|
||||
import { CollectionView } from "../models/view/collectionView";
|
||||
|
||||
export abstract class CollectionService {
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { EncArrayBuffer } from "../models/domain/encArrayBuffer";
|
||||
import { EncString } from "../models/domain/encString";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
||||
|
||||
import { ProfileOrganizationResponse } from "../models/response/profileOrganizationResponse";
|
||||
import { ProfileProviderOrganizationResponse } from "../models/response/profileProviderOrganizationResponse";
|
||||
import { ProfileProviderResponse } from "../models/response/profileProviderResponse";
|
||||
|
||||
import { HashPurpose } from "../enums/hashPurpose";
|
||||
import { KdfType } from "../enums/kdfType";
|
||||
import { KeySuffixOptions } from "../enums/keySuffixOptions";
|
||||
import { EncArrayBuffer } from "../models/domain/encArrayBuffer";
|
||||
import { EncString } from "../models/domain/encString";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
||||
import { ProfileOrganizationResponse } from "../models/response/profileOrganizationResponse";
|
||||
import { ProfileProviderOrganizationResponse } from "../models/response/profileProviderOrganizationResponse";
|
||||
import { ProfileProviderResponse } from "../models/response/profileProviderResponse";
|
||||
|
||||
export abstract class CryptoService {
|
||||
setKey: (key: SymmetricCryptoKey) => Promise<any>;
|
||||
@@ -21,7 +19,7 @@ export abstract class CryptoService {
|
||||
) => Promise<void>;
|
||||
setProviderKeys: (orgs: ProfileProviderResponse[]) => Promise<void>;
|
||||
getKey: (keySuffix?: KeySuffixOptions, userId?: string) => Promise<SymmetricCryptoKey>;
|
||||
getKeyFromStorage: (keySuffix: KeySuffixOptions) => Promise<SymmetricCryptoKey>;
|
||||
getKeyFromStorage: (keySuffix: KeySuffixOptions, userId?: string) => Promise<SymmetricCryptoKey>;
|
||||
getKeyHash: () => Promise<string>;
|
||||
compareAndUpdateKeyHash: (masterPassword: string, key: SymmetricCryptoKey) => Promise<boolean>;
|
||||
getEncKey: (key?: SymmetricCryptoKey) => Promise<SymmetricCryptoKey>;
|
||||
|
||||
@@ -29,6 +29,6 @@ export abstract class EnvironmentService {
|
||||
getEventsUrl: () => string;
|
||||
getKeyConnectorUrl: () => string;
|
||||
setUrlsFromStorage: () => Promise<void>;
|
||||
setUrls: (urls: any, saveSettings?: boolean) => Promise<Urls>;
|
||||
setUrls: (urls: Urls) => Promise<Urls>;
|
||||
getUrls: () => Urls;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { EventView } from "../models/view/eventView";
|
||||
|
||||
export type ExportFormat = "csv" | "json" | "encrypted_json";
|
||||
|
||||
export abstract class ExportService {
|
||||
getExport: (format?: "csv" | "json" | "encrypted_json") => Promise<string>;
|
||||
getOrganizationExport: (
|
||||
organizationId: string,
|
||||
format?: "csv" | "json" | "encrypted_json"
|
||||
) => Promise<string>;
|
||||
getExport: (format?: ExportFormat, organizationId?: string) => Promise<string>;
|
||||
getPasswordProtectedExport: (password: string, organizationId?: string) => Promise<string>;
|
||||
getOrganizationExport: (organizationId: string, format?: ExportFormat) => Promise<string>;
|
||||
getEventExport: (events: EventView[]) => Promise<string>;
|
||||
getFileName: (prefix?: string, extension?: string) => string;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { FolderData } from "../models/data/folderData";
|
||||
|
||||
import { Folder } from "../models/domain/folder";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
||||
import { TreeNode } from "../models/domain/treeNode";
|
||||
|
||||
import { FolderView } from "../models/view/folderView";
|
||||
|
||||
export abstract class FolderService {
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import { ImportOption, ImportType } from "../enums/importOptions";
|
||||
import { ImportError } from "../importers/importError";
|
||||
import { Importer } from "../importers/importer";
|
||||
|
||||
export interface ImportOption {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
export abstract class ImportService {
|
||||
featuredImportOptions: ImportOption[];
|
||||
regularImportOptions: ImportOption[];
|
||||
featuredImportOptions: readonly ImportOption[];
|
||||
regularImportOptions: readonly ImportOption[];
|
||||
getImportOptions: () => ImportOption[];
|
||||
import: (importer: Importer, fileContents: string, organizationId?: string) => Promise<Error>;
|
||||
getImporter: (format: string, organizationId: string) => Importer;
|
||||
import: (
|
||||
importer: Importer,
|
||||
fileContents: string,
|
||||
organizationId?: string
|
||||
) => Promise<ImportError>;
|
||||
getImporter: (
|
||||
format: ImportType | "bitwardenpasswordprotected",
|
||||
organizationId: string,
|
||||
password?: string
|
||||
) => Importer;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Organization } from "../models/domain/organization";
|
||||
import { IdentityTokenResponse } from "../models/response/identityTokenResponse";
|
||||
|
||||
export abstract class KeyConnectorService {
|
||||
getAndSetKey: (url?: string) => Promise<void>;
|
||||
@@ -6,6 +7,10 @@ export abstract class KeyConnectorService {
|
||||
getUsesKeyConnector: () => Promise<boolean>;
|
||||
migrateUser: () => Promise<void>;
|
||||
userNeedsMigration: () => Promise<boolean>;
|
||||
convertNewSsoUserToKeyConnector: (
|
||||
tokenResponse: IdentityTokenResponse,
|
||||
orgId: string
|
||||
) => Promise<void>;
|
||||
setUsesKeyConnector: (enabled: boolean) => Promise<void>;
|
||||
setConvertAccountRequired: (status: boolean) => Promise<void>;
|
||||
getConvertAccountRequired: () => Promise<boolean>;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { OrganizationData } from "../models/data/organizationData";
|
||||
|
||||
import { Organization } from "../models/domain/organization";
|
||||
|
||||
export abstract class OrganizationService {
|
||||
@@ -8,4 +7,5 @@ export abstract class OrganizationService {
|
||||
getAll: (userId?: string) => Promise<Organization[]>;
|
||||
save: (orgs: { [id: string]: OrganizationData }) => Promise<any>;
|
||||
canManageSponsorships: () => Promise<boolean>;
|
||||
hasOrganizations: (userId?: string) => Promise<boolean>;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ClientType } from "../enums/clientType";
|
||||
import { DeviceType } from "../enums/deviceType";
|
||||
import { ThemeType } from "../enums/themeType";
|
||||
|
||||
@@ -6,9 +7,9 @@ interface ToastOptions {
|
||||
}
|
||||
|
||||
export abstract class PlatformUtilsService {
|
||||
identityClientId: string;
|
||||
getDevice: () => DeviceType;
|
||||
getDeviceString: () => string;
|
||||
getClientType: () => ClientType;
|
||||
isFirefox: () => boolean;
|
||||
isChrome: () => boolean;
|
||||
isEdge: () => boolean;
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import { PolicyType } from "../enums/policyType";
|
||||
import { PolicyData } from "../models/data/policyData";
|
||||
|
||||
import { MasterPasswordPolicyOptions } from "../models/domain/masterPasswordPolicyOptions";
|
||||
import { Policy } from "../models/domain/policy";
|
||||
import { ResetPasswordPolicyOptions } from "../models/domain/resetPasswordPolicyOptions";
|
||||
|
||||
import { ListResponse } from "../models/response/listResponse";
|
||||
import { PolicyResponse } from "../models/response/policyResponse";
|
||||
|
||||
import { PolicyType } from "../enums/policyType";
|
||||
|
||||
export abstract class PolicyService {
|
||||
clearCache: () => void;
|
||||
getAll: (type?: PolicyType, userId?: string) => Promise<Policy[]>;
|
||||
getPolicyForOrganization: (policyType: PolicyType, organizationId: string) => Promise<Policy>;
|
||||
replace: (policies: { [id: string]: PolicyData }) => Promise<any>;
|
||||
clear: (userId?: string) => Promise<any>;
|
||||
getMasterPasswordPoliciesForInvitedUsers: (orgId: string) => Promise<MasterPasswordPolicyOptions>;
|
||||
getMasterPasswordPolicyOptions: (policies?: Policy[]) => Promise<MasterPasswordPolicyOptions>;
|
||||
evaluateMasterPassword: (
|
||||
passwordStrength: number,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ProviderData } from "../models/data/providerData";
|
||||
|
||||
import { Provider } from "../models/domain/provider";
|
||||
|
||||
export abstract class ProviderService {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { SendData } from "../models/data/sendData";
|
||||
|
||||
import { EncArrayBuffer } from "../models/domain/encArrayBuffer";
|
||||
import { Send } from "../models/domain/send";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
||||
|
||||
import { SendView } from "../models/view/sendView";
|
||||
|
||||
export abstract class SendService {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { KdfType } from "../enums/kdfType";
|
||||
import { ThemeType } from "../enums/themeType";
|
||||
import { UriMatchType } from "../enums/uriMatchType";
|
||||
|
||||
import { CipherData } from "../models/data/cipherData";
|
||||
import { CollectionData } from "../models/data/collectionData";
|
||||
import { EventData } from "../models/data/eventData";
|
||||
@@ -11,24 +11,24 @@ import { OrganizationData } from "../models/data/organizationData";
|
||||
import { PolicyData } from "../models/data/policyData";
|
||||
import { ProviderData } from "../models/data/providerData";
|
||||
import { SendData } from "../models/data/sendData";
|
||||
|
||||
import { Account } from "../models/domain/account";
|
||||
import { EncString } from "../models/domain/encString";
|
||||
import { EnvironmentUrls } from "../models/domain/environmentUrls";
|
||||
import { GeneratedPasswordHistory } from "../models/domain/generatedPasswordHistory";
|
||||
import { Policy } from "../models/domain/policy";
|
||||
import { StorageOptions } from "../models/domain/storageOptions";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
||||
|
||||
import { WindowState } from "../models/domain/windowState";
|
||||
import { CipherView } from "../models/view/cipherView";
|
||||
import { CollectionView } from "../models/view/collectionView";
|
||||
import { FolderView } from "../models/view/folderView";
|
||||
import { SendView } from "../models/view/sendView";
|
||||
|
||||
export abstract class StateService {
|
||||
accounts: BehaviorSubject<{ [userId: string]: Account }>;
|
||||
export abstract class StateService<T extends Account = Account> {
|
||||
accounts: BehaviorSubject<{ [userId: string]: T }>;
|
||||
activeAccount: BehaviorSubject<string>;
|
||||
|
||||
addAccount: (account: Account) => Promise<void>;
|
||||
addAccount: (account: T) => Promise<void>;
|
||||
setActiveUser: (userId: string) => Promise<void>;
|
||||
clean: (options?: StorageOptions) => Promise<void>;
|
||||
init: () => Promise<void>;
|
||||
@@ -60,8 +60,8 @@ export abstract class StateService {
|
||||
getCanAccessPremium: (options?: StorageOptions) => Promise<boolean>;
|
||||
getClearClipboard: (options?: StorageOptions) => Promise<number>;
|
||||
setClearClipboard: (value: number, options?: StorageOptions) => Promise<void>;
|
||||
getCollapsedGroupings: (options?: StorageOptions) => Promise<Set<string>>;
|
||||
setCollapsedGroupings: (value: Set<string>, options?: StorageOptions) => Promise<void>;
|
||||
getCollapsedGroupings: (options?: StorageOptions) => Promise<string[]>;
|
||||
setCollapsedGroupings: (value: string[], options?: StorageOptions) => Promise<void>;
|
||||
getConvertAccountToKeyConnector: (options?: StorageOptions) => Promise<boolean>;
|
||||
setConvertAccountToKeyConnector: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getCryptoMasterKey: (options?: StorageOptions) => Promise<SymmetricCryptoKey>;
|
||||
@@ -213,8 +213,8 @@ export abstract class StateService {
|
||||
setEntityId: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getEntityType: (options?: StorageOptions) => Promise<any>;
|
||||
setEntityType: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getEnvironmentUrls: (options?: StorageOptions) => Promise<any>;
|
||||
setEnvironmentUrls: (value: any, options?: StorageOptions) => Promise<void>;
|
||||
getEnvironmentUrls: (options?: StorageOptions) => Promise<EnvironmentUrls>;
|
||||
setEnvironmentUrls: (value: EnvironmentUrls, options?: StorageOptions) => Promise<void>;
|
||||
getEquivalentDomains: (options?: StorageOptions) => Promise<any>;
|
||||
setEquivalentDomains: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getEventCollection: (options?: StorageOptions) => Promise<EventData[]>;
|
||||
@@ -285,8 +285,8 @@ export abstract class StateService {
|
||||
setSsoOrganizationIdentifier: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getSsoState: (options?: StorageOptions) => Promise<string>;
|
||||
setSsoState: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getTheme: (options?: StorageOptions) => Promise<string>;
|
||||
setTheme: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getTheme: (options?: StorageOptions) => Promise<ThemeType>;
|
||||
setTheme: (value: ThemeType, options?: StorageOptions) => Promise<void>;
|
||||
getTwoFactorToken: (options?: StorageOptions) => Promise<string>;
|
||||
setTwoFactorToken: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getUserId: (options?: StorageOptions) => Promise<string>;
|
||||
@@ -298,6 +298,6 @@ export abstract class StateService {
|
||||
setVaultTimeoutAction: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getStateVersion: () => Promise<number>;
|
||||
setStateVersion: (value: number) => Promise<void>;
|
||||
getWindow: () => Promise<Map<string, any>>;
|
||||
setWindow: (value: Map<string, any>) => Promise<void>;
|
||||
getWindow: () => Promise<WindowState>;
|
||||
setWindow: (value: WindowState) => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { IdentityTokenResponse } from "../models/response/identityTokenResponse";
|
||||
|
||||
export abstract class TokenService {
|
||||
setTokens: (
|
||||
accessToken: string,
|
||||
@@ -12,10 +14,9 @@ export abstract class TokenService {
|
||||
getClientId: () => Promise<string>;
|
||||
setClientSecret: (clientSecret: string) => Promise<any>;
|
||||
getClientSecret: () => Promise<string>;
|
||||
toggleTokens: () => Promise<any>;
|
||||
setTwoFactorToken: (token: string, email: string) => Promise<any>;
|
||||
getTwoFactorToken: (email: string) => Promise<string>;
|
||||
clearTwoFactorToken: (email: string) => Promise<any>;
|
||||
setTwoFactorToken: (tokenResponse: IdentityTokenResponse) => Promise<any>;
|
||||
getTwoFactorToken: () => Promise<string>;
|
||||
clearTwoFactorToken: () => Promise<any>;
|
||||
clearToken: (userId?: string) => Promise<any>;
|
||||
decodeToken: (token?: string) => any;
|
||||
getTokenExpirationDate: () => Promise<Date>;
|
||||
|
||||
23
common/src/abstractions/twoFactor.service.ts
Normal file
23
common/src/abstractions/twoFactor.service.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { TwoFactorProviderType } from "../enums/twoFactorProviderType";
|
||||
import { IdentityTwoFactorResponse } from "../models/response/identityTwoFactorResponse";
|
||||
|
||||
export interface TwoFactorProviderDetails {
|
||||
type: TwoFactorProviderType;
|
||||
name: string;
|
||||
description: string;
|
||||
priority: number;
|
||||
sort: number;
|
||||
premium: boolean;
|
||||
}
|
||||
|
||||
export abstract class TwoFactorService {
|
||||
init: () => void;
|
||||
getSupportedProviders: (win: Window) => TwoFactorProviderDetails[];
|
||||
getDefaultProvider: (webAuthnSupported: boolean) => TwoFactorProviderType;
|
||||
setSelectedProvider: (type: TwoFactorProviderType) => void;
|
||||
clearSelectedProvider: () => void;
|
||||
|
||||
setProviders: (response: IdentityTwoFactorResponse) => void;
|
||||
clearProviders: () => void;
|
||||
getProviders: () => Map<TwoFactorProviderType, { [key: string]: string }>;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { SecretVerificationRequest } from "../models/request/secretVerificationRequest";
|
||||
|
||||
import { Verification } from "../types/verification";
|
||||
|
||||
export abstract class UserVerificationService {
|
||||
|
||||
5
common/src/enums/authenticationType.ts
Normal file
5
common/src/enums/authenticationType.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum AuthenticationType {
|
||||
Password = 0,
|
||||
Sso = 1,
|
||||
Api = 2,
|
||||
}
|
||||
8
common/src/enums/clientType.ts
Normal file
8
common/src/enums/clientType.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export enum ClientType {
|
||||
Web = "web",
|
||||
Browser = "browser",
|
||||
Desktop = "desktop",
|
||||
Mobile = "mobile",
|
||||
Cli = "cli",
|
||||
DirectoryConnector = "connector",
|
||||
}
|
||||
74
common/src/enums/importOptions.ts
Normal file
74
common/src/enums/importOptions.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
export interface ImportOption {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const featuredImportOptions = [
|
||||
{ id: "bitwardenjson", name: "Bitwarden (json)" },
|
||||
{ id: "bitwardencsv", name: "Bitwarden (csv)" },
|
||||
{ id: "chromecsv", name: "Chrome (csv)" },
|
||||
{ id: "dashlanecsv", name: "Dashlane (csv)" },
|
||||
{ id: "firefoxcsv", name: "Firefox (csv)" },
|
||||
{ id: "keepass2xml", name: "KeePass 2 (xml)" },
|
||||
{ id: "lastpasscsv", name: "LastPass (csv)" },
|
||||
{ id: "safaricsv", name: "Safari and macOS (csv)" },
|
||||
{ id: "1password1pux", name: "1Password (1pux)" },
|
||||
] as const;
|
||||
|
||||
export const regularImportOptions = [
|
||||
{ id: "keepassxcsv", name: "KeePassX (csv)" },
|
||||
{ id: "1password1pif", name: "1Password (1pif)" },
|
||||
{ id: "1passwordwincsv", name: "1Password 6 and 7 Windows (csv)" },
|
||||
{ id: "1passwordmaccsv", name: "1Password 6 and 7 Mac (csv)" },
|
||||
{ id: "dashlanejson", name: "Dashlane (json)" },
|
||||
{ id: "roboformcsv", name: "RoboForm (csv)" },
|
||||
{ id: "keepercsv", name: "Keeper (csv)" },
|
||||
// Temporarily remove this option for the Feb release
|
||||
// { id: "keeperjson", name: "Keeper (json)" },
|
||||
{ id: "enpasscsv", name: "Enpass (csv)" },
|
||||
{ id: "enpassjson", name: "Enpass (json)" },
|
||||
{ id: "safeincloudxml", name: "SafeInCloud (xml)" },
|
||||
{ id: "pwsafexml", name: "Password Safe (xml)" },
|
||||
{ id: "stickypasswordxml", name: "Sticky Password (xml)" },
|
||||
{ id: "msecurecsv", name: "mSecure (csv)" },
|
||||
{ id: "truekeycsv", name: "True Key (csv)" },
|
||||
{ id: "passwordbossjson", name: "Password Boss (json)" },
|
||||
{ id: "zohovaultcsv", name: "Zoho Vault (csv)" },
|
||||
{ id: "splashidcsv", name: "SplashID (csv)" },
|
||||
{ id: "passworddragonxml", name: "Password Dragon (xml)" },
|
||||
{ id: "padlockcsv", name: "Padlock (csv)" },
|
||||
{ id: "passboltcsv", name: "Passbolt (csv)" },
|
||||
{ id: "clipperzhtml", name: "Clipperz (html)" },
|
||||
{ id: "aviracsv", name: "Avira (csv)" },
|
||||
{ id: "saferpasscsv", name: "SaferPass (csv)" },
|
||||
{ id: "upmcsv", name: "Universal Password Manager (csv)" },
|
||||
{ id: "ascendocsv", name: "Ascendo DataVault (csv)" },
|
||||
{ id: "meldiumcsv", name: "Meldium (csv)" },
|
||||
{ id: "passkeepcsv", name: "PassKeep (csv)" },
|
||||
{ id: "operacsv", name: "Opera (csv)" },
|
||||
{ id: "vivaldicsv", name: "Vivaldi (csv)" },
|
||||
{ id: "gnomejson", name: "GNOME Passwords and Keys/Seahorse (json)" },
|
||||
{ id: "blurcsv", name: "Blur (csv)" },
|
||||
{ id: "passwordagentcsv", name: "Password Agent (csv)" },
|
||||
{ id: "passpackcsv", name: "Passpack (csv)" },
|
||||
{ id: "passmanjson", name: "Passman (json)" },
|
||||
{ id: "avastcsv", name: "Avast Passwords (csv)" },
|
||||
{ id: "avastjson", name: "Avast Passwords (json)" },
|
||||
{ id: "fsecurefsk", name: "F-Secure KEY (fsk)" },
|
||||
{ id: "kasperskytxt", name: "Kaspersky Password Manager (txt)" },
|
||||
{ id: "remembearcsv", name: "RememBear (csv)" },
|
||||
{ id: "passwordwallettxt", name: "PasswordWallet (txt)" },
|
||||
{ id: "mykicsv", name: "Myki (csv)" },
|
||||
{ id: "securesafecsv", name: "SecureSafe (csv)" },
|
||||
{ id: "logmeoncecsv", name: "LogMeOnce (csv)" },
|
||||
{ id: "blackberrycsv", name: "BlackBerry Password Keeper (csv)" },
|
||||
{ id: "buttercupcsv", name: "Buttercup (csv)" },
|
||||
{ id: "codebookcsv", name: "Codebook (csv)" },
|
||||
{ id: "encryptrcsv", name: "Encryptr (csv)" },
|
||||
{ id: "yoticsv", name: "Yoti (csv)" },
|
||||
{ id: "nordpasscsv", name: "Nordpass (csv)" },
|
||||
] as const;
|
||||
|
||||
export type ImportType =
|
||||
| typeof featuredImportOptions[number]["id"]
|
||||
| typeof regularImportOptions[number]["id"];
|
||||
33
common/src/enums/ssoEnums.ts
Normal file
33
common/src/enums/ssoEnums.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export enum SsoType {
|
||||
None = 0,
|
||||
OpenIdConnect = 1,
|
||||
Saml2 = 2,
|
||||
}
|
||||
|
||||
export enum OpenIdConnectRedirectBehavior {
|
||||
RedirectGet = 0,
|
||||
FormPost = 1,
|
||||
}
|
||||
|
||||
export enum Saml2BindingType {
|
||||
HttpRedirect = 1,
|
||||
HttpPost = 2,
|
||||
}
|
||||
|
||||
export enum Saml2NameIdFormat {
|
||||
NotConfigured = 0,
|
||||
Unspecified = 1,
|
||||
EmailAddress = 2,
|
||||
X509SubjectName = 3,
|
||||
WindowsDomainQualifiedName = 4,
|
||||
KerberosPrincipalName = 5,
|
||||
EntityIdentifier = 6,
|
||||
Persistent = 7,
|
||||
Transient = 8,
|
||||
}
|
||||
|
||||
export enum Saml2SigningBehavior {
|
||||
IfIdpWantAuthnRequestsSigned = 0,
|
||||
Always = 1,
|
||||
Never = 3,
|
||||
}
|
||||
7
common/src/enums/stateVersion.ts
Normal file
7
common/src/enums/stateVersion.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export enum StateVersion {
|
||||
One = 1, // Original flat key/value pair store
|
||||
Two = 2, // Move to a typed State object
|
||||
Three = 3, // Fix migration of users' premium status
|
||||
Four = 4, // Fix 'Never Lock' option by removing stale data
|
||||
Latest = Four,
|
||||
}
|
||||
13
common/src/factories/accountFactory.ts
Normal file
13
common/src/factories/accountFactory.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Account } from "../models/domain/account";
|
||||
|
||||
export class AccountFactory<T extends Account = Account> {
|
||||
private accountConstructor: new (init: Partial<T>) => T;
|
||||
|
||||
constructor(accountConstructor: new (init: Partial<T>) => T) {
|
||||
this.accountConstructor = accountConstructor;
|
||||
}
|
||||
|
||||
create(args: Partial<T>) {
|
||||
return new this.accountConstructor(args);
|
||||
}
|
||||
}
|
||||
13
common/src/factories/globalStateFactory.ts
Normal file
13
common/src/factories/globalStateFactory.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { GlobalState } from "../models/domain/globalState";
|
||||
|
||||
export class GlobalStateFactory<T extends GlobalState = GlobalState> {
|
||||
private globalStateConstructor: new (init: Partial<T>) => T;
|
||||
|
||||
constructor(globalStateConstructor: new (init: Partial<T>) => T) {
|
||||
this.globalStateConstructor = globalStateConstructor;
|
||||
}
|
||||
|
||||
create(args?: Partial<T>) {
|
||||
return new this.globalStateConstructor(args);
|
||||
}
|
||||
}
|
||||
29
common/src/factories/stateFactory.ts
Normal file
29
common/src/factories/stateFactory.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Account } from "../models/domain/account";
|
||||
import { GlobalState } from "../models/domain/globalState";
|
||||
|
||||
import { AccountFactory } from "./accountFactory";
|
||||
import { GlobalStateFactory } from "./globalStateFactory";
|
||||
|
||||
export class StateFactory<
|
||||
TGlobal extends GlobalState = GlobalState,
|
||||
TAccount extends Account = Account
|
||||
> {
|
||||
private globalStateFactory: GlobalStateFactory<TGlobal>;
|
||||
private accountFactory: AccountFactory<TAccount>;
|
||||
|
||||
constructor(
|
||||
globalStateConstructor: new (init: Partial<TGlobal>) => TGlobal,
|
||||
accountConstructor: new (init: Partial<TAccount>) => TAccount
|
||||
) {
|
||||
this.globalStateFactory = new GlobalStateFactory(globalStateConstructor);
|
||||
this.accountFactory = new AccountFactory(accountConstructor);
|
||||
}
|
||||
|
||||
createGlobal(args: Partial<TGlobal>): TGlobal {
|
||||
return this.globalStateFactory.create(args);
|
||||
}
|
||||
|
||||
createAccount(args: Partial<TAccount>): TAccount {
|
||||
return this.accountFactory.create(args);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class AscendoCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class AvastCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class AvastJsonImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class AviraCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
import * as papa from "papaparse";
|
||||
|
||||
import { LogService } from "../abstractions/log.service";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { CipherView } from "../models/view/cipherView";
|
||||
import { CollectionView } from "../models/view/collectionView";
|
||||
import { LoginUriView } from "../models/view/loginUriView";
|
||||
|
||||
import { Utils } from "../misc/utils";
|
||||
|
||||
import { FieldView } from "../models/view/fieldView";
|
||||
import { FolderView } from "../models/view/folderView";
|
||||
import { LoginView } from "../models/view/loginView";
|
||||
import { SecureNoteView } from "../models/view/secureNoteView";
|
||||
|
||||
import { CipherRepromptType } from "../enums/cipherRepromptType";
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { FieldType } from "../enums/fieldType";
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
|
||||
import { Utils } from "../misc/utils";
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
import { CipherView } from "../models/view/cipherView";
|
||||
import { CollectionView } from "../models/view/collectionView";
|
||||
import { FieldView } from "../models/view/fieldView";
|
||||
import { FolderView } from "../models/view/folderView";
|
||||
import { LoginUriView } from "../models/view/loginUriView";
|
||||
import { LoginView } from "../models/view/loginView";
|
||||
import { SecureNoteView } from "../models/view/secureNoteView";
|
||||
import { ConsoleLogService } from "../services/consoleLog.service";
|
||||
|
||||
export abstract class BaseImporter {
|
||||
@@ -161,7 +154,6 @@ export abstract class BaseImporter {
|
||||
if (result.errors != null && result.errors.length > 0) {
|
||||
result.errors.forEach((e) => {
|
||||
if (e.row != null) {
|
||||
// tslint:disable-next-line
|
||||
this.logService.warning("Error parsing row " + e.row + ": " + e.message);
|
||||
}
|
||||
});
|
||||
@@ -454,4 +446,21 @@ export abstract class BaseImporter {
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
}
|
||||
}
|
||||
|
||||
protected processFullName(cipher: CipherView, fullName: string) {
|
||||
if (this.isNullOrWhitespace(fullName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nameParts = fullName.split(" ");
|
||||
if (nameParts.length > 0) {
|
||||
cipher.identity.firstName = this.getValueOrDefault(nameParts[0]);
|
||||
}
|
||||
if (nameParts.length === 2) {
|
||||
cipher.identity.lastName = this.getValueOrDefault(nameParts[1]);
|
||||
} else if (nameParts.length >= 3) {
|
||||
cipher.identity.middleName = this.getValueOrDefault(nameParts[1]);
|
||||
cipher.identity.lastName = nameParts.slice(2, nameParts.length).join(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { CipherView } from "../models/view/cipherView";
|
||||
import { CollectionView } from "../models/view/collectionView";
|
||||
import { FieldView } from "../models/view/fieldView";
|
||||
import { FolderView } from "../models/view/folderView";
|
||||
import { LoginView } from "../models/view/loginView";
|
||||
import { SecureNoteView } from "../models/view/secureNoteView";
|
||||
|
||||
import { CipherRepromptType } from "../enums/cipherRepromptType";
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { FieldType } from "../enums/fieldType";
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
import { CipherView } from "../models/view/cipherView";
|
||||
import { CollectionView } from "../models/view/collectionView";
|
||||
import { FieldView } from "../models/view/fieldView";
|
||||
import { LoginView } from "../models/view/loginView";
|
||||
import { SecureNoteView } from "../models/view/secureNoteView";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class BitwardenCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
@@ -63,7 +60,7 @@ export class BitwardenCsvImporter extends BaseImporter implements Importer {
|
||||
10
|
||||
);
|
||||
} catch (e) {
|
||||
// tslint:disable-next-line
|
||||
// eslint-disable-next-line
|
||||
console.error("Unable to parse reprompt value", e);
|
||||
cipher.reprompt = CipherRepromptType.None;
|
||||
}
|
||||
@@ -102,7 +99,7 @@ export class BitwardenCsvImporter extends BaseImporter implements Importer {
|
||||
cipher.secureNote = new SecureNoteView();
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
break;
|
||||
default:
|
||||
default: {
|
||||
cipher.type = CipherType.Login;
|
||||
cipher.login = new LoginView();
|
||||
cipher.login.totp = this.getValueOrDefault(value.login_totp || value.totp);
|
||||
@@ -111,6 +108,7 @@ export class BitwardenCsvImporter extends BaseImporter implements Importer {
|
||||
const uris = this.parseSingleRowCsv(value.login_uri || value.uri);
|
||||
cipher.login.uris = this.makeUriArray(uris);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result.ciphers.push(cipher);
|
||||
|
||||
@@ -1,28 +1,33 @@
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { CryptoService } from "../abstractions/crypto.service";
|
||||
import { I18nService } from "../abstractions/i18n.service";
|
||||
import { EncString } from "../models/domain/encString";
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { CipherWithIds } from "../models/export/cipherWithIds";
|
||||
import { CollectionWithId } from "../models/export/collectionWithId";
|
||||
import { FolderWithId } from "../models/export/folderWithId";
|
||||
|
||||
import { CryptoService } from "../abstractions/crypto.service";
|
||||
import { I18nService } from "../abstractions/i18n.service";
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class BitwardenJsonImporter extends BaseImporter implements Importer {
|
||||
private results: any;
|
||||
private result: ImportResult;
|
||||
|
||||
constructor(private cryptoService: CryptoService, private i18nService: I18nService) {
|
||||
constructor(protected cryptoService: CryptoService, protected i18nService: I18nService) {
|
||||
super();
|
||||
}
|
||||
|
||||
async parse(data: string): Promise<ImportResult> {
|
||||
this.result = new ImportResult();
|
||||
this.results = JSON.parse(data);
|
||||
if (this.results == null || this.results.items == null || this.results.items.length === 0) {
|
||||
if (this.results == null || this.results.items == null) {
|
||||
if (this.results?.passwordProtected) {
|
||||
this.result.success = false;
|
||||
this.result.missingPassword = true;
|
||||
this.result.errorMessage = this.i18nService.t("importPasswordRequired");
|
||||
return this.result;
|
||||
}
|
||||
|
||||
this.result.success = false;
|
||||
return this.result;
|
||||
}
|
||||
|
||||
81
common/src/importers/bitwardenPasswordProtectedImporter.ts
Normal file
81
common/src/importers/bitwardenPasswordProtectedImporter.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { CryptoService } from "../abstractions/crypto.service";
|
||||
import { I18nService } from "../abstractions/i18n.service";
|
||||
import { KdfType } from "../enums/kdfType";
|
||||
import { EncString } from "../models/domain/encString";
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
||||
|
||||
import { BitwardenJsonImporter } from "./bitwardenJsonImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
interface BitwardenPasswordProtectedFileFormat {
|
||||
encrypted: boolean;
|
||||
passwordProtected: boolean;
|
||||
salt: string;
|
||||
kdfIterations: number;
|
||||
kdfType: number;
|
||||
encKeyValidation_DO_NOT_EDIT: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter implements Importer {
|
||||
private key: SymmetricCryptoKey;
|
||||
|
||||
constructor(cryptoService: CryptoService, i18nService: I18nService, private password: string) {
|
||||
super(cryptoService, i18nService);
|
||||
}
|
||||
|
||||
async parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const parsedData = JSON.parse(data);
|
||||
if (this.cannotParseFile(parsedData)) {
|
||||
result.success = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!(await this.checkPassword(parsedData))) {
|
||||
result.success = false;
|
||||
result.errorMessage = this.i18nService.t("importEncKeyError");
|
||||
return result;
|
||||
}
|
||||
|
||||
const encData = new EncString(parsedData.data);
|
||||
const clearTextData = await this.cryptoService.decryptToUtf8(encData, this.key);
|
||||
return await super.parse(clearTextData);
|
||||
}
|
||||
|
||||
private async checkPassword(jdoc: BitwardenPasswordProtectedFileFormat): Promise<boolean> {
|
||||
this.key = await this.cryptoService.makePinKey(
|
||||
this.password,
|
||||
jdoc.salt,
|
||||
KdfType.PBKDF2_SHA256,
|
||||
jdoc.kdfIterations
|
||||
);
|
||||
|
||||
const encKeyValidation = new EncString(jdoc.encKeyValidation_DO_NOT_EDIT);
|
||||
|
||||
const encKeyValidationDecrypt = await this.cryptoService.decryptToUtf8(
|
||||
encKeyValidation,
|
||||
this.key
|
||||
);
|
||||
if (encKeyValidationDecrypt === null) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private cannotParseFile(jdoc: BitwardenPasswordProtectedFileFormat): boolean {
|
||||
return (
|
||||
!jdoc ||
|
||||
!jdoc.encrypted ||
|
||||
!jdoc.passwordProtected ||
|
||||
!jdoc.salt ||
|
||||
!jdoc.kdfIterations ||
|
||||
typeof jdoc.kdfIterations !== "number" ||
|
||||
jdoc.kdfType == null ||
|
||||
KdfType[jdoc.kdfType] == null ||
|
||||
!jdoc.encKeyValidation_DO_NOT_EDIT ||
|
||||
!jdoc.data
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class BlackBerryCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class BlurCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
const OfficialProps = ["!group_id", "!group_name", "title", "username", "password", "URL", "id"];
|
||||
|
||||
export class ButtercupCsvImporter extends BaseImporter implements Importer {
|
||||
@@ -25,6 +25,7 @@ export class ButtercupCsvImporter extends BaseImporter implements Importer {
|
||||
|
||||
let processingCustomFields = false;
|
||||
for (const prop in value) {
|
||||
// eslint-disable-next-line
|
||||
if (value.hasOwnProperty(prop)) {
|
||||
if (!processingCustomFields && OfficialProps.indexOf(prop) === -1) {
|
||||
processingCustomFields = true;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class ChromeCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class ClipperzHtmlImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
@@ -31,6 +31,7 @@ export class ClipperzHtmlImporter extends BaseImporter implements Importer {
|
||||
|
||||
if (entry.currentVersion != null && entry.currentVersion.fields != null) {
|
||||
for (const property in entry.currentVersion.fields) {
|
||||
// eslint-disable-next-line
|
||||
if (!entry.currentVersion.fields.hasOwnProperty(property)) {
|
||||
continue;
|
||||
}
|
||||
@@ -50,7 +51,7 @@ export class ClipperzHtmlImporter extends BaseImporter implements Importer {
|
||||
case "url":
|
||||
cipher.login.uris = this.makeUriArray(field.value);
|
||||
break;
|
||||
default:
|
||||
default: {
|
||||
const labelLower = field.label != null ? field.label.toLowerCase() : null;
|
||||
if (
|
||||
cipher.login.password == null &&
|
||||
@@ -71,6 +72,7 @@ export class ClipperzHtmlImporter extends BaseImporter implements Importer {
|
||||
this.processKvp(cipher, field.label, field.value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class CodebookCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
271
common/src/importers/dashlaneImporters/dashlaneCsvImporter.ts
Normal file
271
common/src/importers/dashlaneImporters/dashlaneCsvImporter.ts
Normal file
@@ -0,0 +1,271 @@
|
||||
import { CipherType } from "../../enums/cipherType";
|
||||
import { SecureNoteType } from "../../enums/secureNoteType";
|
||||
import { ImportResult } from "../../models/domain/importResult";
|
||||
import { CardView } from "../../models/view/cardView";
|
||||
import { CipherView } from "../../models/view/cipherView";
|
||||
import { IdentityView } from "../../models/view/identityView";
|
||||
import { LoginView } from "../../models/view/loginView";
|
||||
import { BaseImporter } from "../baseImporter";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
import {
|
||||
CredentialsRecord,
|
||||
IdRecord,
|
||||
PaymentsRecord,
|
||||
PersonalInformationRecord,
|
||||
SecureNoteRecord,
|
||||
} from "./types/dashlaneCsvTypes";
|
||||
|
||||
const _mappedCredentialsColums = new Set([
|
||||
"title",
|
||||
"note",
|
||||
"username",
|
||||
"password",
|
||||
"url",
|
||||
"otpSecret",
|
||||
"category",
|
||||
]);
|
||||
|
||||
const _mappedPersonalInfoAsIdentiyColumns = new Set([
|
||||
"type",
|
||||
"title",
|
||||
"first_name",
|
||||
"middle_name",
|
||||
"last_name",
|
||||
"login",
|
||||
"email",
|
||||
"phone_number",
|
||||
"address",
|
||||
"country",
|
||||
"state",
|
||||
"city",
|
||||
"zip",
|
||||
// Skip item_name as we already have set a combined name
|
||||
"item_name",
|
||||
]);
|
||||
|
||||
const _mappedSecureNoteColumns = new Set(["title", "note"]);
|
||||
|
||||
export class DashlaneCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results = this.parseCsv(data, true);
|
||||
if (results == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
if (results[0].type != null && results[0].title != null) {
|
||||
const personalRecords = results as PersonalInformationRecord[];
|
||||
|
||||
// If personalRecords has only one "name" then create an Identity-Cipher
|
||||
if (personalRecords.filter((x) => x.type === "name").length === 1) {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.type = CipherType.Identity;
|
||||
cipher.identity = new IdentityView();
|
||||
results.forEach((row) => {
|
||||
this.parsePersonalInformationRecordAsIdentity(cipher, row);
|
||||
});
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
|
||||
results.forEach((row) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
|
||||
const rowKeys = Object.keys(row);
|
||||
if (rowKeys[0] === "username") {
|
||||
this.processFolder(result, row.category);
|
||||
this.parseCredentialsRecord(cipher, row);
|
||||
}
|
||||
|
||||
if (rowKeys[0] === "type" && rowKeys[1] === "account_name") {
|
||||
this.parsePaymentRecord(cipher, row);
|
||||
}
|
||||
|
||||
if (rowKeys[0] === "type" && rowKeys[1] === "number") {
|
||||
this.parseIdRecord(cipher, row);
|
||||
}
|
||||
|
||||
if ((rowKeys[0] === "type") != null && rowKeys[1] === "title") {
|
||||
this.parsePersonalInformationRecord(cipher, row);
|
||||
}
|
||||
|
||||
if (rowKeys[0] === "title" && rowKeys[1] === "note") {
|
||||
this.parseSecureNoteRecords(cipher, row);
|
||||
}
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
parseCredentialsRecord(cipher: CipherView, row: CredentialsRecord) {
|
||||
cipher.type = CipherType.Login;
|
||||
cipher.login = new LoginView();
|
||||
|
||||
cipher.name = row.title;
|
||||
cipher.notes = row.note;
|
||||
cipher.login.username = row.username;
|
||||
cipher.login.password = row.password;
|
||||
cipher.login.totp = row.otpSecret;
|
||||
cipher.login.uris = this.makeUriArray(row.url);
|
||||
|
||||
this.importUnmappedFields(cipher, row, _mappedCredentialsColums);
|
||||
}
|
||||
|
||||
parsePaymentRecord(cipher: CipherView, row: PaymentsRecord) {
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card = new CardView();
|
||||
|
||||
cipher.name = row.account_name;
|
||||
let mappedValues: string[] = [];
|
||||
switch (row.type) {
|
||||
case "credit_card":
|
||||
cipher.card.cardholderName = row.account_name;
|
||||
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);
|
||||
|
||||
// If you add more mapped fields please extend this
|
||||
mappedValues = [
|
||||
"account_name",
|
||||
"account_holder",
|
||||
"cc_number",
|
||||
"code",
|
||||
"expiration_month",
|
||||
"expiration_year",
|
||||
];
|
||||
break;
|
||||
case "bank":
|
||||
cipher.card.cardholderName = row.account_holder;
|
||||
cipher.card.number = row.account_number;
|
||||
|
||||
// If you add more mapped fields please extend this
|
||||
mappedValues = ["account_name", "account_holder", "account_number"];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this.importUnmappedFields(cipher, row, new Set(mappedValues));
|
||||
}
|
||||
|
||||
parseIdRecord(cipher: CipherView, row: IdRecord) {
|
||||
cipher.type = CipherType.Identity;
|
||||
cipher.identity = new IdentityView();
|
||||
|
||||
const mappedValues: string[] = ["name", "number"];
|
||||
switch (row.type) {
|
||||
case "card":
|
||||
cipher.name = `${row.name} ${row.type}`;
|
||||
this.processFullName(cipher, row.name);
|
||||
cipher.identity.licenseNumber = row.number;
|
||||
break;
|
||||
case "passport":
|
||||
cipher.name = `${row.name} ${row.type}`;
|
||||
this.processFullName(cipher, row.name);
|
||||
cipher.identity.passportNumber = row.number;
|
||||
break;
|
||||
case "license":
|
||||
cipher.name = `${row.name} ${row.type}`;
|
||||
this.processFullName(cipher, row.name);
|
||||
cipher.identity.licenseNumber = row.number;
|
||||
cipher.identity.state = row.state;
|
||||
|
||||
mappedValues.push("state");
|
||||
break;
|
||||
case "social_security":
|
||||
cipher.name = `${row.name} ${row.type}`;
|
||||
this.processFullName(cipher, row.name);
|
||||
cipher.identity.ssn = row.number;
|
||||
break;
|
||||
case "tax_number":
|
||||
cipher.name = row.type;
|
||||
cipher.identity.licenseNumber = row.number;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// If you add more mapped fields please extend this
|
||||
this.importUnmappedFields(cipher, row, new Set(mappedValues));
|
||||
}
|
||||
|
||||
parsePersonalInformationRecord(cipher: CipherView, row: PersonalInformationRecord) {
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
if (row.type === "name") {
|
||||
cipher.name = `${row.title} ${row.first_name} ${row.middle_name} ${row.last_name}`
|
||||
.replace(" ", " ")
|
||||
.trim();
|
||||
} else {
|
||||
cipher.name = row.item_name;
|
||||
}
|
||||
|
||||
const dataRow = row as any;
|
||||
Object.keys(row).forEach((key) => {
|
||||
this.processKvp(cipher, key, dataRow[key]);
|
||||
});
|
||||
}
|
||||
|
||||
parsePersonalInformationRecordAsIdentity(cipher: CipherView, row: PersonalInformationRecord) {
|
||||
switch (row.type) {
|
||||
case "name":
|
||||
this.processFullName(cipher, `${row.first_name} ${row.middle_name} ${row.last_name}`);
|
||||
cipher.identity.title = row.title;
|
||||
cipher.name = cipher.identity.fullName;
|
||||
|
||||
cipher.identity.username = row.login;
|
||||
break;
|
||||
case "email":
|
||||
cipher.identity.email = row.email;
|
||||
break;
|
||||
case "number":
|
||||
cipher.identity.phone = row.phone_number;
|
||||
break;
|
||||
case "address":
|
||||
cipher.identity.address1 = row.address;
|
||||
cipher.identity.city = row.city;
|
||||
cipher.identity.postalCode = row.zip;
|
||||
cipher.identity.state = row.state;
|
||||
cipher.identity.country = row.country;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this.importUnmappedFields(cipher, row, _mappedPersonalInfoAsIdentiyColumns);
|
||||
}
|
||||
|
||||
parseSecureNoteRecords(cipher: CipherView, row: SecureNoteRecord) {
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
cipher.name = row.title;
|
||||
cipher.notes = row.note;
|
||||
|
||||
this.importUnmappedFields(cipher, row, _mappedSecureNoteColumns);
|
||||
}
|
||||
|
||||
importUnmappedFields(cipher: CipherView, row: any, mappedValues: Set<string>) {
|
||||
const unmappedFields = Object.keys(row).filter((x) => !mappedValues.has(x));
|
||||
unmappedFields.forEach((key) => {
|
||||
const item = row as any;
|
||||
this.processKvp(cipher, key, item[key]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { CardView } from "../models/view/cardView";
|
||||
import { CipherView } from "../models/view/cipherView";
|
||||
import { IdentityView } from "../models/view/identityView";
|
||||
import { SecureNoteView } from "../models/view/secureNoteView";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
import { CipherType } from "../../enums/cipherType";
|
||||
import { SecureNoteType } from "../../enums/secureNoteType";
|
||||
import { ImportResult } from "../../models/domain/importResult";
|
||||
import { CardView } from "../../models/view/cardView";
|
||||
import { CipherView } from "../../models/view/cipherView";
|
||||
import { IdentityView } from "../../models/view/identityView";
|
||||
import { SecureNoteView } from "../../models/view/secureNoteView";
|
||||
import { BaseImporter } from "../baseImporter";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
const HandledResults = new Set([
|
||||
"ADDRESS",
|
||||
@@ -53,6 +50,7 @@ export class DashlaneJsonImporter extends BaseImporter implements Importer {
|
||||
}
|
||||
|
||||
for (const key in results) {
|
||||
// eslint-disable-next-line
|
||||
if (results.hasOwnProperty(key) && !HandledResults.has(key)) {
|
||||
this.processNote(results[key], null, "Generic Note");
|
||||
}
|
||||
@@ -161,6 +159,7 @@ export class DashlaneJsonImporter extends BaseImporter implements Importer {
|
||||
cipher.name = this.getValueOrDefault(obj[nameProperty]);
|
||||
}
|
||||
for (const key in obj) {
|
||||
// eslint-disable-next-line
|
||||
if (obj.hasOwnProperty(key) && key !== nameProperty) {
|
||||
this.processKvp(cipher, key, obj[key].toString());
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// tslint:disable
|
||||
export class CredentialsRecord {
|
||||
username: string;
|
||||
username2: string;
|
||||
username3: string;
|
||||
title: string;
|
||||
password: string;
|
||||
note: string;
|
||||
url: string;
|
||||
category: string;
|
||||
otpSecret: string;
|
||||
}
|
||||
|
||||
export class PaymentsRecord {
|
||||
type: string;
|
||||
account_name: string;
|
||||
account_holder: string;
|
||||
cc_number: string;
|
||||
code: string;
|
||||
expiration_month: string;
|
||||
expiration_year: string;
|
||||
routing_number: string;
|
||||
account_number: string;
|
||||
country: string;
|
||||
issuing_bank: string;
|
||||
}
|
||||
|
||||
export class IdRecord {
|
||||
type: string;
|
||||
number: string;
|
||||
name: string;
|
||||
issue_date: string;
|
||||
expiration_date: string;
|
||||
place_of_issue: string;
|
||||
state: string;
|
||||
}
|
||||
|
||||
export class PersonalInformationRecord {
|
||||
type: string;
|
||||
title: string;
|
||||
first_name: string;
|
||||
middle_name: string;
|
||||
last_name: string;
|
||||
login: string;
|
||||
date_of_birth: string;
|
||||
place_of_birth: string;
|
||||
email: string;
|
||||
email_type: string;
|
||||
item_name: string;
|
||||
phone_number: string;
|
||||
address: string;
|
||||
country: string;
|
||||
state: string;
|
||||
city: string;
|
||||
zip: string;
|
||||
address_recipient: string;
|
||||
address_building: string;
|
||||
address_apartment: string;
|
||||
address_floor: string;
|
||||
address_door_code: string;
|
||||
job_title: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export class SecureNoteRecord {
|
||||
title: string;
|
||||
note: string;
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { CardView } from "../models/view/cardView";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class EncryptrCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
import { CardView } from "../models/view/cardView";
|
||||
import { SecureNoteView } from "../models/view/secureNoteView";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class EnpassCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { FieldType } from "../enums/fieldType";
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { CardView } from "../models/view/cardView";
|
||||
import { CipherView } from "../models/view/cipherView";
|
||||
import { FolderView } from "../models/view/folderView";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { FieldType } from "../enums/fieldType";
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class EnpassJsonImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class FirefoxCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { CardView } from "../models/view/cardView";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class FSecureFskImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
@@ -17,6 +15,7 @@ export class FSecureFskImporter extends BaseImporter implements Importer {
|
||||
}
|
||||
|
||||
for (const key in results.data) {
|
||||
// eslint-disable-next-line
|
||||
if (!results.data.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class GnomeJsonImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
@@ -14,7 +14,7 @@ export class GnomeJsonImporter extends BaseImporter implements Importer {
|
||||
|
||||
for (const keyRing in results) {
|
||||
if (
|
||||
!results.hasOwnProperty(keyRing) ||
|
||||
!results.hasOwnProperty(keyRing) || // eslint-disable-line
|
||||
this.isNullOrWhitespace(keyRing) ||
|
||||
results[keyRing].length === 0
|
||||
) {
|
||||
@@ -45,7 +45,7 @@ export class GnomeJsonImporter extends BaseImporter implements Importer {
|
||||
: null;
|
||||
for (const attr in value.attributes) {
|
||||
if (
|
||||
!value.attributes.hasOwnProperty(attr) ||
|
||||
!value.attributes.hasOwnProperty(attr) || // eslint-disable-line
|
||||
attr === "username_value" ||
|
||||
attr === "xdg:schema"
|
||||
) {
|
||||
|
||||
5
common/src/importers/importError.ts
Normal file
5
common/src/importers/importError.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export class ImportError extends Error {
|
||||
constructor(message?: string, public passwordRequired: boolean = false) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
const NotesHeader = "Notes\n\n";
|
||||
const ApplicationsHeader = "Applications\n\n";
|
||||
const WebsitesHeader = "Websites\n\n";
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { FieldType } from "../enums/fieldType";
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
import { FolderView } from "../models/view/folderView";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { FieldType } from "../enums/fieldType";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { FolderView } from "../models/view/folderView";
|
||||
|
||||
export class KeePass2XmlImporter extends BaseImporter implements Importer {
|
||||
result = new ImportResult();
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class KeePassXCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { FolderView } from "../models/view/folderView";
|
||||
import { ImportResult } from "../../models/domain/importResult";
|
||||
import { BaseImporter } from "../baseImporter";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
export class KeeperCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
69
common/src/importers/keeperImporters/keeperJsonImporter.ts
Normal file
69
common/src/importers/keeperImporters/keeperJsonImporter.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { ImportResult } from "../../models/domain/importResult";
|
||||
import { BaseImporter } from "../baseImporter";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
import { KeeperJsonExport, RecordsEntity } from "./types/keeperJsonTypes";
|
||||
|
||||
export class KeeperJsonImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const keeperExport: KeeperJsonExport = JSON.parse(data);
|
||||
if (keeperExport == null || keeperExport.records == null || keeperExport.records.length === 0) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
keeperExport.records.forEach((record) => {
|
||||
this.parseFolders(result, record);
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = record.title;
|
||||
cipher.login.username = record.login;
|
||||
cipher.login.password = record.password;
|
||||
|
||||
cipher.login.uris = this.makeUriArray(record.login_url);
|
||||
cipher.notes = record.notes;
|
||||
|
||||
if (record.custom_fields != null) {
|
||||
let customfieldKeys = Object.keys(record.custom_fields);
|
||||
if (record.custom_fields["TFC:Keeper"] != null) {
|
||||
customfieldKeys = customfieldKeys.filter((item) => item !== "TFC:Keeper");
|
||||
cipher.login.totp = record.custom_fields["TFC:Keeper"];
|
||||
}
|
||||
|
||||
customfieldKeys.forEach((key) => {
|
||||
this.processKvp(cipher, key, record.custom_fields[key]);
|
||||
});
|
||||
}
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(result);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
private parseFolders(result: ImportResult, record: RecordsEntity) {
|
||||
if (record.folders == null || record.folders.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
record.folders.forEach((item) => {
|
||||
if (item.folder != null) {
|
||||
this.processFolder(result, item.folder);
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.shared_folder != null) {
|
||||
this.processFolder(result, item.shared_folder);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
export interface KeeperJsonExport {
|
||||
shared_folders?: SharedFoldersEntity[] | null;
|
||||
records?: RecordsEntity[] | null;
|
||||
}
|
||||
|
||||
export interface SharedFoldersEntity {
|
||||
path: string;
|
||||
manage_users: boolean;
|
||||
manage_records: boolean;
|
||||
can_edit: boolean;
|
||||
can_share: boolean;
|
||||
permissions?: PermissionsEntity[] | null;
|
||||
}
|
||||
|
||||
export interface PermissionsEntity {
|
||||
uid?: string | null;
|
||||
manage_users: boolean;
|
||||
manage_records: boolean;
|
||||
name?: string | null;
|
||||
}
|
||||
|
||||
export interface RecordsEntity {
|
||||
title: string;
|
||||
login: string;
|
||||
password: string;
|
||||
login_url: string;
|
||||
notes?: string;
|
||||
custom_fields?: CustomFields;
|
||||
folders?: FoldersEntity[] | null;
|
||||
}
|
||||
|
||||
export type CustomFields = {
|
||||
[key: string]: string | null;
|
||||
};
|
||||
|
||||
export interface FoldersEntity {
|
||||
folder?: string | null;
|
||||
shared_folder?: string | null;
|
||||
can_edit?: boolean | null;
|
||||
can_share?: boolean | null;
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { CardView } from "../models/view/cardView";
|
||||
import { CipherView } from "../models/view/cipherView";
|
||||
import { FolderView } from "../models/view/folderView";
|
||||
@@ -10,8 +8,8 @@ import { IdentityView } from "../models/view/identityView";
|
||||
import { LoginView } from "../models/view/loginView";
|
||||
import { SecureNoteView } from "../models/view/secureNoteView";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class LastPassCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
@@ -22,11 +20,12 @@ export class LastPassCsvImporter extends BaseImporter implements Importer {
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
results.forEach((value, index) => {
|
||||
results.forEach((value) => {
|
||||
const cipherIndex = result.ciphers.length;
|
||||
let folderIndex = result.folders.length;
|
||||
let grouping = value.grouping;
|
||||
if (grouping != null) {
|
||||
// eslint-disable-next-line
|
||||
grouping = grouping.replace(/\\/g, "/").replace(/[\x00-\x1F\x7F-\x9F]/g, "");
|
||||
}
|
||||
const hasFolder = this.getValueOrDefault(grouping, "(none)") !== "(none)";
|
||||
@@ -90,6 +89,7 @@ export class LastPassCsvImporter extends BaseImporter implements Importer {
|
||||
|
||||
private buildBaseCipher(value: any) {
|
||||
const cipher = new CipherView();
|
||||
// eslint-disable-next-line
|
||||
if (value.hasOwnProperty("profilename") && value.hasOwnProperty("profilelanguage")) {
|
||||
// form fill
|
||||
cipher.favorite = false;
|
||||
@@ -272,6 +272,7 @@ export class LastPassCsvImporter extends BaseImporter implements Importer {
|
||||
cipher.notes = val;
|
||||
}
|
||||
processingNotes = true;
|
||||
// eslint-disable-next-line
|
||||
} else if (map.hasOwnProperty(key)) {
|
||||
dataObj[map[key]] = val;
|
||||
} else {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class LogMeOnceCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class MeldiumCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
import { SecureNoteView } from "../models/view/secureNoteView";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class MSecureCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,14 +1,42 @@
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
import { CardView } from "../models/view/cardView";
|
||||
import { CipherView } from "../models/view/cipherView";
|
||||
import { IdentityView } from "../models/view/identityView";
|
||||
import { SecureNoteView } from "../models/view/secureNoteView";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
const mappedBaseColumns = ["nickname", "additionalInfo"];
|
||||
const _mappedUserAccountColumns = new Set(
|
||||
mappedBaseColumns.concat(["url", "username", "password", "twofaSecret"])
|
||||
);
|
||||
const _mappedCreditCardColumns = new Set(
|
||||
mappedBaseColumns.concat(["cardNumber", "cardName", "exp_month", "exp_year", "cvv"])
|
||||
);
|
||||
|
||||
const _mappedIdentityColumns = new Set(
|
||||
mappedBaseColumns.concat([
|
||||
"title",
|
||||
"firstName",
|
||||
"middleName",
|
||||
"lastName",
|
||||
"email",
|
||||
"firstAddressLine",
|
||||
"secondAddressLine",
|
||||
"city",
|
||||
"country",
|
||||
"zipCode",
|
||||
])
|
||||
);
|
||||
|
||||
const _mappedIdCardColumns = new Set(mappedBaseColumns.concat(["idName", "idNumber", "idCountry"]));
|
||||
|
||||
const _mappedTwoFaColumns = new Set(mappedBaseColumns.concat(["authToken"]));
|
||||
|
||||
const _mappedUserNoteColumns = new Set(mappedBaseColumns.concat(["content"]));
|
||||
|
||||
export class MykiCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
@@ -29,7 +57,14 @@ export class MykiCsvImporter extends BaseImporter implements Importer {
|
||||
cipher.login.uris = this.makeUriArray(value.url);
|
||||
cipher.login.username = this.getValueOrDefault(value.username);
|
||||
cipher.login.password = this.getValueOrDefault(value.password);
|
||||
cipher.login.totp = this.getValueOrDefault(value.twoFactAuthToken);
|
||||
cipher.login.totp = this.getValueOrDefault(value.twofaSecret);
|
||||
|
||||
this.importUnmappedFields(cipher, value, _mappedUserAccountColumns);
|
||||
} else if (value.authToken !== undefined) {
|
||||
// TwoFA
|
||||
cipher.login.totp = this.getValueOrDefault(value.authToken);
|
||||
|
||||
this.importUnmappedFields(cipher, value, _mappedTwoFaColumns);
|
||||
} else if (value.cardNumber !== undefined) {
|
||||
// Cards
|
||||
cipher.card = new CardView();
|
||||
@@ -40,6 +75,8 @@ export class MykiCsvImporter extends BaseImporter implements Importer {
|
||||
cipher.card.expMonth = this.getValueOrDefault(value.exp_month);
|
||||
cipher.card.expYear = this.getValueOrDefault(value.exp_year);
|
||||
cipher.card.code = this.getValueOrDefault(value.cvv);
|
||||
|
||||
this.importUnmappedFields(cipher, value, _mappedCreditCardColumns);
|
||||
} else if (value.firstName !== undefined) {
|
||||
// Identities
|
||||
cipher.identity = new IdentityView();
|
||||
@@ -55,13 +92,49 @@ export class MykiCsvImporter extends BaseImporter implements Importer {
|
||||
cipher.identity.city = this.getValueOrDefault(value.city);
|
||||
cipher.identity.country = this.getValueOrDefault(value.country);
|
||||
cipher.identity.postalCode = this.getValueOrDefault(value.zipCode);
|
||||
|
||||
this.importUnmappedFields(cipher, value, _mappedIdentityColumns);
|
||||
} else if (value.idType !== undefined) {
|
||||
// IdCards
|
||||
|
||||
cipher.identity = new IdentityView();
|
||||
cipher.type = CipherType.Identity;
|
||||
this.processFullName(cipher, value.idName);
|
||||
cipher.identity.country = this.getValueOrDefault(value.idCountry);
|
||||
|
||||
switch (value.idType) {
|
||||
// case "Driver's License":
|
||||
// case "ID Card":
|
||||
// case "Outdoor License":
|
||||
// case "Software License":
|
||||
// case "Tax Number":
|
||||
// case "Bank Account":
|
||||
// case "Insurance Card":
|
||||
// case "Health Card":
|
||||
// case "Membership":
|
||||
// case "Database":
|
||||
// case "Reward Program":
|
||||
// case "Tour Visa":
|
||||
case "Passport":
|
||||
cipher.identity.passportNumber = value.idNumber;
|
||||
break;
|
||||
case "Social Security":
|
||||
cipher.identity.ssn = value.idNumber;
|
||||
break;
|
||||
default:
|
||||
cipher.identity.licenseNumber = value.idNumber;
|
||||
break;
|
||||
}
|
||||
|
||||
this.importUnmappedFields(cipher, value, _mappedIdCardColumns);
|
||||
} else if (value.content !== undefined) {
|
||||
// Notes
|
||||
cipher.secureNote = new SecureNoteView();
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
cipher.name = this.getValueOrDefault(value.title, "--");
|
||||
cipher.notes = this.getValueOrDefault(value.content);
|
||||
|
||||
this.importUnmappedFields(cipher, value, _mappedUserNoteColumns);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -73,4 +146,12 @@ export class MykiCsvImporter extends BaseImporter implements Importer {
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
importUnmappedFields(cipher: CipherView, row: any, mappedValues: Set<string>) {
|
||||
const unmappedFields = Object.keys(row).filter((x) => !mappedValues.has(x));
|
||||
unmappedFields.forEach((key) => {
|
||||
const item = row as any;
|
||||
this.processKvp(cipher, key, item[key]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { CipherView } from "../models/view/cipherView";
|
||||
import { LoginView } from "../models/view/loginView";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
type nodePassCsvParsed = {
|
||||
name: string;
|
||||
@@ -74,7 +72,7 @@ export class NordPassCsvImporter extends BaseImporter implements Importer {
|
||||
case CipherType.Identity:
|
||||
cipher.type = CipherType.Identity;
|
||||
|
||||
this.processName(cipher, this.getValueOrDefault(record.full_name));
|
||||
this.processFullName(cipher, this.getValueOrDefault(record.full_name));
|
||||
cipher.identity.address1 = this.getValueOrDefault(record.address1);
|
||||
cipher.identity.address2 = this.getValueOrDefault(record.address2);
|
||||
cipher.identity.city = this.getValueOrDefault(record.city);
|
||||
@@ -126,21 +124,4 @@ export class NordPassCsvImporter extends BaseImporter implements Importer {
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private processName(cipher: CipherView, fullName: string) {
|
||||
if (this.isNullOrWhitespace(fullName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nameParts = fullName.split(" ");
|
||||
if (nameParts.length > 0) {
|
||||
cipher.identity.firstName = this.getValueOrDefault(nameParts[0]);
|
||||
}
|
||||
if (nameParts.length === 2) {
|
||||
cipher.identity.lastName = this.getValueOrDefault(nameParts[1]);
|
||||
} else if (nameParts.length >= 3) {
|
||||
cipher.identity.middleName = this.getValueOrDefault(nameParts[1]);
|
||||
cipher.identity.lastName = nameParts.slice(2, nameParts.length).join(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { BaseImporter } from "../baseImporter";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
import { CipherType } from "../../enums/cipherType";
|
||||
import { FieldType } from "../../enums/fieldType";
|
||||
import { SecureNoteType } from "../../enums/secureNoteType";
|
||||
import { ImportResult } from "../../models/domain/importResult";
|
||||
|
||||
import { CardView } from "../../models/view/cardView";
|
||||
import { CipherView } from "../../models/view/cipherView";
|
||||
import { IdentityView } from "../../models/view/identityView";
|
||||
import { PasswordHistoryView } from "../../models/view/passwordHistoryView";
|
||||
import { SecureNoteView } from "../../models/view/secureNoteView";
|
||||
|
||||
import { CipherType } from "../../enums/cipherType";
|
||||
import { FieldType } from "../../enums/fieldType";
|
||||
import { SecureNoteType } from "../../enums/secureNoteType";
|
||||
import { BaseImporter } from "../baseImporter";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
export class OnePassword1PifImporter extends BaseImporter implements Importer {
|
||||
result = new ImportResult();
|
||||
@@ -172,7 +169,11 @@ export class OnePassword1PifImporter extends BaseImporter implements Importer {
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldValue = field[valueKey].toString();
|
||||
// TODO: when date FieldType exists, store this as a date field type instead of formatted Text if k is 'date'
|
||||
const fieldValue =
|
||||
field.k === "date"
|
||||
? new Date(field[valueKey] * 1000).toUTCString()
|
||||
: field[valueKey].toString();
|
||||
const fieldDesignation =
|
||||
field[designationKey] != null ? field[designationKey].toString() : null;
|
||||
|
||||
|
||||
@@ -0,0 +1,615 @@
|
||||
import { CipherRepromptType } from "../../enums/cipherRepromptType";
|
||||
import { CipherType } from "../../enums/cipherType";
|
||||
import { FieldType } from "../../enums/fieldType";
|
||||
import { SecureNoteType } from "../../enums/secureNoteType";
|
||||
import { ImportResult } from "../../models/domain/importResult";
|
||||
import { CardView } from "../../models/view/cardView";
|
||||
import { CipherView } from "../../models/view/cipherView";
|
||||
import { IdentityView } from "../../models/view/identityView";
|
||||
import { LoginView } from "../../models/view/loginView";
|
||||
import { PasswordHistoryView } from "../../models/view/passwordHistoryView";
|
||||
import { SecureNoteView } from "../../models/view/secureNoteView";
|
||||
import { BaseImporter } from "../baseImporter";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
import {
|
||||
CategoryEnum,
|
||||
Details,
|
||||
ExportData,
|
||||
FieldsEntity,
|
||||
Item,
|
||||
LoginFieldTypeEnum,
|
||||
Overview,
|
||||
PasswordHistoryEntity,
|
||||
SectionsEntity,
|
||||
UrlsEntity,
|
||||
Value,
|
||||
VaultsEntity,
|
||||
} from "./types/onepassword1PuxImporterTypes";
|
||||
|
||||
export class OnePassword1PuxImporter extends BaseImporter implements Importer {
|
||||
result = new ImportResult();
|
||||
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const exportData: ExportData = JSON.parse(data);
|
||||
|
||||
const account = exportData.accounts[0];
|
||||
// TODO Add handling of multiple vaults
|
||||
// const personalVaults = account.vaults[0].filter((v) => v.attrs.type === VaultAttributeTypeEnum.Personal);
|
||||
account.vaults.forEach((vault: VaultsEntity) => {
|
||||
vault.items.forEach((item: Item) => {
|
||||
if (item.trashed === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
|
||||
const category = item.categoryUuid as CategoryEnum;
|
||||
switch (category) {
|
||||
case CategoryEnum.Login:
|
||||
case CategoryEnum.Database:
|
||||
case CategoryEnum.Password:
|
||||
case CategoryEnum.WirelessRouter:
|
||||
case CategoryEnum.Server:
|
||||
case CategoryEnum.API_Credential:
|
||||
cipher.type = CipherType.Login;
|
||||
cipher.login = new LoginView();
|
||||
break;
|
||||
case CategoryEnum.CreditCard:
|
||||
case CategoryEnum.BankAccount:
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card = new CardView();
|
||||
break;
|
||||
case CategoryEnum.SecureNote:
|
||||
case CategoryEnum.SoftwareLicense:
|
||||
case CategoryEnum.EmailAccount:
|
||||
case CategoryEnum.MedicalRecord:
|
||||
// case CategoryEnum.Document:
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote = new SecureNoteView();
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
break;
|
||||
case CategoryEnum.Identity:
|
||||
case CategoryEnum.DriversLicense:
|
||||
case CategoryEnum.OutdoorLicense:
|
||||
case CategoryEnum.Membership:
|
||||
case CategoryEnum.Passport:
|
||||
case CategoryEnum.RewardsProgram:
|
||||
case CategoryEnum.SocialSecurityNumber:
|
||||
cipher.type = CipherType.Identity;
|
||||
cipher.identity = new IdentityView();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
cipher.favorite = item.favIndex === 1 ? true : false;
|
||||
|
||||
this.processOverview(item.overview, cipher);
|
||||
|
||||
this.processLoginFields(item, cipher);
|
||||
|
||||
this.processDetails(category, item.details, cipher);
|
||||
|
||||
this.parsePasswordHistory(item.details.passwordHistory, cipher);
|
||||
|
||||
this.processSections(category, item.details.sections, cipher);
|
||||
|
||||
if (!this.isNullOrWhitespace(item.details.notesPlain)) {
|
||||
cipher.notes = item.details.notesPlain.split(this.newLineRegex).join("\n") + "\n";
|
||||
}
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
this.result.ciphers.push(cipher);
|
||||
});
|
||||
});
|
||||
|
||||
if (this.organization) {
|
||||
this.moveFoldersToCollections(this.result);
|
||||
}
|
||||
|
||||
this.result.success = true;
|
||||
return Promise.resolve(this.result);
|
||||
}
|
||||
|
||||
private processOverview(overview: Overview, cipher: CipherView) {
|
||||
if (overview == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
cipher.name = this.getValueOrDefault(overview.title);
|
||||
|
||||
if (overview.urls != null) {
|
||||
const urls: string[] = [];
|
||||
overview.urls.forEach((url: UrlsEntity) => {
|
||||
if (!this.isNullOrWhitespace(url.url)) {
|
||||
urls.push(url.url);
|
||||
}
|
||||
});
|
||||
cipher.login.uris = this.makeUriArray(urls);
|
||||
}
|
||||
|
||||
if (overview.tags != null && overview.tags.length > 0) {
|
||||
const folderName = this.capitalize(overview.tags[0]);
|
||||
this.processFolder(this.result, folderName);
|
||||
}
|
||||
}
|
||||
|
||||
private capitalize(inputString: string): string {
|
||||
return inputString.trim().replace(/\w\S*/g, (w) => w.replace(/^\w/, (c) => c.toUpperCase()));
|
||||
}
|
||||
|
||||
private processLoginFields(item: Item, cipher: CipherView) {
|
||||
if (item.details == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.details.loginFields == null || item.details.loginFields.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
item.details.loginFields.forEach((loginField) => {
|
||||
if (loginField.designation === "username" && loginField.value !== "") {
|
||||
cipher.type = CipherType.Login;
|
||||
cipher.login.username = loginField.value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (loginField.designation === "password" && loginField.value !== "") {
|
||||
cipher.type = CipherType.Login;
|
||||
cipher.login.password = loginField.value;
|
||||
return;
|
||||
}
|
||||
|
||||
let fieldValue = loginField.value;
|
||||
let fieldType: FieldType = FieldType.Text;
|
||||
switch (loginField.fieldType) {
|
||||
case LoginFieldTypeEnum.Password:
|
||||
fieldType = FieldType.Hidden;
|
||||
break;
|
||||
case LoginFieldTypeEnum.CheckBox:
|
||||
fieldValue = loginField.value !== "" ? "true" : "false";
|
||||
fieldType = FieldType.Boolean;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.processKvp(cipher, loginField.name, fieldValue, fieldType);
|
||||
});
|
||||
}
|
||||
|
||||
private processDetails(category: CategoryEnum, details: Details, cipher: CipherView) {
|
||||
if (category !== CategoryEnum.Password) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (details == null) {
|
||||
return;
|
||||
}
|
||||
cipher.login.password = details.password;
|
||||
}
|
||||
|
||||
private processSections(category: CategoryEnum, sections: SectionsEntity[], cipher: CipherView) {
|
||||
if (sections == null || sections.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
sections.forEach((section: SectionsEntity) => {
|
||||
if (section.fields == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.parseSectionFields(category, section.fields, cipher);
|
||||
});
|
||||
}
|
||||
|
||||
private parseSectionFields(category: CategoryEnum, fields: FieldsEntity[], cipher: CipherView) {
|
||||
fields.forEach((field: FieldsEntity) => {
|
||||
const valueKey = Object.keys(field.value)[0];
|
||||
const anyField = field as any;
|
||||
|
||||
if (
|
||||
anyField.value == null ||
|
||||
anyField.value[valueKey] == null ||
|
||||
anyField.value[valueKey] === ""
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldName = this.getFieldName(field.id, field.title);
|
||||
const fieldValue = this.extractValue(field.value, valueKey);
|
||||
|
||||
if (cipher.type === CipherType.Login) {
|
||||
if (this.fillLogin(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (category) {
|
||||
case CategoryEnum.Login:
|
||||
case CategoryEnum.Database:
|
||||
case CategoryEnum.EmailAccount:
|
||||
case CategoryEnum.WirelessRouter:
|
||||
break;
|
||||
|
||||
case CategoryEnum.Server:
|
||||
if (this.isNullOrWhitespace(cipher.login.uri) && field.id === "url") {
|
||||
cipher.login.uris = this.makeUriArray(fieldValue);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case CategoryEnum.API_Credential:
|
||||
if (this.fillApiCredentials(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if (cipher.type === CipherType.Card) {
|
||||
if (this.fillCreditCard(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (category === CategoryEnum.BankAccount) {
|
||||
if (this.fillBankAccount(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (cipher.type === CipherType.Identity) {
|
||||
if (this.fillIdentity(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
if (valueKey === "address") {
|
||||
// fieldValue is an object casted into a string, so access the plain value instead
|
||||
const { street, city, country, zip, state } = field.value.address;
|
||||
cipher.identity.address1 = this.getValueOrDefault(street);
|
||||
cipher.identity.city = this.getValueOrDefault(city);
|
||||
if (!this.isNullOrWhitespace(country)) {
|
||||
cipher.identity.country = country.toUpperCase();
|
||||
}
|
||||
cipher.identity.postalCode = this.getValueOrDefault(zip);
|
||||
cipher.identity.state = this.getValueOrDefault(state);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (category) {
|
||||
case CategoryEnum.Identity:
|
||||
break;
|
||||
case CategoryEnum.DriversLicense:
|
||||
if (this.fillDriversLicense(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CategoryEnum.OutdoorLicense:
|
||||
if (this.fillOutdoorLicense(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CategoryEnum.Membership:
|
||||
if (this.fillMembership(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CategoryEnum.Passport:
|
||||
if (this.fillPassport(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CategoryEnum.RewardsProgram:
|
||||
if (this.fillRewardsProgram(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CategoryEnum.SocialSecurityNumber:
|
||||
if (this.fillSSN(field, fieldValue, cipher)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Do not include a password field if it's already in the history
|
||||
if (
|
||||
field.title === "password" &&
|
||||
cipher.passwordHistory != null &&
|
||||
cipher.passwordHistory.some((h) => h.password === fieldValue)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO ?? If one of the fields is marked as guarded, then activate Password-Reprompt for the entire item
|
||||
if (field.guarded && cipher.reprompt === CipherRepromptType.None) {
|
||||
cipher.reprompt = CipherRepromptType.Password;
|
||||
}
|
||||
|
||||
const fieldType = valueKey === "concealed" ? FieldType.Hidden : FieldType.Text;
|
||||
this.processKvp(cipher, fieldName, fieldValue, fieldType);
|
||||
});
|
||||
}
|
||||
|
||||
private getFieldName(id: string, title: string): string {
|
||||
if (this.isNullOrWhitespace(title)) {
|
||||
return id;
|
||||
}
|
||||
|
||||
// Naive approach of checking if the fields id is usable
|
||||
if (id.length > 25 && RegExp(/[0-9]{2}[A-Z]{2}/, "i").test(id)) {
|
||||
return title;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
private extractValue(value: Value, valueKey: string): string {
|
||||
if (valueKey === "date") {
|
||||
return new Date(value.date * 1000).toUTCString();
|
||||
}
|
||||
|
||||
if (valueKey === "monthYear") {
|
||||
return value.monthYear.toString();
|
||||
}
|
||||
|
||||
return (value as any)[valueKey];
|
||||
}
|
||||
|
||||
private fillLogin(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
const fieldName = this.getFieldName(field.id, field.title);
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.login.username) && fieldName === "username") {
|
||||
cipher.login.username = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.login.password) && fieldName === "password") {
|
||||
cipher.login.password = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
this.isNullOrWhitespace(cipher.login.totp) &&
|
||||
field.id != null &&
|
||||
field.id.startsWith("TOTP_")
|
||||
) {
|
||||
cipher.login.totp = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private fillApiCredentials(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
const fieldName = this.getFieldName(field.id, field.title);
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.login.password) && fieldName === "credential") {
|
||||
cipher.login.password = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.login.uri) && fieldName === "hostname") {
|
||||
cipher.login.uris = this.makeUriArray(fieldValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private fillCreditCard(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
if (this.isNullOrWhitespace(cipher.card.number) && field.id === "ccnum") {
|
||||
cipher.card.number = fieldValue;
|
||||
cipher.card.brand = this.getCardBrand(fieldValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.card.code) && field.id === "cvv") {
|
||||
cipher.card.code = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.card.cardholderName) && field.id === "cardholder") {
|
||||
cipher.card.cardholderName = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.card.expiration) && field.id === "expiry") {
|
||||
const monthYear: string = fieldValue.toString().trim();
|
||||
cipher.card.expMonth = monthYear.substring(4, 6);
|
||||
if (cipher.card.expMonth[0] === "0") {
|
||||
cipher.card.expMonth = cipher.card.expMonth.substring(1, 2);
|
||||
}
|
||||
cipher.card.expYear = monthYear.substring(0, 4);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (field.id === "type") {
|
||||
// Skip since brand was determined from number above
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private fillBankAccount(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
if (this.isNullOrWhitespace(cipher.card.cardholderName) && field.id === "owner") {
|
||||
cipher.card.cardholderName = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private fillIdentity(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
if (this.isNullOrWhitespace(cipher.identity.firstName) && field.id === "firstname") {
|
||||
cipher.identity.firstName = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.lastName) && field.id === "lastname") {
|
||||
cipher.identity.lastName = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.middleName) && field.id === "initial") {
|
||||
cipher.identity.middleName = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.phone) && field.id === "defphone") {
|
||||
cipher.identity.phone = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.company) && field.id === "company") {
|
||||
cipher.identity.company = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.email) && field.id === "email") {
|
||||
cipher.identity.email = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.username) && field.id === "username") {
|
||||
cipher.identity.username = fieldValue;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private fillDriversLicense(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
if (this.isNullOrWhitespace(cipher.identity.firstName) && field.id === "fullname") {
|
||||
this.processFullName(cipher, fieldValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.address1) && field.id === "address") {
|
||||
cipher.identity.address1 = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO ISO code
|
||||
if (this.isNullOrWhitespace(cipher.identity.country) && field.id === "country") {
|
||||
cipher.identity.country = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.state) && field.id === "state") {
|
||||
cipher.identity.state = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.licenseNumber) && field.id === "number") {
|
||||
cipher.identity.licenseNumber = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private fillOutdoorLicense(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
if (this.isNullOrWhitespace(cipher.identity.firstName) && field.id === "name") {
|
||||
this.processFullName(cipher, fieldValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO ISO code
|
||||
if (this.isNullOrWhitespace(cipher.identity.country) && field.id === "country") {
|
||||
cipher.identity.country = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.state) && field.id === "state") {
|
||||
cipher.identity.state = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private fillMembership(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
if (this.isNullOrWhitespace(cipher.identity.firstName) && field.id === "member_name") {
|
||||
this.processFullName(cipher, fieldValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.company) && field.id === "org_name") {
|
||||
cipher.identity.company = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.phone) && field.id === "phone") {
|
||||
cipher.identity.phone = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private fillPassport(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
if (this.isNullOrWhitespace(cipher.identity.firstName) && field.id === "fullname") {
|
||||
this.processFullName(cipher, fieldValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO Iso
|
||||
if (this.isNullOrWhitespace(cipher.identity.country) && field.id === "issuing_country") {
|
||||
cipher.identity.country = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.passportNumber) && field.id === "number") {
|
||||
cipher.identity.passportNumber = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private fillRewardsProgram(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
if (this.isNullOrWhitespace(cipher.identity.firstName) && field.id === "member_name") {
|
||||
this.processFullName(cipher, fieldValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.company) && field.id === "company_name") {
|
||||
cipher.identity.company = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private fillSSN(field: FieldsEntity, fieldValue: string, cipher: CipherView): boolean {
|
||||
if (this.isNullOrWhitespace(cipher.identity.firstName) && field.id === "name") {
|
||||
this.processFullName(cipher, fieldValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isNullOrWhitespace(cipher.identity.ssn) && field.id === "number") {
|
||||
cipher.identity.ssn = fieldValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private parsePasswordHistory(historyItems: PasswordHistoryEntity[], cipher: CipherView) {
|
||||
if (historyItems == null || historyItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const maxSize = historyItems.length > 5 ? 5 : historyItems.length;
|
||||
cipher.passwordHistory = historyItems
|
||||
.filter((h: any) => !this.isNullOrWhitespace(h.value) && h.time != null)
|
||||
.sort((a, b) => b.time - a.time)
|
||||
.slice(0, maxSize)
|
||||
.map((h: any) => {
|
||||
const ph = new PasswordHistoryView();
|
||||
ph.password = h.value;
|
||||
ph.lastUsedDate = new Date(("" + h.time).length >= 13 ? h.time : h.time * 1000);
|
||||
return ph;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { CipherType } from "../../enums/cipherType";
|
||||
import { FieldType } from "../../enums/fieldType";
|
||||
import { ImportResult } from "../../models/domain/importResult";
|
||||
import { CipherView } from "../../models/view/cipherView";
|
||||
import { BaseImporter } from "../baseImporter";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
import { CipherType } from "../../enums/cipherType";
|
||||
import { FieldType } from "../../enums/fieldType";
|
||||
import { CipherView } from "../../models/view/cipherView";
|
||||
import { CipherImportContext } from "./cipherImportContext";
|
||||
|
||||
export const IgnoredProperties = [
|
||||
@@ -68,6 +68,7 @@ export abstract class OnePasswordCsvImporter extends BaseImporter implements Imp
|
||||
|
||||
let altUsername: string = null;
|
||||
for (const property in value) {
|
||||
// eslint-disable-next-line
|
||||
if (!value.hasOwnProperty(property) || this.isNullOrWhitespace(value[property])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Importer } from "../importer";
|
||||
import { IgnoredProperties, OnePasswordCsvImporter } from "./onepasswordCsvImporter";
|
||||
|
||||
import { CipherType } from "../../enums/cipherType";
|
||||
import { CardView } from "../../models/view/cardView";
|
||||
import { CipherView } from "../../models/view/cipherView";
|
||||
import { IdentityView } from "../../models/view/identityView";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
import { IgnoredProperties, OnePasswordCsvImporter } from "./onepasswordCsvImporter";
|
||||
|
||||
export class OnePasswordMacCsvImporter extends OnePasswordCsvImporter implements Importer {
|
||||
setCipherType(value: any, cipher: CipherView) {
|
||||
@@ -23,6 +23,7 @@ export class OnePasswordMacCsvImporter extends OnePasswordCsvImporter implements
|
||||
case "Login":
|
||||
case "Secure Note":
|
||||
IgnoredProperties.push("type");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Importer } from "../importer";
|
||||
import { CipherImportContext } from "./cipherImportContext";
|
||||
import { OnePasswordCsvImporter } from "./onepasswordCsvImporter";
|
||||
|
||||
import { CipherType } from "../../enums/cipherType";
|
||||
import { CardView } from "../../models/view/cardView";
|
||||
import { CipherView } from "../../models/view/cipherView";
|
||||
import { IdentityView } from "../../models/view/identityView";
|
||||
import { LoginView } from "../../models/view/loginView";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
import { CipherImportContext } from "./cipherImportContext";
|
||||
import { OnePasswordCsvImporter } from "./onepasswordCsvImporter";
|
||||
|
||||
export class OnePasswordWinCsvImporter extends OnePasswordCsvImporter implements Importer {
|
||||
constructor() {
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
export interface ExportData {
|
||||
accounts?: AccountsEntity[] | null;
|
||||
}
|
||||
export interface AccountsEntity {
|
||||
attrs: AccountAttributes;
|
||||
vaults?: VaultsEntity[] | null;
|
||||
}
|
||||
export interface AccountAttributes {
|
||||
accountName: string;
|
||||
name: string;
|
||||
avatar: string;
|
||||
email: string;
|
||||
uuid: string;
|
||||
domain: string;
|
||||
}
|
||||
export interface VaultsEntity {
|
||||
attrs: VaultAttributes;
|
||||
items?: Item[] | null;
|
||||
}
|
||||
export interface VaultAttributes {
|
||||
uuid: string;
|
||||
desc: string;
|
||||
avatar: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export enum CategoryEnum {
|
||||
Login = "001",
|
||||
CreditCard = "002",
|
||||
SecureNote = "003",
|
||||
Identity = "004",
|
||||
Password = "005",
|
||||
Document = "006",
|
||||
SoftwareLicense = "100",
|
||||
BankAccount = "101",
|
||||
Database = "102",
|
||||
DriversLicense = "103",
|
||||
OutdoorLicense = "104",
|
||||
Membership = "105",
|
||||
Passport = "106",
|
||||
RewardsProgram = "107",
|
||||
SocialSecurityNumber = "108",
|
||||
WirelessRouter = "109",
|
||||
Server = "110",
|
||||
EmailAccount = "111",
|
||||
API_Credential = "112",
|
||||
MedicalRecord = "113",
|
||||
}
|
||||
|
||||
export interface Item {
|
||||
uuid: string;
|
||||
favIndex: number;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
trashed?: boolean;
|
||||
categoryUuid: string;
|
||||
details: Details;
|
||||
overview: Overview;
|
||||
}
|
||||
export interface Details {
|
||||
loginFields?: (LoginFieldsEntity | null)[] | null;
|
||||
notesPlain?: string | null;
|
||||
sections?: (SectionsEntity | null)[] | null;
|
||||
passwordHistory?: (PasswordHistoryEntity | null)[] | null;
|
||||
documentAttributes?: DocumentAttributes | null;
|
||||
password?: string | null;
|
||||
}
|
||||
|
||||
export enum LoginFieldTypeEnum {
|
||||
TextOrHtml = "T",
|
||||
EmailAddress = "E",
|
||||
URL = "U",
|
||||
Number = "N",
|
||||
Password = "P",
|
||||
TextArea = "A",
|
||||
PhoneNumber = "T",
|
||||
CheckBox = "C",
|
||||
}
|
||||
export interface LoginFieldsEntity {
|
||||
value: string;
|
||||
id: string;
|
||||
name: string;
|
||||
fieldType: LoginFieldTypeEnum | string;
|
||||
designation?: string | null;
|
||||
}
|
||||
export interface SectionsEntity {
|
||||
title: string;
|
||||
name?: string | null;
|
||||
fields?: FieldsEntity[] | null;
|
||||
}
|
||||
export interface FieldsEntity {
|
||||
title: string;
|
||||
id: string;
|
||||
value: Value;
|
||||
indexAtSource: number;
|
||||
guarded: boolean;
|
||||
multiline: boolean;
|
||||
dontGenerate: boolean;
|
||||
placeholder?: string;
|
||||
inputTraits: InputTraits;
|
||||
clipboardFilter?: string | null;
|
||||
}
|
||||
export interface Value {
|
||||
totp?: string | null;
|
||||
date?: number | null;
|
||||
string?: string | null;
|
||||
concealed?: string | null;
|
||||
email?: string | null;
|
||||
phone?: string | null;
|
||||
menu?: string | null;
|
||||
gender?: string | null;
|
||||
monthYear?: number | null;
|
||||
url?: string | null;
|
||||
address?: Address | null;
|
||||
creditCardType?: string | null;
|
||||
creditCardNumber?: string | null;
|
||||
reference?: string | null;
|
||||
}
|
||||
export interface Address {
|
||||
street: string;
|
||||
city: string;
|
||||
country: string;
|
||||
zip: string;
|
||||
state: string;
|
||||
}
|
||||
export interface InputTraits {
|
||||
keyboard: string;
|
||||
correction: string;
|
||||
capitalization: string;
|
||||
}
|
||||
export interface PasswordHistoryEntity {
|
||||
value: string;
|
||||
time: number;
|
||||
}
|
||||
export interface DocumentAttributes {
|
||||
fileName: string;
|
||||
documentId: string;
|
||||
decryptedSize: number;
|
||||
}
|
||||
export interface Overview {
|
||||
subtitle: string;
|
||||
title: string;
|
||||
url: string;
|
||||
urls?: UrlsEntity[] | null;
|
||||
ps?: number | null;
|
||||
pbe?: number | null;
|
||||
pgrng?: boolean | null;
|
||||
tags?: string[] | null;
|
||||
}
|
||||
export interface UrlsEntity {
|
||||
label: string;
|
||||
url: string;
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
import { CollectionView } from "../models/view/collectionView";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { CollectionView } from "../models/view/collectionView";
|
||||
import { FolderView } from "../models/view/folderView";
|
||||
|
||||
export class PadlockCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class PassKeepCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class PassmanJsonImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
import { CollectionView } from "../models/view/collectionView";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { CollectionView } from "../models/view/collectionView";
|
||||
|
||||
export class PasspackCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class PasswordAgentCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { CardView } from "../models/view/cardView";
|
||||
import { FolderView } from "../models/view/folderView";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class PasswordBossJsonImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
@@ -52,6 +50,7 @@ export class PasswordBossJsonImporter extends BaseImporter implements Importer {
|
||||
}
|
||||
|
||||
for (const property in value.identifiers) {
|
||||
// eslint-disable-next-line
|
||||
if (!value.identifiers.hasOwnProperty(property)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class PasswordDragonXmlImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class PasswordSafeXmlImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class PasswordWalletTxtImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
import { CardView } from "../models/view/cardView";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { CardView } from "../models/view/cardView";
|
||||
|
||||
export class RememBearCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class RoboFormCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class SafariCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { FieldType } from "../enums/fieldType";
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { CipherView } from "../models/view/cipherView";
|
||||
import { FieldView } from "../models/view/fieldView";
|
||||
import { FolderView } from "../models/view/folderView";
|
||||
import { SecureNoteView } from "../models/view/secureNoteView";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
|
||||
import { FieldType } from "../enums/fieldType";
|
||||
import { CipherView } from "../models/view/cipherView";
|
||||
import { FieldView } from "../models/view/fieldView";
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class SafeInCloudXmlImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class SaferPassCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class SecureSafeCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
import { CipherView } from "../models/view/cipherView";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class SplashIdCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class StickyPasswordXmlImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { CardView } from "../models/view/cardView";
|
||||
import { SecureNoteView } from "../models/view/secureNoteView";
|
||||
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { SecureNoteType } from "../enums/secureNoteType";
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
const PropertiesToIgnore = [
|
||||
"kind",
|
||||
@@ -70,7 +68,7 @@ export class TrueKeyCsvImporter extends BaseImporter implements Importer {
|
||||
}
|
||||
for (const property in value) {
|
||||
if (
|
||||
value.hasOwnProperty(property) &&
|
||||
value.hasOwnProperty(property) && // eslint-disable-line
|
||||
PropertiesToIgnore.indexOf(property.toLowerCase()) < 0 &&
|
||||
!this.isNullOrWhitespace(value[property])
|
||||
) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class UpmCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
|
||||
export class YotiCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
import { ImportResult } from "../models/domain/importResult";
|
||||
import { CipherView } from "../models/view/cipherView";
|
||||
|
||||
import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class ZohoVaultCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { I18nService } from "../abstractions/i18n.service";
|
||||
|
||||
import { IFrameComponent } from "./iframe_component";
|
||||
|
||||
export class CaptchaIFrame extends IFrameComponent {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { I18nService } from "../abstractions/i18n.service";
|
||||
|
||||
export abstract class IFrameComponent {
|
||||
iframe: HTMLIFrameElement;
|
||||
private connectorLink: HTMLAnchorElement;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ItemView } from "../models/view/itemView";
|
||||
|
||||
import { LinkedIdType } from "../enums/linkedIdType";
|
||||
import { ItemView } from "../models/view/itemView";
|
||||
|
||||
export class LinkedMetadata {
|
||||
constructor(readonly propertyKey: string, private readonly _i18nKey?: string) {}
|
||||
|
||||
70
common/src/misc/logInStrategies/apiLogin.strategy.ts
Normal file
70
common/src/misc/logInStrategies/apiLogin.strategy.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { AppIdService } from "../../abstractions/appId.service";
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { EnvironmentService } from "../../abstractions/environment.service";
|
||||
import { KeyConnectorService } from "../../abstractions/keyConnector.service";
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { MessagingService } from "../../abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { TokenService } from "../../abstractions/token.service";
|
||||
import { TwoFactorService } from "../../abstractions/twoFactor.service";
|
||||
import { ApiLogInCredentials } from "../../models/domain/logInCredentials";
|
||||
import { ApiTokenRequest } from "../../models/request/identityToken/apiTokenRequest";
|
||||
import { IdentityTokenResponse } from "../../models/response/identityTokenResponse";
|
||||
|
||||
import { LogInStrategy } from "./logIn.strategy";
|
||||
|
||||
export class ApiLogInStrategy extends LogInStrategy {
|
||||
tokenRequest: ApiTokenRequest;
|
||||
|
||||
constructor(
|
||||
cryptoService: CryptoService,
|
||||
apiService: ApiService,
|
||||
tokenService: TokenService,
|
||||
appIdService: AppIdService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
messagingService: MessagingService,
|
||||
logService: LogService,
|
||||
stateService: StateService,
|
||||
twoFactorService: TwoFactorService,
|
||||
private environmentService: EnvironmentService,
|
||||
private keyConnectorService: KeyConnectorService
|
||||
) {
|
||||
super(
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
appIdService,
|
||||
platformUtilsService,
|
||||
messagingService,
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService
|
||||
);
|
||||
}
|
||||
|
||||
async onSuccessfulLogin(tokenResponse: IdentityTokenResponse) {
|
||||
if (tokenResponse.apiUseKeyConnector) {
|
||||
const keyConnectorUrl = this.environmentService.getKeyConnectorUrl();
|
||||
await this.keyConnectorService.getAndSetKey(keyConnectorUrl);
|
||||
}
|
||||
}
|
||||
|
||||
async logIn(credentials: ApiLogInCredentials) {
|
||||
this.tokenRequest = new ApiTokenRequest(
|
||||
credentials.clientId,
|
||||
credentials.clientSecret,
|
||||
await this.buildTwoFactor(),
|
||||
await this.buildDeviceRequest()
|
||||
);
|
||||
|
||||
return this.startLogIn();
|
||||
}
|
||||
|
||||
protected async saveAccountInformation(tokenResponse: IdentityTokenResponse) {
|
||||
await super.saveAccountInformation(tokenResponse);
|
||||
await this.stateService.setApiKeyClientId(this.tokenRequest.clientId);
|
||||
await this.stateService.setApiKeyClientSecret(this.tokenRequest.clientSecret);
|
||||
}
|
||||
}
|
||||
178
common/src/misc/logInStrategies/logIn.strategy.ts
Normal file
178
common/src/misc/logInStrategies/logIn.strategy.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { AppIdService } from "../../abstractions/appId.service";
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { MessagingService } from "../../abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { TokenService } from "../../abstractions/token.service";
|
||||
import { TwoFactorService } from "../../abstractions/twoFactor.service";
|
||||
import { TwoFactorProviderType } from "../../enums/twoFactorProviderType";
|
||||
import { Account, AccountProfile, AccountTokens } from "../../models/domain/account";
|
||||
import { AuthResult } from "../../models/domain/authResult";
|
||||
import {
|
||||
ApiLogInCredentials,
|
||||
PasswordLogInCredentials,
|
||||
SsoLogInCredentials,
|
||||
} from "../../models/domain/logInCredentials";
|
||||
import { DeviceRequest } from "../../models/request/deviceRequest";
|
||||
import { ApiTokenRequest } from "../../models/request/identityToken/apiTokenRequest";
|
||||
import { PasswordTokenRequest } from "../../models/request/identityToken/passwordTokenRequest";
|
||||
import { SsoTokenRequest } from "../../models/request/identityToken/ssoTokenRequest";
|
||||
import { TokenRequestTwoFactor } from "../../models/request/identityToken/tokenRequest";
|
||||
import { KeysRequest } from "../../models/request/keysRequest";
|
||||
import { IdentityCaptchaResponse } from "../../models/response/identityCaptchaResponse";
|
||||
import { IdentityTokenResponse } from "../../models/response/identityTokenResponse";
|
||||
import { IdentityTwoFactorResponse } from "../../models/response/identityTwoFactorResponse";
|
||||
|
||||
export abstract class LogInStrategy {
|
||||
protected abstract tokenRequest: ApiTokenRequest | PasswordTokenRequest | SsoTokenRequest;
|
||||
protected captchaBypassToken: string = null;
|
||||
|
||||
constructor(
|
||||
protected cryptoService: CryptoService,
|
||||
protected apiService: ApiService,
|
||||
protected tokenService: TokenService,
|
||||
protected appIdService: AppIdService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected messagingService: MessagingService,
|
||||
protected logService: LogService,
|
||||
protected stateService: StateService,
|
||||
protected twoFactorService: TwoFactorService
|
||||
) {}
|
||||
|
||||
abstract logIn(
|
||||
credentials: ApiLogInCredentials | PasswordLogInCredentials | SsoLogInCredentials
|
||||
): Promise<AuthResult>;
|
||||
|
||||
async logInTwoFactor(
|
||||
twoFactor: TokenRequestTwoFactor,
|
||||
captchaResponse: string = null
|
||||
): Promise<AuthResult> {
|
||||
this.tokenRequest.setTwoFactor(twoFactor);
|
||||
return this.startLogIn();
|
||||
}
|
||||
|
||||
protected async startLogIn(): Promise<AuthResult> {
|
||||
this.twoFactorService.clearSelectedProvider();
|
||||
|
||||
const response = await this.apiService.postIdentityToken(this.tokenRequest);
|
||||
|
||||
if (response instanceof IdentityTwoFactorResponse) {
|
||||
return this.processTwoFactorResponse(response);
|
||||
} else if (response instanceof IdentityCaptchaResponse) {
|
||||
return this.processCaptchaResponse(response);
|
||||
} else if (response instanceof IdentityTokenResponse) {
|
||||
return this.processTokenResponse(response);
|
||||
}
|
||||
|
||||
throw new Error("Invalid response object.");
|
||||
}
|
||||
|
||||
protected onSuccessfulLogin(response: IdentityTokenResponse): Promise<void> {
|
||||
// Implemented in subclass if required
|
||||
return null;
|
||||
}
|
||||
|
||||
protected async buildDeviceRequest() {
|
||||
const appId = await this.appIdService.getAppId();
|
||||
return new DeviceRequest(appId, this.platformUtilsService);
|
||||
}
|
||||
|
||||
protected async buildTwoFactor(userProvidedTwoFactor?: TokenRequestTwoFactor) {
|
||||
if (userProvidedTwoFactor != null) {
|
||||
return userProvidedTwoFactor;
|
||||
}
|
||||
|
||||
const storedTwoFactorToken = await this.tokenService.getTwoFactorToken();
|
||||
if (storedTwoFactorToken != null) {
|
||||
return {
|
||||
token: storedTwoFactorToken,
|
||||
provider: TwoFactorProviderType.Remember,
|
||||
remember: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
token: null,
|
||||
provider: null,
|
||||
remember: false,
|
||||
};
|
||||
}
|
||||
|
||||
protected async saveAccountInformation(tokenResponse: IdentityTokenResponse) {
|
||||
const accountInformation = await this.tokenService.decodeToken(tokenResponse.accessToken);
|
||||
await this.stateService.addAccount(
|
||||
new Account({
|
||||
profile: {
|
||||
...new AccountProfile(),
|
||||
...{
|
||||
userId: accountInformation.sub,
|
||||
email: accountInformation.email,
|
||||
hasPremiumPersonally: accountInformation.premium,
|
||||
kdfIterations: tokenResponse.kdfIterations,
|
||||
kdfType: tokenResponse.kdf,
|
||||
},
|
||||
},
|
||||
tokens: {
|
||||
...new AccountTokens(),
|
||||
...{
|
||||
accessToken: tokenResponse.accessToken,
|
||||
refreshToken: tokenResponse.refreshToken,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
protected async processTokenResponse(response: IdentityTokenResponse): Promise<AuthResult> {
|
||||
const result = new AuthResult();
|
||||
result.resetMasterPassword = response.resetMasterPassword;
|
||||
result.forcePasswordReset = response.forcePasswordReset;
|
||||
|
||||
await this.saveAccountInformation(response);
|
||||
|
||||
if (response.twoFactorToken != null) {
|
||||
await this.tokenService.setTwoFactorToken(response);
|
||||
}
|
||||
|
||||
const newSsoUser = response.key == null;
|
||||
if (!newSsoUser) {
|
||||
await this.cryptoService.setEncKey(response.key);
|
||||
await this.cryptoService.setEncPrivateKey(
|
||||
response.privateKey ?? (await this.createKeyPairForOldAccount())
|
||||
);
|
||||
}
|
||||
|
||||
await this.onSuccessfulLogin(response);
|
||||
|
||||
await this.stateService.setBiometricLocked(false);
|
||||
this.messagingService.send("loggedIn");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async processTwoFactorResponse(response: IdentityTwoFactorResponse): Promise<AuthResult> {
|
||||
const result = new AuthResult();
|
||||
result.twoFactorProviders = response.twoFactorProviders2;
|
||||
this.twoFactorService.setProviders(response);
|
||||
this.captchaBypassToken = response.captchaToken ?? null;
|
||||
return result;
|
||||
}
|
||||
|
||||
private async processCaptchaResponse(response: IdentityCaptchaResponse): Promise<AuthResult> {
|
||||
const result = new AuthResult();
|
||||
result.captchaSiteKey = response.siteKey;
|
||||
return result;
|
||||
}
|
||||
|
||||
private async createKeyPairForOldAccount() {
|
||||
try {
|
||||
const [publicKey, privateKey] = await this.cryptoService.makeKeyPair();
|
||||
await this.apiService.postAccountKeys(new KeysRequest(publicKey, privateKey.encryptedString));
|
||||
return privateKey.encryptedString;
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
95
common/src/misc/logInStrategies/passwordLogin.strategy.ts
Normal file
95
common/src/misc/logInStrategies/passwordLogin.strategy.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { AppIdService } from "../../abstractions/appId.service";
|
||||
import { AuthService } from "../../abstractions/auth.service";
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { MessagingService } from "../../abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { TokenService } from "../../abstractions/token.service";
|
||||
import { TwoFactorService } from "../../abstractions/twoFactor.service";
|
||||
import { HashPurpose } from "../../enums/hashPurpose";
|
||||
import { AuthResult } from "../../models/domain/authResult";
|
||||
import { PasswordLogInCredentials } from "../../models/domain/logInCredentials";
|
||||
import { SymmetricCryptoKey } from "../../models/domain/symmetricCryptoKey";
|
||||
import { PasswordTokenRequest } from "../../models/request/identityToken/passwordTokenRequest";
|
||||
import { TokenRequestTwoFactor } from "../../models/request/identityToken/tokenRequest";
|
||||
|
||||
import { LogInStrategy } from "./logIn.strategy";
|
||||
|
||||
export class PasswordLogInStrategy extends LogInStrategy {
|
||||
get email() {
|
||||
return this.tokenRequest.email;
|
||||
}
|
||||
|
||||
get masterPasswordHash() {
|
||||
return this.tokenRequest.masterPasswordHash;
|
||||
}
|
||||
|
||||
tokenRequest: PasswordTokenRequest;
|
||||
|
||||
private localHashedPassword: string;
|
||||
private key: SymmetricCryptoKey;
|
||||
|
||||
constructor(
|
||||
cryptoService: CryptoService,
|
||||
apiService: ApiService,
|
||||
tokenService: TokenService,
|
||||
appIdService: AppIdService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
messagingService: MessagingService,
|
||||
logService: LogService,
|
||||
stateService: StateService,
|
||||
twoFactorService: TwoFactorService,
|
||||
private authService: AuthService
|
||||
) {
|
||||
super(
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
appIdService,
|
||||
platformUtilsService,
|
||||
messagingService,
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService
|
||||
);
|
||||
}
|
||||
|
||||
async onSuccessfulLogin() {
|
||||
await this.cryptoService.setKey(this.key);
|
||||
await this.cryptoService.setKeyHash(this.localHashedPassword);
|
||||
}
|
||||
|
||||
async logInTwoFactor(
|
||||
twoFactor: TokenRequestTwoFactor,
|
||||
captchaResponse: string
|
||||
): Promise<AuthResult> {
|
||||
this.tokenRequest.captchaResponse = captchaResponse ?? this.captchaBypassToken;
|
||||
return super.logInTwoFactor(twoFactor);
|
||||
}
|
||||
|
||||
async logIn(credentials: PasswordLogInCredentials) {
|
||||
const { email, masterPassword, captchaToken, twoFactor } = credentials;
|
||||
|
||||
this.key = await this.authService.makePreloginKey(masterPassword, email);
|
||||
|
||||
// Hash the password early (before authentication) so we don't persist it in memory in plaintext
|
||||
this.localHashedPassword = await this.cryptoService.hashPassword(
|
||||
masterPassword,
|
||||
this.key,
|
||||
HashPurpose.LocalAuthorization
|
||||
);
|
||||
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, this.key);
|
||||
|
||||
this.tokenRequest = new PasswordTokenRequest(
|
||||
email,
|
||||
hashedPassword,
|
||||
captchaToken,
|
||||
await this.buildTwoFactor(twoFactor),
|
||||
await this.buildDeviceRequest()
|
||||
);
|
||||
|
||||
return this.startLogIn();
|
||||
}
|
||||
}
|
||||
70
common/src/misc/logInStrategies/ssoLogin.strategy.ts
Normal file
70
common/src/misc/logInStrategies/ssoLogin.strategy.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { AppIdService } from "../../abstractions/appId.service";
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { KeyConnectorService } from "../../abstractions/keyConnector.service";
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { MessagingService } from "../../abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { TokenService } from "../../abstractions/token.service";
|
||||
import { TwoFactorService } from "../../abstractions/twoFactor.service";
|
||||
import { SsoLogInCredentials } from "../../models/domain/logInCredentials";
|
||||
import { SsoTokenRequest } from "../../models/request/identityToken/ssoTokenRequest";
|
||||
import { IdentityTokenResponse } from "../../models/response/identityTokenResponse";
|
||||
|
||||
import { LogInStrategy } from "./logIn.strategy";
|
||||
|
||||
export class SsoLogInStrategy extends LogInStrategy {
|
||||
tokenRequest: SsoTokenRequest;
|
||||
orgId: string;
|
||||
|
||||
constructor(
|
||||
cryptoService: CryptoService,
|
||||
apiService: ApiService,
|
||||
tokenService: TokenService,
|
||||
appIdService: AppIdService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
messagingService: MessagingService,
|
||||
logService: LogService,
|
||||
stateService: StateService,
|
||||
twoFactorService: TwoFactorService,
|
||||
private keyConnectorService: KeyConnectorService
|
||||
) {
|
||||
super(
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
appIdService,
|
||||
platformUtilsService,
|
||||
messagingService,
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService
|
||||
);
|
||||
}
|
||||
|
||||
async onSuccessfulLogin(tokenResponse: IdentityTokenResponse) {
|
||||
const newSsoUser = tokenResponse.key == null;
|
||||
|
||||
if (tokenResponse.keyConnectorUrl != null) {
|
||||
if (!newSsoUser) {
|
||||
await this.keyConnectorService.getAndSetKey(tokenResponse.keyConnectorUrl);
|
||||
} else {
|
||||
await this.keyConnectorService.convertNewSsoUserToKeyConnector(tokenResponse, this.orgId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async logIn(credentials: SsoLogInCredentials) {
|
||||
this.orgId = credentials.orgId;
|
||||
this.tokenRequest = new SsoTokenRequest(
|
||||
credentials.code,
|
||||
credentials.codeVerifier,
|
||||
credentials.redirectUrl,
|
||||
await this.buildTwoFactor(credentials.twoFactor),
|
||||
await this.buildDeviceRequest()
|
||||
);
|
||||
|
||||
return this.startLogIn();
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
/* eslint-disable no-useless-escape */
|
||||
import * as tldts from "tldts";
|
||||
|
||||
import { I18nService } from "../abstractions/i18n.service";
|
||||
|
||||
// tslint:disable-next-line
|
||||
const nodeURL = typeof window === "undefined" ? require("url") : null;
|
||||
|
||||
export class Utils {
|
||||
static inited = false;
|
||||
static isNativeScript = false;
|
||||
static isNode = false;
|
||||
static isBrowser = true;
|
||||
static isMobileBrowser = false;
|
||||
@@ -31,18 +30,13 @@ export class Utils {
|
||||
(process as any).release != null &&
|
||||
(process as any).release.name === "node";
|
||||
Utils.isBrowser = typeof window !== "undefined";
|
||||
Utils.isNativeScript = !Utils.isNode && !Utils.isBrowser;
|
||||
Utils.isMobileBrowser = Utils.isBrowser && this.isMobile(window);
|
||||
Utils.isAppleMobileBrowser = Utils.isBrowser && this.isAppleMobile(window);
|
||||
Utils.global = Utils.isNativeScript
|
||||
? global
|
||||
: Utils.isNode && !Utils.isBrowser
|
||||
? global
|
||||
: window;
|
||||
Utils.global = Utils.isNode && !Utils.isBrowser ? global : window;
|
||||
}
|
||||
|
||||
static fromB64ToArray(str: string): Uint8Array {
|
||||
if (Utils.isNode || Utils.isNativeScript) {
|
||||
if (Utils.isNode) {
|
||||
return new Uint8Array(Buffer.from(str, "base64"));
|
||||
} else {
|
||||
const binaryString = window.atob(str);
|
||||
@@ -59,7 +53,7 @@ export class Utils {
|
||||
}
|
||||
|
||||
static fromHexToArray(str: string): Uint8Array {
|
||||
if (Utils.isNode || Utils.isNativeScript) {
|
||||
if (Utils.isNode) {
|
||||
return new Uint8Array(Buffer.from(str, "hex"));
|
||||
} else {
|
||||
const bytes = new Uint8Array(str.length / 2);
|
||||
@@ -71,7 +65,7 @@ export class Utils {
|
||||
}
|
||||
|
||||
static fromUtf8ToArray(str: string): Uint8Array {
|
||||
if (Utils.isNode || Utils.isNativeScript) {
|
||||
if (Utils.isNode) {
|
||||
return new Uint8Array(Buffer.from(str, "utf8"));
|
||||
} else {
|
||||
const strUtf8 = unescape(encodeURIComponent(str));
|
||||
@@ -92,7 +86,7 @@ export class Utils {
|
||||
}
|
||||
|
||||
static fromBufferToB64(buffer: ArrayBuffer): string {
|
||||
if (Utils.isNode || Utils.isNativeScript) {
|
||||
if (Utils.isNode) {
|
||||
return Buffer.from(buffer).toString("base64");
|
||||
} else {
|
||||
let binary = "";
|
||||
@@ -113,7 +107,7 @@ export class Utils {
|
||||
}
|
||||
|
||||
static fromBufferToUtf8(buffer: ArrayBuffer): string {
|
||||
if (Utils.isNode || Utils.isNativeScript) {
|
||||
if (Utils.isNode) {
|
||||
return Buffer.from(buffer).toString("utf8");
|
||||
} else {
|
||||
const bytes = new Uint8Array(buffer);
|
||||
@@ -128,7 +122,7 @@ export class Utils {
|
||||
|
||||
// ref: https://stackoverflow.com/a/40031979/1090359
|
||||
static fromBufferToHex(buffer: ArrayBuffer): string {
|
||||
if (Utils.isNode || Utils.isNativeScript) {
|
||||
if (Utils.isNode) {
|
||||
return Buffer.from(buffer).toString("hex");
|
||||
} else {
|
||||
const bytes = new Uint8Array(buffer);
|
||||
@@ -161,7 +155,7 @@ export class Utils {
|
||||
}
|
||||
|
||||
static fromUtf8ToB64(utfStr: string): string {
|
||||
if (Utils.isNode || Utils.isNativeScript) {
|
||||
if (Utils.isNode) {
|
||||
return Buffer.from(utfStr, "utf8").toString("base64");
|
||||
} else {
|
||||
return decodeURIComponent(escape(window.btoa(utfStr)));
|
||||
@@ -173,7 +167,7 @@ export class Utils {
|
||||
}
|
||||
|
||||
static fromB64ToUtf8(b64Str: string): string {
|
||||
if (Utils.isNode || Utils.isNativeScript) {
|
||||
if (Utils.isNode) {
|
||||
return Buffer.from(b64Str, "base64").toString("utf8");
|
||||
} else {
|
||||
return decodeURIComponent(escape(window.atob(b64Str)));
|
||||
@@ -183,9 +177,7 @@ export class Utils {
|
||||
// ref: http://stackoverflow.com/a/2117523/1090359
|
||||
static newGuid(): string {
|
||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
||||
// tslint:disable-next-line
|
||||
const r = (Math.random() * 16) | 0;
|
||||
// tslint:disable-next-line
|
||||
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
@@ -313,6 +305,10 @@ export class Utils {
|
||||
return str == null || typeof str !== "string" || str.trim() === "";
|
||||
}
|
||||
|
||||
static isNullOrEmpty(str: string): boolean {
|
||||
return str == null || typeof str !== "string" || str == "";
|
||||
}
|
||||
|
||||
static nameOf<T>(name: string & keyof T) {
|
||||
return name;
|
||||
}
|
||||
@@ -350,7 +346,6 @@ export class Utils {
|
||||
private static isMobile(win: Window) {
|
||||
let mobile = false;
|
||||
((a) => {
|
||||
// tslint:disable-next-line
|
||||
if (
|
||||
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(
|
||||
a
|
||||
@@ -375,7 +370,7 @@ export class Utils {
|
||||
private static getUrlObject(uriString: string): URL {
|
||||
try {
|
||||
if (nodeURL != null) {
|
||||
return nodeURL.URL ? new nodeURL.URL(uriString) : nodeURL.parse(uriString);
|
||||
return new nodeURL.URL(uriString);
|
||||
} else if (typeof URL === "function") {
|
||||
return new URL(uriString);
|
||||
} else if (window != null) {
|
||||
|
||||
@@ -12,9 +12,9 @@ export class WebAuthnIFrame {
|
||||
private webAuthnNewTab: boolean,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private successCallback: Function,
|
||||
private errorCallback: Function,
|
||||
private infoCallback: Function
|
||||
private successCallback: Function, // eslint-disable-line
|
||||
private errorCallback: Function, // eslint-disable-line
|
||||
private infoCallback: Function // eslint-disable-line
|
||||
) {
|
||||
this.connectorLink = win.document.createElement("a");
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { BaseResponse } from "../response/baseResponse";
|
||||
|
||||
import { FieldType } from "../../enums/fieldType";
|
||||
import { LinkedIdType } from "../../enums/linkedIdType";
|
||||
import { BaseResponse } from "../response/baseResponse";
|
||||
|
||||
export class FieldApi extends BaseResponse {
|
||||
name: string;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user