diff --git a/src/SetupScreen.tsx b/src/SetupScreen.tsx index 8c78b9c..8147667 100644 --- a/src/SetupScreen.tsx +++ b/src/SetupScreen.tsx @@ -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 &&

{error}

} + {insecureAck && ( +

+ This is an insecure http:// address — your password will be sent + unencrypted. Only continue on a network you trust. Tap again to connect anyway. +

+ )}

The address of your Bill Tracker server. Must be reachable from this device.

@@ -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'}