feat: login IT con allowlist (scrypt+HMAC) + middleware protege tablero/gestion

This commit is contained in:
Nurfog committed 2026-09-09 14:30:27 -03:00
1 parent 08bf7254a1
commit 57b64af1ba
13 files changed
+392 -14

No files matched your search

+146
View File
@@ -0,0 +1,146 @@
"use client";
import { useState, Suspense } from "react";
import { useRouter, useSearchParams } from "next/navigation";
function LoginForm() {
const router = useRouter();
const next = useSearchParams().get("next") || "/tablero";
const [rut, setRut] = useState("");
const [clave, setClave] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const submit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError(null);
try {
const res = await fetch("/api/auth", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: rut, password: clave }),
});
const j = await res.json();
if (!res.ok) throw new Error(j.error || `HTTP ${res.status}`);
router.push(next.startsWith("/") ? next : "/tablero");
router.refresh();
} catch (err) {
setError(err instanceof Error ? err.message : "Error al ingresar");
} finally {
setLoading(false);
}
};
return (
<>
<div className="sidenav">
<div className="login-main-text text-light text-center">
<img src="/favicon.png" alt="SAM" />
<h1>SAM</h1>
<h3>Welcome</h3>
<h3>Bienvenido/a</h3>
<h3>Portal IT</h3>
</div>
</div>
<div className="main">
<div className="row">
<div className="col-md-12">
<div className="row">
<div className="col-md-12 text-center">
<div className="row">
<div className="col-md-2" />
<div className="col-md-8">
<div className="card alert alert-secondary">
<div className="card-body">
<form onSubmit={submit}>
<div className="row">
<div className="col-md-12 mb-3">
<div className="row">
<div className="col-md-3" />
<div className="col-md-6 text-center">
<img
src="/logo-sam.png"
alt="SAM"
className="img-fluid"
style={{ maxWidth: 220 }}
/>
</div>
<div className="col-md-3" />
</div>
</div>
{error && (
<div className="col-md-12 mb-3">
<div className="alert alert-danger py-2 mb-0">
{error}
</div>
</div>
)}
<div className="col-md-3 mt-2 text-start">
<label className="form-label fw-semibold">USUARIO</label>
</div>
<div className="col-md-6 mt-2">
<input
name="TextBoxRut"
type="text"
value={rut}
onChange={(e) => setRut(e.target.value)}
className="form-control shadow"
required
placeholder="Usuario"
autoComplete="username"
/>
</div>
<div className="col-md-3 mt-2 text-start">
<label className="form-label fw-semibold">CLAVE</label>
</div>
<div className="col-md-6 mt-2">
<input
name="TextBoxClave"
type="password"
value={clave}
onChange={(e) => setClave(e.target.value)}
className="form-control shadow"
required
placeholder="Clave"
autoComplete="current-password"
/>
</div>
<div className="col-md-12 mt-3">
<div className="d-grid">
<button
type="submit"
disabled={loading}
className="btn btn-success btn-sm"
>
{loading ? (
<span className="spinner-border spinner-border-sm" />
) : (
"Ingresar"
)}
</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</>
);
}
export default function Login() {
return (
<Suspense>
<LoginForm />
</Suspense>
);
}