feat(webui): delete account #615

Merged
Sainan merged 3 commits from delete-account into main 2024-12-22 18:34:14 -08:00
4 changed files with 41 additions and 7 deletions

View File

@ -0,0 +1,19 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { Account } from "@/src/models/loginModel";
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
import { PersonalRooms } from "@/src/models/personalRoomsModel";
import { Ship } from "@/src/models/shipModel";
export const deleteAccountController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
await Promise.all([
Account.deleteOne({ _id: accountId }),
Inventory.deleteOne({ accountOwnerId: accountId }),
Loadout.deleteOne({ loadoutOwnerId: accountId }),
PersonalRooms.deleteOne({ personalRoomsOwnerId: accountId }),
Ship.deleteOne({ ShipOwnerId: accountId })
]);
res.end();
};
coderabbitai[bot] commented 2024-12-22 17:50:13 -08:00 (Migrated from github.com)
Review

⚠️ Potential issue

Critical: Implement proper security measures and error handling

The current implementation has several security and reliability concerns:

  1. Using GET for deletion operations violates HTTP semantics
  2. No additional authentication verification
  3. No confirmation token validation
  4. Missing error handling
  5. No transaction management

Consider implementing these improvements:

-export const deleteAccountController: RequestHandler = async (req, res) => {
+export const deleteAccountController: RequestHandler = async (req, res, next) => {
+    try {
+        // Verify confirmation token
+        const confirmationToken = req.body.confirmationToken;
+        if (!confirmationToken) {
+            return res.status(400).json({ error: 'Confirmation token required' });
+        }
+
         const accountId = await getAccountIdForRequest(req);
-        await Promise.all([
-            Account.deleteOne({ _id: accountId }),
-            Inventory.deleteOne({ accountOwnerId: accountId }),
-            Loadout.deleteOne({ loadoutOwnerId: accountId }),
-            PersonalRooms.deleteOne({ personalRoomsOwnerId: accountId }),
-            Ship.deleteOne({ ShipOwnerId: accountId })
-        ]);
-        res.end();
+        
+        // Start transaction
+        const session = await mongoose.startSession();
+        await session.withTransaction(async () => {
+            await Promise.all([
+                Account.deleteOne({ _id: accountId }, { session }),
+                Inventory.deleteOne({ accountOwnerId: accountId }, { session }),
+                Loadout.deleteOne({ loadoutOwnerId: accountId }, { session }),
+                PersonalRooms.deleteOne({ personalRoomsOwnerId: accountId }, { session }),
+                Ship.deleteOne({ ShipOwnerId: accountId }, { session })
+            ]);
+        });
+        
+        res.status(200).json({ message: 'Account deleted successfully' });
+    } catch (error) {
+        next(error);
+    }
 };

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

_:warning: Potential issue_ **Critical: Implement proper security measures and error handling** The current implementation has several security and reliability concerns: 1. Using GET for deletion operations violates HTTP semantics 2. No additional authentication verification 3. No confirmation token validation 4. Missing error handling 5. No transaction management Consider implementing these improvements: ```diff -export const deleteAccountController: RequestHandler = async (req, res) => { +export const deleteAccountController: RequestHandler = async (req, res, next) => { + try { + // Verify confirmation token + const confirmationToken = req.body.confirmationToken; + if (!confirmationToken) { + return res.status(400).json({ error: 'Confirmation token required' }); + } + const accountId = await getAccountIdForRequest(req); - await Promise.all([ - Account.deleteOne({ _id: accountId }), - Inventory.deleteOne({ accountOwnerId: accountId }), - Loadout.deleteOne({ loadoutOwnerId: accountId }), - PersonalRooms.deleteOne({ personalRoomsOwnerId: accountId }), - Ship.deleteOne({ ShipOwnerId: accountId }) - ]); - res.end(); + + // Start transaction + const session = await mongoose.startSession(); + await session.withTransaction(async () => { + await Promise.all([ + Account.deleteOne({ _id: accountId }, { session }), + Inventory.deleteOne({ accountOwnerId: accountId }, { session }), + Loadout.deleteOne({ loadoutOwnerId: accountId }, { session }), + PersonalRooms.deleteOne({ personalRoomsOwnerId: accountId }, { session }), + Ship.deleteOne({ ShipOwnerId: accountId }, { session }) + ]); + }); + + res.status(200).json({ message: 'Account deleted successfully' }); + } catch (error) { + next(error); + } }; ``` > Committable suggestion skipped: line range outside the PR's diff. <!-- This is an auto-generated comment by CodeRabbit -->

View File

@ -4,6 +4,7 @@ import { tunablesController } from "@/src/controllers/custom/tunablesController"
import { getItemListsController } from "@/src/controllers/custom/getItemListsController";
import { pushArchonCrystalUpgradeController } from "@/src/controllers/custom/pushArchonCrystalUpgradeController";
import { popArchonCrystalUpgradeController } from "@/src/controllers/custom/popArchonCrystalUpgradeController";
import { deleteAccountController } from "@/src/controllers/custom/deleteAccountController";
import { createAccountController } from "@/src/controllers/custom/createAccountController";
import { addItemController } from "@/src/controllers/custom/addItemController";
@ -17,6 +18,7 @@ customRouter.get("/tunables.json", tunablesController);
customRouter.get("/getItemLists", getItemListsController);
customRouter.get("/pushArchonCrystalUpgrade", pushArchonCrystalUpgradeController);
customRouter.get("/popArchonCrystalUpgrade", popArchonCrystalUpgradeController);
customRouter.get("/deleteAccount", deleteAccountController);
customRouter.post("/createAccount", createAccountController);
customRouter.post("/addItem", addItemController);

View File

@ -38,6 +38,8 @@
<button class="nav-link dropdown-toggle displayname" data-bs-toggle="dropdown" aria-expanded="false"></button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="/webui/" onclick="logout();">Logout</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" onclick="deleteAccount();">Delete Account</a></li>
coderabbitai[bot] commented 2024-12-22 17:50:13 -08:00 (Migrated from github.com)
Review

🛠️ Refactor suggestion

Enhance user safety and accessibility

The current implementation lacks:

  1. Visual warning about the irreversible action
  2. Confirmation dialog
  3. Accessibility attributes
  4. Loading state during deletion

Consider these improvements:

 <li><hr class="dropdown-divider"></li>
-<li><a class="dropdown-item" onclick="deleteAccount();">Delete Account</a></li>
+<li>
+    <button 
+        class="dropdown-item text-danger"
+        data-bs-toggle="modal"
+        data-bs-target="#deleteAccountModal"
+        aria-label="Delete account permanently"
+    >
+        Delete Account
+    </button>
+</li>

Add this modal for confirmation:

<div class="modal fade" id="deleteAccountModal" tabindex="-1" aria-labelledby="deleteAccountModalLabel" aria-hidden="true">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title" id="deleteAccountModalLabel">Delete Account</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body">
                <p class="text-danger">Warning: This action cannot be undone. All your data will be permanently deleted.</p>
                <p>Please type "DELETE" to confirm:</p>
                <input type="text" class="form-control" id="deleteConfirmation" />
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
                <button type="button" class="btn btn-danger" id="confirmDeleteAccount" disabled>Delete Account</button>
            </div>
        </div>
    </div>
</div>
_:hammer_and_wrench: Refactor suggestion_ **Enhance user safety and accessibility** The current implementation lacks: 1. Visual warning about the irreversible action 2. Confirmation dialog 3. Accessibility attributes 4. Loading state during deletion Consider these improvements: ```diff <li><hr class="dropdown-divider"></li> -<li><a class="dropdown-item" onclick="deleteAccount();">Delete Account</a></li> +<li> + <button + class="dropdown-item text-danger" + data-bs-toggle="modal" + data-bs-target="#deleteAccountModal" + aria-label="Delete account permanently" + > + Delete Account + </button> +</li> ``` Add this modal for confirmation: ```html <div class="modal fade" id="deleteAccountModal" tabindex="-1" aria-labelledby="deleteAccountModalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="deleteAccountModalLabel">Delete Account</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> <p class="text-danger">Warning: This action cannot be undone. All your data will be permanently deleted.</p> <p>Please type "DELETE" to confirm:</p> <input type="text" class="form-control" id="deleteConfirmation" /> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> <button type="button" class="btn btn-danger" id="confirmDeleteAccount" disabled>Delete Account</button> </div> </div> </div> </div> ``` <!-- This is an auto-generated comment by CodeRabbit -->
</ul>
</li>
</ul>
@ -47,13 +49,7 @@
<div class="offcanvas-lg offcanvas-start" tabindex="-1" id="sidebar" aria-labelledby="sidebarLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="sidebarLabel">Sidebar</h5>
<button
type="button"
class="btn-close"
data-bs-dismiss="offcanvas"
data-bs-target="#sidebar"
aria-label="Close"
></button>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<div class="navbar p-0">

View File

@ -61,6 +61,23 @@ function logout() {
localStorage.removeItem("password");
}
function deleteAccount() {
if (
window.confirm(
"Are you sure you want to delete your account " +
$(".displayname").text() +
" (" +
localStorage.getItem("email") +
")? This action cannot be undone."
)
) {
fetch("/custom/deleteAccount?" + window.authz).then(function () {
logout();
single.loadRoute("/webui/"); // Show login screen
});
}
}
coderabbitai[bot] commented 2024-12-22 17:50:13 -08:00 (Migrated from github.com)
Review

⚠️ Potential issue

Enhance error handling and security in account deletion.

The account deletion implementation needs improvements in several areas:

  1. Error handling is missing for failed requests
  2. Auth token in URL could be exposed in logs
  3. No loading state during deletion

Consider applying these improvements:

 function deleteAccount() {
     if (
         window.confirm(
             "Are you sure you want to delete your account " +
                 $(".displayname").text() +
                 " (" +
                 localStorage.getItem("email") +
                 ")? This action cannot be undone."
         )
     ) {
-        fetch("/custom/deleteAccount?" + window.authz).then(function () {
-            logout();
-            single.loadRoute("/webui/"); // Show login screen
-        });
+        const button = document.querySelector("#delete-account-btn");
+        if (button) button.disabled = true;
+        
+        fetch("/custom/deleteAccount", {
+            method: 'POST',
+            headers: {
+                'Authorization': window.authz
+            }
+        })
+        .then(response => {
+            if (!response.ok) {
+                throw new Error('Account deletion failed');
+            }
+            logout();
+            single.loadRoute("/webui/");
+        })
+        .catch(error => {
+            alert("Failed to delete account: " + error.message);
+        })
+        .finally(() => {
+            if (button) button.disabled = false;
+        });
     }
 }
📝 Committable suggestion

‼️ 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.

function deleteAccount() {
    if (
        window.confirm(
            "Are you sure you want to delete your account " +
                $(".displayname").text() +
                " (" +
                localStorage.getItem("email") +
                ")? This action cannot be undone."
        )
    ) {
        const button = document.querySelector("#delete-account-btn");
        if (button) button.disabled = true;
        
        fetch("/custom/deleteAccount", {
            method: 'POST',
            headers: {
                'Authorization': window.authz
            }
        })
        .then(response => {
            if (!response.ok) {
                throw new Error('Account deletion failed');
            }
            logout();
            single.loadRoute("/webui/");
        })
        .catch(error => {
            alert("Failed to delete account: " + error.message);
        })
        .finally(() => {
            if (button) button.disabled = false;
        });
    }
}
🧰 Tools
🪛 eslint

[error] 64-64: 'deleteAccount' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 68-68: '$' is not defined.

(no-undef)


[error] 76-76: 'single' is not defined.

(no-undef)


💡 Codebase verification

Account deletion implementation needs error handling improvements

The verification revealed several issues that need attention:

  • Frontend: The fetch call lacks error handling and doesn't handle failed requests

    fetch("/custom/deleteAccount?" + window.authz).then(function () {
    
  • Backend: The controller doesn't include try-catch blocks or error responses

    export const deleteAccountController: RequestHandler = async (req, res) => {
      const accountId = await getAccountIdForRequest(req);
      await Promise.all([...]);
      res.end();
    };
    
  • Both frontend and backend should handle cases where:

    • Account ID retrieval fails
    • Database operations fail
    • Network requests fail
    • Authentication errors occur
🔗 Analysis chain

Verify the account deletion implementation across the codebase.

Let's ensure the account deletion feature is properly integrated.


Let me generate additional verification scripts to check for error handling and response handling in both frontend and backend:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the account deletion implementation

# Test 1: Check if deleteAccount function is properly bound to a UI element
echo "Checking for deleteAccount button/link in HTML..."
rg -l "deleteAccount\(\)" --type html

# Test 2: Check for related backend route implementation
echo "Checking for deleteAccount route implementation..."
rg -l "deleteAccount" --type typescript --type javascript

# Test 3: Check for proper error handling in related files
echo "Checking error handling in related files..."
rg "deleteAccount" -A 5 -B 5

Length of output: 5308


Script:

#!/bin/bash
# Check frontend error handling for the fetch call
echo "Checking frontend error handling..."
rg "fetch\(.*deleteAccount.*\)" -A 10 -B 2

# Check backend error handling in the controller
echo "Checking backend error handling..."
ast-grep --pattern 'export const deleteAccountController: RequestHandler = async (req, res) => {
  $$$
}'

# Check if all necessary models are properly cleaned up
echo "Checking for all database models that might need cleanup..."
rg "mongoose\.model|Schema\(" --type typescript

Length of output: 2179

🧰 Tools
🪛 eslint

[error] 64-64: 'deleteAccount' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 68-68: '$' is not defined.

(no-undef)


[error] 76-76: 'single' is not defined.

(no-undef)

_:warning: Potential issue_ **Enhance error handling and security in account deletion.** The account deletion implementation needs improvements in several areas: 1. Error handling is missing for failed requests 2. Auth token in URL could be exposed in logs 3. No loading state during deletion Consider applying these improvements: ```diff function deleteAccount() { if ( window.confirm( "Are you sure you want to delete your account " + $(".displayname").text() + " (" + localStorage.getItem("email") + ")? This action cannot be undone." ) ) { - fetch("/custom/deleteAccount?" + window.authz).then(function () { - logout(); - single.loadRoute("/webui/"); // Show login screen - }); + const button = document.querySelector("#delete-account-btn"); + if (button) button.disabled = true; + + fetch("/custom/deleteAccount", { + method: 'POST', + headers: { + 'Authorization': window.authz + } + }) + .then(response => { + if (!response.ok) { + throw new Error('Account deletion failed'); + } + logout(); + single.loadRoute("/webui/"); + }) + .catch(error => { + alert("Failed to delete account: " + error.message); + }) + .finally(() => { + if (button) button.disabled = false; + }); } } ``` <!-- 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 function deleteAccount() { if ( window.confirm( "Are you sure you want to delete your account " + $(".displayname").text() + " (" + localStorage.getItem("email") + ")? This action cannot be undone." ) ) { const button = document.querySelector("#delete-account-btn"); if (button) button.disabled = true; fetch("/custom/deleteAccount", { method: 'POST', headers: { 'Authorization': window.authz } }) .then(response => { if (!response.ok) { throw new Error('Account deletion failed'); } logout(); single.loadRoute("/webui/"); }) .catch(error => { alert("Failed to delete account: " + error.message); }) .finally(() => { if (button) button.disabled = false; }); } } ````` </details> <!-- suggestion_end --> <details> <summary>🧰 Tools</summary> <details> <summary>🪛 eslint</summary> [error] 64-64: 'deleteAccount' is defined but never used. (@typescript-eslint/no-unused-vars) --- [error] 68-68: '$' is not defined. (no-undef) --- [error] 76-76: 'single' is not defined. (no-undef) </details> </details> --- _:bulb: Codebase verification_ **Account deletion implementation needs error handling improvements** The verification revealed several issues that need attention: * Frontend: The fetch call lacks error handling and doesn't handle failed requests ```javascript fetch("/custom/deleteAccount?" + window.authz).then(function () { ``` * Backend: The controller doesn't include try-catch blocks or error responses ```typescript export const deleteAccountController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); await Promise.all([...]); res.end(); }; ``` * Both frontend and backend should handle cases where: - Account ID retrieval fails - Database operations fail - Network requests fail - Authentication errors occur <details> <summary>🔗 Analysis chain</summary> **Verify the account deletion implementation across the codebase.** Let's ensure the account deletion feature is properly integrated. --- Let me generate additional verification scripts to check for error handling and response handling in both frontend and backend: </details> <details> <summary>🏁 Scripts executed</summary> The following scripts were executed for the analysis: Script: ```shell #!/bin/bash # Description: Verify the account deletion implementation # Test 1: Check if deleteAccount function is properly bound to a UI element echo "Checking for deleteAccount button/link in HTML..." rg -l "deleteAccount\(\)" --type html # Test 2: Check for related backend route implementation echo "Checking for deleteAccount route implementation..." rg -l "deleteAccount" --type typescript --type javascript # Test 3: Check for proper error handling in related files echo "Checking error handling in related files..." rg "deleteAccount" -A 5 -B 5 ``` Length of output: 5308 --- Script: ```shell #!/bin/bash # Check frontend error handling for the fetch call echo "Checking frontend error handling..." rg "fetch\(.*deleteAccount.*\)" -A 10 -B 2 # Check backend error handling in the controller echo "Checking backend error handling..." ast-grep --pattern 'export const deleteAccountController: RequestHandler = async (req, res) => { $$$ }' # Check if all necessary models are properly cleaned up echo "Checking for all database models that might need cleanup..." rg "mongoose\.model|Schema\(" --type typescript ``` Length of output: 2179 </details> <details> <summary>🧰 Tools</summary> <details> <summary>🪛 eslint</summary> [error] 64-64: 'deleteAccount' is defined but never used. (@typescript-eslint/no-unused-vars) --- [error] 68-68: '$' is not defined. (no-undef) --- [error] 76-76: 'single' is not defined. (no-undef) </details> </details> <!-- This is an auto-generated comment by CodeRabbit -->
if (localStorage.getItem("email") && localStorage.getItem("password")) {
loginFromLocalStorage();
}