diff --git a/config.json b/config.json index 12756797..d5839841 100644 --- a/config.json +++ b/config.json @@ -81,6 +81,7 @@ "defaultLimit": 10, "frontPageText": "SynBioHub is a design repository for people designing biological constructs. It enables DNA and protein designs to be uploaded, then provides a shareable link to allow others to view them. SynBioHub also facilitates searching for information about existing useful parts and designs by combining data from a variety of sources.", "allowPublicSignup": true, + "requireLogin": false, "showModuleInteractions": false, "prewarmSearch": true, "bioschemas": { diff --git a/lib/app.js b/lib/app.js index 8d5b163f..6566c92f 100644 --- a/lib/app.js +++ b/lib/app.js @@ -174,7 +174,7 @@ function App () { app.post('/setup', uploadToMemory.single('logo'), views.setup) } - app.all('/browse', views.browse) + app.all('/browse', requirePublicLogin, views.browse) function forceNoHTML (req, res, next) { // User Endpoints @@ -197,14 +197,14 @@ function App () { // Misc. Endpoints app.get('/logo*', views.logo) - app.get('/autocomplete/:query', api.autocomplete) - app.get('/api/datatables', bodyParser.urlencoded({ extended: true }), api.datatables) + app.get('/autocomplete/:query', requirePublicLogin, api.autocomplete) + app.get('/api/datatables', requirePublicLogin, bodyParser.urlencoded({ extended: true }), api.datatables) // Plugin Endpoints - app.get('/stream/:id', views.stream) - app.get('/api/stream/:id', api.stream.serve) - app.delete('/api/stream/:id', api.stream.serve) - app.post('/call', actions.callPlugin) + app.get('/stream/:id', requirePublicLogin, views.stream) + app.get('/api/stream/:id', requirePublicLogin, api.stream.serve) + app.delete('/api/stream/:id', requirePublicLogin, api.stream.serve) + app.post('/call', requirePublicLogin, actions.callPlugin) // Edit Mutable Fields Endpoints app.post('/updateMutableDescription', requireUser, actions.updateMutableDescription) @@ -219,22 +219,22 @@ function App () { // Submission Endpoints app.get('/submit/', requireUser, views.submit) app.post('/submit/', requireUser, views.submit) - app.post('/remoteSubmit/', forceNoHTML, /* requireUser, */ views.submit) // Deprecated + app.post('/remoteSubmit/', forceNoHTML, requirePublicLogin, /* requireUser, */ views.submit) // Deprecated // Administration Endpoints app.get('/admin', requireAdmin, views.admin.status) app.get('/admin/graphs', requireAdmin, views.admin.graphs) app.get('/admin/log', requireAdmin, views.admin.log) - app.get('/admin/virtuoso', api.healthCheck) + app.get('/admin/virtuoso', requirePublicLogin, api.healthCheck) app.get('/admin/mail', requireAdmin, views.admin.mail) app.post('/admin/mail', requireAdmin, bodyParser.urlencoded({ extended: true }), views.admin.mail) - app.get('/admin/plugins', views.admin.plugins) + app.get('/admin/plugins', requirePublicLogin, views.admin.plugins) app.post('/admin/savePlugin', requireAdmin, bodyParser.urlencoded({ extended: true }), actions.admin.savePlugin) app.post('/admin/deletePlugin', requireAdmin, bodyParser.urlencoded({ extended: true }), actions.admin.deletePlugin) - app.get('/admin/registries', views.admin.registries) + app.get('/admin/registries', requirePublicLogin, views.admin.registries) app.post('/admin/saveRegistry', requireAdmin, bodyParser.urlencoded({ extended: true }), actions.admin.saveRegistry) app.post('/admin/deleteRegistry', requireAdmin, bodyParser.urlencoded({ extended: true }), actions.admin.deleteRegistry) app.post( @@ -247,7 +247,7 @@ function App () { app.post('/admin/federate', requireAdmin, bodyParser.urlencoded({ extended: true }), actions.admin.federate) // This endpoint is used by Web-of-Registries to update SynBioHub's list of registries - app.post('/updateWebOfRegistries', bodyParser.json(), api.updateWebOfRegistries) + app.post('/updateWebOfRegistries', requirePublicLogin, bodyParser.json(), api.updateWebOfRegistries) app.get('/admin/remotes', requireAdmin, views.admin.remotes) app.post('/admin/saveRemote', requireAdmin, bodyParser.urlencoded({ extended: true }), actions.admin.saveRemote) @@ -273,40 +273,40 @@ function App () { app.post('/admin/deleteUser', requireAdmin, bodyParser.urlencoded({ extended: true }), actions.admin.deleteUser) // Search Endpoints - app.get('/search/:query?', views.search) - app.get('/searchCount/:query?', views.search) - app.get('/remoteSearch/:query?', forceNoHTML, views.search) /// DEPRECATED, use /search + app.get('/search/:query?', requirePublicLogin, views.search) + app.get('/searchCount/:query?', requirePublicLogin, views.search) + app.get('/remoteSearch/:query?', forceNoHTML, requirePublicLogin, views.search) /// DEPRECATED, use /search - app.get('/sbsearch', views.sbsearch) - app.post('/sbsearch', views.sbsearch) + app.get('/sbsearch', requirePublicLogin, views.sbsearch) + app.post('/sbsearch', requirePublicLogin, views.sbsearch) - app.get('/advancedSearch', views.advancedSearch) - app.post('/advancedSearch', views.advancedSearch) - app.post('/createCollection', views.advancedSearch) + app.get('/advancedSearch', requirePublicLogin, views.advancedSearch) + app.post('/advancedSearch', requirePublicLogin, views.advancedSearch) + app.post('/createCollection', requirePublicLogin, views.advancedSearch) - app.get('/:type/count', api.count) - app.get('/rootCollections', api.rootCollections) + app.get('/:type/count', requirePublicLogin, api.count) + app.get('/rootCollections', requirePublicLogin, api.rootCollections) app.get('/manage', requireUser, views.manage) app.get('/shared', requireUser, views.shared) - app.get('/public/:collectionId/:displayId/:version/subCollections', api.subCollections) - app.get('/public/:collectionId/:displayId/:version/twins', views.search) - app.get('/public/:collectionId/:displayId/:version/uses', views.search) - app.get('/public/:collectionId/:displayId/:version/similar', views.search) + app.get('/public/:collectionId/:displayId/:version/subCollections', requirePublicLogin, api.subCollections) + app.get('/public/:collectionId/:displayId/:version/twins', requirePublicLogin, views.search) + app.get('/public/:collectionId/:displayId/:version/uses', requirePublicLogin, views.search) + app.get('/public/:collectionId/:displayId/:version/similar', requirePublicLogin, views.search) - app.get('/user/:userId/:collectionId/:displayId/:version/subCollections', api.subCollections) - app.get('/user/:userId/:collectionId/:displayId/:version/twins', views.search) - app.get('/user/:userId/:collectionId/:displayId/:version/uses', views.search) - app.get('/user/:userId/:collectionId/:displayId/:version/similar', views.search) + app.get('/user/:userId/:collectionId/:displayId/:version/subCollections', requirePublicLogin, api.subCollections) + app.get('/user/:userId/:collectionId/:displayId/:version/twins', requirePublicLogin, views.search) + app.get('/user/:userId/:collectionId/:displayId/:version/uses', requirePublicLogin, views.search) + app.get('/user/:userId/:collectionId/:displayId/:version/similar', requirePublicLogin, views.search) - app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/subCollections', api.subCollections) - app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/twins', views.search) - app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/uses', views.search) - app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/similar', views.search) + app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/subCollections', requirePublicLogin, api.subCollections) + app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/twins', requirePublicLogin, views.search) + app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/uses', requirePublicLogin, views.search) + app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/similar', requirePublicLogin, views.search) - app.get('/sparql', sparql) - app.post('/sparql', bodyParser.urlencoded({ extended: true }), sparql) + app.get('/sparql', requirePublicLogin, sparql) + app.post('/sparql', requirePublicLogin, bodyParser.urlencoded({ extended: true }), sparql) // Manage Submissions Endpoints app.post( @@ -334,12 +334,13 @@ function App () { actions.makePublic ) - app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/remove', actions.remove) - app.post('/user/:userId/:collectionId/:displayId/:version/:hash/share/removeMembership', actions.removeMembership) - app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/replace', actions.replace) - app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/makePublic', actions.makePublic) + app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/remove', requirePublicLogin, actions.remove) + app.post('/user/:userId/:collectionId/:displayId/:version/:hash/share/removeMembership', requirePublicLogin, actions.removeMembership) + app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/replace', requirePublicLogin, actions.replace) + app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/makePublic', requirePublicLogin, actions.makePublic) app.post( '/user/:userId/:collectionId/:displayId/:version/:hash/share/makePublic', + requirePublicLogin, uploadToMemory.single('file'), actions.makePublic ) @@ -387,15 +388,17 @@ function App () { actions.createICEPart ) - app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/createBenchlingSequence', actions.createBenchlingSequence) + app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/createBenchlingSequence', requirePublicLogin, actions.createBenchlingSequence) app.post( '/user/:userId/:collectionId/:displayId/:version/:hash/share/createBenchlingSequence', + requirePublicLogin, uploadToMemory.single('file'), actions.createBenchlingSequence ) - app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/createICEPart', actions.createICEPart) + app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/createICEPart', requirePublicLogin, actions.createICEPart) app.post( '/user/:userId/:collectionId/:displayId/:version/:hash/share/createICEPart', + requirePublicLogin, uploadToMemory.single('file'), actions.createICEPart ) @@ -409,78 +412,78 @@ function App () { app.post('/user/:userId/:collectionId/:displayId/:version/addOwner', requireUser, views.addOwner) app.post('/user/:userId/:collectionId/:displayId/:version/removeOwner/:username', requireUser, actions.removeOwner) - app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/addOwner', views.addOwner) - app.post('/user/:userId/:collectionId/:displayId/:version/:hash/share/addOwner', views.addOwner) - app.post('/user/:userId/:collectionId/:displayId/:version/:hash/share/removeOwner/:username', actions.removeOwner) + app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/addOwner', requirePublicLogin, views.addOwner) + app.post('/user/:userId/:collectionId/:displayId/:version/:hash/share/addOwner', requirePublicLogin, views.addOwner) + app.post('/user/:userId/:collectionId/:displayId/:version/:hash/share/removeOwner/:username', requirePublicLogin, actions.removeOwner) // Attachment Endpoints app.post('/public/:collectionId/:displayId/:version/attach', requireUser, actions.upload) app.post('/public/:collectionId/:displayId/:version/attachUrl', requireUser, api.attachUrl) - app.get('/public/:collectionId/:displayId/:version/download', api.download) + app.get('/public/:collectionId/:displayId/:version/download', requirePublicLogin, api.download) app.post('/user/:userId/:collectionId/:displayId/:version/attach', requireUser, actions.upload) app.post('/user/:userId/:collectionId/:displayId/:version/attachUrl', requireUser, api.attachUrl) app.get('/user/:userId/:collectionId/:displayId/:version/download', requireUser, api.download) - app.post('/user/:userId/:collectionId/:displayId/:version/:hash/share/attach', actions.upload) - app.post('/user/:userId/:collectionId/:displayId/:version/:hash/share/attachUrl', api.attachUrl) - app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/download', api.download) + app.post('/user/:userId/:collectionId/:displayId/:version/:hash/share/attach', requirePublicLogin, actions.upload) + app.post('/user/:userId/:collectionId/:displayId/:version/:hash/share/attachUrl', requirePublicLogin, api.attachUrl) + app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/download', requirePublicLogin, api.download) // Download Endpoints - app.get('/public/:collectionId/:displayId/sbol', api.persistentIdentity) - app.get('/public/:collectionId/:displayId/sbolnr', api.persistentIdentity) - app.get('/user/:userId/:collectionId/:displayId/sbol', api.persistentIdentity) - app.get('/user/:userId/:collectionId/:displayId/sbolnr', api.persistentIdentity) - - app.get('/public/:collectionId/:displayId/:version/sbol', api.sbol) - app.get('/public/:collectionId/:displayId/:version/sbolnr', api.sbolnr) - app.get('/public/:collectionId/:displayId/:version/omex', api.omex) - app.get('/public/:collectionId/:displayId/:version/summary', api.summary) - app.get('/public/:collectionId/:displayId/:version/fasta', api.fasta) - app.get('/public/:collectionId/:displayId/:version/gb', api.genBank) - app.get('/public/:collectionId/:displayId/:version/gff', api.gff3) - app.get('/public/:collectionId/:displayId/:version/metadata', api.metadata) - - app.get('/user/:userId/:collectionId/:displayId/:version/sbol', api.sbol) - app.get('/user/:userId/:collectionId/:displayId/:version/sbolnr', api.sbolnr) - app.get('/user/:userId/:collectionId/:displayId/:version/omex', api.omex) - app.get('/user/:userId/:collectionId/:displayId/:version/summary', api.summary) - app.get('/user/:userId/:collectionId/:displayId/:version/fasta', api.fasta) - app.get('/user/:userId/:collectionId/:displayId/:version/gb', api.genBank) - app.get('/user/:userId/:collectionId/:displayId/:version/gff', api.gff3) - app.get('/user/:userId/:collectionId/:displayId/:version/metadata', api.metadata) - - app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/sbol', api.sbol) - app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/sbolnr', api.sbolnr) - app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/omex', api.omex) - app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/summary', api.summary) - app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/fasta', api.fasta) - app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/gb', api.genBank) - app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/gff', api.gff3) - app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/metadata', api.metadata) - - app.get('/public/:collectionId/:displayId/:version/visualization', views.visualization) - app.get('/user/:userId/:collectionId/:displayId/:version/visualization', views.visualization) - app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/visualization', views.visualization) + app.get('/public/:collectionId/:displayId/sbol', requirePublicLogin, api.persistentIdentity) + app.get('/public/:collectionId/:displayId/sbolnr', requirePublicLogin, api.persistentIdentity) + app.get('/user/:userId/:collectionId/:displayId/sbol', requirePublicLogin, api.persistentIdentity) + app.get('/user/:userId/:collectionId/:displayId/sbolnr', requirePublicLogin, api.persistentIdentity) + + app.get('/public/:collectionId/:displayId/:version/sbol', requirePublicLogin, api.sbol) + app.get('/public/:collectionId/:displayId/:version/sbolnr', requirePublicLogin, api.sbolnr) + app.get('/public/:collectionId/:displayId/:version/omex', requirePublicLogin, api.omex) + app.get('/public/:collectionId/:displayId/:version/summary', requirePublicLogin, api.summary) + app.get('/public/:collectionId/:displayId/:version/fasta', requirePublicLogin, api.fasta) + app.get('/public/:collectionId/:displayId/:version/gb', requirePublicLogin, api.genBank) + app.get('/public/:collectionId/:displayId/:version/gff', requirePublicLogin, api.gff3) + app.get('/public/:collectionId/:displayId/:version/metadata', requirePublicLogin, api.metadata) + + app.get('/user/:userId/:collectionId/:displayId/:version/sbol', requirePublicLogin, api.sbol) + app.get('/user/:userId/:collectionId/:displayId/:version/sbolnr', requirePublicLogin, api.sbolnr) + app.get('/user/:userId/:collectionId/:displayId/:version/omex', requirePublicLogin, api.omex) + app.get('/user/:userId/:collectionId/:displayId/:version/summary', requirePublicLogin, api.summary) + app.get('/user/:userId/:collectionId/:displayId/:version/fasta', requirePublicLogin, api.fasta) + app.get('/user/:userId/:collectionId/:displayId/:version/gb', requirePublicLogin, api.genBank) + app.get('/user/:userId/:collectionId/:displayId/:version/gff', requirePublicLogin, api.gff3) + app.get('/user/:userId/:collectionId/:displayId/:version/metadata', requirePublicLogin, api.metadata) + + app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/sbol', requirePublicLogin, api.sbol) + app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/sbolnr', requirePublicLogin, api.sbolnr) + app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/omex', requirePublicLogin, api.omex) + app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/summary', requirePublicLogin, api.summary) + app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/fasta', requirePublicLogin, api.fasta) + app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/gb', requirePublicLogin, api.genBank) + app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/gff', requirePublicLogin, api.gff3) + app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/metadata', requirePublicLogin, api.metadata) + + app.get('/public/:collectionId/:displayId/:version/visualization', requirePublicLogin, views.visualization) + app.get('/user/:userId/:collectionId/:displayId/:version/visualization', requirePublicLogin, views.visualization) + app.get('/user/:userId/:collectionId/:displayId/:version/:hash/share/visualization', requirePublicLogin, views.visualization) // View/Download Endpoints - app.get('/public/:collectionId/:displayId', views.persistentIdentity) - app.get('/user/:userId/:collectionId/:displayId', views.persistentIdentity) + app.get('/public/:collectionId/:displayId', requirePublicLogin, views.persistentIdentity) + app.get('/user/:userId/:collectionId/:displayId', requirePublicLogin, views.persistentIdentity) - app.get('/user/:userId/:collectionId/:displayId(*)/:version/:hash/share/full', views.topLevel) - app.get('/user/:userId/:collectionId/:displayId(*)/:version/:hash/share', views.topLevel) - app.post('/user/:userId/:collectionId/:displayId(*)/:version/:hash/share/full', views.topLevel) - app.post('/user/:userId/:collectionId/:displayId(*)/:version/:hash/share', views.topLevel) + app.get('/user/:userId/:collectionId/:displayId(*)/:version/:hash/share/full', requirePublicLogin, views.topLevel) + app.get('/user/:userId/:collectionId/:displayId(*)/:version/:hash/share', requirePublicLogin, views.topLevel) + app.post('/user/:userId/:collectionId/:displayId(*)/:version/:hash/share/full', requirePublicLogin, views.topLevel) + app.post('/user/:userId/:collectionId/:displayId(*)/:version/:hash/share', requirePublicLogin, views.topLevel) - app.get('/user/:userId/:collectionId/:displayId(*)/:version/full', views.topLevel) - app.get('/user/:userId/:collectionId/:displayId(*)/:version', views.topLevel) - app.post('/user/:userId/:collectionId/:displayId(*)/:version/full', views.topLevel) - app.post('/user/:userId/:collectionId/:displayId(*)/:version', views.topLevel) + app.get('/user/:userId/:collectionId/:displayId(*)/:version/full', requirePublicLogin, views.topLevel) + app.get('/user/:userId/:collectionId/:displayId(*)/:version', requirePublicLogin, views.topLevel) + app.post('/user/:userId/:collectionId/:displayId(*)/:version/full', requirePublicLogin, views.topLevel) + app.post('/user/:userId/:collectionId/:displayId(*)/:version', requirePublicLogin, views.topLevel) - app.get('/public/:collectionId/:displayId(*)/:version/full', views.topLevel) - app.get('/public/:collectionId/:displayId(*)/:version', views.topLevel) - app.post('/public/:collectionId/:displayId(*)/:version/full', views.topLevel) - app.post('/public/:collectionId/:displayId(*)/:version', views.topLevel) + app.get('/public/:collectionId/:displayId(*)/:version/full', requirePublicLogin, views.topLevel) + app.get('/public/:collectionId/:displayId(*)/:version', requirePublicLogin, views.topLevel) + app.post('/public/:collectionId/:displayId(*)/:version/full', requirePublicLogin, views.topLevel) + app.post('/public/:collectionId/:displayId(*)/:version', requirePublicLogin, views.topLevel) app.get('/expose/:id', api.expose) @@ -510,6 +513,18 @@ function App () { } } + function requirePublicLogin (req, res, next) { + if (config.get('requireLogin') && !req.user) { + if (!req.accepts('text/html')) { + res.status(401).send('Login required') + } else { + res.redirect('/login?next=' + encodeURIComponent(req.url)) + } + } else { + next() + } + } + function requireUser (req, res, next) { if (!req.user) { if (!req.accepts('text/html')) { diff --git a/lib/views/setup.js b/lib/views/setup.js index 8fb2912a..de701003 100644 --- a/lib/views/setup.js +++ b/lib/views/setup.js @@ -45,6 +45,7 @@ const settingsSchema = Joi.object({ affiliation: Joi.string().allow(''), color: Joi.string().trim().allow(''), allowPublicSignup: Joi.bool(), + requireLogin: Joi.bool(), authProvider: Joi.string().allow('') }) @@ -110,6 +111,7 @@ async function setupPost (req, res, settings) { virtuosoINI: req.body.virtuosoINI, virtuosoDB: req.body.virtuosoDB, allowPublicSignup: !!req.body.allowPublicSignup, + requireLogin: !!req.body.requireLogin, authProvider: req.body.authProvider, googleClientId: req.body.googleClientId, googleClientSecret: req.body.googleClientSecret, @@ -162,6 +164,7 @@ async function setupPost (req, res, settings) { config.set('themeParameters', { default: { baseColor: updatedSettings.color } }) config.set('frontPageText', updatedSettings.frontPageText) config.set('allowPublicSignup', updatedSettings.allowPublicSignup) + config.set('requireLogin', updatedSettings.requireLogin) config.set('databasePrefix', updatedSettings.uriPrefix) config.set('altHome', updatedSettings.altHome) diff --git a/templates/views/setup.jade b/templates/views/setup.jade index d8719596..70a5e13e 100644 --- a/templates/views/setup.jade +++ b/templates/views/setup.jade @@ -47,6 +47,12 @@ block body input(type="checkbox", name="allowPublicSignup", checked=config.allowPublicSignup) | Allow public account creation + br + div.checkbox + label + input(type="checkbox", name="requireLogin", checked=config.requireLogin) + | Require login for all operations + br h2 2. Some technical details div.form-group diff --git a/tests/first_time_setup.py b/tests/first_time_setup.py index 767198b9..b1da3587 100644 --- a/tests/first_time_setup.py +++ b/tests/first_time_setup.py @@ -33,6 +33,7 @@ def test_post(self): 'virtuosoINI': '/etc/virtuoso-opensource-7/virtuoso.ini', 'virtuosoDB': '/var/lib/virtuoso-opensource-7/db', 'allowPublicSignup': 'true', + 'requireLogin': 'false', } compare_post_request('setup', setup, headers = {"Accept": "text/plain"})