690401:1326 fix secutities uuid
This commit is contained in:
@@ -8,15 +8,16 @@ import { Can } from '@/components/common/can';
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
interface CorrespondencesPageProps {
|
||||
searchParams?: {
|
||||
searchParams: Promise<{
|
||||
type?: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
export default function CorrespondencesPage({
|
||||
export default async function CorrespondencesPage({
|
||||
searchParams,
|
||||
}: CorrespondencesPageProps) {
|
||||
const isRfaView = searchParams?.type?.toUpperCase() === 'RFA';
|
||||
const params = await searchParams;
|
||||
const isRfaView = params?.type?.toUpperCase() === 'RFA';
|
||||
const heading = isRfaView ? 'RFAs (Request for Approval)' : 'Correspondences';
|
||||
const description = isRfaView
|
||||
? 'Unified list view for RFA documents'
|
||||
|
||||
@@ -7,6 +7,7 @@ import QueryProvider from '@/providers/query-provider';
|
||||
import SessionProvider from '@/providers/session-provider'; // ✅ Import เข้ามา
|
||||
import ThemeProvider from '@/providers/theme-provider';
|
||||
import { Toaster } from '@/components/ui/sonner';
|
||||
import { headers } from 'next/headers';
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
|
||||
@@ -19,13 +20,15 @@ interface RootLayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function RootLayout({ children }: RootLayoutProps) {
|
||||
export default async function RootLayout({ children }: RootLayoutProps) {
|
||||
const nonce = (await headers()).get('x-nonce') || '';
|
||||
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<head />
|
||||
<body className={cn('min-h-screen bg-background font-sans antialiased', inter.className)}>
|
||||
<SessionProvider>
|
||||
<ThemeProvider>
|
||||
<SessionProvider nonce={nonce}>
|
||||
<ThemeProvider nonce={nonce}>
|
||||
<QueryProvider>
|
||||
{children}
|
||||
<Toaster />
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
'use client';
|
||||
|
||||
import { useAuthStore } from '@/lib/stores/auth-store';
|
||||
import { ReactNode } from 'react';
|
||||
import { ReactNode, useEffect, useState } from 'react';
|
||||
|
||||
interface CanProps {
|
||||
permission?: string;
|
||||
@@ -18,6 +18,15 @@ interface CanProps {
|
||||
|
||||
export function Can({ permission, role, children, fallback = null }: CanProps) {
|
||||
const { hasPermission, hasRole } = useAuthStore();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
if (!mounted) {
|
||||
return <>{fallback}</>;
|
||||
}
|
||||
|
||||
let allowed = true;
|
||||
|
||||
|
||||
@@ -162,13 +162,13 @@ export const {
|
||||
}
|
||||
|
||||
return {
|
||||
id: backendData.user.user_id.toString(),
|
||||
publicId: backendData.user.publicId, // ✅ Added (ADR-019 Waived for session)
|
||||
id: backendData.user.publicId, // ✅ Use publicId for session identity (ADR-019)
|
||||
publicId: backendData.user.publicId,
|
||||
name: `${backendData.user.firstName ?? ''} ${backendData.user.lastName ?? ''}`.trim(),
|
||||
email: backendData.user.email,
|
||||
username: backendData.user.username,
|
||||
firstName: backendData.user.firstName, // ✅ Added
|
||||
lastName: backendData.user.lastName, // ✅ Added
|
||||
firstName: backendData.user.firstName,
|
||||
lastName: backendData.user.lastName,
|
||||
role: backendData.user.role || 'User',
|
||||
organizationId: backendData.user.primaryOrganizationId,
|
||||
accessToken: backendData.access_token,
|
||||
|
||||
@@ -3,8 +3,8 @@ import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
export interface User {
|
||||
id: string; // Internal stringified INT (for stability)
|
||||
publicId?: string; // ADR-019: Public UUIDv7
|
||||
id: string; // publicId (ADR-019)
|
||||
publicId: string;
|
||||
username: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
|
||||
@@ -78,18 +78,6 @@ const nextConfig = {
|
||||
key: 'X-Content-Type-Options',
|
||||
value: 'nosniff',
|
||||
},
|
||||
{
|
||||
key: 'Content-Security-Policy',
|
||||
value: [
|
||||
"default-src 'self'",
|
||||
"script-src 'self' 'unsafe-eval'", // จำเป็นสำหรับ Workflow DSL Engine (new Function())
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"img-src 'self' data: https:",
|
||||
"font-src 'self'",
|
||||
"connect-src 'self' ws: wss:",
|
||||
"frame-src 'self'",
|
||||
].join('; '),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -4,9 +4,15 @@
|
||||
import { SessionProvider as NextAuthSessionProvider } from 'next-auth/react';
|
||||
import { AuthSync } from '@/components/auth/auth-sync';
|
||||
|
||||
export default function SessionProvider({ children }: { children: React.ReactNode }) {
|
||||
export default function SessionProvider({
|
||||
children,
|
||||
nonce,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
nonce?: string;
|
||||
}) {
|
||||
return (
|
||||
<NextAuthSessionProvider>
|
||||
<NextAuthSessionProvider nonce={nonce}>
|
||||
<AuthSync />
|
||||
{children}
|
||||
</NextAuthSessionProvider>
|
||||
|
||||
@@ -4,8 +4,10 @@ import { ThemeProvider as NextThemesProvider } from 'next-themes';
|
||||
|
||||
export default function ThemeProvider({
|
||||
children,
|
||||
nonce,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
nonce?: string;
|
||||
}) {
|
||||
return (
|
||||
<NextThemesProvider
|
||||
@@ -13,6 +15,7 @@ export default function ThemeProvider({
|
||||
defaultTheme="dark"
|
||||
enableSystem={false}
|
||||
themes={['light', 'dark']}
|
||||
nonce={nonce}
|
||||
>
|
||||
{children}
|
||||
</NextThemesProvider>
|
||||
|
||||
+41
-1
@@ -43,7 +43,47 @@ export default auth((req) => {
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.next(); // แก้ไขจาก null
|
||||
// 5. Generate CSP with Nonce (Security Rule Tier 1)
|
||||
// ใช้ Nonce Strategy เพื่ออนุญาต Inline Script เฉพาะที่ระบุตัวตนได้ ป้องกัน XSS
|
||||
const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
|
||||
|
||||
let connectSrcApi = 'http://localhost:3001';
|
||||
if (process.env.NEXT_PUBLIC_API_URL) {
|
||||
try {
|
||||
connectSrcApi = new URL(process.env.NEXT_PUBLIC_API_URL).origin;
|
||||
} catch {
|
||||
connectSrcApi = process.env.NEXT_PUBLIC_API_URL;
|
||||
}
|
||||
}
|
||||
|
||||
const cspHeader = `
|
||||
default-src 'self';
|
||||
script-src 'self' 'nonce-${nonce}' 'strict-dynamic' 'unsafe-eval';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
img-src 'self' blob: data: https:;
|
||||
font-src 'self' data:;
|
||||
connect-src 'self' ws: wss: ${connectSrcApi};
|
||||
object-src 'none';
|
||||
base-uri 'self';
|
||||
form-action 'self';
|
||||
frame-ancestors 'none';
|
||||
upgrade-insecure-requests;
|
||||
`
|
||||
.replace(/\s{2,}/g, ' ')
|
||||
.trim();
|
||||
|
||||
const requestHeaders = new Headers(req.headers);
|
||||
requestHeaders.set('x-nonce', nonce);
|
||||
requestHeaders.set('Content-Security-Policy', cspHeader);
|
||||
|
||||
const response = NextResponse.next({
|
||||
request: {
|
||||
headers: requestHeaders,
|
||||
},
|
||||
});
|
||||
response.headers.set('Content-Security-Policy', cspHeader);
|
||||
|
||||
return response;
|
||||
});
|
||||
|
||||
// กำหนดว่า Middleware นี้จะทำงานกับ Route ไหนบ้าง
|
||||
|
||||
Vendored
+12
-12
@@ -4,11 +4,11 @@ import _NextAuth, { DefaultSession } from 'next-auth';
|
||||
declare module 'next-auth' {
|
||||
interface Session {
|
||||
user: {
|
||||
id: string;
|
||||
publicId: string; // ✅ Added (ADR-019 Waived for session)
|
||||
username: string; // ✅ Added
|
||||
firstName: string; // ✅ Added
|
||||
lastName: string; // ✅ Added
|
||||
id: string; // publicId (ADR-019)
|
||||
publicId: string;
|
||||
username: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
role: string;
|
||||
organizationId?: number;
|
||||
} & DefaultSession['user'];
|
||||
@@ -18,11 +18,11 @@ declare module 'next-auth' {
|
||||
}
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
publicId: string; // ✅ Added
|
||||
username: string; // ✅ Added
|
||||
firstName: string; // ✅ Added
|
||||
lastName: string; // ✅ Added
|
||||
id: string; // publicId (ADR-019)
|
||||
publicId: string;
|
||||
username: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
role: string;
|
||||
organizationId?: number;
|
||||
accessToken?: string;
|
||||
@@ -32,8 +32,8 @@ declare module 'next-auth' {
|
||||
|
||||
declare module 'next-auth/jwt' {
|
||||
interface JWT {
|
||||
id: string;
|
||||
username: string; // ✅ Added
|
||||
id: string; // publicId or username depending on auth flow
|
||||
username: string;
|
||||
role: string;
|
||||
organizationId?: number;
|
||||
accessToken?: string;
|
||||
|
||||
Reference in New Issue
Block a user