From d4b5df56041c71859a40326d420ee1e0187ea0fa Mon Sep 17 00:00:00 2001 From: Illya Gerasymchuk Date: Thu, 9 Jan 2025 15:55:10 +0000 Subject: [PATCH] feat: improve auto-cookie refresh + /api/me/info handling --- src/app/api/me/info/route.ts | 18 ++++-- src/lib/auth/index.ts | 3 +- src/middleware.ts | 122 +++++++++++++++++++++++++++++++---- 3 files changed, 123 insertions(+), 20 deletions(-) diff --git a/src/app/api/me/info/route.ts b/src/app/api/me/info/route.ts index d993e0c..2127e87 100644 --- a/src/app/api/me/info/route.ts +++ b/src/app/api/me/info/route.ts @@ -1,8 +1,3 @@ -/** - * In-development verstion of /api/me/info - * - * Later, this will replace /api/me/info and its usages. - */ import { NextResponse } from "next/server"; import { cookies } from "next/headers"; import prisma from "@/lib/prisma"; @@ -11,6 +6,7 @@ import { UserService } from "@/services/UserService"; import logger from "@/logging"; import { deriveUserId } from "@/lib/user/derive"; import { ApiResponse } from "@/lib/api-response"; +import { JWSInvalid, JWSSignatureVerificationFailed, JWTClaimValidationFailed, JWTExpired, JWTInvalid } from "jose/errors"; const userService = new UserService(prisma); @@ -42,10 +38,22 @@ export async function GET() { return ApiResponse.success(userInfo); } catch (error) { logger.error("User info error:", error); + + if ( + error instanceof JWTInvalid || + error instanceof JWTExpired || + error instanceof JWTClaimValidationFailed || + error instanceof JWSSignatureVerificationFailed || + error instanceof JWSInvalid + ) { + return ApiResponse.unauthorized("Invalid or expired access token"); + } + if (error instanceof Error && error.message === "Invalid token") { return ApiResponse.unauthorized("Unauthorized"); } + return ApiResponse.error("Internal server error"); } diff --git a/src/lib/auth/index.ts b/src/lib/auth/index.ts index d71dae8..d1a3c65 100644 --- a/src/lib/auth/index.ts +++ b/src/lib/auth/index.ts @@ -12,7 +12,7 @@ export async function getOrCreateUserFromRequest(req: Request): Promise process.env.NEXT_APP_URL; @@ -144,7 +145,7 @@ async function handleTokenRefresh(refreshToken: string, cookieManager: CookieMan // Get all Set-Cookie headers const cookies = refreshResponse.headers.getSetCookie(); - + // Parse the cookies to get the new tokens const newAccessToken = cookies .find(cookie => cookie.startsWith('access_token=')) @@ -167,7 +168,7 @@ async function handleTokenRefresh(refreshToken: string, cookieManager: CookieMan } // Update authenticateRequest to use CookieManager -async function authenticateRequest(cookieManager: CookieManager): Promise { +async function authenticateRequest(cookieManager: CookieManager, request: NextRequest): Promise { const { accessToken, refreshToken } = cookieManager.getRequestTokens(); // No tokens available @@ -197,6 +198,10 @@ async function authenticateRequest(cookieManager: CookieManager): Promise cookie.startsWith('access_token=')) + ?.split(';')[0] + .split('=')[1]; + + if (!newAccessToken) { + return generateUnauthorizedResponse(routeType, request); + } + + logger.error(`3. newAccessToken: ${newAccessToken}`); + + const requestHeaders = new Headers(request.headers); + const requestCookies = requestHeaders.get('Cookie') || ''; + const cookieArray = requestCookies.split('; '); + const updatedCookies = cookieArray.filter(cookie => !cookie.startsWith('access_token=')); + updatedCookies.push(`access_token=${newAccessToken}`); + + requestHeaders.set('Cookie', updatedCookies.join('; ')); + + const modifiedRequest = new NextRequest(request, { + headers: requestHeaders, + }); + + // Verify admin status with new token + const isAdmin = await checkAdminAccess(newAccessToken); + + // Create response with the new tokens + const response = NextResponse.next({request: modifiedRequest}); + + // Set each cookie as a separate Set-Cookie header as per HTTP/1.1 spec + cookies.forEach(cookie => { + response.headers.append('Set-Cookie', cookie); + }); + + // For admin routes, check permissions + if (routeType === 'admin' && !isAdmin) { + return generateAdminUnauthorizedResponse(routeType, request); + } + + return response; + } catch (error) { + logger.error("[Middleware] Refresh failed:", error); + return generateUnauthorizedResponse(routeType, request); + } } - // Handle response based on authentication and permissions - return handleAuthResponse(request, routeType, authResult, response); + return generateUnauthorizedResponse(routeType, request); } /**