chore(SW-3381) Moved LoginButton to design system * chore(SW-3381) Moved LoginButton to design system Approved-by: Anton Gunnarsson
100 lines
3.4 KiB
TypeScript
100 lines
3.4 KiB
TypeScript
import { type NextMiddleware, NextResponse } from "next/server"
|
|
|
|
import { login } from "@scandic-hotels/common/constants/routes/handleAuth"
|
|
import { logger } from "@scandic-hotels/common/logger"
|
|
import { findLang } from "@scandic-hotels/common/utils/languages"
|
|
|
|
import { authRequired, mfaRequired } from "@/constants/routes/authRequired"
|
|
import { getInternalNextURL, getPublicNextURL } from "@/server/utils"
|
|
|
|
import { auth } from "@/auth"
|
|
|
|
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 lang = findLang(request.nextUrl.pathname)!
|
|
|
|
const isLoggedIn = !!request.auth
|
|
const hasError = request.auth?.error
|
|
|
|
// Inside auth() we need an internal request for rewrites.
|
|
// @see getInternalNextURL()
|
|
const nextUrlInternal = getInternalNextURL(request)
|
|
|
|
const nextUrlPublic = getPublicNextURL(request)
|
|
|
|
/**
|
|
* Function to validate MFA from token data
|
|
* @returns boolean
|
|
*/
|
|
function isMFAInvalid() {
|
|
const isMFATokenValid = request.auth?.token.mfa_expires_at
|
|
? request.auth.token.mfa_expires_at > Date.now()
|
|
: false
|
|
return !(request.auth?.token.mfa_scope && isMFATokenValid)
|
|
}
|
|
const isMFAPath = mfaRequired.includes(request.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`, nextUrlInternal), {
|
|
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,
|
|
}
|
|
logger.debug(`[authRequired] redirecting to: ${redirectUrl}`, redirectOpts)
|
|
return NextResponse.redirect(redirectUrl, redirectOpts)
|
|
}) as unknown as NextMiddleware // See comment above
|
|
|
|
export const matcher: MiddlewareMatcher = (request) => {
|
|
return authRequired.includes(request.nextUrl.pathname)
|
|
}
|