Created
January 30, 2025 19:15
-
-
Save diego3g/37f6745e07ccc028f7c0e00607131333 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import dayjs from 'dayjs' | |
import { jwtDecode } from 'jwt-decode' | |
import { NextResponse, URLPattern } from 'next/server' | |
import type { MiddlewareConfig, NextRequest } from 'next/server' | |
import { env } from './env' | |
const baseURL = env.NEXT_PUBLIC_WEB_URL | |
/** | |
* When `whenAuthenticated` is `redirect`, the user is redirected to event page when | |
* visiting public URLs while authenticated, otherwise the `whenAuthenticated` next will | |
* let the user visit the public page even if authenticated. | |
*/ | |
const publicRoutesPatterns = [ | |
{ | |
pattern: new URLPattern('/event/:slug/sign-in', baseURL), | |
whenAuthenticated: 'redirect', | |
}, | |
{ | |
pattern: new URLPattern('/event/:slug/sign-in/code', baseURL), | |
whenAuthenticated: 'redirect', | |
}, | |
{ | |
pattern: new URLPattern('/event/:slug/authenticate', baseURL), | |
whenAuthenticated: 'next', | |
}, | |
{ | |
pattern: new URLPattern('/event/:slug/share/:postId', baseURL), | |
whenAuthenticated: 'next', | |
}, | |
{ | |
pattern: new URLPattern('/event/:slug/referral-link', baseURL), | |
whenAuthenticated: 'next', | |
}, | |
{ | |
pattern: new URLPattern( | |
'/event/:slug/referral-link/:subscriberId', | |
baseURL | |
), | |
whenAuthenticated: 'next', | |
}, | |
{ | |
pattern: new URLPattern('/event/:slug/sign-out', baseURL), | |
whenAuthenticated: 'next', | |
}, | |
{ | |
pattern: new URLPattern('/images/referral-opengraph', baseURL), | |
whenAuthenticated: 'next', | |
}, | |
] as const | |
function getIsPublicUrl(url: string) { | |
const [input] = url.split('?') | |
for (const { pattern, whenAuthenticated } of publicRoutesPatterns) { | |
if (pattern.test(input)) { | |
return { isPublicUrl: true, whenAuthenticated } | |
} | |
} | |
return { isPublicUrl: false, whenAuthenticated: null } | |
} | |
function getUrlParams<T extends Record<string, string>>( | |
url: string, | |
pattern: URLPattern | |
) { | |
const [input] = url.split('?') | |
const result = pattern.exec(input) | |
if (result !== null) { | |
return result.pathname.groups as T | |
} | |
throw new Error('Path not properly handled by Next.js middleware') | |
} | |
export function middleware(request: NextRequest) { | |
try { | |
const { isPublicUrl, whenAuthenticated } = getIsPublicUrl(request.url) | |
const { slug } = getUrlParams<{ slug: string }>( | |
request.url, | |
new URLPattern('/event/:slug/:path*', baseURL) | |
) | |
const authenticationToken = request.cookies.get('token') | |
/** | |
* If token is not present and the URL is public, continue... | |
*/ | |
if (!authenticationToken && isPublicUrl) { | |
return NextResponse.next() | |
} | |
/** | |
* If token is not present but the URL is private, redirect to sign in | |
*/ | |
if (!authenticationToken && !isPublicUrl) { | |
const signInUrl = new URL(`/event/${slug}/sign-in`, baseURL) | |
return NextResponse.redirect(signInUrl) | |
} | |
/** | |
* If token is present and the URL is private, we might redirect to event | |
*/ | |
if ( | |
authenticationToken && | |
isPublicUrl && | |
whenAuthenticated === 'redirect' | |
) { | |
const eventUrl = new URL(`/event/${slug}`, baseURL) | |
return NextResponse.redirect(eventUrl) | |
} | |
if (authenticationToken && !isPublicUrl) { | |
let isTokenMalformedOrAlmostExpired = false | |
try { | |
const decoded = jwtDecode<{ sub: string; exp: number }>( | |
authenticationToken.value | |
) | |
const expiryDate = new Date(decoded.exp * 1000) | |
isTokenMalformedOrAlmostExpired = | |
dayjs(expiryDate).diff(new Date(), 'minutes') < 15 | |
} catch { | |
isTokenMalformedOrAlmostExpired = true | |
} | |
/** | |
* If token is present but almost malformed or almost expired, redirect to sign in | |
*/ | |
if (isTokenMalformedOrAlmostExpired) { | |
const signOutUrl = new URL( | |
`/event/${slug}/sign-out`, | |
env.NEXT_PUBLIC_WEB_URL | |
) | |
signOutUrl.searchParams.set('code', 'jwt-expired') | |
return NextResponse.redirect(signOutUrl) | |
} | |
} | |
return NextResponse.next() | |
} catch { | |
return NextResponse.next() | |
} | |
} | |
export const config: MiddlewareConfig = { | |
matcher: '/event/:slug/:path*', | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment