diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2016-10-02 15:39:09 +0200 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2016-10-02 15:39:09 +0200 |
commit | a6375e69668ea42e19531c6bc68dcd37f3f7cbd7 (patch) | |
tree | 03204a408d56311692c3528bedcf95d2455e94f2 /server/helpers | |
parent | 052937db8a8d282eccdbdf38d487ed8d85d3c0a7 (diff) | |
parent | c4403b29ad4db097af528a7f04eea07e0ed320d0 (diff) | |
download | PeerTube-a6375e69668ea42e19531c6bc68dcd37f3f7cbd7.tar.gz PeerTube-a6375e69668ea42e19531c6bc68dcd37f3f7cbd7.tar.zst PeerTube-a6375e69668ea42e19531c6bc68dcd37f3f7cbd7.zip |
Merge branch 'master' into webseed-merged
Diffstat (limited to 'server/helpers')
-rw-r--r-- | server/helpers/custom-validators/index.js | 17 | ||||
-rw-r--r-- | server/helpers/custom-validators/misc.js | 18 | ||||
-rw-r--r-- | server/helpers/custom-validators/pods.js | 21 | ||||
-rw-r--r-- | server/helpers/custom-validators/users.js | 31 | ||||
-rw-r--r-- | server/helpers/custom-validators/videos.js (renamed from server/helpers/custom-validators.js) | 105 | ||||
-rw-r--r-- | server/helpers/logger.js | 10 | ||||
-rw-r--r-- | server/helpers/peertube-crypto.js | 60 | ||||
-rw-r--r-- | server/helpers/requests.js | 15 | ||||
-rw-r--r-- | server/helpers/utils.js | 4 |
9 files changed, 189 insertions, 92 deletions
diff --git a/server/helpers/custom-validators/index.js b/server/helpers/custom-validators/index.js new file mode 100644 index 000000000..96b5b20b9 --- /dev/null +++ b/server/helpers/custom-validators/index.js | |||
@@ -0,0 +1,17 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const miscValidators = require('./misc') | ||
4 | const podsValidators = require('./pods') | ||
5 | const usersValidators = require('./users') | ||
6 | const videosValidators = require('./videos') | ||
7 | |||
8 | const validators = { | ||
9 | misc: miscValidators, | ||
10 | pods: podsValidators, | ||
11 | users: usersValidators, | ||
12 | videos: videosValidators | ||
13 | } | ||
14 | |||
15 | // --------------------------------------------------------------------------- | ||
16 | |||
17 | module.exports = validators | ||
diff --git a/server/helpers/custom-validators/misc.js b/server/helpers/custom-validators/misc.js new file mode 100644 index 000000000..052726241 --- /dev/null +++ b/server/helpers/custom-validators/misc.js | |||
@@ -0,0 +1,18 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const miscValidators = { | ||
4 | exists, | ||
5 | isArray | ||
6 | } | ||
7 | |||
8 | function exists (value) { | ||
9 | return value !== undefined && value !== null | ||
10 | } | ||
11 | |||
12 | function isArray (value) { | ||
13 | return Array.isArray(value) | ||
14 | } | ||
15 | |||
16 | // --------------------------------------------------------------------------- | ||
17 | |||
18 | module.exports = miscValidators | ||
diff --git a/server/helpers/custom-validators/pods.js b/server/helpers/custom-validators/pods.js new file mode 100644 index 000000000..40f8b5d0b --- /dev/null +++ b/server/helpers/custom-validators/pods.js | |||
@@ -0,0 +1,21 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const validator = require('express-validator').validator | ||
4 | |||
5 | const miscValidators = require('./misc') | ||
6 | |||
7 | const podsValidators = { | ||
8 | isEachUniqueUrlValid | ||
9 | } | ||
10 | |||
11 | function isEachUniqueUrlValid (urls) { | ||
12 | return miscValidators.isArray(urls) && | ||
13 | urls.length !== 0 && | ||
14 | urls.every(function (url) { | ||
15 | return validator.isURL(url) && urls.indexOf(url) === urls.lastIndexOf(url) | ||
16 | }) | ||
17 | } | ||
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | |||
21 | module.exports = podsValidators | ||
diff --git a/server/helpers/custom-validators/users.js b/server/helpers/custom-validators/users.js new file mode 100644 index 000000000..88fa1592e --- /dev/null +++ b/server/helpers/custom-validators/users.js | |||
@@ -0,0 +1,31 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const validator = require('express-validator').validator | ||
4 | const values = require('lodash/values') | ||
5 | |||
6 | const constants = require('../../initializers/constants') | ||
7 | const USERS_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.USERS | ||
8 | |||
9 | const usersValidators = { | ||
10 | isUserPasswordValid, | ||
11 | isUserRoleValid, | ||
12 | isUserUsernameValid | ||
13 | } | ||
14 | |||
15 | function isUserPasswordValid (value) { | ||
16 | return validator.isLength(value, USERS_CONSTRAINTS_FIELDS.PASSWORD) | ||
17 | } | ||
18 | |||
19 | function isUserRoleValid (value) { | ||
20 | return values(constants.USER_ROLES).indexOf(value) !== -1 | ||
21 | } | ||
22 | |||
23 | function isUserUsernameValid (value) { | ||
24 | const max = USERS_CONSTRAINTS_FIELDS.USERNAME.max | ||
25 | const min = USERS_CONSTRAINTS_FIELDS.USERNAME.min | ||
26 | return validator.matches(value, new RegExp(`^[a-zA-Z0-9._]{${min},${max}}$`)) | ||
27 | } | ||
28 | |||
29 | // --------------------------------------------------------------------------- | ||
30 | |||
31 | module.exports = usersValidators | ||
diff --git a/server/helpers/custom-validators.js b/server/helpers/custom-validators/videos.js index b666644c0..a507ff686 100644 --- a/server/helpers/custom-validators.js +++ b/server/helpers/custom-validators/videos.js | |||
@@ -2,66 +2,51 @@ | |||
2 | 2 | ||
3 | const validator = require('express-validator').validator | 3 | const validator = require('express-validator').validator |
4 | 4 | ||
5 | const constants = require('../initializers/constants') | 5 | const constants = require('../../initializers/constants') |
6 | const VIDEOS_CONSTRAINTS_FIELDS = constants.VIDEOS_CONSTRAINTS_FIELDS | 6 | const usersValidators = require('./users') |
7 | 7 | const miscValidators = require('./misc') | |
8 | const customValidators = { | 8 | const VIDEOS_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.VIDEOS |
9 | exists: exists, | 9 | |
10 | isEachRemoteVideosValid: isEachRemoteVideosValid, | 10 | const videosValidators = { |
11 | isArray: isArray, | 11 | isEachRemoteVideosValid, |
12 | isVideoAuthorValid: isVideoAuthorValid, | 12 | isVideoAuthorValid, |
13 | isVideoDateValid: isVideoDateValid, | 13 | isVideoDateValid, |
14 | isVideoDescriptionValid: isVideoDescriptionValid, | 14 | isVideoDescriptionValid, |
15 | isVideoDurationValid: isVideoDurationValid, | 15 | isVideoDurationValid, |
16 | isVideoMagnetUriValid: isVideoMagnetUriValid, | 16 | isVideoMagnetUriValid, |
17 | isVideoNameValid: isVideoNameValid, | 17 | isVideoNameValid, |
18 | isVideoPodUrlValid: isVideoPodUrlValid, | 18 | isVideoPodUrlValid, |
19 | isVideoTagsValid: isVideoTagsValid, | 19 | isVideoTagsValid, |
20 | isVideoThumbnailValid: isVideoThumbnailValid, | 20 | isVideoThumbnailValid, |
21 | isVideoThumbnail64Valid: isVideoThumbnail64Valid | 21 | isVideoThumbnail64Valid |
22 | } | ||
23 | |||
24 | function exists (value) { | ||
25 | return value !== undefined && value !== null | ||
26 | } | 22 | } |
27 | 23 | ||
28 | function isEachRemoteVideosValid (requests) { | 24 | function isEachRemoteVideosValid (requests) { |
29 | return requests.every(function (request) { | 25 | return miscValidators.isArray(requests) && |
30 | const video = request.data | 26 | requests.every(function (request) { |
31 | return ( | 27 | const video = request.data |
32 | isRequestTypeAddValid(request.type) && | 28 | return ( |
33 | isVideoAuthorValid(video.author) && | 29 | isRequestTypeAddValid(request.type) && |
34 | isVideoDateValid(video.createdDate) && | 30 | isVideoAuthorValid(video.author) && |
35 | isVideoDescriptionValid(video.description) && | 31 | isVideoDateValid(video.createdDate) && |
36 | isVideoDurationValid(video.duration) && | 32 | isVideoDescriptionValid(video.description) && |
37 | isVideoMagnetUriValid(video.magnetUri) && | 33 | isVideoDurationValid(video.duration) && |
38 | isVideoNameValid(video.name) && | 34 | isVideoMagnetUriValid(video.magnetUri) && |
39 | isVideoPodUrlValid(video.podUrl) && | 35 | isVideoNameValid(video.name) && |
40 | isVideoTagsValid(video.tags) && | 36 | isVideoPodUrlValid(video.podUrl) && |
41 | isVideoThumbnail64Valid(video.thumbnailBase64) | 37 | isVideoTagsValid(video.tags) && |
42 | ) || | 38 | isVideoThumbnail64Valid(video.thumbnailBase64) |
43 | ( | 39 | ) || |
44 | isRequestTypeRemoveValid(request.type) && | 40 | ( |
45 | isVideoNameValid(video.name) && | 41 | isRequestTypeRemoveValid(request.type) && |
46 | isVideoMagnetUriValid(video.magnetUri) | 42 | isVideoNameValid(video.name) && |
47 | ) | 43 | isVideoMagnetUriValid(video.magnetUri) |
48 | }) | 44 | ) |
49 | } | 45 | }) |
50 | |||
51 | function isArray (value) { | ||
52 | return Array.isArray(value) | ||
53 | } | ||
54 | |||
55 | function isRequestTypeAddValid (value) { | ||
56 | return value === 'add' | ||
57 | } | ||
58 | |||
59 | function isRequestTypeRemoveValid (value) { | ||
60 | return value === 'remove' | ||
61 | } | 46 | } |
62 | 47 | ||
63 | function isVideoAuthorValid (value) { | 48 | function isVideoAuthorValid (value) { |
64 | return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.AUTHOR) | 49 | return usersValidators.isUserUsernameValid(value) |
65 | } | 50 | } |
66 | 51 | ||
67 | function isVideoDateValid (value) { | 52 | function isVideoDateValid (value) { |
@@ -90,7 +75,7 @@ function isVideoPodUrlValid (value) { | |||
90 | } | 75 | } |
91 | 76 | ||
92 | function isVideoTagsValid (tags) { | 77 | function isVideoTagsValid (tags) { |
93 | return isArray(tags) && | 78 | return miscValidators.isArray(tags) && |
94 | validator.isInt(tags.length, VIDEOS_CONSTRAINTS_FIELDS.TAGS) && | 79 | validator.isInt(tags.length, VIDEOS_CONSTRAINTS_FIELDS.TAGS) && |
95 | tags.every(function (tag) { | 80 | tags.every(function (tag) { |
96 | return validator.isAlphanumeric(tag) && | 81 | return validator.isAlphanumeric(tag) && |
@@ -109,6 +94,14 @@ function isVideoThumbnail64Valid (value) { | |||
109 | 94 | ||
110 | // --------------------------------------------------------------------------- | 95 | // --------------------------------------------------------------------------- |
111 | 96 | ||
112 | module.exports = customValidators | 97 | module.exports = videosValidators |
113 | 98 | ||
114 | // --------------------------------------------------------------------------- | 99 | // --------------------------------------------------------------------------- |
100 | |||
101 | function isRequestTypeAddValid (value) { | ||
102 | return value === 'add' | ||
103 | } | ||
104 | |||
105 | function isRequestTypeRemoveValid (value) { | ||
106 | return value === 'remove' | ||
107 | } | ||
diff --git a/server/helpers/logger.js b/server/helpers/logger.js index 8ae90a4b2..590ceaeb6 100644 --- a/server/helpers/logger.js +++ b/server/helpers/logger.js | |||
@@ -1,23 +1,23 @@ | |||
1 | // Thanks http://tostring.it/2014/06/23/advanced-logging-with-nodejs/ | 1 | // Thanks http://tostring.it/2014/06/23/advanced-logging-with-nodejs/ |
2 | 'use strict' | 2 | 'use strict' |
3 | 3 | ||
4 | const config = require('config') | ||
5 | const mkdirp = require('mkdirp') | 4 | const mkdirp = require('mkdirp') |
6 | const path = require('path') | 5 | const path = require('path') |
7 | const winston = require('winston') | 6 | const winston = require('winston') |
8 | winston.emitErrs = true | 7 | winston.emitErrs = true |
9 | 8 | ||
10 | const logDir = path.join(__dirname, '..', '..', config.get('storage.logs')) | 9 | const constants = require('../initializers/constants') |
11 | const label = config.get('webserver.host') + ':' + config.get('webserver.port') | 10 | |
11 | const label = constants.CONFIG.WEBSERVER.HOST + ':' + constants.CONFIG.WEBSERVER.PORT | ||
12 | 12 | ||
13 | // Create the directory if it does not exist | 13 | // Create the directory if it does not exist |
14 | mkdirp.sync(logDir) | 14 | mkdirp.sync(constants.CONFIG.STORAGE.LOG_DIR) |
15 | 15 | ||
16 | const logger = new winston.Logger({ | 16 | const logger = new winston.Logger({ |
17 | transports: [ | 17 | transports: [ |
18 | new winston.transports.File({ | 18 | new winston.transports.File({ |
19 | level: 'debug', | 19 | level: 'debug', |
20 | filename: path.join(logDir, 'all-logs.log'), | 20 | filename: path.join(constants.CONFIG.STORAGE.LOG_DIR, 'all-logs.log'), |
21 | handleExceptions: true, | 21 | handleExceptions: true, |
22 | json: true, | 22 | json: true, |
23 | maxsize: 5242880, | 23 | maxsize: 5242880, |
diff --git a/server/helpers/peertube-crypto.js b/server/helpers/peertube-crypto.js index 46dff8d03..1ff638b04 100644 --- a/server/helpers/peertube-crypto.js +++ b/server/helpers/peertube-crypto.js | |||
@@ -1,24 +1,24 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const config = require('config') | 3 | const bcrypt = require('bcrypt') |
4 | const crypto = require('crypto') | 4 | const crypto = require('crypto') |
5 | const fs = require('fs') | 5 | const fs = require('fs') |
6 | const openssl = require('openssl-wrapper') | 6 | const openssl = require('openssl-wrapper') |
7 | const path = require('path') | ||
8 | const ursa = require('ursa') | 7 | const ursa = require('ursa') |
9 | 8 | ||
9 | const constants = require('../initializers/constants') | ||
10 | const logger = require('./logger') | 10 | const logger = require('./logger') |
11 | 11 | ||
12 | const certDir = path.join(__dirname, '..', '..', config.get('storage.certs')) | ||
13 | const algorithm = 'aes-256-ctr' | 12 | const algorithm = 'aes-256-ctr' |
14 | 13 | ||
15 | const peertubeCrypto = { | 14 | const peertubeCrypto = { |
16 | checkSignature: checkSignature, | 15 | checkSignature, |
17 | createCertsIfNotExist: createCertsIfNotExist, | 16 | comparePassword, |
18 | decrypt: decrypt, | 17 | createCertsIfNotExist, |
19 | encrypt: encrypt, | 18 | cryptPassword, |
20 | getCertDir: getCertDir, | 19 | decrypt, |
21 | sign: sign | 20 | encrypt, |
21 | sign | ||
22 | } | 22 | } |
23 | 23 | ||
24 | function checkSignature (publicKey, rawData, hexSignature) { | 24 | function checkSignature (publicKey, rawData, hexSignature) { |
@@ -27,6 +27,14 @@ function checkSignature (publicKey, rawData, hexSignature) { | |||
27 | return isValid | 27 | return isValid |
28 | } | 28 | } |
29 | 29 | ||
30 | function comparePassword (plainPassword, hashPassword, callback) { | ||
31 | bcrypt.compare(plainPassword, hashPassword, function (err, isPasswordMatch) { | ||
32 | if (err) return callback(err) | ||
33 | |||
34 | return callback(null, isPasswordMatch) | ||
35 | }) | ||
36 | } | ||
37 | |||
30 | function createCertsIfNotExist (callback) { | 38 | function createCertsIfNotExist (callback) { |
31 | certsExist(function (exist) { | 39 | certsExist(function (exist) { |
32 | if (exist === true) { | 40 | if (exist === true) { |
@@ -39,8 +47,18 @@ function createCertsIfNotExist (callback) { | |||
39 | }) | 47 | }) |
40 | } | 48 | } |
41 | 49 | ||
50 | function cryptPassword (password, callback) { | ||
51 | bcrypt.genSalt(constants.BCRYPT_SALT_SIZE, function (err, salt) { | ||
52 | if (err) return callback(err) | ||
53 | |||
54 | bcrypt.hash(password, salt, function (err, hash) { | ||
55 | return callback(err, hash) | ||
56 | }) | ||
57 | }) | ||
58 | } | ||
59 | |||
42 | function decrypt (key, data, callback) { | 60 | function decrypt (key, data, callback) { |
43 | fs.readFile(getCertDir() + 'peertube.key.pem', function (err, file) { | 61 | fs.readFile(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem', function (err, file) { |
44 | if (err) return callback(err) | 62 | if (err) return callback(err) |
45 | 63 | ||
46 | const myPrivateKey = ursa.createPrivateKey(file) | 64 | const myPrivateKey = ursa.createPrivateKey(file) |
@@ -67,12 +85,8 @@ function encrypt (publicKey, data, callback) { | |||
67 | }) | 85 | }) |
68 | } | 86 | } |
69 | 87 | ||
70 | function getCertDir () { | ||
71 | return certDir | ||
72 | } | ||
73 | |||
74 | function sign (data) { | 88 | function sign (data) { |
75 | const myKey = ursa.createPrivateKey(fs.readFileSync(certDir + 'peertube.key.pem')) | 89 | const myKey = ursa.createPrivateKey(fs.readFileSync(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem')) |
76 | const signature = myKey.hashAndSign('sha256', data, 'utf8', 'hex') | 90 | const signature = myKey.hashAndSign('sha256', data, 'utf8', 'hex') |
77 | 91 | ||
78 | return signature | 92 | return signature |
@@ -85,7 +99,7 @@ module.exports = peertubeCrypto | |||
85 | // --------------------------------------------------------------------------- | 99 | // --------------------------------------------------------------------------- |
86 | 100 | ||
87 | function certsExist (callback) { | 101 | function certsExist (callback) { |
88 | fs.exists(certDir + 'peertube.key.pem', function (exists) { | 102 | fs.exists(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem', function (exists) { |
89 | return callback(exists) | 103 | return callback(exists) |
90 | }) | 104 | }) |
91 | } | 105 | } |
@@ -99,15 +113,25 @@ function createCerts (callback) { | |||
99 | } | 113 | } |
100 | 114 | ||
101 | logger.info('Generating a RSA key...') | 115 | logger.info('Generating a RSA key...') |
102 | openssl.exec('genrsa', { 'out': certDir + 'peertube.key.pem', '2048': false }, function (err) { | 116 | |
117 | let options = { | ||
118 | 'out': constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem', | ||
119 | '2048': false | ||
120 | } | ||
121 | openssl.exec('genrsa', options, function (err) { | ||
103 | if (err) { | 122 | if (err) { |
104 | logger.error('Cannot create private key on this pod.') | 123 | logger.error('Cannot create private key on this pod.') |
105 | return callback(err) | 124 | return callback(err) |
106 | } | 125 | } |
107 | logger.info('RSA key generated.') | 126 | logger.info('RSA key generated.') |
108 | 127 | ||
128 | options = { | ||
129 | 'in': constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem', | ||
130 | 'pubout': true, | ||
131 | 'out': constants.CONFIG.STORAGE.CERT_DIR + 'peertube.pub' | ||
132 | } | ||
109 | logger.info('Manage public key...') | 133 | logger.info('Manage public key...') |
110 | openssl.exec('rsa', { 'in': certDir + 'peertube.key.pem', 'pubout': true, 'out': certDir + 'peertube.pub' }, function (err) { | 134 | openssl.exec('rsa', options, function (err) { |
111 | if (err) { | 135 | if (err) { |
112 | logger.error('Cannot create public key on this pod.') | 136 | logger.error('Cannot create public key on this pod.') |
113 | return callback(err) | 137 | return callback(err) |
diff --git a/server/helpers/requests.js b/server/helpers/requests.js index 547230adc..95775c981 100644 --- a/server/helpers/requests.js +++ b/server/helpers/requests.js | |||
@@ -1,19 +1,14 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const config = require('config') | ||
4 | const replay = require('request-replay') | 3 | const replay = require('request-replay') |
5 | const request = require('request') | 4 | const request = require('request') |
6 | 5 | ||
7 | const constants = require('../initializers/constants') | 6 | const constants = require('../initializers/constants') |
8 | const peertubeCrypto = require('./peertube-crypto') | 7 | const peertubeCrypto = require('./peertube-crypto') |
9 | 8 | ||
10 | const http = config.get('webserver.https') ? 'https' : 'http' | ||
11 | const host = config.get('webserver.host') | ||
12 | const port = config.get('webserver.port') | ||
13 | |||
14 | const requests = { | 9 | const requests = { |
15 | makeRetryRequest: makeRetryRequest, | 10 | makeRetryRequest, |
16 | makeSecureRequest: makeSecureRequest | 11 | makeSecureRequest |
17 | } | 12 | } |
18 | 13 | ||
19 | function makeRetryRequest (params, callback) { | 14 | function makeRetryRequest (params, callback) { |
@@ -29,8 +24,6 @@ function makeRetryRequest (params, callback) { | |||
29 | } | 24 | } |
30 | 25 | ||
31 | function makeSecureRequest (params, callback) { | 26 | function makeSecureRequest (params, callback) { |
32 | const myUrl = http + '://' + host + ':' + port | ||
33 | |||
34 | const requestParams = { | 27 | const requestParams = { |
35 | url: params.toPod.url + params.path | 28 | url: params.toPod.url + params.path |
36 | } | 29 | } |
@@ -42,8 +35,8 @@ function makeSecureRequest (params, callback) { | |||
42 | // Add signature if it is specified in the params | 35 | // Add signature if it is specified in the params |
43 | if (params.sign === true) { | 36 | if (params.sign === true) { |
44 | requestParams.json.signature = { | 37 | requestParams.json.signature = { |
45 | url: myUrl, | 38 | url: constants.CONFIG.WEBSERVER.URL, |
46 | signature: peertubeCrypto.sign(myUrl) | 39 | signature: peertubeCrypto.sign(constants.CONFIG.WEBSERVER.URL) |
47 | } | 40 | } |
48 | } | 41 | } |
49 | 42 | ||
diff --git a/server/helpers/utils.js b/server/helpers/utils.js index a77116e08..9c2d402e3 100644 --- a/server/helpers/utils.js +++ b/server/helpers/utils.js | |||
@@ -5,8 +5,8 @@ const crypto = require('crypto') | |||
5 | const logger = require('./logger') | 5 | const logger = require('./logger') |
6 | 6 | ||
7 | const utils = { | 7 | const utils = { |
8 | cleanForExit: cleanForExit, | 8 | cleanForExit, |
9 | generateRandomString: generateRandomString | 9 | generateRandomString |
10 | } | 10 | } |
11 | 11 | ||
12 | function generateRandomString (size, callback) { | 12 | function generateRandomString (size, callback) { |