fix(app): add masking of phones to settings

This commit is contained in:
Hampus Kraft 2026-02-21 15:46:29 +00:00
parent a129b162b7
commit 2db53689a1
No known key found for this signature in database
GPG Key ID: 6090864C465A454D
4 changed files with 78 additions and 6 deletions

View File

@ -37,6 +37,7 @@ const AccountSecurityTab: React.FC = observer(() => {
const {t} = useLingui();
const user = UserStore.currentUser;
const [showMaskedEmail, setShowMaskedEmail] = useState(false);
const [showMaskedPhone, setShowMaskedPhone] = useState(false);
const [passkeys, setPasskeys] = useState<Array<UserActionCreators.WebAuthnCredential>>([]);
const [loadingPasskeys, setLoadingPasskeys] = useState(false);
const [enablingSmsMfa, setEnablingSmsMfa] = useState(false);
@ -100,9 +101,11 @@ const AccountSecurityTab: React.FC = observer(() => {
loadingPasskeys={loadingPasskeys}
enablingSmsMfa={enablingSmsMfa}
disablingSmsMfa={disablingSmsMfa}
showMaskedPhone={showMaskedPhone}
loadPasskeys={loadPasskeys}
setEnablingSmsMfa={setEnablingSmsMfa}
setDisablingSmsMfa={setDisablingSmsMfa}
setShowMaskedPhone={setShowMaskedPhone}
/>
</SettingsSection>

View File

@ -36,6 +36,7 @@ export const AccountSecurityInlineTab = observer(() => {
const {t} = useLingui();
const user = UserStore.currentUser;
const [showMaskedEmail, setShowMaskedEmail] = useState(false);
const [showMaskedPhone, setShowMaskedPhone] = useState(false);
const [passkeys, setPasskeys] = useState<Array<UserActionCreators.WebAuthnCredential>>([]);
const [loadingPasskeys, setLoadingPasskeys] = useState(false);
const [enablingSmsMfa, setEnablingSmsMfa] = useState(false);
@ -89,9 +90,11 @@ export const AccountSecurityInlineTab = observer(() => {
loadingPasskeys={loadingPasskeys}
enablingSmsMfa={enablingSmsMfa}
disablingSmsMfa={disablingSmsMfa}
showMaskedPhone={showMaskedPhone}
loadPasskeys={loadPasskeys}
setEnablingSmsMfa={setEnablingSmsMfa}
setDisablingSmsMfa={setDisablingSmsMfa}
setShowMaskedPhone={setShowMaskedPhone}
/>
</SettingsSection>
<SettingsSection id="danger_zone" title={t`Danger Zone`}>

View File

@ -97,6 +97,48 @@
gap: 0.5rem;
}
.phoneRow {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
@media (min-width: 640px) {
.phoneRow {
flex-direction: row;
align-items: center;
gap: 0.5rem;
}
}
.phoneText {
color: var(--text-primary-muted);
font-size: 0.875rem;
}
.phoneTextSelectable {
user-select: text;
-webkit-user-select: text;
}
.toggleButton {
margin-top: 0.1em;
text-align: left;
color: var(--text-link);
font-size: 0.875rem;
cursor: pointer;
}
.toggleButton:hover {
text-decoration: underline;
}
@media (min-width: 640px) {
.toggleButton {
text-align: center;
}
}
.claimButton {
align-self: flex-start;
}

View File

@ -41,6 +41,15 @@ import type React from 'react';
const logger = new Logger('SecurityTab');
const maskPhone = (phone: string): string => {
if (phone.length <= 4) {
return phone.replace(/./g, '*');
}
const lastTwo = phone.slice(-2);
const masked = phone.slice(0, -2).replace(/\d/g, '*');
return `${masked}${lastTwo}`;
};
interface SecurityTabProps {
user: UserRecord;
isClaimed: boolean;
@ -51,9 +60,11 @@ interface SecurityTabProps {
loadingPasskeys: boolean;
enablingSmsMfa: boolean;
disablingSmsMfa: boolean;
showMaskedPhone: boolean;
loadPasskeys: () => Promise<void>;
setEnablingSmsMfa: React.Dispatch<React.SetStateAction<boolean>>;
setDisablingSmsMfa: React.Dispatch<React.SetStateAction<boolean>>;
setShowMaskedPhone: (show: boolean) => void;
}
export const SecurityTabContent: React.FC<SecurityTabProps> = observer(
@ -67,9 +78,11 @@ export const SecurityTabContent: React.FC<SecurityTabProps> = observer(
loadingPasskeys,
enablingSmsMfa,
disablingSmsMfa,
showMaskedPhone,
loadPasskeys,
setEnablingSmsMfa,
setDisablingSmsMfa,
setShowMaskedPhone,
}) => {
const {t, i18n} = useLingui();
@ -344,13 +357,24 @@ export const SecurityTabContent: React.FC<SecurityTabProps> = observer(
<div className={styles.label}>
<Trans>Phone Number</Trans>
</div>
<div className={styles.description}>
{user.phone ? (
<Trans>Phone number added: {user.phone}</Trans>
) : (
{user.phone ? (
<div className={styles.phoneRow}>
<span className={`${styles.phoneText} ${showMaskedPhone ? styles.phoneTextSelectable : ''}`}>
{showMaskedPhone ? user.phone : maskPhone(user.phone)}
</span>
<button
type="button"
className={styles.toggleButton}
onClick={() => setShowMaskedPhone(!showMaskedPhone)}
>
{showMaskedPhone ? t`Hide` : t`Reveal`}
</button>
</div>
) : (
<div className={styles.description}>
<Trans>Add a phone number to enable SMS two-factor authentication</Trans>
)}
</div>
</div>
)}
</div>
{user.phone ? (
<Button