Dashboard
++ ยินดีต้อนรับสู่ DMS +
+diff --git a/backend/src/middleware/requireBearer.js b/backend/src/middleware/requireBearer.js new file mode 100644 index 0000000..1111111 --- /dev/null +++ b/backend/src/middleware/requireBearer.js @@ -0,0 +1,44 @@ +// backend/src/middleware/requireBearer.js +import jwt from "jsonwebtoken"; +import { findUserById } from "../db/models/users.js"; + +export async function requireBearer(req, res, next) { + const hdr = req.get("Authorization") || ""; + const m = hdr.match(/^Bearer\s+(.+)$/i); + if (!m) return res.status(401).json({ error: "Unauthenticated" }); + try { + const payload = jwt.verify(m[1], process.env.JWT_ACCESS_SECRET, { + issuer: "dms-backend", + }); + const user = await findUserById(payload.user_id); + if (!user) return res.status(401).json({ error: "Unauthenticated" }); + req.user = { + user_id: user.user_id, + username: user.username, + email: user.email, + first_name: user.first_name, + last_name: user.last_name, + }; + next(); + } catch { + return res.status(401).json({ error: "Unauthenticated" }); + } +} diff --git a/backend/src/routes/auth.js b/backend/src/routes/auth.js index 2222222..3333333 100644 --- a/backend/src/routes/auth.js +++ b/backend/src/routes/auth.js @@ -1,99 +1,109 @@ -// (เดิม) ผูกกับคุกกี้ / ส่ง ok:true ฯลฯ +// backend/src/routes/auth.js — Bearer Token ล้วน import { Router } from "express"; import jwt from "jsonwebtoken"; -import { findUserByUsername } from "../db/models/users.js"; +import { findUserByUsername, findUserById } from "../db/models/users.js"; import { verifyPassword } from "../utils/passwords.js"; -// NOTE: ลบการใช้งาน res.cookie(...) ทั้งหมด +// NOTE: ไม่มีการใช้ res.cookie(...) อีกต่อไป const router = Router(); function signAccessToken(user) { return jwt.sign( { user_id: user.user_id, username: user.username }, process.env.JWT_ACCESS_SECRET, - { issuer: "dms-backend", expiresIn: "30m" } // ปรับได้ + { issuer: "dms-backend", expiresIn: "30m" } ); } function signRefreshToken(user) { return jwt.sign( - { user_id: user.user_id, username: user.username }, + { user_id: user.user_id, username: user.username, t: "refresh" }, process.env.JWT_REFRESH_SECRET, { issuer: "dms-backend", expiresIn: "30d" } ); } router.post("/login", async (req, res) => { const { username, password } = req.body || {}; const user = await findUserByUsername(username); if (!user || !(await verifyPassword(password, user.password_hash))) { return res.status(401).json({ error: "INVALID_CREDENTIALS" }); } const token = signAccessToken(user); const refresh_token = signRefreshToken(user); return res.json({ token, refresh_token, user: { user_id: user.user_id, username: user.username, email: user.email, first_name: user.first_name, last_name: user.last_name, }, }); }); +router.post("/refresh", async (req, res) => { + const hdr = req.get("Authorization") || ""; + const m = hdr.match(/^Bearer\s+(.+)$/i); + const r = m?.[1]; + if (!r) return res.status(401).json({ error: "NO_REFRESH_TOKEN" }); + try { + const payload = jwt.verify(r, process.env.JWT_REFRESH_SECRET, { + issuer: "dms-backend", + }); + const user = await findUserById(payload.user_id); + if (!user) return res.status(401).json({ error: "USER_NOT_FOUND" }); + const token = signAccessToken(user); + return res.json({ token }); + } catch { + return res.status(401).json({ error: "INVALID_REFRESH_TOKEN" }); + } +}); + export default router; diff --git a/backend/src/index.js b/backend/src/index.js index 4444444..5555555 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -1,60 +1,69 @@ import express from "express"; import cors from "cors"; import authRouter from "./routes/auth.js"; +import { requireBearer } from "./middleware/requireBearer.js"; -// import routers อื่น ๆ ตามจริง เช่น rfasRouter, transmittalsRouter const app = express(); -// CORS เดิม (อาจมี credentials) -app.use(cors({ - origin: true, - credentials: true, -})); +// ✅ CORS สำหรับ Bearer: ไม่ต้อง credentials, อนุญาต Authorization header +app.use(cors({ + origin: [ + "https://lcbp3.np-dms.work", + "http://localhost:3000" + ], + methods: ["GET","POST","PUT","PATCH","DELETE","OPTIONS"], + allowedHeaders: ["Authorization","Content-Type","Accept","Origin","Referer","User-Agent","X-Requested-With","Cache-Control","Pragma"], + exposedHeaders: ["Content-Disposition","Content-Length"] +})); app.use(express.json()); -// routes เดิม -app.use("/api/auth", authRouter); -// app.use("/api/rfas", rfasRouter); -// app.use("/api/transmittals", transmittalsRouter); +// ✅ เส้นทาง auth (ไม่ต้องมี token) +app.use("/api/auth", authRouter); + +// ✅ ตั้ง guard สำหรับเส้นทางที่เหลือต้องล็อกอิน +app.use("/api", requireBearer); +// แล้วค่อย mount routers protected ใต้ /api +// app.use("/api/rfas", rfasRouter); +// app.use("/api/transmittals", transmittalsRouter); app.use((err, _req, res, _next) => { console.error(err); res.status(500).json({ error: "INTERNAL_SERVER_ERROR" }); }); const port = process.env.PORT || 4000; app.listen(port, () => console.log(`backend listening on :${port}`)); diff --git a/frontend/app/(auth)/login/page.jsx b/frontend/app/(auth)/login/page.jsx index 6666666..7777777 100644 --- a/frontend/app/(auth)/login/page.jsx +++ b/frontend/app/(auth)/login/page.jsx @@ -1,200 +1,236 @@ // File: frontend/app/(auth)/login/page.jsx "use client"; -// เวอร์ชันเดิม +// ✅ Bearer-only + Debug toggle (NEXT_PUBLIC_DEBUG_AUTH) import { useState, useMemo, Suspense } from "react"; import { useSearchParams, useRouter } from "next/navigation"; import { 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); +} function LoginForm() { const router = useRouter(); const searchParams = useSearchParams(); 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(""); async function onSubmit(e) { e.preventDefault(); setErr(""); if (!username.trim() || !password) { setErr("กรอกชื่อผู้ใช้และรหัสผ่านให้ครบ"); return; } try { setSubmitting(true); + dlog("API_BASE =", API_BASE || "(empty → relative)"); + dlog("nextPath =", nextPath, "remember =", remember); const res = await fetch(`${API_BASE}/api/auth/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password }), cache: "no-store", }); - const data = await res.json().catch(() => ({})); + dlog("response.status =", res.status); + dlog("response.headers.content-type =", res.headers.get("content-type")); + let data = {}; + try { data = await res.json(); } catch (e) { dlog("response.json() error =", e); } + dlog("response.body =", data); if (!res.ok) { - setErr(data?.error || "เข้าสู่ระบบไม่สำเร็จ"); + 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; + } 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"); try { window.dispatchEvent( new StorageEvent("storage", { key: "dms.auth", newValue: "login" }) ); } catch {} - router.replace(nextPath); + dlog("navigating →", nextPath); + router.replace(nextPath); } catch (e) { + dlog("exception =", e); setErr("เชื่อมต่อเซิร์ฟเวอร์ไม่ได้ กรุณาลองใหม่"); } finally { setSubmitting(false); + dlog("done"); } } return (
+ ยินดีต้อนรับสู่ DMS +
+