diff --git a/auth/authentication.js b/auth/authentication.js index c1e69def..4ab54673 100644 --- a/auth/authentication.js +++ b/auth/authentication.js @@ -2,31 +2,71 @@ const db = require('../models'); const logger = require('winston'); module.exports = { - authenticateChannelCredentials (channelName, userPassword) { + authenticateUser (channelName, channelId, channelPassword, user) { + // case: no channelName or channel Id are provided (anonymous), regardless of whether user token is provided + if (!channelName && !channelId) { + return { + channelName : null, + channelClaimId: null, + }; + } + // case: channelName or channel Id are provided with user token + if (user) { + if (channelName && channelName !== user.channelName) { + throw new Error('the provided channel name does not match user credentials'); + } + if (channelId && channelId !== user.channelClaimId) { + throw new Error('the provided channel id does not match user credentials'); + } + return { + channelName : user.channelName, + channelClaimId: user.channelClaimId, + }; + } + // case: channelName or channel Id are provided with password instead of user token + if (!channelPassword) throw new Error('no channel password provided'); + return module.exports.authenticateChannelCredentials(channelName, channelId, channelPassword); + }, + authenticateChannelCredentials (channelName, channelId, userPassword) { return new Promise((resolve, reject) => { - const userName = channelName.substring(1); - logger.debug(`authenticateChannelCredentials > channelName: ${channelName} username: ${userName} pass: ${userPassword}`); - db.User - .findOne({where: { userName }}) + // hoisted variables + let channelData; + // build the params for finding the channel + let channelFindParams = {}; + if (channelName) channelFindParams['channelName'] = channelName; + if (channelId) channelFindParams['channelClaimId'] = channelId; + // find the channel + db.Channel + .findOne({ + where: channelFindParams, + }) + .then(channel => { + if (!channel) { + logger.debug('no channel found'); + throw new Error('Authentication failed, you do not have access to that channel'); + } + channelData = channel.get(); + logger.debug('channel data:', channelData); + return db.User.findOne({ + where: { userName: channelData.channelName.substring(1) }, + }); + }) .then(user => { if (!user) { logger.debug('no user found'); - resolve(false); - return; + throw new Error('Authentication failed, you do not have access to that channel'); } return user.comparePassword(userPassword, (passwordErr, isMatch) => { if (passwordErr) { logger.error('comparePassword error:', passwordErr); - resolve(false); - return; + throw new Error('Authentication failed, you do not have access to that channel'); } if (!isMatch) { logger.debug('incorrect password'); - resolve(false); - return; + throw new Error('Authentication failed, you do not have access to that channel'); } logger.debug('...password was a match...'); - resolve(true); + resolve(channelData); }); }) .catch(error => { @@ -34,12 +74,4 @@ module.exports = { }); }); }, - authenticateIfNoUserToken (channelName, channelPassword, user) { - return new Promise((resolve, reject) => { - if (user || !channelName) { - return resolve(true); - } - return resolve(module.exports.authenticateChannelCredentials(channelName, channelPassword)); - }); - }, }; diff --git a/controllers/publishController.js b/controllers/publishController.js index 9e912cd7..588e888e 100644 --- a/controllers/publishController.js +++ b/controllers/publishController.js @@ -86,31 +86,24 @@ module.exports = { }); }); }, - checkClaimNameAvailability (name) { - return new Promise((resolve, reject) => { - // find any records where the name is used - db.File.findAll({ where: { name } }) - .then(result => { - if (result.length >= 1) { - const claimAddress = config.wallet.lbryClaimAddress; - // filter out any results that were not published from spee.ch's wallet address - const filteredResult = result.filter((claim) => { - return (claim.address === claimAddress); - }); - // return based on whether any non-spee.ch claims were left - if (filteredResult.length >= 1) { - resolve(false); - } else { - resolve(true); - } - } else { - resolve(true); - } - }) - .catch(error => { - reject(error); - }); - }); + validateClaimName (name) { + // find any records where the name is used + return db.File.findAll({ where: { name } }) + .then(result => { + if (result.length >= 1) { + const claimAddress = config.wallet.lbryClaimAddress; + // filter out any results that were not published from spee.ch's wallet address + const filteredResult = result.filter((claim) => { + return (claim.address === claimAddress); + }); + // return based on whether any non-spee.ch claims were left + if (filteredResult.length >= 1) { + throw new Error('That claim is already in use'); + }; + return name; + }; + return name; + }); }, checkChannelAvailability (name) { return new Promise((resolve, reject) => { diff --git a/helpers/publishHelpers.js b/helpers/publishHelpers.js index a9cbf2bd..b21b26a9 100644 --- a/helpers/publishHelpers.js +++ b/helpers/publishHelpers.js @@ -57,30 +57,6 @@ module.exports = { fileType: file.type, }; }, - parsePublishApiChannel ({channelName, channelPassword}, user) { - logger.debug('publish api parser input:', {channelName, channelPassword, user}); - // if anonymous or '' provided, publish will be anonymous (even if client is logged in) - // if a channel name is provided... - if (channelName) { - // make sure a password was provided if no user token is provided - if (!user && !channelPassword) { - throw new Error('Unauthenticated channel name provided without password'); - } - // if request comes from the client with a token - // ensure this publish uses that channel name - if (user) { - channelName = user.channelName; - } ; - // add the @ if the channel name is missing it - if (channelName.indexOf('@') !== 0) { - channelName = `@${channelName}`; - } - } - return { - channelName, - channelPassword, - }; - }, validateFileTypeAndSize (file) { // check file type and size switch (file.type) { @@ -110,7 +86,7 @@ module.exports = { } return file; }, - createPublishParams (filePath, name, title, description, license, nsfw, thumbnail, channelName) { + createPublishParams (filePath, name, title, description, license, nsfw, thumbnail) { logger.debug(`Creating Publish Parameters`); // provide defaults for title if (title === null || title.trim() === '') { @@ -143,10 +119,6 @@ module.exports = { if (thumbnail !== null) { publishParams['metadata']['thumbnail'] = thumbnail; } - // add channel to params, if applicable - if (channelName) { - publishParams['channel_name'] = channelName; - } return publishParams; }, deleteTemporaryFile (filePath) { diff --git a/react/containers/PublishUrlInput/view.jsx b/react/containers/PublishUrlInput/view.jsx index 38ed7c91..1a73d255 100644 --- a/react/containers/PublishUrlInput/view.jsx +++ b/react/containers/PublishUrlInput/view.jsx @@ -38,13 +38,9 @@ class PublishUrlInput extends React.Component { } checkClaimIsAvailable (claim) { request(`/api/claim/availability/${claim}`) - .then(isAvailable => { - // console.log('checkClaimIsAvailable request response:', isAvailable); - if (isAvailable) { - this.props.onUrlError(null); - } else { - this.props.onUrlError('That url has already been claimed'); - } + .then(validatedClaimName => { + console.log('api/claim/availability response:', validatedClaimName); + this.props.onUrlError(null); }) .catch((error) => { this.props.onUrlError(error.message); diff --git a/routes/api-routes.js b/routes/api-routes.js index 9178834d..6e982c6f 100644 --- a/routes/api-routes.js +++ b/routes/api-routes.js @@ -3,12 +3,12 @@ const multipart = require('connect-multiparty'); const { files, site } = require('../config/speechConfig.js'); const multipartMiddleware = multipart({uploadDir: files.uploadDirectory}); const db = require('../models'); -const { checkClaimNameAvailability, checkChannelAvailability, publish } = require('../controllers/publishController.js'); +const { validateClaimName, checkChannelAvailability, publish } = require('../controllers/publishController.js'); const { getClaimList, resolveUri, getClaim } = require('../helpers/lbryApi.js'); -const { createPublishParams, parsePublishApiRequestBody, parsePublishApiRequestFiles, parsePublishApiChannel, addGetResultsToFileData, createFileData } = require('../helpers/publishHelpers.js'); +const { createPublishParams, parsePublishApiRequestBody, parsePublishApiRequestFiles, addGetResultsToFileData, createFileData } = require('../helpers/publishHelpers.js'); const errorHandlers = require('../helpers/errorHandlers.js'); const { sendGAAnonymousPublishTiming, sendGAChannelPublishTiming } = require('../helpers/googleAnalytics.js'); -const { authenticateIfNoUserToken } = require('../auth/authentication.js'); +const { authenticateUser } = require('../auth/authentication.js'); const { getChannelData, getChannelClaims, getClaimId } = require('../controllers/serveController.js'); const NO_CHANNEL = 'NO_CHANNEL'; @@ -108,13 +108,9 @@ module.exports = (app) => { }); // route to check whether this site published to a claim app.get('/api/claim/availability/:name', ({ ip, originalUrl, params }, res) => { - checkClaimNameAvailability(params.name) + validateClaimName(params.name) .then(result => { - if (result === true) { - res.status(200).json(true); - } else { - res.status(200).json(false); - } + res.status(200).json(result); }) .catch(error => { errorHandlers.handleErrorResponse(originalUrl, ip, error, res); @@ -135,7 +131,7 @@ module.exports = (app) => { logger.debug('api/claim-publish body:', body); logger.debug('api/claim-publish files:', files); // define variables - let name, fileName, filePath, fileType, nsfw, license, title, description, thumbnail, channelName, channelPassword; + let name, fileName, filePath, fileType, nsfw, license, title, description, thumbnail, channelName, channelId, channelPassword; // record the start time of the request const publishStartTime = Date.now(); // validate the body and files of the request @@ -143,27 +139,24 @@ module.exports = (app) => { // validateApiPublishRequest(body, files); ({name, nsfw, license, title, description, thumbnail} = parsePublishApiRequestBody(body)); ({fileName, filePath, fileType} = parsePublishApiRequestFiles(files)); - ({channelName, channelPassword} = parsePublishApiChannel(body, user)); + ({channelName, channelId, channelPassword} = body); } catch (error) { return res.status(400).json({success: false, message: error.message}); } // check channel authorization - authenticateIfNoUserToken(channelName, channelPassword, user) - .then(authenticated => { - if (!authenticated) { - throw new Error('Authentication failed, you do not have access to that channel'); + Promise.all([ + authenticateUser(channelName, channelId, channelPassword, user), + validateClaimName(name), + createPublishParams(filePath, name, title, description, license, nsfw, thumbnail), + ]) + .then(([{channelName, channelClaimId}, validatedClaimName, publishParams]) => { + // add channel details to the publish params + if (channelName && channelClaimId) { + publishParams['channel_name'] = channelName; + publishParams['channel_id'] = channelClaimId; } - // make sure the claim name is available - return checkClaimNameAvailability(name); - }) - .then(result => { - if (!result) { - throw new Error('That name is already claimed by another user.'); - } - // create publish parameters object - return createPublishParams(filePath, name, title, description, license, nsfw, thumbnail, channelName); - }) - .then(publishParams => { + logger.debug('validated claim name:', validatedClaimName); + logger.debug('publish params:', publishParams); // publish the asset return publish(publishParams, fileName, fileType); })