npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

flowstate

v0.6.0

Published

Per-request state management middleware.

Downloads

128,481

Readme

flowstate

This middleware manages and propagates per-request state across HTTP requests to a web application. This allows for implementing flows which are sequences of requests and responses that, taken together, culminate in a desired outcome.

By default, this state is kept in the session. The session itself stores state by setting a cookie which applies to all requests to an application. This middleware isolates that state so it can be applied to an individual sequence of requests. To do this, state is propagated in return_to and state parameters across requests. This middleware does this automatically whenever possible, such as when redirecting. When not possible, such as when rendering a view, locals and helpers are made available to the view so that return_to and state parameters can be added to links and forms.

This middleware emerged from the state management functionality implemented by authentication-related packages, in particular passport-oauth2 and oauth2orize which implement OAuth 2.0. With this package, the functionality is made generic so that it can be applied to any HTTP endpoint.

Install

$ npm install flowstate

Usage

Add Middleware

Add state middleware to your application or route:

var flowstate = require('flowstate');

app.get('/login', flowstate(), function(req, res, next) {
  // ...
});

The middleware will attempt to load any state intended for the endpoint, based the state parameter in the query or body of the request. If state is loaded, it will be set at req.state so that the handler can process it. The value set at req.state is referred to as the "current state".

If state is not loaded, an "uninitialized" state will be set at req.state. A state is uninitialized when it is new but not modified. If the request contains a return_to and optional state parameter, those will be captured by the uninitialized state as the location to return the user to when the current state has been completely processed.

When a response is sent, any modifications to the current state will be saved if the state is not complete. If the state is complete, any persisted state will be removed. Note that an uninitialized state will never be saved since it is not modified. However, the location to return the user to will be preserved by propagating the return_to and optional state parameters on subsequent requests.

Render a View

app.get('/login', flowstate(), function(req, res, next) {
  var msgs = req.state.messages || [];
  res.locals.messages = msgs;
  res.locals.hasMessages = !! msgs.length;
  res.render('login');
});

When a response is sent by rendering a view, if there is state associated with the request, res.locals.state will be set to the current state's handle. Otherwise the return_to and state parameters, if any, will be propagated by setting res.locals.returnTo and res.locals.state. The view is expected to decorate links with these properties and add them as hidden input to forms, in order to propagate state to subsequent requests.

For example, if the above /login endpoint is requested with a return_to parameter:

GET /login?return_to=%2Fdashboard  HTTP/1.1

Then res.locals.returnTo will be set to /dashboard, making it available to the view.

If the /login endpoint is requested with both a return_to and state parameter:

GET /login?return_to=%2Fauthorize%2Fcontinue&state=xyz  HTTP/1.1

Then res.locals.returnTo will be set to /authorize/continue and res.locals.state will be set to xyz, making them available to the view.

If the /login endpoint is requested with:

GET /login?state=Zwu8y84x  HTTP/1.1

Assuming the state was valid and intended for /login, res.locals.state will be set to Zwu8y84x and made available to the view. res.locals.returnTo will not be set.

Redirect to a Location

app.post('/login', flowstate(), authenticate(), function(req, res, next) {
  if (mfaRequired(req.user)) {
    return res.redirect('/stepup');
  }
  // ...
}, function(err, req, res, next) {
  if (err.status !== 401) { return next(err); }
  req.state.messages = req.state.messages || [];
  req.state.messages.push('Invalid username or password.');
  req.state.failureCount = req.state.failureCount ? req.state.failureCount + 1 : 1;
  req.state.complete(false);
  res.redirect('/login');
});

When a response redirects the browser, if the current state is complete, any return_to and state parameters will be propagated by decorating the target URL. If the current state is not complete, modifications will be saved and the redirect will be decorated with the current state's handle.

For example, if the above /login endpoint is requested with a return_to and state parameter:

POST /login HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded

username=alice&password=letmein&return_to=%2Fauthorize%2Fcontinue&state=xyz

Then the user will be redirected to /stepup?return_to=%2Fauthorize%2Fcontinue&state=xyz, assuming the password is valid and MFA is required.

If the password is not valid, an uninitialized state is set at req.state that captures the return_to and state parameters. It is then saved and the user is redirected to /login?state=Zwu8y84x (where 'Zwu8y84x' is the handle of the newly saved state). The state data stored in the session is as follows:

{
  "state": {
    "Zwu8y84x": {
      "location": "https://www.example.com/login",
      "messages": [ "Invalid username or password." ],
      "failureCount": 1,
      "returnTo": "/authorize/continue",
      "state": "xyz"
    }
  }
}

This redirect will cause the browser to request the GET /login route above. Since the request is made with a state=Zwu8y84x query parameter, the route will load the state and make the handle (as well as messages) available to the view. The view must add the handle to the login form as a hidden input field named state. When submitted, the browser will then make a request with that state parameter:

POST /login HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded

username=alice&password=letmeinnow&state=Zwu8y84x

This time, the POST /login route will load the state. If the password is valid and MFA is required, the user will be will be redirected to /stepup?return_to=%2Fauthorize%2Fcontinue&state=xyz, as before. This is because the original return_to and state parameters were captured by the loaded state object, and are propagated by decorating the redirect location.

If another invalid password is submitted, the cycle of redirecting, rendering the login view, and prompting the user for a password will repeat, with the failureCount incremented and saved each time.

Resume State

app.post('/login', flowstate(), authenticate(), function(req, res, next) {
  if (mfaRequired(req.user)) {
    return res.redirect('/stepup');
  }
  res.resumeState(next);
}, function(req, res, next) {
  res.redirect('/');
}, function(err, req, res, next) {
  // ...
});

When a user has completed a given flow, they should be returned to the location they were navigating prior to entering the flow. This is accomplished by calling resumeState(), a function added to the response by this middleware.

If a current state was loaded, resumeState() will return the user to the captured return_to and state parameters, if any. Otherwise, it will return the user to the return_to and state parameters carried by the request. If neither of these exist, resumeState() will call a callback, which will typically be next to invoke the next middleware. This middleware can then redirect the user to a default location.

For example, when POST /login is requested with a state parameter:

POST /login HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded

username=alice&password=letmeinnow&state=Zwu8y84x

Then the user will be redirected to /authorize/continue&state=xyz, assuming the password is valid and MFA is not required.

If the /login endpoint is requested with a return_to parameter:

POST /login HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded

username=alice&password=letmein&return_to=%2Fdashboard

Then the user will be redirected to /dashboard, after logging in.

If the /login endpoint is requested without any state-related parameters:

POST /login HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded

username=alice&password=letmein

Then the user will be redirected to / by the next middleware in the stack.

Push State

Authors

License

The MIT License

Copyright (c) 2016-2023 Jared Hanson