diff --git a/.github/workflows/deploy-lib.yml b/.github/workflows/deploy-lib.yml index 4712c1f..afc8de1 100644 --- a/.github/workflows/deploy-lib.yml +++ b/.github/workflows/deploy-lib.yml @@ -39,15 +39,16 @@ jobs: node-version: '22.x' registry-url: 'https://registry.npmjs.org' + - name: Update npm + run: npm install -g npm@latest + - name: Install dependencies and build working-directory: './libs/contract' run: npm i && npm run prepublish - name: Publish package on NPM working-directory: './libs/contract' - run: npm publish --provenance --access public - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: npm publish --access public send-telegram-message: name: Send Telegram message diff --git a/dev.Dockerfile b/dev.Dockerfile index b6c13cd..bb94857 100644 --- a/dev.Dockerfile +++ b/dev.Dockerfile @@ -18,7 +18,7 @@ RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | b ENV PATH="/root/.nvm/versions/node/v22.14.0/bin:${PATH}" # Установка Xray -RUN curl -L https://raw.githubusercontent.com/remnawave/scripts/main/scripts/install-latest-xray.sh | bash -s -- v25.6.8 +RUN curl -L https://raw.githubusercontent.com/remnawave/scripts/main/scripts/install-latest-xray.sh | bash -s -- v25.12.8 RUN mkdir -p /var/log/supervisor /var/lib/rnode/xray /app \ diff --git a/libs/contract/api/controllers/handler.ts b/libs/contract/api/controllers/handler.ts index 33c280a..6f213f0 100644 --- a/libs/contract/api/controllers/handler.ts +++ b/libs/contract/api/controllers/handler.ts @@ -3,6 +3,8 @@ export const HANDLER_CONTROLLER = 'handler' as const; export const HANDLER_ROUTES = { REMOVE_USER: 'remove-user', ADD_USER: 'add-user', + ADD_USERS: 'add-users', + REMOVE_USERS: 'remove-users', GET_INBOUND_USERS_COUNT: 'get-inbound-users-count', GET_INBOUND_USERS: 'get-inbound-users', } as const; diff --git a/libs/contract/api/routes.ts b/libs/contract/api/routes.ts index aa31c5f..17bbbb7 100644 --- a/libs/contract/api/routes.ts +++ b/libs/contract/api/routes.ts @@ -24,6 +24,8 @@ export const REST_API = { REMOVE_USER: `${ROOT}/${CONTROLLERS.HANDLER_CONTROLLER}/${CONTROLLERS.HANDLER_ROUTES.REMOVE_USER}`, GET_INBOUND_USERS_COUNT: `${ROOT}/${CONTROLLERS.HANDLER_CONTROLLER}/${CONTROLLERS.HANDLER_ROUTES.GET_INBOUND_USERS_COUNT}`, GET_INBOUND_USERS: `${ROOT}/${CONTROLLERS.HANDLER_CONTROLLER}/${CONTROLLERS.HANDLER_ROUTES.GET_INBOUND_USERS}`, + ADD_USERS: `${ROOT}/${CONTROLLERS.HANDLER_CONTROLLER}/${CONTROLLERS.HANDLER_ROUTES.ADD_USERS}`, + REMOVE_USERS: `${ROOT}/${CONTROLLERS.HANDLER_CONTROLLER}/${CONTROLLERS.HANDLER_ROUTES.REMOVE_USERS}`, }, VISION: { UNBLOCK_IP: `${CONTROLLERS.VISION_CONTROLLER}/${CONTROLLERS.VISION_ROUTES.UNBLOCK_IP}`, diff --git a/libs/contract/commands/handler/add-user.command.ts b/libs/contract/commands/handler/add-user.command.ts index b928390..aef264c 100644 --- a/libs/contract/commands/handler/add-user.command.ts +++ b/libs/contract/commands/handler/add-user.command.ts @@ -39,21 +39,9 @@ export namespace AddUserCommand { ivCheck: z.boolean(), }); - const BaseShadowsocks2022User = z.object({ - type: z.literal('shadowsocks2022'), - tag: z.string(), - username: z.string(), - key: z.string(), - }); - export const RequestSchema = z.object({ data: z.array( - z.discriminatedUnion('type', [ - BaseTrojanUser, - BaseVlessUser, - BaseShadowsocksUser, - BaseShadowsocks2022User, - ]), + z.discriminatedUnion('type', [BaseTrojanUser, BaseVlessUser, BaseShadowsocksUser]), ), hashData: z.object({ vlessUuid: z.string().uuid(), diff --git a/libs/contract/commands/handler/add-users.command.ts b/libs/contract/commands/handler/add-users.command.ts new file mode 100644 index 0000000..eb49c72 --- /dev/null +++ b/libs/contract/commands/handler/add-users.command.ts @@ -0,0 +1,57 @@ +import { z } from 'zod'; + +import { REST_API } from '../../api'; + +export namespace AddUsersCommand { + export const url = REST_API.HANDLER.ADD_USERS; + + const BaseTrojanUser = z.object({ + type: z.literal('trojan'), + tag: z.string(), + }); + + const BaseVlessUser = z.object({ + type: z.literal('vless'), + tag: z.string(), + flow: z.enum(['xtls-rprx-vision', '']), + }); + + const BaseShadowsocksUser = z.object({ + type: z.literal('shadowsocks'), + tag: z.string(), + }); + + export const RequestSchema = z.object({ + affectedInboundTags: z.array(z.string()), + users: z.array( + z.object({ + inboundData: z.array( + z.discriminatedUnion('type', [ + BaseTrojanUser, + BaseVlessUser, + BaseShadowsocksUser, + ]), + ), + + userData: z.object({ + userId: z.string(), + hashUuid: z.string().uuid(), + vlessUuid: z.string().uuid(), + trojanPassword: z.string(), + ssPassword: z.string(), + }), + }), + ), + }); + + export type Request = z.infer; + + export const ResponseSchema = z.object({ + response: z.object({ + success: z.boolean(), + error: z.string().nullable(), + }), + }); + + export type Response = z.infer; +} diff --git a/libs/contract/commands/handler/index.ts b/libs/contract/commands/handler/index.ts index aa919b3..c5eed12 100644 --- a/libs/contract/commands/handler/index.ts +++ b/libs/contract/commands/handler/index.ts @@ -1,4 +1,6 @@ export * from './add-user.command'; +export * from './add-users.command'; export * from './get-inbound-users-count.command'; export * from './get-inbound-users.command'; export * from './remove-user.command'; +export * from './remove-users.command'; diff --git a/libs/contract/commands/handler/remove-users.command.ts b/libs/contract/commands/handler/remove-users.command.ts new file mode 100644 index 0000000..93f6326 --- /dev/null +++ b/libs/contract/commands/handler/remove-users.command.ts @@ -0,0 +1,27 @@ +import { z } from 'zod'; + +import { REST_API } from '../../api'; + +export namespace RemoveUsersCommand { + export const url = REST_API.HANDLER.REMOVE_USERS; + + export const RequestSchema = z.object({ + users: z.array( + z.object({ + userId: z.string(), + hashUuid: z.string().uuid(), + }), + ), + }); + + export type Request = z.infer; + + export const ResponseSchema = z.object({ + response: z.object({ + success: z.boolean(), + error: z.string().nullable(), + }), + }); + + export type Response = z.infer; +} diff --git a/libs/contract/package.json b/libs/contract/package.json index 6a00327..04a6011 100644 --- a/libs/contract/package.json +++ b/libs/contract/package.json @@ -1,6 +1,6 @@ { "name": "@remnawave/node-contract", - "version": "0.6.1", + "version": "2.5.0", "description": "A node-contract library for Remnawave Panel", "main": "build/index.js", "types": "build/index.d.ts", diff --git a/package-lock.json b/package-lock.json index 77bad47..9b6b731 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,22 +1,22 @@ { "name": "@remnawave/node", - "version": "2.3.2", + "version": "2.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@remnawave/node", - "version": "2.3.2", + "version": "2.5.0", "license": "AGPL-3.0-only", "dependencies": { "@henrygd/semaphore": "0.1.0", "@kastov/body-parser-with-zstd": "2.2.2", - "@nestjs/common": "11.1.9", + "@nestjs/common": "11.1.10", "@nestjs/config": "4.0.2", - "@nestjs/core": "11.1.9", + "@nestjs/core": "11.1.10", "@nestjs/jwt": "11.0.2", "@nestjs/passport": "11.0.5", - "@nestjs/platform-express": "11.1.9", + "@nestjs/platform-express": "11.1.10", "@remnawave/hashed-set": "^0.0.4", "@remnawave/supervisord-nestjs": "0.1.1", "@remnawave/xtls-sdk": "0.8.0", @@ -39,7 +39,7 @@ "reflect-metadata": "0.2.2", "rxjs": "7.8.2", "semver": "^7.7.3", - "systeminformation": "^5.27.13", + "systeminformation": "^5.28.3", "table": "^6.9.0", "winston": "^3.19.0", "zod": "3.25.76" @@ -1175,12 +1175,12 @@ } }, "node_modules/@nestjs/common": { - "version": "11.1.9", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.9.tgz", - "integrity": "sha512-zDntUTReRbAThIfSp3dQZ9kKqI+LjgLp5YZN5c1bgNRDuoeLySAoZg46Bg1a+uV8TMgIRziHocglKGNzr6l+bQ==", + "version": "11.1.10", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.10.tgz", + "integrity": "sha512-NoBzJFtq1bzHGia5Q5NO1pJNpx530nupbEu/auCWOFCGL5y8Zo8kiG28EXTCDfIhQgregEtn1Cs6H8WSLUC8kg==", "license": "MIT", "dependencies": { - "file-type": "21.1.0", + "file-type": "21.1.1", "iterare": "1.2.1", "load-esm": "1.0.3", "tslib": "2.8.1", @@ -1221,9 +1221,9 @@ } }, "node_modules/@nestjs/core": { - "version": "11.1.9", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.9.tgz", - "integrity": "sha512-a00B0BM4X+9z+t3UxJqIZlemIwCQdYoPKrMcM+ky4z3pkqqG1eTWexjs+YXpGObnLnjtMPVKWlcZHp3adDYvUw==", + "version": "11.1.10", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.10.tgz", + "integrity": "sha512-LYpaacSb8X9dcRpeZxA7Mvi5Aozv11s6028ZNoVKY2j/fyThLd+xrkksg3u+sw7F8mipFaxS/LuVpoHQ/MrACg==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -1285,13 +1285,13 @@ } }, "node_modules/@nestjs/platform-express": { - "version": "11.1.9", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.9.tgz", - "integrity": "sha512-GVd3+0lO0mJq2m1kl9hDDnVrX3Nd4oH3oDfklz0pZEVEVS0KVSp63ufHq2Lu9cyPdSBuelJr9iPm2QQ1yX+Kmw==", + "version": "11.1.10", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.10.tgz", + "integrity": "sha512-B2kvhfY+pE41Y6MXuJs80T7yfYjXzqHkWVyZJ5CAa3nFN3X2OIca6RH+b+7l3wZ+4x1tgsv48Q2P8ZfrDqJWYQ==", "license": "MIT", "dependencies": { "cors": "2.8.5", - "express": "5.1.0", + "express": "5.2.1", "multer": "2.0.2", "path-to-regexp": "8.3.0", "tslib": "2.8.1" @@ -1850,14 +1850,13 @@ } }, "node_modules/@tokenizer/inflate": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.3.1.tgz", - "integrity": "sha512-4oeoZEBQdLdt5WmP/hx1KZ6D3/Oid/0cUb2nk4F0pTDAWy+KCH3/EnAkZF/bvckWo8I33EqBm01lIPgmgc8rCA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", "license": "MIT", "dependencies": { - "debug": "^4.4.1", - "fflate": "^0.8.2", - "token-types": "^6.0.0" + "debug": "^4.4.3", + "token-types": "^6.1.1" }, "engines": { "node": ">=18" @@ -4044,18 +4043,19 @@ } }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", + "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -4214,12 +4214,6 @@ "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", "license": "MIT" }, - "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", - "license": "MIT" - }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -4234,14 +4228,14 @@ } }, "node_modules/file-type": { - "version": "21.1.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.1.0.tgz", - "integrity": "sha512-boU4EHmP3JXkwDo4uhyBhTt5pPstxB6eEXKJBu2yu2l7aAMMm7QQYQEzssJmKReZYrFdFOJS8koVo6bXIBGDqA==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.1.1.tgz", + "integrity": "sha512-ifJXo8zUqbQ/bLbl9sFoqHNTNWbnPY1COImFfM6CCy7z+E+jC1eY9YfOKkx0fckIg+VljAy2/87T61fp0+eEkg==", "license": "MIT", "dependencies": { - "@tokenizer/inflate": "^0.3.1", - "strtok3": "^10.3.1", - "token-types": "^6.0.0", + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" }, "engines": { @@ -6570,25 +6564,29 @@ } }, "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { - "debug": "^4.3.5", + "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", - "statuses": "^2.0.1" + "statuses": "^2.0.2" }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/serialize-javascript": { @@ -6602,9 +6600,9 @@ } }, "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -6614,6 +6612,10 @@ }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/setprototypeof": { @@ -6931,9 +6933,9 @@ } }, "node_modules/systeminformation": { - "version": "5.27.13", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.27.13.tgz", - "integrity": "sha512-geeE/7eNDoOhdc9j+qCsLlwbcyh0HnqhOZzmfNK4WBioWGUZbhwYrg+YZsZ3UJh4tmybQsnDuqzr3UoumMifew==", + "version": "5.28.3", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.28.3.tgz", + "integrity": "sha512-crbaZrBH3TpTbqc0PKFqnUFHdAJOxV9UhF3KCGSrf+YP+SkoMHmxU2Nr9yIG2xgCr4645Z9Ec4GHQQQ7kGX/HA==", "license": "MIT", "os": [ "darwin", diff --git a/package.json b/package.json index 269120e..9f72514 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@remnawave/node", - "version": "2.3.2", + "version": "2.5.0", "description": "Remnawave Node", "private": false, "type": "commonjs", @@ -31,12 +31,12 @@ "dependencies": { "@henrygd/semaphore": "0.1.0", "@kastov/body-parser-with-zstd": "2.2.2", - "@nestjs/common": "11.1.9", + "@nestjs/common": "11.1.10", "@nestjs/config": "4.0.2", - "@nestjs/core": "11.1.9", + "@nestjs/core": "11.1.10", "@nestjs/jwt": "11.0.2", "@nestjs/passport": "11.0.5", - "@nestjs/platform-express": "11.1.9", + "@nestjs/platform-express": "11.1.10", "@remnawave/hashed-set": "^0.0.4", "@remnawave/supervisord-nestjs": "0.1.1", "@remnawave/xtls-sdk": "0.8.0", @@ -59,7 +59,7 @@ "reflect-metadata": "0.2.2", "rxjs": "7.8.2", "semver": "^7.7.3", - "systeminformation": "^5.27.13", + "systeminformation": "^5.28.3", "table": "^6.9.0", "winston": "^3.19.0", "zod": "3.25.76" diff --git a/src/modules/handler/dtos/add-users.dto.ts b/src/modules/handler/dtos/add-users.dto.ts new file mode 100644 index 0000000..2702da5 --- /dev/null +++ b/src/modules/handler/dtos/add-users.dto.ts @@ -0,0 +1,6 @@ +import { createZodDto } from 'nestjs-zod'; + +import { AddUsersCommand } from '@libs/contracts/commands/handler'; + +export class AddUsersRequestDto extends createZodDto(AddUsersCommand.RequestSchema) {} +export class AddUsersResponseDto extends createZodDto(AddUsersCommand.ResponseSchema) {} diff --git a/src/modules/handler/dtos/index.ts b/src/modules/handler/dtos/index.ts index d704e78..0bda0c1 100644 --- a/src/modules/handler/dtos/index.ts +++ b/src/modules/handler/dtos/index.ts @@ -1,4 +1,6 @@ export * from './add-user.dto'; +export * from './add-users.dto'; export * from './get-inbound-users-count.dto'; export * from './get-inbound-users.dto'; export * from './remove-user.dto'; +export * from './remove-users.dto'; diff --git a/src/modules/handler/dtos/remove-users.dto.ts b/src/modules/handler/dtos/remove-users.dto.ts new file mode 100644 index 0000000..76bc229 --- /dev/null +++ b/src/modules/handler/dtos/remove-users.dto.ts @@ -0,0 +1,6 @@ +import { createZodDto } from 'nestjs-zod'; + +import { RemoveUsersCommand } from '@libs/contracts/commands/handler'; + +export class RemoveUsersRequestDto extends createZodDto(RemoveUsersCommand.RequestSchema) {} +export class RemoveUsersResponseDto extends createZodDto(RemoveUsersCommand.ResponseSchema) {} diff --git a/src/modules/handler/handler.controller.ts b/src/modules/handler/handler.controller.ts index d6e176a..76530ef 100644 --- a/src/modules/handler/handler.controller.ts +++ b/src/modules/handler/handler.controller.ts @@ -5,6 +5,14 @@ import { HttpExceptionFilter } from '@common/exception'; import { errorHandler } from '@common/helpers'; import { HANDLER_CONTROLLER, HANDLER_ROUTES } from '@libs/contracts/api/controllers/handler'; +import { + AddUsersRequestDto, + AddUsersResponseDto, + RemoveUserRequestDto, + RemoveUserResponseDto, + RemoveUsersRequestDto, + RemoveUsersResponseDto, +} from './dtos'; import { GetInboundUsersCountRequestDto, GetInboundUsersCountResponseDto, @@ -14,7 +22,6 @@ import { GetInboundUsersResponseDto, } from './dtos/get-inbound-users.dto'; import { AddUserRequestDto, AddUserResponseDto } from './dtos/add-user.dto'; -import { RemoveUserRequestDto, RemoveUserResponseDto } from './dtos'; import { HandlerService } from './handler.service'; @UseFilters(HttpExceptionFilter) @@ -66,4 +73,24 @@ export class HandlerController { response: data, }; } + + @Post(HANDLER_ROUTES.ADD_USERS) + public async addUsers(@Body() body: AddUsersRequestDto): Promise { + const response = await this.handlerService.addUsers(body); + const data = errorHandler(response); + + return { + response: data, + }; + } + + @Post(HANDLER_ROUTES.REMOVE_USERS) + public async removeUsers(@Body() body: RemoveUsersRequestDto): Promise { + const response = await this.handlerService.removeUsers(body); + const data = errorHandler(response); + + return { + response: data, + }; + } } diff --git a/src/modules/handler/handler.service.ts b/src/modules/handler/handler.service.ts index 6eed4a9..aecc8eb 100644 --- a/src/modules/handler/handler.service.ts +++ b/src/modules/handler/handler.service.ts @@ -1,3 +1,5 @@ +import ems from 'enhanced-ms'; + import { Injectable, Logger } from '@nestjs/common'; import { @@ -10,6 +12,7 @@ import { XtlsApi } from '@remnawave/xtls-sdk'; import { ICommandResponse } from '@common/types/command-response.type'; import { ERRORS } from '@libs/contracts/constants/errors'; +import { CipherType } from '@libs/contracts/commands'; import { GetInboundUsersCountResponseModel, @@ -17,7 +20,12 @@ import { AddUserResponseModel, RemoveUserResponseModel, } from './models'; -import { IRemoveUserRequest, TAddUserRequest } from './interfaces'; +import { + AddUserRequestDto, + AddUsersRequestDto, + RemoveUserRequestDto, + RemoveUsersRequestDto, +} from './dtos'; import { InternalService } from '../internal/internal.service'; @Injectable() @@ -29,7 +37,7 @@ export class HandlerService { private readonly internalService: InternalService, ) {} - public async addUser(data: TAddUserRequest): Promise> { + public async addUser(data: AddUserRequestDto): Promise> { try { const { data: requestData, hashData } = data; const response: Array> = []; @@ -137,7 +145,7 @@ export class HandlerService { } public async removeUser( - data: IRemoveUserRequest, + data: RemoveUserRequestDto, ): Promise> { try { const { username, hashData } = data; @@ -190,6 +198,178 @@ export class HandlerService { } } + public async addUsers( + data: AddUsersRequestDto, + ): Promise> { + const tm = performance.now(); + try { + const { affectedInboundTags, users } = data; + + for (const tag of affectedInboundTags) { + this.internalService.addXtlsConfigInbound(tag); + } + + this.logger.log( + `Adding ${users.length} users to inbounds: ${affectedInboundTags.join(', ')}`, + ); + + for (const user of users) { + for (const tag of this.internalService.getXtlsConfigInbounds()) { + await this.xtlsApi.handler.removeUser(tag, user.userData.userId); + + await this.internalService.removeUserFromInbound(tag, user.userData.hashUuid); + } + + for (const item of user.inboundData) { + let tempRes = null; + + switch (item.type) { + case 'trojan': + tempRes = await this.xtlsApi.handler.addTrojanUser({ + tag: item.tag, + username: user.userData.userId, + password: user.userData.trojanPassword, + level: 0, + }); + if (tempRes.isOk) { + await this.internalService.addUserToInbound( + item.tag, + user.userData.vlessUuid, + ); + } + + break; + case 'vless': + tempRes = await this.xtlsApi.handler.addVlessUser({ + tag: item.tag, + username: user.userData.userId, + uuid: user.userData.vlessUuid, + flow: item.flow, + level: 0, + }); + if (tempRes.isOk) { + await this.internalService.addUserToInbound( + item.tag, + user.userData.vlessUuid, + ); + } + break; + case 'shadowsocks': + tempRes = await this.xtlsApi.handler.addShadowsocksUser({ + tag: item.tag, + username: user.userData.userId, + password: user.userData.ssPassword, + cipherType: CipherType.CHACHA20_POLY1305, + ivCheck: false, + level: 0, + }); + if (tempRes.isOk) { + await this.internalService.addUserToInbound( + item.tag, + user.userData.vlessUuid, + ); + } + break; + } + } + } + + return { + isOk: true, + response: new AddUserResponseModel(true, null), + }; + } catch (error) { + this.logger.error(error); + let message = ''; + if (error instanceof Error) { + message = error.message; + } + return { + isOk: false, + code: ERRORS.INTERNAL_SERVER_ERROR.code, + response: new AddUserResponseModel(false, message), + }; + } finally { + this.logger.log( + 'Users addition took: ' + + ems(performance.now() - tm, { + extends: 'short', + includeMs: true, + }), + ); + } + } + + public async removeUsers( + data: RemoveUsersRequestDto, + ): Promise> { + const tm = performance.now(); + try { + const inboundTags = this.internalService.getXtlsConfigInbounds(); + + if (inboundTags.size === 0) { + return { + isOk: true, + response: new RemoveUserResponseModel(true, null), + }; + } + + this.logger.log( + `Removing ${data.users.length} users from inbounds: ${Array.from(inboundTags).join(', ')}`, + ); + + const removeUsersResponse: Array> = []; + + for (const user of data.users) { + const { userId, hashUuid } = user; + + for (const tag of inboundTags) { + this.logger.debug(`Removing user: ${userId} from tag: ${tag}`); + + const tempRes = await this.xtlsApi.handler.removeUser(tag, userId); + + await this.internalService.removeUserFromInbound(tag, hashUuid); + removeUsersResponse.push(tempRes); + } + } + + if (removeUsersResponse.every((res) => !res.isOk)) { + this.logger.error(JSON.stringify(removeUsersResponse, null, 2)); + return { + isOk: true, + response: new RemoveUserResponseModel( + false, + removeUsersResponse.find((res) => !res.isOk)?.message ?? null, + ), + }; + } + + return { + isOk: true, + response: new RemoveUserResponseModel(true, null), + }; + } catch (error: unknown) { + this.logger.error(error); + let message = ''; + if (error instanceof Error) { + message = error.message; + } + return { + isOk: false, + code: ERRORS.INTERNAL_SERVER_ERROR.code, + response: new RemoveUserResponseModel(false, message), + }; + } finally { + this.logger.log( + 'Users removal took: ' + + ems(performance.now() - tm, { + extends: 'short', + includeMs: true, + }), + ); + } + } + public async getInboundUsers( tag: string, ): Promise> { diff --git a/src/modules/handler/interfaces/add-user.interface.ts b/src/modules/handler/interfaces/add-user.interface.ts deleted file mode 100644 index b368251..0000000 --- a/src/modules/handler/interfaces/add-user.interface.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { CipherType } from '@remnawave/xtls-sdk/build/src/xray-protos/proxy/shadowsocks/config'; - -export interface TAddUserRequest { - hashData: { - vlessUuid: string; - prevVlessUuid?: string; - }; - data: Array< - | { - cipherType: CipherType; - ivCheck: boolean; - password: string; - tag: string; - type: 'shadowsocks'; - username: string; - } - | { - flow: '' | 'xtls-rprx-vision'; - tag: string; - type: 'vless'; - username: string; - uuid: string; - } - | { - http_password: string; - http_username: string; - tag: string; - type: 'http'; - username: string; - } - | { - key: string; - tag: string; - type: 'shadowsocks2022'; - username: string; - } - | { - password: string; - tag: string; - type: 'trojan'; - username: string; - } - | { - socks_password: string; - socks_username: string; - tag: string; - type: 'socks'; - username: string; - } - >; -} diff --git a/src/modules/handler/interfaces/index.ts b/src/modules/handler/interfaces/index.ts deleted file mode 100644 index 5164d04..0000000 --- a/src/modules/handler/interfaces/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './add-user.interface'; -export * from './remove-user.interface'; diff --git a/src/modules/handler/interfaces/remove-user.interface.ts b/src/modules/handler/interfaces/remove-user.interface.ts deleted file mode 100644 index 24ca0c9..0000000 --- a/src/modules/handler/interfaces/remove-user.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface IRemoveUserRequest { - username: string; - hashData: { - vlessUuid: string; - }; -}