Compare commits
No commits in common. "a0134466eeec1647ae836fe9ee9656b87abcdb6c" and "ecbe9d184bd96b2f3688eaa6f4f27712923da930" have entirely different histories.
a0134466ee
...
ecbe9d184b
@ -25,12 +25,6 @@ jobs:
|
|||||||
- name: Copy .env.example to .env
|
- name: Copy .env.example to .env
|
||||||
run: cp functions/.env.example functions/.env
|
run: cp functions/.env.example functions/.env
|
||||||
|
|
||||||
- name: Create private key file
|
|
||||||
run: |
|
|
||||||
mkdir -p functions/assets/keys
|
|
||||||
echo "${{ secrets.FITLIEN_PRIVATEKEY_DEV }}" > functions/assets/keys/fitLien_private.pem
|
|
||||||
chmod 600 functions/assets/keys/fitLien_private.pem
|
|
||||||
|
|
||||||
- name: Replace variables in .env
|
- name: Replace variables in .env
|
||||||
run: |
|
run: |
|
||||||
sed -i "s/#{TWILIO_ACCOUNT_SID}#/${{ secrets.TWILIO_ACCOUNT_SID }}/" functions/.env
|
sed -i "s/#{TWILIO_ACCOUNT_SID}#/${{ secrets.TWILIO_ACCOUNT_SID }}/" functions/.env
|
||||||
|
|||||||
@ -25,12 +25,6 @@ jobs:
|
|||||||
- name: Copy .env.example to .env
|
- name: Copy .env.example to .env
|
||||||
run: cp functions/.env.example functions/.env
|
run: cp functions/.env.example functions/.env
|
||||||
|
|
||||||
- name: Create private key file
|
|
||||||
run: |
|
|
||||||
mkdir -p functions/assets/keys
|
|
||||||
echo "${{ secrets.FITLIEN_PRIVATEKEY_DEV }}" > functions/assets/keys/fitLien_private.pem
|
|
||||||
chmod 600 functions/assets/keys/fitLien_private.pem
|
|
||||||
|
|
||||||
- name: Replace variables in .env
|
- name: Replace variables in .env
|
||||||
run: |
|
run: |
|
||||||
sed -i "s/#{TWILIO_ACCOUNT_SID}#/${{ secrets.TWILIO_ACCOUNT_SID }}/" functions/.env
|
sed -i "s/#{TWILIO_ACCOUNT_SID}#/${{ secrets.TWILIO_ACCOUNT_SID }}/" functions/.env
|
||||||
|
|||||||
@ -25,12 +25,6 @@ jobs:
|
|||||||
- name: Copy .env.example to .env
|
- name: Copy .env.example to .env
|
||||||
run: cp functions/.env.example functions/.env
|
run: cp functions/.env.example functions/.env
|
||||||
|
|
||||||
- name: Create private key file
|
|
||||||
run: |
|
|
||||||
mkdir -p functions/assets/keys
|
|
||||||
echo "${{ secrets.FITLIEN_PRIVATEKEY }}" > functions/assets/keys/fitLien_private.pem
|
|
||||||
chmod 600 functions/assets/keys/fitLien_private.pem
|
|
||||||
|
|
||||||
- name: Replace variables in .env
|
- name: Replace variables in .env
|
||||||
run: |
|
run: |
|
||||||
sed -i "s/#{TWILIO_ACCOUNT_SID}#/${{ secrets.TWILIO_ACCOUNT_SID }}/" functions/.env
|
sed -i "s/#{TWILIO_ACCOUNT_SID}#/${{ secrets.TWILIO_ACCOUNT_SID }}/" functions/.env
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@ -26,9 +26,6 @@ pids
|
|||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
lib-cov
|
lib-cov
|
||||||
|
|
||||||
# Private key
|
|
||||||
/functions/assets/keys/fitLien_private.pem
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
# Coverage directory used by tools like istanbul
|
||||||
coverage
|
coverage
|
||||||
|
|
||||||
@ -70,6 +67,3 @@ node_modules/
|
|||||||
|
|
||||||
# dataconnect generated files
|
# dataconnect generated files
|
||||||
.dataconnect
|
.dataconnect
|
||||||
|
|
||||||
.DS_Store
|
|
||||||
**/.DS_Store
|
|
||||||
27
functions/package-lock.json
generated
27
functions/package-lock.json
generated
@ -25,15 +25,13 @@
|
|||||||
"node-fetch": "^2.7.0",
|
"node-fetch": "^2.7.0",
|
||||||
"pdfjs-dist": "^5.0.375",
|
"pdfjs-dist": "^5.0.375",
|
||||||
"pdfmake": "^0.2.20",
|
"pdfmake": "^0.2.20",
|
||||||
"twilio": "^5.4.0",
|
"twilio": "^5.4.0"
|
||||||
"xmldom": "^0.6.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/long": "^5.0.0",
|
"@types/long": "^5.0.0",
|
||||||
"@types/mime-types": "^2.1.4",
|
"@types/mime-types": "^2.1.4",
|
||||||
"@types/node": "^22.15.31",
|
"@types/node": "^22.13.14",
|
||||||
"@types/pdfmake": "^0.2.11",
|
"@types/pdfmake": "^0.2.11",
|
||||||
"@types/xmldom": "^0.1.34",
|
|
||||||
"firebase-functions-test": "^3.1.0",
|
"firebase-functions-test": "^3.1.0",
|
||||||
"typescript": "^5.8.2"
|
"typescript": "^5.8.2"
|
||||||
},
|
},
|
||||||
@ -2831,10 +2829,9 @@
|
|||||||
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="
|
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.15.31",
|
"version": "22.15.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.31.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.2.tgz",
|
||||||
"integrity": "sha512-jnVe5ULKl6tijxUhvQeNbQG/84fHfg+yMak02cT8QVhBx/F05rAVxCGBYYTh2EKz22D6JF5ktXuNwdx7b9iEGw==",
|
"integrity": "sha512-uKXqKN9beGoMdBfcaTY1ecwz6ctxuJAcUlwE55938g0ZJ8lRxwAZqRz2AJ4pzpt5dHdTPMB863UZ0ESiFUcP7A==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
@ -2949,12 +2946,6 @@
|
|||||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||||
"optional": true
|
"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": {
|
"node_modules/@types/yargs": {
|
||||||
"version": "17.0.33",
|
"version": "17.0.33",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
|
||||||
@ -8333,14 +8324,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
||||||
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="
|
"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": {
|
"node_modules/y18n": {
|
||||||
"version": "5.0.8",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
|||||||
@ -32,15 +32,13 @@
|
|||||||
"node-fetch": "^2.7.0",
|
"node-fetch": "^2.7.0",
|
||||||
"pdfjs-dist": "^5.0.375",
|
"pdfjs-dist": "^5.0.375",
|
||||||
"pdfmake": "^0.2.20",
|
"pdfmake": "^0.2.20",
|
||||||
"twilio": "^5.4.0",
|
"twilio": "^5.4.0"
|
||||||
"xmldom": "^0.6.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/long": "^5.0.0",
|
"@types/long": "^5.0.0",
|
||||||
"@types/mime-types": "^2.1.4",
|
"@types/mime-types": "^2.1.4",
|
||||||
"@types/node": "^22.15.31",
|
"@types/node": "^22.13.14",
|
||||||
"@types/pdfmake": "^0.2.11",
|
"@types/pdfmake": "^0.2.11",
|
||||||
"@types/xmldom": "^0.1.34",
|
|
||||||
"firebase-functions-test": "^3.1.0",
|
"firebase-functions-test": "^3.1.0",
|
||||||
"typescript": "^5.8.2"
|
"typescript": "^5.8.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
import { onRequest } from "firebase-functions/v2/https";
|
import { onRequest } from "firebase-functions/v2/https";
|
||||||
import { getCorsHandler } from "../shared/middleware";
|
import { getCorsHandler } from "../shared/middleware";
|
||||||
import { getAdmin, getLogger } from "../shared/config";
|
import { getAdmin, getLogger } from "../shared/config";
|
||||||
import { Request } from "firebase-functions/v2/https";
|
|
||||||
import { Response } from "express";
|
|
||||||
|
|
||||||
|
|
||||||
const corsHandler = getCorsHandler();
|
const corsHandler = getCorsHandler();
|
||||||
const admin = getAdmin();
|
const admin = getAdmin();
|
||||||
@ -11,7 +8,7 @@ const logger = getLogger();
|
|||||||
|
|
||||||
export const registerClient = onRequest({
|
export const registerClient = onRequest({
|
||||||
region: '#{SERVICES_RGN}#'
|
region: '#{SERVICES_RGN}#'
|
||||||
}, async (req: Request, res: Response) => {
|
}, async (req, res) => {
|
||||||
return corsHandler(req, res, async () => {
|
return corsHandler(req, res, async () => {
|
||||||
try {
|
try {
|
||||||
if (req.method !== 'POST') {
|
if (req.method !== 'POST') {
|
||||||
@ -36,21 +33,27 @@ export const registerClient = onRequest({
|
|||||||
return res.status(403).json({ error: 'Forbidden. Only gym owners can register clients.' });
|
return res.status(403).json({ error: 'Forbidden. Only gym owners can register clients.' });
|
||||||
}
|
}
|
||||||
const gymUser = req.body;
|
const gymUser = req.body;
|
||||||
if (!gymUser.fields["phone-number"]) {
|
if (!gymUser.phoneNumber) {
|
||||||
return res.status(400).json({ error: 'Phone number is required' });
|
return res.status(400).json({ error: 'Phone number is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isdCode = gymUser.isdCode || '91';
|
||||||
|
const formattedPhoneNumber = gymUser.phoneNumber.startsWith('+')
|
||||||
|
? gymUser.phoneNumber
|
||||||
|
: `${isdCode}${gymUser.phoneNumber}`;
|
||||||
|
|
||||||
let clientUid;
|
let clientUid;
|
||||||
try {
|
try {
|
||||||
const userRecord = await admin.auth().getUserByPhoneNumber(gymUser.fields["phone-number"])
|
const userRecord = await admin.auth().getUserByPhoneNumber(formattedPhoneNumber)
|
||||||
.catch(() => null);
|
.catch(() => null);
|
||||||
|
|
||||||
if (userRecord) {
|
if (userRecord) {
|
||||||
clientUid = userRecord.uid;
|
clientUid = userRecord.uid;
|
||||||
} else {
|
} else {
|
||||||
const newUser = await admin.auth().createUser({
|
const newUser = await admin.auth().createUser({
|
||||||
phoneNumber: gymUser.fields["phone-number"],
|
phoneNumber: formattedPhoneNumber,
|
||||||
displayName: gymUser.fields["first-name"] || '',
|
displayName: gymUser.name || '',
|
||||||
|
email: gymUser.email || null,
|
||||||
});
|
});
|
||||||
clientUid = newUser.uid;
|
clientUid = newUser.uid;
|
||||||
}
|
}
|
||||||
@ -76,6 +79,7 @@ export const registerClient = onRequest({
|
|||||||
|
|
||||||
const clientData = {
|
const clientData = {
|
||||||
...gymUser,
|
...gymUser,
|
||||||
|
phoneNumber: formattedPhoneNumber,
|
||||||
};
|
};
|
||||||
|
|
||||||
await admin.firestore().collection('client_profiles').doc(clientUid).set(clientData);
|
await admin.firestore().collection('client_profiles').doc(clientUid).set(clientData);
|
||||||
@ -1,7 +0,0 @@
|
|||||||
export type DoorAccessUser = {
|
|
||||||
name: string;
|
|
||||||
location: string;
|
|
||||||
role: string;
|
|
||||||
expireFrom: Date | null;
|
|
||||||
expireTo: Date | null;
|
|
||||||
};
|
|
||||||
@ -1,560 +0,0 @@
|
|||||||
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';
|
|
||||||
import { RSADecryption } from "../shared/decrypt";
|
|
||||||
const logger = getLogger();
|
|
||||||
const corsHandler = getCorsHandler();
|
|
||||||
|
|
||||||
export interface EmployeeCodeRequest {
|
|
||||||
employeeCode: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PushLogRequest extends EmployeeCodeRequest {
|
|
||||||
attendanceDate: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdateEmployeeExRequest {
|
|
||||||
employeeCode: string;
|
|
||||||
employeeName: string;
|
|
||||||
employeeLocation: string;
|
|
||||||
employeeRole: string;
|
|
||||||
employeeVerificationType: string;
|
|
||||||
employeeExpiryFrom: string;
|
|
||||||
employeeExpiryTo: string;
|
|
||||||
employeeCardNumber: string;
|
|
||||||
groupId: string;
|
|
||||||
employeePhoto: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDecryptedPassword(password: string | null): string {
|
|
||||||
if (!password) {
|
|
||||||
throw new Error('Password is required');
|
|
||||||
}
|
|
||||||
return RSADecryption.decryptPassword(password);
|
|
||||||
}
|
|
||||||
|
|
||||||
const escapeXml = (str: string) => {
|
|
||||||
return str
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''');
|
|
||||||
};
|
|
||||||
|
|
||||||
function createGetEmployeeDetailsRequest(username: string, password: string, employeeCode: string): string | null {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isValidDateString(dateString: string): boolean {
|
|
||||||
const dateRegex = /^\d{4}\-\d{2}\-\d{2}$/;
|
|
||||||
return dateRegex.test(dateString);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createUpdateEmployeeExRequest(username: string, password: string, request: UpdateEmployeeExRequest): string | null {
|
|
||||||
|
|
||||||
if (!username || !password || !request.employeeCode || !request.employeeName || !request.employeeLocation) {
|
|
||||||
throw new Error('Missing required fields');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isValidDateString(request.employeeExpiryFrom) || !isValidDateString(request.employeeExpiryTo)) {
|
|
||||||
throw new Error('Invalid date format');
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
|
||||||
<UpdateEmployeeEx xmlns="http://tempuri.org/">
|
|
||||||
<UserName>${escapeXml(username)}</UserName>
|
|
||||||
<Password>${escapeXml(password)}</Password>
|
|
||||||
<EmployeeCode>${escapeXml(request.employeeCode)}</EmployeeCode>
|
|
||||||
<EmployeeName>${escapeXml(request.employeeName)}</EmployeeName>
|
|
||||||
<EmployeeLocation>${escapeXml(request.employeeLocation)}</EmployeeLocation>
|
|
||||||
<EmployeeRole>${escapeXml(request.employeeRole)}</EmployeeRole>
|
|
||||||
<EmployeeVerificationType>Card</EmployeeVerificationType>
|
|
||||||
<EmployeeExpiryFrom>${escapeXml(request.employeeExpiryFrom)}</EmployeeExpiryFrom>
|
|
||||||
<EmployeeExpiryTo>${escapeXml(request.employeeExpiryTo)}</EmployeeExpiryTo>
|
|
||||||
<EmployeeCardNumber>${escapeXml(request.employeeCardNumber)}</EmployeeCardNumber>
|
|
||||||
<GroupId></GroupId>
|
|
||||||
<EmployeePhoto></EmployeePhoto>
|
|
||||||
</UpdateEmployeeEx>
|
|
||||||
</soap12:Body>
|
|
||||||
</soap12:Envelope>
|
|
||||||
`;
|
|
||||||
return soapRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseUpdateEmployeeExResponse(soapResponse: string): string | null {
|
|
||||||
const parser = new DOMParser();
|
|
||||||
const xmlDoc = parser.parseFromString(soapResponse, "text/xml");
|
|
||||||
const currentElement = xmlDoc.documentElement.firstChild as HTMLElement;
|
|
||||||
const resultText = currentElement.textContent;
|
|
||||||
return resultText;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDeleteEmployeeRequest(username: string, password: string, employeeCode: string): string | null {
|
|
||||||
if (!username || !password || !employeeCode) {
|
|
||||||
throw new Error('Missing required fields');
|
|
||||||
}
|
|
||||||
const soapRequst = `<?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>
|
|
||||||
<DeleteEmployee xmlns="http://tempuri.org/">
|
|
||||||
<UserName>${escapeXml(username)}</UserName>
|
|
||||||
<Password>${escapeXml(password)}</Password>
|
|
||||||
<EmployeeCode>${escapeXml(employeeCode)}</EmployeeCode>
|
|
||||||
</DeleteEmployee>
|
|
||||||
</soap12:Body>
|
|
||||||
</soap12:Envelope>`;
|
|
||||||
return soapRequst;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseDeleteEmployeeResponse(soapResponse: string): string | null {
|
|
||||||
const parser = new DOMParser();
|
|
||||||
const xmlDoc = parser.parseFromString(soapResponse, "text/xml");
|
|
||||||
const currentElement = xmlDoc.documentElement.firstChild as HTMLElement;
|
|
||||||
const resultText = currentElement.textContent;
|
|
||||||
return resultText;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createGetEmployeePunchLogsRequest(username: string, password: string,
|
|
||||||
employeeCode: string, attendanceDate: string): string | null {
|
|
||||||
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>
|
|
||||||
<GetEmployeePunchLogs xmlns="http://tempuri.org/">
|
|
||||||
<UserName>cosqclient</UserName>
|
|
||||||
<Password>3bbb58d5</Password>
|
|
||||||
<EmployeeCode>1</EmployeeCode>
|
|
||||||
<AttendanceDate>2025-05-24</AttendanceDate>
|
|
||||||
</GetEmployeePunchLogs>
|
|
||||||
</soap12:Body>
|
|
||||||
</soap12:Envelope>`;
|
|
||||||
return soapRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDateFromTime(date: Date, timeString: string): Date {
|
|
||||||
const [hour, minute, second] = timeString.split(':').map(str => parseInt(str, 10));
|
|
||||||
const newDate = new Date(date.getTime());
|
|
||||||
newDate.setHours(hour);
|
|
||||||
newDate.setMinutes(minute);
|
|
||||||
newDate.setSeconds(second);
|
|
||||||
return newDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseGetEmployeePunchLogsResponse(soapResponse: string, attendanceDate: string): Date[] {
|
|
||||||
const rootDate = new Date(attendanceDate);
|
|
||||||
const parser = new DOMParser();
|
|
||||||
const xmlDoc = parser.parseFromString(soapResponse, "text/xml");
|
|
||||||
const currentElement = xmlDoc.documentElement.firstChild as HTMLElement;
|
|
||||||
const resultText = currentElement.textContent;
|
|
||||||
const punchLogs: Date[] = [];
|
|
||||||
const parts = resultText!.split(';');
|
|
||||||
for (const part of parts) {
|
|
||||||
try {
|
|
||||||
const logDateTime = new Date(part);
|
|
||||||
if (isNaN(logDateTime.getTime())) {
|
|
||||||
throw new Error('Invalid date format');
|
|
||||||
}
|
|
||||||
punchLogs.push(logDateTime);
|
|
||||||
} catch {
|
|
||||||
try {
|
|
||||||
const timeParts = part.split(',');
|
|
||||||
for (const timePart of timeParts) {
|
|
||||||
try {
|
|
||||||
const logDateTime = createDateFromTime(rootDate, timePart);
|
|
||||||
punchLogs.push(logDateTime);
|
|
||||||
} catch {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const sortedLogs = punchLogs.sort((a, b) => b.getTime() - a.getTime());
|
|
||||||
return sortedLogs;
|
|
||||||
}
|
|
||||||
|
|
||||||
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): Promise<DoorAccessUser> {
|
|
||||||
const soapRequest = createGetEmployeeDetailsRequest(username, password, employeeCode);
|
|
||||||
const soapResponse = await sendSoapRequest(soapRequest!, endpoint);
|
|
||||||
const parsedResponse = parseGetEmployeeDetailsResponse(soapResponse);
|
|
||||||
return parsedResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateUserEx(username: string,
|
|
||||||
password: string,
|
|
||||||
request: UpdateEmployeeExRequest,
|
|
||||||
endpoint: string) {
|
|
||||||
const soapRequest = createUpdateEmployeeExRequest(username, password, request);
|
|
||||||
const soapResponse = await sendSoapRequest(soapRequest!, endpoint);
|
|
||||||
const parsedResponse = parseUpdateEmployeeExResponse(soapResponse);
|
|
||||||
return parsedResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteEmplyee(username: string,
|
|
||||||
password: string,
|
|
||||||
employeeCode: string, endpoint: string) {
|
|
||||||
const soapRequest = createDeleteEmployeeRequest(username, password, employeeCode);
|
|
||||||
const soapResponse = await sendSoapRequest(soapRequest!, endpoint);
|
|
||||||
const parsedResponse = parseDeleteEmployeeResponse(soapResponse);
|
|
||||||
return parsedResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getEmployeePunchLogs(username: string,
|
|
||||||
password: string,
|
|
||||||
employeeCode: string,
|
|
||||||
attendanceDate: string, endpoint: string): Promise<Date[]> {
|
|
||||||
const soapRequest = createGetEmployeePunchLogsRequest(username, password, employeeCode, attendanceDate);
|
|
||||||
const soapResponse = await sendSoapRequest(soapRequest!, endpoint);
|
|
||||||
const parsedResponse = parseGetEmployeePunchLogsResponse(soapResponse, attendanceDate);
|
|
||||||
return parsedResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const esslGetUserDetails = onRequest({
|
|
||||||
region: '#{SERVICES_RGN}#'
|
|
||||||
}, async (request: Request, response: Response) => {
|
|
||||||
return corsHandler(request, response, async () => {
|
|
||||||
try {
|
|
||||||
let username: string | null = request.body.username as string;
|
|
||||||
let password: string | null = request.body.password as string;
|
|
||||||
let endpoint: string | null = request.body.endpoint as string;
|
|
||||||
let gymId: string | null = request.body.gymId as string;
|
|
||||||
const getEmployeeDetailsRequest = request.body.params as EmployeeCodeRequest;
|
|
||||||
|
|
||||||
if (!username) {
|
|
||||||
throw new Error('Missing username or password');
|
|
||||||
}
|
|
||||||
username = username.trim();
|
|
||||||
if (!password) {
|
|
||||||
if (!gymId) {
|
|
||||||
throw new Error('Missing password or gymId');
|
|
||||||
}
|
|
||||||
// todo: Get password from gym configuration by decrypting with private key
|
|
||||||
throw new Error('Gym-based password retrieval not implemented yet');
|
|
||||||
}
|
|
||||||
password = getDecryptedPassword(password);
|
|
||||||
if (!getEmployeeDetailsRequest) {
|
|
||||||
throw new Error('Missing request params');
|
|
||||||
}
|
|
||||||
const employeeCode = getEmployeeDetailsRequest.employeeCode;
|
|
||||||
if (!employeeCode) {
|
|
||||||
throw new Error('Missing employeeCode');
|
|
||||||
}
|
|
||||||
if (!endpoint) {
|
|
||||||
throw new Error('Missing endpoint');
|
|
||||||
}
|
|
||||||
if (!endpoint || endpoint.trim() === '') {
|
|
||||||
throw new Error('Missing endpoint');
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
new URL(endpoint);
|
|
||||||
} catch (_) {
|
|
||||||
throw new Error('Endpoint is not a valid URI or URL');
|
|
||||||
}
|
|
||||||
if (!endpoint.endsWith('/webservice.asmx')) {
|
|
||||||
if (endpoint.endsWith('/')) {
|
|
||||||
endpoint = endpoint.substring(0, endpoint.length - 1);
|
|
||||||
}
|
|
||||||
endpoint += '/webservice.asmx';
|
|
||||||
}
|
|
||||||
const userDetails = await getUserDetails(username, password, employeeCode, endpoint);
|
|
||||||
response.send(userDetails);
|
|
||||||
} catch (error: any) {
|
|
||||||
logger.error(error);
|
|
||||||
response.status(500).send({ error: error.message });
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
export const esslUpdateUser = onRequest({
|
|
||||||
region: '#{SERVICES_RGN}#'
|
|
||||||
}, async (request: Request, response: Response) => {
|
|
||||||
return corsHandler(request, response, async () => {
|
|
||||||
try {
|
|
||||||
let username: string | null = request.body.username as string;
|
|
||||||
let password: string | null = request.body.password as string;
|
|
||||||
let endpoint: string | null = request.body.endpoint as string;
|
|
||||||
let gymId: string | null = request.body.gymId as string;
|
|
||||||
const updateEmployeeExRequest = request.body.params as UpdateEmployeeExRequest;
|
|
||||||
|
|
||||||
if (!username) {
|
|
||||||
throw new Error('Missing username');
|
|
||||||
}
|
|
||||||
username = username.trim();
|
|
||||||
|
|
||||||
if (!password) {
|
|
||||||
if (!gymId) {
|
|
||||||
throw new Error('Missing password or gymId');
|
|
||||||
}
|
|
||||||
// TODO: Get password from gym configuration by decrypting with private key
|
|
||||||
throw new Error('Gym-based password retrieval not implemented yet');
|
|
||||||
}
|
|
||||||
password = getDecryptedPassword(password);
|
|
||||||
if (!endpoint) {
|
|
||||||
throw new Error('Missing endpoint');
|
|
||||||
}
|
|
||||||
endpoint = endpoint.trim();
|
|
||||||
if (!endpoint || endpoint.trim() === '') {
|
|
||||||
throw new Error('Missing endpoint');
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
new URL(endpoint);
|
|
||||||
} catch (_) {
|
|
||||||
throw new Error('Endpoint is not a valid URI or URL');
|
|
||||||
}
|
|
||||||
if (!endpoint.endsWith('/webservice.asmx')) {
|
|
||||||
if (endpoint.endsWith('/')) {
|
|
||||||
endpoint = endpoint.substring(0, endpoint.length - 1);
|
|
||||||
}
|
|
||||||
endpoint += '/webservice.asmx';
|
|
||||||
}
|
|
||||||
if (!updateEmployeeExRequest) {
|
|
||||||
throw new Error('Missing request params');
|
|
||||||
}
|
|
||||||
const result = await updateUserEx(username, password, updateEmployeeExRequest, endpoint);
|
|
||||||
response.send(result);
|
|
||||||
} catch (error: any) {
|
|
||||||
logger.error(error);
|
|
||||||
response.status(500).send({ error: error.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export const esslDeleteUser = onRequest({
|
|
||||||
region: '#{SERVICES_RGN}#'
|
|
||||||
}, async (request: Request, response: Response) => {
|
|
||||||
return corsHandler(request, response, async () => {
|
|
||||||
try {
|
|
||||||
let username: string | null = request.body.username as string;
|
|
||||||
let password: string | null = request.body.password as string;
|
|
||||||
let endpoint: string | null = request.body.endpoint as string;
|
|
||||||
let gymId: string | null = request.body.gymId as string;
|
|
||||||
const getEmployeeDetailsRequest = request.body.params as EmployeeCodeRequest;
|
|
||||||
|
|
||||||
if (!username) {
|
|
||||||
throw new Error('Missing username');
|
|
||||||
}
|
|
||||||
username = username.trim();
|
|
||||||
|
|
||||||
if (!password) {
|
|
||||||
if (!gymId) {
|
|
||||||
throw new Error('Missing password or gymId');
|
|
||||||
}
|
|
||||||
// TODO: Get password from gym configuration by decrypting with private key
|
|
||||||
throw new Error('Gym-based password retrieval not implemented yet');
|
|
||||||
}
|
|
||||||
password = getDecryptedPassword(password);
|
|
||||||
if (!getEmployeeDetailsRequest) {
|
|
||||||
throw new Error('Missing request params');
|
|
||||||
}
|
|
||||||
const employeeCode = getEmployeeDetailsRequest.employeeCode;
|
|
||||||
if (!employeeCode) {
|
|
||||||
throw new Error('Missing employeeCode');
|
|
||||||
}
|
|
||||||
if (!endpoint) {
|
|
||||||
throw new Error('Missing endpoint');
|
|
||||||
}
|
|
||||||
if (!endpoint || endpoint.trim() === '') {
|
|
||||||
throw new Error('Missing endpoint');
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
new URL(endpoint);
|
|
||||||
} catch (_) {
|
|
||||||
throw new Error('Endpoint is not a valid URI or URL');
|
|
||||||
}
|
|
||||||
if (!endpoint.endsWith('/webservice.asmx')) {
|
|
||||||
if (endpoint.endsWith('/')) {
|
|
||||||
endpoint = endpoint.substring(0, endpoint.length - 1);
|
|
||||||
}
|
|
||||||
endpoint += '/webservice.asmx';
|
|
||||||
}
|
|
||||||
const result = await deleteEmplyee(username, password, employeeCode, endpoint);
|
|
||||||
response.send(result);
|
|
||||||
} catch (error: any) {
|
|
||||||
logger.error(error);
|
|
||||||
response.status(500).send({ error: error.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
export const esslGetEmployeePunchLogs = onRequest({
|
|
||||||
region: '#{SERVICES_RGN}#'
|
|
||||||
}, async (request: Request, response: Response) => {
|
|
||||||
return corsHandler(request, response, async () => {
|
|
||||||
try {
|
|
||||||
let username: string | null = request.body.username as string;
|
|
||||||
let password: string | null = request.body.password as string;
|
|
||||||
let endpoint: string | null = request.body.endpoint as string;
|
|
||||||
let gymId: string | null = request.body.gymId as string;
|
|
||||||
const pushLogRequst = request.body.params as PushLogRequest;
|
|
||||||
|
|
||||||
if (!username) {
|
|
||||||
throw new Error('Missing username');
|
|
||||||
}
|
|
||||||
username = username.trim();
|
|
||||||
|
|
||||||
if (!password) {
|
|
||||||
if (!gymId) {
|
|
||||||
throw new Error('Missing password or gymId');
|
|
||||||
}
|
|
||||||
// TODO: Get password from gym configuration by decrypting with private key
|
|
||||||
throw new Error('Gym-based password retrieval not implemented yet');
|
|
||||||
}
|
|
||||||
password = getDecryptedPassword(password);
|
|
||||||
if (!pushLogRequst) {
|
|
||||||
throw new Error('Missing request params');
|
|
||||||
}
|
|
||||||
const employeeCode = pushLogRequst.employeeCode;
|
|
||||||
if (!employeeCode) {
|
|
||||||
throw new Error('Missing employeeCode');
|
|
||||||
}
|
|
||||||
const attendanceDate = pushLogRequst.attendanceDate;
|
|
||||||
if (!attendanceDate) {
|
|
||||||
throw new Error('Missing attendanceDate');
|
|
||||||
}
|
|
||||||
const isValidDate = /^\d{4}-\d{2}-\d{2}$/;
|
|
||||||
if (!attendanceDate.match(isValidDate)) {
|
|
||||||
throw new Error('attendanceDate is not in the valid format YYYY-MM-DD');
|
|
||||||
}
|
|
||||||
if (!endpoint) {
|
|
||||||
throw new Error('Missing endpoint');
|
|
||||||
}
|
|
||||||
if (!endpoint || endpoint.trim() === '') {
|
|
||||||
throw new Error('Missing endpoint');
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
new URL(endpoint);
|
|
||||||
} catch (_) {
|
|
||||||
throw new Error('Endpoint is not a valid URI or URL');
|
|
||||||
}
|
|
||||||
if (!endpoint.endsWith('/webservice.asmx')) {
|
|
||||||
if (endpoint.endsWith('/')) {
|
|
||||||
endpoint = endpoint.substring(0, endpoint.length - 1);
|
|
||||||
}
|
|
||||||
endpoint += '/webservice.asmx';
|
|
||||||
}
|
|
||||||
const result = await getEmployeePunchLogs(username,
|
|
||||||
password,
|
|
||||||
employeeCode,
|
|
||||||
attendanceDate,
|
|
||||||
endpoint);
|
|
||||||
response.send(result);
|
|
||||||
} catch (error: any) {
|
|
||||||
logger.error(error);
|
|
||||||
response.status(500).send({ error: error.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
export {
|
|
||||||
esslGetUserDetails, esslUpdateUser,
|
|
||||||
esslDeleteUser, esslGetEmployeePunchLogs
|
|
||||||
} from './essl';
|
|
||||||
@ -2,7 +2,6 @@ import { getLogger } from "../shared/config";
|
|||||||
import { getCorsHandler } from "../shared/middleware";
|
import { getCorsHandler } from "../shared/middleware";
|
||||||
import { onRequest } from "firebase-functions/v2/https";
|
import { onRequest } from "firebase-functions/v2/https";
|
||||||
import { Request } from "firebase-functions/v2/https";
|
import { Request } from "firebase-functions/v2/https";
|
||||||
import { Response } from "express";
|
|
||||||
import { SESClient } from "@aws-sdk/client-ses";
|
import { SESClient } from "@aws-sdk/client-ses";
|
||||||
import { SendEmailCommand, SendRawEmailCommand } from "@aws-sdk/client-ses";
|
import { SendEmailCommand, SendRawEmailCommand } from "@aws-sdk/client-ses";
|
||||||
import { HttpsError } from "firebase-functions/v2/https";
|
import { HttpsError } from "firebase-functions/v2/https";
|
||||||
@ -63,7 +62,7 @@ async function sendSimpleEmail(data: EmailRequest, recipients: string[]) {
|
|||||||
|
|
||||||
async function sendEmailWithAttachments(data: EmailRequest, recipients: string[]) {
|
async function sendEmailWithAttachments(data: EmailRequest, recipients: string[]) {
|
||||||
const ses = new SESClient({
|
const ses = new SESClient({
|
||||||
region: '#{SERVICES_RGN}#',
|
region: 'ap-south-1',
|
||||||
credentials: {
|
credentials: {
|
||||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID || '',
|
accessKeyId: process.env.AWS_ACCESS_KEY_ID || '',
|
||||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || ''
|
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || ''
|
||||||
@ -136,8 +135,8 @@ async function downloadFileFromUrl(url: string): Promise<Buffer> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const sendEmailSES = onRequest({
|
export const sendEmailSES = onRequest({
|
||||||
region: '#{SERVICES_RGN}#'
|
region: 'asia-south1'
|
||||||
}, (request: Request, response: Response) => {
|
}, (request: Request, response) => {
|
||||||
return corsHandler(request, response, async () => {
|
return corsHandler(request, response, async () => {
|
||||||
try {
|
try {
|
||||||
const toAddress = request.body.toAddress;
|
const toAddress = request.body.toAddress;
|
||||||
|
|||||||
@ -16,8 +16,4 @@ export { accessFile } from './storage';
|
|||||||
export { processNotificationOnCreate } from './notifications';
|
export { processNotificationOnCreate } from './notifications';
|
||||||
export * from './payments';
|
export * from './payments';
|
||||||
export { getPlaceDetails, getPlacesAutocomplete } from './places';
|
export { getPlaceDetails, getPlacesAutocomplete } from './places';
|
||||||
export { registerClient } from './users';
|
export { registerClient } from './clientRegistration';
|
||||||
export {
|
|
||||||
esslGetUserDetails, esslUpdateUser,
|
|
||||||
esslDeleteUser, esslGetEmployeePunchLogs
|
|
||||||
} from './dooraccess';
|
|
||||||
|
|||||||
@ -1,51 +0,0 @@
|
|||||||
import * as crypto from 'crypto';
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as path from 'path';
|
|
||||||
|
|
||||||
export class RSADecryption {
|
|
||||||
private static privateKeyObject: crypto.KeyObject | null = null;
|
|
||||||
|
|
||||||
private static getPrivateKeyObject(): crypto.KeyObject {
|
|
||||||
if (!this.privateKeyObject) {
|
|
||||||
const keyPath = path.join(__dirname, '../../assets/keys/fitLien_private.pem');
|
|
||||||
const keyContent = fs.readFileSync(keyPath, 'utf8');
|
|
||||||
this.privateKeyObject = crypto.createPrivateKey({
|
|
||||||
key: keyContent,
|
|
||||||
format: 'pem'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return this.privateKeyObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static decryptPassword(encryptedPassword: string): string {
|
|
||||||
try {
|
|
||||||
if (!encryptedPassword || encryptedPassword.trim() === '') {
|
|
||||||
throw new Error('Encrypted password cannot be empty');
|
|
||||||
}
|
|
||||||
|
|
||||||
const privateKeyObject = this.getPrivateKeyObject();
|
|
||||||
const encryptedBuffer = Buffer.from(encryptedPassword, 'base64');
|
|
||||||
|
|
||||||
if (encryptedBuffer.length === 0) {
|
|
||||||
throw new Error('Encrypted password buffer is empty');
|
|
||||||
}
|
|
||||||
|
|
||||||
const decryptedBuffer = crypto.privateDecrypt(
|
|
||||||
{
|
|
||||||
key: privateKeyObject,
|
|
||||||
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
|
|
||||||
oaepHash: 'sha1'
|
|
||||||
},
|
|
||||||
encryptedBuffer
|
|
||||||
);
|
|
||||||
|
|
||||||
return decryptedBuffer.toString('utf8');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Decryption error details:', {
|
|
||||||
message: error instanceof Error ? error.message : 'Unknown error',
|
|
||||||
encryptedPasswordLength: encryptedPassword?.length || 0
|
|
||||||
});
|
|
||||||
throw new Error(`Failed to decrypt password: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user