diff --git a/src/core/session.rs b/src/core/session.rs index 32a7cc9..a933d7f 100644 --- a/src/core/session.rs +++ b/src/core/session.rs @@ -1,5 +1,3 @@ -use std::env; - use crate::{ errors::{Error, Result}, models::session_model::SessionResponse, @@ -278,52 +276,85 @@ impl Session { pub async fn get_all(mongo_client: &Client) -> Result> { let db = mongo_client.database("auth"); let collection_session: Collection = db.collection("sessions"); - let collection_dek: Collection = db.collection("deks"); - let mut cursor_dek = collection_dek.find(None, None).await.unwrap(); + // get all the sessions + let mut cursor = collection_session.find(None, None).await.unwrap(); let mut sessions = Vec::new(); - let kek = env::var("SERVER_KEK").expect("Server Kek must be set."); - // iterate over the sessions and decrypt the data - while let Some(dek) = cursor_dek.next().await { - let dek_data: Dek = match dek { - Ok(data) => data.decrypt(&kek), - Err(_) => { - return Err(Error::ServerError { - message: "Failed to get DEK".to_string(), - }); - } - }; - - // find the session in the sessions collection using the encrypted email to iterate over the sessions - let cursor_session = collection_session - .find_one( - Some(doc! { - "uid": &dek_data.uid, - }), - None, - ) - .await - .unwrap(); + while let Some(session) = cursor.next().await { + match session { + Ok(data) => { + let dek_data = match Dek::get(mongo_client, &data.uid).await { + Ok(dek) => dek, + Err(e) => return Err(e), + }; - match cursor_session { - Some(session) => { - let session_data = session.decrypt(&dek_data.dek); + let decrypted_session = data.decrypt(&dek_data.dek); sessions.push(SessionResponse { - uid: session_data.uid, - session_id: session_data.session_id, - email: session_data.email, - user_agent: session_data.user_agent, - is_revoked: session_data.is_revoked, - created_at: session_data.created_at, - updated_at: session_data.updated_at, + uid: decrypted_session.uid, + session_id: decrypted_session.session_id, + email: decrypted_session.email, + user_agent: decrypted_session.user_agent, + is_revoked: decrypted_session.is_revoked, + created_at: decrypted_session.created_at, + updated_at: decrypted_session.updated_at, + }); + } + Err(_) => { + return Err(Error::ServerError { + message: "Failed to get session".to_string(), }); } - None => {()} } } + // let collection_dek: Collection = db.collection("deks"); + + // let mut cursor_dek = collection_dek.find(None, None).await.unwrap(); + + // let mut sessions = Vec::new(); + // let kek = env::var("SERVER_KEK").expect("Server Kek must be set."); + + // // iterate over the sessions and decrypt the data + // while let Some(dek) = cursor_dek.next().await { + // let dek_data: Dek = match dek { + // Ok(data) => data.decrypt(&kek), + // Err(_) => { + // return Err(Error::ServerError { + // message: "Failed to get DEK".to_string(), + // }); + // } + // }; + + // // find the session in the sessions collection using the encrypted email to iterate over the sessions + // let cursor_session = collection_session + // .find_one( + // Some(doc! { + // "uid": &dek_data.uid, + // }), + // None, + // ) + // .await + // .unwrap(); + + // match cursor_session { + // Some(session) => { + // let session_data = session.decrypt(&dek_data.dek); + + // sessions.push(SessionResponse { + // uid: session_data.uid, + // session_id: session_data.session_id, + // email: session_data.email, + // user_agent: session_data.user_agent, + // is_revoked: session_data.is_revoked, + // created_at: session_data.created_at, + // updated_at: session_data.updated_at, + // }); + // } + // None => {()} + // } + // } // sort the sessions by created_at sessions.sort_by(|a, b| a.created_at.cmp(&b.created_at)); diff --git a/ui/app/api/session/get-all/route.ts b/ui/app/api/session/get-all/route.ts new file mode 100644 index 0000000..0ab2143 --- /dev/null +++ b/ui/app/api/session/get-all/route.ts @@ -0,0 +1,20 @@ +export async function GET(req: Request) { + const endPoint: (string | undefined) = `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/session/get-all`; + + if (endPoint) { + try { + const res = await fetch(endPoint, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', // Set the appropriate content type for your request + 'x-api-key': process.env.X_API_KEY!, + }, + cache: 'no-cache', + }); + const data = await res.json(); + return Response.json({ data }) + } catch (error) { + console.error('Error during request:', error); + } + } +} \ No newline at end of file diff --git a/ui/app/layout.tsx b/ui/app/layout.tsx index 54748f6..f9482b3 100644 --- a/ui/app/layout.tsx +++ b/ui/app/layout.tsx @@ -32,7 +32,7 @@ export default function RootLayout({
-
+
{children}
diff --git a/ui/app/sessions/page.tsx b/ui/app/sessions/page.tsx new file mode 100644 index 0000000..c59020e --- /dev/null +++ b/ui/app/sessions/page.tsx @@ -0,0 +1,14 @@ +"use client" + +import SessionTableAll from '@/components/session/SessionTableAll' +import React from 'react' + +const Sessions = () => { + return ( +
+ +
+ ) +} + +export default Sessions \ No newline at end of file diff --git a/ui/app/user/[userID]/page.tsx b/ui/app/users/[userID]/page.tsx similarity index 99% rename from ui/app/user/[userID]/page.tsx rename to ui/app/users/[userID]/page.tsx index cdbdd52..a24c4a2 100644 --- a/ui/app/user/[userID]/page.tsx +++ b/ui/app/users/[userID]/page.tsx @@ -29,7 +29,7 @@ import { GoClockFill } from "react-icons/go"; import { TiTick } from "react-icons/ti"; import { IoIosWarning } from "react-icons/io"; import { format } from "date-fns"; -import SessionTable from "@/components/session/SessionTable"; +import SessionTableUser from "@/components/session/SessionTableUser"; import PasswordInput from "@/components/ui/passwordInput"; import { FaRegCopy } from "react-icons/fa"; import useCopy from "@/hooks/useCopy"; @@ -529,7 +529,7 @@ const UserDetails = ({ params }: any) => {
- + )} diff --git a/ui/app/user/page.tsx b/ui/app/users/page.tsx similarity index 100% rename from ui/app/user/page.tsx rename to ui/app/users/page.tsx diff --git a/ui/components/Users/Users.tsx b/ui/components/Users/Users.tsx index 6baf583..f128549 100644 --- a/ui/components/Users/Users.tsx +++ b/ui/components/Users/Users.tsx @@ -70,7 +70,7 @@ const Users = () => { cell: ({ row }) => { const user = row.original; return ( -
router.push(`/user/${user.uid}`)}> +
router.push(`/users/${user.uid}`)}>
{user.name}
{ + const [loading, setLoading] = React.useState(false); + const [sessions, setSessions] = React.useState([]); + + // fetch all sessions + const fetchAllSessions = useCallback(async () => { + try { + setLoading(true); + const res = await fetch( + `${process.env.NEXT_PUBLIC_ENDPOINT}/api/session/get-all`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + cache: "no-cache", + } + ); + const { data } = await res.json(); + setSessions(data); + } catch (error) { + console.error("Error during POST request:", error); + } + setLoading(false); + }, []); + + // revoke session function + const revokeSession = async (session_id: string, uid: string) => { + try { + setLoading(true); + await fetch(`${process.env.NEXT_PUBLIC_ENDPOINT}/api/session/revoke`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + session_id, + uid, + }), + }); + await fetchAllSessions(); + } catch (error) { + console.error("Error during POST request:", error); + } + setLoading(false); + }; + + // delete session function + const deleteSession = async (session_id: string, uid: string) => { + try { + setLoading(true); + await fetch(`${process.env.NEXT_PUBLIC_ENDPOINT}/api/session/delete`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + session_id, + uid, + }), + }); + await fetchAllSessions(); + } catch (error) { + console.error("Error during POST request:", error); + } + setLoading(false); + }; + + useEffect(() => { + fetchAllSessions(); + }, [fetchAllSessions]); + + const parser = new UAParser("user-agent"); + + const sessionColumns: ColumnDef[] = [ + { + accessorKey: "user_agent", + header: "User Agent", + cell: ({ row }) => { + parser.setUA(row.original.user_agent); + const result = parser.getResult(); + + console.log(result); + + return ( + // render device type and browser name with logo +
+
+
+ {result.device.type === "mobile" && + ( + device-logo + )} + {result.device.type === undefined && + ( + device-logo + )} +
+
+

{result.device?.vendor} - {result.device?.model}

+

{result.os?.name} - Version: {result.os?.version ? result.os.version : "Unknown"}

+
+ {result.browser.name?.includes("Chrome") && ( + browser-logo + )} + {result.browser.name?.includes("Mozilla") && ( + browser-logo + )} + {result.browser.name?.includes("Safari") && ( + browser-logo + )} +

{result.browser?.name} - Version: {result.browser?.version}

+
+
+
+ +
+ ); + }, + }, + { + accessorKey: "updated_at", + header: "Updated At", + cell: ({ row }) => { + return ( +
+ { + format( + parseInt(row.original.updated_at.$date.$numberLong) + , "PP - p" + ) + } +
+ ); + }, + }, + { + accessorKey: "created_at", + header: "Expires At", + cell: ({ row }) => { + return ( +
+ { + format(addDays( + parseInt(row.original.created_at.$date.$numberLong) + , 45), "PP - p") + } +
+ ); + }, + }, + { + accessorKey: "is_revoked", + header: "Revoked", + cell: ({ row }) => { + return ( +
+ {row.original.is_revoked ? "Yes" : "No"} +
+ ); + }, + }, + { + accessorKey: "action", + header: "Action", + cell: ({ row }) => { + const session = row.original; + return ( +
+ + + + + + {!row.original.is_revoked && ( + + + + Revoke + + + + + Are you absolutely sure? + + + This action cannot be undone. + + + + Cancel + + + + + + )} + + + + Delete + + + + + Are you absolutely sure? + + + This action cannot be undone. + + + + Cancel + + + + + + + +
+ ); + }, + }, + ]; + + return ( + + ); +}; + +export default SessionTableAll; diff --git a/ui/components/session/SessionTable.tsx b/ui/components/session/SessionTableUser.tsx similarity index 100% rename from ui/components/session/SessionTable.tsx rename to ui/components/session/SessionTableUser.tsx diff --git a/ui/components/ui/data-table.tsx b/ui/components/ui/data-table.tsx index 67cd37f..7f15f8c 100644 --- a/ui/components/ui/data-table.tsx +++ b/ui/components/ui/data-table.tsx @@ -40,57 +40,55 @@ export function DataTable({ }); return ( -
-
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ); - })} - - ))} - - - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() +
+
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() )} - - ))} - - )) - ) : ( - - - No results. - + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} - )} - -
-
+ )) + ) : ( + + + No results. + + + )} + +
); } diff --git a/ui/constants/appconstants.tsx b/ui/constants/appconstants.tsx index 529fe15..e7d6d84 100644 --- a/ui/constants/appconstants.tsx +++ b/ui/constants/appconstants.tsx @@ -1,6 +1,7 @@ import { IPages } from "@/components/shared/Sidebar/Sidebar"; import { FaUsers } from "react-icons/fa"; import { IoStatsChartSharp } from "react-icons/io5"; +import { FaClockRotateLeft } from "react-icons/fa6"; export const AppPages: IPages[] = [ { @@ -12,7 +13,13 @@ export const AppPages: IPages[] = [ { name: "Users", icon: , - link: '/user', + link: '/users', showOnSidebar: true, }, + { + name: "Sessions", + icon: , + link: '/sessions', + showOnSidebar: true, + } ] \ No newline at end of file diff --git a/ui/interfaces/ISession.ts b/ui/interfaces/ISession.ts index b88e2ff..55903cb 100644 --- a/ui/interfaces/ISession.ts +++ b/ui/interfaces/ISession.ts @@ -1,6 +1,7 @@ export interface ISession { session_id: string; email: string; + uid: string; user_agent: string; is_revoked: boolean; created_at: DateRecord;