Search code examples
emailurlmeteorclient-sidemeteor-accounts

Meteor - Password recovery / Email confirmation dynamic url


Basically, I'm using the accounts-base package on meteor and on meteor startup, I set up what template the server should use for the password recovery mail, email confirmation mail, etc.

For example, in my server/startup.js on meteor startup I do many things like :

  Accounts.urls.verifyEmail = function (token) {
        return Meteor.absoluteUrl(`verify-email/${token}`);
      };

  Accounts.emailTemplates.verifyEmail.html = function (user, url) {
        return EmailService.render.email_verification(user, url);
      };

The problem is that my app is hosted on multiple host names like company1.domain.com, company2.domain.com, company3.domain.com and if a client wants to reset his password from company1.domain.com, the recovery url provided should be company1.domain.com/recovery.

If another client tried to connect on company2.domain.com, then the recovery url should be company2.domain.com.

From my understanding, this is not really achievable because the method used by the Accounts Package is "Meteor.absoluteUrl()", which returns the server ROOT_URL variable (a single one for the server).

On the client-side, I do many things based on the window.location.href but I cannot seem, when trying to reset a password or when trying to confirm an email address, to send this url to the server.

I'm trying to find a way to dynamically generate the url depending on the host where the client is making the request from, but since the url is generated server-side, I cannot find an elegent way to do so. I'm thinking I could probably call a meteor server method right before trying to reset a password or create an account and dynamically set the ROOT_URL variable there, but that seems unsafe and risky because two people could easily try to reset in the same timeframe and potentially screw things up, or people could abuse it.

Isn't there any way to tell the server, from the client side, that the URL I want generated for the current email has to be the client current's location ? I would love to be able to override some functions from the account-base meteor package and achieve something like :

 Accounts.urls.verifyEmail = function (token, clientHost) {
        return `${clientHost}/verify-email/${token}`;
      };

  Accounts.emailTemplates.verifyEmail.html = function (user, url) {
        return EmailService.render.email_verification(user, url);
      };

But I'm not sure if that's possible, I don't have any real experience when it comes to overriding "behind the scene" functionalities from base packages, I like everything about what is happening EXCEPT that the url generated is always the same.


Solution

  • Okay so I managed to find a way to achieve what I was looking for, it's a bit hack-ish, but hey..

    Basically, useraccounts has a feature where any hidden input in the register at-form will be added to the user profile. So I add an hidden field to store the user current location.

    AccountsTemplates.addField({
      _id: 'signup_location',
      type: 'hidden',
    });
    

    When the template is rendered, I fill in this hidden input with jQuery.

    Template.Register.onRendered(() => {
      this.$('#at-field-signup_location').val(window.location.href);
    });
    

    And then, when I'm actually sending the emailVerification email, I can look up this value if it is available.

      Accounts.urls.verifyEmail = function (token) {
        return Meteor.absoluteUrl(`verify-email/${token}`);
      };
    
      Accounts.emailTemplates.verifyEmail.html = function (user, url) {
        const signupLocation = user.profile.signup_location;
        if (signupLocation) {
          let newUrl = url.substring(url.indexOf('verify-email'));
          newUrl = `${signupLocation}/${newUrl}`;
          return EmailService.render.email_verification(user, newUrl);
        }
    
        return EmailService.render.email_verification(user, url);
      };
    

    So this fixes it for the signUp flow, I may use the a similar concept for resetPassword and resendVerificationUrl since the signupLocation is now in the user profile.

    You should probably keep an array of every subdomains in your settings and keep the id of the corresponding one in the user profile, so if your domain changes in the future then the reference will still valid and consistent.