From 46c822552535e723d474e276a721b3b74fefa2e1 Mon Sep 17 00:00:00 2001 From: fetok12 Date: Mon, 10 Apr 2023 00:52:42 +0300 Subject: [PATCH 001/234] Session Management --- server/api/controllers/user.js | 33 ++++--- server/constants.js | 1 + .../20230422073930-create-session.js | 57 +++++++++++ .../20230422073931-add-attempt-login.js | 15 +++ server/db/models/index.js | 2 + server/db/models/login.js | 5 + server/db/models/session.js | 54 ++++++++++ .../utils/hollaex-tools-lib/tools/security.js | 99 ++++++++++++++++++- server/utils/hollaex-tools-lib/tools/user.js | 21 +++- 9 files changed, 269 insertions(+), 18 deletions(-) create mode 100644 server/db/migrations/20230422073930-create-session.js create mode 100644 server/db/migrations/20230422073931-add-attempt-login.js create mode 100644 server/db/models/session.js diff --git a/server/api/controllers/user.js b/server/api/controllers/user.js index 8bc1e3a584..ec5a005fc8 100644 --- a/server/api/controllers/user.js +++ b/server/api/controllers/user.js @@ -369,6 +369,10 @@ const loginPost = (req, res) => { ]); }) .then(([user, passwordIsValid]) => { + if(ip) { + toolsLib.user.updateLoginAttempt(user.id, ip); + } + if (!passwordIsValid) { throw new Error(INVALID_CREDENTIALS); } @@ -389,14 +393,6 @@ const loginPost = (req, res) => { } }) .then(([user]) => { - if (ip) { - toolsLib.user.registerUserLogin(user.id, ip, { - device, - domain, - origin, - referer - }); - } const data = { ip, time, @@ -414,8 +410,10 @@ const loginPost = (req, res) => { if (!service) { sendEmail(MAILTYPE.LOGIN, email, data, user.settings, domain); } - return res.status(201).json({ - token: toolsLib.security.issueToken( + + return all([ + user, + toolsLib.security.issueToken( user.id, user.network_id, email, @@ -427,7 +425,20 @@ const loginPost = (req, res) => { user.is_communicator, long_term ? TOKEN_TIME_LONG : TOKEN_TIME_NORMAL ) - }); + ]) + }) + .then(([user, token]) => { + if (ip) { + toolsLib.user.registerUserLogin(user.id, ip, { + device, + domain, + origin, + referer, + token, + expiry: long_term ? TOKEN_TIME_LONG : TOKEN_TIME_NORMAL + }); + } + return res.status(201).json({ token }); }) .catch((err) => { loggerUser.error(req.uuid, 'controllers/user/loginPost catch', err.message); diff --git a/server/constants.js b/server/constants.js index aad91712fb..9122b757da 100644 --- a/server/constants.js +++ b/server/constants.js @@ -244,6 +244,7 @@ exports.HOLLAEX_NETWORK_PATH_ACTIVATE = '/exchange/activate'; exports.INIT_CHANNEL = 'channel:init'; exports.WITHDRAWALS_REQUEST_KEY = 'withdrawals:request'; exports.HMAC_TOKEN_KEY = 'hmac:token'; +exports.SESSION_TOKEN_KEY = 'session:token'; exports.EVENTS_CHANNEL = 'channel:events'; exports.CONFIGURATION_CHANNEL = CONFIGURATION_CHANNEL; diff --git a/server/db/migrations/20230422073930-create-session.js b/server/db/migrations/20230422073930-create-session.js new file mode 100644 index 0000000000..8102018ac8 --- /dev/null +++ b/server/db/migrations/20230422073930-create-session.js @@ -0,0 +1,57 @@ +'use strict'; +const { ROLES } = require('../../constants'); +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable('Sessions', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + token: { + type: Sequelize.STRING(1000), + allowNull: false + }, + login_id: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: 'Logins', + key: 'id' + } + }, + status: { + type: Sequelize.BOOLEAN, + allowNull: false + }, + last_seen: { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.literal('NOW()') + }, + expiry_date: { + type: Sequelize.DATE, + allowNull: false + }, + role: { + type: Sequelize.STRING, + allowNull: false, + defaultValue: ROLES.USER + }, + created_at: { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.literal('NOW()') + }, + updated_at: { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.literal('NOW()') + } + }, { + underscored: true + }); + }, + down: (queryInterface) => queryInterface.dropTable('Sessions') +}; diff --git a/server/db/migrations/20230422073931-add-attempt-login.js b/server/db/migrations/20230422073931-add-attempt-login.js new file mode 100644 index 0000000000..07040c0e40 --- /dev/null +++ b/server/db/migrations/20230422073931-add-attempt-login.js @@ -0,0 +1,15 @@ +'use strict'; + +const TABLE = 'Logins'; +const COLUMN = 'attempt'; + +module.exports = { + up: (queryInterface, Sequelize) => + queryInterface.addColumn(TABLE, COLUMN, { + type: Sequelize.INTEGER, + allowNull: true, + defaultValue: 0 + }), + down: (queryInterface) => + queryInterface.removeColumn(TABLE, COLUMN) +}; diff --git a/server/db/models/index.js b/server/db/models/index.js index d2d1d641d3..0f33ac55c4 100644 --- a/server/db/models/index.js +++ b/server/db/models/index.js @@ -42,6 +42,8 @@ model = require(path.join(__dirname, './plugin'))(sequelize, Sequelize.DataTypes db[model.name] = model; model = require(path.join(__dirname, './broker'))(sequelize, Sequelize.DataTypes); db[model.name] = model; +model = require(path.join(__dirname, './session'))(sequelize, Sequelize.DataTypes); +db[model.name] = model; Object.keys(db).forEach(function (modelName) { if ('associate' in db[modelName]) { diff --git a/server/db/models/login.js b/server/db/models/login.js index e44bfce6e6..78070bced3 100644 --- a/server/db/models/login.js +++ b/server/db/models/login.js @@ -27,6 +27,11 @@ module.exports = function (sequelize, DataTypes) { allowNull: true, defaultValue: '' }, + attempt: { + type: DataTypes.NUMBER, + allowNull: false, + defaultValue: 0 + }, timestamp: { type: DataTypes.DATE, allowNull: false, diff --git a/server/db/models/session.js b/server/db/models/session.js new file mode 100644 index 0000000000..397cfc0f8c --- /dev/null +++ b/server/db/models/session.js @@ -0,0 +1,54 @@ +'use strict'; +const { ROLES } = require('../../constants'); + +module.exports = function (sequelize, DataTypes) { + const Session = sequelize.define( + 'Session', + { + token: { + type: DataTypes.STRING(1000), + allowNull: false + }, + login_id: { + type: DataTypes.INTEGER, + onDelete: 'CASCADE', + allowNull: false, + references: { + model: 'Logins', + key: 'id' + } + }, + status: { + type: DataTypes.BOOLEAN, + allowNull: false + }, + last_seen: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: DataTypes.NOW + }, + expiry_date: { + type: DataTypes.DATE, + allowNull: false + }, + role: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: ROLES.USER + } + }, + { + underscored: true, + tableName: 'Sessions' + } + ); + Session.associate = (models) => { + Session.belongsTo(models.Login, { + as: 'login', + foreignKey: 'login_id', + targetKey: 'id', + onDelete: 'CASCADE' + }); + }; + return Session; +}; diff --git a/server/utils/hollaex-tools-lib/tools/security.js b/server/utils/hollaex-tools-lib/tools/security.js index 45f3351fcf..9269354393 100644 --- a/server/utils/hollaex-tools-lib/tools/security.js +++ b/server/utils/hollaex-tools-lib/tools/security.js @@ -49,7 +49,8 @@ const { SECRET, TOKEN_TYPES, HMAC_TOKEN_EXPIRY, - HMAC_TOKEN_KEY + HMAC_TOKEN_KEY, + SESSION_TOKEN_KEY } = require(`${SERVER_PATH}/constants`); const { getNodeLib } = require(`${SERVER_PATH}/init`); const { resolve, reject, promisify } = require('bluebird'); @@ -550,7 +551,7 @@ const verifyBearerTokenMiddleware = (req, authOrSecDef, token, cb, isSocket = fa if (token && token.indexOf('Bearer ') === 0) { const tokenString = token.split(' ')[1]; - jwt.verify(tokenString, SECRET, (verificationError, decodedToken) => { + jwt.verify(tokenString, SECRET, async (verificationError, decodedToken) => { //check if the JWT was verified correctly if (!verificationError && decodedToken) { loggerAuth.verbose( @@ -591,6 +592,11 @@ const verifyBearerTokenMiddleware = (req, authOrSecDef, token, cb, isSocket = fa return sendError(DEACTIVATED_USER); } + try { + await verifySession(tokenString); + } catch (err) { + return sendError(err.message); + } req.auth = decodedToken; return cb(null); } else { @@ -849,6 +855,91 @@ const verifyHmacTokenPromise = (apiKey, apiSignature, apiExpires, method, origin } }; + +const createSession = async (token, loginId, userId, expiry) => { + + const { getUserRole } = require('./user'); + + const userRole = await getUserRole({ kit_id: userId }); + + const base64Payload = token.split(".")[1]; + const payloadBuffer = Buffer.from(base64Payload, "base64"); + const decoded = JSON.parse(payloadBuffer.toString()); + + // const hashedToken = await generateHash(token); + const hashedToken = token; + + return getModel('session').create({ + token: hashedToken, + role: userRole, + login_id: loginId, + status: true, + last_seen: new Date(), + expiry_date: new Date(decoded.exp * 1000) + }) +} + +const verifySession = async (token) => { + + const session = await findSession(token); + + if (!session || !session.status) { + loggerAuth.error( + 'security/verifySession invalid session', + session.status + ); + throw new Error(TOKEN_REVOKED); + } + + if(new Date(session.expiry_date).getTime() < new Date().getTime()) { + throw new Error(TOKEN_EXPIRED); + } + + if(new Date(session.last_seen).getTime() + 1000 * 60 * 5 < new Date().getTime()) { + const sessionData = await dbQuery.findOne('session', { where: { token } }); + sessionData.update( + { last_seen: new Date() } + ); + } +} + +const findSession = async (token) => { + + // const hashedToken = await generateHash(token); + const hashedToken = token; + + let session = await client.hgetAsync(SESSION_TOKEN_KEY, hashedToken) + + if (!session) { + loggerAuth.verbose( + 'security/findSession jwt token not found in redis', + token + ); + + session = await dbQuery.findOne('session', { + where: { + token: hashedToken + } + }); + + if(session) + client.hsetAsync(SESSION_TOKEN_KEY, hashedToken, JSON.stringify(session)); + + loggerAuth.verbose( + 'security/findSession token stored in redis', + token + ); + + return session; + } else { + loggerAuth.debug( + 'security/findSession token found in redis', + token + ); + return JSON.parse(session); + } +} + /** * Function that checks to see if user's scope is valid for the endpoint. * @param {array} endpointScopes - Authorized scopes for the endpoint. @@ -1247,5 +1338,7 @@ module.exports = { sendConfirmationEmail, confirmByEmail, calculateSignature, - generateDashToken + generateDashToken, + createSession, + verifySession }; diff --git a/server/utils/hollaex-tools-lib/tools/user.js b/server/utils/hollaex-tools-lib/tools/user.js index 415725b9ac..3425723004 100644 --- a/server/utils/hollaex-tools-lib/tools/user.js +++ b/server/utils/hollaex-tools-lib/tools/user.js @@ -67,7 +67,7 @@ const { const { sendEmail } = require(`${SERVER_PATH}/mail`); const { MAILTYPE } = require(`${SERVER_PATH}/mail/strings`); const { getKitConfig, isValidTierLevel, getKitTier, isDatetime } = require('./common'); -const { isValidPassword } = require('./security'); +const { isValidPassword, createSession } = require('./security'); const { getNodeLib } = require(`${SERVER_PATH}/init`); const { all, reject } = require('bluebird'); const { Op } = require('sequelize'); @@ -324,7 +324,9 @@ const registerUserLogin = ( device: null, domain: null, origin: null, - referer: null + referer: null, + token: null, + expiry: null } ) => { const login = { @@ -348,9 +350,19 @@ const registerUserLogin = ( login.referer = opts.referer; } - return getModel('login').create(login); + return getModel('login').create(login) + .then((loginData) => { + if(opts.token) { + return createSession(opts.token, loginData.id, userId, opts.expiry); + } + }) + .catch(err => reject(err)) }; +const updateLoginAttempt = (userId, ip) => { + return getModel('login').increment('attempt', { by: 1, where: { user_id: userId, ip }}); +} + /* Public Endpoints*/ @@ -1926,5 +1938,6 @@ module.exports = { updateUserMeta, mapNetworkIdToKitId, mapKitIdToNetworkId, - updateUserInfo + updateUserInfo, + updateLoginAttempt }; From 06cd9f78d3df9d2319be434fbbf693383eeeb409 Mon Sep 17 00:00:00 2001 From: fetok12 Date: Wed, 12 Apr 2023 00:59:38 +0300 Subject: [PATCH 002/234] Session Endpoints --- server/api/controllers/admin.js | 52 +++++++- server/api/swagger/admin.yaml | 123 ++++++++++++++++++- server/db/models/login.js | 1 + server/messages.js | 2 + server/utils/hollaex-tools-lib/tools/user.js | 69 ++++++++++- 5 files changed, 241 insertions(+), 6 deletions(-) diff --git a/server/api/controllers/admin.js b/server/api/controllers/admin.js index b5ed7ab5b9..406bac4bfc 100644 --- a/server/api/controllers/admin.js +++ b/server/api/controllers/admin.js @@ -2243,6 +2243,54 @@ const getWalletsByAdmin = (req, res) => { }); }; +const getUserSessionsByAdmin = (req, res) => { + loggerAdmin.verbose(req.uuid, 'controllers/admin/getUserSessionsByAdmin/auth', req.auth); + + const { user_id, limit, page, order_by, order, start_date, end_date } = req.swagger.params; + + if (order_by.value && typeof order_by.value !== 'string') { + loggerAdmin.error( + req.uuid, + 'controllers/admin/getUserSessionsByAdmin invalid order_by', + order_by.value + ); + return res.status(400).json({ message: 'Invalid order by' }); + } + + toolsLib.user.getExchangeUserSessions({ + user_id: user_id.value, + limit: limit.value, + page: page.value, + order_by: order_by.value, + order: order.value, + start_date: start_date.value, + end_date: end_date.value + } + ) + .then((data) => { + return res.json(data); + }) + .catch((err) => { + loggerAdmin.error(req.uuid, 'controllers/admin/getUserSessionsByAdmin', err.message); + return res.status(err.statusCode || 400).json({ message: errorMessageConverter(err) }); + }); +}; + +const revokeUserSessionByAdmin = (req, res) => { + loggerAdmin.verbose(req.uuid, 'controllers/admin/revokeUserSessionByAdmin/auth', req.auth); + + const { session_id } = req.swagger.params.data.value; + + toolsLib.user.revokeExchangeUserSession(session_id) + .then((data) => { + return res.json(data); + }) + .catch((err) => { + loggerAdmin.error(req.uuid, 'controllers/admin/revokeUserSessionByAdmin', err.message); + return res.status(err.statusCode || 400).json({ message: errorMessageConverter(err) }); + }); +} + module.exports = { createInitialAdmin, getAdminKit, @@ -2299,5 +2347,7 @@ module.exports = { getUserReferer, createUserByAdmin, createUserWalletByAdmin, - getWalletsByAdmin + getWalletsByAdmin, + getUserSessionsByAdmin, + revokeUserSessionByAdmin }; diff --git a/server/api/swagger/admin.yaml b/server/api/swagger/admin.yaml index 37508c69d0..62bf3d3731 100644 --- a/server/api/swagger/admin.yaml +++ b/server/api/swagger/admin.yaml @@ -2721,4 +2721,125 @@ paths: - admin - supervisor - kyc - - support \ No newline at end of file + - support + /admin/user/session: + x-swagger-router-controller: admin + get: + description: Get user sessions for admin + operationId: getUserSessionsByAdmin + parameters: + - in: query + name: user_id + description: User id + required: false + type: number + format: int32 + - in: query + name: limit + description: "Number of elements to return. Default: 50. Maximun: 100" + required: false + type: number + format: int32 + - in: query + name: page + description: Page of data to retrieve + required: false + type: number + format: int32 + - in: query + name: currency + description: Currency symbol + required: false + type: string + - in: query + name: order_by + description: Field to order data + required: false + type: string + - in: query + name: order + description: direction to order + required: false + type: string + enum: ['asc', 'desc'] + - in: query + name: start_date + description: Starting date of queried data + required: false + type: string + format: date-time + - in: query + name: end_date + description: Ending date of queried data + required: false + type: string + format: date-time + - in: query + name: address + description: crypto address of the wallet + required: false + type: string + - in: query + name: is_valid + description: specify whether or not wallet is wallet + required: false + type: boolean + default: true + - in: query + name: network + description: Blockchain network of wallet + required: false + type: string + tags: + - Admin + responses: + 200: + description: Success + schema: + $ref: "#/definitions/ObjectResponse" + default: + description: Error + schema: + $ref: "#/definitions/MessageResponse" + security: + - Token: [] + x-security-types: + - bearer + - hmac + x-security-scopes: + - admin + /admin/user/revoke-session: + x-swagger-router-controller: admin + post: + description: Revoke user session By admin + operationId: revokeUserSessionByAdmin + tags: + - Admin + parameters: + - name: data + in: body + required: true + schema: + type: object + required: + - session_id + properties: + session_id: + type: number + format: int32 + responses: + 200: + description: Success + schema: + $ref: "#/definitions/ObjectResponse" + default: + description: Error + schema: + $ref: "#/definitions/MessageResponse" + security: + - Token: [] + x-security-types: + - bearer + - hmac + x-security-scopes: + - admin \ No newline at end of file diff --git a/server/db/models/login.js b/server/db/models/login.js index 78070bced3..29713dc306 100644 --- a/server/db/models/login.js +++ b/server/db/models/login.js @@ -51,6 +51,7 @@ module.exports = function (sequelize, DataTypes) { foreignKey: 'user_id', targetKey: 'id' }); + Login.hasOne(models.Session); }; return Login; diff --git a/server/messages.js b/server/messages.js index 085e4a8cda..b6affdede0 100644 --- a/server/messages.js +++ b/server/messages.js @@ -199,3 +199,5 @@ exports.INVALID_TOKEN_TYPE = 'invalid token type'; exports.NO_AUTH_TOKEN = 'no auth token sent'; exports.WHITELIST_DISABLE_ADMIN = 'Admin cannot disable whitelisting feature'; exports.WHITELIST_NOT_PROVIDED = 'Admin needs to provide whitelisted IP(s)'; +exports.SESSION_NOT_FOUND = 'Session not found'; +exports.SESSION_ALREADY_REVOKED = 'Session already revoked' \ No newline at end of file diff --git a/server/utils/hollaex-tools-lib/tools/user.js b/server/utils/hollaex-tools-lib/tools/user.js index 3425723004..b9ecd61679 100644 --- a/server/utils/hollaex-tools-lib/tools/user.js +++ b/server/utils/hollaex-tools-lib/tools/user.js @@ -50,9 +50,11 @@ const { USER_NOT_DEACTIVATED, CANNOT_CHANGE_ADMIN_ROLE, VERIFICATION_CODE_USED, - USER_NOT_REGISTERED_ON_NETWORK + USER_NOT_REGISTERED_ON_NETWORK, + SESSION_NOT_FOUND, + SESSION_ALREADY_REVOKED } = require(`${SERVER_PATH}/messages`); -const { publisher } = require('./database/redis'); +const { publisher, client } = require('./database/redis'); const { CONFIGURATION_CHANNEL, AUDIT_KEYS, @@ -62,7 +64,8 @@ const { SETTING_KEYS, OMITTED_USER_FIELDS, DEFAULT_ORDER_RISK_PERCENTAGE, - AFFILIATION_CODE_LENGTH + AFFILIATION_CODE_LENGTH, + SESSION_TOKEN_KEY } = require(`${SERVER_PATH}/constants`); const { sendEmail } = require(`${SERVER_PATH}/mail`); const { MAILTYPE } = require(`${SERVER_PATH}/mail/strings`); @@ -1888,6 +1891,62 @@ const updateUserInfo = async (userId, data = {}) => { return omitUserFields(user.dataValues); }; +const getExchangeUserSessions = (opts = { + user_id: null, + limit: null, + page: null, + order_by: null, + order: null, + start_date: null, + end_date: null +}) => { + + const pagination = paginationQuery(opts.limit, opts.page); + const ordering = orderingQuery(opts.order_by, opts.order); + const timeframe = timeframeQuery(opts.start_date, opts.end_date); + + return dbQuery.findAndCountAllWithRows('login', { + where: { + ...(opts.user_id && {user_id: opts.user_id}), + created_at: timeframe + }, + include: [ + { + model: getModel('user'), + attributes: ['id', 'email'] + }, + { + model: getModel('session'), + attributes: { + exclude: ['token'] + } + } + ], + order: [ordering], + ...pagination + }); +} + +const revokeExchangeUserSession = async (sessionId) => { + const session = await getModel('session').findOne({ where: { id: sessionId } }); + + if(!session) { + throw new Error(SESSION_NOT_FOUND); + } + + if(!session.status) { + throw new Error(SESSION_ALREADY_REVOKED); + } + client.hdelAsync(SESSION_TOKEN_KEY, session.token); + + const updatedSession = await session.update({ status: false }, { + fields: ['status'] + }); + + delete updatedSession.dataValues.token; + return updatedSession.dataValues; +} + module.exports = { loginUser, getUserTier, @@ -1939,5 +1998,7 @@ module.exports = { mapNetworkIdToKitId, mapKitIdToNetworkId, updateUserInfo, - updateLoginAttempt + updateLoginAttempt, + getExchangeUserSessions, + revokeExchangeUserSession }; From 0b49981fe910c113ee5e226ea72f5cfff71597c5 Mon Sep 17 00:00:00 2001 From: fetok12 Date: Wed, 12 Apr 2023 01:12:54 +0300 Subject: [PATCH 003/234] Logic Refinements --- server/utils/hollaex-tools-lib/tools/security.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/utils/hollaex-tools-lib/tools/security.js b/server/utils/hollaex-tools-lib/tools/security.js index 9269354393..1d12c62dd6 100644 --- a/server/utils/hollaex-tools-lib/tools/security.js +++ b/server/utils/hollaex-tools-lib/tools/security.js @@ -897,9 +897,10 @@ const verifySession = async (token) => { if(new Date(session.last_seen).getTime() + 1000 * 60 * 5 < new Date().getTime()) { const sessionData = await dbQuery.findOne('session', { where: { token } }); - sessionData.update( + const updatedSession = await sessionData.update( { last_seen: new Date() } ); + client.hsetAsync(SESSION_TOKEN_KEY, updatedSession.dataValues.token, JSON.stringify(updatedSession.dataValues)); } } @@ -922,7 +923,7 @@ const findSession = async (token) => { } }); - if(session) + if(session && session.status && new Date(session.expiry_date).getTime() > new Date().getTime()) client.hsetAsync(SESSION_TOKEN_KEY, hashedToken, JSON.stringify(session)); loggerAuth.verbose( From 777bb6ed20f7eae9e7d57d11be13ae600a7e3803 Mon Sep 17 00:00:00 2001 From: fetok12 Date: Wed, 12 Apr 2023 01:18:18 +0300 Subject: [PATCH 004/234] Logic Refinements --- server/api/swagger/admin.yaml | 21 ------------------- .../utils/hollaex-tools-lib/tools/security.js | 2 +- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/server/api/swagger/admin.yaml b/server/api/swagger/admin.yaml index 62bf3d3731..c83fc0b0fd 100644 --- a/server/api/swagger/admin.yaml +++ b/server/api/swagger/admin.yaml @@ -2746,11 +2746,6 @@ paths: required: false type: number format: int32 - - in: query - name: currency - description: Currency symbol - required: false - type: string - in: query name: order_by description: Field to order data @@ -2774,22 +2769,6 @@ paths: required: false type: string format: date-time - - in: query - name: address - description: crypto address of the wallet - required: false - type: string - - in: query - name: is_valid - description: specify whether or not wallet is wallet - required: false - type: boolean - default: true - - in: query - name: network - description: Blockchain network of wallet - required: false - type: string tags: - Admin responses: diff --git a/server/utils/hollaex-tools-lib/tools/security.js b/server/utils/hollaex-tools-lib/tools/security.js index 1d12c62dd6..3bf8a86162 100644 --- a/server/utils/hollaex-tools-lib/tools/security.js +++ b/server/utils/hollaex-tools-lib/tools/security.js @@ -856,7 +856,7 @@ const verifyHmacTokenPromise = (apiKey, apiSignature, apiExpires, method, origin }; -const createSession = async (token, loginId, userId, expiry) => { +const createSession = async (token, loginId, userId) => { const { getUserRole } = require('./user'); From e639d2f2108b4795a1cef3023c1f1a70baef2c3e Mon Sep 17 00:00:00 2001 From: fetok12 Date: Thu, 13 Apr 2023 23:44:56 +0300 Subject: [PATCH 005/234] Hash token --- server/utils/hollaex-tools-lib/tools/security.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/server/utils/hollaex-tools-lib/tools/security.js b/server/utils/hollaex-tools-lib/tools/security.js index 3bf8a86162..7d83caa389 100644 --- a/server/utils/hollaex-tools-lib/tools/security.js +++ b/server/utils/hollaex-tools-lib/tools/security.js @@ -866,8 +866,7 @@ const createSession = async (token, loginId, userId) => { const payloadBuffer = Buffer.from(base64Payload, "base64"); const decoded = JSON.parse(payloadBuffer.toString()); - // const hashedToken = await generateHash(token); - const hashedToken = token; + const hashedToken = crypto.createHash('md5').update(token).digest('hex'); return getModel('session').create({ token: hashedToken, @@ -906,15 +905,14 @@ const verifySession = async (token) => { const findSession = async (token) => { - // const hashedToken = await generateHash(token); - const hashedToken = token; + const hashedToken = crypto.createHash('md5').update(token).digest('hex');; let session = await client.hgetAsync(SESSION_TOKEN_KEY, hashedToken) if (!session) { loggerAuth.verbose( 'security/findSession jwt token not found in redis', - token + hashedToken ); session = await dbQuery.findOne('session', { @@ -928,14 +926,14 @@ const findSession = async (token) => { loggerAuth.verbose( 'security/findSession token stored in redis', - token + hashedToken ); return session; } else { loggerAuth.debug( 'security/findSession token found in redis', - token + hashedToken ); return JSON.parse(session); } From d9a57d241fc237bf9c683831d7eaff348a439d6d Mon Sep 17 00:00:00 2001 From: fetok12 Date: Thu, 13 Apr 2023 23:46:55 +0300 Subject: [PATCH 006/234] Fix comma --- server/utils/hollaex-tools-lib/tools/security.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/utils/hollaex-tools-lib/tools/security.js b/server/utils/hollaex-tools-lib/tools/security.js index 7d83caa389..fd0e994b25 100644 --- a/server/utils/hollaex-tools-lib/tools/security.js +++ b/server/utils/hollaex-tools-lib/tools/security.js @@ -905,7 +905,7 @@ const verifySession = async (token) => { const findSession = async (token) => { - const hashedToken = crypto.createHash('md5').update(token).digest('hex');; + const hashedToken = crypto.createHash('md5').update(token).digest('hex'); let session = await client.hgetAsync(SESSION_TOKEN_KEY, hashedToken) From 55814fafeea9de9068a550ee6a596a6d30722830 Mon Sep 17 00:00:00 2001 From: fetok12 Date: Thu, 20 Apr 2023 02:00:13 +0300 Subject: [PATCH 007/234] Advanced FIltering User --- server/api/controllers/admin.js | 52 ++- server/api/swagger/admin.yaml | 80 +++++ server/utils/hollaex-tools-lib/tools/user.js | 220 ++++-------- .../containers/Admin/ListUsers/FullList.js | 32 +- .../containers/Admin/ListUsers/UserFilters.js | 333 ++++++++++++++++++ web/src/containers/Admin/User/index.js | 4 +- 6 files changed, 565 insertions(+), 156 deletions(-) create mode 100644 web/src/containers/Admin/ListUsers/UserFilters.js diff --git a/server/api/controllers/admin.js b/server/api/controllers/admin.js index 7f2f51b05b..6474d68721 100644 --- a/server/api/controllers/admin.js +++ b/server/api/controllers/admin.js @@ -112,7 +112,39 @@ const putAdminKit = (req, res) => { const getUsersAdmin = (req, res) => { loggerAdmin.verbose(req.uuid, 'controllers/admin/getUsers/auth', req.auth); - const { id, search, type, pending, pending_type, limit, page, order_by, order, start_date, end_date, format } = req.swagger.params; + const { + id, + search, + type, + pending, + pending_type, + limit, + page, + order_by, + order, + start_date, + end_date, + format, + email, + username, + full_name, + dob_start_date, + dob_end_date, + gender, + nationality, + crypto_wallet, + verification_level, + email_verified, + otp_enabled, + is_admin, + is_supervisor, + is_support, + is_kyc, + is_communicator, + affiliation_rate, + phone_number + + } = req.swagger.params; if (order_by.value && typeof order_by.value !== 'string') { loggerAdmin.error( @@ -140,6 +172,24 @@ const getUsersAdmin = (req, res) => { end_date: end_date.value, format: format.value, type: type.value, + email: email.value, + username: username.value, + full_name: full_name.value, + dob_start_date: dob_start_date.value, + dob_end_date: dob_end_date.value, + gender: gender.value, + nationality: nationality.value, + crypto_wallet: crypto_wallet.value, + verification_level: verification_level.value, + email_verified: email_verified.value, + otp_enabled: otp_enabled.value, + is_admin: is_admin.value, + is_supervisor: is_supervisor.value, + is_support: is_support.value, + is_kyc: is_kyc.value, + is_communicator: is_communicator.value, + affiliation_rate: affiliation_rate.value, + phone_number: phone_number.value, additionalHeaders: { 'x-forwarded-for': req.headers['x-forwarded-for'] } diff --git a/server/api/swagger/admin.yaml b/server/api/swagger/admin.yaml index 1a79a5e658..b28e1ea070 100644 --- a/server/api/swagger/admin.yaml +++ b/server/api/swagger/admin.yaml @@ -1140,6 +1140,86 @@ paths: required: false type: string format: date-time + - in: query + name: email + required: false + type: string + maxLength: 256 + - in: query + name: username + required: false + type: string + maxLength: 256 + - in: query + name: full_name + required: false + type: string + maxLength: 256 + - in: query + name: dob_start_date + required: false + type: string + format: date-time + - in: query + name: dob_end_date + required: false + type: string + format: date-time + - in: query + name: gender + required: false + type: boolean + - in: query + name: nationality + required: false + type: string + maxLength: 256 + - in: query + name: crypto_wallet + required: false + type: string + maxLength: 256 + - in: query + name: phone_number + required: false + type: string + maxLength: 256 + - in: query + name: verification_level + required: false + type: number + - in: query + name: email_verified + required: false + type: boolean + - in: query + name: otp_enabled + required: false + type: boolean + - in: query + name: is_admin + required: false + type: boolean + - in: query + name: is_supervisor + required: false + type: boolean + - in: query + name: is_support + required: false + type: boolean + - in: query + name: is_kyc + required: false + type: boolean + - in: query + name: is_communicator + required: false + type: boolean + - in: query + name: affiliation_rate + required: false + type: boolean - in: query name: format description: Specify data format diff --git a/server/utils/hollaex-tools-lib/tools/user.js b/server/utils/hollaex-tools-lib/tools/user.js index 415725b9ac..1efe61ee36 100644 --- a/server/utils/hollaex-tools-lib/tools/user.js +++ b/server/utils/hollaex-tools-lib/tools/user.js @@ -518,165 +518,96 @@ const getAllUsersAdmin = (opts = { end_date: null, format: null, type: null, + email: null, + username: null, + full_name: null, + pending_verification: null, + dob_start_date: null, + dob_end_date: null, + gender: null, + nationality: null, + crypto_wallet: null, + verification_level: null, + email_verified: null, + otp_enabled: null, + is_admin: null, + is_supervisor: null, + is_support: null, + is_kyc: null, + is_communicator: null, + affiliation_rate: null, + phone_number: null, additionalHeaders: null }) => { + const { + id, + gender, + email_verified, + otp_enabled, + is_admin, + is_supervisor, + is_support, + is_kyc, + is_communicator, + dob_start_date, + dob_end_date + } = opts; + const pagination = paginationQuery(opts.limit, opts.page); const timeframe = timeframeQuery(opts.start_date, opts.end_date); + const dob_timeframe = timeframeQuery(dob_start_date, dob_end_date); const ordering = orderingQuery(opts.order_by, opts.order); let query = { where: { - created_at: timeframe - } + created_at: timeframe, + ...(id != null && { id }), + ...((dob_start_date != null || dob_end_date != null) && { dob: dob_timeframe }), + ...(email_verified != null && { email_verified }), + ...(gender != null && { gender }), + ...(otp_enabled != null && { otp_enabled }), + ...(is_admin != null && { is_admin }), + ...(is_supervisor != null && { is_supervisor }), + ...(is_support != null && { is_support }), + ...(is_kyc != null && { is_kyc }), + ...(is_communicator != null && { is_communicator }), + [Op.and]: [], + }, + order: [ordering] }; - if (opts.id || opts.search) { - query.attributes = { - exclude: ['balance', 'password', 'updated_at'] - }; - if (opts.id) { - query.where.id = opts.id; - } - else if (opts.type != null) { - query.where = { - [Op.or]: [] - }; - - switch (opts.type) { - case 'id': - query.where[Op.or].push( - { - id: { - [Op.like]: `%${opts.search}%` - } - } - ); - break; - case 'email': - query.where[Op.or].push( - { - email: { - [Op.like]: `%${opts.search}%` - } - } - ); - break; - case 'crypto_wallet': - query.where[Op.or].push( - { - crypto_wallet: { - [Op.like]: `%${opts.search}%` - } - } - ); - break; - case 'createdAt': - query.where[Op.or].push( - { - createdAt: { - [Op.like]: `%${opts.search}%` - } - } - ); - break; - case 'updatedAt': - query.where[Op.or].push( - { - updatedAt: { - [Op.like]: `%${opts.search}%` - } - } - ); - break; - case 'id_data': - query.where[Op.or].push(getModel('sequelize').literal(`id_data ->> 'number'='${opts.search}'`)); - break; - case 'phone_number': - query.where[Op.or].push( - { - phone_number: { - [Op.like]: `%${opts.search}%` - } - } - ); - break; - case 'verification_level': - query.where[Op.or].push( - { - verification_level: { - [Op.like]: `%${opts.search}%` - } - } - ); - break; - case 'pending_verification': - query.where[Op.or].push( - { - id_data: { - status: 1 - } - } - ); - break; - case 'pending_bank': - query.where[Op.or].push(getModel('sequelize').literal('bank_account @> \'[{"status":1}]\'')); - break; - default: - break; - } - - } - else { - query.where = { - [Op.or]: [ - { - email: { - [Op.like]: `%${opts.search}%` - } - }, - { - username: { - [Op.like]: `%${opts.search}%` - } - }, - { - full_name: { - [Op.like]: `%${opts.search}%` - } - }, - { - phone_number: { - [Op.like]: `%${opts.search}%` - } - }, - getModel('sequelize').literal(`id_data ->> 'number'='${opts.search}'`) - ] - }; + query.attributes = { + exclude: ['balance', 'password', 'updated_at'] + }; + Object.keys(pick(opts, ['email', 'nationality', 'username', 'full_name', 'phone_number', 'verification_level', 'affiliation_rate'])).forEach(key => { + if(opts[key] != null) { + query.where[Op.and].push( + { + [key]: { + [Op.like]: `%${opts[key]}%` + } + } + ) } - } else if (isBoolean(opts.pending) && opts.pending) { - - query = { - attributes: [ - 'id', - 'email', - 'verification_level', - 'id_data', - 'bank_account', - 'activated' - ], - order: [['updated_at', 'desc']] - }; + }) + + //TO DO: 'crypto_wallet', + if (isBoolean(opts.pending) && opts.pending) { + query.order = [['updated_at', 'desc']]; if (opts.pending_type) { if (opts.pending_type === 'id') { query.where = { + ...query.where, activated: true, id_data: { status: 1 // users that have a pending id waiting for admin to confirm } }; } else if (opts.pending_type === 'bank') { + query.where = { [Op.and]: [ + ...query.where[Op.and], { activated: true }, { id_data: { @@ -692,14 +623,6 @@ const getAllUsersAdmin = (opts = { } else { throw new Error('pending type is not defined. You need to select id or bank is pending_type'); } - } else { - query = { - where: {}, - attributes: { - exclude: ['password', 'is_admin', 'is_support', 'is_supervisor', 'is_kyc', 'is_communicator'] - }, - order: [ordering] - }; } if (!opts.format) { @@ -711,12 +634,7 @@ const getAllUsersAdmin = (opts = { return dbQuery.findAndCountAllWithRows('user', query) .then(async ({ count, data }) => { if (opts.id || opts.search) { - if (count === 0) { - // Need to throw error if query was for one user and the user is not found - const error = new Error(USER_NOT_FOUND); - error.status = 404; - throw error; - } else if (data[0].verification_level > 0 && data[0].network_id) { + if (count > 0 && data[0].verification_level > 0 && data[0].network_id) { const userNetworkData = await getNodeLib().getUser(data[0].network_id, { additionalHeaders: opts.additionalHeaders }); data[0].balance = userNetworkData.balance; data[0].wallet = userNetworkData.wallet; diff --git a/web/src/containers/Admin/ListUsers/FullList.js b/web/src/containers/Admin/ListUsers/FullList.js index 8a045db6d3..d1b08efa9a 100644 --- a/web/src/containers/Admin/ListUsers/FullList.js +++ b/web/src/containers/Admin/ListUsers/FullList.js @@ -6,6 +6,7 @@ import { Link } from 'react-router'; import { formatDate } from 'utils'; import { requestUsers } from './actions'; import AddUser from './AddUser'; +import UseFilters from './UserFilters'; import './index.css'; @@ -24,6 +25,8 @@ class FullListUsers extends Component { currentTablePage: 1, isRemaining: true, isVisible: false, + displayFilterModel: false, + filters: null, }; } @@ -36,8 +39,8 @@ class FullListUsers extends Component { loading: true, error: '', }); - - requestUsers({ page, limit }) + const { filters } = this.state; + requestUsers({ page, limit, ...(filters != null && filters) }) .then((res) => { let temp = page === 1 ? res.data : [...this.state.users, ...res.data]; let users = temp.sort((a, b) => { @@ -80,6 +83,13 @@ class FullListUsers extends Component { this.setState({ isVisible: false }); }; + applyFilters = (filters) => { + this.setState({ filters }, () => { + const { page, limit } = this.state; + this.requestFullUsers(page, limit); + }); + }; + render() { const renderLink = (value) => ( + +
+ { + this.setState({ displayFilterModel: value }); + }} + applyFilters={this.applyFilters} + />
); } diff --git a/web/src/containers/Admin/ListUsers/UserFilters.js b/web/src/containers/Admin/ListUsers/UserFilters.js new file mode 100644 index 0000000000..2d0618207a --- /dev/null +++ b/web/src/containers/Admin/ListUsers/UserFilters.js @@ -0,0 +1,333 @@ +import React, { useState } from 'react'; +import { + Button, + Input, + Modal, + Select, + message, + Slider, + Switch, + DatePicker, +} from 'antd'; +import { CloseOutlined } from '@ant-design/icons'; +import { DeleteOutlined } from '@ant-design/icons'; +import moment from 'moment'; + +const UseFilters = ({ + displayFilterModel, + setDisplayFilterModel, + applyFilters, +}) => { + const { Option } = Select; + const fieldKeyValue = { + id: { type: 'string', label: 'User ID' }, + email: { type: 'string', label: 'Email' }, + username: { type: 'string', label: 'User Name' }, + full_name: { type: 'string', label: 'Full Name' }, + pending: { type: 'boolean', label: 'Pending' }, + pending_type: { + type: 'dropdown', + label: 'Pending Type', + value: 'id', + options: [ + { label: 'id', value: 'id' }, + { label: 'bank', value: 'bank' }, + ], + }, + start_date: { type: 'date', label: 'User Creation Date Start' }, + end_date: { type: 'date', label: 'User Creation Date End' }, + dob_start_date: { type: 'date', label: 'User DOB Date Start' }, + dob_end_date: { type: 'date', label: 'User DOB Date End' }, + gender: { + type: 'dropdown', + label: 'Gender', + options: [ + { label: 'Male', value: 0 }, + { label: 'Female', value: 1 }, + ], + }, + nationality: { type: 'string', label: 'Nationality' }, + phone_number: { type: 'string', label: 'Phone Number' }, + crypto_wallet: { type: 'string', label: 'Crypto Wallet' }, + verification_level: { type: 'string', label: 'Verification Level' }, + email_verified: { type: 'boolean', label: 'Email Verified' }, + otp_enabled: { type: 'boolean', label: 'OTP Enabled' }, + is_admin: { type: 'boolean', label: 'Admin Role' }, + is_supervisor: { type: 'boolean', label: 'Supervisor Role' }, + is_support: { type: 'boolean', label: 'Support Role' }, + is_kyc: { type: 'boolean', label: 'Kyc Role' }, + is_communicator: { type: 'boolean', label: 'Communicator Role' }, + affiliation_rate: { type: 'string', label: 'Affiliation Rate' }, + }; + + const [showAddFilter, setShowAddFilter] = useState(false); + const [filters, setFilters] = useState([ + { field: 'id', type: 'string', label: 'User ID', value: null }, + ]); + + const [field, setField] = useState(); + const dateFormat = 'YYYY/MM/DD'; + + const goBack = () => { + setDisplayFilterModel(false); + }; + + const handleFilters = () => { + const queryFilters = {}; + + filters.forEach((filter) => { + if (filter.value != null && filter.value !== '') queryFilters[filter.field] = filter.value; + }); + + applyFilters(queryFilters); + setDisplayFilterModel(false); + }; + + return ( + <> + } + bodyStyle={{ + backgroundColor: '#27339D', + marginTop: 60, + }} + visible={showAddFilter} + footer={null} + onCancel={() => { + setShowAddFilter(false); + }} + > +
Select Field
+ +
+ + +
+ +
+ + +
+
+ } + bodyStyle={{ + backgroundColor: '#27339D', + }} + visible={displayFilterModel} + footer={null} + onCancel={() => { + setDisplayFilterModel(false); + }} + > +
Add Filters
+ + {filters.map((filter, index) => { + return ( +
+ + {filter.type === 'string' && ( + { + const newFilters = [...filters]; + newFilters[index].value = e.target.value; + setFilters(newFilters); + }} + style={{ marginTop: 10 }} + placeholder={filter.label} + /> + )} + {filter.type === 'range' && ( + { + const newFilters = [...filters]; + newFilters[index].value = e; + setFilters(newFilters); + }} + /> + )} + {filter.type === 'boolean' && ( + { + const newFilters = [...filters]; + newFilters[index].value = e; + setFilters(newFilters); + }} + /> + )} + {filter.type === 'dropdown' && ( + + )} + {filter.type === 'date' && ( + { + const newFilters = [...filters]; + newFilters[index].value = moment(dateString).format(); + setFilters(newFilters); + }} + format={dateFormat} + /> + )} +
+ ); + })} + +
+
+ +
+
+ +
+ + +
+
+ + ); +}; + +export default UseFilters; diff --git a/web/src/containers/Admin/User/index.js b/web/src/containers/Admin/User/index.js index 78b43c5558..afd4a85fa1 100644 --- a/web/src/containers/Admin/User/index.js +++ b/web/src/containers/Admin/User/index.js @@ -289,7 +289,7 @@ class App extends Component { ) : (
- + {/*

SEARCH FOR USER

- + */}

LIST OF ALL USERS

From da7179e808768f84d1c3f7056092ec2fa1bf3d70 Mon Sep 17 00:00:00 2001 From: fetok12 Date: Fri, 21 Apr 2023 01:18:29 +0300 Subject: [PATCH 008/234] Logic Refinements --- server/api/controllers/admin.js | 14 ------ server/api/swagger/admin.yaml | 29 ------------ server/utils/hollaex-tools-lib/tools/user.js | 21 +-------- .../containers/Admin/ListUsers/FullList.js | 2 +- .../containers/Admin/ListUsers/UserFilters.js | 7 --- web/src/containers/Admin/User/index.js | 45 ------------------- 6 files changed, 2 insertions(+), 116 deletions(-) diff --git a/server/api/controllers/admin.js b/server/api/controllers/admin.js index 6474d68721..8be7b09447 100644 --- a/server/api/controllers/admin.js +++ b/server/api/controllers/admin.js @@ -132,16 +132,9 @@ const getUsersAdmin = (req, res) => { dob_end_date, gender, nationality, - crypto_wallet, verification_level, email_verified, otp_enabled, - is_admin, - is_supervisor, - is_support, - is_kyc, - is_communicator, - affiliation_rate, phone_number } = req.swagger.params; @@ -179,16 +172,9 @@ const getUsersAdmin = (req, res) => { dob_end_date: dob_end_date.value, gender: gender.value, nationality: nationality.value, - crypto_wallet: crypto_wallet.value, verification_level: verification_level.value, email_verified: email_verified.value, otp_enabled: otp_enabled.value, - is_admin: is_admin.value, - is_supervisor: is_supervisor.value, - is_support: is_support.value, - is_kyc: is_kyc.value, - is_communicator: is_communicator.value, - affiliation_rate: affiliation_rate.value, phone_number: phone_number.value, additionalHeaders: { 'x-forwarded-for': req.headers['x-forwarded-for'] diff --git a/server/api/swagger/admin.yaml b/server/api/swagger/admin.yaml index b28e1ea070..bddd1cf228 100644 --- a/server/api/swagger/admin.yaml +++ b/server/api/swagger/admin.yaml @@ -1174,11 +1174,6 @@ paths: required: false type: string maxLength: 256 - - in: query - name: crypto_wallet - required: false - type: string - maxLength: 256 - in: query name: phone_number required: false @@ -1196,30 +1191,6 @@ paths: name: otp_enabled required: false type: boolean - - in: query - name: is_admin - required: false - type: boolean - - in: query - name: is_supervisor - required: false - type: boolean - - in: query - name: is_support - required: false - type: boolean - - in: query - name: is_kyc - required: false - type: boolean - - in: query - name: is_communicator - required: false - type: boolean - - in: query - name: affiliation_rate - required: false - type: boolean - in: query name: format description: Specify data format diff --git a/server/utils/hollaex-tools-lib/tools/user.js b/server/utils/hollaex-tools-lib/tools/user.js index 1efe61ee36..285fdaf042 100644 --- a/server/utils/hollaex-tools-lib/tools/user.js +++ b/server/utils/hollaex-tools-lib/tools/user.js @@ -526,16 +526,9 @@ const getAllUsersAdmin = (opts = { dob_end_date: null, gender: null, nationality: null, - crypto_wallet: null, verification_level: null, email_verified: null, otp_enabled: null, - is_admin: null, - is_supervisor: null, - is_support: null, - is_kyc: null, - is_communicator: null, - affiliation_rate: null, phone_number: null, additionalHeaders: null }) => { @@ -544,11 +537,6 @@ const getAllUsersAdmin = (opts = { gender, email_verified, otp_enabled, - is_admin, - is_supervisor, - is_support, - is_kyc, - is_communicator, dob_start_date, dob_end_date } = opts; @@ -565,11 +553,6 @@ const getAllUsersAdmin = (opts = { ...(email_verified != null && { email_verified }), ...(gender != null && { gender }), ...(otp_enabled != null && { otp_enabled }), - ...(is_admin != null && { is_admin }), - ...(is_supervisor != null && { is_supervisor }), - ...(is_support != null && { is_support }), - ...(is_kyc != null && { is_kyc }), - ...(is_communicator != null && { is_communicator }), [Op.and]: [], }, order: [ordering] @@ -577,7 +560,7 @@ const getAllUsersAdmin = (opts = { query.attributes = { exclude: ['balance', 'password', 'updated_at'] }; - Object.keys(pick(opts, ['email', 'nationality', 'username', 'full_name', 'phone_number', 'verification_level', 'affiliation_rate'])).forEach(key => { + Object.keys(pick(opts, ['email', 'nationality', 'username', 'full_name', 'phone_number', 'verification_level',])).forEach(key => { if(opts[key] != null) { query.where[Op.and].push( { @@ -589,8 +572,6 @@ const getAllUsersAdmin = (opts = { } }) - //TO DO: 'crypto_wallet', - if (isBoolean(opts.pending) && opts.pending) { query.order = [['updated_at', 'desc']]; diff --git a/web/src/containers/Admin/ListUsers/FullList.js b/web/src/containers/Admin/ListUsers/FullList.js index d1b08efa9a..0739f12958 100644 --- a/web/src/containers/Admin/ListUsers/FullList.js +++ b/web/src/containers/Admin/ListUsers/FullList.js @@ -168,7 +168,7 @@ class FullListUsers extends Component {
this.props.handleDownload({})} + onClick={() => this.props.handleDownload(this.state.filters)} > Download table diff --git a/web/src/containers/Admin/ListUsers/UserFilters.js b/web/src/containers/Admin/ListUsers/UserFilters.js index 2d0618207a..7ac156250b 100644 --- a/web/src/containers/Admin/ListUsers/UserFilters.js +++ b/web/src/containers/Admin/ListUsers/UserFilters.js @@ -48,16 +48,9 @@ const UseFilters = ({ }, nationality: { type: 'string', label: 'Nationality' }, phone_number: { type: 'string', label: 'Phone Number' }, - crypto_wallet: { type: 'string', label: 'Crypto Wallet' }, verification_level: { type: 'string', label: 'Verification Level' }, email_verified: { type: 'boolean', label: 'Email Verified' }, otp_enabled: { type: 'boolean', label: 'OTP Enabled' }, - is_admin: { type: 'boolean', label: 'Admin Role' }, - is_supervisor: { type: 'boolean', label: 'Supervisor Role' }, - is_support: { type: 'boolean', label: 'Support Role' }, - is_kyc: { type: 'boolean', label: 'Kyc Role' }, - is_communicator: { type: 'boolean', label: 'Communicator Role' }, - affiliation_rate: { type: 'string', label: 'Affiliation Rate' }, }; const [showAddFilter, setShowAddFilter] = useState(false); diff --git a/web/src/containers/Admin/User/index.js b/web/src/containers/Admin/User/index.js index afd4a85fa1..3eb8cd1603 100644 --- a/web/src/containers/Admin/User/index.js +++ b/web/src/containers/Admin/User/index.js @@ -289,51 +289,6 @@ class App extends Component { ) : (
- {/* -

SEARCH FOR USER

- - {userInformationList.length ? ( - { - return data.id; - }} - /> - ) : null} - - - -
- -
-
*/} -

LIST OF ALL USERS

From 7a3100ea0353925570ac01369bc8b8c56532f6e8 Mon Sep 17 00:00:00 2001 From: fetok12 Date: Wed, 24 May 2023 00:35:33 +0300 Subject: [PATCH 009/234] Fix Search --- server/utils/hollaex-tools-lib/tools/user.js | 34 ++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/server/utils/hollaex-tools-lib/tools/user.js b/server/utils/hollaex-tools-lib/tools/user.js index 285fdaf042..9c81354602 100644 --- a/server/utils/hollaex-tools-lib/tools/user.js +++ b/server/utils/hollaex-tools-lib/tools/user.js @@ -560,6 +560,40 @@ const getAllUsersAdmin = (opts = { query.attributes = { exclude: ['balance', 'password', 'updated_at'] }; + + if (opts.search) { + query.attributes = { + exclude: ['balance', 'password', 'updated_at'] + }; + if (opts.id) { + query.where.id = opts.id; + } + query.where = { + [Op.or]: [ + { + email: { + [Op.like]: `%${opts.search}%` + } + }, + { + username: { + [Op.like]: `%${opts.search}%` + } + }, + { + full_name: { + [Op.like]: `%${opts.search}%` + } + }, + { + phone_number: { + [Op.like]: `%${opts.search}%` + } + }, + getModel('sequelize').literal(`id_data ->> 'number'='${opts.search}'`) + ] + }; + } Object.keys(pick(opts, ['email', 'nationality', 'username', 'full_name', 'phone_number', 'verification_level',])).forEach(key => { if(opts[key] != null) { query.where[Op.and].push( From e4ece516fc357145de2d72bd3baa16070778871e Mon Sep 17 00:00:00 2001 From: fetok12 Date: Thu, 25 May 2023 23:52:22 +0300 Subject: [PATCH 010/234] Dynamic broker logic --- server/constants.js | 15 + .../20230525045574-add-dynamic-broker.js | 48 +++ server/db/models/broker.js | 23 ++ server/messages.js | 2 + .../utils/hollaex-tools-lib/tools/broker.js | 339 +++++++++--------- 5 files changed, 264 insertions(+), 163 deletions(-) create mode 100644 server/db/migrations/20230525045574-add-dynamic-broker.js diff --git a/server/constants.js b/server/constants.js index c6b30bb66b..d7f4451174 100644 --- a/server/constants.js +++ b/server/constants.js @@ -605,6 +605,21 @@ exports.VERIFY_STATUS = { }; // PLUGIN CONSTANTS END ------------------------------ to be moved +// BROKER CONSTANTS START + +exports.EXCHANGE_PLAN_INTERVAL_TIME = { + FIAT: 5, + BOOST: 5, + CRYPTO: 60 +}; +exports.EXCHANGE_PLAN_PRICE_SOURCE = { + FIAT: ['hollaex', 'binance', 'bitfinex', 'coinbase', 'kraken', 'uniswap'], + BOOST: ['hollaex', 'binance', 'bitfinex', 'coinbase', 'kraken', 'uniswap'], + CRYPTO: ['hollaex', 'binance'] +}; + +// BROKER CONSTANTS END + exports.CUSTOM_CSS = ` .topbar-wrapper img { content:url('${exports.GET_KIT_CONFIG().logo_image}}'); diff --git a/server/db/migrations/20230525045574-add-dynamic-broker.js b/server/db/migrations/20230525045574-add-dynamic-broker.js new file mode 100644 index 0000000000..4acd545e44 --- /dev/null +++ b/server/db/migrations/20230525045574-add-dynamic-broker.js @@ -0,0 +1,48 @@ +'use strict'; + +const TABLE = 'Brokers'; + +module.exports = { + async up(queryInterface, Sequelize) { + return Promise.all([ + queryInterface + .addColumn(TABLE, 'tracked_smybol', { + type: Sequelize.STRING, + allowNull: true + }), + queryInterface + .addColumn(TABLE, 'spread', { + type: Sequelize.INTEGER, + defaultValue: 0, + allowNull: true + }), + queryInterface + .addColumn(TABLE, 'multiplier', { + type: Sequelize.INTEGER, + defaultValue: 1, + allowNull: true + }), + queryInterface + .addColumn(TABLE, 'exchange_name', { + type: Sequelize.STRING, + allowNull: true + }), + queryInterface + .addColumn(TABLE, 'refresh_interval', { + type: Sequelize.INTEGER, + allowNull: true + }) + ]); + + }, + + async down(queryInterface, Sequelize) { + return Promise.all([ + queryInterface.removeColumn(TABLE, 'tracked_smybol'), + queryInterface.removeColumn(TABLE, 'spread'), + queryInterface.removeColumn(TABLE, 'multiplier'), + queryInterface.removeColumn(TABLE, 'exchange_name'), + queryInterface.removeColumn(TABLE, 'refresh_interval'), + ]); + } +}; diff --git a/server/db/models/broker.js b/server/db/models/broker.js index 9564492fae..cae0c7a3df 100644 --- a/server/db/models/broker.js +++ b/server/db/models/broker.js @@ -14,6 +14,10 @@ module.exports = function (sequelize, DataTypes) { type: DataTypes.STRING, allowNull: false, }, + tracked_smybol: { + type: DataTypes.STRING, + allowNull: true, + }, buy_price: { type: DataTypes.DOUBLE, allowNull: false, @@ -59,10 +63,29 @@ module.exports = function (sequelize, DataTypes) { defaultValue: 30, allowNull: true }, + spread: { + type: DataTypes.INTEGER, + defaultValue: 0, + allowNull: true + }, + multiplier: { + type: DataTypes.INTEGER, + defaultValue: 1, + allowNull: true + }, + exchange_name: { + type: DataTypes.STRING, + allowNull: true, + }, rebalancing_symbol: { type: DataTypes.STRING, allowNull: true, }, + refresh_interval: { + type: DataTypes.INTEGER, + defaultValue: 0, + allowNull: true + }, account: { type: DataTypes.JSONB, allowNull: true, diff --git a/server/messages.js b/server/messages.js index 085e4a8cda..3900eb7688 100644 --- a/server/messages.js +++ b/server/messages.js @@ -193,6 +193,8 @@ exports.BROKER_EXISTS = 'A deal for this symbol alreadys exists'; exports.BROKER_FORMULA_NOT_FOUND = 'Broker formula not found'; exports.SPREAD_MISSING = 'Spread is missing'; exports.MANUAL_BROKER_CREATE_ERROR = 'Manual broker cannot select an exchange'; +exports.DYNAMIC_BROKER_CREATE_ERROR = 'Cannot create a dynamic broker without required fields'; +exports.DYNAMIC_BROKER_EXCHANGE_PLAN_ERROR = 'Cannot create a dynamic broker with Basic plan'; exports.EXCHANGE_NOT_FOUND = 'Exchange not found'; exports.SYMBOL_NOT_FOUND = 'Symbol not found'; exports.INVALID_TOKEN_TYPE = 'invalid token type'; diff --git a/server/utils/hollaex-tools-lib/tools/broker.js b/server/utils/hollaex-tools-lib/tools/broker.js index a974580bda..d1268aa18e 100644 --- a/server/utils/hollaex-tools-lib/tools/broker.js +++ b/server/utils/hollaex-tools-lib/tools/broker.js @@ -6,7 +6,7 @@ const ccxt = require('ccxt'); const rp = require('request-promise'); const randomString = require('random-string'); const dbQuery = require('./database/query'); -const { SERVER_PATH } = require('../constants'); +const { SERVER_PATH, EXCHANGE_PLAN_INTERVAL_TIME, EXCHANGE_PLAN_PRICE_SOURCE } = require('../constants'); const { getNodeLib } = require(`${SERVER_PATH}/init`); const { client } = require('./database/redis'); const { getUserByKitId } = require('./user'); @@ -18,6 +18,9 @@ const { verifyBearerTokenPromise } = require('./security'); const { Op } = require('sequelize'); const { loggerBroker } = require('../../../config/logger'); +const connectedExchanges = {}; + + const { TOKEN_EXPIRED, AUTH_NOT_MATCHED, @@ -29,6 +32,8 @@ const { BROKER_FORMULA_NOT_FOUND, SPREAD_MISSING, MANUAL_BROKER_CREATE_ERROR, + DYNAMIC_BROKER_CREATE_ERROR, + DYNAMIC_BROKER_EXCHANGE_PLAN_ERROR, EXCHANGE_NOT_FOUND, SYMBOL_NOT_FOUND } = require(`${SERVER_PATH}/messages`); @@ -60,90 +65,165 @@ const getDecimals = (value = 0) => { return str.split('-')[1] || 0; }; -const binanceScript = async () => { - const BINANCE_URL = 'https://api3.binance.com/api/v3/ticker/price'; +const determineRefreshInterval = (plan) => { + if(plan === 'crypto') { + return EXCHANGE_PLAN_INTERVAL_TIME.CRYTO; + } + else if (plan === 'fiat') { + return EXCHANGE_PLAN_INTERVAL_TIME.FIAT; + } + else if (plan === 'boost') { + return EXCHANGE_PLAN_INTERVAL_TIME.BOOST; + } +} - // Get the price from redis - const formattedSymbol = symbol.split('-').join('').toUpperCase(); - const quotePrice = await client.getAsync('prices'); - - const runScript = (prices) => { - //Calculate the price - const foundSymbol = JSON.parse(prices).find((data) => data.symbol === formattedSymbol); - if (!foundSymbol) { - throw new Error('Pair not found'); - } - const baseCurrencyPrice = calculatePrice(foundSymbol.price, side, spread, multiplier); +const determinePriceSource = (plan) => { + if(plan === 'crypto') { + return EXCHANGE_PLAN_PRICE_SOURCE.CRYTO; + } + else if (plan === 'fiat') { + return EXCHANGE_PLAN_PRICE_SOURCE.FIAT; + } + else if (plan === 'boost') { + return EXCHANGE_PLAN_PRICE_SOURCE.BOOST; + } +} +const setExchange = (data) => { + if (connectedExchanges[data.id]) { + return connectedExchanges[data.id].exchange + } + const exchangeClass = ccxt[data.exchange]; + + const exchange = new exchangeClass({ + timeout: 5000, + ...(data.api_key && { 'apiKey': data.api_key }), + ...(data.api_secret && { 'secret': data.api_secret }), + }) + + if (data.id) { + connectedExchanges[data.id] = { exchange, api_key: data.api_key, api_secret: data.api_secret }; + } + + return exchange; +} - const decimalPoint = getDecimals(broker.increment_size); - const roundedPrice = math.round( - baseCurrencyPrice, - decimalPoint - ); +const getQuoteDynamicBroker = async (selectedExchange, side, broker, user_id = null, + orderData = { + spending_currency: null, + receiving_currency: null, + spending_amount: null, + receiving_amount: null + }) => { - const responseObject = { - price: roundedPrice - }; - //check if there is user_id, if so, assing token - if (user_id) { - - let size; - if (orderData) { - let { spending_currency, receiving_currency, spending_amount, receiving_amount } = orderData; - - if (spending_amount != null) { - const sourceAmount = math.round( - side === 'buy' ? spending_amount / responseObject.price : spending_amount * responseObject.price, - decimalPoint - ); - - receiving_amount = sourceAmount; - - } else if (receiving_amount != null) { - const sourceAmount = math.round( - side === 'buy' ? receiving_amount * responseObject.price : receiving_amount / responseObject.price, - decimalPoint - ); - spending_amount = sourceAmount; - } - - if (`${spending_currency}-${receiving_currency}` === symbol) { - size = spending_amount; - } else { - size = receiving_amount - } - } - // Generate randomToken to be used during deal execution - const randomToken = generateRandomToken(user_id, symbol, side, broker.quote_expiry_time, roundedPrice, size, 'broker'); - responseObject.token = randomToken; - // set expiry - const expiryDate = new Date(); - expiryDate.setSeconds(expiryDate.getSeconds() + broker.quote_expiry_time || 30); - responseObject.expiry = expiryDate; - } + const { symbol, tracked_symbol, spread, quote_expiry_time, refresh_interval } = broker; + // Get the price from redis + const formattedSymbol = tracked_symbol.split('-').join('').toUpperCase(); + const userCachekey = `${broker.id}-${symbol}`; + let marketTicker = await client.getAsync(userCachekey); + + if (!marketTicker) { + marketTicker = await selectedExchange.fetchTicker(formattedSymbol); + client.setexAsync(userCachekey, refresh_interval, marketTicker); + } + + //Calculate the price + const foundSymbol = JSON.parse(marketTicker); + if (!foundSymbol) { + throw new Error('Pair not found'); + } + const baseCurrencyPrice = calculatePrice(foundSymbol.last, side, spread); + + const decimalPoint = getDecimals(broker.increment_size); + const roundedPrice = math.round( + baseCurrencyPrice, + decimalPoint + ); - return responseObject; + const responseObject = { + price: roundedPrice }; - // If it doesn't exist, fetch all market from Binance - if (!quotePrice) { - return rp(BINANCE_URL) - .then((res) => { - //Store all market prices in Redis with 1 minute expiry time - //response is a stringfied object. - client.setexAsync('prices', 60, res); - - return runScript(res); - }) - .catch((err) => { - throw new Error(err); - }); - - } else { - return runScript(quotePrice); + //check if there is user_id, if so, assing token + if (user_id) { + + const size = calculateSize(orderData); + + // Generate randomToken to be used during deal execution + const randomToken = generateRandomToken(user_id, symbol, side, quote_expiry_time, roundedPrice, size, 'broker'); + responseObject.token = randomToken; + // set expiry + const expiryDate = new Date(); + expiryDate.setSeconds(expiryDate.getSeconds() + quote_expiry_time || 30); + responseObject.expiry = expiryDate; } + + return responseObject; + }; +const getQuoteManualBroker = async (broker, side, user_id = null, orderData = { + spending_currency: null, + receiving_currency: null, + spending_amount: null, + receiving_amount: null +}) => { + const { symbol, quote_expiry_time, sell_price, buy_price, increment_size } = broker; + + const baseCurrencyPrice = side === 'buy' ? sell_price : buy_price; + + const decimalPoint = getDecimals(increment_size); + const roundedPrice = math.round( + baseCurrencyPrice, + decimalPoint + ); + const responseObject = { + price: roundedPrice + }; + + const size = calculateSize(orderData); + + if (user_id) { + const randomToken = generateRandomToken(user_id, symbol, side, quote_expiry_time, roundedPrice, size, 'broker'); + responseObject.token = randomToken; + // set expiry + const expiryDate = new Date(); + expiryDate.setSeconds(expiryDate.getSeconds() + quote_expiry_time || 30); + responseObject.expiry = expiryDate; + } + return responseObject; +} + +const calculateSize = (orderData) => { + let size = null; + + if (orderData) { + let { spending_currency, receiving_currency, spending_amount, receiving_amount } = orderData + + if (spending_amount != null) { + const sourceAmount = math.round( + side === 'buy' ? spending_amount / responseObject.price : spending_amount * responseObject.price, + decimalPoint + ); + + receiving_amount = sourceAmount; + + } else if (receiving_amount != null) { + const sourceAmount = math.round( + side === 'buy' ? receiving_amount * responseObject.price : receiving_amount / responseObject.price, + decimalPoint + ); + spending_amount = sourceAmount; + } + + if (`${spending_currency}-${receiving_currency}` === symbol) { + size = spending_amount; + } else { + size = receiving_amount + } + } + return size; +} + const calculatePrice = (price, side, spread, multiplier = 1) => { // Calculate the price const multipliedPrice = parseFloat(price) * multiplier; @@ -203,66 +283,10 @@ const fetchBrokerQuote = async (brokerQuote) => { throw new Error(BROKER_PAUSED); } if (broker.type === 'dynamic') { - if (broker.formula) { - //Run formula - const resObject = _eval(broker.formula, 'formula', { - symbol, side, user_id, client, broker, calculatePrice, generateRandomToken, getDecimals, math, rp, orderData - }, true); - - return resObject; - - } else { - throw new Error(BROKER_FORMULA_NOT_FOUND); - } + const selectedExchange = setExchange(broker.exchange_name); + return getQuoteDynamicBroker(selectedExchange, side, broker, user_id, orderData); } else { - const baseCurrencyPrice = side === 'buy' ? broker.sell_price : broker.buy_price; - - const decimalPoint = getDecimals(broker.increment_size); - const roundedPrice = math.round( - baseCurrencyPrice, - decimalPoint - ); - const responseObject = { - price: roundedPrice - }; - - let size; - - if (orderData) { - let { spending_currency, receiving_currency, spending_amount, receiving_amount } = orderData - - if (spending_amount != null) { - const sourceAmount = math.round( - side === 'buy' ? spending_amount / responseObject.price : spending_amount * responseObject.price, - decimalPoint - ); - - receiving_amount = sourceAmount; - - } else if (receiving_amount != null) { - const sourceAmount = math.round( - side === 'buy' ? receiving_amount * responseObject.price : receiving_amount / responseObject.price, - decimalPoint - ); - spending_amount = sourceAmount; - } - - if (`${spending_currency}-${receiving_currency}` === symbol) { - size = spending_amount; - } else { - size = receiving_amount - } - } - - if (user_id) { - const randomToken = generateRandomToken(user_id, symbol, side, broker.quote_expiry_time, roundedPrice, size, 'broker'); - responseObject.token = randomToken; - // set expiry - const expiryDate = new Date(); - expiryDate.setSeconds(expiryDate.getSeconds() + broker.quote_expiry_time || 30); - responseObject.expiry = expiryDate; - } - return responseObject; + return await getQuoteManualBroker(broker, side, user_id, orderData); } } catch (err) { @@ -400,45 +424,34 @@ const createBrokerPair = async (brokerPair) => { if (deal) { throw new Error(BROKER_EXISTS); } + const exchangeInfo = toolsLib.getKitConfig(); + const { - formula, exchange_name, spread, - multiplier, - type + quote_expiry_time, + tracked_symbol, + type, } = brokerPair; - if (exchange_name && type === 'manual') { - throw new Error(MANUAL_BROKER_CREATE_ERROR); + if (type !== 'manual' && (!exchange_name || !spread || !quote_expiry_time || !refresh_interval || !tracked_symbol)) { + throw new Error(DYNAMIC_BROKER_CREATE_ERROR); } - if (exchange_name && !spread) { - throw new Error(SPREAD_MISSING); + if (type !== 'manual' && exchangeInfo.plan === 'basic') { + throw new Error(DYNAMIC_BROKER_EXCHANGE_PLAN_ERROR); + } + + if (type === 'dynamic' && !determinePriceSource(exchangeInfo.plan).includes(exchange_name)) { + throw new Error(DYNAMIC_BROKER_CREATE_ERROR); } - let adminFormula = null; - - if (type === 'dynamic') { - // If it is a custom script(users send their own formula) - if (formula) { - adminFormula = formula; - } - // If user selects a exchange - else if (exchange_name === 'binance') { - const binanceFormula = ` - const spread = ${spread}; - const multiplier = ${multiplier || 1}; - module.exports = (${binanceScript.toString()})() - `; - - adminFormula = binanceFormula; - } else { - throw new Error(EXCHANGE_NOT_FOUND); - } + if(type === 'dynamic') { + brokerPair.refresh_interval = determineRefreshInterval(exchangeInfo.plan); } + const newBrokerObject = { - ...brokerPair, - formula: adminFormula + ...brokerPair }; From a4533e65ddb6f66f022cd1c721157f584ea2da76 Mon Sep 17 00:00:00 2001 From: fetok12 Date: Fri, 26 May 2023 00:50:54 +0300 Subject: [PATCH 011/234] UI Dynamic Broker --- server/api/controllers/broker.js | 28 ++++++- server/api/swagger/broker.yaml | 25 ++++++ .../utils/hollaex-tools-lib/tools/broker.js | 10 ++- .../containers/Admin/Trades/Otcdeskpopup.js | 81 +++++++++++++++---- web/src/containers/Admin/Trades/actions.js | 2 + 5 files changed, 126 insertions(+), 20 deletions(-) diff --git a/server/api/controllers/broker.js b/server/api/controllers/broker.js index 1836c3906f..f4ce8136a5 100644 --- a/server/api/controllers/broker.js +++ b/server/api/controllers/broker.js @@ -39,6 +39,31 @@ const getBrokerQuote = (req, res) => { }); }; +const getTrackedExchangeMarkets = (req, res) => { + + loggerBroker.verbose( + req.uuid, + 'controllers/broker/getTrackedExchangeMarkets get', + req.auth + ); + const { + exchange_name + } = req.swagger.params; + + toolsLib.broker.fetchTrackedExchangeMarkets(exchange_name.value) + .then((data) => { + return res.json(data); + }) + .catch((err) => { + loggerBroker.error( + req.uuid, + 'controllers/broker/getTrackedExchangeMarkets err', + err.message + ); + return res.status(err.statusCode || 400).json({ message: errorMessageConverter(err) }); + }); +} + const createBrokerPair = (req, res) => { loggerBroker.verbose( req.uuid, @@ -350,5 +375,6 @@ module.exports = { updateBrokerPair, deleteBrokerPair, getBrokerPairs, - executeBrokerDeal + executeBrokerDeal, + getTrackedExchangeMarkets }; \ No newline at end of file diff --git a/server/api/swagger/broker.yaml b/server/api/swagger/broker.yaml index 6d06664c8e..dc4e3ba869 100644 --- a/server/api/swagger/broker.yaml +++ b/server/api/swagger/broker.yaml @@ -167,6 +167,31 @@ paths: description: Error schema: $ref: "#/definitions/MessageResponse" + /broker/market: + x-swagger-router-controller: broker + get: + operationId: getTrackedExchangeMarkets + description: Get tracked exchange markets + tags: + - Broker + parameters: + - in: query + name: exchange_name + description: exchange name + required: true + type: string + maxLength: 256 + responses: + 200: + description: Success + schema: + type: array + items: + type: object + default: + description: Error + schema: + $ref: "#/definitions/MessageResponse" /broker/test: x-swagger-router-controller: broker post: diff --git a/server/utils/hollaex-tools-lib/tools/broker.js b/server/utils/hollaex-tools-lib/tools/broker.js index d1268aa18e..08beb8effd 100644 --- a/server/utils/hollaex-tools-lib/tools/broker.js +++ b/server/utils/hollaex-tools-lib/tools/broker.js @@ -283,7 +283,7 @@ const fetchBrokerQuote = async (brokerQuote) => { throw new Error(BROKER_PAUSED); } if (broker.type === 'dynamic') { - const selectedExchange = setExchange(broker.exchange_name); + const selectedExchange = setExchange({ exchange: broker.exchange_name }); return getQuoteDynamicBroker(selectedExchange, side, broker, user_id, orderData); } else { return await getQuoteManualBroker(broker, side, user_id, orderData); @@ -549,6 +549,11 @@ const updateBrokerPair = async (id, data) => { }); }; +const fetchTrackedExchangeMarkets = async (exchange) => { + const selectedExchage = setExchange({ exchange }); + return selectedExchage.fetchMarkets(); +} + const deleteBrokerPair = async (id) => { const brokerPair = await getModel('broker').findOne({ where: { id } }); @@ -615,5 +620,6 @@ module.exports = { reverseTransaction, testBroker, testRebalance, - generateRandomToken + generateRandomToken, + fetchTrackedExchangeMarkets }; \ No newline at end of file diff --git a/web/src/containers/Admin/Trades/Otcdeskpopup.js b/web/src/containers/Admin/Trades/Otcdeskpopup.js index abe1d8a8df..5f24e785d2 100644 --- a/web/src/containers/Admin/Trades/Otcdeskpopup.js +++ b/web/src/containers/Admin/Trades/Otcdeskpopup.js @@ -19,7 +19,7 @@ import { import { STATIC_ICONS } from 'config/icons'; import Coins from '../Coins'; -import { createTestBroker, getBrokerConnect } from './actions'; +import { createTestBroker, getBrokerConnect, getTrackedExchangeMarkets } from './actions'; import Pophedge from './HedgeMarketPopup'; import { handleUpgrade } from 'utils/utils'; import { formatToCurrency } from 'utils/currency'; @@ -104,6 +104,9 @@ const Otcdeskpopup = ({ const [formula, setFormula] = useState(''); const [hedgeApi, setHedgeApi] = useState('bitmex'); const [selelctedPlatform, setSelectedPlatform] = useState('binance'); + const [selectedExchange, setSelectedExchange] = useState('binance'); + const [exchangeMarkets, setExchangeMarkets] = useState([]); + const [selectedMarket, setSelectedMarket] = useState(); const [apiData, setApi] = useState({}); const [spreadMul, setSpreadMul] = useState({}); const [MarketPop, SetMarketPop] = useState(false); @@ -684,7 +687,7 @@ const Otcdeskpopup = ({ value={previewData && previewData.type} > - + @@ -770,32 +773,76 @@ const Otcdeskpopup = ({ )} -
+ {/*
Select price source:
Coming soon for upgraded HollaEx operators.
-
- {/*
+
*/} + {
Platform price source
+
-
*/} +
} + + {
+
Track market price
+
+ + +
+
} + {!chainlink && !customlink && (
- {/*
+
+
Track market price
+
+ +
+
+
Market pair
*/} + />
)}
diff --git a/web/src/containers/Admin/Trades/actions.js b/web/src/containers/Admin/Trades/actions.js index 823f1ccc99..8bf4598795 100644 --- a/web/src/containers/Admin/Trades/actions.js +++ b/web/src/containers/Admin/Trades/actions.js @@ -54,6 +54,8 @@ export const createTestBroker = (values) => { return requestAuthenticated('/broker/test', options); }; +export const getTrackedExchangeMarkets = (exchange_name) => requestAuthenticated(`/broker/market?exchange_name=${exchange_name}`); + export const updateBroker = (values) => { const options = { method: 'PUT', From 309ae15b231684631afa755fa55b4df82765ba8c Mon Sep 17 00:00:00 2001 From: fetok12 Date: Fri, 26 May 2023 14:46:14 +0300 Subject: [PATCH 012/234] Update Otcdeskpopup.js --- .../containers/Admin/Trades/Otcdeskpopup.js | 210 ++++-------------- 1 file changed, 42 insertions(+), 168 deletions(-) diff --git a/web/src/containers/Admin/Trades/Otcdeskpopup.js b/web/src/containers/Admin/Trades/Otcdeskpopup.js index 5f24e785d2..ebae0e9c7b 100644 --- a/web/src/containers/Admin/Trades/Otcdeskpopup.js +++ b/web/src/containers/Admin/Trades/Otcdeskpopup.js @@ -823,174 +823,48 @@ const Otcdeskpopup = ({
} - {!chainlink && !customlink && ( -
-
-
Track market price
-
- -
-
-
-
Market pair
-
- -
-
- {!isUpgrade && ( -
-
Spread percentage
- - handleSpreadMul( - parseFloat(e.target.value), - 'spread' - ) - } - value={previewData && previewData.spread} - /> -
-
- -
-
- A large spread percentage will widen the - distance of your set best bid & ask price. A - smaller percentage will narrow the price spread. -
-
-
Multiplier
- - handleSpreadMul( - parseFloat(e.target.value), - 'multiplier' - ) - } - value={previewData && previewData.multiplier} - /> -
-
- -
-
- A multiplier will multiply the price source. For - example, inputting a 2 in the multiplier field - will set your deal's price 2x of the price - source. -
-
-
- Price quote expiry time (seconds) -
- - handleSpreadMul( - parseInt(e.target.value), - 'quote_expiry_time' - ) - } - value={ - previewData && previewData.quote_expiry_time - } - /> -
- )} -
- )} - {chainlink && !customlink && ( -
-
- Select from Defi price source -
-
- Coming soon for upgraded - HollaEx operators. -
-
- )} - {customlink && !chainlink && ( -
-
Formulate price and source
-