From f07fd12bfa892b0f501c2751d458d9a590e385e6 Mon Sep 17 00:00:00 2001 From: vzakharchenko Date: Sun, 31 Oct 2021 08:59:18 +0200 Subject: [PATCH] added support login --- .eslintrc | 2 + index.ts | 368 +++++++++++++++++++++++----------- jslib/public.js | 27 ++- package.json | 20 +- src/ListSMS.ts | 44 ++-- src/MobileData.ts | 27 +-- src/startSession.ts | 146 +++++++++++--- src/utils/DefaultRestCalls.ts | 27 ++- src/utils/HuaweiUtils.ts | 21 ++ src/utils/restCalls.ts | 4 + 10 files changed, 474 insertions(+), 212 deletions(-) create mode 100644 src/utils/HuaweiUtils.ts diff --git a/.eslintrc b/.eslintrc index cc0f4cd..155751f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -32,6 +32,8 @@ "@typescript-eslint/no-var-requires": 0, "no-console": 0, "import/no-unresolved": 0, + "require-atomic-updates": 0, + "@shopify/prefer-early-return": 0, " import/no-unresolved": 0 } } diff --git a/index.ts b/index.ts index 934ce84..d5b008d 100644 --- a/index.ts +++ b/index.ts @@ -1,9 +1,10 @@ import yargs from 'yargs'; import {hideBin} from 'yargs/helpers'; -import {startSession} from "./src/startSession"; +import {login, logout, startSession} from "./src/startSession"; import { - deleteMessage, getInBoxSMS, + deleteMessage, getContactSMSPages, + getInBoxSMS, getSMSByUsers, getSMSContacts, getSMSPages, @@ -14,29 +15,52 @@ import {controlMobileData, reconnect, status} from "./src/MobileData"; // @ts-ignore yargs(hideBin(process.argv)) .command('sendSMS', 'send SMS to contact or group of contacts', (yargs) => { + // @ts-ignore return yargs .positional('url', { describe: 'huawei host', default: '192.168.8.1' + }) + .positional('username', { + describe: 'huawei username', + default: 'admin' + }) + .positional('password', { + describe: 'huawei password', + type: 'string' }).positional('phone', { describe: 'phones with ; as separator ', - type:"string", + type: "string", }).positional('message', { describe: 'text message ', - type:"string", + type: "string", }) }, async (argv) => { - const sessionData = await startSession(argv.url); - if (!argv.phone) { - throw new Error('Phone number is not defined'); - return; + await login(argv.url, argv.username, argv.password); + try { + const sessionData = await startSession(argv.url); + if (!argv.phone) { + throw new Error('Phone number is not defined'); + return; + } + await sendMessage(sessionData, argv.phone, argv.message || ''); + } finally { + await logout(argv.url); } - await sendMessage(sessionData, argv.phone, argv.message||''); }).command('contacts', 'get contact list with the latest sms messages', (yargs) => { - return yargs + // @ts-ignore + return yargs .positional('url', { describe: 'huawei host', default: '192.168.8.1' + }) + .positional('username', { + describe: 'huawei username', + default: 'admin' + }) + .positional('password', { + describe: 'huawei password', + type: 'string' }).positional('page', { describe: 'sms page', default: 1 @@ -48,22 +72,28 @@ yargs(hideBin(process.argv)) default: 'none' }) }, async (argv) => { - const sessionData = await startSession(argv.url); - switch (argv.exportFormat) { - case 'json': { - break; - } - case 'xml': { - break; - } - case 'none': { - break; - } - default: { - throw new Error(`export Format ${argv.exportFile} does not supported: supported only: xml,json,none`) + await login(argv.url, argv.username, argv.password); + try { + const sessionData = await startSession(argv.url); + switch (argv.exportFormat) { + case 'json': { + break; + } + case 'xml': { + break; + } + case 'none': { + break; + } + default: { + throw new Error(`export Format ${argv.exportFile} does not supported: supported only: xml,json,none`) + } } + + await getSMSContacts(sessionData, argv.page, argv.exportFile, argv.exportFormat); + } finally { + await logout(argv.url); } - await getSMSContacts(sessionData, argv.page, argv.exportFile, argv.exportFormat); }).command('messages', 'get all messages from InBox', (yargs) => { return yargs .positional('url', { @@ -72,7 +102,16 @@ yargs(hideBin(process.argv)) }).positional('deleteAfter', { describe: 'delete all messages after reading ', default: false - }).positional('exportFile', { + }) + .positional('username', { + describe: 'huawei username', + default: 'admin' + }) + .positional('password', { + describe: 'huawei password', + type: 'string' + }) + .positional('exportFile', { describe: 'export to file', default: './inbox.list' }).positional('exportFormat', { @@ -80,24 +119,38 @@ yargs(hideBin(process.argv)) default: 'none' }) }, async (argv) => { - const sessionData = await startSession(argv.url); - switch (argv.exportFormat) { - case 'json': { - break; - } - case 'none': { - break; - } - default: { - throw new Error(`export Format ${argv.exportFile} does not supported: supported only: json,none`) + await login(argv.url, argv.username, argv.password); + try { + const sessionData = await startSession(argv.url); + switch (argv.exportFormat) { + case 'json': { + break; + } + case 'none': { + break; + } + default: { + throw new Error(`export Format ${argv.exportFile} does not supported: supported only: json,none`) + } } + await getInBoxSMS(sessionData, argv.deleteAfter, argv.exportFile, argv.exportFormat); + } finally { + await logout(argv.url); } - await getInBoxSMS(sessionData, argv.deleteAfter, argv.exportFile, argv.exportFormat); }).command('contactPages', 'contact list pages', (yargs) => { + // @ts-ignore return yargs .positional('url', { describe: 'huawei host', default: '192.168.8.1' + }) + .positional('username', { + describe: 'huawei username', + default: 'admin' + }) + .positional('password', { + describe: 'huawei password', + type: 'string' }).positional('exportFile', { describe: 'export to file', default: './contactsCount.list' @@ -106,23 +159,30 @@ yargs(hideBin(process.argv)) default: 'none' }) }, async (argv) => { - const sessionData = await startSession(argv.url); - switch (argv.exportFormat) { - case 'json': { - break; - } - case 'xml': { - break; - } - case 'none': { - break; - } - default: { - throw new Error(`export Format ${argv.exportFile} does not supported: supported only: xml,json,none`) + await login(argv.url, argv.username, argv.password); + try { + const sessionData = await startSession(argv.url); + + switch (argv.exportFormat) { + case 'json': { + break; + } + case 'xml': { + break; + } + case 'none': { + break; + } + default: { + throw new Error(`export Format ${argv.exportFile} does not supported: supported only: xml,json,none`) + } } + await getSMSPages(sessionData, argv.exportFile, argv.exportFormat); + } finally { + await logout(argv.url); } - await getSMSPages(sessionData, argv.exportFile, argv.exportFormat); }).command('sms', 'get contact SMS list', (yargs) => { + // @ts-ignore return yargs .positional('url', { describe: 'huawei host', @@ -130,6 +190,14 @@ yargs(hideBin(process.argv)) }).positional('phone', { describe: 'contact phone number', type: 'string' + }) + .positional('username', { + describe: 'huawei username', + default: 'admin' + }) + .positional('password', { + describe: 'huawei password', + type: 'string' }).positional('page', { describe: 'sms page', default: 1 @@ -144,30 +212,44 @@ yargs(hideBin(process.argv)) default: false }) }, async (argv) => { - const sessionData = await startSession(argv.url); - if (!argv.phone) { - throw new Error('phone is not defined'); - } - switch (argv.exportFormat) { - case 'json': { - break; - } - case 'xml': { - break; - } - case 'none': { - break; + await login(argv.url, argv.username, argv.password); + try { + const sessionData = await startSession(argv.url); + if (!argv.phone) { + throw new Error('phone is not defined'); } - default: { - throw new Error(`export Format ${argv.exportFile} does not supported: supported only: xml,json,none`) + switch (argv.exportFormat) { + case 'json': { + break; + } + case 'xml': { + break; + } + case 'none': { + break; + } + default: { + throw new Error(`export Format ${argv.exportFile} does not supported: supported only: xml,json,none`) + } } + await getSMSByUsers(sessionData, argv.phone, argv.page, argv.exportFile, argv.exportFormat, argv.deleteAfter); + } finally { + await logout(argv.url); } - await getSMSByUsers(sessionData, argv.phone, argv.page, argv.exportFile, argv.exportFormat, argv.deleteAfter); }).command('pages', 'count of sms pages', (yargs) => { +// @ts-ignore return yargs .positional('url', { describe: 'huawei host', default: '192.168.8.1' + }) + .positional('username', { + describe: 'huawei username', + default: 'admin' + }) + .positional('password', { + describe: 'huawei password', + type: 'string' }).positional('phone', { describe: 'contact phone number', type: 'string' @@ -179,72 +261,111 @@ yargs(hideBin(process.argv)) default: 'none' }) }, async (argv) => { - const sessionData = await startSession(argv.url); - if (!argv.phone) { - throw new Error('phone is not defined'); - } - switch (argv.exportFormat) { - case 'json': { - break; - } - case 'xml': { - break; - } - case 'none': { - break; + await login(argv.url, argv.username, argv.password); + try { + const sessionData = await startSession(argv.url); + if (!argv.phone) { + throw new Error('phone is not defined'); } - default: { - throw new Error(`export Format ${argv.exportFile} does not supported: supported only: xml,json,none`) + switch (argv.exportFormat) { + case 'json': { + break; + } + case 'xml': { + break; + } + case 'none': { + break; + } + default: { + throw new Error(`export Format ${argv.exportFile} does not supported: supported only: xml,json,none`) + } } + await getContactSMSPages(sessionData, argv.phone, argv.exportFile, argv.exportFormat); + } finally { + await logout(argv.url); } - await getContactSMSPages(sessionData, argv.phone, argv.exportFile, argv.exportFormat); -}).command('deleteSMS', 'delete sms by smsId', (yargs) => { +}).command('deleteSMS', 'delete sms by smsId', (yargs: any) => { + // @ts-ignore return yargs .positional('url', { describe: 'huawei host', default: '192.168.8.1' + }) + .positional('username', { + describe: 'huawei username', + default: 'admin' + }) + .positional('password', { + describe: 'huawei password', + type: 'string' }).positional('messageId', { describe: 'messageId or index', type: 'string' }) -}, async (argv) => { - const sessionData = await startSession(argv.url); - if (!argv.messageId) { - throw new Error('messageId is not defined'); +}, async (argv: any) => { + await login(argv.url, argv.username, argv.password); + try { + const sessionData = await startSession(argv.url); + if (!argv.messageId) { + throw new Error('messageId is not defined'); + } + await deleteMessage(sessionData, argv.messageId); + } finally { + await logout(argv.url) } - await deleteMessage(sessionData, argv.messageId); -}).command('mobileData', 'Enable/Disable or Reconnect Mobile Data', (yargs:any) => { +}).command('mobileData', 'Enable/Disable or Reconnect Mobile Data', (yargs: any) => { + // @ts-ignore return yargs .positional('url', { describe: 'huawei host', default: '192.168.8.1' + }).positional('username', { + describe: 'huawei username', + default: 'admin' + }) + .positional('password', { + describe: 'huawei password', + type: 'string' }).positional('mode', { describe: 'change mobile data to on,off or reconnect', }) -}, async (argv:any) => { - const sessionData = await startSession(argv.url); - switch (argv.mode) { - case 'reconnect': { - await reconnect(sessionData); - return; - } - case 'on': { - break; - } - case 'off': { - break - } - default: { - throw new Error('Does not support Mode: ' + argv.mode + '. Supported only on,off,reconnect') +}, async (argv: any) => { + await login(argv.url, argv.username, argv.password); + try { + const sessionData = await startSession(argv.url); + switch (argv.mode) { + case 'reconnect': { + await reconnect(sessionData); + return; + } + case 'on': { + break; + } + case 'off': { + break + } + default: { + throw new Error('Does not support Mode: ' + argv.mode + '. Supported only on,off,reconnect') + } } + await controlMobileData(sessionData, argv.mode); + } finally { + await logout(argv.url); } - await controlMobileData(sessionData, argv.mode); -}).command('monitoring', 'current Monitoring status', (yargs:any) => { +}).command('monitoring', 'current Monitoring status', (yargs: any) => { // @ts-ignore return yargs .positional('url', { describe: 'huawei host', default: '192.168.8.1' + }).positional('username', { + describe: 'huawei username', + default: 'admin' + }) + .positional('password', { + describe: 'huawei password', + type: 'string' }).positional('exportFile', { describe: 'export to file', default: './monitoring.log' @@ -252,23 +373,28 @@ yargs(hideBin(process.argv)) describe: 'export format (xml, json, none)', default: 'none' }) -}, async (argv:any) => { - const sessionData = await startSession(argv.url); - switch (argv.exportFormat) { - case 'json': { - break; - } - case 'xml': { - break; - } - case 'none': { - break; - } - default: { - throw new Error(`export Format ${argv.exportFile} does not supported: supported only: xml,json,none`) +}, async (argv: any) => { + await login(argv.url, argv.username, argv.password); + try { + const sessionData = await startSession(argv.url); + switch (argv.exportFormat) { + case 'json': { + break; + } + case 'xml': { + break; + } + case 'none': { + break; + } + default: { + throw new Error(`export Format ${argv.exportFile} does not supported: supported only: xml,json,none`) + } } + await status(sessionData, argv.exportFile, argv.exportFormat); + } finally { + await logout(argv.url); } - await status(sessionData, argv.exportFile, argv.exportFormat); }) // .option('verbose', { // alias: 'v', diff --git a/jslib/public.js b/jslib/public.js index a8ae124..daa8773 100644 --- a/jslib/public.js +++ b/jslib/public.js @@ -4,7 +4,15 @@ const {restCalls} = require("../src/utils/DefaultRestCalls"); const parser = require('xml2js'); const CryptoJS = require('crypto-js'); const {RSAKey} = require('./rsa'); - +const publicKey = { + publicKey:null, +}; +const publicSession = { + login:'0', + session:'', + token1:'', + token2:'' +}; var C = CryptoJS; var C_lib = C.lib; @@ -103,12 +111,15 @@ C.SCRAM = function (cfg) { async function getPublicKey(session){ - const resp = await restCalls.fetchData(`http://${session.url}/api/webserver/publickey`,'GET', { - 'cookie': `sessionId=${session.SesInfo}`, - __RequestVerificationToken: session.TokInfo - }); - const message = await parser.parseStringPromise(resp); - return message.response; + if (!publicKey.publicKey){ + const resp = await restCalls.fetchData(`http://${session.url}/api/webserver/publickey`,'GET', { + 'cookie': `sessionId=${session.SesInfo}`, + __RequestVerificationToken: session.TokInfo + }); + const message = await parser.parseStringPromise(resp); + publicKey.publicKey= message.response; + } + return publicKey.publicKey } function utf8Encode(string) { @@ -293,3 +304,5 @@ function dataDecrypt(scram,smsNonce,smsSalt,nonceStr,encryptedData) { module.exports.dataDecrypt = dataDecrypt; module.exports.CryptoJS = CryptoJS; module.exports.doRSAEncrypt = doRSAEncrypt; +module.exports.publicKey = publicKey; +module.exports.publicSession = publicSession; diff --git a/package.json b/package.json index e3db6e7..6ddab80 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,18 @@ { "name": "e3372h-320-cli", - "version": "1.0.5", + "version": "1.1.1", "description": "e3372h-320 client", "main": "index.js", "scripts": { - "sendSMS": "node index.js sendSMS --phone=+11111111111 --message=test123 --url=192.168.89.1 ", - "contacts": "node index.js contacts --url=192.168.89.1", - "getContactsCount": "node index.js contactPages --url=192.168.89.1 --exportFormat=json", - "sms": "node index.js sms --phone=+11111111111 --url=192.168.89.1 --page=1", - "pages": "node index.js pages --phone=+11111111111 --url=192.168.89.1 --exportFormat=json", - "enableData": "node index.js mobileData --mode=on --url=192.168.89.1", - "disableData": "node index.js mobileData --mode=off --url=192.168.89.1", - "reconnected": "node index.js mobileData --mode=reconnect --url=192.168.89.1", - "monitoring": "node index.js monitoring --url=192.168.89.1", + "sendSMS": "node index.js sendSMS --phone=+11111111111 --message=test123 --url=192.168.89.1 --password=testPassword ", + "contacts": "node index.js contacts --url=192.168.89.1 --password=testPassword", + "getContactsCount": "node index.js contactPages --url=192.168.89.1 --exportFormat=json --password=testPassword", + "sms": "node index.js sms --phone=+380674819807 --url=192.168.89.1 --page=1 --password=testPassword", + "pages": "node index.js pages --phone=+11111111111 --url=192.168.89.1 --exportFormat=json --password=testPassword", + "enableData": "node index.js mobileData --mode=on --url=192.168.89.1 --password=testPassword", + "disableData": "node index.js mobileData --mode=off --url=192.168.89.1 --password=testPassword", + "reconnected": "node index.js mobileData --mode=reconnect --url=192.168.89.1 --password=testPassword", + "monitoring": "node index.js monitoring --url=192.168.89.1 --password=testPassword", "lint": "eslint src --ext .ts src", "build": "tsc --build", "lint:fix": "eslint --fix src --ext .ts" diff --git a/src/ListSMS.ts b/src/ListSMS.ts index 801087d..b7917d7 100644 --- a/src/ListSMS.ts +++ b/src/ListSMS.ts @@ -5,6 +5,7 @@ import parser from 'xml2js'; import {SessionData, startSession} from './startSession'; import {restCalls} from "./utils/DefaultRestCalls"; import {ExportFormat} from "./utils/Constants"; +import {getSessionHeaders} from "./utils/HuaweiUtils"; const huawei = require('../jslib/public'); @@ -45,12 +46,9 @@ export async function getSMSByUsers(sessionData: SessionData, const nonceStr = smsNonce + smsSalt; const encrpt = await huawei.doRSAEncrypt(sessionData0, nonceStr); const data = await huawei.doRSAEncrypt(sessionData, `${phone}${pageindex}20${encrpt}`); - const resp = await restCalls.sendData(`http://${sessionData0.url}/api/sms/sms-list-phone`, 'POST', data, { - __RequestVerificationToken: sessionData0.TokInfo, - 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8;enc', - Cookie: `SessionId=${sessionData0.SesInfo}`, - }); - const pwdret = await parser.parseStringPromise((resp)); + const resp = await restCalls.sendDataRaw(`http://${sessionData0.url}/api/sms/sms-list-phone`, 'POST', data, await getSessionHeaders(sessionData.url)); + huawei.publicSession.token2 = resp.headers.__requestverificationtoken; + const pwdret = await parser.parseStringPromise((resp.data)); const ret = huawei.dataDecrypt(scram, smsNonce, smsSalt, nonceStr, pwdret); if (deleteAfter) { const json = await parser.parseStringPromise(ret); @@ -81,20 +79,16 @@ export async function getContactSMSPages(sessionData: SessionData, exportFile: string, exportFormat: ExportFormat) { const data = await huawei.doRSAEncrypt(sessionData, `${phone}`); - const resp = await restCalls.sendData(`http://${sessionData.url}/api/sms/sms-count-contact`, 'POST', data, { - __RequestVerificationToken: sessionData.TokInfo, - 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8;enc', - Cookie: `SessionId=${sessionData.SesInfo}`, - }); - - const json = await parser.parseStringPromise(resp); + const resp = await restCalls.sendDataRaw(`http://${sessionData.url}/api/sms/sms-count-contact`, 'POST', data, await getSessionHeaders(sessionData.url)); + huawei.publicSession.token2 = resp.headers.__requestverificationtoken; + const json = await parser.parseStringPromise(resp.data); let number = Math.floor(json.response.count / 21); if (number > 0) { number += 1; } if (exportFormat !== 'hide') { if (exportFormat === 'xml') { - await saveFile(exportFile, resp); + await saveFile(exportFile, resp.data); console.info(`xml file ${exportFile} created`); } else if (exportFormat === 'json') { await saveFile(exportFile, JSON.stringify(await parser.parseStringPromise(resp))); @@ -114,7 +108,7 @@ export async function getSMSPages(sessionData: SessionData, Cookie: `SessionId=${sessionData.SesInfo}`, }); const json = await parser.parseStringPromise(resp); - const number = Math.floor((json.response.LocalInbox[0] + json.response.LocalOutbox[0]) / 21); + const number = Math.floor((json.response.LocalInbox[0] + json.response.LocalOutbox[0]) / 21) + 1; if (exportFormat !== 'hide') { if (exportFormat === 'xml') { await saveFile(exportFile, resp); @@ -153,12 +147,9 @@ export async function sendMessage(sessionData: SessionData, const nonceStr = smsNonce + smsSalt; const encrpt = await huawei.doRSAEncrypt(sessionData, nonceStr); const data = await huawei.doRSAEncrypt(sessionData, `-1${(phones)}${message}${message.length}12021-10-27 00:12:24${encrpt}`); - const resp = await restCalls.sendData(`http://${sessionData.url}/api/sms/send-sms`, 'POST', data, { - __RequestVerificationToken: sessionData.TokInfo, - 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8;enc', - Cookie: `SessionId=${sessionData.SesInfo}`, - }); - const json = await parser.parseStringPromise(resp); + const resp = await restCalls.sendDataRaw(`http://${sessionData.url}/api/sms/send-sms`, 'POST', data, await getSessionHeaders(sessionData.url)); + huawei.publicSession.token2 = resp.headers.__requestverificationtoken; + const json = await parser.parseStringPromise(resp.data); if (json.response !== 'OK') { throw new Error(`Delete message error: ${JSON.stringify(json)}`); } @@ -215,13 +206,10 @@ export async function getSMSContacts(sessionData0: SessionData, const nonceStr = smsNonce + smsSalt; const encrpt = await huawei.doRSAEncrypt(sessionData, nonceStr); const data = await huawei.doRSAEncrypt(sessionData, `${pageindex}20${encrpt}`); - const resp = await restCalls.sendData(`http://${sessionData.url}/api/sms/sms-list-contact`, 'POST', - data, { - 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8;enc', - __RequestVerificationToken: sessionData.TokInfo, - Cookie: `SessionId=${sessionData.SesInfo}`, - }); - const pwdret = await parser.parseStringPromise(resp); + const resp = await restCalls.sendDataRaw(`http://${sessionData.url}/api/sms/sms-list-contact`, 'POST', + data, await getSessionHeaders(sessionData.url)); + huawei.publicSession.token2 = resp.headers.__requestverificationtoken; + const pwdret = await parser.parseStringPromise(resp.data); const ret = huawei.dataDecrypt(scram, smsNonce, smsSalt, nonceStr, pwdret); const json = await parser.parseStringPromise(ret); if (exportFormat !== 'hide') { diff --git a/src/MobileData.ts b/src/MobileData.ts index 8582d22..f13b551 100644 --- a/src/MobileData.ts +++ b/src/MobileData.ts @@ -5,7 +5,9 @@ import parser from "xml2js"; import {SessionData} from "./startSession"; import {restCalls} from "./utils/DefaultRestCalls"; import {ExportFormat} from "./utils/Constants"; +import {getSessionHeaders} from "./utils/HuaweiUtils"; +const huawei = require('../jslib/public'); type MobileStatus = 'on' | 'off'; @@ -14,12 +16,10 @@ async function saveFile(filename: string, data: string) { } export async function controlMobileData(sessionData: SessionData, mobileStatus: MobileStatus) { - const data = `${mobileStatus === 'on' ? 1 : 0}`; - const resp = await restCalls.sendData(`http://${sessionData.url}/api/dialup/mobile-dataswitch`, 'POST', data, { - __RequestVerificationToken: sessionData.TokInfo, - Cookie: `SessionId=${sessionData.SesInfo}`, - }); - const json = await parser.parseStringPromise(resp); + const data = await huawei.doRSAEncrypt(sessionData, `${mobileStatus === 'on' ? 1 : 0}`); + const resp = await restCalls.sendDataRaw(`http://${sessionData.url}/api/dialup/mobile-dataswitch`, 'POST', data, await getSessionHeaders(sessionData.url)); + huawei.publicSession.token2 = resp.headers.__requestverificationtoken; + const json = await parser.parseStringPromise(resp.data); if (json.response !== 'OK') { throw new Error(`Control Mobile Data error: ${JSON.stringify(json)}`); } @@ -27,12 +27,10 @@ export async function controlMobileData(sessionData: SessionData, mobileStatus: } export async function reconnect(sessionData: SessionData) { - const data = `1`; - const resp = await restCalls.sendData(`http://${sessionData.url}/api/net/reconnect`, 'POST', data, { - __RequestVerificationToken: sessionData.TokInfo, - Cookie: `SessionId=${sessionData.SesInfo}`, - }); - const json = await parser.parseStringPromise(resp); + const data = await huawei.doRSAEncrypt(sessionData, `1`); + const resp = await restCalls.sendDataRaw(`http://${sessionData.url}/api/net/reconnect`, 'POST', data, await getSessionHeaders(sessionData.url)); + huawei.publicSession.token2 = resp.headers.__requestverificationtoken; + const json = await parser.parseStringPromise(resp.data); if (json.response !== 'OK') { throw new Error(`Reconnecting error: ${JSON.stringify(json)}`); } @@ -41,10 +39,7 @@ export async function reconnect(sessionData: SessionData) { export async function status(sessionData: SessionData, exportFile: string, exportFormat: ExportFormat) { - const resp = await restCalls.fetchData(`http://${sessionData.url}/api/monitoring/status`, 'GET', { - __RequestVerificationToken: sessionData.TokInfo, - Cookie: `SessionId=${sessionData.SesInfo}`, - }); + const resp = await restCalls.fetchData(`http://${sessionData.url}/api/monitoring/status`, 'GET', await getSessionHeaders(sessionData.url)); if (exportFormat !== 'hide') { if (exportFormat === 'xml') { await saveFile(exportFile, resp); diff --git a/src/startSession.ts b/src/startSession.ts index 855c070..f09bc76 100644 --- a/src/startSession.ts +++ b/src/startSession.ts @@ -2,6 +2,8 @@ import parser from 'xml2js'; import {restCalls} from "./utils/DefaultRestCalls"; +const huawei = require('../jslib/public'); + export type SessionData = { TokInfo: string, SesInfo: string, @@ -13,37 +15,123 @@ type SessionData0 = { SesInfo: string } -// async function saveFile(data: SessionData) { -// await fs.promises.writeFile(os.tmpdir() + '/huawei.tmp', JSON.stringify(data)); -// } -// -// async function readFile(): Promise { -// try { -// return JSON.parse(await fs.promises.readFile(os.tmpdir() + '/huawei.tmp', 'utf-8')); -// } catch (e) { -// console.error("read session data error", e); -// await fs.promises.rm(os.tmpdir() + '/huawei.tmp'); -// throw e; -// } -// } -// -async function getSessionId(url: string): Promise { +export async function hilinkLogin(url: string): Promise { + const resp = await restCalls.fetchData(`http://${url}/api/user/hilink_login`, 'GET'); + const message = await parser.parseStringPromise(resp); + return message.response.hilink_login[0]; +} + +async function checkLogin(url: string, username: string, password: string|undefined): Promise { + const number = await hilinkLogin(url); + huawei.publicSession.login = number; + if (number === '1') { + if (!username) { + throw new Error('username is required'); + } + if (!password) { + throw new Error('password is required'); + } + } + return number === '1'; +} + + +export async function logout(url: string) { + if (huawei.publicSession.login === '1') { + const resp = await restCalls.sendDataRaw(`http://${url}/api/user/logout`, 'POST', + `1` + , { + __RequestVerificationToken: `${huawei.publicSession.token2}`, + Cookie: `${huawei.publicSession.session}`, + _ResponseSource: 'Broswer', + }); + huawei.publicSession.token2 = resp.headers.__requestverificationtoken; + const message = await parser.parseStringPromise(resp.data); + if (message.response !== 'OK') { + throw new Error(`Logout error: ${resp}`); + } + } +} + + +export async function login(url: string, user: string, password: string|undefined) { + if (await checkLogin(url, user, password)) { + const cryptoJS = huawei.CryptoJS; + const scram = cryptoJS.SCRAM(); + const firstNonce = scram.nonce().toString(); + const sd = await restCalls.fetchDataRaw(`http://${url}`, 'GET'); + let header = sd.headers['set-cookie'] || ['']; + const sd2 = await getSessionId0(url); + + const sessionSec = header[0]; + await getToken(url, sessionSec); + const resp = await restCalls.sendDataRaw(`http://${url}/api/user/challenge_login`, 'POST', + `${user}${firstNonce}1`, + { + __RequestVerificationToken: sd2.TokInfo, + Cookie: `${sessionSec}`, + _ResponseSource: 'Broswer', + DNT: 1, + }); + const challengeLogin = await parser.parseStringPromise(resp.data); + + const scarmSalt = cryptoJS.enc.Hex.parse(challengeLogin.response.salt[0]); + const iter = challengeLogin.response.iterations[0]; + const finalNonce = challengeLogin.response.servernonce[0]; + const authMsg = `${firstNonce},${finalNonce},${finalNonce}`; + const saltPassword = scram.saltedPassword(password, scarmSalt, iter).toString(); + const serverKey = scram.serverKey(cryptoJS.enc.Hex.parse(saltPassword)).toString(); + const clientProof = scram.clientProof(password, scarmSalt, iter, authMsg).toString(); + + const resp1 = await restCalls.sendDataRaw(`http://${url}/api/user/authentication_login`, 'POST', + `${clientProof}${finalNonce}`, + { + Cookie: `${sessionSec}`, + __RequestVerificationToken: resp.headers.__requestverificationtoken, + _ResponseSource: 'Broswer', + DNT: 1, + }); + header = resp1.headers['set-cookie'] || ['']; + huawei.publicSession.session = header[0].replace('; path=/; HttpOnly;', ''); + huawei.publicSession.token1 = resp1.headers.__requestverificationtokenone; + huawei.publicSession.token2 = resp1.headers.__requestverificationtokentwo; + const data = await parser.parseStringPromise(resp1.data); + huawei.publicKey.publicKey = + { + encpubkeyn: data.response.rsan, + encpubkeye: data.response.rsae, + }; + } +} + + +export async function getToken(url: string, cooke: string): Promise { + const resp = await restCalls.fetchDataRaw(`http://${url}/api/webserver/token`, 'GET', + { + _ResponseSource: 'Broswer', + DNT: 1, + Cookie: cooke, + }); + const message = await parser.parseStringPromise(resp.data); + + return { + token: message.response.token[0], + resp, + }; +} + +async function getSessionId0(url: string): Promise { const resp = await restCalls.fetchData(`http://${url}/api/webserver/SesTokInfo`, 'GET'); const message = await parser.parseStringPromise(resp); - return message.response; -} -// -// export async function getCurrentSession() { -// try { -// const session: SessionData = await readFile(); -// await startSession(session.url); -// return await readFile(); -// } catch (e) { -// console.error("Session file does not exist or not valid please start a new session", e); -// throw new Error("Session Does not exist") -// } -// -// } + return { + TokInfo: message.response.TokInfo[0], + SesInfo: message.response.SesInfo[0], + }; +} + +async function getSessionId(url: string): Promise { + return getSessionId0(url); +} export async function startSession(url: string) { const session0: SessionData0 = await getSessionId(url); diff --git a/src/utils/DefaultRestCalls.ts b/src/utils/DefaultRestCalls.ts index 1465324..728e245 100644 --- a/src/utils/DefaultRestCalls.ts +++ b/src/utils/DefaultRestCalls.ts @@ -1,4 +1,4 @@ -import fetch from 'axios'; +import fetch, {AxiosPromise} from 'axios'; import {HTTPMethod, RestCalls} from './restCalls'; @@ -15,6 +15,18 @@ class DefaultRestCalls implements RestCalls { return ret.data; } + async fetchDataRaw(url: string, method: HTTPMethod, headers?: any): Promise { + const ret = await fetch({ + url, + method, + headers, + transformResponse: (req) => req, + withCredentials: true, + timeout: 29000, + }); + return ret; + } + async sendData(url: string, method: HTTPMethod, data: string, headers?: any): Promise { const ret = await fetch({ url, @@ -28,6 +40,19 @@ class DefaultRestCalls implements RestCalls { return ret.data; } + async sendDataRaw(url: string, method: HTTPMethod, data: string, headers?: any): Promise { + const ret = await fetch({ + url, + method, + data, + transformResponse: (req) => req, + headers, + withCredentials: true, + timeout: 29000, + }); + return ret; + } + } export const restCalls = new DefaultRestCalls(); diff --git a/src/utils/HuaweiUtils.ts b/src/utils/HuaweiUtils.ts new file mode 100644 index 0000000..4d41715 --- /dev/null +++ b/src/utils/HuaweiUtils.ts @@ -0,0 +1,21 @@ +import {startSession} from "../startSession"; + +const huawei = require('../../jslib/public'); + +export async function getSessionHeaders(url:string):Promise { + if (huawei.publicSession.login === '1') { + return { + 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8;enc', + __RequestVerificationToken: `${huawei.publicSession.token2}`, + Cookie: `${huawei.publicSession.session}`, + _ResponseSource: 'Broswer', + }; + } else { + const sessionData = await startSession(url); + return { + 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8;enc', + __RequestVerificationToken: sessionData.TokInfo, + Cookie: `SessionId=${sessionData.SesInfo}`, + }; + } +} diff --git a/src/utils/restCalls.ts b/src/utils/restCalls.ts index 57cef61..ccb079b 100644 --- a/src/utils/restCalls.ts +++ b/src/utils/restCalls.ts @@ -1,3 +1,5 @@ +import {AxiosPromise} from "axios"; + export type HTTPMethod = | 'get' | 'GET' | 'delete' | 'DELETE' @@ -12,5 +14,7 @@ export type HTTPMethod = export interface RestCalls { fetchData(url:string, method?:HTTPMethod, headers?:any):Promise; + fetchDataRaw(url:string, method?:HTTPMethod, headers?:any):Promise; sendData(url:string, method:HTTPMethod, data:string, headers?:any):Promise; + sendDataRaw(url:string, method:HTTPMethod, data:string, headers?:any):Promise; }