diff --git a/app/src/components/mode-specific/desktop/MySources/Sources/InstructionsModal/Android/ConfigureAndroidApps.js b/app/src/components/mode-specific/desktop/MySources/Sources/InstructionsModal/Android/ConfigureAndroidApps.js index d0eec416d7..d1f498ed87 100644 --- a/app/src/components/mode-specific/desktop/MySources/Sources/InstructionsModal/Android/ConfigureAndroidApps.js +++ b/app/src/components/mode-specific/desktop/MySources/Sources/InstructionsModal/Android/ConfigureAndroidApps.js @@ -8,7 +8,7 @@ const ConfigureAndroidApps = () => { diff --git a/app/src/features/settings/components/Profile/ManageAccount.js b/app/src/features/settings/components/Profile/ManageAccount.js index c6178f22ab..10c182eab8 100644 --- a/app/src/features/settings/components/Profile/ManageAccount.js +++ b/app/src/features/settings/components/Profile/ManageAccount.js @@ -5,6 +5,7 @@ import { Button as AntButton } from "antd"; import UserInfo from "./UserInfo"; //UTILS import { getUserAuthDetails } from "store/slices/global/user/selectors"; +import { getUserAvatarUrl } from "utils/ImageUtils"; import { redirectToDeleteAccount, redirectToSignDPA } from "../../../../utils/RedirectionUtils"; // ACTIONS import { handleForgotPasswordButtonOnClick } from "features/onboarding/components/auth/components/Form/actions"; @@ -21,9 +22,7 @@ const ManageAccount = () => { //Component State const [isChangePasswordLoading, setIsChangePasswordLoading] = useState(false); - // Fallback image - const defaultImageSrc = "https://www.gravatar.com/avatar/00000000000000000000000000000000?d=mp&f=y"; - let userImageSrc = user.details.profile.photoURL ? user.details.profile.photoURL : defaultImageSrc; + let userImageSrc = getUserAvatarUrl(user.details.profile.email, user.details.profile.photoURL); const userDisplayName = user.details.profile.displayName ? user.details.profile.displayName : "User"; diff --git a/app/src/layouts/DashboardLayout/MenuHeader/HeaderUser.jsx b/app/src/layouts/DashboardLayout/MenuHeader/HeaderUser.jsx index 3fd0225e55..aa8ca161e5 100644 --- a/app/src/layouts/DashboardLayout/MenuHeader/HeaderUser.jsx +++ b/app/src/layouts/DashboardLayout/MenuHeader/HeaderUser.jsx @@ -16,7 +16,7 @@ import { import { handleLogoutButtonOnClick } from "features/onboarding/components/auth/components/Form/actions"; import APP_CONSTANTS from "config/constants"; import { SOURCE } from "modules/analytics/events/common/constants"; -import { parseGravatarImage } from "utils/Misc"; +import { getUserAvatarUrl } from "utils/ImageUtils"; import { trackHeaderClicked } from "modules/analytics/events/common/onboarding/header"; import { trackUpgradeClicked } from "modules/analytics/events/misc/monetizationExperiment"; import { getAppFlavour } from "utils/AppUtils"; @@ -43,7 +43,9 @@ export default function HeaderUser() { const userName = user.loggedIn ? user?.details?.profile?.displayName ?? "User" : null; const userPhoto = - user.loggedIn && user?.details?.profile?.photoURL ? parseGravatarImage(user.details.profile.photoURL) : null; + user.loggedIn && user?.details?.profile + ? getUserAvatarUrl(user.details.profile.email, user.details.profile.photoURL) + : null; const userEmail = user?.details?.profile?.email; const planDetails = user?.details?.planDetails; diff --git a/app/src/utils/ImageUtils.js b/app/src/utils/ImageUtils.js index 2e2d011a51..ee0cab80ae 100644 --- a/app/src/utils/ImageUtils.js +++ b/app/src/utils/ImageUtils.js @@ -14,3 +14,35 @@ export const getRandomAvatar = () => { export const generateGravatarURL = (email = "sagar@requestly.io") => { return `https://www.gravatar.com/avatar/${md5(email)}?s=200&d=${getRandomAvatar()}`; }; + +/** + * True when `url`'s host is Gravatar. Parses the URL and matches on the hostname + * rather than a substring — `url.includes("gravatar.com")` would also match + * unrelated hosts such as `gravatar.com.evil.com` or `host/path?ref=gravatar.com`. + * Unparseable values return false (treated as "not a Gravatar URL"). + */ +const isGravatarUrl = (url) => { + try { + const host = new URL(url).hostname.toLowerCase(); + return host === "gravatar.com" || host.endsWith(".gravatar.com"); + } catch { + return false; + } +}; + +/** + * Resolves the avatar to display for a user. + * + * Precedence: the auth provider's photo (e.g. Google) when we have a real one, + * otherwise a Gravatar resolved live from the current login email — so changes a + * user makes on Gravatar are reflected here. For email/password accounts we persist + * a synthetic gravatar.com URL as `photoURL`, so any Gravatar-hosted value is treated + * as "no provider photo" and re-resolved live from the email. `d=mp` is Gravatar's + * deterministic fallback, shown when the email has no Gravatar image. + */ +export const getUserAvatarUrl = (email = "", providerPhotoURL = "") => { + if (providerPhotoURL && !isGravatarUrl(providerPhotoURL)) { + return providerPhotoURL; + } + return `https://www.gravatar.com/avatar/${md5(email)}?s=200&d=mp`; +};