mirror of
https://github.com/bitwarden/browser
synced 2025-12-13 14:53:33 +00:00
[PM-25609] use password health service (#16482)
* isValidCipher and findWeakPasswordDetails * auditPasswordLeaks$ * missing deps fix * refactor: remove unused dependencies from RiskInsightsReportService - Remove PasswordStrengthServiceAbstraction and AuditService from constructor - Update module dependency injection to only provide these services to PasswordHealthService - Remove unused imports and mock services from test file - Ensure proper separation of concerns where password health logic is centralized in PasswordHealthService
This commit is contained in:
@@ -2,5 +2,6 @@ export * from "./member-cipher-details-api.service";
|
|||||||
export * from "./password-health.service";
|
export * from "./password-health.service";
|
||||||
export * from "./critical-apps.service";
|
export * from "./critical-apps.service";
|
||||||
export * from "./critical-apps-api.service";
|
export * from "./critical-apps-api.service";
|
||||||
|
export * from "./risk-insights-api.service";
|
||||||
export * from "./risk-insights-report.service";
|
export * from "./risk-insights-report.service";
|
||||||
export * from "./risk-insights-data.service";
|
export * from "./risk-insights-data.service";
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { mock } from "jest-mock-extended";
|
import { mock } from "jest-mock-extended";
|
||||||
import { firstValueFrom, of } from "rxjs";
|
import { firstValueFrom, of } from "rxjs";
|
||||||
import { ZXCVBNResult } from "zxcvbn";
|
|
||||||
|
|
||||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
|
||||||
import { EncryptedString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
import { EncryptedString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
|
||||||
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
@@ -16,6 +13,7 @@ import { MemberCipherDetailsResponse } from "../response/member-cipher-details.r
|
|||||||
import { mockCiphers } from "./ciphers.mock";
|
import { mockCiphers } from "./ciphers.mock";
|
||||||
import { MemberCipherDetailsApiService } from "./member-cipher-details-api.service";
|
import { MemberCipherDetailsApiService } from "./member-cipher-details-api.service";
|
||||||
import { mockMemberCipherDetails } from "./member-cipher-details-api.service.spec";
|
import { mockMemberCipherDetails } from "./member-cipher-details-api.service.spec";
|
||||||
|
import { PasswordHealthService } from "./password-health.service";
|
||||||
import { RiskInsightsApiService } from "./risk-insights-api.service";
|
import { RiskInsightsApiService } from "./risk-insights-api.service";
|
||||||
import { RiskInsightsEncryptionService } from "./risk-insights-encryption.service";
|
import { RiskInsightsEncryptionService } from "./risk-insights-encryption.service";
|
||||||
import { RiskInsightsReportService } from "./risk-insights-report.service";
|
import { RiskInsightsReportService } from "./risk-insights-report.service";
|
||||||
@@ -24,10 +22,9 @@ describe("RiskInsightsReportService", () => {
|
|||||||
let service: RiskInsightsReportService;
|
let service: RiskInsightsReportService;
|
||||||
|
|
||||||
// Mock services
|
// Mock services
|
||||||
const pwdStrengthService = mock<PasswordStrengthServiceAbstraction>();
|
|
||||||
const auditService = mock<AuditService>();
|
|
||||||
const cipherService = mock<CipherService>();
|
const cipherService = mock<CipherService>();
|
||||||
const memberCipherDetailsService = mock<MemberCipherDetailsApiService>();
|
const memberCipherDetailsService = mock<MemberCipherDetailsApiService>();
|
||||||
|
const mockPasswordHealthService = mock<PasswordHealthService>();
|
||||||
const mockRiskInsightsApiService = mock<RiskInsightsApiService>();
|
const mockRiskInsightsApiService = mock<RiskInsightsApiService>();
|
||||||
const mockRiskInsightsEncryptionService = mock<RiskInsightsEncryptionService>({
|
const mockRiskInsightsEncryptionService = mock<RiskInsightsEncryptionService>({
|
||||||
encryptRiskInsightsReport: jest.fn().mockResolvedValue("encryptedReportData"),
|
encryptRiskInsightsReport: jest.fn().mockResolvedValue("encryptedReportData"),
|
||||||
@@ -40,26 +37,38 @@ describe("RiskInsightsReportService", () => {
|
|||||||
let mockMemberDetails: MemberCipherDetailsResponse[];
|
let mockMemberDetails: MemberCipherDetailsResponse[];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
pwdStrengthService.getPasswordStrength.mockImplementation((password: string) => {
|
|
||||||
const score = password.length < 4 ? 1 : 4;
|
|
||||||
return { score } as ZXCVBNResult;
|
|
||||||
});
|
|
||||||
|
|
||||||
auditService.passwordLeaked.mockImplementation((password: string) =>
|
|
||||||
Promise.resolve(password === "123" ? 100 : 0),
|
|
||||||
);
|
|
||||||
|
|
||||||
cipherService.getAllFromApiForOrganization.mockResolvedValue(mockCiphers);
|
cipherService.getAllFromApiForOrganization.mockResolvedValue(mockCiphers);
|
||||||
|
|
||||||
memberCipherDetailsService.getMemberCipherDetails.mockResolvedValue(mockMemberCipherDetails);
|
memberCipherDetailsService.getMemberCipherDetails.mockResolvedValue(mockMemberCipherDetails);
|
||||||
|
|
||||||
|
// Mock PasswordHealthService methods
|
||||||
|
mockPasswordHealthService.isValidCipher.mockImplementation((cipher: any) => {
|
||||||
|
return (
|
||||||
|
cipher.type === 1 && cipher.login?.password && !cipher.isDeleted && cipher.viewPassword
|
||||||
|
);
|
||||||
|
});
|
||||||
|
mockPasswordHealthService.findWeakPasswordDetails.mockImplementation((cipher: any) => {
|
||||||
|
if (cipher.login?.password === "123") {
|
||||||
|
return { score: 1, detailValue: { label: "veryWeak", badgeVariant: "danger" } };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
mockPasswordHealthService.auditPasswordLeaks$.mockImplementation((ciphers: any[]) => {
|
||||||
|
const exposedDetails = ciphers
|
||||||
|
.filter((cipher) => cipher.login?.password === "123")
|
||||||
|
.map((cipher) => ({
|
||||||
|
exposedXTimes: 100,
|
||||||
|
cipherId: cipher.id,
|
||||||
|
}));
|
||||||
|
return of(exposedDetails);
|
||||||
|
});
|
||||||
|
|
||||||
service = new RiskInsightsReportService(
|
service = new RiskInsightsReportService(
|
||||||
pwdStrengthService,
|
|
||||||
auditService,
|
|
||||||
cipherService,
|
cipherService,
|
||||||
memberCipherDetailsService,
|
memberCipherDetailsService,
|
||||||
mockRiskInsightsApiService,
|
mockRiskInsightsApiService,
|
||||||
mockRiskInsightsEncryptionService,
|
mockRiskInsightsEncryptionService,
|
||||||
|
mockPasswordHealthService,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Reset mock ciphers before each test
|
// Reset mock ciphers before each test
|
||||||
|
|||||||
@@ -3,27 +3,20 @@
|
|||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
concatMap,
|
concatMap,
|
||||||
filter,
|
|
||||||
first,
|
first,
|
||||||
firstValueFrom,
|
firstValueFrom,
|
||||||
forkJoin,
|
forkJoin,
|
||||||
from,
|
from,
|
||||||
map,
|
map,
|
||||||
mergeMap,
|
|
||||||
Observable,
|
Observable,
|
||||||
of,
|
of,
|
||||||
switchMap,
|
switchMap,
|
||||||
toArray,
|
|
||||||
zip,
|
zip,
|
||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
|
|
||||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
|
||||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
|
||||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
|
||||||
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -37,10 +30,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
LEGACY_CipherHealthReportDetail,
|
LEGACY_CipherHealthReportDetail,
|
||||||
LEGACY_CipherHealthReportUriDetail,
|
LEGACY_CipherHealthReportUriDetail,
|
||||||
ExposedPasswordDetail,
|
|
||||||
LEGACY_MemberDetailsFlat,
|
LEGACY_MemberDetailsFlat,
|
||||||
WeakPasswordDetail,
|
|
||||||
WeakPasswordScore,
|
|
||||||
LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher,
|
LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher,
|
||||||
} from "../models/password-health";
|
} from "../models/password-health";
|
||||||
import {
|
import {
|
||||||
@@ -55,6 +45,7 @@ import {
|
|||||||
} from "../models/report-models";
|
} from "../models/report-models";
|
||||||
|
|
||||||
import { MemberCipherDetailsApiService } from "./member-cipher-details-api.service";
|
import { MemberCipherDetailsApiService } from "./member-cipher-details-api.service";
|
||||||
|
import { PasswordHealthService } from "./password-health.service";
|
||||||
import { RiskInsightsApiService } from "./risk-insights-api.service";
|
import { RiskInsightsApiService } from "./risk-insights-api.service";
|
||||||
import { RiskInsightsEncryptionService } from "./risk-insights-encryption.service";
|
import { RiskInsightsEncryptionService } from "./risk-insights-encryption.service";
|
||||||
|
|
||||||
@@ -81,12 +72,11 @@ export class RiskInsightsReportService {
|
|||||||
// _ciphers$ = this._ciphersSubject.asObservable();
|
// _ciphers$ = this._ciphersSubject.asObservable();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private passwordStrengthService: PasswordStrengthServiceAbstraction,
|
|
||||||
private auditService: AuditService,
|
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private memberCipherDetailsApiService: MemberCipherDetailsApiService,
|
private memberCipherDetailsApiService: MemberCipherDetailsApiService,
|
||||||
private riskInsightsApiService: RiskInsightsApiService,
|
private riskInsightsApiService: RiskInsightsApiService,
|
||||||
private riskInsightsEncryptionService: RiskInsightsEncryptionService,
|
private riskInsightsEncryptionService: RiskInsightsEncryptionService,
|
||||||
|
private passwordHealthService: PasswordHealthService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
// [FIXME] CipherData
|
// [FIXME] CipherData
|
||||||
@@ -367,10 +357,12 @@ export class RiskInsightsReportService {
|
|||||||
): Promise<LEGACY_CipherHealthReportDetail[]> {
|
): Promise<LEGACY_CipherHealthReportDetail[]> {
|
||||||
const cipherHealthReports: LEGACY_CipherHealthReportDetail[] = [];
|
const cipherHealthReports: LEGACY_CipherHealthReportDetail[] = [];
|
||||||
const passwordUseMap = new Map<string, number>();
|
const passwordUseMap = new Map<string, number>();
|
||||||
const exposedDetails = await this.findExposedPasswords(ciphers);
|
const exposedDetails = await firstValueFrom(
|
||||||
|
this.passwordHealthService.auditPasswordLeaks$(ciphers),
|
||||||
|
);
|
||||||
for (const cipher of ciphers) {
|
for (const cipher of ciphers) {
|
||||||
if (this.validateCipher(cipher)) {
|
if (this.passwordHealthService.isValidCipher(cipher)) {
|
||||||
const weakPassword = this.findWeakPassword(cipher);
|
const weakPassword = this.passwordHealthService.findWeakPasswordDetails(cipher);
|
||||||
// Looping over all ciphers needs to happen first to determine reused passwords over all ciphers.
|
// Looping over all ciphers needs to happen first to determine reused passwords over all ciphers.
|
||||||
// Store in the set and evaluate later
|
// Store in the set and evaluate later
|
||||||
if (passwordUseMap.has(cipher.login.password)) {
|
if (passwordUseMap.has(cipher.login.password)) {
|
||||||
@@ -448,104 +440,6 @@ export class RiskInsightsReportService {
|
|||||||
return appReports;
|
return appReports;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async findExposedPasswords(ciphers: CipherView[]): Promise<ExposedPasswordDetail[]> {
|
|
||||||
const exposedDetails: ExposedPasswordDetail[] = [];
|
|
||||||
const promises: Promise<void>[] = [];
|
|
||||||
|
|
||||||
ciphers.forEach((ciph) => {
|
|
||||||
if (this.validateCipher(ciph)) {
|
|
||||||
const promise = this.auditService
|
|
||||||
.passwordLeaked(ciph.login.password)
|
|
||||||
.then((exposedCount) => {
|
|
||||||
if (exposedCount > 0) {
|
|
||||||
const detail = {
|
|
||||||
exposedXTimes: exposedCount,
|
|
||||||
cipherId: ciph.id,
|
|
||||||
} as ExposedPasswordDetail;
|
|
||||||
exposedDetails.push(detail);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
promises.push(promise);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await Promise.all(promises);
|
|
||||||
|
|
||||||
return exposedDetails;
|
|
||||||
}
|
|
||||||
|
|
||||||
private findWeakPassword(cipher: CipherView): WeakPasswordDetail {
|
|
||||||
const hasUserName = this.isUserNameNotEmpty(cipher);
|
|
||||||
let userInput: string[] = [];
|
|
||||||
if (hasUserName) {
|
|
||||||
const atPosition = cipher.login.username.indexOf("@");
|
|
||||||
if (atPosition > -1) {
|
|
||||||
userInput = userInput
|
|
||||||
.concat(
|
|
||||||
cipher.login.username
|
|
||||||
.substring(0, atPosition)
|
|
||||||
.trim()
|
|
||||||
.toLowerCase()
|
|
||||||
.split(/[^A-Za-z0-9]/),
|
|
||||||
)
|
|
||||||
.filter((i) => i.length >= 3);
|
|
||||||
} else {
|
|
||||||
userInput = cipher.login.username
|
|
||||||
.trim()
|
|
||||||
.toLowerCase()
|
|
||||||
.split(/[^A-Za-z0-9]/)
|
|
||||||
.filter((i) => i.length >= 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const { score } = this.passwordStrengthService.getPasswordStrength(
|
|
||||||
cipher.login.password,
|
|
||||||
null,
|
|
||||||
userInput.length > 0 ? userInput : null,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (score != null && score <= 2) {
|
|
||||||
const scoreValue = this.weakPasswordScore(score);
|
|
||||||
const weakPasswordDetail = { score: score, detailValue: scoreValue } as WeakPasswordDetail;
|
|
||||||
return weakPasswordDetail;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private weakPasswordScore(score: number): WeakPasswordScore {
|
|
||||||
switch (score) {
|
|
||||||
case 4:
|
|
||||||
return { label: "strong", badgeVariant: "success" };
|
|
||||||
case 3:
|
|
||||||
return { label: "good", badgeVariant: "primary" };
|
|
||||||
case 2:
|
|
||||||
return { label: "weak", badgeVariant: "warning" };
|
|
||||||
default:
|
|
||||||
return { label: "veryWeak", badgeVariant: "danger" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private isUserNameNotEmpty(c: CipherView): boolean {
|
|
||||||
return !Utils.isNullOrWhitespace(c.login.username);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates that the cipher is a login item, has a password
|
|
||||||
* is not deleted, and the user can view the password
|
|
||||||
* @param c the input cipher
|
|
||||||
*/
|
|
||||||
private validateCipher(c: CipherView): boolean {
|
|
||||||
const { type, login, isDeleted, viewPassword } = c;
|
|
||||||
if (
|
|
||||||
type !== CipherType.Login ||
|
|
||||||
login.password == null ||
|
|
||||||
login.password === "" ||
|
|
||||||
isDeleted ||
|
|
||||||
!viewPassword
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _buildPasswordUseMap(ciphers: CipherView[]): Map<string, number> {
|
private _buildPasswordUseMap(ciphers: CipherView[]): Map<string, number> {
|
||||||
const passwordUseMap = new Map<string, number>();
|
const passwordUseMap = new Map<string, number>();
|
||||||
ciphers.forEach((cipher) => {
|
ciphers.forEach((cipher) => {
|
||||||
@@ -696,11 +590,13 @@ export class RiskInsightsReportService {
|
|||||||
ciphers: CipherView[],
|
ciphers: CipherView[],
|
||||||
memberDetails: MemberDetails[],
|
memberDetails: MemberDetails[],
|
||||||
): Observable<CipherHealthReport[]> {
|
): Observable<CipherHealthReport[]> {
|
||||||
const validCiphers = ciphers.filter((cipher) => this.isValidCipher(cipher));
|
const validCiphers = ciphers.filter((cipher) =>
|
||||||
|
this.passwordHealthService.isValidCipher(cipher),
|
||||||
|
);
|
||||||
// Build password use map
|
// Build password use map
|
||||||
const passwordUseMap = this._buildPasswordUseMap(validCiphers);
|
const passwordUseMap = this._buildPasswordUseMap(validCiphers);
|
||||||
|
|
||||||
return this.auditPasswordLeaks$(validCiphers).pipe(
|
return this.passwordHealthService.auditPasswordLeaks$(validCiphers).pipe(
|
||||||
map((exposedDetails) => {
|
map((exposedDetails) => {
|
||||||
return validCiphers.map((cipher) => {
|
return validCiphers.map((cipher) => {
|
||||||
const exposedPassword = exposedDetails.find((x) => x.cipherId === cipher.id);
|
const exposedPassword = exposedDetails.find((x) => x.cipherId === cipher.id);
|
||||||
@@ -710,7 +606,7 @@ export class RiskInsightsReportService {
|
|||||||
cipher: cipher,
|
cipher: cipher,
|
||||||
cipherMembers,
|
cipherMembers,
|
||||||
healthData: {
|
healthData: {
|
||||||
weakPasswordDetail: this.findWeakPasswordDetails(cipher),
|
weakPasswordDetail: this.passwordHealthService.findWeakPasswordDetails(cipher),
|
||||||
exposedPasswordDetail: exposedPassword,
|
exposedPasswordDetail: exposedPassword,
|
||||||
reusedPasswordCount: passwordUseMap.get(cipher.login.password) ?? 0,
|
reusedPasswordCount: passwordUseMap.get(cipher.login.password) ?? 0,
|
||||||
},
|
},
|
||||||
@@ -721,119 +617,4 @@ export class RiskInsightsReportService {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO This is a temp implementation until the function is available in the password health service
|
|
||||||
/**
|
|
||||||
* Validates that the cipher is a login item, has a password
|
|
||||||
* is not deleted, and the user can view the password
|
|
||||||
* @param c the input cipher
|
|
||||||
*/
|
|
||||||
isValidCipher(c: CipherView): boolean {
|
|
||||||
const { type, login, isDeleted, viewPassword } = c;
|
|
||||||
if (
|
|
||||||
type !== CipherType.Login ||
|
|
||||||
login.password == null ||
|
|
||||||
login.password === "" ||
|
|
||||||
isDeleted ||
|
|
||||||
!viewPassword
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO This is a temp implementation until the function is available in the password health service
|
|
||||||
/**
|
|
||||||
* Extracts username parts from the cipher's username.
|
|
||||||
* This is used to help determine password strength.
|
|
||||||
*
|
|
||||||
* @param cipherUsername The username from the cipher.
|
|
||||||
* @returns An array of username parts.
|
|
||||||
*/
|
|
||||||
extractUsernameParts(cipherUsername: string) {
|
|
||||||
const atPosition = cipherUsername.indexOf("@");
|
|
||||||
const userNameToProcess =
|
|
||||||
atPosition > -1 ? cipherUsername.substring(0, atPosition) : cipherUsername;
|
|
||||||
|
|
||||||
return userNameToProcess
|
|
||||||
.trim()
|
|
||||||
.toLowerCase()
|
|
||||||
.split(/[^A-Za-z0-9]/)
|
|
||||||
.filter((i) => i.length >= 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO This is a temp implementation until the function is available in the password health service
|
|
||||||
/**
|
|
||||||
* Checks if the cipher has a weak password based on the password strength score.
|
|
||||||
*
|
|
||||||
* @param cipher
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
findWeakPasswordDetails(cipher: CipherView): WeakPasswordDetail | null {
|
|
||||||
// Validate the cipher
|
|
||||||
if (!this.isValidCipher(cipher)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the username
|
|
||||||
const userInput = this.isUserNameNotEmpty(cipher)
|
|
||||||
? this.extractUsernameParts(cipher.login.username)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const { score } = this.passwordStrengthService.getPasswordStrength(
|
|
||||||
cipher.login.password,
|
|
||||||
null,
|
|
||||||
userInput,
|
|
||||||
);
|
|
||||||
|
|
||||||
// If a score is not found or a score is less than 3, it's weak
|
|
||||||
if (score != null && score <= 2) {
|
|
||||||
return { score: score, detailValue: this.getPasswordScoreInfo(score) };
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO This is a temp implementation until the function is available in the password health service
|
|
||||||
/**
|
|
||||||
* Gets the password score information based on the score.
|
|
||||||
*
|
|
||||||
* @param score
|
|
||||||
* @returns An object containing the label and badge variant for the password score.
|
|
||||||
*/
|
|
||||||
getPasswordScoreInfo(score: number): WeakPasswordScore {
|
|
||||||
switch (score) {
|
|
||||||
case 4:
|
|
||||||
return { label: "strong", badgeVariant: "success" };
|
|
||||||
case 3:
|
|
||||||
return { label: "good", badgeVariant: "primary" };
|
|
||||||
case 2:
|
|
||||||
return { label: "weak", badgeVariant: "warning" };
|
|
||||||
default:
|
|
||||||
return { label: "veryWeak", badgeVariant: "danger" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO This is a temp implementation until the function is available in the password health service
|
|
||||||
/**
|
|
||||||
* Finds exposed passwords in a list of ciphers.
|
|
||||||
*
|
|
||||||
* @param ciphers The list of ciphers to check.
|
|
||||||
* @returns An observable that emits an array of ExposedPasswordDetail.
|
|
||||||
*/
|
|
||||||
auditPasswordLeaks$(ciphers: CipherView[]): Observable<ExposedPasswordDetail[]> {
|
|
||||||
return from(ciphers).pipe(
|
|
||||||
filter((cipher) => this.isValidCipher(cipher)),
|
|
||||||
mergeMap((cipher) =>
|
|
||||||
this.auditService
|
|
||||||
.passwordLeaked(cipher.login.password)
|
|
||||||
.then((exposedCount) => ({ cipher, exposedCount })),
|
|
||||||
),
|
|
||||||
filter(({ exposedCount }) => exposedCount > 0),
|
|
||||||
map(({ cipher, exposedCount }) => ({
|
|
||||||
exposedXTimes: exposedCount,
|
|
||||||
cipherId: cipher.id,
|
|
||||||
})),
|
|
||||||
toArray(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { CriticalAppsService } from "@bitwarden/bit-common/dirt/reports/risk-ins
|
|||||||
import {
|
import {
|
||||||
CriticalAppsApiService,
|
CriticalAppsApiService,
|
||||||
MemberCipherDetailsApiService,
|
MemberCipherDetailsApiService,
|
||||||
|
PasswordHealthService,
|
||||||
|
RiskInsightsApiService,
|
||||||
RiskInsightsDataService,
|
RiskInsightsDataService,
|
||||||
RiskInsightsReportService,
|
RiskInsightsReportService,
|
||||||
} from "@bitwarden/bit-common/dirt/reports/risk-insights/services";
|
} from "@bitwarden/bit-common/dirt/reports/risk-insights/services";
|
||||||
@@ -29,13 +31,22 @@ import { RiskInsightsComponent } from "./risk-insights.component";
|
|||||||
provide: MemberCipherDetailsApiService,
|
provide: MemberCipherDetailsApiService,
|
||||||
deps: [ApiService],
|
deps: [ApiService],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: PasswordHealthService,
|
||||||
|
deps: [PasswordStrengthServiceAbstraction, AuditService],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: RiskInsightsApiService,
|
||||||
|
deps: [ApiService],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: RiskInsightsReportService,
|
provide: RiskInsightsReportService,
|
||||||
deps: [
|
deps: [
|
||||||
PasswordStrengthServiceAbstraction,
|
|
||||||
AuditService,
|
|
||||||
CipherService,
|
CipherService,
|
||||||
MemberCipherDetailsApiService,
|
MemberCipherDetailsApiService,
|
||||||
|
RiskInsightsApiService,
|
||||||
|
RiskInsightsEncryptionService,
|
||||||
|
PasswordHealthService,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
safeProvider({
|
safeProvider({
|
||||||
|
|||||||
Reference in New Issue
Block a user