These are now defined in Netlify UI for dedicated environments (test, stage, production): AUTH_URL NEXTAUTH_URL PUBLIC_URL Code now falls back to incoming request host. Mainly used for deployment previews which do not have Akamai in front, meaning we do not need the above workaround as incoming request host matches the actual public facing host. When Akamai is in front, we lose the public facing host in Netlify's routing layer as they internally use `x-forwarded-for` and we can't claim it for our usage.
97 lines
3.2 KiB
TypeScript
97 lines
3.2 KiB
TypeScript
import { NextResponse } from "next/server"
|
|
|
|
import { authRequired, mfaRequired } from "@/constants/routes/authRequired"
|
|
import { login } from "@/constants/routes/handleAuth"
|
|
import { getPublicNextURL } from "@/server/utils"
|
|
|
|
import { auth } from "@/auth"
|
|
import { findLang } from "@/utils/languages"
|
|
|
|
import type { NextMiddleware } from "next/server"
|
|
|
|
import type { MiddlewareMatcher } from "@/types/middleware"
|
|
|
|
/**
|
|
* AppRouteHandlerFnContext is the context that is passed to the handler as
|
|
* the second argument. This is only done for Route handlers (route.js) and
|
|
* not for middleware. Middleware`s second argument is `event` of type
|
|
* `NextFetchEvent`.
|
|
*
|
|
* Auth.js uses the same pattern for both Route handlers and Middleware,
|
|
* the auth()-wrapper:
|
|
*
|
|
* auth((req) => { ... })
|
|
*
|
|
* But there is a difference between middleware and route handlers, route
|
|
* handlers get passed a context which middleware do not get (they get a
|
|
* NextFetchEvent instead). Using the same function for both works runtime
|
|
* because Auth.js handles this properly. But fails in typings as the second
|
|
* argument doesn't match for middleware.
|
|
*
|
|
* We want to avoid using ts-expect-error because that hides other errors
|
|
* not related to this typing error and ts-expect-error cannot be scoped either.
|
|
*
|
|
* So we type assert this export to NextMiddleware. The lesser of all evils.
|
|
*
|
|
* https://github.com/nextauthjs/next-auth/blob/3c035ec62f2f21d7cab65504ba83fb1a9a13be01/packages/next-auth/src/lib/index.ts#L265
|
|
* https://authjs.dev/reference/nextjs
|
|
*/
|
|
export const middleware = auth(async (request) => {
|
|
const { nextUrl } = request
|
|
const lang = findLang(nextUrl.pathname)!
|
|
|
|
const isLoggedIn = !!request.auth
|
|
const hasError = request.auth?.error
|
|
|
|
const nextUrlPublic = getPublicNextURL(request)
|
|
|
|
/**
|
|
* Function to validate MFA from token data
|
|
* @returns boolean
|
|
*/
|
|
function isMFAInvalid() {
|
|
const isMFATokenValid = request.auth
|
|
? request.auth.token.mfa_expires_at > Date.now()
|
|
: false
|
|
return !(request.auth?.token.mfa_scope && isMFATokenValid)
|
|
}
|
|
const isMFAPath = mfaRequired.includes(nextUrl.pathname)
|
|
|
|
if (isLoggedIn && isMFAPath && isMFAInvalid()) {
|
|
const headers = new Headers(request.headers)
|
|
headers.set("x-returnurl", nextUrlPublic.href)
|
|
headers.set("x-login-source", "mfa")
|
|
return NextResponse.rewrite(new URL(`/${lang}/login`, request.nextUrl), {
|
|
request: {
|
|
headers,
|
|
},
|
|
})
|
|
}
|
|
|
|
if (isLoggedIn && !hasError) {
|
|
const headers = new Headers(request.headers)
|
|
headers.set("x-continue", "1")
|
|
return NextResponse.next({
|
|
headers,
|
|
})
|
|
}
|
|
|
|
const headers = new Headers()
|
|
headers.append(
|
|
"set-cookie",
|
|
`redirectTo=${encodeURIComponent(nextUrlPublic.href)}; Path=/; HttpOnly; SameSite=Lax`
|
|
)
|
|
|
|
const loginUrl = login[lang]
|
|
const redirectUrl = new URL(loginUrl, nextUrlPublic)
|
|
const redirectOpts = {
|
|
headers,
|
|
}
|
|
console.log(`[authRequired] redirecting to: ${redirectUrl}`, redirectOpts)
|
|
return NextResponse.redirect(redirectUrl, redirectOpts)
|
|
}) as NextMiddleware // See comment above
|
|
|
|
export const matcher: MiddlewareMatcher = (request) => {
|
|
return authRequired.includes(request.nextUrl.pathname)
|
|
}
|