From 6f1ae95a473aacdacc4a4c936016f39e84d90457 Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Tue, 24 Jan 2023 20:58:46 +0300 Subject: [PATCH 01/28] feat: add optional name and dataLimit prop for createNewAccessKey method --- src/shadowbox/model/access_key.ts | 6 +++- src/shadowbox/server/api.yml | 2 +- src/shadowbox/server/manager_service.ts | 42 +++++++++++++++++++---- src/shadowbox/server/server_access_key.ts | 20 +++++++---- 4 files changed, 56 insertions(+), 14 deletions(-) diff --git a/src/shadowbox/model/access_key.ts b/src/shadowbox/model/access_key.ts index c19f83e8f..67ab56a00 100644 --- a/src/shadowbox/model/access_key.ts +++ b/src/shadowbox/model/access_key.ts @@ -50,7 +50,11 @@ export interface AccessKey { export interface AccessKeyRepository { // Creates a new access key. Parameters are chosen automatically. - createNewAccessKey(encryptionMethod?: string): Promise; + createNewAccessKey( + encryptionMethod?: string, + name?: string, + limit?: DataLimit + ): Promise; // Removes the access key given its id. Throws on failure. removeAccessKey(id: AccessKeyId); // Returns the access key with the given id. Throws on failure. diff --git a/src/shadowbox/server/api.yml b/src/shadowbox/server/api.yml index 60db5cf82..7a316c523 100644 --- a/src/shadowbox/server/api.yml +++ b/src/shadowbox/server/api.yml @@ -150,7 +150,7 @@ paths: type: string examples: '0': - value: '{"method":"aes-192-gcm"}' + value: '{"method":"aes-192-gcm","name":"First","limit":{"bytes":10000}}' responses: '201': description: The newly created access key diff --git a/src/shadowbox/server/manager_service.ts b/src/shadowbox/server/manager_service.ts index 39e6f44ac..7e51f49a1 100644 --- a/src/shadowbox/server/manager_service.ts +++ b/src/shadowbox/server/manager_service.ts @@ -311,7 +311,11 @@ export class ShadowsocksManagerService { } // Creates a new access key - public async createNewAccessKey(req: RequestType, res: ResponseType, next: restify.Next): Promise { + public async createNewAccessKey( + req: RequestType, + res: ResponseType, + next: restify.Next + ): Promise { try { logging.debug(`createNewAccessKey request ${JSON.stringify(req.params)}`); let encryptionMethod = req.params.method; @@ -319,16 +323,42 @@ export class ShadowsocksManagerService { encryptionMethod = ''; } if (typeof encryptionMethod !== 'string') { - return next(new restifyErrors.InvalidArgumentError( + return next( + new restifyErrors.InvalidArgumentError( {statusCode: 400}, - `Expected a string encryptionMethod, instead got ${encryptionMethod} of type ${ - typeof encryptionMethod}`)); + `Expected a string encryptionMethod, instead got ${encryptionMethod} of type ${typeof encryptionMethod}` + ) + ); + } + + let name = req.params.name; + if (!name) { + name = ''; } - const accessKeyJson = accessKeyToApiJson(await this.accessKeys.createNewAccessKey(encryptionMethod)); + if (typeof name !== 'string') { + return next( + new restifyErrors.InvalidArgumentError( + {statusCode: 400}, + `Expected a string name, instead got ${name} of type ${typeof name}` + ) + ); + } + + let limit = req.params.limit as DataLimit; + if (limit) { + limit = validateDataLimit(limit); + } + if (!limit) { + limit = undefined; + } + + const accessKeyJson = accessKeyToApiJson( + await this.accessKeys.createNewAccessKey(encryptionMethod, name, limit) + ); res.send(201, accessKeyJson); logging.debug(`createNewAccessKey response ${JSON.stringify(accessKeyJson)}`); return next(); - } catch(error) { + } catch (error) { logging.error(error); if (error instanceof errors.InvalidCipher) { return next(new restifyErrors.InvalidArgumentError({statusCode: 400}, error.message)); diff --git a/src/shadowbox/server/server_access_key.ts b/src/shadowbox/server/server_access_key.ts index b75b447c6..33761eb01 100644 --- a/src/shadowbox/server/server_access_key.ts +++ b/src/shadowbox/server/server_access_key.ts @@ -97,10 +97,12 @@ function accessKeyToStorageJson(accessKey: AccessKey): AccessKeyStorageJson { } function isValidCipher(cipher: string): boolean { - if (["aes-256-gcm", "aes-192-gcm", "aes-128-gcm", "chacha20-ietf-poly1305"].indexOf(cipher) === -1) { - return false; - } - return true; + if ( + ['aes-256-gcm', 'aes-192-gcm', 'aes-128-gcm', 'chacha20-ietf-poly1305'].indexOf(cipher) === -1 + ) { + return false; + } + return true; } // AccessKeyRepository that keeps its state in a config file and uses ShadowsocksServer @@ -166,7 +168,11 @@ export class ServerAccessKeyRepository implements AccessKeyRepository { this.portForNewAccessKeys = port; } - async createNewAccessKey(encryptionMethod?: string): Promise { + async createNewAccessKey( + encryptionMethod?: string, + name?: string, + limit?: DataLimit + ): Promise { const id = this.keyConfig.data().nextId.toString(); this.keyConfig.data().nextId += 1; const metricsId = uuidv4(); @@ -182,7 +188,9 @@ export class ServerAccessKeyRepository implements AccessKeyRepository { encryptionMethod, password, }; - const accessKey = new ServerAccessKey(id, '', metricsId, proxyParams); + name = name || ''; + limit = undefined || limit; + const accessKey = new ServerAccessKey(id, name, metricsId, proxyParams, limit); this.accessKeys.push(accessKey); this.saveAccessKeys(); await this.updateServer(); From 9a5b0e680bcea488855c460e069d0d5828eb8d65 Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Tue, 24 Jan 2023 21:46:23 +0300 Subject: [PATCH 02/28] fix: add error catching --- src/shadowbox/server/manager_service.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/shadowbox/server/manager_service.ts b/src/shadowbox/server/manager_service.ts index 7e51f49a1..a9165a2ba 100644 --- a/src/shadowbox/server/manager_service.ts +++ b/src/shadowbox/server/manager_service.ts @@ -363,6 +363,12 @@ export class ShadowsocksManagerService { if (error instanceof errors.InvalidCipher) { return next(new restifyErrors.InvalidArgumentError({statusCode: 400}, error.message)); } + if ( + error instanceof restifyErrors.InvalidArgumentError || + error instanceof restifyErrors.MissingParameterError + ) { + return next(error); + } return next(new restifyErrors.InternalServerError()); } } From 1ab3d5fd9b36d68cc20e8e5a2419f4569bcaf2b4 Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Tue, 24 Jan 2023 21:47:16 +0300 Subject: [PATCH 03/28] test: name and limit/dataLimit optional props --- src/shadowbox/server/manager_service.spec.ts | 78 ++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/shadowbox/server/manager_service.spec.ts b/src/shadowbox/server/manager_service.spec.ts index 24396816a..a798b4c6c 100644 --- a/src/shadowbox/server/manager_service.spec.ts +++ b/src/shadowbox/server/manager_service.spec.ts @@ -337,6 +337,84 @@ describe('ShadowsocksManagerService', () => { }; service.createNewAccessKey({params: {method: 'aes-256-gcm'}}, res, done); }); + it('use default name is params is not defined', (done) => { + const repo = getAccessKeyRepository(); + const service = new ShadowsocksManagerServiceBuilder().accessKeys(repo).build(); + + const res = { + send: (httpCode, data) => { + expect(httpCode).toEqual(201); + expect(data.name).toEqual(''); + responseProcessed = true; // required for afterEach to pass. + }, + }; + service.createNewAccessKey({params: {}}, res, done); + }); + it('rejects non-string name', (done) => { + const repo = getAccessKeyRepository(); + const service = new ShadowsocksManagerServiceBuilder().accessKeys(repo).build(); + + const res = {send: (_httpCode, _data) => {}}; + service.createNewAccessKey({params: {name: Number('9876')}}, res, (error) => { + expect(error.statusCode).toEqual(400); + responseProcessed = true; // required for afterEach to pass. + done(); + }); + }); + it('defined name is equal to stored', (done) => { + const repo = getAccessKeyRepository(); + const service = new ShadowsocksManagerServiceBuilder().accessKeys(repo).build(); + + const ACCESSKEY_NAME = 'accesskeyname'; + const res = { + send: (httpCode, data) => { + expect(httpCode).toEqual(201); + expect(data.name).toEqual(ACCESSKEY_NAME); + responseProcessed = true; // required for afterEach to pass. + }, + }; + service.createNewAccessKey({params: {name: ACCESSKEY_NAME}}, res, done); + }); + it('limit can be undefined', (done) => { + const repo = getAccessKeyRepository(); + const service = new ShadowsocksManagerServiceBuilder().accessKeys(repo).build(); + + const res = { + send: (httpCode, data) => { + expect(httpCode).toEqual(201); + expect(data.limit).toBeUndefined(); + responseProcessed = true; // required for afterEach to pass. + }, + }; + service.createNewAccessKey({params: {}}, res, done); + }); + it('rejects non-numeric limits', (done) => { + const repo = getAccessKeyRepository(); + const service = new ShadowsocksManagerServiceBuilder().accessKeys(repo).build(); + + const ACCESSKEY_LIMIT = {bytes: '9876'}; + + const res = {send: (_httpCode, _data) => {}}; + service.createNewAccessKey({params: {limit: ACCESSKEY_LIMIT}}, res, (error) => { + expect(error.statusCode).toEqual(400); + responseProcessed = true; // required for afterEach to pass. + done(); + }); + }); + it('defined limit is equal to stored', (done) => { + const repo = getAccessKeyRepository(); + const service = new ShadowsocksManagerServiceBuilder().accessKeys(repo).build(); + + const ACCESSKEY_LIMIT = {bytes: 9876}; + const res = { + send: (httpCode, data) => { + expect(httpCode).toEqual(201); + expect(data.dataLimit).toEqual(ACCESSKEY_LIMIT); + responseProcessed = true; // required for afterEach to pass. + }, + }; + service.createNewAccessKey({params: {limit: ACCESSKEY_LIMIT}}, res, done); + }); it('method must be of type string', (done) => { const repo = getAccessKeyRepository(); const service = new ShadowsocksManagerServiceBuilder().accessKeys(repo).build(); From e2c45d360d2c11134a999f155940095cec771f1b Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Thu, 26 Jan 2023 22:45:32 +0300 Subject: [PATCH 04/28] feat: use interface instead of params --- src/shadowbox/model/access_key.ts | 15 ++++++++++----- src/shadowbox/server/manager_service.ts | 21 +++++---------------- src/shadowbox/server/server_access_key.ts | 17 ++++++++--------- 3 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/shadowbox/model/access_key.ts b/src/shadowbox/model/access_key.ts index 67ab56a00..6307a4a16 100644 --- a/src/shadowbox/model/access_key.ts +++ b/src/shadowbox/model/access_key.ts @@ -48,13 +48,18 @@ export interface AccessKey { readonly dataLimit?: DataLimit; } +export interface AccessKeyCreateParams { + // The encryption method to use for the access key. + readonly encryptionMethod?: string; + // The name to give the access key. + readonly name?: string; + // The data transfer limit to apply to the access key. + readonly dataLimit?: DataLimit; +} + export interface AccessKeyRepository { // Creates a new access key. Parameters are chosen automatically. - createNewAccessKey( - encryptionMethod?: string, - name?: string, - limit?: DataLimit - ): Promise; + createNewAccessKey(params?: AccessKeyCreateParams): Promise; // Removes the access key given its id. Throws on failure. removeAccessKey(id: AccessKeyId); // Returns the access key with the given id. Throws on failure. diff --git a/src/shadowbox/server/manager_service.ts b/src/shadowbox/server/manager_service.ts index a9165a2ba..b0f614f65 100644 --- a/src/shadowbox/server/manager_service.ts +++ b/src/shadowbox/server/manager_service.ts @@ -318,10 +318,7 @@ export class ShadowsocksManagerService { ): Promise { try { logging.debug(`createNewAccessKey request ${JSON.stringify(req.params)}`); - let encryptionMethod = req.params.method; - if (!encryptionMethod) { - encryptionMethod = ''; - } + const encryptionMethod = req.params.method || ''; if (typeof encryptionMethod !== 'string') { return next( new restifyErrors.InvalidArgumentError( @@ -331,10 +328,7 @@ export class ShadowsocksManagerService { ); } - let name = req.params.name; - if (!name) { - name = ''; - } + const name = req.params.name || ''; if (typeof name !== 'string') { return next( new restifyErrors.InvalidArgumentError( @@ -344,16 +338,11 @@ export class ShadowsocksManagerService { ); } - let limit = req.params.limit as DataLimit; - if (limit) { - limit = validateDataLimit(limit); - } - if (!limit) { - limit = undefined; - } + const limit = (req.params.limit as DataLimit) || undefined; + const dataLimit = validateDataLimit(limit); const accessKeyJson = accessKeyToApiJson( - await this.accessKeys.createNewAccessKey(encryptionMethod, name, limit) + await this.accessKeys.createNewAccessKey({encryptionMethod, name, dataLimit}) ); res.send(201, accessKeyJson); logging.debug(`createNewAccessKey response ${JSON.stringify(accessKeyJson)}`); diff --git a/src/shadowbox/server/server_access_key.ts b/src/shadowbox/server/server_access_key.ts index 33761eb01..a25762ab5 100644 --- a/src/shadowbox/server/server_access_key.ts +++ b/src/shadowbox/server/server_access_key.ts @@ -22,6 +22,7 @@ import * as logging from '../infrastructure/logging'; import {PrometheusClient} from '../infrastructure/prometheus_scraper'; import { AccessKey, + AccessKeyCreateParams, AccessKeyId, AccessKeyMetricsId, AccessKeyRepository, @@ -111,6 +112,8 @@ function isValidCipher(cipher: string): boolean { export class ServerAccessKeyRepository implements AccessKeyRepository { private static DATA_LIMITS_ENFORCEMENT_INTERVAL_MS = 60 * 60 * 1000; // 1h private NEW_USER_ENCRYPTION_METHOD = 'chacha20-ietf-poly1305'; + private NEW_USER_DEFAULT_NAME = ''; + private NEW_USER_DEFAULT_DATA_LIMIT = undefined; private accessKeys: ServerAccessKey[]; constructor( @@ -168,16 +171,12 @@ export class ServerAccessKeyRepository implements AccessKeyRepository { this.portForNewAccessKeys = port; } - async createNewAccessKey( - encryptionMethod?: string, - name?: string, - limit?: DataLimit - ): Promise { + async createNewAccessKey(params?: AccessKeyCreateParams): Promise { const id = this.keyConfig.data().nextId.toString(); this.keyConfig.data().nextId += 1; const metricsId = uuidv4(); const password = generatePassword(); - encryptionMethod = encryptionMethod || this.NEW_USER_ENCRYPTION_METHOD; + const encryptionMethod = params.encryptionMethod || this.NEW_USER_ENCRYPTION_METHOD; // Validate encryption method. if (!isValidCipher(encryptionMethod)) { throw new errors.InvalidCipher(encryptionMethod); @@ -188,9 +187,9 @@ export class ServerAccessKeyRepository implements AccessKeyRepository { encryptionMethod, password, }; - name = name || ''; - limit = undefined || limit; - const accessKey = new ServerAccessKey(id, name, metricsId, proxyParams, limit); + const name = params.name || this.NEW_USER_DEFAULT_NAME; + const dataLimit = this.NEW_USER_DEFAULT_DATA_LIMIT || params.dataLimit; + const accessKey = new ServerAccessKey(id, name, metricsId, proxyParams, dataLimit); this.accessKeys.push(accessKey); this.saveAccessKeys(); await this.updateServer(); From 09f88bfe74f629bd0ccbfe469011da2e979abed7 Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Thu, 26 Jan 2023 23:05:04 +0300 Subject: [PATCH 05/28] fix: validate only if limit is defined --- src/shadowbox/server/manager_service.ts | 7 +++++-- src/shadowbox/server/server_access_key.spec.ts | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/shadowbox/server/manager_service.ts b/src/shadowbox/server/manager_service.ts index b0f614f65..c0b81ae17 100644 --- a/src/shadowbox/server/manager_service.ts +++ b/src/shadowbox/server/manager_service.ts @@ -338,8 +338,11 @@ export class ShadowsocksManagerService { ); } - const limit = (req.params.limit as DataLimit) || undefined; - const dataLimit = validateDataLimit(limit); + const dataLimit = (req.params.limit as DataLimit) || undefined; + + if (dataLimit) { + validateDataLimit(dataLimit); + } const accessKeyJson = accessKeyToApiJson( await this.accessKeys.createNewAccessKey({encryptionMethod, name, dataLimit}) diff --git a/src/shadowbox/server/server_access_key.spec.ts b/src/shadowbox/server/server_access_key.spec.ts index 95a6d2e5f..0145a99ef 100644 --- a/src/shadowbox/server/server_access_key.spec.ts +++ b/src/shadowbox/server/server_access_key.spec.ts @@ -60,7 +60,7 @@ describe('ServerAccessKeyRepository', () => { it('New access keys sees the encryption method correctly', (done) => { const repo = new RepoBuilder().build(); - repo.createNewAccessKey('aes-256-gcm').then((accessKey) => { + repo.createNewAccessKey({encryptionMethod: 'aes-256-gcm'}).then((accessKey) => { expect(accessKey).toBeDefined(); expect(accessKey.proxyParams.encryptionMethod).toEqual('aes-256-gcm'); done(); From f34c8413824f044f7bfb73b5d380429073967559 Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Wed, 8 Mar 2023 13:58:42 +0500 Subject: [PATCH 06/28] ci: fix query-filters --- .github/codeql/config.yml | 7 +++++++ .../vulnerability_analysis.yml} | 9 ++------- 2 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 .github/codeql/config.yml rename .github/{workflows/codeql_vulnerability_analysis.yml => codeql/vulnerability_analysis.yml} (79%) diff --git a/.github/codeql/config.yml b/.github/codeql/config.yml new file mode 100644 index 000000000..a65b99ce9 --- /dev/null +++ b/.github/codeql/config.yml @@ -0,0 +1,7 @@ +name: Javascript CodeQL Configuration + +query-filters: + - exclude: + id: js/disabling-certificate-validation + - exclude: + id: js/missing-rate-limiting diff --git a/.github/workflows/codeql_vulnerability_analysis.yml b/.github/codeql/vulnerability_analysis.yml similarity index 79% rename from .github/workflows/codeql_vulnerability_analysis.yml rename to .github/codeql/vulnerability_analysis.yml index 6c4a50ede..683167f51 100644 --- a/.github/workflows/codeql_vulnerability_analysis.yml +++ b/.github/codeql/vulnerability_analysis.yml @@ -1,4 +1,4 @@ -name: "CodeQL" +name: "CodeQL analysis" on: pull_request: @@ -22,12 +22,6 @@ jobs: contents: read security-events: write - query-filters: - - exclude: - id: js/disabling-certificate-validation - - exclude: - id: js/missing-rate-limiting - strategy: fail-fast: false steps: @@ -38,6 +32,7 @@ jobs: uses: github/codeql-action/init@v2 with: languages: javascript + config-file: ./.github/codeql/config.yml - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 From 9512ca3faaeb1f85dd72ee0540789024d6d24441 Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Wed, 8 Mar 2023 14:00:55 +0500 Subject: [PATCH 07/28] ci: fix codeql workflow --- .../codeql_vulnerability_analysis.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{codeql/vulnerability_analysis.yml => workflows/codeql_vulnerability_analysis.yml} (100%) diff --git a/.github/codeql/vulnerability_analysis.yml b/.github/workflows/codeql_vulnerability_analysis.yml similarity index 100% rename from .github/codeql/vulnerability_analysis.yml rename to .github/workflows/codeql_vulnerability_analysis.yml From f39b4536d1deea530ed471c6ea93579b79dffbed Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Wed, 8 Mar 2023 14:06:28 +0500 Subject: [PATCH 08/28] ci(codeql): security and quality queries --- .github/codeql/config.yml | 6 ++---- .github/workflows/codeql_vulnerability_analysis.yml | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/codeql/config.yml b/.github/codeql/config.yml index a65b99ce9..ab510459c 100644 --- a/.github/codeql/config.yml +++ b/.github/codeql/config.yml @@ -1,7 +1,5 @@ -name: Javascript CodeQL Configuration - query-filters: - exclude: - id: js/disabling-certificate-validation + id: js/disabling-certificate-validation - exclude: - id: js/missing-rate-limiting + id: js/missing-rate-limiting diff --git a/.github/workflows/codeql_vulnerability_analysis.yml b/.github/workflows/codeql_vulnerability_analysis.yml index 683167f51..637e96830 100644 --- a/.github/workflows/codeql_vulnerability_analysis.yml +++ b/.github/workflows/codeql_vulnerability_analysis.yml @@ -32,6 +32,7 @@ jobs: uses: github/codeql-action/init@v2 with: languages: javascript + queries: +security-and-quality config-file: ./.github/codeql/config.yml - name: Perform CodeQL Analysis From 62eab04ea1cdcee88925837b3431426bfcc0c250 Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Fri, 28 Jul 2023 02:26:43 +0500 Subject: [PATCH 09/28] fix(shadowbox): use optional chaining for check params defined --- src/shadowbox/server/server_access_key.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/shadowbox/server/server_access_key.ts b/src/shadowbox/server/server_access_key.ts index a25762ab5..bccc4c4e4 100644 --- a/src/shadowbox/server/server_access_key.ts +++ b/src/shadowbox/server/server_access_key.ts @@ -176,7 +176,8 @@ export class ServerAccessKeyRepository implements AccessKeyRepository { this.keyConfig.data().nextId += 1; const metricsId = uuidv4(); const password = generatePassword(); - const encryptionMethod = params.encryptionMethod || this.NEW_USER_ENCRYPTION_METHOD; + const encryptionMethod = params?.encryptionMethod ?? this.NEW_USER_ENCRYPTION_METHOD; + // Validate encryption method. if (!isValidCipher(encryptionMethod)) { throw new errors.InvalidCipher(encryptionMethod); @@ -187,8 +188,8 @@ export class ServerAccessKeyRepository implements AccessKeyRepository { encryptionMethod, password, }; - const name = params.name || this.NEW_USER_DEFAULT_NAME; - const dataLimit = this.NEW_USER_DEFAULT_DATA_LIMIT || params.dataLimit; + const name = params?.name ?? this.NEW_USER_DEFAULT_NAME; + const dataLimit = params?.dataLimit ?? this.NEW_USER_DEFAULT_DATA_LIMIT; const accessKey = new ServerAccessKey(id, name, metricsId, proxyParams, dataLimit); this.accessKeys.push(accessKey); this.saveAccessKeys(); From 9c295b498aa8e59b6c932160f45655b70ee31035 Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Fri, 28 Jul 2023 02:42:11 +0500 Subject: [PATCH 10/28] fix(shadowbox): check if encryptionMethod is empty string --- src/shadowbox/server/server_access_key.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shadowbox/server/server_access_key.ts b/src/shadowbox/server/server_access_key.ts index bccc4c4e4..d7ae7994a 100644 --- a/src/shadowbox/server/server_access_key.ts +++ b/src/shadowbox/server/server_access_key.ts @@ -176,7 +176,7 @@ export class ServerAccessKeyRepository implements AccessKeyRepository { this.keyConfig.data().nextId += 1; const metricsId = uuidv4(); const password = generatePassword(); - const encryptionMethod = params?.encryptionMethod ?? this.NEW_USER_ENCRYPTION_METHOD; + const encryptionMethod = params?.encryptionMethod || this.NEW_USER_ENCRYPTION_METHOD; // Validate encryption method. if (!isValidCipher(encryptionMethod)) { From 22554db3d453192429bd05df45a22e905fecdee2 Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Wed, 27 Dec 2023 20:20:50 +0500 Subject: [PATCH 11/28] feat(shadowbox): provide the password prop on createNewAccessKey --- src/shadowbox/model/access_key.ts | 2 + src/shadowbox/server/api.yml | 2 +- src/shadowbox/server/manager_service.spec.ts | 41 ++++++++++++++++++++ src/shadowbox/server/manager_service.ts | 25 +++++++++++- src/shadowbox/server/server_access_key.ts | 2 +- 5 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/shadowbox/model/access_key.ts b/src/shadowbox/model/access_key.ts index 6307a4a16..d2f5cfc8c 100644 --- a/src/shadowbox/model/access_key.ts +++ b/src/shadowbox/model/access_key.ts @@ -53,6 +53,8 @@ export interface AccessKeyCreateParams { readonly encryptionMethod?: string; // The name to give the access key. readonly name?: string; + // The password to use for the access key. + readonly password?: string; // The data transfer limit to apply to the access key. readonly dataLimit?: DataLimit; } diff --git a/src/shadowbox/server/api.yml b/src/shadowbox/server/api.yml index 7a316c523..f491cd744 100644 --- a/src/shadowbox/server/api.yml +++ b/src/shadowbox/server/api.yml @@ -150,7 +150,7 @@ paths: type: string examples: '0': - value: '{"method":"aes-192-gcm","name":"First","limit":{"bytes":10000}}' + value: '{"method":"aes-192-gcm","name":"First","password":"8iu8V8EeoFVpwQvQeS9wiD","limit":{"bytes":10000}}' responses: '201': description: The newly created access key diff --git a/src/shadowbox/server/manager_service.spec.ts b/src/shadowbox/server/manager_service.spec.ts index a798b4c6c..fe9743583 100644 --- a/src/shadowbox/server/manager_service.spec.ts +++ b/src/shadowbox/server/manager_service.spec.ts @@ -449,6 +449,47 @@ describe('ShadowsocksManagerService', () => { done(); }); }); + + it('generates a new password when no password is provided', async (done) => { + const repo = getAccessKeyRepository(); + const service = new ShadowsocksManagerServiceBuilder().accessKeys(repo).build(); + + const res = { + send: (httpCode, data) => { + expect(httpCode).toEqual(201); + expect(data.password).toBeDefined(); + responseProcessed = true; // required for afterEach to pass. + }, + }; + await service.createNewAccessKey({params: {}}, res, done); + }); + + it('uses the provided password when one is provided', async (done) => { + const repo = getAccessKeyRepository(); + const service = new ShadowsocksManagerServiceBuilder().accessKeys(repo).build(); + + const PASSWORD = '8iu8V8EeoFVpwQvQeS9wiD'; + const res = { + send: (httpCode, data) => { + expect(httpCode).toEqual(201); + expect(data.password).toEqual(PASSWORD); + responseProcessed = true; // required for afterEach to pass. + }, + }; + await service.createNewAccessKey({params: {password: PASSWORD}}, res, done); + }); + + it('rejects a password that is less than 22 bytes', async (done) => { + const repo = getAccessKeyRepository(); + const service = new ShadowsocksManagerServiceBuilder().accessKeys(repo).build(); + const PASSWORD = 'password'; + const res = {send: SEND_NOTHING}; + await service.createNewAccessKey({params: {password: PASSWORD}}, res, (error) => { + expect(error.statusCode).toEqual(400); + responseProcessed = true; // required for afterEach to pass. + done(); + }); + }); }); describe('setPortForNewAccessKeys', () => { it('changes ports for new access keys', async (done) => { diff --git a/src/shadowbox/server/manager_service.ts b/src/shadowbox/server/manager_service.ts index c0b81ae17..252c17e67 100644 --- a/src/shadowbox/server/manager_service.ts +++ b/src/shadowbox/server/manager_service.ts @@ -192,6 +192,24 @@ function validateDataLimit(limit: unknown): DataLimit { return limit as DataLimit; } +function validatePassword(password: string): string { + if (!password) { + throw new restifyErrors.MissingParameterError( + {statusCode: 400}, + 'Missing `password` parameter' + ); + } + + const bytesOfPassword = Buffer.byteLength(password as string, 'utf8'); + if (bytesOfPassword < 22) { + throw new restifyErrors.InvalidArgumentError( + {statusCode: 400}, + 'password must be at least 22 bytes' + ); + } + return password; +} + // The ShadowsocksManagerService manages the access keys that can use the server // as a proxy using Shadowsocks. It runs an instance of the Shadowsocks server // for each existing access key, with the port and password assigned for that access key. @@ -344,8 +362,13 @@ export class ShadowsocksManagerService { validateDataLimit(dataLimit); } + const password = (req.params.password as string) || undefined; + if (password) { + validatePassword(password); + } + const accessKeyJson = accessKeyToApiJson( - await this.accessKeys.createNewAccessKey({encryptionMethod, name, dataLimit}) + await this.accessKeys.createNewAccessKey({encryptionMethod, name, dataLimit, password}) ); res.send(201, accessKeyJson); logging.debug(`createNewAccessKey response ${JSON.stringify(accessKeyJson)}`); diff --git a/src/shadowbox/server/server_access_key.ts b/src/shadowbox/server/server_access_key.ts index d7ae7994a..2c54ced84 100644 --- a/src/shadowbox/server/server_access_key.ts +++ b/src/shadowbox/server/server_access_key.ts @@ -175,7 +175,7 @@ export class ServerAccessKeyRepository implements AccessKeyRepository { const id = this.keyConfig.data().nextId.toString(); this.keyConfig.data().nextId += 1; const metricsId = uuidv4(); - const password = generatePassword(); + const password = params?.password ?? generatePassword(); const encryptionMethod = params?.encryptionMethod || this.NEW_USER_ENCRYPTION_METHOD; // Validate encryption method. From 450d58c13f9968d71e17feff0ac90b7ddf707b7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danil=20Shaymurzin=20=E2=9A=A1=EF=B8=8F?= Date: Sat, 30 Dec 2023 17:16:41 +0500 Subject: [PATCH 12/28] Update src/shadowbox/server/manager_service.ts Co-authored-by: Vinicius Fortuna --- src/shadowbox/server/manager_service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shadowbox/server/manager_service.ts b/src/shadowbox/server/manager_service.ts index 252c17e67..78cceb01d 100644 --- a/src/shadowbox/server/manager_service.ts +++ b/src/shadowbox/server/manager_service.ts @@ -204,7 +204,7 @@ function validatePassword(password: string): string { if (bytesOfPassword < 22) { throw new restifyErrors.InvalidArgumentError( {statusCode: 400}, - 'password must be at least 22 bytes' + 'Password must be at least 22 bytes' ); } return password; From 49b13a6ef1617b78c211c1d49789fb848445a80b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danil=20Shaymurzin=20=E2=9A=A1=EF=B8=8F?= Date: Sat, 30 Dec 2023 20:39:18 +0500 Subject: [PATCH 13/28] Update src/shadowbox/server/manager_service.ts Co-authored-by: Vinicius Fortuna --- src/shadowbox/server/manager_service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shadowbox/server/manager_service.ts b/src/shadowbox/server/manager_service.ts index 78cceb01d..1c1e02b68 100644 --- a/src/shadowbox/server/manager_service.ts +++ b/src/shadowbox/server/manager_service.ts @@ -200,7 +200,7 @@ function validatePassword(password: string): string { ); } - const bytesOfPassword = Buffer.byteLength(password as string, 'utf8'); + const bytesOfPassword = Buffer.byteLength(password, 'utf8'); if (bytesOfPassword < 22) { throw new restifyErrors.InvalidArgumentError( {statusCode: 400}, From 199d5f62ce92b26cd2674116592a7f2e8e25c4ee Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Sun, 31 Dec 2023 01:00:37 +0500 Subject: [PATCH 14/28] fix: remove odd static props --- package-lock.json | 3 +-- src/shadowbox/server/server_access_key.ts | 7 ++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4449d4b68..3c8bacc3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,8 +31,7 @@ "typescript": "^4" }, "engines": { - "node": "^18.16.0", - "npm": "^9.5.1" + "node": "18.x.x" } }, "node_modules/@colors/colors": { diff --git a/src/shadowbox/server/server_access_key.ts b/src/shadowbox/server/server_access_key.ts index 2c54ced84..6d46e6a5f 100644 --- a/src/shadowbox/server/server_access_key.ts +++ b/src/shadowbox/server/server_access_key.ts @@ -112,8 +112,6 @@ function isValidCipher(cipher: string): boolean { export class ServerAccessKeyRepository implements AccessKeyRepository { private static DATA_LIMITS_ENFORCEMENT_INTERVAL_MS = 60 * 60 * 1000; // 1h private NEW_USER_ENCRYPTION_METHOD = 'chacha20-ietf-poly1305'; - private NEW_USER_DEFAULT_NAME = ''; - private NEW_USER_DEFAULT_DATA_LIMIT = undefined; private accessKeys: ServerAccessKey[]; constructor( @@ -188,9 +186,8 @@ export class ServerAccessKeyRepository implements AccessKeyRepository { encryptionMethod, password, }; - const name = params?.name ?? this.NEW_USER_DEFAULT_NAME; - const dataLimit = params?.dataLimit ?? this.NEW_USER_DEFAULT_DATA_LIMIT; - const accessKey = new ServerAccessKey(id, name, metricsId, proxyParams, dataLimit); + const name = params?.name ?? ''; + const accessKey = new ServerAccessKey(id, name, metricsId, proxyParams); this.accessKeys.push(accessKey); this.saveAccessKeys(); await this.updateServer(); From 476bd2311aad20506afb2eba09272e9768597a86 Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Sun, 31 Dec 2023 01:10:02 +0500 Subject: [PATCH 15/28] fix: dataLimit validation function to get the types of object --- src/shadowbox/server/manager_service.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/shadowbox/server/manager_service.ts b/src/shadowbox/server/manager_service.ts index 1c1e02b68..4b15021db 100644 --- a/src/shadowbox/server/manager_service.ts +++ b/src/shadowbox/server/manager_service.ts @@ -356,10 +356,9 @@ export class ShadowsocksManagerService { ); } - const dataLimit = (req.params.limit as DataLimit) || undefined; - - if (dataLimit) { - validateDataLimit(dataLimit); + let dataLimit: DataLimit = undefined; + if (req.params.limit) { + dataLimit = validateDataLimit(req.params.limit); } const password = (req.params.password as string) || undefined; From d02409102cd942c57954db2d7b5ec0dcd24d0666 Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Sun, 31 Dec 2023 01:46:34 +0500 Subject: [PATCH 16/28] fix: validatePassword prop maybe is non-string --- src/shadowbox/server/manager_service.spec.ts | 12 ++++++++++++ src/shadowbox/server/manager_service.ts | 9 ++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/shadowbox/server/manager_service.spec.ts b/src/shadowbox/server/manager_service.spec.ts index fe9743583..41e5c7297 100644 --- a/src/shadowbox/server/manager_service.spec.ts +++ b/src/shadowbox/server/manager_service.spec.ts @@ -490,6 +490,18 @@ describe('ShadowsocksManagerService', () => { done(); }); }); + + it('rejects a password that is not a string', async (done) => { + const repo = getAccessKeyRepository(); + const service = new ShadowsocksManagerServiceBuilder().accessKeys(repo).build(); + const PASSWORD = Number.MAX_SAFE_INTEGER; + const res = {send: SEND_NOTHING}; + await service.createNewAccessKey({params: {password: PASSWORD}}, res, (error) => { + expect(error.statusCode).toEqual(400); + responseProcessed = true; // required for afterEach to pass. + done(); + }); + }); }); describe('setPortForNewAccessKeys', () => { it('changes ports for new access keys', async (done) => { diff --git a/src/shadowbox/server/manager_service.ts b/src/shadowbox/server/manager_service.ts index 4b15021db..44181df1b 100644 --- a/src/shadowbox/server/manager_service.ts +++ b/src/shadowbox/server/manager_service.ts @@ -192,7 +192,7 @@ function validateDataLimit(limit: unknown): DataLimit { return limit as DataLimit; } -function validatePassword(password: string): string { +function validatePassword(password: unknown): string { if (!password) { throw new restifyErrors.MissingParameterError( {statusCode: 400}, @@ -200,6 +200,13 @@ function validatePassword(password: string): string { ); } + if (typeof password !== 'string') { + throw new restifyErrors.InvalidArgumentError( + {statusCode: 400}, + 'Parameter `password` must be of type string' + ); + } + const bytesOfPassword = Buffer.byteLength(password, 'utf8'); if (bytesOfPassword < 22) { throw new restifyErrors.InvalidArgumentError( From 455991a38a50f0f282aea888fb8d11c6f2f9728e Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Sun, 31 Dec 2023 01:57:41 +0500 Subject: [PATCH 17/28] fix: revert removed prop --- src/shadowbox/server/server_access_key.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shadowbox/server/server_access_key.ts b/src/shadowbox/server/server_access_key.ts index 6d46e6a5f..90c19abb9 100644 --- a/src/shadowbox/server/server_access_key.ts +++ b/src/shadowbox/server/server_access_key.ts @@ -187,7 +187,7 @@ export class ServerAccessKeyRepository implements AccessKeyRepository { password, }; const name = params?.name ?? ''; - const accessKey = new ServerAccessKey(id, name, metricsId, proxyParams); + const accessKey = new ServerAccessKey(id, name, metricsId, proxyParams, params.dataLimit); this.accessKeys.push(accessKey); this.saveAccessKeys(); await this.updateServer(); From ada6d384449d76e2fc94672e5972900c3084904e Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Sun, 31 Dec 2023 01:58:16 +0500 Subject: [PATCH 18/28] style: use condition chain for check prop --- src/shadowbox/server/manager_service.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/shadowbox/server/manager_service.ts b/src/shadowbox/server/manager_service.ts index 44181df1b..29fbf09da 100644 --- a/src/shadowbox/server/manager_service.ts +++ b/src/shadowbox/server/manager_service.ts @@ -363,18 +363,16 @@ export class ShadowsocksManagerService { ); } - let dataLimit: DataLimit = undefined; - if (req.params.limit) { - dataLimit = validateDataLimit(req.params.limit); - } - - const password = (req.params.password as string) || undefined; - if (password) { - validatePassword(password); - } + const dataLimit = req.params.limit ? validateDataLimit(req.params.limit) : undefined; + const password = req.params.password ? validatePassword(req.params.password) : undefined; const accessKeyJson = accessKeyToApiJson( - await this.accessKeys.createNewAccessKey({encryptionMethod, name, dataLimit, password}) + await this.accessKeys.createNewAccessKey({ + encryptionMethod, + name, + dataLimit, + password, + }) ); res.send(201, accessKeyJson); logging.debug(`createNewAccessKey response ${JSON.stringify(accessKeyJson)}`); From 36c6a0cae051145568da76db217f2f93f084b4a0 Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Sun, 31 Dec 2023 02:09:07 +0500 Subject: [PATCH 19/28] fix: params check for dataLimit prop --- src/shadowbox/server/server_access_key.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/shadowbox/server/server_access_key.ts b/src/shadowbox/server/server_access_key.ts index 90c19abb9..1b2da7e60 100644 --- a/src/shadowbox/server/server_access_key.ts +++ b/src/shadowbox/server/server_access_key.ts @@ -187,7 +187,8 @@ export class ServerAccessKeyRepository implements AccessKeyRepository { password, }; const name = params?.name ?? ''; - const accessKey = new ServerAccessKey(id, name, metricsId, proxyParams, params.dataLimit); + const dataLimit = params?.dataLimit; + const accessKey = new ServerAccessKey(id, name, metricsId, proxyParams, dataLimit); this.accessKeys.push(accessKey); this.saveAccessKeys(); await this.updateServer(); From f0fc931a992a36bee4c02dd8db3d483230e6731b Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Sun, 31 Dec 2023 02:19:35 +0500 Subject: [PATCH 20/28] fix: remove double password prop validation --- src/shadowbox/server/manager_service.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/shadowbox/server/manager_service.ts b/src/shadowbox/server/manager_service.ts index 29fbf09da..556aca3ce 100644 --- a/src/shadowbox/server/manager_service.ts +++ b/src/shadowbox/server/manager_service.ts @@ -193,13 +193,6 @@ function validateDataLimit(limit: unknown): DataLimit { } function validatePassword(password: unknown): string { - if (!password) { - throw new restifyErrors.MissingParameterError( - {statusCode: 400}, - 'Missing `password` parameter' - ); - } - if (typeof password !== 'string') { throw new restifyErrors.InvalidArgumentError( {statusCode: 400}, From 21fb92cc9b950cc7b53bfe2e5673c9993def8278 Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Sun, 31 Dec 2023 02:32:24 +0500 Subject: [PATCH 21/28] docs: add properties for api schema --- src/shadowbox/server/api.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/shadowbox/server/api.yml b/src/shadowbox/server/api.yml index f491cd744..56249c871 100644 --- a/src/shadowbox/server/api.yml +++ b/src/shadowbox/server/api.yml @@ -146,8 +146,16 @@ paths: schema: type: object properties: + name: + type: string method: type: string + password: + type: string + port: + type: integer + dataLimit: + $ref: "#/components/schemas/DataLimit" examples: '0': value: '{"method":"aes-192-gcm","name":"First","password":"8iu8V8EeoFVpwQvQeS9wiD","limit":{"bytes":10000}}' From 09cda031d53c988870a7c343efef7aeb64f16a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danil=20Shaymurzin=20=E2=9A=A1=EF=B8=8F?= Date: Wed, 3 Jan 2024 04:31:48 +0500 Subject: [PATCH 22/28] Update manager_service.ts Co-authored-by: Vinicius Fortuna --- src/shadowbox/server/manager_service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shadowbox/server/manager_service.ts b/src/shadowbox/server/manager_service.ts index 556aca3ce..2841c8e92 100644 --- a/src/shadowbox/server/manager_service.ts +++ b/src/shadowbox/server/manager_service.ts @@ -356,8 +356,8 @@ export class ShadowsocksManagerService { ); } - const dataLimit = req.params.limit ? validateDataLimit(req.params.limit) : undefined; - const password = req.params.password ? validatePassword(req.params.password) : undefined; + const dataLimit = req.params.limit ?? validateDataLimit(req.params.limit); + const password = req.params.password ?? validatePassword(req.params.password); const accessKeyJson = accessKeyToApiJson( await this.accessKeys.createNewAccessKey({ From ab03340170054eb6e50724e0cbb4efbf2a757e31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danil=20Shaymurzin=20=E2=9A=A1=EF=B8=8F?= Date: Wed, 10 Jan 2024 07:27:54 +0500 Subject: [PATCH 23/28] Update src/shadowbox/server/manager_service.ts Co-authored-by: Vinicius Fortuna --- src/shadowbox/server/manager_service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shadowbox/server/manager_service.ts b/src/shadowbox/server/manager_service.ts index 2841c8e92..eeed8a19b 100644 --- a/src/shadowbox/server/manager_service.ts +++ b/src/shadowbox/server/manager_service.ts @@ -356,8 +356,8 @@ export class ShadowsocksManagerService { ); } - const dataLimit = req.params.limit ?? validateDataLimit(req.params.limit); - const password = req.params.password ?? validatePassword(req.params.password); + const dataLimit = validateDataLimit(req.params.limit); + const password = validatePassword(req.params.password); const accessKeyJson = accessKeyToApiJson( await this.accessKeys.createNewAccessKey({ From 727bcd0b823af551c9fe898b566166fec851193f Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Thu, 11 Jan 2024 22:57:56 +0500 Subject: [PATCH 24/28] perf(shadownbox): remove checking password bytes lenght --- src/shadowbox/server/manager_service.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/shadowbox/server/manager_service.ts b/src/shadowbox/server/manager_service.ts index eeed8a19b..0dc592e59 100644 --- a/src/shadowbox/server/manager_service.ts +++ b/src/shadowbox/server/manager_service.ts @@ -200,13 +200,6 @@ function validatePassword(password: unknown): string { ); } - const bytesOfPassword = Buffer.byteLength(password, 'utf8'); - if (bytesOfPassword < 22) { - throw new restifyErrors.InvalidArgumentError( - {statusCode: 400}, - 'Password must be at least 22 bytes' - ); - } return password; } From 6a5b8c3033a9b89b1a18e3c8c7a1fbec89ca6f54 Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Thu, 11 Jan 2024 23:14:11 +0500 Subject: [PATCH 25/28] refactor: validating params system --- src/shadowbox/server/manager_service.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/shadowbox/server/manager_service.ts b/src/shadowbox/server/manager_service.ts index 0dc592e59..366384f54 100644 --- a/src/shadowbox/server/manager_service.ts +++ b/src/shadowbox/server/manager_service.ts @@ -179,9 +179,10 @@ function validateAccessKeyId(accessKeyId: unknown): string { } function validateDataLimit(limit: unknown): DataLimit { - if (!limit) { - throw new restifyErrors.MissingParameterError({statusCode: 400}, 'Missing `limit` parameter'); + if (typeof limit === 'undefined') { + return undefined; } + const bytes = (limit as DataLimit).bytes; if (!(Number.isInteger(bytes) && bytes >= 0)) { throw new restifyErrors.InvalidArgumentError( @@ -192,15 +193,18 @@ function validateDataLimit(limit: unknown): DataLimit { return limit as DataLimit; } -function validatePassword(password: unknown): string { - if (typeof password !== 'string') { +function validateIsString(value: unknown): string { + if (typeof value === 'undefined') { + return undefined; + } + + if (typeof value !== 'string') { throw new restifyErrors.InvalidArgumentError( {statusCode: 400}, - 'Parameter `password` must be of type string' + `Expected a string, instead got ${value} of type ${typeof value}` ); } - - return password; + return value; } // The ShadowsocksManagerService manages the access keys that can use the server @@ -350,7 +354,7 @@ export class ShadowsocksManagerService { } const dataLimit = validateDataLimit(req.params.limit); - const password = validatePassword(req.params.password); + const password = validateIsString(req.params.password); const accessKeyJson = accessKeyToApiJson( await this.accessKeys.createNewAccessKey({ From c92f4896e2fd6e294a453247e2fa4f61bf3f2b53 Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Thu, 11 Jan 2024 23:36:25 +0500 Subject: [PATCH 26/28] refactor: validate unknown string params --- src/shadowbox/server/manager_service.ts | 36 +++++-------------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/src/shadowbox/server/manager_service.ts b/src/shadowbox/server/manager_service.ts index 366384f54..ab202b55d 100644 --- a/src/shadowbox/server/manager_service.ts +++ b/src/shadowbox/server/manager_service.ts @@ -193,18 +193,14 @@ function validateDataLimit(limit: unknown): DataLimit { return limit as DataLimit; } -function validateIsString(value: unknown): string { - if (typeof value === 'undefined') { - return undefined; - } - - if (typeof value !== 'string') { +function validateStringParam(param: unknown, paramName: string): string { + if (typeof param !== 'string') { throw new restifyErrors.InvalidArgumentError( {statusCode: 400}, - `Expected a string, instead got ${value} of type ${typeof value}` + `Expected a string for ${paramName}, instead got ${param} of type ${typeof param}` ); } - return value; + return param; } // The ShadowsocksManagerService manages the access keys that can use the server @@ -333,28 +329,10 @@ export class ShadowsocksManagerService { ): Promise { try { logging.debug(`createNewAccessKey request ${JSON.stringify(req.params)}`); - const encryptionMethod = req.params.method || ''; - if (typeof encryptionMethod !== 'string') { - return next( - new restifyErrors.InvalidArgumentError( - {statusCode: 400}, - `Expected a string encryptionMethod, instead got ${encryptionMethod} of type ${typeof encryptionMethod}` - ) - ); - } - - const name = req.params.name || ''; - if (typeof name !== 'string') { - return next( - new restifyErrors.InvalidArgumentError( - {statusCode: 400}, - `Expected a string name, instead got ${name} of type ${typeof name}` - ) - ); - } - + const encryptionMethod = validateStringParam(req.params.method || '', 'encryptionMethod'); + const name = validateStringParam(req.params.name || '', 'name'); const dataLimit = validateDataLimit(req.params.limit); - const password = validateIsString(req.params.password); + const password = validateStringParam(req.params.password, 'password'); const accessKeyJson = accessKeyToApiJson( await this.accessKeys.createNewAccessKey({ From d53542dae1b8a9807b6f561ec5b892873353bb0c Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Thu, 11 Jan 2024 23:38:34 +0500 Subject: [PATCH 27/28] chore: remove unusable test --- src/shadowbox/server/manager_service.spec.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/shadowbox/server/manager_service.spec.ts b/src/shadowbox/server/manager_service.spec.ts index 41e5c7297..ab7f1305f 100644 --- a/src/shadowbox/server/manager_service.spec.ts +++ b/src/shadowbox/server/manager_service.spec.ts @@ -479,18 +479,6 @@ describe('ShadowsocksManagerService', () => { await service.createNewAccessKey({params: {password: PASSWORD}}, res, done); }); - it('rejects a password that is less than 22 bytes', async (done) => { - const repo = getAccessKeyRepository(); - const service = new ShadowsocksManagerServiceBuilder().accessKeys(repo).build(); - const PASSWORD = 'password'; - const res = {send: SEND_NOTHING}; - await service.createNewAccessKey({params: {password: PASSWORD}}, res, (error) => { - expect(error.statusCode).toEqual(400); - responseProcessed = true; // required for afterEach to pass. - done(); - }); - }); - it('rejects a password that is not a string', async (done) => { const repo = getAccessKeyRepository(); const service = new ShadowsocksManagerServiceBuilder().accessKeys(repo).build(); From 9afefff7c5c77a1a06942454230da987a57bc9a3 Mon Sep 17 00:00:00 2001 From: Danil Shaymurzin Date: Thu, 11 Jan 2024 23:43:22 +0500 Subject: [PATCH 28/28] fix: skip to validating undefined param --- src/shadowbox/server/manager_service.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/shadowbox/server/manager_service.ts b/src/shadowbox/server/manager_service.ts index ab202b55d..8e896a1f8 100644 --- a/src/shadowbox/server/manager_service.ts +++ b/src/shadowbox/server/manager_service.ts @@ -194,6 +194,10 @@ function validateDataLimit(limit: unknown): DataLimit { } function validateStringParam(param: unknown, paramName: string): string { + if (typeof param === 'undefined') { + return undefined; + } + if (typeof param !== 'string') { throw new restifyErrors.InvalidArgumentError( {statusCode: 400},