2016-04-04 12:36:30 +00:00
'use strict' ;
2017-12-10 20:44:35 +00:00
const log = require ( 'npmlog' ) ;
const config = require ( 'config' ) ;
const router = require ( '../lib/router-async' ) . create ( ) ;
const confirmations = require ( '../models/confirmations' ) ;
const subscriptions = require ( '../models/subscriptions' ) ;
const lists = require ( '../models/lists' ) ;
const fields = require ( '../models/fields' ) ;
const settings = require ( '../models/settings' ) ;
const _ = require ( '../lib/translate' ) . _ ;
const contextHelpers = require ( '../lib/context-helpers' ) ;
const forms = require ( '../models/forms' ) ;
const openpgp = require ( 'openpgp' ) ;
const util = require ( 'util' ) ;
const cors = require ( 'cors' ) ;
const cache = require ( 'memory-cache' ) ;
const geoip = require ( 'geoip-ultralight' ) ;
const passport = require ( '../lib/passport' ) ;
const tools = require ( '../lib/tools-async' ) ;
const helpers = require ( '../lib/helpers' ) ;
const mailHelpers = require ( '../lib/subscription-mail-helpers' ) ;
const interoperableErrors = require ( '../shared/interoperable-errors' ) ;
const mjml = require ( 'mjml' ) ;
const hbs = require ( 'hbs' ) ;
const mjmlTemplates = new Map ( ) ;
const objectHash = require ( 'object-hash' ) ;
const bluebird = require ( 'bluebird' ) ;
const fsReadFile = bluebird . promisify ( require ( 'fs' ) . readFile ) ;
const originWhitelist = config . cors && config . cors . origins || [ ] ;
const corsOptions = {
2017-04-03 17:53:01 +00:00
allowedHeaders : [ 'Content-Type' , 'Origin' , 'Accept' , 'X-Requested-With' ] ,
methods : [ 'GET' , 'POST' ] ,
optionsSuccessStatus : 200 , // IE11 chokes on 204
origin : ( origin , callback ) => {
if ( originWhitelist . includes ( origin ) ) {
callback ( null , true ) ;
} else {
2017-12-10 20:44:35 +00:00
const err = new Error ( _ ( 'Not allowed by CORS' ) ) ;
2017-04-03 17:53:01 +00:00
err . status = 403 ;
callback ( err ) ;
}
}
} ;
2017-12-10 20:44:35 +00:00
const corsOrCsrfProtection = ( req , res , next ) => {
2017-04-03 17:53:01 +00:00
if ( req . get ( 'X-Requested-With' ) === 'XMLHttpRequest' ) {
cors ( corsOptions ) ( req , res , next ) ;
} else {
passport . csrfProtection ( req , res , next ) ;
}
} ;
2016-04-04 12:36:30 +00:00
2017-12-10 20:44:35 +00:00
async function takeConfirmationAndValidate ( req , action , errorFactory ) {
const confirmation = await confirmations . takeConfirmation ( req . params . cid ) ;
2016-04-04 12:36:30 +00:00
2017-12-10 20:44:35 +00:00
if ( ! confirmation || confirmation . action !== action ) {
throw errorFactory ( ) ;
}
2016-04-04 12:36:30 +00:00
2017-12-10 20:44:35 +00:00
return confirmation ;
}
2016-04-04 12:36:30 +00:00
2017-12-10 20:44:35 +00:00
async function injectCustomFormData ( customFormId , viewKey , data ) {
function sortAndFilterCustomFieldsBy ( key ) {
data . customFields = data . customFields . filter ( fld => fld [ key ] !== null ) ;
data . customFields . sort ( ( a , b ) => a [ key ] - b [ key ] ) ;
}
2016-04-04 12:36:30 +00:00
2017-12-10 20:44:35 +00:00
if ( viewKey === 'web_subscribe' ) {
sortAndFilterCustomFieldsBy ( 'order_subscribe' ) ;
} else if ( viewKey === 'web_manage' ) {
sortAndFilterCustomFieldsBy ( 'order_manage' ) ;
}
if ( ! customFormId ) {
data . formInputStyle = '@import url(/subscription/form-input-style.css);' ;
return ;
}
const form = await forms . getById ( contextHelpers . getAdminContext ( ) , customFormId ) ;
data . template . template = form [ viewKey ] || data . template . template ;
data . template . layout = form . layout || data . template . layout ;
data . formInputStyle = form . formInputStyle || '@import url(/subscription/form-input-style.css);' ;
const configItems = await settings . get ( [ 'ua_code' ] ) ;
data . uaCode = configItems . uaCode ;
data . customSubscriptionScripts = config . customsubscriptionscripts || [ ] ;
2017-05-06 10:35:32 +00:00
}
2017-12-10 20:44:35 +00:00
async function getMjmlTemplate ( template ) {
let key = ( typeof template === 'object' ) ? objectHash ( template ) : template ;
2017-05-06 10:35:32 +00:00
2017-12-10 20:44:35 +00:00
if ( mjmlTemplates . has ( key ) ) {
return mjmlTemplates . get ( key ) ;
}
2017-05-06 10:35:32 +00:00
2017-12-10 20:44:35 +00:00
let source ;
if ( typeof template === 'object' ) {
source = await tools . mergeTemplateIntoLayout ( template . template , template . layout ) ;
} else {
source = await fsReadFile ( path . join ( _ _dirname , '..' , 'views' , template ) , 'utf-8' ) ;
}
2016-04-21 17:17:19 +00:00
2017-12-10 20:44:35 +00:00
const compiled = mjml . mjml2html ( source ) ;
if ( compiled . errors . length ) {
throw new Error ( compiled . errors [ 0 ] . message || compiled . errors [ 0 ] ) ;
}
2016-06-30 21:06:46 +00:00
2017-12-10 20:44:35 +00:00
const renderer = hbs . handlebars . compile ( compiled . html ) ;
mjmlTemplates . set ( key , renderer ) ;
2017-05-03 19:46:49 +00:00
2017-12-10 20:44:35 +00:00
return renderer ;
}
function captureFlashMessages ( req , res ) {
return new Promise ( ( resolve , reject ) => {
res . render ( 'subscription/capture-flash-messages' , { layout : null } , ( err , flash ) => {
reject ( err ) ;
resolve ( flash ) ;
2017-05-06 10:35:32 +00:00
} ) ;
2017-12-10 20:44:35 +00:00
} )
}
2017-05-03 19:46:49 +00:00
2017-04-30 17:01:22 +00:00
2017-05-03 19:46:49 +00:00
2017-12-10 20:44:35 +00:00
router . getAsync ( '/confirm/subscribe/:cid' , async ( req , res ) => {
const confirmation = await takeConfirmationAndValidate ( req , 'subscribe' , ( ) => new interoperableErrors . InvalidConfirmationForSubscriptionError ( 'Request invalid or already completed. If your subscription request is still pending, please subscribe again.' ) ) ;
const subscription = confirmation . data ;
2017-05-03 19:46:49 +00:00
2017-12-10 20:44:35 +00:00
const meta = {
cid : req . params . cid ,
ip : confirmation . ip ,
country : geoip . lookupCountry ( confirmation . ip ) || null
} ;
2017-05-03 19:46:49 +00:00
2017-12-10 20:44:35 +00:00
subscription . status = SubscriptionStatus . SUBSCRIBED ;
2017-05-03 19:46:49 +00:00
2017-12-10 20:44:35 +00:00
await subscriptions . create ( contextHelpers . getAdminContext ( ) , confirmation . list , subscription , meta ) ;
const list = await lists . getById ( contextHelpers . getAdminContext ( ) , confirmation . list ) ;
await mailHelpers . sendSubscriptionConfirmed ( list , subscription . email , subscription ) ;
res . redirect ( '/subscription/' + list . cid + '/subscribed-notice' ) ;
2017-05-06 10:35:32 +00:00
} ) ;
2017-05-03 19:46:49 +00:00
2017-12-10 20:44:35 +00:00
router . getAsync ( '/confirm/change-address/:cid' , async ( req , res ) => {
const confirmation = await takeConfirmationAndValidate ( req , 'change-address' , ( ) => new interoperableErrors . InvalidConfirmationForAddressChangeError ( 'Request invalid or already completed. If your address change request is still pending, please change the address again.' ) ) ;
const data = confirmation . data ;
2017-05-03 19:46:49 +00:00
2017-12-10 20:44:35 +00:00
const subscription = await subscriptions . updateAddressAndGet ( contextHelpers . getAdminContext ( ) , list . id , data . subscriptionId , data . emailNew ) ;
2017-05-03 19:46:49 +00:00
2017-12-10 20:44:35 +00:00
await mailHelpers . sendSubscriptionConfirmed ( list , data . emailNew , subscription ) ;
2017-05-03 19:46:49 +00:00
2017-12-10 20:44:35 +00:00
req . flash ( 'info' , _ ( 'Email address changed' ) ) ;
res . redirect ( '/subscription/' + list . cid + '/manage/' + subscription . cid ) ;
} ) ;
2017-05-03 19:46:49 +00:00
2017-12-10 20:44:35 +00:00
router . getAsync ( '/confirm/unsubscribe/:cid' , async ( req , res ) => {
const confirmation = await takeConfirmationAndValidate ( req , 'unsubscribe' , ( ) => new interoperableErrors . InvalidConfirmationForUnsubscriptionError ( 'Request invalid or already completed. If your unsubscription request is still pending, please unsubscribe again.' ) ) ;
const data = confirmation . data ;
const subscription = await subscriptions . unsubscribeAndGet ( contextHelpers . getAdminContext ( ) , list . id , data . subscriptionId ) ;
await mailHelpers . sendUnsubscriptionConfirmed ( list , subscription . email , subscription ) ;
res . redirect ( '/subscription/' + list . cid + '/unsubscribed-notice' ) ;
2016-04-04 12:36:30 +00:00
} ) ;
2017-12-10 20:44:35 +00:00
router . getAsync ( '/:cid' , passport . csrfProtection , async ( req , res ) => {
const list = await lists . getByCid ( req . params . cid ) ;
2016-04-04 12:36:30 +00:00
2017-12-10 20:44:35 +00:00
if ( ! list . publicSubscribe ) {
throw new interoperableErrors . SubscriptionNotAllowedError ( 'The list does not allow public subscriptions.' ) ;
}
2017-04-30 17:01:22 +00:00
2017-12-10 20:44:35 +00:00
const ucid = req . query . cid ;
2016-04-04 12:36:30 +00:00
2017-12-10 20:44:35 +00:00
const data = { } ;
data . layout = 'subscription/layout' ;
data . title = list . name ;
data . cid = list . cid ;
data . csrfToken = req . csrfToken ( ) ;
2016-04-04 12:36:30 +00:00
2017-12-10 20:44:35 +00:00
let subscription ;
if ( ucid ) {
subscription = await subscriptions . getById ( contextHelpers . getAdminContext ( ) , list . id , ucid ) ;
}
2017-03-19 12:36:57 +00:00
2017-12-10 20:44:35 +00:00
data . customFields = fields . getRow ( contextHelpers . getAdminContext ( ) , list . id , subscription ) ;
data . useEditor = true ;
2017-05-04 21:42:46 +00:00
2017-12-10 20:44:35 +00:00
const configItems = await settings . get ( [ 'pgpPrivateKey' , 'defaultAddress' , 'defaultPostaddress' ] ) ;
data . hasPubkey = ! ! configItems . pgpPrivateKey ;
data . defaultAddress = configItems . defaultAddress ;
data . defaultPostaddress = configItems . defaultPostaddress ;
2017-05-04 21:42:46 +00:00
2017-12-10 20:44:35 +00:00
data . template = {
template : 'subscription/web-subscribe.mjml.hbs' ,
layout : 'subscription/layout.mjml.hbs'
} ;
2017-05-04 21:42:46 +00:00
2017-12-10 20:44:35 +00:00
await injectCustomFormData ( req . query . fid || list . defaultForm , 'subscription/web-subscribe' , data ) ;
2017-05-04 21:42:46 +00:00
2017-12-10 20:44:35 +00:00
const htmlRenderer = await getMjmlTemplate ( data . template ) ;
data . isWeb = true ;
data . needsJsWarning = true ;
data . flashMessages = await captureFlashMessages ( res ) ;
res . send ( htmlRenderer ( data ) ) ;
2016-04-04 12:36:30 +00:00
} ) ;
2017-12-10 20:44:35 +00:00
2017-04-03 17:53:01 +00:00
router . options ( '/:cid/widget' , cors ( corsOptions ) ) ;
2017-12-10 20:44:35 +00:00
router . getAsync ( '/:cid/widget' , cors ( corsOptions ) , async ( req , res ) => {
req . needsAPIJSONResponse = true ;
const cached = cache . get ( req . path ) ;
2017-04-03 17:53:01 +00:00
if ( cached ) {
return res . status ( 200 ) . json ( cached ) ;
}
2017-12-10 20:44:35 +00:00
const list = await lists . getByCid ( req . params . cid ) ;
2017-04-03 17:53:01 +00:00
2017-12-10 20:44:35 +00:00
const configItems = settings . get ( [ 'serviceUrl' , 'pgpPrivateKey' ] ) ;
2017-04-03 17:53:01 +00:00
2017-12-10 20:44:35 +00:00
const data = {
title : list . name ,
cid : list . cid ,
serviceUrl : configItems . serviceUrl ,
hasPubkey : ! ! configItems . pgpPrivateKey ,
customFields : fields . getRow ( contextHelpers . getAdminContext ( ) , list . id ) ,
template : { } ,
layout : null ,
} ;
2017-04-03 17:53:01 +00:00
2017-12-10 20:44:35 +00:00
await injectCustomFormData ( req . query . fid || list . defaultForm , 'subscription/web-subscribe' , data ) ;
2017-04-03 17:53:01 +00:00
2017-12-10 20:44:35 +00:00
const renderAsync = bluebird . promisify ( res . render ) ;
const html = await renderAsync ( 'subscription/widget-subscribe' , data ) ;
2017-04-03 17:53:01 +00:00
2017-12-10 20:44:35 +00:00
const response = {
data : {
title : data . title ,
cid : data . cid ,
html
}
} ;
cache . put ( req . path , response , 30000 ) ; // ms
res . status ( 200 ) . json ( response ) ;
2017-04-03 17:53:01 +00:00
} ) ;
2017-12-10 20:44:35 +00:00
2017-04-03 17:53:01 +00:00
router . options ( '/:cid/subscribe' , cors ( corsOptions ) ) ;
2017-12-10 20:44:35 +00:00
router . postAsync ( '/:cid/subscribe' , passport . parseForm , corsOrCsrfProtection , async ( req , res ) => {
const email = ( req . body . email || '' ) . toString ( ) . trim ( ) ;
if ( req . xhr ) {
req . needsAPIJSONResponse = true ;
}
2017-04-03 17:53:01 +00:00
2016-04-04 12:36:30 +00:00
if ( ! email ) {
2017-04-03 17:53:01 +00:00
if ( req . xhr ) {
2017-12-10 20:44:35 +00:00
throw new Error ( 'Email address not set' ) ;
2017-04-03 17:53:01 +00:00
}
2017-12-10 20:44:35 +00:00
2017-03-07 14:30:56 +00:00
req . flash ( 'danger' , _ ( 'Email address not set' ) ) ;
2016-04-04 12:36:30 +00:00
return res . redirect ( '/subscription/' + encodeURIComponent ( req . params . cid ) + '?' + tools . queryParams ( req . body ) ) ;
}
2017-12-10 20:44:35 +00:00
const emailErr = await tools . validateEmail ( email ) ;
if ( emailErr ) {
if ( req . xhr ) {
throw new Error ( emailErr . message ) ;
2017-04-30 14:51:47 +00:00
}
2017-12-10 20:44:35 +00:00
req . flash ( 'danger' , emailErr . message ) ;
return res . redirect ( '/subscription/' + encodeURIComponent ( req . params . cid ) + '?' + tools . queryParams ( req . body ) ) ;
}
2016-09-08 14:49:01 +00:00
2017-12-10 20:44:35 +00:00
// Check if the subscriber seems legit. This is a really simple check, the only requirement is that
// the subscriber has JavaScript turned on and thats it. If Mailtrain gets more targeted then this
// simple check should be replaced with an actual captcha
let subTime = Number ( req . body . sub ) || 0 ;
// allow clock skew 24h in the past and 24h to the future
let subTimeTest = ! ! ( subTime > Date . now ( ) - 24 * 3600 * 1000 && subTime < Date . now ( ) + 24 * 3600 * 1000 ) ;
let addressTest = ! req . body . address ;
let testsPass = subTimeTest && addressTest ;
2016-04-04 12:36:30 +00:00
2017-12-10 20:44:35 +00:00
const list = await lists . getByCid ( req . params . cid ) ;
2017-05-03 19:46:49 +00:00
2017-12-10 20:44:35 +00:00
if ( ! list . publicSubscribe ) {
throw new interoperableErrors . SubscriptionNotAllowedError ( 'The list does not allow public subscriptions.' ) ;
}
2016-04-04 12:36:30 +00:00
2017-12-10 20:44:35 +00:00
let subscriptionData = { } ;
Object . keys ( req . body ) . forEach ( key => {
if ( key !== 'email' && key . charAt ( 0 ) !== '_' ) {
subscriptionData [ key ] = ( req . body [ key ] || '' ) . toString ( ) . trim ( ) ;
}
2016-04-04 12:36:30 +00:00
} ) ;
2017-12-10 20:44:35 +00:00
const subscription = subscriptions . getByEmail ( list . id , email )
if ( subscription && subscription . status === subscriptions . Status . SUBSCRIBED ) {
await mailHelpers . sendAlreadySubscribed ( list , email , subscription ) ;
res . redirect ( '/subscription/' + req . params . cid + '/confirm-subscription-notice' ) ;
} else {
const data = {
email ,
subscriptionData
} ;
const confirmCid = await confirmations . addConfirmation ( list . id , 'subscribe' , req . ip , data ) ;
if ( ! testsPass ) {
log . info ( 'Subscription' , 'Confirmation message for %s marked to be skipped (%s)' , email , JSON . stringify ( data ) ) ;
} else {
await mailHelpers . sendConfirmSubscription ( list , email , confirmCid , subscriptionData ) ;
2016-04-04 12:36:30 +00:00
}
2017-12-10 20:44:35 +00:00
if ( req . xhr ) {
return res . status ( 200 ) . json ( {
msg : _ ( 'Please Confirm Subscription' )
} ) ;
2016-04-04 12:36:30 +00:00
}
2017-12-10 20:44:35 +00:00
res . redirect ( '/subscription/' + req . params . cid + '/confirm-subscription-notice' ) ;
}
} ) ;
2016-04-04 12:36:30 +00:00
2017-12-10 20:44:35 +00:00
router . getAsync ( '/:lcid/manage/:ucid' , passport . csrfProtection , async ( req , res ) => {
const list = await lists . getByCid ( req . params . lcid ) ;
const subscription = await subscriptions . getByCid ( contextHelpers . getAdminContext ( ) , list . id , req . params . ucid ) ;
2016-04-04 12:36:30 +00:00
2017-12-10 20:44:35 +00:00
if ( ! subscription || subscription . status !== subscriptions . Status . SUBSCRIBED ) {
throw new Error ( _ ( 'Subscription not found in this list' ) ) ;
}
2016-04-04 12:36:30 +00:00
2017-12-10 20:44:35 +00:00
subscription . lcid = req . params . lcid ;
subscription . title = list . name ;
subscription . csrfToken = req . csrfToken ( ) ;
subscription . layout = 'subscription/layout' ;
subscription . customFields = await fields . getRow ( contextHelpers . getAdminContext ( ) , list . id , subscription ) ;
subscription . useEditor = true ;
const configItems = await settings . get ( [ 'pgpPrivateKey' , 'defaultAddress' , 'defaultPostaddress' ] ) ;
subscription . hasPubkey = ! ! configItems . pgpPrivateKey ;
subscription . defaultAddress = configItems . defaultAddress ;
subscription . defaultPostaddress = configItems . defaultPostaddress ;
subscription . template = {
template : 'subscription/web-manage.mjml.hbs' ,
layout : 'subscription/layout.mjml.hbs'
} ;
await injectCustomFormData ( req . query . fid || list . defaultForm , 'subscription/web-manage' , subscription ) ;
const htmlRenderer = await getMjmlTemplate ( data . template ) ;
data . isWeb = true ;
data . needsJsWarning = true ;
data . isManagePreferences = true ;
data . flashMessages = await captureFlashMessages ( res ) ;
res . send ( htmlRenderer ( data ) ) ;
2016-04-04 12:36:30 +00:00
} ) ;
2017-12-10 20:44:35 +00:00
router . postAsync ( '/:lcid/manage' , passport . parseForm , passport . csrfProtection , async ( req , res ) => {
const list = await lists . getByCid ( req . params . lcid ) ;
const subscription = await subscriptions . getByCid ( contextHelpers . getAdminContext ( ) , list . id , req . body . cid ) ;
2016-04-04 12:36:30 +00:00
2017-12-10 20:44:35 +00:00
if ( ! subscription || subscription . status !== subscriptions . Status . SUBSCRIBED ) {
throw new Error ( _ ( 'Subscription not found in this list' ) ) ;
}
2016-04-04 12:36:30 +00:00
2017-05-04 21:42:46 +00:00
2017-12-10 20:44:35 +00:00
delete req . body . email ; // email change is not allowed
delete req . body . status ; // status change is not allowed
2017-05-04 21:42:46 +00:00
2017-12-10 20:44:35 +00:00
// FIXME - az sem
// FIXME, allow update of only fields that have order_manage
await subscriptions . updateWithConsistencyCheck ( contextHelpers . getAdminContext ( ) , list . id , subscription )
subscriptions . update ( list . id , subscription . cid , req . body , false , err => {
if ( err ) {
return next ( err ) ;
}
res . redirect ( '/subscription/' + req . params . lcid + '/updated-notice' ) ;
2016-04-04 12:36:30 +00:00
} ) ;
} ) ;
2016-12-07 14:12:26 +00:00
router . get ( '/:lcid/manage-address/:ucid' , passport . csrfProtection , ( req , res , next ) => {
lists . getByCid ( req . params . lcid , ( err , list ) => {
if ( ! err && ! list ) {
2017-03-07 14:30:56 +00:00
err = new Error ( _ ( 'Selected list not found' ) ) ;
2016-12-07 14:12:26 +00:00
err . status = 404 ;
}
if ( err ) {
return next ( err ) ;
}
2017-03-19 12:36:57 +00:00
settings . list ( [ 'defaultAddress' , 'defaultPostaddress' ] , ( err , configItems ) => {
if ( err ) {
return next ( err ) ;
2016-12-07 14:12:26 +00:00
}
2017-03-19 12:36:57 +00:00
subscriptions . get ( list . id , req . params . ucid , ( err , subscription ) => {
2017-05-04 21:42:46 +00:00
if ( ! err && ( ! subscription || subscription . status !== subscriptions . Status . SUBSCRIBED ) ) {
err = new Error ( _ ( 'Subscription not found in this list' ) ) ;
2017-03-19 12:36:57 +00:00
err . status = 404 ;
}
subscription . lcid = req . params . lcid ;
subscription . title = list . name ;
subscription . csrfToken = req . csrfToken ( ) ;
subscription . defaultAddress = configItems . defaultAddress ;
subscription . defaultPostaddress = configItems . defaultPostaddress ;
subscription . template = {
template : 'subscription/web-manage-address.mjml.hbs' ,
layout : 'subscription/layout.mjml.hbs'
} ;
helpers . injectCustomFormData ( req . query . fid || list . defaultForm , 'subscription/web-manage-address' , subscription , ( err , data ) => {
if ( err ) {
return next ( err ) ;
}
helpers . getMjmlTemplate ( data . template , ( err , htmlRenderer ) => {
if ( err ) {
return next ( err ) ;
}
helpers . captureFlashMessages ( req , res , ( err , flash ) => {
if ( err ) {
return next ( err ) ;
}
2016-12-07 14:12:26 +00:00
2017-03-19 12:36:57 +00:00
data . isWeb = true ;
data . needsJsWarning = true ;
data . flashMessages = flash ;
res . send ( htmlRenderer ( data ) ) ;
} ) ;
} ) ;
} ) ;
} ) ;
2016-12-07 14:12:26 +00:00
} ) ;
} ) ;
} ) ;
router . post ( '/:lcid/manage-address' , passport . parseForm , passport . csrfProtection , ( req , res , next ) => {
lists . getByCid ( req . params . lcid , ( err , list ) => {
if ( ! err && ! list ) {
2017-03-07 14:30:56 +00:00
err = new Error ( _ ( 'Selected list not found' ) ) ;
2016-12-07 14:12:26 +00:00
err . status = 404 ;
}
if ( err ) {
return next ( err ) ;
}
2017-05-04 21:42:46 +00:00
let bodyData = tools . convertKeys ( req . body ) ; // This is here to convert "email-new" to "emailNew"
const emailOld = ( bodyData . email || '' ) . toString ( ) . trim ( ) ;
const emailNew = ( bodyData . emailNew || '' ) . toString ( ) . trim ( ) ;
2017-05-03 19:46:49 +00:00
2017-05-04 21:42:46 +00:00
if ( emailOld === emailNew ) {
req . flash ( 'info' , _ ( 'Nothing seems to be changed' ) ) ;
res . redirect ( '/subscription/' + req . params . lcid + '/manage/' + req . body . cid ) ;
2016-12-07 14:12:26 +00:00
2017-05-04 21:42:46 +00:00
} else {
subscriptions . updateAddressCheck ( list , req . body . cid , emailNew , req . ip , ( err , subscription , newEmailAvailable ) => {
2017-05-03 19:46:49 +00:00
if ( err ) {
return next ( err ) ;
}
2017-05-04 21:42:46 +00:00
function sendWebResponse ( err ) {
2017-05-03 19:46:49 +00:00
if ( err ) {
return next ( err ) ;
}
2017-05-04 21:42:46 +00:00
req . flash ( 'info' , _ ( 'An email with further instructions has been sent to the provided address' ) ) ;
res . redirect ( '/subscription/' + req . params . lcid + '/manage/' + req . body . cid ) ;
}
2017-05-03 19:46:49 +00:00
2017-05-04 21:42:46 +00:00
if ( newEmailAvailable ) {
const data = {
subscriptionId : subscription . id ,
emailNew
} ;
confirmations . addConfirmation ( list . id , 'change-address' , req . ip , data , ( err , confirmCid ) => {
if ( err ) {
return next ( err ) ;
}
mailHelpers . sendConfirmAddressChange ( list , emailNew , confirmCid , subscription , sendWebResponse ) ;
} ) ;
} else {
mailHelpers . sendAlreadySubscribed ( list , emailNew , subscription , sendWebResponse ) ;
}
} ) ;
}
2016-12-07 14:12:26 +00:00
} ) ;
} ) ;
2016-04-04 12:36:30 +00:00
router . get ( '/:lcid/unsubscribe/:ucid' , passport . csrfProtection , ( req , res , next ) => {
lists . getByCid ( req . params . lcid , ( err , list ) => {
if ( ! err && ! list ) {
2017-03-07 14:30:56 +00:00
err = new Error ( _ ( 'Selected list not found' ) ) ;
2016-04-04 12:36:30 +00:00
err . status = 404 ;
}
if ( err ) {
return next ( err ) ;
}
2017-03-19 12:36:57 +00:00
settings . list ( [ 'defaultAddress' , 'defaultPostaddress' ] , ( err , configItems ) => {
2016-04-04 12:36:30 +00:00
if ( err ) {
return next ( err ) ;
}
2017-03-19 12:36:57 +00:00
subscriptions . get ( list . id , req . params . ucid , ( err , subscription ) => {
2017-05-04 21:42:46 +00:00
if ( ! err && ( ! subscription || subscription . status !== subscriptions . Status . SUBSCRIBED ) ) {
2017-05-03 19:46:49 +00:00
err = new Error ( _ ( 'Subscription not found in this list' ) ) ;
2017-03-19 12:36:57 +00:00
err . status = 404 ;
}
if ( err ) {
return next ( err ) ;
}
2017-05-26 22:43:56 +00:00
const autoUnsubscribe = req . query . auto === 'yes' ;
if ( autoUnsubscribe ) {
handleUnsubscribe ( list , subscription , autoUnsubscribe , req . query . c , req . ip , res , next ) ;
} else if ( req . query . formTest ||
2017-05-04 21:42:46 +00:00
list . unsubscriptionMode === lists . UnsubscriptionMode . ONE _STEP _WITH _FORM ||
2017-05-03 19:46:49 +00:00
list . unsubscriptionMode === lists . UnsubscriptionMode . TWO _STEP _WITH _FORM ) {
2017-03-19 12:36:57 +00:00
2017-05-03 19:46:49 +00:00
subscription . lcid = req . params . lcid ;
subscription . ucid = req . params . ucid ;
subscription . title = list . name ;
subscription . csrfToken = req . csrfToken ( ) ;
subscription . campaign = req . query . c ;
subscription . defaultAddress = configItems . defaultAddress ;
subscription . defaultPostaddress = configItems . defaultPostaddress ;
2017-03-19 12:36:57 +00:00
2017-05-03 19:46:49 +00:00
subscription . template = {
template : 'subscription/web-unsubscribe.mjml.hbs' ,
layout : 'subscription/layout.mjml.hbs'
} ;
helpers . injectCustomFormData ( req . query . fid || list . defaultForm , 'subscription/web-unsubscribe' , subscription , ( err , data ) => {
2017-03-19 12:36:57 +00:00
if ( err ) {
return next ( err ) ;
}
2017-05-03 19:46:49 +00:00
helpers . getMjmlTemplate ( data . template , ( err , htmlRenderer ) => {
2017-03-19 12:36:57 +00:00
if ( err ) {
return next ( err ) ;
}
2017-05-03 19:46:49 +00:00
helpers . captureFlashMessages ( req , res , ( err , flash ) => {
if ( err ) {
return next ( err ) ;
}
data . isWeb = true ;
data . flashMessages = flash ;
res . send ( htmlRenderer ( data ) ) ;
} ) ;
2017-03-19 12:36:57 +00:00
} ) ;
} ) ;
2017-05-03 19:46:49 +00:00
} else { // UnsubscriptionMode.ONE_STEP || UnsubscriptionMode.TWO_STEP || UnsubscriptionMode.MANUAL
2017-05-26 22:43:56 +00:00
handleUnsubscribe ( list , subscription , autoUnsubscribe , req . query . c , req . ip , res , next ) ;
2017-05-03 19:46:49 +00:00
}
2017-03-19 12:36:57 +00:00
} ) ;
2016-04-04 12:36:30 +00:00
} ) ;
} ) ;
} ) ;
router . post ( '/:lcid/unsubscribe' , passport . parseForm , passport . csrfProtection , ( req , res , next ) => {
lists . getByCid ( req . params . lcid , ( err , list ) => {
if ( ! err && ! list ) {
2017-03-07 14:30:56 +00:00
err = new Error ( _ ( 'Selected list not found' ) ) ;
2016-04-04 12:36:30 +00:00
err . status = 404 ;
}
if ( err ) {
return next ( err ) ;
}
2017-05-03 19:46:49 +00:00
const campaignId = ( req . body . campaign || '' ) . toString ( ) . trim ( ) || false ;
subscriptions . get ( list . id , req . body . ucid , ( err , subscription ) => {
2017-05-04 21:42:46 +00:00
if ( ! err && ( ! subscription || subscription . status !== subscriptions . Status . SUBSCRIBED ) ) {
2017-05-03 19:46:49 +00:00
err = new Error ( _ ( 'Subscription not found in this list' ) ) ;
err . status = 404 ;
}
2016-04-04 12:36:30 +00:00
if ( err ) {
2017-05-03 19:46:49 +00:00
return next ( err ) ;
2016-04-04 12:36:30 +00:00
}
2016-04-21 17:17:19 +00:00
2017-05-26 22:43:56 +00:00
handleUnsubscribe ( list , subscription , false , campaignId , req . ip , res , next ) ;
2017-05-03 19:46:49 +00:00
} ) ;
} ) ;
} ) ;
2017-05-26 22:43:56 +00:00
function handleUnsubscribe ( list , subscription , autoUnsubscribe , campaignId , ip , res , next ) {
if ( ( list . unsubscriptionMode === lists . UnsubscriptionMode . ONE _STEP || list . unsubscriptionMode === lists . UnsubscriptionMode . ONE _STEP _WITH _FORM ) ||
( autoUnsubscribe && ( list . unsubscriptionMode === lists . UnsubscriptionMode . TWO _STEP || list . unsubscriptionMode === lists . UnsubscriptionMode . TWO _STEP _WITH _FORM ) ) ) {
2017-05-03 19:46:49 +00:00
2017-05-26 22:43:56 +00:00
subscriptions . changeStatus ( list . id , subscription . id , campaignId , subscriptions . Status . UNSUBSCRIBED , ( err , found ) => {
2017-05-03 19:46:49 +00:00
if ( err ) {
return next ( err ) ;
}
2017-06-10 23:26:15 +00:00
2017-05-26 22:43:56 +00:00
// TODO: Shall we do anything with "found"?
2017-05-03 19:46:49 +00:00
2017-05-26 22:43:56 +00:00
mailHelpers . sendUnsubscriptionConfirmed ( list , subscription . email , subscription , err => {
2016-04-21 17:17:19 +00:00
if ( err ) {
2017-05-03 19:46:49 +00:00
return next ( err ) ;
2016-04-21 17:17:19 +00:00
}
2017-05-26 22:43:56 +00:00
res . redirect ( '/subscription/' + list . cid + '/unsubscribed-notice' ) ;
2016-04-21 17:17:19 +00:00
} ) ;
2016-04-04 12:36:30 +00:00
} ) ;
2017-05-03 19:46:49 +00:00
2017-05-26 22:43:56 +00:00
} else if ( list . unsubscriptionMode === lists . UnsubscriptionMode . TWO _STEP || list . unsubscriptionMode === lists . UnsubscriptionMode . TWO _STEP _WITH _FORM ) {
2017-05-03 19:46:49 +00:00
2017-05-26 22:43:56 +00:00
const data = {
subscriptionId : subscription . id ,
campaignId
} ;
confirmations . addConfirmation ( list . id , 'unsubscribe' , ip , data , ( err , confirmCid ) => {
2017-05-03 19:46:49 +00:00
if ( err ) {
return next ( err ) ;
}
2017-05-26 22:43:56 +00:00
mailHelpers . sendConfirmUnsubscription ( list , subscription . email , confirmCid , subscription , err => {
2017-05-03 19:46:49 +00:00
if ( err ) {
return next ( err ) ;
}
2017-05-26 22:43:56 +00:00
res . redirect ( '/subscription/' + list . cid + '/confirm-unsubscription-notice' ) ;
2017-05-03 19:46:49 +00:00
} ) ;
} ) ;
2017-05-26 22:43:56 +00:00
2017-05-03 19:46:49 +00:00
} else { // UnsubscriptionMode.MANUAL
res . redirect ( '/subscription/' + list . cid + '/manual-unsubscribe-notice' ) ;
}
}
2016-04-04 12:36:30 +00:00
2017-04-30 14:51:47 +00:00
router . get ( '/:cid/confirm-subscription-notice' , ( req , res , next ) => {
2017-05-03 19:46:49 +00:00
webNotice ( 'confirm-subscription' , req , res , next ) ;
2017-04-30 14:51:47 +00:00
} ) ;
router . get ( '/:cid/confirm-unsubscription-notice' , ( req , res , next ) => {
2017-05-03 19:46:49 +00:00
webNotice ( 'confirm-unsubscription' , req , res , next ) ;
2017-04-30 14:51:47 +00:00
} ) ;
router . get ( '/:cid/subscribed-notice' , ( req , res , next ) => {
2017-05-03 19:46:49 +00:00
webNotice ( 'subscribed' , req , res , next ) ;
2017-04-30 14:51:47 +00:00
} ) ;
router . get ( '/:cid/updated-notice' , ( req , res , next ) => {
2017-05-03 19:46:49 +00:00
webNotice ( 'updated' , req , res , next ) ;
2017-04-30 14:51:47 +00:00
} ) ;
router . get ( '/:cid/unsubscribed-notice' , ( req , res , next ) => {
2017-05-03 19:46:49 +00:00
webNotice ( 'unsubscribed' , req , res , next ) ;
} ) ;
router . get ( '/:cid/manual-unsubscribe-notice' , ( req , res , next ) => {
webNotice ( 'manual-unsubscribe' , req , res , next ) ;
2017-04-30 14:51:47 +00:00
} ) ;
2017-04-03 17:53:01 +00:00
router . post ( '/publickey' , passport . parseForm , ( req , res , next ) => {
2016-04-16 21:09:23 +00:00
settings . list ( [ 'pgpPassphrase' , 'pgpPrivateKey' ] , ( err , configItems ) => {
if ( err ) {
return next ( err ) ;
}
if ( ! configItems . pgpPrivateKey ) {
2017-03-07 14:30:56 +00:00
err = new Error ( _ ( 'Public key is not set' ) ) ;
2016-04-16 21:09:23 +00:00
err . status = 404 ;
return next ( err ) ;
}
let privKey ;
try {
privKey = openpgp . key . readArmored ( configItems . pgpPrivateKey ) . keys [ 0 ] ;
if ( configItems . pgpPassphrase && ! privKey . decrypt ( configItems . pgpPassphrase ) ) {
privKey = false ;
}
} catch ( E ) {
// just ignore if failed
}
if ( ! privKey ) {
2017-03-07 14:30:56 +00:00
err = new Error ( _ ( 'Public key is not set' ) ) ;
2016-04-16 21:09:23 +00:00
err . status = 404 ;
return next ( err ) ;
}
let pubkey = privKey . toPublic ( ) . armor ( ) ;
res . writeHead ( 200 , {
'Content-Type' : 'application/octet-stream' ,
'Content-Disposition' : 'attachment; filename=public.asc'
} ) ;
res . end ( pubkey ) ;
} ) ;
} ) ;
2017-04-30 14:51:47 +00:00
2017-05-03 19:46:49 +00:00
function webNotice ( type , req , res , next ) {
2017-04-30 14:51:47 +00:00
lists . getByCid ( req . params . cid , ( err , list ) => {
if ( ! err && ! list ) {
err = new Error ( _ ( 'Selected list not found' ) ) ;
err . status = 404 ;
}
if ( err ) {
return next ( err ) ;
}
2017-05-03 19:46:49 +00:00
settings . list ( [ 'defaultHomepage' , 'serviceUrl' , 'defaultAddress' , 'defaultPostaddress' , 'adminEmail' ] , ( err , configItems ) => {
2017-04-30 14:51:47 +00:00
if ( err ) {
return next ( err ) ;
}
let data = {
title : list . name ,
homepage : configItems . defaultHomepage || configItems . serviceUrl ,
defaultAddress : configItems . defaultAddress ,
defaultPostaddress : configItems . defaultPostaddress ,
2017-05-03 19:46:49 +00:00
contactAddress : configItems . defaultAddress ,
2017-04-30 14:51:47 +00:00
template : {
template : 'subscription/web-' + type + '-notice.mjml.hbs' ,
layout : 'subscription/layout.mjml.hbs'
}
} ;
helpers . injectCustomFormData ( req . query . fid || list . defaultForm , 'subscription/web-' + type + '-notice' , data , ( err , data ) => {
if ( err ) {
return next ( err ) ;
}
helpers . getMjmlTemplate ( data . template , ( err , htmlRenderer ) => {
if ( err ) {
return next ( err ) ;
}
helpers . captureFlashMessages ( req , res , ( err , flash ) => {
if ( err ) {
return next ( err ) ;
}
data . isWeb = true ;
data . isConfirmNotice = true ;
data . flashMessages = flash ;
res . send ( htmlRenderer ( data ) ) ;
} ) ;
} ) ;
} ) ;
} ) ;
} ) ;
}
2016-04-04 12:36:30 +00:00
module . exports = router ;