{"record":{"id":"1e9abc69471f34dd","repo":"wg-easy/wg-easy","slug":"user-not-found","errorCode":null,"errorMessage":"User not found","messagePattern":"User not found","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/server/database/repositories/user/service.ts","lineNumber":160,"sourceCode":"  async update(id: ID, name: string, email: string | null) {\n    return this.#statements.update.execute({ id, name, email });\n  }\n\n  async updatePassword(\n    id: ID,\n    currentPassword: string | null,\n    newPassword: string\n  ) {\n    const hash = await hashPassword(newPassword);\n\n    return this.#db.transaction(async (tx) => {\n      // get user again to avoid password changing while request\n      const txUser = await tx.query.user\n        .findFirst({ where: eq(user.id, id) })\n        .execute();\n\n      if (!txUser) {\n        throw new Error('User not found');\n      }\n\n      // only check password if already set\n      if (txUser.password !== null) {\n        if (!currentPassword) {\n          throw new Error('Invalid password');\n        }\n\n        const passwordValid = await isPasswordValid(\n          currentPassword,\n          txUser.password\n        );\n\n        if (!passwordValid) {\n          throw new Error('Invalid password');\n        }\n      }\n","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/wg-easy/wg-easy/blob/5c38c1427a0c6c62f1bbc6724233a71d931ac431/src/server/database/repositories/user/service.ts#L142-L178","documentation":"Thrown by UserService.updatePassword() inside a transaction when re-fetching the target user by id returns null. The re-read exists specifically to avoid changing a password for a user deleted while the request was in flight. It means the user id supplied does not resolve to an existing row at update time.","triggerScenarios":"Calling updatePassword(id, ...) with an id that is not in the user table; the user being deleted between request validation and the transactional update; stale id from a client after the account was removed.","commonSituations":"Two admin sessions where one deletes the account while the other changes its password; password-reset links used after account deletion; client caching an outdated user list.","solutions":["Verify the user id exists before/while calling updatePassword and return 404 on failure","Catch the error and surface 'account no longer exists' instead of a generic failure","Re-fetch the user in the UI before submitting a password change","Wrap in try-catch to map to HTTP 404"],"exampleFix":"// before\nawait userService.updatePassword(id, current, next);\n// after\ntry { await userService.updatePassword(id, current, next); }\ncatch (e) { if (e.message === 'User not found') throw new NotFoundError('user'); throw e; }","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for (let i = 0; i < 3; i++) {\n  try { await userService.updatePassword(id, current, next); break; }\n  catch (e) { if (e.message === 'User not found') { if (i === 2) throw new NotFoundError('User'); continue; } throw e; }\n}","preventionTips":["Re-fetch the user before mutating account state","Handle concurrent admin deletions gracefully","Return 404 semantics to clients so they refresh state"],"tags":["database","user","concurrency","missing-row"],"backgroundTag":"record-not-found","analyzedSha":"5c38c1427a0c6c62f1bbc6724233a71d931ac431","analyzedAt":"2026-08-30T04:35:56.098Z","schemaVersion":2},"datasetVersion":"2026-08-30T08:17:16.595Z"}