flood icon indicating copy to clipboard operation
flood copied to clipboard

User migration failing because destroyUserServices is called before bootstrapServicesForUser

Open CircuitCoder opened this issue 4 years ago • 0 comments

Type: Bug Report

  • [x] Try to follow the update procedure described in the README and try again before opening this issue.

Your Environment

  • Version used: v4.6.1
    • Also tried with source on commit dc17bfb2. Proposed fix is also based on this revision.
  • Environment name and version:
    • Node.js version: v16.5.0
    • npm version 7.20.1
  • Operating system and version: ArchLinux
  • Torrent client and version: rtorrent 0.9.8 (from arch repo)

Summary

Migration from legacy flood immediately fails with exception:

TypeError: Cannot convert undefined or null to object
    at Function.keys (<anonymous>)
    at destroyUserServices (/usr/local/share/.config/yarn/global/node_modules/flood/dist/index.js:452:201743)
    at /usr/local/share/.config/yarn/global/node_modules/flood/dist/index.js:452:96461
    at async /usr/local/share/.config/yarn/global/node_modules/flood/dist/index.js:452:61310
    at async Promise.all (index 0)
    at async Promise.all (index 0)

Possible Solution

It seems that the migration procedure (userV1 -> userV2) involves removing the old user and re-adding with the new format.

User.removeUser invokes destroyUserServices, which misses a check if services for that user is not bootstrapped yet.

Adding such check makes the migration run without errors. After one successful migration, running the prebuilt stable version of flood also works fine.

diff --git a/server/services/index.ts b/server/services/index.ts
index a1841adc..ad30a5c3 100644
--- a/server/services/index.ts
+++ b/server/services/index.ts
@@ -45,6 +45,9 @@ export const getAllServices = ({_id}: UserInDatabase) => {
 export const destroyUserServices = (userId: UserInDatabase['_id']) => {
   const userServiceInstances = serviceInstances[userId];
   delete serviceInstances[userId];
+
+  if (userServiceInstances === undefined || userServiceInstances === null) return;
+
   Object.keys(userServiceInstances).forEach((key) => {
     const serviceName = key as keyof ServiceInstances;
     userServiceInstances[serviceName].destroy();

Steps to Reproduce

  1. Place a single file in ~/.local/share/flood/db, users.db
{"username":"test","password":"$argon2i$v=19$m=16,t=2,p=1$OTI0Nzg5MTIzNDE$2X2rnIZsGui24TSx90fkHw","socketPath":"/var/run/rtorrent.sock","timestamp":1631140831,"_id":"98Zo5C1sTae6IxHY"}
{"$$indexCreated":{"fieldName":"username","unique":true,"sparse":false}}
  1. Run flood installed with npm.

CircuitCoder avatar Sep 08 '21 23:09 CircuitCoder