UNPKG

openhab-multiuser-proxy

Version:

Multi-User support for openHAB REST API with NGINX.

266 lines (251 loc) 10.7 kB
import logger from './../../logger.js'; import fetch from 'node-fetch'; import { getHeaders } from '../../utils.js'; import { itemsListDb, itemsForUserDb } from '../../db.js'; import { CACHE_TIME, CACHE_TIME_ACL, ADMIN_OU, EVERYONE_OU, ACL_PREFIX } from '../../server.js'; /** * Items backend namespace. Providing access to the openHAB backend. * * @namespace itemsBackend */ /** * Gets all Items. * Utilising LokiJS to cache the Items list for better performance. * * @memberof itemsBackend * @param {String} HOST hostname of openHAB server * @param {*} expressReq request object from expressjs * @returns {Object} Object: { json: JSON reponse, status: HTTP status code } */ export const getAllItems = async function (HOST, expressReq) { //process only query parameters defined in API let query = ''; if (expressReq.query.metadata) query = 'metadata=' + expressReq.query.metadata; if (expressReq.query.recursive) { if (query) query = query + '&'; query = query + 'recursive=' + expressReq.query.recursive; } if (expressReq.query.type) { if (query) query = query + '&'; query = query + 'type=' + expressReq.query.type; } if (expressReq.query.tags) { if (query) query = query + '&'; query = query + 'tags=' + expressReq.query.tags; } if (expressReq.query.fields) { if (query) query = query + '&'; query = query + 'fields=' + expressReq.query.fields; } const now = Date.now(); const itemsList = itemsListDb.findOne({ name: query }); if (itemsList) { if (now < itemsList.lastupdate + CACHE_TIME) { // Currently stored version not older than CACHE_TIME. logger.debug('getAllItems(): Found in database and not older than CACHE_TIME.'); return itemsList.json; } itemsListDb.findAndRemove({ name: query }); } const headers = await getHeaders(expressReq); if (query) query = '?' + query; try { const response = await fetch(HOST + '/rest/items' + query, { headers: headers }); const json = await response.json(); itemsListDb.insert({ name: query, lastupdate: now, json: json }); const status = response.status; logger.debug(`getAllItems(): Successfully requested backend ${HOST + '/rest/items' + query}, HTTP response code ${status}`); return json; } catch (err) { const error = new Error(`getAllItems(): An error occurred while getting all Items from ${HOST + '/rest/items' + query}: ${err}`); logger.error(error); error(); } }; /** * Gets a single Item by itemname. * * @memberof itemsBackend * @param {String} HOST hostname of openHAB server * @param {*} expressReq request object from expressjs * @param {String} itemname Item name * @returns {Object} Object: { json: JSON reponse, status: HTTP status code } */ export const getItem = async function (HOST, expressReq, itemname) { const headers = await getHeaders(expressReq); //process only query parameters defined in API let query = ''; if (expressReq.query.metadata) query = 'metadata=' + expressReq.query.metadata; if (expressReq.query.recursive) { if (query) query = query + '&'; query = query + 'recursive=' + expressReq.query.recursive; } if (query) query = '?' + query; try { const response = await fetch(HOST + '/rest/items/' + itemname + query, { headers: headers }); const json = await response.json(); const status = response.status; logger.debug(`getItem(): Successfully requested backend ${HOST + '/rest/items/' + itemname + query}, HTTP response code ${status}`); return { json: json, status: status }; } catch (err) { const error = new Error(`getItem(): An error occurred when requesting backend ${HOST + '/rest/items/' + itemname + query}: ${err}`); logger.error(error); error(); } }; /** * Gets itemnames's of all allowed Items for a user. * Utilising LokiJS to cache filtered Items list for better performance. * * @memberof itemsBackend * @param {String} HOST hostname of openHAB server * @param {*} expressReq request object from expressjs * @param {String} user username * @param {String|Array<String>} org organizations the user is member of * @returns {Array<String>} itemname's of items allowed for a user */ export const getItemsForUser = async function (HOST, expressReq, user, org) { if (!user) throw Error('Parameter user is required!'); if (!org) org = []; if (typeof org === 'string') org = org.toString().split('.'); const now = Date.now(); const storedItems = itemsForUserDb.findOne({ name: user }); if (storedItems) { if (now < storedItems.lastupdate + CACHE_TIME_ACL) { // Currently stored version not older than CACHE_TIME_ACL. logger.debug('getItemsForUser(): Found in database and not older than CACHE_TIME_ACL.'); return storedItems.items; } itemsForUserDb.findAndRemove({ name: user }); } const headers = await getHeaders(expressReq); try { const response = await fetch(HOST + '/rest/items?recursive=false&fields=name%2C%20tags', { headers: headers }); const allItems = await response.json(); let filteredItems = []; for (const i in allItems) { for (const j in allItems[i].tags) { if (allItems[i].tags[j].startsWith(ACL_PREFIX)) { if (allItems[i].tags[j].substring(ACL_PREFIX.length) === user || org.includes(allItems[i].tags[j].substring(ACL_PREFIX.length)) || org.includes(ADMIN_OU) || allItems[i].tags[j].substring(ACL_PREFIX.length) === EVERYONE_OU) { //Access allow when tags include user name, user org or EVERYONE_OU, Member of ADMIN_OU has full access if (!filteredItems.includes(allItems[i].name)) filteredItems.push(allItems[i].name); } } } } itemsForUserDb.insert({ name: user, lastupdate: now, items: filteredItems }); const status = response.status; logger.debug(`getItemsForUser(): Successfully requested backend ${HOST + '/rest/items?recursive=false&fields=name%2C%20tags'}, HTTP response code ${status}`); return filteredItems; } catch (err) { const error = new Error(`getItemsForUser(): An error occurred while getting all Items from ${HOST + '/rest/items?recursive=false&fields=name%2C%20tags'}: ${err}`); logger.error(error); error(); } }; /** * Gets the state of an Item. * * @memberof itemsBackend * @param {String} HOST hostname of openHAB server * @param {*} expressReq request object from expressjs * @param {String} itemname Item name * @returns {Object} Object: { state: Item state, status: HTTP status code } */ export const getItemState = async function (HOST, expressReq, itemname) { const headers = await getHeaders(expressReq); try { const response = await fetch(HOST + '/rest/items/' + itemname + '/state', { headers: headers }); const state = await response.text(); const status = response.status; logger.debug(`getItemState(): Got state ${state} from ${HOST + '/rest/items/' + itemname + '/state'}, HTTP response code ${status}`); return { state: state, status: status }; } catch (err) { const error = new Error(`getItemState(): An error occurred while getting state from ${HOST + '/rest/items/' + itemname + '/state'}: ${err}`); logger.error(error); error(); } }; /** * Gets the item which defines the requested semantics of an Item. * * @memberof itemsBackend * @param {String} HOST hostname of openHAB server * @param {*} expressReq request object from expressjs * @param {String} itemname Item name * @returns {Object} Object: { json: JSON reponse, status: HTTP status code } */ export const getItemSemantic = async function (HOST, expressReq, itemname, semanticClass) { const headers = await getHeaders(expressReq); try { const response = await fetch(HOST + '/rest/items/' + itemname + '/semantic/' + semanticClass, { headers: headers }); const json = await response.json(); const status = response.status; logger.debug(`getItemSemantic(): Got Item ${itemname} from ${HOST + '/rest/items/' + itemname + '/semantic/' + semanticClass}, HTTP response code ${status}`); return { json: json, status: status }; } catch (err) { const error = new Error(`getItemSemantic(): An error occurred while getting semantics from ${HOST + '/rest/items/' + itemname + '/semantic/' + semanticClass}: ${err}`); logger.error(error); error(); } }; /** * Sends a command to an Item. * * @memberof itemsBackend * @param {String} HOST hostname of openHAB server * @param {*} expressReq request object from expressjs * @param {String} itemname Item name * @param {String} command valid item command (e.g. ON, OFF, UP, DOWN, REFRESH) * @returns {Integer} Response code from backend */ export const sendItemCommand = async function (HOST, expressReq, itemname, command) { const headers = await getHeaders(expressReq); Object.assign(headers, {accept: '*/*'}); Object.assign(headers, {'content-type': 'text/plain'}); try { const status = await (await fetch(HOST + '/rest/items/' + itemname, { headers: headers, method: 'POST', body: command })).status; logger.debug(`sendItemCommand(): Sent command ${command} to ${HOST + '/rest/items/' + itemname}, HTTP response code ${status}`); return status; } catch (err) { const error = new Error(`sendItemCommand(): An error occurred while sending command to ${HOST + '/rest/items/' + itemname}: ${err}`); logger.error(error); error(); } }; /** * Sends list of items a SSE connection will receive state updates to. * * @memberof itemsBackend * @param {String} HOST hostname of openHAB server * @param {*} expressReq request object from expressjs * @param {String} connectionId Connection ID * @param {String} items Items list * @returns {Integer} Response code from backend */ export const sendEventsItems = async function (HOST, expressReq, connectionId, items) { const headers = await getHeaders(expressReq); Object.assign(headers, {accept: '*/*'}); Object.assign(headers, {'content-type': 'application/json'}); try { const status = await (await fetch(HOST + '/rest/events/states/' + connectionId, { headers: headers, method: 'POST', body: JSON.stringify(items) })).status; logger.debug(`sendEventsItems(): Sent items list to ${HOST + '/rest/events/states/' + connectionId}, HTTP response code ${status}`); return status; } catch (err) { const error = new Error(`sendEventsItems(): An error occurred while sending items list to ${HOST + '/rest/events/states/' + connectionId}: ${err}`); logger.error(error); error(); } };