{"record":{"id":"560e481e60a53e29","repo":"MHSanaei/3x-ui","slug":"password-can-not-be-empty","errorCode":null,"errorMessage":"password can not be empty","messagePattern":"password can not be empty","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/web/service/panel/user.go","lineNumber":147,"sourceCode":"\t\t_ = s.settingService.SetTwoFactorEnable(false)\n\t\t_ = s.settingService.SetTwoFactorToken(\"\")\n\t}\n\n\treturn db.Model(model.User{}).\n\t\tWhere(\"id = ?\", id).\n\t\tUpdates(map[string]any{\n\t\t\t\"username\":    username,\n\t\t\t\"password\":    hashedPassword,\n\t\t\t\"login_epoch\": gorm.Expr(\"login_epoch + 1\"),\n\t\t}).\n\t\tError\n}\n\nfunc (s *UserService) UpdateFirstUser(username string, password string) error {\n\tif username == \"\" {\n\t\treturn errors.New(\"username can not be empty\")\n\t} else if password == \"\" {\n\t\treturn errors.New(\"password can not be empty\")\n\t}\n\thashedPassword, er := crypto.HashPasswordAsBcrypt(password)\n\n\tif er != nil {\n\t\treturn er\n\t}\n\n\tdb := database.GetDB()\n\tuser := &model.User{}\n\terr := db.Model(model.User{}).First(user).Error\n\tif database.IsNotFound(err) {\n\t\tuser.Username = username\n\t\tuser.Password = hashedPassword\n\t\treturn db.Model(model.User{}).Create(user).Error\n\t} else if err != nil {\n\t\treturn err\n\t}\n\tuser.Username = username","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/web/service/panel/user.go#L129-L165","documentation":"The sibling guard in UpdateFirstUser: the password argument is empty, so the function refuses before hashing. Because UpdateFirstUser always writes BOTH username and password in one Updates() call, an empty password would not be skipped — it would overwrite the admin's password hash with the bcrypt of \"\", effectively breaking admin auth.","triggerScenarios":"Calling UpdateFirstUser(username, \"\") — a settings form where the password input was left blank, or an API client that only intended to rename the user and passed no password.","commonSituations":"'Change username only' flows that reuse the update endpoint without re-sending the current password; frontend password field cleared before submit; migration scripts that copy a user with no password set.","solutions":["Always pass the intended password (current or new) whenever this endpoint is used — it updates both fields atomically","If only the username should change, use the endpoint/flow that updates credentials separately rather than emptying the password","Validate non-empty password at the form/API layer before the call"],"exampleFix":"// before\nerr := userService.UpdateFirstUser(\"admin\", \"\")\n\n// after\nerr := userService.UpdateFirstUser(\"admin\", req.Password)\n// with earlier validation: if req.Password == \"\" { return 400 }","handlingStrategy":"validation","validationCode":"if password == \"\" {\n    return errors.New(\"password required\")\n}\nerr := userService.UpdateFirstUser(username, password)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Remember UpdateFirstUser writes username AND password atomically — never call it with one field empty","Send both fields from the settings form or use a dedicated single-field flow","Hash-validate on the frontend that both inputs are non-blank before submit"],"tags":["validation","auth","admin","user-management"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}