From 47bd8610d24bdebb2cd34f18304d47c61b36d017 Mon Sep 17 00:00:00 2001 From: Benoy Bose Date: Mon, 9 Jun 2025 10:03:16 +0530 Subject: [PATCH] Adding esslGetUserDetails --- functions/package-lock.json | 18 ++- functions/package.json | 4 +- functions/src/dooraccess/doorAccessUser.ts | 7 + functions/src/dooraccess/essl.ts | 162 +++++++++++++++++++++ functions/src/dooraccess/index.ts | 1 + functions/src/email/sendEmailSES.ts | 2 +- functions/src/index.ts | 1 + 7 files changed, 192 insertions(+), 3 deletions(-) create mode 100644 functions/src/dooraccess/doorAccessUser.ts create mode 100644 functions/src/dooraccess/essl.ts create mode 100644 functions/src/dooraccess/index.ts diff --git a/functions/package-lock.json b/functions/package-lock.json index 853a3bf..4de688d 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -25,13 +25,15 @@ "node-fetch": "^2.7.0", "pdfjs-dist": "^5.0.375", "pdfmake": "^0.2.20", - "twilio": "^5.4.0" + "twilio": "^5.4.0", + "xmldom": "^0.6.0" }, "devDependencies": { "@types/long": "^5.0.0", "@types/mime-types": "^2.1.4", "@types/node": "^22.13.14", "@types/pdfmake": "^0.2.11", + "@types/xmldom": "^0.1.34", "firebase-functions-test": "^3.1.0", "typescript": "^5.8.2" }, @@ -2946,6 +2948,12 @@ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "optional": true }, + "node_modules/@types/xmldom": { + "version": "0.1.34", + "resolved": "https://registry.npmjs.org/@types/xmldom/-/xmldom-0.1.34.tgz", + "integrity": "sha512-7eZFfxI9XHYjJJuugddV6N5YNeXgQE1lArWOcd1eCOKWb/FGs5SIjacSYuEJuwhsGS3gy4RuZ5EUIcqYscuPDA==", + "dev": true + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -8324,6 +8332,14 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" }, + "node_modules/xmldom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.6.0.tgz", + "integrity": "sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/functions/package.json b/functions/package.json index b29002f..d95bb67 100644 --- a/functions/package.json +++ b/functions/package.json @@ -32,13 +32,15 @@ "node-fetch": "^2.7.0", "pdfjs-dist": "^5.0.375", "pdfmake": "^0.2.20", - "twilio": "^5.4.0" + "twilio": "^5.4.0", + "xmldom": "^0.6.0" }, "devDependencies": { "@types/long": "^5.0.0", "@types/mime-types": "^2.1.4", "@types/node": "^22.13.14", "@types/pdfmake": "^0.2.11", + "@types/xmldom": "^0.1.34", "firebase-functions-test": "^3.1.0", "typescript": "^5.8.2" }, diff --git a/functions/src/dooraccess/doorAccessUser.ts b/functions/src/dooraccess/doorAccessUser.ts new file mode 100644 index 0000000..58fcd2c --- /dev/null +++ b/functions/src/dooraccess/doorAccessUser.ts @@ -0,0 +1,7 @@ +export type DoorAccessUser = { + name: string; + location: string; + role: string; + expireFrom: Date | null; + expireTo: Date | null; +}; diff --git a/functions/src/dooraccess/essl.ts b/functions/src/dooraccess/essl.ts new file mode 100644 index 0000000..231b06a --- /dev/null +++ b/functions/src/dooraccess/essl.ts @@ -0,0 +1,162 @@ +import { onRequest } from "firebase-functions/https"; +import { DoorAccessUser } from "./doorAccessUser"; +import { Request } from "firebase-functions/v2/https"; +import { Response } from "express"; +import { getCorsHandler } from "../shared/middleware"; +import { getLogger } from "../shared/config"; +import { DOMParser } from 'xmldom'; + +const logger = getLogger(); +const corsHandler = getCorsHandler(); + +const escapeXml = (str: string) => { + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +}; + +function createGetEmployeeDetailsRequest(username: string, password: string, employeeCode: string) { + const soapRequest = ` + + + + ${escapeXml(username)} + ${escapeXml(password)} + ${escapeXml(employeeCode)} + + +`; + + return soapRequest; +} + +function parseGetEmployeeDetailsResponse(soapResponse: string): DoorAccessUser { + const parser = new DOMParser(); + const xmlDoc = parser.parseFromString(soapResponse, "text/xml"); + if (xmlDoc.documentElement.tagName !== 'soap:Envelope') { + throw new Error("Invalid SOAP response"); + } + if (null == xmlDoc.documentElement.firstChild) { + throw new Error("Invalid SOAP response"); + } + + let currentElement = xmlDoc.documentElement.firstChild as HTMLElement; + if (currentElement.tagName !== 'soap:Body') { + throw new Error("Invalid SOAP response"); + } + + currentElement = currentElement.firstChild as HTMLElement; + if (currentElement.tagName !== 'GetEmployeeDetailsResponse') { + throw new Error("Invalid SOAP response"); + } + + currentElement = currentElement.firstChild as HTMLElement; + if (currentElement.tagName !== 'GetEmployeeDetailsResult') { + throw new Error("Invalid SOAP response"); + } + + const resultText = currentElement.textContent; + if (!resultText) { + throw new Error("GetEmployeeDetailsResult is empty"); + } + + const userDetails: DoorAccessUser = + { + name: '', + location: '', + role: '', + expireFrom: null, + expireTo: null + }; + const pairs = resultText.split(','); + pairs.forEach(pair => { + const [key, value] = pair.split('='); + if (key && value !== undefined) { + const cleanKey = key.trim(); + const cleanValue = value.trim(); + switch (cleanKey) { + case 'EmployeeName': + userDetails.name = cleanValue; + break; + case 'EmployeeLocation': + userDetails.location = cleanValue; + break; + case 'EmployeeRole': + userDetails.role = cleanValue; + break; + case 'EmployeeExpiryFrom': + userDetails.expireFrom = new Date(cleanValue); + break; + case 'EmployeeExpiryTo': + userDetails.expireTo = new Date(cleanValue); + break; + default: + break; + } + } + }); + + return userDetails; +} + +async function sendSoapRequest(soapRequest: string, endpoint: string) { + try { + const headers: any = { + 'Content-Type': 'application/soap+xml; charset=utf-8', + 'Content-Length': soapRequest.length.toString() + }; + + const response = await fetch(endpoint, { + method: 'POST', + headers: headers, + body: soapRequest + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return await response.text(); + } catch (error: any) { + throw new Error(`SOAP request failed: ${error.message}`); + } +} + +async function getUserDetails(username: string, + password: string, + employeeCode: string, endpoint: string) { + const soapRequest = createGetEmployeeDetailsRequest(username, password, employeeCode); + const soapResponse = await sendSoapRequest(soapRequest, endpoint); + const parsedResponse = parseGetEmployeeDetailsResponse(soapResponse); + return parsedResponse; +} + +export const esslGetUserDetails = onRequest({ + region: '#{SERVICES_RGN}#' +}, async (request: Request, response: Response) => { + return corsHandler(request, response, async () => { + try { + const username = request.body.username; + const password = request.body.password; + const employeeCode = request.body.employeeCode; + const endpoint = request.body.endpoint; + if ((!username) || (!password)) { + throw new Error('Missing username or password'); + } + if (!employeeCode) { + throw new Error('Missing employee code'); + } + if (!endpoint) { + throw new Error('Missing endpoint'); + } + const userDetails = await getUserDetails(username, password, employeeCode, endpoint); + response.send(userDetails); + } catch (error: any) { + logger.error(error); + response.status(500).send({ error: error.message }); + } + }) +}); diff --git a/functions/src/dooraccess/index.ts b/functions/src/dooraccess/index.ts new file mode 100644 index 0000000..a855858 --- /dev/null +++ b/functions/src/dooraccess/index.ts @@ -0,0 +1 @@ +export { esslGetUserDetails } from './essl'; \ No newline at end of file diff --git a/functions/src/email/sendEmailSES.ts b/functions/src/email/sendEmailSES.ts index 67b2886..8d0437c 100644 --- a/functions/src/email/sendEmailSES.ts +++ b/functions/src/email/sendEmailSES.ts @@ -63,7 +63,7 @@ async function sendSimpleEmail(data: EmailRequest, recipients: string[]) { async function sendEmailWithAttachments(data: EmailRequest, recipients: string[]) { const ses = new SESClient({ - region: 'ap-south-1', + region: '#{SERVICES_RGN}#', credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID || '', secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || '' diff --git a/functions/src/index.ts b/functions/src/index.ts index 314a462..12cacad 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -17,3 +17,4 @@ export { processNotificationOnCreate } from './notifications'; export * from './payments'; export { getPlaceDetails, getPlacesAutocomplete } from './places'; export { registerClient } from './users'; +export { esslGetUserDetails } from './dooraccess';