{"record":{"id":"9cfa3087478f06c2","repo":"laurent22/joplin","slug":"password-must-be-set","errorCode":null,"errorMessage":"password must be set","messagePattern":"password must be set","errorType":"validation","errorClass":"ErrorUnprocessableEntity","httpStatus":422,"severity":"error","filePath":"packages/server/src/models/UserModel.ts","lineNumber":466,"sourceCode":"\t\tconst result = zxcvbn(password);\n\t\tif (result.score < 3) {\n\t\t\tlet msg: string[] = [result.feedback.warning];\n\t\t\tif (result.feedback.suggestions) {\n\t\t\t\tmsg = msg.concat(result.feedback.suggestions);\n\t\t\t}\n\t\t\tthrow new ErrorUnprocessableEntity(msg.join(' '));\n\t\t}\n\t}\n\n\tprotected async validate(object: User, options: ValidateOptions = {}): Promise<User> {\n\t\tconst user: User = await super.validate(object, options);\n\n\t\t// Note that we don't validate the password here because it's already\n\t\t// been hashed by then.\n\t\tif (options.isNew) {\n\t\t\tif (!user.email) throw new ErrorUnprocessableEntity('email must be set');\n\t\t\tif ('email' in user && !user.email.includes('@')) throw new ErrorUnprocessableEntity(`Should include @ in email address, email: ${user.email}`);\n\t\t\tif (!user.password && !user.must_set_password) throw new ErrorUnprocessableEntity('password must be set');\n\t\t} else {\n\t\t\tif ('email' in user && !user.email) throw new ErrorUnprocessableEntity('email must be set');\n\t\t\tif (user.email && !user.email.includes('@')) throw new ErrorUnprocessableEntity(`Should include @ in email address, email: ${user.email}`);\n\t\t\tif ('password' in user && !user.password) throw new ErrorUnprocessableEntity('password must be set');\n\t\t}\n\n\t\tif (user.email) {\n\t\t\tconst existingUser = await this.loadByEmail(user.email);\n\t\t\tif (existingUser && existingUser.id !== user.id) throw new ErrorUnprocessableEntity(`there is already a user with this email: ${user.email}`);\n\t\t\t// See https://www.rfc-editor.org/errata_search.php?rfc=3696&eid=1690 (found via https://stackoverflow.com/a/574698)\n\t\t\tif (user.email.length > 254) throw new ErrorUnprocessableEntity('Please enter an email address between 0 and 254 characters');\n\t\t\tvalidateEmail(user.email);\n\t\t}\n\n\t\tif (user.full_name && user.full_name.length > 256) throw new ErrorUnprocessableEntity('Full name must be at most 256 characters');\n\n\t\treturn super.validate(user, options);\n\t}","sourceCodeStart":448,"sourceCodeEnd":484,"githubUrl":"https://github.com/laurent22/joplin/blob/981a03c5c9e88130bccff4db47c411d35ec7ae2c/packages/server/src/models/UserModel.ts#L448-L484","documentation":"UserModel.validate throws ErrorUnprocessableEntity('password must be set') when creating a new user (options.isNew) with neither a password nor must_set_password set. New users must either have a (pre-hashed) password or be flagged to set one later, e.g. via an invite/email link. The password itself is not validated here because it is already hashed by the time validate runs.","triggerScenarios":"Calling UserModel.save() with options.isNew true and a user object that has empty/undefined password and must_set_password falsy (falsy or absent).","commonSituations":"Admin scripts or API calls creating users while omitting the password field; provisioning code that intends to email a set-password link but forgets must_set_password: true; migrating users from another system without hashes.","solutions":["Include a password field (which will be hashed upstream) in the user object when saving a new user.","If the user should set the password later, set must_set_password: true on the new user.","Verify the hashing middleware/hook did not drop or clear the password field before save()."],"exampleFix":"// before\nawait models.user().save({ email: 'a@b.com' }, { isNew: true });\n// after\nawait models.user().save({ email: 'a@b.com', password: 'plain', must_set_password: false }, { isNew: true });\n// or, for invited users:\nawait models.user().save({ email: 'a@b.com', must_set_password: true }, { isNew: true });","handlingStrategy":"validation","validationCode":"if (isNewUser && !user.password && !user.must_set_password) throw new Error('password or must_set_password required');","typeGuard":"function hasNewUserCredential(u: { password?: string; must_set_password?: boolean }): boolean { return !!u.password || u.must_set_password === true; }","tryCatchPattern":"try { await models.user().save(user, { isNew: true }); } catch (e) { if (e.message === 'password must be set') /* prompt for password or set must_set_password */; }","preventionTips":["Always build new-user payloads through a helper that requires either password or must_set_password.","Never send raw empty-string passwords; omit the key or supply a hash."],"tags":["server","validation","user-model"],"backgroundTag":"missing-required-argument","analyzedSha":"981a03c5c9e88130bccff4db47c411d35ec7ae2c","analyzedAt":"2026-09-17T14:49:40.960Z","contentChangedAt":"2026-09-17T14:49:40.960Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}