diff --git a/README.md b/README.md index 8fa170e..8142386 100644 --- a/README.md +++ b/README.md @@ -12,27 +12,21 @@ #### USING THE SDK IN NODEJS #### -* You need to include the npm package 'latch-sdk' in your package.json file and then require the "latch-sdk" in your NodeJS file. +* You need to include the npm package 'latch-sdk' in your package.json file and then import the "latch-sdk" with the needed functions in your NodeJS file. ``` - var latch = require('latch-sdk'); + import { init, pair ... } from 'latch-sdk'; ``` * Initialize latch with your AppId and SecretKey. Hostname and port are optional. ``` - latch.init({ appId: 'MY_APP_ID', secretKey: 'MY_SECRET_KEY', hostname: 'HOSTNAME:PORT' }); + init({ appId: 'MY_APP_ID', secretKey: 'MY_SECRET_KEY', hostname: 'HOSTNAME:PORT' }); ``` * Call to Latch Server. Pairing will return an account id that you should store for future api calls ``` let PAIRING_CODE = "" - - let response = latch.pair(PAIRING_CODE, function (err, result) { - if (err) { - console.log(util.inspect(err, {showHidden: true, depth: null, colors: true})); - } else { - console.log(util.inspect(result, {showHidden: true, depth: null, colors: true})); - } - }); + let response = await pair(PAIRING_CODE); + console.dir(response, { depth: null }); ``` #### USING THE SDK IN NODEJS FOR WEB3 SERVICES #### @@ -54,23 +48,10 @@ The two additional parameters are: - MESSAGE TO SIGN : "Latch-Web3" * Call to Latch Server for pairing as usual, but with the newly methods: -``` - let MY_APPID = "" - let MY_SECRETKEY = "" - - let WEB3WALLET = "" - let WEB3SIGNATURE = "" - let PAIRING_CODE = "" - - latch.init({ appId: MY_APPID, secretKey: MY_SECRETKEY }); - - let response = latch.pair(PAIRING_CODE, function (err, result) { - if (err) { - console.log(util.inspect(err, {showHidden: true, depth: null, colors: true})); - } else { - console.log(util.inspect(result, {showHidden: true, depth: null, colors: true})); - } - }, WEB3WALLET, WEB3SIGNATURE); +``` + init({ appId: , secretKey: }); + let response = await pair(, , ); + console.dir(response, { depth: null }); ``` You have an example of use in the file [example](examples/example.js) diff --git a/config.js b/config.js index 36f8466..315d700 100644 --- a/config.js +++ b/config.js @@ -17,37 +17,35 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -API_VERSION = "1.3" +const API_VERSION = '1.3' -var config = { - appId: '', - secretKey: '', +export const config = { + APP_ID: '', + SECRET_KEY: '', - API_HOST: "https://latch.tu.com:443", - API_CHECK_STATUS_URL: "/api/" + API_VERSION + "/status", - API_PAIR_URL: "/api/" + API_VERSION + "/pair", - API_PAIR_WITH_ID_URL: "/api/" + API_VERSION + "/pairWithId", - API_UNPAIR_URL: "/api/" + API_VERSION + "/unpair", - API_LOCK_URL: "/api/" + API_VERSION + "/lock", - API_UNLOCK_URL: "/api/" + API_VERSION + "/unlock", - API_HISTORY_URL: "/api/" + API_VERSION + "/history", - API_OPERATION_URL: "/api/" + API_VERSION + "/operation", - API_SUBSCRIPTION_URL: "/api/" + API_VERSION + "/subscription", - API_APPLICATION_URL: "/api/" + API_VERSION + "/application", - API_INSTANCE_URL: "/api/" + API_VERSION + "/instance", - API_TOTP_URL: "/api/" + API_VERSION + "/totps", - API_CONTROL_STATUS_CHECK_URL: "/api/" + API_VERSION + "/control-status", + API_HOST: 'https://latch.tu.com:443', + API_CHECK_STATUS_URL: `/api/${API_VERSION}/status`, + API_PAIR_URL: `/api/${API_VERSION}/pair`, + API_PAIR_WITH_ID_URL: `/api/${API_VERSION}/pairWithId`, + API_UNPAIR_URL: `/api/${API_VERSION}/unpair`, + API_LOCK_URL: `/api/${API_VERSION}/lock`, + API_UNLOCK_URL: `/api/${API_VERSION}/unlock`, + API_HISTORY_URL: `/api/${API_VERSION}/history`, + API_OPERATION_URL: `/api/${API_VERSION}/operation`, + API_SUBSCRIPTION_URL: `/api/${API_VERSION}/subscription`, + API_APPLICATION_URL: `/api/${API_VERSION}/application`, + API_INSTANCE_URL: `/api/${API_VERSION}/instance`, + API_TOTP_URL: `/api/${API_VERSION}/totps`, + API_CONTROL_STATUS_CHECK_URL: `/api/${API_VERSION}/control-status`, - AUTHORIZATION_HEADER_NAME: "Authorization", - DATE_HEADER_NAME: "X-11Paths-Date", - PLUGIN_HEADER_NAME: "Latch-Plugin-Name", - AUTHORIZATION_METHOD: "11PATHS", - AUTHORIZATION_HEADER_FIELD_SEPARATOR: " ", + AUTHORIZATION_HEADER_NAME: 'Authorization', + DATE_HEADER_NAME: 'X-11Paths-Date', + PLUGIN_HEADER_NAME: 'Latch-Plugin-Name', + AUTHORIZATION_METHOD: '11PATHS', + AUTHORIZATION_HEADER_FIELD_SEPARATOR: ' ', - UTC_STRING_FORMAT: "%Y-%m-%d %H:%M:%S", + UTC_STRING_FORMAT: '%Y-%m-%d %H:%M:%S', - X_11PATHS_HEADER_PREFIX: "X-11paths-", - X_11PATHS_HEADER_SEPARATOR: ":", + X_11PATHS_HEADER_PREFIX: 'X-11paths-', + X_11PATHS_HEADER_SEPARATOR: ':', }; - -module.exports = config; diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..6d8e357 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,28 @@ +import globals from "globals"; +import pluginJs from "@eslint/js"; +import mochaPlugin from 'eslint-plugin-mocha'; + + +export default [ + { + languageOptions: { + globals: globals.node + } + }, + pluginJs.configs.recommended, + { + rules: { + // note you must disable the base rule as it can report incorrect errors + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + "warn", // or "error" + { + "argsIgnorePattern": "_", + "varsIgnorePattern": "_", + "caughtErrorsIgnorePattern": "_" + } + ] + } + }, + mochaPlugin.configs.flat.recommended +]; \ No newline at end of file diff --git a/examples/example.js b/examples/example.js index a7e80a7..291685c 100644 --- a/examples/example.js +++ b/examples/example.js @@ -16,167 +16,249 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ - +import { createInstance, createOperation, createTotp, deleteInstance, deleteOperation, deleteTotp, getInstances, getOperations, init, lock, LOCK_ON_REQUEST, pair, pairWithId, status, TWO_FACTOR, unlock, unpair, updateInstance, validateTotp } from '../index.js'; // To run an example just fill in the value of the constants and run the example. +const CONFIG = { + appId: '', + secretKey: '' +} -const latch = require('../index.js'); -const util = require('util'); - +const readInput = (message) => { + console.log(Array.isArray(message) ? message.join('\n') : message + ":"); -// Mandatory -const MY_APPID = ""; -const MY_SECRETKEY = ""; + return new Promise((resolve) => { + process.stdin.once('data', (data) => { + resolve(data.toString().trim()); + }); + }); +}; -// Mandatory depending on the method -const MY_ACCOUNTID = "" -const MY_ACCOUNT_NAME = "" -const PAIRING_CODE = "" -const OPERATIONID = "" +const checkStatus = (id, response) => { + if (response.data.operations[id].status == 'on') { + console.log("Your latch is open and you are able to perform action") + } else if (response.data.operations[id].status == 'off') { + console.log("Your latch is lock and you can not be allowed to perform action") + } else { + console.log("Error processing the response") + } +} -// Mandatory for web3 -const WEB3WALLET = "" -const WEB3SIGNATURE = "" +const warning = ` +\x1b[41mIMPORTANT!!\x1b[0m +To run this example you need to get the "Application ID" and "Secret", (fundamental values for integrating Latch in any application). +It’s necessary to register a developer account in Latch's website: https://latch.tu.com. On the upper right side, click on "Developer area"`; -latch.init({ appId: MY_APPID, secretKey: MY_SECRETKEY }); +const menu = ["","", + "--- Main Menu ---", + "1. Pair a account with your app, token generated from user", + "2. Pair a account with web3 services", + "3. Pair a account with an alias", + "4. Unpair a account with your app", + "-----------------", + "5. Create operation", + "6. Lock operation", + "7. Unlock operation", + "8. Status operation", + "9. Get operations", + "10. Delete operation", + "-----------------", + "11. Create TOTP", + "12. Validate TOTP", + "13. Delete TOTP", + "-----------------", + "14. Create instance", + "15. Update instance", + "16. Status instance", + "17. Get instances", + "18. Delete instance", + "Select an option: " +]; +const menuTwoFactor = ["--- Two factor options ---", + "1. Disabled", + "2. Opt in", + "3. Mandatory", + "Select an option: " +]; -// USING THE SDK IN NODEJS +const menuLockOnRequest = ["--- Lock on request options ---", + "1. Disabled", + "2. Opt in", + "3. Mandatory", + "Select an option: " +]; -// PAIR WITH ID (FOR TESTING) -function example_pair_with_id() { - let response = latch.pairWithId(MY_ACCOUNT_NAME, function(err, result) { - if (err) { - console.log(util.inspect(err, { showHidden: true, depth: null, colors:true })); - } else { - console.log(util.inspect(result, { showHidden: true, depth: null, colors: true })); - } - }); +const readTwoFactor = async () => { + let twoFactor = await readInput(menuTwoFactor); + switch (twoFactor) { + case '1': return TWO_FACTOR.DISABLED; + case '2': return TWO_FACTOR.OPT_IN; + case '3': return TWO_FACTOR.MANDATORY; + default: return TWO_FACTOR.DISABLED; + } } -// PAIR WITH PAIRING CODE -function example_pair_with_paring_code() { - let response = latch.pair(PAIRING_CODE, function (err, result) { - if (err) { - console.log(util.inspect(err, {showHidden: true, depth: null, colors: true})); - } else { - console.log(util.inspect(result, {showHidden: true, depth: null, colors: true})); - } - }); +const readLockOnRequest = async () => { + let twoFactor = await readInput(menuLockOnRequest); + switch (twoFactor) { + case '1': return LOCK_ON_REQUEST.DISABLED; + case '2': return LOCK_ON_REQUEST.OPT_IN; + case '3': return LOCK_ON_REQUEST.MANDATORY; + default: return LOCK_ON_REQUEST.DISABLED; + } } - -// USING THE SDK IN NODEJS FOR WEB3 SERVICES - -// PAIR WITH ID (WEB3) -function example_pair_with_id_web3() { - let response = latch.pairWithId(MY_ACCOUNT_NAME, function (err, result) { - if (err) { - console.log(util.inspect(err, {showHidden: true, depth: null, colors: true})); - } else { - console.log(util.inspect(result, {showHidden: true, depth: null, colors: true})); +const handleOption = async (option) => { + switch (option) { + case '1': { + let token = await readInput('Generated pairing token from the user account'); + let response = await pair(token); + console.dir(response, { depth: null }); + console.log('Store the accountId for future uses'); + break; } - }, WEB3WALLET, WEB3SIGNATURE); -} - -// PAIR WITH PAIRING CODE (WEB3) -function example_pair_with_paring_code_web3() { - let response = latch.pair(PAIRING_CODE, function (err, result) { - if (err) { - console.log(util.inspect(err, {showHidden: true, depth: null, colors: true})); - } else { - console.log(util.inspect(result, {showHidden: true, depth: null, colors: true})); + case '2': { + let token = await readInput('Generated pairing token from the user account'); + let web3Wallet = await readInput('Wallet'); + let web3Signature = await readInput('Signature'); + let response = await pair(token, web3Wallet, web3Signature); + console.dir(response, { depth: null }); + console.log('Store the accountId for future uses'); + break; } - }, WEB3WALLET, WEB3SIGNATURE); -} - -// USING THE SDK IN NODEJS FOR ALL (WEB3 AND NOT WEB3) - -// GET STATUS -function example_get_status() { - let response = latch.status(MY_ACCOUNTID, null, null, function (err, result) { - if (err) { - console.log(util.inspect(err, {showHidden: true, depth: null, colors: true})); - } else { - console.log(util.inspect(result, {showHidden: true, depth: null, colors: true})); + case '3': { + let token = await readInput('Generated pairing token from the user account'); + let commonName = await readInput('Alias attached to this pairing, for panel admins like Latch Support Tool'); + let response = await pair(token, undefined, undefined, commonName); + console.dir(response, { depth: null }); + console.log('Store the accountId for future uses'); + break; } - }); -} - -function example_get_status_operation() { - - operationStatus = latch.operationStatus(MY_ACCOUNTID, OPERATIONID, null, null, function (err, result) { - if (err) { - console.log(util.inspect(err, {showHidden: true, depth: null, colors: true})); - } else { - console.log(util.inspect(result, {showHidden: true, depth: null, colors: true})); + case '4': { + let accountId = await readInput('The account identifier, from the pairing process'); + let response = await unpair(accountId); + console.dir(response, { depth: null }); + break; } - }); -} - - -// LOCK STATUS -function example_lock() { - let response = latch.lock(MY_ACCOUNTID, null, function (err, result) { - if (err) { - console.log(util.inspect(err, {showHidden: true, depth: null, colors: true})); - } else { - console.log(util.inspect(result, {showHidden: true, depth: null, colors: true})); + case '5': { + let parentOperationId = await readInput('Parent operation id (application id if operation root)'); + let nameOperation = await readInput('Name operation'); + let twoFactor = await readTwoFactor(); + let lockOnRequest = await readLockOnRequest(); + let response = await createOperation(parentOperationId, nameOperation, twoFactor, lockOnRequest); + console.dir(response, { depth: null }); + console.log('Store the operationId for future uses'); + break; } - }); -} - -// UNLOCK STATUS -function example_unlock() { - let response = latch.unlock(MY_ACCOUNTID, null, function (err, result) { - if (err) { - console.log(util.inspect(err, {showHidden: true, depth: null, colors: true})); - } else { - console.log(util.inspect(result, {showHidden: true, depth: null, colors: true})); + case '6': { + let accountId = await readInput('The account identifier'); + let operationId = await readInput('The operation identifier'); + let response = await lock(accountId, operationId); + console.dir(response, { depth: null }); + break; } - }); -} - -// LOCK FOR OPERATION -function example_lock_for_operation() { - let response = latch.lock(MY_ACCOUNTID, OPERATIONID, function (err, result) { - if (err) { - console.log(util.inspect(err, {showHidden: true, depth: null, colors: true})); - } else { - console.log(util.inspect(result, {showHidden: true, depth: null, colors: true})); + case '7': { + let accountId = await readInput('The account identifier'); + let operationId = await readInput('The operation identifier'); + let response = await unlock(accountId, operationId); + console.dir(response, { depth: null }); + break; } - }); -} - -// UNLOCK FOR OPERATION -function example_unlock_for_operation() { - let response = latch.unlock(MY_ACCOUNTID, OPERATIONID, function (err, result) { - if (err) { - console.log(util.inspect(err, {showHidden: true, depth: null, colors: true})); - } else { - console.log(util.inspect(result, {showHidden: true, depth: null, colors: true})); + case '8': { + let accountId = await readInput('The account identifier'); + let operationId = await readInput('The operation identifier'); + let response = await status(accountId, operationId); + console.dir(response, { depth: null }); + checkStatus(operationId, response); + break; } - }); -} - -// UNPAIR -function example_unpair() { - let response = latch.unpair(MY_ACCOUNTID, function (err, result) { - if (err) { - console.log(util.inspect(err, {showHidden: true, depth: null, colors: true})); - } else { - console.log(util.inspect(result, {showHidden: true, depth: null, colors: true})); + case '9': { + let operationId = await readInput('The operation identifier root (empty for all)'); + let response = await getOperations(operationId); + console.dir(response, { depth: null }); + break; } - }); -} + case '10': { + let operationId = await readInput('The operation identifier'); + let response = await deleteOperation(operationId); + console.dir(response, { depth: null }); + break; + } + case '11': { + let userId = await readInput('User identifier'); + let commonName = await readInput('Name for the Totp'); + let response = await createTotp(userId, commonName); + console.dir(response, { depth: null }); + break; + } + case '12': { + let totpId = await readInput('Totp Identifier'); + let code = await readInput('Code generated'); + let response = await validateTotp(totpId, code); + console.dir(response, { depth: null }); + break; + } + case '13': { + let totpId = await readInput('Totp Identifier'); + let response = await deleteTotp(totpId); + console.dir(response, { depth: null }); + break; + } + case '14': { + let nameInstance = await readInput('Name for the instance'); + let accountId = await readInput('Account identifier'); + let operationId = await readInput('Id operation (Optional, blank if none)'); + let response = await createInstance(nameInstance,accountId,operationId) + console.dir(response, { depth: null }); + break; + } + case '15': { + let instanceId = await readInput('Instance identifier'); + let accountId = await readInput('Account identifier');14 + let operationId = await readInput('Id operation (Optional, blank if none)'); + let nameInstance = await readInput('Name for the instance'); + let response = await updateInstance(instanceId, accountId, operationId, nameInstance); + console.dir(response, { depth: null }); + break; + } + case '16': { + let accountId = await readInput('Account identifier'); + let operationId = await readInput('Id operation (Optional, blank if none)'); + let instanceId = await readInput('Instance identifier'); + let isSilent = await readInput('Silent operation (true/false)'); + let isNoOtp = await readInput('No Otp (true/false)'); + let response = await status(accountId, operationId, instanceId, isSilent, isNoOtp); + console.dir(response, { depth: null }); + checkStatus(instanceId, response); + break; + } + case '17': { + let accountId = await readInput('Account identifier'); + let operationId = await readInput('Id operation (Optional, blank if none)'); + let response = await getInstances(accountId, operationId); + console.dir(response, { depth: null }); + break; + } + case '18': { + let instanceId = await readInput('Instance identifier'); + let accountId = await readInput('Account identifier'); + let operationId = await readInput('Id operation (Optional, blank if none)'); + let response = await deleteInstance(instanceId, accountId, operationId); + console.dir(response, { depth: null }); + break; + } + default: + console.log("\nInvalid option. Please select a valid option"); + } + mainMenu(); +}; +const mainMenu = async () => { + let option = await readInput(menu); + handleOption(option); +}; -// TO RUN EXAMPLE -//example_pair_with_id_web3() -//example_get_status() -//example_get_status_operation() -//example_lock() -//example_lock_for_operation() -//example_unlock() -//example_unlock_for_operation() -//example_unpair() +console.log(warning); +init(CONFIG); +mainMenu(); \ No newline at end of file diff --git a/examples/example_basic.js b/examples/example_basic.js new file mode 100644 index 0000000..4a19b7e --- /dev/null +++ b/examples/example_basic.js @@ -0,0 +1,98 @@ +/* + * Latch NodeJS SDK Example + * Copyright (C) 2024 Telefonica Innovación Digital + + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +import { init, lock, pair, status, unlock, unpair } from '../index.js'; + +// To run an example just fill in the value of the constants and run the example. +const CONFIG = { + appId: '', + secretKey: '' +} + +const readInput = (message) => { + console.log(Array.isArray(message) ? message.join('\n') : message + ":"); + + return new Promise((resolve) => { + process.stdin.once('data', (data) => { + resolve(data.toString().trim()); + }); + }); +}; + +const showResponse = (msg, response) => { + console.log(msg); + console.log('##################'); + console.dir(response, { depth: null }); + console.log('##################'); +} + +const checkStatus = async (accountId, elementId) => { + let response = await status(accountId) + checkErrorResponse(response); + if (response.data.operations[elementId].status == 'on') { + console.log("Your latch is open and you are able to perform action"); + } else if (response.data.operations[elementId].status == 'off') { + console.log("Your latch is lock and you can not be allowed to perform action"); + } else { + console.log("Error processing the response"); + process.exit(1); + } +} + +const checkErrorResponse = (response) => { + if (response.error != null) { + console.error(`Error in PAIR request with error_code: ${response.error.code} and message: ${response.error.message}`); + process.exit(1) + } +} + +//Initialize configuration +init(CONFIG); + +//Pairing process +let token = await readInput('Generated pairing token from the user account'); +let commonName = await readInput('Do you want a alias for the pairing, it will be showed in admin panels like Latch Support Tool (L:ST). Optional, blank if none ') ?? null; + +let response = await pair(token, null, null, commonName); +showResponse('Response pair', response); +checkErrorResponse(response); + +console.log('Store the accountId for future uses'); +let accountId = response.data.accountId; + +//Check status account +//When the state is checked, it can be checked at different levels. Application, Operation or Instance +await checkStatus(accountId, CONFIG.appId) + + +//Lock the account +response = await lock(accountId) +checkErrorResponse(response); +//Lock and Unlock responses are empty if all is correct +await checkStatus(accountId, CONFIG.appId) + + +//Unlock the account +response = await unlock(accountId) +checkErrorResponse(response); +await checkStatus(accountId, CONFIG.appId) + + +//Unpairing process +response = unpair(accountId); +await checkStatus(accountId, CONFIG.appId) diff --git a/helper.js b/helper.js new file mode 100644 index 0000000..e18b102 --- /dev/null +++ b/helper.js @@ -0,0 +1,76 @@ +import crypto from 'crypto'; +import { config } from './config.js'; + +const signData = (data) => { + if (data) { + let hmac = crypto.createHmac('sha1', config.SECRET_KEY); + hmac.setEncoding('base64'); + hmac.write(data); + hmac.end(); + return hmac.read(); + } else { + return ''; + } +}; + +const dateFormat = (date, fstr, utc) => { + utc = utc ? 'getUTC' : 'get'; + return fstr.replace(/%[YmdHMS]/g, (m) => { + switch (m) { + case '%Y': return date[utc + 'FullYear'](); // no leading zeros required + case '%m': m = 1 + date[utc + 'Month'](); break; + case '%d': m = date[utc + 'Date'](); break; + case '%H': m = date[utc + 'Hours'](); break; + case '%M': m = date[utc + 'Minutes'](); break; + case '%S': m = date[utc + 'Seconds'](); break; + default: return m.slice(1); // unknown code, remove % + } + // add leading zero if required + return ('0' + m).slice(-2); + }); +}; + +const getSerialicedParameters = (params) => Object.entries(params).sort().map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join('&') + +const addParamOptional = (params, nameParam, param) => (param ?? '' !== '') ? { ...params, [nameParam]: param } : params; +const addUriOptionalOption = (uri, name, hasOption = true) => `${uri}${hasOption ? `/${name}` : ''}`; +const addUriOptionalParam = (uri, param, value) => `${uri}${(value ?? '' !== '') ? `/${param}/${value}` : ''}`; +const addUriOptionalPath = (uri, param) => `${uri}${(param ?? '' !== '') ? `/${param}` : ''}`; + +const http = async (HTTPMethod, queryString, params = {}, xHeaders = '', utc = '') => { + utc = utc || dateFormat(new Date(), config.UTC_STRING_FORMAT, true); + + let serialized_params = getSerialicedParameters(params); + let stringToSign = `${HTTPMethod.toUpperCase().trim()}\n${utc}\n${xHeaders}\n${queryString.trim()}${serialized_params ? '\n' + serialized_params : ''}`; + + let authorizationHeader = config.AUTHORIZATION_METHOD + + config.AUTHORIZATION_HEADER_FIELD_SEPARATOR + + config.APP_ID + + config.AUTHORIZATION_HEADER_FIELD_SEPARATOR + + signData(stringToSign); + + let headers = { + [config.AUTHORIZATION_HEADER_NAME]: authorizationHeader, + [config.DATE_HEADER_NAME]: utc, + [config.PLUGIN_HEADER_NAME]: "Nodejs" + }; + + let options = { + method: HTTPMethod, + headers: headers + } + + if (HTTPMethod == "POST" || HTTPMethod == "PUT") { + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + options.body = serialized_params + } + + + + let url = config.API_HOST.origin + queryString; + + const response = await fetch(url, options); + return (response.status != 204) ? await response.json() : null; +}; + +export { http, addParamOptional, addUriOptionalOption, addUriOptionalParam, addUriOptionalPath } \ No newline at end of file diff --git a/index.js b/index.js index 5cc9ed8..0f19388 100644 --- a/index.js +++ b/index.js @@ -17,531 +17,354 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -var crypto = require('crypto'); -var https = require('https'); -var http = require('http'); -var url = require('url'); -var config = require('./config'); - -var latch = { - /** - * Callback `next` - * - * @callback next - * @param {Object} error Message if error - * @param {Object} data Message if ok - */ - - - /** - * Options initialize - * - * @typedef {Object} Options - * @property {string} appId Id application - * @property {string} secretKey Secret key - * @property {string} [hostname] URI Latch service - */ - - /** - * @typedef {string} TwoFactor - **/ - - /** - * Enum for Two factor values. - * @readonly - * @enum {TwoFactor} - */ - TWO_FACTOR : { - DISABLED: 'DISABLED', - OPT_IN: 'OPT_IN', - MANDATORY: 'MANDATORY' - }, - - /** - * @typedef {string} LockOnRequest - **/ - - /** - * Enum for Lock on request values. - * @readonly - * @enum {LockOnRequest} - */ - LOCK_ON_REQUEST: { - DISABLED: 'DISABLED', - OPT_IN: 'OPT_IN', - MANDATORY: 'MANDATORY' - }, - - /** - * Configure the Application ID and secret obtained from latch.tu.com - * @param {Options} options - */ - init: function (options) { - if (!('appId' in options) || (!('secretKey' in options))) { - throw (new Error('You need to specify both the appId and secretKey')); - } - - if ((options.appId.length != 20) || (options.secretKey.length != 40)) { - throw (new Error('Please check your appId and secretKey, they seem to be wrong')); - } - - config.appId = options.appId; - config.secretKey = options.secretKey; - if ('hostname' in options) { - config.API_HOST = url.parse(options.hostname); - } else { - config.API_HOST = url.parse(config.API_HOST); - } - }, - - /** - * Pairs the origin provider with a user account (mail). - * @param {string} id The user identified (email) - * @param {next} next The callback that handles the response - * @param {string} [web3Wallet] Wallet identificador - * @param {string} [web3Signature] Wallet signature - */ - pairWithId: function (accountId, next, web3Wallet, web3Signature) { - if (typeof web3Wallet === 'undefined' || typeof web3Signature === 'undefined') { - _http("GET", config.API_PAIR_WITH_ID_URL + "/" + accountId, '', '', '', next); - } - else { - let params = { wallet: web3Wallet, signature: web3Signature }; - _http("POST", config.API_PAIR_WITH_ID_URL + "/" + accountId, params, '', '', next); - } - }, - - /** - * Pairs the token provider with a user account. - * - * @param {string} token - * @param {next} next The callback that handles the response - * @param {string} [web3Wallet] Wallet identificador - * @param {string} [web3Signature] Wallet signature - */ - pair: function (token, next, web3Wallet, web3Signature) { - if (typeof web3Wallet === 'undefined' || typeof web3Signature === 'undefined') { - _http("GET", config.API_PAIR_URL + "/" + token, '', '', '', next); - } - else { - let params = { wallet: web3Wallet, signature: web3Signature }; - _http("POST", config.API_PAIR_URL + "/" + token, params, '', '', next); - } - }, - - /** - * Return application status for a given accountId - * @param {string} accountId The accountId which status is going to be retrieved - * @param {string} [silent=''] Something for not sending lock/unlock push notifications to the mobile devices, empty string otherwise - * @param {string} [nootp=''] Something for not generating a OTP if needed - * @param {next} next The callback that handles the response - */ - status: function (accountId, silent, nootp, next) { - var url = config.API_CHECK_STATUS_URL + "/" + accountId; - if (nootp != null) { - url += '/nootp' - } - if (silent != null) { - url += '/silent'; - } - _http("GET", url, '', '', '', next); - }, - - /** - * Return operation status for a given accountId - * @param {string} accountId The user identified (email) - * @param {string} operationId The operation identifier - * @param {string} [silent=''] Something for not sending lock/unlock push notifications to the mobile devices, empty string otherwise - * @param {string} [nootp=''] Something for not generating a OTP if needed - * @param {next} next The callback that handles the response - */ - operationStatus: function (accountId, operationId, silent, nootp, next) { - var url = config.API_CHECK_STATUS_URL + "/" + accountId + "/op/" + operationId; - if (nootp != null) { - url += '/nootp' - } - if (silent != null) { - url += '/silent'; - } - _http("GET", url, '', '', '', next); - }, - - /** - * Unpairs the origin provider with a user account. - * @param {string} accountId The account identified - * @param {next} next The callback that handles the response - */ - unpair: function (accountId, next) { - _http("GET", config.API_UNPAIR_URL + "/" + accountId, '', '', '', next); - }, - - /** - * Locks the operation - * @param {string} accountId The user identified (email) - * @param {string} operationId The operation identifier - * @param {next} next The callback that handles the response - */ - lock: function (accountId, operationId, next) { - var url = config.API_LOCK_URL + "/" + accountId; - if (operationId != null) { - url += "/op/" + operationId - } - _http("POST", url, '', '', '', next); - }, - - /** - * Unlocks the operation - * @param {string} accountId The user identified (email) - * @param {string} operationId The operation identifier - * @param {next} next The callback that handles the response - */ - unlock: function (accountId, operationId, next) { - var url = config.API_UNLOCK_URL + "/" + accountId; - if (operationId != null) { - url += "/op/" + operationId - } - _http("POST", url, '', '', '', next); - }, - - /** - * Get history status - * @param {string} accountId The user identified (email) - * @param {number} [fromTime] From in epoch format - * @param {number} [toTime] To in epoch format - * @param {next} next The callback that handles the response - */ - history: function (accountId, fromTime, toTime, next) { - if (toTime == '') { - toTime = int(round(time.time() * 1000)) - } - _http("GET", config.API_HISTORY_URL + "/" + accountId + "/" + String(fromTime) + "/" + String(toTime), '', '', '', next); - }, - - /** - * Add a new operation - * @param {string} parentId identifies the parent of the operation to be created - * @param {string} name The name of the operation - * @param {TwoFactor} [twoFactor] Specifies if the Two Factor protection is enabled for this operation - * @param {LockOnRequest} [lockOnRequest] Specifies if the 'Lock latches on status request' feature is disabled, opt-in or mandatory for this operation - * @param {next} next The callback that handles the response - */ - createOperation: function (parentId, name, twoFactor, lockOnRequest, next) { - var params = { parentId: parentId, name: name }; - if (twoFactor != null) { - params.two_factor = twoFactor - } - if (lockOnRequest != null) { - params.lock_on_request = lockOnRequest - } - _http("PUT", config.API_OPERATION_URL, params, '', '', next); - }, - - /** - * - * @param {string} operationId The operation identifier - * @param {string} name The name of the operation - * @param {TwoFactor} [twoFactor] Specifies if the Two Factor protection is enabled for this operation - * @param {LockOnRequest} [lockOnRequest] Specifies if the 'Lock latches on status request' feature is disabled, opt-in or mandatory for this operation - * @param {next} next The callback that handles the response - */ - updateOperation: function (operationId, name, twoFactor, lockOnRequest, next) { - var params = { name: name }; - if (twoFactor != null) { - params.two_factor = twoFactor - } - if (lockOnRequest != null) { - params.lock_on_request = lockOnRequest - } - _http("POST", config.API_OPERATION_URL + "/" + operationId, params, '', '', next); - }, - - /** - * Remove a operation - * @param {string} operationId The operation identifier - * @param {next} next The callback that handles the response - */ - deleteOperation: function (operationId, next) { - _http("DELETE", config.API_OPERATION_URL + "/" + operationId, '', '', '', next); - }, - - /** - * Get information about the operation - * @param {string} operationId The operation identifier - * @param {next} next The callback that handles the response - */ - getOperations: function (operationId, next) { - var url = config.API_OPERATION_URL; - if (operationId != null) { - url += "/" + operationId; - } - _http("GET", url, '', '', '', next); - }, - - /** - * Retrieve instances for a given alias - * @param {string} accountId The user identified (email) - * @param {string} [operationId] The operation identifier(Optional) If retrieving instances for an operation instead of application - * @param {next} next The callback that handles the response - */ - getInstances: function (accountId, operationId, next) { - var url = config.API_INSTANCE_URL + "/" + accountId; - if (operationId != null) { - url += "/op/" + operationId; - } - _http("GET", url, '', '', '', next); - }, - - /** - * Retrieve status for a given instance from a alias - * @param {string} instanceId The instance identifier - * @param {string} accountId The user identified (email) - * @param {string} [operationId] The operation identifier - * @param {string} [silent=''] Something for not sending lock/unlock push notifications to the mobile devices, empty string otherwise - * @param {string} [nootp=''] Something for not generating a OTP if needed - * @param {next} next The callback that handles the response - */ - instanceStatus: function (instanceId, accountId, operationId, silent, nootp, next) { - var url = config.API_CHECK_STATUS_URL + "/" + accountId; - if (operationId != null) { - url += "/op/" + operationId; - } - url += "/i/" + instanceId; - if (nootp != null) { - url += '/nootp' - } - if (silent != null) { - url += '/silent'; - } - _http("GET", url, '', '', '', next); - }, - - /** - * Create a instance - * @param {string} name The name for the instace - * @param {string} accountId The user identified (email) - * @param {string} [operationId] The operation identifier - * @param {next} next The callback that handles the response - */ - createInstance: function (name, accountId, operationId, next) { - var params = { instances: name }; - var url = config.API_INSTANCE_URL + "/" + accountId; - if (operationId != null) { - url += "/op/" + operationId; - } - _http("PUT", url, params, '', '', next); - }, - - /** - * Update the instance - * @param {string} instanceId The instance identifier - * @param {string} accountId The user identified (email) - * @param {string} [operationId] The operation identifier - * @param {string} name - * @param {TwoFactor} [twoFactor] Specifies if the Two Factor protection is enabled for this application - * @param {LockOnRequest} [lockOnRequest] Specifies if the 'Lock latches on status request' feature is disabled, opt-in or mandatory for this operation - * @param {next} next The callback that handles the response - */ - updateInstance: function (instanceId, accountId, operationId, name, twoFactor, lockOnRequest, next) { - var params = { name: name }; - if (twoFactor != null) { - params.two_factor = twoFactor - } - if (lockOnRequest != null) { - params.lock_on_request = lockOnRequest - } - var url = config.API_INSTANCE_URL + "/" + accountId; - if (operationId != null) { - url += "/op/" + operationId; - } - url += "/i/" + instanceId; - _http("POST", url, params, '', '', next); - }, - - /** - * Remove the instance - * @param {string} instanceId The instance identifier - * @param {string} accountId The user identified (email) - * @param {string} [operationId] The operation identifier - * @param {next} next The callback that handles the response - */ - deleteInstance: function (instanceId, accountId, operationId, next) { - var url = config.API_INSTANCE_URL + "/" + accountId; - if (operationId != null) { - url += "/op/" + operationId; - } - url += "/i/" + instanceId; - _http("DELETE", url, '', '', '', next); - }, - - /** - * Create a Time-based one-time password - * @param {string} id User identifier (mail) - * @param {string} name Name for the Totp - * @param {next} next The callback that handles the response - */ - createTotp: function (userId, commonName, next) { - let params = { userId, commonName }; - let url = config.API_TOTP_URL; - _http("POST", url, params, '', '', next); - }, - - /** - * Get data information about the totp - * @param {string} totpId Totp Identifier - * @param {next} next The callback that handles the response - */ - getTotp: function (totpId, next) { - let url = `${config.API_TOTP_URL}/${totpId}` - _http("GET", url, '', '', '', next); - }, - - /** - * Validate a code from a totp - * @param {string} totpId Totp Identifier - * @param {string} code Code generated - * @param {next} next The callback that handles the response - */ - validateTotp: function (totpId, code, next) { - let params = { code }; - let url = `${config.API_TOTP_URL}/${totpId}/validate` - _http("POST", url, params, '', '', next); - }, - - /** - * Remove a totp - * @param {string} totpId Totp Identifier - * @param {next} next The callback that handles the response - */ - deleteTotp: function (totpId, next) { - let url = `${config.API_TOTP_URL}/${totpId}` - _http("DELETE", url, '', '', '', next); - }, - - /** - * Check operation status - * @param {string} controlId - * @param {next} next The callback that handles the response - */ - checkControlStatus: function (controlId, next) { - let url = `${config.API_CONTROL_STATUS_CHECK_URL}/${controlId}` - _http("GET", url, '', '', '', next); +import { config } from './config.js'; +import { http, addParamOptional, addUriOptionalOption, addUriOptionalParam, addUriOptionalPath } from './helper.js'; + +/** + * `LatchError` + * + * @typedef {Object} LatchError + * @property {Object} code A numeric value used to identify the error + * @property {Object} message Description of the type of error + */ + +/** + * Promise `LatchResponse` + * + * @typedef {Object} LatchResponse + * @property {LatchError} error Message if error + * @property {Object} data Message if ok + */ + + +/** + * Options initialize + * + * @typedef {Object} Options + * @property {string} appId Id application + * @property {string} secretKey Secret key + * @property {string} [hostname] URI Latch service + */ + +/** + * @typedef {string} TwoFactor + **/ + +/** + * Enum for Two factor values. + * @readonly + * @enum {TwoFactor} + */ +export const TWO_FACTOR = { + DISABLED: 'DISABLED', + OPT_IN: 'OPT_IN', + MANDATORY: 'MANDATORY' +} + +/** + * @typedef {string} LockOnRequest + **/ + +/** + * Enum for Lock on request values. + * @readonly + * @enum {LockOnRequest} + */ +export const LOCK_ON_REQUEST = { + DISABLED: 'DISABLED', + OPT_IN: 'OPT_IN', + MANDATORY: 'MANDATORY' +} + +/** + * Configure the Application ID and secret obtained from latch.tu.com + * @param {Options} options + */ +export const init = (options) => { + if (!('appId' in options) || (!('secretKey' in options))) { + throw (new Error('You need to specify both the appId and secretKey')); } -}; -module.exports = latch; + if ((options.appId.length != 20) || (options.secretKey.length != 40)) { + throw (new Error('Please check your appId and secretKey, they seem to be wrong')); + } -var signData = function (data) { - if (data) { - var hmac = crypto.createHmac('sha1', config.secretKey); - hmac.setEncoding('base64'); - hmac.write(data); - hmac.end(); - return hmac.read(); + config.APP_ID = options.appId; + config.SECRET_KEY = options.secretKey; + if ('hostname' in options) { + config.API_HOST = new URL(options.hostname); } else { - return ''; + config.API_HOST = new URL(config.API_HOST); } -}; - -var dateFormat = function (date, fstr, utc) { - utc = utc ? 'getUTC' : 'get'; - return fstr.replace(/%[YmdHMS]/g, function (m) { - switch (m) { - case '%Y': return date[utc + 'FullYear'](); // no leading zeros required - case '%m': m = 1 + date[utc + 'Month'](); break; - case '%d': m = date[utc + 'Date'](); break; - case '%H': m = date[utc + 'Hours'](); break; - case '%M': m = date[utc + 'Minutes'](); break; - case '%S': m = date[utc + 'Seconds'](); break; - default: return m.slice(1); // unknown code, remove % - } - // add leading zero if required - return ('0' + m).slice(-2); - }); -}; - -var _http = function (HTTPMethod, queryString, params, xHeaders, utc, next) { - xHeaders = xHeaders || ''; - utc = utc || dateFormat(new Date(), config.UTC_STRING_FORMAT, true); - - var stringToSign = (HTTPMethod.toUpperCase().trim() + "\n" + - utc + "\n" + - xHeaders + "\n" + - queryString.trim()); - - if (params != '') { - var serialized_params = ""; - - var sortable = []; - for (var key in params) { - sortable.push([key, params[key]]); - } - sortable.sort(); - - for (var key in sortable) { - if (serialized_params != "") { - serialized_params += "&"; - } - serialized_params += sortable[key][0] + "=" + encodeURIComponent(sortable[key][1]); - } - - stringToSign += "\n" + serialized_params; +} + +/** + * Pairs the origin provider with a user account (mail). + * @param {string} id The user identified (email) + * @param {string} [web3Wallet] Wallet identificador + * @param {string} [web3Signature] Wallet signature + * @param {string} [commonName] Name attached to this pairing. Showed in admin panels. + * @returns {Promise} A promise with the response + */ +export const pairWithId = (id, web3Wallet, web3Signature, commonName) => { + let method = 'GET'; + let params = {}; + if ((web3Wallet ?? '' !== '') && (web3Signature ?? '' !== '')) { + method = 'POST'; + params = { ...params, wallet: web3Wallet, signature: web3Signature }; } - - var authorizationHeader = config.AUTHORIZATION_METHOD + config.AUTHORIZATION_HEADER_FIELD_SEPARATOR + - config.appId + config.AUTHORIZATION_HEADER_FIELD_SEPARATOR + signData(stringToSign); - - var headers = {}; - headers[config.AUTHORIZATION_HEADER_NAME] = authorizationHeader; - headers[config.DATE_HEADER_NAME] = utc; - headers[config.PLUGIN_HEADER_NAME] = "Nodejs"; - - if (HTTPMethod == "POST" || HTTPMethod == "PUT") - headers['Content-Type'] = 'application/x-www-form-urlencoded'; - - var options = { - 'hostname': config.API_HOST.hostname, - 'port': config.API_HOST.port, - 'path': queryString, - 'method': HTTPMethod, - 'headers': headers, - 'protocol': config.API_HOST.protocol - }; - - var latchResponse = ''; - - var req = (options.protocol == 'http:' ? http : https).request(options, function (res) { - res.setEncoding('utf8'); - res.on('data', function (chunk) { - latchResponse += chunk; - }); - res.on('end', function () { - var jsonresponse; - - try { - jsonresponse = JSON.parse(latchResponse); - } catch (e) { - next(new Error('problem with JSON parse: ' + e.message)); - } - - next(null, jsonresponse); - }); - - if (res.statusCode === 204) { - next(null, {}); - } - }); - - req.on('error', function (e) { - next(new Error('problem with request: ' + e.message)); - }); - - // post the data - if (HTTPMethod == "POST" || HTTPMethod == "PUT") { - if (params != '') { - req.write(serialized_params); - } + if (commonName ?? '' !== '') { + method = 'POST'; + params = { ...params, commonName }; } + return http(method, `${config.API_PAIR_WITH_ID_URL}/${id}`, params); +} + +/** + * Pairs the token provider with a user account. + * + * @param {string} token + * @param {string} [web3Wallet] Wallet identificador + * @param {string} [web3Signature] Wallet signature + * @param {string} [commonName] Name attached to this pairing. Showed in admin panels. + * @returns {Promise} A promise with the response + */ +export const pair = (token, web3Wallet, web3Signature, commonName) => { + let method = 'GET'; + let params = {}; + if ((web3Wallet ?? '' !== '') && (web3Signature ?? '' !== '')) { + method = 'POST'; + params = { ...params, wallet: web3Wallet, signature: web3Signature }; + } + if (commonName ?? '' !== '') { + method = 'POST'; + params = { ...params, commonName }; + } + return http(method, `${config.API_PAIR_URL}/${token}`, params); +} - - req.end(); -}; \ No newline at end of file +/** + * Unpairs the origin provider with a user account. + * @param {string} accountId The account identified + * @returns {Promise} A promise with the response + */ +export const unpair = (accountId) => { + return http('GET', `${config.API_UNPAIR_URL}/${accountId}`); +} + +/** + * Locks the operation + * @param {string} accountId The account identifier + * @param {string} operationId The operation identifier + * @returns {Promise} A promise with the response + */ +export const lock = (accountId, operationId) => { + var url = `${config.API_LOCK_URL}/${accountId}`; + url = addUriOptionalParam(url, 'op', operationId); + return http('POST', url); +} + +/** + * Unlocks the operation + * @param {string} accountId The account identifier + * @param {string} operationId The operation identifier + * @returns {Promise} A promise with the response + */ +export const unlock = (accountId, operationId) => { + var url = `${config.API_UNLOCK_URL}/${accountId}`; + url = addUriOptionalParam(url, 'op', operationId); + return http('POST', url); +} + +/** + * Get history status + * @param {string} accountId The account identifier + * @param {number} [fromTime] From in epoch format + * @param {number} [toTime] To in epoch format + * @returns {Promise} A promise with the response + */ +export const history = (accountId, fromTime = 0, toTime = new Date().getTime()) => { + return http('GET', `${config.API_HISTORY_URL}/${accountId}/${String(fromTime)}/${String(toTime)}`); +} + +/** + * Add a new operation + * @param {string} parentId identifies the parent of the operation to be created + * @param {string} name The name of the operation + * @param {TwoFactor} [twoFactor] Specifies if the Two Factor protection is enabled for this operation + * @param {LockOnRequest} [lockOnRequest] Specifies if the 'Lock latches on status request' feature is disabled, opt-in or mandatory for this operation + * @returns {Promise} A promise with the response + */ +export const createOperation = (parentId, name, twoFactor, lockOnRequest) => { + var params = { parentId, name }; + params = addParamOptional(params, 'two_factor', twoFactor); + params = addParamOptional(params, 'lock_on_request', lockOnRequest); + return http("PUT", config.API_OPERATION_URL, params); +} + +/** + * Update an operation + * @param {string} operationId The operation identifier + * @param {string} name The name of the operation + * @param {TwoFactor} [twoFactor] Specifies if the Two Factor protection is enabled for this operation + * @param {LockOnRequest} [lockOnRequest] Specifies if the 'Lock latches on status request' feature is disabled, opt-in or mandatory for this operation + * @returns {Promise} A promise with the response + */ +export const updateOperation = (operationId, name, twoFactor, lockOnRequest) => { + var params = { name: name }; + params = addParamOptional(params, 'two_factor', twoFactor); + params = addParamOptional(params, 'lock_on_request', lockOnRequest); + return http('POST', `${config.API_OPERATION_URL}/${operationId}`, params); +} + +/** + * Remove an operation + * @param {string} operationId The operation identifier + * @returns {Promise} A promise with the response + */ +export const deleteOperation = (operationId) => { + return http("DELETE", `${config.API_OPERATION_URL}/${operationId}`); +} + +/** + * Get information about the operation + * @param {string} operationId The operation identifier + * @returns {Promise} A promise with the response + */ +export const getOperations = (operationId) => { + var url = config.API_OPERATION_URL; + url = addUriOptionalPath(url, operationId); + return http('GET', url); +} + +/** + * Retrieve instances for a given alias + * @param {string} accountId The account identifier + * @param {string} [operationId] The operation identifier(Optional) If retrieving instances for an operation instead of application + * @returns {Promise} A promise with the response + */ +export const getInstances = (accountId, operationId) => { + var url = `${config.API_INSTANCE_URL}/${accountId}`; + url = addUriOptionalParam(url, 'op', operationId); + return http('GET', url); +} + +/** + * Retrieve status for a given account/operation/instance + * @param {string} accountId The account identifier + * @param {string} [operationId] The operation identifier + * @param {string} [instanceId] The instance identifier + * @param {boolean} [silent=false] True for not sending lock/unlock push notifications to the mobile devices, false otherwise + * @param {boolean} [nootp=false] True for not generating a OTP if needed + * @returns {Promise} A promise with the response + */ +export const status = (accountId, operationId, instanceId, silent = false, nootp = false) => { + var url = `${config.API_CHECK_STATUS_URL}/${accountId}`; + url = addUriOptionalParam(url, 'op', operationId); + url = addUriOptionalParam(url, 'i', instanceId); + url = addUriOptionalOption(url, 'nootp', nootp); + url = addUriOptionalOption(url, 'silent', silent); + return http('GET', url); +} + +/** + * Create a instance + * @param {string} name The name for the instace + * @param {string} accountId The account identifier + * @param {string} [operationId] The operation identifier + * @returns {Promise} A promise with the response + */ +export const createInstance = (name, accountId, operationId) => { + var params = { instances: name }; + var url = `${config.API_INSTANCE_URL}/${accountId}`; + url = addUriOptionalParam(url, 'op', operationId); + return http("PUT", url, params); +} + +/** + * Update the instance + * @param {string} instanceId The instance identifier + * @param {string} accountId The account identifier + * @param {string} [operationId] The operation identifier + * @param {string} name + * @param {TwoFactor} [twoFactor] Specifies if the Two Factor protection is enabled for this application + * @param {LockOnRequest} [lockOnRequest] Specifies if the 'Lock latches on status request' feature is disabled, opt-in or mandatory for this operation + * @returns {Promise} A promise with the response + */ +export const updateInstance = (instanceId, accountId, operationId, name, twoFactor, lockOnRequest) => { + var params = { name }; + params = addParamOptional(params, 'two_factor', twoFactor); + params = addParamOptional(params, 'lock_on_request', lockOnRequest); + var url = `${config.API_INSTANCE_URL}/${accountId}`; + url = addUriOptionalParam(url, 'op', operationId); + url = addUriOptionalParam(url, 'i', instanceId); + return http('POST', url, params); +} + +/** + * Remove the instance + * @param {string} instanceId The instance identifier + * @param {string} accountId The account identifier + * @param {string} [operationId] The operation identifier + * @returns {Promise} A promise with the response + */ +export const deleteInstance = (instanceId, accountId, operationId) => { + var url = config.API_INSTANCE_URL + "/" + accountId; + url = addUriOptionalParam(url, 'op', operationId); + url = addUriOptionalParam(url, 'i', instanceId); + return http("DELETE", url); +} + +/** + * Create a Time-based one-time password + * @param {string} id User identifier (mail) + * @param {string} name Name for the Totp + * @returns {Promise} A promise with the response + */ +export const createTotp = (id, name) => { + let params = { userId: id, commonName: name }; + let url = config.API_TOTP_URL; + return http('POST', url, params); +} + +/** + * Get data information about the totp + * @param {string} totpId Totp Identifier + * @returns {Promise} A promise with the response + */ +export const getTotp = (totpId) => { + let url = `${config.API_TOTP_URL}/${totpId}` + return http('GET', url); +} + +/** + * Validate a code from a totp + * @param {string} totpId Totp Identifier + * @param {string} code Code generated + * @returns {Promise} A promise with the response + */ +export const validateTotp = (totpId, code) => { + let params = { code }; + let url = `${config.API_TOTP_URL}/${totpId}/validate` + return http('POST', url, params); +} + +/** + * Remove a totp + * @param {string} totpId Totp Identifier + * @returns {Promise} A promise with the response + */ +export const deleteTotp = (totpId) => { + let url = `${config.API_TOTP_URL}/${totpId}` + return http("DELETE", url); +} + +/** + * Check operation status + * @param {string} controlId + * @returns {Promise} A promise with the response + */ +export const checkControlStatus = (controlId) => { + let url = `${config.API_CONTROL_STATUS_CHECK_URL}/${controlId}` + return http('GET', url); +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7ad519e..e398ac1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,255 @@ "name": "latch-sdk", "version": "0.3.0", "devDependencies": { - "chai": "^5.1.1", - "mocha": "^10.7.3" + "@eslint/js": "^9.14.0", + "chai": "^5.1.2", + "eslint": "^9.14.0", + "eslint-plugin-mocha": "^10.5.0", + "globals": "^15.12.0", + "mocha": "^10.8.2", + "typescript": "^5.6.3" }, "engines": { "node": ">= 0.10.0" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", + "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.14.0.tgz", + "integrity": "sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.2.tgz", + "integrity": "sha512-CXtq5nR4Su+2I47WPOlWud98Y5Lv8Kyxp2ukhgFx/eW6Blm18VXJO5WuQylPugRo8nbluoi6GvvxBLqHcvqUUw==", + "dev": true, + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -95,12 +337,13 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/braces": { @@ -121,6 +364,15 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -134,9 +386,9 @@ } }, "node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", "dev": true, "dependencies": { "assertion-error": "^2.0.1", @@ -165,18 +417,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/check-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", @@ -210,6 +450,18 @@ "fsevents": "~2.3.2" } }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -239,6 +491,26 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", + "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -277,6 +549,12 @@ "node": ">=6" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "node_modules/diff": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", @@ -313,6 +591,242 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.14.0.tgz", + "integrity": "sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.18.0", + "@eslint/core": "^0.7.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.14.0", + "@eslint/plugin-kit": "^0.2.0", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.0", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-mocha": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.5.0.tgz", + "integrity": "sha512-F2ALmQVPT1GoP27O1JTZGrV9Pqg8k79OeIuvw63UxMtQKREZtmkK1NFgkZQ2TW7L2JSSFKHFPTtHu5z8R9QNRw==", + "dev": true, + "dependencies": { + "eslint-utils": "^3.0.0", + "globals": "^13.24.0", + "rambda": "^7.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-mocha/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -350,6 +864,25 @@ "flat": "cli.js" } }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -400,15 +933,48 @@ } }, "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globals": { + "version": "15.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz", + "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/has-flag": { @@ -429,6 +995,40 @@ "he": "bin/he" } }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -518,6 +1118,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -530,6 +1136,46 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -545,6 +1191,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -568,21 +1220,21 @@ "dev": true }, "node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10" + "node": "*" } }, "node_modules/mocha": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", - "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", "dev": true, "dependencies": { "ansi-colors": "^4.1.3", @@ -614,12 +1266,54 @@ "node": ">= 14.0.0" } }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -638,6 +1332,23 @@ "wrappy": "1" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -668,6 +1379,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -677,6 +1400,15 @@ "node": ">=8" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/pathval": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", @@ -698,6 +1430,30 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/rambda": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.5.0.tgz", + "integrity": "sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==", + "dev": true + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -728,6 +1484,15 @@ "node": ">=0.10.0" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -757,6 +1522,27 @@ "randombytes": "^2.1.0" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -796,20 +1582,23 @@ } }, "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { "has-flag": "^4.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": ">=8" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -822,6 +1611,76 @@ "node": ">=8.0" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/workerpool": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", diff --git a/package.json b/package.json index 9cbf837..9b19be6 100644 --- a/package.json +++ b/package.json @@ -27,10 +27,15 @@ } ], "main": "index.js", - "type": "commonjs", + "type": "module", "devDependencies": { - "chai": "^5.1.1", - "mocha": "^10.7.3" + "@eslint/js": "^9.14.0", + "chai": "^5.1.2", + "eslint": "^9.14.0", + "eslint-plugin-mocha": "^10.5.0", + "globals": "^15.12.0", + "mocha": "^10.8.2", + "typescript": "^5.6.3" }, "scripts": { "test": "mocha" diff --git a/test/latchSpec.mjs b/test/latchSpec.mjs index edb45da..c4fb0a9 100644 --- a/test/latchSpec.mjs +++ b/test/latchSpec.mjs @@ -18,8 +18,9 @@ */ import { expect } from "chai"; -import config from "../config.js"; -import latch from "../index.js"; +import { config } from "../config.js"; +import { createOperation, createTotp, deleteOperation, deleteTotp, getOperations, init, lock, LOCK_ON_REQUEST, operationStatus, pair, pairWithId, TWO_FACTOR, unlock, unpair, validateTotp, status, history, updateOperation, createInstance, deleteInstance, getInstances, instanceStatus, updateInstance, getTotp, checkControlStatus } from '../index.js'; +import { describe, it, before } from "mocha"; const NAME_OPERATION = 'OPERATION-TEST-VALIDATION' const NAME_INSTANCE = 'INSTANCE-TEST-VALIDATION' @@ -36,267 +37,198 @@ if (appId == '' || secretKey == '' || email == '' || code == '') throw new Error let appValidConfig = { appId, secretKey }; -describe("latch", function () { - describe("#latch.init()", function () { - it("should stop if the appId is missing", function () { - var args = { secretKey: '1234' } +describe("Latch", function () { + describe("Set app config", function () { + it("should stop if the appId is missing", async function () { + let appConfig = { secretKey: '1234' } - expect(latch.init.bind(latch, args)).to.throw(Error); - expect(latch.init.bind(latch, args)).to.throw(/specify both the appId and secretKey/); + expect(() => init(appConfig)).to.throw(Error); + expect(() => init(appConfig)).to.throw(/specify both the appId and secretKey/); }); - it("should stop if the secretKey is missing", function () { - var args = { appId: '1234' } + it("should stop if the secretKey is missing", async function () { + let appConfig = { appId: '1234' } - expect(latch.init.bind(latch, args)).to.throw(Error); - expect(latch.init.bind(latch, args)).to.throw(/specify both the appId and secretKey/); + expect(() => init(appConfig)).to.throw(Error); + expect(() => init(appConfig)).to.throw(/specify both the appId and secretKey/); }); - it("should stop if the appId is not 20 chars", function () { - var args = { appId: '1234', secretKey: '1234567890123456789012345678901234567890' } + it("should stop if the appId is not 20 chars", async function () { + let appConfig = { appId: '1234', secretKey: '1234567890123456789012345678901234567890' } - expect(latch.init.bind(latch, args)).to.throw(Error); - expect(latch.init.bind(latch, args)).to.throw(/check your appId and secretKey/); + expect(() => init(appConfig)).to.throw(Error); + expect(() => init(appConfig)).to.throw(/check your appId and secretKey/); }); - it("should stop if the secretKey is not 40 chars", function () { - var args = { appId: '12345678901234567890', secretKey: '1234' } + it("should stop if the secretKey is not 40 chars", async function () { + let appConfig = { appId: '12345678901234567890', secretKey: '1234' } - expect(latch.init.bind(latch, args)).to.throw(Error); - expect(latch.init.bind(latch, args)).to.throw(/check your appId and secretKey/); + expect(() => init(appConfig)).to.throw(Error); + expect(() => init(appConfig)).to.throw(/check your appId and secretKey/); }); - it("should parse the hostname into an URI object", function () { - var args = { appId: '12345678901234567890', secretKey: '1234567890123456789012345678901234567890', hostname: 'https://latch.tu.com' } + it("should parse the hostname into an URI object", async function () { + let appConfig = { appId: '12345678901234567890', secretKey: '1234567890123456789012345678901234567890', hostname: 'https://latch.tu.com' } - latch.init(args); + init(appConfig); expect(config.API_HOST.protocol).to.equal('https:'); expect(config.API_HOST.hostname).to.equal('latch.tu.com'); }); - it("should receive an API error", function (done) { - var args = { appId: '12345678901234567890', secretKey: '1234567890123456789012345678901234567890' } + it("should receive an API error", async function () { + let appConfig = { appId: '12345678901234567890', secretKey: '1234567890123456789012345678901234567890' }; + init(appConfig); + let response = await status('1234'); + expect(response).to.have.a.property('error').that.is.an('object'); + expect(response.error).to.have.a.property('code', 102); + expect(response.error).to.have.a.property('message', 'Invalid application signature'); + }); + }); + + describe("Latch API requests with valid config", function () { + before(function() { + init(appValidConfig); + }) + + let accountId; + let operationId; + let instanceId; + let totpId; + + it("check pair", async function () { + let responsePair = await pair(code); + expect(responsePair).to.have.a.property('data').that.is.an('object'); + expect(responsePair.data).to.have.a.property('accountId').that.is.an('string'); + accountId = responsePair.data.accountId; + }); + + it("check create operation", async function () { + let responseCreateOperation = await createOperation(appId, NAME_OPERATION, TWO_FACTOR.DISABLED, LOCK_ON_REQUEST.DISABLED); + expect(responseCreateOperation).to.have.a.property('data').that.is.an('object'); + expect(responseCreateOperation.data).to.have.a.property('operationId').that.is.an('string'); + operationId = responseCreateOperation.data.operationId; + }); + + it("check status", async function () { + let response = await status(accountId); + expect(response).to.have.a.property('data').that.is.an('object'); + expect(response.data).to.have.a.property('operations').that.is.an('object'); + expect(response.data.operations).to.have.a.property(appId).that.is.an('object'); + expect(response.data.operations[appId]).to.have.a.property('status').that.is.an('string'); + }); + + it("check lock", async function () { + let response = await lock(accountId); + expect(response).to.be.an('object').that.is.empty; + }); + + it("check unlock", async function () { + let response = await unlock(accountId); + expect(response).to.be.an('object').that.is.empty; + }); + + it("check history", async function () { + let response = await history(accountId); + expect(response).to.have.a.property('data').that.is.an('object'); + expect(response.data).to.have.a.property('history').that.is.an('array'); + }); + + it("check operationStatus", async function () { + let response = await operationStatus(accountId, appId); + expect(response).to.have.a.property('data').that.is.an('object'); + expect(response.data).to.have.a.property('operations').that.is.an('object'); + expect(response.data.operations).to.have.a.property(appId).that.is.an('object'); + expect(response.data.operations[appId]).to.have.a.property('status').that.is.an('string'); + }); + + it("check getOperations", async function () { + let response = await getOperations(appId); + expect(response).to.have.a.property('data').that.is.an('object'); + expect(response.data).to.have.a.property('operations').that.is.an('object'); + + }); + + it("check updateOperation", async function () { + let response = await updateOperation(operationId, 'test'); + expect(response).to.be.an('object').that.is.empty; + response = await updateOperation(operationId, 'test', TWO_FACTOR.MANDATORY, LOCK_ON_REQUEST.MANDATORY); + expect(response).to.be.an('object').that.is.empty; + }); + + it("check create instance", async function () { + let responseCreateInstance = await createInstance(NAME_INSTANCE, accountId, operationId); + expect(responseCreateInstance).to.have.a.property('data').that.is.an('object'); + expect(responseCreateInstance.data).to.have.a.property('instances').that.is.an('object'); + instanceId = Object.entries(responseCreateInstance.data.instances).find(([_, value]) => value == NAME_INSTANCE)[0]; + }); + + it("check getInstances", async function () { + let response = await getInstances(accountId, operationId); + expect(response).to.have.a.property('data').that.is.an('object'); + expect(response.data).to.have.a.property(instanceId).that.is.an('object'); + expect(response.data[instanceId]).to.have.a.property('name',NAME_INSTANCE); + }); + + it("check instanceStatus", async function () { + let response = await instanceStatus(instanceId, accountId, operationId); + expect(response).to.have.a.property('data').that.is.an('object'); + expect(response.data).to.have.a.property('operations').that.is.an('object'); + expect(response.data.operations).to.have.a.property(instanceId).that.is.an('object'); + expect(response.data.operations[instanceId]).to.have.a.property('status').that.is.an('string'); + }); + + it("check updateInstance", async function () { + let response = await updateInstance(instanceId, accountId, operationId, 'instace-testUpdate'); + expect(response).to.be.an('object').that.is.empty; + response = await updateInstance(instanceId, accountId, operationId, 'instace-testUpdate', TWO_FACTOR.MANDATORY, LOCK_ON_REQUEST.MANDATORY); + expect(response).to.be.an('object').that.is.empty; + }); + + it("check delete instance", async function () { + let responseDeleteInstance = await deleteInstance(instanceId, accountId,operationId) + expect(responseDeleteInstance).to.be.an('object').that.is.empty; + }); + + it("check create totp", async function () { + let responseCreateTotp = await createTotp(email, NAME_TOTP); + expect(responseCreateTotp).to.have.a.property('data').that.is.an('object'); + expect(responseCreateTotp.data).to.have.a.property('totpId').that.is.an('string'); + totpId = responseCreateTotp.data.totpId; + }); + + it("check getTotp", async function () { + let response = await getTotp(totpId); + expect(response).to.have.a.property('data').that.is.an('object'); + expect(response.data).to.have.a.property('totpId',totpId); + }); + + it("check validateTotp", async function () { + let response = await validateTotp(totpId, "123456"); + expect(response).to.have.a.property('error').that.is.an('object'); + expect(response.error).to.have.a.property('code', 306); + expect(response.error).to.have.a.property('message', 'Invalid totp code'); + }); + + it("check delete totp", async function () { + let responseDeleteTotp = await deleteTotp(totpId, accountId) + expect(responseDeleteTotp).to.be.null; + }); + + it("check checkControlStatus", async function () { + let response = await checkControlStatus("12345"); + expect(response).to.have.a.property('error').that.is.an('object'); + expect(response.error).to.have.a.property('code', 1100); + expect(response.error).to.have.a.property('message', 'Authorization control not found'); + }); - latch.init(args); - latch.status('1234', '', '', function (err, result) { - expect(result).to.have.a.property('error').that.is.an('object'); - expect(result.error).to.have.a.property('code', 102); - expect(result.error).to.have.a.property('message', 'Invalid application signature'); - done(); - }); + it("check delete operation", async function () { + let responseDeleteOperation = await deleteOperation(operationId); + expect(responseDeleteOperation).to.be.an('object').that.is.empty; }); - describe("Latch API requests with valid config", function () { - before(function () { - latch.init(appValidConfig); - }) - - let accountId; - let operationId; - let instanceId; - let totpId; - - describe("Latch API requests with valid config", function () { - it("check pair", function (done) { - latch.pair(code, function (err, result) { - expect(result).to.have.a.property('data').that.is.an('object'); - expect(result.data).to.have.a.property('accountId').that.is.an('string'); - accountId = result.data.accountId; - done(); - }); - }); - }); - describe("Latch API requests with valid config", function () { - it("check create operation", function (done) { - latch.createOperation(appId, NAME_OPERATION, 'DISABLED', 'DISABLED', function (err, result) { - expect(result).to.have.a.property('data').that.is.an('object'); - expect(result.data).to.have.a.property('operationId').that.is.an('string'); - operationId = result.data.operationId; - done(); - }); - }); - }); - describe("Latch API requests with valid config", function () { - it("check status", function (done) { - latch.status(accountId, '', '', function (err, result) { - expect(result).to.have.a.property('data').that.is.an('object'); - expect(result.data).to.have.a.property('operations').that.is.an('object'); - expect(result.data.operations).to.have.a.property(appId).that.is.an('object'); - expect(result.data.operations[appId]).to.have.a.property('status').that.is.an('string'); - done(); - }); - }); - }); - describe("Latch API requests with valid config", function () { - it("check lock", function (done) { - latch.lock(accountId, undefined, function (err, result) { - expect(result).to.be.an('object').that.is.empty; - done(); - }); - }); - }); - describe("Latch API requests with valid config", function () { - it("check unlock", function (done) { - latch.unlock(accountId, undefined, function (err, result) { - expect(result).to.be.an('object').that.is.empty; - done(); - }); - }); - }); - describe("Latch API requests with valid config", function () { - it("check history", function (done) { - latch.history(accountId, undefined, undefined, function (err, result) { - expect(result).to.have.a.property('data').that.is.an('object'); - expect(result.data).to.have.a.property('history').that.is.an('array'); - done(); - }); - }); - }); - describe("Latch API requests with valid config", function () { - it("check operationStatus", function (done) { - latch.operationStatus(accountId, appId, undefined, undefined, function (err, result) { - expect(result).to.have.a.property('data').that.is.an('object'); - expect(result.data).to.have.a.property('operations').that.is.an('object'); - expect(result.data.operations).to.have.a.property(appId).that.is.an('object'); - expect(result.data.operations[appId]).to.have.a.property('status').that.is.an('string'); - done(); - }); - }); - }); - describe("Latch API requests with valid config", function () { - it("check getOperations", function (done) { - latch.getOperations(appId, function (err, result) { - expect(result).to.have.a.property('data').that.is.an('object'); - expect(result.data).to.have.a.property('operations').that.is.an('object'); - done(); - }); - - }); - }); - describe("Latch API requests with valid config", function () { - it("check updateOperation", function (done) { - latch.updateOperation(operationId, NAME_OPERATION + '-update-1', undefined, undefined, function (err, result) { - expect(result).to.be.an('object').that.is.empty; - latch.updateOperation(operationId, NAME_OPERATION + '-update-2', 'MANDATORY', 'MANDATORY', function (err, result) { - expect(result).to.be.an('object').that.is.empty; - done(); - }); - }); - }); - }); - describe("Latch API requests with valid config", function () { - it("check create instance", function (done) { - latch.createInstance(NAME_INSTANCE, accountId, operationId, function (err, result) { - expect(result).to.have.a.property('data').that.is.an('object'); - expect(result.data).to.have.a.property('instances').that.is.an('object'); - instanceId = Object.entries(result.data.instances).find(([_, value]) => value == NAME_INSTANCE)[0]; - done(); - }); - }); - }); - describe("Latch API requests with valid config", function () { - it("check getInstances", function (done) { - latch.getInstances(accountId, operationId, function (err, result) { - expect(result).to.have.a.property('data').that.is.an('object'); - expect(result.data).to.have.a.property(instanceId).that.is.an('object'); - expect(result.data[instanceId]).to.have.a.property('name', NAME_INSTANCE); - done(); - }); - }); - }); - describe("Latch API requests with valid config", function () { - it("check instanceStatus", function (done) { - latch.instanceStatus(instanceId, accountId, operationId, undefined, undefined, function (err, result) { - expect(result).to.have.a.property('data').that.is.an('object'); - expect(result.data).to.have.a.property('operations').that.is.an('object'); - expect(result.data.operations).to.have.a.property(instanceId).that.is.an('object'); - expect(result.data.operations[instanceId]).to.have.a.property('status').that.is.an('string'); - done(); - }); - }); - }); - describe("Latch API requests with valid config", function () { - it("check updateInstance", function (done) { - latch.updateInstance(instanceId, accountId, operationId, NAME_INSTANCE + '-update-1', undefined, undefined, function (err, result) { - expect(result).to.be.an('object').that.is.empty; - latch.updateInstance(instanceId, accountId, operationId, NAME_INSTANCE + '-update-1', 'MANDATORY', 'MANDATORY', function (err, result) { - expect(result).to.be.an('object').that.is.empty; - done(); - }); - }); - }); - }); - describe("Latch API requests with valid config", function () { - it("check delete instance", function (done) { - latch.deleteInstance(instanceId, accountId, operationId, function (err, result) { - expect(result).to.be.an('object').that.is.empty; - done(); - }); - }); - }); - describe("Latch API requests with valid config", function () { - it("check create totp", function (done) { - latch.createTotp(email, NAME_TOTP, function (err, result) { - expect(result).to.have.a.property('data').that.is.an('object'); - expect(result.data).to.have.a.property('totpId').that.is.an('string'); - totpId = result.data.totpId; - done(); - }); - }); - }); - describe("Latch API requests with valid config", function () { - it("check getTotp", function (done) { - latch.getTotp(totpId, function (err, result) { - expect(result).to.have.a.property('data').that.is.an('object'); - expect(result.data).to.have.a.property('totpId', totpId); - done(); - }); - }); - }); - describe("Latch API requests with valid config", function () { - it("check validateTotp", function (done) { - latch.validateTotp(totpId, "123456", function (err, result) { - expect(result).to.have.a.property('error').that.is.an('object'); - expect(result.error).to.have.a.property('code', 306); - expect(result.error).to.have.a.property('message', 'Invalid totp code'); - done(); - }); - }); - }); - describe("Latch API requests with valid config", function () { - it("check delete totp", function (done) { - latch.deleteTotp(totpId, function (err, result) { - expect(result).to.be.an('object').that.is.empty; - done(); - }); - }); - }); - describe("Latch API requests with valid config", function () { - it("check checkControlStatus", function (done) { - latch.checkControlStatus("12345", function (err, result) { - expect(result).to.have.a.property('error').that.is.an('object'); - expect(result.error).to.have.a.property('code', 1100); - expect(result.error).to.have.a.property('message', 'Authorization control not found'); - done(); - }); - }); - }); - describe("Latch API requests with valid config", function () { - it("check delete operation", function (done) { - latch.deleteOperation(operationId, function (err, result) { - expect(result).to.be.an('object').that.is.empty; - done(); - }); - }); - }); - describe("Latch API requests with valid config", function () { - it("check unpair", function (done) { - latch.unpair(accountId, function (err, result) { - expect(result).to.be.an('object').that.is.empty; - done(); - }); - }); - }); + it("check unpair", async function () { + let responseUnpair = await unpair(accountId); + expect(responseUnpair).to.be.an('object').that.is.empty; }); }); }); \ No newline at end of file