diff --git a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.spec.ts b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.spec.ts index acc34232571..80893737ffd 100644 --- a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.spec.ts +++ b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.spec.ts @@ -121,4 +121,153 @@ describe("InactiveTwoFactorReportComponent", () => { it("should call fullSync method of syncService", () => { expect(syncServiceMock.fullSync).toHaveBeenCalledWith(false); }); + + describe("isInactive2faCipher", () => { + beforeEach(() => { + // Add both domain and host to services map + component.services.set("example.com", "https://example.com/2fa-doc"); + component.services.set("sub.example.com", "https://sub.example.com/2fa-doc"); + fixture.detectChanges(); + }); + it("should return true and documentation for cipher with matching domain", () => { + const cipher = createCipherView({ + login: { + uris: [{ uri: "https://example.com/login" }], + }, + }); + const [doc, isInactive] = (component as any).isInactive2faCipher(cipher); + expect(isInactive).toBe(true); + expect(doc).toBe("https://example.com/2fa-doc"); + }); + + it("should return true and documentation for cipher with matching host", () => { + const cipher = createCipherView({ + login: { + uris: [{ uri: "https://sub.example.com/login" }], + }, + }); + const [doc, isInactive] = (component as any).isInactive2faCipher(cipher); + expect(isInactive).toBe(true); + expect(doc).toBe("https://sub.example.com/2fa-doc"); + }); + + it("should return false for cipher with non-matching domain or host", () => { + const cipher = createCipherView({ + login: { + uris: [{ uri: "https://otherdomain.com/login" }], + }, + }); + const [doc, isInactive] = (component as any).isInactive2faCipher(cipher); + expect(isInactive).toBe(false); + expect(doc).toBe(""); + }); + + it("should return false if cipher type is not Login", () => { + const cipher = createCipherView({ + type: 2, + login: { + uris: [{ uri: "https://example.com/login" }], + }, + }); + const [doc, isInactive] = (component as any).isInactive2faCipher(cipher); + expect(isInactive).toBe(false); + expect(doc).toBe(""); + }); + + it("should return false if cipher has TOTP", () => { + const cipher = createCipherView({ + login: { + totp: "some-totp", + uris: [{ uri: "https://example.com/login" }], + }, + }); + const [doc, isInactive] = (component as any).isInactive2faCipher(cipher); + expect(isInactive).toBe(false); + expect(doc).toBe(""); + }); + + it("should return false if cipher is deleted", () => { + const cipher = createCipherView({ + isDeleted: true, + login: { + uris: [{ uri: "https://example.com/login" }], + }, + }); + const [doc, isInactive] = (component as any).isInactive2faCipher(cipher); + expect(isInactive).toBe(false); + expect(doc).toBe(""); + }); + + it("should return false if cipher does not have edit access and no organization", () => { + component.organization = null; + const cipher = createCipherView({ + edit: false, + login: { + uris: [{ uri: "https://example.com/login" }], + }, + }); + const [doc, isInactive] = (component as any).isInactive2faCipher(cipher); + expect(isInactive).toBe(false); + expect(doc).toBe(""); + }); + + it("should return false if cipher does not have viewPassword", () => { + const cipher = createCipherView({ + viewPassword: false, + login: { + uris: [{ uri: "https://example.com/login" }], + }, + }); + const [doc, isInactive] = (component as any).isInactive2faCipher(cipher); + expect(isInactive).toBe(false); + expect(doc).toBe(""); + }); + + it("should check all uris and return true if any matches domain or host", () => { + const cipher = createCipherView({ + login: { + uris: [ + { uri: "https://otherdomain.com/login" }, + { uri: "https://sub.example.com/dashboard" }, + ], + }, + }); + const [doc, isInactive] = (component as any).isInactive2faCipher(cipher); + expect(isInactive).toBe(true); + expect(doc).toBe("https://sub.example.com/2fa-doc"); + }); + + it("should return false if uris array is empty", () => { + const cipher = createCipherView({ + login: { + uris: [], + }, + }); + const [doc, isInactive] = (component as any).isInactive2faCipher(cipher); + expect(isInactive).toBe(false); + expect(doc).toBe(""); + }); + + function createCipherView({ + type = 1, + login = {}, + isDeleted = false, + edit = true, + viewPassword = true, + }: any): any { + return { + id: "test-id", + type, + login: { + totp: null, + hasUris: true, + uris: [], + ...login, + }, + isDeleted, + edit, + viewPassword, + }; + } + }); }); diff --git a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts index 8b0fdda70e3..2a8ec12ac6a 100644 --- a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts @@ -109,7 +109,18 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl const u = login.uris[i]; if (u.uri != null && u.uri !== "") { const uri = u.uri.replace("www.", ""); + const host = Utils.getHost(uri); const domain = Utils.getDomain(uri); + // check host first + if (host != null && this.services.has(host)) { + if (this.services.get(host) != null) { + docFor2fa = this.services.get(host) || ""; + } + isInactive2faCipher = true; + break; + } + + // then check domain if (domain != null && this.services.has(domain)) { if (this.services.get(domain) != null) { docFor2fa = this.services.get(domain) || "";