From e31e9f75e9872b816b39da1954cd5f426ac8ae0f Mon Sep 17 00:00:00 2001 From: DhanshCOSQ Date: Wed, 11 Jun 2025 13:41:37 +0530 Subject: [PATCH] password complete --- .gitignore | 3 + functions/package-lock.json | 9 +- functions/package.json | 2 +- functions/src/dooraccess/essl.ts | 225 +++++++++++++++++-------------- functions/src/shared/decrypt.ts | 51 +++++++ 5 files changed, 184 insertions(+), 106 deletions(-) create mode 100644 functions/src/shared/decrypt.ts diff --git a/.gitignore b/.gitignore index 2bedd41..b848764 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,9 @@ pids # Directory for instrumented libs generated by jscoverage/JSCover lib-cov +# Private key +/functions/assets/keys/fitLien_private.pem + # Coverage directory used by tools like istanbul coverage diff --git a/functions/package-lock.json b/functions/package-lock.json index 4de688d..b349a53 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -31,7 +31,7 @@ "devDependencies": { "@types/long": "^5.0.0", "@types/mime-types": "^2.1.4", - "@types/node": "^22.13.14", + "@types/node": "^22.15.31", "@types/pdfmake": "^0.2.11", "@types/xmldom": "^0.1.34", "firebase-functions-test": "^3.1.0", @@ -2831,9 +2831,10 @@ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" }, "node_modules/@types/node": { - "version": "22.15.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.2.tgz", - "integrity": "sha512-uKXqKN9beGoMdBfcaTY1ecwz6ctxuJAcUlwE55938g0ZJ8lRxwAZqRz2AJ4pzpt5dHdTPMB863UZ0ESiFUcP7A==", + "version": "22.15.31", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.31.tgz", + "integrity": "sha512-jnVe5ULKl6tijxUhvQeNbQG/84fHfg+yMak02cT8QVhBx/F05rAVxCGBYYTh2EKz22D6JF5ktXuNwdx7b9iEGw==", + "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } diff --git a/functions/package.json b/functions/package.json index d95bb67..334660d 100644 --- a/functions/package.json +++ b/functions/package.json @@ -38,7 +38,7 @@ "devDependencies": { "@types/long": "^5.0.0", "@types/mime-types": "^2.1.4", - "@types/node": "^22.13.14", + "@types/node": "^22.15.31", "@types/pdfmake": "^0.2.11", "@types/xmldom": "^0.1.34", "firebase-functions-test": "^3.1.0", diff --git a/functions/src/dooraccess/essl.ts b/functions/src/dooraccess/essl.ts index 8be6a88..ff26ad3 100644 --- a/functions/src/dooraccess/essl.ts +++ b/functions/src/dooraccess/essl.ts @@ -5,7 +5,7 @@ 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(); @@ -30,6 +30,13 @@ export interface UpdateEmployeeExRequest { 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, '&') @@ -324,7 +331,6 @@ export const esslGetUserDetails = onRequest({ 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) { @@ -336,8 +342,9 @@ export const esslGetUserDetails = onRequest({ 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 = password.trim(); + password = getDecryptedPassword(password); if (!getEmployeeDetailsRequest) { throw new Error('Missing request params'); } @@ -380,18 +387,21 @@ export const esslUpdateUser = onRequest({ 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 or password'); + 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 + // 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'); } @@ -426,112 +436,125 @@ export const esslDeleteUser = onRequest({ region: '#{SERVICES_RGN}#' }, async (request: Request, response: Response) => { return corsHandler(request, response, async () => { - 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 - } - password = password.trim(); - 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); + 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'); } - endpoint += '/webservice.asmx'; + 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 }); } - const result = await deleteEmplyee(username, password, employeeCode, endpoint); - response.send(result); }); }); + export const esslGetEmployeePunchLogs = onRequest({ region: '#{SERVICES_RGN}#' }, async (request: Request, response: Response) => { return corsHandler(request, response, async () => { - 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 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 - } - password = password.trim(); - 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); + 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'); } - endpoint += '/webservice.asmx'; + 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 }); } - const result = await getEmployeePunchLogs(username, - password, - employeeCode, - attendanceDate, - endpoint); - response.send(result); }); -}); +}); \ No newline at end of file diff --git a/functions/src/shared/decrypt.ts b/functions/src/shared/decrypt.ts new file mode 100644 index 0000000..63e9b9f --- /dev/null +++ b/functions/src/shared/decrypt.ts @@ -0,0 +1,51 @@ +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'}`); + } + } +} -- 2.43.0