1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-12 14:34:02 +00:00

fix getReusedPasswords

This commit is contained in:
jaasen-livefront
2024-11-18 15:23:40 -08:00
parent ecd782f94d
commit 6b25515d1d
4 changed files with 70 additions and 88 deletions

View File

@@ -91,7 +91,7 @@ export const mockCiphers: any[] = [
id: "cbea34a8-bde4-46ad-9d19-b05001228xy4",
organizationId: null,
folderId: null,
name: "Weak password Cipher",
name: "Strong password Cipher",
notes: null,
type: 1,
favorite: false,
@@ -115,14 +115,15 @@ export const mockCiphers: any[] = [
id: "cbea34a8-bde4-46ad-9d19-b05001227nm5",
organizationId: null,
folderId: null,
name: "No password Cipher",
name: "Exposed password Cipher",
notes: null,
type: 1,
favorite: false,
organizationUseTotp: false,
login: {
hasUris: true,
uris: [createLoginUriView("123formbuilder.com")],
password: "123",
uris: [createLoginUriView("123formbuilder.com"), createLoginUriView("www.google.com")],
},
edit: true,
viewPassword: true,

View File

@@ -13,7 +13,6 @@ export const mockMemberCipherDetails: any = [
"cbea34a8-bde4-46ad-9d19-b05001228ab1",
"cbea34a8-bde4-46ad-9d19-b05001228ab2",
"cbea34a8-bde4-46ad-9d19-b05001228xy4",
"cbea34a8-bde4-46ad-9d19-b05001227nm5",
],
},
{
@@ -34,7 +33,6 @@ export const mockMemberCipherDetails: any = [
cipherIds: [
"cbea34a8-bde4-46ad-9d19-b05001228cd3",
"cbea34a8-bde4-46ad-9d19-b05001228xy4",
"cbea34a8-bde4-46ad-9d19-b05001227nm5",
"cbea34a8-bde4-46ad-9d19-b05001227nm7",
],
},
@@ -56,14 +54,13 @@ export const mockMemberCipherDetails: any = [
"cbea34a8-bde4-46ad-9d19-b05001228ab1",
"cbea34a8-bde4-46ad-9d19-b05001228cd3",
"cbea34a8-bde4-46ad-9d19-b05001228xy4",
"cbea34a8-bde4-46ad-9d19-b05001227nm5",
],
},
{
userName: "Chris Finch",
email: "chris.finch@wernhamhogg.uk",
usesKeyConnector: true,
cipherIds: ["cbea34a8-bde4-46ad-9d19-b05001227nm5"],
cipherIds: ["cbea34a8-bde4-46ad-9d19-b05001228ab2"],
},
];

View File

@@ -61,28 +61,28 @@ describe("PasswordHealthService", () => {
atRiskPasswords: 1,
totalPasswords: 2,
atRiskMembers: 2,
totalMembers: 4,
},
{
application: "123formbuilder.com",
atRiskPasswords: 0,
totalPasswords: 1,
atRiskMembers: 0,
totalMembers: 5,
},
{
application: "example.com",
application: "123formbuilder.com",
atRiskPasswords: 1,
totalPasswords: 1,
atRiskMembers: 1,
totalMembers: 1,
},
{
application: "example.com",
atRiskPasswords: 2,
totalPasswords: 2,
atRiskMembers: 5,
totalMembers: 5,
},
{
application: "google.com",
atRiskPasswords: 1,
totalPasswords: 1,
atRiskMembers: 2,
totalMembers: 2,
atRiskPasswords: 2,
totalPasswords: 2,
atRiskMembers: 3,
totalMembers: 3,
},
];
@@ -91,10 +91,22 @@ describe("PasswordHealthService", () => {
expect(result.details.sort(sortFn)).toEqual(expected.sort(sortFn));
expect(result.totalAtRiskMembers).toBe(5);
expect(result.totalMembers).toBe(6);
expect(result.totalAtRiskApps).toBe(3);
expect(result.totalAtRiskApps).toBe(4);
expect(result.totalApps).toBe(4);
});
describe("getReusedPasswords", () => {
it.only("should return an array of reused passwords", () => {
const ciphers = [
{ login: { password: "123" }, type: CipherType.Login, viewPassword: true },
{ login: { password: "123" }, type: CipherType.Login, viewPassword: true },
{ login: { password: "abc" }, type: CipherType.Login, viewPassword: true },
] as CipherView[];
const result = service.getReusedPasswords(ciphers);
expect(result).toEqual(new Set(["123"]));
});
});
describe("isWeakPassword", () => {
it("should return true for a weak password", () => {
const cipher = new CipherView();
@@ -114,31 +126,4 @@ describe("PasswordHealthService", () => {
expect(service.isWeakPassword(cipher)).toBe(false);
});
});
describe("isReusedPassword", () => {
it("should return false for a new password", () => {
const cipher = new CipherView();
cipher.type = CipherType.Login;
cipher.login = { password: "uniquePassword", username: "user" } as LoginView;
cipher.viewPassword = true;
expect(service.isReusedPassword(cipher)).toBe(false);
});
it("should return true for a reused password", () => {
const cipher1 = new CipherView();
cipher1.type = CipherType.Login;
cipher1.login = { password: "reusedPassword", username: "user" } as LoginView;
cipher1.viewPassword = true;
const cipher2 = new CipherView();
cipher2.type = CipherType.Login;
cipher2.login = { password: "reusedPassword", username: "user" } as LoginView;
cipher2.viewPassword = true;
service.isReusedPassword(cipher1); // Adds 'reusedPassword' to usedPasswords
expect(service.isReusedPassword(cipher2)).toBe(true);
});
});
});

View File

@@ -69,10 +69,12 @@ export class PasswordHealthService {
// Set to store at-risk cipher IDs
const atRiskCipherIds = new Set<string>();
const reusedPasswords = this.getReusedPasswords(ciphers);
// Determine at-risk ciphers
for (const cipher of ciphers) {
const isReused = reusedPasswords.has(cipher.login.password);
const isWeak = this.isWeakPassword(cipher);
const isReused = this.isReusedPassword(cipher);
const isExposed = await this.isExposedPassword(cipher);
if (isWeak || isReused || isExposed) {
atRiskCipherIds.add(cipher.id);
@@ -154,54 +156,51 @@ export class PasswordHealthService {
};
}
async isExposedPassword(cipher: CipherView) {
const { type, login, isDeleted, viewPassword } = cipher;
if (
type !== CipherType.Login ||
login.password == null ||
login.password === "" ||
isDeleted ||
!viewPassword
) {
return;
getReusedPasswords(ciphers: CipherView[]): Set<string> {
const seenPasswords = new Set<string>();
const reusedPasswords = new Set<string>();
for (const cipher of ciphers) {
if (this.isValidPassword(cipher)) {
const password = cipher.login.password;
if (seenPasswords.has(password)) {
reusedPasswords.add(password);
} else {
seenPasswords.add(password);
}
}
}
const exposedCount = await this.auditService.passwordLeaked(login.password);
return reusedPasswords;
}
isValidPassword(cipher: CipherView) {
const { type, login, isDeleted, viewPassword } = cipher;
return (
type === CipherType.Login &&
login.password != null &&
login.password !== "" &&
!isDeleted &&
viewPassword
);
}
async isExposedPassword(cipher: CipherView) {
if (!this.isValidPassword(cipher)) {
return false;
}
const exposedCount = await this.auditService.passwordLeaked(cipher.login.password);
return exposedCount > 0;
}
isReusedPassword(cipher: CipherView) {
const { type, login, isDeleted, viewPassword } = cipher;
if (
type !== CipherType.Login ||
login.password == null ||
login.password === "" ||
isDeleted ||
!viewPassword
) {
return;
}
if (this.usedPasswords.includes(login.password)) {
return true;
}
this.usedPasswords.push(login.password);
return false;
}
isWeakPassword(cipher: CipherView) {
const { type, login, isDeleted, viewPassword } = cipher;
if (
type !== CipherType.Login ||
login.password == null ||
login.password === "" ||
isDeleted ||
!viewPassword
) {
return;
if (!this.isValidPassword(cipher)) {
return false;
}
const { login } = cipher;
const hasUserName = !Utils.isNullOrWhitespace(cipher.login.username);
let userInput: string[] = [];
if (hasUserName) {