Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cross session issue with Express 3

I have a very weird session issue - some odd cross session problem that has me baffled.

Basically I have an express 3 app in which I have 2, seemingly unrelated things - one is a standard contact form that someone can use to email me and the other is an API call to mailchimp to add subscribers when they place an order. These are 2 completely different calls/actions.

At the top of my contact form I have a check for the variable 'error' which I can use in my controller code to say something such as "invalid email" or other error message. My contact form has 2 controller actions - one for GET which shows the form and one for POST which submits and shows errors if any.

The issue is this - when I load the contact form initially I see this message: "Error: [email protected] is already subscribed to list General. Click here to update your profile." which is a MailChimp message - not only unrelated to the process but unrelated to my session! This error can happen if a returning customer places and order and they are already subscribed. I don't ever show them this message not, as far as I can tell, actually store it. So I am confused.

One thing to note is that the order part is called from the node server upon receiving an xml file from the payment gateway (so not a regular session user) - so maybe there are some session internals I am not understanding.

The code is below but here's a summary of the issue. A node server, without a user session active, is making an API call to mailchimp to add a subscriber using this module - It is common that the same email will be subscribed twice so mailchimp will reply with an "already subscribed" message which I *do no*t store or load in Express's error locals. Then a user visits the site, goes to contact form which checks if there are any error messages from that user trying to submit the form with say missing fields - and they see the mailchimp message showing another person's email address.

Here's the relevant code.

app.js:

var express = require("express"),
flash = require("connect-flash");

...

app.use(flash());

...

app.use(function(req, res, next) {
  var msgs;
  msgs = req.session.messages || [];
  res.locals.messages = msgs;
  res.locals.hasMessages = !!msgs.length;
  req.session.messages = [];
  return next();
});

...

app.get("/contact", express.csrf(), routes.main.contact);
app.post("/contact", express.csrf(), routes.main.submit);

contact form controller:

exports.contact = function(req, res) {
    res.locals.token = req.session._csrf;
    res.render('main/contact');
};

exports.submit = function(req, res) {

  var send = function(message, fn) {
    var sendgrid = new SendGrid(settings.sendgrid_username, settings.sendgrid_password);
    sendgrid.send({
      to: settings.contact_email,
      from: message.email,
      subject: 'Contact Message',
      text: message.message
    }, fn);
  };

  var validate = function(message) {
    var v = new Validator(),
      errors = []
      ;

    v.error = function(msg) {
      errors.push(msg);
    };

    v.check(message.name, 'Please enter your name').len(1, 100);
    v.check(message.email, 'Please enter a valid email address').isEmail();
    v.check(message.message, 'Please enter a valid message').len(1, 1000);

    return errors;
  };

  function render() {
    res.locals.token = req.session._csrf;
    res.render('main/contact', locals);
  }

  var message = req.body.message,
    errors = validate(message),
    locals = {}
    ;

  if (errors.length === 0) {
    send(message, function(success) {
      if (!success) {
        locals.error = 'Error sending message';
        locals.message = message;
      } else {
        locals.notice = 'Your message has been sent.';
      }
      render();
    });
  } else {
    locals.error = 'Your message has errors:';
    locals.errors = errors;
    locals.message = message;
    render();
  }
};

contact form:

{% if error or notice %}
<div id="message" class="alert alert-{% if error %}error{% else %}success{% endif %} ">
  <button type="button" class="close" data-dismiss="alert">×</button>
  <h4>{% if error %}{{ error }}{% else %}{{ notice }}{% endif %}</h4>
  {% if errors %}
  <ul>
  {% for e in errors %}
    <li>{{ e }}</li>
  {% endfor %}
  </ul>
  {% endif %}
</div>
{% endif %}

<form method="post">
...

order form controller (called by payment gateway api on successful order - never called directly by user)

var joinNewsletter = function(data) {

  try {
      var api = new MailChimpAPI(settings.mailchimp_api, { version : '1.3', secure : false });
      api.listSubscribe({
        id: settings.mailchimp_list_id,
        email_address: data.email,
        merge_vars: {
          fname: data.first,
          lname: data.last
        },
        double_optin: data.optin || false
      }, function() {});
  } catch (error) {
      console.log(error.message);
  }

};
...
 joinNewsletter({
    email: order.email,
    first: order.fname,
    last: order.lname
  });
like image 451
cyberwombat Avatar asked Nov 03 '22 21:11

cyberwombat


1 Answers

Oh good god I figured it out after a looooot of debugging. The issues are twofold:

  • The MailChimpAPI module has a bug - when an error is received it creates this code:

    error = new Error(message || (message = ''));

The error var is undeclared so it's hitting the global error var.

  • The view code checks if 'error' exists and prints it - that was my own variable. But apparently in the inner mechanism of Express it checks for the global error var.

It seems that error is being handled as an app.locals variable hence bridging user sessions.

I am fixing it by a) patching the MailChimp helper code and filing a bug report and b) making sure to not use 'error' as a view variable ever again.

Woah.

like image 87
cyberwombat Avatar answered Nov 09 '22 02:11

cyberwombat