feat(auth): refresh tokens + remember me
Backend: add refresh JWT (30d, token_type claim), POST /auth/refresh endpoint (rotates token pair), remember_me on login, JWT_REFRESH_EXPIRY_DAYS env var. Extractors now reject refresh tokens on protected routes. Frontend: sessionStorage for non-remembered sessions, localStorage + refresh token for remembered sessions. Transparent 401 recovery in api.ts (retry once after refresh). Remember me checkbox on login page with security note when checked.
This commit is contained in:
@@ -8,12 +8,13 @@ import { useConfig } from "@/hooks/use-channels";
|
||||
export default function LoginPage() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [rememberMe, setRememberMe] = useState(false);
|
||||
const { mutate: login, isPending, error } = useLogin();
|
||||
const { data: config } = useConfig();
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
login({ email, password });
|
||||
login({ email, password, rememberMe });
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -54,6 +55,23 @@ export default function LoginPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="flex cursor-pointer items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={rememberMe}
|
||||
onChange={(e) => setRememberMe(e.target.checked)}
|
||||
className="h-3.5 w-3.5 rounded border-zinc-600 bg-zinc-900 accent-white"
|
||||
/>
|
||||
<span className="text-xs text-zinc-400">Remember me</span>
|
||||
</label>
|
||||
{rememberMe && (
|
||||
<p className="pl-5 text-xs text-amber-500/80">
|
||||
A refresh token will be stored locally — don't share it.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-400">{error.message}</p>}
|
||||
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user