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) {
|
||||
const [url, setUrl] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [insecureAck, setInsecureAck] = useState(false);
|
||||
const [connecting, setConnecting] = useState(false);
|
||||
|
||||
function handleConnect() {
|
||||
|
|
@ -32,6 +45,12 @@ export default function SetupScreen({ onConnect, onLocalMode }: Props) {
|
|||
setError('Enter a valid URL, e.g. https://bills.yourdomain.com');
|
||||
return;
|
||||
}
|
||||
// First tap on an insecure URL: warn and require a second, explicit tap.
|
||||
if (isInsecureUrl(normalized) && !insecureAck) {
|
||||
setError('');
|
||||
setInsecureAck(true);
|
||||
return;
|
||||
}
|
||||
setError('');
|
||||
setConnecting(true);
|
||||
onConnect(normalized);
|
||||
|
|
@ -69,11 +88,17 @@ export default function SetupScreen({ onConnect, onLocalMode }: Props) {
|
|||
spellCheck={false}
|
||||
placeholder="https://bills.yourdomain.com"
|
||||
value={url}
|
||||
onChange={e => { setUrl(e.target.value); setError(''); }}
|
||||
onChange={e => { setUrl(e.target.value); setError(''); setInsecureAck(false); }}
|
||||
onKeyDown={handleKey}
|
||||
disabled={connecting}
|
||||
/>
|
||||
{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">
|
||||
The address of your Bill Tracker server. Must be reachable from this device.
|
||||
</p>
|
||||
|
|
@ -84,7 +109,7 @@ export default function SetupScreen({ onConnect, onLocalMode }: Props) {
|
|||
onClick={handleConnect}
|
||||
disabled={connecting || !url.trim()}
|
||||
>
|
||||
{connecting ? 'Connecting…' : 'Continue'}
|
||||
{connecting ? 'Connecting…' : insecureAck ? 'Connect anyway' : 'Continue'}
|
||||
</button>
|
||||
|
||||
<div className="divider">
|
||||
|
|
|
|||
Loading…
Reference in New Issue