Comments (3)
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.
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.
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)
- Readme no longer reflects the functions
- Problem with New Route being set
- Router loops when inRoute doesn't change as a result of outRoute change
- If we update a parameter with value 0 it doesn't propagate
- Adjusting outroute incorrectly adds existing outroute path when outroute was not active
- A numeric segment with a minus sign is not being converted to a numeric parameter
- Documentation problems
- Feature request / suggestion HOT 8
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from akc-route.