feat(mobile): warn on insecure http:// server URLs before native login
SetupScreen now flags a plain http:// address (to anything but loopback) — the
native login POST would send the password in cleartext — and requires an explicit
second tap ("Connect anyway") to proceed, with an override for trusted LAN setups.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
c1523ece1a
commit
7f7c0ef7b0
|
|
@ -21,9 +21,22 @@ function isValidUrl(url: string): boolean {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Plain http:// (to anything but loopback) sends the password in cleartext during
|
||||||
|
// the native login POST. Warn and require an explicit acknowledgement.
|
||||||
|
function isInsecureUrl(url: string): boolean {
|
||||||
|
try {
|
||||||
|
const u = new URL(url);
|
||||||
|
if (u.protocol !== 'http:') return false;
|
||||||
|
return !(u.hostname === 'localhost' || u.hostname === '127.0.0.1' || u.hostname === '::1');
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function SetupScreen({ onConnect, onLocalMode }: Props) {
|
export default function SetupScreen({ onConnect, onLocalMode }: Props) {
|
||||||
const [url, setUrl] = useState('');
|
const [url, setUrl] = useState('');
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
const [insecureAck, setInsecureAck] = useState(false);
|
||||||
const [connecting, setConnecting] = useState(false);
|
const [connecting, setConnecting] = useState(false);
|
||||||
|
|
||||||
function handleConnect() {
|
function handleConnect() {
|
||||||
|
|
@ -32,6 +45,12 @@ export default function SetupScreen({ onConnect, onLocalMode }: Props) {
|
||||||
setError('Enter a valid URL, e.g. https://bills.yourdomain.com');
|
setError('Enter a valid URL, e.g. https://bills.yourdomain.com');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// First tap on an insecure URL: warn and require a second, explicit tap.
|
||||||
|
if (isInsecureUrl(normalized) && !insecureAck) {
|
||||||
|
setError('');
|
||||||
|
setInsecureAck(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
setError('');
|
setError('');
|
||||||
setConnecting(true);
|
setConnecting(true);
|
||||||
onConnect(normalized);
|
onConnect(normalized);
|
||||||
|
|
@ -69,11 +88,17 @@ export default function SetupScreen({ onConnect, onLocalMode }: Props) {
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
placeholder="https://bills.yourdomain.com"
|
placeholder="https://bills.yourdomain.com"
|
||||||
value={url}
|
value={url}
|
||||||
onChange={e => { setUrl(e.target.value); setError(''); }}
|
onChange={e => { setUrl(e.target.value); setError(''); setInsecureAck(false); }}
|
||||||
onKeyDown={handleKey}
|
onKeyDown={handleKey}
|
||||||
disabled={connecting}
|
disabled={connecting}
|
||||||
/>
|
/>
|
||||||
{error && <p className="form-error">{error}</p>}
|
{error && <p className="form-error">{error}</p>}
|
||||||
|
{insecureAck && (
|
||||||
|
<p className="form-error">
|
||||||
|
This is an insecure <strong>http://</strong> address — your password will be sent
|
||||||
|
unencrypted. Only continue on a network you trust. Tap again to connect anyway.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
<p className="form-hint">
|
<p className="form-hint">
|
||||||
The address of your Bill Tracker server. Must be reachable from this device.
|
The address of your Bill Tracker server. Must be reachable from this device.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -84,7 +109,7 @@ export default function SetupScreen({ onConnect, onLocalMode }: Props) {
|
||||||
onClick={handleConnect}
|
onClick={handleConnect}
|
||||||
disabled={connecting || !url.trim()}
|
disabled={connecting || !url.trim()}
|
||||||
>
|
>
|
||||||
{connecting ? 'Connecting…' : 'Continue'}
|
{connecting ? 'Connecting…' : insecureAck ? 'Connect anyway' : 'Continue'}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="divider">
|
<div className="divider">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue