From f61bc055e0a67811cac338b31e42e8c69d090b65 Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Wed, 10 Jul 2024 19:32:34 +0530 Subject: [PATCH 1/3] feat: Api for creating file and Health check api --- middleware.ts | 56 ++++++++++++++++-------------- src/Middleware/AuthMiddleware.ts | 31 +++++++++++++++++ src/app/api/files/create/route.ts | 35 +++++++++++++++++++ src/app/api/files/private/route.ts | 2 +- src/app/api/health/route.ts | 19 ++++++++++ src/app/dashboard/layout.tsx | 52 +++++++++------------------ src/app/dashboard/page.tsx | 17 ++++++++- src/config/AxiosInstance.ts | 1 + src/config/AxiosProtectedRoute.ts | 33 ++++++++++++++++++ src/lib/API-URLs.ts | 3 +- src/models/file.ts | 8 ++--- src/models/user.ts | 2 +- src/types/types.d.ts | 8 ++++- 13 files changed, 194 insertions(+), 73 deletions(-) create mode 100644 src/Middleware/AuthMiddleware.ts create mode 100644 src/app/api/files/create/route.ts create mode 100644 src/app/api/health/route.ts create mode 100644 src/config/AxiosProtectedRoute.ts diff --git a/middleware.ts b/middleware.ts index 421fb49..637e0ea 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,34 +1,36 @@ -import { NextResponse } from "next/server"; -import type { NextRequest } from "next/server"; -import jwt from "jsonwebtoken"; -import User from "@/models/user"; -import { mongoDB } from "@/lib/MongoDB"; +// import { NextResponse } from "next/server"; +// import type { NextRequest } from "next/server"; +// import jwt from "jsonwebtoken"; +// import User from "@/models/user"; +// import { mongoDB } from "@/lib/MongoDB"; -export async function middleware(req: NextRequest) { - try { - const token : any = req.cookies.get('accessToken') || req.headers.get('Authorization')?.replace('Bearer ', ''); +// export async function middleware(req: NextRequest) { +// try { +// const token : any = req.cookies.get('accessToken') || req.headers.get('Authorization')?.replace('Bearer ', ''); +// console.log(token) - if (!token) { - return NextResponse.json({ error: 'Unauthorized Access!' }, { status: 401 }); - } +// if (!token) { +// return NextResponse.json({ error: 'Unauthorized Access!' }, { status: 401 }); +// } - const decodedToken : any = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET as string); - await mongoDB(); - const user = await User.findById(decodedToken._id).select('-password -refreshToken'); +// const decodedToken : any = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET as string); - if (!user) return NextResponse.json({ error: 'Unauthorized Access!' }, { status: 401 }); +// await mongoDB(); +// const user = await User.findById(decodedToken._id).select('-password -refreshToken'); - // Pass user data in request headers for further processing in API route - req.headers.set('user', JSON.stringify(user)); +// if (!user) return NextResponse.json({ error: 'Unauthorized Access!' }, { status: 401 }); + +// // Pass user data in request headers for further processing in API route +// req.headers.set('user', JSON.stringify(user)); - return NextResponse.next(); - } catch (err) { - console.log(err); - return NextResponse.json({ message: 'Error occurred!' }, { status: 500 }); - } -} - -export const config = { - matcher: ["/dashboard"], -}; +// return NextResponse.next(); +// } catch (err) { +// console.log(err); +// return NextResponse.json({ message: 'Error occurred!' }, { status: 500 }); +// } +// } + +// export const config = { +// matcher: ['/api/:path*'], +// }; diff --git a/src/Middleware/AuthMiddleware.ts b/src/Middleware/AuthMiddleware.ts new file mode 100644 index 0000000..6eafe08 --- /dev/null +++ b/src/Middleware/AuthMiddleware.ts @@ -0,0 +1,31 @@ +import { NextResponse } from "next/server"; +import jwt from "jsonwebtoken"; +import User from "@/models/user"; +import { mongoDB } from "@/lib/MongoDB"; + +export async function AuthMiddleware(req: any) { + try { + const token = req.cookies.get('accessToken') || req.headers.get('Authorization')?.replace('Bearer ', ''); + + if (!token || token === undefined) { + return NextResponse.json({ error: 'Unauthorized Access!' }, { status: 401 }); + } + + const decodedToken:any = jwt.verify(token, process.env.NEXTAUTH_SECRET as string); + + await mongoDB(); + const user = await User.findById(decodedToken._id).select('-password -refreshToken'); + + if (!user) { + return NextResponse.json({ error: 'Unauthorized Access!' }, { status: 401 }); + } + + // Pass user data in request headers for further processing in API route + req.headers.set('user', JSON.stringify(user)); + + return NextResponse.next(); + } catch (err) { + console.log(err); + return NextResponse.json({ message: 'Error occurred!' }, { status: 500 }); + } +} diff --git a/src/app/api/files/create/route.ts b/src/app/api/files/create/route.ts new file mode 100644 index 0000000..2dc1e39 --- /dev/null +++ b/src/app/api/files/create/route.ts @@ -0,0 +1,35 @@ +import { mongoDB } from "@/lib/MongoDB"; +import { AuthMiddleware } from "@/Middleware/AuthMiddleware"; +import FileModel from "@/models/file"; +import { ApiUser } from "@/types/types"; +import { NextResponse } from "next/server"; + +export const POST = async (req: Request) => { + + const result = await AuthMiddleware(req); + + if (result instanceof NextResponse) { + + try { + const { fileName, filePrivate } = await req.json(); + + await mongoDB(); + + const user: ApiUser = JSON.parse(req.headers.get("user") || "{}"); + + const file = await FileModel.create({ + fileName, + filePrivate, + createdBy:user._id, + readBy:[user._id], + writtenBy:[user._id] + }); + + return NextResponse.json({ status: 200 }); + } catch (err) { + return NextResponse.json(`Err : ${err}`, {status:500}); + } + } else { + return result; + } +}; diff --git a/src/app/api/files/private/route.ts b/src/app/api/files/private/route.ts index d114b20..39f3a42 100644 --- a/src/app/api/files/private/route.ts +++ b/src/app/api/files/private/route.ts @@ -22,4 +22,4 @@ export const PUT = async(req: Request) => { } catch (err) { console.log(err) } -} +} \ No newline at end of file diff --git a/src/app/api/health/route.ts b/src/app/api/health/route.ts new file mode 100644 index 0000000..99a27af --- /dev/null +++ b/src/app/api/health/route.ts @@ -0,0 +1,19 @@ +import { AuthMiddleware } from "@/Middleware/AuthMiddleware"; +import { NextRequest } from "next/server"; +import { NextResponse } from "next/server"; + +export const GET = async (req: NextRequest, res: NextResponse) => { + try { + const result = await AuthMiddleware(req); + // If middleware returns NextResponse.next(), proceed with API logic + if (result.status==200) { + return NextResponse.json({ status: "UP" }, { status: 200 }); + } else { + // Handle any errors from the middleware + return result; + } + } catch (error) { + console.error("Error in health endpoint:", error); + return NextResponse.json({ message: 'Error occurred!' }, { status: 500 }); + } +}; \ No newline at end of file diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx index 6d740c5..1baf6a7 100644 --- a/src/app/dashboard/layout.tsx +++ b/src/app/dashboard/layout.tsx @@ -13,14 +13,14 @@ import { useMutation } from "convex/react"; import Image from "next/image"; import { SessionProvider, useSession } from "next-auth/react"; import Link from "next/link"; -import { logIn } from "../Redux/Auth/auth-slice"; + function DashboardLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { - const {data:session,status} = useSession(); + const { data: session, status } = useSession(); const createTeam = useMutation(api.teams.createTeam); const convex = useConvex(); const [fileList_, setFileList_] = useState(); @@ -30,22 +30,6 @@ function DashboardLayout({ const [hasCheckedTeam, setHasCheckedTeam] = useState(false); const dispatch = useDispatch(); - // useEffect(() => { - // if (session) { - // dispatch( - // logIn({ - // id: session.user.id, - // accessToken: session.user.accessToken, - // refreshToken: session.user.refreshToken, - // email: session.user.email, - // firstName: session.user.firstName, - // lastName: session.user.lastName, - // image:session.user.image - // }) - // ); - // } - // }, [session]); - useEffect(() => { if (session && !hasCheckedTeam) { checkTeam(); @@ -88,40 +72,38 @@ function DashboardLayout({ />
-

Welcome!

+

+ Welcome! +

To access this page, please{" "}

Don't have an account? - - Signup - + Signup

); - return (!loading || session === undefined) ? ( + return !loading || session === undefined ? (
- -
-
- + +
+
+ +
+
{children}
-
{children}
-
- +
) : ( diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 63ace03..914120f 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -15,6 +15,10 @@ import Link from "next/link"; import InviteModal from "@/components/shared/InviteModal"; import JoinTeamModal from "@/components/shared/JoinTeamModal"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import axiosProtectedInstance from "@/config/AxiosProtectedRoute"; +import { checkHealthUrl } from "@/lib/API-URLs"; +import createAxiosInstance from "@/config/AxiosProtectedRoute"; +import { useRouter } from "next/navigation"; export interface FILE { archive: boolean; @@ -39,8 +43,19 @@ function Dashboard() { const dispatch = useDispatch(); const [userData, setUserdata] = useState(); const user = useSelector((state:RootState) => state.auth.user); + const router = useRouter() - + const accessToken = useSelector((state:RootState)=>state.auth.user.accessToken); + + const axiosInstance = createAxiosInstance(accessToken); + + useEffect(() => { + const checkHealth = async () => { + const res = await axiosInstance.get(checkHealthUrl); + if(res.status !== 200) router.push('/signin'); + }; + checkHealth(); + }, []); const checkUser = async () => { const result = await convex.query(api.user.getUser, { email: user?.email }); diff --git a/src/config/AxiosInstance.ts b/src/config/AxiosInstance.ts index 7a202a4..417e4c7 100644 --- a/src/config/AxiosInstance.ts +++ b/src/config/AxiosInstance.ts @@ -5,6 +5,7 @@ const axiosInstance = axios.create({ timeout: 10000, // Request timeout headers: { 'Content-Type': 'application/json', + }, }); diff --git a/src/config/AxiosProtectedRoute.ts b/src/config/AxiosProtectedRoute.ts new file mode 100644 index 0000000..68f2b4a --- /dev/null +++ b/src/config/AxiosProtectedRoute.ts @@ -0,0 +1,33 @@ +import axios, { AxiosInstance } from 'axios'; + +const createAxiosInstance = (accessToken?: string): AxiosInstance => { + if (accessToken === undefined) { + throw new Error('Access token is required to create an authenticated Axios instance.'); + } + + const instance = axios.create({ + baseURL: process.env.NEXT_PUBLIC_CLIENT_URL, // Replace with your API base URL + timeout: 10000, // Request timeout + headers: { + 'Content-Type': 'application/json', + // Optionally add other headers + }, + }); + + // Request interceptor to include Bearer token if accessToken is provided + instance.interceptors.request.use( + (config) => { + if (accessToken) { + config.headers.Authorization = `Bearer ${accessToken}`; + } + return config; + }, + (error) => { + return Promise.reject(error); + } + ); + + return instance; +}; + +export default createAxiosInstance; diff --git a/src/lib/API-URLs.ts b/src/lib/API-URLs.ts index d6c108c..ecb034f 100644 --- a/src/lib/API-URLs.ts +++ b/src/lib/API-URLs.ts @@ -4,4 +4,5 @@ export const getTeamMembersData = "/api/teams/members"; export const deleteTeamMemberUrl = "/api/teams/members"; export const updateReadAccessUrl = "/api/files/read"; export const updateWriteAccessUrl = "/api/files/write"; -export const registerUserUrl = "/api/auth/register"; \ No newline at end of file +export const registerUserUrl = "/api/auth/register"; +export const checkHealthUrl = "/api/health"; \ No newline at end of file diff --git a/src/models/file.ts b/src/models/file.ts index 950b06d..d3ece66 100644 --- a/src/models/file.ts +++ b/src/models/file.ts @@ -6,11 +6,9 @@ interface File { archive: boolean; document: string; whiteboard: string; - private: boolean; + filePrivate: boolean; writtenBy: any[]; readBy: any[]; - write: boolean; - read: boolean; } const FileSchema = new Schema( @@ -20,11 +18,9 @@ const FileSchema = new Schema( archive: { type: Boolean }, document: { type: String }, whiteboard: { type: String }, - private: { type: Boolean, required: true }, + filePrivate: { type: Boolean, required: true }, writtenBy: [{ type: Schema.Types.ObjectId, required: true, ref: "User" }], readBy: [{ type: Schema.Types.ObjectId, required: true, ref: "User" }], - write: { type: Boolean }, - read: { type: Boolean }, }, { timestamps: true } ); diff --git a/src/models/user.ts b/src/models/user.ts index 69c3808..9e85b6b 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -1,7 +1,7 @@ import mongoose, { Schema } from "mongoose"; import jwt from "jsonwebtoken" -interface User { +export interface User { email: string; firstName: string; lastName: string; diff --git a/src/types/types.d.ts b/src/types/types.d.ts index ece19dd..c0a717c 100644 --- a/src/types/types.d.ts +++ b/src/types/types.d.ts @@ -1,3 +1,5 @@ +import { User } from "@/models/user"; + type USER = { isAuth: boolean; id:string; @@ -7,4 +9,8 @@ type USER = { refreshToken:string; accessToken:string; image:string | undefined; - }; \ No newline at end of file + }; + +interface ApiUser extends User{ + _id:string; +} \ No newline at end of file From d7f52245ac5f888d8e3599950208cb95f564db20 Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Wed, 10 Jul 2024 19:33:34 +0530 Subject: [PATCH 2/3] fix: middlware --- middleware.ts | 36 ------------------------------------ 1 file changed, 36 deletions(-) delete mode 100644 middleware.ts diff --git a/middleware.ts b/middleware.ts deleted file mode 100644 index 637e0ea..0000000 --- a/middleware.ts +++ /dev/null @@ -1,36 +0,0 @@ -// import { NextResponse } from "next/server"; -// import type { NextRequest } from "next/server"; -// import jwt from "jsonwebtoken"; -// import User from "@/models/user"; -// import { mongoDB } from "@/lib/MongoDB"; - -// export async function middleware(req: NextRequest) { -// try { -// const token : any = req.cookies.get('accessToken') || req.headers.get('Authorization')?.replace('Bearer ', ''); -// console.log(token) - -// if (!token) { -// return NextResponse.json({ error: 'Unauthorized Access!' }, { status: 401 }); -// } - - -// const decodedToken : any = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET as string); - -// await mongoDB(); -// const user = await User.findById(decodedToken._id).select('-password -refreshToken'); - -// if (!user) return NextResponse.json({ error: 'Unauthorized Access!' }, { status: 401 }); - -// // Pass user data in request headers for further processing in API route -// req.headers.set('user', JSON.stringify(user)); - -// return NextResponse.next(); -// } catch (err) { -// console.log(err); -// return NextResponse.json({ message: 'Error occurred!' }, { status: 500 }); -// } -// } - -// export const config = { -// matcher: ['/api/:path*'], -// }; From f7142ae54cbc80cb6e41b21e19ad841be801e3e1 Mon Sep 17 00:00:00 2001 From: Sid-80 Date: Wed, 10 Jul 2024 19:39:51 +0530 Subject: [PATCH 3/3] fix : logout --- src/app/dashboard/page.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 914120f..b4df977 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -19,6 +19,7 @@ import axiosProtectedInstance from "@/config/AxiosProtectedRoute"; import { checkHealthUrl } from "@/lib/API-URLs"; import createAxiosInstance from "@/config/AxiosProtectedRoute"; import { useRouter } from "next/navigation"; +import { logOut } from "../Redux/Auth/auth-slice"; export interface FILE { archive: boolean; @@ -52,7 +53,10 @@ function Dashboard() { useEffect(() => { const checkHealth = async () => { const res = await axiosInstance.get(checkHealthUrl); - if(res.status !== 200) router.push('/signin'); + if(res.status !== 200) { + router.push('/signin'); + dispatch(logOut()) + }; }; checkHealth(); }, []);