user.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856
  1. /* eslint-disable no-use-before-define */
  2. const debug = require('debug')('growi:models:user');
  3. const logger = require('@alias/logger')('growi:models:user');
  4. const path = require('path');
  5. const mongoose = require('mongoose');
  6. const uniqueValidator = require('mongoose-unique-validator');
  7. const mongoosePaginate = require('mongoose-paginate');
  8. const ObjectId = mongoose.Schema.Types.ObjectId;
  9. const crypto = require('crypto');
  10. const async = require('async');
  11. module.exports = function(crowi) {
  12. const STATUS_REGISTERED = 1;
  13. const STATUS_ACTIVE = 2;
  14. const STATUS_SUSPENDED = 3;
  15. const STATUS_DELETED = 4;
  16. const STATUS_INVITED = 5;
  17. const USER_PUBLIC_FIELDS = '_id image isEmailPublished isGravatarEnabled googleId name username email introduction status lang createdAt admin';
  18. const IMAGE_POPULATION = { path: 'imageAttachment', select: 'filePathProxied' };
  19. const LANG_EN = 'en';
  20. const LANG_EN_US = 'en-US';
  21. const LANG_EN_GB = 'en-GB';
  22. const LANG_JA = 'ja';
  23. const PAGE_ITEMS = 50;
  24. let userEvent;
  25. // init event
  26. if (crowi != null) {
  27. userEvent = crowi.event('user');
  28. userEvent.on('activated', userEvent.onActivated);
  29. }
  30. const userSchema = new mongoose.Schema({
  31. userId: String,
  32. image: String,
  33. imageAttachment: { type: ObjectId, ref: 'Attachment' },
  34. isGravatarEnabled: { type: Boolean, default: false },
  35. isEmailPublished: { type: Boolean, default: true },
  36. googleId: String,
  37. name: { type: String },
  38. username: { type: String, required: true, unique: true },
  39. email: { type: String, unique: true, sparse: true },
  40. // === The official settings
  41. // username: { type: String, index: true },
  42. // email: { type: String, required: true, index: true },
  43. // === crowi-plus (>= 2.1.0, <2.3.0) settings
  44. // email: { type: String, required: true, unique: true },
  45. introduction: { type: String },
  46. password: String,
  47. apiToken: String,
  48. lang: {
  49. type: String,
  50. // eslint-disable-next-line no-eval
  51. enum: Object.keys(getLanguageLabels()).map((k) => { return eval(k) }),
  52. default: LANG_EN_US,
  53. },
  54. status: {
  55. type: Number, required: true, default: STATUS_ACTIVE, index: true,
  56. },
  57. createdAt: { type: Date, default: Date.now },
  58. lastLoginAt: { type: Date },
  59. admin: { type: Boolean, default: 0, index: true },
  60. });
  61. userSchema.plugin(mongoosePaginate);
  62. userSchema.plugin(uniqueValidator);
  63. function validateCrowi() {
  64. if (crowi == null) {
  65. throw new Error('"crowi" is null. Init User model with "crowi" argument first.');
  66. }
  67. }
  68. function decideUserStatusOnRegistration() {
  69. validateCrowi();
  70. const Config = crowi.model('Config');
  71. const config = crowi.getConfig();
  72. if (!config.crowi) {
  73. return STATUS_ACTIVE; // is this ok?
  74. }
  75. // status decided depends on registrationMode
  76. switch (config.crowi['security:registrationMode']) {
  77. case Config.SECURITY_REGISTRATION_MODE_OPEN:
  78. return STATUS_ACTIVE;
  79. case Config.SECURITY_REGISTRATION_MODE_RESTRICTED:
  80. case Config.SECURITY_REGISTRATION_MODE_CLOSED: // 一応
  81. return STATUS_REGISTERED;
  82. default:
  83. return STATUS_ACTIVE; // どっちにすんのがいいんだろうな
  84. }
  85. }
  86. function generateRandomEmail() {
  87. const randomstr = generateRandomTempPassword();
  88. return `change-it-${randomstr}@example.com`;
  89. }
  90. function generateRandomTempPassword() {
  91. const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!=-_';
  92. let password = '';
  93. const len = 12;
  94. for (let i = 0; i < len; i++) {
  95. const randomPoz = Math.floor(Math.random() * chars.length);
  96. password += chars.substring(randomPoz, randomPoz + 1);
  97. }
  98. return password;
  99. }
  100. function generatePassword(password) {
  101. validateCrowi();
  102. const hasher = crypto.createHash('sha256');
  103. hasher.update(crowi.env.PASSWORD_SEED + password);
  104. return hasher.digest('hex');
  105. }
  106. function generateApiToken(user) {
  107. const hasher = crypto.createHash('sha256');
  108. hasher.update((new Date()).getTime() + user._id);
  109. return hasher.digest('base64');
  110. }
  111. function getLanguageLabels() {
  112. const lang = {};
  113. lang.LANG_EN = LANG_EN;
  114. lang.LANG_EN_US = LANG_EN_US;
  115. lang.LANG_EN_GB = LANG_EN_GB;
  116. lang.LANG_JA = LANG_JA;
  117. return lang;
  118. }
  119. userSchema.methods.populateImage = async function() {
  120. // eslint-disable-next-line no-return-await
  121. return await this.populate(IMAGE_POPULATION);
  122. };
  123. userSchema.methods.isPasswordSet = function() {
  124. if (this.password) {
  125. return true;
  126. }
  127. return false;
  128. };
  129. userSchema.methods.isPasswordValid = function(password) {
  130. return this.password === generatePassword(password);
  131. };
  132. userSchema.methods.setPassword = function(password) {
  133. this.password = generatePassword(password);
  134. return this;
  135. };
  136. userSchema.methods.isEmailSet = function() {
  137. if (this.email) {
  138. return true;
  139. }
  140. return false;
  141. };
  142. userSchema.methods.updateLastLoginAt = function(lastLoginAt, callback) {
  143. this.lastLoginAt = lastLoginAt;
  144. this.save((err, userData) => {
  145. return callback(err, userData);
  146. });
  147. };
  148. userSchema.methods.updateIsGravatarEnabled = function(isGravatarEnabled, callback) {
  149. this.isGravatarEnabled = isGravatarEnabled;
  150. this.save((err, userData) => {
  151. return callback(err, userData);
  152. });
  153. };
  154. userSchema.methods.updateIsEmailPublished = function(isEmailPublished, callback) {
  155. this.isEmailPublished = isEmailPublished;
  156. this.save((err, userData) => {
  157. return callback(err, userData);
  158. });
  159. };
  160. userSchema.methods.updatePassword = function(password, callback) {
  161. this.setPassword(password);
  162. this.save((err, userData) => {
  163. return callback(err, userData);
  164. });
  165. };
  166. userSchema.methods.canDeleteCompletely = function() {
  167. const isDeniedCompletelyDelete = crowi.configManager.getConfig('crowi', 'security:isEnabledDeleteCompletely');
  168. if ((!this.admin) && isDeniedCompletelyDelete) {
  169. return false;
  170. }
  171. return true;
  172. };
  173. userSchema.methods.updateApiToken = function(callback) {
  174. const self = this;
  175. self.apiToken = generateApiToken(this);
  176. return new Promise(((resolve, reject) => {
  177. self.save((err, userData) => {
  178. if (err) {
  179. return reject(err);
  180. }
  181. return resolve(userData);
  182. });
  183. }));
  184. };
  185. userSchema.methods.updateImage = async function(attachment) {
  186. this.imageAttachment = attachment;
  187. return this.save();
  188. };
  189. userSchema.methods.deleteImage = async function() {
  190. validateCrowi();
  191. const Attachment = crowi.model('Attachment');
  192. // the 'image' field became DEPRECATED in v3.3.8
  193. this.image = undefined;
  194. if (this.imageAttachment != null) {
  195. Attachment.removeWithSubstance(this.imageAttachment._id);
  196. }
  197. this.imageAttachment = undefined;
  198. return this.save();
  199. };
  200. userSchema.methods.updateGoogleId = function(googleId, callback) {
  201. this.googleId = googleId;
  202. this.save((err, userData) => {
  203. return callback(err, userData);
  204. });
  205. };
  206. userSchema.methods.deleteGoogleId = function(callback) {
  207. return this.updateGoogleId(null, callback);
  208. };
  209. userSchema.methods.activateInvitedUser = async function(username, name, password) {
  210. this.setPassword(password);
  211. this.name = name;
  212. this.username = username;
  213. this.status = STATUS_ACTIVE;
  214. this.save((err, userData) => {
  215. userEvent.emit('activated', userData);
  216. if (err) {
  217. throw new Error(err);
  218. }
  219. return userData;
  220. });
  221. };
  222. userSchema.methods.removeFromAdmin = function(callback) {
  223. debug('Remove from admin', this);
  224. this.admin = 0;
  225. this.save((err, userData) => {
  226. return callback(err, userData);
  227. });
  228. };
  229. userSchema.methods.makeAdmin = function(callback) {
  230. debug('Admin', this);
  231. this.admin = 1;
  232. this.save((err, userData) => {
  233. return callback(err, userData);
  234. });
  235. };
  236. userSchema.methods.asyncMakeAdmin = async function(callback) {
  237. this.admin = 1;
  238. return this.save();
  239. };
  240. userSchema.methods.statusActivate = function(callback) {
  241. debug('Activate User', this);
  242. this.status = STATUS_ACTIVE;
  243. this.save((err, userData) => {
  244. userEvent.emit('activated', userData);
  245. return callback(err, userData);
  246. });
  247. };
  248. userSchema.methods.statusSuspend = function(callback) {
  249. debug('Suspend User', this);
  250. this.status = STATUS_SUSPENDED;
  251. if (this.email === undefined || this.email === null) { // migrate old data
  252. this.email = '-';
  253. }
  254. if (this.name === undefined || this.name === null) { // migrate old data
  255. this.name = `-${Date.now()}`;
  256. }
  257. if (this.username === undefined || this.usename === null) { // migrate old data
  258. this.username = '-';
  259. }
  260. this.save((err, userData) => {
  261. return callback(err, userData);
  262. });
  263. };
  264. userSchema.methods.statusDelete = function(callback) {
  265. debug('Delete User', this);
  266. const now = new Date();
  267. const deletedLabel = `deleted_at_${now.getTime()}`;
  268. this.status = STATUS_DELETED;
  269. this.username = deletedLabel;
  270. this.password = '';
  271. this.name = '';
  272. this.email = `${deletedLabel}@deleted`;
  273. this.googleId = null;
  274. this.isGravatarEnabled = false;
  275. this.image = null;
  276. this.save((err, userData) => {
  277. return callback(err, userData);
  278. });
  279. };
  280. userSchema.methods.updateGoogleId = function(googleId, callback) {
  281. this.googleId = googleId;
  282. this.save((err, userData) => {
  283. return callback(err, userData);
  284. });
  285. };
  286. userSchema.statics.getLanguageLabels = getLanguageLabels;
  287. userSchema.statics.getUserStatusLabels = function() {
  288. const userStatus = {};
  289. userStatus[STATUS_REGISTERED] = '承認待ち';
  290. userStatus[STATUS_ACTIVE] = 'Active';
  291. userStatus[STATUS_SUSPENDED] = 'Suspended';
  292. userStatus[STATUS_DELETED] = 'Deleted';
  293. userStatus[STATUS_INVITED] = '招待済み';
  294. return userStatus;
  295. };
  296. userSchema.statics.isEmailValid = function(email, callback) {
  297. validateCrowi();
  298. const config = crowi.getConfig();
  299. const whitelist = config.crowi['security:registrationWhiteList'];
  300. if (Array.isArray(whitelist) && whitelist.length > 0) {
  301. return config.crowi['security:registrationWhiteList'].some((allowedEmail) => {
  302. const re = new RegExp(`${allowedEmail}$`);
  303. return re.test(email);
  304. });
  305. }
  306. return true;
  307. };
  308. userSchema.statics.filterToPublicFields = function(user) {
  309. debug('User is', typeof user, user);
  310. if (typeof user !== 'object' || !user._id) {
  311. return user;
  312. }
  313. const filteredUser = {};
  314. const fields = USER_PUBLIC_FIELDS.split(' ');
  315. for (let i = 0; i < fields.length; i++) {
  316. const key = fields[i];
  317. if (user[key]) {
  318. filteredUser[key] = user[key];
  319. }
  320. }
  321. return filteredUser;
  322. };
  323. userSchema.statics.findUsers = function(options, callback) {
  324. const sort = options.sort || { status: 1, createdAt: 1 };
  325. this.find()
  326. .sort(sort)
  327. .skip(options.skip || 0)
  328. .limit(options.limit || 21)
  329. .exec((err, userData) => {
  330. callback(err, userData);
  331. });
  332. };
  333. userSchema.statics.findAllUsers = function(option) {
  334. // eslint-disable-next-line no-param-reassign
  335. option = option || {};
  336. const sort = option.sort || { createdAt: -1 };
  337. const fields = option.fields || USER_PUBLIC_FIELDS;
  338. let status = option.status || [STATUS_ACTIVE, STATUS_SUSPENDED];
  339. if (!Array.isArray(status)) {
  340. status = [status];
  341. }
  342. return this.find()
  343. .or(status.map((s) => { return { status: s } }))
  344. .select(fields)
  345. .sort(sort);
  346. };
  347. userSchema.statics.findUsersByIds = function(ids, option) {
  348. // eslint-disable-next-line no-param-reassign
  349. option = option || {};
  350. const sort = option.sort || { createdAt: -1 };
  351. const status = option.status || STATUS_ACTIVE;
  352. const fields = option.fields || USER_PUBLIC_FIELDS;
  353. return this.find({ _id: { $in: ids }, status })
  354. .select(fields)
  355. .sort(sort);
  356. };
  357. userSchema.statics.findAdmins = function(callback) {
  358. this.find({ admin: true })
  359. .exec((err, admins) => {
  360. debug('Admins: ', admins);
  361. callback(err, admins);
  362. });
  363. };
  364. userSchema.statics.findUsersWithPagination = async function(options) {
  365. const sort = options.sort || { status: 1, username: 1, createdAt: 1 };
  366. // eslint-disable-next-line no-return-await
  367. return await this.paginate({ status: { $ne: STATUS_DELETED } }, { page: options.page || 1, limit: options.limit || PAGE_ITEMS }, (err, result) => {
  368. if (err) {
  369. debug('Error on pagination:', err);
  370. throw new Error(err);
  371. }
  372. return result;
  373. }, { sortBy: sort });
  374. };
  375. userSchema.statics.findUsersByPartOfEmail = function(emailPart, options) {
  376. const status = options.status || null;
  377. const emailPartRegExp = new RegExp(emailPart.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'));
  378. const User = this;
  379. return new Promise((resolve, reject) => {
  380. const query = User.find({ email: emailPartRegExp }, USER_PUBLIC_FIELDS);
  381. if (status) {
  382. query.and({ status });
  383. }
  384. query
  385. .limit(PAGE_ITEMS + 1)
  386. .exec((err, userData) => {
  387. if (err) {
  388. return reject(err);
  389. }
  390. return resolve(userData);
  391. });
  392. });
  393. };
  394. userSchema.statics.findUserByUsername = function(username) {
  395. if (username == null) {
  396. return Promise.resolve(null);
  397. }
  398. return this.findOne({ username });
  399. };
  400. userSchema.statics.findUserByApiToken = function(apiToken) {
  401. if (apiToken == null) {
  402. return Promise.resolve(null);
  403. }
  404. return this.findOne({ apiToken });
  405. };
  406. userSchema.statics.findUserByGoogleId = function(googleId, callback) {
  407. if (googleId == null) {
  408. callback(null, null);
  409. }
  410. this.findOne({ googleId }, (err, userData) => {
  411. callback(err, userData);
  412. });
  413. };
  414. userSchema.statics.findUserByUsernameOrEmail = function(usernameOrEmail, password, callback) {
  415. this.findOne()
  416. .or([
  417. { username: usernameOrEmail },
  418. { email: usernameOrEmail },
  419. ])
  420. .exec((err, userData) => {
  421. callback(err, userData);
  422. });
  423. };
  424. userSchema.statics.findUserByEmailAndPassword = function(email, password, callback) {
  425. const hashedPassword = generatePassword(password);
  426. this.findOne({ email, password: hashedPassword }, (err, userData) => {
  427. callback(err, userData);
  428. });
  429. };
  430. userSchema.statics.isUserCountExceedsUpperLimit = async function() {
  431. const Config = crowi.model('Config');
  432. const userUpperLimit = Config.userUpperLimit(crowi);
  433. if (userUpperLimit === 0) {
  434. return false;
  435. }
  436. const activeUsers = await this.countListByStatus(STATUS_ACTIVE);
  437. if (userUpperLimit !== 0 && userUpperLimit <= activeUsers) {
  438. return true;
  439. }
  440. return false;
  441. };
  442. userSchema.statics.countListByStatus = async function(status) {
  443. const User = this;
  444. const conditions = { status };
  445. // TODO count は非推奨。mongoose のバージョンアップ後に countDocuments に変更する。
  446. return User.count(conditions);
  447. };
  448. userSchema.statics.isRegisterableUsername = async function(username) {
  449. let usernameUsable = true;
  450. const userData = await this.findOne({ username });
  451. if (userData) {
  452. usernameUsable = false;
  453. }
  454. return usernameUsable;
  455. };
  456. userSchema.statics.isRegisterable = function(email, username, callback) {
  457. const User = this;
  458. let emailUsable = true;
  459. let usernameUsable = true;
  460. // username check
  461. this.findOne({ username }, (err, userData) => {
  462. if (userData) {
  463. usernameUsable = false;
  464. }
  465. // email check
  466. User.findOne({ email }, (err, userData) => {
  467. if (userData) {
  468. emailUsable = false;
  469. }
  470. if (!emailUsable || !usernameUsable) {
  471. return callback(false, { email: emailUsable, username: usernameUsable });
  472. }
  473. return callback(true, {});
  474. });
  475. });
  476. };
  477. userSchema.statics.removeCompletelyById = function(id, callback) {
  478. const User = this;
  479. User.findById(id, (err, userData) => {
  480. if (!userData) {
  481. return callback(err, null);
  482. }
  483. debug('Removing user:', userData);
  484. // 物理削除可能なのは、承認待ちユーザー、招待中ユーザーのみ
  485. // 利用を一度開始したユーザーは論理削除のみ可能
  486. if (userData.status !== STATUS_REGISTERED && userData.status !== STATUS_INVITED) {
  487. return callback(new Error('Cannot remove completely the user whoes status is not INVITED'), null);
  488. }
  489. userData.remove((err) => {
  490. if (err) {
  491. return callback(err, null);
  492. }
  493. return callback(null, 1);
  494. });
  495. });
  496. };
  497. userSchema.statics.resetPasswordByRandomString = function(id) {
  498. const User = this;
  499. return new Promise(((resolve, reject) => {
  500. User.findById(id, (err, userData) => {
  501. if (!userData) {
  502. return reject(new Error('User not found'));
  503. }
  504. // is updatable check
  505. // if (userData.isUp
  506. const newPassword = generateRandomTempPassword();
  507. userData.setPassword(newPassword);
  508. userData.save((err, userData) => {
  509. if (err) {
  510. return reject(err);
  511. }
  512. resolve({ user: userData, newPassword });
  513. });
  514. });
  515. }));
  516. };
  517. userSchema.statics.createUsersByInvitation = function(emailList, toSendEmail, callback) {
  518. validateCrowi();
  519. const User = this;
  520. const createdUserList = [];
  521. const Config = crowi.model('Config');
  522. const config = crowi.getConfig();
  523. const mailer = crowi.getMailer();
  524. if (!Array.isArray(emailList)) {
  525. debug('emailList is not array');
  526. }
  527. async.each(
  528. emailList,
  529. (email, next) => {
  530. const newUser = new User();
  531. let tmpUsername;
  532. let password;
  533. // eslint-disable-next-line no-param-reassign
  534. email = email.trim();
  535. // email check
  536. // TODO: 削除済みはチェック対象から外そう〜
  537. User.findOne({ email }, (err, userData) => {
  538. // The user is exists
  539. if (userData) {
  540. createdUserList.push({
  541. email,
  542. password: null,
  543. user: null,
  544. });
  545. return next();
  546. }
  547. /* eslint-disable newline-per-chained-call */
  548. tmpUsername = `temp_${Math.random().toString(36).slice(-16)}`;
  549. password = Math.random().toString(36).slice(-16);
  550. /* eslint-enable newline-per-chained-call */
  551. newUser.username = tmpUsername;
  552. newUser.email = email;
  553. newUser.setPassword(password);
  554. newUser.createdAt = Date.now();
  555. newUser.status = STATUS_INVITED;
  556. const globalLang = Config.globalLang(config);
  557. if (globalLang != null) {
  558. newUser.lang = globalLang;
  559. }
  560. newUser.save((err, userData) => {
  561. if (err) {
  562. createdUserList.push({
  563. email,
  564. password: null,
  565. user: null,
  566. });
  567. debug('save failed!! ', err);
  568. }
  569. else {
  570. createdUserList.push({
  571. email,
  572. password,
  573. user: userData,
  574. });
  575. debug('saved!', email);
  576. }
  577. next();
  578. });
  579. });
  580. },
  581. (err) => {
  582. if (err) {
  583. debug('error occured while iterate email list');
  584. }
  585. if (toSendEmail) {
  586. // TODO: メール送信部分のロジックをサービス化する
  587. async.each(
  588. createdUserList,
  589. (user, next) => {
  590. if (user.password === null) {
  591. return next();
  592. }
  593. mailer.send({
  594. to: user.email,
  595. subject: `Invitation to ${Config.appTitle(config)}`,
  596. template: path.join(crowi.localeDir, 'en-US/admin/userInvitation.txt'),
  597. vars: {
  598. email: user.email,
  599. password: user.password,
  600. url: crowi.configManager.getSiteUrl(),
  601. appTitle: Config.appTitle(config),
  602. },
  603. },
  604. (err, s) => {
  605. debug('completed to send email: ', err, s);
  606. next();
  607. });
  608. },
  609. (err) => {
  610. debug('Sending invitation email completed.', err);
  611. },
  612. );
  613. }
  614. debug('createdUserList!!! ', createdUserList);
  615. return callback(null, createdUserList);
  616. },
  617. );
  618. };
  619. userSchema.statics.createUserByEmailAndPasswordAndStatus = async function(name, username, email, password, lang, status, callback) {
  620. const User = this;
  621. const newUser = new User();
  622. // check user upper limit
  623. const isUserCountExceedsUpperLimit = await User.isUserCountExceedsUpperLimit();
  624. if (isUserCountExceedsUpperLimit) {
  625. const err = new UserUpperLimitException();
  626. return callback(err);
  627. }
  628. // check email duplication because email must be unique
  629. const count = await this.count({ email });
  630. if (count > 0) {
  631. // eslint-disable-next-line no-param-reassign
  632. email = generateRandomEmail();
  633. }
  634. newUser.name = name;
  635. newUser.username = username;
  636. newUser.email = email;
  637. if (password != null) {
  638. newUser.setPassword(password);
  639. }
  640. const Config = crowi.model('Config');
  641. const config = crowi.getConfig();
  642. const globalLang = Config.globalLang(config);
  643. if (globalLang != null) {
  644. newUser.lang = globalLang;
  645. }
  646. if (lang != null) {
  647. newUser.lang = lang;
  648. }
  649. newUser.createdAt = Date.now();
  650. newUser.status = status || decideUserStatusOnRegistration();
  651. newUser.save((err, userData) => {
  652. if (err) {
  653. logger.error('createUserByEmailAndPasswordAndStatus failed: ', err);
  654. return callback(err);
  655. }
  656. if (userData.status === STATUS_ACTIVE) {
  657. userEvent.emit('activated', userData);
  658. }
  659. return callback(err, userData);
  660. });
  661. };
  662. /**
  663. * A wrapper function of createUserByEmailAndPasswordAndStatus with callback
  664. *
  665. */
  666. userSchema.statics.createUserByEmailAndPassword = function(name, username, email, password, lang, callback) {
  667. this.createUserByEmailAndPasswordAndStatus(name, username, email, password, lang, undefined, callback);
  668. };
  669. /**
  670. * A wrapper function of createUserByEmailAndPasswordAndStatus
  671. *
  672. * @return {Promise<User>}
  673. */
  674. userSchema.statics.createUser = function(name, username, email, password, lang, status) {
  675. const User = this;
  676. return new Promise((resolve, reject) => {
  677. User.createUserByEmailAndPasswordAndStatus(name, username, email, password, lang, status, (err, userData) => {
  678. if (err) {
  679. return reject(err);
  680. }
  681. return resolve(userData);
  682. });
  683. });
  684. };
  685. userSchema.statics.getUsernameByPath = function(path) {
  686. let username = null;
  687. const match = path.match(/^\/user\/([^/]+)\/?/);
  688. if (match) {
  689. username = match[1];
  690. }
  691. return username;
  692. };
  693. class UserUpperLimitException {
  694. constructor() {
  695. this.name = this.constructor.name;
  696. }
  697. }
  698. userSchema.statics.STATUS_REGISTERED = STATUS_REGISTERED;
  699. userSchema.statics.STATUS_ACTIVE = STATUS_ACTIVE;
  700. userSchema.statics.STATUS_SUSPENDED = STATUS_SUSPENDED;
  701. userSchema.statics.STATUS_DELETED = STATUS_DELETED;
  702. userSchema.statics.STATUS_INVITED = STATUS_INVITED;
  703. userSchema.statics.USER_PUBLIC_FIELDS = USER_PUBLIC_FIELDS;
  704. userSchema.statics.IMAGE_POPULATION = IMAGE_POPULATION;
  705. userSchema.statics.PAGE_ITEMS = PAGE_ITEMS;
  706. userSchema.statics.LANG_EN = LANG_EN;
  707. userSchema.statics.LANG_EN_US = LANG_EN_US;
  708. userSchema.statics.LANG_EN_GB = LANG_EN_US;
  709. userSchema.statics.LANG_JA = LANG_JA;
  710. return mongoose.model('User', userSchema);
  711. };