diff --git a/cli/configure.js b/cli/configure.js
index 0d59cbbd..c50b81fa 100644
--- a/cli/configure.js
+++ b/cli/configure.js
@@ -157,7 +157,7 @@ inquirer
method: 'channel_new',
params: {
channel_name: thumbnailChannelDefault,
- amount : 0.1,
+ amount : '0.1',
},
})
.then(response => {
diff --git a/cli/defaults/siteConfig.json b/cli/defaults/siteConfig.json
index 8359f0e6..91efe08f 100644
--- a/cli/defaults/siteConfig.json
+++ b/cli/defaults/siteConfig.json
@@ -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,
diff --git a/client/src/components/ChannelTools/index.jsx b/client/src/components/ChannelTools/index.jsx
deleted file mode 100644
index 5e640dd9..00000000
--- a/client/src/components/ChannelTools/index.jsx
+++ /dev/null
@@ -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 (
-
-
- Log in to an existing channel:
-
-
-
- Create a brand new channel:
-
-
-
- );
-};
-
-export default ChannelTools;
diff --git a/client/src/containers/ChannelSelect/index.js b/client/src/containers/ChannelSelect/index.js
index 9efce26d..23d731ad 100644
--- a/client/src/containers/ChannelSelect/index.js
+++ b/client/src/containers/ChannelSelect/index.js
@@ -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,
};
};
diff --git a/client/src/containers/ChannelSelect/view.jsx b/client/src/containers/ChannelSelect/view.jsx
index 4a4dc5fe..d04007d5 100644
--- a/client/src/containers/ChannelSelect/view.jsx
+++ b/client/src/containers/ChannelSelect/view.jsx
@@ -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 (
+
+ }
+ content={{loggedInChannelName}}
+ />
+
+ );
+ }
return (
{
+ return {
+ closedRegistration,
+ };
+};
+
+export default connect(mapStateToProps, null)(View);
diff --git a/client/src/containers/ChannelTools/view.jsx b/client/src/containers/ChannelTools/view.jsx
new file mode 100644
index 00000000..0be7bef0
--- /dev/null
+++ b/client/src/containers/ChannelTools/view.jsx
@@ -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 (
+
+
+ Log in to an existing channel:
+
+
+ {!this.props.closedRegistration && (
+ Create a brand new channel:
+
+
)}
+
+ );
+ }
+}
+
+export default ChannelTools;
diff --git a/client/src/containers/NavigationLinks/index.jsx b/client/src/containers/NavigationLinks/index.jsx
index 264bb027..d5a9c454 100644
--- a/client/src/containers/NavigationLinks/index.jsx
+++ b/client/src/containers/NavigationLinks/index.jsx
@@ -1,12 +1,15 @@
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 {
- channelName : name,
- channelShortId: shortId,
- channelLongId : longId,
+ showPublish : (!site.publishOnlyApproved || isApprovedChannel({ longId }, site.approvedChannels)),
+ closedRegistration: site.closedRegistration,
+ channelName : name,
+ channelShortId : shortId,
+ channelLongId : longId,
};
};
diff --git a/client/src/containers/NavigationLinks/view.jsx b/client/src/containers/NavigationLinks/view.jsx
index 929006d1..01775688 100644
--- a/client/src/containers/NavigationLinks/view.jsx
+++ b/client/src/containers/NavigationLinks/view.jsx
@@ -28,16 +28,17 @@ class NavigationLinks extends React.Component {
}
}
render () {
+ const { channelName, showPublish, closedRegistration } = this.props;
return (
-
Publish
-
+ }
About
- { this.props.channelName ? (
+ { channelName ? (
- ) : (
+ ) : !closedRegistration && (
{
+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,
};
};
diff --git a/client/src/pages/HomePage/view.jsx b/client/src/pages/HomePage/view.jsx
index 8ca1e065..05adfe45 100644
--- a/client/src/pages/HomePage/view.jsx
+++ b/client/src/pages/HomePage/view.jsx
@@ -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 ? (
+
+ ) : (
{
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({
diff --git a/server/controllers/api/claim/publish/createPublishParams.js b/server/controllers/api/claim/publish/createPublishParams.js
index facdc32f..a278bbee 100644
--- a/server/controllers/api/claim/publish/createPublishParams.js
+++ b/server/controllers/api/claim/publish/createPublishParams.js
@@ -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,
diff --git a/server/controllers/api/claim/publish/createThumbnailPublishParams.js b/server/controllers/api/claim/publish/createThumbnailPublishParams.js
index 1032c9a3..668bd07c 100644
--- a/server/controllers/api/claim/publish/createThumbnailPublishParams.js
+++ b/server/controllers/api/claim/publish/createThumbnailPublishParams.js
@@ -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}`,
diff --git a/server/controllers/api/claim/publish/index.js b/server/controllers/api/claim/publish/index.js
index d636de8d..f51d4055 100644
--- a/server/controllers/api/claim/publish/index.js
+++ b/server/controllers/api/claim/publish/index.js
@@ -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,
diff --git a/server/controllers/assets/utils/getClaimIdAndServeAsset.js b/server/controllers/assets/utils/getClaimIdAndServeAsset.js
index 00bf9532..1feb5ba2 100644
--- a/server/controllers/assets/utils/getClaimIdAndServeAsset.js
+++ b/server/controllers/assets/utils/getClaimIdAndServeAsset.js
@@ -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({
diff --git a/server/lbrynet/index.js b/server/lbrynet/index.js
index 4ff64521..655909fa 100644
--- a/server/lbrynet/index.js
+++ b/server/lbrynet/index.js
@@ -116,7 +116,7 @@ module.exports = {
method: 'channel_new',
params: {
channel_name: name,
- amount : 0.1,
+ amount : '0.1',
},
})
.then(response => {
diff --git a/server/models/claim.js b/server/models/claim.js
index 6c1f4092..63aa8b94 100644
--- a/server/models/claim.js
+++ b/server/models/claim.js
@@ -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
diff --git a/server/speechPassport/utils/local-signup.js b/server/speechPassport/utils/local-signup.js
index 1a2067e8..2662b721 100644
--- a/server/speechPassport/utils/local-signup.js
+++ b/server/speechPassport/utils/local-signup.js
@@ -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 => {
diff --git a/utils/isApprovedChannel.js b/utils/isApprovedChannel.js
new file mode 100644
index 00000000..aaf8dcf8
--- /dev/null
+++ b/utils/isApprovedChannel.js
@@ -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;