CoolFace
Apppublic

Leon4gr45/builder

sourceHugging Facemitupdated 2d agoView on Hugging Face
0likes
middleware.ts228 linesDownload Raw Back to root
1/**2 * Next.js Middleware3 *4 * Handles authentication and routing for Server mode.5 * In Browser mode, server-only routes are blocked.6 * In Server mode, all data routes require authentication.7 *8 * Workspace routing:9 * - /w/[workspaceId]/* pages require auth10 * - /api/w/[workspaceId]/* routes require auth11 * - /api/server-generate/* routes require auth12 * - Legacy /admin/{view} paths redirect to /w/{defaultWorkspaceId}/{view}13 */14 15import { NextResponse } from 'next/server';16import type { NextRequest } from 'next/server';17import { verifySession, maybeRefreshSession, SESSION_COOKIE_NAME, SESSION_DURATION } from '@/lib/auth/session';18import type { SessionData } from '@/lib/auth/session';19 20async function nextWithRefreshedSession(session: SessionData): Promise<NextResponse> {21  const response = NextResponse.next();22  const refreshed = await maybeRefreshSession(session);23  if (refreshed) {24    response.cookies.set(SESSION_COOKIE_NAME, refreshed, {25      httpOnly: true,26      secure: process.env.SECURE_COOKIES !== 'false' && process.env.NODE_ENV === 'production',27      sameSite: 'lax',28      maxAge: SESSION_DURATION / 1000,29      path: '/',30    });31  }32  return response;33}34 35// Views that have moved from /admin/{view} to /w/{workspaceId}/{view}36const WORKSPACE_VIEWS = ['projects', 'dashboard', 'deployments', 'settings', 'skills', 'templates', 'docs'];37 38function loginRedirect(request: NextRequest): NextResponse {39  const gatewayUrl = process.env.NEXT_PUBLIC_GATEWAY_URL;40  if (gatewayUrl) return NextResponse.redirect(gatewayUrl + '/login');41  return NextResponse.redirect(new URL('/admin/login', request.url));42}43 44export async function middleware(request: NextRequest) {45  const isServerMode = process.env.NEXT_PUBLIC_SERVER_MODE === 'true';46  const { pathname } = request.nextUrl;47  const isDesktop = process.env.OSW_DESKTOP === 'true';48 49  // Desktop app: skip auth but still handle workspace routing50  if (isDesktop) {51    // Legacy redirect: /admin/{view} -> /w/{workspaceId}/{view}52    if (pathname.startsWith('/admin')) {53      for (const view of WORKSPACE_VIEWS) {54        if (pathname === `/admin/${view}` || pathname.startsWith(`/admin/${view}/`)) {55          const workspaceId = request.cookies.get('osw_workspace')?.value;56          if (workspaceId) {57            const newPath = pathname.replace(`/admin/${view}`, `/w/${workspaceId}/${view}`);58            return NextResponse.redirect(new URL(newPath + request.nextUrl.search, request.url));59          }60          // No workspace cookie yet — redirect to root to trigger bootstrap61          return NextResponse.redirect(new URL('/', request.url));62        }63      }64    }65    return NextResponse.next();66  }67 68  // ============================================69  // Workspace page routes: /w/[workspaceId]/*70  // ============================================71  if (pathname.startsWith('/w/')) {72    if (!isServerMode) {73      return NextResponse.redirect(new URL('/', request.url));74    }75 76    const token = request.cookies.get('osw_session')?.value;77    if (!token) {78      const response = loginRedirect(request);79      // Clear stale workspace cookie80      response.cookies.delete('osw_workspace');81      return response;82    }83 84    const session = await verifySession(token);85    if (!session) {86      const response = loginRedirect(request);87      // Clear stale cookies88      response.cookies.delete('osw_session');89      response.cookies.delete('osw_workspace');90      return response;91    }92 93    return nextWithRefreshedSession(session);94  }95 96  // ============================================97  // Server-generate API routes: /api/server-generate/*98  // ============================================99  if (pathname.startsWith('/api/server-generate')) {100    if (!isServerMode) {101      return NextResponse.json({ error: 'Not available in Browser mode' }, { status: 404 });102    }103 104    const token = request.cookies.get('osw_session')?.value;105    if (!token) {106      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });107    }108    const session = await verifySession(token);109    if (!session) {110      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });111    }112 113    return nextWithRefreshedSession(session);114  }115 116  // ============================================117  // Workspace API routes: /api/w/[workspaceId]/*118  // ============================================119  if (pathname.startsWith('/api/w/')) {120    if (!isServerMode) {121      return NextResponse.json({ error: 'Not available in Browser mode' }, { status: 404 });122    }123 124    const token = request.cookies.get('osw_session')?.value;125    if (!token) {126      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });127    }128    const session = await verifySession(token);129    if (!session) {130      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });131    }132 133    return nextWithRefreshedSession(session);134  }135 136  // ============================================137  // Admin API routes: /api/admin/*138  // ============================================139  if (pathname.startsWith('/api/admin')) {140    if (!isServerMode) {141      return NextResponse.json({ error: 'Not available in Browser mode' }, { status: 404 });142    }143    // Defense-in-depth: verify session for admin API routes144    const token = request.cookies.get('osw_session')?.value;145    const apiKey = request.headers.get('x-instance-api-key');146    if (!token && !apiKey) {147      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });148    }149    if (token) {150      const session = await verifySession(token);151      if (!session) {152        return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });153      }154      return nextWithRefreshedSession(session);155    }156    return NextResponse.next();157  }158 159  // ============================================160  // Admin pages: /admin/*161  // ============================================162  if (pathname.startsWith('/admin')) {163    if (!isServerMode) {164      return NextResponse.redirect(new URL('/', request.url));165    }166 167    // When managed by an external auth provider, redirect login/register there168    const gatewayUrl = process.env.NEXT_PUBLIC_GATEWAY_URL;169    if (gatewayUrl && (pathname === '/admin/login' || pathname === '/admin/register')) {170      return NextResponse.redirect(gatewayUrl + '/login');171    }172 173    // Allow login and register pages without auth174    // (Register API enforces REGISTRATION_MODE + zero-users check server-side)175    if (pathname === '/admin/login' || pathname === '/admin/register') {176      return NextResponse.next();177    }178 179    const token = request.cookies.get('osw_session')?.value;180    if (!token) return loginRedirect(request);181 182    const session = await verifySession(token);183    if (!session) return loginRedirect(request);184 185    // Only admins can access user/workspace management186    if (!session.isAdmin && (pathname.startsWith('/admin/users') || pathname.startsWith('/admin/workspaces'))) {187      // Redirect non-admin to their default workspace188      const workspaceId = request.cookies.get('osw_workspace')?.value;189      if (workspaceId) {190        return NextResponse.redirect(new URL(`/w/${workspaceId}/projects`, request.url));191      }192      return loginRedirect(request);193    }194 195    // Legacy redirect: /admin/{view} -> /w/{workspaceId}/{view}196    for (const view of WORKSPACE_VIEWS) {197      if (pathname === `/admin/${view}` || pathname.startsWith(`/admin/${view}/`)) {198        const workspaceId = request.cookies.get('osw_workspace')?.value;199        if (workspaceId) {200          const newPath = pathname.replace(`/admin/${view}`, `/w/${workspaceId}/${view}`);201          return NextResponse.redirect(new URL(newPath + request.nextUrl.search, request.url));202        }203        // No workspace cookie — redirect to login204        return loginRedirect(request);205      }206    }207 208    // /admin root redirect209    if (pathname === '/admin' || pathname === '/admin/') {210      const workspaceId = request.cookies.get('osw_workspace')?.value;211      if (workspaceId) {212        return NextResponse.redirect(new URL(`/w/${workspaceId}/projects`, request.url));213      }214      return loginRedirect(request);215    }216 217    return nextWithRefreshedSession(session);218  }219 220  return NextResponse.next();221}222 223export const config = {224  matcher: [225    '/((?!_next/static|_next/image|favicon.ico|deployments/|health|api-docs|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',226  ],227};228