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

update code

parent 47a23d19
......@@ -6,6 +6,7 @@ import {
verifyCsrfToken,
} from "@/lib/csrf";
import { apiRequest } from "@/lib/api";
import { getPhoneCookieConfig } from "@/lib/session";
import { buildUnauthorizedResponse, hasVerifiedSession } from "@/lib/session-guard";
function buildCsrfResponse(message: string, status: number) {
......@@ -28,23 +29,45 @@ export async function POST(request: NextRequest) {
const csrfCookie = request.cookies.get(getCsrfCookieConfig().name)?.value;
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) {
return buildCsrfResponse("CSRF token không hợp lệ.", 403);
}
try {
const phoneForLog = request.cookies.get(getPhoneCookieConfig().name)?.value || "";
const { ok, payload, status } = await apiRequest({
url: "/posts", //point/api/otp/generate
url: process.env.NEXT_PUBLIC_BE_API_OTP_GENERATE ?? "",
method: "POST",
body: JSON.stringify({
access_token: accessToken,
ma_khang: maKH,
phone: phoneForLog
}),
phoneForLog,
});
const refreshedCsrfToken = createCsrfToken();
const nextPayload =
payload && typeof payload === "object"
? { ...payload, 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, {
status: ok ? status : status || 502,
status: isBusinessSuccess ? status : status >= 400 ? status : 400,
});
const cookie = getCsrfCookieConfig();
......
......@@ -6,6 +6,7 @@ import {
verifyCsrfToken,
} from "@/lib/csrf";
import { apiRequest } from "@/lib/api";
import { getPhoneCookieConfig } from "@/lib/session";
import { buildUnauthorizedResponse, hasVerifiedSession } from "@/lib/session-guard";
function buildCsrfResponse(message: string, status: number) {
......@@ -46,10 +47,26 @@ export async function POST(request: NextRequest) {
}
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({
url: "/epoint/api/otp/verify",
url: process.env.NEXT_PUBLIC_BE_API_OTP_VERIFY ?? "",
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 nextPayload =
......
......@@ -59,7 +59,7 @@ async function generateOtp(csrfToken: string) {
if (!response.ok) {
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 & {
csrfToken?: string;
};
......@@ -86,7 +86,7 @@ async function verifyOtp(otp: string, csrfToken: string) {
if (!response.ok) {
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 & {
csrfToken?: string;
};
......@@ -130,7 +130,7 @@ export default function PageClient({
const generateOtpMutation = useMutation({
mutationFn: () => {
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);
......@@ -159,7 +159,7 @@ export default function PageClient({
const verifyOtpMutation = useMutation({
mutationFn: () => {
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);
......@@ -205,7 +205,7 @@ export default function PageClient({
const handleOtpSubmit = () => {
if (otp.length !== OTP_LENGTH) {
setError("Vui long nhap du 6 so OTP.");
setError("Vui lòng nhp đủ 6 s OTP.");
return;
}
......@@ -304,7 +304,7 @@ export default function PageClient({
<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="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">
{initialVerifyError}
</p>
......@@ -313,7 +313,7 @@ export default function PageClient({
onClick={() => setVerifyErrorPopupOpen(false)}
className="w-full rounded-2xl bg-brand-blue px-4 py-3 text-base font-bold text-white"
>
Dong
Đóng
</button>
</div>
</div>
......
......@@ -13,6 +13,7 @@ type ApiRequestOptions = {
headers?: HeadersInit;
body?: BodyInit | null;
baseUrl?: string;
phoneForLog?: string;
};
type ApiRequestResult<T> = {
......@@ -37,9 +38,10 @@ export async function apiRequest<T extends Record<string, unknown> | ApiError>({
headers,
body = null,
baseUrl,
phoneForLog = "",
}: ApiRequestOptions): Promise<ApiRequestResult<T>> {
try {
const apiSPC = baseUrl || process.env.NEXT_PUBLIC_SPC_API;
const apiSPC = baseUrl || process.env.NEXT_PUBLIC_BE_API;
if (!apiSPC) {
throw new Error("Thiếu cấu hình domain.");
......@@ -59,8 +61,9 @@ export async function apiRequest<T extends Record<string, unknown> | ApiError>({
const vnTime = new Date().toLocaleString("vi-VN", {
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 {
ok: response.ok,
......@@ -71,8 +74,9 @@ export async function apiRequest<T extends Record<string, unknown> | ApiError>({
const vnTime = new Date().toLocaleString("vi-VN", {
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) {
throw error;
......
const SESSION_COOKIE_NAME = "epoint_session";
const PHONE_COOKIE_NAME = "epoint_phone";
const SESSION_TTL_MS = 24 * 60 * 60 * 1000;
type SessionPayload = {
......@@ -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(
session: Awaited<ReturnType<typeof verifySessionToken>> | null,
) {
......
import { NextRequest, NextResponse } from "next/server";
import { verifyTokenForWebView } from "@/lib/api";
import { apiRequest, VerifyTokenForWebViewResponse } from "@/lib/api";
import {
createSessionToken,
getPhoneCookieConfig,
getSessionCookieConfig,
isSuccessfulSession,
verifySessionToken,
......@@ -26,6 +27,32 @@ function encodeHeaderValue(value: string) {
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) {
if (request.nextUrl.pathname !== "/") {
return NextResponse.next();
......@@ -33,11 +60,12 @@ export async function middleware(request: NextRequest) {
const requestHeaders = new Headers(request.headers);
const sessionCookieConfig = getSessionCookieConfig();
const phoneCookieConfig = getPhoneCookieConfig();
const phoneFromCookie = request.cookies.get(phoneCookieConfig.name)?.value || "";
//check session to call api
const sessionCookie = request.cookies.get(getSessionCookieConfig().name)?.value;
const session = await verifySessionToken(sessionCookie);
const buildNextResponse = () =>
NextResponse.next({
request: {
......@@ -65,14 +93,26 @@ export async function middleware(request: NextRequest) {
);
const response = buildNextResponse();
response.cookies.delete(sessionCookieConfig.name);
response.cookies.delete(phoneCookieConfig.name);
return response;
}
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") {
const phone = extractPhoneFromPayload(payload as Record<string, unknown>);
requestHeaders.set("x-session-valid", "1");
const response = buildNextResponse();
response.cookies.set(
......@@ -83,6 +123,11 @@ export async function middleware(request: NextRequest) {
}),
sessionCookieConfig.options,
);
if (phone) {
response.cookies.set(phoneCookieConfig.name, phone, phoneCookieConfig.options);
} else {
response.cookies.delete(phoneCookieConfig.name);
}
return response;
}
......@@ -102,6 +147,7 @@ export async function middleware(request: NextRequest) {
}),
sessionCookieConfig.options,
);
response.cookies.delete(phoneCookieConfig.name);
return response;
} catch (error) {
......@@ -121,6 +167,7 @@ export async function middleware(request: NextRequest) {
}),
sessionCookieConfig.options,
);
response.cookies.delete(phoneCookieConfig.name);
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