fixed merge conflicts

This commit is contained in:
bill bittner 2018-03-05 20:30:16 -08:00
commit b7b3a78730
10 changed files with 189 additions and 167 deletions

View file

@ -2,44 +2,73 @@ const db = require('../models');
const logger = require('winston'); const logger = require('winston');
module.exports = { 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) => { return new Promise((resolve, reject) => {
const userName = channelName.substring(1); // hoisted variables
logger.debug(`authenticateChannelCredentials > channelName: ${channelName} username: ${userName} pass: ${userPassword}`); let channelData;
db.User // build the params for finding the channel
.findOne({where: { userName }}) 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 => { .then(user => {
if (!user) { if (!user) {
logger.debug('no user found'); logger.debug('no user found');
resolve(false); throw new Error('Authentication failed, you do not have access to that channel');
return;
} }
return user.comparePassword(userPassword, (passwordErr, isMatch) => { return user.comparePassword(userPassword);
if (passwordErr) { })
logger.error('comparePassword error:', passwordErr); .then(isMatch => {
resolve(false); if (!isMatch) {
return; logger.debug('incorrect password');
} throw new Error('Authentication failed, you do not have access to that channel');
if (!isMatch) { }
logger.debug('incorrect password'); logger.debug('...password was a match...');
resolve(false); resolve(channelData);
return;
}
logger.debug('...password was a match...');
resolve(true);
});
}) })
.catch(error => { .catch(error => {
reject(error); reject(error);
}); });
}); });
}, },
authenticateIfNoUserToken (channelName, channelPassword, user) {
return new Promise((resolve, reject) => {
if (user || !channelName) {
return resolve(true);
}
return resolve(module.exports.authenticateChannelCredentials(channelName, channelPassword));
});
},
}; };

View file

@ -38,7 +38,8 @@ module.exports = {
defaultDescription: 'Open-source, decentralized image and video sharing.', defaultDescription: 'Open-source, decentralized image and video sharing.',
}, },
testing: { testing: {
testChannel : '@testpublishchannel', // a channel to make test publishes in testChannel : '@testpublishchannel', // a channel to make test publishes in
testChannelId : 'xyz123...', // the claim id for the test channel
testChannelPassword: 'password', // password for the test channel testChannelPassword: 'password', // password for the test channel
}, },
api: { api: {

View file

@ -86,31 +86,24 @@ module.exports = {
}); });
}); });
}, },
checkClaimNameAvailability (name) { claimNameIsAvailable (name) {
return new Promise((resolve, reject) => { // find any records where the name is used
// find any records where the name is used return db.File.findAll({ where: { name } })
db.File.findAll({ where: { name } }) .then(result => {
.then(result => { if (result.length >= 1) {
if (result.length >= 1) { const claimAddress = config.wallet.lbryClaimAddress;
const claimAddress = config.wallet.lbryClaimAddress; // filter out any results that were not published from spee.ch's wallet address
// filter out any results that were not published from spee.ch's wallet address const filteredResult = result.filter((claim) => {
const filteredResult = result.filter((claim) => { return (claim.address === claimAddress);
return (claim.address === claimAddress); });
}); // return based on whether any non-spee.ch claims were left
// return based on whether any non-spee.ch claims were left if (filteredResult.length >= 1) {
if (filteredResult.length >= 1) { throw new Error('That claim is already in use');
resolve(false); };
} else { return name;
resolve(true); };
} return name;
} else { });
resolve(true);
}
})
.catch(error => {
reject(error);
});
});
}, },
checkChannelAvailability (name) { checkChannelAvailability (name) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View file

@ -58,30 +58,6 @@ module.exports = {
thumbnailFileType: (thumbnail ? thumbnail.type : null), thumbnailFileType: (thumbnail ? thumbnail.type : null),
}; };
}, },
parsePublishApiChannel ({channelName, channelPassword}, user) {
logger.debug('api/claim/publish, channel data:', {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) { validateFileTypeAndSize (file) {
// check file type and size // check file type and size
switch (file.type) { switch (file.type) {
@ -111,7 +87,7 @@ module.exports = {
} }
return file; return file;
}, },
createPublishParams (filePath, name, title, description, license, nsfw, thumbnail, channelName) { createBasicPublishParams (filePath, name, title, description, license, nsfw, thumbnail) {
logger.debug(`Creating Publish Parameters`); logger.debug(`Creating Publish Parameters`);
// provide defaults for title // provide defaults for title
if (title === null || title.trim() === '') { if (title === null || title.trim() === '') {
@ -144,10 +120,6 @@ module.exports = {
if (thumbnail) { if (thumbnail) {
publishParams['metadata']['thumbnail'] = thumbnail; publishParams['metadata']['thumbnail'] = thumbnail;
} }
// add channel to params, if applicable
if (channelName) {
publishParams['channel_name'] = channelName;
}
return publishParams; return publishParams;
}, },
createThumbnailPublishParams (thumbnailFilePath, claimName, license, nsfw) { createThumbnailPublishParams (thumbnailFilePath, claimName, license, nsfw) {

View file

@ -24,8 +24,8 @@ module.exports = (sequelize, { STRING }) => {
User.hasOne(db.Channel); User.hasOne(db.Channel);
}; };
User.prototype.comparePassword = function (password, callback) { User.prototype.comparePassword = function (password) {
bcrypt.compare(password, this.password, callback); return bcrypt.compare(password, this.password);
}; };
User.prototype.changePassword = function (newPassword) { User.prototype.changePassword = function (newPassword) {

View file

@ -30,33 +30,33 @@ module.exports = new PassportLocalStrategy(
passwordField: 'password', passwordField: 'password',
}, },
(username, password, done) => { (username, password, done) => {
logger.debug('logging user in'); return db.User
return db .findOne({
.User where: {userName: username},
.findOne({where: {userName: username}}) })
.then(user => { .then(user => {
if (!user) { if (!user) {
// logger.debug('no user found'); logger.debug('no user found');
return done(null, false, {message: 'Incorrect username or password'}); return done(null, false, {message: 'Incorrect username or password'});
} }
user.comparePassword(password, (passwordErr, isMatch) => { return user.comparePassword(password)
if (passwordErr) { .then(isMatch => {
logger.error('passwordErr:', passwordErr); if (!isMatch) {
return done(null, false, {message: passwordErr}); logger.debug('incorrect password');
} return done(null, false, {message: 'Incorrect username or password'});
if (!isMatch) { }
// logger.debug('incorrect password'); logger.debug('Password was a match, returning User');
return done(null, false, {message: 'Incorrect username or password'}); return returnUserAndChannelInfo(user)
} .then(userInfo => {
logger.debug('Password was a match, returning User'); return done(null, userInfo);
return returnUserAndChannelInfo(user) })
.then((userInfo) => { .catch(error => {
return done(null, userInfo); return error;
}) });
.catch(error => { })
return done(error); .catch(error => {
}); return error;
}); });
}) })
.catch(error => { .catch(error => {
return done(error); return done(error);

View file

@ -37,13 +37,8 @@ class PublishUrlInput extends React.Component {
} }
checkClaimIsAvailable (claim) { checkClaimIsAvailable (claim) {
request(`/api/claim/availability/${claim}`) request(`/api/claim/availability/${claim}`)
.then(isAvailable => { .then(() => {
console.log('checkClaimIsAvailable request response:', isAvailable); this.props.onUrlError(null);
if (isAvailable) {
this.props.onUrlError(null);
} else {
this.props.onUrlError('That url has already been claimed');
}
}) })
.catch((error) => { .catch((error) => {
this.props.onUrlError(error.message); this.props.onUrlError(error.message);

View file

@ -3,12 +3,14 @@ const multipart = require('connect-multiparty');
const { files, site } = require('../config/speechConfig.js'); const { files, site } = require('../config/speechConfig.js');
const multipartMiddleware = multipart({uploadDir: files.uploadDirectory}); const multipartMiddleware = multipart({uploadDir: files.uploadDirectory});
const db = require('../models'); const db = require('../models');
const { checkClaimNameAvailability, checkChannelAvailability, publish } = require('../controllers/publishController.js'); const { claimNameIsAvailable, checkChannelAvailability, publish } = require('../controllers/publishController.js');
const { getClaimList, resolveUri, getClaim } = require('../helpers/lbryApi.js'); const { getClaimList, resolveUri, getClaim } = require('../helpers/lbryApi.js');
const { addGetResultsToFileData, createFileData, createPublishParams, createThumbnailPublishParams, parsePublishApiRequestBody, parsePublishApiRequestFiles, parsePublishApiChannel } = require('../helpers/publishHelpers.js');
const { addGetResultsToFileData, createBasicPublishParams, createThumbnailPublishParams, parsePublishApiRequestBody, parsePublishApiRequestFiles, createFileData } = require('../helpers/publishHelpers.js');
const errorHandlers = require('../helpers/errorHandlers.js'); const errorHandlers = require('../helpers/errorHandlers.js');
const { sendGAAnonymousPublishTiming, sendGAChannelPublishTiming } = require('../helpers/googleAnalytics.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 { getChannelData, getChannelClaims, getClaimId } = require('../controllers/serveController.js');
const NO_CHANNEL = 'NO_CHANNEL'; const NO_CHANNEL = 'NO_CHANNEL';
@ -108,13 +110,9 @@ module.exports = (app) => {
}); });
// route to check whether this site published to a claim // route to check whether this site published to a claim
app.get('/api/claim/availability/:name', ({ ip, originalUrl, params }, res) => { app.get('/api/claim/availability/:name', ({ ip, originalUrl, params }, res) => {
checkClaimNameAvailability(params.name) claimNameIsAvailable(params.name)
.then(result => { .then(result => {
if (result === true) { res.status(200).json(result);
res.status(200).json(true);
} else {
res.status(200).json(false);
}
}) })
.catch(error => { .catch(error => {
errorHandlers.handleErrorResponse(originalUrl, ip, error, res); errorHandlers.handleErrorResponse(originalUrl, ip, error, res);
@ -135,7 +133,7 @@ module.exports = (app) => {
logger.debug('api/claim/publish req.body:', body); logger.debug('api/claim/publish req.body:', body);
logger.debug('api/claim/publish req.files:', files); logger.debug('api/claim/publish req.files:', files);
// define variables // define variables
let name, fileName, filePath, fileType, thumbnailFileName, thumbnailFilePath, thumbnailFileType, nsfw, license, title, description, thumbnail, channelName, channelPassword; let name, fileName, filePath, fileType, thumbnailFileName, thumbnailFilePath, thumbnailFileType, nsfw, license, title, description, thumbnail, channelName, channelId, channelPassword;
// record the start time of the request // record the start time of the request
const publishStartTime = Date.now(); const publishStartTime = Date.now();
// validate the body and files of the request // validate the body and files of the request
@ -143,30 +141,23 @@ module.exports = (app) => {
// validateApiPublishRequest(body, files); // validateApiPublishRequest(body, files);
({name, nsfw, license, title, description, thumbnail} = parsePublishApiRequestBody(body)); ({name, nsfw, license, title, description, thumbnail} = parsePublishApiRequestBody(body));
({fileName, filePath, fileType, thumbnailFileName, thumbnailFilePath, thumbnailFileType} = parsePublishApiRequestFiles(files)); ({fileName, filePath, fileType, thumbnailFileName, thumbnailFilePath, thumbnailFileType} = parsePublishApiRequestFiles(files));
({channelName, channelPassword} = parsePublishApiChannel(body, user)); ({channelName, channelId, channelPassword} = body);
} catch (error) { } catch (error) {
return res.status(400).json({success: false, message: error.message}); return res.status(400).json({success: false, message: error.message});
} }
// check channel authorization // check channel authorization
authenticateIfNoUserToken(channelName, channelPassword, user) Promise.all([
.then(authenticated => { authenticateUser(channelName, channelId, channelPassword, user),
if (!authenticated) { claimNameIsAvailable(name),
throw new Error('Authentication failed, you do not have access to that channel'); createBasicPublishParams(filePath, name, title, description, license, nsfw, thumbnail),
createThumbnailPublishParams(thumbnailFilePath, name, license, nsfw),
])
.then(([{channelName, channelClaimId}, validatedClaimName, publishParams, thumbnailPublishParams]) => {
// 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 Promise.all([
createPublishParams(filePath, name, title, description, license, nsfw, thumbnail, channelName),
createThumbnailPublishParams(thumbnailFilePath, name, license, nsfw),
]);
})
.then(([publishParams, thumbnailPublishParams]) => {
// publish the thumbnail // publish the thumbnail
if (thumbnailPublishParams) { if (thumbnailPublishParams) {
publish(thumbnailPublishParams, thumbnailFileName, thumbnailFileType); publish(thumbnailPublishParams, thumbnailFileName, thumbnailFileType);

View file

@ -26,18 +26,17 @@ db.sequelize.sync() // sync sequelize
if (!user) { if (!user) {
throw new Error('no user found'); throw new Error('no user found');
} }
return new Promise((resolve, reject) => { return Promise.all([
user.comparePassword(oldPassword, (passwordErr, isMatch) => { user.comparePassword(oldPassword),
if (passwordErr) { user,
return reject(passwordErr); ]);
} })
if (!isMatch) { .then(([isMatch, user]) => {
return reject('Incorrect old password.'); if (!isMatch) {
} throw new Error('Incorrect old password.');
logger.debug('Password was a match, updating password'); }
return resolve(user.changePassword(newPassword)); logger.debug('Password was a match, updating password');
}); return user.changePassword(newPassword);
});
}) })
.then(() => { .then(() => {
logger.debug('Password successfully updated'); logger.debug('Password successfully updated');

View file

@ -3,7 +3,7 @@ const expect = chai.expect;
const chaiHttp = require('chai-http'); const chaiHttp = require('chai-http');
const { site, testing } = require('../../config/speechConfig.js'); const { site, testing } = require('../../config/speechConfig.js');
const { host } = site; const { host } = site;
const { testChannel, testChannelPassword } = testing; const { testChannel, testChannelId, testChannelPassword } = testing;
const requestTimeout = 20000; const requestTimeout = 20000;
const publishTimeout = 120000; const publishTimeout = 120000;
const fs = require('fs'); const fs = require('fs');
@ -109,19 +109,64 @@ describe('end-to-end', function () {
const filePath = './test/mock-data/bird.jpeg'; const filePath = './test/mock-data/bird.jpeg';
const fileName = 'byrd.jpeg'; const fileName = 'byrd.jpeg';
const channelName = testChannel; const channelName = testChannel;
const channelId = testChannelId;
const channelPassword = testChannelPassword; const channelPassword = testChannelPassword;
const date = new Date();
const name = `test-publish-${date.getFullYear()}-${date.getMonth()}-${date.getDate()}-${date.getTime()}`;
describe('anonymous publishes', function () { describe('api/claim/publish', function () {
it(`should receive a status code 200 within ${publishTimeout}ms @usesLbc`, function (done) {
const date = new Date(); it(`should receive a status code 400 if username does not exist`, function (done) {
const name = `test-publish-${date.getFullYear()}-${date.getMonth()}-${date.getDate()}-${date.getTime()}`;
chai.request(host) chai.request(host)
.post(publishUrl) .post(publishUrl)
.type('form') .type('form')
.attach('file', fs.readFileSync(filePath), fileName) .attach('file', fs.readFileSync(filePath), fileName)
.field('name', name) .field('name', name)
.field('channelName', `@${name}`)
.field('channelPassword', channelPassword)
.end(function (err, res) {
expect(res).to.have.status(400);
done();
});
}).timeout(publishTimeout);
it(`should receive a status code 400 if the wrong password is used with the channel name`, function (done) {
chai.request(host)
.post(publishUrl)
.type('form')
.attach('file', fs.readFileSync(filePath), fileName)
.field('name', name)
.field('channelName', channelName)
.field('channelPassword', 'xxxxx')
.end(function (err, res) {
expect(res).to.have.status(400);
done();
});
}).timeout(publishTimeout);
it(`should receive a status code 400 if the wrong password is used with the channel id`, function (done) {
chai.request(host)
.post(publishUrl)
.type('form')
.attach('file', fs.readFileSync(filePath), fileName)
.field('name', name)
.field('channelName', channelName)
.field('channelPassword', 'xxxxx')
.end(function (err, res) {
expect(res).to.have.status(400);
done();
});
}).timeout(publishTimeout);
});
describe('anonymous publishes', function () {
it(`should receive a status code 200 within ${publishTimeout}ms @usesLbc`, function (done) {
chai.request(host)
.post(publishUrl)
.type('form')
.attach('file', fs.readFileSync(filePath), fileName)
.field('name', `${name}-anonymous`)
.end(function (err, res) { .end(function (err, res) {
// expect(err).to.be.null;
expect(res).to.have.status(200); expect(res).to.have.status(200);
done(); done();
}); });
@ -130,17 +175,14 @@ describe('end-to-end', function () {
describe('in-channel publishes', function () { describe('in-channel publishes', function () {
it(`should receive a status code 200 within ${publishTimeout}ms @usesLbc`, function (done) { it(`should receive a status code 200 within ${publishTimeout}ms @usesLbc`, function (done) {
const date = new Date();
const name = `test-publish-${date.getFullYear()}-${date.getMonth()}-${date.getDate()}-${date.getTime()}`;
chai.request(host) chai.request(host)
.post(publishUrl) .post(publishUrl)
.type('form') .type('form')
.attach('file', fs.readFileSync(filePath), fileName) .attach('file', fs.readFileSync(filePath), fileName)
.field('name', name) .field('name', `${name}-channel`)
.field('channelName', channelName) .field('channelName', channelName)
.field('channelPassword', channelPassword) .field('channelPassword', channelPassword)
.end(function (err, res) { .end(function (err, res) {
// expect(err).to.be.null;
expect(res).to.have.status(200); expect(res).to.have.status(200);
done(); done();
}); });