Repository: Infisical/infisical
Pull request #3489: feat: add user get token CLI and revamp session management
Author: login: sheensantoscapadngan
Created: 2025-04-25T12:45:08Z | Merged: 2025-04-25T15:45:38Z

# Description 📣
This PR adds the ability for users to export user token using the CLI
![image](https://github.com/user-attachments/assets/dbbb64fb-61a3-446f-a255-eb319ce5aec6)

This also revamps session management section of personal settings.

**Before**
![image](https://github.com/user-attachments/assets/0b63ff71-ac32-4fb0-83a4-3f57e58db489)

**After**
![image](https://github.com/user-attachments/assets/750f4ffe-5042-421a-827d-2f89b84679fe)

<!-- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. Here's how we expect a pull request to be : https://infisical.com/docs/contributing/getting-started/pull-requests -->

## Type ✨

- [ ] Bug fix
- [ ] New feature
- [ ] Improvement
- [ ] Breaking change
- [ ] Documentation

# Tests 🛠️

<!-- Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. You may want to add screenshots when relevant and possible -->

```sh
# Here's some code block to paste some code snippets
```

---

- [ ] I have read the [contributing guide](https://infisical.com/docs/contributing/getting-started/overview), agreed and acknowledged the [code of conduct](https://infisical.com/docs/contributing/getting-started/code-of-conduct). 📝

<!-- If you have any questions regarding contribution, here's the FAQ : https://infisical.com/docs/contributing/getting-started/faq -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added the ability for users to revoke individual sessions directly from the personal settings page, with confirmation and notifications.
  - Introduced a CLI command to retrieve the current user's session ID and access token.
  - Enabled targeted session revocation by session ID via a new API endpoint.

- **Improvements**
  - Enhanced session table UI to display operating system, browser, and device details, along with improved date formatting and tooltips.
  - Updated documentation to include new CLI commands and improved formatting for command examples.
  - Session lists are now ordered by most recently used.

- **Chores**
  - Added internal utilities for parsing user agent strings and managing session revocation state.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/backend/src/server/routes/v2/user-router.ts b/backend/src/server/routes/v2/user-router.ts
index 851d9c4ff30..027f527fc5b 100644
--- a/backend/src/server/routes/v2/user-router.ts
+++ b/backend/src/server/routes/v2/user-router.ts
@@ -252,6 +252,31 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
     }
   });
 
+  server.route({
+    method: "DELETE",
+    url: "/me/sessions/:sessionId",
+    config: {
+      rateLimit: writeLimit
+    },
+    schema: {
+      params: z.object({
+        sessionId: z.string().trim()
+      }),
+      response: {
+        200: z.object({
+          message: z.string()
+        })
+      }
+    },
+    onRequest: verifyAuth([AuthMode.JWT]),
+    handler: async (req) => {
+      await server.services.authToken.revokeMySessionById(req.permission.id, req.params.sessionId);
+      return {
+        message: "Successfully revoked session"
+      };
+    }
+  });
+
   server.route({
     method: "GET",
     url: "/me",
diff --git a/backend/src/services/auth-token/auth-token-dal.ts b/backend/src/services/auth-token/auth-token-dal.ts
index 221b691cfd2..ca7841d8ee7 100644
--- a/backend/src/services/auth-token/auth-token-dal.ts
+++ b/backend/src/services/auth-token/auth-token-dal.ts
@@ -47,7 +47,10 @@ export const tokenDALFactory = (db: TDbClient) => {
 
   const findTokenSessions = async (filter: Partial<TAuthTokenSessions>, tx?: Knex) => {
     try {
-      const sessions = await (tx || db.replicaNode())(TableName.AuthTokenSession).where(filter);
+      const sessions = await (tx || db.replicaNode())(TableName.AuthTokenSession)
+        .where(filter)
+        .orderBy("lastUsed", "desc");
+
       return sessions;
     } catch (error) {
       throw new DatabaseError({ name: "Find all token session", error });
diff --git a/backend/src/services/auth-token/auth-token-service.ts b/backend/src/services/auth-token/auth-token-service.ts
index 2468e3c8a89..f264643403a 100644
--- a/backend/src/services/auth-token/auth-token-service.ts
+++ b/backend/src/services/auth-token/auth-token-service.ts
@@ -151,6 +151,9 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAu
 
   const revokeAllMySessions = async (userId: string) => tokenDAL.deleteTokenSession({ userId });
 
+  const revokeMySessionById = async (userId: string, sessionId: string) =>
+    tokenDAL.deleteTokenSession({ userId, id: sessionId });
+
   const validateRefreshToken = async (refreshToken?: string) => {
     const appCfg = getConfig();
     if (!refreshToken)
@@ -223,6 +226,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAu
     clearTokenSessionById,
     getTokenSessionByUser,
     revokeAllMySessions,
+    revokeMySessionById,
     validateRefreshToken,
     fnValidateJwtIdentity,
     getUserTokenSessionById
diff --git a/cli/packages/cmd/user.go b/cli/packages/cmd/user.go
index d3e6096a9b8..2879e4ccb3a 100644
--- a/cli/packages/cmd/user.go
+++ b/cli/packages/cmd/user.go
@@ -1,9 +1,12 @@
 package cmd
 
 import (
+	"encoding/base64"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"net/url"
+	"strings"
 
 	"github.com/Infisical/infisical-merge/packages/config"
 	"github.com/Infisical/infisical-merge/packages/models"
@@ -85,6 +88,57 @@ var switchCmd = &cobra.Command{
 	},
 }
 
+var userGetCmd = &cobra.Command{
+	Use:                   "get",
+	Short:                 "Used to get properties of an Infisical profile",
+	DisableFlagsInUseLine: true,
+	Example:               "infisical user get",
+	Args:                  cobra.ExactArgs(0),
+	Run: func(cmd *cobra.Command, args []string) {
+		cmd.Help()
+	},
+}
+
+var userGetTokenCmd = &cobra.Command{
+	Use:                   "token",
+	Short:                 "Used to get the access token of an Infisical user",
+	DisableFlagsInUseLine: true,
+	Example:               "infisical user get token",
+	Args:                  cobra.ExactArgs(0),
+	PreRun: func(cmd *cobra.Command, args []string) {
+		util.RequireLogin()
+	},
+	Run: func(cmd *cobra.Command, args []string) {
+		loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails(true)
+		if loggedInUserDetails.LoginExpired {
+			util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
+		}
+		if err != nil {
+			util.HandleError(err, "[infisical user get token]: Unable to get logged in user token")
+		}
+
+		tokenParts := strings.Split(loggedInUserDetails.UserCredentials.JTWToken, ".")
+		if len(tokenParts) != 3 {
+			util.HandleError(errors.New("invalid token format"), "[infisical user get token]: Invalid token format")
+		}
+
+		payload, err := base64.RawURLEncoding.DecodeString(tokenParts[1])
+		if err != nil {
+			util.HandleError(err, "[infisical user get token]: Unable to decode token payload")
+		}
+
+		var tokenPayload struct {
+			TokenVersionId string `json:"tokenVersionId"`
+		}
+		if err := json.Unmarshal(payload, &tokenPayload); err != nil {
+			util.HandleError(err, "[infisical user get token]: Unable to parse token payload")
+		}
+
+		fmt.Println("Session ID:", tokenPayload.TokenVersionId)
+		fmt.Println("Token:", loggedInUserDetails.UserCredentials.JTWToken)
+	},
+}
+
 var updateCmd = &cobra.Command{
 	Use:                   "update",
 	Short:                 "Used to update properties of an Infisical profile",
@@ -185,6 +239,8 @@ var domainCmd = &cobra.Command{
 func init() {
 	updateCmd.AddCommand(domainCmd)
 	userCmd.AddCommand(updateCmd)
+	userGetCmd.AddCommand(userGetTokenCmd)
+	userCmd.AddCommand(userGetCmd)
 	userCmd.AddCommand(switchCmd)
 	rootCmd.AddCommand(userCmd)
 }
diff --git a/docs/cli/commands/user.mdx b/docs/cli/commands/user.mdx
index 38a92bbab30..43a6111e1a3 100644
--- a/docs/cli/commands/user.mdx
+++ b/docs/cli/commands/user.mdx
@@ -8,22 +8,46 @@ infisical user
 ```
 
 ## Description
+
 This command allows you to manage the current logged in users on the CLI
 
-### Sub-commands 
+### Sub-commands
+
 <Accordion title="infisical user switch" defaultOpen="true">
-  Use this command to switch between profiles that are currently logged into the CLI 
+  Use this command to switch between profiles that are currently logged into the CLI
+
+```bash
+infisical user switch
+```
 
-  ```bash
-  infisical user switch
-  ```
 </Accordion>
 
 <Accordion title="infisical user update domain">
   With this command, you can modify the backend API that is utilized for all requests associated with a specific profile. 
   For instance, you have the option to point the profile to use either the Infisical Cloud or your own self-hosted Infisical instance.
 
-  ```bash 
-  infisical user update domain
+```bash
+infisical user update domain
+```
+
+</Accordion>
+
+<Accordion title="infisical user get token">
+  Use this command to get your current Infisical access token and session information. This command requires you to be logged in.
+
+  The command will display:
+
+  - Your session ID
+  - Your full JWT access token
+
+  ```bash
+  infisical user get token
+  ```
+
+  Example output:
+
+  ```bash
+  Session ID: abc123-xyz-456
+  Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
   ```
 </Accordion>
diff --git a/frontend/src/hooks/api/users/index.tsx b/frontend/src/hooks/api/users/index.tsx
index b9d9f159b84..0774275f966 100644
--- a/frontend/src/hooks/api/users/index.tsx
+++ b/frontend/src/hooks/api/users/index.tsx
@@ -1,6 +1,7 @@
 export {
   useAddUserToWsE2EE,
   useAddUserToWsNonE2EE,
+  useRevokeMySessionById,
   useSendEmailVerificationCode,
   useVerifyEmailVerificationCode
 } from "./mutation";
diff --git a/frontend/src/hooks/api/users/mutation.tsx b/frontend/src/hooks/api/users/mutation.tsx
index c5cf6c27b3a..1b873b31cb0 100644
--- a/frontend/src/hooks/api/users/mutation.tsx
+++ b/frontend/src/hooks/api/users/mutation.tsx
@@ -171,3 +171,16 @@ export const useResendOrgMemberInvitation = () => {
     }
   });
 };
+
+export const useRevokeMySessionById = () => {
+  const queryClient = useQueryClient();
+  return useMutation({
+    mutationFn: async (sessionId: string) => {
+      const { data } = await apiRequest.delete(`/api/v2/users/me/sessions/${sessionId}`);
+      return data;
+    },
+    onSuccess() {
+      queryClient.invalidateQueries({ queryKey: userKeys.mySessions });
+    }
+  });
+};
diff --git a/frontend/src/lib/fn/string.ts b/frontend/src/lib/fn/string.ts
index f4c4a43db50..6b2842701c8 100644
--- a/frontend/src/lib/fn/string.ts
+++ b/frontend/src/lib/fn/string.ts
@@ -11,3 +11,65 @@ export const formatReservedPaths = (secretPath: string) => {
 export const camelCaseToSpaces = (input: string) => {
   return input.replace(/([a-z])([A-Z])/g, "$1 $2");
 };
+
+export const formatSessionUserAgent = (userAgent: string) => {
+  const result = {
+    os: "Unknown",
+    browser: "Unknown",
+    device: "Desktop"
+  };
+
+  // Operating System detection
+  if (userAgent.includes("Windows")) {
+    result.os = "Windows";
+  } else if (
+    userAgent.includes("Mac OS") ||
+    userAgent.includes("Macintosh") ||
+    userAgent.includes("macOS")
+  ) {
+    result.os = "macOS";
+  } else if (userAgent.includes("Linux") && !userAgent.includes("Android")) {
+    result.os = "Linux";
+  } else if (userAgent.includes("Android")) {
+    result.os = "Android";
+    result.device = "Mobile";
+  } else if (
+    userAgent.includes("iOS") ||
+    userAgent.includes("iPhone") ||
+    userAgent.includes("iPad")
+  ) {
+    result.os = "iOS";
+    result.device = userAgent.includes("iPad") ? "Tablet" : "Mobile";
+  }
+
+  // Browser detection
+  if (userAgent.includes("Firefox/")) {
+    result.browser = "Firefox";
+  } else if (userAgent.includes("Edge/") || userAgent.includes("Edg/")) {
+    result.browser = "Edge";
+  } else if (userAgent.includes("Brave/") || userAgent.includes("Brave ")) {
+    result.browser = "Brave";
+  } else if (
+    userAgent.includes("Chrome/") &&
+    !userAgent.includes("Chromium/") &&
+    !userAgent.includes("Edg/")
+  ) {
+    result.browser = "Chrome";
+  } else if (
+    userAgent.includes("Safari/") &&
+    !userAgent.includes("Chrome/") &&
+    !userAgent.includes("Chromium/")
+  ) {
+    result.browser = "Safari";
+  } else if (userAgent.includes("Opera/") || userAgent.includes("OPR/")) {
+    result.browser = "Opera";
+  } else if (userAgent.includes("Trident/") || userAgent.includes("MSIE")) {
+    result.browser = "Internet Explorer";
+  }
+
+  if (userAgent.toLowerCase() === "cli") {
+    result.browser = "CLI";
+  }
+
+  return result;
+};
diff --git a/frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx b/frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx
index 9a24b1e2701..8d10be31808 100644
--- a/frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx
+++ b/frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx
@@ -1,6 +1,8 @@
 import { faServer } from "@fortawesome/free-solid-svg-icons";
 
+import { createNotification } from "@app/components/notifications";
 import {
+  DeleteActionModal,
   EmptyState,
   Table,
   TableContainer,
@@ -9,59 +11,131 @@ import {
   Td,
   Th,
   THead,
+  Tooltip,
   Tr
 } from "@app/components/v2";
-import { useGetMySessions } from "@app/hooks/api";
+import { Button } from "@app/components/v2/Button";
+import { useGetMySessions, useRevokeMySessionById } from "@app/hooks/api";
+import { usePopUp } from "@app/hooks/usePopUp";
+import { timeAgo } from "@app/lib/fn/date";
+import { formatSessionUserAgent } from "@app/lib/fn/string";
+
+const formatLocalDateTime = (date: Date): string => {
+  return date.toLocaleString(undefined, {
+    weekday: "long",
+    year: "numeric",
+    month: "long",
+    day: "numeric",
+    hour: "2-digit",
+    minute: "2-digit",
+    second: "2-digit"
+  });
+};
 
 export const SessionsTable = () => {
   const { data, isPending } = useGetMySessions();
+  const { mutateAsync: revokeMySessionById } = useRevokeMySessionById();
+  const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
+    "deleteSession"
+  ] as const);
 
-  const formatDate = (dateToFormat: string) => {
-    const date = new Date(dateToFormat);
-    const year = date.getFullYear();
-    const month = date.getMonth() + 1;
-    const day = date.getDate();
+  const handleSignOut = async (sessionId: string) => {
+    try {
+      await revokeMySessionById(sessionId);
+      createNotification({
+        text: "Session revoked successfully",
+        type: "success"
+      });
+    } catch (error) {
+      console.error(error);
+      createNotification({
+        text: "Failed to revoke session",
+        type: "error"
+      });
+    }
 
-    const formattedDate = `${day}/${month}/${year}`;
-
-    return formattedDate;
+    handlePopUpClose("deleteSession");
   };
 
   return (
-    <TableContainer className="mt-4">
-      <Table>
-        <THead>
-          <Tr>
-            <Th>Created</Th>
-            <Th>Last active</Th>
-            <Th>IP address</Th>
-            <Th>Device</Th>
-          </Tr>
-        </THead>
-        <TBody>
-          {isPending && <TableSkeleton columns={4} innerKey="sesssions" />}
-          {!isPending &&
-            data &&
-            data.length > 0 &&
-            data.map(({ id, createdAt, lastUsed, ip, userAgent }) => {
-              return (
-                <Tr className="h-10" key={`session-${id}`}>
-                  <Td>{formatDate(createdAt)}</Td>
-                  <Td>{formatDate(lastUsed)}</Td>
-                  <Td>{ip}</Td>
-                  <Td>{userAgent}</Td>
-                </Tr>
-              );
-            })}
-          {!isPending && data && data?.length === 0 && (
+    <>
+      <DeleteActionModal
+        isOpen={popUp.deleteSession.isOpen}
+        title="Are you sure want to sign out of this session?"
+        onChange={(isOpen) => handlePopUpToggle("deleteSession", isOpen)}
+        deleteKey="confirm"
+        onDeleteApproved={() =>
+          handleSignOut((popUp?.deleteSession?.data as { sessionId: string })?.sessionId)
+        }
+      />
+      <TableContainer className="mt-4">
+        <Table>
+          <THead>
             <Tr>
-              <Td colSpan={4}>
-                <EmptyState title="No sessions on file" icon={faServer} />
-              </Td>
+              <Th>IP & Session ID</Th>
+              <Th>OS & Browser</Th>
+              <Th>Last accessed</Th>
+              <Th>Manage</Th>
             </Tr>
-          )}
-        </TBody>
-      </Table>
-    </TableContainer>
+          </THead>
+          <TBody>
+            {isPending && <TableSkeleton columns={4} innerKey="sessions" />}
+            {!isPending &&
+              data &&
+              data.length > 0 &&
+              data.map(({ id, createdAt, lastUsed, ip, userAgent }) => {
+                const { os, browser } = formatSessionUserAgent(userAgent);
+                const lastUsedDate = new Date(lastUsed);
+                const createdAtDate = new Date(createdAt);
+
+                return (
+                  <Tr className="h-20" key={`session-${id}`}>
+                    <Td>
+                      <div className="flex flex-col">
+                        <span className="font-medium">{ip}</span>
+                        <span className="text-sm text-gray-500">ID: {id}</span>
+                      </div>
+                    </Td>
+                    <Td>
+                      <div className="flex flex-col">
+                        <span className="font-medium">{os}</span>
+                        <span className="text-sm text-gray-500">{browser}</span>
+                      </div>
+                    </Td>
+                    <Td>
+                      <div className="flex flex-col">
+                        <Tooltip content={formatLocalDateTime(lastUsedDate)}>
+                          <span className="font-medium">{timeAgo(lastUsedDate, new Date())}</span>
+                        </Tooltip>
+                        <Tooltip content={formatLocalDateTime(createdAtDate)}>
+                          <span className="text-sm text-gray-500">
+                            Created {timeAgo(createdAtDate, new Date())}
+                          </span>
+                        </Tooltip>
+                      </div>
+                    </Td>
+                    <Td>
+                      <Button
+                        variant="plain"
+                        colorSchema="danger"
+                        onClick={() => handlePopUpOpen("deleteSession", { sessionId: id })}
+                      >
+                        Sign out
+                      </Button>
+                    </Td>
+                  </Tr>
+                );
+              })}
+            {!isPending && data && data?.length === 0 && (
+              <Tr>
+                <Td colSpan={4}>
+                  <EmptyState title="No sessions on file" icon={faServer} />
+                </Td>
+              </Tr>
+            )}
+          </TBody>
+        </Table>
+      </TableContainer>
+    </>
   );
 };


Reviews:
user: login: greptile-apps[bot]
body: 
## PR Summary

This PR adds a new CLI command to export user tokens and revamps the session management UI with enhanced functionality and improved user experience.

- Security: The `formatSessionUserAgent` function in `frontend/src/lib/fn/string.ts` uses native regex (`replace(/([a-z])([A-Z])/g, "$1 $2")`) which should be replaced with RE2 to prevent potential ReDoS attacks
- The new `/me/sessions/:sessionId` DELETE endpoint in `user-router.ts` should validate that the session ID format matches expected UUID/pattern before processing
- The JWT token display in CLI output (`user get token` command) should include a warning about token sensitivity and proper handling
- The session management UI should consider adding rate limiting on the client side for session revocation requests to prevent abuse
- Documentation should be added in `/docs` folder explaining how customers can discover and use the new session management features in the UI

<sub>9 file(s) reviewed, 8 comment(s)</sub>
<sub>[Edit PR Review Bot Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews&utm_content=infisical_3489)</sub>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T12:47:26Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>frontend/src/lib/fn/string.ts (1)</summary><blockquote>

`15-75`: **Comprehensive user agent parsing, but consider future maintainability.**

The `formatSessionUserAgent` function provides thorough detection for common operating systems and browsers, enhancing the session management UI. However, the cascading if-else structure might become harder to maintain as more platforms need to be supported.




Consider refactoring to a more maintainable pattern such as regex-based detection or a mapping object:

```diff
export const formatSessionUserAgent = (userAgent: string) => {
  const result = {
    os: "Unknown",
    browser: "Unknown",
    device: "Desktop"
  };

-  // Operating System detection
-  if (userAgent.includes("Windows")) {
-    result.os = "Windows";
-  } else if (
-    userAgent.includes("Mac OS") ||
-    userAgent.includes("Macintosh") ||
-    userAgent.includes("macOS")
-  ) {
-    result.os = "macOS";
-  } else if (userAgent.includes("Linux") && !userAgent.includes("Android")) {
-    result.os = "Linux";
-  } else if (userAgent.includes("Android")) {
-    result.os = "Android";
-    result.device = "Mobile";
-  } else if (
-    userAgent.includes("iOS") ||
-    userAgent.includes("iPhone") ||
-    userAgent.includes("iPad")
-  ) {
-    result.os = "iOS";
-    result.device = userAgent.includes("iPad") ? "Tablet" : "Mobile";
-  }
+  // Operating System detection
+  const osPatterns = [
+    { pattern: "Windows", name: "Windows" },
+    { pattern: /(Mac OS|Macintosh|macOS)/, name: "macOS" },
+    { pattern: /Linux(?!.*Android)/, name: "Linux" },
+    { pattern: "Android", name: "Android", device: "Mobile" },
+    { pattern: /(iOS|iPhone|iPad)/, name: "iOS", device: (ua: string) => ua.includes("iPad") ? "Tablet" : "Mobile" }
+  ];
+
+  for (const { pattern, name, device } of osPatterns) {
+    const regex = pattern instanceof RegExp ? pattern : new RegExp(pattern);
+    if (regex.test(userAgent)) {
+      result.os = name;
+      if (device) {
+        result.device = typeof device === "function" ? device(userAgent) : device;
+      }
+      break;
+    }
+  }

  // Browser detection (similar refactoring could be applied)
  // ...

  return result;
};
```

</blockquote></details>
<details>
<summary>docs/cli/commands/user.mdx (1)</summary><blockquote>

`11-12`: **Fix grammar in description.**

When 'logged-in' is used as a modifier, it should be hyphenated.

```diff
-This command allows you to manage the current logged in users on the CLI
+This command allows you to manage the current logged-in users on the CLI
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[grammar] ~12-~12: When ‘logged-in’ is used as a modifier, it is usually spelled with a hyphen.
Context: ...ommand allows you to manage the current logged in users on the CLI  ### Sub-commands  <Ac...

(LOGGED_IN_HYPHEN)

</details>

</details>

</blockquote></details>
<details>
<summary>frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx (2)</summary><blockquote>

`82-90`: **Handle missing or malformed date and user-agent data gracefully**

`lastUsed`, `createdAt`, or `userAgent` may be empty/null (e.g., very old tokens or CLI sessions). Defensive parsing prevents “Invalid Date” or runtime errors.

```diff
-                 const { os, browser } = formatSessionUserAgent(userAgent);
-                 const lastUsedDate = new Date(lastUsed);
-                 const createdAtDate = new Date(createdAt);
+                 const { os, browser } = formatSessionUserAgent(userAgent ?? "");
+                 const lastUsedDate = lastUsed ? new Date(lastUsed) : null;
+                 const createdAtDate = createdAt ? new Date(createdAt) : null;
```

Remember to null-check in the markup (`{lastUsedDate && …}`) so tooltips don’t render `Invalid Date`.

---

`118-124`: **Disable the “Sign out” button while the mutation is pending**

This prevents double-clicks that fire multiple DELETE requests.

```diff
-                      <Button
+                      <Button
                         variant="plain"
                         colorSchema="danger"
+                        disabled={isRevoking}
                         onClick={() => handlePopUpOpen("deleteSession", { sessionId: id })}
                       >
                         Sign out
                       </Button>
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between f5bb0d4a8696d399c42b007bd090a37228defa96 and b0288c49c0797a8fe3346d93aa72b52e0da890ab.

</details>

<details>
<summary>📒 Files selected for processing (9)</summary>

* `backend/src/server/routes/v2/user-router.ts` (1 hunks)
* `backend/src/services/auth-token/auth-token-dal.ts` (1 hunks)
* `backend/src/services/auth-token/auth-token-service.ts` (2 hunks)
* `cli/packages/cmd/user.go` (3 hunks)
* `docs/cli/commands/user.mdx` (1 hunks)
* `frontend/src/hooks/api/users/index.tsx` (1 hunks)
* `frontend/src/hooks/api/users/mutation.tsx` (1 hunks)
* `frontend/src/lib/fn/string.ts` (1 hunks)
* `frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx` (2 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>backend/src/server/routes/v2/user-router.ts (1)</summary><blockquote>

<details>
<summary>backend/src/server/config/rateLimiter.ts (1)</summary>

* `writeLimit` (36-41)

</details>

</blockquote></details>
<details>
<summary>cli/packages/cmd/user.go (2)</summary><blockquote>

<details>
<summary>cli/packages/util/helper.go (1)</summary>

* `RequireLogin` (172-179)

</details>
<details>
<summary>cli/packages/util/credentials.go (1)</summary>

* `GetCurrentLoggedInUserDetails` (57-127)

</details>

</blockquote></details>

</details><details>
<summary>🪛 LanguageTool</summary>

<details>
<summary>docs/cli/commands/user.mdx</summary>

[grammar] ~12-~12: When ‘logged-in’ is used as a modifier, it is usually spelled with a hyphen.
Context: ...ommand allows you to manage the current logged in users on the CLI  ### Sub-commands  <Ac...

(LOGGED_IN_HYPHEN)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (4)</summary>

* GitHub Check: Check API Changes
* GitHub Check: Check TS and Lint
* GitHub Check: Run integration test
* GitHub Check: Check Frontend Type and Lint check

</details>

<details>
<summary>🔇 Additional comments (12)</summary><blockquote>

<details>
<summary>backend/src/services/auth-token/auth-token-service.ts (2)</summary>

`154-155`: **Well-implemented session revocation function.**

The `revokeMySessionById` method follows the same clean pattern as the existing `revokeAllMySessions` function, but adds more granular control by allowing revocation of a specific session.

---

`229-229`: **Proper service method export.**

The new function is correctly exported as part of the service object, making it accessible to consumers of this service.

</details>
<details>
<summary>frontend/src/hooks/api/users/index.tsx (1)</summary>

`4-4`: **Properly exposed new hook for session management.**

The `useRevokeMySessionById` hook is correctly exported, making it available for use in the session management UI components.

</details>
<details>
<summary>frontend/src/hooks/api/users/mutation.tsx (1)</summary>

`175-186`: **Well-implemented mutation hook for session revocation.**

The hook follows React Query best practices:
1. Uses `useMutation` with proper typing
2. Handles the DELETE request to the correct endpoint
3. Invalidates the sessions cache on success to keep the UI in sync

</details>
<details>
<summary>backend/src/services/auth-token/auth-token-dal.ts (1)</summary>

`50-53`: **Improved user experience with ordered sessions.**

The addition of ordering sessions by `lastUsed` in descending order enhances the user experience by displaying the most recently used sessions first. This change supports the revamped session management mentioned in the PR objectives.

</details>
<details>
<summary>docs/cli/commands/user.mdx (2)</summary>

`14-33`: **Improved formatting for command examples.**

The addition of code blocks for the command examples improves readability and consistency in the documentation.

---

`35-52`: **Well-documented CLI token command.**

The documentation for the new `infisical user get token` command is clear, concise, and follows the same format as other commands. It correctly describes the command purpose, requirements, output format, and provides helpful examples.

</details>
<details>
<summary>backend/src/server/routes/v2/user-router.ts (1)</summary>

`255-278`: **Well-implemented individual session revocation endpoint.**

This new endpoint provides targeted session management capability, allowing users to revoke specific sessions by their ID. The implementation follows established patterns with appropriate authentication, validation, and rate limiting.

</details>
<details>
<summary>cli/packages/cmd/user.go (4)</summary>

`3-9`: **Appropriate imports for JWT token handling.**

The addition of imports for base64 decoding and JSON parsing is necessary for extracting the session ID from the JWT token.

---

`91-100`: **Well-structured command hierarchy with parent command.**

The addition of a parent `get` command follows good CLI design patterns and allows for future expansion of get-related commands.

---

`102-140`: **Secure and user-friendly token retrieval command.**

The implementation correctly:
1. Requires the user to be logged in
2. Handles expired login sessions
3. Parses the JWT token to extract the session ID
4. Provides clear output with both session ID and full token

This implementation enables the user to get their token via CLI as described in the PR objectives.

---

`242-244`: **Proper command registration in command hierarchy.**

The new commands are correctly added to the command hierarchy in the `init()` function.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-25T12:49:30Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (4)</summary><blockquote>

<details>
<summary>frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx (4)</summary><blockquote>

`17-21`: _🛠️ Refactor suggestion_

**Import `useQueryClient` to enable cache invalidation after session revocation**

The component imports the necessary hooks for data fetching and mutation, but it's missing `useQueryClient` which is needed to refresh the sessions list after successful revocation. Without this, the UI will show stale data until the next refresh.


```diff
 import { Button } from "@app/components/v2/Button";
 import { useGetMySessions, useRevokeMySessionById } from "@app/hooks/api";
+import { useQueryClient } from "@tanstack/react-query";
 import { usePopUp } from "@app/hooks/usePopUp";
 import { timeAgo } from "@app/lib/fn/date";
 import { formatSessionUserAgent } from "@app/lib/fn/string";
```

---

`35-41`: _🛠️ Refactor suggestion_

**Add query client initialization and expose mutation loading state**

The component should initialize the query client and track the loading state of the revocation mutation. This enables cache invalidation and improves UX by disabling controls during operations.


```diff
 export const SessionsTable = () => {
   const { data, isPending } = useGetMySessions();
-  const { mutateAsync: revokeMySessionById } = useRevokeMySessionById();
+  const queryClient = useQueryClient();
+  const { mutateAsync: revokeMySessionById, isPending: isRevoking } = useRevokeMySessionById();
   const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
     "deleteSession"
   ] as const);
```

---

`42-58`: _🛠️ Refactor suggestion_

**Invalidate the sessions cache after successful revocation**

After successfully revoking a session, the cached session data should be invalidated to trigger a refetch. Without this, the table will continue to display the revoked session until the page is refreshed.


```diff
   const handleSignOut = async (sessionId: string) => {
     try {
       await revokeMySessionById(sessionId);
+      queryClient.invalidateQueries({ queryKey: ["mySessions"] });
       createNotification({
         text: "Session revoked successfully",
         type: "success"
       });
```

---

`67-69`: _⚠️ Potential issue_

**Fix potential runtime error when handling undefined data**

The current type assertion could cause a runtime error if `popUp.deleteSession.data` is undefined. Add a null check to prevent crashes.


```diff
         onDeleteApproved={() =>
-          handleSignOut((popUp?.deleteSession?.data as { sessionId: string })?.sessionId)
+          popUp.deleteSession.data && 
+          handleSignOut((popUp.deleteSession.data as { sessionId: string }).sessionId)
         }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b0288c49c0797a8fe3346d93aa72b52e0da890ab and 178acc412d3c9603435fdd798fbec24d93eb4d46.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx` (2 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (5)</summary>

* GitHub Check: Run integration test
* GitHub Check: Check Frontend Type and Lint check
* GitHub Check: Check TS and Lint
* GitHub Check: test
* GitHub Check: Check API Changes

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx (5)</summary>

`23-33`: **Good implementation of date formatting utility function**

The `formatLocalDateTime` function follows best practices by using locale-aware formatting and providing comprehensive date-time information, which enhances the user experience.

---

`107-114`: **Excellent use of tooltips for detailed timestamp information**

The implementation of tooltips for timestamps is a great UX improvement. It provides users with both a relative time ("2 hours ago") and allows them to see the exact date and time on hover.

---

`87-90`: **Nice use of destructuring and date handling**

Good practice using destructuring to extract OS and browser information, and properly converting string dates to Date objects for formatting.

---

`93-98`: **Well-structured session information display**

The session information is well organized with clear visual hierarchy - the IP address is prominently displayed with the session ID as supplementary information.

---

`129-135`: **Good empty state handling**

The empty state is handled appropriately with a clear message and icon, providing good user feedback when no sessions are available.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-25T12:55:34Z
user: login: carlosmonastyrski
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T15:28:24Z
user: login: carlosmonastyrski
body: LGTM, tested it with two accounts locally and worked smoothly, great job Sheen! 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T15:40:04Z

Review comments:
diff_hunk: @@ -85,6 +88,57 @@ var switchCmd = &cobra.Command{
 	},
 }
 
+var userGetCmd = &cobra.Command{
+	Use:                   "get",
+	Short:                 "Used to get properties of an Infisical profile",
+	DisableFlagsInUseLine: true,
+	Example:               "infisical user get",
+	Args:                  cobra.ExactArgs(0),
+	Run: func(cmd *cobra.Command, args []string) {
+		cmd.Help()
+	},
+}
+
+var userGetTokenCmd = &cobra.Command{
+	Use:                   "token",
+	Short:                 "Used to get the access token of an Infisical user",
+	DisableFlagsInUseLine: true,
+	Example:               "infisical user get token",
+	Args:                  cobra.ExactArgs(0),
+	PreRun: func(cmd *cobra.Command, args []string) {
+		util.RequireLogin()
+	},
+	Run: func(cmd *cobra.Command, args []string) {
+		loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails(true)
+		if loggedInUserDetails.LoginExpired {
+			util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
+		}
+		if err != nil {
+			util.HandleError(err, "[infisical user get token]: Unable to get logged in user token")
+		}
+
+		tokenParts := strings.Split(loggedInUserDetails.UserCredentials.JTWToken, ".")
path: cli/packages/cmd/user.go
user: login: greptile-apps[bot]
body: syntax: typo in variable name 'JTWToken' should be 'JWTToken'

```suggestion
		tokenParts := strings.Split(loggedInUserDetails.UserCredentials.JWTToken, ".")
```
created_at: 2025-04-25T12:45:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -85,6 +88,57 @@ var switchCmd = &cobra.Command{
 	},
 }
 
+var userGetCmd = &cobra.Command{
+	Use:                   "get",
+	Short:                 "Used to get properties of an Infisical profile",
+	DisableFlagsInUseLine: true,
+	Example:               "infisical user get",
+	Args:                  cobra.ExactArgs(0),
+	Run: func(cmd *cobra.Command, args []string) {
+		cmd.Help()
+	},
+}
+
+var userGetTokenCmd = &cobra.Command{
+	Use:                   "token",
+	Short:                 "Used to get the access token of an Infisical user",
+	DisableFlagsInUseLine: true,
+	Example:               "infisical user get token",
+	Args:                  cobra.ExactArgs(0),
+	PreRun: func(cmd *cobra.Command, args []string) {
+		util.RequireLogin()
+	},
+	Run: func(cmd *cobra.Command, args []string) {
+		loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails(true)
+		if loggedInUserDetails.LoginExpired {
+			util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
+		}
+		if err != nil {
+			util.HandleError(err, "[infisical user get token]: Unable to get logged in user token")
+		}
+
+		tokenParts := strings.Split(loggedInUserDetails.UserCredentials.JTWToken, ".")
+		if len(tokenParts) != 3 {
+			util.HandleError(errors.New("invalid token format"), "[infisical user get token]: Invalid token format")
+		}
+
+		payload, err := base64.RawURLEncoding.DecodeString(tokenParts[1])
+		if err != nil {
+			util.HandleError(err, "[infisical user get token]: Unable to decode token payload")
+		}
+
+		var tokenPayload struct {
+			TokenVersionId string `json:"tokenVersionId"`
+		}
+		if err := json.Unmarshal(payload, &tokenPayload); err != nil {
+			util.HandleError(err, "[infisical user get token]: Unable to parse token payload")
+		}
+
+		fmt.Println("Session ID:", tokenPayload.TokenVersionId)
+		fmt.Println("Token:", loggedInUserDetails.UserCredentials.JTWToken)
path: cli/packages/cmd/user.go
user: login: greptile-apps[bot]
body: style: printing sensitive token information to stdout could expose tokens in logs. Consider adding a warning about token sensitivity
created_at: 2025-04-25T12:45:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -252,6 +252,31 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
     }
   });
 
+  server.route({
+    method: "DELETE",
+    url: "/me/sessions/:sessionId",
+    config: {
+      rateLimit: writeLimit
+    },
+    schema: {
+      params: z.object({
+        sessionId: z.string().trim()
+      }),
+      response: {
+        200: z.object({
+          message: z.string()
+        })
+      }
+    },
+    onRequest: verifyAuth([AuthMode.JWT]),
path: backend/src/server/routes/v2/user-router.ts
user: login: greptile-apps[bot]
body: style: Consider adding API_KEY auth mode here for consistency with other routes that manage user data
created_at: 2025-04-25T12:45:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -252,6 +252,31 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
     }
   });
 
+  server.route({
+    method: "DELETE",
+    url: "/me/sessions/:sessionId",
+    config: {
+      rateLimit: writeLimit
path: backend/src/server/routes/v2/user-router.ts
user: login: greptile-apps[bot]
body: style: Consider using authRateLimit instead of writeLimit for session management operations
created_at: 2025-04-25T12:45:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -8,22 +8,46 @@ infisical user
 ```
 
 ## Description
+
 This command allows you to manage the current logged in users on the CLI
 
-### Sub-commands 
+### Sub-commands
+
 <Accordion title="infisical user switch" defaultOpen="true">
-  Use this command to switch between profiles that are currently logged into the CLI 
+  Use this command to switch between profiles that are currently logged into the CLI
+
+```bash
+infisical user switch
+```
 
-  ```bash
-  infisical user switch
-  ```
 </Accordion>
 
 <Accordion title="infisical user update domain">
   With this command, you can modify the backend API that is utilized for all requests associated with a specific profile. 
   For instance, you have the option to point the profile to use either the Infisical Cloud or your own self-hosted Infisical instance.
 
-  ```bash 
-  infisical user update domain
+```bash
+infisical user update domain
+```
+
+</Accordion>
+
+<Accordion title="infisical user get token">
+  Use this command to get your current Infisical access token and session information. This command requires you to be logged in.
+
+  The command will display:
+
+  - Your session ID
+  - Your full JWT access token
+
+  ```bash
+  infisical user get token
+  ```
+
+  Example output:
+
+  ```bash
+  Session ID: abc123-xyz-456
+  Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
   ```
path: docs/cli/commands/user.mdx
user: login: greptile-apps[bot]
body: style: Example output shows sensitive information. Consider using a more obviously fake token format like 'eyJ...XXXX'
created_at: 2025-04-25T12:46:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,3 +11,65 @@ export const formatReservedPaths = (secretPath: string) => {
 export const camelCaseToSpaces = (input: string) => {
   return input.replace(/([a-z])([A-Z])/g, "$1 $2");
path: frontend/src/lib/fn/string.ts
user: login: greptile-apps[bot]
body: logic: Replace native regex with RE2 to prevent ReDoS vulnerability

```suggestion
  return re2Instance.replace(input, "([a-z])([A-Z])", "$1 $2");
```
created_at: 2025-04-25T12:46:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,3 +11,65 @@ export const formatReservedPaths = (secretPath: string) => {
 export const camelCaseToSpaces = (input: string) => {
   return input.replace(/([a-z])([A-Z])/g, "$1 $2");
 };
+
+export const formatSessionUserAgent = (userAgent: string) => {
+  const result = {
+    os: "Unknown",
+    browser: "Unknown",
+    device: "Desktop"
+  };
+
+  // Operating System detection
+  if (userAgent.includes("Windows")) {
+    result.os = "Windows";
+  } else if (
+    userAgent.includes("Mac OS") ||
+    userAgent.includes("Macintosh") ||
+    userAgent.includes("macOS")
+  ) {
+    result.os = "macOS";
+  } else if (userAgent.includes("Linux") && !userAgent.includes("Android")) {
+    result.os = "Linux";
+  } else if (userAgent.includes("Android")) {
+    result.os = "Android";
+    result.device = "Mobile";
+  } else if (
+    userAgent.includes("iOS") ||
+    userAgent.includes("iPhone") ||
+    userAgent.includes("iPad")
+  ) {
+    result.os = "iOS";
+    result.device = userAgent.includes("iPad") ? "Tablet" : "Mobile";
+  }
+
+  // Browser detection
+  if (userAgent.includes("Firefox/")) {
+    result.browser = "Firefox";
+  } else if (userAgent.includes("Edge/") || userAgent.includes("Edg/")) {
+    result.browser = "Edge";
+  } else if (userAgent.includes("Brave/") || userAgent.includes("Brave ")) {
+    result.browser = "Brave";
+  } else if (
+    userAgent.includes("Chrome/") &&
+    !userAgent.includes("Chromium/") &&
+    !userAgent.includes("Edg/")
+  ) {
+    result.browser = "Chrome";
+  } else if (
+    userAgent.includes("Safari/") &&
+    !userAgent.includes("Chrome/") &&
+    !userAgent.includes("Chromium/")
+  ) {
+    result.browser = "Safari";
+  } else if (userAgent.includes("Opera/") || userAgent.includes("OPR/")) {
+    result.browser = "Opera";
+  } else if (userAgent.includes("Trident/") || userAgent.includes("MSIE")) {
+    result.browser = "Internet Explorer";
+  }
+
+  if (userAgent.toLowerCase() === "cli") {
+    result.browser = "CLI";
+  }
path: frontend/src/lib/fn/string.ts
user: login: greptile-apps[bot]
body: style: CLI check should come first to avoid unnecessary user agent parsing for CLI requests

```suggestion
  if (userAgent.toLowerCase() === "cli") {
    return {
      os: "Unknown",
      browser: "CLI",
      device: "Desktop"
    };
  }
```
created_at: 2025-04-25T12:46:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9,59 +11,131 @@ import {
   Td,
   Th,
   THead,
+  Tooltip,
   Tr
 } from "@app/components/v2";
-import { useGetMySessions } from "@app/hooks/api";
+import { Button } from "@app/components/v2/Button";
+import { useGetMySessions, useRevokeMySessionById } from "@app/hooks/api";
+import { usePopUp } from "@app/hooks/usePopUp";
+import { timeAgo } from "@app/lib/fn/date";
+import { formatSessionUserAgent } from "@app/lib/fn/string";
+
+const formatLocalDateTime = (date: Date): string => {
+  return date.toLocaleString(undefined, {
+    weekday: "long",
+    year: "numeric",
+    month: "long",
+    day: "numeric",
+    hour: "2-digit",
+    minute: "2-digit",
+    second: "2-digit"
+  });
+};
 
 export const SessionsTable = () => {
   const { data, isPending } = useGetMySessions();
+  const { mutateAsync: revokeMySessionById } = useRevokeMySessionById();
+  const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
+    "deleteSession"
+  ] as const);
 
-  const formatDate = (dateToFormat: string) => {
-    const date = new Date(dateToFormat);
-    const year = date.getFullYear();
-    const month = date.getMonth() + 1;
-    const day = date.getDate();
+  const handleSignOut = async (sessionId: string) => {
+    try {
+      await revokeMySessionById(sessionId);
+      createNotification({
+        text: "Session revoked successfully",
+        type: "success"
+      });
+    } catch (error) {
+      console.error(error);
+      createNotification({
+        text: "Failed to revoke session",
+        type: "error"
+      });
+    }
 
-    const formattedDate = `${day}/${month}/${year}`;
-
-    return formattedDate;
+    handlePopUpClose("deleteSession");
   };
 
   return (
-    <TableContainer className="mt-4">
-      <Table>
-        <THead>
-          <Tr>
-            <Th>Created</Th>
-            <Th>Last active</Th>
-            <Th>IP address</Th>
-            <Th>Device</Th>
-          </Tr>
-        </THead>
-        <TBody>
-          {isPending && <TableSkeleton columns={4} innerKey="sesssions" />}
-          {!isPending &&
-            data &&
-            data.length > 0 &&
-            data.map(({ id, createdAt, lastUsed, ip, userAgent }) => {
-              return (
-                <Tr className="h-10" key={`session-${id}`}>
-                  <Td>{formatDate(createdAt)}</Td>
-                  <Td>{formatDate(lastUsed)}</Td>
-                  <Td>{ip}</Td>
-                  <Td>{userAgent}</Td>
-                </Tr>
-              );
-            })}
-          {!isPending && data && data?.length === 0 && (
+    <>
+      <DeleteActionModal
+        isOpen={popUp.deleteSession.isOpen}
+        title="Are you sure want to sign out of this session?"
+        onChange={(isOpen) => handlePopUpToggle("deleteSession", isOpen)}
+        deleteKey="confirm"
+        onDeleteApproved={() =>
+          handleSignOut((popUp?.deleteSession?.data as { sessionId: string }).sessionId)
+        }
path: frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx
user: login: greptile-apps[bot]
body: logic: Type assertion on popUp.deleteSession.data could cause runtime error if data is undefined. Consider adding null check

```suggestion
        onDeleteApproved={() => {
          const sessionId = popUp?.deleteSession?.data?.sessionId;
          if (sessionId) handleSignOut(sessionId);
        }}
```
created_at: 2025-04-25T12:47:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9,59 +11,131 @@ import {
   Td,
   Th,
   THead,
+  Tooltip,
   Tr
 } from "@app/components/v2";
-import { useGetMySessions } from "@app/hooks/api";
+import { Button } from "@app/components/v2/Button";
+import { useGetMySessions, useRevokeMySessionById } from "@app/hooks/api";
+import { usePopUp } from "@app/hooks/usePopUp";
+import { timeAgo } from "@app/lib/fn/date";
+import { formatSessionUserAgent } from "@app/lib/fn/string";
path: frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Import `useQueryClient` to refresh the sessions list after a successful revocation**

`handleSignOut` currently shows a toast but does not refresh the cached `useGetMySessions` data, so the revoked row remains visible until a manual refresh. Adding React-Query’s `useQueryClient` lets you invalidate the list immediately.

```diff
 import { Button } from "@app/components/v2/Button";
 import { useGetMySessions, useRevokeMySessionById } from "@app/hooks/api";
+import { useQueryClient } from "@tanstack/react-query";
 import { usePopUp } from "@app/hooks/usePopUp";
 import { timeAgo } from "@app/lib/fn/date";
 import { formatSessionUserAgent } from "@app/lib/fn/string";
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import { Button } from "@app/components/v2/Button";
import { useGetMySessions, useRevokeMySessionById } from "@app/hooks/api";
import { useQueryClient } from "@tanstack/react-query";
import { usePopUp } from "@app/hooks/usePopUp";
import { timeAgo } from "@app/lib/fn/date";
import { formatSessionUserAgent } from "@app/lib/fn/string";
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-25T12:49:28Z
author_association: NONE
diff_hunk: @@ -9,59 +11,131 @@
   Td,
   Th,
   THead,
+  Tooltip,
   Tr
 } from "@app/components/v2";
-import { useGetMySessions } from "@app/hooks/api";
+import { Button } from "@app/components/v2/Button";
+import { useGetMySessions, useRevokeMySessionById } from "@app/hooks/api";
+import { usePopUp } from "@app/hooks/usePopUp";
+import { timeAgo } from "@app/lib/fn/date";
+import { formatSessionUserAgent } from "@app/lib/fn/string";
+
+const formatLocalDateTime = (date: Date): string => {
+  return date.toLocaleString(undefined, {
+    weekday: "long",
+    year: "numeric",
+    month: "long",
+    day: "numeric",
+    hour: "2-digit",
+    minute: "2-digit",
+    second: "2-digit"
+  });
+};
 
 export const SessionsTable = () => {
   const { data, isPending } = useGetMySessions();
+  const { mutateAsync: revokeMySessionById } = useRevokeMySessionById();
+  const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
+    "deleteSession"
+  ] as const);
 
-  const formatDate = (dateToFormat: string) => {
-    const date = new Date(dateToFormat);
-    const year = date.getFullYear();
-    const month = date.getMonth() + 1;
-    const day = date.getDate();
+  const handleSignOut = async (sessionId: string) => {
+    try {
+      await revokeMySessionById(sessionId);
+      createNotification({
+        text: "Session revoked successfully",
+        type: "success"
+      });
+    } catch (error) {
+      console.error(error);
+      createNotification({
+        text: "Failed to revoke session",
+        type: "error"
+      });
+    }
 
-    const formattedDate = `${day}/${month}/${year}`;
-
-    return formattedDate;
+    handlePopUpClose("deleteSession");
   };
path: frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Invalidate the sessions cache right after a successful revocation**

Without invalidation the table shows stale data. Trigger a refetch (or update cache manually) once the mutation resolves.

```diff
       await revokeMySessionById(sessionId);
+      queryClient.invalidateQueries({ queryKey: ["mySessions"] });
       createNotification({
         text: "Session revoked successfully",
         type: "success"
       });
```

(Replace `"mySessions"` with the actual key used inside `useGetMySessions` if different.)



> Committable suggestion skipped: line range outside the PR's diff.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-25T12:49:29Z
author_association: NONE
diff_hunk: @@ -9,59 +11,131 @@
   Td,
   Th,
   THead,
+  Tooltip,
   Tr
 } from "@app/components/v2";
-import { useGetMySessions } from "@app/hooks/api";
+import { Button } from "@app/components/v2/Button";
+import { useGetMySessions, useRevokeMySessionById } from "@app/hooks/api";
+import { usePopUp } from "@app/hooks/usePopUp";
+import { timeAgo } from "@app/lib/fn/date";
+import { formatSessionUserAgent } from "@app/lib/fn/string";
+
+const formatLocalDateTime = (date: Date): string => {
+  return date.toLocaleString(undefined, {
+    weekday: "long",
+    year: "numeric",
+    month: "long",
+    day: "numeric",
+    hour: "2-digit",
+    minute: "2-digit",
+    second: "2-digit"
+  });
+};
 
 export const SessionsTable = () => {
   const { data, isPending } = useGetMySessions();
+  const { mutateAsync: revokeMySessionById } = useRevokeMySessionById();
+  const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
+    "deleteSession"
+  ] as const);
 
path: frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Wire the query client and expose the mutation loading state**

Inject the query client and surface the mutation status; this enables automatic UI refresh and disables controls while the call is in-flight.

```diff
-  const { data, isPending } = useGetMySessions();
-  const { mutateAsync: revokeMySessionById } = useRevokeMySessionById();
+  const queryClient = useQueryClient();
+  const { data, isPending } = useGetMySessions();
+  const {
+    mutateAsync: revokeMySessionById,
+    isPending: isRevoking
+  } = useRevokeMySessionById();
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
export const SessionsTable = () => {
-  const { data, isPending } = useGetMySessions();
-  const { mutateAsync: revokeMySessionById } = useRevokeMySessionById();
+  const queryClient = useQueryClient();
+  const { data, isPending } = useGetMySessions();
+  const {
+    mutateAsync: revokeMySessionById,
+    isPending: isRevoking
+  } = useRevokeMySessionById();

  const {
    popUp,
    handlePopUpOpen,
    handlePopUpClose,
    handlePopUpToggle
  } = usePopUp([
    "deleteSession"
  ] as const);
  // …
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-25T12:49:29Z
author_association: NONE
diff_hunk: @@ -9,59 +11,131 @@
   Td,
   Th,
   THead,
+  Tooltip,
   Tr
 } from "@app/components/v2";
-import { useGetMySessions } from "@app/hooks/api";
+import { Button } from "@app/components/v2/Button";
+import { useGetMySessions, useRevokeMySessionById } from "@app/hooks/api";
+import { usePopUp } from "@app/hooks/usePopUp";
+import { timeAgo } from "@app/lib/fn/date";
+import { formatSessionUserAgent } from "@app/lib/fn/string";
+
+const formatLocalDateTime = (date: Date): string => {
+  return date.toLocaleString(undefined, {
+    weekday: "long",
+    year: "numeric",
+    month: "long",
+    day: "numeric",
+    hour: "2-digit",
+    minute: "2-digit",
+    second: "2-digit"
+  });
+};
 
 export const SessionsTable = () => {
   const { data, isPending } = useGetMySessions();
+  const { mutateAsync: revokeMySessionById } = useRevokeMySessionById();
+  const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
+    "deleteSession"
+  ] as const);
 
-  const formatDate = (dateToFormat: string) => {
-    const date = new Date(dateToFormat);
-    const year = date.getFullYear();
-    const month = date.getMonth() + 1;
-    const day = date.getDate();
+  const handleSignOut = async (sessionId: string) => {
+    try {
+      await revokeMySessionById(sessionId);
+      createNotification({
+        text: "Session revoked successfully",
+        type: "success"
+      });
+    } catch (error) {
+      console.error(error);
+      createNotification({
+        text: "Failed to revoke session",
+        type: "error"
+      });
+    }
 
-    const formattedDate = `${day}/${month}/${year}`;
-
-    return formattedDate;
+    handlePopUpClose("deleteSession");
   };
 
   return (
-    <TableContainer className="mt-4">
-      <Table>
-        <THead>
-          <Tr>
-            <Th>Created</Th>
-            <Th>Last active</Th>
-            <Th>IP address</Th>
-            <Th>Device</Th>
-          </Tr>
-        </THead>
-        <TBody>
-          {isPending && <TableSkeleton columns={4} innerKey="sesssions" />}
-          {!isPending &&
-            data &&
-            data.length > 0 &&
-            data.map(({ id, createdAt, lastUsed, ip, userAgent }) => {
-              return (
-                <Tr className="h-10" key={`session-${id}`}>
-                  <Td>{formatDate(createdAt)}</Td>
-                  <Td>{formatDate(lastUsed)}</Td>
-                  <Td>{ip}</Td>
-                  <Td>{userAgent}</Td>
-                </Tr>
-              );
-            })}
-          {!isPending && data && data?.length === 0 && (
+    <>
+      <DeleteActionModal
+        isOpen={popUp.deleteSession.isOpen}
+        title="Are you sure want to sign out of this session?"
+        onChange={(isOpen) => handlePopUpToggle("deleteSession", isOpen)}
+        deleteKey="confirm"
+        onDeleteApproved={() =>
+          handleSignOut((popUp?.deleteSession?.data as { sessionId: string }).sessionId)
+        }
+      />
path: frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Avoid a potential runtime crash when `data` is undefined**

If, for any reason, `popUp.deleteSession.data` is `undefined`, the cast will throw. A simple guard prevents this.

```diff
-          handleSignOut((popUp?.deleteSession?.data as { sessionId: string }).sessionId)
+          popUp.deleteSession.data &&
+          handleSignOut((popUp.deleteSession.data as { sessionId: string }).sessionId)
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
          popUp.deleteSession.data &&
          handleSignOut((popUp.deleteSession.data as { sessionId: string }).sessionId)
        }
      />
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-25T12:49:29Z
author_association: NONE
diff_hunk: @@ -9,59 +11,131 @@ import {
   Td,
   Th,
   THead,
+  Tooltip,
   Tr
 } from "@app/components/v2";
-import { useGetMySessions } from "@app/hooks/api";
+import { Button } from "@app/components/v2/Button";
+import { useGetMySessions, useRevokeMySessionById } from "@app/hooks/api";
+import { usePopUp } from "@app/hooks/usePopUp";
+import { timeAgo } from "@app/lib/fn/date";
+import { formatSessionUserAgent } from "@app/lib/fn/string";
+
+const formatLocalDateTime = (date: Date): string => {
+  return date.toLocaleString(undefined, {
+    weekday: "long",
+    year: "numeric",
+    month: "long",
+    day: "numeric",
+    hour: "2-digit",
+    minute: "2-digit",
+    second: "2-digit"
+  });
+};
 
 export const SessionsTable = () => {
   const { data, isPending } = useGetMySessions();
+  const { mutateAsync: revokeMySessionById } = useRevokeMySessionById();
+  const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
+    "deleteSession"
+  ] as const);
 
-  const formatDate = (dateToFormat: string) => {
-    const date = new Date(dateToFormat);
-    const year = date.getFullYear();
-    const month = date.getMonth() + 1;
-    const day = date.getDate();
+  const handleSignOut = async (sessionId: string) => {
+    try {
+      await revokeMySessionById(sessionId);
+      createNotification({
+        text: "Session revoked successfully",
+        type: "success"
+      });
+    } catch (error) {
+      console.error(error);
+      createNotification({
+        text: "Failed to revoke session",
+        type: "error"
+      });
+    }
 
-    const formattedDate = `${day}/${month}/${year}`;
-
-    return formattedDate;
+    handlePopUpClose("deleteSession");
   };
 
   return (
-    <TableContainer className="mt-4">
-      <Table>
-        <THead>
-          <Tr>
-            <Th>Created</Th>
-            <Th>Last active</Th>
-            <Th>IP address</Th>
-            <Th>Device</Th>
-          </Tr>
-        </THead>
-        <TBody>
-          {isPending && <TableSkeleton columns={4} innerKey="sesssions" />}
-          {!isPending &&
-            data &&
-            data.length > 0 &&
-            data.map(({ id, createdAt, lastUsed, ip, userAgent }) => {
-              return (
-                <Tr className="h-10" key={`session-${id}`}>
-                  <Td>{formatDate(createdAt)}</Td>
-                  <Td>{formatDate(lastUsed)}</Td>
-                  <Td>{ip}</Td>
-                  <Td>{userAgent}</Td>
-                </Tr>
-              );
-            })}
-          {!isPending && data && data?.length === 0 && (
+    <>
+      <DeleteActionModal
+        isOpen={popUp.deleteSession.isOpen}
+        title="Are you sure want to sign out of this session?"
+        onChange={(isOpen) => handlePopUpToggle("deleteSession", isOpen)}
+        deleteKey="confirm"
+        onDeleteApproved={() =>
+          handleSignOut((popUp?.deleteSession?.data as { sessionId: string })?.sessionId)
+        }
+      />
+      <TableContainer className="mt-4">
+        <Table>
+          <THead>
             <Tr>
-              <Td colSpan={4}>
-                <EmptyState title="No sessions on file" icon={faServer} />
-              </Td>
+              <Th>IP & Session ID</Th>
+              <Th>OS & Browser</Th>
+              <Th>Last accessed</Th>
+              <Th>Manage</Th>
             </Tr>
-          )}
-        </TBody>
-      </Table>
-    </TableContainer>
+          </THead>
+          <TBody>
+            {isPending && <TableSkeleton columns={4} innerKey="sessions" />}
+            {!isPending &&
+              data &&
+              data.length > 0 &&
+              data.map(({ id, createdAt, lastUsed, ip, userAgent }) => {
+                const { os, browser } = formatSessionUserAgent(userAgent);
+                const lastUsedDate = new Date(lastUsed);
+                const createdAtDate = new Date(createdAt);
+
+                return (
+                  <Tr className="h-20" key={`session-${id}`}>
+                    <Td>
+                      <div className="flex flex-col">
+                        <span className="font-medium">{ip}</span>
+                        <span className="text-sm text-gray-500">ID: {id}</span>
+                      </div>
+                    </Td>
+                    <Td>
+                      <div className="flex flex-col">
+                        <span className="font-medium">{os}</span>
+                        <span className="text-sm text-gray-500">{browser}</span>
+                      </div>
+                    </Td>
+                    <Td>
+                      <div className="flex flex-col">
+                        <Tooltip content={formatLocalDateTime(lastUsedDate)}>
+                          <span className="font-medium">{timeAgo(lastUsedDate, new Date())}</span>
+                        </Tooltip>
+                        <Tooltip content={formatLocalDateTime(createdAtDate)}>
+                          <span className="text-sm text-gray-500">
+                            Created {timeAgo(createdAtDate, new Date())}
+                          </span>
+                        </Tooltip>
+                      </div>
+                    </Td>
+                    <Td>
+                      <Button
+                        variant="plain"
+                        colorSchema="danger"
+                        onClick={() => handlePopUpOpen("deleteSession", { sessionId: id })}
+                      >
+                        Sign out
+                      </Button>
path: frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Disable the Sign out button during revocation**

The Sign out button should be disabled while the revocation is in progress to prevent multiple clicks and improve user feedback.


```diff
                      <Button
                        variant="plain"
                        colorSchema="danger"
+                       isDisabled={isRevoking}
                        onClick={() => handlePopUpOpen("deleteSession", { sessionId: id })}
                      >
-                       Sign out
+                       {isRevoking ? "Signing out..." : "Sign out"}
                      </Button>
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
                      <Button
                        variant="plain"
                        colorSchema="danger"
                        isDisabled={isRevoking}
                        onClick={() => handlePopUpOpen("deleteSession", { sessionId: id })}
                      >
                        {isRevoking ? "Signing out..." : "Sign out"}
                      </Button>
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-25T12:55:33Z
author_association: NONE
diff_hunk: @@ -9,59 +11,131 @@ import {
   Td,
   Th,
   THead,
+  Tooltip,
   Tr
 } from "@app/components/v2";
-import { useGetMySessions } from "@app/hooks/api";
+import { Button } from "@app/components/v2/Button";
+import { useGetMySessions, useRevokeMySessionById } from "@app/hooks/api";
+import { usePopUp } from "@app/hooks/usePopUp";
+import { timeAgo } from "@app/lib/fn/date";
+import { formatSessionUserAgent } from "@app/lib/fn/string";
+
+const formatLocalDateTime = (date: Date): string => {
+  return date.toLocaleString(undefined, {
+    weekday: "long",
+    year: "numeric",
+    month: "long",
+    day: "numeric",
+    hour: "2-digit",
+    minute: "2-digit",
+    second: "2-digit"
+  });
+};
 
 export const SessionsTable = () => {
   const { data, isPending } = useGetMySessions();
+  const { mutateAsync: revokeMySessionById } = useRevokeMySessionById();
+  const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
+    "deleteSession"
+  ] as const);
 
-  const formatDate = (dateToFormat: string) => {
-    const date = new Date(dateToFormat);
-    const year = date.getFullYear();
-    const month = date.getMonth() + 1;
-    const day = date.getDate();
+  const handleSignOut = async (sessionId: string) => {
+    try {
+      await revokeMySessionById(sessionId);
+      createNotification({
+        text: "Session revoked successfully",
+        type: "success"
+      });
+    } catch (error) {
+      console.error(error);
path: frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx
user: login: carlosmonastyrski
body: Shouldn't we just add that error to be shown on the notification error message instead of doing that on the console? 🤔 
created_at: 2025-04-25T15:28:24Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-25T12:45:15Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

"""
## Walkthrough

This update introduces targeted session management functionality across the backend, frontend, CLI, and documentation. On the backend, a new DELETE route `/me/sessions/:sessionId` is added to allow authenticated users to revoke specific sessions by session ID. Supporting this, a new service method `revokeMySessionById` is implemented, and session queries are now ordered by recent usage. On the frontend, a new React Query mutation hook `useRevokeMySessionById` is created and integrated into the `SessionsTable` component, enabling users to sign out individual sessions with confirmation and notification feedback. The session information display is enhanced with parsed OS, browser, and device details, and improved timestamp formatting. In the CLI, a new `user get token` command is introduced to retrieve and display the current user's JWT token and session ID. Documentation is updated to reflect these CLI changes and provide usage examples.

## Suggested reviewers

- maidul98


<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=Infisical/infisical&utm_content=3489):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: maidul98
created_at: 2025-04-25T12:47:05Z
body: ![](https://res.cloudinary.com/snyk/image/upload/r-d/scm-platform/snyk-pull-requests/pr-banner-default.svg)

### :tada: **Snyk checks have passed. No issues have been found so far.**

:white_check_mark: **security/snyk** check is complete. No issues have been found. [(View Details)](https://app.snyk.io/org/maidul98/pr-checks/cfcbb51e-4c38-48bf-8150-4f0ed5812e9a?refs=178acc412d3c9603435fdd798fbec24d93eb4d46&source=prChecksComment)



author_association: COLLABORATOR