strolch-wc-auth/strolch-wc-auth.html

640 lines
25 KiB
HTML

<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../paper-material/paper-material.html">
<link rel="import" href="../paper-card/paper-card.html">
<link rel="import" href="../paper-checkbox/paper-checkbox.html">
<link rel="import" href="../paper-input/paper-input.html">
<link rel="import" href="../paper-dialog/paper-dialog.html">
<link rel="import" href="../paper-button/paper-button.html">
<link rel="import" href="../iron-a11y-keys/iron-a11y-keys.html">
<link rel="import" href="../iron-ajax/iron-ajax.html">
<link rel="import" href="../strolch-wc-localize-behavior/strolch-wc-localize-behavior.html">
<dom-module id="strolch-wc-auth">
<template>
<style>
:host {
display: block;
min-height: 100%;
--input-line-height: 24px;
}
.container {
width: 100%;
}
.centered {
width: 350px;
margin-left: auto;
margin-right: auto;
margin-top: 3rem;
}
paper-card {
width: 100%;
}
.card-actions {
text-align: right;
}
paper-input {
--paper-input-container-input: {
height: var(--input-line-height);
line-height: var(--input-line-height);
};
}
</style>
<div class="container">
<div id="authForm" class="centered">
<iron-a11y-keys id="a11y" keys="enter" on-keys-pressed="_submitForm"></iron-a11y-keys>
<paper-card heading="[[localize('login')]]">
<div class="card-content">
<p>[[localize('pleaseLogin', 'appTitle', appTitle)]]</p>
<paper-input id="usernameInput"
value="{{username}}"
label="[[localize('username')]]"
required
autofocus="true"
auto-validate></paper-input>
<paper-input id="passwordInput"
value="{{password}}"
label="[[localize('password')]]"
required
type="password"
on-focus="onFocus"
auto-validate></paper-input>
<template is="dom-if" if="[[showKeepAlive]]">
<paper-checkbox checked="{{keepAlive}}">[[localize('keepAlive')]]</paper-checkbox>
</template>
<template is="dom-if" if="[[showVersion]]">
<template is="dom-if" if="[[showAppVersion]]">
<p>[[appName]] [[appVersion]] @ [[_getEnv(environment)]]</p>
</template>
<template is="dom-if" if="[[showAppEnvironment]]">
<p>[[appName]] @ [[_getEnv(environment)]]</p>
</template>
</template>
</div>
<div class="card-actions">
<paper-button on-tap="_showResetForm">[[localize('reset')]]</paper-button>
<paper-button on-tap="_submitForm">[[localize('login')]]</paper-button>
</div>
</paper-card>
</div>
<div id="initiateResetForm" class="centered" hidden>
<iron-a11y-keys id="a11y" keys="enter" on-keys-pressed="_initiateReset"></iron-a11y-keys>
<paper-card heading="[[localize('passwordReset')]]">
<div class="card-content">
<p>[[localize('passwordResetDesc')]]</p>
<paper-input id="usernameResetInput"
value="{{username}}"
label="[[localize('username')]]"
required
autofocus="true"
auto-validate></paper-input>
</div>
<div class="card-actions">
<paper-button on-tap="_cancelReset">[[localize('cancel')]]</paper-button>
<paper-button on-tap="_initiateReset">[[localize('reset')]]</paper-button>
</div>
</paper-card>
</div>
<div id="resetInitiated" class="centered" hidden>
<iron-a11y-keys id="a11y" keys="enter" on-keys-pressed="_validateChallenge"></iron-a11y-keys>
<paper-card heading="[[localize('passwordReset')]]">
<div class="card-content">
<p>[[localize('passwordResetInitiated')]]</p>
<p>[[localize('passwordResetCode')]]</p>
<paper-input id="codeInput"
bind-value="{{code}}"
label="[[localize('code')]]"
required
autofocus="true"
auto-validate></paper-input>
</div>
<div class="card-actions">
<paper-button on-tap="_validateChallenge">[[localize('send')]]</paper-button>
</div>
</paper-card>
</div>
<div id="resetForm" class="centered" hidden>
<iron-a11y-keys id="a11y" keys="enter" on-keys-pressed="_doReset"></iron-a11y-keys>
<paper-card heading="[[localize('passwordReset')]]">
<div class="card-content">
<template is="dom-if" if="[[isPasswordChangeRequested]]">
<p>[[localize('passwordChangeRequest')]]</p>
</template>
<template is="dom-if" if="[[!isPasswordChangeRequested]]">
<p>[[localize('passwordResetNew')]]</p>
</template>
<paper-input id="password1Input"
bind-value="{{password1}}"
label="[[localize('password')]]"
required
type="password"
on-focus="onFocus"
auto-validate></paper-input>
<paper-input id="password2Input"
bind-value="{{password2}}"
label="[[localize('repeat')]]"
required
type="password"
on-focus="onFocus"
auto-validate></paper-input>
</div>
<div class="card-actions">
<paper-button on-tap="_doReset">[[localize('reset')]]</paper-button>
</div>
</paper-card>
</div>
<div id="passwordResetDone" class="centered" hidden>
<iron-a11y-keys id="a11y" keys="enter" on-keys-pressed="_toLogin"></iron-a11y-keys>
<paper-card heading="[[localize('passwordReset')]]">
<div class="card-content">
<p>[[localize('passwordResetDone')]]</p>
</div>
<div class="card-actions">
<paper-button id="backToLoginBtn" on-tap="_showLoginForm">[[localize('login')]]</paper-button>
</div>
</paper-card>
</div>
</div>
<paper-dialog id="dlg" modal on-iron-overlay-closed="onCloseDlg">
<h2>[[dlgTitle]]</h2>
<p>[[dlgText]]</p>
<div class="buttons">
<paper-button dialog-confirm autofocus>[[localize('close')]]</paper-button>
</div>
</paper-dialog>
<iron-ajax id="ajaxAuthCheck"
handle-as="json"
method="GET"
on-response="sessionValidated"
on-error="sessionInvalid"></iron-ajax>
<iron-ajax id="ajaxAuth"
content-type="application/json"
handle-as="json"
method="POST"
on-response="_ajaxResponse"
on-error="_handleAjaxError"></iron-ajax>
<iron-ajax id="ajaxGetVersion"
url="[[basePath]]rest/strolch/version"
content-type="application/json"
handle-as="json"
method="GET"
on-response="onGetVersionResponse"
on-error="onGetVersionError"></iron-ajax>
</template>
<script>
Polymer({
is: 'strolch-wc-auth',
behaviors: [
StrolchLocalizeBehavior
],
properties: {
basePath: {
type: String,
value: './'
},
localesPath: {
type: String,
value: './locales.json'
},
appTitle: {
type: String
},
showKeepAlive: {
type: Boolean,
value: false
},
showVersion: {
type: Boolean,
value: false
},
showAppVersion: {
type: Boolean,
value: false
},
showAppEnvironment: {
type: Boolean,
value: false
},
appVersion: {
type: String,
value: null
},
environment: {
type: String,
value: null
},
username: {
type: String
},
password: {
type: String
},
keepAlive: {
type: Boolean,
value: false
},
password1: {
type: String
},
password2: {
type: String
},
code: {
type: String
},
dlgTitle: {
type: String
},
isPasswordChangeRequested: {
type: Boolean,
value: false
}
},
computeShowAppVersion: function (appVersion, environment) {
return appVersion != null && environment != null;
},
computeShowAppEnvironment: function (appVersion, environment) {
return appVersion == null && environment != null;
},
ready: function () {
// do nothing
},
onFocus: function (evt) {
evt.target.$.input.select();
},
onCloseDlg: function (evt) {
this.$.passwordInput.inputElement.select();
},
reload: function () {
if (this.showVersion) {
this.$.ajaxGetVersion.generateRequest();
}
this.username = '';
this.password = '';
this.password1 = '';
this.password2 = '';
this.code = '';
this._hideAll();
this.$.authForm.hidden = false;
if (Strolch.hasAuthToken()) {
this.$.ajaxAuthCheck.url = this.basePath + 'rest/strolch/authentication/' + Strolch.getCookie("strolch.authorization");
this.$.ajaxAuthCheck.generateRequest();
console.log("Validating session...");
}
this.async(function () {
this.$.usernameInput.focus();
}, 100);
},
sessionValidated: function (e) {
console.log("Session validated.");
var userConfig = e.detail.response;
Strolch.setUserConfig(userConfig);
Strolch.sessionVerified = true;
this.fire('strolch-session-valid', {
sessionVerified: true
});
if (userConfig.keepAlive && userConfig.refreshAllowed) {
this.handleKeepAlive(userConfig);
}
},
handleKeepAlive: function (userConfig) {
console.log("Checking keep alive and Session TTL...");
var expiry = new Date(Strolch.getCookie("strolch.authorization.expirationDate"));
var now = new Date();
var diff = expiry.getTime() - now.getTime();
var expiryMin = Math.floor(diff / 1000 / 60);
var keepAliveMinutes = Number.parseInt(userConfig.keepAliveMinutes);
var keepAliveDays = keepAliveMinutes / 60 / 24;
var delayMin = 14;
if (keepAliveMinutes > 1440) {
// more than a day
if (expiryMin < 1440) {
// expires today
// refresh now
console.log("Keep alive is " + keepAliveDays + " days and expiring today. Refreshing...");
this.refreshSession();
} else {
// check again in delayMin min
console.log("Keep alive is " + keepAliveDays + " days and expiring in the future. Delaying refresh for " + delayMin + "m...");
this.async(function () {
this.handleKeepAlive(Strolch.getUserConfig());
}, delayMin * 60 * 1000);
}
} else if (keepAliveMinutes < 15) {
console.error("The keepAliveMinutes is < 15minutes! Server is badly configured, ignoring!");
} else {
// less than a day
if (expiryMin > 15) {
// more than 15 min
// check again in delayMin
console.log("Keep alive is " + keepAliveMinutes + " minutes and expiring in " + expiryMin + "m. Delaying refresh for " + delayMin + "m...");
setTimeout(function () {
this.handleKeepAlive(Strolch.getUserConfig());
}.bind(this), delayMin * 60 * 1000);
} else {
// less than 15 min
// refresh now
console.log("Keep alive is " + keepAliveMinutes + " minutes and expiring in " + expiryMin + "m. Refreshing...");
this.refreshSession();
}
}
},
sessionInvalid: function () {
console.log("Session invalid.");
Strolch.sessionVerified = false;
Strolch.clearStorageData();
},
_ajaxResponse: function () {
console.log('No response handler defined!');
},
_handleAjaxError: function (e) {
var dlgText;
if (e.detail.request.response) {
dlgText = e.detail.request.response.msg;
} else {
dlgText = e.detail.error;
}
this.showError(this.dlgTitle, dlgText);
},
logout: function () {
console.log('Logging out...');
this._ajaxResponse = function (e) {
console.log('Logged out.');
Strolch.clearStorageData();
};
this.dlgTitle = this.localize('logoutFailed');
this.$.ajaxAuth.url = this.basePath + 'rest/strolch/authentication/' + Strolch.getAuthToken();
this.$.ajaxAuth.method = 'DELETE';
this.$.ajaxAuth.generateRequest();
},
refreshSession: function () {
console.log("Refreshing session...");
if (Strolch.isEmptyString(Strolch.getAuthToken())) {
console.log("Can not refresh session as no auth token available!");
return;
}
this._ajaxResponse = function (e) {
console.log('Refreshed session which was about to expire...');
var data = e.detail.response;
var cookieExpiry = new Date(data.authorizationExpiration);
Strolch.setCookie("strolch.authorization", data.authToken, cookieExpiry);
Strolch.setCookie("strolch.authorization.expirationDate", data.authorizationExpiration, cookieExpiry);
Strolch.setAuthToken(data.authToken);
Strolch.setUserConfig(data);
};
this.dlgTitle = this.localize('sessionRefereshFailed');
this.$.ajaxAuth.url = this.basePath + 'rest/strolch/authentication/' + Strolch.getAuthToken();
this.$.ajaxAuth.method = 'PUT';
this.$.ajaxAuth.generateRequest();
},
_submitForm: function () {
if (!this.$.usernameInput.validate()) {
this.showError(this.localize('missingInput'), this.localize('usernameEmpty'));
return;
}
if (!this.$.passwordInput.validate()) {
this.showError(this.localize('missingInput'), this.localize('passwordEmpty'));
return;
}
console.log('Authenticating...');
this._ajaxResponse = function (e) {
if (e.detail.response.usage === "set-password") {
this.authToken = e.detail.response.authToken;
this.isPasswordChangeRequested = true;
this._hideAll();
this.$.resetForm.hidden = false;
this.$.password1Input.focus();
return;
}
console.log('Logged in.');
Strolch.setAuthToken(e.detail.response.authToken);
Strolch.setUserConfig(e.detail.response);
location.reload();
};
this.dlgTitle = this.localize('authenticationFailed');
this.$.ajaxAuth.body = {
username: this.username,
password: btoa(unescape(encodeURIComponent(this.password))),
keepAlive: this.keepAlive
};
this.password = "";
this.$.ajaxAuth.url = this.basePath + 'rest/strolch/authentication';
this.$.ajaxAuth.method = 'POST';
this.$.ajaxAuth.generateRequest();
},
_hideAll: function () {
this.$.authForm.hidden = true;
this.$.initiateResetForm.hidden = true;
this.$.resetInitiated.hidden = true;
this.$.resetForm.hidden = true;
this.$.passwordResetDone.hidden = true;
},
_showResetForm: function () {
this._hideAll();
this.$.initiateResetForm.hidden = false;
this.$.usernameResetInput.focus();
},
_showLoginForm: function () {
this._hideAll();
this.$.authForm.hidden = false;
this.$.passwordInput.inputElement.select();
},
_cancelReset: function () {
this._hideAll();
this.$.authForm.hidden = false;
},
_initiateReset: function () {
if (!this.$.usernameResetInput.validate()) return;
console.log('Initiating reset...');
this._ajaxResponse = function (data) {
this._hideAll();
this.$.resetInitiated.hidden = false;
this.$.codeInput.focus();
};
this.dlgTitle = this.localize('resetFailed');
this.$.ajaxAuth.url = this.basePath + 'rest/strolch/authentication/challenge';
this.$.ajaxAuth.method = 'POST';
this.$.ajaxAuth.body = {
username: this.username,
usage: 'set-password'
};
this.$.ajaxAuth.generateRequest();
},
_validateChallenge: function () {
if (!this.$.codeInput.validate()) return;
console.log('Validating challenge...');
this._ajaxResponse = function (data) {
this.authToken = data.detail.response.authToken;
this._hideAll();
this.$.resetForm.hidden = false;
this.$.password1Input.focus();
};
this.dlgTitle = this.localize('resetFailed');
this.$.ajaxAuth.url = this.basePath + 'rest/strolch/authentication/challenge';
this.$.ajaxAuth.method = 'PUT';
this.$.ajaxAuth.body = {
username: this.username,
challenge: this.code
};
this.$.ajaxAuth.generateRequest();
},
_doReset: function () {
if (!this.$.password1Input.validate() || !this.$.password2Input.validate()) return;
if (this.password1 !== this.password2) {
this.$.password1Input.invalid = true;
this.$.password2Input.invalid = true;
var dlgTitle = this.localize('resetFailed');
var dlgText = this.localize('passwordsDontMatch');
this.showError(dlgTitle, dlgText);
}
console.log('Doing reset...');
this._ajaxResponse = function (e) {
this._hideAll();
this.$.passwordResetDone.hidden = false;
this.$.backToLoginBtn.focus();
};
this.dlgTitle = this.localize('resetFailed');
this.$.ajaxAuth.url = this.basePath + 'rest/strolch/privilege/users/' + this.username + '/password';
this.$.ajaxAuth.headers.authorization = this.authToken;
this.$.ajaxAuth.method = 'PUT';
this.$.ajaxAuth.body = {
password: btoa(unescape(encodeURIComponent(this.password1)))
};
this.password = "";
this.password1 = "";
this.password2 = "";
this.$.ajaxAuth.generateRequest();
},
onGetVersionResponse: function (e) {
var version = e.detail.response;
this.appName = version.agentVersion.agentName;
this.appVersion = version.appVersion.artifactVersion;
this.environment = version.agentVersion.environment;
this.showAppVersion = this.computeShowAppVersion(this.appVersion, this.environment)
this.showAppEnvironment = this.computeShowAppEnvironment(this.appVersion, this.environment)
Strolch.setAppVersion(version);
sessionStorage.setItem("strolchAppVersion", JSON.stringify(version));
},
onGetVersionError: function (e) {
var readyState = e.detail.request.xhr.readyState;
var status = e.detail.request.xhr.status;
console.log("Ignoring get version error due to readyState: " + readyState + " / status: " + status);
},
_getEnv: function (environment) {
console.log("environment=" + environment)
if (environment == null)
return "-";
if (environment.indexOf('prod') >= 0) {
return this.localize('production');
} else if (environment.indexOf('test') >= 0) {
return this.localize('testing');
} else if (environment.indexOf('stag') >= 0) {
return this.localize('staging');
} else if (environment.indexOf('dev') >= 0) {
return this.localize('development');
} else {
return environment;
}
},
showError: function (title, text) {
this.dlgTitle = title;
this.dlgText = text;
console.log('ERROR: ' + this.dlgTitle + ': ' + this.dlgText);
this.$.dlg.open();
}
});
</script>
</dom-module>