diff --git a/packages/git-proxy-cli/index.ts b/packages/git-proxy-cli/index.ts index 31ebc8a4c..0cc60b6d5 100644 --- a/packages/git-proxy-cli/index.ts +++ b/packages/git-proxy-cli/index.ts @@ -1,5 +1,5 @@ #!/usr/bin/env node -import axios from 'axios'; +import axios, { isAxiosError } from 'axios'; import yargs from 'yargs/yargs'; import { hideBin } from 'yargs/helpers'; import fs from 'fs'; @@ -47,13 +47,16 @@ async function login(username: string, password: string) { const user = `"${response.data.username}" <${response.data.email}>`; const isAdmin = response.data.admin ? ' (admin)' : ''; console.log(`Login ${user}${isAdmin}: OK`); - } catch (error: any) { - if (error.response) { + } catch (error: unknown) { + if (isAxiosError(error) && error.response) { console.error(`Error: Login '${username}': '${error.response.status}'`); process.exitCode = 1; - } else { + } else if (error instanceof Error) { console.error(`Error: Login '${username}': '${error.message}'`); process.exitCode = 2; + } else { + console.error(`Error: Login '${username}': '${error}'`); + process.exitCode = 2; } } } @@ -165,8 +168,9 @@ async function getGitPushes(filters: Partial) { }); console.log(util.inspect(records, false, null, false)); - } catch (error: any) { - console.error(`Error: List: '${error.message}'`); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error(`Error: List: '${msg}'`); process.exitCode = 2; } } @@ -207,12 +211,12 @@ async function authoriseGitPush(id: string) { ); console.log(`Authorise: ID: '${id}': OK`); - } catch (error: any) { + } catch (error: unknown) { // default error - let errorMessage = `Error: Authorise: '${error.message}'`; + let errorMessage = `Error: Authorise: '${error instanceof Error ? error.message : String(error)}'`; process.exitCode = 2; - if (error.response) { + if (isAxiosError(error) && error.response) { switch (error.response.status) { case 401: errorMessage = 'Error: Authorise: Authentication required'; @@ -254,12 +258,12 @@ async function rejectGitPush(id: string) { ); console.log(`Reject: ID: '${id}': OK`); - } catch (error: any) { + } catch (error: unknown) { // default error - let errorMessage = `Error: Reject: '${error.message}'`; + let errorMessage = `Error: Reject: '${error instanceof Error ? error.message : String(error)}'`; process.exitCode = 2; - if (error.response) { + if (isAxiosError(error) && error.response) { switch (error.response.status) { case 401: errorMessage = 'Error: Reject: Authentication required'; @@ -301,12 +305,12 @@ async function cancelGitPush(id: string) { ); console.log(`Cancel: ID: '${id}': OK`); - } catch (error: any) { + } catch (error: unknown) { // default error - let errorMessage = `Error: Cancel: '${error.message}'`; + let errorMessage = `Error: Cancel: '${error instanceof Error ? error.message : String(error)}'`; process.exitCode = 2; - if (error.response) { + if (isAxiosError(error) && error.response) { switch (error.response.status) { case 401: errorMessage = 'Error: Cancel: Authentication required'; @@ -338,8 +342,9 @@ async function logout() { headers: { Cookie: cookies }, }, ); - } catch (error: any) { - console.log(`Warning: Logout: '${error.message}'`); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.log(`Warning: Logout: '${msg}'`); } } @@ -362,10 +367,10 @@ async function reloadConfig() { await axios.post(`${baseUrl}/api/v1/admin/reload-config`, {}, { headers: { Cookie: cookies } }); console.log('Configuration reloaded successfully'); - } catch (error: any) { - const errorMessage = `Error: Reload config: '${error.message}'`; + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error(`Error: Reload config: '${msg}'`); process.exitCode = 2; - console.error(errorMessage); } } @@ -408,11 +413,11 @@ async function createUser( ); console.log(`User '${username}' created successfully`); - } catch (error: any) { - let errorMessage = `Error: Create User: '${error.message}'`; + } catch (error: unknown) { + let errorMessage = `Error: Create User: '${error instanceof Error ? error.message : String(error)}'`; process.exitCode = 2; - if (error.response) { + if (isAxiosError(error) && error.response) { switch (error.response.status) { case 401: errorMessage = 'Error: Create User: Authentication required'; diff --git a/packages/git-proxy-cli/test/testCli.test.ts b/packages/git-proxy-cli/test/testCli.test.ts index 3e5545d1f..4c73db39d 100644 --- a/packages/git-proxy-cli/test/testCli.test.ts +++ b/packages/git-proxy-cli/test/testCli.test.ts @@ -3,8 +3,7 @@ import path from 'path'; import { describe, it, beforeAll, afterAll } from 'vitest'; import { setConfigFile } from '../../../src/config/file'; - -import { Repo } from '../../../src/db/types'; +import { SAMPLE_REPO } from '../../../src/proxy/processors/constants'; setConfigFile(path.join(process.cwd(), 'test', 'testCli.proxy.config.json')); @@ -14,6 +13,7 @@ const GHOST_PUSH_ID = '0000000000000000000000000000000000000000__79b4d8953cbc324bcc1eb53d6412ff89666c241f'; // repo for test cases const TEST_REPO_CONFIG = { + ...SAMPLE_REPO, project: 'finos', name: 'git-proxy-test', url: 'https://github.com/finos/git-proxy-test.git', @@ -220,7 +220,7 @@ describe('test git-proxy-cli', function () { const pushId = `auth000000000000000000000000000000000000__${Date.now()}`; beforeAll(async function () { - await helper.addRepoToDb(TEST_REPO_CONFIG as Repo); + await helper.addRepoToDb(TEST_REPO_CONFIG); await helper.addUserToDb(TEST_USER, TEST_PASSWORD, TEST_EMAIL, TEST_GIT_ACCOUNT); await helper.addGitPushToDb(pushId, TEST_REPO_CONFIG.url, TEST_USER, TEST_EMAIL); }); @@ -297,7 +297,7 @@ describe('test git-proxy-cli', function () { const pushId = `cancel0000000000000000000000000000000000__${Date.now()}`; beforeAll(async function () { - await helper.addRepoToDb(TEST_REPO_CONFIG as Repo); + await helper.addRepoToDb(TEST_REPO_CONFIG); await helper.addUserToDb(TEST_USER, TEST_PASSWORD, TEST_EMAIL, TEST_GIT_ACCOUNT); await helper.addGitPushToDb(pushId, TEST_USER, TEST_EMAIL, TEST_REPO); }); @@ -420,7 +420,7 @@ describe('test git-proxy-cli', function () { const pushId = `reject0000000000000000000000000000000000__${Date.now()}`; beforeAll(async function () { - await helper.addRepoToDb(TEST_REPO_CONFIG as Repo); + await helper.addRepoToDb(TEST_REPO_CONFIG); await helper.addUserToDb(TEST_USER, TEST_PASSWORD, TEST_EMAIL, TEST_GIT_ACCOUNT); await helper.addGitPushToDb(pushId, TEST_REPO_CONFIG.url, TEST_USER, TEST_EMAIL); }); @@ -582,8 +582,9 @@ describe('test git-proxy-cli', function () { // Clean up the created user try { await helper.removeUserFromDb(uniqueUsername); - } catch (error: any) { - // Ignore cleanup errors + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error(`Error cleaning up user: ${msg}`); } } }); @@ -612,8 +613,9 @@ describe('test git-proxy-cli', function () { // Clean up the created user try { await helper.removeUserFromDb(uniqueUsername); - } catch (error: any) { - console.error('Error cleaning up user', error); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error(`Error cleaning up user: ${msg}`); } } }); @@ -625,7 +627,7 @@ describe('test git-proxy-cli', function () { const pushId = `0000000000000000000000000000000000000000__${Date.now()}`; beforeAll(async function () { - await helper.addRepoToDb(TEST_REPO_CONFIG as Repo); + await helper.addRepoToDb(TEST_REPO_CONFIG); await helper.addUserToDb(TEST_USER, TEST_PASSWORD, TEST_EMAIL, TEST_GIT_ACCOUNT); await helper.addGitPushToDb(pushId, TEST_REPO_CONFIG.url, TEST_USER, TEST_EMAIL); }); diff --git a/packages/git-proxy-cli/test/testCliUtils.ts b/packages/git-proxy-cli/test/testCliUtils.ts index a0b19ceb0..320642527 100644 --- a/packages/git-proxy-cli/test/testCliUtils.ts +++ b/packages/git-proxy-cli/test/testCliUtils.ts @@ -2,6 +2,7 @@ import fs from 'fs'; import util from 'util'; import { exec } from 'child_process'; import { expect } from 'vitest'; +import { Request } from 'express'; import Proxy from '../../../src/proxy'; import { Action } from '../../../src/proxy/actions/Action'; @@ -10,12 +11,24 @@ import { exec as execProcessor } from '../../../src/proxy/processors/push-action import * as db from '../../../src/db'; import { Repo } from '../../../src/db/types'; import service from '../../../src/service'; +import { CommitData } from '../../../src/proxy/processors/types'; const execAsync = util.promisify(exec); // cookie file name const GIT_PROXY_COOKIE_FILE = 'git-proxy-cookie'; +/** + * Type guard to check if error is from child_process exec + */ +function isExecError(error: unknown): error is Error & { + code: number; + stdout: string; + stderr: string; +} { + return error instanceof Error && 'code' in error && 'stdout' in error && 'stderr' in error; +} + /** * @async * @param {string} cli - The CLI command to be executed. @@ -54,28 +67,29 @@ async function runCli( expect(stderr).toContain(expectedErrorMessage); }); } - } catch (error: any) { - const exitCode = error.code; - if (!exitCode) { - // an AssertionError is thrown from failing some of the expectations - // in the 'try' block: forward it to Mocha to process + } catch (error: unknown) { + if (isExecError(error)) { + const exitCode = error.code; + + if (debug) { + console.log(`error.stdout: ${error.stdout}`); + console.log(`error.stderr: ${error.stderr}`); + } + expect(exitCode).toEqual(expectedExitCode); + if (expectedMessages) { + expectedMessages.forEach((expectedMessage) => { + expect(error.stdout).toContain(expectedMessage); + }); + } + if (expectedErrorMessages) { + expectedErrorMessages.forEach((expectedErrorMessage) => { + expect(error.stderr).toContain(expectedErrorMessage); + }); + } + } else { + // Assertion error, forward to Vitest to process throw error; } - if (debug) { - console.log(`error.stdout: ${error.stdout}`); - console.log(`error.stderr: ${error.stderr}`); - } - expect(exitCode).toEqual(expectedExitCode); - if (expectedMessages) { - expectedMessages.forEach((expectedMessage) => { - expect(error.stdout).toContain(expectedMessage); - }); - } - if (expectedErrorMessages) { - expectedErrorMessages.forEach((expectedErrorMessage) => { - expect(error.stderr).toContain(expectedErrorMessage); - }); - } } finally { if (debug) { console.log(`cli: '${cli}': done`); @@ -214,7 +228,7 @@ async function addGitPushToDb( `\n\n\nGitProxy has received your push:\n\nhttp://localhost:8080/requests/${id}\n\n\n`, // blockedMessage null, // content ); - const commitData = []; + const commitData: CommitData[] = []; commitData.push({ tree: 'tree test', parent: 'parent', @@ -223,10 +237,11 @@ async function addGitPushToDb( message: 'message', authorEmail: 'authorEmail', committerEmail: 'committerEmail', + commitTimestamp: '1234567890', }); action.commitData = commitData; action.addStep(step); - const result = await execProcessor(null, action); + const result = await execProcessor({} as Request, action); if (debug) { console.log(`New git push added to DB: ${util.inspect(result)}`); } diff --git a/src/config/ConfigLoader.ts b/src/config/ConfigLoader.ts index 22dd6abfd..bd39d2747 100644 --- a/src/config/ConfigLoader.ts +++ b/src/config/ConfigLoader.ts @@ -20,7 +20,7 @@ function isValidPath(filePath: string): boolean { try { path.resolve(filePath); return true; - } catch (error) { + } catch (error: unknown) { return false; } } @@ -79,8 +79,9 @@ export class ConfigLoader extends EventEmitter { fs.mkdirSync(this.cacheDir, { recursive: true }); console.log(`Created cache directory at ${this.cacheDir}`); return true; - } catch (err) { - console.error('Failed to create cache directory:', err); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error('Failed to create cache directory:', msg); return false; } } @@ -153,8 +154,9 @@ export class ConfigLoader extends EventEmitter { try { console.log(`Loading configuration from ${source.type} source`); return await this.loadFromSource(source); - } catch (error: any) { - console.error(`Error loading from ${source.type} source:`, error.message); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error(`Error loading from ${source.type} source:`, msg); return null; } }), @@ -189,8 +191,9 @@ export class ConfigLoader extends EventEmitter { } else { console.log('Configuration has not changed, no update needed'); } - } catch (error: any) { - console.error('Error reloading configuration:', error); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error('Error reloading configuration:', msg); this.emit('configurationError', error); } finally { this.isReloading = false; @@ -223,10 +226,9 @@ export class ConfigLoader extends EventEmitter { // Use QuickType to validate and parse the configuration try { return Convert.toGitProxyConfig(content); - } catch (error) { - throw new Error( - `Invalid configuration file format: ${error instanceof Error ? error.message : 'Unknown error'}`, - ); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + throw new Error(`Invalid configuration file format: ${msg}`); } } @@ -244,10 +246,9 @@ export class ConfigLoader extends EventEmitter { const configJson = typeof response.data === 'string' ? response.data : JSON.stringify(response.data); return Convert.toGitProxyConfig(configJson); - } catch (error) { - throw new Error( - `Invalid configuration format from HTTP source: ${error instanceof Error ? error.message : 'Unknown error'}`, - ); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + throw new Error(`Invalid configuration format from HTTP source: ${msg}`); } } @@ -306,18 +307,20 @@ export class ConfigLoader extends EventEmitter { try { await execFileAsync('git', ['clone', source.repository, repoDir], execOptions); console.log('Repository cloned successfully'); - } catch (error: any) { - console.error('Failed to clone repository:', error.message); - throw new Error(`Failed to clone repository: ${error.message}`); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error('Failed to clone repository:', msg); + throw new Error(`Failed to clone repository: ${msg}`); } } else { console.log(`Pulling latest changes from ${source.repository}`); try { await execFileAsync('git', ['pull'], { cwd: repoDir }); console.log('Repository pulled successfully'); - } catch (error: any) { - console.error('Failed to pull repository:', error.message); - throw new Error(`Failed to pull repository: ${error.message}`); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error('Failed to pull repository:', msg); + throw new Error(`Failed to pull repository: ${msg}`); } } @@ -327,9 +330,10 @@ export class ConfigLoader extends EventEmitter { try { await execFileAsync('git', ['checkout', source.branch], { cwd: repoDir }); console.log(`Branch ${source.branch} checked out successfully`); - } catch (error: any) { - console.error(`Failed to checkout branch ${source.branch}:`, error.message); - throw new Error(`Failed to checkout branch ${source.branch}: ${error.message}`); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error(`Failed to checkout branch ${source.branch}:`, msg); + throw new Error(`Failed to checkout branch ${source.branch}: ${msg}`); } } @@ -351,9 +355,10 @@ export class ConfigLoader extends EventEmitter { const config = Convert.toGitProxyConfig(content); console.log('Configuration loaded successfully from Git'); return config; - } catch (error: any) { - console.error('Failed to read or parse configuration file:', error.message); - throw new Error(`Failed to read or parse configuration file: ${error.message}`); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error('Failed to read or parse configuration file:', msg); + throw new Error(`Failed to read or parse configuration file: ${msg}`); } } diff --git a/src/config/index.ts b/src/config/index.ts index ca35c8b06..b9de36b53 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -61,8 +61,9 @@ function loadFullConfiguration(): GitProxyConfig { // Don't use QuickType validation for partial configurations const rawUserConfig = JSON.parse(userConfigContent); userSettings = cleanUndefinedValues(rawUserConfig); - } catch (error) { - console.error(`Error loading user config from ${userConfigFile}:`, error); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error(`Error loading user config from ${userConfigFile}:`, msg); throw error; } } @@ -311,14 +312,16 @@ const handleConfigUpdate = async (newConfig: Configuration) => { await proxy.start(); console.log('Services restarted with new configuration'); - } catch (error) { - console.error('Failed to apply new configuration:', error); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error('Failed to apply new configuration:', msg); // Attempt to restart with previous config try { const proxy = require('../proxy'); await proxy.start(); - } catch (startError) { - console.error('Failed to restart services:', startError); + } catch (startError: unknown) { + const msg = startError instanceof Error ? startError.message : String(startError); + console.error('Failed to restart services:', msg); } } }; @@ -355,7 +358,8 @@ try { loadFullConfiguration(); initializeConfigLoader(); console.log('Configuration loaded successfully'); -} catch (error) { - console.error('Failed to load configuration:', error); +} catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error('Failed to load configuration:', msg); throw error; } diff --git a/src/db/file/pushes.ts b/src/db/file/pushes.ts index 416845688..5f335fa33 100644 --- a/src/db/file/pushes.ts +++ b/src/db/file/pushes.ts @@ -3,6 +3,7 @@ import Datastore from '@seald-io/nedb'; import { Action } from '../../proxy/actions/Action'; import { toClass } from '../helper'; import { PushQuery } from '../types'; +import { Attestation } from '../../proxy/processors/types'; const COMPACTION_INTERVAL = 1000 * 60 * 60 * 24; // once per day @@ -15,10 +16,11 @@ if (process.env.NODE_ENV === 'test') { } try { db.ensureIndex({ fieldName: 'id', unique: true }); -} catch (e) { +} catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); console.error( 'Failed to build a unique index of push id values. Please check your database file for duplicate entries or delete the duplicate through the UI and restart. ', - e, + msg, ); } db.setAutocompactionInterval(COMPACTION_INTERVAL); @@ -97,7 +99,10 @@ export const writeAudit = async (action: Action): Promise => { }); }; -export const authorise = async (id: string, attestation: any): Promise<{ message: string }> => { +export const authorise = async ( + id: string, + attestation?: Attestation, +): Promise<{ message: string }> => { const action = await getPush(id); if (!action) { throw new Error(`push ${id} not found`); @@ -111,7 +116,10 @@ export const authorise = async (id: string, attestation: any): Promise<{ message return { message: `authorised ${id}` }; }; -export const reject = async (id: string, attestation: any): Promise<{ message: string }> => { +export const reject = async ( + id: string, + attestation?: Attestation, +): Promise<{ message: string }> => { const action = await getPush(id); if (!action) { throw new Error(`push ${id} not found`); diff --git a/src/db/file/repo.ts b/src/db/file/repo.ts index 48214122c..19d72ac83 100644 --- a/src/db/file/repo.ts +++ b/src/db/file/repo.ts @@ -22,10 +22,11 @@ if (process.env.NODE_ENV === 'test') { } try { db.ensureIndex({ fieldName: 'url', unique: true }); -} catch (e) { +} catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); console.error( 'Failed to build a unique index of Repository URLs. Please check your database file for duplicate entries or delete the duplicate through the UI and restart. ', - e, + msg, ); } diff --git a/src/db/file/users.ts b/src/db/file/users.ts index a39b5b170..1588b5d0a 100644 --- a/src/db/file/users.ts +++ b/src/db/file/users.ts @@ -22,18 +22,20 @@ if (process.env.NODE_ENV === 'test') { // Using a unique constraint with the index try { db.ensureIndex({ fieldName: 'username', unique: true }); -} catch (e) { +} catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); console.error( 'Failed to build a unique index of usernames. Please check your database file for duplicate entries or delete the duplicate through the UI and restart. ', - e, + msg, ); } try { db.ensureIndex({ fieldName: 'email', unique: true }); -} catch (e) { +} catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); console.error( 'Failed to build a unique index of user email addresses. Please check your database file for duplicate entries or delete the duplicate through the UI and restart. ', - e, + msg, ); } db.setAutocompactionInterval(COMPACTION_INTERVAL); diff --git a/src/db/index.ts b/src/db/index.ts index f71179cf3..5a727d94f 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -6,6 +6,7 @@ import * as mongo from './mongo'; import * as neDb from './file'; import { Action } from '../proxy/actions/Action'; import MongoDBStore from 'connect-mongo'; +import { Attestation } from '../proxy/processors/types'; import { processGitUrl } from '../proxy/routes/helper'; import { initializeFolders } from './file/helper'; @@ -164,10 +165,10 @@ export const getPushes = (query: Partial): Promise => start export const writeAudit = (action: Action): Promise => start().writeAudit(action); export const getPush = (id: string): Promise => start().getPush(id); export const deletePush = (id: string): Promise => start().deletePush(id); -export const authorise = (id: string, attestation: any): Promise<{ message: string }> => +export const authorise = (id: string, attestation?: Attestation): Promise<{ message: string }> => start().authorise(id, attestation); export const cancel = (id: string): Promise<{ message: string }> => start().cancel(id); -export const reject = (id: string, attestation: any): Promise<{ message: string }> => +export const reject = (id: string, attestation?: Attestation): Promise<{ message: string }> => start().reject(id, attestation); export const getRepos = (query?: Partial): Promise => start().getRepos(query); export const getRepo = (name: string): Promise => start().getRepo(name); diff --git a/src/db/mongo/pushes.ts b/src/db/mongo/pushes.ts index 968b2858a..36c467661 100644 --- a/src/db/mongo/pushes.ts +++ b/src/db/mongo/pushes.ts @@ -2,6 +2,7 @@ import { connect, findDocuments, findOneDocument } from './helper'; import { Action } from '../../proxy/actions'; import { toClass } from '../helper'; import { PushQuery } from '../types'; +import { Attestation } from '../../proxy/processors/types'; const collectionName = 'pushes'; @@ -43,7 +44,7 @@ export const getPushes = async ( }; export const getPush = async (id: string): Promise => { - const doc = await findOneDocument(collectionName, { id }); + const doc = await findOneDocument(collectionName, { id }); return doc ? (toClass(doc, Action.prototype) as Action) : null; }; @@ -63,7 +64,10 @@ export const writeAudit = async (action: Action): Promise => { await collection.updateOne({ id: data.id }, { $set: data }, options); }; -export const authorise = async (id: string, attestation: any): Promise<{ message: string }> => { +export const authorise = async ( + id: string, + attestation?: Attestation, +): Promise<{ message: string }> => { const action = await getPush(id); if (!action) { throw new Error(`push ${id} not found`); @@ -77,7 +81,10 @@ export const authorise = async (id: string, attestation: any): Promise<{ message return { message: `authorised ${id}` }; }; -export const reject = async (id: string, attestation: any): Promise<{ message: string }> => { +export const reject = async ( + id: string, + attestation?: Attestation, +): Promise<{ message: string }> => { const action = await getPush(id); if (!action) { throw new Error(`push ${id} not found`); diff --git a/src/db/mongo/repo.ts b/src/db/mongo/repo.ts index 655ef40b1..17be25e2a 100644 --- a/src/db/mongo/repo.ts +++ b/src/db/mongo/repo.ts @@ -1,11 +1,11 @@ import _ from 'lodash'; -import { Repo } from '../types'; +import { Repo, RepoQuery } from '../types'; import { connect } from './helper'; import { toClass } from '../helper'; import { ObjectId, OptionalId, Document } from 'mongodb'; const collectionName = 'repos'; -export const getRepos = async (query: any = {}): Promise => { +export const getRepos = async (query: Partial = {}): Promise => { const collection = await connect(collectionName); const docs = await collection.find(query).toArray(); return _.chain(docs) diff --git a/src/db/mongo/users.ts b/src/db/mongo/users.ts index f4300c39e..c352acf53 100644 --- a/src/db/mongo/users.ts +++ b/src/db/mongo/users.ts @@ -1,6 +1,6 @@ import { OptionalId, Document, ObjectId } from 'mongodb'; import { toClass } from '../helper'; -import { User } from '../types'; +import { User, UserQuery } from '../types'; import { connect } from './helper'; import _ from 'lodash'; const collectionName = 'users'; @@ -23,7 +23,7 @@ export const findUserByOIDC = async function (oidcId: string): Promise { +export const getUsers = async function (query: Partial = {}): Promise { if (query.username) { query.username = query.username.toLowerCase(); } diff --git a/src/db/types.ts b/src/db/types.ts index e4ae2eab5..5f7a7d6ba 100644 --- a/src/db/types.ts +++ b/src/db/types.ts @@ -1,5 +1,6 @@ import { Action } from '../proxy/actions/Action'; import MongoDBStore from 'connect-mongo'; +import { Attestation } from '../proxy/processors/types'; export type PushQuery = { error: boolean; @@ -96,9 +97,9 @@ export interface Sink { writeAudit: (action: Action) => Promise; getPush: (id: string) => Promise; deletePush: (id: string) => Promise; - authorise: (id: string, attestation: any) => Promise<{ message: string }>; + authorise: (id: string, attestation?: Attestation) => Promise<{ message: string }>; cancel: (id: string) => Promise<{ message: string }>; - reject: (id: string, attestation: any) => Promise<{ message: string }>; + reject: (id: string, attestation?: Attestation) => Promise<{ message: string }>; getRepos: (query?: Partial) => Promise; getRepo: (name: string) => Promise; getRepoByUrl: (url: string) => Promise; diff --git a/src/plugin.ts b/src/plugin.ts index 92fb9a99c..abc304316 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,4 +1,7 @@ +import { Request } from 'express'; + import { Action } from './proxy/actions'; +import Module from 'node:module'; const lpModule = import('load-plugin'); /* eslint-disable @typescript-eslint/no-unused-expressions */ @@ -67,7 +70,7 @@ class PluginLoader { const moduleResults = await Promise.allSettled(modulePromises); const loadedModules = moduleResults .filter( - (result): result is PromiseFulfilledResult => + (result): result is PromiseFulfilledResult => result.status === 'fulfilled' && result.value !== null, ) .map((result) => result.value); @@ -87,7 +90,7 @@ class PluginLoader { */ const pluginTypeResults = settledPluginTypeResults .filter( - (result): result is PromiseFulfilledResult => + (result): result is PromiseFulfilledResult => result.status === 'fulfilled' && result.value !== null, ) .map((result) => result.value); @@ -101,17 +104,18 @@ class PluginLoader { combinedPlugins.forEach((plugin) => { console.log(`Loaded plugin: ${plugin.constructor.name}`); }); - } catch (error) { - console.error(`Error loading plugins: ${error}`); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error(`Error loading plugins: ${msg}`); } } /** * Resolve & load a Node module from either a given specifier (file path, import specifier or package name) using load-plugin. * @param {string} target The module specifier to load - * @return {Promise} A resolved & loaded Module + * @return {Promise} A resolved & loaded Module */ - private async _loadPluginModule(target: string): Promise { + private async _loadPluginModule(target: string): Promise { const lp = await lpModule; const resolvedModuleFile = await lp.resolvePlugin(target); return await lp.loadPlugin(resolvedModuleFile); @@ -177,7 +181,7 @@ class ProxyPlugin { */ class PushActionPlugin extends ProxyPlugin { isGitProxyPushActionPlugin: boolean; - exec: (req: any, action: Action) => Promise; + exec: (req: Request, action: Action) => Promise; /** * Wrapper class which contains at least one function executed as part of the action chain for git push operations. @@ -193,7 +197,7 @@ class PushActionPlugin extends ProxyPlugin { * - Takes in an Action object as the second parameter (`action`). * - Returns a Promise that resolves to an Action. */ - constructor(exec: (req: any, action: Action) => Promise) { + constructor(exec: (req: Request, action: Action) => Promise) { super(); this.isGitProxyPushActionPlugin = true; this.exec = exec; @@ -205,7 +209,7 @@ class PushActionPlugin extends ProxyPlugin { */ class PullActionPlugin extends ProxyPlugin { isGitProxyPullActionPlugin: boolean; - exec: (req: any, action: Action) => Promise; + exec: (req: Request, action: Action) => Promise; /** * Wrapper class which contains at least one function executed as part of the action chain for git pull operations. @@ -221,7 +225,7 @@ class PullActionPlugin extends ProxyPlugin { * - Takes in an Action object as the second parameter (`action`). * - Returns a Promise that resolves to an Action. */ - constructor(exec: (req: any, action: Action) => Promise) { + constructor(exec: (req: Request, action: Action) => Promise) { super(); this.isGitProxyPullActionPlugin = true; this.exec = exec; diff --git a/src/proxy/actions/autoActions.ts b/src/proxy/actions/autoActions.ts index 450c97d80..245fee5aa 100644 --- a/src/proxy/actions/autoActions.ts +++ b/src/proxy/actions/autoActions.ts @@ -5,14 +5,20 @@ const attemptAutoApproval = async (action: Action) => { try { const attestation = { timestamp: new Date(), - autoApproved: true, + automated: true, + questions: [], + reviewer: { + username: 'system', + email: 'system@git-proxy.com', + }, }; await authorise(action.id, attestation); console.log('Push automatically approved by system.'); return true; - } catch (error: any) { - console.error('Error during auto-approval:', error.message); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error('Error during auto-approval:', msg); return false; } }; @@ -21,14 +27,20 @@ const attemptAutoRejection = async (action: Action) => { try { const attestation = { timestamp: new Date(), - autoApproved: true, + automated: true, + questions: [], + reviewer: { + username: 'system', + email: 'system@git-proxy.com', + }, }; await reject(action.id, attestation); console.log('Push automatically rejected by system.'); return true; - } catch (error: any) { - console.error('Error during auto-rejection:', error.message); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error('Error during auto-rejection:', msg); return false; } }; diff --git a/src/proxy/chain.ts b/src/proxy/chain.ts index 5aeac2d96..1ba0e6a80 100644 --- a/src/proxy/chain.ts +++ b/src/proxy/chain.ts @@ -1,9 +1,11 @@ +import { Request, Response } from 'express'; + import { PluginLoader } from '../plugin'; import { Action } from './actions'; import * as proc from './processors'; import { attemptAutoApproval, attemptAutoRejection } from './actions/autoActions'; -const pushActionChain: ((req: any, action: Action) => Promise)[] = [ +const pushActionChain: ((req: Request, action: Action) => Promise)[] = [ proc.push.parsePush, proc.push.checkEmptyBranch, proc.push.checkRepoInAuthorisedList, @@ -23,17 +25,17 @@ const pushActionChain: ((req: any, action: Action) => Promise)[] = [ proc.push.blockForAuth, ]; -const pullActionChain: ((req: any, action: Action) => Promise)[] = [ +const pullActionChain: ((req: Request, action: Action) => Promise)[] = [ proc.push.checkRepoInAuthorisedList, ]; -const defaultActionChain: ((req: any, action: Action) => Promise)[] = [ +const defaultActionChain: ((req: Request, action: Action) => Promise)[] = [ proc.push.checkRepoInAuthorisedList, ]; let pluginsInserted = false; -export const executeChain = async (req: any, res: any): Promise => { +export const executeChain = async (req: Request, _res: Response): Promise => { let action: Action = {} as Action; try { @@ -46,9 +48,10 @@ export const executeChain = async (req: any, res: any): Promise => { break; } } - } catch (e) { + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); action.error = true; - action.errorMessage = `An error occurred when executing the chain: ${e}`; + action.errorMessage = `An error occurred when executing the chain: ${msg}`; console.error(action.errorMessage); } finally { await proc.push.audit(req, action); @@ -70,7 +73,7 @@ let chainPluginLoader: PluginLoader; export const getChain = async ( action: Action, -): Promise<((req: any, action: Action) => Promise)[]> => { +): Promise<((req: Request, action: Action) => Promise)[]> => { if (chainPluginLoader === undefined) { console.error( 'Plugin loader was not initialized! This is an application error. Please report it to the GitProxy maintainers. Skipping plugins...', diff --git a/src/proxy/index.ts b/src/proxy/index.ts index a50f7531f..b3e81d3df 100644 --- a/src/proxy/index.ts +++ b/src/proxy/index.ts @@ -110,7 +110,7 @@ export class Proxy { } resolve(); - } catch (error) { + } catch (error: unknown) { reject(error); } }); diff --git a/src/proxy/processors/constants.ts b/src/proxy/processors/constants.ts index 3ad5784b4..f48ef6563 100644 --- a/src/proxy/processors/constants.ts +++ b/src/proxy/processors/constants.ts @@ -1,6 +1,30 @@ +import { Repo } from '../../db/types'; +import { CommitData } from './types'; + export const BRANCH_PREFIX = 'refs/heads/'; export const EMPTY_COMMIT_HASH = '0000000000000000000000000000000000000000'; export const FLUSH_PACKET = '0000'; export const PACK_SIGNATURE = 'PACK'; export const PACKET_SIZE = 4; export const GIT_OBJECT_TYPE_COMMIT = 1; + +export const SAMPLE_COMMIT: CommitData = { + tree: '1234567890', + parent: '0000000000000000000000000000000000000000', + author: 'test', + committer: 'test', + authorEmail: 'test@test.com', + committerEmail: 'test@test.com', + commitTimestamp: '1234567890', + message: 'test', +}; + +export const SAMPLE_REPO = { + project: 'myrepo', + name: 'myrepo', + url: 'https://github.com/myrepo.git', + users: { + canPush: ['alice'], + canAuthorise: ['bob'], + }, +}; diff --git a/src/proxy/processors/pre-processor/parseAction.ts b/src/proxy/processors/pre-processor/parseAction.ts index 619deea93..22a1d3a2b 100644 --- a/src/proxy/processors/pre-processor/parseAction.ts +++ b/src/proxy/processors/pre-processor/parseAction.ts @@ -1,12 +1,10 @@ +import { Request } from 'express'; + import { Action } from '../../actions'; import { processUrlPath } from '../../routes/helper'; import * as db from '../../../db'; -const exec = async (req: { - originalUrl: string; - method: string; - headers: Record; -}) => { +const exec = async (req: Request) => { const id = Date.now(); const timestamp = id; let type = 'default'; diff --git a/src/proxy/processors/push-action/audit.ts b/src/proxy/processors/push-action/audit.ts index 32e556fb7..47d07fc8e 100644 --- a/src/proxy/processors/push-action/audit.ts +++ b/src/proxy/processors/push-action/audit.ts @@ -1,7 +1,9 @@ +import { Request } from 'express'; + import { writeAudit } from '../../../db'; import { Action } from '../../actions'; -const exec = async (req: any, action: Action) => { +const exec = async (_req: Request, action: Action) => { if (action.type !== 'pull') { await writeAudit(action); } diff --git a/src/proxy/processors/push-action/blockForAuth.ts b/src/proxy/processors/push-action/blockForAuth.ts index 4fde08e0d..86628995c 100644 --- a/src/proxy/processors/push-action/blockForAuth.ts +++ b/src/proxy/processors/push-action/blockForAuth.ts @@ -1,7 +1,9 @@ +import { Request } from 'express'; + import { Action, Step } from '../../actions'; import { getServiceUIURL } from '../../../service/urls'; -const exec = async (req: any, action: Action) => { +const exec = async (req: Request, action: Action) => { const step = new Step('authBlock'); const url = getServiceUIURL(req); diff --git a/src/proxy/processors/push-action/checkAuthorEmails.ts b/src/proxy/processors/push-action/checkAuthorEmails.ts index e8d51f09d..333ddaae4 100644 --- a/src/proxy/processors/push-action/checkAuthorEmails.ts +++ b/src/proxy/processors/push-action/checkAuthorEmails.ts @@ -1,7 +1,9 @@ +import { Request } from 'express'; +import { isEmail } from 'validator'; + import { Action, Step } from '../../actions'; import { getCommitConfig } from '../../../config'; import { CommitData } from '../types'; -import { isEmail } from 'validator'; const isEmailAllowed = (email: string): boolean => { const commitConfig = getCommitConfig(); @@ -29,7 +31,7 @@ const isEmailAllowed = (email: string): boolean => { return true; }; -const exec = async (req: any, action: Action): Promise => { +const exec = async (_req: Request, action: Action): Promise => { const step = new Step('checkAuthorEmails'); const uniqueAuthorEmails = [ diff --git a/src/proxy/processors/push-action/checkCommitMessages.ts b/src/proxy/processors/push-action/checkCommitMessages.ts index 7eb9f6cad..f02a49c06 100644 --- a/src/proxy/processors/push-action/checkCommitMessages.ts +++ b/src/proxy/processors/push-action/checkCommitMessages.ts @@ -1,3 +1,5 @@ +import { Request } from 'express'; + import { Action, Step } from '../../actions'; import { getCommitConfig } from '../../../config'; @@ -39,8 +41,9 @@ const isMessageAllowed = (commitMessage: string): boolean => { console.log('Commit message is blocked via configured literals/patterns...'); return false; } - } catch (error) { - console.log('Invalid regex pattern...'); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.log(`Invalid regex pattern... ${msg}`); return false; } @@ -48,7 +51,7 @@ const isMessageAllowed = (commitMessage: string): boolean => { }; // Execute if the repo is approved -const exec = async (req: any, action: Action): Promise => { +const exec = async (_req: Request, action: Action): Promise => { const step = new Step('checkCommitMessages'); const uniqueCommitMessages = [...new Set(action.commitData?.map((commit) => commit.message))]; diff --git a/src/proxy/processors/push-action/checkEmptyBranch.ts b/src/proxy/processors/push-action/checkEmptyBranch.ts index 86f6b5138..c92c4a4fc 100644 --- a/src/proxy/processors/push-action/checkEmptyBranch.ts +++ b/src/proxy/processors/push-action/checkEmptyBranch.ts @@ -1,23 +1,26 @@ -import { Action, Step } from '../../actions'; +import { Request } from 'express'; import simpleGit from 'simple-git'; + +import { Action, Step } from '../../actions'; import { EMPTY_COMMIT_HASH } from '../constants'; -const isEmptyBranch = async (action: Action) => { +const isEmptyBranch = async (action: Action): Promise => { if (action.commitFrom === EMPTY_COMMIT_HASH) { try { const git = simpleGit(`${action.proxyGitPath}/${action.repoName}`); const type = await git.raw(['cat-file', '-t', action.commitTo || '']); return type.trim() === 'commit'; - } catch (err) { - console.log(`Commit ${action.commitTo} not found: ${err}`); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.log(`Commit ${action.commitTo} not found: ${msg}`); } } return false; }; -const exec = async (req: any, action: Action): Promise => { +const exec = async (_req: Request, action: Action): Promise => { const step = new Step('checkEmptyBranch'); if (action.commitData && action.commitData.length > 0) { diff --git a/src/proxy/processors/push-action/checkHiddenCommits.ts b/src/proxy/processors/push-action/checkHiddenCommits.ts index 852328287..5a9c58a74 100644 --- a/src/proxy/processors/push-action/checkHiddenCommits.ts +++ b/src/proxy/processors/push-action/checkHiddenCommits.ts @@ -1,8 +1,10 @@ import path from 'path'; import { Action, Step } from '../../actions'; import { spawnSync } from 'child_process'; +import { EMPTY_COMMIT_HASH } from '../constants'; +import { Request } from 'express'; -const exec = async (req: any, action: Action): Promise => { +const exec = async (_req: Request, action: Action): Promise => { const step = new Step('checkHiddenCommits'); try { @@ -16,8 +18,7 @@ const exec = async (req: any, action: Action): Promise => { // build introducedCommits set const introducedCommits = new Set(); - const revRange = - oldOid === '0000000000000000000000000000000000000000' ? newOid : `${oldOid}..${newOid}`; + const revRange = oldOid === EMPTY_COMMIT_HASH ? newOid : `${oldOid}..${newOid}`; const revList = spawnSync('git', ['rev-list', revRange], { cwd: repoPath, encoding: 'utf-8' }) .stdout.trim() .split('\n') @@ -68,9 +69,10 @@ const exec = async (req: any, action: Action): Promise => { step.log('All pack commits are referenced in the introduced range.'); step.setContent(`All ${packCommits.size} pack commits are within introduced commits.`); } - } catch (e: any) { - step.setError(e.message); - throw e; + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + step.setError(msg); + throw error; } finally { action.addStep(step); } diff --git a/src/proxy/processors/push-action/checkIfWaitingAuth.ts b/src/proxy/processors/push-action/checkIfWaitingAuth.ts index baedb0df3..8b6b17509 100644 --- a/src/proxy/processors/push-action/checkIfWaitingAuth.ts +++ b/src/proxy/processors/push-action/checkIfWaitingAuth.ts @@ -1,8 +1,10 @@ +import { Request } from 'express'; + import { Action, Step } from '../../actions'; import { getPush } from '../../../db'; // Execute function -const exec = async (req: any, action: Action): Promise => { +const exec = async (_req: Request, action: Action): Promise => { const step = new Step('checkIfWaitingAuth'); try { const existingAction = await getPush(action.id); @@ -14,9 +16,10 @@ const exec = async (req: any, action: Action): Promise => { } } } - } catch (e: any) { - step.setError(e.toString('utf-8')); - throw e; + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + step.setError(msg); + throw error; } finally { action.addStep(step); } diff --git a/src/proxy/processors/push-action/checkRepoInAuthorisedList.ts b/src/proxy/processors/push-action/checkRepoInAuthorisedList.ts index d34e52d48..286953a06 100644 --- a/src/proxy/processors/push-action/checkRepoInAuthorisedList.ts +++ b/src/proxy/processors/push-action/checkRepoInAuthorisedList.ts @@ -1,8 +1,10 @@ +import { Request } from 'express'; + import { Action, Step } from '../../actions'; import { getRepoByUrl } from '../../../db'; // Execute if the repo is approved -const exec = async (req: any, action: Action): Promise => { +const exec = async (_req: Request, action: Action): Promise => { const step = new Step('checkRepoInAuthorisedList'); const found = (await getRepoByUrl(action.url)) !== null; diff --git a/src/proxy/processors/push-action/checkUserPushPermission.ts b/src/proxy/processors/push-action/checkUserPushPermission.ts index 83f16c968..2064be561 100644 --- a/src/proxy/processors/push-action/checkUserPushPermission.ts +++ b/src/proxy/processors/push-action/checkUserPushPermission.ts @@ -1,8 +1,10 @@ +import { Request } from 'express'; + import { Action, Step } from '../../actions'; import { getUsers, isUserPushAllowed } from '../../../db'; // Execute if the repo is approved -const exec = async (req: any, action: Action): Promise => { +const exec = async (_req: Request, action: Action): Promise => { const step = new Step('checkUserPushPermission'); const userEmail = action.userEmail; diff --git a/src/proxy/processors/push-action/clearBareClone.ts b/src/proxy/processors/push-action/clearBareClone.ts index 91f7f5b22..c4dbd1699 100644 --- a/src/proxy/processors/push-action/clearBareClone.ts +++ b/src/proxy/processors/push-action/clearBareClone.ts @@ -1,7 +1,9 @@ -import { Action, Step } from '../../actions'; +import { Request } from 'express'; import fs from 'node:fs'; -const exec = async (req: any, action: Action): Promise => { +import { Action, Step } from '../../actions'; + +const exec = async (_req: Request, action: Action): Promise => { const step = new Step('clearBareClone'); // Recursively remove the contents of ./.remote and ignore exceptions diff --git a/src/proxy/processors/push-action/getDiff.ts b/src/proxy/processors/push-action/getDiff.ts index dbdc4e4e9..c7c9791a6 100644 --- a/src/proxy/processors/push-action/getDiff.ts +++ b/src/proxy/processors/push-action/getDiff.ts @@ -1,9 +1,10 @@ -import { Action, Step } from '../../actions'; +import { Request } from 'express'; import simpleGit from 'simple-git'; +import { Action, Step } from '../../actions'; import { EMPTY_COMMIT_HASH } from '../constants'; -const exec = async (req: any, action: Action): Promise => { +const exec = async (_req: Request, action: Action): Promise => { const step = new Step('diff'); try { @@ -33,8 +34,9 @@ const exec = async (req: any, action: Action): Promise => { const diff = await git.diff([revisionRange]); step.log(diff); step.setContent(diff); - } catch (e: any) { - step.setError(e.toString('utf-8')); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + step.setError(msg); } finally { action.addStep(step); } diff --git a/src/proxy/processors/push-action/gitleaks.ts b/src/proxy/processors/push-action/gitleaks.ts index 1cf5b2236..1c6f67425 100644 --- a/src/proxy/processors/push-action/gitleaks.ts +++ b/src/proxy/processors/push-action/gitleaks.ts @@ -1,9 +1,10 @@ -import { Action, Step } from '../../actions'; -import { getAPIs } from '../../../config'; import { spawn } from 'node:child_process'; -import fs from 'node:fs/promises'; import { PathLike } from 'node:fs'; +import fs from 'node:fs/promises'; +import { Request } from 'express'; +import { Action, Step } from '../../actions'; +import { getAPIs } from '../../../config'; const EXIT_CODE = 99; function runCommand( @@ -66,7 +67,7 @@ async function fileIsReadable(path: PathLike): Promise { } await fs.access(path, fs.constants.R_OK); return true; - } catch (e) { + } catch (error: unknown) { return false; } } @@ -109,14 +110,15 @@ const getPluginConfig = async (): Promise => { }; }; -const exec = async (req: any, action: Action): Promise => { +const exec = async (_req: Request, action: Action): Promise => { const step = new Step('gitleaks'); let config: ConfigOptions | undefined = undefined; try { config = await getPluginConfig(); - } catch (e) { - console.error('failed to get gitleaks config, please fix the error:', e); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error('failed to get gitleaks config, please fix the error:', msg); action.error = true; step.setError('failed setup gitleaks, please contact an administrator\n'); action.addStep(step); @@ -174,9 +176,10 @@ const exec = async (req: any, action: Action): Promise => { console.log('succeeded'); console.log(gitleaks.stderr); } - } catch (e) { + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); action.error = true; - step.setError('failed to spawn gitleaks, please contact an administrator\n'); + step.setError(`failed to spawn gitleaks, please contact an administrator\n: ${msg}`); action.addStep(step); return action; } diff --git a/src/proxy/processors/push-action/parsePush.ts b/src/proxy/processors/push-action/parsePush.ts index ababdb751..a97b90606 100644 --- a/src/proxy/processors/push-action/parsePush.ts +++ b/src/proxy/processors/push-action/parsePush.ts @@ -1,7 +1,9 @@ -import { Action, Step } from '../../actions'; +import { Request } from 'express'; import fs from 'fs'; import lod from 'lodash'; import { createInflate } from 'zlib'; + +import { Action, Step } from '../../actions'; import { CommitContent, CommitData, CommitHeader, PackMeta, PersonLine } from '../types'; import { BRANCH_PREFIX, @@ -27,11 +29,11 @@ const EIGHTH_BIT_MASK = 0x80; /** * Executes the parsing of a push request. - * @param {*} req - The request object containing the push data. + * @param {Request} req - The Express Request object containing the push data. * @param {Action} action - The action object to be modified. * @return {Promise} The modified action object. */ -async function exec(req: any, action: Action): Promise { +async function exec(req: Request, action: Action): Promise { const step = new Step('parsePackFile'); try { if (!req.body || req.body.length === 0) { @@ -81,7 +83,7 @@ async function exec(req: any, action: Action): Promise { const [meta, contentBuff] = getPackMeta(buf); const contents = await getContents(contentBuff, meta.entries); - action.commitData = getCommitData(contents as any); + action.commitData = getCommitData(contents); if (action.commitData.length === 0) { step.log('No commit data found when parsing push.'); @@ -99,10 +101,9 @@ async function exec(req: any, action: Action): Promise { step.content = { meta: meta, }; - } catch (e: any) { - step.setError( - `Unable to parse push. Please contact an administrator for support: ${e.toString('utf-8')}`, - ); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + step.setError(`Unable to parse push. Please contact an administrator for support: ${msg}`); } finally { action.addStep(step); } @@ -476,8 +477,8 @@ const decompressGitObjects = async (buffer: Buffer): Promise => { }; // stop on errors, except maybe buffer errors? - const onError = (e: any) => { - error = e; + const onError = (e: unknown) => { + error = e instanceof Error ? e : new Error(String(e)); console.warn(`Error during inflation: ${JSON.stringify(e)}`); error = new Error('Error during inflation', { cause: e }); inflater.end(); @@ -503,9 +504,10 @@ const decompressGitObjects = async (buffer: Buffer): Promise => { offset++; } }); - } catch (e) { - console.warn(`Error during decompression: ${JSON.stringify(e)}`); - error = new Error('Error during decompression', { cause: e }); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.warn(`Error during decompression: ${msg}`); + throw new Error(`Error during decompression: ${msg}`); } } const result = { diff --git a/src/proxy/processors/push-action/preReceive.ts b/src/proxy/processors/push-action/preReceive.ts index 1c3ad36b9..c34e60c68 100644 --- a/src/proxy/processors/push-action/preReceive.ts +++ b/src/proxy/processors/push-action/preReceive.ts @@ -1,14 +1,16 @@ +import { spawnSync } from 'child_process'; +import { Request } from 'express'; import fs from 'fs'; import path from 'path'; + import { Action, Step } from '../../actions'; -import { spawnSync } from 'child_process'; -const sanitizeInput = (_req: any, action: Action): string => { +const sanitizeInput = (_req: Request, action: Action): string => { return `${action.commitFrom} ${action.commitTo} ${action.branch} \n`; }; const exec = async ( - req: any, + req: Request, action: Action, hookFilePath: string = './hooks/pre-receive.sh', ): Promise => { @@ -62,10 +64,11 @@ const exec = async ( action.addStep(step); } return action; - } catch (error: any) { + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); step.error = true; step.log('Push failed, pre-receive hook returned an error.'); - step.setError(`Hook execution error: ${stderrTrimmed || error.message}`); + step.setError(`Hook execution error: ${stderrTrimmed || msg}`); action.addStep(step); return action; } diff --git a/src/proxy/processors/push-action/pullRemote.ts b/src/proxy/processors/push-action/pullRemote.ts index 9c8661166..0ad673406 100644 --- a/src/proxy/processors/push-action/pullRemote.ts +++ b/src/proxy/processors/push-action/pullRemote.ts @@ -1,11 +1,13 @@ -import { Action, Step } from '../../actions'; +import { Request } from 'express'; import fs from 'fs'; import git from 'isomorphic-git'; import gitHttpClient from 'isomorphic-git/http/node'; +import { Action, Step } from '../../actions'; + const dir = './.remote'; -const exec = async (req: any, action: Action): Promise => { +const exec = async (req: Request, action: Action): Promise => { const step = new Step('pullRemote'); try { @@ -24,6 +26,11 @@ const exec = async (req: any, action: Action): Promise => { step.log(`Executing ${cmd}`); const authHeader = req.headers?.authorization; + + if (!authHeader) { + throw new Error('Authorization header is required'); + } + const [username, password] = Buffer.from(authHeader.split(' ')[1], 'base64') .toString() .split(':'); @@ -41,9 +48,10 @@ const exec = async (req: any, action: Action): Promise => { step.log(`Completed ${cmd}`); step.setContent(`Completed ${cmd}`); - } catch (e: any) { - step.setError(e.toString('utf-8')); - throw e; + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + step.setError(msg); + throw error; } finally { action.addStep(step); } diff --git a/src/proxy/processors/push-action/scanDiff.ts b/src/proxy/processors/push-action/scanDiff.ts index 56f3ddc11..e7511bc10 100644 --- a/src/proxy/processors/push-action/scanDiff.ts +++ b/src/proxy/processors/push-action/scanDiff.ts @@ -1,7 +1,9 @@ +import escapeStringRegexp from 'escape-string-regexp'; +import { Request } from 'express'; +import parseDiff, { File } from 'parse-diff'; + import { Action, Step } from '../../actions'; import { getCommitConfig, getPrivateOrganizations } from '../../../config'; -import parseDiff, { File } from 'parse-diff'; -import escapeStringRegexp from 'escape-string-regexp'; const commitConfig = getCommitConfig(); const privateOrganizations = getPrivateOrganizations(); @@ -154,7 +156,7 @@ const formatMatches = (matches: Match[]) => { }); }; -const exec = async (req: any, action: Action): Promise => { +const exec = async (_req: Request, action: Action): Promise => { const step = new Step('scanDiff'); const { steps, commitFrom, commitTo } = action; diff --git a/src/proxy/processors/push-action/writePack.ts b/src/proxy/processors/push-action/writePack.ts index c41181483..35c82ebdf 100644 --- a/src/proxy/processors/push-action/writePack.ts +++ b/src/proxy/processors/push-action/writePack.ts @@ -1,9 +1,11 @@ -import path from 'path'; -import { Action, Step } from '../../actions'; import { spawnSync } from 'child_process'; +import { Request } from 'express'; import fs from 'fs'; +import path from 'path'; + +import { Action, Step } from '../../actions'; -const exec = async (req: any, action: Action) => { +const exec = async (req: Request, action: Action) => { const step = new Step('writePack'); try { if (!action.proxyGitPath || !action.repoName) { @@ -29,9 +31,10 @@ const exec = async (req: any, action: Action) => { step.log(`new idx files: ${newIdxFiles}`); step.setContent(content); - } catch (e: any) { - step.setError(e.toString('utf-8')); - throw e; + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + step.setError(msg); + throw error; } finally { action.addStep(step); } diff --git a/src/proxy/processors/types.ts b/src/proxy/processors/types.ts index c4c447b5d..09c352369 100644 --- a/src/proxy/processors/types.ts +++ b/src/proxy/processors/types.ts @@ -1,8 +1,10 @@ +import { Request } from 'express'; + import { Question } from '../../config/generated/config'; import { Action } from '../actions'; export interface Processor { - exec(req: any, action: Action): Promise; + exec(req: Request, action: Action): Promise; metadata: ProcessorMetadata; } @@ -13,10 +15,11 @@ export interface ProcessorMetadata { export type Attestation = { reviewer: { username: string; - gitAccount: string; + email: string; }; timestamp: string | Date; questions: Question[]; + automated?: boolean; }; export type CommitContent = { diff --git a/src/proxy/routes/helper.ts b/src/proxy/routes/helper.ts index 54d72edca..5c4821359 100644 --- a/src/proxy/routes/helper.ts +++ b/src/proxy/routes/helper.ts @@ -1,3 +1,5 @@ +import { IncomingHttpHeaders } from 'http'; + /** Regex used to analyze un-proxied Git URLs */ const GIT_URL_REGEX = /(.+:\/\/)([^/]+)(\/.+\.git)(\/.+)*/; @@ -150,7 +152,7 @@ export const processGitURLForNameAndOrg = (gitUrl: string): GitNameBreakdown | n * @return {boolean} If true, this is a valid and expected git request. * Otherwise, false. */ -export const validGitRequest = (gitPath: string, headers: any): boolean => { +export const validGitRequest = (gitPath: string, headers: IncomingHttpHeaders): boolean => { const { 'user-agent': agent, accept } = headers; if (!agent) { return false; diff --git a/src/proxy/routes/index.ts b/src/proxy/routes/index.ts index ac53f0d2d..05b795bae 100644 --- a/src/proxy/routes/index.ts +++ b/src/proxy/routes/index.ts @@ -47,10 +47,10 @@ const proxyFilter: ProxyOptions['filter'] = async (req, res) => { } // For POST pack requests, use the raw body extracted by extractRawBody middleware - if (isPackPost(req) && (req as any).bodyRaw) { - (req as any).body = (req as any).bodyRaw; + if (isPackPost(req) && req.bodyRaw) { + req.body = req.bodyRaw; // Clean up the bodyRaw property before forwarding the request - delete (req as any).bodyRaw; + delete req.bodyRaw; } const action = await executeChain(req, res); @@ -68,8 +68,9 @@ const proxyFilter: ProxyOptions['filter'] = async (req, res) => { // this is the only case where we do not respond directly, instead we return true to proxy the request return true; - } catch (e) { - const message = `Error occurred in proxy filter function ${(e as Error).message ?? e}`; + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + const message = `Error occurred in proxy filter function ${msg}`; logAction(req.url, req.headers.host, req.headers['user-agent'], ActionType.ERROR, message); sendErrorResponse(req, res, message); @@ -162,12 +163,17 @@ const extractRawBody = async (req: Request, res: Response, next: NextFunction) = try { const buf = await getRawBody(pluginStream, { limit: '1gb' }); - (req as any).bodyRaw = buf; - (req as any).pipe = (dest: any, opts: any) => proxyStream.pipe(dest, opts); + req.bodyRaw = buf; + req.pipe = (dest, opts) => proxyStream.pipe(dest, opts); next(); - } catch (e) { - console.error(e); - proxyStream.destroy(e as Error); + } catch (error: unknown) { + if (error instanceof Error) { + console.error(error.message); + proxyStream.destroy(error); + } else { + console.error(String(error)); + proxyStream.destroy(new Error(String(error))); + } res.status(500).end('Proxy error'); } }; diff --git a/src/service/passport/activeDirectory.ts b/src/service/passport/activeDirectory.ts index 6814bcacc..30a814ea0 100644 --- a/src/service/passport/activeDirectory.ts +++ b/src/service/passport/activeDirectory.ts @@ -43,7 +43,7 @@ export const configure = async (passport: PassportStatic): Promise void, + done: (err: unknown, user: unknown) => void, ) { try { profile.username = profile._json.sAMAccountName?.toLowerCase(); @@ -63,8 +63,9 @@ export const configure = async (passport: PassportStatic): Promise void) { + passport.serializeUser(function ( + user: Partial, + done: (err: unknown, user: Partial) => void, + ) { done(null, user); }); - passport.deserializeUser(function (user: any, done: (err: any, user: any) => void) { + passport.deserializeUser(function ( + user: Partial, + done: (err: unknown, user: Partial) => void, + ) { done(null, user); }); diff --git a/src/service/passport/jwtUtils.ts b/src/service/passport/jwtUtils.ts index eefe262cd..4426f3306 100644 --- a/src/service/passport/jwtUtils.ts +++ b/src/service/passport/jwtUtils.ts @@ -17,8 +17,9 @@ export async function getJwks(authorityUrl: string): Promise { const { data: jwks }: { data: JwksResponse } = await axios.get(jwksUri); return jwks.keys; - } catch (error) { - console.error('Error fetching JWKS:', error); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error('Error fetching JWKS:', msg); throw new Error('Failed to fetch JWKS'); } } @@ -73,8 +74,9 @@ export async function validateJwt( } return { verifiedPayload, error: null }; - } catch (error: any) { - const errorMessage = `JWT validation failed: ${error.message}\n`; + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + const errorMessage = `JWT validation failed: ${msg}\n`; console.error(errorMessage); return { error: errorMessage, verifiedPayload: null }; } diff --git a/src/service/passport/local.ts b/src/service/passport/local.ts index 10324f772..00dd63984 100644 --- a/src/service/passport/local.ts +++ b/src/service/passport/local.ts @@ -1,5 +1,5 @@ import bcrypt from 'bcryptjs'; -import { Strategy as LocalStrategy } from 'passport-local'; +import { IVerifyOptions, Strategy as LocalStrategy } from 'passport-local'; import type { PassportStatic } from 'passport'; import * as db from '../../db'; @@ -11,28 +11,28 @@ export const configure = async (passport: PassportStatic): Promise void, + done: (err: unknown, user?: Partial, info?: IVerifyOptions) => void, ) => { try { const user = await db.findUser(username); if (!user) { - return done(null, false, { message: 'Incorrect username.' }); + return done(null, undefined, { message: 'Incorrect username.' }); } const passwordCorrect = await bcrypt.compare(password, user.password ?? ''); if (!passwordCorrect) { - return done(null, false, { message: 'Incorrect password.' }); + return done(null, undefined, { message: 'Incorrect password.' }); } return done(null, user); - } catch (err) { - return done(err); + } catch (error: unknown) { + return done(error); } }, ), ); - passport.serializeUser((user: any, done) => { + passport.serializeUser((user: Partial, done) => { done(null, user.username); }); @@ -40,8 +40,8 @@ export const configure = async (passport: PassportStatic): Promise void) => { + async (tokenSet: any, done: (err: unknown, user?: Partial) => void) => { const idTokenClaims = tokenSet.claims(); const expectedSub = idTokenClaims.sub; const userInfo = await fetchUserInfo(config, tokenSet.access_token, expectedSub); @@ -41,7 +42,7 @@ export const configure = async (passport: PassportStatic): Promise { + passport.serializeUser((user: Partial, done) => { done(null, user.oidcId || user.username); }); @@ -59,15 +60,16 @@ export const configure = async (passport: PassportStatic): Promise void, + done: (err: unknown, user?: Partial) => void, ): Promise => { - console.log('handleUserAuthentication called'); try { const user = await db.findUserByOIDC(userInfo.sub); @@ -100,21 +101,26 @@ export const handleUserAuthentication = async ( } return done(null, user); - } catch (err) { - return done(err); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + return done(msg); } }; /** * Extracts email from OIDC profile. * Different providers use different fields to store the email. - * @param {any} profile - The user profile from the OIDC provider + * @param {UserInfoResponse} profile - The user profile from the OIDC provider * @return {string | null} - The email address from the profile */ -export const safelyExtractEmail = (profile: any): string | null => { - return ( - profile.email || (profile.emails && profile.emails.length > 0 ? profile.emails[0].value : null) - ); +export const safelyExtractEmail = (profile: UserInfoResponse): string | null => { + if (profile.email) { + return profile.email; + } + if (profile.emails && Array.isArray(profile.emails) && profile.emails.length > 0) { + return (profile.emails[0] as { value: string }).value; + } + return null; }; /** diff --git a/src/service/routes/auth.ts b/src/service/routes/auth.ts index 9835af3c8..7f92f9cf4 100644 --- a/src/service/routes/auth.ts +++ b/src/service/routes/auth.ts @@ -66,8 +66,9 @@ const loginSuccessHandler = () => async (req: Request, res: Response) => { message: 'success', user: currentUser, }); - } catch (e) { - console.log(`service.routes.auth.login: Error logging user in ${JSON.stringify(e)}`); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.log(`service.routes.auth.login: Error logging user in ${msg}`); res.status(500).send('Failed to login').end(); } }; @@ -103,28 +104,31 @@ router.post( router.get('/openidconnect', passport.authenticate(authStrategies['openidconnect'].type)); router.get('/openidconnect/callback', (req: Request, res: Response, next: NextFunction) => { - passport.authenticate(authStrategies['openidconnect'].type, (err: any, user: any, info: any) => { - if (err) { - console.error('Authentication error:', err); - return res.status(500).end(); - } - if (!user) { - console.error('No user found:', info); - return res.status(401).end(); - } - req.logIn(user, (err) => { + passport.authenticate( + authStrategies['openidconnect'].type, + (err: unknown, user: Partial, info: unknown) => { if (err) { - console.error('Login error:', err); + console.error('Authentication error:', err); return res.status(500).end(); } - console.log('Logged in successfully. User:', user); - return res.redirect(`${uiHost}:${uiPort}/dashboard/profile`); - }); - })(req, res, next); + if (!user) { + console.error('No user found:', info); + return res.status(401).end(); + } + req.logIn(user, (err) => { + if (err) { + console.error('Login error:', err); + return res.status(500).end(); + } + console.log('Logged in successfully. User:', user); + return res.redirect(`${uiHost}:${uiPort}/dashboard/profile`); + }); + }, + )(req, res, next); }); router.post('/logout', (req: Request, res: Response, next: NextFunction) => { - req.logout((err: any) => { + req.logout((err: unknown) => { if (err) return next(err); }); res.clearCookie('connect.sid'); @@ -204,11 +208,12 @@ router.post('/gitAccount', async (req: Request, res: Response) => { user.gitAccount = req.body.gitAccount; db.updateUser(user); res.status(200).end(); - } catch (e: any) { + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); res .status(500) .send({ - message: `Failed to update git account: ${e.message}`, + message: `Failed to update git account: ${msg}`, }) .end(); } @@ -247,10 +252,11 @@ router.post('/create-user', async (req: Request, res: Response) => { username, }) .end(); - } catch (error: any) { - console.error('Error creating user:', error); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error('Error creating user:', msg); res.status(500).send({ - message: error.message || 'Failed to create user', + message: `Failed to create user: ${msg}`, }); } }); diff --git a/src/service/routes/index.ts b/src/service/routes/index.ts index 23b63b02a..f2d76a4d0 100644 --- a/src/service/routes/index.ts +++ b/src/service/routes/index.ts @@ -7,8 +7,9 @@ import users from './users'; import healthcheck from './healthcheck'; import config from './config'; import { jwtAuthHandler } from '../passport/jwtAuthHandler'; +import { Proxy } from '../../proxy'; -const routes = (proxy: any) => { +const routes = (proxy: Proxy) => { const router = express.Router(); router.use('/api', home); router.use('/api/auth', auth.router); diff --git a/src/service/routes/push.ts b/src/service/routes/push.ts index fbce5335e..f6baea681 100644 --- a/src/service/routes/push.ts +++ b/src/service/routes/push.ts @@ -71,7 +71,7 @@ router.post('/:id/reject', async (req: Request, res: Response) => { const isAllowed = await db.canUserApproveRejectPush(id, username); if (isAllowed) { - const result = await db.reject(id, null); + const result = await db.reject(id); console.log(`User ${username} rejected push request for ${id}`); res.send(result); } else { @@ -156,7 +156,7 @@ router.post('/:id/authorise', async (req: Request, res: Response) => { timestamp: new Date(), reviewer: { username, - reviewerEmail, + email: reviewerEmail, }, }; const result = await db.authorise(id, attestation); diff --git a/src/service/routes/repo.ts b/src/service/routes/repo.ts index 6d42ec515..06b6690b8 100644 --- a/src/service/routes/repo.ts +++ b/src/service/routes/repo.ts @@ -5,11 +5,12 @@ import { getProxyURL } from '../urls'; import { getAllProxiedHosts } from '../../db'; import { RepoQuery } from '../../db/types'; import { isAdminUser } from './utils'; +import { Proxy } from '../../proxy'; // create a reference to the proxy service as arrow functions will lose track of the `proxy` parameter // used to restart the proxy when a new host is added -let theProxy: any = null; -const repo = (proxy: any) => { +let theProxy: Proxy | null = null; +const repo = (proxy: Proxy) => { theProxy = proxy; const router = express.Router(); @@ -131,8 +132,8 @@ const repo = (proxy: any) => { if (currentHosts.length < previousHosts.length) { // restart the proxy console.log('Restarting the proxy to remove a host'); - await theProxy.stop(); - await theProxy.start(); + await theProxy?.stop(); + await theProxy?.start(); } res.send({ message: 'deleted' }); @@ -184,19 +185,17 @@ const repo = (proxy: any) => { // restart the proxy if we're proxying a new domain if (newOrigin) { console.log('Restarting the proxy to handle an additional host'); - await theProxy.stop(); - await theProxy.start(); + await theProxy?.stop(); + await theProxy?.start(); + } + } catch (error: unknown) { + if (error instanceof Error) { + console.error('Repository creation failed due to error: ', error.message); + console.error(error.stack); } - } catch (e: any) { - console.error('Repository creation failed due to error: ', e.message ? e.message : e); - console.error(e.stack); res.status(500).send({ message: 'Failed to create repository due to error' }); } } - } else { - res.status(401).send({ - message: 'You are not authorised to perform this action...', - }); } }); diff --git a/src/types/express.d.ts b/src/types/express.d.ts new file mode 100644 index 000000000..891c7e22c --- /dev/null +++ b/src/types/express.d.ts @@ -0,0 +1,7 @@ +import { Readable } from 'stream'; + +declare module 'express-serve-static-core' { + interface Request { + bodyRaw?: Buffer; + } +} diff --git a/src/ui/assets/jss/material-dashboard-react/layouts/dashboardStyle.ts b/src/ui/assets/jss/material-dashboard-react/layouts/dashboardStyle.ts index 411803438..a9dba3b52 100644 --- a/src/ui/assets/jss/material-dashboard-react/layouts/dashboardStyle.ts +++ b/src/ui/assets/jss/material-dashboard-react/layouts/dashboardStyle.ts @@ -27,7 +27,7 @@ const appStyle = (theme: Theme): AppStyleProps => ({ ...transition, maxHeight: '100%', width: '100%', - WebkitOverflowScrolling: 'touch' as any, + WebkitOverflowScrolling: 'touch', }, content: { marginTop: '70px', diff --git a/src/ui/auth/AuthProvider.tsx b/src/ui/auth/AuthProvider.tsx index 57e6913c0..ab70788dd 100644 --- a/src/ui/auth/AuthProvider.tsx +++ b/src/ui/auth/AuthProvider.tsx @@ -11,7 +11,9 @@ export const AuthProvider: React.FC> = ({ childr try { const data = await getUserInfo(); setUser(data); - } catch (error) { + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error(`Error refreshing user: ${msg}`); setUser(null); } finally { setIsLoading(false); diff --git a/src/ui/components/Navbars/DashboardNavbarLinks.tsx b/src/ui/components/Navbars/DashboardNavbarLinks.tsx index d23d3b65a..ae1eda7ce 100644 --- a/src/ui/components/Navbars/DashboardNavbarLinks.tsx +++ b/src/ui/components/Navbars/DashboardNavbarLinks.tsx @@ -28,7 +28,7 @@ const DashboardNavbarLinks: React.FC = () => { const [openProfile, setOpenProfile] = useState(null); const [, setAuth] = useState(true); const [, setIsLoading] = useState(true); - const [errorMessage, setErrorMessage] = useState(''); + const [, setErrorMessage] = useState(''); const [user, setUser] = useState(null); useEffect(() => { @@ -59,14 +59,14 @@ const DashboardNavbarLinks: React.FC = () => { setAuth(false); navigate(0); } - } catch (error) { - console.error('Logout failed:', error); + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error('Logout failed:', msg); } }; return (
- {errorMessage &&
{errorMessage}
}