mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 11:05:54 +01:00 
			
		
		
		
	| @@ -69,3 +69,6 @@ function done(err, result) { | |||||||
| 	process.send(err ? { err: err.message } : { result: result }); | 	process.send(err ? { err: err.message } : { result: result }); | ||||||
| 	process.disconnect(); | 	process.disconnect(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | require('./promisify')(exports); | ||||||
|   | |||||||
| @@ -1,52 +1,39 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
| var async = require('async'); |  | ||||||
| var nconf = require('nconf'); |  | ||||||
|  |  | ||||||
| var db = require('../database'); | const nconf = require('nconf'); | ||||||
| var Password = require('../password'); |  | ||||||
|  | const db = require('../database'); | ||||||
|  | const Password = require('../password'); | ||||||
|  |  | ||||||
| module.exports = function (User) { | module.exports = function (User) { | ||||||
| 	User.hashPassword = function (password, callback) { | 	User.hashPassword = async function (password) { | ||||||
| 		if (!password) { | 		if (!password) { | ||||||
| 			return callback(null, password); | 			return password; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		Password.hash(nconf.get('bcrypt_rounds') || 12, password, callback); | 		return await Password.hash(nconf.get('bcrypt_rounds') || 12, password); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	User.isPasswordCorrect = function (uid, password, ip, callback) { | 	User.isPasswordCorrect = async function (uid, password, ip) { | ||||||
| 		password = password || ''; | 		password = password || ''; | ||||||
| 		var hashedPassword; | 		var hashedPassword = await db.getObjectField('user:' + uid, 'password'); | ||||||
| 		async.waterfall([ | 		if (!hashedPassword) { | ||||||
| 			function (next) { | 			// Non-existant user, submit fake hash for comparison | ||||||
| 				db.getObjectField('user:' + uid, 'password', next); | 			hashedPassword = ''; | ||||||
| 			}, | 		} | ||||||
| 			function (_hashedPassword, next) { |  | ||||||
| 				hashedPassword = _hashedPassword; |  | ||||||
| 				if (!hashedPassword) { |  | ||||||
| 					// Non-existant user, submit fake hash for comparison |  | ||||||
| 					hashedPassword = ''; |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				User.isPasswordValid(password, 0, next); | 		await User.isPasswordValid(password, 0); | ||||||
| 			}, | 		await User.auth.logAttempt(uid, ip); | ||||||
| 			async.apply(User.auth.logAttempt, uid, ip), | 		const ok = await Password.compare(password, hashedPassword); | ||||||
| 			function (next) { | 		if (ok) { | ||||||
| 				Password.compare(password, hashedPassword, next); | 			User.auth.clearLoginAttempts(uid); | ||||||
| 			}, | 		} | ||||||
| 			function (ok, next) { | 		return ok; | ||||||
| 				if (ok) { |  | ||||||
| 					User.auth.clearLoginAttempts(uid); |  | ||||||
| 				} |  | ||||||
| 				next(null, ok); |  | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	User.hasPassword = function (uid, callback) { | 	User.hasPassword = async function (uid) { | ||||||
| 		db.getObjectField('user:' + uid, 'password', function (err, hashedPassword) { | 		const hashedPassword = await db.getObjectField('user:' + uid, 'password'); | ||||||
| 			callback(err, !!hashedPassword); | 		return !!hashedPassword; | ||||||
| 		}); |  | ||||||
| 	}; | 	}; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
| var async = require('async'); |  | ||||||
| var winston = require('winston'); | var winston = require('winston'); | ||||||
|  |  | ||||||
| var file = require('../file'); | var file = require('../file'); | ||||||
| @@ -9,156 +8,129 @@ var meta = require('../meta'); | |||||||
| var db = require('../database'); | var db = require('../database'); | ||||||
|  |  | ||||||
| module.exports = function (User) { | module.exports = function (User) { | ||||||
| 	User.updateCoverPosition = function (uid, position, callback) { | 	User.updateCoverPosition = async function (uid, position) { | ||||||
| 		// Reject anything that isn't two percentages | 		// Reject anything that isn't two percentages | ||||||
| 		if (!/^[\d.]+%\s[\d.]+%$/.test(position)) { | 		if (!/^[\d.]+%\s[\d.]+%$/.test(position)) { | ||||||
| 			winston.warn('[user/updateCoverPosition] Invalid position received: ' + position); | 			winston.warn('[user/updateCoverPosition] Invalid position received: ' + position); | ||||||
| 			return callback(new Error('[[error:invalid-data]]')); | 			throw new Error('[[error:invalid-data]]'); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		User.setUserField(uid, 'cover:position', position, callback); | 		await User.setUserField(uid, 'cover:position', position); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	User.updateCoverPicture = function (data, callback) { | 	User.updateCoverPicture = async function (data) { | ||||||
| 		var url; | 		const picture = { | ||||||
| 		var picture = { |  | ||||||
| 			name: 'profileCover', | 			name: 'profileCover', | ||||||
| 			uid: data.uid, | 			uid: data.uid, | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		if (!data.imageData && data.position) { | 		try { | ||||||
| 			return User.updateCoverPosition(data.uid, data.position, callback); | 			if (!data.imageData && data.position) { | ||||||
|  | 				return await User.updateCoverPosition(data.uid, data.position); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (!data.imageData && !data.file) { | ||||||
|  | 				throw new Error('[[error:invalid-data]]'); | ||||||
|  | 			} | ||||||
|  | 			const size = data.file ? data.file.size : image.sizeFromBase64(data.imageData); | ||||||
|  | 			if (size > meta.config.maximumCoverImageSize * 1024) { | ||||||
|  | 				throw new Error('[[error:file-too-big, ' + meta.config.maximumCoverImageSize + ']]'); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (data.file) { | ||||||
|  | 				picture.path = data.file.path; | ||||||
|  | 			} else { | ||||||
|  | 				picture.path = await image.writeImageDataToTempFile(data.imageData); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			const type = data.file ? data.file.type : image.mimeFromBase64(data.imageData); | ||||||
|  | 			if (!type || !type.match(/^image./)) { | ||||||
|  | 				throw new Error('[[error:invalid-image]]'); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			const extension = file.typeToExtension(type); | ||||||
|  | 			const filename = generateProfileImageFilename(data.uid, 'profilecover', extension); | ||||||
|  | 			const uploadData = await image.uploadImage(filename, 'profile', picture); | ||||||
|  |  | ||||||
|  | 			await User.setUserField(data.uid, 'cover:url', uploadData.url); | ||||||
|  |  | ||||||
|  | 			if (data.position) { | ||||||
|  | 				await User.updateCoverPosition(data.uid, data.position); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return { | ||||||
|  | 				url: uploadData.url, | ||||||
|  | 			}; | ||||||
|  | 		} finally { | ||||||
|  | 			file.delete(picture.path || (data.file && data.file.path)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (!data.imageData && !data.file) { |  | ||||||
| 			return callback(new Error('[[error:invalid-data]]')); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		async.waterfall([ |  | ||||||
| 			function (next) { |  | ||||||
| 				var size = data.file ? data.file.size : image.sizeFromBase64(data.imageData); |  | ||||||
| 				if (size > meta.config.maximumCoverImageSize * 1024) { |  | ||||||
| 					return next(new Error('[[error:file-too-big, ' + meta.config.maximumCoverImageSize + ']]')); |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				if (data.file) { |  | ||||||
| 					return setImmediate(next, null, data.file.path); |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				image.writeImageDataToTempFile(data.imageData, next); |  | ||||||
| 			}, |  | ||||||
| 			function (path, next) { |  | ||||||
| 				picture.path = path; |  | ||||||
|  |  | ||||||
| 				var type = data.file ? data.file.type : image.mimeFromBase64(data.imageData); |  | ||||||
| 				if (!type || !type.match(/^image./)) { |  | ||||||
| 					return next(new Error('[[error:invalid-image]]')); |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				var extension = file.typeToExtension(type); |  | ||||||
| 				var filename = generateProfileImageFilename(data.uid, 'profilecover', extension); |  | ||||||
| 				image.uploadImage(filename, 'profile', picture, next); |  | ||||||
| 			}, |  | ||||||
| 			function (uploadData, next) { |  | ||||||
| 				url = uploadData.url; |  | ||||||
| 				User.setUserField(data.uid, 'cover:url', uploadData.url, next); |  | ||||||
| 			}, |  | ||||||
| 			function (next) { |  | ||||||
| 				if (data.position) { |  | ||||||
| 					User.updateCoverPosition(data.uid, data.position, next); |  | ||||||
| 				} else { |  | ||||||
| 					setImmediate(next); |  | ||||||
| 				} |  | ||||||
| 			}, |  | ||||||
| 		], function (err) { |  | ||||||
| 			file.delete(picture.path); |  | ||||||
| 			callback(err, { |  | ||||||
| 				url: url, |  | ||||||
| 			}); |  | ||||||
| 		}); |  | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	User.uploadCroppedPicture = function (data, callback) { | 	User.uploadCroppedPicture = async function (data) { | ||||||
| 		if (!meta.config.allowProfileImageUploads) { | 		const picture = { | ||||||
| 			return callback(new Error('[[error:profile-image-uploads-disabled]]')); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if (!data.imageData && !data.file) { |  | ||||||
| 			return callback(new Error('[[error:invalid-data]]')); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		var size = data.file ? data.file.size : image.sizeFromBase64(data.imageData); |  | ||||||
| 		var uploadSize = meta.config.maximumProfileImageSize; |  | ||||||
| 		if (size > uploadSize * 1024) { |  | ||||||
| 			return callback(new Error('[[error:file-too-big, ' + uploadSize + ']]')); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		var type = data.file ? data.file.type : image.mimeFromBase64(data.imageData); |  | ||||||
| 		if (!type || !type.match(/^image./)) { |  | ||||||
| 			return callback(new Error('[[error:invalid-image]]')); |  | ||||||
| 		} |  | ||||||
| 		var extension = file.typeToExtension(type); |  | ||||||
| 		if (!extension) { |  | ||||||
| 			return callback(new Error('[[error:invalid-image-extension]]')); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		var uploadedImage; |  | ||||||
|  |  | ||||||
| 		var picture = { |  | ||||||
| 			name: 'profileAvatar', | 			name: 'profileAvatar', | ||||||
| 			uid: data.uid, | 			uid: data.uid, | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		async.waterfall([ | 		try { | ||||||
| 			function (next) { | 			if (!meta.config.allowProfileImageUploads) { | ||||||
| 				if (data.file) { | 				throw new Error('[[error:profile-image-uploads-disabled]]'); | ||||||
| 					return setImmediate(next, null, data.file.path); | 			} | ||||||
| 				} |  | ||||||
| 				image.writeImageDataToTempFile(data.imageData, next); |  | ||||||
| 			}, |  | ||||||
| 			function (path, next) { |  | ||||||
| 				convertToPNG(path, extension, next); |  | ||||||
| 			}, |  | ||||||
| 			function (path, next) { |  | ||||||
| 				picture.path = path; |  | ||||||
| 				image.resizeImage({ |  | ||||||
| 					path: picture.path, |  | ||||||
| 					width: meta.config.profileImageDimension, |  | ||||||
| 					height: meta.config.profileImageDimension, |  | ||||||
| 				}, next); |  | ||||||
| 			}, |  | ||||||
| 			function (next) { |  | ||||||
| 				var filename = generateProfileImageFilename(data.uid, 'profileavatar', extension); |  | ||||||
| 				image.uploadImage(filename, 'profile', picture, next); |  | ||||||
| 			}, |  | ||||||
| 			function (_uploadedImage, next) { |  | ||||||
| 				uploadedImage = _uploadedImage; |  | ||||||
|  |  | ||||||
| 				User.setUserFields(data.uid, { | 			if (!data.imageData && !data.file) { | ||||||
| 					uploadedpicture: uploadedImage.url, | 				throw new Error('[[error:invalid-data]]'); | ||||||
| 					picture: uploadedImage.url, | 			} | ||||||
| 				}, next); |  | ||||||
| 			}, | 			const size = data.file ? data.file.size : image.sizeFromBase64(data.imageData); | ||||||
| 		], function (err) { | 			const uploadSize = meta.config.maximumProfileImageSize; | ||||||
| 			file.delete(picture.path); | 			if (size > uploadSize * 1024) { | ||||||
| 			callback(err, uploadedImage); | 				throw new Error('[[error:file-too-big, ' + uploadSize + ']]'); | ||||||
| 		}); | 			} | ||||||
|  |  | ||||||
|  | 			const type = data.file ? data.file.type : image.mimeFromBase64(data.imageData); | ||||||
|  | 			if (!type || !type.match(/^image./)) { | ||||||
|  | 				throw new Error('[[error:invalid-image]]'); | ||||||
|  | 			} | ||||||
|  | 			const extension = file.typeToExtension(type); | ||||||
|  | 			if (!extension) { | ||||||
|  | 				throw new Error('[[error:invalid-image-extension]]'); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (data.file) { | ||||||
|  | 				picture.path = data.file.path; | ||||||
|  | 			} else { | ||||||
|  | 				picture.path = await image.writeImageDataToTempFile(data.imageData); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			picture.path = await convertToPNG(picture.path, extension); | ||||||
|  |  | ||||||
|  | 			await image.resizeImage({ | ||||||
|  | 				path: picture.path, | ||||||
|  | 				width: meta.config.profileImageDimension, | ||||||
|  | 				height: meta.config.profileImageDimension, | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  | 			const filename = generateProfileImageFilename(data.uid, 'profileavatar', extension); | ||||||
|  | 			const uploadedImage = await image.uploadImage(filename, 'profile', picture); | ||||||
|  |  | ||||||
|  | 			await User.setUserFields(data.uid, { | ||||||
|  | 				uploadedpicture: uploadedImage.url, | ||||||
|  | 				picture: uploadedImage.url, | ||||||
|  | 			}); | ||||||
|  | 			return uploadedImage; | ||||||
|  | 		} finally { | ||||||
|  | 			file.delete(picture.path || (data.file && data.file.path)); | ||||||
|  | 		} | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	function convertToPNG(path, extension, callback) { | 	async function convertToPNG(path, extension) { | ||||||
| 		var convertToPNG = meta.config['profile:convertProfileImageToPNG'] === 1; | 		var convertToPNG = meta.config['profile:convertProfileImageToPNG'] === 1; | ||||||
| 		if (!convertToPNG) { | 		if (!convertToPNG) { | ||||||
| 			return setImmediate(callback, null, path); | 			return path; | ||||||
| 		} | 		} | ||||||
| 		async.waterfall([ | 		const newPath = await image.normalise(path, extension); | ||||||
| 			function (next) { | 		file.delete(path); | ||||||
| 				image.normalise(path, extension, next); | 		return newPath; | ||||||
| 			}, |  | ||||||
| 			function (newPath, next) { |  | ||||||
| 				file.delete(path); |  | ||||||
| 				next(null, newPath); |  | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	function generateProfileImageFilename(uid, type, extension) { | 	function generateProfileImageFilename(uid, type, extension) { | ||||||
| @@ -167,7 +139,7 @@ module.exports = function (User) { | |||||||
| 		return uid + '-' + type + (keepAllVersions ? '-' + Date.now() : '') + (convertToPNG ? '.png' : extension); | 		return uid + '-' + type + (keepAllVersions ? '-' + Date.now() : '') + (convertToPNG ? '.png' : extension); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	User.removeCoverPicture = function (data, callback) { | 	User.removeCoverPicture = async function (data) { | ||||||
| 		db.deleteObjectFields('user:' + data.uid, ['cover:url', 'cover:position'], callback); | 		await db.deleteObjectFields('user:' + data.uid, ['cover:url', 'cover:position']); | ||||||
| 	}; | 	}; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -899,7 +899,7 @@ describe('User', function () { | |||||||
| 		it('should return error if profile image uploads disabled', function (done) { | 		it('should return error if profile image uploads disabled', function (done) { | ||||||
| 			meta.config.allowProfileImageUploads = 0; | 			meta.config.allowProfileImageUploads = 0; | ||||||
| 			var picture = { | 			var picture = { | ||||||
| 				path: path.join(nconf.get('base_dir'), 'test/files/test.png'), | 				path: path.join(nconf.get('base_dir'), 'test/files/test_copy.png'), | ||||||
| 				size: 7189, | 				size: 7189, | ||||||
| 				name: 'test.png', | 				name: 'test.png', | ||||||
| 				type: 'image/png', | 				type: 'image/png', | ||||||
| @@ -916,7 +916,7 @@ describe('User', function () { | |||||||
| 		it('should return error if profile image is too big', function (done) { | 		it('should return error if profile image is too big', function (done) { | ||||||
| 			meta.config.allowProfileImageUploads = 1; | 			meta.config.allowProfileImageUploads = 1; | ||||||
| 			var picture = { | 			var picture = { | ||||||
| 				path: path.join(nconf.get('base_dir'), 'test/files/test.png'), | 				path: path.join(nconf.get('base_dir'), 'test/files/test_copy.png'), | ||||||
| 				size: 265000, | 				size: 265000, | ||||||
| 				name: 'test.png', | 				name: 'test.png', | ||||||
| 				type: 'image/png', | 				type: 'image/png', | ||||||
| @@ -933,7 +933,7 @@ describe('User', function () { | |||||||
|  |  | ||||||
| 		it('should return error if profile image has no mime type', function (done) { | 		it('should return error if profile image has no mime type', function (done) { | ||||||
| 			var picture = { | 			var picture = { | ||||||
| 				path: path.join(nconf.get('base_dir'), 'test/files/test.png'), | 				path: path.join(nconf.get('base_dir'), 'test/files/test_copy.png'), | ||||||
| 				size: 7189, | 				size: 7189, | ||||||
| 				name: 'test', | 				name: 'test', | ||||||
| 			}; | 			}; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user