feat: implement middleware for route protection and localization
- Enhanced middleware functionality to manage access for protected routes based on user authentication status. - Added logic to redirect users to the login page if they attempt to access protected routes while not logged in. - Implemented a utility function to strip locale from the pathname for better route handling. - Updated route definitions to categorize routes that are not accessible to logged-in users and those that require authentication. - Improved comments and logging for better traceability and understanding of middleware operations.
This commit is contained in:
parent
313625577c
commit
72326403a0
@ -51,9 +51,8 @@ export function CheckoutButton({
|
||||
metadata,
|
||||
});
|
||||
|
||||
// Redirect to checkout
|
||||
// Redirect to checkout page
|
||||
if (result && result.data?.success && result.data.data?.url) {
|
||||
// redirect to checkout page
|
||||
window.location.href = result.data.data?.url;
|
||||
} else {
|
||||
console.error('Create checkout session error, result:', result);
|
||||
|
@ -33,19 +33,4 @@ export const routing = defineRouting({
|
||||
// The prefix to use for the locale in the URL
|
||||
// https://next-intl.dev/docs/routing#locale-prefix
|
||||
localePrefix: 'as-needed',
|
||||
// The pathnames for each locale
|
||||
// https://next-intl.dev/docs/routing#pathnames
|
||||
//
|
||||
// https://next-intl.dev/docs/routing/navigation#link
|
||||
// if we set pathnames, we need to use pathname in LocaleLink
|
||||
// pathnames: {
|
||||
// // used in sietmap.ts
|
||||
// "/": "/",
|
||||
// // used in blog pages
|
||||
// "/blog/[...slug]": "/blog/[...slug]",
|
||||
// "/blog/category/[slug]": "/blog/category/[slug]",
|
||||
// },
|
||||
});
|
||||
|
||||
// export type Pathnames = keyof typeof routing.pathnames;
|
||||
// export type Locale = (typeof routing.locales)[number];
|
||||
|
@ -1,30 +1,72 @@
|
||||
import createMiddleware from 'next-intl/middleware';
|
||||
import { routing } from './i18n/routing';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { LOCALES, routing } from './i18n/routing';
|
||||
import { getSession } from './lib/server';
|
||||
import { DEFAULT_LOGIN_REDIRECT, protectedRoutes, routesNotAllowedByLoggedInUsers } from './routes';
|
||||
|
||||
export default createMiddleware(routing);
|
||||
const intlMiddleware = createMiddleware(routing);
|
||||
|
||||
// TODO: add middleware rules for protected routes
|
||||
export default async function middleware(req: NextRequest) {
|
||||
const { nextUrl } = req;
|
||||
console.log('>> middleware start, pathname', nextUrl.pathname);
|
||||
|
||||
const session = await getSession();
|
||||
const isLoggedIn = !!session;
|
||||
// console.log('middleware, isLoggedIn', isLoggedIn);
|
||||
|
||||
// Get the pathname of the request (e.g. /zh/dashboard to /dashboard)
|
||||
const pathnameWithoutLocale = getPathnameWithoutLocale(nextUrl.pathname, LOCALES);
|
||||
|
||||
// If the route can not be accessed by logged in users, redirect if the user is logged in
|
||||
if (isLoggedIn) {
|
||||
const isNotAllowedRoute = routesNotAllowedByLoggedInUsers.some(route => new RegExp(`^${route}$`).test(pathnameWithoutLocale));
|
||||
if (isNotAllowedRoute) {
|
||||
console.log('<< middleware end, not allowed route, already logged in, redirecting to dashboard');
|
||||
return NextResponse.redirect(new URL(DEFAULT_LOGIN_REDIRECT, nextUrl));
|
||||
}
|
||||
}
|
||||
|
||||
const isProtectedRoute = protectedRoutes.some(route => new RegExp(`^${route}$`).test(pathnameWithoutLocale));
|
||||
// console.log('middleware, isProtectedRoute', isProtectedRoute);
|
||||
|
||||
// If the route is a protected route, redirect to login if user is not logged in
|
||||
if (!isLoggedIn && isProtectedRoute) {
|
||||
let callbackUrl = nextUrl.pathname;
|
||||
if (nextUrl.search) {
|
||||
callbackUrl += nextUrl.search;
|
||||
}
|
||||
const encodedCallbackUrl = encodeURIComponent(callbackUrl);
|
||||
console.log('<< middleware end, not logged in, redirecting to login, callbackUrl', callbackUrl);
|
||||
return NextResponse.redirect(
|
||||
new URL(`/auth/login?callbackUrl=${encodedCallbackUrl}`, nextUrl),
|
||||
);
|
||||
}
|
||||
|
||||
// Apply intlMiddleware for all routes
|
||||
console.log('<< middleware end, applying intlMiddleware');
|
||||
return intlMiddleware(req);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pathname of the request (e.g. /zh/dashboard to /dashboard)
|
||||
*/
|
||||
function getPathnameWithoutLocale(pathname: string, locales: string[]): string {
|
||||
const localePattern = new RegExp(`^/(${locales.join('|')})/`);
|
||||
return pathname.replace(localePattern, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Next.js internationalized routing
|
||||
* specify the routes the middleware applies to
|
||||
*
|
||||
* https://next-intl.dev/docs/routing#base-path
|
||||
*/
|
||||
export const config = {
|
||||
// The `matcher` is relative to the `basePath`
|
||||
matcher: [
|
||||
// This entry handles the root of the base
|
||||
// path and should always be included
|
||||
'/',
|
||||
|
||||
// Set a cookie to remember the previous locale for
|
||||
// all requests that have a locale prefix
|
||||
'/(zh|en)/:path*',
|
||||
|
||||
// Enable redirects that add missing locales
|
||||
// (e.g. `/pathnames` -> `/zh/pathnames`)
|
||||
// Exclude API routes and other Next.js internal routes
|
||||
// if not exclude api routes, auth routes will not work
|
||||
matcher: [
|
||||
// Match all pathnames except for
|
||||
// - … if they start with `/api`, `/_next` or `/_vercel`
|
||||
// - … the ones containing a dot (e.g. `favicon.ico`)
|
||||
'/((?!api|_next|_vercel|.*\\..*).*)',
|
||||
],
|
||||
};
|
||||
|
@ -6,10 +6,10 @@
|
||||
export enum Routes {
|
||||
Root = '/',
|
||||
|
||||
// pages
|
||||
FAQ = '/#faq',
|
||||
Features = '/#features',
|
||||
Pricing = '/pricing',
|
||||
|
||||
Blog = '/blog',
|
||||
Docs = '/docs',
|
||||
About = '/about',
|
||||
@ -17,7 +17,6 @@ export enum Routes {
|
||||
Waitlist = '/waitlist',
|
||||
Changelog = '/changelog',
|
||||
Roadmap = 'https://mksaas.featurebase.app',
|
||||
|
||||
CookiePolicy = '/cookie',
|
||||
PrivacyPolicy = '/privacy',
|
||||
TermsOfService = '/terms',
|
||||
@ -29,11 +28,6 @@ export enum Routes {
|
||||
ForgotPassword = '/auth/forgot-password',
|
||||
ResetPassword = '/auth/reset-password',
|
||||
|
||||
AIText = '/ai/text',
|
||||
AIImage = '/ai/image',
|
||||
AIVideo = '/ai/video',
|
||||
AIAudio = '/ai/audio',
|
||||
|
||||
// dashboard routes
|
||||
Dashboard = '/dashboard',
|
||||
SettingsProfile = '/settings/profile',
|
||||
@ -41,6 +35,12 @@ export enum Routes {
|
||||
SettingsSecurity = '/settings/security',
|
||||
SettingsNotifications = '/settings/notifications',
|
||||
|
||||
// AI routes
|
||||
AIText = '/ai/text',
|
||||
AIImage = '/ai/image',
|
||||
AIVideo = '/ai/video',
|
||||
AIAudio = '/ai/audio',
|
||||
|
||||
// Block routes
|
||||
HeroBlocks = '/blocks/hero-section',
|
||||
LogoBlocks = '/blocks/logo-cloud',
|
||||
@ -60,51 +60,23 @@ export enum Routes {
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of routes that are accessible to the public
|
||||
* These routes do not require authentication
|
||||
* @type {string[]}
|
||||
* The routes that can not be accessed by logged in users
|
||||
*/
|
||||
export const publicRoutes = [
|
||||
'/',
|
||||
|
||||
// pages
|
||||
'/blog(/.*)?',
|
||||
'/blocks(/.*)?',
|
||||
'/terms-of-service(/.*)?',
|
||||
'/privacy-policy(/.*)?',
|
||||
'/cookie-policy(/.*)?',
|
||||
'/about(/.*)?',
|
||||
'/contact(/.*)?',
|
||||
'/waitlist(/.*)?',
|
||||
'/changelog(/.*)?',
|
||||
|
||||
// unsubscribe newsletter
|
||||
'/unsubscribe(/.*)?',
|
||||
|
||||
// stripe webhook
|
||||
'/api/webhook',
|
||||
|
||||
// og images
|
||||
'/api/og',
|
||||
];
|
||||
|
||||
/**
|
||||
* The routes for the authentication pages
|
||||
*/
|
||||
export const authRoutes = [
|
||||
Routes.AuthError,
|
||||
export const routesNotAllowedByLoggedInUsers = [
|
||||
Routes.Login,
|
||||
Routes.Register,
|
||||
Routes.ForgotPassword,
|
||||
Routes.ResetPassword,
|
||||
];
|
||||
|
||||
/**
|
||||
* The prefix for API authentication routes
|
||||
* Routes that start with this prefix are used for API authentication purposes
|
||||
* @type {string}
|
||||
* The routes that are protected and require authentication
|
||||
*/
|
||||
export const apiAuthPrefix = '/api/auth';
|
||||
export const protectedRoutes = [
|
||||
Routes.Dashboard,
|
||||
Routes.SettingsProfile,
|
||||
Routes.SettingsBilling,
|
||||
Routes.SettingsSecurity,
|
||||
Routes.SettingsNotifications,
|
||||
];
|
||||
|
||||
/**
|
||||
* The default redirect path after logging in
|
||||
|
Loading…
Reference in New Issue
Block a user