diff --git a/.eslintrc b/.eslintrc index bc106670..5198af19 100644 --- a/.eslintrc +++ b/.eslintrc @@ -32,6 +32,8 @@ "allow": ["done", "err", "res", "req", "resolve", "reject"] } ], - "jsx-uses-vars": 0 + "jsx-uses-vars": 0, + "indent": 0, + "max-len": [2, { "code": 100, "ignoreStrings": true, "ignoreTemplateLiterals": true }] } } diff --git a/ACL/ACL/index.js b/ACL/ACL/index.js new file mode 100644 index 00000000..6a899d00 --- /dev/null +++ b/ACL/ACL/index.js @@ -0,0 +1,3 @@ +module.exports = { + Visitor: [[true]], +}; diff --git a/ACL/Admin.Collectives.Campaigns.Events/index.js b/ACL/Admin.Collectives.Campaigns.Events/index.js new file mode 100644 index 00000000..4ca131f4 --- /dev/null +++ b/ACL/Admin.Collectives.Campaigns.Events/index.js @@ -0,0 +1,27 @@ +/* globals User */ +module.exports = { + Visitor: [[false]], + User: [[false], ['index', true]], + CollectiveManager: [ + ['index', true], + [ + 'create', + 'delete', + req => + User.knex() + .table('CollectiveAdmins') + .where({ + collective_id: req.post.collectiveId, + user_id: req.user.id, + }) + .then(results => { + if (results.length === 0) { + return false; + } + + return true; + }), + ], + ], + Admin: [[true]], +}; diff --git a/ACL/Admin.Collectives.Campaigns.KBPosts/index.js b/ACL/Admin.Collectives.Campaigns.KBPosts/index.js new file mode 100644 index 00000000..4ca131f4 --- /dev/null +++ b/ACL/Admin.Collectives.Campaigns.KBPosts/index.js @@ -0,0 +1,27 @@ +/* globals User */ +module.exports = { + Visitor: [[false]], + User: [[false], ['index', true]], + CollectiveManager: [ + ['index', true], + [ + 'create', + 'delete', + req => + User.knex() + .table('CollectiveAdmins') + .where({ + collective_id: req.post.collectiveId, + user_id: req.user.id, + }) + .then(results => { + if (results.length === 0) { + return false; + } + + return true; + }), + ], + ], + Admin: [[true]], +}; diff --git a/ACL/Admin.Collectives.Campaigns/index.js b/ACL/Admin.Collectives.Campaigns/index.js new file mode 100644 index 00000000..acad065e --- /dev/null +++ b/ACL/Admin.Collectives.Campaigns/index.js @@ -0,0 +1,34 @@ +/* globals Admin */ +module.exports = { + Visitor: [['new', 'create', 'edit', 'update', 'activate', 'deactivate', false]], + User: [['new', 'create', 'edit', 'update', 'activate', 'deactivate', false]], + CollectiveManager: [ + ['new', 'create', true], + [ + 'edit', + 'update', + 'activate', + 'deactivate', + req => { + Admin.Campaign.query() + .where('id', req.params.id) + .then(([campaign]) => + Admin.Campaign.knex() + .table('CollectiveAdmins') + .where({ + collective_id: campaign.collectiveId, + user_id: req.user.id, + }), + ) + .then(results => { + if (results.length === 0) { + return false; + } + + return true; + }); + }, + ], + ], + Admin: [['new', 'create', 'edit', 'update', 'activate', 'deactivate', true]], +}; diff --git a/ACL/Admin.Collectives.Users/index.js b/ACL/Admin.Collectives.Users/index.js new file mode 100644 index 00000000..d89385cc --- /dev/null +++ b/ACL/Admin.Collectives.Users/index.js @@ -0,0 +1,28 @@ +/* globals User */ +module.exports = { + Visitor: [[false]], + User: [[false]], + CollectiveManager: [ + ['index', true], + [ + 'show', + 'edit', + 'update', + req => + User.knex() + .table('CollectiveAdmins') + .where({ + collective_id: req.params.id, + user_id: req.user.id, + }) + .then(results => { + if (results.length === 0) { + return false; + } + + return true; + }), + ], + ], + Admin: [[true]], +}; diff --git a/ACL/Admin.Collectives/index.js b/ACL/Admin.Collectives/index.js new file mode 100644 index 00000000..d89385cc --- /dev/null +++ b/ACL/Admin.Collectives/index.js @@ -0,0 +1,28 @@ +/* globals User */ +module.exports = { + Visitor: [[false]], + User: [[false]], + CollectiveManager: [ + ['index', true], + [ + 'show', + 'edit', + 'update', + req => + User.knex() + .table('CollectiveAdmins') + .where({ + collective_id: req.params.id, + user_id: req.user.id, + }) + .then(results => { + if (results.length === 0) { + return false; + } + + return true; + }), + ], + ], + Admin: [[true]], +}; diff --git a/ACL/Admin.Disputes/index.js b/ACL/Admin.Disputes/index.js new file mode 100644 index 00000000..55d430bd --- /dev/null +++ b/ACL/Admin.Disputes/index.js @@ -0,0 +1,5 @@ +module.exports = { + Visitor: [[false]], + User: [[false]], + Admin: [[true]], +}; diff --git a/ACL/Admin.Users/index.js b/ACL/Admin.Users/index.js new file mode 100644 index 00000000..55d430bd --- /dev/null +++ b/ACL/Admin.Users/index.js @@ -0,0 +1,5 @@ +module.exports = { + Visitor: [[false]], + User: [[false]], + Admin: [[true]], +}; diff --git a/ACL/Campaigns.Events/index.js b/ACL/Campaigns.Events/index.js new file mode 100644 index 00000000..a83eec81 --- /dev/null +++ b/ACL/Campaigns.Events/index.js @@ -0,0 +1,21 @@ +/* globals User, CollectiveAdmins */ +module.exports = { + Visitor: [[false], ['index', true]], + User: [['index', true]], + CollectiveManager: [ + ['create', 'index', true], + [ + 'edit', + 'update', + 'delete', + req => + CollectiveAdmins.query() + .where({ + collective_id: req.params.collectiveId, + user_id: req.user.id, + }) + .then(results => results.length > 0), + ], + ], + Admin: [[true]], +}; diff --git a/ACL/Campaigns/index.js b/ACL/Campaigns/index.js new file mode 100644 index 00000000..90fdddeb --- /dev/null +++ b/ACL/Campaigns/index.js @@ -0,0 +1,13 @@ +/* global Campaign */ + +const isPublicCampaign = req => + Campaign.query() + .where('id', req.params.id) + .then(([result]) => result.published); + +module.exports = { + Visitor: [['show', isPublicCampaign], ['join', false]], + User: [['show', 'join', isPublicCampaign]], + CollectiveManager: [['show', 'join', true]], + Admin: [['show', 'join', true]], +}; diff --git a/ACL/Collectives/index.js b/ACL/Collectives/index.js new file mode 100644 index 00000000..e62df47b --- /dev/null +++ b/ACL/Collectives/index.js @@ -0,0 +1,9 @@ +/* globals CollectiveBans */ +module.exports = { + Visitor: [['join', false], ['index', 'show', true]], + User: [ + ['index', true], + // Users are already blocked from the passport middleware + ['join', true], + ], +}; diff --git a/ACL/Dashboard/index.js b/ACL/Dashboard/index.js new file mode 100644 index 00000000..5ef3360f --- /dev/null +++ b/ACL/Dashboard/index.js @@ -0,0 +1,5 @@ +module.exports = { + Visitor: [['index', false]], + User: [['index', true]], + Admin: [['index', true]], +}; diff --git a/ACL/DisputeTools/index.js b/ACL/DisputeTools/index.js new file mode 100644 index 00000000..c0285b29 --- /dev/null +++ b/ACL/DisputeTools/index.js @@ -0,0 +1,5 @@ +module.exports = { + Visitor: [['index', true], ['show', false]], + User: [['index', true], ['show', true]], + Admin: [['index', true], ['show', true]], +}; diff --git a/ACL/Disputes/index.js b/ACL/Disputes/index.js new file mode 100644 index 00000000..948de9bb --- /dev/null +++ b/ACL/Disputes/index.js @@ -0,0 +1,32 @@ +module.exports = { + Visitor: [ + [false], + [ + 'show', + 'updateDisputeData', + 'updateSubmission', + 'addAttachment', + 'download', + 'setSignature', + 'removeAttachment', + false, + ], + ], + User: [ + ['index', 'create', true], + [ + 'show', + 'edit', + 'update', + 'destroy', + 'updateSubmission', + 'updateDisputeData', + 'addAttachment', + 'download', + 'setSignature', + 'removeAttachment', + req => req.dispute.userId === req.user.id, + ], + ], + Admin: [['download', true]], +}; diff --git a/ACL/Home/index.js b/ACL/Home/index.js new file mode 100644 index 00000000..6a899d00 --- /dev/null +++ b/ACL/Home/index.js @@ -0,0 +1,3 @@ +module.exports = { + Visitor: [[true]], +}; diff --git a/ACL/Posts/index.js b/ACL/Posts/index.js new file mode 100644 index 00000000..5fa60c5b --- /dev/null +++ b/ACL/Posts/index.js @@ -0,0 +1,54 @@ +/* globals User */ +module.exports = { + Visitor: [[false], ['createComment', false], ['index', true]], + User: [ + ['create', 'createComment', 'votePoll', 'index', true], + [ + 'edit', + 'update', + 'delete', + req => { + if (req.post.userId !== req.user.id) { + return false; + } + + return true; + }, + ], + ], + CollectiveManager: [ + ['create', 'index', 'createComment', true], + [ + 'edit', + 'update', + 'delete', + req => { + if (req.user.role === 'Admin') { + return true; + } + + return User.knex() + .table('Campaigns') + .select('collective_id') + .where('id', req.params.campaign_id) + .then(([result]) => + User.knex() + .table('CollectiveAdmins') + .where({ + collective_id: result.collective_id, + user_id: req.user.id, + }) + .then(results => { + if (results.length === 0) { + return false; + } + + return true; + }) + .catch(() => false), + ); + }, + ], + ], + Admin: [['createComment', true], [true]], +}; diff --git a/ACL/Sessions/index.js b/ACL/Sessions/index.js new file mode 100644 index 00000000..ad9538d3 --- /dev/null +++ b/ACL/Sessions/index.js @@ -0,0 +1,14 @@ +module.exports = { + Visitor: [ + [ + 'new', + 'create', + 'destroy', + 'showEmailForm', + 'sendResetEmail', + 'showPasswordForm', + 'resetPassword', + true, + ], + ], +}; diff --git a/ACL/Users/index.js b/ACL/Users/index.js new file mode 100644 index 00000000..1efcc36e --- /dev/null +++ b/ACL/Users/index.js @@ -0,0 +1,24 @@ +/* globals Account */ + +module.exports = { + Visitor: [[false], ['activation', 'activate', 'create', 'new', true]], + User: [ + [false], + ['activation', true], + ['edit', 'update', req => req.params.id === req.user.id], + [ + 'show', + req => { + if (req.params.id === req.user.id) { + return true; + } + + return Account.query() + .where({ user_id: req.params.id }) + .limit(1) + .then(([account]) => !account.private); + }, + ], + ], + Admin: [[true]], +}; diff --git a/ACL/index.js b/ACL/index.js new file mode 100644 index 00000000..f045feee --- /dev/null +++ b/ACL/index.js @@ -0,0 +1,3 @@ +/* Roles */ + +module.exports = ['Visitor.User.CollectiveManager.Admin']; diff --git a/config/RouteMappings.js b/config/RouteMappings.js index 7a855867..d47ffcf8 100644 --- a/config/RouteMappings.js +++ b/config/RouteMappings.js @@ -47,89 +47,19 @@ const routeMappings = RouteMappings() as: 'tool', }) - // Users and Sessions - - .get('/signup', { - to: 'Users#new', - as: 'signup', - }) - - .get('/login', { - to: 'Sessions#new', - as: 'login', - }) - - .post('/login', { - to: 'Sessions#create', - }) - - .delete('/logout', { - to: 'Sessions#destroy', - as: 'logout', - }) - - .get('/reset-password', { - to: 'Sessions#showEmailForm', - as: 'resetPassword', - }) - - .post('/reset-password', { - to: 'Sessions#sendResetEmail', - }) - - .get('/reset-password/:token', { - to: 'Sessions#showPasswordForm', - as: 'resetPasswordWithToken', - }) - - .put('/reset-password/:token', { - to: 'Sessions#resetPassword', - }) - - .get('/acl', { - to: 'ACL#index', - as: 'acl', - }) - // Admin .namespace('/Admin', mapAdmins => - mapAdmins() - .resources('/Disputes', mapAdminDisputes => - mapAdminDisputes() - .get('/:id/admins', { - to: 'Disputes#getAvailableAdmins', - as: 'getAvailableAdmins', - }) - .post('/:id/admins', { - to: 'Disputes#updateAdmins', - as: 'updateAdmins', - }), - ) - .resources('/Users', mapAdminUsers => - mapAdminUsers() - .post('/:id/ban', { - to: 'Users#ban', - as: 'ban', - }) - .delete('/:id/ban', { - to: 'Users#unban', - as: 'unban', - }), - ), - ) - - // Users - - .resources('/Users', mapUsers => - mapUsers() - .get('/activation', { - to: 'Users#activation', - as: 'activation', - }) - .get('/:token/activate', { - to: 'Users#activate', - as: 'activate', - }), + mapAdmins().resources('/Disputes', mapAdminDisputes => + mapAdminDisputes() + .get('/:id/admins', { + to: 'Disputes#getAvailableAdmins', + as: 'getAvailableAdmins', + }) + .post('/:id/admins', { + to: 'Disputes#updateAdmins', + as: 'updateAdmins', + }), + ), ) // Dispute Tools diff --git a/config/config.sample.js b/config/config.sample.js index 4e02c51a..d2f47ed5 100644 --- a/config/config.sample.js +++ b/config/config.sample.js @@ -13,14 +13,15 @@ const config = { port: process.env.PORT || 3000, disableActivation: true, - sessions: { - key: 'session', - secret: 'SECRET', - }, - siteURL: `http://localhost:${process.env.PORT || 3000}`, enableLithium: false, + sso: { + endpoint: 'http://localhost:3000/session/sso_provider', + secret: 'super secret string of something', + jwtSecret: 'another super secret', + }, + mailers: { contactEmail: 'contact@example.com', senderEmail: 'no-reply@example.com', @@ -70,21 +71,21 @@ const config = { test: { port: process.env.PORT || 3000, - sessions: { - key: process.env.SESSION_NAME || 'session', - secret: process.env.SESSION_SECRET || 'SECRET', - }, - siteURL: `http${process.env.SECURE === 'true' ? 's' : ''}://${process.env - .HOST || 'localhost'}:${process.env.PORT || 3000}`, + siteURL: `http${process.env.SECURE === 'true' ? 's' : ''}://${process.env.HOST || + 'localhost'}:${process.env.PORT || 3000}`, enableLithium: false, + sso: { + endpoint: '', + secret: 'super secret string of something', + jwtSecret: 'another super secret', + }, + // Mailer mailers: { contactEmail: process.env.CONTACT_EMAIL || 'contact@example.com', senderEmail: process.env.SENDER_EMAIL || 'no-reply@example.com', - disputesBCCAddresses: [ - process.env.DISPUTES_EMAIL || 'disputes@example.com', - ], + disputesBCCAddresses: [process.env.DISPUTES_EMAIL || 'disputes@example.com'], }, nodemailer: { diff --git a/config/middlewares.js b/config/middlewares.js index b874c3b1..e8d7da41 100644 --- a/config/middlewares.js +++ b/config/middlewares.js @@ -23,42 +23,18 @@ const middlewares = [ name: 'Method Override', path: 'middlewares/MethodOverride.js', }, - { - name: 'Redis', - path: 'middlewares/redis.js', - }, { name: 'Locals', path: 'middlewares/locals.js', }, - { - name: 'CSRF', - path: 'middlewares/CSRF.js', - }, - { - name: 'CSRF Error', - path: 'middlewares/CSRFError.js', - }, { name: 'Flash Messages', path: 'middlewares/flashMessages.js', }, - { - name: 'Passport Initialize', - path: 'middlewares/PassportInit.js', - }, - { - name: 'Passport Session', - path: 'middlewares/PassportSession.js', - }, { name: 'SafeLocals', path: 'middlewares/SafeLocals.js', }, - { - name: 'Bancheck', - path: 'middlewares/checkIfBan.js', - }, { name: 'RestifyACL', path: 'middlewares/RestifyACL.js', diff --git a/controllers/DisputeToolsController.js b/controllers/DisputeToolsController.js index c21876d0..ad4ff850 100644 --- a/controllers/DisputeToolsController.js +++ b/controllers/DisputeToolsController.js @@ -1,22 +1,26 @@ -/* globals RestfulController, DisputeTool, neonode, Class, CONFIG, Dispute */ +/* globals RestfulController, DisputeTool, Class */ const marked = require('marked'); +const authenticate = require('../services/authentication'); +const authorization = require('../services/authorization'); const DisputeToolsController = Class('DisputeToolsController').inherits(RestfulController)({ beforeActions: [ { before: [ - (req, res, next) => neonode.controllers.Home._authenticate(req, res, next), - (req, res, next) => { - DisputeTool.query() - .where({ - id: req.params.id, - }) - .then(([disputeTool]) => { + authenticate, + authorization(({ user }) => user !== undefined), + async (req, res, next) => { + try { + const disputeTool = await DisputeTool.findById(req.params.id); + if (disputeTool) { res.locals.disputeTool = disputeTool; - next(); - }) - .catch(next); + } else { + throw new Error('Not found!'); + } + } catch (e) { + next(e); + } }, ], actions: ['show'], diff --git a/controllers/DisputesController.js b/controllers/DisputesController.js index 8d1ae79d..61ce340a 100644 --- a/controllers/DisputesController.js +++ b/controllers/DisputesController.js @@ -9,12 +9,20 @@ const _ = require('lodash'); const RESTfulAPI = require(path.join(process.cwd(), 'lib', 'RESTfulAPI')); const Raven = require('raven'); -const DisputesController = Class('DisputesController').inherits( - RestfulController, -)({ +const authenticate = require('../services/authentication'); +const authorize = require('../services/authorization'); + +const DisputesController = Class('DisputesController').inherits(RestfulController)({ beforeActions: [ { - before: '_loadDispute', + before: [authenticate], + actions: ['*'], + }, + { + before: [ + '_loadDispute', + authorize((req, res) => req.user.id === res.locals.dispute.userId), + ], actions: [ 'show', 'edit', @@ -63,7 +71,7 @@ const DisputesController = Class('DisputesController').inherits( _loadDispute(req, res, next) { Dispute.query() .where({ id: req.params.id }) - .include('[user.account, statuses, attachments, disputeTool]') + .include('[user, statuses, attachments, disputeTool]') .then(([dispute]) => { if (!dispute) { next(new NotFoundError()); @@ -76,8 +84,7 @@ const DisputesController = Class('DisputesController').inherits( (a, b) => new Date(b.createdAt) - new Date(a.createdAt), ); - const optionData = - dispute.disputeTool.data.options[dispute.data.option]; + const optionData = dispute.disputeTool.data.options[dispute.data.option]; if (optionData && optionData.more) { optionData.more = marked(optionData.more); } @@ -124,9 +131,7 @@ const DisputesController = Class('DisputesController').inherits( option: req.body.option, user: req.user, }) - .then(disputeId => - res.redirect(CONFIG.router.helpers.Disputes.show.url(disputeId)), - ) + .then(disputeId => res.redirect(CONFIG.router.helpers.Disputes.show.url(disputeId))) .catch(next); }, @@ -154,9 +159,7 @@ const DisputesController = Class('DisputesController').inherits( }), ) .then(() => dispute.save()) - .then(() => - res.redirect(CONFIG.router.helpers.Disputes.show.url(dispute.id)), - ) + .then(() => res.redirect(CONFIG.router.helpers.Disputes.show.url(dispute.id))) .catch(next); }, @@ -184,9 +187,7 @@ const DisputesController = Class('DisputesController').inherits( }), ) .catch(e => { - logger.error( - ' ---> Failed to send smail to user (on #setSignature)', - ); + logger.error(' ---> Failed to send smail to user (on #setSignature)'); logger.error(e.stack); Raven.captureException(e, { req }); }), @@ -216,9 +217,7 @@ const DisputesController = Class('DisputesController').inherits( return res.format({ html() { req.flash('error', `${e.toString()} (on #${req.body.command})`); - return res.redirect( - CONFIG.router.helpers.Disputes.show.url(dispute.id), - ); + return res.redirect(CONFIG.router.helpers.Disputes.show.url(dispute.id)); }, json() { return res.json({ error: e.toString() }); @@ -231,9 +230,7 @@ const DisputesController = Class('DisputesController').inherits( .then(() => res.format({ html() { - return res.redirect( - CONFIG.router.helpers.Disputes.show.url(dispute.id), - ); + return res.redirect(CONFIG.router.helpers.Disputes.show.url(dispute.id)); }, json() { return res.json({ status: 'confirmed' }); @@ -248,14 +245,10 @@ const DisputesController = Class('DisputesController').inherits( dispute .setSignature(req.body.signature) - .then(() => - res.redirect(CONFIG.router.helpers.Disputes.show.url(dispute.id)), - ) + .then(() => res.redirect(CONFIG.router.helpers.Disputes.show.url(dispute.id))) .catch(e => { req.flash('error', `${e.toString()} (on #setSignature)`); - return res.redirect( - CONFIG.router.helpers.Disputes.show.url(dispute.id), - ); + return res.redirect(CONFIG.router.helpers.Disputes.show.url(dispute.id)); }); }, @@ -264,9 +257,7 @@ const DisputesController = Class('DisputesController').inherits( if (!req.files.attachment) { req.flash('error', 'There is no file to process'); - return res.redirect( - CONFIG.router.helpers.Disputes.show.url(dispute.id), - ); + return res.redirect(CONFIG.router.helpers.Disputes.show.url(dispute.id)); } return Promise.each(req.files.attachment, attachment => @@ -274,16 +265,11 @@ const DisputesController = Class('DisputesController').inherits( ) .then(() => dispute.save()) .catch(() => { - req.flash( - 'error', - 'A problem occurred trying to process the attachments', - ); + req.flash('error', 'A problem occurred trying to process the attachments'); }) .finally(() => { req.flash('success', 'Attachment successfully added!'); - return res.redirect( - CONFIG.router.helpers.Disputes.show.url(dispute.id), - ); + return res.redirect(CONFIG.router.helpers.Disputes.show.url(dispute.id)); }); }, @@ -292,24 +278,18 @@ const DisputesController = Class('DisputesController').inherits( if (!req.params.attachment_id) { req.flash('error', 'Missing attachment id'); - return res.redirect( - CONFIG.router.helpers.Disputes.show.url(dispute.id), - ); + return res.redirect(CONFIG.router.helpers.Disputes.show.url(dispute.id)); } return dispute .removeAttachment(req.params.attachment_id) .then(() => { req.flash('success', 'Attachment removed'); - return res.redirect( - CONFIG.router.helpers.Disputes.show.url(dispute.id), - ); + return res.redirect(CONFIG.router.helpers.Disputes.show.url(dispute.id)); }) .catch(err => { req.flash('error', err.message); - return res.redirect( - CONFIG.router.helpers.Disputes.show.url(dispute.id), - ); + return res.redirect(CONFIG.router.helpers.Disputes.show.url(dispute.id)); }); }, @@ -343,14 +323,9 @@ const DisputesController = Class('DisputesController').inherits( return true; } - const currentStatus = _.sortBy(dispute.statuses, 'updatedAt').slice( - -1, - )[0]; + const currentStatus = _.sortBy(dispute.statuses, 'updatedAt').slice(-1)[0]; - return ( - currentStatus.status !== 'Completed' || - currentStatus.updatedAt > renderer.updatedAt - ); + return currentStatus.status !== 'Completed' || currentStatus.updatedAt > renderer.updatedAt; }; DisputeRenderer.query() diff --git a/controllers/HomeController.js b/controllers/HomeController.js index 96abd967..8702701c 100644 --- a/controllers/HomeController.js +++ b/controllers/HomeController.js @@ -2,28 +2,12 @@ const stripe = require('stripe'); -const stripeClient = stripe(CONFIG.env().stripe.secret); +const { stripe: { secret: stripeSecret }, mailers: { contactEmail: CONTACT_EMAIL } } = CONFIG.env(); -const CONTACT_EMAIL = CONFIG.env().mailers.contactEmail; +const stripeClient = stripe(stripeSecret); const HomeController = Class('HomeController').inherits(BaseController)({ prototype: { - _authenticate(req, res, next) { - if (!req.user) { - return res.format({ - html() { - req.flash('info', 'You have to login first.'); - return res.redirect(CONFIG.router.helpers.login.url()); - }, - json() { - return res.status(403).end(); - }, - }); - } - - return next(); - }, - donate(req, res) { const token = req.body.token; const amount = Math.floor(Number(req.body.amount)); @@ -134,9 +118,7 @@ const HomeController = Class('HomeController').inherits(BaseController)({ }) .then(subscription => { res.status(200).json({ - success: - subscription.status === 'active' && - subscription.plan.id === planId, + success: subscription.status === 'active' && subscription.plan.id === planId, }); }) .catch(error => { @@ -149,10 +131,7 @@ const HomeController = Class('HomeController').inherits(BaseController)({ charge() .then(_charge => { res.status(200).json({ - success: - _charge.captured && - _charge.paid && - _charge.status === 'succeeded', + success: _charge.captured && _charge.paid && _charge.status === 'succeeded', }); }) .catch(error => { @@ -203,10 +182,7 @@ const HomeController = Class('HomeController').inherits(BaseController)({ ContactMailer.sendMessage(CONTACT_EMAIL, emailerOptions) .then(result => { logger.info('ContactMailer.sendMessage result', result); - req.flash( - 'success', - 'Your message has been sent, thank you for contacting us.', - ); + req.flash('success', 'Your message has been sent, thank you for contacting us.'); res.redirect(CONFIG.router.helpers.contact.url()); next(); }) diff --git a/lib/boot.js b/lib/boot.js index e859f21c..42e6c257 100644 --- a/lib/boot.js +++ b/lib/boot.js @@ -3,17 +3,14 @@ const path = require('path'); const uuid = require('uuid'); const Knex = require('knex'); -const AWS = require('aws-sdk'); const nodemailer = require('nodemailer'); -require('scandium-express'); +// require('scandium-express'); -require(path.join(process.cwd(), 'lib/ACL/acl_support.js')); +// require(path.join(process.cwd(), 'lib/ACL/acl_support.js')); require(path.join(process.cwd(), 'lib', 'BaseMailer')); global.NotFoundError = require('./errors/NotFoundError'); -global.AWS = AWS; - // Load LithiumEngine if (CONFIG.env().enableLithium) { require(path.join(process.cwd(), 'lib', 'LithiumEngine.js')); @@ -46,12 +43,6 @@ Krypton.Model.prototype.init = function init(config) { return this; }; -/* AWS Setup */ -// AWS.config.update({ -// region: 'us-east-1', -// accessKeyId: process.env.AWS_KEY || CONFIG.env().AWS.accessKeyId, -// secretAccessKey: process.env.AWS_SECRET || CONFIG.env().AWS.secretAccessKey, -// }); /* BaseMailer */ const transport = nodemailer.createTransport(CONFIG.env().nodemailer); diff --git a/lib/core/Neonode.js b/lib/core/Neonode.js index d6e790ba..2f349587 100644 --- a/lib/core/Neonode.js +++ b/lib/core/Neonode.js @@ -44,6 +44,8 @@ const Neonode = Class({}, 'Neonode')({ }), ); + this.app.use(require('cookie-parser')()); + // ************************************************************************* // Setup Pug engine for Express // ************************************************************************* @@ -109,9 +111,7 @@ const Neonode = Class({}, 'Neonode')({ // External Middlewares // ************************************************************************* CONFIG.middlewares.forEach(middleware => { - logger.info( - `Loading ${middleware.name} middleware: ${middleware.path} ...`, - ); + logger.info(`Loading ${middleware.name} middleware: ${middleware.path} ...`); const middlewareFile = require(path.join(cwd, middleware.path)); diff --git a/lib/passport/local_strategy.js b/lib/passport/local_strategy.js deleted file mode 100644 index 1cc51da9..00000000 --- a/lib/passport/local_strategy.js +++ /dev/null @@ -1,74 +0,0 @@ -/* globals User, CONFIG */ - -const passport = require('passport'); -const bcrypt = require('bcrypt-node'); -const LocalStrategy = require('passport-local').Strategy; - -passport.serializeUser((user, done) => { - done(null, user.id); -}); - -passport.deserializeUser((req, id, done) => { - User.query() - .include('[account, debtTypes]') - .where('id', id) - .then(result => { - if (result.length !== 1) { - return done( - new Error("Passport: Can't deserialize user, invalid user.id"), - ); - } - - // shall not pass - if (result[0].banned) { - req.ban = true; - return done(null, false); - } - - return done(null, result[0]); - }); -}); - -passport.use( - 'local', - new LocalStrategy( - { - usernameField: 'email', - passwordField: 'password', - passReqToCallback: true, - }, - (request, email, password, done) => { - User.query() - .where('email', email) - .then(result => { - if (result.length === 0) { - return done(new Error('User not found')); - } - - const user = result[0]; - - if (!CONFIG.env().disableActivation && user.activationToken) { - return done(new Error("User hasn't been activated")); - } - - return bcrypt.compare( - password, - user.encryptedPassword, - (err, valid) => { - if (err) { - return done(err); - } - - if (!valid) { - return done(new Error('Wrong password')); - } - - return done(null, user); - }, - ); - }); - }, - ), -); - -module.exports = passport; diff --git a/mailers/UserMailer.js b/mailers/UserMailer.js new file mode 100644 index 00000000..f6650e18 --- /dev/null +++ b/mailers/UserMailer.js @@ -0,0 +1,32 @@ +/* globals Class, BaseMailer, CONFIG */ + +const UserMailer = Class('UserMailer').inherits(BaseMailer)({ + _options: { + from: `The Debt Collective <${CONFIG.env().mailers.senderEmail}>`, + subject: 'The Debt Collective', + }, + + sendActivation(...args) { + return this._send('sendActivation', ...args); + }, + + sendResetPasswordLink(...args) { + return this._send('sendResetPasswordLink', ...args); + }, + + sendDispute(...args) { + return this._send('sendDispute', ...args); + }, + + sendSubscription(...args) { + return this._send('sendSubscription', ...args); + }, + + sendDisputeToAdmin(locals) { + const mails = CONFIG.env().mailers.disputesBCCAddresses; + + return this._send('sendDisputeToAdmin', mails, locals); + }, +}); + +module.exports = UserMailer; diff --git a/middlewares/PassportInit.js b/middlewares/PassportInit.js deleted file mode 100644 index 6f5f2993..00000000 --- a/middlewares/PassportInit.js +++ /dev/null @@ -1,3 +0,0 @@ -const passport = require('passport'); - -module.exports = passport.initialize(); diff --git a/middlewares/PassportSession.js b/middlewares/PassportSession.js deleted file mode 100644 index 175e9011..00000000 --- a/middlewares/PassportSession.js +++ /dev/null @@ -1,3 +0,0 @@ -const passport = require('passport'); - -module.exports = passport.session(); diff --git a/middlewares/Router.js b/middlewares/Router.js index 7d8a12f7..165a6d7c 100644 --- a/middlewares/Router.js +++ b/middlewares/Router.js @@ -1,4 +1,4 @@ -/* globals logger, CONFIG, neonode, ACL */ +/* globals logger, CONFIG, neonode */ const _ = require('lodash'); const Table = require('cli-table'); @@ -22,8 +22,7 @@ routeMapper.routes.forEach(route => { const verbs = [route.verb]; const action = route._actionName || _handler[_handler.length - 1]; - let controller = - route._resourceName || _handler.slice(0, _handler.length - 1).join('.'); + let controller = route._resourceName || _handler.slice(0, _handler.length - 1).join('.'); if (_handler.indexOf(controller) > -1) { // action @@ -41,22 +40,15 @@ routeMapper.routes.forEach(route => { const controllerObject = neonode.controllers[controller]; const controllerMethod = controllerObject && controllerObject[action]; - const beforeActions = - (controllerObject && controllerObject.constructor.beforeActions) || []; + const beforeActions = (controllerObject && controllerObject.constructor.beforeActions) || []; if (!controllerObject) { - throw new Error( - `Controller '${controller}' is missing. Handler: ${route.handler.join( - '.', - )}`, - ); + throw new Error(`Controller '${controller}' is missing. Handler: ${route.handler.join('.')}`); } if (!controllerMethod) { throw new Error( - `Action '${action}' for '${controller}' is missing. Handler: ${route.handler.join( - '.', - )}`, + `Action '${action}' for '${controller}' is missing. Handler: ${route.handler.join('.')}`, ); } @@ -68,7 +60,7 @@ routeMapper.routes.forEach(route => { const filters = _.flatten( beforeActions .filter(item => { - if (item.actions.indexOf(action) !== -1) { + if (item.actions.includes(action) || item.actions.includes('*')) { return true; } @@ -83,9 +75,7 @@ routeMapper.routes.forEach(route => { if (neonode.controllers[controller][filter]) { args.push(neonode.controllers[controller][filter]); } else { - throw new Error( - `BeforeActions Error: Unknown method ${filter} in ${controller}`, - ); + throw new Error(`BeforeActions Error: Unknown method ${filter} in ${controller}`); } } else if (_.isFunction(filter)) { // if is a function just add it to the middleware stack @@ -96,15 +86,6 @@ routeMapper.routes.forEach(route => { }); } - // append built middleware for this resource - if ( - ACL && - ACL.middlewares[controller] && - ACL.middlewares[controller][action] - ) { - args.push(ACL.middlewares[controller][action]); - } - args.push(controllerMethod); router.route(route.path)[verb](args); diff --git a/middlewares/SafeLocals.js b/middlewares/SafeLocals.js index c42e2e4f..7ea20c6a 100644 --- a/middlewares/SafeLocals.js +++ b/middlewares/SafeLocals.js @@ -1,16 +1,6 @@ /* globals CONFIG, Collective */ module.exports = function locals(req, res, next) { - if (CONFIG.env().sessions !== false) { - const token = req.csrfToken && req.csrfToken(); - - res.locals.csrfToken = token; - - if (CONFIG.environment === 'test') { - res.cookie('XSRF-TOKEN', token); - } - } - res.locals.currentUser = req.user || null; req.role = (req.user && req.user.role) || 'Visitor'; diff --git a/middlewares/accessControl.js b/middlewares/accessControl.js new file mode 100644 index 00000000..9d9bb372 --- /dev/null +++ b/middlewares/accessControl.js @@ -0,0 +1,3 @@ +module.exports = (req, res, next) => { + next(); +}; diff --git a/middlewares/flashMessages.js b/middlewares/flashMessages.js index 0eea0ec9..f0b1626b 100644 --- a/middlewares/flashMessages.js +++ b/middlewares/flashMessages.js @@ -1,7 +1,8 @@ -const { sessions } = require('../config/config').env(); +module.exports = (req, res, next) => { + req.flash = (key, value) => { + if (!res.locals.flash) res.locals.flash = {}; -if (sessions) { - module.exports = require('req-flash')({ locals: 'flash' }); -} else { - module.exports = (req, res, next) => next(); -} + res.locals.flash[key] = value; + }; + next(); +}; diff --git a/middlewares/redis.js b/middlewares/redis.js deleted file mode 100644 index f11a318d..00000000 --- a/middlewares/redis.js +++ /dev/null @@ -1,32 +0,0 @@ -/* globals CONFIG */ -if (CONFIG.env().sessions !== false) { - const redis = require('redis'); - - const redisClient = redis.createClient({ - host: process.env.REDIS_HOST || 'localhost', - }); - - const session = require('express-session'); - - const RedisStore = require('connect-redis')(session); - - const redisStoreInstance = new RedisStore({ - client: redisClient, - }); - - const sessionMiddleWare = session({ - resave: false, - saveUninitialized: true, - key: CONFIG.env().sessions.key, - // FIXME: this is not working as expected behind nginx-proxy - // cookie: {secure: CONFIG.environment === 'production'}, - store: redisStoreInstance, - secret: CONFIG.env().sessions.secret, - }); - - module.exports = sessionMiddleWare; -} else { - module.exports = (req, res, next) => { - next(); - }; -} diff --git a/migrations/20160808180039_create_users.js b/migrations/20160808180039_create_users.js index 1b7dd8d2..c4ea90ae 100644 --- a/migrations/20160808180039_create_users.js +++ b/migrations/20160808180039_create_users.js @@ -2,12 +2,9 @@ exports.up = knex => knex.schema.createTable('Users', t => { t.uuid('id').primary(); t - .string('email', 255) + .bigInteger('external_id') .unique() .notNullable(); - t.string('encrypted_password', 512).notNullable(); - t.string('activation_token', 512).defaultTo(null); - t.string('role', 128).notNullable(); t.timestamps(); }); diff --git a/migrations/20160816161457_add_resetPasswordToken_to_users.js b/migrations/20160816161457_add_resetPasswordToken_to_users.js deleted file mode 100644 index 34a00298..00000000 --- a/migrations/20160816161457_add_resetPasswordToken_to_users.js +++ /dev/null @@ -1,9 +0,0 @@ -exports.up = knex => - knex.schema.table('Users', t => { - t.string('reset_password_token', 512).defaultTo(null); - }); - -exports.down = knex => - knex.schema.table('Users', t => { - t.dropColumn('reset_password_token'); - }); diff --git a/migrations/20160829130040_create_accounts.js b/migrations/20160829130040_create_accounts.js deleted file mode 100644 index bc6c823f..00000000 --- a/migrations/20160829130040_create_accounts.js +++ /dev/null @@ -1,21 +0,0 @@ -exports.up = knex => - knex.schema.createTable('Accounts', t => { - t.uuid('id').primary(); - t - .uuid('user_id') - .notNullable() - .references('id') - .inTable('Users') - .onDelete('CASCADE'); - t.string('fullname', 512).notNullable(); - t.text('bio'); - t.string('state', 32); - t.string('zip', 16); - t.string('phone', 32); - t.jsonb('social_links').defaultTo('{}'); - t.string('image_path', 512); - t.jsonb('image_meta').defaultTo('{}'); - t.timestamps(); - }); - -exports.down = knex => knex.schema.dropTable('Accounts'); diff --git a/migrations/20171104185000_add_account_privacy_settings.js b/migrations/20171104185000_add_account_privacy_settings.js deleted file mode 100644 index 5c273734..00000000 --- a/migrations/20171104185000_add_account_privacy_settings.js +++ /dev/null @@ -1,11 +0,0 @@ -exports.up = knex => - knex.schema.table('Accounts', a => { - a.boolean('private').defaultTo(false); - a.boolean('disputes_private').defaultTo(false); - }); - -exports.down = knex => - knex.schema.table('Accounts', a => { - a.dropColumn('private'); - a.dropColumn('disputes_private'); - }); diff --git a/migrations/20171222212754_add_location_fields_to_accounts.js b/migrations/20171222212754_add_location_fields_to_accounts.js deleted file mode 100644 index 249a5843..00000000 --- a/migrations/20171222212754_add_location_fields_to_accounts.js +++ /dev/null @@ -1,13 +0,0 @@ -exports.up = knex => - knex.schema.table('Accounts', t => { - t.text('city'); - t.float('latitude'); - t.float('longitude'); - }); - -exports.down = knex => - knex.schema.table('Accounts', t => { - t.dropColumn('city'); - t.dropColumn('latitude'); - t.dropColumn('longitude'); - }); diff --git a/model-relations/Account.js b/model-relations/Account.js deleted file mode 100644 index 19224267..00000000 --- a/model-relations/Account.js +++ /dev/null @@ -1,10 +0,0 @@ -/* globals Account, User */ - -Account.relations = { - user: { - type: 'HasOne', - relatedModel: User, - ownerCol: 'user_id', - relatedCol: 'id', - }, -}; diff --git a/model-relations/User.js b/model-relations/User.js index c318fd22..c7b72a51 100644 --- a/model-relations/User.js +++ b/model-relations/User.js @@ -1,13 +1,6 @@ -/* globals Account, User, Dispute */ +/* globals User, Dispute */ User.relations = { - account: { - type: 'HasOne', - relatedModel: Account, - ownerCol: 'id', - relatedCol: 'user_id', - }, - disputes: { type: 'HasMany', relatedModel: Dispute, diff --git a/models/Account.js b/models/Account.js deleted file mode 100644 index 5cb850f8..00000000 --- a/models/Account.js +++ /dev/null @@ -1,157 +0,0 @@ -/* global Krypton, Class, CONFIG, AttachmentsProcessor, AWS, S3Uploader */ -const path = require('path'); -const stream = require('stream'); -const gm = require('gm').subClass({ - imageMagick: process.env.GM === 'true' || false, -}); -const { assignDefaultConfig, buildFullPaths } = require('../lib/AWS'); -const { US_STATES } = require('../lib/data'); - -const Account = Class('Account') - .inherits(Krypton.Model) - .includes(Krypton.Attachment)({ - tableName: 'Accounts', - states: US_STATES, - validations: { - userId: ['required'], - fullname: ['required'], - state: [ - 'required', - { - rule(val) { - if (Account.states.indexOf(val) === -1) { - throw new Error("The Account's state is invalid."); - } - }, - message: "The Account's state is invalid.", - }, - ], - zip: [ - 'required', - { - rule(val) { - if (/(^\d{5}$)|(^\d{5}-\d{4}$)/.test(val) === false) { - throw new Error("The Account's zip code is invalid."); - } - }, - message: "The Account's zip code is invalid.", - }, - ], - phone: ['maxLength:20'], - }, - attributes: [ - 'id', - 'userId', - 'fullname', - 'bio', - 'city', - 'state', - 'zip', - 'latitude', - 'longitude', - 'phone', - 'socialLinks', - 'imagePath', - 'imageMeta', - 'private', - 'disputesPrivate', - 'createdAt', - 'updatedAt', - ], - attachmentStorage: new Krypton.AttachmentStorage.S3( - assignDefaultConfig({ - maxFileSize: 5242880, - acceptedMimeTypes: [/image/], - }), - ), - - prototype: { - bio: '', - imageMeta: {}, - socialLinks: {}, - - init(config) { - Krypton.Model.prototype.init.call(this, config); - - this.hasAttachment({ - name: 'image', - versions: { - small(readStream) { - return gm(readStream) - .resize(64, null) - .gravity('Center') - .crop(64, 64, 0, 0) - .setFormat('jpg') - .stream(); - }, - smallGrayscale(readStream) { - return gm(readStream) - .resize(64, null) - .type('Grayscale') - .gravity('Center') - .crop(64, 64, 0, 0) - .setFormat('jpg') - .stream(); - }, - smallRedSquare(readStream) { - const passThrough = new stream.PassThrough(); - - gm(readStream) - .resize(64, null) - .type('Grayscale') - .crop(64, 64, 0, 0) - .stream() - .pipe(passThrough); - - return gm(passThrough) - .gravity('Center') - .composite( - path.join(process.cwd(), 'lib/assets', '64_red-square.png'), - ) - .setFormat('jpg') - .stream(); - }, - medium(readStream) { - return gm(readStream) - .resize(256, null) - .gravity('Center') - .crop(256, 256, 0, 0) - .setFormat('jpg') - .stream(); - }, - mediumGrayscale(readStream) { - return gm(readStream) - .resize(256, null) - .type('Grayscale') - .gravity('Center') - .crop(256, 256, 0, 0) - .setFormat('jpg') - .stream(); - }, - mediumRedSquare(readStream) { - const passThrough = new stream.PassThrough(); - - gm(readStream) - .resize(256, null) - .type('Grayscale') - .crop(256, 256, 0, 0) - .stream() - .pipe(passThrough); - - return gm(passThrough) - .gravity('Center') - .composite( - path.join(process.cwd(), 'lib/assets', '256_red-square.png'), - ) - .setFormat('jpg') - .stream(); - }, - }, - }); - - this.image.urls = buildFullPaths(this.image); - }, - }, -}); - -module.exports = Account; diff --git a/models/Attachment.js b/models/Attachment.js index f5a0b9c0..d68a394e 100644 --- a/models/Attachment.js +++ b/models/Attachment.js @@ -1,5 +1,4 @@ /* global Krypton, Class, CONFIG, AWS, S3Uploader */ -// const gm = require('gm').subClass({ imageMagick: process.env.GM === 'true' || false }); const { assignDefaultConfig } = require('../lib/AWS'); @@ -11,15 +10,7 @@ const Attachment = Class('Attachment') foreignKey: ['required'], type: ['required'], }, - attributes: [ - 'id', - 'type', - 'foreignKey', - 'filePath', - 'fileMeta', - 'createdAt', - 'updatedAt', - ], + attributes: ['id', 'type', 'foreignKey', 'filePath', 'fileMeta', 'createdAt', 'updatedAt'], attachmentStorage: new Krypton.AttachmentStorage.S3( assignDefaultConfig({ acceptedMimeTypes: [/image/, /application/], diff --git a/models/DisputeTool.js b/models/DisputeTool.js index 17007049..774b3826 100644 --- a/models/DisputeTool.js +++ b/models/DisputeTool.js @@ -4,26 +4,21 @@ const path = require('path'); const DisputeTool = Class('DisputeTool').inherits(Krypton.Model)({ tableName: 'DisputeTools', - attributes: [ - 'id', - 'name', - 'about', - 'excerpt', - 'completed', - 'createdAt', - 'updatedAt', - ], + attributes: ['id', 'name', 'about', 'excerpt', 'completed', 'createdAt', 'updatedAt'], + + async findById(id) { + const [dt] = await DisputeTool.query() + .where({ id }) + .limit(1); + + return dt; + }, + prototype: { init(config) { Krypton.Model.prototype.init.call(this, config); - const dataFile = path.join( - process.cwd(), - 'lib', - 'data', - 'dispute-tools', - `${this.id}.js`, - ); + const dataFile = path.join(process.cwd(), 'lib', 'data', 'dispute-tools', `${this.id}.js`); delete require.cache[require.resolve(dataFile)]; diff --git a/models/User.js b/models/User.js index 0ad81791..17308eab 100644 --- a/models/User.js +++ b/models/User.js @@ -1,174 +1,19 @@ -/* global Krypton, Class, CONFIG, UserMailer */ - -const bcrypt = require('bcrypt-node'); -const uuid = require('uuid'); +/* global Krypton, Class */ const User = Class('User').inherits(Krypton.Model)({ tableName: 'Users', - validations: { - email: [ - 'required', - 'email', - 'maxLength:255', - { - rule(val) { - const target = this.target; - - const query = User.query().where({ - email: val, - }); - - if (target.id) { - query.andWhere('id', '!=', target.id); - } - - return query.then(result => { - if (result.length > 0) { - throw new Error("The User's email already exists."); - } - }); - }, - message: "The User's email already exists.", - }, - ], - password: ['minLength:8'], - role: [ - 'required', - { - rule(val) { - if (User.roles.indexOf(val) === -1) { - throw new Error("The User's role is invalid."); - } - }, - message: "The User's role is invalid.", - }, - ], - }, - - attributes: [ - 'id', - 'email', - 'encryptedPassword', - 'activationToken', - 'resetPasswordToken', - 'role', - 'banned', - 'createdAt', - 'updatedAt', - ], - - roles: ['Admin', 'User'], - - search(qs) { - const query = this.knex() - .select('Users.*') - .from('Users') - .join('Accounts', 'Users.id', 'Accounts.user_id'); - - if (qs.search && qs.search !== '') { - query - .where('Users.email', 'ilike', `%${qs.search}%`) - .orWhere('Accounts.fullname', 'ilike', `%${qs.search}%`) - .orWhere('Accounts.zip', 'ilike', `%${qs.search}%`); - } + validations: {}, - if (qs.state && qs.state !== '') { - query.andWhere('Accounts.state', '=', qs.state); - } - - return query.then(results => results.map(item => item.id)); - }, + attributes: ['id', 'externalId', 'createdAt', 'updatedAt'], prototype: { - email: null, - password: null, - _oldEmail: null, - init(config) { Krypton.Model.prototype.init.call(this, config); - - const model = this; - - // Start Model Hooks: - - this._oldEmail = model.email; - - model.on('beforeValidation', done => { - if (!model.id && (!model.password || model.password.length === 0)) { - return done(new Error('Must provide a password')); - } - - return done(); - }); - - // If password is present hash password and set it as encryptedPassword - model.on('beforeSave', done => { - if (!model.password) { - return done(); - } - - return bcrypt.hash(model.password, bcrypt.genSaltSync(10), null, (err, hash) => { - if (err) { - return done(err); - } - - model.encryptedPassword = hash; - - model.password = null; - return done(); - }); - }); - - // Updates old email when record saves - model.on('afterSave', done => { - this._oldEmail = model.email; - done(); - }); - - // setActivationToken helper function - const setActivationToken = done => { - model.activationToken = uuid.v4(); - return done(); - }; - - // Create a hash and set it as activationToken - model.on('beforeCreate', done => { - setActivationToken(done); - }); - - // If email changes, set activationToken again - model.on('beforeUpdate', done => { - if (model._oldEmail === model.email) { - return done(); - } - - model._oldEmail = model.email; - - return setActivationToken(done); - }); - }, - - activate() { - this.activationToken = null; - - return this; }, - - sendActivation() { - const model = this; - - return UserMailer.sendActivation(this.email, { - user: model, - _options: { - subject: 'Activate your account - Welcome to The Debt Collective!', - }, - }); + setInfo(info) { + Object.assign(this, info); }, }, - - destroy() { - super.destroy(); - }, }); module.exports = User; diff --git a/services/authentication.js b/services/authentication.js new file mode 100644 index 00000000..8242bb7f --- /dev/null +++ b/services/authentication.js @@ -0,0 +1,25 @@ +const sso = require('./sso'); + +module.exports = async (req, res, next) => { + if (!req.user) { + if (req.cookies['dispute-tool']) { + return sso.extractCookie(req, res, next); + } + + if (req.query.sso && req.query.sig) { + const payload = sso.extractPayload(req.query); + req.user = await sso.handlePayload(payload); + + return sso.createCookie(req, res, next); + } + + return res.format({ + html() { + return res.redirect(sso.buildRedirect(req)); + }, + json() { + return res.status(403).end(); + }, + }); + } +}; diff --git a/services/authorization.js b/services/authorization.js new file mode 100644 index 00000000..625f5afd --- /dev/null +++ b/services/authorization.js @@ -0,0 +1,12 @@ +/** + * Creates an authorization test + * @param {(user: User, req: Request) => boolean} test + * Should return true if authorized, false otherwise + */ +module.exports = test => (req, res, next) => { + if (test(req, res)) { + next(); + } else { + throw new Error('Authorization failed!'); + } +}; diff --git a/services/sso.js b/services/sso.js new file mode 100644 index 00000000..a095a026 --- /dev/null +++ b/services/sso.js @@ -0,0 +1,133 @@ +/* globals CONFIG, User */ + +const moment = require('moment'); +const crypto = require('crypto'); + +const { siteURL, sso: { endpoint, secret, jwtSecret } } = CONFIG.env(); +const nonces = {}; + +// 1hr +// const nonceExpiration = 3600000; + +const validNonce = nonce => nonces[nonce] !== undefined; + +const generateNonce = () => ({ + n: crypto.randomBytes(10).readUInt32LE(), + t: Date.now(), +}); + +const generateSignature = urlEncoded => + crypto + .createHmac('sha256', secret) + .update(urlEncoded) + .digest('hex'); + +const cleanPayload = payload => ({ + groups: payload.groups, + email: payload.email, + username: payload.username, + admin: payload.admin === 'true', + moderator: payload.moderator === 'false', +}); + +const generateToken = url => { + const { n, t } = generateNonce(); + nonces[n] = t; + const payload = `nonce=${n}&return_sso_url=${url}`; + const b64payload = Buffer.from(payload).toString('base64'); + const urlEncoded = encodeURIComponent(b64payload); + return `sso=${urlEncoded}&sig=${generateSignature(b64payload)}`; +}; + +const cleanUser = user => ({ + id: user.id, + groups: user.groups, + email: user.email, + username: user.username, + admin: user.admin, + moderator: user.moderator, + externalId: user.externalId, +}); + +module.exports = { + buildRedirect(req) { + return `${endpoint}?${generateToken(`${siteURL}${req.originalUrl}`)}`; + }, + + extractPayload({ sso, sig }) { + if (generateSignature(decodeURIComponent(sso)) === sig) { + return Buffer.from(sso, 'base64') + .toString('utf8') + .split('&') + .map(q => q.split('=')) + .reduce((acc, [key, value]) => { + value = decodeURIComponent(value); + if (acc[key] !== undefined) { + if (Array.isArray(acc[key])) { + acc[key].push(value); + } else { + acc[key] = [acc[key], value]; + } + } else { + acc[key] = value; + } + return acc; + }, {}); + } + + throw new Error('Authentication failed!'); + }, + + async handlePayload(payload) { + if (validNonce(payload.nonce)) { + let [user] = await User.query() + .where('external_id', payload.external_id) + .limit(1); + + if (!user) { + user = new User({ + externalId: payload.external_id, + }); + await user.save(); + } + + user.setInfo(cleanPayload(payload)); + + return user; + } + }, + + createCookie(req, res, next) { + // create a JWT cookie for the user so we don't have to authenticate with discourse every time + const json = JSON.stringify( + Object.assign( + { + sig: generateSignature(jwtSecret), + }, + cleanUser(req.user), + ), + ); + + const b64jwt = Buffer.from(json).toString('base64'); + + res.cookie('dispute-tool', b64jwt, { + maxAge: moment() + .add(1, 'd') + .valueOf(), + }); + + next(); + }, + + extractCookie(req, res, next) { + const cookie = req.cookies['dispute-tool']; + const jwt = Buffer.from(cookie, 'base64').toString('utf8'); + const claim = JSON.parse(jwt); + if (claim.sig === generateSignature(jwtSecret)) { + req.user = cleanUser(claim); + next(); + } else { + next(new Error('Authentication failed!')); + } + }, +}; diff --git a/src/javascripts/_entries/admin/users/edit.js b/src/javascripts/_entries/admin/users/edit.js deleted file mode 100644 index bb9f074d..00000000 --- a/src/javascripts/_entries/admin/users/edit.js +++ /dev/null @@ -1,27 +0,0 @@ -import NodeSupport from '../../../lib/widget/NodeSupport'; -import Common from '../../../components/Common'; -import EditForm from '../../../components/admin/users/edit/AdminUsersEditForm'; - -class ViewAdminUsersEdit extends NodeSupport { - constructor(config) { - super(); - - this.appendChild( - new Common({ - name: 'Common', - currentUser: config.currentUser, - currentURL: config.currentURL, - isAdmin: true, - }), - ); - - this.appendChild( - new EditForm({ - name: 'EditForm', - element: document.querySelector('[data-component-editform]'), - }), - ); - } -} - -window.ViewAdminUsersEdit = ViewAdminUsersEdit; diff --git a/src/javascripts/_entries/admin/users/index.js b/src/javascripts/_entries/admin/users/index.js deleted file mode 100644 index 5c677c44..00000000 --- a/src/javascripts/_entries/admin/users/index.js +++ /dev/null @@ -1,26 +0,0 @@ -import NodeSupport from '../../../lib/widget/NodeSupport'; -import Common from '../../../components/Common'; -import Controller from '../../../components/admin/users/index/AdminUsersIndexController'; - -class ViewAdminUsersIndex extends NodeSupport { - constructor(config) { - super(); - - this.appendChild( - new Common({ - name: 'Common', - currentUser: config.currentUser, - currentURL: config.currentURL, - isAdmin: true, - }), - ); - - this.appendChild( - new Controller({ - name: 'AdminUsersIndexController', - }), - ); - } -} - -window.ViewAdminUsersIndex = ViewAdminUsersIndex; diff --git a/src/javascripts/_entries/sessions/changePassword.js b/src/javascripts/_entries/sessions/changePassword.js deleted file mode 100644 index 8a03d1aa..00000000 --- a/src/javascripts/_entries/sessions/changePassword.js +++ /dev/null @@ -1,28 +0,0 @@ -import NodeSupport from '../../lib/widget/NodeSupport'; -import Common from '../../components/Common'; -import SessionsChangePasswordForm from '../../components/sessions/ChangePasswordForm'; - -class ViewChangePassword extends NodeSupport { - constructor(config) { - super(); - - this.appendChild( - new Common({ - name: 'Common', - currentUser: config.currentUser, - currentURL: config.currentURL, - }), - ); - - this.appendChild( - new SessionsChangePasswordForm({ - name: 'SessionsChangePasswordForm', - element: document.querySelector( - '[data-component-sessions-change-password]', - ), - }), - ); - } -} - -window.ViewChangePassword = ViewChangePassword; diff --git a/src/javascripts/_entries/sessions/new.js b/src/javascripts/_entries/sessions/new.js deleted file mode 100644 index aeac0a59..00000000 --- a/src/javascripts/_entries/sessions/new.js +++ /dev/null @@ -1,26 +0,0 @@ -import NodeSupport from '../../lib/widget/NodeSupport'; -import Common from '../../components/Common'; -import SessionsNewForm from '../../components/sessions/NewForm'; - -class ViewSessionsNew extends NodeSupport { - constructor(config) { - super(); - - this.appendChild( - new Common({ - name: 'Common', - currentUser: config.currentUser, - currentURL: config.currentURL, - }), - ); - - this.appendChild( - new SessionsNewForm({ - name: 'SessionsNewForm', - element: document.querySelector('[data-component-sessionsnewform]'), - }), - ); - } -} - -window.ViewSessionsNew = ViewSessionsNew; diff --git a/src/javascripts/_entries/sessions/passwordRecover.js b/src/javascripts/_entries/sessions/passwordRecover.js deleted file mode 100644 index 65681c0f..00000000 --- a/src/javascripts/_entries/sessions/passwordRecover.js +++ /dev/null @@ -1,28 +0,0 @@ -import NodeSupport from '../../lib/widget/NodeSupport'; -import Common from '../../components/Common'; -import SessionsPasswordRecoverForm from '../../components/sessions/PasswordRecoverForm'; - -class ViewPasswordRecover extends NodeSupport { - constructor(config) { - super(); - - this.appendChild( - new Common({ - name: 'Common', - currentUser: config.currentUser, - currentURL: config.currentURL, - }), - ); - - this.appendChild( - new SessionsPasswordRecoverForm({ - name: 'SessionsPasswordRecoverForm', - element: document.querySelector( - '[data-component-sessions-password-recover]', - ), - }), - ); - } -} - -window.ViewPasswordRecover = ViewPasswordRecover; diff --git a/src/javascripts/_entries/users/edit.js b/src/javascripts/_entries/users/edit.js deleted file mode 100644 index 95a3c2cd..00000000 --- a/src/javascripts/_entries/users/edit.js +++ /dev/null @@ -1,26 +0,0 @@ -import NodeSupport from '../../lib/widget/NodeSupport'; -import Common from '../../components/Common'; -import UsersEditForm from '../../components/users/EditForm'; - -class ViewUsersEdit extends NodeSupport { - constructor(config) { - super(); - - this.appendChild( - new Common({ - name: 'Common', - currentUser: config.currentUser, - currentURL: config.currentURL, - }), - ); - - this.appendChild( - new UsersEditForm({ - name: 'UsersEditForm', - element: document.querySelector('[data-component-usereditform]'), - }), - ); - } -} - -window.ViewUsersEdit = ViewUsersEdit; diff --git a/src/javascripts/_entries/users/new.js b/src/javascripts/_entries/users/new.js deleted file mode 100644 index 2ee4a6be..00000000 --- a/src/javascripts/_entries/users/new.js +++ /dev/null @@ -1,26 +0,0 @@ -import NodeSupport from '../../lib/widget/NodeSupport'; -import Common from '../../components/Common'; -import UsersNewForm from '../../components/users/NewForm'; - -class ViewUsersNew extends NodeSupport { - constructor(config) { - super(); - - this.appendChild( - new Common({ - name: 'Common', - currentUser: config.currentUser, - currentURL: config.currentURL, - }), - ); - - this.appendChild( - new UsersNewForm({ - name: 'UsersNewForm', - element: document.querySelector('[data-component-usernewform]'), - }), - ); - } -} - -window.ViewUsersNew = ViewUsersNew; diff --git a/src/javascripts/_entries/users/show.js b/src/javascripts/_entries/users/show.js deleted file mode 100644 index 58a757aa..00000000 --- a/src/javascripts/_entries/users/show.js +++ /dev/null @@ -1,18 +0,0 @@ -import NodeSupport from '../../lib/widget/NodeSupport'; -import Common from '../../components/Common'; - -class ViewUsersShow extends NodeSupport { - constructor(config) { - super(); - - this.appendChild( - new Common({ - name: 'Common', - currentUser: config.currentUser, - currentURL: config.currentURL, - }), - ); - } -} - -window.ViewUsersShow = ViewUsersShow; diff --git a/src/javascripts/admin.js b/src/javascripts/admin.js index dcd28e77..58832e79 100644 --- a/src/javascripts/admin.js +++ b/src/javascripts/admin.js @@ -1,7 +1,5 @@ require('./shared'); require('./_entries/admin/disputes'); -require('./_entries/admin/users/edit'); -require('./_entries/admin/users'); require('../stylesheets/admin.css'); diff --git a/src/javascripts/components/Header.js b/src/javascripts/components/Header.js index 1f381929..e77a3d89 100644 --- a/src/javascripts/components/Header.js +++ b/src/javascripts/components/Header.js @@ -1,18 +1,13 @@ import Widget from '../lib/widget'; import ResponsiveMenu from './ResponsiveMenu'; -import Modal from './Modal'; import Dropdown from './Dropdown'; -import UsersNewForm from './users/NewForm'; -import SessionsNewForm from './sessions/NewForm'; export default class Header extends Widget { constructor(config) { super(config); this.bg = this.element.querySelector('.Header__bg'); - this.hamburgerMenuElement = this.element.querySelector( - '.js-hamburger-menu', - ); + this.hamburgerMenuElement = this.element.querySelector('.js-hamburger-menu'); this.appendChild( new ResponsiveMenu({ @@ -35,13 +30,8 @@ export default class Header extends Widget { } _bindEvents() { - this._handleHamburgerMenuClickRef = this._handleHamburgerMenuClick.bind( - this, - ); - this.hamburgerMenuElement.addEventListener( - 'click', - this._handleHamburgerMenuClickRef, - ); + this._handleHamburgerMenuClickRef = this._handleHamburgerMenuClick.bind(this); + this.hamburgerMenuElement.addEventListener('click', this._handleHamburgerMenuClickRef); } _handleHamburgerMenuClick() { @@ -54,70 +44,7 @@ export default class Header extends Widget { .forEach(d => new Dropdown({ element: d })); } - _handleVisitorUser() { - const signupModalElement = document.querySelector( - '[data-component-modal="signup"]', - ); - const loginModalElement = document.querySelector( - '[data-component-modal="login"]', - ); - const signupLinks = [].slice.call( - document.querySelectorAll('.js-signup-link'), - ); - const loginLinks = [].slice.call( - document.querySelectorAll('.js-login-link'), - ); - - if (signupModalElement && signupLinks.length) { - this.appendChild( - new Modal({ - name: 'signupModal', - element: signupModalElement, - }), - ).appendChild( - new UsersNewForm({ - name: 'userNewForm', - element: signupModalElement.querySelector( - '[data-component-usernewform]', - ), - }), - ); - - signupLinks.forEach(link => { - link.addEventListener( - 'click', - this._handleClickHijacking.bind(this, this.signupModal), - ); - }); - } else { - throw new Error('Header: signupModalElement || signupLinks not found.'); - } - - if (loginModalElement && loginLinks.length) { - this.appendChild( - new Modal({ - name: 'loginModal', - element: loginModalElement, - }), - ).appendChild( - new SessionsNewForm({ - name: 'sessionsNewForm', - element: loginModalElement.querySelector( - '[data-component-sessionsnewform]', - ), - }), - ); - - loginLinks.forEach(link => { - link.addEventListener( - 'click', - this._handleClickHijacking.bind(this, this.loginModal), - ); - }); - } else { - throw new Error('Header: loginModalElement || loginLinks not found.'); - } - } + _handleVisitorUser() {} _handleClickHijacking(instance, ev) { ev.preventDefault(); diff --git a/src/javascripts/components/admin/users/edit/AdminUsersEditForm.js b/src/javascripts/components/admin/users/edit/AdminUsersEditForm.js deleted file mode 100644 index 0c96fe3a..00000000 --- a/src/javascripts/components/admin/users/edit/AdminUsersEditForm.js +++ /dev/null @@ -1,156 +0,0 @@ -/* global Checkit */ -import Widget from '../../../../lib/widget'; -import Button from '../../../../components/Button'; - -export default class AdminUsersEditForm extends Widget { - static get constraints() { - return { - role: ['required'], - fullname: ['required'], - email: ['required', 'email'], - state: ['required'], - zip: [ - 'required', - { - rule(val) { - if (/(^\d{5}$)|(^\d{5}-\d{4}$)/.test(val) === false) { - throw new Error('The zip code is invalid.'); - } - }, - }, - ], - 'twitter-url': [ - { - rule: 'url', - message: 'The twitter address is not valid', - }, - ], - 'facebook-url': [ - { - rule: 'url', - message: 'The facebook address is not valid', - }, - ], - 'website-url': [ - { - rule: 'url', - message: 'The website address is not valid', - }, - ], - }; - } - - constructor(config) { - super(config); - - this.fileInput = this.element.querySelector('[type="file"]'); - this.image = this.element.querySelector('.EditProfile__image-wrapper'); - this.roleSelect = this.element.querySelector('[name="role"]'); - this.socialLinks = this.element.querySelector('[name="socialLinks"]'); - - this.ui = {}; - Object.keys(this.constructor.constraints).forEach(key => { - const query = `[name="${key}"]`; - this.ui[key] = this.element.querySelector(query); - }); - this._checkit = new Checkit(this.constructor.constraints); - - this.appendChild( - new Button({ - name: 'Button', - element: this.element.querySelector('button[type="submit"]'), - }), - ); - - this._bindEvents(); - } - - _bindEvents() { - this._handleFormSubmitRef = this._handleFormSubmit.bind(this); - this.element.addEventListener('submit', this._handleFormSubmitRef); - - this._handleFileChangeRef = this._handleFileChange.bind(this); - this.fileInput.addEventListener('change', this._handleFileChangeRef); - } - - _handleFormSubmit(ev) { - this.Button.disable(); - this._clearFieldErrors(); - - const [err] = this._checkit.validateSync(this._getFieldsData()); - - if (err) { - ev.preventDefault(); - this.Button.enable(); - return this._displayFieldErrors(err.errors); - } - - this._updateSocialLinksValue(); - this.Button.updateText(); - - return null; - } - - _handleFileChange(ev) { - const input = ev.currentTarget; - - if (input.files && input.files[0] && input.files[0].type.match('image.*')) { - const reader = new FileReader(); - - reader.onload = e => { - this.image.style.backgroundImage = `url(${e.target.result})`; - reader.onload = null; - }; - - reader.readAsDataURL(input.files[0]); - } - } - - _updateSocialLinksValue() { - const result = {}; - const twitter = this.ui['twitter-url'].value; - const facebook = this.ui['facebook-url'].value; - const website = this.ui['website-url'].value; - - if (twitter) result.twitter = twitter; - if (facebook) result.facebook = facebook; - if (website) result.website = website; - - if (Object.keys(result).length) { - this.socialLinks.value = JSON.stringify(result); - } - } - - _displayFieldErrors(errors) { - Object.keys(errors).forEach(key => { - const parent = this.ui[key].parentNode; - let errorLabel = parent.querySelector('.-on-error'); - - parent.classList.add('error'); - - if (errorLabel) { - errorLabel.innerText = `▲ ${errors[key].message}`; - return; - } - - errorLabel = parent.nextSibling; - if (errorLabel && errorLabel.classList.contains('-on-error')) { - errorLabel.innerText = `▲ ${errors[key].message}`; - } - }); - } - - _clearFieldErrors() { - Object.keys(this.constructor.constraints).forEach(key => { - this.ui[key].parentNode.classList.remove('error'); - }); - } - - _getFieldsData() { - const data = {}; - Object.keys(this.constructor.constraints).forEach(key => { - data[key] = this.ui[key].value; - }); - return data; - } -} diff --git a/src/javascripts/components/admin/users/index/AdminUsersIndexController.js b/src/javascripts/components/admin/users/index/AdminUsersIndexController.js deleted file mode 100644 index f23f36d4..00000000 --- a/src/javascripts/components/admin/users/index/AdminUsersIndexController.js +++ /dev/null @@ -1,88 +0,0 @@ -import Widget from '../../../../lib/widget'; -import serialize from '../../../../lib/serialize'; -import AdminUsersIndexTableControls from './AdminUsersIndexTableControls'; - -export default class AdminUsersIndexController extends Widget { - constructor(config) { - super(config); - - this.appendChild( - new AdminUsersIndexTableControls({ - name: 'AdminUsersIndexTableControls', - element: document.querySelector('thead'), - }), - ); - - this.originalQuery = { - filters: { - role: this.AdminUsersIndexTableControls.roleSelect.value, - }, - search: this.AdminUsersIndexTableControls.searchInput.value, - state: this.AdminUsersIndexTableControls.stateSelect.value, - order: this.AdminUsersIndexTableControls.orderSelect.value, - }; - - this._query = JSON.parse(JSON.stringify(this.originalQuery)); - - this.pagination = document.querySelector('.Pagination ul'); - - this._bindEvents(); - } - - _bindEvents() { - this.AdminUsersIndexTableControls.bind('searchInput', data => { - this._query.search = data.value; - this._updateApplyButton(); - }); - - this.AdminUsersIndexTableControls.bind('stateChange', data => { - this._query.state = data.value; - this._updateApplyButton(); - }); - - this.AdminUsersIndexTableControls.bind('roleChange', data => { - this._query.filters.role = data.value; - this._updateApplyButton(); - }); - - this.AdminUsersIndexTableControls.bind('orderChange', data => { - this._query.order = data.value; - this._updateApplyButton(); - }); - - this.AdminUsersIndexTableControls.bind('applyFilters', () => { - const search = serialize(this._query); - window.location.replace(`?${search}`); - }); - - this.AdminUsersIndexTableControls.bind('resetFilters', () => { - window.location.replace('?page=1'); - }); - - this._paginationClickHandlerRef = this._paginationClickHandler.bind(this); - this.pagination.addEventListener('click', this._paginationClickHandlerRef); - } - - _updateApplyButton() { - if ( - this._query.filters.role !== this.originalQuery.filters.role || - this._query.search !== this.originalQuery.search || - this._query.state !== this.originalQuery.state || - this._query.order !== this.originalQuery.order - ) { - return this.AdminUsersIndexTableControls.enableApplyButton(); - } - - return this.AdminUsersIndexTableControls.disableApplyButton(); - } - - _paginationClickHandler(ev) { - const target = ev.target; - ev.stopPropagation(); - - if (target.tagName === 'BUTTON') { - const search = serialize(this.originalQuery); - window.location.replace(`?page=${target.dataset.page}&${search}`); - } - } -} diff --git a/src/javascripts/components/admin/users/index/AdminUsersIndexTableControls.js b/src/javascripts/components/admin/users/index/AdminUsersIndexTableControls.js deleted file mode 100644 index f1770615..00000000 --- a/src/javascripts/components/admin/users/index/AdminUsersIndexTableControls.js +++ /dev/null @@ -1,71 +0,0 @@ -import Widget from '../../../../lib/widget'; - -export default class AdminUsersIndexTableControls extends Widget { - constructor(config) { - super(config); - - this.searchInput = this.element.querySelector( - '[name="usersListValue[search]"]', - ); - this.stateSelect = this.element.querySelector( - '[name="usersListValue[state]"]', - ); - this.roleSelect = this.element.querySelector( - '[name="usersListValue[role]"]', - ); - this.orderSelect = this.element.querySelector( - '[name="usersListValue[order]"]', - ); - - this.applyFiltersBtn = this.element.querySelector( - '[name="usersListValue[applyFilters]"]', - ); - this.resetFiltersBtn = this.element.querySelector( - '[name="usersListValue[resetFilters]"]', - ); - - this._bindEvents(); - } - - _bindEvents() { - this.searchInput.addEventListener('input', ev => { - this.dispatch('searchInput', { - value: ev.target.value.toLowerCase(), - }); - }); - - this.stateSelect.addEventListener('change', ev => { - this.dispatch('stateChange', { - value: ev.target.value, - }); - }); - - this.roleSelect.addEventListener('change', ev => { - this.dispatch('roleChange', { - value: ev.target.value, - }); - }); - - this.orderSelect.addEventListener('change', ev => { - this.dispatch('orderChange', { - value: ev.target.value, - }); - }); - - this.applyFiltersBtn.addEventListener('click', () => { - this.dispatch('applyFilters'); - }); - - this.resetFiltersBtn.addEventListener('click', () => { - this.dispatch('resetFilters'); - }); - } - - enableApplyButton() { - this.applyFiltersBtn.removeAttribute('disabled'); - } - - disableApplyButton() { - this.applyFiltersBtn.setAttribute('disabled', true); - } -} diff --git a/src/javascripts/components/sessions/ChangePasswordForm.js b/src/javascripts/components/sessions/ChangePasswordForm.js deleted file mode 100644 index bbaad9f3..00000000 --- a/src/javascripts/components/sessions/ChangePasswordForm.js +++ /dev/null @@ -1,76 +0,0 @@ -/* global Checkit */ -import Widget from '../../lib/widget'; -import Button from '../../components/Button'; - -export default class SessionsChangePasswordForm extends Widget { - static get constraints() { - return { - password: ['required', 'minLength:8'], - confirmPassword: ['required', 'minLength:8', 'matchesField:password'], - }; - } - - constructor(config) { - super(config); - - this.ui = {}; - Object.keys(SessionsChangePasswordForm.constraints).forEach(key => { - const query = `[name="${key}"]`; - this.ui[key] = this.element.querySelector(query); - }); - this._checkit = new Checkit(SessionsChangePasswordForm.constraints); - - this.appendChild( - new Button({ - name: 'Button', - element: this.element.querySelector('button'), - }), - ); - - this._bindEvents(); - } - - _bindEvents() { - this._handleFormSubmit = this._handleFormSubmit.bind(this); - this.element - .querySelector('form') - .addEventListener('submit', this._handleFormSubmit); - } - - _handleFormSubmit(ev) { - this.Button.disable(); - this._clearFieldErrors(); - - const [err] = this._checkit.validateSync(this._getFieldsData()); - - if (err) { - ev.preventDefault(); - this.Button.enable(); - return this._displayFieldErrors(err.errors); - } - - this.Button.updateText(); - - return undefined; - } - - _displayFieldErrors(errors) { - Object.keys(errors).forEach(key => { - this.ui[key].parentNode.classList.add('error'); - }); - } - - _clearFieldErrors() { - Object.keys(SessionsChangePasswordForm.constraints).forEach(key => { - this.ui[key].parentNode.classList.remove('error'); - }); - } - - _getFieldsData() { - const data = {}; - Object.keys(SessionsChangePasswordForm.constraints).forEach(key => { - data[key] = this.ui[key].value; - }); - return data; - } -} diff --git a/src/javascripts/components/sessions/NewForm.js b/src/javascripts/components/sessions/NewForm.js deleted file mode 100644 index 5f91d7c9..00000000 --- a/src/javascripts/components/sessions/NewForm.js +++ /dev/null @@ -1,76 +0,0 @@ -/* global Checkit */ -import Widget from '../../lib/widget'; -import Button from '../../components/Button'; - -export default class SessionsNewForm extends Widget { - static get constraints() { - return { - email: ['required', 'email'], - password: ['required', 'minLength:8'], - }; - } - - constructor(config) { - super(config); - - this.ui = {}; - Object.keys(SessionsNewForm.constraints).forEach(key => { - const query = `[name="${key}"]`; - this.ui[key] = this.element.querySelector(query); - }); - this._checkit = new Checkit(SessionsNewForm.constraints); - - this.appendChild( - new Button({ - name: 'Button', - element: this.element.querySelector('button'), - }), - ); - - this._bindEvents(); - } - - _bindEvents() { - this._handleFormSubmit = this._handleFormSubmit.bind(this); - this.element - .querySelector('form') - .addEventListener('submit', this._handleFormSubmit); - } - - _handleFormSubmit(ev) { - this.Button.disable(); - this._clearFieldErrors(); - - const [err] = this._checkit.validateSync(this._getFieldsData()); - - if (err) { - ev.preventDefault(); - this.Button.enable(); - return this._displayFieldErrors(err.errors); - } - - this.Button.updateText(); - - return undefined; - } - - _displayFieldErrors(errors) { - Object.keys(errors).forEach(key => { - this.ui[key].parentNode.classList.add('error'); - }); - } - - _clearFieldErrors() { - Object.keys(SessionsNewForm.constraints).forEach(key => { - this.ui[key].parentNode.classList.remove('error'); - }); - } - - _getFieldsData() { - const data = {}; - Object.keys(SessionsNewForm.constraints).forEach(key => { - data[key] = this.ui[key].value; - }); - return data; - } -} diff --git a/src/javascripts/components/sessions/PasswordRecoverForm.js b/src/javascripts/components/sessions/PasswordRecoverForm.js deleted file mode 100644 index 1481c9f8..00000000 --- a/src/javascripts/components/sessions/PasswordRecoverForm.js +++ /dev/null @@ -1,75 +0,0 @@ -/* global Checkit */ -import Widget from '../../lib/widget'; -import Button from '../../components/Button'; - -export default class SessionsPasswordRecoverForm extends Widget { - static get constraints() { - return { - email: ['required', 'email'], - }; - } - - constructor(config) { - super(config); - - this.ui = {}; - Object.keys(SessionsPasswordRecoverForm.constraints).forEach(key => { - const query = `[name="${key}"]`; - this.ui[key] = this.element.querySelector(query); - }); - this._checkit = new Checkit(SessionsPasswordRecoverForm.constraints); - - this.appendChild( - new Button({ - name: 'Button', - element: this.element.querySelector('button'), - }), - ); - - this._bindEvents(); - } - - _bindEvents() { - this._handleFormSubmit = this._handleFormSubmit.bind(this); - this.element - .querySelector('form') - .addEventListener('submit', this._handleFormSubmit); - } - - _handleFormSubmit(ev) { - this.Button.disable(); - this._clearFieldErrors(); - - const [err] = this._checkit.validateSync(this._getFieldsData()); - - if (err) { - ev.preventDefault(); - this.Button.enable(); - return this._displayFieldErrors(err.errors); - } - - this.Button.updateText(); - - return undefined; - } - - _displayFieldErrors(errors) { - Object.keys(errors).forEach(key => { - this.ui[key].parentNode.classList.add('error'); - }); - } - - _clearFieldErrors() { - Object.keys(SessionsPasswordRecoverForm.constraints).forEach(key => { - this.ui[key].parentNode.classList.remove('error'); - }); - } - - _getFieldsData() { - const data = {}; - Object.keys(SessionsPasswordRecoverForm.constraints).forEach(key => { - data[key] = this.ui[key].value; - }); - return data; - } -} diff --git a/src/javascripts/components/users/EditForm.js b/src/javascripts/components/users/EditForm.js deleted file mode 100644 index be28aa6f..00000000 --- a/src/javascripts/components/users/EditForm.js +++ /dev/null @@ -1,156 +0,0 @@ -/* global Checkit */ -import Widget from '../../lib/widget'; -import Button from '../../components/Button'; - -export default class UsersEditForm extends Widget { - static get constraints() { - return { - fullname: ['required'], - email: ['required', 'email'], - state: ['required'], - private: ['required'], - disputesPrivate: ['required'], - zip: [ - 'required', - { - rule(val) { - if (/(^\d{5}$)|(^\d{5}-\d{4}$)/.test(val) === false) { - throw new Error('The zip code is invalid.'); - } - }, - }, - ], - 'twitter-url': [ - { - rule: 'url', - message: 'The twitter address is not valid', - }, - ], - 'facebook-url': [ - { - rule: 'url', - message: 'The facebook address is not valid', - }, - ], - 'website-url': [ - { - rule: 'url', - message: 'The website address is not valid', - }, - ], - }; - } - - constructor(config) { - super(config); - - this.fileInput = this.element.querySelector('[type="file"]'); - this.image = this.element.querySelector('.EditProfile__image-wrapper'); - this.socialLinks = this.element.querySelector('[name="socialLinks"]'); - - this.ui = {}; - Object.keys(UsersEditForm.constraints).forEach(key => { - const query = `[name="${key}"]`; - this.ui[key] = this.element.querySelector(query); - }); - this._checkit = new Checkit(UsersEditForm.constraints); - - this.appendChild( - new Button({ - name: 'Button', - element: this.element.querySelector('button[type="submit"]'), - }), - ); - - this._bindEvents(); - } - - _bindEvents() { - this._handleFormSubmitRef = this._handleFormSubmit.bind(this); - this.element.addEventListener('submit', this._handleFormSubmitRef); - - this._handleFileChangeRef = this._handleFileChange.bind(this); - this.fileInput.addEventListener('change', this._handleFileChangeRef); - } - - _handleFormSubmit(ev) { - this.Button.disable(); - this._clearFieldErrors(); - - const [err] = this._checkit.validateSync(this._getFieldsData()); - - if (err) { - ev.preventDefault(); - this.Button.enable(); - return this._displayFieldErrors(err.errors); - } - - this._updateSocialLinksValue(); - this.Button.updateText(); - - return undefined; - } - - _handleFileChange(ev) { - const input = ev.currentTarget; - - if (input.files && input.files[0] && input.files[0].type.match('image.*')) { - const reader = new FileReader(); - - reader.onload = e => { - this.image.style.backgroundImage = `url(${e.target.result})`; - reader.onload = null; - }; - - reader.readAsDataURL(input.files[0]); - } - } - - _updateSocialLinksValue() { - const result = {}; - const twitter = this.ui['twitter-url'].value; - const facebook = this.ui['facebook-url'].value; - const website = this.ui['website-url'].value; - - if (twitter) result.twitter = twitter; - if (facebook) result.facebook = facebook; - if (website) result.website = website; - - if (Object.keys(result).length) { - this.socialLinks.value = JSON.stringify(result); - } - } - - _displayFieldErrors(errors) { - Object.keys(errors).forEach(key => { - const parent = this.ui[key].parentNode; - let errorLabel = parent.querySelector('.-on-error'); - - parent.classList.add('error'); - - if (errorLabel) { - errorLabel.innerText = `▲ ${errors[key].message}`; - return; - } - - errorLabel = parent.nextSibling; - if (errorLabel && errorLabel.classList.contains('-on-error')) { - errorLabel.innerText = `▲ ${errors[key].message}`; - } - }); - } - - _clearFieldErrors() { - Object.keys(UsersEditForm.constraints).forEach(key => { - this.ui[key].parentNode.classList.remove('error'); - }); - } - - _getFieldsData() { - const data = {}; - Object.keys(UsersEditForm.constraints).forEach(key => { - data[key] = this.ui[key].value; - }); - return data; - } -} diff --git a/src/javascripts/components/users/NewForm.js b/src/javascripts/components/users/NewForm.js deleted file mode 100644 index 22499217..00000000 --- a/src/javascripts/components/users/NewForm.js +++ /dev/null @@ -1,142 +0,0 @@ -/* global Checkit */ -import Widget from '../../lib/widget'; -import Button from '../../components/Button'; - -export default class UsersNewForm extends Widget { - static get constraints() { - return { - fullname: ['required'], - state: ['required'], - zip: [ - 'required', - { - rule(val) { - if (/(^\d{5}$)|(^\d{5}-\d{4}$)/.test(val) === false) { - throw new Error('Invalid zip code.'); - } - }, - }, - ], - email: ['required', 'email'], - password: ['required', 'minLength:8'], - }; - } - - constructor(config) { - super(config); - - this.ui = {}; - const _pluralRe = new RegExp(/s$/); - let query = ''; - Object.keys(UsersNewForm.constraints).forEach(key => { - query = `[name="${key}"]`; - if (_pluralRe.test(key)) { - this.ui[key] = [].slice.call(this.element.querySelectorAll(query)); - } else { - this.ui[key] = this.element.querySelector(query); - } - }); - this._checkit = new Checkit(UsersNewForm.constraints); - - this.appendChild( - new Button({ - name: 'ButtonSubmit', - element: this.element.querySelector('button[type="submit"]'), - }), - ); - - this.step1Layer = this.element.querySelector('.js-step-1'); - this.step2Layer = this.element.querySelector('.js-step-2'); - this.nextBtn = this.element.querySelector('#sign-up-btn-next'); - this.prevBtn = this.element.querySelector('#sign-up-btn-back'); - - this.appendChild( - new Button({ - name: 'ButtonContinue', - element: this.nextBtn, - }), - ); - - this._bindEvents(); - } - - _bindEvents() { - this._handleShowNextStepRef = this._handleShowNextStep.bind(this); - this.nextBtn.addEventListener('click', this._handleShowNextStepRef); - - this._handleShowPrevStepRef = this._handleShowPrevStep.bind(this); - this.prevBtn.addEventListener('click', this._handleShowPrevStepRef); - - this._handleFormSubmit = this._handleFormSubmit.bind(this); - this.element.querySelector('form').addEventListener('submit', this._handleFormSubmit); - } - - _handleShowNextStep() { - this.step1Layer.classList.add('hide'); - this.step2Layer.classList.remove('hide'); - } - - _handleShowPrevStep() { - this.step1Layer.classList.remove('hide'); - this.step2Layer.classList.add('hide'); - } - - _handleFormSubmit(ev) { - this.ButtonSubmit.disable(); - this._clearFieldErrors(); - - const [err] = this._checkit.validateSync(this._getFieldsData()); - - if (err) { - ev.preventDefault(); - this.ButtonSubmit.enable(); - return this._displayFieldErrors(err.errors); - } - - this.ButtonSubmit.updateText(); - - return undefined; - } - - _displayFieldErrors(errors) { - let parent; - let errorLabel; - - Object.keys(errors).forEach(key => { - parent = this.ui[key].parentNode || this.ui[key][0].parentNode; - errorLabel = parent.querySelector('.-on-error'); - - parent.classList.add('error'); - - if (errorLabel) { - errorLabel.innerText = `▲ ${errors[key].message}`; - return; - } - - errorLabel = parent.nextSibling; - if (errorLabel && errorLabel.classList.contains('-on-error')) { - errorLabel.innerText = `▲ ${errors[key].message}`; - } - }); - } - - _clearFieldErrors() { - let parent; - Object.keys(UsersNewForm.constraints).forEach(key => { - parent = this.ui[key].parentNode || this.ui[key][0].parentNode; - parent.classList.remove('error'); - }); - } - - _getFieldsData() { - const data = {}; - Object.keys(UsersNewForm.constraints).forEach(key => { - if (this.ui[key] instanceof Array) { - data[key] = this.ui[key].filter(c => c.checked).map(v => v.value); - } else { - data[key] = this.ui[key].value; - } - }); - return data; - } -} diff --git a/src/javascripts/index.js b/src/javascripts/index.js index d1f5dc8c..af9d0f94 100644 --- a/src/javascripts/index.js +++ b/src/javascripts/index.js @@ -1,8 +1,5 @@ require('./shared'); -require('./_entries/sessions/new'); -require('./_entries/sessions/changePassword'); -require('./_entries/sessions/passwordRecover'); require('./_entries/dispute-tools'); require('./_entries/dispute-tools/show'); require('./_entries/disputes/show'); @@ -12,8 +9,5 @@ require('./_entries/home/contact'); require('./_entries/home/tos'); require('./_entries/home/vision'); require('./_entries/home'); -require('./_entries/users/edit'); -require('./_entries/users/new'); -require('./_entries/users/show'); require('../stylesheets/index.css'); diff --git a/src/stylesheets/_entries/admin/users/edit.css b/src/stylesheets/_entries/admin/users/edit.css deleted file mode 100644 index c471f586..00000000 --- a/src/stylesheets/_entries/admin/users/edit.css +++ /dev/null @@ -1,19 +0,0 @@ -/* view utils */ -.-border-neutral-mid { - border-color: var(--k-color-palette-neutral-mid); -} - -/* view styles */ -.EditProfile__image { - &-wrapper { - padding-top: 100%; - background-color: var(--k-color-palette-neutral-mid); - background-size: cover; - background-position: 50%; - } - - &-placeholder-text { - color: #fff; - opacity: 0.3; - } -} diff --git a/src/stylesheets/_entries/admin/users/index.css b/src/stylesheets/_entries/admin/users/index.css deleted file mode 100644 index 72db52dd..00000000 --- a/src/stylesheets/_entries/admin/users/index.css +++ /dev/null @@ -1 +0,0 @@ -@import '../../../components/Pagination'; diff --git a/src/stylesheets/_entries/users/edit.css b/src/stylesheets/_entries/users/edit.css deleted file mode 100644 index 9d3b5cb4..00000000 --- a/src/stylesheets/_entries/users/edit.css +++ /dev/null @@ -1,12 +0,0 @@ -.EditProfile__image { - &-wrapper { - padding-top: 100%; - background-color: var(--k-color-palette-neutral-dark); - background-size: cover; - background-position: 50%; - } - - &-placeholder-text { - opacity: 0.3; - } -} diff --git a/src/stylesheets/_entries/users/show.css b/src/stylesheets/_entries/users/show.css deleted file mode 100644 index 542bf2fc..00000000 --- a/src/stylesheets/_entries/users/show.css +++ /dev/null @@ -1,25 +0,0 @@ -.ProfileWrapper { - min-height: calc(100vh - (var(--header-height) + var(--footer-height))); -} - -.Profile__map { - top: -60px; - background-position: 50%; - background-size: cover; - opacity: 0.7; - mix-blend-mode: lighten; -} - -.Profile__image-wrapper { - order: 1; -} - -.Profile__info-wrapper { - background-color: var(--k-background-color); -} - -hr { - height: 5px; - background-color: var(--k-color-palette-accent); - border: none; -} diff --git a/src/stylesheets/admin.css b/src/stylesheets/admin.css index cdd7ff10..9a70edbd 100644 --- a/src/stylesheets/admin.css +++ b/src/stylesheets/admin.css @@ -1,4 +1,2 @@ @import './base_admin.css'; @import './_entries/admin/disputes/index.css'; -@import './_entries/admin/users/edit.css'; -@import './_entries/admin/users/index.css'; diff --git a/src/stylesheets/components/users/NewForm.css b/src/stylesheets/components/users/NewForm.css deleted file mode 100644 index 6a3313d5..00000000 --- a/src/stylesheets/components/users/NewForm.css +++ /dev/null @@ -1,12 +0,0 @@ -.UsersNewForm__step { - display: block; -} - -#sign-up-btn-back { - background-color: var(--k-color-palette-neutral-dark); - color: #fff; - - &:hover { - background-color: color(var(--k-color-palette-neutral-dark) tint(10%)); - } -} diff --git a/src/stylesheets/index.css b/src/stylesheets/index.css index a18a55df..5ee99c42 100644 --- a/src/stylesheets/index.css +++ b/src/stylesheets/index.css @@ -7,5 +7,3 @@ @import './_entries/home/dtr.css'; @import './_entries/home/index.css'; @import './_entries/home/vision.css'; -@import './_entries/users/edit.css'; -@import './_entries/users/show.css'; diff --git a/tests/integration/controllers/ACLController.js b/tests/integration/controllers/ACLController.js deleted file mode 100644 index 29460e01..00000000 --- a/tests/integration/controllers/ACLController.js +++ /dev/null @@ -1,23 +0,0 @@ -/* globals User, CONFIG */ - -const sa = require('superagent'); -const expect = require('chai').expect; - -const agent = sa.agent(); -const url = CONFIG.env().siteURL; -const urls = CONFIG.router.helpers; - -describe('ACLController', () => { - it('Should return a object with Resources and capabilities', done => { - agent - .get(`${url}${urls.acl.url()}`) - .set('Accept', 'application/json') - .end((err, res) => { - expect(res.status).to.equal(200); - expect(res.body.root).to.be.instanceof(Object); - expect(res.body.root.index).to.be.instanceof(Object); - expect(res.body.root.index.path).to.be.equal('/'); - done(); - }); - }); -}); diff --git a/views/admin/users/edit.pug b/views/admin/users/edit.pug deleted file mode 100644 index f4b35d1f..00000000 --- a/views/admin/users/edit.pug +++ /dev/null @@ -1,151 +0,0 @@ -extends ../../layouts/admin.pug - -block title - | Admin/Users/edit — The Debt Collective - -block content - .px2.pb4 - .max-width-1.mx-auto - - h2.pt4 Edit User - h4.pb3= user.account.fullname - - form( - method='post' - action= routeMappings.Admin.Users.update.url(user.id) - enctype="multipart/form-data" - data-component-editform - ) - input(type='hidden' name='_csrf' value=csrfToken) - input(name='_method' type='hidden' value='PUT') - - if _backUrl - input(type='hidden' name='_backUrl' value=_backUrl) - - .clearfix.mxn1.pb1 - .col.col-8.px1 - if (user.account.image.exists('medium')) - .EditProfile__image-wrapper.relative.block.-fw(style=`background-image: url(${user.account.image.urls['medium']});`) - else - .EditProfile__image-wrapper.relative.block.-fw(style='background-image: url(/images/profile/placeholder.png);') - .EditProfile__image-placeholder-text.-h4.-ff-sec.-fw-700.absolute.top-0.-fw.-fh.flex.items-center.justify-center - p No picture - .col.col-4.px1 - .relative.-k-btn.btn-link.btn-outline.-primary.-sm - input.absolute.top-0.left-0.-fh.-fw(name="image" type="file" accept="image/*" style="opacity: 0;") - span.-fw-500.-primary Upload - - .py1 - label.block.pb1.-fw-500 Role - .-k-select.-border-neutral-mid - select.-fw(name='role') - option(value='') Role - each role in User.roles - option( - value= role - selected!= user.role === role - )= role - p(class="-on-error -danger -caption -fw-500 mt1") - - .py1 - label.block.pb1.-fw-500 Fullname - input.-k-input.-fw.-border-neutral-mid( - type='text' - name='fullname' - value= user.account.fullname - ) - p(class="-on-error -danger -caption -fw-500 mt1") - - .py1 - label.block.pb1.-fw-500 Email - input.-k-input.-fw.-border-neutral-mid( - type='email' - name='email' - value= user.email - ) - p(class="-on-error -danger -caption -fw-500 mt1") - - .py1 - label.block.pb1.-fw-500 Phone - input.-k-input.-fw.-border-neutral-mid( - type='phone' - name='phone' - value= user.account.phone - ) - - .clearfix.mxn1.py1 - .col.col-6.px1 - label.block.pb1.-fw-500 State - .-k-select.-border-neutral-mid - select.-fw(name="state") - option(value="") State - each state in US_STATES - option( - value= state - selected!= user.account.state === state - )= state - p(class="-on-error -danger -caption -fw-500 mt1") - - .col.col-6.px1 - label.block.pb1.-fw-500 ZIP - input.-k-input.-fw.-border-neutral-mid( - type='text' - name='zip' - value= user.account.zip - ) - p(class="-on-error -danger -caption -fw-500 mt1") - - .py1 - label.block.pb1.-fw-500 Bio - textarea.-k-textarea.-fw.-ff-sec.-border-neutral-mid(name="bio" rows="4") #{user.account.bio} - - div - input( - name="socialLinks" - type="hidden" - value=user.account.socialLinks - ) - - .py1 - label.block.pb1.-fw-500 Twitter link - input.-k-input.-fw.-border-neutral-mid( - name="twitter-url" - type='text' - value=user.account.socialLinks.twitter - placeholder="https://twitter.com/username" - ) - p(class="-on-error -danger -caption -fw-500 mt1") - - .py1 - label.block.pb1.-fw-500 Facebook link - input.-k-input.-fw.-border-neutral-mid( - name="facebook-url" - type='text' - value=user.account.socialLinks.facebook - placeholder="https://facebook.com/username" - ) - p(class="-on-error -danger -caption -fw-500 mt1") - - .py1 - label.block.pb1.-fw-500 Website - input.-k-input.-fw.-border-neutral-mid( - name="website-url" - type='text' - value=user.account.socialLinks.website - placeholder="http://website.com" - ) - p(class="-on-error -danger -caption -fw-500 mt1") - - .pt3 - button.-k-btn.btn-primary.-fw.-fw-700(type="submit") Save changes - -block scripts - script. - window.addEventListener('load', function() { - var options = { - currentUser: !{JSON.stringify(UserRenderer(currentUser))}, - currentURL: !{JSON.stringify(currentURL)}, - }; - - new ViewAdminUsersEdit(options); - }, true); diff --git a/views/admin/users/index.pug b/views/admin/users/index.pug deleted file mode 100644 index a9bd6857..00000000 --- a/views/admin/users/index.pug +++ /dev/null @@ -1,151 +0,0 @@ -extends ../../layouts/admin.pug - -block title - | Admin/Users — The Debt Collective - -block content - - - var queryFilters = headers.query && headers.query.filters - var querySearch = headers.query && headers.query.search; - var queryState = headers.query && headers.query.state; - var queryOrder = headers.query && headers.query.order - - .wrapper.px2.pb3 - h2.pt4.pb2 Users - - table.-fw(cellpadding='0' cellspacing='0') - thead - tr - th(colspan='3') - .-input-group-icon - svg(width='18' height='18'): use(xlink:href='#svg-search') - input.-k-input.-clean.-fw-700( - name="usersListValue[search]" - placeholder='Seach by name, email, zip...' - value= querySearch - ) - - th - .-k-select.-clean - select.-fw-700.-fw(name='usersListValue[state]') - option(value="") State - each state in US_STATES - option( - value= state - selected= queryState === state - )= state - - th - .-k-select.-clean - select.-fw-700.-fw(name='usersListValue[role]') - option(value="") Role - each role in User.roles - option( - value= role - selected= queryFilters && queryFilters.role === role - )= role - - th - .-k-select.-clean - select.-fw-700.-fw(name='usersListValue[order]') - option(value="") Date - option( - value='created_at' - selected= queryOrder === 'created_at' - )= 'ASC' - option( - value='-created_at' - selected= queryOrder === '-created_at' - )= 'DESC' - - th.-wsnw - button.-k-btn.btn-primary.-fw-700( - name='usersListValue[applyFilters]' - disabled= true - ) Apply - button.-k-btn.btn-link( - name='usersListValue[resetFilters]' - disabled= !querySearch && !queryFilters && !queryState && !queryOrder - ) Reset - - - tbody - each user in users - tr(class=user.banned ? '.banned' : null) - td - .flex.items-center - - - var _avatarUrl = '/images/profile/placeholder-small.png'; - - if (user.account.image.exists('small')) - _avatarUrl = user.account.image.urls['small']; - - div: img.block(src= _avatarUrl alt=user.account.fullname width="50" height="50") - p.pl2.-fw-700= user.account.fullname - td.-fw-500= user.email - td.-fw-500= user.account.zip - td.-fw-500= user.account.state - td.-fw-700= user.role - td.-fw-500= new Date(user.createdAt).toDateString() - td.center - a.inline-block.btn-clear.p1.align-top( - href= routeMappings.Admin.Users.edit.url(user.id) + '?_backUrl=' + routeMappings.Admin.Users.url() - ) - svg.-pen.block.align-top(width='15' height='15') - use(xlink:href='#svg-pencil') - - if user.id === currentUser.id - .inline-block.p1.ml2.align-top - svg.-pen.-neutral-mid.block.align-top(width='15' height='15') - use(xlink:href='#svg-flag') - else - form.inline-block( - action=routeMappings.Admin.Users.ban.url(user.id) - method='post' - onsubmit="return confirm('Are you sure you want to ban or unban this user?')" - title='Ban/unban user' - ) - // Ban button - form.inline-block(action=routeMappings.Admin.Users.ban.url(user.id) method='post') - input(type='hidden' name='_csrf' value=csrfToken) - input(type='hidden' name='_backUrl' value=routeMappings.Admin.Users.url()) - - if user.banned - input(type='hidden' name='_method' value=routeMappings.Admin.Users.unban.verb) - button.p1.ml2.align-top.btn-clear(type='submit') - svg.-pen.-danger.block.align-top(width='15' height='15') - use(xlink:href='#svg-flag') - else - input(type='hidden' name='_method' value=routeMappings.Admin.Users.ban.verb) - button.p1.ml2.align-top.btn-clear(type='submit') - svg.-pen.-dark.block.align-top(width='15' height='15') - use(xlink:href='#svg-flag') - - // Delete button - form.inline-block( - onsubmit="return confirm('Are you sure you want to permanently delete this user?')" - action=routeMappings.Admin.Users.destroy.url(user.id) - method="post" - data-component-form="delete-user" - ) - input(name="_csrf" type="hidden" value=csrfToken) - input(type="hidden" name="_method" value="DELETE") - button.p1.ml2.align-top.btn-clear( - type="submit" - ) - svg.-pen.block.align-top(width='15' height='15'): use(xlink:href='#svg-thrash') - - include ../../mixins/pagination - +mixinPagination(headers.current_page, headers.total_pages)(class='pt3') - -block scripts - script. - window.addEventListener('load', function() { - var options = { - currentUser: !{JSON.stringify(UserRenderer(currentUser))}, - currentURL: !{JSON.stringify(currentURL)}, - isAdmin: true, - }; - - new ViewAdminUsersIndex(options); - }, true); diff --git a/views/layouts/application.pug b/views/layouts/application.pug index 170eee50..9bb5d430 100644 --- a/views/layouts/application.pug +++ b/views/layouts/application.pug @@ -64,25 +64,11 @@ html(lang='en') block body include ../includes/donation-flow - if !currentUser - if (currentURL !== routeMappings.signup.url()) && (currentURL !== routeMappings.login.url()) && (currentURL !== routeMappings.Users.create.url()) - .Modal(data-component-modal='signup' aria-hidden='true' role='dialog' aria-labelledby='modalSignupTitle' aria-describedby='modalSignupDesc') - button(class='Modal__close' aria-label='close') - svg(class='-s18'): use(xlink:href='#svg-close') - .Modal__body(role='document') - include ../includes/users/new-form.pug - - .Modal(data-component-modal='login' aria-hidden='true' role='dialog' aria-labelledby='modalLoginTitle' aria-describedby='modalLoginDesc') - button(class='Modal__close' aria-label='close') - svg(class='-s18'): use(xlink:href='#svg-close') - .Modal__body(role='document') - include ../includes/sessions/new-form.pug - script(src='/build/shared.js') script(src='/build/index.js') script(src='https://js.stripe.com/v2/' async) include ../mixins/utils.pug - + block scripts if NODE_ENV === 'production' script(type="text/javascript"). diff --git a/views/layouts/placeholder.pug b/views/layouts/placeholder.pug index 75bb52c1..567c83d0 100644 --- a/views/layouts/placeholder.pug +++ b/views/layouts/placeholder.pug @@ -67,20 +67,6 @@ html(lang='en') block body include ../includes/donation-flow - if !currentUser - if (currentURL !== routeMappings.signup.url()) && (currentURL !== routeMappings.login.url()) && (currentURL !== routeMappings.Users.create.url()) - .Modal(data-component-modal='signup' aria-hidden='true' role='dialog' aria-labelledby='modalSignupTitle' aria-describedby='modalSignupDesc') - button(class='Modal__close' aria-label='close') - svg(class='-s18'): use(xlink:href='#svg-close') - .Modal__body(role='document') - include ../includes/users/new-form.pug - - .Modal(data-component-modal='login' aria-hidden='true' role='dialog' aria-labelledby='modalLoginTitle' aria-describedby='modalLoginDesc') - button(class='Modal__close' aria-label='close') - svg(class='-s18'): use(xlink:href='#svg-close') - .Modal__body(role='document') - include ../includes/sessions/new-form.pug - script(src='/build/shared.js') script(src='/build/index.js') script(src='https://js.stripe.com/v2/' async) diff --git a/views/layouts/shared.pug b/views/layouts/shared.pug index 8a7852de..bd2e7ece 100644 --- a/views/layouts/shared.pug +++ b/views/layouts/shared.pug @@ -34,20 +34,6 @@ html(lang='en') include ../includes/donation-flow - if !currentUser - if (currentURL !== routeMappings.signup.url()) && (currentURL !== routeMappings.login.url()) && (currentURL !== routeMappings.Users.create.url()) - .Modal(data-component-modal='signup' aria-hidden='true' role='dialog' aria-labelledby='modalSignupTitle' aria-describedby='modalSignupDesc') - button(class='Modal__close' aria-label='close') - svg(class='-s18'): use(xlink:href='#svg-close') - .Modal__body(role='document') - include ../includes/users/new-form.pug - - .Modal(data-component-modal='login' aria-hidden='true' role='dialog' aria-labelledby='modalLoginTitle' aria-describedby='modalLoginDesc') - button(class='Modal__close' aria-label='close') - svg(class='-s18'): use(xlink:href='#svg-close') - .Modal__body(role='document') - include ../includes/sessions/new-form.pug - include ../mixins/utils script(src='/build/shared.js') script(src='/build/index.js') diff --git a/views/mixins/header-nav.pug b/views/mixins/header-nav.pug index 44283419..6a1b9f93 100644 --- a/views/mixins/header-nav.pug +++ b/views/mixins/header-nav.pug @@ -14,44 +14,27 @@ }, ], loggedOut: [ - { - text: 'Join Us', - title: 'Join Us', - url: routeMappings.signup.url(), - className: 'js-signup-link -primary', - parentClassName: 'px2 pl4' - }, - { - text: 'Log In', - title: 'Log In', - url: routeMappings.login.url(), - className: 'js-login-link', - parentClassName: 'pl2' - }, + //- { + //- text: 'Join Us', + //- title: 'Join Us', + //- url: routeMappings.signup.url(), + //- className: 'js-signup-link -primary', + //- parentClassName: 'px2 pl4' + //- }, + //- { + //- text: 'Log In', + //- title: 'Log In', + //- url: routeMappings.login.url(), + //- className: 'js-login-link', + //- parentClassName: 'pl2' + //- }, ], loggedIn: [], admin: [], } - if (currentUser) { - _menuItemsData.loggedIn = _menuItemsData.loggedIn.concat([ - { - text: 'Profile and tools', - url: routeMappings.Users.show.url(currentUser.id), - }, - { - text: 'Edit profile', - url: routeMappings.Users.edit.url(currentUser.id), - } - ]); - } - if (currentUser && currentUser.role === 'Admin') { _menuItemsData.admin = _menuItemsData.admin.concat([ - { - text: 'Users', - url: routeMappings.Admin.Users.url(), - }, { text: 'Disputes', url: routeMappings.Admin.Disputes.url(), diff --git a/views/sessions/new.pug b/views/sessions/new.pug deleted file mode 100644 index 45879826..00000000 --- a/views/sessions/new.pug +++ /dev/null @@ -1,19 +0,0 @@ -extends ../layouts/application.pug - -block title - | Log In — The Debt Collective - -block content - .wrapper.py4 - include ../includes/sessions/new-form.pug - -block scripts - script. - window.addEventListener('load', function() { - var options = { - currentUser: !{JSON.stringify(UserRenderer(currentUser))}, - currentURL: !{JSON.stringify(currentURL)}, - }; - - new ViewSessionsNew(options); - }, true); diff --git a/views/sessions/showEmailForm.pug b/views/sessions/showEmailForm.pug deleted file mode 100644 index 80302b5c..00000000 --- a/views/sessions/showEmailForm.pug +++ /dev/null @@ -1,32 +0,0 @@ -extends ../layouts/application.pug - -block title - | Password Recover — The Debt Collective - -block content - .wrapper.py4.px2 - .max-width-1.mx-auto(data-component-sessions-password-recover) - h3 Password recover - p.-fw-500.mb3 Enter your email address and we will send you a link to reset your password. - - form(method="post" action=routeMappings.resetPassword.url()) - input(type="hidden" name="_csrf" value=csrfToken) - - .clearfix.mb3 - .col.col-3.pt1.pr1.right-align.-fw-500 Email - .col.col-9(class=errors && errors.email ? 'error' : '') - input(name="email" class="-k-input -fw" type="email") - p(class="-on-error -danger -caption -fw-500 mt1") ▲ Invalid email - - button.-k-btn.btn-primary.-fw.-fw-700(type="submit") Reset password - -block scripts - script. - window.addEventListener('load', function() { - var options = { - currentUser: !{JSON.stringify(UserRenderer(currentUser))}, - currentURL: !{JSON.stringify(currentURL)}, - }; - - new ViewPasswordRecover(options); - }, true); diff --git a/views/sessions/showPasswordForm.pug b/views/sessions/showPasswordForm.pug deleted file mode 100644 index dc98a50c..00000000 --- a/views/sessions/showPasswordForm.pug +++ /dev/null @@ -1,39 +0,0 @@ -extends ../layouts/application.pug - -block title - | Change Password — The Debt Collective - -block content - .wrapper.py4.px2 - .max-width-1.mx-auto(data-component-sessions-change-password) - h3 Change password - p.-fw-500.mb3 Password must contain 8 characters long. - - form(method="post" action=routeMappings.resetPasswordWithToken.url(token)) - input(type="hidden" name="_csrf" value=csrfToken) - input(type="hidden" name="_method" value="PUT") - - .clearfix.mb3 - .col.col-3.pt1.pr1.right-align.-fw-500 Password - .col.col-9(class=errors && errors.password ? 'error' : '') - input(name="password" class="-k-input -fw" type="password" autocomplete="off") - p(class="-on-error -danger -caption -fw-500 mt1") ▲ Password must contain 8 characters long - - .clearfix.mb3 - .col.col-3.pt1.pr1.right-align.-fw-500 Confirm - .col.col-9(class=errors && errors.confirmPassword ? 'error' : '') - input(name="confirmPassword" class="-k-input -fw" type="password" autocomplete="off") - p(class="-on-error -danger -caption -fw-500 mt1") ▲ Password does not match - - button.-k-btn.btn-primary.-fw.-fw-700(type="submit") Change password - -block scripts - script. - window.addEventListener('load', function() { - var options = { - currentUser: !{JSON.stringify(UserRenderer(currentUser))}, - currentURL: !{JSON.stringify(currentURL)}, - }; - - new ViewChangePassword(options); - }, true); diff --git a/views/users/activate.pug b/views/users/activate.pug deleted file mode 100644 index 144079a8..00000000 --- a/views/users/activate.pug +++ /dev/null @@ -1 +0,0 @@ -Invalid activation token diff --git a/views/users/activation.pug b/views/users/activation.pug deleted file mode 100644 index 6c19a471..00000000 --- a/views/users/activation.pug +++ /dev/null @@ -1,15 +0,0 @@ -extends ../layouts/application.pug - -block title - | Verify Email — The Debt Collective - -block content - .wrapper.py4.px2 - .max-width-1.mx-auto - h3 Verify email - - p.-fw-500.pt2 Before you can continue on The Debt Collective page, we need you to verify your email address. - - if email - p.-fw-500.pt4.pb1 An email containing verification instructions was sent to: - p.-primary= email diff --git a/views/users/edit.pug b/views/users/edit.pug deleted file mode 100644 index a5775168..00000000 --- a/views/users/edit.pug +++ /dev/null @@ -1,113 +0,0 @@ -extends ../layouts/application.pug - -block title - | Edit Profile — The Debt Collective - -block content - .px2.py4 - .max-width-1.mx-auto - h2.pb3.-fw-500 Edit Profile - form( - action=routeMappings.Users.update.url(user.id) - method="post" - enctype="multipart/form-data" - data-component-usereditform - ) - input(name="_csrf" type="hidden" value=csrfToken) - input(name="_method" type="hidden" value="PUT") - - .clearfix.mxn1.pb3 - .col.col-8.px1 - if (user.account.image.exists('mediumGrayscale')) - .EditProfile__image-wrapper.relative.block.-fw(style=`background-image: url(${user.account.image.urls['mediumGrayscale']});`) - else - .EditProfile__image-wrapper.relative.block.-fw(style='background-image: url(/images/profile/placeholder.png);') - .EditProfile__image-placeholder-text.-h4.-ff-sec.-fw-700.absolute.top-0.-fw.-fh.flex.items-center.justify-center - p No picture - .col.col-4.px1 - .relative.-k-btn.btn-link.btn-outline.-primary.-sm - input.absolute.top-0.left-0.-fh.-fw(name="image" type="file" accept="image/*" style="opacity: 0;") - span.-fw-500.-primary Upload - - div - .pb1.-fw-500 Full name - input.-k-input.-fw.-ff-sec(name="fullname" type="text" value=user.account.fullname) - p(class="-on-error -danger -caption -fw-500 mt1") - - div - .pt2.pb1.-fw-500 Email (not public) - input.-k-input.-fw.-ff-sec(name="email" type="email" value=user.email) - p(class="-on-error -danger -caption -fw-500 mt1") - - div - .pt2.pb1.-fw-500 Phone (not public) - input.-k-input.-fw.-ff-sec(name="phone" type="tel" value=user.account.phone placeholder="Optional") - p(class="-on-error -danger -caption -fw-500 mt1") - - .clearfix.mxn1 - .col.col-6.px1 - .pt2.pb1.-fw-500 State - .-k-select - select.-fw(name="state") - option(value="") State - each state in US_STATES - option( - value= state - selected!= user.account.state === state - )= state - p(class="-on-error -danger -caption -fw-500 mt1") - .col.col-6.px1 - .pt2.pb1.-fw-500 ZIP - input.-k-input.-fw(name="zip" type="text" value=user.account.zip) - p(class="-on-error -danger -caption -fw-500 mt1") - - div - .pt2.pb1.-fw-500 Bio - textarea.-k-textarea.-fw.-ff-sec(name="bio" rows="4") #{user.account.bio} - - div - input.-k-input.-fw(name="socialLinks" type="hidden" value=user.account.socialLinks) - div - .pt2.pb1.-fw-500 Twitter link - input.-k-input.-fw.-ff-sec(name="twitter-url" value=user.account.socialLinks.twitter placeholder="https://twitter.com/username") - p(class="-on-error -danger -caption -fw-500 mt1") - div - .pt2.pb1.-fw-500 Facebook link - input.-k-input.-fw.-ff-sec(name="facebook-url" value=user.account.socialLinks.facebook placeholder="https://facebook.com/username") - p(class="-on-error -danger -caption -fw-500 mt1") - div - .pt2.pb1.-fw-500 Website - input.-k-input.-fw.-ff-sec(name="website-url" value=user.account.socialLinks.website placeholder="http://website.com") - p(class="-on-error -danger -caption -fw-500 mt1") - - div - .pt2.pb1.-fw-500 Privacy - label.-ff-sec.block.pb2 - input.mr1( - name="private" - type="checkbox" - checked=user.account.private - ) - | Make my profile private - - label.-ff-sec.block.pb2 - input.mr1( - name="disputesPrivate" - type="checkbox" - checked=user.account.disputesPrivate - ) - | Make my disputes private - - .pt3 - button.-k-btn.btn-primary.-fw.-fw-700(type="submit") Save changes - -block scripts - script. - window.addEventListener('load', function() { - var options = { - currentUser: !{JSON.stringify(UserRenderer(currentUser))}, - currentURL: !{JSON.stringify(currentURL)}, - }; - - new ViewUsersEdit(options); - }, true); diff --git a/views/users/new.pug b/views/users/new.pug deleted file mode 100644 index 710fdbf1..00000000 --- a/views/users/new.pug +++ /dev/null @@ -1,11 +0,0 @@ -extends ../layouts/application.pug - -block title - | Sign up — The Debt Collective - -block content - -block scripts - script. - window.addEventListener('load', function() { - }, true); diff --git a/views/users/show.pug b/views/users/show.pug deleted file mode 100644 index eef2f1b7..00000000 --- a/views/users/show.pug +++ /dev/null @@ -1,75 +0,0 @@ -extends ../layouts/application.pug - -block title - | #{user.account.fullname} — The Debt Collective - -block content - .ProfileWrapper.wrapper.py4.px2.flex.items-center.relative - - var _state = user.account.state.toLowerCase().replace(' ', '-') - - var _stateMapUrl = `/images/profile/states-US/${_state}.png` - .Profile__map.absolute.left-0.right-0.bottom-0.-bg-neutral-dark( - style={'background-image': `url(${_stateMapUrl})`} - ) - .max-width-3.mx-auto.relative.-fw - .clearfix - .Profile__image-wrapper.relative.right.sm-col-4.col-12 - if (user.account.image.exists('mediumGrayscale')) - img.block.-fw(src=user.account.image.urls['mediumGrayscale'] alt=user.account.fullname) - else - img.block.-fw(src='/images/profile/placeholder.png') - .col.sm-col-8.col-12 - .Profile__info-wrapper.flex.flex-column.pt3.pr3.pb2.pl3 - .flex-auto - h3.pb2= user.account.fullname - p.pb2.-caption.-fw-700.-accent= user.account.state - .-fw-700 - each line in user.account.bio.split(/\n/g) - p.pb1= line - .flex.pt4 - if currentUser && user._capabilities.edit - a(href=user._capabilities.edit.path class='-caption -fw-700' role='button' tabindex='0') - svg.mr1(width='11' height='11'): use(xlink:href='#svg-pencil') - | Edit profile - if user.account.socialLinks - ul.list-reset.center.flex-auto - each val, index in user.account.socialLinks - li.inline-block.align-top.mx2 - a.block.-white( - href=val - rel='noopener noreferrer' - target='_blank' - role='button' - tabindex='0' - title=`${user.account.fullname}’s ${index}` - ) - svg(width='18' height='18'): use(xlink:href=`#svg-${index}`) - .sr-only=`${user.account.fullname}’s ${index}` - - if user.disputes.length - .-bg-neutral-dark.px3.pb2 - hr.mb3 - h4.pb1.-accent My tools - ul.list-reset - each dispute in user.disputes - li - a.block.clearfix.py1.mxn1.-white(href=(currentUser.id === user.id ? routeMappings.Disputes.show.url(dispute.id) : '#')) - .col.col-8.px1 - .-fw-700 - | #{dispute.disputeTool.name} - if dispute.data.option !== 'none' - | / #{dispute.data.option} - .col.col-4.px1.right-align - .pt1.-caption.-ttu.-fw-700( - class=`-status-${dispute.statuses[0].status.toLowerCase().replace(/\W/g, '-')}` - )= dispute.statuses[0].status - -block scripts - script. - window.addEventListener('load', function() { - var options = { - currentUser: !{JSON.stringify(UserRenderer(currentUser))}, - currentURL: !{JSON.stringify(currentURL)}, - }; - - new ViewUsersShow(options); - }, true);