mirror of
https://github.com/bitwarden/jslib
synced 2026-01-03 09:03:13 +00:00
Merge branch 'master' into AccountService
This commit is contained in:
@@ -118,7 +118,7 @@ export class AddEditCustomFieldsComponent implements OnChanges {
|
||||
}
|
||||
|
||||
this.cipher.fields
|
||||
.filter(f => f.type = FieldType.Linked)
|
||||
.filter(f => f.type === FieldType.Linked)
|
||||
.forEach(f => f.linkedId = this.linkedFieldOptions[0].value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,10 @@ export class ExportComponent implements OnInit {
|
||||
}
|
||||
|
||||
const secret = this.exportForm.get('secret').value;
|
||||
if (!await this.userVerificationService.verifyUser(secret)) {
|
||||
try {
|
||||
await this.userVerificationService.verifyUser(secret);
|
||||
} catch (e) {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), e.message);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
NG_VALUE_ACCESSOR,
|
||||
} from '@angular/forms';
|
||||
|
||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
||||
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
|
||||
import { UserVerificationService } from 'jslib-common/abstractions/userVerification.service';
|
||||
|
||||
import { VerificationType } from 'jslib-common/enums/verificationType';
|
||||
|
||||
@@ -34,27 +34,20 @@ export class VerifyMasterPasswordComponent implements ControlValueAccessor, OnIn
|
||||
|
||||
private onChange: (value: Verification) => void;
|
||||
|
||||
constructor(private keyConnectorService: KeyConnectorService, private apiService: ApiService) { }
|
||||
constructor(private keyConnectorService: KeyConnectorService,
|
||||
private userVerificationService: UserVerificationService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
this.usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
|
||||
this.processChanges(this.secret.value);
|
||||
|
||||
this.secret.valueChanges.subscribe(secret => {
|
||||
if (this.onChange == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.onChange({
|
||||
type: this.usesKeyConnector ? VerificationType.OTP : VerificationType.MasterPassword,
|
||||
secret: secret,
|
||||
});
|
||||
});
|
||||
this.secret.valueChanges.subscribe(secret => this.processChanges(secret));
|
||||
}
|
||||
|
||||
async requestOTP() {
|
||||
if (this.usesKeyConnector) {
|
||||
this.disableRequestOTP = true;
|
||||
await this.apiService.postAccountRequestOTP();
|
||||
await this.userVerificationService.requestOTP();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,4 +71,15 @@ export class VerifyMasterPasswordComponent implements ControlValueAccessor, OnIn
|
||||
this.secret.enable();
|
||||
}
|
||||
}
|
||||
|
||||
private processChanges(secret: string) {
|
||||
if (this.onChange == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.onChange({
|
||||
type: this.usesKeyConnector ? VerificationType.OTP : VerificationType.MasterPassword,
|
||||
secret: secret,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,4 +6,5 @@ export abstract class UserVerificationService {
|
||||
buildRequest: <T extends SecretVerificationRequest> (verification: Verification,
|
||||
requestClass?: new () => T, alreadyHashed?: boolean) => Promise<T>;
|
||||
verifyUser: (verification: Verification) => Promise<boolean>;
|
||||
requestOTP: () => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FieldType } from '../../enums/fieldType';
|
||||
import { LinkedIdType } from '../../enums/linkedIdType';
|
||||
|
||||
import { FieldView } from '../view/fieldView';
|
||||
|
||||
@@ -18,6 +19,7 @@ export class Field {
|
||||
view.type = req.type;
|
||||
view.value = req.value;
|
||||
view.name = req.name;
|
||||
view.linkedId = req.linkedId;
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -25,12 +27,14 @@ export class Field {
|
||||
domain.type = req.type;
|
||||
domain.value = req.value != null ? new EncString(req.value) : null;
|
||||
domain.name = req.name != null ? new EncString(req.name) : null;
|
||||
domain.linkedId = req.linkedId;
|
||||
return domain;
|
||||
}
|
||||
|
||||
name: string;
|
||||
value: string;
|
||||
type: FieldType;
|
||||
linkedId: LinkedIdType;
|
||||
|
||||
constructor(o?: FieldView | FieldDomain) {
|
||||
if (o == null) {
|
||||
@@ -45,5 +49,6 @@ export class Field {
|
||||
this.value = o.value?.encryptedString;
|
||||
}
|
||||
this.type = o.type;
|
||||
this.linkedId = o.linkedId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ export class IdentityTokenResponse extends BaseResponse {
|
||||
kdf: KdfType;
|
||||
kdfIterations: number;
|
||||
forcePasswordReset: boolean;
|
||||
apiUseKeyConnector: boolean;
|
||||
keyConnectorUrl: string;
|
||||
|
||||
constructor(response: any) {
|
||||
@@ -31,6 +32,7 @@ export class IdentityTokenResponse extends BaseResponse {
|
||||
this.kdf = this.getResponseProperty('Kdf');
|
||||
this.kdfIterations = this.getResponseProperty('KdfIterations');
|
||||
this.forcePasswordReset = this.getResponseProperty('ForcePasswordReset');
|
||||
this.apiUseKeyConnector = this.getResponseProperty('ApiUseKeyConnector');
|
||||
this.keyConnectorUrl = this.getResponseProperty('KeyConnectorUrl');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1609,6 +1609,13 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
authed: boolean, hasResponse: boolean, apiUrl?: string,
|
||||
alterHeaders?: (headers: Headers) => void): Promise<any> {
|
||||
apiUrl = Utils.isNullOrWhitespace(apiUrl) ? this.environmentService.getApiUrl() : apiUrl;
|
||||
|
||||
const requestUrl = apiUrl + path;
|
||||
// Prevent directory traversal from malicious paths
|
||||
if (new URL(requestUrl).href !== requestUrl) {
|
||||
return Promise.reject('Invalid request url path.');
|
||||
}
|
||||
|
||||
const headers = new Headers({
|
||||
'Device-Type': this.deviceType,
|
||||
});
|
||||
@@ -1647,7 +1654,7 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
}
|
||||
|
||||
requestInit.headers = headers;
|
||||
const response = await this.fetch(new Request(apiUrl + path, requestInit));
|
||||
const response = await this.fetch(new Request(requestUrl, requestInit));
|
||||
|
||||
if (hasResponse && response.status === 200) {
|
||||
const responseJson = await response.json();
|
||||
|
||||
@@ -376,8 +376,9 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
|
||||
if (tokenResponse.keyConnectorUrl != null) {
|
||||
await this.keyConnectorService.getAndSetKey(tokenResponse.keyConnectorUrl);
|
||||
} else if (this.environmentService.getKeyConnectorUrl() != null) {
|
||||
await this.keyConnectorService.getAndSetKey();
|
||||
} else if (tokenResponse.apiUseKeyConnector) {
|
||||
const keyConnectorUrl = this.environmentService.getKeyConnectorUrl();
|
||||
await this.keyConnectorService.getAndSetKey(keyConnectorUrl);
|
||||
}
|
||||
|
||||
await this.cryptoService.setEncKey(tokenResponse.key);
|
||||
|
||||
@@ -85,13 +85,13 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
featuredImportOptions = [
|
||||
{ id: 'bitwardenjson', name: 'Bitwarden (json)' },
|
||||
{ id: 'bitwardencsv', name: 'Bitwarden (csv)' },
|
||||
{ id: 'lastpasscsv', name: 'LastPass (csv)' },
|
||||
{ id: 'chromecsv', name: 'Chrome (csv)' },
|
||||
{ id: 'firefoxcsv', name: 'Firefox (csv)' },
|
||||
{ id: 'safaricsv', name: 'Safari (csv)' },
|
||||
{ id: 'keepass2xml', name: 'KeePass 2 (xml)' },
|
||||
{ id: '1password1pif', name: '1Password (1pif)' },
|
||||
{ id: 'dashlanejson', name: 'Dashlane (json)' },
|
||||
{ id: 'firefoxcsv', name: 'Firefox (csv)' },
|
||||
{ id: 'keepass2xml', name: 'KeePass 2 (xml)' },
|
||||
{ id: 'lastpasscsv', name: 'LastPass (csv)' },
|
||||
{ id: 'safaricsv', name: 'Safari and macOS (csv)' },
|
||||
{ id: '1password1pif', name: '1Password (1pif)' },
|
||||
];
|
||||
|
||||
regularImportOptions: ImportOption[] = [
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ApiService } from '../abstractions/api.service';
|
||||
import { CryptoService } from '../abstractions/crypto.service';
|
||||
import { EnvironmentService } from '../abstractions/environment.service';
|
||||
import { KeyConnectorService as KeyConnectorServiceAbstraction } from '../abstractions/keyConnector.service';
|
||||
import { LogService } from '../abstractions/log.service';
|
||||
import { OrganizationService } from '../abstractions/organization.service';
|
||||
@@ -17,9 +16,8 @@ import { KeyConnectorUserKeyRequest } from '../models/request/keyConnectorUserKe
|
||||
|
||||
export class KeyConnectorService implements KeyConnectorServiceAbstraction {
|
||||
constructor(private stateService: StateService, private cryptoService: CryptoService,
|
||||
private apiService: ApiService, private environmentService: EnvironmentService,
|
||||
private tokenService: TokenService, private logService: LogService,
|
||||
private organizationService: OrganizationService) { }
|
||||
private apiService: ApiService, private tokenService: TokenService,
|
||||
private logService: LogService, private organizationService: OrganizationService) { }
|
||||
|
||||
setUsesKeyConnector(usesKeyConnector: boolean) {
|
||||
return this.stateService.setUsesKeyConnector(usesKeyConnector);
|
||||
@@ -51,15 +49,7 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
|
||||
await this.apiService.postConvertToKeyConnector();
|
||||
}
|
||||
|
||||
async getAndSetKey(url?: string) {
|
||||
if (url == null) {
|
||||
url = this.environmentService.getKeyConnectorUrl();
|
||||
}
|
||||
|
||||
if (url == null) {
|
||||
throw new Error('No Key Connector URL found.');
|
||||
}
|
||||
|
||||
async getAndSetKey(url: string) {
|
||||
try {
|
||||
const userKeyResponse = await this.apiService.getUserKeyFromKeyConnector(url);
|
||||
const keyArr = Utils.fromB64ToArray(userKeyResponse.key);
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { UserVerificationService as UserVerificationServiceAbstraction } from '../abstractions/userVerification.service';
|
||||
|
||||
import { ApiService } from '../abstractions/api.service';
|
||||
import { CryptoService } from '../abstractions/crypto.service';
|
||||
import { I18nService } from '../abstractions/i18n.service';
|
||||
import { LogService } from '../abstractions/log.service';
|
||||
import { PlatformUtilsService } from '../abstractions/platformUtils.service';
|
||||
|
||||
import { VerificationType } from '../enums/verificationType';
|
||||
|
||||
@@ -15,17 +11,13 @@ import { SecretVerificationRequest } from '../models/request/secretVerificationR
|
||||
|
||||
import { Verification } from '../types/verification';
|
||||
|
||||
@Injectable()
|
||||
export class UserVerificationService implements UserVerificationServiceAbstraction {
|
||||
constructor(private cryptoService: CryptoService, private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService, private apiService: ApiService,
|
||||
private logService: LogService) { }
|
||||
private apiService: ApiService) { }
|
||||
|
||||
async buildRequest<T extends SecretVerificationRequest>(verification: Verification,
|
||||
requestClass?: new () => T, alreadyHashed?: boolean) {
|
||||
if (verification?.secret == null || verification.secret === '') {
|
||||
throw new Error('No secret provided for verification.');
|
||||
}
|
||||
this.validateInput(verification);
|
||||
|
||||
const request = requestClass != null
|
||||
? new requestClass()
|
||||
@@ -43,28 +35,35 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
|
||||
}
|
||||
|
||||
async verifyUser(verification: Verification): Promise<boolean> {
|
||||
if (verification?.secret == null || verification.secret === '') {
|
||||
throw new Error('No secret provided for verification.');
|
||||
}
|
||||
this.validateInput(verification);
|
||||
|
||||
if (verification.type === VerificationType.OTP) {
|
||||
const request = new VerifyOTPRequest(verification.secret);
|
||||
try {
|
||||
await this.apiService.postAccountVerifyOTP(request);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('invalidVerificationCode'));
|
||||
return false;
|
||||
throw new Error(this.i18nService.t('invalidVerificationCode'));
|
||||
}
|
||||
} else {
|
||||
const passwordValid = await this.cryptoService.compareAndUpdateKeyHash(verification.secret, null);
|
||||
if (!passwordValid) {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('invalidMasterPassword'));
|
||||
return false;
|
||||
throw new Error(this.i18nService.t('invalidMasterPassword'));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async requestOTP() {
|
||||
await this.apiService.postAccountRequestOTP();
|
||||
}
|
||||
|
||||
private validateInput(verification: Verification) {
|
||||
if (verification?.secret == null || verification.secret === '') {
|
||||
if (verification.type === VerificationType.OTP) {
|
||||
throw new Error(this.i18nService.t('verificationCodeRequired'));
|
||||
} else {
|
||||
throw new Error(this.i18nService.t('masterPassRequired'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user