Adding esslGetUserDetails

This commit is contained in:
Benoy Bose 2025-06-09 10:03:16 +05:30
parent e517367967
commit 47bd8610d2
7 changed files with 192 additions and 3 deletions

View File

@ -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",

View File

@ -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"
},

View File

@ -0,0 +1,7 @@
export type DoorAccessUser = {
name: string;
location: string;
role: string;
expireFrom: Date | null;
expireTo: Date | null;
};

View File

@ -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, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;');
};
function createGetEmployeeDetailsRequest(username: string, password: string, employeeCode: string) {
const soapRequest = `<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<GetEmployeeDetails xmlns="http://tempuri.org/">
<UserName>${escapeXml(username)}</UserName>
<Password>${escapeXml(password)}</Password>
<EmployeeCode>${escapeXml(employeeCode)}</EmployeeCode>
</GetEmployeeDetails>
</soap12:Body>
</soap12:Envelope>`;
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 });
}
})
});

View File

@ -0,0 +1 @@
export { esslGetUserDetails } from './essl';

View File

@ -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 || ''

View File

@ -17,3 +17,4 @@ export { processNotificationOnCreate } from './notifications';
export * from './payments';
export { getPlaceDetails, getPlacesAutocomplete } from './places';
export { registerClient } from './users';
export { esslGetUserDetails } from './dooraccess';