Coder Social home page Coder Social logo

Securing Routes? about akc-route HOT 3 OPEN

c256985 avatar c256985 commented on August 28, 2024
Securing Routes?

from akc-route.

Comments (3)

akc42 avatar akc42 commented on August 28, 2024

For a while, I have had a situation in which certain routes required specific user permissions. I handle these on the client by hiding the links that take me to that route, and on the server by returning a not authorised (clients are logged on via an encrypted cookie which contains user permissions). But just in the last week I now have a requirement to prompt the user for the password also when they try and access some particular urls (which generate reports of particularly sensitive information)

The way I do it, is to have a "password gateway" element between <akc-location> and the highest <akc-route> in the chain.It has an inRoute and an outRoute. It has a list of urls that constitute the secure area and an internal flag that remembers if the last request was within or without the secure area. Changes in inRoute are passed straight through to outRoute, unless its a transition from non-secure to secure - in which case a dialog box is raised to prompt for the password. Upon entry of the password, an api request is sent to the server to validate password, and only when a successful response is received is the inRoute passed through to the outRoute.

Changes to outRoute have to be passed back up to the inRoute, so changes made by an element to a route down the chain can be reflected back to the location bar. The only issue with that was some care has to be taken as when to copy route objects or just reference them to avoid an infinite loop of changes

Here is the guts of that element

<dom-module id="pas-password-gateway">
  <template>
    <style>
      :host {
        display: block;
      }
      paper-button {
        background-color:var(--app-button-color);
        color:var(--app-button-text);
        text-align: center;
      }

      paper-button.data {
        background-color: var(--app-button-data-color);
      }

      paper-button.cancel {
        background-color: var(--app-cancel-color);
      }
    </style>
    <pas-waiting waiting="[[waiting]]"></pas-waiting>
    <iron-a11y-keys
      target="[[keyTarget]]"
      keys="enter"
      stop-keyboard-event-propagation
      on-keys-pressed="_keyUpdate"></iron-a11y-keys>
    <iron-a11y-keys
      target="[[keyTarget]]"
      keys="esc"
      stop-keyboard-event-propagation
      on-keys-pressed="_keyCancel"></iron-a11y-keys>
    <paper-dialog
      id="inputdialog"
      horizontal-align="center"
      vertical-align="top"
      always-on-top
      no-cancel-on-outside-click
      on-iron-overlay-closed="_inputClosed">
      <h2>Please enter your password</h2>
      <paper-input
      label="Password"
      autofocus
      error-message="Password Incorrect"
      type="password"
      name="password"
      id="pw"
      invalid="[[invalid]]"
      value="{{password}}"></paper-input>
      <div class="buttons">
        <paper-button
          class="action"
          raised
          dialog-confirm><iron-icon icon="pas:check"></iron-icon>Check</paper-button>
        <paper-button
          class="cancel"
          raised
          dialog-dismiss><iron-icon icon="pas:cancel"></iron-icon>Cancel</paper-button>
      </div>
    </paper-dialog>
  </template>
  <script>
    class PasPasswordGateway extends PAS.Ajax(Polymer.Element) {
      static get is() {return 'pas-password-gateway';}
      static get properties() {
        return {
          inRoute: {
            type: Object,
            notify: true
          },
          outRoute: {
            type: Object,
            value: function() {return {path: '', active: false, params: {}, query: {}};},
            notify: true
          },
          keyTarget: {
            type: Object
          },
          waiting: {
            type: Boolean,
            value: false
          },
          password: {
            type: String,
            value: ''
          },
          invalid: {
            type: Boolean,
            value: false
          }
        };
      }
      static get observers() {
        return [
          '_inRouteChanged(inRoute.*)',
          '_outRouteChanged(outRoute.*)'
        ];
      }
      static get secureRoutes() {
        return [
          '/reports/bydate/bill',
          '/reports/queries/paysum',
          '/reports/queries/payres',
          '/reports/queries/paymet',
          '/reports/queries/costeye',
          '/reports/queries/lenspay',
          '/reports/queries/foretot',
          '/reports/queries/sumfore',
          '/reports/queries/detfore'
        ];
      }
      ready() {
        super.ready();
        this.keyTarget = this.$.inputdialog;
        this.inSecureSection = false;
        this.dialogOpen = false;
        this.changesPath = '';
        this.changesValue = {path: '', active: false, params: {}, query: {}};
        this.passingDown = false;
      }
      _inputClosed(e) {
        e.stopPropagation();
        if (e.target !== this.$.inputdialog) return;
        if (e.detail.confirmed || (e.detail.confirmed === undefined && !e.detail.canceled)) {
          this.waiting = true;
          this.api('checkpass', {pass: this.password}).then(response => {
            this.waiting = false;
            if (response.pass) {
              this.dialogOpen = false;
              this.invalid = false;
              this.inSecureSection = true;
              this.set('outRoute' + this.changesPath, this.changesValue);
              this.changesPath = '';
              this.changesValue = {path: '', active: false, params: {}, query: {}};
              this.password = '';
            } else {
              this.invalid = true;
              this.$.inputdialog.open(); //need to collect password
            }
          });
        } else {
          this.dialogOpen = false;
          this.changesPath = '';
          this.changesValue = {path: '', active: false, params: {}, query: {}};
          this.password = '';
          window.history.go(-1); //have to return to previous route
        }
      }

      _inRouteChanged(changes) {
        if (changes.base === undefined) return;
        if (this._isASecureRoute(changes.base.path)) {
          if(!this.inSecureSection) {
            if (!this.dialogOpen) {
              this.dialogOpen = true;
              this.changesPath = changes.path.substring(7);
              this.passingDown = true;
              if (this.changesPath.length === 0) {
                this.changesValue = Object.assign({}, changes.value);
              } else {
                this.changesValue = changes.value;
              }
              this.passingDown = false;
              this.$.inputdialog.open(); //need to collect password
            }
          } else {
            //we already entered  secure section and not left it so we can continue
            this.passingDown = true;
            if (changes.path.length === 7) {
              this.set('outRoute', Object.assign({}, changes.value));
            } else {
              this.set('outRoute' + changes.path.substring(7), changes.value);
            }
            this.passingDown = false;
          }
        } else {
          this.inSecureSection = false;
          this.passingDown = true;
          if (changes.path.length === 7) {
            this.set('outRoute', Object.assign({}, changes.value));
          } else {
            this.set('outRoute' + changes.path.substring(7), changes.value);
          }
          this.passingDown = false;
        }
      }
      _isASecureRoute(path) {
        for (let sp of PasPasswordGateway.secureRoutes) {
          if (path.substring(0,sp.length) === sp) return true;
        }
        return false;
      }
      _keyCancel(e) {
        e.stopPropagation();
        this.$.inputdialog.cancel();
      }
      _keyUpdate(e) {
        e.stopPropagation();
        this.$.inputdialog.close();
      }
      _outRouteChanged(changes) {
        //don't reflext upwards if not ready yet
        if (this.inRoute === undefined || changes.base === undefined || this.passingDown) return;
        if (changes.base.active) {
          if (changes.path.length === 8) {
            this.set('inRoute', Object.assign({},changes.value));
          } else {
            this.set('inRoute' + changes.path.substring(8), changes.value);
          }
        }
      }
    }
    customElements.define(PasPasswordGateway.is, PasPasswordGateway);
  </script>
</dom-module>

I realise looking above there are a couple of things that might need some explanation,

PAS.Ajax is a class mixin which provides this.api wrapper around the fetch api, but with lots of the specifics sorted from my app. It sends the the userid and password to /api/checkpass and fulfils the return promise with the json response.

<pas-waiting> just puts a rotating busy icon in the middle screen when this.waiting is true

from akc-route.

c256985 avatar c256985 commented on August 28, 2024

Interesting. You might want to use a map or a set for secure routes, since this would obviate the need to iterate through the routes. The API wrapper is also an interesting idea, since this presumably gives you a more natural REST interface with the appropriate PUT/GET/DELETE/PATCH verbs.

It might also be good to factor out the routes as a configuration and have some sort of named routes. You could then have production, test, and dev routes, and the API wrapper would construct the appropriate URL for you. This would let the developer then say, "I need the "getUsers" route for production, and it then checks to see if the user has the appropriate role, and builds the appropriate URL on the fly.

from akc-route.

akc42 avatar akc42 commented on August 28, 2024

Problem with a map (or set) is that I am comparing a partial string with the secure route as the actual path has quite a bit more in it, so its helpful when iterating the array to have the length of each secure path, so I can just check the front of the actual path.

from akc-route.

Related Issues (9)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.