d3ro-voice/apps/admin/src/middleware.ts
Yun Chan 708e20f747
Some checks failed
CI Pipeline / Code Quality & Typecheck (push) Waiting to run
CI Pipeline / Test Suite (macos-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (ubuntu-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (windows-latest) (push) Blocked by required conditions
CI Pipeline / Build Validation (admin) (push) Blocked by required conditions
CI Pipeline / Build Validation (desktop) (push) Blocked by required conditions
Deploy Landing Page / deploy (push) Blocked by required conditions
Deploy Landing Page / build (push) Waiting to run
Release & Packaging Pipeline / Build & Publish Admin Docker Image (push) Failing after 8s
Release & Code Signing CA Pipeline / build-and-sign-windows (push) Failing after 1m51s
Build macOS / Build & Package (macOS) (push) Failing after 4s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
Release & Code Signing CA Pipeline / build-and-sign-macos (push) Failing after 3s
Release & Packaging Pipeline / Package macOS Desktop App (push) Failing after 4s
Release & Packaging Pipeline / Package Windows Desktop App (push) Failing after 2m28s
Release & Packaging Pipeline / Publish Official GitHub Release (push) Has been skipped
feat: complete release preparation, 10+ ad mediation, CI/CD, and docker deployment
2026-08-20 11:12:05 +09:00

70 lines
2.6 KiB
TypeScript

// apps/admin/src/middleware.ts
// D3RO Voice — Industrial Grade Admin Route & Security Guard
import { NextResponse, type NextRequest } from 'next/server'
const PUBLIC_PATHS = ['/login', '/auth/callback', '/api/auth/login', '/api/auth/logout', '/favicon.ico', '/robots.txt']
export async function middleware(request: NextRequest): Promise<NextResponse> {
const { pathname } = request.nextUrl
// 1. Check if path is public (e.g. login, static assets)
const isPublic = PUBLIC_PATHS.some((path) => pathname === path || pathname.startsWith(path + '/'))
const isStatic = pathname.startsWith('/_next') || pathname.startsWith('/static') || pathname.includes('.')
// 2. Validate session cookie
const sessionCookie = request.cookies.get('d3ro_admin_session')?.value
let isAuthenticated = false
if (sessionCookie) {
try {
const decoded = JSON.parse(Buffer.from(sessionCookie, 'base64').toString('utf-8'))
if (decoded && decoded.expiresAt && decoded.expiresAt > Date.now()) {
isAuthenticated = true
}
} catch {
isAuthenticated = false
}
}
// 3. Unauthenticated access to protected route -> Redirect to /login
if (!isAuthenticated && !isPublic && !isStatic) {
const loginUrl = new URL('/login', request.url)
if (pathname !== '/') {
loginUrl.searchParams.set('redirect', pathname)
}
const redirectResponse = NextResponse.redirect(loginUrl)
addSecurityHeaders(redirectResponse)
return redirectResponse
}
// 4. Authenticated user visiting /login -> Redirect to Dashboard /
if (isAuthenticated && pathname === '/login') {
const dashboardUrl = new URL('/', request.url)
const redirectResponse = NextResponse.redirect(dashboardUrl)
addSecurityHeaders(redirectResponse)
return redirectResponse
}
// 5. Proceed with Security Headers attached
const response = NextResponse.next({ request: { headers: request.headers } })
addSecurityHeaders(response)
return response
}
function addSecurityHeaders(response: NextResponse): void {
// Anti-Crawling & Anti-Reconnaissance (Shodan, Google, Bing, AI scrapers)
response.headers.set('X-Robots-Tag', 'noindex, nofollow, noarchive, nosnippet, noimageindex')
// Clickjacking Prevention
response.headers.set('X-Frame-Options', 'DENY')
// MIME Sniffing Prevention
response.headers.set('X-Content-Type-Options', 'nosniff')
// Referrer Privacy
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')
// Feature Policy
response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()')
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
}