Skip to content
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
d3d73d9
refactor(ts): passport/activeDirectory
jescalada Aug 24, 2025
ba086f1
chore: add missing types
jescalada Aug 24, 2025
0c7d1fb
refactor(ts): JWT handler and utils
jescalada Aug 24, 2025
b419f4e
refactor(ts): passport/index
jescalada Aug 24, 2025
06a64ea
refactor(ts): passport/local
jescalada Aug 24, 2025
4dfbc2d
refactor(ts): passport/ldaphelper
jescalada Aug 24, 2025
09a1876
refactor(ts): passport/oidc
jescalada Aug 24, 2025
abc09bd
refactor(ts): auth routes
jescalada Aug 24, 2025
03c4952
refactor(ts): config routes
jescalada Aug 24, 2025
7ed9eb0
refactor(ts): misc routes and index
jescalada Aug 24, 2025
3d99de2
refactor(ts): push routes and update related types/db handlers
jescalada Aug 24, 2025
944e0b5
refactor(ts): repo routes
jescalada Aug 24, 2025
6a7089f
refactor(ts): user routes
jescalada Aug 24, 2025
6c9d3bf
refactor(ts): emailSender and missing implementation
jescalada Aug 24, 2025
6899e4e
refactor(ts): service/index and missing types
jescalada Aug 24, 2025
63c30a0
refactor(ts): urls
jescalada Aug 24, 2025
812a910
fix: failing tests due to incorrect imports
jescalada Aug 27, 2025
9d5bdd8
chore: update .eslintrc
jescalada Aug 27, 2025
c951015
chore: fix type checks
jescalada Aug 27, 2025
b046903
chore: fix CLI service imports
jescalada Aug 27, 2025
97ad7c7
Merge branch 'main' of https://github.com/finos/git-proxy into servic…
jescalada Aug 28, 2025
9008ac5
chore: run npm format
jescalada Aug 28, 2025
f36b3d1
test: add basic oidc tests and ignore openid-client type error on import
jescalada Aug 28, 2025
51df315
test: increase testOidc and testPush coverage
jescalada Aug 28, 2025
f7ed291
test: improve push test coverage
jescalada Aug 28, 2025
b2b1b14
test: add missing smtp tests
jescalada Aug 28, 2025
ae43800
Update .eslintrc.json
jescalada Aug 29, 2025
17a8adf
Update src/db/file/users.ts
jescalada Aug 29, 2025
c7cf87e
Update src/service/passport/jwtAuthHandler.ts
jescalada Aug 29, 2025
8aa1a97
Update src/service/passport/index.ts
jescalada Aug 29, 2025
962a0ba
chore: fix service/index proxy type and npm run format
jescalada Aug 29, 2025
7eda433
Update src/service/passport/jwtAuthHandler.ts
jescalada Aug 29, 2025
df80fef
Update src/service/passport/jwtUtils.ts
jescalada Aug 29, 2025
095ae62
chore: add getSessionStore helper for fs sink and fix types
jescalada Aug 29, 2025
b094ff1
Merge branch 'service-ts-refactor-redone' of https://github.com/jesca…
jescalada Aug 29, 2025
f9cea8c
chore: remove unnecessary casting for JWT verifiedPayload
jescalada Aug 29, 2025
ee63f9c
chore: update getSessionStore call
jescalada Aug 29, 2025
0dc78ce
chore: replace unused UserInfoResponse with imported version
jescalada Aug 29, 2025
2429fbe
chore: improve userEmail checks on push routes
jescalada Aug 29, 2025
a368642
chore: update packages
jescalada Aug 29, 2025
8971566
Merge branch 'main' into service-ts-refactor-redone
jescalada Aug 29, 2025
6c427b9
chore: add typing for thirdPartyApiConfig
jescalada Sep 3, 2025
5805dd9
chore: fix AD passport types
jescalada Sep 3, 2025
bec32f7
chore: replace AD type with activedirectory2
jescalada Sep 4, 2025
573cc92
chore: improve loginSuccessHandler
jescalada Sep 4, 2025
a211560
chore: fix PushQuery typing
jescalada Sep 4, 2025
e299e85
chore: fix "any" in repo and users routes and fix failing tests
jescalada Sep 4, 2025
3dd1bd0
refactor: flatten push routes and fix typings
jescalada Sep 4, 2025
8e6d1d3
chore: add isAdminUser check to repo routes
jescalada Sep 4, 2025
db60fbf
test: improve push test checks for cancel endpoint
jescalada Sep 4, 2025
dfb1e04
Merge branch 'main' into service-ts-refactor-redone
jescalada Sep 4, 2025
95495f2
chore: fix createDefaultAdmin and isAdminUser functions
jescalada Sep 4, 2025
3469b54
chore: fix thirdPartyApiConfig and AD type errors
jescalada Sep 4, 2025
cd68915
chore: remove nodemailer and unused functionality
jescalada Sep 4, 2025
728b5aa
chore: fix failing CLI test (email not unique)
jescalada Sep 4, 2025
4d3d083
chore: remove unused smtp config variables
jescalada Sep 4, 2025
0343438
Update src/service/routes/publicApi.ts
jescalada Sep 5, 2025
0109b0b
chore: fix toPublicUser calls and typing
jescalada Sep 5, 2025
052a00e
Merge branch 'main' into service-ts-refactor-redone
jescalada Sep 10, 2025
3a66ca4
chore: update sample test src/service import
jescalada Sep 10, 2025
dd42438
Merge remote-tracking branch 'origin/main' into service-ts-refactor-r…
jescalada Sep 22, 2025
a3e5f22
Merge remote-tracking branch 'origin/main' into service-ts-refactor-r…
jescalada Sep 24, 2025
8fb0236
Merge branch 'main' into service-ts-refactor-redone
kriswest Sep 24, 2025
a124277
Merge branch 'main' into service-ts-refactor-redone
jescalada Sep 24, 2025
eef5f40
Merge branch 'main' into service-ts-refactor-redone
jescalada Sep 26, 2025
bd96208
Merge branch 'main' into service-ts-refactor-redone
jescalada Oct 1, 2025
36a68f3
chore: fix type error on AuthenticationElement rename
jescalada Oct 1, 2025
e257953
Merge branch 'main' into service-ts-refactor-redone
jescalada Oct 2, 2025
8bc162e
Merge branch 'main' into service-ts-refactor-redone
jescalada Oct 6, 2025
9b1e905
Merge branch 'main' into service-ts-refactor-redone
kriswest Oct 9, 2025
07d059e
chore: remove unused types file and references
jescalada Oct 10, 2025
2fd8e11
Update src/service/passport/ldaphelper.ts
jescalada Oct 10, 2025
d1b4388
chore: improve jwtAuthHandler checks
jescalada Oct 10, 2025
bd7cb33
Merge branch 'service-ts-refactor-redone' of https://github.com/jesca…
jescalada Oct 10, 2025
233deac
fix: remove gitAccount and fix authorise push route conflicts
jescalada Oct 10, 2025
c4190dc
chore: remove unused reviewerGitAccount processing and fix getProxyUr…
jescalada Oct 10, 2025
fa102f0
Merge branch 'main' into service-ts-refactor-redone
jescalada Oct 17, 2025
820400d
chore: fix type errors
jescalada Oct 17, 2025
efe59f5
fix: convert imports to ESM to fix CLI test failures
jescalada Oct 17, 2025
a58f95c
feat: add reviewer email to push authorise endpoint
jescalada Oct 17, 2025
bf37942
chore: remove service parameter from testCliUtils start/stop calls
jescalada Oct 17, 2025
29b3bda
chore: add test-package/package-lock.json to .gitignore
jescalada Oct 17, 2025
9bc41e2
Delete test/fixtures/test-package/package-lock.json
jescalada Oct 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
"@typescript-eslint/no-explicit-any": "off", // temporary until TS refactor is complete
"@typescript-eslint/no-unused-vars": "off", // temporary until TS refactor is complete
"@typescript-eslint/no-require-imports": "off", // prevents error on old "require" imports
"@typescript-eslint/no-unused-expressions": "off" // prevents error on test "expect" expressions
"@typescript-eslint/no-unused-expressions": "off", // prevents error on test "expect" expressions
"new-cap": "off" // prevents errors on express.Router()
},
"settings": {
"react": {
Expand Down
8 changes: 8 additions & 0 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,14 @@
}
}
}
},
"smtpHost": {
"type": "string",
"description": "SMTP host to use for sending emails"
},
"smtpPort": {
"type": "number",
"description": "SMTP port to use for sending emails"
}
},
"definitions": {
Expand Down
4,460 changes: 2,982 additions & 1,478 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,20 @@
"@babel/preset-react": "^7.27.1",
"@commitlint/cli": "^19.8.1",
"@commitlint/config-conventional": "^19.8.1",
"@types/cors": "^2.8.19",
"@types/domutils": "^1.7.8",
"@types/express": "^5.0.3",
"@types/express-http-proxy": "^1.6.7",
"@types/express-session": "^1.18.2",
"@types/jsonwebtoken": "^9.0.10",
"@types/jwk-to-pem": "^2.0.3",
"@types/lodash": "^4.17.20",
"@types/lusca": "^1.7.5",
"@types/mocha": "^10.0.10",
"@types/node": "^22.17.0",
"@types/nodemailer": "^7.0.1",
"@types/passport": "^1.0.17",
"@types/passport-local": "^1.0.38",
"@types/react-dom": "^17.0.26",
"@types/react-html-parser": "^2.0.7",
"@types/validator": "^13.15.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/git-proxy-cli/test/testCli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require('../../../src/config/file').configFile = path.join(
'test',
'testCli.proxy.config.json',
);
const service = require('../../../src/service');
const service = require('../../../src/service').default;

/* test constants */
// push ID which does not exist
Expand Down
4 changes: 3 additions & 1 deletion proxy.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -182,5 +182,7 @@
"loginRequired": true
}
]
}
},
"smtpHost": "",
"smtpPort": 0
}
16 changes: 16 additions & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ let _contactEmail: string = defaultSettings.contactEmail;
let _csrfProtection: boolean = defaultSettings.csrfProtection;
let _domains: Record<string, unknown> = defaultSettings.domains;
let _rateLimit: RateLimitConfig = defaultSettings.rateLimit;
let _smtpHost: string = defaultSettings.smtpHost;
let _smtpPort: number = defaultSettings.smtpPort;

// These are not always present in the default config file, so casting is required
let _tlsEnabled = defaultSettings.tls.enabled;
Expand Down Expand Up @@ -264,6 +266,20 @@ export const getRateLimit = () => {
return _rateLimit;
};

export const getSmtpHost = () => {
if (_userSettings && _userSettings.smtpHost) {
_smtpHost = _userSettings.smtpHost;
}
return _smtpHost;
};

export const getSmtpPort = () => {
if (_userSettings && _userSettings.smtpPort) {
_smtpPort = _userSettings.smtpPort;
}
return _smtpPort;
};

// Function to handle configuration updates
const handleConfigUpdate = async (newConfig: typeof _config) => {
console.log('Configuration updated from external source');
Expand Down
35 changes: 35 additions & 0 deletions src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export interface UserSettings {
csrfProtection: boolean;
domains: Record<string, unknown>;
rateLimit: RateLimitConfig;
smtpHost?: string;
smtpPort?: number;
}

export interface TLSConfig {
Expand All @@ -49,6 +51,39 @@ export interface Authentication {
type: string;
enabled: boolean;
options?: Record<string, unknown>;
oidcConfig?: OidcConfig;
adConfig?: AdConfig;
jwtConfig?: JwtConfig;

// Deprecated fields for backwards compatibility
// TODO: remove in future release and keep the ones in adConfig
userGroup?: string;
adminGroup?: string;
domain?: string;
}

export interface OidcConfig {
issuer: string;
clientID: string;
clientSecret: string;
callbackURL: string;
scope: string;
}

export interface AdConfig {
url: string;
baseDN: string;
searchBase: string;
userGroup?: string;
adminGroup?: string;
domain?: string;
}

export interface JwtConfig {
clientID: string;
authorityURL: string;
roleMapping: Record<string, unknown>;
expectedAudience?: string;
}

export interface TempPasswordConfig {
Expand Down
3 changes: 2 additions & 1 deletion src/db/file/pushes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ const defaultPushQuery: PushQuery = {
blocked: true,
allowPush: false,
authorised: false,
type: 'push',
};

export const getPushes = (query: PushQuery): Promise<Action[]> => {
export const getPushes = (query: Partial<PushQuery>): Promise<Action[]> => {
if (!query) query = defaultPushQuery;
return new Promise((resolve, reject) => {
db.find(query, (err: Error, docs: Action[]) => {
Expand Down
9 changes: 4 additions & 5 deletions src/db/file/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,10 @@ export const deleteUser = (username: string): Promise<void> => {
});
};

export const updateUser = (user: User): Promise<void> => {
user.username = user.username.toLowerCase();
if (user.email) {
user.email = user.email.toLowerCase();
}
export const updateUser = (user: Partial<User>): Promise<void> => {
if (user.username) user.username = user.username.toLowerCase();
if (user.email) user.email = user.email.toLowerCase();

return new Promise((resolve, reject) => {
// The mongo db adaptor adds fields to existing documents, where this adaptor replaces the document
// hence, retrieve and merge documents to avoid dropping fields (such as the gitaccount)
Expand Down
4 changes: 2 additions & 2 deletions src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export const canUserCancelPush = async (id: string, user: string) => {

export const getSessionStore = (): MongoDBStore | null =>
sink.getSessionStore ? sink.getSessionStore() : null;
export const getPushes = (query: PushQuery): Promise<Action[]> => sink.getPushes(query);
export const getPushes = (query: Partial<PushQuery>): Promise<Action[]> => sink.getPushes(query);
export const writeAudit = (action: Action): Promise<void> => sink.writeAudit(action);
export const getPush = (id: string): Promise<Action | null> => sink.getPush(id);
export const deletePush = (id: string): Promise<void> => sink.deletePush(id);
Expand All @@ -182,4 +182,4 @@ export const findUserByEmail = (email: string): Promise<User | null> => sink.fin
export const findUserByOIDC = (oidcId: string): Promise<User | null> => sink.findUserByOIDC(oidcId);
export const getUsers = (query?: object): Promise<User[]> => sink.getUsers(query);
export const deleteUser = (username: string): Promise<void> => sink.deleteUser(username);
export const updateUser = (user: User): Promise<void> => sink.updateUser(user);
export const updateUser = (user: Partial<User>): Promise<void> => sink.updateUser(user);
5 changes: 4 additions & 1 deletion src/db/mongo/pushes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ const defaultPushQuery: PushQuery = {
blocked: true,
allowPush: false,
authorised: false,
type: 'push',
};

export const getPushes = async (query: PushQuery = defaultPushQuery): Promise<Action[]> => {
export const getPushes = async (
query: Partial<PushQuery> = defaultPushQuery,
): Promise<Action[]> => {
return findDocuments<Action>(collectionName, query, {
projection: {
_id: 0,
Expand Down
6 changes: 4 additions & 2 deletions src/db/mongo/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ export const createUser = async function (user: User): Promise<void> {
await collection.insertOne(user as OptionalId<Document>);
};

export const updateUser = async (user: User): Promise<void> => {
user.username = user.username.toLowerCase();
export const updateUser = async (user: Partial<User>): Promise<void> => {
if (user.username) {
user.username = user.username.toLowerCase();
}
if (user.email) {
user.email = user.email.toLowerCase();
}
Expand Down
5 changes: 3 additions & 2 deletions src/db/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type PushQuery = {
blocked: boolean;
allowPush: boolean;
authorised: boolean;
type: string;
};

export type UserRole = 'canPush' | 'canAuthorise';
Expand Down Expand Up @@ -62,7 +63,7 @@ export class User {

export interface Sink {
getSessionStore?: () => MongoDBStore;
getPushes: (query: PushQuery) => Promise<Action[]>;
getPushes: (query: Partial<PushQuery>) => Promise<Action[]>;
writeAudit: (action: Action) => Promise<void>;
getPush: (id: string) => Promise<Action | null>;
deletePush: (id: string) => Promise<void>;
Expand All @@ -85,5 +86,5 @@ export interface Sink {
getUsers: (query?: object) => Promise<User[]>;
createUser: (user: User) => Promise<void>;
deleteUser: (username: string) => Promise<void>;
updateUser: (user: User) => Promise<void>;
updateUser: (user: Partial<User>) => Promise<void>;
}
6 changes: 3 additions & 3 deletions src/service/emailSender.js → src/service/emailSender.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const nodemailer = require('nodemailer');
const config = require('../config');
import nodemailer from 'nodemailer';
import * as config from '../config';

exports.sendEmail = async (from, to, subject, body) => {
export const sendEmail = async (from: string, to: string, subject: string, body: string) => {
const smtpHost = config.getSmtpHost();
const smtpPort = config.getSmtpPort();
const transporter = nodemailer.createTransport({
Expand Down
80 changes: 25 additions & 55 deletions src/service/index.js → src/service/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
const express = require('express');
const session = require('express-session');
const http = require('http');
const cors = require('cors');
const app = express();
const path = require('path');
const config = require('../config');
const db = require('../db');
const rateLimit = require('express-rate-limit');
const lusca = require('lusca');
const configLoader = require('../config/ConfigLoader');
import express, { Express } from 'express';
import session from 'express-session';
import http from 'http';
import cors from 'cors';
import path from 'path';
import rateLimit from 'express-rate-limit';
import lusca from 'lusca';

import * as config from '../config';
import * as db from '../db';
import { serverConfig } from '../config/env';

const limiter = rateLimit(config.getRateLimit());

const { GIT_PROXY_UI_PORT: uiPort } = require('../config/env').serverConfig;
const { GIT_PROXY_UI_PORT: uiPort } = serverConfig;

const app: Express = express();
const _httpServer = http.createServer(app);

const corsOptions = {
Expand All @@ -23,57 +24,22 @@ const corsOptions = {

/**
* Internal function used to bootstrap the Git Proxy API's express application.
* @param {proxy} proxy A reference to the proxy express application, used to restart it when necessary.
* @param {Express} proxy A reference to the proxy express application, used to restart it when necessary.
* @return {Promise<Express>}
*/
async function createApp(proxy) {
async function createApp(proxy: Express) {
// configuration of passport is async
// Before we can bind the routes - we need the passport strategy
const passport = await require('./passport').configure();
const routes = require('./routes');
const routes = await import('./routes');
const absBuildPath = path.join(__dirname, '../../build');
app.use(cors(corsOptions));
app.set('trust proxy', 1);
app.use(limiter);

// Add new admin-only endpoint to reload config
app.post('/api/v1/admin/reload-config', async (req, res) => {
if (!req.isAuthenticated() || !req.user.admin) {
return res.status(403).json({ error: 'Unauthorized' });
}

try {
// 1. Reload configuration
await configLoader.loadConfiguration();

// 2. Stop existing services
await proxy.stop();

// 3. Apply new configuration
config.validate();

// 4. Restart services with new config
await proxy.start();

console.log('Configuration reloaded and services restarted successfully');
res.json({ status: 'success', message: 'Configuration reloaded and services restarted' });
} catch (error) {
console.error('Failed to reload configuration and restart services:', error);

// Attempt to restart with existing config if reload fails
try {
await proxy.start();
} catch (startError) {
console.error('Failed to restart services:', startError);
}

res.status(500).json({ error: 'Failed to reload configuration' });
}
});

app.use(
session({
store: config.getDatabase().type === 'mongo' ? db.getSessionStore(session) : null,
store: config.getDatabase().type === 'mongo' ? db.getSessionStore() || undefined : undefined,
secret: config.getCookieSecret(),
resave: false,
saveUninitialized: false,
Expand Down Expand Up @@ -102,7 +68,7 @@ async function createApp(proxy) {
app.use(passport.session());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use('/', routes(proxy));
app.use('/', routes.default(proxy));
app.use('/', express.static(absBuildPath));
app.get('/*', (req, res) => {
res.sendFile(path.join(`${absBuildPath}/index.html`));
Expand All @@ -113,10 +79,10 @@ async function createApp(proxy) {

/**
* Starts the proxy service.
* @param {proxy?} proxy A reference to the proxy express application, used to restart it when necessary.
* @param {*} proxy A reference to the proxy express application, used to restart it when necessary.
* @return {Promise<Express>} the express application (used for testing).
*/
async function start(proxy) {
async function start(proxy: any) {
if (!proxy) {
console.warn("WARNING: proxy is null and can't be controlled by the API service");
}
Expand All @@ -139,4 +105,8 @@ async function stop() {
_httpServer.close();
}

module.exports = { start, stop, httpServer: _httpServer };
export default {
start,
stop,
httpServer: _httpServer,
};
Loading
Loading