diff --git a/.docker/centrifugo/config.json b/.docker/centrifugo/config.json index 9ac01b5..e5ab7be 100644 --- a/.docker/centrifugo/config.json +++ b/.docker/centrifugo/config.json @@ -6,20 +6,16 @@ ], "publish": true, "proxy_publish": true, - "proxy_subscribe": true, "proxy_connect": true, - "allow_subscribe_for_client": true, "address": "0.0.0.0", "port": 8089, "grpc_api": true, "grpc_api_address": "0.0.0.0", "grpc_api_port": 10000, - "proxy_connect_endpoint": "grpc://api:10001", + "proxy_connect_endpoint": "grpc://buggregator-api:10001", "proxy_connect_timeout": "10s", - "proxy_publish_endpoint": "grpc://api:10001", + "proxy_publish_endpoint": "grpc://buggregator-api:10001", "proxy_publish_timeout": "10s", - "proxy_subscribe_endpoint": "grpc://api:10001", - "proxy_subscribe_timeout": "10s", - "proxy_rpc_endpoint": "grpc://api:10001", + "proxy_rpc_endpoint": "grpc://buggregator-api:10001", "proxy_rpc_timeout": "10s" } diff --git a/app/.rr-prod.yaml b/app/.rr-prod.yaml index 1703b2a..d4f911a 100644 --- a/app/.rr-prod.yaml +++ b/app/.rr-prod.yaml @@ -15,12 +15,15 @@ http: address: '0.0.0.0:8000' middleware: - gzip - - static - static: - dir: public - forbid: - - .php - - .htaccess + - headers + headers: + cors: + allowed_origin: ${RR_CORS_ALLOWED_ORIGIN:-*} + allowed_headers: ${RR_CORS_ALLOWED_HEADERS:-*} + allowed_methods: ${RR_CORS_ALLOWED_METHODS:-GET,POST,PUT,DELETE} + allow_credentials: false + exposed_headers: ${RR_CORS_EXPOSED_HEADERS:-Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,Content-Disposition,X-RateLimit-Remaining,X-RateLimit-Retry-After,X-RateLimit-Limit,X-Captcha} + max_age: 600 pool: num_workers: 4 supervisor: diff --git a/app/app/config/broadcasting.php b/app/app/config/broadcasting.php new file mode 100644 index 0000000..d31fdd6 --- /dev/null +++ b/app/app/config/broadcasting.php @@ -0,0 +1,18 @@ + env('BROADCAST_CONNECTION', 'centrifugo'), + 'aliases' => [], + 'connections' => [ + 'centrifugo' => [ + 'driver' => 'centrifugo', + ], + 'null' => [ + 'driver' => NullBroadcast::class, + ], + ], +]; diff --git a/app/app/config/cache.php b/app/app/config/cache.php index 25eada2..db5120b 100644 --- a/app/app/config/cache.php +++ b/app/app/config/cache.php @@ -5,7 +5,9 @@ return [ 'default' => env('CACHE_STORAGE', 'rr'), - 'aliases' => [], + 'aliases' => [ + 'github' => 'rr', + ], 'storages' => [ 'rr' => [ diff --git a/app/app/config/centrifugo.php b/app/app/config/centrifugo.php new file mode 100644 index 0000000..19d5720 --- /dev/null +++ b/app/app/config/centrifugo.php @@ -0,0 +1,17 @@ + [ + RequestType::Connect->value => ConnectService::class, + RequestType::RPC->value => RPCService::class, + ], + 'interceptors' => [ + '*' => [], + ], +]; diff --git a/app/app/config/events.php b/app/app/config/events.php new file mode 100644 index 0000000..e3efc65 --- /dev/null +++ b/app/app/config/events.php @@ -0,0 +1,9 @@ + [ + \App\Application\Broadcasting\BroadcastEventInterceptor::class + ] +]; diff --git a/app/app/src/Application/Bootloader/GithubBootloader.php b/app/app/src/Application/Bootloader/GithubBootloader.php new file mode 100644 index 0000000..45e57ed --- /dev/null +++ b/app/app/src/Application/Bootloader/GithubBootloader.php @@ -0,0 +1,34 @@ + static fn(CacheStorageProviderInterface $cache) => new Client( + client: new \GuzzleHttp\Client([ + 'base_uri' => 'https://api.github.com/', + 'headers' => [ + 'Accept' => 'application/vnd.github.v3+json', + ], + ]), + cache: $cache, + ), + + WebhookGate::class => static fn( + EnvironmentInterface $env, + ) => new WebhookGate(secret: $env->get('GITHUB_WEBHOOK_SECRET', 'secret')), + ]; + } +} diff --git a/app/app/src/Application/Bootloader/RoutesBootloader.php b/app/app/src/Application/Bootloader/RoutesBootloader.php index 2836e2a..03cf099 100644 --- a/app/app/src/Application/Bootloader/RoutesBootloader.php +++ b/app/app/src/Application/Bootloader/RoutesBootloader.php @@ -4,19 +4,15 @@ namespace App\Application\Bootloader; -use Nyholm\Psr7\Stream; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; use Spiral\Bootloader\Http\RoutesBootloader as BaseRoutesBootloader; use Spiral\Cookies\Middleware\CookiesMiddleware; use Spiral\Csrf\Middleware\CsrfMiddleware; -use Spiral\Debug\Middleware\DumperMiddleware; use Spiral\Debug\StateCollector\HttpCollector; use Spiral\Filter\ValidationHandlerMiddleware; use Spiral\Http\Middleware\ErrorHandlerMiddleware; use Spiral\Http\Middleware\JsonPayloadMiddleware; use Spiral\Router\Bootloader\AnnotatedRoutesBootloader; -use Spiral\Router\Loader\Configurator\RoutingConfigurator; +use Spiral\Router\GroupRegistry; use Spiral\Session\Middleware\SessionMiddleware; /** @@ -57,13 +53,8 @@ protected function middlewareGroups(): array ]; } - protected function defineRoutes(RoutingConfigurator $routes): void + protected function configureRouteGroups(GroupRegistry $groups): void { - $routes->default('/') - ->callable(function (ServerRequestInterface $r, ResponseInterface $response) { - return $response - ->withStatus(404) - ->withBody(Stream::create('Not found')); - }); + $groups->getGroup('api')->setPrefix('/api'); } } diff --git a/app/app/src/Application/Broadcasting/BroadcastEventInterceptor.php b/app/app/src/Application/Broadcasting/BroadcastEventInterceptor.php new file mode 100644 index 0000000..2e2d02c --- /dev/null +++ b/app/app/src/Application/Broadcasting/BroadcastEventInterceptor.php @@ -0,0 +1,35 @@ +callAction($controller, $action, $parameters); + + if ($event instanceof ShouldBroadcastInterface) { + $this->broadcast->publish( + $event->getBroadcastTopics(), + \json_encode([ + 'event' => $event->getEventName(), + 'data' => $event->jsonSerialize(), + ], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE), + ); + } + + return $result; + } +} diff --git a/app/app/src/Application/Broadcasting/Channel/Channel.php b/app/app/src/Application/Broadcasting/Channel/Channel.php new file mode 100644 index 0000000..eeca81c --- /dev/null +++ b/app/app/src/Application/Broadcasting/Channel/Channel.php @@ -0,0 +1,17 @@ +name; + } +} diff --git a/app/app/src/Application/Broadcasting/Channel/EventsChannel.php b/app/app/src/Application/Broadcasting/Channel/EventsChannel.php new file mode 100644 index 0000000..9bc8487 --- /dev/null +++ b/app/app/src/Application/Broadcasting/Channel/EventsChannel.php @@ -0,0 +1,13 @@ +respond( + new ConnectResponse( + user: (string)$request->getAttribute('user_id'), + channels: ['events'], + ), + ); + } catch (\Throwable $e) { + $request->error($e->getCode(), $e->getMessage()); + } + } +} diff --git a/app/app/src/Endpoint/Centrifugo/RPCService.php b/app/app/src/Endpoint/Centrifugo/RPCService.php new file mode 100644 index 0000000..1e25aac --- /dev/null +++ b/app/app/src/Endpoint/Centrifugo/RPCService.php @@ -0,0 +1,80 @@ +http->handle( + $this->createHttpRequest($request), + ); + + $result = \json_decode((string)$response->getBody(), true); + $result['code'] = $response->getStatusCode(); + } catch (ValidationException $e) { + $result['code'] = $e->getCode(); + $result['errors'] = $e->errors; + $result['message'] = $e->getMessage(); + } catch (\Throwable $e) { + $result['code'] = $e->getCode(); + $result['message'] = $e->getMessage(); + } + + try { + $request->respond( + new RPCResponse( + data: $result, + ), + ); + } catch (\Throwable $e) { + $request->error($e->getCode(), $e->getMessage()); + } + } + + public function createHttpRequest(Request\RPC $request): ServerRequestInterface + { + [$method, $uri] = \explode(':', $request->method, 2); + $method = \strtoupper($method); + + $httpRequest = $this->requestFactory->createServerRequest(\strtoupper($method), \ltrim($uri, '/')) + ->withHeader('Content-Type', 'application/json'); + + $data = $request->getData(); + + $token = $data['token'] ?? null; + unset($data['token']); + + $httpRequest = match ($method) { + 'GET', 'HEAD' => $httpRequest->withQueryParams($data), + 'POST', 'PUT', 'DELETE' => $httpRequest->withParsedBody($data), + default => throw new \InvalidArgumentException('Unsupported method'), + }; + + if (!empty($token)) { + $httpRequest = $httpRequest->withHeader('X-Auth-Token', $token); + } + + return $httpRequest; + } +} diff --git a/app/app/src/Endpoint/Http/Controller/GithubWebhookAction.php b/app/app/src/Endpoint/Http/Controller/GithubWebhookAction.php new file mode 100644 index 0000000..9f09b25 --- /dev/null +++ b/app/app/src/Endpoint/Http/Controller/GithubWebhookAction.php @@ -0,0 +1,74 @@ +validate((string)$input->request()->getBody(), $input->header('x-hub-signature-256'))) { + // throw new ForbiddenException('Invalid signature'); + } + + $event = match ($input->header('x-github-event')) { + 'star' => $this->handleStar($input), + 'release' => $this->handleRelease($input), + default => null, + }; + + if ($event !== null) { + $client->clearCache($input->data('repository.full_name')); + $this->events->dispatch($event); + } + + return $response->create(200); + } + + private function handleStar(InputManager $input): ?object + { + $action = $input->data('action'); + + return match ($action) { + 'created' => new RepositoryStarred( + repository: $input->data('repository.name'), + stars: $input->data('repository.stargazers_count'), + ), + default => null, + }; + } + + private function handleRelease(InputManager $input): ?object + { + $action = $input->data('action'); + + return match ($action) { + 'published' => new NewRelease( + repository: $input->data('repository.name'), + version: $input->data('release.tag_name'), + ), + default => null, + }; + } +} diff --git a/app/app/src/Endpoint/Http/Controller/SettingsAction.php b/app/app/src/Endpoint/Http/Controller/SettingsAction.php new file mode 100644 index 0000000..d8dada5 --- /dev/null +++ b/app/app/src/Endpoint/Http/Controller/SettingsAction.php @@ -0,0 +1,30 @@ + [ + 'server' => [ + 'stars' => $client->getStars('buggregator/server'), + 'last_version' => $client->getLastVersion('buggregator/server'), + ], + 'trap' => [ + 'stars' => $client->getStars('buggregator/trap'), + 'last_version' => $client->getLastVersion('buggregator/trap'), + ], + ], + ]); + } +} diff --git a/app/app/src/Endpoint/Http/Resource/SettingsResource.php b/app/app/src/Endpoint/Http/Resource/SettingsResource.php new file mode 100644 index 0000000..f1f0156 --- /dev/null +++ b/app/app/src/Endpoint/Http/Resource/SettingsResource.php @@ -0,0 +1,12 @@ +cache = $cache->storage('github'); + } + + public function getStars(string $repository): int + { + $cacheKey = $this->getCacheKey($repository, __METHOD__); + if ($this->cache->has($cacheKey)) { + return $this->cache->get($cacheKey); + } + + $response = $this->client->sendRequest( + new Request( + 'GET', + "repos/{$repository}", + ), + ); + + $data = \json_decode($response->getBody()->getContents(), true); + $this->cache->set($cacheKey, $data['stargazers_count']); + + return $data['stargazers_count']; + } + + public function getLastVersion(string $repository): string + { + $cacheKey = $this->getCacheKey($repository, __METHOD__); + if ($this->cache->has($cacheKey)) { + return $this->cache->get($cacheKey); + } + + $response = $this->client->sendRequest( + new Request( + 'GET', + "repos/{$repository}/releases/latest", + ), + ); + + $data = \json_decode($response->getBody()->getContents(), true); + $this->cache->set($cacheKey, $data['tag_name']); + + return $data['tag_name']; + } + + private function getCacheKey(string $repository, string $prefix): string + { + return "{$prefix}:{$repository}"; + } + + public function clearCache(string $repository): void + { + $refl = new \ReflectionClass($this); + foreach ($refl->getMethods() as $method) { + if ($method->isPublic()) { + $this->cache->delete($this->getCacheKey($repository, $method->getName())); + } + } + } +} diff --git a/app/app/src/Github/ClientInterface.php b/app/app/src/Github/ClientInterface.php new file mode 100644 index 0000000..92e2998 --- /dev/null +++ b/app/app/src/Github/ClientInterface.php @@ -0,0 +1,14 @@ + $this->repository, + 'version' => $this->version, + ]; + } +} diff --git a/app/app/src/Github/Event/RepositoryStarred.php b/app/app/src/Github/Event/RepositoryStarred.php new file mode 100644 index 0000000..ed0bc7a --- /dev/null +++ b/app/app/src/Github/Event/RepositoryStarred.php @@ -0,0 +1,36 @@ + $this->repository, + 'stars' => $this->stars, + ]; + } +} diff --git a/app/app/src/Github/WebhookGate.php b/app/app/src/Github/WebhookGate.php new file mode 100644 index 0000000..aca8775 --- /dev/null +++ b/app/app/src/Github/WebhookGate.php @@ -0,0 +1,21 @@ +secret), + \substr($signature, 7), + ); + } +} diff --git a/app/composer.json b/app/composer.json index 91b9c4c..483e926 100644 --- a/app/composer.json +++ b/app/composer.json @@ -10,16 +10,18 @@ }, "require": { "php": ">=8.2", - "ext-sockets": "*", "ext-mbstring": "*", - "spiral/framework": "^3.12", - "spiral/roadrunner-cli": "^2.5", - "spiral/nyholm-bridge": "^1.3", - "spiral/roadrunner-bridge": "^3.0", + "ext-sockets": "*", + "guzzlehttp/guzzle": "^7.8", + "spiral-packages/laravel-validator": "^1.1", "spiral-packages/yii-error-handler-bridge": "^1.1", + "spiral-packages/league-event": "^1.0", "spiral/cycle-bridge": "^2.5", - "spiral-packages/laravel-validator": "^1.1", "spiral/data-grid-bridge": "^3.0", + "spiral/framework": "^3.12", + "spiral/nyholm-bridge": "^1.3", + "spiral/roadrunner-bridge": "^3.0", + "spiral/roadrunner-cli": "^2.5", "spiral/sentry-bridge": "^v2.2" }, "require-dev": { diff --git a/app/composer.lock b/app/composer.lock index b9787e4..11afd69 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "37a2fcb507a1d780934864aa88651310", + "content-hash": "761610fde8b364c52240d0252a2d87cd", "packages": [ { "name": "alexkart/curl-builder", @@ -1492,6 +1492,215 @@ }, "time": "2023-08-14T23:57:54+00:00" }, + { + "name": "guzzlehttp/guzzle", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.1", + "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:35:24+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:19:20+00:00" + }, { "name": "guzzlehttp/psr7", "version": "2.6.2", @@ -2160,6 +2369,65 @@ }, "time": "2024-03-08T09:58:59+00:00" }, + { + "name": "league/event", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/event.git", + "reference": "221867a61087ee265ca07bd39aa757879afca820" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/event/zipball/221867a61087ee265ca07bd39aa757879afca820", + "reference": "221867a61087ee265ca07bd39aa757879afca820", + "shasum": "" + }, + "require": { + "php": ">=7.2.0", + "psr/event-dispatcher": "^1.0" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "phpstan/phpstan": "^0.12.45", + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Event\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Event package", + "keywords": [ + "emitter", + "event", + "listener" + ], + "support": { + "issues": "https://github.com/thephpleague/event/issues", + "source": "https://github.com/thephpleague/event/tree/3.0.2" + }, + "time": "2022-10-29T09:31:25+00:00" + }, { "name": "league/flysystem", "version": "3.27.0", @@ -4609,6 +4877,62 @@ }, "time": "2022-09-29T15:46:44+00:00" }, + { + "name": "spiral-packages/league-event", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/spiral-packages/league-event.git", + "reference": "ef6e87e2e5a2d12ecfc92e99a6e6f0aec72f7aaf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spiral-packages/league-event/zipball/ef6e87e2e5a2d12ecfc92e99a6e6f0aec72f7aaf", + "reference": "ef6e87e2e5a2d12ecfc92e99a6e6f0aec72f7aaf", + "shasum": "" + }, + "require": { + "league/event": "^3.0", + "php": "^8.1", + "spiral/events": "^3.0" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "phpunit/phpunit": "^9.5", + "roave/security-advisories": "dev-latest", + "spiral/testing": "^2.0", + "vimeo/psalm": "^4.22" + }, + "type": "library", + "extra": { + "spiral": { + "bootloaders": [ + "Spiral\\League\\Event\\Bootloader\\EventBootloader" + ] + } + }, + "autoload": { + "psr-4": { + "Spiral\\League\\Event\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The League Event bridge for Spiral Framework", + "homepage": "https://github.com/spiral-packages/symfony-event-dispatcher", + "keywords": [ + "event-dispatcher", + "spiral", + "spiral-packages" + ], + "support": { + "issues": "https://github.com/spiral-packages/league-event/issues", + "source": "https://github.com/spiral-packages/league-event/tree/1.0.1" + }, + "time": "2022-09-14T08:02:26+00:00" + }, { "name": "spiral-packages/yii-error-handler-bridge", "version": "1.1.0", @@ -11678,8 +12002,8 @@ "prefer-lowest": false, "platform": { "php": ">=8.2", - "ext-sockets": "*", - "ext-mbstring": "*" + "ext-mbstring": "*", + "ext-sockets": "*" }, "platform-dev": [], "plugin-api-version": "2.3.0" diff --git a/docker-compose.yaml b/docker-compose.yaml index 8e71dab..b7bae4f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -48,12 +48,6 @@ services: RR_CENTRIFUGE_PROXY_ADDRESS: tcp://0.0.0.0:10001 RR_CENTRIFUGE_GRPC_API_ADDRESS: tcp://buggregator-ws:10000 - healthcheck: - test: curl --fail http://127.0.0.1:2114 || exit 1 - interval: 10s - timeout: 30s - retries: 3 - start_period: 5s labels: - "traefik.enable=true" - "traefik.http.routers.buggregator-api-http.entrypoints=web" @@ -76,8 +70,8 @@ services: - "traefik.http.routers.buggregator-spa-http.entrypoints=web" - "traefik.http.routers.buggregator-spa-http.rule=Host(`buggregator.localhost`)" - "traefik.http.services.buggregator-spa-http.loadbalancer.server.port=3000" - volumes: - - ./spa:/app +# volumes: +# - ./spa:/app networks: - buggregator-network diff --git a/spa/app/api/Api.ts b/spa/app/api/Api.ts new file mode 100644 index 0000000..c26063f --- /dev/null +++ b/spa/app/api/Api.ts @@ -0,0 +1,35 @@ +import apiMethods from '~/app/api/methods'; +import { RPCClient } from "~/app/ws/RPCClient"; +import { SettingsResponse } from "~/config/types"; + +type SettingsApi = { + get: () => SettingsResponse, +} + +type ExamplesApi = { + call: (action: string) => void, +} + +export default class Api { + private readonly rpc: RPCClient; + private readonly _api_url: string; + private readonly _examples_url: string; + + constructor(rpc: RPCClient, api_url: string, examples_url: string) { + this.rpc = rpc; + this._api_url = api_url; + this._examples_url = examples_url; + } + + get settings(): SettingsApi { + return { + get: apiMethods.settings(this.rpc), + } + } + + get examples(): ExamplesApi { + return { + call: apiMethods.callExampleAction(this._examples_url), + } + } +} diff --git a/spa/app/api/methods.ts b/spa/app/api/methods.ts new file mode 100644 index 0000000..04ace8f --- /dev/null +++ b/spa/app/api/methods.ts @@ -0,0 +1,27 @@ +import { + SettingsResponse, + ServerResponse +} from "~/config/types"; +import { RPCClient } from "~/app/ws/RPCClient"; + +const settings = (rpc: RPCClient) => () => rpc.call('get:api/settings') + .then((response: ServerResponse) => response.data); + +const callExampleAction = (host: string) => (action: string) => { + action = action.toLowerCase(); + + const path: string = action === 'profiler:report' ? '_profiler' : ''; + + return useFetch(`${host}/${path}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({action}) + }) +} + +export default { + settings, + callExampleAction +} diff --git a/spa/app/api/schema.d.ts b/spa/app/api/schema.d.ts new file mode 100644 index 0000000..dae9206 --- /dev/null +++ b/spa/app/api/schema.d.ts @@ -0,0 +1,3191 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + + +/** OneOf type helpers */ +type Without = { [P in Exclude]?: never }; +type XOR = (T | U) extends object ? (Without & U) | (Without & T) : T | U; +type OneOf = T extends [infer Only] ? Only : T extends [infer A, infer B, ...infer Rest] ? OneOf<[XOR, ...Rest]> : never; + +export interface paths { + "/reports/download": { + post: operations["2b005afc5ea56154dcaee39c5aaf760c"]; + }; + "/translations/{filename}": { + get: operations["8c26f9ccde28cd0eb6130fd2f44605a6"]; + }; + "/api/v1/asset/domain/{uuid}": { + get: operations["asset/domain/get"]; + }; + "/api/v1/asset/{uuid}": { + get: operations["asset/get"]; + }; + "/api/v1/asset/ip/{uuid}": { + get: operations["asset/ip/get"]; + }; + "/api/v1/asset/os/{uuid}": { + get: operations["asset/os/get"]; + }; + "/api/v1/asset/port/{uuid}": { + get: operations["asset/port/get"]; + }; + "/api/v1/asset/{uuid}/vulnerability/{vulUuid}/details": { + get: operations["asset/vulnerability/details"]; + }; + "/api/v1/asset/{uuid}/vulnerabilities": { + get: operations["asset/vulnerability/list"]; + }; + "/api/v1/auth/change-password": { + /** @description Change password */ + post: operations["changePassword"]; + }; + "/api/v1/auth/forgot-password": { + /** @description Reset password */ + post: operations["forgotPassword"]; + }; + "/api/v1/auth/login": { + /** @description Log in by credentials */ + post: operations["login"]; + }; + "/api/v1/auth/logout": { + /** @description Logout a customer */ + post: operations["logout"]; + }; + "/api/v1/me": { + get: operations["me"]; + }; + "/api/v1/auth/register": { + /** @description Register a new customer */ + post: operations["register"]; + }; + "/api/v1/auth/sessions": { + /** @description Retrieve a list of sessions */ + get: operations["sessions.list"]; + }; + "/api/v1/auth/sessions/{uuid}/revoke": { + /** @description Revoke a session by UUID */ + post: operations["sessions.revoke"]; + }; + "/api/v1/auth/sessions/revoke": { + /** @description Revoke all sessions for the current user */ + post: operations["sessions.revokeAll"]; + }; + "/api/v1/profile/change-password": { + /** @description Change password */ + post: operations["changePasswordProfile"]; + }; + "/api/v1/profile/change-timezone": { + /** @description Change timezone */ + post: operations["changeTimeZone"]; + }; + "/api/v1/dashboard": { + get: operations["getDashboard"]; + }; + "/api/v1/dashboard/asset-changes": { + get: operations["3b44fdc99fa98a60db9f7ff54f5fe7c8"]; + }; + "/api/v1/dashboard/asset-count": { + get: operations["ecc48c49a2b657934d9ae02d44dad622"]; + }; + "/api/v1/dashboard/attack-vector-count": { + get: operations["5f173cefa03130c82d7cb590454151d8"]; + }; + "/api/v1/dashboard/credentials": { + get: operations["8fbabf2484f42b43812d52ca18a9a988"]; + }; + "/api/v1/dashboard/os": { + get: operations["34847e5e0fb5d3b3cf990d3890ac6b06"]; + }; + "/api/v1/dashboard/open-ports": { + get: operations["4b71b242c7e6f92273eb65e7b71bf8ca"]; + }; + "/api/v1/dashboard/overall-risk-score": { + get: operations["a04f7b986fb9621a5b23776135540585"]; + }; + "/api/v1/dashboard/vulnerability-count": { + get: operations["755168a919c3f29bf53ad0a6971d0f0d"]; + }; + "/api/v1/downloads": { + /** @description Get downloads list */ + get: operations["downloads/list"]; + }; + "/api/v1/notifications/unread": { + /** @description Unread notifications */ + get: operations["getUnreadNotifications"]; + }; + "/api/v1/notifications": { + /** @description Mark notification as read */ + delete: operations["markNotificationsAsRead"]; + }; + "/api/v1/project/{uuid}/assets/change-affiliations": { + post: operations["project/asset/change-affiliations"]; + }; + "/api/v1/project/{uuid}/assets/domains": { + /** @description Get project domain list */ + get: operations["project/asset/domain/list"]; + }; + "/api/v1/project/{uuid}/assets/export": { + /** @description Export project asset list */ + post: operations["project/asset/list/export"]; + }; + "/api/v1/project/{uuid}/asset-counters": { + /** @description Get project asset counters */ + get: operations["project/asset/counters"]; + }; + "/api/v1/project/{uuid}/assets/ip": { + /** @description Get project ip list */ + get: operations["project/asset/ip/list"]; + }; + "/api/v1/project/{uuid}/assets": { + /** @description Get project asset list */ + get: operations["project/asset/list"]; + }; + "/api/v1/project/{uuid}/assets/os": { + /** @description Get project operation system asset list */ + get: operations["project/asset/os/list"]; + }; + "/api/v1/project/{uuid}/assets/ports": { + /** @description Get project port asset list */ + get: operations["project/asset/ports/list"]; + }; + "/api/v1/project": { + /** @description Create a new project */ + post: operations["project/create"]; + }; + "/api/v1/project/{uuid}": { + /** @description Get project by UUID */ + get: operations["project/get"]; + }; + "/api/v1/projects": { + /** @description Get projects list */ + get: operations["project/list"]; + }; + "/api/v1/project/{uuid}/vulnerability/change-statuses": { + post: operations["project/vulnerability/change-statuses"]; + }; + "/api/v1/project/{uuid}/vulnerabilities/export": { + /** @description Export project vulnerability list */ + post: operations["asset/vulnerability/list/export"]; + }; + "/api/v1/project/{uuid}/vulnerability-counters": { + get: operations["getProjectVulnerabilityCounters"]; + }; + "/api/v1/project/{uuid}/vulnerabilities": { + get: operations["listProjectVulnerabilities"]; + }; + "/api/v1/reports": { + get: operations["listReports"]; + }; + "/api/v1/two-step-auth/connect": { + post: operations["twoStepConnect"]; + }; + "/api/v1/two-step-auth/disconnect": { + post: operations["twoStepDisconnect"]; + }; + "/api/v1/two-step-auth/show-qr-code": { + get: operations["twoStepShowQrCode"]; + }; + "/api/v1/two-step-auth/verify-code": { + post: operations["twoStepShowVerifyCode"]; + }; + "/api/v1/vulnerability/{uuid}": { + get: operations["getVulnerabilityByUuid"]; + }; +} + +export type webhooks = Record; + +export interface components { + schemas: { + /** @example 018944be-ffa3-728b-9439-cc2f9922a65b */ + Uuid: string; + /** + * @description ICANN domain name without any protocols + * @example intruforce.com + */ + DomainName: string; + /** + * @description IP address name + * @example 185.215.4.51 + */ + IpName: string; + /** + * @description Operation system name + * @example Linux v1.0 - v99.0 + */ + OsName: string; + /** + * @description Network port number + * @example 443 + */ + PortNumber: Record; + AssetCounters: { + total?: number; + domains?: number; + ips?: number; + }; + VulnerabilityCounters: { + total?: number; + vulnerabilities?: number; + }; + Status: { + status?: boolean; + }; + PaginationMeta: { + pagination?: { + /** + * @description Current page number + * @example 1 + */ + current_page?: Record; + /** + * @description Last page number + * @example 20 + */ + last_page?: Record; + /** + * @description Items per page + * @example 10 + */ + per_page?: Record; + /** + * @description Total count of items + * @example 200 + */ + total?: Record; + }; + }; + Project: { + /** + * @description Project UUID + * @example 018944be-ffa3-728b-9439-cc2f9922a65b + */ + uuid?: string; + /** + * @description Project name + * @example intruforce + */ + name?: string; + /** + * @description Project description + * @example Dolor sit amet + */ + description?: string; + }; + Asset: { + /** @example 018944be-ffa3-728b-9439-cc2f9922a65b */ + uuid?: string; + project?: { + /** @example 018944be-ffa3-728b-9439-cc2f9922a654 */ + uuid?: string; + /** @example Project name */ + name?: string; + }; + /** @example intruforce.com */ + static_value?: string; + /** @example 0 */ + affiliation?: number; + /** @example 0 */ + type?: number; + /** @example 2023-07-06T01:01:01+00:00 */ + last_discovery_time?: string; + /** @example 2023-07-06T01:01:01+00:00 */ + discovery_time?: string; + /** @example 2023-07-06T01:01:01+00:00 */ + last_scan_time?: string; + /** @example {"foo": "bar"} */ + data?: string; + }; + Vulnerability: { + /** @example 018944be-ffa3-728b-9439-cc2f9922a65b */ + uuid?: string; + /** @example intruforce.com */ + code?: string; + /** @example Dolor sit amet */ + description?: string; + /** @example https://intruforce.com */ + url?: string; + /** + * @description timestamp + * @example 1234567890 + */ + discovered_at?: string; + /** + * @description timestamp + * @example 1234567890 + */ + last_discovered_at?: string; + }; + Report: { + /** @example 018944be-ffa3-728b-9439-cc2f9922a65b */ + uuid?: string; + /** @example Report #11 */ + name?: string; + /** @example Dolor sit amet */ + description?: string; + /** @example reports/report_3832752.pdf */ + path?: string; + /** @example pdf */ + file_type?: string; + /** @example 10000 */ + file_size?: number; + /** @example 2023-07-06T01:01:01+00:00 */ + uploaded_at?: string; + }; + QRCode: { + url?: string; + secret?: string; + }; + Token: { + token?: string; + type?: string; + /** Format: date-time */ + expires_at?: string; + }; + BackupCodes: string[]; + Notifications: { + uuid?: string; + type?: string; + data?: string; + }[]; + Auth: { + token?: { + /** @example test */ + token?: string; + /** @example 2023-07-06T01:01:01+00:00 */ + expires_at?: string; + }; + }; + Customer: { + customer?: { + /** @example 018944be-ffa3-728b-9439-cc2f9922a65b */ + uuid?: string; + /** @example test@example.com */ + email?: string; + profile?: { + full_name?: { + /** @example John */ + first_name?: string; + /** @example Doe */ + last_name?: string; + }; + /** @example intruforce */ + company_name?: string; + /** @example Head of security */ + position_in_company?: string; + }; + }; + }; + ThreatAssessment: { + /** @example 8 */ + score?: number; + /** @example 2023-07-06T01:01:01+00:00 */ + updated_at?: string; + }; + AttackVector: { + /** @example 4 */ + real?: number; + /** @example 10 */ + potential?: number; + /** @example 2023-07-06T01:01:01+00:00 */ + updated_at?: string; + }; + PasswordLeak: { + /** @example 15 */ + email_total?: number; + /** @example 5 */ + email_compromised?: number; + /** @example 30 */ + desktop_total?: number; + /** @example 14 */ + desktop_compromised?: number; + /** @example 2023-07-06T01:01:01+00:00 */ + updated_at?: string; + }; + Credential: { + /** @example 19 */ + total?: number; + /** @example 6 */ + compromised?: number; + service?: OneOf<[{ + /** @example TCP */ + name?: string; + /** @example 100 */ + total?: number; + /** @example 20 */ + compromised?: number; + }, { + /** @example UDP */ + name?: string; + /** @example 120 */ + total?: number; + /** @example 20 */ + compromised?: number; + }]>[]; + /** @example 2023-07-06T01:01:01+00:00 */ + updated_at?: string; + }; + TotalAsset: { + /** @example 100 */ + total?: number; + /** @example 5 */ + critical_vulnerabilities?: number; + /** @example 30 */ + high_level_vulnerabilities?: number; + /** @example 2023-07-06T01:01:01+00:00 */ + updated_at?: string; + }; + OpenPort: { + /** @example 79 */ + total?: number; + /** @example 3 */ + high_risk?: number; + /** @example 40 */ + medium_risk?: number; + /** @example 2023-07-06T01:01:01+00:00 */ + updated_at?: string; + }; + AssetChange: { + /** @example 5 */ + new?: number; + /** @example 0 */ + unavailable?: number; + /** @example 2023-07-06T01:01:01+00:00 */ + updated_at?: string; + }; + Download: { + /** + * @description Download UUID + * @example 018944be-ffa3-728b-9439-cc2f9922a65b + */ + uuid?: string; + /** + * @description Download name + * @example Some download + */ + name?: string; + /** + * @description Download description + * @example Dolor sit amet + */ + description?: string; + /** + * @description Download status + * @example 0 + */ + status?: number; + /** + * @description Download progress + * @example 0 + */ + progress?: number; + files?: { + /** + * @description File UUID + * @example 018944be-ffa3-728b-9439-cc2f9922a65b + */ + uuid?: string; + /** + * @description File name + * @example file.txt + */ + name?: string; + /** + * @description File URL + * @example https://example.com/file.txt + */ + url?: string; + /** + * @description File type + * @example txt + */ + fileType?: string; + /** + * @description File size + * @example 100 + */ + fileSize?: number; + }; + /** + * @description Download creation date + * @example 2023-07-06T01:01:01+00:00 + */ + created_at?: string; + /** + * @description Download update date + * @example 2023-07-06T01:01:01+00:00 + */ + updated_at?: string; + /** + * @description Download expiration date + * @example 2023-07-06T01:01:01+00:00 + */ + expires_at?: string; + }; + UnauthorizedError: { + /** @example unauthorized */ + message?: string; + /** @example 401 */ + code?: number; + }; + ForbiddenError: { + /** @example token_is_missing */ + message?: string; + /** @example 403 */ + code?: number; + }; + ValidationError: { + /** @example validation_errors */ + message?: string; + /** @example 422 */ + code?: number; + errors?: { + /** @example name */ + field?: string; + errors?: { + /** @example string_expected */ + message?: string; + meta?: { + /** @example min */ + key?: string; + /** @example 3 */ + value?: string; + }[]; + }[]; + }[]; + }; + /** + * @description Risk types: + * * `0` - Total risk of asset or issue + * * `1` - Expert - risk by expert + * * `4` - CVSS v2 + * * `5` - CVSS v3 + * @example 0 + * @enum {integer} + */ + RiskType: None | Zero | Low | Medium | High | Critical; + /** + * @description Risk levels: + * * `0` - None (not set) + * * `1` - Zero + * * `2` - Low + * * `3` - Medium + * * `4` - High + * * `5` - Critical + * @example 5 + * @enum {integer} + */ + RiskLevel: None | Zero | Low | Medium | High | Critical; + /** + * @description Risk value object, can be convert to float by amount / denominator + * @enum {object} + */ + Risk: None | Zero | Low | Medium | High | Critical; + DownloadReportFilter: { + /** @description Reports to download */ + reports: components["schemas"]["ReportUuidFilter"][]; + }; + ReportUuidFilter: { + /** @description Report UUID */ + uuid: string; + }; + AssetCountersSchema: { + filter?: components["schemas"]["AssetCountersSchemaFilter"]; + }; + AssetCountersSchemaFilter: { + /** + * @description Asset affiliation status + * @default [ + * 0, + * 1 + * ] + */ + affiliation?: ((0 | 1 | 3 | 4 | null)[]) | null; + }; + AssetListSchema: { + sort?: components["schemas"]["AssetListSchemaSort"]; + filter?: components["schemas"]["AssetListSchemaFilter"]; + paginate?: components["schemas"]["AssetListSchemaSort"]; + }; + AssetListSchemaSort: { + /** + * @default desc + * @enum {string|null} + */ + by_risk?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + risk?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + risk_level?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + static_value?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + affiliation?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_affiliation?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + type?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_type?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + last_discovery_time?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_last_discovery_time?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + discovery_time?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_discovery_time?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + dns?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_dns?: "asc" | "desc" | null; + }; + AssetListSchemaPaginate: { + /** @default 1 */ + page?: Record | null; + /** + * @default 25 + * @enum {intval|null} + */ + limit?: 10 | 25 | 50 | null; + }; + AssetListSchemaFilter: { + /** + * @default all + * @enum {string} + */ + type?: "all" | "domain" | "ip_address"; + /** @description Name of asset */ + static_value?: string | null; + /** + * @description Asset affiliation status + * @default [ + * 0, + * 1 + * ] + */ + affiliation?: ((0 | 1 | 3 | 4 | null)[]) | null; + /** + * @description Risk levels + * @default [] + */ + risk_level?: ((0 | 1 | 2 | 3 | 4 | 5 | null)[]) | null; + }; + DomainListSchema: { + sort?: components["schemas"]["DomainListSchemaSort"]; + filter?: components["schemas"]["DomainListSchemaFilter"]; + paginate?: components["schemas"]["DomainListSchemaSort"]; + }; + DomainListSchemaSort: { + /** + * @default desc + * @enum {string|null} + */ + by_risk?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + risk?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + risk_level?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + static_value?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_name?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + affiliation?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_affiliation?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + last_discovery_time?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_last_discovery_time?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + discovery_time?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_discovery_time?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + dns?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_dns?: "asc" | "desc" | null; + }; + DomainListSchemaPaginate: { + /** @default 1 */ + page?: Record | null; + /** + * @default 25 + * @enum {intval|null} + */ + limit?: 10 | 25 | 50 | null; + }; + DomainListSchemaFilter: { + /** @description Name of asset */ + static_value?: string | null; + /** + * @description Asset affiliation status + * @default [ + * 0, + * 1 + * ] + */ + affiliation?: ((0 | 1 | 3 | 4 | null)[]) | null; + /** + * @description Risk levels + * @default [] + */ + risk_level?: ((0 | 1 | 2 | 3 | 4 | 5 | null)[]) | null; + }; + IpAddressListSchema: { + sort?: components["schemas"]["IpAddressListSchemaSort"]; + filter?: components["schemas"]["IpAddressListSchemaFilter"]; + paginate?: components["schemas"]["IpAddressListSchemaSort"]; + }; + IpAddressListSchemaSort: { + /** + * @default desc + * @enum {string|null} + */ + by_risk?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + risk?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + risk_level?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + static_value?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_name?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + affiliation?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_affiliation?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + last_discovery_time?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_last_discovery_time?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + discovery_time?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_discovery_time?: "asc" | "desc" | null; + }; + IpAddressListSchemaPaginate: { + /** @default 1 */ + page?: Record | null; + /** + * @default 25 + * @enum {intval|null} + */ + limit?: 10 | 25 | 50 | null; + }; + IpAddressListSchemaFilter: { + /** @description Name of asset */ + static_value?: string | null; + /** + * @description Asset affiliation status + * @default [ + * 0, + * 1 + * ] + */ + affiliation?: ((0 | 1 | 3 | 4 | null)[]) | null; + /** + * @description Risk levels + * @default [] + */ + risk_level?: ((0 | 1 | 2 | 3 | 4 | 5 | null)[]) | null; + }; + OperationSystemListSchema: { + sort?: components["schemas"]["OperationSystemListSchemaSort"]; + filter?: components["schemas"]["OperationSystemListSchemaFilter"]; + paginate?: components["schemas"]["OperationSystemListSchemaSort"]; + }; + OperationSystemListSchemaSort: { + /** + * @default desc + * @enum {string|null} + */ + by_risk?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + risk?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + risk_level?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + static_value?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_name?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + affiliation?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_affiliation?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + last_discovery_time?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_last_discovery_time?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + discovery_time?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_discovery_time?: "asc" | "desc" | null; + }; + OperationSystemListSchemaPaginate: { + /** @default 1 */ + page?: Record | null; + /** + * @default 25 + * @enum {intval|null} + */ + limit?: 10 | 25 | 50 | null; + }; + OperationSystemListSchemaFilter: { + /** @description Name of asset */ + static_value?: string | null; + /** + * @description Asset affiliation status + * @default [ + * 0, + * 1 + * ] + */ + affiliation?: ((0 | 1 | 3 | 4 | null)[]) | null; + /** + * @description Risk levels + * @default [] + */ + risk_level?: ((0 | 1 | 2 | 3 | 4 | 5 | null)[]) | null; + }; + PortListSchema: { + sort?: components["schemas"]["PortListSchemaSort"]; + filter?: components["schemas"]["PortListSchemaFilter"]; + paginate?: components["schemas"]["PortListSchemaSort"]; + }; + PortListSchemaSort: { + /** + * @default desc + * @enum {string|null} + */ + by_risk?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + risk?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + risk_level?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + static_value?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_name?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + affiliation?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_affiliation?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + last_discovery_time?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_last_discovery_time?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + discovery_time?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_discovery_time?: "asc" | "desc" | null; + }; + PortListSchemaPaginate: { + /** @default 1 */ + page?: Record | null; + /** + * @default 25 + * @enum {intval|null} + */ + limit?: 10 | 25 | 50 | null; + }; + PortListSchemaFilter: { + /** @description Name of asset */ + static_value?: string | null; + /** + * @description Asset affiliation status + * @default [ + * 0, + * 1 + * ] + */ + affiliation?: ((0 | 1 | 3 | 4 | null)[]) | null; + /** + * @description Risk levels + * @default [] + */ + risk_level?: ((0 | 1 | 2 | 3 | 4 | 5 | null)[]) | null; + }; + IssueCountersSchema: { + filter?: components["schemas"]["IssueCountersSchemaFilter"]; + }; + IssueCountersSchemaFilter: { + /** + * @description Issue status + * @default [ + * 0, + * 2 + * ] + */ + status?: ((0 | 1 | 2 | 3 | 4 | null)[]) | null; + }; + IssueListSchema: { + sort?: components["schemas"]["IssueListSchemaSort"]; + filter?: components["schemas"]["IssueListSchemaFilter"]; + paginate?: components["schemas"]["IssueListSchemaPaginate"]; + }; + IssueListSchemaSort: { + /** + * @default null + * @enum {string|null} + */ + risk?: "asc" | "desc" | null; + /** + * @default desc + * @enum {string|null} + */ + by_risk?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + risk_level?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + name?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_name?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + asset_name?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_asset_name?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + status?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + last_discovered_at?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_last_discovered_at?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + discovered_at?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_discovered_at?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + port?: "asc" | "desc" | null; + /** + * @default null + * @enum {string|null} + */ + by_port?: "asc" | "desc" | null; + }; + IssueListSchemaPaginate: { + /** @default 1 */ + page?: Record | null; + /** + * @default 25 + * @enum {intval|null} + */ + limit?: 10 | 25 | 50 | null; + }; + IssueListSchemaFilter: { + /** @description Name or code of issue */ + name?: string | null; + /** + * @description Asset affiliation status + * @default [ + * 0, + * 1 + * ] + */ + affiliation?: ((0 | 1 | 3 | 4 | null)[]) | null; + /** + * @description Issue status + * @default [ + * 0, + * 2 + * ] + */ + status?: ((0 | 1 | 2 | 3 | 4 | null)[]) | null; + /** @description Risk levels */ + risk_level?: ((0 | 1 | 2 | 3 | 4 | 5 | null)[]) | null; + /** @description Port numbers */ + port?: number[] | null; + /** @description Name of asset */ + asset_name?: string | null; + }; + AssetAffiliationFilter: unknown; + ChangeAffiliation: { + /** @description Assets to change affiliation */ + assets?: components["schemas"]["AssetAffiliationFilter"][]; + }; + ListAssetsFilter: { + page?: number; + per_page?: number; + type?: string; + affiliation?: string; + }; + ChangeVulnerabilityStatuses: { + /** @description Vulnerabilities to change statuses */ + vulnerabilities: components["schemas"]["VulnerabilityStatus"][]; + }; + ListAssetVulnerabilitiesFilter: { + statuses?: number[]; + page?: number; + per_page?: number; + }; + VulnerabilityStatus: unknown; + ChangePasswordFilter: { + /** @description Auth token */ + token: Record | null; + password: Record | null; + }; + ForgotPasswordFilter: { + /** Format: email */ + email: string; + }; + LoginFilter: { + /** Format: email */ + email: string; + password: string; + }; + RegisterFilter: { + /** + * Format: email + * @example customer@site.com + */ + email: string; + /** @example John */ + first_name?: string; + /** @example Doe */ + last_name?: string; + /** @example Some Company */ + company_name?: string; + /** @example CEO */ + position?: string; + /** @example Company infrastructure */ + project_name?: string; + /** @example Company infrastructure description */ + project_description?: string; + }; + CustomerChangePasswordFilter: { + current_password: Record | null; + new_password: Record | null; + }; + CustomerChangeTimezoneFilter: { + timezone: Record | null; + }; + DashboardCommonFilter: []; + ExportVulnerabilitiesFilter: { + format: []; + }; + MarkNotificationAsReadFilter: { + /** @description Notifications to mark as read */ + notifications: components["schemas"]["NotificationUuidFilter"][]; + }; + NotificationUuidFilter: { + /** @description Notification UUID */ + uuid: string; + }; + CreateProjectFilter: { + name: string; + description: string; + }; + GetProjectAssetCountersFilter: { + affiliations?: string; + }; + GetProjectVulnerabilityCountersFilter: { + affiliations?: string; + asset_affiliations?: string; + }; + ListReportFilter: { + page?: number; + }; + ConnectFilter: { + verification_code: string; + secret: string; + }; + DisconnectFilter: { + verification_code?: string; + }; + VerifyCodeFilter: { + verification_code?: string; + signature: string; + }; + ListVulnerabilitiesFilter: { + page?: number; + per_page?: number; + statuses?: string; + affiliation?: string; + }; + /** @description Count of risk levels of one of type issue */ + AssetRiskLevelCount: { + level?: components["schemas"]["RiskLevel"]; + /** @example 10 */ + count?: number; + }; + /** @description Asset risk by type and level */ + AssetRisk: { + risk?: components["schemas"]["Risk"]; + type?: components["schemas"]["RiskType"]; + level?: components["schemas"]["RiskLevel"]; + }; + AssetDiscovering: { + /** + * Format: date-time + * @example 2024-03-02T06:58:23.000000Z + */ + first_seen?: string; + /** + * Format: date-time + * @example 2024-03-02T06:58:23.000000Z + */ + last_seen?: string; + source?: components["schemas"]["AssetSource"]; + }; + AssetProject: { + uuid?: components["schemas"]["Uuid"]; + /** @example Project - intruforce.com */ + name?: string; + }; + AssetScanning: { + /** + * Format: date-time + * @example 2024-03-02T06:58:23.000000Z + */ + last_scan?: string; + }; + SoftwareService: { + /** + * @description Common type of software service + * @example ssh + */ + name?: string; + /** + * @description Name of product of software like Nginx or OpenSSH, can be null + * @example OpenSSH + */ + product?: string | null; + /** + * @description Version of product, can be null + * @example v1.0.0 + */ + version?: string | null; + }; + AssetDomainInfo: { + domain?: components["schemas"]["DomainName"]; + /** + * @description Level of nesting domain name + * @example 2 + */ + level?: number; + /** + * @description Domain zone, 1 level domain name + * @example com + */ + zone?: string; + }; + Domain: { + uuid?: components["schemas"]["Uuid"]; + name?: components["schemas"]["DomainName"]; + affiliation?: components["schemas"]["AssetAffiliation"]; + type?: components["schemas"]["AssetType"]; + info?: components["schemas"]["AssetDomainInfo"]; + project?: components["schemas"]["AssetProject"]; + discovering?: components["schemas"]["AssetDiscovering"]; + scanning?: components["schemas"]["AssetScanning"]; + risks?: components["schemas"]["AssetRisk"][]; + /** @description Risk levels counts of all open visible issues of asset */ + issue_levels?: components["schemas"]["AssetRiskLevelCount"][]; + /** @example false */ + wildcard_detected?: boolean; + summary_info?: components["schemas"]["AssetDomainSummaryInfo"]; + whois?: components["schemas"]["WhoisRaw"]; + /** @description Dns records of domain */ + dns?: components["schemas"]["DnsRecord"][]; + }; + RelatedDomains: { + /** @description Several or all related domain names, depends on List or Get requests */ + domains?: components["schemas"]["DomainName"][]; + /** @description Quantity of all related domains */ + quantity?: number; + }; + DnsRecord: { + /** + * @description Dns type of records like `A`, `AAAA`, `MX` etc + * @example MX + */ + type?: string; + /** @description Value of dns record */ + value?: string; + /** + * Format: date-time + * @description Last seen of dns record + */ + last_discovery_time?: string; + /** + * Format: date-time + * @description First seen of dns record + */ + discovery_time?: string; + /** @description DNS priority */ + priority?: number | null; + }; + AssetDomainSummaryInfo: { + related_domains?: components["schemas"]["RelatedDomains"]; + ip_addresses?: components["schemas"]["AssetIpAddressSummaryItem"][]; + /** @description Quantity of related ip addresses */ + ip_addresses_count?: number | null; + /** @description Group of dns types with counts */ + dns_type_counts?: components["schemas"]["DnsTypCount"][]; + }; + DnsTypCount: { + /** + * @description Dns type like A, AAAA, MX + * @example MX + */ + type?: string; + count?: number; + }; + AssetIpAddressSummaryItem: { + uuid?: components["schemas"]["Uuid"]; + name?: components["schemas"]["IpName"]; + }; + IpAddress: { + uuid?: components["schemas"]["Uuid"]; + name?: components["schemas"]["IpName"]; + info?: components["schemas"]["AssetIpInfo"]; + affiliation?: components["schemas"]["AssetAffiliation"]; + type?: components["schemas"]["AssetType"]; + project?: components["schemas"]["AssetProject"]; + discovering?: components["schemas"]["AssetDiscovering"]; + scanning?: components["schemas"]["AssetScanning"]; + risks?: components["schemas"]["AssetRisk"][]; + /** @description Risk levels counts of all open visible issues of asset */ + issue_levels?: components["schemas"]["AssetRiskLevelCount"][]; + summary_info?: components["schemas"]["AssetIpAddressSummaryInfo"]; + whois?: components["schemas"]["WhoisRaw"]; + }; + AssetIpAddressSummaryInfo: { + related_domains?: components["schemas"]["RelatedDomains"]; + tcp_ports?: components["schemas"]["AssetPortSummaryItem"][]; + operation_systems?: components["schemas"]["AssetOperationSystemSummaryItem"][]; + }; + AssetIpInfo: { + ip?: components["schemas"]["IpName"]; + /** + * @description Version of IP Address 4 or 6 + * @example 4 + * @enum {integer} + */ + ip_version?: 4 | 6; + }; + AssetOperationSystemInfo: { + name?: components["schemas"]["OsName"]; + accuracy?: components["schemas"]["Accuracy"]; + }; + AssetOperationSystemSummaryItem: { + uuid?: components["schemas"]["Uuid"]; + name?: components["schemas"]["OsName"]; + accuracy?: components["schemas"]["Accuracy"]; + }; + /** + * @description Accuracy of usage + * @example 77 + */ + Accuracy: Record | null; + OperationSystem: { + uuid?: components["schemas"]["Uuid"]; + name?: components["schemas"]["OsName"]; + info?: components["schemas"]["AssetOperationSystemInfo"]; + ip?: components["schemas"]["AssetIpAddressSummaryItem"]; + service?: components["schemas"]["SoftwareService"]; + affiliation?: components["schemas"]["AssetAffiliation"]; + type?: components["schemas"]["AssetType"]; + discovering?: components["schemas"]["AssetDiscovering"]; + scanning?: components["schemas"]["AssetScanning"]; + risks?: components["schemas"]["AssetRisk"][]; + /** @description Risk levels counts of all open visible issues of asset */ + issue_levels?: components["schemas"]["AssetRiskLevelCount"][]; + }; + AssetPortInfo: { + number?: components["schemas"]["PortNumber"]; + protocol?: components["schemas"]["AssetTransportProtocol"]; + port_state?: components["schemas"]["AssetPortState"]; + }; + AssetPortSummaryItem: { + uuid?: components["schemas"]["Uuid"]; + number?: components["schemas"]["PortNumber"]; + risk_level?: components["schemas"]["RiskLevel"]; + }; + Port: { + uuid?: components["schemas"]["Uuid"]; + /** + * @description Port asset name (with service) + * @example 22/ssh + */ + name?: string; + info?: components["schemas"]["AssetPortInfo"]; + ip?: components["schemas"]["AssetIpAddressSummaryItem"]; + service?: components["schemas"]["SoftwareService"]; + affiliation?: components["schemas"]["AssetAffiliation"]; + type?: components["schemas"]["AssetType"]; + discovering?: components["schemas"]["AssetDiscovering"]; + scanning?: components["schemas"]["AssetScanning"]; + risks?: components["schemas"]["AssetRisk"][]; + /** @description Risk levels counts of all open visible issues of asset */ + issue_levels?: components["schemas"]["AssetRiskLevelCount"][]; + }; + /** + * @description Asset affiliation statuses: + * * `0` - Unknown + * * `1` - Owned + * * `3` - Not owned + * * `4` - Ignored + * @enum {int} + */ + AssetAffiliation: 0 | 1 | 3 | 4; + /** + * @description Asset discovering sources statuses: + * * `0` - Initial + * * `1` - Expert (by manager) + * * `2` - Asm (by scanner) + * @enum {int} + */ + AssetSource: 0 | 1 | 2; + /** + * @description Asset types: + * * `0` - Domain + * * `1` - IpAddress + * * `2` - Port (software) + * * `5` - Operation System + * @enum {int} + */ + AssetType: 0 | 1 | 2 | 5; + /** + * @description Asset port states from nmap: + * * `0` - Open + * * `1` - Filtered + * * `2` - Unfiltered + * * `3` - Closed + * * `4` - Open Filtered + * * `5` - Closed Filtered + * * `6` - Unknown + * @enum {int} + */ + AssetPortState: 0 | 1 | 3 | 6; + /** + * @description Asset types: + * * `1` - TCP + * * `2` - UDP + * @enum {int} + */ + AssetTransportProtocol: 1 | 2; + WhoisRaw: { + /** @description Big plain raw whois text */ + raw?: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} + +export type external = Record; + +export interface operations { + + "2b005afc5ea56154dcaee39c5aaf760c": { + requestBody: { + content: { + "application/json": components["schemas"]["DownloadReportFilter"]; + }; + }; + responses: { + /** @description Result */ + 200: { + headers: { + /** @description Content type */ + "Content-Type"?: string; + /** @description Content type */ + "Content-Disposition"?: string; + }; + content: { + "application/pdf": string; + "application/zip": string; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + "8c26f9ccde28cd0eb6130fd2f44605a6": { + parameters: { + path: { + /** + * @description Filename + * @example en.json + */ + filename: string; + }; + }; + responses: { + /** @description Json file with translations */ + 200: { + headers: { + /** @description Content type */ + "Content-Type"?: string; + }; + content: { + "application/json": string; + }; + }; + /** @description File not found */ + 404: never; + }; + }; + "asset/domain/get": { + parameters: { + path: { + /** @description Domain UUID */ + uuid: string; + }; + }; + responses: { + /** @description Retrieve the asset */ + 200: { + content: { + "application/json": components["schemas"]["Domain"]; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + "asset/get": { + parameters: { + path: { + /** @description Asset UUID */ + uuid: string; + }; + }; + responses: { + /** @description Retrieve the asset */ + 200: { + content: { + "application/json": components["schemas"]["Asset"]; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + "asset/ip/get": { + parameters: { + path: { + /** @description IpAddress UUID */ + uuid: components["schemas"]["Uuid"]; + }; + }; + responses: { + /** @description Retrieve the ip address */ + 200: { + content: { + "application/json": components["schemas"]["IpAddress"]; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + "asset/os/get": { + parameters: { + path: { + /** @description OperationSystem UUID */ + uuid: components["schemas"]["Uuid"]; + }; + }; + responses: { + /** @description Retrieve the operation system */ + 200: { + content: { + "application/json": components["schemas"]["OperationSystem"]; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + "asset/port/get": { + parameters: { + path: { + /** @description PortAsset UUID */ + uuid: components["schemas"]["Uuid"]; + }; + }; + responses: { + /** @description Retrieve the port asset */ + 200: { + content: { + "application/json": components["schemas"]["Port"]; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + "asset/vulnerability/details": { + parameters: { + path: { + /** @description Asset UUID */ + assetUuid: string; + /** @description Vulnerability UUID */ + vulUuid: string; + }; + }; + responses: { + /** @description Retrieve the vulnerability details */ + 200: { + content: { + "application/json": components["schemas"]["Vulnerability"]; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + "asset/vulnerability/list": { + parameters: { + query?: { + sort?: components["schemas"]["IssueListSchemaSort"]; + filter?: components["schemas"]["IssueListSchemaFilter"]; + paginate?: components["schemas"]["IssueListSchemaPaginate"]; + }; + path: { + /** + * @description Asset UUID + * @example 00000000-0000-0000-0000-000000000000 + */ + uuid: string; + }; + }; + responses: { + /** @description Retrieve a list of asset vulnerabilities */ + 200: { + content: { + "application/json": { + data?: components["schemas"]["Vulnerability"][]; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + /** @description Change password */ + changePassword: { + requestBody: { + content: { + "application/json": components["schemas"]["ChangePasswordFilter"]; + }; + }; + responses: { + /** @description Retrieve a result */ + 200: { + content: { + "application/json": { + result?: boolean; + }; + }; + }; + /** @description Validation error */ + 422: { + content: { + "application/json": components["schemas"]["ValidationError"]; + }; + }; + }; + }; + /** @description Reset password */ + forgotPassword: { + requestBody: { + content: { + "application/json": components["schemas"]["ForgotPasswordFilter"]; + }; + }; + responses: { + /** @description Retrieve a result */ + 200: { + content: { + "application/json": { + result?: boolean; + }; + }; + }; + /** @description Validation error */ + 422: { + content: { + "application/json": components["schemas"]["ValidationError"]; + }; + }; + }; + }; + /** @description Log in by credentials */ + login: { + parameters: { + header?: { + /** @example Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 */ + "User-Agent"?: string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["LoginFilter"]; + }; + }; + responses: { + /** @description Retrieve a customer */ + 200: { + headers: { + /** @description Remaining tokens */ + "X-RateLimit-Remaining"?: number; + /** @description Retry after */ + "X-RateLimit-Retry-After"?: number; + /** @description Rate Limit */ + "X-RateLimit-Limit"?: number; + /** @description Captcha client key. If this header is present, captcha is required. */ + "X-Captcha"?: string; + }; + content: { + "application/json": components["schemas"]["Auth"]; + }; + }; + /** @description Validation error */ + 422: { + content: { + "application/json": components["schemas"]["ValidationError"]; + }; + }; + }; + }; + /** @description Logout a customer */ + logout: { + parameters: { + header?: { + /** @example Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 */ + "User-Agent"?: string; + }; + }; + responses: { + /** @description No content */ + 204: never; + /** @description Validation error */ + 422: { + content: { + "application/json": components["schemas"]["ValidationError"]; + }; + }; + }; + }; + me: { + responses: { + /** @description Retrieve a profile */ + 200: { + content: { + "application/json": components["schemas"]["Customer"]; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + /** @description Register a new customer */ + register: { + requestBody: { + content: { + "application/json": components["schemas"]["RegisterFilter"]; + }; + }; + responses: { + /** @description Retrieve a customer */ + 200: { + content: { + "application/json": components["schemas"]["Auth"]; + }; + }; + /** @description Validation error */ + 422: { + content: { + "application/json": components["schemas"]["ValidationError"]; + }; + }; + }; + }; + /** @description Retrieve a list of sessions */ + "sessions.list": { + responses: { + /** @description Retrieve a list of sessions */ + 200: { + content: { + "application/json": { + sessions?: { + uuid?: string; + /** @example 2021-01-01T00:00:00+00:00 */ + last_activity_at?: string; + /** @example 2021-01-01T00:00:00+00:00 */ + started_at?: string; + user_agent?: string; + ip?: string; + is_active?: boolean; + /** @example 2021-01-01T00:00:00+00:00 */ + expires_at?: string; + }[]; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + /** @description Revoke a session by UUID */ + "sessions.revoke": { + parameters: { + path: { + /** @description Session UUID */ + uuid: string; + }; + }; + responses: { + /** @description Session revoked successfully */ + 200: never; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + /** @description Revoke all sessions for the current user */ + "sessions.revokeAll": { + responses: { + /** @description Sessions revoked successfully */ + 200: never; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + /** @description Change password */ + changePasswordProfile: { + requestBody: { + content: { + "application/json": components["schemas"]["CustomerChangePasswordFilter"]; + }; + }; + responses: { + /** @description Result */ + 200: never; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + /** @description Validation error */ + 422: { + content: { + "application/json": components["schemas"]["ValidationError"]; + }; + }; + }; + }; + /** @description Change timezone */ + changeTimeZone: { + requestBody: { + content: { + "application/json": components["schemas"]["CustomerChangeTimezoneFilter"]; + }; + }; + responses: { + /** @description Result */ + 200: never; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + /** @description Validation error */ + 422: { + content: { + "application/json": components["schemas"]["ValidationError"]; + }; + }; + }; + }; + getDashboard: { + responses: { + /** @description Retrieve a dashboard */ + 200: { + content: { + "application/json": { + /** + * @deprecated + * @description Dashboard main + */ + main?: { + threat_assessment?: components["schemas"]["ThreatAssessment"]; + vulnerability?: components["schemas"]["Vulnerability"]; + attack_vector?: components["schemas"]["AttackVector"]; + password_leak?: components["schemas"]["PasswordLeak"]; + credential?: components["schemas"]["Credential"]; + }; + /** + * @deprecated + * @description Dashboard assets + */ + assets?: { + total_asset?: components["schemas"]["TotalAsset"]; + open_port?: components["schemas"]["OpenPort"]; + attack_vector?: components["schemas"]["AssetChange"]; + }; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + "3b44fdc99fa98a60db9f7ff54f5fe7c8": { + parameters: { + query?: { + /** @example 0000-0000-0000-0000-0000 */ + project_uuid?: string; + }; + }; + responses: { + /** @description Спислок изменений активов */ + 200: { + content: { + "application/json": { + changes?: { + static_value?: string; + ports?: { + port?: number; + }[]; + status?: number; + }[]; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + ecc48c49a2b657934d9ae02d44dad622: { + parameters: { + query?: { + /** @example 0000-0000-0000-0000-0000 */ + project_uuid?: string; + }; + }; + responses: { + /** @description Количество активов */ + 200: { + content: { + "application/json": { + total?: number; + critical_risk?: number; + high_risk?: number; + new?: number; + unavailable?: number; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + "5f173cefa03130c82d7cb590454151d8": { + parameters: { + query?: { + /** @example 0000-0000-0000-0000-0000 */ + project_uuid?: string; + }; + }; + responses: { + /** @description Количество атакующих векторов */ + 200: { + content: { + "application/json": { + real?: number; + potential?: number; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + "8fbabf2484f42b43812d52ca18a9a988": { + parameters: { + query?: { + /** @example 0000-0000-0000-0000-0000 */ + project_uuid?: string; + }; + }; + responses: { + /** @description Список учетных данных */ + 200: { + content: { + "application/json": { + credentials?: { + service_name?: string; + total?: number; + compromised?: number; + }[]; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + "34847e5e0fb5d3b3cf990d3890ac6b06": { + parameters: { + query?: { + /** @example 0000-0000-0000-0000-0000 */ + project_uuid?: string; + }; + }; + responses: { + /** @description Спислок операционных систем */ + 200: { + content: { + "application/json": { + os?: { + os?: string; + total?: number; + }[]; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + "4b71b242c7e6f92273eb65e7b71bf8ca": { + parameters: { + query?: { + /** @example 0000-0000-0000-0000-0000 */ + project_uuid?: string; + }; + }; + responses: { + /** @description Спислок открытых портов */ + 200: { + content: { + "application/json": { + ports?: { + port?: number; + protocol?: string; + total?: number; + }[]; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + a04f7b986fb9621a5b23776135540585: { + parameters: { + query?: { + /** @example 0000-0000-0000-0000-0000 */ + project_uuid?: string; + }; + }; + responses: { + /** @description Общая оценка угроз */ + 200: { + content: { + "application/json": { + /** @description Оценка */ + score?: string; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + "755168a919c3f29bf53ad0a6971d0f0d": { + parameters: { + query?: { + /** @example 0000-0000-0000-0000-0000 */ + project_uuid?: string; + }; + }; + responses: { + /** @description Количество уязвимостей */ + 200: { + content: { + "application/json": { + /** @description Критический риск */ + critical_risk?: number; + /** @description Высокий риск */ + high_risk?: number; + /** @description Средний риск */ + medium_risk?: number; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + /** @description Get downloads list */ + "downloads/list": { + responses: { + /** @description Retrieve a list of downloads */ + 200: { + content: { + "application/json": { + data?: components["schemas"]["Download"][]; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + /** @description Unread notifications */ + getUnreadNotifications: { + responses: { + /** @description Result */ + 200: { + content: { + "application/json": components["schemas"]["Notifications"]; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + /** @description Validation error */ + 422: { + content: { + "application/json": components["schemas"]["ValidationError"]; + }; + }; + }; + }; + /** @description Mark notification as read */ + markNotificationsAsRead: { + requestBody: { + content: { + "application/json": components["schemas"]["MarkNotificationAsReadFilter"]; + }; + }; + responses: { + /** @description Result */ + 200: never; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + /** @description Validation error */ + 422: { + content: { + "application/json": components["schemas"]["ValidationError"]; + }; + }; + }; + }; + "project/asset/change-affiliations": { + requestBody: { + content: { + "application/json": components["schemas"]["ChangeAffiliation"]; + }; + }; + responses: { + /** @description Retrieve the status */ + 200: { + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + /** @description Get project domain list */ + "project/asset/domain/list": { + parameters: { + query?: { + sort?: components["schemas"]["DomainListSchemaSort"]; + filter?: components["schemas"]["DomainListSchemaFilter"]; + paginate?: components["schemas"]["DomainListSchemaPaginate"]; + }; + path: { + /** @description Project UUID */ + uuid: string; + }; + }; + responses: { + /** @description Retrieve a list of domains */ + 200: { + content: { + "application/json": { + data?: components["schemas"]["Domain"][]; + meta?: components["schemas"]["PaginationMeta"][]; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + /** @description Export project asset list */ + "project/asset/list/export": { + parameters: { + path: { + /** @description Project UUID */ + uuid: string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExportVulnerabilitiesFilter"]; + }; + }; + responses: { + /** @description Retrieve a download UUID */ + 200: { + content: { + "application/json": { + /** + * @description Download UUID + * @example 00000000-0000-0000-0000-000000000000 + */ + uuid?: string; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + /** @description Get project asset counters */ + "project/asset/counters": { + parameters: { + query?: { + filter?: components["schemas"]["AssetCountersSchemaFilter"]; + }; + path: { + /** @description Project UUID */ + uuid: string; + }; + }; + responses: { + /** @description Retrieve the status */ + 200: { + content: { + "application/json": components["schemas"]["AssetCounters"]; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + /** @description Get project ip list */ + "project/asset/ip/list": { + parameters: { + query?: { + sort?: components["schemas"]["IpAddressListSchemaSort"]; + filter?: components["schemas"]["IpAddressListSchemaFilter"]; + paginate?: components["schemas"]["IpAddressListSchemaPaginate"]; + }; + path: { + /** @description Project UUID */ + uuid: string; + }; + }; + responses: { + /** @description Retrieve a list of ip addresses */ + 200: { + content: { + "application/json": { + data?: components["schemas"]["IpAddress"][]; + meta?: components["schemas"]["PaginationMeta"][]; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + /** @description Get project asset list */ + "project/asset/list": { + parameters: { + query?: { + sort?: components["schemas"]["AssetListSchemaSort"]; + filter?: components["schemas"]["AssetListSchemaFilter"]; + paginate?: components["schemas"]["AssetListSchemaPaginate"]; + }; + path: { + /** @description Project UUID */ + uuid: string; + }; + }; + responses: { + /** @description Retrieve a list of assets */ + 200: { + content: { + "application/json": { + data?: components["schemas"]["Asset"][]; + meta?: components["schemas"]["PaginationMeta"][]; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + /** @description Get project operation system asset list */ + "project/asset/os/list": { + parameters: { + query?: { + sort?: components["schemas"]["OperationSystemListSchemaSort"]; + filter?: components["schemas"]["OperationSystemListSchemaFilter"]; + paginate?: components["schemas"]["OperationSystemListSchemaPaginate"]; + }; + path: { + /** @description Project UUID */ + uuid: string; + }; + }; + responses: { + /** @description Retrieve a list of operation systems */ + 200: { + content: { + "application/json": { + data?: components["schemas"]["OperationSystem"][]; + meta?: components["schemas"]["PaginationMeta"][]; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + /** @description Get project port asset list */ + "project/asset/ports/list": { + parameters: { + query?: { + sort?: components["schemas"]["PortListSchemaSort"]; + filter?: components["schemas"]["PortListSchemaFilter"]; + paginate?: components["schemas"]["PortListSchemaPaginate"]; + }; + path: { + /** @description Project UUID */ + uuid: string; + }; + }; + responses: { + /** @description Retrieve a list of ports */ + 200: { + content: { + "application/json": { + data?: components["schemas"]["Port"][]; + meta?: components["schemas"]["PaginationMeta"][]; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + /** @description Create a new project */ + "project/create": { + requestBody: { + content: { + "application/json": components["schemas"]["CreateProjectFilter"]; + }; + }; + responses: { + /** @description Retrieve a project */ + 200: { + content: { + "application/json": components["schemas"]["Project"]; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + /** @description Get project by UUID */ + "project/get": { + parameters: { + path: { + /** @description Project UUID */ + uuid: string; + }; + }; + responses: { + /** @description Retrieve a project */ + 200: { + content: { + "application/json": components["schemas"]["Project"]; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + /** @description Get projects list */ + "project/list": { + responses: { + /** @description Retrieve a list of projects */ + 200: { + content: { + "application/json": { + data?: components["schemas"]["Project"][]; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + "project/vulnerability/change-statuses": { + requestBody: { + content: { + "application/json": components["schemas"]["ChangeVulnerabilityStatuses"]; + }; + }; + responses: { + /** @description Retrieve the result */ + 200: { + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + /** @description Export project vulnerability list */ + "asset/vulnerability/list/export": { + parameters: { + path: { + /** + * @description Project UUID + * @example 00000000-0000-0000-0000-000000000000 + */ + uuid: string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExportVulnerabilitiesFilter"]; + }; + }; + responses: { + /** @description Retrieve a download UUID */ + 200: { + content: { + "application/json": { + /** + * @description Download UUID + * @example 00000000-0000-0000-0000-000000000000 + */ + uuid?: string; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + getProjectVulnerabilityCounters: { + parameters: { + query?: { + filter?: components["schemas"]["IssueCountersSchema"]; + }; + path: { + /** @description Project UUID */ + uuid: string; + }; + }; + responses: { + /** @description Retrieve the status */ + 200: { + content: { + "application/json": components["schemas"]["VulnerabilityCounters"]; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + listProjectVulnerabilities: { + parameters: { + query?: { + sort?: components["schemas"]["IssueListSchemaSort"]; + filter?: components["schemas"]["IssueListSchemaFilter"]; + paginate?: components["schemas"]["IssueListSchemaPaginate"]; + }; + path: { + /** + * @description Project UUID + * @example 00000000-0000-0000-0000-000000000000 + */ + uuid: string; + }; + }; + responses: { + /** @description Retrieve a list of vulnerabilities */ + 200: { + content: { + "application/json": { + data?: components["schemas"]["Vulnerability"][]; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + listReports: { + parameters: { + query?: { + /** @example 1 */ + page?: string; + }; + }; + responses: { + /** @description Retrieve a list of reports */ + 200: { + content: { + "application/json": { + data?: components["schemas"]["Report"][]; + meta?: components["schemas"]["PaginationMeta"][]; + }; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + twoStepConnect: { + requestBody: { + content: { + "application/json": components["schemas"]["ConnectFilter"]; + }; + }; + responses: { + /** @description Returns 2fa backup codes */ + 200: { + content: { + "application/json": components["schemas"]["BackupCodes"]; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + twoStepDisconnect: { + requestBody: { + content: { + "application/json": components["schemas"]["DisconnectFilter"]; + }; + }; + responses: { + /** @description Returns QR code */ + 200: { + content: { + "application/json": components["schemas"]["QRCode"]; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + twoStepShowQrCode: { + responses: { + /** @description Returns QR Code */ + 200: { + content: { + "application/json": components["schemas"]["QRCode"]; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; + twoStepShowVerifyCode: { + requestBody: { + content: { + "application/json": components["schemas"]["VerifyCodeFilter"]; + }; + }; + responses: { + /** @description Returns auth token */ + 200: { + content: { + "application/json": components["schemas"]["Token"]; + }; + }; + }; + }; + getVulnerabilityByUuid: { + parameters: { + path: { + /** @description Vulnerability UUID */ + uuid: string; + }; + }; + responses: { + /** @description Retrieve the asset */ + 200: { + content: { + "application/json": components["schemas"]["Vulnerability"]; + }; + }; + /** @description Unauthorized */ + 401: { + content: { + "application/json": components["schemas"]["UnauthorizedError"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ForbiddenError"]; + }; + }; + }; + }; +} diff --git a/spa/app/logger.ts b/spa/app/logger.ts new file mode 100644 index 0000000..d1b08db --- /dev/null +++ b/spa/app/logger.ts @@ -0,0 +1,73 @@ +import { white } from 'console-log-colors' + +const colorMapper = (service: string) => { + switch (service.toLowerCase()) { + case 'rpc': + return white.bgGreen.bold + case 'api': + return white.bgBlue.bold + case 'ws': + return white.bgMagenta.bold + case 'store': + return white.bgRed.bold + } + + return white.bgBlack.bold +} + +export class Logger { + private readonly mode: string + private readonly prefix: string + private readonly mapper; + + constructor(mode: string, prefix: string = '') { + this.mode = mode + this.prefix = prefix + this.mapper = colorMapper(prefix) + } + + withPrefix(prefix: string): Logger { + return new Logger(this.mode, prefix) + } + + debug(...content): void { + this.__log('debug', ...content) + } + + error(...content): void { + this.__log('error', ...content) + } + + info(...content): void { + this.__log('info', ...content) + } + + success(...content): void { + this.__log('success', ...content) + } + + __log(type: string, ...content): void { + if (this.mode !== 'development') { + return + } + + if (this.prefix) { + content.unshift(this.mapper(`[${this.prefix}]`)) + } + + switch (type) { + case 'debug': + console.info(...content) + break + case 'success': + console.info(...content) + break + case 'info': + console.info(...content) + break + case 'error': + console.error(...content) + break + } + } +} diff --git a/spa/app/plugins/apiClient.ts b/spa/app/plugins/apiClient.ts new file mode 100644 index 0000000..6e43865 --- /dev/null +++ b/spa/app/plugins/apiClient.ts @@ -0,0 +1,18 @@ +import Api from "~/app/api/Api"; +import { WsClient } from "~/app/ws/client"; +import { RPCClient } from "~/app/ws/RPCClient"; +import { Logger } from "~/app/logger"; + +export default defineNuxtPlugin(({$ws, $logger}: { $ws: WsClient, $logger: Logger }) => { + const rpc: RPCClient = new RPCClient($ws, $logger) + + const config = useRuntimeConfig() + const examples_url: string = config.public.examples_url + const api_url: string = config.public.rest_api_url + + return { + provide: { + api: new Api(rpc, api_url, examples_url) + } + } +}) diff --git a/spa/app/plugins/centrifugo.ts b/spa/app/plugins/centrifugo.ts new file mode 100644 index 0000000..f451011 --- /dev/null +++ b/spa/app/plugins/centrifugo.ts @@ -0,0 +1,35 @@ +import { WsClient } from "~/app/ws/client"; +import { Centrifuge } from "centrifuge"; +import { useAppStore } from "~/stores/app"; + +const guessWsConnection = (): string => { + const WS_HOST: string = window.location.host + const WS_PROTOCOL: string = window.location.protocol === 'https:' ? 'wss' : 'ws' + + return `${WS_PROTOCOL}://${WS_HOST}/connection/websocket`; +} + +export default defineNuxtPlugin(async (nuxtApp) => { + const config = useRuntimeConfig() + const ws_url: string = (config.public.ws_url) || guessWsConnection() + + const client: WsClient = new WsClient( + new Centrifuge(ws_url), + nuxtApp.$logger + ) + + await client.connect() + + nuxtApp.hook('app:created', () => { + const settings = useAppStore() + settings.fetch() + + settings.subscribeToUpdates() + }) + + return { + provide: { + ws: client + } + } +}) diff --git a/spa/app/plugins/logger.ts b/spa/app/plugins/logger.ts new file mode 100644 index 0000000..ffc5ebd --- /dev/null +++ b/spa/app/plugins/logger.ts @@ -0,0 +1,9 @@ +import { Logger } from "~/app/logger"; + +export default defineNuxtPlugin(() => { + return { + provide: { + logger: new Logger(process.env.NODE_ENV!).withPrefix('APP'), + } + } +}) diff --git a/spa/app/ws/RPCClient.ts b/spa/app/ws/RPCClient.ts new file mode 100644 index 0000000..490f731 --- /dev/null +++ b/spa/app/ws/RPCClient.ts @@ -0,0 +1,29 @@ +import {WsClient} from "~/app/ws/client"; +import {Logger} from "~/app/logger"; + +export class RPCClient { + private ws: WsClient; + readonly logger: Logger; + + constructor(ws: WsClient, logger: Logger) { + this.ws = ws; + this.logger = logger.withPrefix('rpc'); + } + + async call(method: string, data: object = {}) { + this.logger.debug(`Request [${method}]`, data); + + return await this.ws.rpc(method, data) + .then(result => { + this.logger.debug(`Response [${method}]`, result); + + if (result.data.code !== 200) { + this.logger.error(`Error [${method}]`, result.data); + + throw new Error(`Something went wrong [${method}]. Error: ${result.data.message}`); + } + + return result; + }) + } +} diff --git a/spa/app/ws/channel.ts b/spa/app/ws/channel.ts new file mode 100644 index 0000000..59b71e7 --- /dev/null +++ b/spa/app/ws/channel.ts @@ -0,0 +1,82 @@ +import {WsClient} from "~/app/ws/client"; +import {PublicationContext, Subscription} from "centrifuge"; + +type Listeners = Function[]; + +export default class Channel { + /** The name of the channel. */ + readonly name: string; + /** The event callbacks applied to the socket. */ + private event: Function | null = null; + /** User supplied callbacks for events on this channel. */ + private listeners: Listeners = []; + private readonly ws: WsClient; + private subscription: Subscription | null = null; + + /** + * Create a new class instance. + */ + constructor(ws: WsClient, name: string) { + this.ws = ws + this.name = name + this._subscribe() + } + + /** + * Listen for an event on the channel instance. + */ + listen(callback: Function): Channel { + this._on(callback) + return this + } + + /** + * Bind the channel's socket to an event and store the callback. + */ + private _on(callback: Function): Channel { + if (!callback) { + throw new Error('Callback should be specified.'); + } + + if (!this.event) { + this.event = (context: PublicationContext): void => { + this.listeners.forEach(cb => cb(context, context.data)) + } + this.subscription!.on('publication', this.event) + } + + this.listeners.push((context: any, data: any): void => { + this.ws.logger.debug(`Event [${context.channel}] received`, context) + callback(data) + }) + + return this + } + + unsubscribe(): void { + this.subscription!.removeAllListeners() + this.ws.centrifuge.removeSubscription(this.subscription) + this.listeners = [] + this.event = null + } + + _subscribe(): void { + let sub: Subscription | null = this.ws.centrifuge.getSubscription(this.name) + + if (!sub) { + sub = this.ws.centrifuge.newSubscription(this.name) + sub.on('subscribing', (context) => { + this.ws.logger.debug(`Subscribing to [${this.name}]`, context) + }) + sub.on('subscribed', (context) => { + this.ws.logger.debug(`Subscribed to [${this.name}]`, context) + }) + sub.on('unsubscribed', (context) => { + this.ws.logger.debug(`Unsubscribed from [${this.name}]`, context) + }) + sub.subscribe() + } + + this.subscription = sub + } +} \ No newline at end of file diff --git a/spa/app/ws/client.ts b/spa/app/ws/client.ts new file mode 100644 index 0000000..4facca2 --- /dev/null +++ b/spa/app/ws/client.ts @@ -0,0 +1,58 @@ +import { Centrifuge } from "centrifuge"; +import Channel from './channel' +import { Logger } from "~/app/logger"; + +type Channels = { + [key: string]: Channel +} + +export class WsClient { + channels: Channels = {} + readonly centrifuge: Centrifuge + readonly logger: Logger + + constructor(centrifuge: Centrifuge, logger: Logger) { + this.centrifuge = centrifuge + this.logger = logger.withPrefix('ws') + } + + rpc(method: string, data: object = {}) { + return this.centrifuge.rpc(method, data) + } + + connect() { + this.centrifuge.on('connecting', (context) => { + this.logger.debug('Connecting', context) + }) + this.centrifuge.on('connected', (context) => { + this.logger.debug('Connected', context) + }) + this.centrifuge.on('disconnected', (context) => { + this.logger.debug('Disconnected', context) + }) + + this.centrifuge.connect() + + return this.centrifuge.ready() + } + + disconnect(): void { + this.disconnectChannels() + this.centrifuge.removeAllListeners() + this.centrifuge.disconnect() + } + + disconnectChannels(): void { + Object.entries(this.channels).forEach(([key, channel]): void => { + channel.unsubscribe() + }) + } + + channel(channel: string): Channel { + if (!this.channels[channel]) { + this.channels[channel] = new Channel(this, channel); + } + + return this.channels[channel]; + } +} diff --git a/spa/assets/img/pacman.png b/spa/assets/img/pacman.png deleted file mode 100644 index f2bd0a0..0000000 Binary files a/spa/assets/img/pacman.png and /dev/null differ diff --git a/spa/public/site.webmanifest b/spa/assets/site.webmanifest similarity index 100% rename from spa/public/site.webmanifest rename to spa/assets/site.webmanifest diff --git a/spa/components/v1/CopyCommand.vue b/spa/components/v1/CopyCommand.vue index 7a3161f..0a8b35b 100644 --- a/spa/components/v1/CopyCommand.vue +++ b/spa/components/v1/CopyCommand.vue @@ -11,8 +11,9 @@ const copyToClipboard = () => { diff --git a/spa/components/v1/Demo.vue b/spa/components/v1/Demo.vue index 615257c..414c5c7 100644 --- a/spa/components/v1/Demo.vue +++ b/spa/components/v1/Demo.vue @@ -4,7 +4,7 @@ import Buttons from "~/components/v1/Demo/Buttons.vue";