diff --git a/docker-compose.yml b/docker-compose.yml index b4e61b26..4cb7f569 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -81,7 +81,7 @@ services: DB_USER: "center" DB_PASSWORD: "Center#2025" DB_NAME: "dms" - JWT_SECRET: "8b0df02e4aee9f9f79a4f2d8ba77b0b82c1ee3446b68cb0bae94ab54d60f8d9e" + JWT_SECRET: "9a6d8705a6695ab9bae4ca1cd46c72a6379aa72404b96e2c5b59af881bb55c639dd583afdce5a885c68e188da55ce6dbc1fb4aa9cd4055ceb51507e56204e4ca" JWT_EXPIRES_IN: "12h" PASSWORD_SALT_ROUNDS: "10" FRONTEND_ORIGIN: "https://lcbp3.np-dms.work" @@ -130,10 +130,11 @@ services: CHOKIDAR_USEPOLLING: "1" WATCHPACK_POLLING: "true" NEXT_PUBLIC_API_BASE: "https://lcbp3.np-dms.work" + NEXT_PUBLIC_AUTH_MODE: "cookie" NEXT_PUBLIC_DEBUG_AUTH: "1" NEXT_TELEMETRY_DISABLED: "1" - JWT_ACCESS_SECRET: "change-this-access-secret" - JWT_REFRESH_SECRET: "change-this-refresh-secret" + JWT_ACCESS_SECRET: "9a6d8705a6695ab9bae4ca1cd46c72a6379aa72404b96e2c5b59af881bb55c639dd583afdce5a885c68e188da55ce6dbc1fb4aa9cd4055ceb51507e56204e4ca" + JWT_REFRESH_SECRET: "743e798bb10d6aba168bf68fc3cf8eff103c18bd34f1957a3906dc87987c0df139ab72498f2fe20d6c4c580f044ccba7d7bfa4393ee6035b73ba038f28d7480c" expose: - "3000" networks: [dmsnet] diff --git a/frontend/app/(auth)/login/page.jsx b/frontend/app/(auth)/login/page.jsx index d8cb953b..178dfe04 100755 --- a/frontend/app/(auth)/login/page.jsx +++ b/frontend/app/(auth)/login/page.jsx @@ -1,54 +1,34 @@ // File: frontend/app/(auth)/login/page.jsx - "use client"; -// ✅ ปรับให้ตรง backend: ใช้ Bearer token (ไม่ใช้ cookie) -// - เรียก POST /api/auth/login → รับ { token, refresh_token, user } -// - เก็บ token/refresh_token ใน localStorage (หรือ sessionStorage ถ้าไม่ติ๊กจำไว้) -// - ไม่ใช้ credentials: "include" อีกต่อไป -// - เอา RootLayout/metadata ออก เพราะไฟล์เพจเป็น client component -// - เพิ่มการอ่าน NEXT_PUBLIC_API_BASE และ error handling ให้ตรงกับ backend -// - เพิ่มโหมดดีบัก เปิดด้วย NEXT_PUBLIC_DEBUG_AUTH=1 - import { useState, useMemo, Suspense } from "react"; import { useSearchParams, useRouter } from "next/navigation"; import { - Card, - CardHeader, - CardTitle, - CardDescription, - CardContent, - CardFooter, + Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Alert, AlertDescription } from "@/components/ui/alert"; -const API_BASE = process.env.NEXT_PUBLIC_API_BASE?.replace(/\/$/, "") || ""; +const API_BASE = (process.env.NEXT_PUBLIC_API_BASE || "").replace(/\/$/, ""); const DEBUG = String(process.env.NEXT_PUBLIC_DEBUG_AUTH || "").trim() !== "" && process.env.NEXT_PUBLIC_DEBUG_AUTH !== "0" && process.env.NEXT_PUBLIC_DEBUG_AUTH !== "false"; function dlog(...args) { - if (DEBUG && typeof window !== "undefined") { - console.debug("[login]", ...args); - } + if (DEBUG && typeof window !== "undefined") console.debug("[login]", ...args); } function LoginForm() { const router = useRouter(); const searchParams = useSearchParams(); - const nextPath = useMemo( - () => searchParams.get("next") || "/dashboard", - [searchParams] - ); + const nextPath = useMemo(() => searchParams.get("next") || "/dashboard", [searchParams]); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [showPw, setShowPw] = useState(false); - const [remember, setRemember] = useState(false); const [submitting, setSubmitting] = useState(false); const [err, setErr] = useState(""); @@ -63,69 +43,37 @@ function LoginForm() { try { setSubmitting(true); - - // ── DEBUG: ค่าเบื้องต้น - dlog("API_BASE =", API_BASE || "(empty → จะเรียก path relative)"); - dlog("nextPath =", nextPath); - dlog("remember =", remember); - dlog("payload =", { username: "[hidden]", password: "[hidden]" }); + dlog("API_BASE =", API_BASE || "(empty → relative path)"); dlog("nextPath =", nextPath); const res = await fetch(`${API_BASE}/api/auth/login`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ username, password }), + credentials: "include", // << ใช้คุกกี้ cache: "no-store", + body: JSON.stringify({ username, password }), }); - dlog("response.status =", res.status); - dlog("response.headers.content-type =", res.headers.get("content-type")); - + dlog("status =", res.status, "ctype =", res.headers.get("content-type")); let data = {}; - try { - data = await res.json(); - } catch (e) { - dlog("response.json() error =", e); - } - dlog("response.body =", data); + try { data = await res.json(); } catch {} if (!res.ok) { const msg = data?.error === "INVALID_CREDENTIALS" ? "ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง" : data?.error || `เข้าสู่ระบบไม่สำเร็จ (HTTP ${res.status})`; - dlog("login FAILED →", msg); setErr(msg); return; } - if (!data?.token) { - dlog("login FAILED → data.token not found"); - setErr("รูปแบบข้อมูลตอบกลับไม่ถูกต้อง (ไม่มี token)"); - return; - } - - // ✅ เก็บ token ตามโหมดจำไว้/ไม่จำ - const storage = remember ? window.localStorage : window.sessionStorage; - storage.setItem("dms.token", data.token); - storage.setItem("dms.refresh_token", data.refresh_token); - storage.setItem("dms.user", JSON.stringify(data.user || {})); - dlog("token stored in", remember ? "localStorage" : "sessionStorage"); - - // (ออปชัน) เผยแพร่ event ให้แท็บอื่นทราบ - try { - window.dispatchEvent( - new StorageEvent("storage", { key: "dms.auth", newValue: "login" }) - ); - } catch {} - - dlog("navigating →", nextPath); + // คุกกี้ (HttpOnly) ถูกตั้งด้วย Set-Cookie จาก backend แล้ว + dlog("login ok → redirect", nextPath); router.replace(nextPath); } catch (e) { dlog("exception =", e); setErr("เชื่อมต่อเซิร์ฟเวอร์ไม่ได้ กรุณาลองใหม่"); } finally { setSubmitting(false); - dlog("done"); } } @@ -133,32 +81,22 @@ function LoginForm() {
- - เข้าสู่ระบบ - - - Document Management System • LCBP3 - + เข้าสู่ระบบ + Document Management System • LCBP3 {err ? ( - - {err} - + {err} ) : null}
setUsername(e.target.value)} - placeholder="เช่น superadmin" - disabled={submitting} + id="username" autoFocus autoComplete="username" + value={username} onChange={(e) => setUsername(e.target.value)} + placeholder="เช่น superadmin" disabled={submitting} />
@@ -166,59 +104,22 @@ function LoginForm() {
setPassword(e.target.value)} - placeholder="••••••••" - disabled={submitting} - className="pr-10" + id="password" type={showPw ? "text" : "password"} autoComplete="current-password" + value={password} onChange={(e) => setPassword(e.target.value)} + placeholder="••••••••" disabled={submitting} className="pr-10" />
-
- - - - ลืมรหัสผ่าน? - -
- - {DEBUG ? ( @@ -245,18 +146,13 @@ export default function LoginPage() { ); } -/** Loading skeleton */ function LoginPageSkeleton() { return (
- - เข้าสู่ระบบ - - - Document Management System • LCBP3 - + เข้าสู่ระบบ + Document Management System • LCBP3
@@ -270,28 +166,11 @@ function LoginPageSkeleton() { ); } -/** Spinner แบบไม่พึ่งไลบรารีเสริม */ function Spinner() { return ( - -
- {/* Sidebar */} - +
+ + +
+ {/* System / Quick Actions */} +
+
Document Management System — LCBP3 Phase 3
+ + {can(user, "admin:view") && ( + Admin + )} + {can(user, "users:manage") && ( + ผู้ใช้/บทบาท + )} + {can(user, "health:view") && ( + Health + )} + {can(user, "workflow:view") && ( + Workflow + )} + {can(user, "rfa:create") && ( + + RFA + )} + {can(user, "drawing:upload") && ( + + Upload Drawing + )} + {can(user, "transmittal:create") && ( + + Transmittal + )} + {can(user, "correspondence:create") && ( + + หนังสือสื่อสาร + )} +
+ + {children} +
+
); }