dataflow/ui/src/pages/Login.jsx
Paul Trowbridge 2c573a5eeb Add login authentication with Basic Auth
- Express auth middleware checks Authorization: Basic header on all /api
  routes using bcrypt against LOGIN_USER/LOGIN_PASSWORD_HASH in .env
- React login screen shown before app loads, stores credentials in memory,
  sends them with every API request, clears and returns to login on 401
- Logout button in sidebar header
- manage.py option 9: set login credentials (bcrypt via node, writes to .env)
- manage.py status shows whether login credentials are configured

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 17:41:07 -04:00

61 lines
2.0 KiB
JavaScript

import { useState } from 'react'
export default function Login({ onLogin }) {
const [user, setUser] = useState('')
const [pass, setPass] = useState('')
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
async function handleSubmit(e) {
e.preventDefault()
setError('')
setLoading(true)
try {
await onLogin(user, pass)
} catch {
setError('Invalid username or password')
} finally {
setLoading(false)
}
}
return (
<div className="flex items-center justify-center h-screen bg-gray-50">
<div className="bg-white border border-gray-200 rounded-lg p-8 w-80 shadow-sm">
<h1 className="text-lg font-semibold text-gray-800 mb-6">Dataflow</h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-xs text-gray-500 mb-1">Username</label>
<input
type="text"
autoFocus
value={user}
onChange={e => setUser(e.target.value)}
className="w-full border border-gray-200 rounded px-3 py-2 text-sm focus:outline-none focus:border-blue-400"
required
/>
</div>
<div>
<label className="block text-xs text-gray-500 mb-1">Password</label>
<input
type="password"
value={pass}
onChange={e => setPass(e.target.value)}
className="w-full border border-gray-200 rounded px-3 py-2 text-sm focus:outline-none focus:border-blue-400"
required
/>
</div>
{error && <p className="text-xs text-red-500">{error}</p>}
<button
type="submit"
disabled={loading}
className="w-full bg-blue-600 text-white rounded px-3 py-2 text-sm font-medium hover:bg-blue-700 disabled:opacity-50"
>
{loading ? 'Signing in…' : 'Sign in'}
</button>
</form>
</div>
</div>
)
}