From dbe5ce14d8acff3502b62b568c968f5e4988acf9 Mon Sep 17 00:00:00 2001 From: Samuel Bodin <1637651+bodinsamuel@users.noreply.github.com> Date: Tue, 19 Mar 2024 16:28:40 +0100 Subject: [PATCH] fix(eslint): pass #6 (#1871) ## Describe your changes - Frontend eslint was not configured correctly - Manually fix some Eslint errors --- .eslintrc | 31 +++ packages/cli/lib/cli.ts | 2 +- packages/cli/lib/services/compile.service.ts | 9 +- packages/cli/lib/services/deploy.service.ts | 26 +- .../cli/lib/services/verification.service.ts | 2 +- packages/cli/scripts/v1-v2.js | 2 + packages/frontend/lib/index.ts | 7 +- packages/jobs/lib/runner/runner.ts | 4 +- packages/persist/lib/server.ts | 14 +- packages/server/lib/clients/auth.client.ts | 151 ++++++------ packages/server/lib/clients/oauth1.client.ts | 43 ++-- .../lib/clients/oauth1.client.unit.test.ts | 14 ++ .../lib/controllers/appAuth.controller.ts | 2 +- .../lib/controllers/oauth.controller.ts | 2 +- .../lib/controllers/proxy.controller.ts | 42 ++-- .../controllers/proxy.controller.unit.test.ts | 18 +- packages/server/lib/server.ts | 14 +- packages/server/lib/utils/config.ts | 10 +- packages/server/lib/utils/web-socket-error.ts | 222 +++++++++--------- 19 files changed, 316 insertions(+), 299 deletions(-) create mode 100644 packages/server/lib/clients/oauth1.client.unit.test.ts diff --git a/.eslintrc b/.eslintrc index fa850afcf9..90f00d1576 100644 --- a/.eslintrc +++ b/.eslintrc @@ -68,6 +68,37 @@ ] }, "overrides": [ + { + "files": [ + "packages/frontend/**/*.ts" + ], + "env": { + "browser": true, + "es6": true, + "node": false + }, + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module", + "ecmaFeatures": { + "impliedStrict": true, + "jsx": false + }, + "project": "packages/frontend/tsconfig.json" + }, + "rules": { + "no-console": "off", + "@typescript-eslint/no-misused-promises": [ + "warn", + { + "checksVoidReturn": { + "arguments": false, + "attributes": false + } + } + ] + } + }, { "files": [ "packages/webapp/**/*.tsx" diff --git a/packages/cli/lib/cli.ts b/packages/cli/lib/cli.ts index 9ed9b9c317..e8d71a16d2 100644 --- a/packages/cli/lib/cli.ts +++ b/packages/cli/lib/cli.ts @@ -316,7 +316,7 @@ export const tscWatch = async (debug = false) => { const integrationFiles = glob.sync(`./*.ts`); for (const file of integrationFiles) { // strip the file to just the last part - const strippedFile = file.replace(/^.*[\\\/]/, ''); + const strippedFile = file.replace(/^.*[\\/]/, ''); compileFile(strippedFile); } return; diff --git a/packages/cli/lib/services/compile.service.ts b/packages/cli/lib/services/compile.service.ts index 5890a6868c..e19c7dd0f0 100644 --- a/packages/cli/lib/services/compile.service.ts +++ b/packages/cli/lib/services/compile.service.ts @@ -30,13 +30,14 @@ class CompileService { await modelService.createModelFile(); } + const compilerOptions = (JSON.parse(tsconfig) as { compilerOptions: Record }).compilerOptions; const compiler = tsNode.create({ skipProject: true, // when installed locally we don't want ts-node to pick up the package tsconfig.json file - compilerOptions: JSON.parse(tsconfig).compilerOptions + compilerOptions }); if (debug) { - printDebug(`Compiler options: ${JSON.stringify(JSON.parse(tsconfig).compilerOptions, null, 2)}`); + printDebug(`Compiler options: ${JSON.stringify(compilerOptions, null, 2)}`); } const integrationFiles = syncName ? [`./${syncName}.ts`] : glob.sync(`./*.ts`); @@ -61,7 +62,7 @@ class CompileService { continue; } - const syncConfig = [...providerConfiguration?.syncs, ...providerConfiguration?.actions].find( + const syncConfig = [...(providerConfiguration?.syncs || []), ...(providerConfiguration?.actions || [])].find( (sync) => sync.name === path.basename(filePath, '.ts') ); const type = syncConfig?.type || SyncConfigType.SYNC; @@ -73,7 +74,7 @@ class CompileService { continue; } const result = compiler.compile(fs.readFileSync(filePath, 'utf8'), filePath); - const jsFilePath = filePath.replace(/\/[^\/]*$/, `/dist/${path.basename(filePath.replace('.ts', '.js'))}`); + const jsFilePath = filePath.replace(/\/[^/]*$/, `/dist/${path.basename(filePath.replace('.ts', '.js'))}`); fs.writeFileSync(jsFilePath, result); console.log(chalk.green(`Compiled "${filePath}" successfully`)); diff --git a/packages/cli/lib/services/deploy.service.ts b/packages/cli/lib/services/deploy.service.ts index 166cef707f..2e7619939b 100644 --- a/packages/cli/lib/services/deploy.service.ts +++ b/packages/cli/lib/services/deploy.service.ts @@ -1,6 +1,6 @@ import chalk from 'chalk'; import promptly from 'promptly'; -import axios, { AxiosResponse } from 'axios'; +import axios, { AxiosError, AxiosResponse } from 'axios'; import type { SyncType, SyncDeploymentResult, StandardNangoConfig, IncomingFlowConfig, NangoConfigMetadata } from '@nangohq/shared'; import { SyncConfigType, localFileService, getInterval, stagingHost, cloudHost } from '@nangohq/shared'; import configService from './config.service.js'; @@ -159,21 +159,13 @@ class DeployService { console.log(chalk.red('Syncs/Actions were not deployed. Exiting')); process.exit(0); } - } catch (err: any) { + } catch (err) { let errorMessage; - if (!err?.response?.data) { - const { - message, - stack, - config: { method }, - code, - status - } = err?.toJSON(); - - const errorObject = { message, stack, code, status, url, method }; + if (err instanceof AxiosError) { + const errorObject = { message: err.message, stack: err.stack, code: err.code, status: err.status, url, method: err.config?.method }; errorMessage = JSON.stringify(errorObject, null, 2); } else { - errorMessage = JSON.stringify(err.response.data, null, 2); + errorMessage = JSON.stringify(err, null, 2); } console.log(chalk.red(`Error deploying the syncs/actions with the following error: ${errorMessage}`)); process.exit(1); @@ -192,8 +184,8 @@ class DeployService { ) { await axios .post(url, body, { headers: enrichHeaders(), httpsAgent: httpsAgent() }) - .then((response: AxiosResponse) => { - const results: SyncDeploymentResult[] = response.data; + .then((response: AxiosResponse) => { + const results = response.data; if (results.length === 0) { console.log(chalk.green(`Successfully removed the syncs/actions.`)); } else { @@ -201,8 +193,8 @@ class DeployService { console.log(chalk.green(`Successfully deployed the syncs/actions: ${nameAndVersions.join(', ')}!`)); } }) - .catch((err: any) => { - const errorMessage = JSON.stringify(err.response.data, null, 2); + .catch((err) => { + const errorMessage = JSON.stringify(err instanceof AxiosError ? err.response?.data : err, null, 2); console.log(chalk.red(`Error deploying the syncs/actions with the following error: ${errorMessage}`)); process.exit(1); }); diff --git a/packages/cli/lib/services/verification.service.ts b/packages/cli/lib/services/verification.service.ts index 319bc9216e..b467caca3e 100644 --- a/packages/cli/lib/services/verification.service.ts +++ b/packages/cli/lib/services/verification.service.ts @@ -18,7 +18,7 @@ class VerificationService { if (debug) { printDebug(`Current full working directory is read as: ${cwd}`); } - const currentDirectorySplit = cwd.split(/[\/\\]/); + const currentDirectorySplit = cwd.split(/[/\\]/); const currentDirectory = currentDirectorySplit[currentDirectorySplit.length - 1]; if (debug) { diff --git a/packages/cli/scripts/v1-v2.js b/packages/cli/scripts/v1-v2.js index 3e9cb8df3b..b9bca60e32 100755 --- a/packages/cli/scripts/v1-v2.js +++ b/packages/cli/scripts/v1-v2.js @@ -54,6 +54,7 @@ function convertYAML(inputYAML) { } data.integrations[integration].actions[taskName] = task; + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete data.integrations[integration][taskName]; } else if (task.runs) { change.type = 'sync'; @@ -101,6 +102,7 @@ function convertYAML(inputYAML) { } data.integrations[integration].syncs[taskName] = task; + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete data.integrations[integration][taskName]; } if (change.changes.length > 0) { diff --git a/packages/frontend/lib/index.ts b/packages/frontend/lib/index.ts index 5afda3a666..f44242c971 100644 --- a/packages/frontend/lib/index.ts +++ b/packages/frontend/lib/index.ts @@ -73,7 +73,7 @@ export default class Nango { // The websockets path is considered relative to the baseUrl, and with the protocol updated const websocketUrl = new URL(config.websocketsPath, baseUrl); this.websocketsBaseUrl = websocketUrl.toString().replace('https://', 'wss://').replace('http://', 'ws://'); - } catch (err) { + } catch { throw new AuthError('Invalid URL provided for the Nango host.', 'invalidHostUrl'); } } @@ -112,7 +112,7 @@ export default class Nango { try { new URL(url); - } catch (err) { + } catch { throw new AuthError('Invalid URL provided for the Nango host.', 'invalidHostUrl'); } @@ -421,7 +421,7 @@ class AuthorizationModal { const data = JSON.parse(message.data); switch (data.message_type) { - case WSMessageType.ConnectionAck: + case WSMessageType.ConnectionAck: { if (this.debug) { console.log(debugLogPrefix, 'Connection ack received. Opening modal...'); } @@ -429,6 +429,7 @@ class AuthorizationModal { const wsClientId = data.ws_client_id; this.open(wsClientId); break; + } case WSMessageType.Error: if (this.debug) { console.log(debugLogPrefix, 'Error received. Rejecting authorization...'); diff --git a/packages/jobs/lib/runner/runner.ts b/packages/jobs/lib/runner/runner.ts index 193f17095f..2969bbdb77 100644 --- a/packages/jobs/lib/runner/runner.ts +++ b/packages/jobs/lib/runner/runner.ts @@ -33,7 +33,7 @@ export async function getOrStartRunner(runnerId: string): Promise { try { await runner.client.health.query(); healthCheck = true; - } catch (err) { + } catch { await new Promise((resolve) => setTimeout(resolve, 1000)); } } @@ -103,7 +103,7 @@ class RunnerCache { } } return undefined; - } catch (err) { + } catch { return undefined; } } diff --git a/packages/persist/lib/server.ts b/packages/persist/lib/server.ts index 2576df640c..2667c075ae 100644 --- a/packages/persist/lib/server.ts +++ b/packages/persist/lib/server.ts @@ -3,7 +3,7 @@ import type { Request, Response, NextFunction } from 'express'; import { validateRequest } from 'zod-express'; import { z } from 'zod'; import persistController from './controllers/persist.controller.js'; -import { logLevelValues } from '@nangohq/shared'; +import { logLevelValues, logger } from '@nangohq/shared'; export const server = express(); server.use(express.json({ limit: '100mb' })); @@ -12,14 +12,14 @@ server.use((req: Request, res: Response, next: NextFunction) => { const originalSend = res.send; res.send = function (body: any) { if (res.statusCode >= 400) { - console.log(`[Persist] [Error] ${req.method} ${req.path} ${res.statusCode} '${JSON.stringify(body)}'`); + logger.info(`[Persist] [Error] ${req.method} ${req.path} ${res.statusCode} '${JSON.stringify(body)}'`); } originalSend.call(this, body) as any; return this; }; next(); if (res.statusCode < 400) { - console.log(`[Persist] ${req.method} ${req.path} ${res.statusCode}`); + logger.info(`[Persist] ${req.method} ${req.path} ${res.statusCode}`); } }); @@ -39,7 +39,7 @@ server.post( msg: z.string() }) }), - persistController.saveActivityLog + persistController.saveActivityLog.bind(persistController) ); const validateRecordsRequest = validateRequest({ @@ -64,9 +64,9 @@ const validateRecordsRequest = validateRequest({ }) }); const recordPath = '/environment/:environmentId/connection/:nangoConnectionId/sync/:syncId/job/:syncJobId/records'; -server.post(recordPath, validateRecordsRequest, persistController.saveRecords); -server.delete(recordPath, validateRecordsRequest, persistController.deleteRecords); -server.put(recordPath, validateRecordsRequest, persistController.updateRecords); +server.post(recordPath, validateRecordsRequest, persistController.saveRecords.bind(persistController)); +server.delete(recordPath, validateRecordsRequest, persistController.deleteRecords.bind(persistController)); +server.put(recordPath, validateRecordsRequest, persistController.updateRecords.bind(persistController)); server.use((_req: Request, res: Response, next: NextFunction) => { res.status(404); diff --git a/packages/server/lib/clients/auth.client.ts b/packages/server/lib/clients/auth.client.ts index b1f0fb266d..21f02fe6bd 100644 --- a/packages/server/lib/clients/auth.client.ts +++ b/packages/server/lib/clients/auth.client.ts @@ -19,89 +19,84 @@ const sessionStore = new KnexSessionStore({ sidfieldname: 'sid' }); -export class AuthClient { - static setup(app: express.Express) { - app.use(cookieParser()); - app.use(express.static(path.join(dirname(), 'public'))); - - app.use( - session({ - secret: process.env['NANGO_ADMIN_KEY'] || 'nango', - resave: false, - saveUninitialized: false, - store: sessionStore, - name: 'nango_session', - unset: 'destroy', - cookie: { maxAge: 30 * 24 * 60 * 60 * 1000, secure: false }, - rolling: true +export function setupAuth(app: express.Express) { + app.use(cookieParser()); + app.use(express.static(path.join(dirname(), 'public'))); + + app.use( + session({ + secret: process.env['NANGO_ADMIN_KEY'] || 'nango', + resave: false, + saveUninitialized: false, + store: sessionStore, + name: 'nango_session', + unset: 'destroy', + cookie: { maxAge: 30 * 24 * 60 * 60 * 1000, secure: false }, + rolling: true + }) + ); + + app.use(passport.initialize()); + app.use(passport.session()); + + if (isCloud()) { + passport.use( + new LocalStrategy({ usernameField: 'email', passwordField: 'password' }, async function ( + email: string, + password: string, + cb: (error: any, user?: Express.User | false, options?: any) => void + ) { + const user = await userService.getUserByEmail(email); + + if (user == null) { + return cb(null, false, { message: 'Incorrect email or password.' }); + } + + const proposedHashedPassword = await util.promisify(crypto.pbkdf2)(password, user.salt, 310000, 32, 'sha256'); + const actualHashedPassword = Buffer.from(user.hashed_password, 'base64'); + + if (proposedHashedPassword.length !== actualHashedPassword.length || !crypto.timingSafeEqual(actualHashedPassword, proposedHashedPassword)) { + return cb(null, false, { message: 'Incorrect email or password.' }); + } + + return cb(null, user); }) ); + } else { + passport.use( + new BasicStrategy(async function (username, password, done) { + const user = await userService.getUserById(0); - app.use(passport.initialize()); - app.use(passport.session()); - - if (isCloud()) { - passport.use( - new LocalStrategy({ usernameField: 'email', passwordField: 'password' }, async function ( - email: string, - password: string, - cb: (error: any, user?: Express.User | false, options?: any) => void - ) { - const user = await userService.getUserByEmail(email); - - if (user == null) { - return cb(null, false, { message: 'Incorrect email or password.' }); - } - - const proposedHashedPassword = await util.promisify(crypto.pbkdf2)(password, user.salt, 310000, 32, 'sha256'); - const actualHashedPassword = Buffer.from(user.hashed_password, 'base64'); - - if ( - proposedHashedPassword.length !== actualHashedPassword.length || - !crypto.timingSafeEqual(actualHashedPassword, proposedHashedPassword) - ) { - return cb(null, false, { message: 'Incorrect email or password.' }); - } - - return cb(null, user); - }) - ); - } else { - passport.use( - new BasicStrategy(async function (username, password, done) { - const user = await userService.getUserById(0); - - if (!isBasicAuthEnabled()) { - return done(null, user); - } - - if (username !== process.env['NANGO_DASHBOARD_USERNAME']) { - return done(null, false); - } - - if (password !== process.env['NANGO_DASHBOARD_PASSWORD']) { - return done(null, false); - } - - if (!user) { - return done(null, false); - } - + if (!isBasicAuthEnabled()) { return done(null, user); - }) - ); - } - - passport.serializeUser(function (user: Express.User, cb) { - process.nextTick(function () { - cb(null, { id: user.id, email: user.email, name: user.name }); - }); + } + + if (username !== process.env['NANGO_DASHBOARD_USERNAME']) { + return done(null, false); + } + + if (password !== process.env['NANGO_DASHBOARD_PASSWORD']) { + return done(null, false); + } + + if (!user) { + return done(null, false); + } + + return done(null, user); + }) + ); + } + + passport.serializeUser(function (user: Express.User, cb) { + process.nextTick(function () { + cb(null, { id: user.id, email: user.email, name: user.name }); }); + }); - passport.deserializeUser(function (user: Express.User, cb) { - process.nextTick(function () { - return cb(null, user); - }); + passport.deserializeUser(function (user: Express.User, cb) { + process.nextTick(function () { + return cb(null, user); }); - } + }); } diff --git a/packages/server/lib/clients/oauth1.client.ts b/packages/server/lib/clients/oauth1.client.ts index fba6bb1c68..87595064e6 100644 --- a/packages/server/lib/clients/oauth1.client.ts +++ b/packages/server/lib/clients/oauth1.client.ts @@ -1,7 +1,3 @@ -/* - * Copyright (c) 2022 Nango, all rights reserved. - */ - import oAuth1 from 'oauth'; import type { Config as ProviderConfig, TemplateOAuth1 as ProviderTemplateOAuth1, Template as ProviderTemplate } from '@nangohq/shared'; import { AuthModes } from '@nangohq/shared'; @@ -58,7 +54,7 @@ export class OAuth1Client { const promise = new Promise((resolve, reject) => { this.client.getOAuthRequestToken( additionalTokenParams, - (error: { statusCode: number; data?: any }, token: any, token_secret: any, parsed_query_string: any) => { + (error: { statusCode: number; data?: any }, token: string, token_secret: string, parsed_query_string: string) => { if (error) { reject(error); } else { @@ -76,44 +72,35 @@ export class OAuth1Client { } async getOAuthAccessToken(oauth_token: string, oauth_token_secret: string, oauth_token_verifier: string): Promise { - let additionalTokenParams = {}; + let additionalTokenParams: Record = {}; if (this.authConfig.token_params) { additionalTokenParams = this.authConfig.token_params; } - const promise = new Promise((resolve, reject) => { + const promise = new Promise>((resolve, reject) => { // This is lifted from https://github.com/ciaranj/node-oauth/blob/master/lib/oauth.js#L456 // Unfortunately that main method does not expose extra params like the initial token request does ¯\_(ツ)_/¯ - // @ts-expect-error additionalTokenParams['oauth_verifier'] = oauth_token_verifier; - // @ts-expect-error + // @ts-expect-error we access private method this.client._performSecureRequest( oauth_token, oauth_token_secret, - // @ts-expect-error + // @ts-expect-error we access private method this.client._clientOptions.accessTokenHttpMethod, - // @ts-expect-error + // @ts-expect-error we access private method this.client._accessUrl, additionalTokenParams, null, undefined, - // @ts-expect-error - function (error, data, response) { - if (error) reject(error); - else { - // @ts-expect-error - const queryParams = new URLSearchParams(data); - - const parsedFull = {}; - for (const pair of queryParams) { - // @ts-expect-error - parsedFull[pair[0]] = pair[1]; - } - - resolve(parsedFull); + function (error, data, _response) { + if (error) { + reject(error); + return; } + + resolve(extractQueryParams(data)); } ); }); @@ -137,6 +124,10 @@ export class OAuth1Client { const url = new URL(this.authConfig.authorization_url); const params = new URLSearchParams(queryParams); - return `${url}?${params.toString()}`; + return `${url.href}?${params.toString()}`; } } + +export function extractQueryParams(data: string | Buffer | undefined): Record { + return Object.fromEntries(new URLSearchParams(typeof data === 'string' ? data : data?.toString())); +} diff --git a/packages/server/lib/clients/oauth1.client.unit.test.ts b/packages/server/lib/clients/oauth1.client.unit.test.ts new file mode 100644 index 0000000000..d9af44aacc --- /dev/null +++ b/packages/server/lib/clients/oauth1.client.unit.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from 'vitest'; +import { extractQueryParams } from './oauth1.client'; + +describe('oauth1', () => { + it('should extract query params', () => { + const res = extractQueryParams('baz=bar&foo=bar'); + expect(res).toStrictEqual({ baz: 'bar', foo: 'bar' }); + }); + + it('should extract undefined query params', () => { + const res = extractQueryParams(undefined); + expect(res).toStrictEqual({}); + }); +}); diff --git a/packages/server/lib/controllers/appAuth.controller.ts b/packages/server/lib/controllers/appAuth.controller.ts index 8174210f16..8993f94423 100644 --- a/packages/server/lib/controllers/appAuth.controller.ts +++ b/packages/server/lib/controllers/appAuth.controller.ts @@ -21,7 +21,7 @@ import { AuthModes } from '@nangohq/shared'; import { missesInterpolationParam } from '../utils/utils.js'; -import { WSErrBuilder } from '../utils/web-socket-error.js'; +import * as WSErrBuilder from '../utils/web-socket-error.js'; import oAuthSessionService from '../services/oauth-session.service.js'; import publisher from '../clients/publisher.client.js'; diff --git a/packages/server/lib/controllers/oauth.controller.ts b/packages/server/lib/controllers/oauth.controller.ts index f93377be65..c0432a8210 100644 --- a/packages/server/lib/controllers/oauth.controller.ts +++ b/packages/server/lib/controllers/oauth.controller.ts @@ -52,7 +52,7 @@ import { ConnectionConfig } from '@nangohq/shared'; import publisher from '../clients/publisher.client.js'; -import { WSErrBuilder } from '../utils/web-socket-error.js'; +import * as WSErrBuilder from '../utils/web-socket-error.js'; import oAuthSessionService from '../services/oauth-session.service.js'; class OAuthController { diff --git a/packages/server/lib/controllers/proxy.controller.ts b/packages/server/lib/controllers/proxy.controller.ts index 3bab17be40..0b2d70603f 100644 --- a/packages/server/lib/controllers/proxy.controller.ts +++ b/packages/server/lib/controllers/proxy.controller.ts @@ -82,7 +82,7 @@ class ProxyController { const queryString = querystring.stringify(query); const endpoint = `${path}${queryString ? `?${queryString}` : ''}`; - const headers = this.parseHeaders(req); + const headers = parseHeaders(req); const externalConfig: UserProvidedProxyConfiguration = { endpoint, @@ -374,31 +374,31 @@ class ProxyController { console.error(content); } } +} - /** - * Parse Headers - * @param {Request} req Express request object - */ - private parseHeaders(req: Request) { - const headers = req.rawHeaders; - const HEADER_PROXY_LOWER = 'nango-proxy-'; - const HEADER_PROXY_UPPER = 'Nango-Proxy-'; - const forwardedHeaders: ForwardedHeaders = {}; - - if (!headers) { - return forwardedHeaders; - } +/** + * Parse Headers + * @param {Request} req Express request object + */ +export function parseHeaders(req: Pick) { + const headers = req.rawHeaders; + const HEADER_PROXY_LOWER = 'nango-proxy-'; + const HEADER_PROXY_UPPER = 'Nango-Proxy-'; + const forwardedHeaders: ForwardedHeaders = {}; + + if (!headers) { + return forwardedHeaders; + } - for (let i = 0, n = headers.length; i < n; i += 2) { - const headerKey = headers[i]; + for (let i = 0, n = headers.length; i < n; i += 2) { + const headerKey = headers[i]; - if (headerKey?.toLowerCase().startsWith(HEADER_PROXY_LOWER) || headerKey?.startsWith(HEADER_PROXY_UPPER)) { - forwardedHeaders[headerKey.slice(HEADER_PROXY_LOWER.length)] = headers[i + 1] || ''; - } + if (headerKey?.toLowerCase().startsWith(HEADER_PROXY_LOWER) || headerKey?.startsWith(HEADER_PROXY_UPPER)) { + forwardedHeaders[headerKey.slice(HEADER_PROXY_LOWER.length)] = headers[i + 1] || ''; } - - return forwardedHeaders; } + + return forwardedHeaders; } export default new ProxyController(); diff --git a/packages/server/lib/controllers/proxy.controller.unit.test.ts b/packages/server/lib/controllers/proxy.controller.unit.test.ts index 9ff7cae4e8..b47d88e2da 100644 --- a/packages/server/lib/controllers/proxy.controller.unit.test.ts +++ b/packages/server/lib/controllers/proxy.controller.unit.test.ts @@ -1,14 +1,14 @@ import { expect, describe, it } from 'vitest'; -import proxyController from './proxy.controller.js'; +import { parseHeaders } from './proxy.controller.js'; +import type { Request } from 'express'; describe('Proxy Controller Construct URL Tests', () => { it('Should parse headers that starts with Nango-Proxy or nango-proxy', () => { - const req: any = { + const req: Pick = { rawHeaders: ['Nango-Proxy-Test-Header', 'TestValue', 'nango-proxy-another-header', 'AnotherValue', 'Irrelevant-Header', 'IrrelevantValue'] }; - // @ts-expect-error - const parsedHeaders = proxyController.parseHeaders(req); + const parsedHeaders = parseHeaders(req); expect(parsedHeaders).toEqual({ 'Test-Header': 'TestValue', @@ -17,21 +17,19 @@ describe('Proxy Controller Construct URL Tests', () => { }); it('Should return an empty object when there are no Nango-Proxy or nango-proxy headers', () => { - const req: any = { + const req: Pick = { rawHeaders: ['Irrelevant-Header-One', 'IrrelevantValueOne', 'Irrelevant-Header-Two', 'IrrelevantValueTwo'] }; - // @ts-expect-error - const parsedHeaders = proxyController.parseHeaders(req); + const parsedHeaders = parseHeaders(req); expect(parsedHeaders).toEqual({}); }); it('Should handle the case when rawHeaders is not an array or empty', () => { - const req: any = {}; + const req = {}; - // @ts-expect-error - const parsedHeaders = proxyController.parseHeaders(req); + const parsedHeaders = parseHeaders(req as Pick); expect(parsedHeaders).toEqual({}); }); diff --git a/packages/server/lib/server.ts b/packages/server/lib/server.ts index b0f0f2dbc9..3425b62715 100644 --- a/packages/server/lib/server.ts +++ b/packages/server/lib/server.ts @@ -26,7 +26,7 @@ import { WebSocketServer, WebSocket } from 'ws'; import http from 'http'; import express from 'express'; import cors from 'cors'; -import { AuthClient } from './clients/auth.client.js'; +import { setupAuth } from './clients/auth.client.js'; import publisher from './clients/publisher.client.js'; import passport from 'passport'; import environmentController from './controllers/environment.controller.js'; @@ -53,16 +53,16 @@ const { NANGO_MIGRATE_AT_START = 'true' } = process.env; const app = express(); // Auth -AuthClient.setup(app); +setupAuth(app); -const apiAuth = [authMiddleware.secretKeyAuth, rateLimiterMiddleware]; -const apiPublicAuth = [authMiddleware.publicKeyAuth, rateLimiterMiddleware]; +const apiAuth = [authMiddleware.secretKeyAuth.bind(authMiddleware), rateLimiterMiddleware]; +const apiPublicAuth = [authMiddleware.publicKeyAuth.bind(authMiddleware), rateLimiterMiddleware]; const webAuth = isCloud() || isEnterprise() - ? [passport.authenticate('session'), authMiddleware.sessionAuth, rateLimiterMiddleware] + ? [passport.authenticate('session'), authMiddleware.sessionAuth.bind(authMiddleware), rateLimiterMiddleware] : isBasicAuthEnabled() - ? [passport.authenticate('basic', { session: false }), authMiddleware.basicAuth, rateLimiterMiddleware] - : [authMiddleware.noAuth, rateLimiterMiddleware]; + ? [passport.authenticate('basic', { session: false }), authMiddleware.basicAuth.bind(authMiddleware), rateLimiterMiddleware] + : [authMiddleware.noAuth.bind(authMiddleware), rateLimiterMiddleware]; app.use( express.json({ diff --git a/packages/server/lib/utils/config.ts b/packages/server/lib/utils/config.ts index e7987fa4a1..da7f15ab1f 100644 --- a/packages/server/lib/utils/config.ts +++ b/packages/server/lib/utils/config.ts @@ -1,11 +1,5 @@ import * as dotenv from 'dotenv'; -class Config { - constructor() { - if (process.env['SERVER_RUN_MODE'] !== 'DOCKERIZED') { - dotenv.config({ path: '../../.env' }); - } - } +if (process.env['SERVER_RUN_MODE'] !== 'DOCKERIZED') { + dotenv.config({ path: '../../.env' }); } - -export default new Config(); diff --git a/packages/server/lib/utils/web-socket-error.ts b/packages/server/lib/utils/web-socket-error.ts index b6f088baea..14836ccd66 100644 --- a/packages/server/lib/utils/web-socket-error.ts +++ b/packages/server/lib/utils/web-socket-error.ts @@ -3,116 +3,114 @@ export interface WSErr { message: string; } -export class WSErrBuilder { - public static UnknownAuthMode(authMode: string): WSErr { - return { - type: 'auth_mode_err', - message: `Auth mode ${authMode} not supported.` - }; - } - - public static ConnectionNotFound(connectionId: string): WSErr { - return { - type: 'connection_not_found', - message: `Connection ${connectionId} not found.` - }; - } - - public static InvalidCallbackOAuth1(): WSErr { - return { - type: 'callback_err', - message: `Did not get oauth_token and/or oauth_verifier in the callback.` - }; - } - - public static InvalidCallbackOAuth2(): WSErr { - return { - type: 'callback_err', - message: `Did not get authorization code in the callback.` - }; - } - - public static UnknownGrantType(grantType: string): WSErr { - return { - type: 'grant_type_err', - message: `The grant type "${grantType}" is not supported by this OAuth flow.` - }; - } - - public static MissingConnectionId(): WSErr { - return { - type: 'missing_connection_id', - message: `Missing Connection ID.` - }; - } - - public static MissingProviderConfigKey(): WSErr { - return { - type: 'no_provider_config_key', - message: `Missing Provider Config unique key.` - }; - } - - public static UnknownProviderConfigKey(providerConfigKey: string): WSErr { - return { - type: 'provider_config_err', - message: `Could not find a Provider Config matching the "${providerConfigKey}" key.` - }; - } - - public static InvalidProviderConfig(providerConfigKey: string): WSErr { - return { - type: 'provider_config_err', - message: `Provider Config "${providerConfigKey}" is missing cliend ID, secret and/or scopes.` - }; - } - - public static InvalidState(state: string): WSErr { - return { - type: 'state_err', - message: `Invalid state parameter passed in the callback: ${state}` - }; - } - - public static TokenError(): WSErr { - return { - type: 'token_err', - message: `Error storing/retrieving the token.` - }; - } - - public static UnknownProviderTemplate(providerTemplate: string): WSErr { - return { - type: 'unknown_config_key', - message: `No Provider Configuration with key "${providerTemplate}".` - }; - } - - public static InvalidConnectionConfig(url: string, params: string): WSErr { - return { - type: 'url_param_err', - message: `Missing Connection Config param(s) in Auth request to interpolate url ${url}. Provided Connection Config: ${params}` - }; - } - - public static UnknownError(errorMessage?: string): WSErr { - return { - type: 'unknown_err', - message: `Unknown error during the Oauth flow.${errorMessage ? ' ' + errorMessage : ''}` - }; - } - - public static MissingHmac(): WSErr { - return { - type: 'missing_hmac', - message: `Missing HMAC digest.` - }; - } - - public static InvalidHmac(): WSErr { - return { - type: 'invalid_hmac', - message: `Invalid HMAC digest.` - }; - } +export function UnknownAuthMode(authMode: string): WSErr { + return { + type: 'auth_mode_err', + message: `Auth mode ${authMode} not supported.` + }; +} + +export function ConnectionNotFound(connectionId: string): WSErr { + return { + type: 'connection_not_found', + message: `Connection ${connectionId} not found.` + }; +} + +export function InvalidCallbackOAuth1(): WSErr { + return { + type: 'callback_err', + message: `Did not get oauth_token and/or oauth_verifier in the callback.` + }; +} + +export function InvalidCallbackOAuth2(): WSErr { + return { + type: 'callback_err', + message: `Did not get authorization code in the callback.` + }; +} + +export function UnknownGrantType(grantType: string): WSErr { + return { + type: 'grant_type_err', + message: `The grant type "${grantType}" is not supported by this OAuth flow.` + }; +} + +export function MissingConnectionId(): WSErr { + return { + type: 'missing_connection_id', + message: `Missing Connection ID.` + }; +} + +export function MissingProviderConfigKey(): WSErr { + return { + type: 'no_provider_config_key', + message: `Missing Provider Config unique key.` + }; +} + +export function UnknownProviderConfigKey(providerConfigKey: string): WSErr { + return { + type: 'provider_config_err', + message: `Could not find a Provider Config matching the "${providerConfigKey}" key.` + }; +} + +export function InvalidProviderConfig(providerConfigKey: string): WSErr { + return { + type: 'provider_config_err', + message: `Provider Config "${providerConfigKey}" is missing cliend ID, secret and/or scopes.` + }; +} + +export function InvalidState(state: string): WSErr { + return { + type: 'state_err', + message: `Invalid state parameter passed in the callback: ${state}` + }; +} + +export function TokenError(): WSErr { + return { + type: 'token_err', + message: `Error storing/retrieving the token.` + }; +} + +export function UnknownProviderTemplate(providerTemplate: string): WSErr { + return { + type: 'unknown_config_key', + message: `No Provider Configuration with key "${providerTemplate}".` + }; +} + +export function InvalidConnectionConfig(url: string, params: string): WSErr { + return { + type: 'url_param_err', + message: `Missing Connection Config param(s) in Auth request to interpolate url ${url}. Provided Connection Config: ${params}` + }; +} + +export function UnknownError(errorMessage?: string): WSErr { + return { + type: 'unknown_err', + message: `Unknown error during the Oauth flow.${errorMessage ? ' ' + errorMessage : ''}` + }; +} + +export function MissingHmac(): WSErr { + return { + type: 'missing_hmac', + message: `Missing HMAC digest.` + }; +} + +export function InvalidHmac(): WSErr { + return { + type: 'invalid_hmac', + message: `Invalid HMAC digest.` + }; }