From 4c5c3f379f77613808253f2be16ac09cb352b526 Mon Sep 17 00:00:00 2001 From: Perry Mitchell Date: Sun, 7 Apr 2024 21:49:59 +0300 Subject: [PATCH] Prevent login save prompt if credentials match --- package-lock.json | 229 ++++++++++++++++++ package.json | 3 +- source/background/services/entry.ts | 2 +- source/background/services/loginMemory.ts | 23 ++ source/background/services/messaging.ts | 15 +- .../saveCredentials/CredentialsSaver.tsx | 2 +- .../saveCredentials/CredentialsSelector.tsx | 3 +- source/full/hooks/credentials.ts | 6 +- source/full/hooks/document.ts | 2 +- source/full/services/credentials.ts | 6 +- source/full/services/disabledDomains.ts | 2 +- source/full/services/vaults.ts | 3 + source/shared/hooks/global.ts | 8 +- source/shared/library/domain.ts | 13 + source/shared/library/otp.ts | 3 +- source/shared/types.ts | 1 + source/tab/services/logins/saving.ts | 6 +- source/tab/services/logins/watcher.ts | 2 +- 18 files changed, 310 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 16fc11e2..366a7c66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,6 +45,7 @@ "obstate": "^0.1.4", "on-navigate": "^0.1.1", "otpauth": "^9.1.5", + "parse-domain": "^8.0.2", "prettier": "^3.1.0", "pug-plugin": "^5.0.2", "react": "^17.0.2", @@ -4522,6 +4523,21 @@ "node": ">=6" } }, + "node_modules/clone-regexp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-3.0.0.tgz", + "integrity": "sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==", + "dev": true, + "dependencies": { + "is-regexp": "^3.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -4856,6 +4872,18 @@ "@babel/types": "^7.6.1" } }, + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -6976,6 +7004,18 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "node_modules/function-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-0.1.1.tgz", + "integrity": "sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/function.prototype.name": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", @@ -8125,6 +8165,18 @@ "stream-each": "^1.2.3" } }, + "node_modules/ip-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-5.0.0.tgz", + "integrity": "sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-absolute": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz", @@ -8365,6 +8417,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-ip": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-5.0.1.tgz", + "integrity": "sha512-FCsGHdlrOnZQcp0+XT5a+pYowf33itBalCl+7ovNXC/7o5BhIpG14M3OrpPPdBSIQJCm+0M5+9mO7S9VVTTCFw==", + "dev": true, + "dependencies": { + "ip-regex": "^5.0.0", + "super-regex": "^0.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-mergeable-object": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-mergeable-object/-/is-mergeable-object-1.1.1.tgz", @@ -8462,6 +8530,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-relative": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz", @@ -10492,6 +10572,37 @@ "node": ">=6" } }, + "node_modules/parse-domain": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/parse-domain/-/parse-domain-8.0.2.tgz", + "integrity": "sha512-maw80QgO2LaQ/ZlnxMxx4TvU2hB6Ao+ZsM2EI8jGRqtybvSasKGc9VbgIiEXr7tH4ASCHx05VYwy50ajoMcIcg==", + "dev": true, + "dependencies": { + "is-ip": "^5.0.1", + "node-fetch": "^3.3.2" + }, + "bin": { + "parse-domain-update": "bin/update.js" + } + }, + "node_modules/parse-domain/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -12939,6 +13050,23 @@ "react-is": ">= 16.8.0" } }, + "node_modules/super-regex": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-0.2.0.tgz", + "integrity": "sha512-WZzIx3rC1CvbMDloLsVw0lkZVKJWbrkJ0k1ghKFmcnPrW1+jWbgTkTEWVtD9lMdmI4jZEz40+naBxl1dCUhXXw==", + "dev": true, + "dependencies": { + "clone-regexp": "^3.0.0", + "function-timeout": "^0.1.0", + "time-span": "^5.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -13102,6 +13230,21 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, + "node_modules/time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "dev": true, + "dependencies": { + "convert-hrtime": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -18118,6 +18261,15 @@ "shallow-clone": "^3.0.0" } }, + "clone-regexp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-3.0.0.tgz", + "integrity": "sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==", + "dev": true, + "requires": { + "is-regexp": "^3.0.0" + } + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -18389,6 +18541,12 @@ "@babel/types": "^7.6.1" } }, + "convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "dev": true + }, "convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -19920,6 +20078,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "function-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-0.1.1.tgz", + "integrity": "sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg==", + "dev": true + }, "function.prototype.name": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", @@ -20714,6 +20878,12 @@ "stream-each": "^1.2.3" } }, + "ip-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-5.0.0.tgz", + "integrity": "sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==", + "dev": true + }, "is-absolute": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz", @@ -20878,6 +21048,16 @@ "is-path-inside": "^3.0.2" } }, + "is-ip": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-5.0.1.tgz", + "integrity": "sha512-FCsGHdlrOnZQcp0+XT5a+pYowf33itBalCl+7ovNXC/7o5BhIpG14M3OrpPPdBSIQJCm+0M5+9mO7S9VVTTCFw==", + "dev": true, + "requires": { + "ip-regex": "^5.0.0", + "super-regex": "^0.2.0" + } + }, "is-mergeable-object": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-mergeable-object/-/is-mergeable-object-1.1.1.tgz", @@ -20942,6 +21122,12 @@ "has-tostringtag": "^1.0.0" } }, + "is-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "dev": true + }, "is-relative": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz", @@ -22514,6 +22700,29 @@ "callsites": "^3.0.0" } }, + "parse-domain": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/parse-domain/-/parse-domain-8.0.2.tgz", + "integrity": "sha512-maw80QgO2LaQ/ZlnxMxx4TvU2hB6Ao+ZsM2EI8jGRqtybvSasKGc9VbgIiEXr7tH4ASCHx05VYwy50ajoMcIcg==", + "dev": true, + "requires": { + "is-ip": "^5.0.1", + "node-fetch": "^3.3.2" + }, + "dependencies": { + "node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "requires": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + } + } + } + }, "parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -24369,6 +24578,17 @@ "supports-color": "^5.5.0" } }, + "super-regex": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-0.2.0.tgz", + "integrity": "sha512-WZzIx3rC1CvbMDloLsVw0lkZVKJWbrkJ0k1ghKFmcnPrW1+jWbgTkTEWVtD9lMdmI4jZEz40+naBxl1dCUhXXw==", + "dev": true, + "requires": { + "clone-regexp": "^3.0.0", + "function-timeout": "^0.1.0", + "time-span": "^5.1.0" + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -24481,6 +24701,15 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, + "time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "dev": true, + "requires": { + "convert-hrtime": "^5.0.0" + } + }, "tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", diff --git a/package.json b/package.json index f97a7106..180dba22 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "dev": "npm run clean && npm run set-version && webpack --mode development -w --progress --config webpack.config.js", "dev:chrome": "BROWSER=chrome npm run dev", "dev:edge": "BROWSER=edge npm run dev", - "dev:firefox": "concurrently -k \"BROWSER=firefox npm run dev\" \"cd dist && web-ext run\" --restart-tries 20 --restart-after 5000", + "dev:firefox": "concurrently -k \"BROWSER=firefox npm run dev\" \"cd dist && web-ext run\" --restart-tries 20 --restart-after 5000 --devtools --keep-profile-changes", "format": "prettier --write '{{source,test}/**/*.{ts,js},webpack.config.js}'", "release": "run-s clean release:chrome release:firefox release:edge", "release:chrome": "npm run build:chrome && mkdirp release/chrome && zip -r release/chrome/extension.zip ./dist", @@ -91,6 +91,7 @@ "obstate": "^0.1.4", "on-navigate": "^0.1.1", "otpauth": "^9.1.5", + "parse-domain": "^8.0.2", "prettier": "^3.1.0", "pug-plugin": "^5.0.2", "react": "^17.0.2", diff --git a/source/background/services/entry.ts b/source/background/services/entry.ts index b085d334..5cb0b890 100644 --- a/source/background/services/entry.ts +++ b/source/background/services/entry.ts @@ -4,7 +4,7 @@ import { formatURL } from "../../shared/library/url.js"; export async function openEntryPageInNewTab(_: SearchResult, url: string): Promise { const tab = await createNewTab(formatURL(url)); - if (typeof tab.id !== "number") { + if (typeof tab?.id !== "number") { throw new Error("No tab ID for created tab"); } return tab.id; diff --git a/source/background/services/loginMemory.ts b/source/background/services/loginMemory.ts index 1af867e8..affe5db6 100644 --- a/source/background/services/loginMemory.ts +++ b/source/background/services/loginMemory.ts @@ -1,4 +1,6 @@ import ExpiryMap from "expiry-map"; +import { searchEntriesByTerm } from "./desktop/actions.js"; +import { domainsReferToSameParent, extractDomain } from "../../shared/library/domain.js"; import { UsedCredentials } from "../types.js"; interface LoginMemoryItem { @@ -23,6 +25,27 @@ export function clearCredentials(id: string): void { } } +export async function credentialsAlreadyStored(credentials: UsedCredentials): Promise { + const results = await searchEntriesByTerm(credentials.username); + const usedDomain = extractDomain(credentials.url); + return results.some((result) => { + // Check username + if (credentials.username !== result.properties.username) return false; + // Skip if search result has no URLs + if (result.urls.length <= 0) return false; + // Check if any of the domains match this one + const resultDomains = result.urls.map((url) => extractDomain(url)); + if (!resultDomains.some((resDomain) => domainsReferToSameParent(resDomain, usedDomain))) { + // No matches + return false; + } + // Check if props match + return ( + result.properties.username === credentials.username && result.properties.password === credentials.password + ); + }); +} + export function getAllCredentials(): Array { const memory = getLoginMemory(); const credentials: Array = []; diff --git a/source/background/services/messaging.ts b/source/background/services/messaging.ts index ad260bfd..791f7711 100644 --- a/source/background/services/messaging.ts +++ b/source/background/services/messaging.ts @@ -21,6 +21,7 @@ import { clearLocalStorage, removeLocalValue, setLocalValue } from "./storage.js import { errorToString } from "../../shared/library/error.js"; import { clearCredentials, + credentialsAlreadyStored, getAllCredentials, getCredentialsForID, getLastCredentials, @@ -176,12 +177,16 @@ async function handleMessage( break; } case BackgroundMessageType.GetLastSavedCredentials: { + const { excludeSaved = false } = msg; const tabID = sender.tab?.id; if (!tabID) { sendResponse({ credentials: [null] }); break; } - const credentials = getLastCredentials(tabID); + let credentials = getLastCredentials(tabID); + if (credentials && excludeSaved && (await credentialsAlreadyStored(credentials))) { + credentials = null; + } sendResponse({ credentials: [credentials] }); @@ -224,10 +229,14 @@ async function handleMessage( break; } case BackgroundMessageType.GetSavedCredentialsForID: { - if (!msg.credentialsID) { + const { credentialsID, excludeSaved = false } = msg; + if (!credentialsID) { throw new Error("No credentials ID provided"); } - const credentials = getCredentialsForID(msg.credentialsID); + let credentials = getCredentialsForID(credentialsID); + if (credentials && excludeSaved && (await credentialsAlreadyStored(credentials))) { + credentials = null; + } sendResponse({ credentials: [credentials] }); diff --git a/source/full/components/pages/saveCredentials/CredentialsSaver.tsx b/source/full/components/pages/saveCredentials/CredentialsSaver.tsx index 50db088b..3fa22b81 100644 --- a/source/full/components/pages/saveCredentials/CredentialsSaver.tsx +++ b/source/full/components/pages/saveCredentials/CredentialsSaver.tsx @@ -146,7 +146,7 @@ export function CredentialsSaver(props: CredentialsSaverProps) { const [credentials, credentialsLoading, credentialsError] = useCapturedCredentials(); const [selectedNodes, setSelectedNodes] = useState>([]); const [expandedNodes, setExpandedNodes] = useState>([]); - const selectedUsedCredentials = useMemo(() => credentials.find(cred => cred.id === selectedCredentialsID), [credentials, selectedCredentialsID]); + const selectedUsedCredentials = useMemo(() => credentials.find(cred => cred?.id === selectedCredentialsID), [credentials, selectedCredentialsID]); const selectedGroupURI = useMemo(() => { return selectedNodes.length === 1 && /^group:/.test(selectedNodes[0]) ? selectedNodes[0] diff --git a/source/full/components/pages/saveCredentials/CredentialsSelector.tsx b/source/full/components/pages/saveCredentials/CredentialsSelector.tsx index 3d9a060c..406edcd6 100644 --- a/source/full/components/pages/saveCredentials/CredentialsSelector.tsx +++ b/source/full/components/pages/saveCredentials/CredentialsSelector.tsx @@ -67,7 +67,8 @@ const URL = styled.span` export function CredentialsSelector(props: CredentialsSelectorProps) { const { disabled: parentDisabled = false, onSelect, selected } = props; - const [credentials, loading, error] = useCapturedCredentials(); + const [credentialsInitial, loading, error] = useCapturedCredentials(); + const credentials = useMemo(() => credentialsInitial.filter(cred => !!cred) as Array, [credentialsInitial]); const disabled = parentDisabled || loading; const handleItemClick = useCallback((credential: UsedCredentials) => { if (disabled) return; diff --git a/source/full/hooks/credentials.ts b/source/full/hooks/credentials.ts index a8679f09..bf462a70 100644 --- a/source/full/hooks/credentials.ts +++ b/source/full/hooks/credentials.ts @@ -2,7 +2,11 @@ import { useAsync } from "../../shared/hooks/async.js"; import { getCredentials } from "../services/credentials.js"; import { UsedCredentials } from "../types.js"; -export function useCapturedCredentials(): [credentials: Array, loading: boolean, error: Error | null] { +export function useCapturedCredentials(): [ + credentials: Array, + loading: boolean, + error: Error | null +] { const { error, loading, value } = useAsync(getCredentials, []); return [value || [], loading, error]; } diff --git a/source/full/hooks/document.ts b/source/full/hooks/document.ts index f5eaf39e..8ee9698c 100644 --- a/source/full/hooks/document.ts +++ b/source/full/hooks/document.ts @@ -2,7 +2,7 @@ import { useEffect } from "react"; const TITLE_SEPARATOR = "⋅"; -let __originalTitle: string = null; +let __originalTitle: string | null = null; export function useTitle(title: string) { useEffect(() => { diff --git a/source/full/services/credentials.ts b/source/full/services/credentials.ts index b009e2de..c880b51b 100644 --- a/source/full/services/credentials.ts +++ b/source/full/services/credentials.ts @@ -13,21 +13,21 @@ export async function clearSavedCredentials(id: string): Promise { } } -export async function getCredentials(): Promise> { +export async function getCredentials(): Promise> { const resp = await sendBackgroundMessage({ type: BackgroundMessageType.GetSavedCredentials }); if (resp.error) { throw new Layerr(resp.error, "Failed fetching saved credentials"); } - return resp.credentials; + return resp.credentials ?? []; } export async function saveCredentialsToEntry(credentials: SavedCredentials): Promise { const { entryID = null } = await sendBackgroundMessage({ sourceID: credentials.sourceID, groupID: credentials.groupID, - entryID: credentials.entryID ?? null, + entryID: credentials.entryID ?? undefined, entryProperties: { password: credentials.password, title: credentials.title, diff --git a/source/full/services/disabledDomains.ts b/source/full/services/disabledDomains.ts index 786dc2f2..d583bfff 100644 --- a/source/full/services/disabledDomains.ts +++ b/source/full/services/disabledDomains.ts @@ -9,7 +9,7 @@ export async function getDisabledDomains(): Promise> { if (resp.error) { throw new Layerr(resp.error, "Failed fetching disabled domains"); } - return resp.domains; + return resp.domains ?? []; } export async function removeDisabledDomain(domain: string): Promise { diff --git a/source/full/services/vaults.ts b/source/full/services/vaults.ts index e9708e22..2d474744 100644 --- a/source/full/services/vaults.ts +++ b/source/full/services/vaults.ts @@ -9,5 +9,8 @@ export async function getVaultsTree(): Promise { if (resp.error) { throw new Layerr(resp.error, "Failed fetching vaults tree"); } + if (!resp.vaultsTree) { + throw new Error("No vaults tree returned"); + } return resp.vaultsTree; } diff --git a/source/shared/hooks/global.ts b/source/shared/hooks/global.ts index ee6458e5..f537bfa6 100644 --- a/source/shared/hooks/global.ts +++ b/source/shared/hooks/global.ts @@ -8,7 +8,7 @@ interface Globals { const __globals: Globals = { configFlagTs: null }; -let __ee: EventEmitter = null; +let __ee: EventEmitter | null = null; export function useGlobal(key: K): [Globals[K], (value: Globals[K]) => void] { useEffect(() => { @@ -20,9 +20,11 @@ export function useGlobal(key: K): [Globals[K], (value: setCurrentValue(__globals[key]); }, [key]); useEffect(() => { + if (!__ee) return; handleEventUpdate(); __ee.on("update", handleEventUpdate); return () => { + if (!__ee) return; __ee.off("update", handleEventUpdate); }; }, [handleEventUpdate]); @@ -31,7 +33,9 @@ export function useGlobal(key: K): [Globals[K], (value: (value: Globals[K]) => { __globals[key] = value; setCurrentValue(value); - __ee.emit("update"); + if (__ee) { + __ee.emit("update"); + } }, [key] ); diff --git a/source/shared/library/domain.ts b/source/shared/library/domain.ts index 469cfc17..2493e307 100644 --- a/source/shared/library/domain.ts +++ b/source/shared/library/domain.ts @@ -1,4 +1,17 @@ import { EntryURLType, PropertyKeyValueObject, getEntryURLs } from "buttercup"; +import { ParseResultListed, ParseResultType, parseDomain } from "parse-domain"; + +export function domainsReferToSameParent(domain1: string, domain2: string): boolean { + if (domain1 === domain2) return true; + const res1 = parseDomain(domain1); + const res2 = parseDomain(domain2); + if (res1.type !== res2.type) return false; + if (res1.type !== ParseResultType.Listed) return false; + const r1 = (res1 as ParseResultListed).icann; + const r2 = (res2 as ParseResultListed).icann; + if (r1.topLevelDomains.join(".") !== r2.topLevelDomains.join(".")) return false; + return r1.domain === r2.domain; +} export function extractDomain(str: string): string { const domainMatch = str.match(/^https?:\/\/([^\/]+)/i); diff --git a/source/shared/library/otp.ts b/source/shared/library/otp.ts index 6d3ffa18..8f6b2f94 100644 --- a/source/shared/library/otp.ts +++ b/source/shared/library/otp.ts @@ -3,7 +3,8 @@ import { Layerr } from "layerr"; import * as OTPAuth from "otpauth"; function extractFirstOTPURI(entry: SearchResult): string | null { - let key: string, value: string; + let key: string | null = null, + value: string | null = null; for (const prop in entry.properties) { if (!/^otpauth:\/\//.test(entry.properties[prop])) continue; if (!key || prop.length < key.length) { diff --git a/source/shared/types.ts b/source/shared/types.ts index 86d12936..ec17f796 100644 --- a/source/shared/types.ts +++ b/source/shared/types.ts @@ -32,6 +32,7 @@ export interface BackgroundMessage { entryID?: EntryID; entryProperties?: Record; entryType?: EntryType; + excludeSaved?: boolean; groupID?: GroupID; notification?: string; searchTerm?: string; diff --git a/source/tab/services/logins/saving.ts b/source/tab/services/logins/saving.ts index 06976624..8281c2e7 100644 --- a/source/tab/services/logins/saving.ts +++ b/source/tab/services/logins/saving.ts @@ -2,9 +2,10 @@ import { Layerr } from "layerr"; import { sendBackgroundMessage } from "../../../shared/services/messaging.js"; import { BackgroundMessageType, UsedCredentials } from "../../types.js"; -export async function getCredentialsForID(id: string): Promise { +export async function getCredentialsForID(id: string, excludeSaved: boolean = false): Promise { const resp = await sendBackgroundMessage({ credentialsID: id, + excludeSaved, type: BackgroundMessageType.GetSavedCredentialsForID }); if (resp.error) { @@ -13,8 +14,9 @@ export async function getCredentialsForID(id: string): Promise { +export async function getLastSavedCredentials(excludeSaved: boolean = false): Promise { const resp = await sendBackgroundMessage({ + excludeSaved, type: BackgroundMessageType.GetLastSavedCredentials }); if (resp.error) { diff --git a/source/tab/services/logins/watcher.ts b/source/tab/services/logins/watcher.ts index a3566b89..c8da28e5 100644 --- a/source/tab/services/logins/watcher.ts +++ b/source/tab/services/logins/watcher.ts @@ -12,7 +12,7 @@ async function checkForLoginSaveAbility(loginID?: string) { const [disabledDomains, config, used] = await Promise.all([ getDisabledDomains(), getConfig(), - loginID ? getCredentialsForID(loginID) : getLastSavedCredentials() + loginID ? getCredentialsForID(loginID, true) : getLastSavedCredentials(true) ]); if (!used || !used.promptSave || used.fromEntry) return; if (currentDomainDisabled(disabledDomains)) {