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.disconnect(); | ||||
| } | ||||
|  | ||||
|  | ||||
| require('./promisify')(exports); | ||||
|   | ||||
| @@ -1,52 +1,39 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| var async = require('async'); | ||||
| var nconf = require('nconf'); | ||||
|  | ||||
| var db = require('../database'); | ||||
| var Password = require('../password'); | ||||
| const nconf = require('nconf'); | ||||
|  | ||||
| const db = require('../database'); | ||||
| const Password = require('../password'); | ||||
|  | ||||
| module.exports = function (User) { | ||||
| 	User.hashPassword = function (password, callback) { | ||||
| 	User.hashPassword = async function (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 || ''; | ||||
| 		var hashedPassword; | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				db.getObjectField('user:' + uid, 'password', next); | ||||
| 			}, | ||||
| 			function (_hashedPassword, next) { | ||||
| 				hashedPassword = _hashedPassword; | ||||
| 				if (!hashedPassword) { | ||||
| 					// Non-existant user, submit fake hash for comparison | ||||
| 					hashedPassword = ''; | ||||
| 				} | ||||
| 		var hashedPassword = await db.getObjectField('user:' + uid, 'password'); | ||||
| 		if (!hashedPassword) { | ||||
| 			// Non-existant user, submit fake hash for comparison | ||||
| 			hashedPassword = ''; | ||||
| 		} | ||||
|  | ||||
| 				User.isPasswordValid(password, 0, next); | ||||
| 			}, | ||||
| 			async.apply(User.auth.logAttempt, uid, ip), | ||||
| 			function (next) { | ||||
| 				Password.compare(password, hashedPassword, next); | ||||
| 			}, | ||||
| 			function (ok, next) { | ||||
| 				if (ok) { | ||||
| 					User.auth.clearLoginAttempts(uid); | ||||
| 				} | ||||
| 				next(null, ok); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 		await User.isPasswordValid(password, 0); | ||||
| 		await User.auth.logAttempt(uid, ip); | ||||
| 		const ok = await Password.compare(password, hashedPassword); | ||||
| 		if (ok) { | ||||
| 			User.auth.clearLoginAttempts(uid); | ||||
| 		} | ||||
| 		return ok; | ||||
| 	}; | ||||
|  | ||||
| 	User.hasPassword = function (uid, callback) { | ||||
| 		db.getObjectField('user:' + uid, 'password', function (err, hashedPassword) { | ||||
| 			callback(err, !!hashedPassword); | ||||
| 		}); | ||||
| 	User.hasPassword = async function (uid) { | ||||
| 		const hashedPassword = await db.getObjectField('user:' + uid, 'password'); | ||||
| 		return !!hashedPassword; | ||||
| 	}; | ||||
| }; | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| var async = require('async'); | ||||
| var winston = require('winston'); | ||||
|  | ||||
| var file = require('../file'); | ||||
| @@ -9,156 +8,129 @@ var meta = require('../meta'); | ||||
| var db = require('../database'); | ||||
|  | ||||
| module.exports = function (User) { | ||||
| 	User.updateCoverPosition = function (uid, position, callback) { | ||||
| 	User.updateCoverPosition = async function (uid, position) { | ||||
| 		// Reject anything that isn't two percentages | ||||
| 		if (!/^[\d.]+%\s[\d.]+%$/.test(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) { | ||||
| 		var url; | ||||
| 		var picture = { | ||||
| 	User.updateCoverPicture = async function (data) { | ||||
| 		const picture = { | ||||
| 			name: 'profileCover', | ||||
| 			uid: data.uid, | ||||
| 		}; | ||||
|  | ||||
| 		if (!data.imageData && data.position) { | ||||
| 			return User.updateCoverPosition(data.uid, data.position, callback); | ||||
| 		try { | ||||
| 			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) { | ||||
| 		if (!meta.config.allowProfileImageUploads) { | ||||
| 			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 = { | ||||
| 	User.uploadCroppedPicture = async function (data) { | ||||
| 		const picture = { | ||||
| 			name: 'profileAvatar', | ||||
| 			uid: data.uid, | ||||
| 		}; | ||||
|  | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				if (data.file) { | ||||
| 					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; | ||||
| 		try { | ||||
| 			if (!meta.config.allowProfileImageUploads) { | ||||
| 				throw new Error('[[error:profile-image-uploads-disabled]]'); | ||||
| 			} | ||||
|  | ||||
| 				User.setUserFields(data.uid, { | ||||
| 					uploadedpicture: uploadedImage.url, | ||||
| 					picture: uploadedImage.url, | ||||
| 				}, next); | ||||
| 			}, | ||||
| 		], function (err) { | ||||
| 			file.delete(picture.path); | ||||
| 			callback(err, uploadedImage); | ||||
| 		}); | ||||
| 			if (!data.imageData && !data.file) { | ||||
| 				throw new Error('[[error:invalid-data]]'); | ||||
| 			} | ||||
|  | ||||
| 			const size = data.file ? data.file.size : image.sizeFromBase64(data.imageData); | ||||
| 			const uploadSize = meta.config.maximumProfileImageSize; | ||||
| 			if (size > uploadSize * 1024) { | ||||
| 				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; | ||||
| 		if (!convertToPNG) { | ||||
| 			return setImmediate(callback, null, path); | ||||
| 			return path; | ||||
| 		} | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				image.normalise(path, extension, next); | ||||
| 			}, | ||||
| 			function (newPath, next) { | ||||
| 				file.delete(path); | ||||
| 				next(null, newPath); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 		const newPath = await image.normalise(path, extension); | ||||
| 		file.delete(path); | ||||
| 		return newPath; | ||||
| 	} | ||||
|  | ||||
| 	function generateProfileImageFilename(uid, type, extension) { | ||||
| @@ -167,7 +139,7 @@ module.exports = function (User) { | ||||
| 		return uid + '-' + type + (keepAllVersions ? '-' + Date.now() : '') + (convertToPNG ? '.png' : extension); | ||||
| 	} | ||||
|  | ||||
| 	User.removeCoverPicture = function (data, callback) { | ||||
| 		db.deleteObjectFields('user:' + data.uid, ['cover:url', 'cover:position'], callback); | ||||
| 	User.removeCoverPicture = async function (data) { | ||||
| 		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) { | ||||
| 			meta.config.allowProfileImageUploads = 0; | ||||
| 			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, | ||||
| 				name: 'test.png', | ||||
| 				type: 'image/png', | ||||
| @@ -916,7 +916,7 @@ describe('User', function () { | ||||
| 		it('should return error if profile image is too big', function (done) { | ||||
| 			meta.config.allowProfileImageUploads = 1; | ||||
| 			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, | ||||
| 				name: 'test.png', | ||||
| 				type: 'image/png', | ||||
| @@ -933,7 +933,7 @@ describe('User', function () { | ||||
|  | ||||
| 		it('should return error if profile image has no mime type', function (done) { | ||||
| 			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, | ||||
| 				name: 'test', | ||||
| 			}; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user