Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Need request context for resetPassword #512

Closed
johnsoftek opened this issue Aug 29, 2014 · 15 comments
Closed

Need request context for resetPassword #512

johnsoftek opened this issue Aug 29, 2014 · 15 comments

Comments

@johnsoftek
Copy link

Loopback should generally not need to know the URL on which it is listening.

However, there are several situations where a URL must be generated for the user to connect back. For example, User.verify and User.resetPassword. In both those cases, we need to create a link to embed in an email for the user to confirm an action by verifying their email address.

At present, both functions require host and port to be configured in config.json or related config.

Another approach is to use the URL of the request to construct a link. I did this in an afterRemote hook on User.create:

  function afterRemote_create_hook(ctx, user, next) {
    var url_bits, protocol, host, port

    if (ctx.req.headers && ctx.req.headers.origin) {
      url_bits = ctx.req.headers.origin.split(':')
      protocol = url_bits[0]
      host = url_bits[1].substring(2)
      port = url_bits[2]
    }

    var app = User.app,
      options = {
        type: 'email',
        to: user.email,
        from: '[email protected]',  // TODO get email sender from Settings
        subject: 'Thanks for registering',
        text: 'Please verify your email address!',
        template: 'server/templates/verify.ejs',
        redirect: '/',
        protocol: protocol || 'http',
        host: host || 'localhost',
        port: port || 3000
    };

    user.verify(options, next);

So registration works fine with the inbound URL.

But I have a problem with resetPassword. I listen on "resetPasswordRequest" event but I have no context of the incoming message to determine the URL.

Is there any way at present to get the context? Or is it a feature request?

@raymondfeng
Copy link
Member

See the PR at #337. With the implicit context propagation, you should be able to pick up the context without messing up existing method signatures.

@johnsoftek
Copy link
Author

So the context will somehow be available from an emit?

@superkhau
Copy link
Contributor

@johnsoftek Has the issue been resolved? Can I close it?

@johnsoftek
Copy link
Author

@superkhau AFAIK, no.

Context propagation relates to supplying context around a remote request. From memory, ResetPassword is quite strange in that it does not return a result for the original request. Instead the example suggests listening on an emit event. That event does not supply the original request context and therefore the original url is not available.

@superkhau
Copy link
Contributor

@bajtos @raymondfeng @ritch PTAL

@bajtos
Copy link
Member

bajtos commented Feb 27, 2015

Regarding the current context - see #982. I would expect that if the event is emitted from a HTTP handler, then the event listeners should be able to access the current context even now, before #982 is fixed.

As for passing the request context to "resetPasswordRequest", perhaps that can be added to loopback. I am not familiar with the current implementation of "reset pasword", @raymondfeng and/or @ritch now more about that. If they agree it's ok to pass more data to "resetPasswordRequest" event, then a new issue should be opened for that.

@bajtos
Copy link
Member

bajtos commented Mar 27, 2015

You should use app.get('url') to get the canonical URL at which the application can be accessed. I am not sure if you approach based on the Origin header will work when the website making the XHR request is on a different domain than LoopBack's REST API, or when the request is sent from a native iOS/Android app.

@danielrvt
Copy link

+1

@superkhau superkhau added feature and removed question labels Feb 4, 2016
@tjc0090
Copy link

tjc0090 commented Mar 31, 2017

This still appears to be an issue as I am faced with an identical situation.

Attempting app.get('url') returns a URL identical to the one contained in config.json however I need to use a different URL to provide to the user after a "resetPasswordRequest" has been emitted. That URL will change across environments and over time as we have not yet moved to production.

Accessing context from the custom method that handles the user's request and is responsible for emitting the "resetPasswordRequest" event is simple enough. But the context is not accessible from the event listener User.on('resetPasswordRequest', function(info)), the listener responsible for acting on the event and dispatching the email.

I have followed the example and have a working process with the caveat that I've hard coded the URL to match my local instance. Not to revive an old topic, but I'm very curious as to any progress or workarounds that may exist.

@bajtos
Copy link
Member

bajtos commented Mar 31, 2017

Attempting app.get('url') returns a URL identical to the one contained in config.json however I need to use a different URL to provide to the user after a "resetPasswordRequest" has been emitted. That URL will change across environments and over time as we have not yet moved to production.

FWIW, the config can be customized using per-environment overrides like server/config.production.json. It should be also possible to reference environment variables inside server/config.json to make it even simpler.

Accessing context from the custom method that handles the user's request and is responsible for emitting the "resetPasswordRequest" event is simple enough. But the context is not accessible from the event listener User.on('resetPasswordRequest', function(info)), the listener responsible for acting on the event and dispatching the email.

IMO, this is a missing feature that we should address.

Right now, resetPassword is defined with the following arguments:

   UserModel.remoteMethod(
      'resetPassword',
      {
        description: 'Reset password for a user with email.',
        accepts: [
          {arg: 'options', type: 'object', required: true, http: {source: 'body'}},
        ],
        http: {verb: 'post', path: '/reset'},
      }
    );

I am proposing to rename the first argument from options to params and then add a second options arg with http: 'optionsFromRequest'. Then we can modify the resetPasswordRequest to receive this new options argument as a second event arg.

        UserModel.emit('resetPasswordRequest', {
-         email: options.email,
+         email: params.email,
          accessToken: accessToken,
          user: user,
-         options: options,
+         options: params,
+       }, options);
     });

Thoughts?

@tjc0090 would you like to contribute this change yourself? I am happy to help you along the way. See http://loopback.io/doc/en/contrib/code-contrib.html to get you started.

@johnsoftek
Copy link
Author

I use:

  function beforeRemote_resetPassword_hook (ctx, unused, next) {
    var req = ctx && ctx.req
    var body = req && req.body
    if (body && body.email && body.origin) {
      origins[body.email] = body.origin
    }
    next()
  }

  function on_resetPasswordRequest (info) {
    //    console.log(info.email) // the email of the requesting user
    //    console.log(info.accessToken.id) // the temp access token to allow password reset
    var app = User.app,
      options = {}
    options.origin = origins[info.email] ||
      info.protocol || (app && app.get('protocol')) || 'http'
      + '://'
      + info.host || (app && app.get('host')) || 'localhost'
      + ':'
      + info.port || (app && app.get('port')) || 3000

      options.resetHref =
        options.origin
        + '/auth/reset'
        + '?uid=' + info.user.id
        + '&accessToken=' + info.accessToken.id

I should probably store the origin in ctx.options...

@Allan1
Copy link

Allan1 commented Jun 17, 2017

Is there a workaround for accessing the current context on resetPasswordRequest? https://loopback.io/doc/en/lb3/Using-current-context.html doesn't show how to access the current context in such situation.

The reason I need this is for using i18n-node translation function, which is attached to both req and res properties of the context object. I'm able to localize messages on any remote methods and operation hooks, because I have access to the context object.

@bajtos
Copy link
Member

bajtos commented Jun 23, 2017

I don't think there is a workaround, but see my #512 (comment) for a pointer on how to fix this problem. I am happy to help you along the way if you decide to contribute this fix.

@stale stale bot added the stale label Aug 23, 2017
@stale
Copy link

stale bot commented Aug 23, 2017

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot closed this as completed Sep 6, 2017
@ataft
Copy link

ataft commented Nov 9, 2017

Hopefully this is fixed in Loopback 4, so for now I'll just do like @johnsoftek with the following:

    User.beforeRemote( 'resetPassword', function( ctx, model, next) {
        ctx.req.body.origin = ctx.req.headers.origin;
        next();
    });

    User.on('resetPasswordRequest', function(info) {

        var origin = info.options.origin || app.get('url');
        var url = origin + '/reset-password?access_token=' + info.accessToken.id;
        var html = 'Click <a href="' + url + '">here</a> to reset your password';

        User.app.models.Email.send({
            to: info.email,
            from: info.email,
            subject: 'Password reset',
            html: html
        }, function(err) {
            if (err) return console.log('> error sending password reset email');
            console.log('> sending password reset email to:', info.email);
        });

    });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants