diff --git a/README.md b/README.md index c6a7f6d2..03d3b0dc 100644 --- a/README.md +++ b/README.md @@ -36,17 +36,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..8c2045c0 100644 --- a/auth/authentication.js +++ b/auth/authentication.js @@ -2,12 +2,14 @@ const db = require('../models'); const logger = require('winston'); module.exports = { - authenticateChannelCredentials (channelName, userPassword) { + authenticateChannelCredentials (skipAuth, channelName, userPassword) { return new Promise((resolve, reject) => { - if (!channelName) { - resolve(true); + // skip authentication if not needed + if (skipAuth) { + resolve(skipAuth); return; } + // authentication const userName = channelName.substring(1); logger.debug(`authenticateChannelCredentials > channelName: ${channelName} username: ${userName} pass: ${userPassword}`); db.User @@ -18,17 +20,23 @@ 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); }); }); }, diff --git a/controllers/publishController.js b/controllers/publishController.js index 9af56217..f57d3eae 100644 --- a/controllers/publishController.js +++ b/controllers/publishController.js @@ -13,17 +13,16 @@ module.exports = { .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 ?? - }) - .then(channel => { - let certificateId; - if (channel) { - certificateId = channel.channelClaimId; - logger.debug('successfully found channel in Channel table'); + if (publishParams.channel_name) { + logger.debug(`this claim was published in channel: ${publishParams.channel_name}`); + return db.Channel.findOne({where: {channelName: publishParams.channel_name}}).then(channel => { return channel.channelClaimId }); } else { - certificateId = null; - logger.debug('channel for publish not found in Channel table'); - }; + logger.debug('this claim was published in channel: n/a'); + return null; + } + }) + .then(certificateId => { + logger.debug(`certificateId: ${certificateId}`); const fileRecord = { name : publishParams.name, claimId : publishResults.claim_id, diff --git a/helpers/errorHandlers.js b/helpers/errorHandlers.js index 5bc73edf..9aafc4b7 100644 --- a/helpers/errorHandlers.js +++ b/helpers/errorHandlers.js @@ -3,11 +3,12 @@ const { postToStats } = require('../controllers/statsController.js'); module.exports = { returnErrorMessageAndStatus: function (error) { - let status; - let message; + let status, message; + // check for daemon being turned off if (error.code === 'ECONNREFUSED') { 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) { @@ -21,7 +22,13 @@ module.exports = { } else { message = error.response; } + // check for spee.ch thrown errors + } else if (error.message) { + status = 400; + message = error.message; + // fallback for everything else } else { + status = 400; message = error; } return [status, message]; diff --git a/helpers/publishHelpers.js b/helpers/publishHelpers.js index a9142274..4489f290 100644 --- a/helpers/publishHelpers.js +++ b/helpers/publishHelpers.js @@ -18,11 +18,10 @@ module.exports = { throw new Error('no file with key of [file] found in request'); } }, - validatePublishSubmission (file, claimName, nsfw) { + validatePublishSubmission (file, claimName) { try { module.exports.validateFile(file); module.exports.validateClaimName(claimName); - module.exports.validateNSFW(nsfw); } catch (error) { throw error; } @@ -76,24 +75,6 @@ module.exports = { throw new Error('Only posts with a "Public Domain" or "Creative Commons" license are eligible for publishing through spee.ch'); } }, - cleanseNSFW (nsfw) { - switch (nsfw) { - case true: - case 'on': - case 'true': - case 1: - case '1': - return true; - case false: - case 'false': - case 'off': - case 0: - case '0': - return false; - default: - return false; - } - }, cleanseChannelName (channelName) { if (channelName) { if (channelName.indexOf('@') !== 0) { @@ -102,12 +83,6 @@ module.exports = { } return channelName; }, - validateNSFW (nsfw) { - if (nsfw === true || nsfw === false) { - return; - } - throw new Error('NSFW must be set to either true or false'); - }, createPublishParams (filePath, name, title, description, license, nsfw, thumbnail, channelName) { logger.debug(`Creating Publish Parameters`); // provide defaults for title diff --git a/public/assets/js/publishFileFunctions.js b/public/assets/js/publishFileFunctions.js index 6dfb8060..4cdc1ebc 100644 --- a/public/assets/js/publishFileFunctions.js +++ b/public/assets/js/publishFileFunctions.js @@ -64,20 +64,12 @@ var publishFileFunctions = { } }, returnNullOrChannel: function () { - const channelInput = document.getElementById('channel-name-select'); - const radios = document.getElementsByName('anonymous-or-channel'); - let anonymousOrInChannel; - // replace channelName with 'anonymous' if appropriate - for (let i = 0; i < radios.length; i++) { - if (radios[i].checked) { - anonymousOrInChannel = radios[i].value; // do whatever you want with the checked radio - break; // only one radio can be logically checked, don't check the rest - } + const channelRadio = document.getElementById('channel-radio'); + if (channelRadio.checked) { + const channelInput = document.getElementById('channel-name-select'); + return channelInput.value.trim(); } - if (anonymousOrInChannel === 'anonymous') { - return null; - }; - return channelInput.value.trim(); + return null; }, createMetadata: function() { const nameInput = document.getElementById('claim-name-input'); @@ -89,13 +81,13 @@ var publishFileFunctions = { return { name: nameInput.value.trim(), - channel: this.returnNullOrChannel(), + channelName: this.returnNullOrChannel(), title: titleInput.value.trim(), description: descriptionInput.value.trim(), license: licenseInput.value.trim(), nsfw: nsfwInput.checked, type: stagedFiles[0].type, - thumbnail: thumbnailInput.value.trim() + thumbnail: thumbnailInput.value.trim(), } }, appendDataToFormData: function (file, metadata) { diff --git a/routes/api-routes.js b/routes/api-routes.js index 6ebca2f5..419ced91 100644 --- a/routes/api-routes.js +++ b/routes/api-routes.js @@ -4,7 +4,7 @@ const multipartMiddleware = multipart(); const db = require('../models'); const { publish } = require('../controllers/publishController.js'); const { getClaimList, resolveUri } = require('../helpers/lbryApi.js'); -const { createPublishParams, validateApiPublishRequest, validatePublishSubmission, cleanseNSFW, cleanseChannelName, checkClaimNameAvailability, checkChannelAvailability } = require('../helpers/publishHelpers.js'); +const { createPublishParams, validateApiPublishRequest, validatePublishSubmission, cleanseChannelName, checkClaimNameAvailability, checkChannelAvailability } = require('../helpers/publishHelpers.js'); const errorHandlers = require('../helpers/errorHandlers.js'); const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.js'); const { authenticateChannelCredentials } = require('../auth/authentication.js'); @@ -71,7 +71,9 @@ module.exports = (app) => { }); }); // route to run a publish request on the daemon - app.post('/api/publish', multipartMiddleware, ({ body, files, ip, originalUrl }, res) => { + app.post('/api/publish', multipartMiddleware, ({ body, files, ip, originalUrl, user }, res) => { + logger.debug('api/publish body:', body); + let file, fileName, filePath, fileType, name, nsfw, license, title, description, thumbnail, anonymous, skipAuth, channelName, channelPassword; // validate that mandatory parts of the request are present try { validateApiPublishRequest(body, files); @@ -81,12 +83,12 @@ module.exports = (app) => { return; } // validate file, name, license, and nsfw - const file = files.file; - const fileName = file.name; - const filePath = file.path; - const fileType = file.type; - const name = body.name; - const nsfw = cleanseNSFW(body.nsfw); // cleanse nsfw input + file = files.file; + fileName = file.name; + filePath = file.path; + fileType = file.type; + name = body.name; + nsfw = (body.nsfw === 'true'); try { validatePublishSubmission(file, name, nsfw); } catch (error) { @@ -94,18 +96,36 @@ module.exports = (app) => { res.status(400).json({success: false, message: error.message}); return; } - logger.debug(`name: ${name}, nsfw: ${nsfw}`); // optional inputs - const license = body.license || null; - const title = body.title || null; - const description = body.description || null; - const thumbnail = body.thumbnail || null; - let channelName = body.channelName || null; + license = body.license || null; + title = body.title || null; + description = body.description || null; + thumbnail = body.thumbnail || null; + anonymous = (body.channelName === 'null') || (body.channelName === undefined); + if (user) { + channelName = user.channelName || null; + } else { + channelName = body.channelName || null; + } + channelPassword = body.channelPassword || null; + skipAuth = false; + // case 1: publish from spee.ch, client logged in + if (user) { + skipAuth = true; + if (anonymous) { + channelName = null; + } + // case 2: publish from api or spee.ch, client not logged in + } else { + if (anonymous) { + skipAuth = true; + channelName = null; + } + } channelName = cleanseChannelName(channelName); - const channelPassword = body.channelPassword || null; - logger.debug(`license: ${license} title: "${title}" description: "${description}" channelName: "${channelName}" channelPassword: "${channelPassword}" nsfw: "${nsfw}"`); + logger.debug(`name: ${name}, license: ${license} title: "${title}" description: "${description}" channelName: "${channelName}" channelPassword: "${channelPassword}" nsfw: "${nsfw}"`); // check channel authorization - authenticateChannelCredentials(channelName, channelPassword) + authenticateChannelCredentials(skipAuth, channelName, channelPassword) .then(result => { if (!result) { throw new Error('Authentication failed, you do not have access to that channel'); 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 @@
- - + +
- - + +