diff --git a/.env b/.env index 81a504deb80420648235cb20ba5959931993792c..a5aa3043233953a098cc98a17bd16aec7e3af668 100644 --- a/.env +++ b/.env @@ -8,3 +8,4 @@ DATABASE_URL="file:./dev.db" JWT_SECRET=ideaswork +NEXT_PUBLIC_BASE_URL=http://129.226.159.129:3000 \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index 29f0b4109b59624a1e2a949f8a12bc9346dd3834..04578866986c3de36ccb2b95ec1a0de8ac6256cc 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,5 @@ import type { Metadata } from "next"; -import localFont from "next/font/local"; import { Toaster } from "@/components/ui/toaster" import Navbar from '@/components/Navbar' import Footer from '@/components/Footer' diff --git a/middleware.ts b/middleware.ts index ef22439ff47aa46ab2bd5bafceac4e05e8fbe1e8..aebacacfd0d1b8537722a7fb66c9d00a8c9efd8a 100644 --- a/middleware.ts +++ b/middleware.ts @@ -2,121 +2,65 @@ import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import { verifyToken } from '@/lib/auth' -// Public paths that don't require authentication -const publicPaths = Object.freeze([ +const publicPaths = new Set([ '/login', '/register', '/', - '/documentation(.*)', // Match all documentation paths and subpaths - '/api/auth/login', - '/api/auth/register', - '/api/auth/me', // Allow all auth endpoints + '/documentation', + '/api/auth', '/api/md', - '/workspace', + '/workspace', '/unauthorized', - '/api/(.*)', // Match all API paths and subpaths using regex pattern - '/worklog/user', + '/worklog/user' ]) -const protectedRoutes = [ - { path: '/', roles: ['Team Member', 'Scrum Master', 'Product Owner'] }, - { path: '/workspace', roles: ['Team Member', 'Scrum Master', 'Product Owner'] }, - { path: '/dashboard', roles: ['Team Member', 'Scrum Master', 'Product Owner'] }, - { path: '/task', roles: ['Team Member', 'Scrum Master', 'Product Owner'] }, - { path: '/worklog/user', roles: ['Team Member', 'Scrum Master', 'Product Owner'] }, - { path: '/documentation', roles: ['Team Member', 'Scrum Master', 'Product Owner'] }, - { path: '/product', roles: ['Scrum Master'] }, - { path: '/sprint', roles: ['Scrum Master'] }, - { path: '/team', roles: ['Scrum Master'] }, - { path: '/userstory', roles: ['Product Owner', 'Scrum Master'] }, - { path: '/backlog', roles: ['Product Owner', 'Scrum Master'] }, - { path: '/worklog/admin', roles: ['Scrum Master'] }, -]; +type Role = 'Team Member' | 'Scrum Master' | 'Product Owner' +type RoleAccess = Record -export async function middleware(request: NextRequest) { - const url = new URL(request.url) - let path = url.pathname - - // Normalize path - path = path.replace(/\/+/g, '/').replace(/\/$/, '') || '/' - - console.log(`[Middleware] Request path: ${path}`) +const roleAccess: RoleAccess = { + 'Team Member': ['/', '/workspace', '/dashboard', '/task', '/worklog/user', '/documentation'], + 'Scrum Master': ['/product', '/sprint', '/team', '/userstory', '/backlog', '/worklog/admin'], + 'Product Owner': ['/userstory', '/backlog'] +} - const isPublicPath = (publicPath: string) => { - if (publicPath.includes('(.*)')) { - const regex = new RegExp(`^${publicPath.replace('(.*)', '.*')}$`) - return regex.test(path) - } - return path === publicPath || path.startsWith(publicPath + '/') - } +export async function middleware(request: NextRequest) { + const { pathname } = new URL(request.url) + const path = pathname.replace(/\/+/g, '/').replace(/\/$/, '') || '/' - // Allow public paths - if (publicPaths.some(isPublicPath)) { - console.log(`[Middleware] Public path accessed: ${path}`) + // Check public paths + const publicPathsArray = Array.from(publicPaths) + if (publicPathsArray.some((p: string) => path === p || path.startsWith(p + '/'))) { return NextResponse.next() } - // Get token from cookies + // Verify token const token = request.cookies.get('token')?.value - console.log(`[Middleware] Token found: ${!!token}`) - - // Verify JWT token - let payload = null + let payload try { payload = token ? await verifyToken(token) : null - console.log(`[Middleware] Token payload:`, payload) - } catch (error) { - console.error(`[Middleware] Token verification error:`, error) - if (!path.startsWith('/login')) { - return NextResponse.redirect(new URL('/login', request.url)) - } - return NextResponse.next() + } catch { + return path.startsWith('/login') ? NextResponse.next() : NextResponse.redirect(new URL('/login', request.url)) } - // Redirect to login if not authenticated if (!payload) { - console.log(`[Middleware] Unauthenticated request, redirecting to login`) - if (!path.startsWith('/login')) { - return NextResponse.redirect(new URL('/login', request.url)) - } - return NextResponse.next() + return path.startsWith('/login') ? NextResponse.next() : NextResponse.redirect(new URL('/login', request.url)) } - // Add user info to request headers - const requestHeaders = new Headers(request.headers) - requestHeaders.set('x-user-id', payload.user_id) - requestHeaders.set('x-user-role', payload.role) + // Set user headers + const headers = new Headers(request.headers) + headers.set('x-user-id', payload.user_id) + headers.set('x-user-role', payload.role) - // Get allowed paths based on user role - const allowedPaths = protectedRoutes - .filter(route => route.roles.includes(payload.role)) - .map(route => route.path) - - // Check if current path is allowed - const isPathAllowed = allowedPaths.some(allowedPath => - path === allowedPath || path.startsWith(allowedPath + '/') - ) - - if (!isPathAllowed) { - console.log(`[Middleware] Access denied for role ${payload.role} to path ${path}`) - console.log(`Allowed paths for role ${payload.role}:`, allowedPaths) - if (!path.startsWith('/unauthorized')) { - return NextResponse.redirect(new URL('/unauthorized', request.url)) - } - return NextResponse.next() + // Check role access + const role = payload.role as Role + const allowedPaths = roleAccess[role] || [] + if (!allowedPaths.some((p: string) => path === p || path.startsWith(p + '/'))) { + return path.startsWith('/unauthorized') ? NextResponse.next() : NextResponse.redirect(new URL('/unauthorized', request.url)) } - // Continue with authenticated request - console.log(`[Middleware] Proceeding with authenticated request`) - return NextResponse.next({ - request: { - headers: requestHeaders - } - }) + return NextResponse.next({ request: { headers } }) } export const config = { - matcher: [ - '/((?!_next/static|_next/image|favicon.ico|api/|static/).*)', // 确保排除所有静态资源 - ], + matcher: ['/((?!_next/static|_next/image|favicon.ico|api/|static/).*)'] } \ No newline at end of file diff --git a/next.config.mjs b/next.config.mjs index ff9ec9bd8102aefe5de6123c7f0e7fa5951858e2..514c846f56d2d51cd34e7d7a3e435294b1ec138e 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,7 +1,8 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - assetPrefix: process.env.NODE_ENV === 'production' ? '' : '', + assetPrefix: process.env.NEXT_PUBLIC_BASE_URL, basePath: '', + trailingSlash: false, // 禁用尾部斜杠 experimental: { forceSwcTransforms: true, },