Commit 5be553be authored by DuyNL's avatar DuyNL
Browse files

update code

parent 47a23d19
...@@ -6,6 +6,7 @@ import { ...@@ -6,6 +6,7 @@ import {
verifyCsrfToken, verifyCsrfToken,
} from "@/lib/csrf"; } from "@/lib/csrf";
import { apiRequest } from "@/lib/api"; import { apiRequest } from "@/lib/api";
import { getPhoneCookieConfig } from "@/lib/session";
import { buildUnauthorizedResponse, hasVerifiedSession } from "@/lib/session-guard"; import { buildUnauthorizedResponse, hasVerifiedSession } from "@/lib/session-guard";
function buildCsrfResponse(message: string, status: number) { function buildCsrfResponse(message: string, status: number) {
...@@ -28,23 +29,45 @@ export async function POST(request: NextRequest) { ...@@ -28,23 +29,45 @@ export async function POST(request: NextRequest) {
const csrfCookie = request.cookies.get(getCsrfCookieConfig().name)?.value; const csrfCookie = request.cookies.get(getCsrfCookieConfig().name)?.value;
const csrfHeader = request.headers.get(csrfHeaderName); const csrfHeader = request.headers.get(csrfHeaderName);
const accessToken =
request.headers.get("access_token") ||
request.headers.get("x-access-token") ||
request.headers.get("access-token") ||
"";
const maKH =
request.headers.get("ma_khang") ||
request.headers.get("Ma_khang") ||
"";
if (!verifyCsrfToken(csrfCookie) || !verifyCsrfToken(csrfHeader) || csrfCookie !== csrfHeader) { if (!verifyCsrfToken(csrfCookie) || !verifyCsrfToken(csrfHeader) || csrfCookie !== csrfHeader) {
return buildCsrfResponse("CSRF token không hợp lệ.", 403); return buildCsrfResponse("CSRF token không hợp lệ.", 403);
} }
try { try {
const phoneForLog = request.cookies.get(getPhoneCookieConfig().name)?.value || "";
const { ok, payload, status } = await apiRequest({ const { ok, payload, status } = await apiRequest({
url: "/posts", //point/api/otp/generate url: process.env.NEXT_PUBLIC_BE_API_OTP_GENERATE ?? "",
method: "POST", method: "POST",
body: JSON.stringify({
access_token: accessToken,
ma_khang: maKH,
phone: phoneForLog
}),
phoneForLog,
}); });
const refreshedCsrfToken = createCsrfToken(); const refreshedCsrfToken = createCsrfToken();
const nextPayload = const nextPayload =
payload && typeof payload === "object" payload && typeof payload === "object"
? { ...payload, csrfToken: refreshedCsrfToken } ? { ...payload, csrfToken: refreshedCsrfToken }
: { data: payload ?? null, csrfToken: refreshedCsrfToken }; : { data: payload ?? null, csrfToken: refreshedCsrfToken };
const isBusinessSuccess =
ok &&
typeof nextPayload === "object" &&
nextPayload !== null &&
"status" in nextPayload &&
nextPayload.status === "success";
const nextResponse = NextResponse.json(nextPayload, { const nextResponse = NextResponse.json(nextPayload, {
status: ok ? status : status || 502, status: isBusinessSuccess ? status : status >= 400 ? status : 400,
}); });
const cookie = getCsrfCookieConfig(); const cookie = getCsrfCookieConfig();
......
...@@ -6,6 +6,7 @@ import { ...@@ -6,6 +6,7 @@ import {
verifyCsrfToken, verifyCsrfToken,
} from "@/lib/csrf"; } from "@/lib/csrf";
import { apiRequest } from "@/lib/api"; import { apiRequest } from "@/lib/api";
import { getPhoneCookieConfig } from "@/lib/session";
import { buildUnauthorizedResponse, hasVerifiedSession } from "@/lib/session-guard"; import { buildUnauthorizedResponse, hasVerifiedSession } from "@/lib/session-guard";
function buildCsrfResponse(message: string, status: number) { function buildCsrfResponse(message: string, status: number) {
...@@ -46,10 +47,26 @@ export async function POST(request: NextRequest) { ...@@ -46,10 +47,26 @@ export async function POST(request: NextRequest) {
} }
try { try {
const accessToken =
request.headers.get("access_token") ||
request.headers.get("x-access-token") ||
request.headers.get("access-token") ||
"";
const maKH =
request.headers.get("ma_khang") ||
request.headers.get("Ma_khang") ||
"";
const phoneForLog = request.cookies.get(getPhoneCookieConfig().name)?.value || "";
const { ok, payload, status } = await apiRequest({ const { ok, payload, status } = await apiRequest({
url: "/epoint/api/otp/verify", url: process.env.NEXT_PUBLIC_BE_API_OTP_VERIFY ?? "",
method: "POST", method: "POST",
body: JSON.stringify({ otp: body.otp }), body: JSON.stringify({
access_token: accessToken,
ma_khang: maKH,
phone: phoneForLog,
id_epoint: 123,
otp: body.otp }),
phoneForLog,
}); });
const refreshedCsrfToken = createCsrfToken(); const refreshedCsrfToken = createCsrfToken();
const nextPayload = const nextPayload =
......
...@@ -59,7 +59,7 @@ async function generateOtp(csrfToken: string) { ...@@ -59,7 +59,7 @@ async function generateOtp(csrfToken: string) {
if (!response.ok) { if (!response.ok) {
const error = new Error( const error = new Error(
payload.message || "Khong the gui OTP. Vui long thu lai.", payload.message || "Không th gi OTP. Vui lòng th li.",
) as Error & { ) as Error & {
csrfToken?: string; csrfToken?: string;
}; };
...@@ -86,7 +86,7 @@ async function verifyOtp(otp: string, csrfToken: string) { ...@@ -86,7 +86,7 @@ async function verifyOtp(otp: string, csrfToken: string) {
if (!response.ok) { if (!response.ok) {
const error = new Error( const error = new Error(
payload.message || "Xac thuc OTP that bai. Vui long thu lai.", payload.message || "Xác thc OTP tht bi. Vui lòng th li.",
) as Error & { ) as Error & {
csrfToken?: string; csrfToken?: string;
}; };
...@@ -130,7 +130,7 @@ export default function PageClient({ ...@@ -130,7 +130,7 @@ export default function PageClient({
const generateOtpMutation = useMutation({ const generateOtpMutation = useMutation({
mutationFn: () => { mutationFn: () => {
if (!csrfToken) { if (!csrfToken) {
throw new Error("CSRF token chua san sang. Vui long thu lai."); throw new Error("CSRF token chưa sn sàng. Vui lòng th li.");
} }
return generateOtp(csrfToken); return generateOtp(csrfToken);
...@@ -159,7 +159,7 @@ export default function PageClient({ ...@@ -159,7 +159,7 @@ export default function PageClient({
const verifyOtpMutation = useMutation({ const verifyOtpMutation = useMutation({
mutationFn: () => { mutationFn: () => {
if (!csrfToken) { if (!csrfToken) {
throw new Error("CSRF token chua san sang. Vui long thu lai."); throw new Error("CSRF token chưa sn sàng. Vui lòng th li.");
} }
return verifyOtp(otp, csrfToken); return verifyOtp(otp, csrfToken);
...@@ -205,7 +205,7 @@ export default function PageClient({ ...@@ -205,7 +205,7 @@ export default function PageClient({
const handleOtpSubmit = () => { const handleOtpSubmit = () => {
if (otp.length !== OTP_LENGTH) { if (otp.length !== OTP_LENGTH) {
setError("Vui long nhap du 6 so OTP."); setError("Vui lòng nhp đủ 6 s OTP.");
return; return;
} }
...@@ -304,7 +304,7 @@ export default function PageClient({ ...@@ -304,7 +304,7 @@ export default function PageClient({
<div className="absolute inset-0 bg-slate-900/35" /> <div className="absolute inset-0 bg-slate-900/35" />
<div className="relative z-10 w-full max-w-[340px] rounded-[24px] bg-white px-5 py-6 shadow-[0_20px_50px_rgba(15,23,42,0.2)]"> <div className="relative z-10 w-full max-w-[340px] rounded-[24px] bg-white px-5 py-6 shadow-[0_20px_50px_rgba(15,23,42,0.2)]">
<div className="space-y-4 text-center"> <div className="space-y-4 text-center">
<h2 className="text-2xl font-bold text-slate-900">Thong bao</h2> <h2 className="text-2xl font-bold text-slate-900">Thông báo</h2>
<p className="text-[15px] leading-6 text-slate-600"> <p className="text-[15px] leading-6 text-slate-600">
{initialVerifyError} {initialVerifyError}
</p> </p>
...@@ -313,7 +313,7 @@ export default function PageClient({ ...@@ -313,7 +313,7 @@ export default function PageClient({
onClick={() => setVerifyErrorPopupOpen(false)} onClick={() => setVerifyErrorPopupOpen(false)}
className="w-full rounded-2xl bg-brand-blue px-4 py-3 text-base font-bold text-white" className="w-full rounded-2xl bg-brand-blue px-4 py-3 text-base font-bold text-white"
> >
Dong Đóng
</button> </button>
</div> </div>
</div> </div>
......
...@@ -13,6 +13,7 @@ type ApiRequestOptions = { ...@@ -13,6 +13,7 @@ type ApiRequestOptions = {
headers?: HeadersInit; headers?: HeadersInit;
body?: BodyInit | null; body?: BodyInit | null;
baseUrl?: string; baseUrl?: string;
phoneForLog?: string;
}; };
type ApiRequestResult<T> = { type ApiRequestResult<T> = {
...@@ -37,9 +38,10 @@ export async function apiRequest<T extends Record<string, unknown> | ApiError>({ ...@@ -37,9 +38,10 @@ export async function apiRequest<T extends Record<string, unknown> | ApiError>({
headers, headers,
body = null, body = null,
baseUrl, baseUrl,
phoneForLog = "",
}: ApiRequestOptions): Promise<ApiRequestResult<T>> { }: ApiRequestOptions): Promise<ApiRequestResult<T>> {
try { try {
const apiSPC = baseUrl || process.env.NEXT_PUBLIC_SPC_API; const apiSPC = baseUrl || process.env.NEXT_PUBLIC_BE_API;
if (!apiSPC) { if (!apiSPC) {
throw new Error("Thiếu cấu hình domain."); throw new Error("Thiếu cấu hình domain.");
...@@ -59,8 +61,9 @@ export async function apiRequest<T extends Record<string, unknown> | ApiError>({ ...@@ -59,8 +61,9 @@ export async function apiRequest<T extends Record<string, unknown> | ApiError>({
const vnTime = new Date().toLocaleString("vi-VN", { const vnTime = new Date().toLocaleString("vi-VN", {
timeZone: "Asia/Ho_Chi_Minh", timeZone: "Asia/Ho_Chi_Minh",
}); });
const phoneLog = phoneForLog ? ` phone=${phoneForLog}` : "";
console.log(`${vnTime}: [apiRequest] ${method} ${url}:`, payload); console.log(`${vnTime}: [apiRequest] ${method} ${url}${phoneLog}:`, payload);
return { return {
ok: response.ok, ok: response.ok,
...@@ -71,8 +74,9 @@ export async function apiRequest<T extends Record<string, unknown> | ApiError>({ ...@@ -71,8 +74,9 @@ export async function apiRequest<T extends Record<string, unknown> | ApiError>({
const vnTime = new Date().toLocaleString("vi-VN", { const vnTime = new Date().toLocaleString("vi-VN", {
timeZone: "Asia/Ho_Chi_Minh", timeZone: "Asia/Ho_Chi_Minh",
}); });
const phoneLog = phoneForLog ? ` phone=${phoneForLog}` : "";
console.error(`${vnTime}: [apiRequest] ${method} ${url} failed: `, error); console.error(`${vnTime}: [apiRequest] ${method} ${url}${phoneLog} failed: `, error);
if (error instanceof Error) { if (error instanceof Error) {
throw error; throw error;
......
const SESSION_COOKIE_NAME = "epoint_session"; const SESSION_COOKIE_NAME = "epoint_session";
const PHONE_COOKIE_NAME = "epoint_phone";
const SESSION_TTL_MS = 24 * 60 * 60 * 1000; const SESSION_TTL_MS = 24 * 60 * 60 * 1000;
type SessionPayload = { type SessionPayload = {
...@@ -125,6 +126,18 @@ export function getSessionCookieConfig() { ...@@ -125,6 +126,18 @@ export function getSessionCookieConfig() {
}; };
} }
export function getPhoneCookieConfig() {
return {
name: PHONE_COOKIE_NAME,
options: {
httpOnly: true,
sameSite: "lax" as const,
secure: process.env.NODE_ENV === "production",
path: "/",
},
};
}
export function isSuccessfulSession( export function isSuccessfulSession(
session: Awaited<ReturnType<typeof verifySessionToken>> | null, session: Awaited<ReturnType<typeof verifySessionToken>> | null,
) { ) {
......
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
import { verifyTokenForWebView } from "@/lib/api"; import { apiRequest, VerifyTokenForWebViewResponse } from "@/lib/api";
import { import {
createSessionToken, createSessionToken,
getPhoneCookieConfig,
getSessionCookieConfig, getSessionCookieConfig,
isSuccessfulSession, isSuccessfulSession,
verifySessionToken, verifySessionToken,
...@@ -26,6 +27,32 @@ function encodeHeaderValue(value: string) { ...@@ -26,6 +27,32 @@ function encodeHeaderValue(value: string) {
return encodeURIComponent(value); return encodeURIComponent(value);
} }
function extractPhoneFromPayload(payload: Record<string, unknown>): string {
const candidateKeys = [
"phone",
"customer_phone",
"phone_number",
"mobile",
"msisdn",
];
for (const key of candidateKeys) {
const value = payload[key];
if (typeof value === "string" && value.trim()) {
return value.trim();
}
}
const nestedData = payload.data;
if (nestedData && typeof nestedData === "object" && !Array.isArray(nestedData)) {
return extractPhoneFromPayload(nestedData as Record<string, unknown>);
}
return "";
}
export async function middleware(request: NextRequest) { export async function middleware(request: NextRequest) {
if (request.nextUrl.pathname !== "/") { if (request.nextUrl.pathname !== "/") {
return NextResponse.next(); return NextResponse.next();
...@@ -33,11 +60,12 @@ export async function middleware(request: NextRequest) { ...@@ -33,11 +60,12 @@ export async function middleware(request: NextRequest) {
const requestHeaders = new Headers(request.headers); const requestHeaders = new Headers(request.headers);
const sessionCookieConfig = getSessionCookieConfig(); const sessionCookieConfig = getSessionCookieConfig();
const phoneCookieConfig = getPhoneCookieConfig();
const phoneFromCookie = request.cookies.get(phoneCookieConfig.name)?.value || "";
//check session to call api //check session to call api
const sessionCookie = request.cookies.get(getSessionCookieConfig().name)?.value; const sessionCookie = request.cookies.get(getSessionCookieConfig().name)?.value;
const session = await verifySessionToken(sessionCookie); const session = await verifySessionToken(sessionCookie);
const buildNextResponse = () => const buildNextResponse = () =>
NextResponse.next({ NextResponse.next({
request: { request: {
...@@ -65,14 +93,26 @@ export async function middleware(request: NextRequest) { ...@@ -65,14 +93,26 @@ export async function middleware(request: NextRequest) {
); );
const response = buildNextResponse(); const response = buildNextResponse();
response.cookies.delete(sessionCookieConfig.name); response.cookies.delete(sessionCookieConfig.name);
response.cookies.delete(phoneCookieConfig.name);
return response; return response;
} }
try { try {
const { ok, payload } = await verifyTokenForWebView(accessToken); const { ok, payload } = await apiRequest<VerifyTokenForWebViewResponse>({
baseUrl: process.env.NEXT_PUBLIC_BE_API,
url:
process.env.NEXT_PUBLIC_BE_API_VERIFY ??
"/20984/gup2start/rest/accountVerifyTokenForWebView/1.0.0",
method: "POST",
body: JSON.stringify({
access_token: accessToken,
}),
phoneForLog: phoneFromCookie,
});
if (ok && payload.status === "success") { if (ok && payload.status === "success") {
const phone = extractPhoneFromPayload(payload as Record<string, unknown>);
requestHeaders.set("x-session-valid", "1"); requestHeaders.set("x-session-valid", "1");
const response = buildNextResponse(); const response = buildNextResponse();
response.cookies.set( response.cookies.set(
...@@ -83,6 +123,11 @@ export async function middleware(request: NextRequest) { ...@@ -83,6 +123,11 @@ export async function middleware(request: NextRequest) {
}), }),
sessionCookieConfig.options, sessionCookieConfig.options,
); );
if (phone) {
response.cookies.set(phoneCookieConfig.name, phone, phoneCookieConfig.options);
} else {
response.cookies.delete(phoneCookieConfig.name);
}
return response; return response;
} }
...@@ -102,6 +147,7 @@ export async function middleware(request: NextRequest) { ...@@ -102,6 +147,7 @@ export async function middleware(request: NextRequest) {
}), }),
sessionCookieConfig.options, sessionCookieConfig.options,
); );
response.cookies.delete(phoneCookieConfig.name);
return response; return response;
} catch (error) { } catch (error) {
...@@ -121,6 +167,7 @@ export async function middleware(request: NextRequest) { ...@@ -121,6 +167,7 @@ export async function middleware(request: NextRequest) {
}), }),
sessionCookieConfig.options, sessionCookieConfig.options,
); );
response.cookies.delete(phoneCookieConfig.name);
return response; return response;
} }
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment