password complete #57
3
.gitignore
vendored
3
.gitignore
vendored
@ -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
|
||||
|
||||
|
||||
9
functions/package-lock.json
generated
9
functions/package-lock.json
generated
@ -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"
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
51
functions/src/shared/decrypt.ts
Normal file
51
functions/src/shared/decrypt.ts
Normal file
@ -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'}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user