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' ) ;
2018-01-27 15:37:14 +00:00
const shares = require ( '../models/shares' ) ;
2017-12-10 20:44:35 +00:00
const settings = require ( '../models/settings' ) ;
const _ = require ( '../lib/translate' ) . _ ;
const contextHelpers = require ( '../lib/context-helpers' ) ;
const forms = require ( '../models/forms' ) ;
2018-04-29 16:13:40 +00:00
const { getTrustedUrl } = require ( '../lib/urls' ) ;
2018-05-09 02:07:01 +00:00
const bluebird = require ( 'bluebird' ) ;
2017-12-10 20:44:35 +00:00
2017-12-30 11:23:16 +00:00
const { SubscriptionStatus } = require ( '../shared/lists' ) ;
2017-12-10 20:44:35 +00:00
const openpgp = require ( 'openpgp' ) ;
const cors = require ( 'cors' ) ;
const cache = require ( 'memory-cache' ) ;
const geoip = require ( 'geoip-ultralight' ) ;
const passport = require ( '../lib/passport' ) ;
2018-04-29 16:13:40 +00:00
const tools = require ( '../lib/tools' ) ;
2017-12-10 20:44:35 +00:00
const mailHelpers = require ( '../lib/subscription-mail-helpers' ) ;
const interoperableErrors = require ( '../shared/interoperable-errors' ) ;
2018-01-28 22:59:05 +00:00
const { cleanupFromPost } = require ( '../lib/helpers' ) ;
2017-12-10 20:44:35 +00:00
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 ) {
2018-04-29 16:13:40 +00:00
data . formInputStyle = '@import url(/public/subscription/form-input-style.css);' ;
2017-12-10 20:44:35 +00:00
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 ;
2018-04-29 16:13:40 +00:00
data . formInputStyle = form . formInputStyle || '@import url(/public/subscription/form-input-style.css);' ;
2017-12-10 20:44:35 +00:00
2018-04-29 16:13:40 +00:00
const configItems = await settings . get ( contextHelpers . getAdminContext ( ) , [ 'uaCode' ] ) ;
2017-12-10 20:44:35 +00:00
data . uaCode = configItems . uaCode ;
2018-04-02 09:58:32 +00:00
data . customSubscriptionScripts = config . customSubscriptionScripts || [ ] ;
2017-05-06 10:35:32 +00:00
}
2018-01-27 15:37:14 +00:00
async function captureFlashMessages ( res ) {
const renderAsync = bluebird . promisify ( res . render . bind ( res ) ) ;
return await renderAsync ( 'subscription/capture-flash-messages' , { layout : null } ) ;
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-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.' ) ) ;
2018-01-27 15:37:14 +00:00
const data = confirmation . data ;
2017-05-03 19:46:49 +00:00
2017-12-10 20:44:35 +00:00
const meta = {
ip : confirmation . ip ,
2018-01-27 15:37:14 +00:00
country : geoip . lookupCountry ( confirmation . ip ) || null ,
2018-02-13 22:50:13 +00:00
updateOfUnsubscribedAllowed : true
2017-12-10 20:44:35 +00:00
} ;
2017-05-03 19:46:49 +00:00
2018-01-27 15:37:14 +00:00
const subscription = data . subscriptionData ;
subscription . email = data . email ;
2017-12-10 20:44:35 +00:00
subscription . status = SubscriptionStatus . SUBSCRIBED ;
2017-05-03 19:46:49 +00:00
2018-01-27 15:37:14 +00:00
try {
await subscriptions . create ( contextHelpers . getAdminContext ( ) , confirmation . list , subscription , meta ) ;
} catch ( err ) {
if ( err instanceof interoperableErrors . DuplicitEmailError ) {
throw new interoperableErrors . DuplicitEmailError ( 'Subscription already present' ) ; // This is here to provide some meaningful error message.
} else {
throw err ;
}
}
2017-12-10 20:44:35 +00:00
const list = await lists . getById ( contextHelpers . getAdminContext ( ) , confirmation . list ) ;
2018-01-27 15:37:14 +00:00
subscription . cid = meta . cid ;
2017-12-10 20:44:35 +00:00
await mailHelpers . sendSubscriptionConfirmed ( list , subscription . email , subscription ) ;
2017-12-30 11:23:16 +00:00
res . redirect ( '/subscription/' + encodeURIComponent ( 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.' ) ) ;
2017-12-30 11:23:16 +00:00
const list = await lists . getById ( contextHelpers . getAdminContext ( ) , confirmation . list ) ;
2017-12-10 20:44:35 +00:00
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' ) ) ;
2017-12-30 11:23:16 +00:00
res . redirect ( '/subscription/' + encodeURIComponent ( list . cid ) + '/manage/' + subscription . cid ) ;
2017-12-10 20:44:35 +00:00
} ) ;
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.' ) ) ;
2017-12-30 11:23:16 +00:00
const list = await lists . getById ( contextHelpers . getAdminContext ( ) , confirmation . list ) ;
2017-12-10 20:44:35 +00:00
const data = confirmation . data ;
2017-12-30 11:23:16 +00:00
const subscription = await subscriptions . unsubscribeByCidAndGet ( contextHelpers . getAdminContext ( ) , list . id , data . subscriptionCid , data . campaignCid ) ;
2017-12-10 20:44:35 +00:00
await mailHelpers . sendUnsubscriptionConfirmed ( list , subscription . email , subscription ) ;
2017-12-30 11:23:16 +00:00
res . redirect ( '/subscription/' + encodeURIComponent ( list . cid ) + '/unsubscribed-notice' ) ;
2016-04-04 12:36:30 +00:00
} ) ;
2018-01-28 22:59:05 +00:00
async function _renderSubscribe ( req , res , list , subscription ) {
const data = { } ;
data . email = subscription && subscription . email ;
2017-12-10 20:44:35 +00:00
data . layout = 'subscription/layout' ;
data . title = list . name ;
data . cid = list . cid ;
data . csrfToken = req . csrfToken ( ) ;
2016-04-04 12:36:30 +00:00
2018-01-28 22:59:05 +00:00
data . customFields = await fields . forHbs ( contextHelpers . getAdminContext ( ) , list . id , subscription ) ;
2017-12-10 20:44:35 +00:00
data . useEditor = true ;
2017-05-04 21:42:46 +00:00
2018-04-29 16:13:40 +00:00
const configItems = await settings . get ( contextHelpers . getAdminContext ( ) , [ 'pgpPrivateKey' ] ) ;
2017-12-10 20:44:35 +00:00
data . hasPubkey = ! ! configItems . pgpPrivateKey ;
2017-05-04 21:42:46 +00:00
2017-12-10 20:44:35 +00:00
data . template = {
template : 'subscription/web-subscribe.mjml.hbs' ,
2018-04-29 16:13:40 +00:00
layout : 'subscription/layout.mjml.hbs' ,
type : 'mjml'
2017-12-10 20:44:35 +00:00
} ;
2017-05-04 21:42:46 +00:00
2017-12-30 11:23:16 +00:00
await injectCustomFormData ( req . query . fid || list . default _form , 'subscription/web-subscribe' , data ) ;
2017-05-04 21:42:46 +00:00
2018-04-29 16:13:40 +00:00
const htmlRenderer = await tools . getTemplate ( data . template ) ;
2017-12-10 20:44:35 +00:00
data . isWeb = true ;
data . needsJsWarning = true ;
2018-05-20 18:27:35 +00:00
2017-12-10 20:44:35 +00:00
data . flashMessages = await captureFlashMessages ( res ) ;
2018-01-27 15:37:14 +00:00
const result = htmlRenderer ( data ) ;
2017-04-03 17:53:01 +00:00
2018-01-28 22:59:05 +00:00
res . send ( result ) ;
}
2017-04-03 17:53:01 +00:00
2018-01-28 22:59:05 +00:00
router . getAsync ( '/:cid' , passport . csrfProtection , async ( req , res ) => {
2018-01-27 15:37:14 +00:00
const list = await lists . getByCid ( contextHelpers . getAdminContext ( ) , req . params . cid ) ;
2017-04-03 17:53:01 +00:00
2018-01-28 22:59:05 +00:00
if ( ! list . public _subscribe ) {
shares . throwPermissionDenied ( ) ;
}
2017-04-03 17:53:01 +00:00
2018-01-28 22:59:05 +00:00
const ucid = req . query . cid ;
2017-04-03 17:53:01 +00:00
2018-01-28 22:59:05 +00:00
let subscription ;
if ( ucid ) {
try {
subscription = await subscriptions . getByCid ( contextHelpers . getAdminContext ( ) , list . id , ucid ) ;
2017-04-03 17:53:01 +00:00
2018-01-28 22:59:05 +00:00
if ( subscription . status === SubscriptionStatus . SUBSCRIBED ) {
subscription = null ;
}
} catch ( err ) {
if ( err instanceof interoperableErrors . NotFoundError ) {
} else {
throw err ;
}
2017-12-10 20:44:35 +00:00
}
2018-01-28 22:59:05 +00:00
}
2017-12-10 20:44:35 +00:00
2018-01-28 22:59:05 +00:00
await _renderSubscribe ( req , res , list , subscription ) ;
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 ) => {
if ( req . xhr ) {
req . needsAPIJSONResponse = true ;
}
2017-04-03 17:53:01 +00:00
2018-01-28 22:59:05 +00:00
const list = await lists . getByCid ( contextHelpers . getAdminContext ( ) , req . params . cid ) ;
if ( ! list . public _subscribe ) {
shares . throwPermissionDenied ( ) ;
}
const subscriptionData = await fields . fromPost ( contextHelpers . getAdminContext ( ) , list . id , req . body ) ;
const email = cleanupFromPost ( req . body . EMAIL ) ;
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' ) ) ;
2018-01-28 22:59:05 +00:00
return await _renderSubscribe ( req , res , list , subscriptionData ) ;
2016-04-04 12:36:30 +00:00
}
2017-12-10 20:44:35 +00:00
const emailErr = await tools . validateEmail ( email ) ;
if ( emailErr ) {
2018-01-27 15:37:14 +00:00
const errMsg = tools . validateEmailGetMessage ( emailErr , email ) ;
2017-12-10 20:44:35 +00:00
if ( req . xhr ) {
2018-01-27 15:37:14 +00:00
throw new Error ( errMsg ) ;
2017-04-30 14:51:47 +00:00
}
2018-01-27 15:37:14 +00:00
req . flash ( 'danger' , errMsg ) ;
2018-01-28 22:59:05 +00:00
subscriptionData . email = email ;
return await _renderSubscribe ( req , res , list , subscriptionData ) ;
2017-12-10 20:44:35 +00:00
}
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
2018-01-28 22:59:05 +00:00
let existingSubscription ;
try {
existingSubscription = await subscriptions . getByEmail ( contextHelpers . getAdminContext ( ) , list . id , email ) ;
} catch ( err ) {
if ( err instanceof interoperableErrors . NotFoundError ) {
} else {
throw err ;
2017-12-10 20:44:35 +00:00
}
2018-01-28 22:59:05 +00:00
}
2017-12-10 20:44:35 +00:00
2018-01-28 22:59:05 +00:00
if ( existingSubscription && existingSubscription . status === SubscriptionStatus . SUBSCRIBED ) {
await mailHelpers . sendAlreadySubscribed ( list , email , existingSubscription ) ;
2017-12-30 11:23:16 +00:00
res . redirect ( '/subscription/' + encodeURIComponent ( req . params . cid ) + '/confirm-subscription-notice' ) ;
2017-12-10 20:44:35 +00:00
} 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-30 11:23:16 +00:00
res . redirect ( '/subscription/' + encodeURIComponent ( req . params . cid ) + '/confirm-subscription-notice' ) ;
2017-12-10 20:44:35 +00:00
}
} ) ;
2016-04-04 12:36:30 +00:00
2018-01-28 22:59:05 +00:00
router . options ( '/:cid/widget' , cors ( corsOptions ) ) ;
router . getAsync ( '/:cid/widget' , cors ( corsOptions ) , async ( req , res ) => {
req . needsAPIJSONResponse = true ;
const cached = cache . get ( req . path ) ;
if ( cached ) {
return res . status ( 200 ) . json ( cached ) ;
}
const list = await lists . getByCid ( contextHelpers . getAdminContext ( ) , req . params . cid ) ;
2018-04-29 16:13:40 +00:00
const configItems = await settings . get ( contextHelpers . getAdminContext ( ) , [ 'pgpPrivateKey' ] ) ;
2018-01-28 22:59:05 +00:00
const data = {
title : list . name ,
cid : list . cid ,
2018-04-29 16:13:40 +00:00
publicKeyUrl : getTrustedUrl ( 'subscription/publickey' ) ,
subscribeUrl : getTrustedUrl ( ` subscription/ ${ list . cid } /subscribe ` ) ,
2018-01-28 22:59:05 +00:00
hasPubkey : ! ! configItems . pgpPrivateKey ,
customFields : await fields . forHbs ( contextHelpers . getAdminContext ( ) , list . id ) ,
template : { } ,
layout : null ,
} ;
await injectCustomFormData ( req . query . fid || list . default _form , 'subscription/web-subscribe' , data ) ;
const renderAsync = bluebird . promisify ( res . render ) ;
const html = await renderAsync ( 'subscription/widget-subscribe' , data ) ;
const response = {
data : {
title : data . title ,
cid : data . cid ,
html
}
} ;
cache . put ( req . path , response , 30000 ) ; // ms
res . status ( 200 ) . json ( response ) ;
} ) ;
2017-12-10 20:44:35 +00:00
router . getAsync ( '/:lcid/manage/:ucid' , passport . csrfProtection , async ( req , res ) => {
2018-01-27 15:37:14 +00:00
const list = await lists . getByCid ( contextHelpers . getAdminContext ( ) , req . params . lcid ) ;
2016-04-04 12:36:30 +00:00
2018-01-28 22:59:05 +00:00
const subscription = await subscriptions . getByCid ( contextHelpers . getAdminContext ( ) , list . id , req . params . ucid ) ;
if ( subscription . status !== SubscriptionStatus . SUBSCRIBED ) {
2017-12-30 11:23:16 +00:00
throw new interoperableErrors . NotFoundError ( 'Subscription not found in this list' ) ;
2017-12-10 20:44:35 +00:00
}
2016-04-04 12:36:30 +00:00
2018-01-27 15:37:14 +00:00
const data = { } ;
data . email = subscription . email ;
data . cid = subscription . cid ;
data . lcid = req . params . lcid ;
data . title = list . name ;
data . csrfToken = req . csrfToken ( ) ;
data . layout = 'data/layout' ;
2017-12-10 20:44:35 +00:00
2018-01-28 22:59:05 +00:00
data . customFields = await fields . forHbs ( contextHelpers . getAdminContext ( ) , list . id , subscription ) ;
2017-12-10 20:44:35 +00:00
2018-01-27 15:37:14 +00:00
data . useEditor = true ;
2017-12-10 20:44:35 +00:00
2018-04-29 16:13:40 +00:00
const configItems = await settings . get ( contextHelpers . getAdminContext ( ) , [ 'pgpPrivateKey' ] ) ;
2018-01-27 15:37:14 +00:00
data . hasPubkey = ! ! configItems . pgpPrivateKey ;
2017-12-10 20:44:35 +00:00
2018-01-27 15:37:14 +00:00
data . template = {
2017-12-10 20:44:35 +00:00
template : 'subscription/web-manage.mjml.hbs' ,
2018-04-29 16:13:40 +00:00
layout : 'subscription/layout.mjml.hbs' ,
type : 'mjml'
2017-12-10 20:44:35 +00:00
} ;
2018-01-27 15:37:14 +00:00
await injectCustomFormData ( req . query . fid || list . default _form , 'data/web-manage' , data ) ;
2017-12-10 20:44:35 +00:00
2018-04-29 16:13:40 +00:00
const htmlRenderer = await tools . getTemplate ( data . template ) ;
2017-12-10 20:44:35 +00:00
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 ) => {
2018-01-27 15:37:14 +00:00
const list = await lists . getByCid ( contextHelpers . getAdminContext ( ) , req . params . lcid ) ;
2016-04-04 12:36:30 +00:00
2018-01-27 15:37:14 +00:00
try {
2018-01-28 22:59:05 +00:00
const subscriptionData = await fields . fromPost ( contextHelpers . getAdminContext ( ) , list . id , req . body ) ;
await subscriptions . updateManaged ( contextHelpers . getAdminContext ( ) , list . id , req . body . cid , subscriptionData ) ;
2018-01-27 15:37:14 +00:00
} catch ( err ) {
if ( err instanceof interoperableErrors . NotFoundError ) {
throw new interoperableErrors . NotFoundError ( 'Subscription not found in this list' ) ;
} else {
throw err ;
}
2017-12-10 20:44:35 +00:00
}
2016-04-04 12:36:30 +00:00
2017-12-30 11:23:16 +00:00
res . redirect ( '/subscription/' + encodeURIComponent ( req . params . lcid ) + '/updated-notice' ) ;
} ) ;
2017-05-04 21:42:46 +00:00
2017-12-30 11:23:16 +00:00
router . getAsync ( '/:lcid/manage-address/:ucid' , passport . csrfProtection , async ( req , res ) => {
2018-01-27 15:37:14 +00:00
const list = await lists . getByCid ( contextHelpers . getAdminContext ( ) , req . params . lcid ) ;
2017-12-30 11:23:16 +00:00
const subscription = await subscriptions . getByCid ( contextHelpers . getAdminContext ( ) , list . id , req . params . ucid , false ) ;
2017-12-10 20:44:35 +00:00
2018-01-28 22:59:05 +00:00
if ( subscription . status !== SubscriptionStatus . SUBSCRIBED ) {
2017-12-30 11:23:16 +00:00
throw new interoperableErrors . NotFoundError ( 'Subscription not found in this list' ) ;
}
2016-04-04 12:36:30 +00:00
2018-01-27 15:37:14 +00:00
const data = { } ;
data . email = subscription . email ;
data . cid = subscription . cid ;
data . lcid = req . params . lcid ;
data . title = list . name ;
data . csrfToken = req . csrfToken ( ) ;
2016-12-07 14:12:26 +00:00
2018-01-27 15:37:14 +00:00
data . template = {
2017-12-30 11:23:16 +00:00
template : 'subscription/web-manage-address.mjml.hbs' ,
2018-04-29 16:13:40 +00:00
layout : 'subscription/layout.mjml.hbs' ,
type : 'mjml'
2017-12-30 11:23:16 +00:00
} ;
2016-12-07 14:12:26 +00:00
2018-01-28 22:59:05 +00:00
await injectCustomFormData ( req . query . fid || list . default _form , 'data/web-manage-address' , data ) ;
2017-12-30 11:23:16 +00:00
2018-04-29 16:13:40 +00:00
const htmlRenderer = await tools . getTemplate ( data . template ) ;
2017-12-30 11:23:16 +00:00
data . isWeb = true ;
data . needsJsWarning = true ;
data . isManagePreferences = true ;
data . flashMessages = await captureFlashMessages ( res ) ;
res . send ( htmlRenderer ( data ) ) ;
2016-12-07 14:12:26 +00:00
} ) ;
2017-12-30 11:23:16 +00:00
router . postAsync ( '/:lcid/manage-address' , passport . parseForm , passport . csrfProtection , async ( req , res ) => {
2018-01-27 15:37:14 +00:00
const list = await lists . getByCid ( contextHelpers . getAdminContext ( ) , req . params . lcid ) ;
2016-12-07 14:12:26 +00:00
2018-01-28 22:59:05 +00:00
const emailNew = cleanupFromPost ( req . body [ 'EMAIL_NEW' ] ) ;
2017-05-03 19:46:49 +00:00
2017-12-30 11:23:16 +00:00
const subscription = await subscriptions . getByCid ( contextHelpers . getAdminContext ( ) , list . id , req . body . cid , false ) ;
2018-01-27 15:37:14 +00:00
if ( subscription . status !== SubscriptionStatus . SUBSCRIBED ) {
2017-12-30 11:23:16 +00:00
throw new interoperableErrors . NotFoundError ( 'Subscription not found in this list' ) ;
}
if ( subscription . email === emailNew ) {
req . flash ( 'info' , _ ( 'Nothing seems to be changed' ) ) ;
} else {
const emailErr = await tools . validateEmail ( emailNew ) ;
if ( emailErr ) {
2018-01-27 15:37:14 +00:00
const errMsg = tools . validateEmailGetMessage ( emailErr , email ) ;
req . flash ( 'danger' , errMsg ) ;
2016-12-07 14:12:26 +00:00
2017-05-04 21:42:46 +00:00
} else {
2018-01-28 22:59:05 +00:00
let newSubscription ;
try {
newSubscription = await subscriptions . getByEmail ( contextHelpers . getAdminContext ( ) , list . id , emailNew , false ) ;
} catch ( err ) {
if ( err instanceof interoperableErrors . NotFoundError ) {
} else {
throw err ;
}
}
2017-12-30 11:23:16 +00:00
if ( newSubscription && newSubscription . status === SubscriptionStatus . SUBSCRIBED ) {
await mailHelpers . sendAlreadySubscribed ( list , emailNew , subscription ) ;
} else {
2018-01-27 15:37:14 +00:00
const data = {
subscriptionId : subscription . id ,
emailNew
} ;
2017-12-30 11:23:16 +00:00
const confirmCid = await confirmations . addConfirmation ( list . id , 'change-address' , req . ip , data ) ;
2018-01-27 15:37:14 +00:00
await mailHelpers . sendConfirmAddressChange ( list , emailNew , confirmCid , subscription ) ;
2017-12-30 11:23:16 +00:00
}
req . flash ( 'info' , _ ( 'An email with further instructions has been sent to the provided address' ) ) ;
2017-05-04 21:42:46 +00:00
}
2017-12-30 11:23:16 +00:00
}
res . redirect ( '/subscription/' + encodeURIComponent ( req . params . lcid ) + '/manage/' + encodeURIComponent ( req . body . cid ) ) ;
2016-12-07 14:12:26 +00:00
} ) ;
2016-04-04 12:36:30 +00:00
2017-12-30 11:23:16 +00:00
router . getAsync ( '/:lcid/unsubscribe/:ucid' , passport . csrfProtection , async ( req , res ) => {
2018-01-27 15:37:14 +00:00
const list = await lists . getByCid ( contextHelpers . getAdminContext ( ) , req . params . lcid ) ;
2016-04-04 12:36:30 +00:00
2018-04-29 16:13:40 +00:00
const configItems = await settings . get ( contextHelpers . getAdminContext ( ) , [ 'defaultAddress' ] ) ;
2016-04-04 12:36:30 +00:00
2017-12-30 11:23:16 +00:00
const autoUnsubscribe = req . query . auto === 'yes' ;
2016-04-04 12:36:30 +00:00
2017-12-30 11:23:16 +00:00
if ( autoUnsubscribe ) {
handleUnsubscribe ( list , req . params . ucid , autoUnsubscribe , req . query . c , req . ip , res , next ) ;
} else if ( req . query . formTest ||
2018-01-27 15:37:14 +00:00
list . unsubscription _mode === lists . UnsubscriptionMode . ONE _STEP _WITH _FORM ||
list . unsubscription _mode === lists . UnsubscriptionMode . TWO _STEP _WITH _FORM ) {
2017-12-30 11:23:16 +00:00
const subscription = await subscriptions . getByCid ( contextHelpers . getAdminContext ( ) , list . id , req . params . ucid , false ) ;
2016-04-04 12:36:30 +00:00
2018-01-28 22:59:05 +00:00
if ( subscription . status !== SubscriptionStatus . SUBSCRIBED ) {
2017-12-30 11:23:16 +00:00
throw new interoperableErrors . NotFoundError ( 'Subscription not found in this list' ) ;
2016-04-04 12:36:30 +00:00
}
2018-01-27 15:37:14 +00:00
const data = { } ;
data . email = subscription . email ;
data . lcid = req . params . lcid ;
data . ucid = req . params . ucid ;
data . title = list . name ;
data . csrfToken = req . csrfToken ( ) ;
data . campaign = req . query . c ;
data . defaultAddress = configItems . defaultAddress ;
data . template = {
2017-12-30 11:23:16 +00:00
template : 'subscription/web-unsubscribe.mjml.hbs' ,
2018-04-29 16:13:40 +00:00
layout : 'subscription/layout.mjml.hbs' ,
type : 'mjml'
2017-12-30 11:23:16 +00:00
} ;
2017-05-03 19:46:49 +00:00
2018-01-27 15:37:14 +00:00
await injectCustomFormData ( req . query . fid || list . default _form , 'subscription/web-unsubscribe' , data ) ;
2017-05-03 19:46:49 +00:00
2018-04-29 16:13:40 +00:00
const htmlRenderer = await tools . getTemplate ( data . template ) ;
2016-04-21 17:17:19 +00:00
2017-12-30 11:23:16 +00:00
data . isWeb = true ;
data . needsJsWarning = true ;
data . isManagePreferences = true ;
data . flashMessages = await captureFlashMessages ( res ) ;
res . send ( htmlRenderer ( data ) ) ;
} else { // UnsubscriptionMode.ONE_STEP || UnsubscriptionMode.TWO_STEP || UnsubscriptionMode.MANUAL
await handleUnsubscribe ( list , req . params . ucid , autoUnsubscribe , req . query . c , req . ip , res ) ;
}
2017-05-03 19:46:49 +00:00
} ) ;
2017-12-30 11:23:16 +00:00
router . postAsync ( '/:lcid/unsubscribe' , passport . parseForm , passport . csrfProtection , async ( req , res ) => {
2018-01-27 15:37:14 +00:00
const list = await lists . getByCid ( contextHelpers . getAdminContext ( ) , req . params . lcid ) ;
2017-12-30 11:23:16 +00:00
2018-01-28 22:59:05 +00:00
const campaignCid = cleanupFromPost ( req . body . campaign ) ;
2017-12-30 11:23:16 +00:00
await handleUnsubscribe ( list , req . body . ucid , false , campaignCid , req . ip , res ) ;
} ) ;
async function handleUnsubscribe ( list , subscriptionCid , autoUnsubscribe , campaignCid , ip , res ) {
2018-01-27 15:37:14 +00:00
if ( ( list . unsubscription _mode === lists . UnsubscriptionMode . ONE _STEP || list . unsubscription _mode === lists . UnsubscriptionMode . ONE _STEP _WITH _FORM ) ||
( autoUnsubscribe && ( list . unsubscription _mode === lists . UnsubscriptionMode . TWO _STEP || list . unsubscription _mode === lists . UnsubscriptionMode . TWO _STEP _WITH _FORM ) ) ) {
2017-05-03 19:46:49 +00:00
2017-12-30 11:23:16 +00:00
try {
const subscription = await subscriptions . unsubscribeByCidAndGet ( contextHelpers . getAdminContext ( ) , list . id , subscriptionCid , campaignCid ) ;
2017-06-10 23:26:15 +00:00
2017-12-30 11:23:16 +00:00
await mailHelpers . sendUnsubscriptionConfirmed ( list , subscription . email , subscription ) ;
2017-05-03 19:46:49 +00:00
2017-12-30 11:23:16 +00:00
res . redirect ( '/subscription/' + encodeURIComponent ( list . cid ) + '/unsubscribed-notice' ) ;
2016-04-21 17:17:19 +00:00
2017-12-30 11:23:16 +00:00
} catch ( err ) {
if ( err instanceof interoperableErrors . NotFoundError ) {
throw new interoperableErrors . NotFoundError ( 'Subscription not found in this list' ) ; // This is here to provide some meaningful error message.
}
}
2017-05-03 19:46:49 +00:00
2017-12-30 11:23:16 +00:00
} else {
const subscription = await subscriptions . getByCid ( contextHelpers . getAdminContext ( ) , list . id , subscriptionCid , false ) ;
2017-05-03 19:46:49 +00:00
2018-01-28 22:59:05 +00:00
if ( subscription . status !== SubscriptionStatus . SUBSCRIBED ) {
2017-12-30 11:23:16 +00:00
throw new interoperableErrors . NotFoundError ( 'Subscription not found in this list' ) ;
}
2017-05-26 22:43:56 +00:00
2018-01-27 15:37:14 +00:00
if ( list . unsubscription _mode === lists . UnsubscriptionMode . TWO _STEP || list . unsubscription _mode === lists . UnsubscriptionMode . TWO _STEP _WITH _FORM ) {
2017-05-03 19:46:49 +00:00
2017-12-30 11:23:16 +00:00
const data = {
subscriptionCid ,
campaignCid
} ;
2017-05-03 19:46:49 +00:00
2017-12-30 11:23:16 +00:00
const confirmCid = await confirmations . addConfirmation ( list . id , 'unsubscribe' , ip , data ) ;
await mailHelpers . sendConfirmUnsubscription ( list , subscription . email , confirmCid , subscription ) ;
res . redirect ( '/subscription/' + encodeURIComponent ( list . cid ) + '/confirm-unsubscription-notice' ) ;
2017-05-26 22:43:56 +00:00
2017-12-30 11:23:16 +00:00
} else { // UnsubscriptionMode.MANUAL
res . redirect ( '/subscription/' + encodeURIComponent ( list . cid ) + '/manual-unsubscribe-notice' ) ;
}
2017-05-03 19:46:49 +00:00
}
}
2016-04-04 12:36:30 +00:00
2017-12-30 11:23:16 +00:00
router . getAsync ( '/:cid/confirm-subscription-notice' , async ( req , res ) => {
await webNotice ( 'confirm-subscription' , req , res ) ;
2017-04-30 14:51:47 +00:00
} ) ;
2017-12-30 11:23:16 +00:00
router . getAsync ( '/:cid/confirm-unsubscription-notice' , async ( req , res ) => {
await webNotice ( 'confirm-unsubscription' , req , res ) ;
2017-04-30 14:51:47 +00:00
} ) ;
2017-12-30 11:23:16 +00:00
router . getAsync ( '/:cid/subscribed-notice' , async ( req , res ) => {
await webNotice ( 'subscribed' , req , res ) ;
2017-04-30 14:51:47 +00:00
} ) ;
2017-12-30 11:23:16 +00:00
router . getAsync ( '/:cid/updated-notice' , async ( req , res ) => {
await webNotice ( 'updated' , req , res ) ;
2017-04-30 14:51:47 +00:00
} ) ;
2017-12-30 11:23:16 +00:00
router . getAsync ( '/:cid/unsubscribed-notice' , async ( req , res ) => {
await webNotice ( 'unsubscribed' , req , res ) ;
2017-05-03 19:46:49 +00:00
} ) ;
2017-12-30 11:23:16 +00:00
router . getAsync ( '/:cid/manual-unsubscribe-notice' , async ( req , res ) => {
await webNotice ( 'manual-unsubscribe' , req , res ) ;
2017-04-30 14:51:47 +00:00
} ) ;
2017-12-30 11:23:16 +00:00
router . postAsync ( '/publickey' , passport . parseForm , async ( req , res ) => {
2018-04-29 16:13:40 +00:00
const configItems = await settings . get ( contextHelpers . getAdminContext ( ) , [ 'pgpPassphrase' , 'pgpPrivateKey' ] ) ;
2016-04-16 21:09:23 +00:00
2017-12-30 11:23:16 +00:00
if ( ! configItems . pgpPrivateKey ) {
const err = new Error ( _ ( 'Public key is not set' ) ) ;
err . status = 404 ;
throw err ;
}
2016-04-16 21:09:23 +00:00
2017-12-30 11:23:16 +00:00
let privKey ;
try {
privKey = openpgp . key . readArmored ( configItems . pgpPrivateKey ) . keys [ 0 ] ;
if ( configItems . pgpPassphrase && ! privKey . decrypt ( configItems . pgpPassphrase ) ) {
privKey = false ;
2016-04-16 21:09:23 +00:00
}
2017-12-30 11:23:16 +00:00
} catch ( E ) {
// just ignore if failed
}
2016-04-16 21:09:23 +00:00
2017-12-30 11:23:16 +00:00
if ( ! privKey ) {
const err = new Error ( _ ( 'Public key is not set' ) ) ;
err . status = 404 ;
throw err ;
}
2016-04-16 21:09:23 +00:00
2017-12-30 11:23:16 +00:00
const pubkey = privKey . toPublic ( ) . armor ( ) ;
2016-04-16 21:09:23 +00:00
2017-12-30 11:23:16 +00:00
res . writeHead ( 200 , {
'Content-Type' : 'application/octet-stream' ,
'Content-Disposition' : 'attachment; filename=public.asc'
2016-04-16 21:09:23 +00:00
} ) ;
2017-12-30 11:23:16 +00:00
res . end ( pubkey ) ;
2016-04-16 21:09:23 +00:00
} ) ;
2017-04-30 14:51:47 +00:00
2017-12-30 11:23:16 +00:00
async function webNotice ( type , req , res ) {
2018-01-27 15:37:14 +00:00
const list = await lists . getByCid ( contextHelpers . getAdminContext ( ) , req . params . cid ) ;
2017-12-30 11:23:16 +00:00
2018-04-29 16:13:40 +00:00
const configItems = await settings . get ( contextHelpers . getAdminContext ( ) , [ 'defaultHomepage' , 'adminEmail' ] ) ;
2017-12-30 11:23:16 +00:00
const data = {
title : list . name ,
2018-04-29 16:13:40 +00:00
homepage : configItems . defaultHomepage || getTrustedUrl ( ) ,
contactAddress : list . from _email || configItems . adminEmail ,
2017-12-30 11:23:16 +00:00
template : {
template : 'subscription/web-' + type + '-notice.mjml.hbs' ,
2018-04-29 16:13:40 +00:00
layout : 'subscription/layout.mjml.hbs' ,
type : 'mjml'
2017-04-30 14:51:47 +00:00
}
2017-12-30 11:23:16 +00:00
} ;
2017-04-30 14:51:47 +00:00
2017-12-30 11:23:16 +00:00
await injectCustomFormData ( req . query . fid || list . default _form , 'subscription/web-' + type + '-notice' , data ) ;
2017-04-30 14:51:47 +00:00
2018-04-29 16:13:40 +00:00
const htmlRenderer = await tools . getTemplate ( data . template ) ;
2017-04-30 14:51:47 +00:00
2017-12-30 11:23:16 +00:00
data . isWeb = true ;
data . isConfirmNotice = true ; // FIXME: Not sure what this does. Check it in a browser with disabled JS
data . isManagePreferences = true ;
data . flashMessages = await captureFlashMessages ( res ) ;
res . send ( htmlRenderer ( data ) ) ;
2017-04-30 14:51:47 +00:00
}
2016-04-04 12:36:30 +00:00
module . exports = router ;