feat: enhance security by implementing HttpOnly refresh tokens and in-memory access token management
- Added isomorphic-dompurify for improved XSS protection - Refactored token storage to use in-memory management for access tokens - Implemented server-side route handlers for OAuth token management - Introduced security headers in next.config.js - Removed client-side exposure of client_secret and sensitive tokens - Updated API interceptors to handle token refresh logic - Cleaned up cookie management for refresh tokens
This commit is contained in:
+4
-7
@@ -2,11 +2,8 @@ import { cookies } from "next/headers";
|
||||
|
||||
export async function getUser() {
|
||||
const cookieStore = await cookies();
|
||||
const raw =
|
||||
cookieStore.get("access_token") &&
|
||||
cookieStore.get("refresh_token") &&
|
||||
cookieStore.get("uuid") &&
|
||||
cookieStore.get("userInfo");
|
||||
if (!raw) return null;
|
||||
return raw;
|
||||
const isAuthenticated =
|
||||
cookieStore.get("refresh_token") && cookieStore.get("userInfo");
|
||||
if (!isAuthenticated) return null;
|
||||
return cookieStore.get("userInfo");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
const COOKIE_NAME = "refresh_token";
|
||||
|
||||
function cookieDomain(host) {
|
||||
if (!host) return undefined;
|
||||
const hostname = host.split(":")[0];
|
||||
if (hostname.includes("localhost") || /^\d+\.\d+\.\d+\.\d+$/.test(hostname)) {
|
||||
return undefined;
|
||||
}
|
||||
return "." + hostname.split(".").slice(-2).join(".");
|
||||
}
|
||||
|
||||
export function setRefreshCookie(response, refreshToken, host, maxAge = 60 * 60 * 24 * 30) {
|
||||
response.cookies.set(COOKIE_NAME, refreshToken, {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
maxAge,
|
||||
domain: cookieDomain(host),
|
||||
});
|
||||
}
|
||||
|
||||
export function clearRefreshCookie(response, host) {
|
||||
response.cookies.set(COOKIE_NAME, "", {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
maxAge: 0,
|
||||
domain: cookieDomain(host),
|
||||
});
|
||||
}
|
||||
|
||||
export { COOKIE_NAME };
|
||||
+10
-12
@@ -1,19 +1,17 @@
|
||||
const DANGEROUS_TAGS = ['script', 'iframe', 'object', 'embed', 'link', 'meta', 'base', 'form'];
|
||||
import DOMPurify from "isomorphic-dompurify";
|
||||
|
||||
export function sanitizeHtml(html) {
|
||||
if (!html || typeof html !== 'string') return '';
|
||||
if (!html || typeof html !== "string") return "";
|
||||
|
||||
let sanitized = html;
|
||||
|
||||
DANGEROUS_TAGS.forEach((tag) => {
|
||||
const openClose = new RegExp(`<${tag}[\\s\\S]*?(?:<\\/${tag}>|/?>)`, 'gi');
|
||||
sanitized = sanitized.replace(openClose, '');
|
||||
return DOMPurify.sanitize(html, {
|
||||
ALLOWED_TAGS: [
|
||||
"p", "br", "strong", "em", "b", "i", "u", "ul", "ol", "li", "a",
|
||||
"h2", "h3", "h4", "h5", "blockquote", "img", "span", "div",
|
||||
"table", "thead", "tbody", "tr", "td", "th",
|
||||
],
|
||||
ALLOWED_ATTR: ["href", "target", "rel", "src", "alt", "title"],
|
||||
ALLOW_DATA_ATTR: false,
|
||||
});
|
||||
|
||||
sanitized = sanitized.replace(/\s+on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]*)/gi, '');
|
||||
sanitized = sanitized.replace(/(?:javascript|vbscript):/gi, '');
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
export function safeJsonParse(str, fallback = null) {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { cookies } from "next/headers";
|
||||
import { axiosInstance } from "@/lib/req";
|
||||
|
||||
export async function getServerAccessToken() {
|
||||
const cookieStore = await cookies();
|
||||
const refreshToken = cookieStore.get("refresh_token")?.value;
|
||||
if (!refreshToken) return null;
|
||||
|
||||
try {
|
||||
const res = await axiosInstance.post(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/oauth/token/refresh`,
|
||||
{ refresh_token: refreshToken },
|
||||
{ headers: { "Content-Type": "application/json", Authorization: "" } }
|
||||
);
|
||||
return res.data?.access_token ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
let accessToken = null;
|
||||
|
||||
export function getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
export function setAccessToken(token) {
|
||||
accessToken = token || null;
|
||||
}
|
||||
|
||||
export function clearAccessToken() {
|
||||
accessToken = null;
|
||||
}
|
||||
Reference in New Issue
Block a user