if (!accessToken || 401) redirect to https://witlab.us/
This commit is contained in:
gofnnp 2025-06-24 19:10:04 +04:00
parent cf2ea84bc9
commit b1c8d4f910
4 changed files with 63 additions and 3 deletions

View File

@ -7,6 +7,7 @@ const nextConfig: NextConfig = {
NEXT_PUBLIC_API_URL: "https://api.app.witlab.us",
// NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
NEXT_PUBLIC_APP_URL: "https://app.witlab.us",
NEXT_PUBLIC_AUTH_REDIRECT_URL: "https://witlab.us",
},
images: {
remotePatterns: [

View File

@ -1,8 +1,50 @@
import { NextRequest, NextResponse } from "next/server";
import createMiddleware from "next-intl/middleware";
import { routing } from "./i18n/routing";
import { ROUTES } from "./shared/constants/client-routes";
export default createMiddleware(routing);
const publicRoutes = [
ROUTES.authCallback(),
ROUTES.paymentSuccess(),
ROUTES.paymentFailed(),
];
function isPublicRoute(pathname: string): boolean {
const pathWithoutLocale = pathname.replace(/^\/[a-z]{2}\//, "/");
return publicRoutes.some(route => pathWithoutLocale.startsWith(route));
}
function createAuthMiddleware() {
return async function authMiddleware(request: NextRequest) {
const pathname = request.nextUrl.pathname;
if (isPublicRoute(pathname)) {
return NextResponse.next();
}
const token = request.cookies.get("accessToken")?.value;
if (!token) {
return NextResponse.redirect(
process.env.NEXT_PUBLIC_AUTH_REDIRECT_URL || ""
);
}
return NextResponse.next();
};
}
const intlMiddleware = createMiddleware(routing);
export default async function middleware(request: NextRequest) {
const authResponse = await createAuthMiddleware()(request);
if (authResponse.status !== 200) {
return authResponse;
}
return intlMiddleware(request);
}
export const config = {
// Match all pathnames except for

View File

@ -1,3 +1,4 @@
import { redirect } from "next/navigation";
import { z } from "zod";
import { getServerAccessToken } from "../auth/token";
@ -18,6 +19,7 @@ type RequestOpts = Omit<RequestInit, "method" | "body"> & {
query?: Record<string, unknown>; // query-string
schema?: z.ZodTypeAny; // runtime validation
revalidate?: number;
skipAuthRedirect?: boolean;
};
class HttpClient {
@ -44,7 +46,14 @@ class HttpClient {
body?: unknown,
errorMessage?: string
): Promise<T> {
const { tags = [], schema, query, revalidate = 300, ...rest } = opts;
const {
tags = [],
schema,
query,
revalidate = 300,
skipAuthRedirect = false,
...rest
} = opts;
const headers = new Headers();
const accessToken = await getServerAccessToken();
@ -61,7 +70,12 @@ class HttpClient {
const payload = await res.json().catch(() => null);
if (!res.ok) throw new ApiError(res.status, payload, errorMessage);
if (!res.ok) {
if (res.status === 401 && !skipAuthRedirect) {
redirect(process.env.NEXT_PUBLIC_AUTH_REDIRECT_URL || "");
}
throw new ApiError(res.status, payload, errorMessage);
}
const data = payload as T;
return schema ? schema.parse(data) : data;

View File

@ -10,6 +10,9 @@ const createRoute = (segments: string[]): string => {
export const ROUTES = {
home: () => createRoute([]),
// Auth
authCallback: () => createRoute(["auth", "callback"]),
// Breath
breath: (id: string) => createRoute(["breath", id]),
breathResult: (id: string, resultId: string) =>