aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/helpers
diff options
context:
space:
mode:
Diffstat (limited to 'server/helpers')
-rw-r--r--server/helpers/core-utils.ts89
-rw-r--r--server/helpers/database-utils.ts69
-rw-r--r--server/helpers/peertube-crypto.ts111
-rw-r--r--server/helpers/requests.ts81
-rw-r--r--server/helpers/utils.ts24
5 files changed, 209 insertions, 165 deletions
diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts
index 32b89b6dd..1e92049f1 100644
--- a/server/helpers/core-utils.ts
+++ b/server/helpers/core-utils.ts
@@ -4,6 +4,20 @@
4*/ 4*/
5 5
6import { join } from 'path' 6import { join } from 'path'
7import { pseudoRandomBytes } from 'crypto'
8import {
9 readdir,
10 readFile,
11 rename,
12 unlink,
13 writeFile,
14 access
15} from 'fs'
16import * as mkdirp from 'mkdirp'
17import * as bcrypt from 'bcrypt'
18import * as createTorrent from 'create-torrent'
19import * as openssl from 'openssl-wrapper'
20import * as Promise from 'bluebird'
7 21
8function isTestInstance () { 22function isTestInstance () {
9 return process.env.NODE_ENV === 'test' 23 return process.env.NODE_ENV === 'test'
@@ -14,9 +28,82 @@ function root () {
14 return join(__dirname, '..', '..', '..') 28 return join(__dirname, '..', '..', '..')
15} 29}
16 30
31function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> {
32 return function promisified (): Promise<A> {
33 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
34 func.apply(null, [ (err: any, res: A) => err ? reject(err) : resolve(res) ])
35 })
36 }
37}
38
39// Thanks to https://gist.github.com/kumasento/617daa7e46f13ecdd9b2
40function promisify1<T, A> (func: (arg: T, cb: (err: any, result: A) => void) => void): (arg: T) => Promise<A> {
41 return function promisified (arg: T): Promise<A> {
42 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
43 func.apply(null, [ arg, (err: any, res: A) => err ? reject(err) : resolve(res) ])
44 })
45 }
46}
47
48function promisify1WithVoid<T> (func: (arg: T, cb: (err: any) => void) => void): (arg: T) => Promise<void> {
49 return function promisified (arg: T): Promise<void> {
50 return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
51 func.apply(null, [ arg, (err: any) => err ? reject(err) : resolve() ])
52 })
53 }
54}
55
56function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> {
57 return function promisified (arg1: T, arg2: U): Promise<A> {
58 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
59 func.apply(null, [ arg1, arg2, (err: any, res: A) => err ? reject(err) : resolve(res) ])
60 })
61 }
62}
63
64function promisify2WithVoid<T, U> (func: (arg1: T, arg2: U, cb: (err: any) => void) => void): (arg1: T, arg2: U) => Promise<void> {
65 return function promisified (arg1: T, arg2: U): Promise<void> {
66 return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
67 func.apply(null, [ arg1, arg2, (err: any) => err ? reject(err) : resolve() ])
68 })
69 }
70}
71
72const readFilePromise = promisify2<string, string, string>(readFile)
73const readFileBufferPromise = promisify1<string, Buffer>(readFile)
74const unlinkPromise = promisify1WithVoid<string>(unlink)
75const renamePromise = promisify2WithVoid<string, string>(rename)
76const writeFilePromise = promisify2<string, any, void>(writeFile)
77const readdirPromise = promisify1<string, string[]>(readdir)
78const mkdirpPromise = promisify1<string, string>(mkdirp)
79const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes)
80const accessPromise = promisify1WithVoid<string|Buffer>(access)
81const opensslExecPromise = promisify2WithVoid<string, any>(openssl.exec)
82const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
83const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
84const bcryptHashPromise = promisify2<any, string|number, string>(bcrypt.hash)
85const createTorrentPromise = promisify2<string, any, any>(createTorrent)
86
17// --------------------------------------------------------------------------- 87// ---------------------------------------------------------------------------
18 88
19export { 89export {
20 isTestInstance, 90 isTestInstance,
21 root 91 root,
92
93 promisify0,
94 promisify1,
95 readdirPromise,
96 readFilePromise,
97 readFileBufferPromise,
98 unlinkPromise,
99 renamePromise,
100 writeFilePromise,
101 mkdirpPromise,
102 pseudoRandomBytesPromise,
103 accessPromise,
104 opensslExecPromise,
105 bcryptComparePromise,
106 bcryptGenSaltPromise,
107 bcryptHashPromise,
108 createTorrentPromise
22} 109}
diff --git a/server/helpers/database-utils.ts b/server/helpers/database-utils.ts
index f8ee9a454..f761cfb89 100644
--- a/server/helpers/database-utils.ts
+++ b/server/helpers/database-utils.ts
@@ -1,70 +1,45 @@
1import * as Sequelize from 'sequelize'
2// TODO: import from ES6 when retry typing file will include errorFilter function 1// TODO: import from ES6 when retry typing file will include errorFilter function
3import * as retry from 'async/retry' 2import * as retry from 'async/retry'
3import * as Promise from 'bluebird'
4 4
5import { database as db } from '../initializers/database'
6import { logger } from './logger' 5import { logger } from './logger'
7 6
8function commitTransaction (t: Sequelize.Transaction, callback: (err: Error) => void) {
9 return t.commit().asCallback(callback)
10}
11
12function rollbackTransaction (err: Error, t: Sequelize.Transaction, callback: (err: Error) => void) {
13 // Try to rollback transaction
14 if (t) {
15 // Do not catch err, report the original one
16 t.rollback().asCallback(function () {
17 return callback(err)
18 })
19 } else {
20 return callback(err)
21 }
22}
23
24type RetryTransactionWrapperOptions = { errorMessage: string, arguments?: any[] } 7type RetryTransactionWrapperOptions = { errorMessage: string, arguments?: any[] }
25function retryTransactionWrapper (functionToRetry: Function, options: RetryTransactionWrapperOptions, finalCallback: Function) { 8function retryTransactionWrapper (functionToRetry: (... args) => Promise<any>, options: RetryTransactionWrapperOptions) {
26 const args = options.arguments ? options.arguments : [] 9 const args = options.arguments ? options.arguments : []
27 10
28 transactionRetryer( 11 return transactionRetryer(
29 function (callback) { 12 function (callback) {
30 return functionToRetry.apply(this, args.concat([ callback ])) 13 functionToRetry.apply(this, args)
31 }, 14 .then(result => callback(null, result))
32 function (err) { 15 .catch(err => callback(err))
33 if (err) {
34 logger.error(options.errorMessage, { error: err })
35 }
36
37 // Do not return the error, continue the process
38 return finalCallback(null)
39 } 16 }
40 ) 17 )
18 .catch(err => {
19 // Do not throw the error, continue the process
20 logger.error(options.errorMessage, { error: err })
21 })
41} 22}
42 23
43function transactionRetryer (func: Function, callback: (err: Error) => void) { 24function transactionRetryer (func: Function) {
44 retry({ 25 return new Promise((res, rej) => {
45 times: 5, 26 retry({
46 27 times: 5,
47 errorFilter: function (err) {
48 const willRetry = (err.name === 'SequelizeDatabaseError')
49 logger.debug('Maybe retrying the transaction function.', { willRetry })
50 return willRetry
51 }
52 }, func, callback)
53}
54 28
55function startSerializableTransaction (callback: (err: Error, t: Sequelize.Transaction) => void) { 29 errorFilter: function (err) {
56 db.sequelize.transaction(/* { isolationLevel: 'SERIALIZABLE' } */).asCallback(function (err, t) { 30 const willRetry = (err.name === 'SequelizeDatabaseError')
57 // We force to return only two parameters 31 logger.debug('Maybe retrying the transaction function.', { willRetry })
58 return callback(err, t) 32 return willRetry
33 }
34 }, func, function (err) {
35 err ? rej(err) : res()
36 })
59 }) 37 })
60} 38}
61 39
62// --------------------------------------------------------------------------- 40// ---------------------------------------------------------------------------
63 41
64export { 42export {
65 commitTransaction,
66 retryTransactionWrapper, 43 retryTransactionWrapper,
67 rollbackTransaction,
68 startSerializableTransaction,
69 transactionRetryer 44 transactionRetryer
70} 45}
diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts
index 0ac875127..8e8001cd6 100644
--- a/server/helpers/peertube-crypto.ts
+++ b/server/helpers/peertube-crypto.ts
@@ -1,7 +1,5 @@
1import * as crypto from 'crypto' 1import * as crypto from 'crypto'
2import * as bcrypt from 'bcrypt'
3import * as fs from 'fs' 2import * as fs from 'fs'
4import * as openssl from 'openssl-wrapper'
5import { join } from 'path' 3import { join } from 'path'
6 4
7import { 5import {
@@ -12,6 +10,14 @@ import {
12 BCRYPT_SALT_SIZE, 10 BCRYPT_SALT_SIZE,
13 PUBLIC_CERT_NAME 11 PUBLIC_CERT_NAME
14} from '../initializers' 12} from '../initializers'
13import {
14 readFilePromise,
15 bcryptComparePromise,
16 bcryptGenSaltPromise,
17 bcryptHashPromise,
18 accessPromise,
19 opensslExecPromise
20} from './core-utils'
15import { logger } from './logger' 21import { logger } from './logger'
16 22
17function checkSignature (publicKey: string, data: string, hexSignature: string) { 23function checkSignature (publicKey: string, data: string, hexSignature: string) {
@@ -60,46 +66,32 @@ function sign (data: string|Object) {
60 return signature 66 return signature
61} 67}
62 68
63function comparePassword (plainPassword: string, hashPassword: string, callback: (err: Error, match?: boolean) => void) { 69function comparePassword (plainPassword: string, hashPassword: string) {
64 bcrypt.compare(plainPassword, hashPassword, function (err, isPasswordMatch) { 70 return bcryptComparePromise(plainPassword, hashPassword)
65 if (err) return callback(err)
66
67 return callback(null, isPasswordMatch)
68 })
69} 71}
70 72
71function createCertsIfNotExist (callback: (err: Error) => void) { 73function createCertsIfNotExist () {
72 certsExist(function (err, exist) { 74 return certsExist().then(exist => {
73 if (err) return callback(err)
74
75 if (exist === true) { 75 if (exist === true) {
76 return callback(null) 76 return undefined
77 } 77 }
78 78
79 createCerts(function (err) { 79 return createCerts()
80 return callback(err)
81 })
82 }) 80 })
83} 81}
84 82
85function cryptPassword (password: string, callback: (err: Error, hash?: string) => void) { 83function cryptPassword (password: string) {
86 bcrypt.genSalt(BCRYPT_SALT_SIZE, function (err, salt) { 84 return bcryptGenSaltPromise(BCRYPT_SALT_SIZE).then(salt => bcryptHashPromise(password, salt))
87 if (err) return callback(err)
88
89 bcrypt.hash(password, salt, function (err, hash) {
90 return callback(err, hash)
91 })
92 })
93} 85}
94 86
95function getMyPrivateCert (callback: (err: Error, privateCert: string) => void) { 87function getMyPrivateCert () {
96 const certPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME) 88 const certPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME)
97 fs.readFile(certPath, 'utf8', callback) 89 return readFilePromise(certPath, 'utf8')
98} 90}
99 91
100function getMyPublicCert (callback: (err: Error, publicCert: string) => void) { 92function getMyPublicCert () {
101 const certPath = join(CONFIG.STORAGE.CERT_DIR, PUBLIC_CERT_NAME) 93 const certPath = join(CONFIG.STORAGE.CERT_DIR, PUBLIC_CERT_NAME)
102 fs.readFile(certPath, 'utf8', callback) 94 return readFilePromise(certPath, 'utf8')
103} 95}
104 96
105// --------------------------------------------------------------------------- 97// ---------------------------------------------------------------------------
@@ -116,23 +108,21 @@ export {
116 108
117// --------------------------------------------------------------------------- 109// ---------------------------------------------------------------------------
118 110
119function certsExist (callback: (err: Error, certsExist: boolean) => void) { 111function certsExist () {
120 const certPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME) 112 const certPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME)
121 fs.access(certPath, function (err) {
122 // If there is an error the certificates do not exist
123 const exists = !err
124 return callback(null, exists)
125 })
126}
127 113
128function createCerts (callback: (err: Error) => void) { 114 // If there is an error the certificates do not exist
129 certsExist(function (err, exist) { 115 return accessPromise(certPath)
130 if (err) return callback(err) 116 .then(() => true)
117 .catch(() => false)
118}
131 119
120function createCerts () {
121 return certsExist().then(exist => {
132 if (exist === true) { 122 if (exist === true) {
133 const errorMessage = 'Certs already exist.' 123 const errorMessage = 'Certs already exist.'
134 logger.warning(errorMessage) 124 logger.warning(errorMessage)
135 return callback(new Error(errorMessage)) 125 throw new Error(errorMessage)
136 } 126 }
137 127
138 logger.info('Generating a RSA key...') 128 logger.info('Generating a RSA key...')
@@ -142,30 +132,27 @@ function createCerts (callback: (err: Error) => void) {
142 'out': privateCertPath, 132 'out': privateCertPath,
143 '2048': false 133 '2048': false
144 } 134 }
145 openssl.exec('genrsa', genRsaOptions, function (err) { 135 return opensslExecPromise('genrsa', genRsaOptions)
146 if (err) { 136 .then(() => {
147 logger.error('Cannot create private key on this pod.') 137 logger.info('RSA key generated.')
148 return callback(err) 138 logger.info('Managing public key...')
149 } 139
150 140 const publicCertPath = join(CONFIG.STORAGE.CERT_DIR, 'peertube.pub')
151 logger.info('RSA key generated.') 141 const rsaOptions = {
152 logger.info('Managing public key...') 142 'in': privateCertPath,
153 143 'pubout': true,
154 const publicCertPath = join(CONFIG.STORAGE.CERT_DIR, 'peertube.pub') 144 'out': publicCertPath
155 const rsaOptions = {
156 'in': privateCertPath,
157 'pubout': true,
158 'out': publicCertPath
159 }
160 openssl.exec('rsa', rsaOptions, function (err) {
161 if (err) {
162 logger.error('Cannot create public key on this pod.')
163 return callback(err)
164 } 145 }
165 146 return opensslExecPromise('rsa', rsaOptions)
166 logger.info('Public key managed.') 147 .then(() => logger.info('Public key managed.'))
167 return callback(null) 148 .catch(err => {
149 logger.error('Cannot create public key on this pod.')
150 throw err
151 })
152 })
153 .catch(err => {
154 logger.error('Cannot create private key on this pod.')
155 throw err
168 }) 156 })
169 })
170 }) 157 })
171} 158}
diff --git a/server/helpers/requests.ts b/server/helpers/requests.ts
index b40fc8e39..b31074373 100644
--- a/server/helpers/requests.ts
+++ b/server/helpers/requests.ts
@@ -1,5 +1,6 @@
1import * as replay from 'request-replay' 1import * as replay from 'request-replay'
2import * as request from 'request' 2import * as request from 'request'
3import * as Promise from 'bluebird'
3 4
4import { 5import {
5 RETRY_REQUESTS, 6 RETRY_REQUESTS,
@@ -14,16 +15,18 @@ type MakeRetryRequestParams = {
14 method: 'GET'|'POST', 15 method: 'GET'|'POST',
15 json: Object 16 json: Object
16} 17}
17function makeRetryRequest (params: MakeRetryRequestParams, callback: request.RequestCallback) { 18function makeRetryRequest (params: MakeRetryRequestParams) {
18 replay( 19 return new Promise<{ response: request.RequestResponse, body: any }>((res, rej) => {
19 request(params, callback), 20 replay(
20 { 21 request(params, (err, response, body) => err ? rej(err) : res({ response, body })),
21 retries: RETRY_REQUESTS, 22 {
22 factor: 3, 23 retries: RETRY_REQUESTS,
23 maxTimeout: Infinity, 24 factor: 3,
24 errorCodes: [ 'EADDRINFO', 'ETIMEDOUT', 'ECONNRESET', 'ESOCKETTIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED' ] 25 maxTimeout: Infinity,
25 } 26 errorCodes: [ 'EADDRINFO', 'ETIMEDOUT', 'ECONNRESET', 'ESOCKETTIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED' ]
26 ) 27 }
28 )
29 })
27} 30}
28 31
29type MakeSecureRequestParams = { 32type MakeSecureRequestParams = {
@@ -33,41 +36,43 @@ type MakeSecureRequestParams = {
33 sign: boolean 36 sign: boolean
34 data?: Object 37 data?: Object
35} 38}
36function makeSecureRequest (params: MakeSecureRequestParams, callback: request.RequestCallback) { 39function makeSecureRequest (params: MakeSecureRequestParams) {
37 const requestParams = { 40 return new Promise<{ response: request.RequestResponse, body: any }>((res, rej) => {
38 url: REMOTE_SCHEME.HTTP + '://' + params.toPod.host + params.path, 41 const requestParams = {
39 json: {} 42 url: REMOTE_SCHEME.HTTP + '://' + params.toPod.host + params.path,
40 } 43 json: {}
44 }
41 45
42 if (params.method !== 'POST') { 46 if (params.method !== 'POST') {
43 return callback(new Error('Cannot make a secure request with a non POST method.'), null, null) 47 return rej(new Error('Cannot make a secure request with a non POST method.'))
44 } 48 }
45 49
46 // Add signature if it is specified in the params 50 // Add signature if it is specified in the params
47 if (params.sign === true) { 51 if (params.sign === true) {
48 const host = CONFIG.WEBSERVER.HOST 52 const host = CONFIG.WEBSERVER.HOST
49 53
50 let dataToSign 54 let dataToSign
51 if (params.data) { 55 if (params.data) {
52 dataToSign = params.data 56 dataToSign = params.data
53 } else { 57 } else {
54 // We do not have data to sign so we just take our host 58 // We do not have data to sign so we just take our host
55 // It is not ideal but the connection should be in HTTPS 59 // It is not ideal but the connection should be in HTTPS
56 dataToSign = host 60 dataToSign = host
57 } 61 }
58 62
59 requestParams.json['signature'] = { 63 requestParams.json['signature'] = {
60 host, // Which host we pretend to be 64 host, // Which host we pretend to be
61 signature: sign(dataToSign) 65 signature: sign(dataToSign)
66 }
62 } 67 }
63 }
64 68
65 // If there are data informations 69 // If there are data informations
66 if (params.data) { 70 if (params.data) {
67 requestParams.json['data'] = params.data 71 requestParams.json['data'] = params.data
68 } 72 }
69 73
70 request.post(requestParams, callback) 74 request.post(requestParams, (err, response, body) => err ? rej(err) : res({ response, body }))
75 })
71} 76}
72 77
73// --------------------------------------------------------------------------- 78// ---------------------------------------------------------------------------
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts
index bbf135fa1..e99a48393 100644
--- a/server/helpers/utils.ts
+++ b/server/helpers/utils.ts
@@ -1,25 +1,14 @@
1import * as express from 'express' 1import * as express from 'express'
2 2
3import { pseudoRandomBytes } from 'crypto' 3import { pseudoRandomBytesPromise } from './core-utils'
4 4import { ResultList } from '../../shared'
5import { logger } from './logger'
6 5
7function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) { 6function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) {
8 res.type('json').status(400).end() 7 res.type('json').status(400).end()
9} 8}
10 9
11function generateRandomString (size: number, callback: (err: Error, randomString?: string) => void) { 10function generateRandomString (size: number) {
12 pseudoRandomBytes(size, function (err, raw) { 11 return pseudoRandomBytesPromise(size).then(raw => raw.toString('hex'))
13 if (err) return callback(err)
14
15 callback(null, raw.toString('hex'))
16 })
17}
18
19function createEmptyCallback () {
20 return function (err) {
21 if (err) logger.error('Error in empty callback.', { error: err })
22 }
23} 12}
24 13
25interface FormatableToJSON { 14interface FormatableToJSON {
@@ -33,17 +22,18 @@ function getFormatedObjects<U, T extends FormatableToJSON> (objects: T[], object
33 formatedObjects.push(object.toFormatedJSON()) 22 formatedObjects.push(object.toFormatedJSON())
34 }) 23 })
35 24
36 return { 25 const res: ResultList<U> = {
37 total: objectsTotal, 26 total: objectsTotal,
38 data: formatedObjects 27 data: formatedObjects
39 } 28 }
29
30 return res
40} 31}
41 32
42// --------------------------------------------------------------------------- 33// ---------------------------------------------------------------------------
43 34
44export { 35export {
45 badRequest, 36 badRequest,
46 createEmptyCallback,
47 generateRandomString, 37 generateRandomString,
48 getFormatedObjects 38 getFormatedObjects
49} 39}