@teipublisher/pb-components
Version:
Collection of webcomponents underlying TEI Publisher
321 lines (298 loc) • 10.4 kB
JavaScript
import { LitElement, html, css } from 'lit-element';
import { pbMixin, waitOnce } from './pb-mixin.js';
import { translate } from "./pb-i18n.js";
import { registry } from "./urls.js";
import '@polymer/iron-ajax';
import '@polymer/paper-dialog';
import '@polymer/paper-dialog-scrollable';
import '@polymer/paper-input/paper-input.js';
import '@polymer/paper-button';
import '@polymer/iron-icons';
import { minVersion } from './utils.js';
/**
* Handles login/logout. Shows a link which opens a login dialog if clicked.
* If a user is logged in, clicking the link will log him out instead.
*
* @slot information - Additional information to be presented on the login dialog
* @fires pb-login - Sends results of checking user credentials
* @cssprop --pb-login-link-color - Color of the link text
* @csspart message-invalid - Block displayed if login is invalid
* @csspart group-invalid - Text displayed if login is invalid concerning group
*/
export class PbLogin extends pbMixin(LitElement) {
static get properties() {
return {
...super.properties,
/** True if user is currently logged in */
loggedIn: {
type: Boolean,
attribute: 'logged-in',
reflect: true
},
/**
* The currently logged in user.
*/
user: {
type: String
},
password: {
type: String
},
/**
* If set, only users being members of the specified group are
* allowed to log in.
* Multiple groups can be defined separating items by space and/or comma.
*/
group: {
type: String
},
/**
* Array of groups the current user is a member of.
*/
groups: {
type: Array
},
/**
* If set to true, automatically show login dialog if user is not logged in
*/
auto: {
type: Boolean
},
/**
* Label to show if not logged in
*/
loginLabel: {
type: String,
reflect: true,
attribute: 'login-label'
},
/**
* Label to show before user name if logged in
*/
logoutLabel: {
type: String,
reflect: true,
attribute: 'logout-label'
},
loginIcon: {
type: String,
attribute: 'login-icon'
},
logoutIcon: {
type: String,
attribute: 'logout-icon'
},
_invalid: {
type: Boolean
},
_hasFocus: {
type: Boolean
}
};
}
constructor() {
super();
this.loggedIn = false;
this.loginLabel = 'login.login';
this.logoutLabel = 'login.as';
this.user = '';
this.groups = [];
this.loginIcon = 'account-circle';
this.logoutIcon = 'supervisor-account';
this._hasFocus = true;
}
firstUpdated() {
super.firstUpdated();
this._checkLogin = this.shadowRoot.getElementById('checkLogin');
this._loginDialog = this.shadowRoot.getElementById('loginDialog');
window.addEventListener('blur', () => {
this._hasFocus = false;
});
window.addEventListener('focus', () => {
if (!this._hasFocus) {
this._hasFocus = true;
this._checkLogin.body = null;
this._checkLogin.generateRequest();
}
});
waitOnce('pb-page-ready', (detail) => {
if (minVersion(detail.apiVersion, '1.0.0')) {
this._checkLogin.url = `${detail.endpoint}/api/login/`;
} else {
this._checkLogin.url = `${detail.endpoint}/login`;
}
this._checkLogin.body = {
user: this.user,
password: this.password
};
this._checkLogin.generateRequest();
});
this.addEventListener('keyup', event => {
if (event.keyCode === 13) {
event.preventDefault();
this._confirmLogin();
}
});
}
render() {
return html`
<a href="#" ="${this._show}" role="button" title="${this.user ? this.user : this.loginLabel}">
${
this.loggedIn ?
html`<iron-icon icon="${this.logoutIcon}"></iron-icon> <span class="label">${translate(this.logoutLabel, { user: this.user })}</span>` :
html`<iron-icon icon="${this.loginIcon}"></iron-icon> <span class="label">${translate(this.loginLabel)}</span>`
}
</a>
<paper-dialog id="loginDialog" ?modal="${this.auto}">
<h2>${translate('login.login')}</h2>
<paper-dialog-scrollable>
<form action="login">
<paper-input id="user" name="user" label="${translate('login.user')}" value="${this.user}" autofocus></paper-input>
<paper-input id="password" name="password" label="${translate('login.password')}" type="password"></paper-input>
<input id="logout" type="hidden" name="logout"></input>
</form>
<slot name="information"></slot>
${this._invalid ?
html`
<p id="message" part="message-invalid">${translate('login.invalid')}<span part="group-invalid">${this.group ? html` (${translate('login.requiredGroup', { group: this.group })})` : null}</span>.
</p>
`: null
}
</paper-dialog-scrollable>
<div class="buttons">
<paper-button autofocus ="${this._confirmLogin}">${translate(this.loginLabel)}</paper-button>
</div>
</paper-dialog>
<iron-ajax id="checkLogin" with-credentials
handle-as="json" ="${this._handleResponse}" ="${this._handleError}"
method="post"
content-type="application/x-www-form-urlencoded"></iron-ajax>
`;
}
static get styles() {
return css`
:host {
display: block;
}
paper-dialog {
min-width: 320px;
max-width: 640px;
min-height: 128px;
}
paper-dialog h2 {
background-color: #607D8B;
padding: 16px 8px;
margin-top: 0;
color: #F0F0F0;
}
a {
color: var(--pb-login-link-color, --pb-link-color);
text-decoration: none;
display: flex;
gap:0.5rem;
}
#message {
color: var(--paper-red-800);
}
`;
}
_show(ev) {
ev.preventDefault();
if (this.loggedIn) {
this._checkLogin.body = {
logout: this.user
};
this._checkLogin.generateRequest();
} else {
this._loginDialog.open();
}
}
_confirmLogin() {
this.user = this.shadowRoot.getElementById('user').value;
this.password = this.shadowRoot.getElementById('password').value;
this._checkLogin.body = {
user: this.user,
password: this.password
};
this._checkLogin.generateRequest();
}
_handleResponse() {
const resp = this._checkLogin.lastResponse;
if (resp.user && this._checkGroup(resp)) {
resp.userChanged = !this.loggedIn || this.user !== resp.user;
this.loggedIn = true;
this.user = resp.user;
this.groups = resp.groups;
this._invalid = false;
this._loginDialog.close();
} else {
resp.userChanged = this.loggedIn;
this.loggedIn = false;
this.password = null;
if (this._loginDialog.opened) {
this._invalid = true;
} else if (this.auto) {
this._loginDialog.open();
}
}
this.emitTo('pb-login', resp);
if (this.loggedIn) {
registry.currentUser = {
user: this.user,
groups: this.groups
}
} else {
registry.currentUser = null;
}
}
_handleError() {
this.loggedIn = false;
this.password = null;
const resp = {
userChanged: this.loggedIn,
user: null
};
if (this._loginDialog.opened) {
this._invalid = true;
} else if (this.auto) {
this._loginDialog.open();
}
registry.currentUser = null;
this.emitTo('pb-login', resp);
}
/**
*
* @param {Array<String>} arr array containg string values (name of groups)
* @param {String} val value to check if it's in the array
* @returns true if the checked values is in the array
*/
_isItemInArray(arr, val) {
return arr.some((arrVal) => val === arrVal);
}
/**
*
* @param {object} info object returned by login function;
* contains groups the user is a member of
* @returns true if user is member of one of defined groups
*/
_checkGroup(info) {
if (this.group) {
let groupArray = this.group.split(/[\s+,]+/);
let exists = false;
if(info.groups)
groupArray.forEach(async (oneItem) => {
exists = this._isItemInArray(info.groups, oneItem) || exists;
});
return exists;
}
return true;
}
/**
* Fired on successful login.
*
* @event pb-login
* @param {String} user logged in user
* @param {Array<String>} groups groups the user is a member of
*/
}
customElements.define('pb-login', PbLogin);