Merge pull request #605 from lbryio/multisite-555

Multisite 555
This commit is contained in:
Travis Eden 2018-10-05 09:33:27 -04:00 committed by GitHub
commit fd86b7ce49
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 194 additions and 60 deletions

View file

@ -157,7 +157,7 @@ inquirer
method: 'channel_new',
params: {
channel_name: thumbnailChannelDefault,
amount : 0.1,
amount : '0.1',
},
})
.then(response => {

View file

@ -26,7 +26,11 @@
"thumbnailChannelId": null,
"additionalClaimAddresses": [],
"disabled": false,
"disabledMessage": "Default publishing disabled message"
"disabledMessage": "Default publishing disabled message",
"closedRegistration": false,
"serveOnlyApproved": false,
"publishOnlyApproved": false,
"approvedChannels": []
},
"startup": {
"performChecks": true,

View file

@ -1,21 +0,0 @@
import React from 'react';
import ChannelLoginForm from '@containers/ChannelLoginForm';
import ChannelCreateForm from '@containers/ChannelCreateForm';
import Row from '@components/Row';
const ChannelTools = () => {
return (
<div>
<Row>
<h3>Log in to an existing channel:</h3>
<ChannelLoginForm />
</Row>
<Row>
<h3>Create a brand new channel:</h3>
<ChannelCreateForm />
</Row>
</div>
);
};
export default ChannelTools;

View file

@ -1,13 +1,18 @@
import {connect} from 'react-redux';
import {setPublishInChannel, updateSelectedChannel, updateError} from '../../actions/publish';
// import isApprovedChannel from '../../../../utils/isApprovedChannel';
import View from './view';
const mapStateToProps = ({ channel, publish }) => {
const mapStateToProps = ({ publish, site, channel: { loggedInChannel: { name, shortId, longId } } }) => {
return {
loggedInChannelName: channel.loggedInChannel.name,
// isApprovedChannel : isApprovedChannel({ longId }, site.approvedChannels),
publishOnlyApproved: site.publishOnlyApproved,
// closedRegistration : site.closedRegistration,
loggedInChannelName: name,
publishInChannel : publish.publishInChannel,
selectedChannel : publish.selectedChannel,
channelError : publish.error.channel,
longId,
};
};

View file

@ -16,9 +16,12 @@ class ChannelSelect extends React.Component {
this.handleSelection = this.handleSelection.bind(this);
}
componentWillMount () {
const { loggedInChannelName } = this.props;
const { loggedInChannelName, onChannelSelect, publishOnlyApproved, onPublishInChannelChange } = this.props;
if (loggedInChannelName) {
this.props.onChannelSelect(loggedInChannelName);
onChannelSelect(loggedInChannelName);
}
if (publishOnlyApproved) {
onPublishInChannelChange(true);
}
}
toggleAnonymousPublish (event) {
@ -34,7 +37,17 @@ class ChannelSelect extends React.Component {
this.props.onChannelSelect(selectedOption);
}
render () {
const { publishInChannel, channelError, selectedChannel, loggedInChannelName } = this.props;
const { publishInChannel, channelError, selectedChannel, loggedInChannelName, publishOnlyApproved } = this.props;
if (publishOnlyApproved) {
return (
<div>
<RowLabeled
label={<Label value={'Channel:'} />}
content={<span>{loggedInChannelName}</span>}
/>
</div>
);
}
return (
<div>
<RowLabeled

View file

@ -0,0 +1,10 @@
import { connect } from 'react-redux';
import View from './view';
const mapStateToProps = ({ site: { closedRegistration } }) => {
return {
closedRegistration,
};
};
export default connect(mapStateToProps, null)(View);

View file

@ -0,0 +1,23 @@
import React from 'react';
import ChannelLoginForm from '@containers/ChannelLoginForm';
import ChannelCreateForm from '@containers/ChannelCreateForm';
import Row from '@components/Row';
class ChannelTools extends React.Component {
render () {
return (
<div>
<Row>
<h3>Log in to an existing channel:</h3>
<ChannelLoginForm />
</Row>
{!this.props.closedRegistration && (<Row>
<h3>Create a brand new channel:</h3>
<ChannelCreateForm />
</Row>)}
</div>
);
}
}
export default ChannelTools;

View file

@ -1,11 +1,14 @@
import { connect } from 'react-redux';
import { logOutChannel, checkForLoggedInChannel } from '../../actions/channel';
import isApprovedChannel from '../../../../utils/isApprovedChannel';
import View from './view';
const mapStateToProps = ({ channel: { loggedInChannel: { name, shortId, longId } } }) => {
const mapStateToProps = ({ site, channel: { loggedInChannel: { name, shortId, longId } } }) => {
return {
showPublish : (!site.publishOnlyApproved || isApprovedChannel({ longId }, site.approvedChannels)),
closedRegistration: site.closedRegistration,
channelName : name,
channelShortId: shortId,
channelShortId : shortId,
channelLongId : longId,
};
};

View file

@ -28,16 +28,17 @@ class NavigationLinks extends React.Component {
}
}
render () {
const { channelName, showPublish, closedRegistration } = this.props;
return (
<div className='navigation-links'>
<NavLink
{showPublish && <NavLink
className='nav-bar-link link--nav'
activeClassName='link--nav-active'
to='/'
exact
>
Publish
</NavLink>
</NavLink>}
<NavLink
className='nav-bar-link link--nav'
activeClassName='link--nav-active'
@ -45,7 +46,7 @@ class NavigationLinks extends React.Component {
>
About
</NavLink>
{ this.props.channelName ? (
{ channelName ? (
<NavBarChannelOptionsDropdown
channelName={this.props.channelName}
handleSelection={this.handleSelection}
@ -53,7 +54,7 @@ class NavigationLinks extends React.Component {
VIEW={VIEW}
LOGOUT={LOGOUT}
/>
) : (
) : !closedRegistration && (
<NavLink
id='nav-bar-login-link'
className='nav-bar-link link--nav'

View file

@ -3,12 +3,14 @@ import ErrorPage from '@pages/ErrorPage';
import ShowAssetLite from '@pages/ShowAssetLite';
import ShowAssetDetails from '@pages/ShowAssetDetails';
import ShowChannel from '@pages/ShowChannel';
import { withRouter } from 'react-router-dom';
import { CHANNEL, ASSET_LITE, ASSET_DETAILS } from '../../constants/show_request_types';
class ContentPageWrapper extends React.Component {
componentDidMount () {
this.props.onHandleShowPageUri(this.props.match.params);
const { onHandleShowPageUri, match, homeChannel } = this.props;
onHandleShowPageUri(homeChannel ? { claim: homeChannel } : match.params);
}
componentWillReceiveProps (nextProps) {
if (nextProps.match.params !== this.props.match.params) {
@ -35,4 +37,4 @@ class ContentPageWrapper extends React.Component {
}
};
export default ContentPageWrapper;
export default withRouter(ContentPageWrapper);

View file

@ -2,10 +2,11 @@ import { connect } from 'react-redux';
import { onHandleShowPageUri } from '../../actions/show';
import View from './view';
const mapStateToProps = ({ show }) => {
const mapStateToProps = ({ show, site, channel }) => {
return {
error : show.request.error,
requestType: show.request.type,
homeChannel: site.publishOnlyApproved && !channel.loggedInChannel.name ? `${site.approvedChannels[0].name}:${site.approvedChannels[0].longId}` : null,
};
};

View file

@ -1,11 +1,14 @@
import React from 'react';
import PageLayout from '@components/PageLayout';
import PublishTool from '@containers/PublishTool';
import ContentPageWrapper from '@pages/ContentPageWrapper';
class HomePage extends React.Component {
render () {
return (
const { homeChannel } = this.props;
return homeChannel ? (
<ContentPageWrapper homeChannel={homeChannel} />
) : (
<PageLayout
pageTitle={'Speech'}
pageUri={''}

View file

@ -4,7 +4,7 @@ import PageLayout from '@components/PageLayout';
import HorizontalSplit from '@components/HorizontalSplit';
import ChannelAbout from '@components/ChannelAbout';
import ChannelTools from '@components/ChannelTools';
import ChannelTools from '@containers/ChannelTools';
class LoginPage extends React.Component {
componentWillReceiveProps (newProps) {

View file

@ -6,8 +6,13 @@ let initialState = {
host : 'default host',
title : 'default title',
twitter : 'default twitter',
defaultDescription: 'default description',
defaultDescription : 'default description',
defaultThumbnail : 'default thumbnail',
closedRegistration : false,
serveOnlyApproved : false,
publishOnlyApproved: false,
approvedChannels : [],
};
if (siteConfig) {
@ -25,6 +30,12 @@ if (siteConfig) {
title,
twitter,
},
publishing: {
closedRegistration,
serveOnlyApproved,
publishOnlyApproved,
approvedChannels,
},
} = siteConfig;
initialState = {
@ -35,6 +46,10 @@ if (siteConfig) {
twitter,
defaultDescription,
defaultThumbnail,
closedRegistration,
serveOnlyApproved,
publishOnlyApproved,
approvedChannels,
};
}

View file

@ -1,8 +1,12 @@
const { handleErrorResponse } = require('../../../utils/errorHandlers.js');
const getChannelData = require('./getChannelData.js');
const isApprovedChannel = require('../../../../../utils/isApprovedChannel');
const { publishing: { serveOnlyApproved, approvedChannels } } = require('@config/siteConfig');
const NO_CHANNEL = 'NO_CHANNEL';
const LONG_ID = 'longId';
const SHORT_ID = 'shortId';
const LONG_CLAIM_LENGTH = 40;
/*
@ -14,6 +18,16 @@ const channelData = ({ ip, originalUrl, body, params }, res) => {
const channelName = params.channelName;
let channelClaimId = params.channelClaimId;
if (channelClaimId === 'none') channelClaimId = null;
const chanObj = {};
if (channelName) chanObj.name = channelName;
if (channelClaimId) chanObj[(channelClaimId.length === LONG_CLAIM_LENGTH ? LONG_ID : SHORT_ID)] = channelClaimId;
if (serveOnlyApproved && !isApprovedChannel(chanObj, approvedChannels)) {
return res.status(404).json({
success: false,
message: 'This content is unavailable',
});
}
getChannelData(channelName, channelClaimId)
.then(data => {
res.status(200).json({

View file

@ -18,7 +18,7 @@ const createPublishParams = (filePath, name, title, description, license, nsfw,
const publishParams = {
name,
file_path: filePath,
bid : 0.01,
bid : '0.01',
metadata : {
description,
title,

View file

@ -10,7 +10,7 @@ const createThumbnailPublishParams = (thumbnailFilePath, claimName, license, nsf
return {
name : `${claimName}-thumb`,
file_path: thumbnailFilePath,
bid : 0.01,
bid : '0.01',
metadata : {
title : `${claimName} thumbnail`,
description: `a thumbnail for ${claimName}`,

View file

@ -3,6 +3,8 @@ const logger = require('winston');
const { details: { host }, publishing: { disabled, disabledMessage } } = require('@config/siteConfig');
const { sendGATimingEvent } = require('../../../../utils/googleAnalytics.js');
const isApprovedChannel = require('../../../../../utils/isApprovedChannel');
const { publishing: { publishOnlyApproved, approvedChannels } } = require('@config/siteConfig');
const { handleErrorResponse } = require('../../../utils/errorHandlers.js');
@ -16,6 +18,7 @@ const parsePublishApiRequestFiles = require('./parsePublishApiRequestFiles.js');
const authenticateUser = require('./authentication.js');
const CLAIM_TAKEN = 'CLAIM_TAKEN';
const UNAPPROVED_CHANNEL = 'UNAPPROVED_CHANNEL';
/*
@ -54,6 +57,13 @@ const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res)
// check channel authorization
authenticateUser(channelName, channelId, channelPassword, user)
.then(({ channelName, channelClaimId }) => {
if (publishOnlyApproved && !isApprovedChannel({ longId: channelClaimId }, approvedChannels)) {
const error = {
name : UNAPPROVED_CHANNEL,
message: 'This spee.ch instance only allows publishing to approved channels',
};
throw error;
}
return Promise.all([
checkClaimAvailability(name),
createPublishParams(filePath, name, title, description, license, nsfw, thumbnail, channelName, channelClaimId),
@ -92,7 +102,7 @@ const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res)
sendGATimingEvent('end-to-end', 'publish', fileType, gaStartTime, Date.now());
})
.catch(error => {
if (error.name === CLAIM_TAKEN) {
if ([CLAIM_TAKEN, UNAPPROVED_CHANNEL].includes(error.name)) {
res.status(400).json({
success: false,
message: error.message,

View file

@ -1,6 +1,7 @@
const logger = require('winston');
const db = require('../../../models');
const isApprovedChannel = require('../../../../utils/isApprovedChannel');
const getClaimId = require('../../utils/getClaimId.js');
const { handleErrorResponse } = require('../../utils/errorHandlers.js');
@ -11,17 +12,28 @@ const NO_CHANNEL = 'NO_CHANNEL';
const NO_CLAIM = 'NO_CLAIM';
const BLOCKED_CLAIM = 'BLOCKED_CLAIM';
const NO_FILE = 'NO_FILE';
const CONTENT_UNAVAILABLE = 'CONTENT_UNAVAILABLE';
const { publishing: { serveOnlyApproved, approvedChannels } } = require('@config/siteConfig');
const getClaimIdAndServeAsset = (channelName, channelClaimId, claimName, claimId, originalUrl, ip, res) => {
getClaimId(channelName, channelClaimId, claimName, claimId)
.then(fullClaimId => {
claimId = fullClaimId;
logger.debug('Full claim id:', fullClaimId);
return db.Claim.getOutpoint(claimName, fullClaimId);
return db.Claim.findOne({
where: {
name : claimName,
claimId: fullClaimId,
},
});
})
.then(outpoint => {
logger.debug('Outpoint:', outpoint);
return db.Blocked.isNotBlocked(outpoint);
.then(claim => {
if (serveOnlyApproved && !isApprovedChannel({ longId: claim.dataValues.certificateId }, approvedChannels)) {
throw new Error(CONTENT_UNAVAILABLE);
}
logger.debug('Outpoint:', claim.dataValues.outpoint);
return db.Blocked.isNotBlocked(claim.dataValues.outpoint);
})
.then(() => {
return db.File.findOne({
@ -52,6 +64,13 @@ const getClaimIdAndServeAsset = (channelName, channelClaimId, claimName, claimId
message: 'No matching channel id could be found for that url',
});
}
if (error === CONTENT_UNAVAILABLE) {
logger.debug('unapproved channel');
return res.status(400).json({
success: false,
message: 'This content is unavailable',
});
}
if (error === BLOCKED_CLAIM) {
logger.debug('claim was blocked');
return res.status(451).json({

View file

@ -116,7 +116,7 @@ module.exports = {
method: 'channel_new',
params: {
channel_name: name,
amount : 0.1,
amount : '0.1',
},
})
.then(response => {

View file

@ -1,6 +1,8 @@
const logger = require('winston');
const returnShortId = require('./utils/returnShortId.js');
const isApprovedChannel = require('../../utils/isApprovedChannel');
const { assetDefaults: { thumbnail: defaultThumbnail }, details: { host } } = require('@config/siteConfig');
const { publishing: { serveOnlyApproved, approvedChannels } } = require('@config/siteConfig');
const NO_CLAIM = 'NO_CLAIM';
@ -354,7 +356,7 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
}
};
Claim.resolveClaim = function (name, claimId) {
Claim.fetchClaim = function (name, claimId) {
logger.debug(`Claim.resolveClaim: ${name} ${claimId}`);
return new Promise((resolve, reject) => {
this
@ -378,6 +380,23 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
});
};
Claim.resolveClaim = function (name, claimId) {
return new Promise((resolve, reject) => {
this
.fetchClaim(name, claimId)
.then(claim => {
logger.info('resolveClaim claims:', claim);
if (serveOnlyApproved && !isApprovedChannel({ longId: claim.certificateId }, approvedChannels)) {
throw new Error('This content is unavailable');
}
return resolve(claim);
})
.catch(error => {
reject(error);
});
});
};
Claim.getOutpoint = function (name, claimId) {
logger.debug(`finding outpoint for ${name}#${claimId}`);
return this

View file

@ -2,6 +2,7 @@ const PassportLocalStrategy = require('passport-local').Strategy;
const { createChannel } = require('../../lbrynet');
const logger = require('winston');
const db = require('../../models');
const { publishing: { closedRegistration } } = require('@config/siteConfig');
module.exports = new PassportLocalStrategy(
{
@ -9,10 +10,13 @@ module.exports = new PassportLocalStrategy(
passwordField: 'password',
},
(username, password, done) => {
if (closedRegistration) {
return done('Registration is disabled');
}
logger.verbose(`new channel signup request. user: ${username} pass: ${password} .`);
let userInfo = {};
// server-side validaton of inputs (username, password)
// create the channel and retrieve the metadata
return createChannel(`@${username}`)
.then(tx => {

View file

@ -0,0 +1,9 @@
function isApprovedChannel (channel, channels) {
const { name, shortId: short, longId: long } = channel;
return Boolean(
(long && channels.find(chan => chan.longId === long)) ||
(name && short && channels.find(chan => chan.name === name && chan.shortId === short))
);
}
module.exports = isApprovedChannel;