diff --git a/README.md b/README.md index c6a7f6d2..f27c9835 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,10 @@ spee.ch is a single-serving site that reads and publishes images and videos to a * start spee.ch * clone this repo * run `npm install` - * to start the server, from your command line run `node speech.js` while passing three environmental variables: - * (1) your lbry wallet address (`LBRY_CLAIM_ADDRESS`), - * (2) your mysql username (`MYSQL_USERNAME`), - * (2) your mysql password (`MYSQL_PASSWORD`), - * (3) the environment to run (`NODE_ENV`). - * i.e. `LBRY_CLAIM_ADDRESS= MYSQL_USERNAME= MYSQL_PASSWORD= NODE_ENV=development node speech.js` - * e.g. `LBRY_CLAIM_ADDRESS=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX MYSQL_USERNAME="lbry" MYSQL_PASSWORD="xxxxxx" NODE_ENV=development node speech.js` + * create your `speechConfig.js` file + * copy `speechConfig.js.example` and name it `speechConfig.js` + * replace the `null` values in the config file with the appropriate values for your environement + * to start the server, from your command line run `node speech.js` * To run hot, use `nodemon` instead of `node` * visit [localhost:3000](http://localhost:3000) @@ -36,17 +33,17 @@ spee.ch is a single-serving site that reads and publishes images and videos to a #### POST * /api/publish - * example: `curl -X POST -F 'name=MyPictureName' -F 'nsfw=false' -F 'file=@/path/to/my/picture.jpeg' https://spee.ch/api/publish` + * example: `curl -X POST -F 'name=MyPictureName' -F 'file=@/path/to/myPicture.jpeg' https://spee.ch/api/publish` * Parameters: - * name (string) - * nsfw (boolean) - * file (.mp4, .jpeg, .jpg, .gif, or .png) - * license (string, optional) - * title (string, optional) - * description (string, optional) - * thumbnail (string, optional) (for .mp4 uploads only) - * channelName(string, optional) - * channelPassword (string, optional) + * `name` + * `file` (.mp4, .jpeg, .jpg, .gif, or .png) + * `nsfw` (optional) + * `license` (optional) + * `title` (optional) + * `description` (optional) + * `thumbnail` url to thumbnail image, for .mp4 uploads only (optional) + * `channelName`(optional) + * `channelPassword` (optional,; required if `channelName` is provided) ## bugs If you find a bug or experience a problem, please report your issue here on github and find us in the lbry slack! diff --git a/auth/authentication.js b/auth/authentication.js index 631aee66..f23d702e 100644 --- a/auth/authentication.js +++ b/auth/authentication.js @@ -4,10 +4,6 @@ const logger = require('winston'); module.exports = { authenticateChannelCredentials (channelName, userPassword) { return new Promise((resolve, reject) => { - if (!channelName) { - resolve(true); - return; - } const userName = channelName.substring(1); logger.debug(`authenticateChannelCredentials > channelName: ${channelName} username: ${userName} pass: ${userPassword}`); db.User @@ -18,18 +14,32 @@ module.exports = { resolve(false); return; } - if (!user.validPassword(userPassword, user.password)) { - logger.debug('incorrect password'); - resolve(false); - return; - } - logger.debug('user found:', user.dataValues); - resolve(true); + return user.comparePassword(userPassword, (passwordErr, isMatch) => { + if (passwordErr) { + logger.error('comparePassword error:', passwordErr); + resolve(false); + return; + } + if (!isMatch) { + logger.debug('incorrect password'); + resolve(false); + return; + } + logger.debug('...password was a match...'); + resolve(true); + }); }) .catch(error => { - logger.error(error); - reject(); + reject(error); }); }); }, + authenticateOrSkip (skipAuth, channelName, channelPassword) { + return new Promise((resolve, reject) => { + if (skipAuth) { + return resolve(true); + } + return resolve(module.exports.authenticateChannelCredentials(channelName, channelPassword)); + }); + }, }; diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json deleted file mode 100644 index 78faebbf..00000000 --- a/config/custom-environment-variables.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "WalletConfig": { - "LbryClaimAddress": "LBRY_CLAIM_ADDRESS" - }, - "Database": { - "Username": "MYSQL_USERNAME", - "Password": "MYSQL_PASSWORD" - }, - "Logging": { - "SlackWebHook": "SLACK_WEB_HOOK" - }, - "Session": { - "SessionKey": "SESSION_KEY" - } -} \ No newline at end of file diff --git a/config/default-0.json b/config/default-0.json deleted file mode 100644 index 9e26dfee..00000000 --- a/config/default-0.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/config/default.json b/config/default.json deleted file mode 100644 index c5b65a20..00000000 --- a/config/default.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "WalletConfig": { - "LbryClaimAddress": null, - "DefaultChannel": null - }, - "AnalyticsConfig":{ - "GoogleId": null - }, - "Database": { - "Database": "lbry", - "Username": null, - "Password": null - }, - "Logging": { - "LogLevel": null, - "SlackWebHook": null, - "SlackErrorChannel": null, - "SlackInfoChannel": null - }, - "Session": { - "SessionKey": null - } -} \ No newline at end of file diff --git a/config/development.json b/config/development.json deleted file mode 100644 index 4beca9ff..00000000 --- a/config/development.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "WalletConfig": { - "DefaultChannel": "@speechDev" - }, - "AnalyticsConfig":{ - "GoogleId": "UA-100747990-1" - }, - "Logging": { - "LogLevel": "silly", - "SlackErrorChannel": "#staging_speech-errors", - "SlackInfoChannel": "none" - } -} \ No newline at end of file diff --git a/config/production.json b/config/production.json deleted file mode 100644 index a5bc5074..00000000 --- a/config/production.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "WalletConfig": { - "DefaultChannel": "@speech" - }, - "AnalyticsConfig":{ - "GoogleId": "UA-60403362-3" - }, - "Logging": { - "LogLevel": "verbose", - "SlackErrorChannel": "#speech-errors", - "SlackInfoChannel": "#speech-logs" - } -} diff --git a/config/slackConfig.js b/config/slackConfig.js new file mode 100644 index 00000000..84ab3376 --- /dev/null +++ b/config/slackConfig.js @@ -0,0 +1,30 @@ +const config = require('./speechConfig.js'); +const winstonSlackWebHook = require('winston-slack-webhook').SlackWebHook; + +module.exports = (winston) => { + if (config.logging.slackWebHook) { + // add a transport for errors to slack + winston.add(winstonSlackWebHook, { + name : 'slack-errors-transport', + level : 'warn', + webhookUrl: config.logging.slackWebHook, + channel : config.logging.slackErrorChannel, + username : 'spee.ch', + iconEmoji : ':face_with_head_bandage:', + }); + winston.add(winstonSlackWebHook, { + name : 'slack-info-transport', + level : 'info', + webhookUrl: config.logging.slackWebHook, + channel : config.logging.slackInfoChannel, + username : 'spee.ch', + iconEmoji : ':nerd_face:', + }); + // send test message + winston.error('Slack "error" logging is online.'); + winston.warn('Slack "warning" logging is online.'); + winston.info('Slack "info" logging is online.'); + } else { + winston.warn('Slack logging is not enabled because no SLACK_WEB_HOOK env var provided.'); + } +}; diff --git a/config/slackLoggerConfig.js b/config/slackLoggerConfig.js deleted file mode 100644 index 4bd88200..00000000 --- a/config/slackLoggerConfig.js +++ /dev/null @@ -1,32 +0,0 @@ -const config = require('config'); -const SLACK_WEB_HOOK = config.get('Logging.SlackWebHook'); -const SLACK_ERROR_CHANNEL = config.get('Logging.SlackErrorChannel'); -const SLACK_INFO_CHANNEL = config.get('Logging.SlackInfoChannel'); -const winstonSlackWebHook = require('winston-slack-webhook').SlackWebHook; - -module.exports = (winston) => { - if (SLACK_WEB_HOOK) { - // add a transport for errors to slack - winston.add(winstonSlackWebHook, { - name : 'slack-errors-transport', - level : 'error', - webhookUrl: SLACK_WEB_HOOK, - channel : SLACK_ERROR_CHANNEL, - username : 'spee.ch', - iconEmoji : ':face_with_head_bandage:', - }); - winston.add(winstonSlackWebHook, { - name : 'slack-info-transport', - level : 'info', - webhookUrl: SLACK_WEB_HOOK, - channel : SLACK_INFO_CHANNEL, - username : 'spee.ch', - iconEmoji : ':nerd_face:', - }); - // send test message - winston.error('Slack error logging is online.'); - winston.info('Slack info logging is online.'); - } else { - winston.error('Slack logging is not enabled because no SLACK_WEB_HOOK env var provided.'); - } -}; diff --git a/config/speechConfig.js.example b/config/speechConfig.js.example new file mode 100644 index 00000000..d10745a3 --- /dev/null +++ b/config/speechConfig.js.example @@ -0,0 +1,22 @@ +module.exports = { + wallet: { + lbryClaimAddress: null, // choose an address from your lbry wallet + }, + analytics: { + googleId: null, // google id for analytics tracking; leave `null` if not applicable + }, + sql: { + database: null, // name of mysql database + username: null, // username for mysql + password: null, // password for mysql + }, + logging: { + logLevel : null, // options: silly, debug, verbose, info + slackWebHook : null, // enter a webhook if you wish to push logs to slack; otherwise leave as `null` + slackErrorChannel: null, // enter a slack channel (#example) for errors to be sent to; otherwise leave null + slackInfoChannel : null, // enter a slack channel (#info) for info level logs to be sent to otherwise leave null + }, + session: { + sessionKey: null, // enter a secret key to be used for session encryption + }, +}; diff --git a/controllers/publishController.js b/controllers/publishController.js index 84c68750..d832c814 100644 --- a/controllers/publishController.js +++ b/controllers/publishController.js @@ -6,24 +6,33 @@ const publishHelpers = require('../helpers/publishHelpers.js'); module.exports = { publish (publishParams, fileName, fileType) { return new Promise((resolve, reject) => { - let publishResults = {}; - // 1. publish the file + let publishResults, certificateId, channelName; + // publish the file return lbryApi.publishClaim(publishParams) - // 2. upsert File record (update is in case the claim has been published before by this daemon) .then(tx => { logger.info(`Successfully published ${fileName}`, tx); publishResults = tx; - return db.Channel.findOne({where: {channelName: publishParams.channel_name}}); // note: should this be db.User ?? + // get the channel information + if (publishParams.channel_name) { + logger.debug(`this claim was published in channel: ${publishParams.channel_name}`); + return db.Channel.findOne({where: {channelName: publishParams.channel_name}}); + } else { + logger.debug('this claim was published in channel: n/a'); + return null; + } }) .then(channel => { - let certificateId; + // set channel information + certificateId = null; + channelName = null; if (channel) { certificateId = channel.channelClaimId; - logger.debug('successfully found channel in Channel table'); - } else { - certificateId = null; - logger.debug('channel for publish not found in Channel table'); - }; + channelName = channel.channelName; + } + logger.debug(`certificateId: ${certificateId}`); + }) + .then(() => { + // create the File record const fileRecord = { name : publishParams.name, claimId : publishResults.claim_id, @@ -37,6 +46,7 @@ module.exports = { fileType, nsfw : publishParams.metadata.nsfw, }; + // create the Claim record const claimRecord = { name : publishParams.name, claimId : publishResults.claim_id, @@ -48,14 +58,16 @@ module.exports = { height : 0, contentType: fileType, nsfw : publishParams.metadata.nsfw, - certificateId, amount : publishParams.bid, + certificateId, + channelName, }; + // upsert criteria const upsertCriteria = { name : publishParams.name, claimId: publishResults.claim_id, }; - // create the records + // upsert the records return Promise.all([db.upsert(db.File, fileRecord, upsertCriteria, 'File'), db.upsert(db.Claim, claimRecord, upsertCriteria, 'Claim')]); }) .then(([file, claim]) => { @@ -67,7 +79,6 @@ module.exports = { resolve(publishResults); // resolve the promise with the result from lbryApi.publishClaim; }) .catch(error => { - logger.error('publishController.publish, error', error); publishHelpers.deleteTemporaryFile(publishParams.file_path); // delete the local file reject(error); }); diff --git a/controllers/statsController.js b/controllers/statsController.js index 50dcc36d..6f3d4475 100644 --- a/controllers/statsController.js +++ b/controllers/statsController.js @@ -1,8 +1,8 @@ const logger = require('winston'); const ua = require('universal-analytics'); -const config = require('config'); +const config = require('../config/speechConfig.js'); const db = require('../models'); -const googleApiKey = config.get('AnalyticsConfig.GoogleId'); +const googleApiKey = config.analytics.googleId; module.exports = { postToStats (action, url, ipAddress, name, claimId, result) { diff --git a/helpers/configVarCheck.js b/helpers/configVarCheck.js index b4b40e51..b849245f 100644 --- a/helpers/configVarCheck.js +++ b/helpers/configVarCheck.js @@ -1,15 +1,13 @@ -const config = require('config'); +const config = require('../config/speechConfig.js'); const logger = require('winston'); -const fs = require('fs'); module.exports = function () { // get the config file - const defaultConfigFile = JSON.parse(fs.readFileSync('./config/default.json')); - for (let configCategoryKey in defaultConfigFile) { - if (defaultConfigFile.hasOwnProperty(configCategoryKey)) { + for (let configCategoryKey in config) { + if (config.hasOwnProperty(configCategoryKey)) { // get the final variables for each config category - const configVariables = config.get(configCategoryKey); + const configVariables = config[configCategoryKey]; for (let configVarKey in configVariables) { if (configVariables.hasOwnProperty(configVarKey)) { // print each variable diff --git a/helpers/errorHandlers.js b/helpers/errorHandlers.js index 3778eeeb..de970625 100644 --- a/helpers/errorHandlers.js +++ b/helpers/errorHandlers.js @@ -2,38 +2,54 @@ const logger = require('winston'); const { postToStats } = require('../controllers/statsController.js'); module.exports = { - handleRequestError (action, originalUrl, ip, error, res) { - logger.error(`Request Error: ${originalUrl}`, module.exports.useObjectPropertiesIfNoKeys(error)); - postToStats(action, originalUrl, ip, null, null, error); - if (error.response) { - res.status(error.response.status).render('requestError', {message: error.response.data.error.message, status: error.response.status}); - } else if (error.code === 'ECONNREFUSED') { - res.status(503).render('requestError', {message: 'Connection refused. The daemon may not be running.', status: 503}); - } else if (error.message) { - res.status(500).render('requestError', {message: error.message, status: 500}); - } else { - res.status(400).render('requestError', {message: error, status: 400}); - } - }, - handlePublishError (error) { - logger.error('Publish Error:', module.exports.useObjectPropertiesIfNoKeys(error)); + returnErrorMessageAndStatus: function (error) { + let status, message; + // check for daemon being turned off if (error.code === 'ECONNREFUSED') { - return 'Connection refused. The daemon may not be running.'; + status = 503; + message = 'Connection refused. The daemon may not be running.'; + // check for errors from the deamon } else if (error.response) { + status = error.response.status || 500; if (error.response.data) { if (error.response.data.message) { - return error.response.data.message; + message = error.response.data.message; } else if (error.response.data.error) { - return error.response.data.error.message; + message = error.response.data.error.message; + } else { + message = error.response.data; } - return error.response.data; + } else { + message = error.response; } - return error.response; + // check for spee.ch thrown errors + } else if (error.message) { + status = 400; + message = error.message; + // fallback for everything else } else { - return error; + status = 400; + message = error; } + return [status, message]; }, - useObjectPropertiesIfNoKeys (err) { + handleRequestError: function (action, originalUrl, ip, error, res) { + logger.error(`Request Error on ${originalUrl}`, module.exports.useObjectPropertiesIfNoKeys(error)); + postToStats(action, originalUrl, ip, null, null, error); + const [status, message] = this.returnErrorMessageAndStatus(error); + res + .status(status) + .render('requestError', this.createErrorResponsePayload(status, message)); + }, + handleApiError: function (action, originalUrl, ip, error, res) { + logger.error(`Api ${action} Error on ${originalUrl}`, module.exports.useObjectPropertiesIfNoKeys(error)); + postToStats(action, originalUrl, ip, null, null, error); + const [status, message] = this.returnErrorMessageAndStatus(error); + res + .status(status) + .json(this.createErrorResponsePayload(status, message)); + }, + useObjectPropertiesIfNoKeys: function (err) { if (Object.keys(err).length === 0) { let newErrorObject = {}; Object.getOwnPropertyNames(err).forEach((key) => { @@ -43,4 +59,11 @@ module.exports = { } return err; }, + createErrorResponsePayload (status, message) { + return { + status, + success: false, + message, + }; + }, }; diff --git a/helpers/handlebarsHelpers.js b/helpers/handlebarsHelpers.js index b4fb3c42..33ccb49d 100644 --- a/helpers/handlebarsHelpers.js +++ b/helpers/handlebarsHelpers.js @@ -1,10 +1,10 @@ const Handlebars = require('handlebars'); -const config = require('config'); +const config = require('../config/speechConfig.js'); module.exports = { // define any extra helpers you may need googleAnalytics () { - const googleApiKey = config.get('AnalyticsConfig.GoogleId'); + const googleApiKey = config.analytics.googleId; return new Handlebars.SafeString( ` - - diff --git a/views/partials/channelCreationForm.handlebars b/views/partials/channelCreationForm.handlebars index 685edad9..911deae5 100644 --- a/views/partials/channelCreationForm.handlebars +++ b/views/partials/channelCreationForm.handlebars @@ -6,7 +6,7 @@
@ - +
diff --git a/views/partials/publishForm-Channel.handlebars b/views/partials/publishForm-Channel.handlebars index 222726b6..43fb5d70 100644 --- a/views/partials/publishForm-Channel.handlebars +++ b/views/partials/publishForm-Channel.handlebars @@ -3,11 +3,11 @@
- - + +
- - + +
diff --git a/views/partials/publishForm-Submit.handlebars b/views/partials/publishForm-Submit.handlebars index 486e8841..e4248c89 100644 --- a/views/partials/publishForm-Submit.handlebars +++ b/views/partials/publishForm-Submit.handlebars @@ -1,9 +1,9 @@
- +
- +

By clicking 'Upload', you affirm that you have the rights to publish this content to the LBRY network, and that you understand the properties of publishing it to a decentralized, user-controlled network. Read more.

diff --git a/views/partials/publishForm-Url.handlebars b/views/partials/publishForm-Url.handlebars index 5e88224b..d5200ae4 100644 --- a/views/partials/publishForm-Url.handlebars +++ b/views/partials/publishForm-Url.handlebars @@ -9,7 +9,7 @@ {{user.channelName}}:{{user.shortChannelId}} xyzThis will be a random id / - +