Skip to content

Instantly share code, notes, and snippets.

@diego3g
Created January 30, 2025 19:15
Show Gist options
  • Save diego3g/37f6745e07ccc028f7c0e00607131333 to your computer and use it in GitHub Desktop.
Save diego3g/37f6745e07ccc028f7c0e00607131333 to your computer and use it in GitHub Desktop.
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