-
+
{data.attestation.reviewer.gitAccount}
{' '}
approved this contribution
diff --git a/src/ui/views/PushDetails/components/AttestationView.jsx b/src/ui/views/PushDetails/components/AttestationView.jsx
index 70540ca76..9ccbfc8a8 100644
--- a/src/ui/views/PushDetails/components/AttestationView.jsx
+++ b/src/ui/views/PushDetails/components/AttestationView.jsx
@@ -62,7 +62,7 @@ export default function AttestationView(props) {
Prior to making this code contribution publicly accessible via GitHub, this code
contribution was reviewed and approved by{' '}
-
+
{props.data.reviewer.gitAccount}
. As a reviewer, it was their responsibility to confirm that open sourcing this
@@ -72,7 +72,7 @@ export default function AttestationView(props) {
-
+
{props.data.reviewer.gitAccount}
{' '}
approved this contribution{' '}
diff --git a/src/ui/views/RepoDetails/RepoDetails.jsx b/src/ui/views/RepoDetails/RepoDetails.jsx
index 9c91c1b68..56d80e278 100644
--- a/src/ui/views/RepoDetails/RepoDetails.jsx
+++ b/src/ui/views/RepoDetails/RepoDetails.jsx
@@ -51,7 +51,7 @@ export default function RepoDetails() {
const removeRepository = async (name) => {
await deleteRepo(name);
- navigate('/admin/repo', { replace: true });
+ navigate('/dashboard/repo', { replace: true });
};
const refresh = () => getRepo(setIsLoading, setData, setAuth, setIsError, repoName);
@@ -151,7 +151,7 @@ export default function RepoDetails() {
return (
- {row}
+ {row}
{user.admin && (
@@ -196,7 +196,7 @@ export default function RepoDetails() {
return (
- {row}
+ {row}
{user.admin && (
diff --git a/src/ui/views/RepoList/Components/RepoOverview.jsx b/src/ui/views/RepoList/Components/RepoOverview.jsx
index a431dc721..826f78c97 100644
--- a/src/ui/views/RepoList/Components/RepoOverview.jsx
+++ b/src/ui/views/RepoList/Components/RepoOverview.jsx
@@ -1,6 +1,5 @@
import React, { useEffect } from 'react';
-import TableCell from '@material-ui/core/TableCell';
-import TableRow from '@material-ui/core/TableRow';
+import { Snackbar, TableCell, TableRow } from '@material-ui/core';
import GridContainer from '../../../components/Grid/GridContainer';
import GridItem from '../../../components/Grid/GridItem';
import { CodeReviewIcon, LawIcon, PeopleIcon } from '@primer/octicons-react';
@@ -572,6 +571,9 @@ import CodeActionButton from '../../../components/CustomButtons/CodeActionButton
export default function Repositories(props) {
const [github, setGitHub] = React.useState({});
+ const [errorMessage, setErrorMessage] = React.useState('');
+ const [snackbarOpen, setSnackbarOpen] = React.useState(false);
+
useEffect(() => {
getGitHubRepository();
}, [props.data.project, props.data.name]);
@@ -581,6 +583,10 @@ export default function Repositories(props) {
.get(`https://api.github.com/repos/${props.data.project}/${props.data.name}`)
.then((res) => {
setGitHub(res.data);
+ })
+ .catch((error) => {
+ setErrorMessage(`Error fetching GitHub repository ${props.data.project}/${props.data.name}: ${error}`);
+ setSnackbarOpen(true);
});
};
@@ -591,7 +597,7 @@ export default function Repositories(props) {
+ setSnackbarOpen(false)}
+ message={errorMessage}
+ />
);
}
diff --git a/src/ui/views/RepoList/Components/Repositories.jsx b/src/ui/views/RepoList/Components/Repositories.jsx
index 4be87b36d..ac9663423 100644
--- a/src/ui/views/RepoList/Components/Repositories.jsx
+++ b/src/ui/views/RepoList/Components/Repositories.jsx
@@ -29,7 +29,7 @@ export default function Repositories(props) {
const itemsPerPage = 5;
const navigate = useNavigate();
const { user } = useContext(UserContext);
- const openRepo = (repo) => navigate(`/admin/repo/${repo}`, { replace: true });
+ const openRepo = (repo) => navigate(`/dashboard/repo/${repo}`, { replace: true });
useEffect(() => {
const query = {};
diff --git a/src/ui/views/User/User.jsx b/src/ui/views/User/User.jsx
index c8b46ebe5..44fa64e1c 100644
--- a/src/ui/views/User/User.jsx
+++ b/src/ui/views/User/User.jsx
@@ -52,7 +52,7 @@ export default function Dashboard() {
if (isLoading) return Loading...
;
if (isError) return Something went wrong ...
;
- if (!auth && window.location.pathname === '/admin/profile') {
+ if (!auth && window.location.pathname === '/dashboard/profile') {
return ;
}
@@ -60,7 +60,7 @@ export default function Dashboard() {
try {
data.gitAccount = escapeHTML(gitAccount);
await updateUser(data);
- navigate(`/admin/user/${data.username}`);
+ navigate(`/dashboard/profile`);
} catch {
setIsError(true);
}
diff --git a/src/ui/views/UserList/Components/UserList.jsx b/src/ui/views/UserList/Components/UserList.jsx
index f148a8384..ee6812485 100644
--- a/src/ui/views/UserList/Components/UserList.jsx
+++ b/src/ui/views/UserList/Components/UserList.jsx
@@ -31,7 +31,7 @@ export default function UserList(props) {
const itemsPerPage = 5;
const [searchQuery, setSearchQuery] = useState('');
- const openUser = (username) => navigate(`/admin/user/${username}`, { replace: true });
+ const openUser = (username) => navigate(`/dashboard/admin/user/${username}`, { replace: true });
useEffect(() => {
diff --git a/test/ConfigLoader.test.js b/test/ConfigLoader.test.js
new file mode 100644
index 000000000..59d63458a
--- /dev/null
+++ b/test/ConfigLoader.test.js
@@ -0,0 +1,426 @@
+import fs from 'fs';
+import path from 'path';
+import { expect } from 'chai';
+import { ConfigLoader } from '../src/config/ConfigLoader';
+import { isValidGitUrl, isValidPath, isValidBranchName } from '../src/config/ConfigLoader';
+import sinon from 'sinon';
+import axios from 'axios';
+
+describe('ConfigLoader', () => {
+ let configLoader;
+ let tempDir;
+ let tempConfigFile;
+
+ beforeEach(() => {
+ // Create temp directory for test files
+ tempDir = fs.mkdtempSync('gitproxy-configloader-test-');
+ tempConfigFile = path.join(tempDir, 'test-config.json');
+ });
+
+ afterEach(() => {
+ // Clean up temp files
+ if (fs.existsSync(tempDir)) {
+ fs.rmSync(tempDir, { recursive: true });
+ }
+ sinon.restore();
+ });
+
+ describe('loadFromFile', () => {
+ it('should load configuration from file', async () => {
+ const testConfig = {
+ proxyUrl: 'https://test.com',
+ cookieSecret: 'test-secret',
+ };
+ fs.writeFileSync(tempConfigFile, JSON.stringify(testConfig));
+
+ configLoader = new ConfigLoader({});
+ const result = await configLoader.loadFromFile({
+ type: 'file',
+ enabled: true,
+ path: tempConfigFile,
+ });
+
+ expect(result).to.deep.equal(testConfig);
+ });
+ });
+
+ describe('loadFromHttp', () => {
+ it('should load configuration from HTTP endpoint', async () => {
+ const testConfig = {
+ proxyUrl: 'https://test.com',
+ cookieSecret: 'test-secret',
+ };
+
+ sinon.stub(axios, 'get').resolves({ data: testConfig });
+
+ configLoader = new ConfigLoader({});
+ const result = await configLoader.loadFromHttp({
+ type: 'http',
+ enabled: true,
+ url: 'http://config-service/config',
+ headers: {},
+ });
+
+ expect(result).to.deep.equal(testConfig);
+ });
+
+ it('should include bearer token if provided', async () => {
+ const axiosStub = sinon.stub(axios, 'get').resolves({ data: {} });
+
+ configLoader = new ConfigLoader({});
+ await configLoader.loadFromHttp({
+ type: 'http',
+ enabled: true,
+ url: 'http://config-service/config',
+ auth: {
+ type: 'bearer',
+ token: 'test-token',
+ },
+ });
+
+ expect(
+ axiosStub.calledWith('http://config-service/config', {
+ headers: { Authorization: 'Bearer test-token' },
+ }),
+ ).to.be.true;
+ });
+ });
+
+ describe('reloadConfiguration', () => {
+ it('should emit configurationChanged event when config changes', async () => {
+ const initialConfig = {
+ configurationSources: {
+ enabled: true,
+ sources: [
+ {
+ type: 'file',
+ enabled: true,
+ path: tempConfigFile,
+ },
+ ],
+ reloadIntervalSeconds: 0,
+ },
+ };
+
+ const newConfig = {
+ proxyUrl: 'https://new-test.com',
+ };
+
+ fs.writeFileSync(tempConfigFile, JSON.stringify(newConfig));
+
+ configLoader = new ConfigLoader(initialConfig);
+ const spy = sinon.spy();
+ configLoader.on('configurationChanged', spy);
+
+ await configLoader.reloadConfiguration();
+
+ expect(spy.calledOnce).to.be.true;
+ expect(spy.firstCall.args[0]).to.deep.include(newConfig);
+ });
+
+ it('should not emit event if config has not changed', async () => {
+ const testConfig = {
+ proxyUrl: 'https://test.com',
+ };
+
+ const config = {
+ configurationSources: {
+ enabled: true,
+ sources: [
+ {
+ type: 'file',
+ enabled: true,
+ path: tempConfigFile,
+ },
+ ],
+ reloadIntervalSeconds: 0,
+ },
+ };
+
+ fs.writeFileSync(tempConfigFile, JSON.stringify(testConfig));
+
+ configLoader = new ConfigLoader(config);
+ const spy = sinon.spy();
+ configLoader.on('configurationChanged', spy);
+
+ await configLoader.reloadConfiguration(); // First reload should emit
+ await configLoader.reloadConfiguration(); // Second reload should not emit since config hasn't changed
+
+ expect(spy.calledOnce).to.be.true; // Should only emit once
+ });
+ });
+
+ describe('initialize', () => {
+ it('should initialize cache directory using env-paths', async () => {
+ const configLoader = new ConfigLoader({});
+ await configLoader.initialize();
+
+ // Check that cacheDir is set and is a string
+ expect(configLoader.cacheDir).to.be.a('string');
+
+ // Check that it contains 'git-proxy' in the path
+ expect(configLoader.cacheDir).to.include('git-proxy');
+
+ // On macOS, it should be in the Library/Caches directory
+ // On Linux, it should be in the ~/.cache directory
+ // On Windows, it should be in the AppData/Local directory
+ if (process.platform === 'darwin') {
+ expect(configLoader.cacheDir).to.include('Library/Caches');
+ } else if (process.platform === 'linux') {
+ expect(configLoader.cacheDir).to.include('.cache');
+ } else if (process.platform === 'win32') {
+ expect(configLoader.cacheDir).to.include('AppData/Local');
+ }
+ });
+
+ it('should create cache directory if it does not exist', async () => {
+ const configLoader = new ConfigLoader({});
+ await configLoader.initialize();
+
+ // Check if directory exists
+ expect(fs.existsSync(configLoader.cacheDir)).to.be.true;
+ });
+ });
+
+ describe('loadRemoteConfig', () => {
+ let configLoader;
+ beforeEach(async () => {
+ const configFilePath = path.join(__dirname, '..', 'proxy.config.json');
+ const config = JSON.parse(fs.readFileSync(configFilePath, 'utf-8'));
+
+ config.configurationSources.enabled = true;
+ configLoader = new ConfigLoader(config);
+ await configLoader.initialize();
+ });
+
+ it('should load configuration from git repository', async function () {
+ // eslint-disable-next-line no-invalid-this
+ this.timeout(10000);
+
+ const source = {
+ type: 'git',
+ repository: 'https://github.com/finos/git-proxy.git',
+ path: 'proxy.config.json',
+ branch: 'main',
+ enabled: true,
+ };
+
+ const config = await configLoader.loadFromGit(source);
+
+ // Verify the loaded config has expected structure
+ expect(config).to.be.an('object');
+ expect(config).to.have.property('proxyUrl');
+ expect(config).to.have.property('cookieSecret');
+ });
+
+ it('should throw error for invalid configuration file path', async function () {
+ const source = {
+ type: 'git',
+ repository: 'https://github.com/finos/git-proxy.git',
+ path: '\0', // Invalid path
+ branch: 'main',
+ enabled: true,
+ };
+
+ try {
+ await configLoader.loadFromGit(source);
+ throw new Error('Expected error was not thrown');
+ } catch (error) {
+ expect(error.message).to.equal('Invalid configuration file path in repository');
+ }
+ });
+
+ it('should load configuration from http', async function () {
+ // eslint-disable-next-line no-invalid-this
+ this.timeout(10000);
+
+ const source = {
+ type: 'http',
+ url: 'https://raw.githubusercontent.com/finos/git-proxy/refs/heads/main/proxy.config.json',
+ enabled: true,
+ };
+
+ const config = await configLoader.loadFromHttp(source);
+
+ // Verify the loaded config has expected structure
+ expect(config).to.be.an('object');
+ expect(config).to.have.property('proxyUrl');
+ expect(config).to.have.property('cookieSecret');
+ });
+ });
+
+ describe('deepMerge', () => {
+ let configLoader;
+
+ beforeEach(() => {
+ configLoader = new ConfigLoader({});
+ });
+
+ it('should merge simple objects', () => {
+ const target = { a: 1, b: 2 };
+ const source = { b: 3, c: 4 };
+
+ const result = configLoader.deepMerge(target, source);
+
+ expect(result).to.deep.equal({ a: 1, b: 3, c: 4 });
+ });
+
+ it('should merge nested objects', () => {
+ const target = {
+ a: 1,
+ b: { x: 1, y: 2 },
+ c: { z: 3 },
+ };
+ const source = {
+ b: { y: 4, w: 5 },
+ c: { z: 6 },
+ };
+
+ const result = configLoader.deepMerge(target, source);
+
+ expect(result).to.deep.equal({
+ a: 1,
+ b: { x: 1, y: 4, w: 5 },
+ c: { z: 6 },
+ });
+ });
+
+ it('should handle arrays by replacing them', () => {
+ const target = {
+ a: [1, 2, 3],
+ b: { items: [4, 5] },
+ };
+ const source = {
+ a: [7, 8],
+ b: { items: [9] },
+ };
+
+ const result = configLoader.deepMerge(target, source);
+
+ expect(result).to.deep.equal({
+ a: [7, 8],
+ b: { items: [9] },
+ });
+ });
+
+ it('should handle null and undefined values', () => {
+ const target = {
+ a: 1,
+ b: null,
+ c: undefined,
+ };
+ const source = {
+ a: null,
+ b: 2,
+ c: 3,
+ };
+
+ const result = configLoader.deepMerge(target, source);
+
+ expect(result).to.deep.equal({
+ a: null,
+ b: 2,
+ c: 3,
+ });
+ });
+
+ it('should handle empty objects', () => {
+ const target = {};
+ const source = { a: 1, b: { c: 2 } };
+
+ const result = configLoader.deepMerge(target, source);
+
+ expect(result).to.deep.equal({ a: 1, b: { c: 2 } });
+ });
+
+ it('should not modify the original objects', () => {
+ const target = { a: 1, b: { c: 2 } };
+ const source = { b: { c: 3 } };
+ const originalTarget = { ...target };
+ const originalSource = { ...source };
+
+ configLoader.deepMerge(target, source);
+
+ expect(target).to.deep.equal(originalTarget);
+ expect(source).to.deep.equal(originalSource);
+ });
+ });
+});
+
+describe('Validation Helpers', () => {
+ describe('isValidGitUrl', () => {
+ it('should validate git URLs correctly', () => {
+ // Valid URLs
+ expect(isValidGitUrl('git://github.com/user/repo.git')).to.be.true;
+ expect(isValidGitUrl('https://github.com/user/repo.git')).to.be.true;
+ expect(isValidGitUrl('ssh://git@github.com/user/repo.git')).to.be.true;
+ expect(isValidGitUrl('user@github.com:user/repo.git')).to.be.true;
+
+ // Invalid URLs
+ expect(isValidGitUrl('not-a-git-url')).to.be.false;
+ expect(isValidGitUrl('http://github.com/user/repo')).to.be.false;
+ expect(isValidGitUrl('')).to.be.false;
+ expect(isValidGitUrl(null)).to.be.false;
+ expect(isValidGitUrl(undefined)).to.be.false;
+ expect(isValidGitUrl(123)).to.be.false;
+ });
+ });
+
+ describe('isValidPath', () => {
+ it('should validate file paths correctly', () => {
+ const cwd = process.cwd();
+
+ // Valid paths
+ expect(isValidPath(path.join(cwd, 'config.json'))).to.be.true;
+ expect(isValidPath(path.join(cwd, 'subfolder/config.json'))).to.be.true;
+ expect(isValidPath('/etc/passwd')).to.be.true;
+ expect(isValidPath('../config.json')).to.be.true;
+
+ // Invalid paths
+ expect(isValidPath('')).to.be.false;
+ expect(isValidPath(null)).to.be.false;
+ expect(isValidPath(undefined)).to.be.false;
+
+ // Additional edge cases
+ expect(isValidPath({})).to.be.false;
+ expect(isValidPath([])).to.be.false;
+ expect(isValidPath(123)).to.be.false;
+ expect(isValidPath(true)).to.be.false;
+ expect(isValidPath('\0invalid')).to.be.false;
+ expect(isValidPath('\u0000')).to.be.false;
+ });
+
+ it('should handle path resolution errors', () => {
+ // Mock path.resolve to throw an error
+ const originalResolve = path.resolve;
+ path.resolve = () => {
+ throw new Error('Mock path resolution error');
+ };
+
+ expect(isValidPath('some/path')).to.be.false;
+
+ // Restore original path.resolve
+ path.resolve = originalResolve;
+ });
+ });
+
+ describe('isValidBranchName', () => {
+ it('should validate git branch names correctly', () => {
+ // Valid branch names
+ expect(isValidBranchName('main')).to.be.true;
+ expect(isValidBranchName('feature/new-feature')).to.be.true;
+ expect(isValidBranchName('release-1.0')).to.be.true;
+ expect(isValidBranchName('fix_123')).to.be.true;
+ expect(isValidBranchName('user/feature/branch')).to.be.true;
+
+ // Invalid branch names
+ expect(isValidBranchName('.invalid')).to.be.false;
+ expect(isValidBranchName('-invalid')).to.be.false;
+ expect(isValidBranchName('branch with spaces')).to.be.false;
+ expect(isValidBranchName('')).to.be.false;
+ expect(isValidBranchName(null)).to.be.false;
+ expect(isValidBranchName(undefined)).to.be.false;
+ expect(isValidBranchName('branch..name')).to.be.false;
+ });
+ });
+});
diff --git a/test/chain.test.js b/test/chain.test.js
index d646b9dc7..1fc749248 100644
--- a/test/chain.test.js
+++ b/test/chain.test.js
@@ -15,65 +15,81 @@ const mockLoader = {
],
};
-const mockPushProcessors = {
- parsePush: sinon.stub(),
- audit: sinon.stub(),
- checkRepoInAuthorisedList: sinon.stub(),
- checkCommitMessages: sinon.stub(),
- checkAuthorEmails: sinon.stub(),
- checkUserPushPermission: sinon.stub(),
- checkIfWaitingAuth: sinon.stub(),
- pullRemote: sinon.stub(),
- writePack: sinon.stub(),
- preReceive: sinon.stub(),
- getDiff: sinon.stub(),
- clearBareClone: sinon.stub(),
- scanDiff: sinon.stub(),
- blockForAuth: sinon.stub(),
+const initMockPushProcessors = (sinon) => {
+ const mockPushProcessors = {
+ parsePush: sinon.stub(),
+ audit: sinon.stub(),
+ checkRepoInAuthorisedList: sinon.stub(),
+ checkCommitMessages: sinon.stub(),
+ checkAuthorEmails: sinon.stub(),
+ checkUserPushPermission: sinon.stub(),
+ checkIfWaitingAuth: sinon.stub(),
+ pullRemote: sinon.stub(),
+ writePack: sinon.stub(),
+ preReceive: sinon.stub(),
+ getDiff: sinon.stub(),
+ gitleaks: sinon.stub(),
+ clearBareClone: sinon.stub(),
+ scanDiff: sinon.stub(),
+ blockForAuth: sinon.stub(),
+ };
+ mockPushProcessors.parsePush.displayName = 'parsePush';
+ mockPushProcessors.audit.displayName = 'audit';
+ mockPushProcessors.checkRepoInAuthorisedList.displayName = 'checkRepoInAuthorisedList';
+ mockPushProcessors.checkCommitMessages.displayName = 'checkCommitMessages';
+ mockPushProcessors.checkAuthorEmails.displayName = 'checkAuthorEmails';
+ mockPushProcessors.checkUserPushPermission.displayName = 'checkUserPushPermission';
+ mockPushProcessors.checkIfWaitingAuth.displayName = 'checkIfWaitingAuth';
+ mockPushProcessors.pullRemote.displayName = 'pullRemote';
+ mockPushProcessors.writePack.displayName = 'writePack';
+ mockPushProcessors.preReceive.displayName = 'preReceive';
+ mockPushProcessors.getDiff.displayName = 'getDiff';
+ mockPushProcessors.gitleaks.displayName = 'gitleaks';
+ mockPushProcessors.clearBareClone.displayName = 'clearBareClone';
+ mockPushProcessors.scanDiff.displayName = 'scanDiff';
+ mockPushProcessors.blockForAuth.displayName = 'blockForAuth';
+ return mockPushProcessors;
};
-mockPushProcessors.parsePush.displayName = 'parsePush';
-mockPushProcessors.audit.displayName = 'audit';
-mockPushProcessors.checkRepoInAuthorisedList.displayName = 'checkRepoInAuthorisedList';
-mockPushProcessors.checkCommitMessages.displayName = 'checkCommitMessages';
-mockPushProcessors.checkAuthorEmails.displayName = 'checkAuthorEmails';
-mockPushProcessors.checkUserPushPermission.displayName = 'checkUserPushPermission';
-mockPushProcessors.checkIfWaitingAuth.displayName = 'checkIfWaitingAuth';
-mockPushProcessors.pullRemote.displayName = 'pullRemote';
-mockPushProcessors.writePack.displayName = 'writePack';
-mockPushProcessors.preReceive.displayName = 'preReceive';
-mockPushProcessors.getDiff.displayName = 'getDiff';
-mockPushProcessors.clearBareClone.displayName = 'clearBareClone';
-mockPushProcessors.scanDiff.displayName = 'scanDiff';
-mockPushProcessors.blockForAuth.displayName = 'blockForAuth';
const mockPreProcessors = {
parseAction: sinon.stub(),
};
+const clearCache = (sandbox) => {
+ delete require.cache[require.resolve('../src/proxy/processors')];
+ delete require.cache[require.resolve('../src/proxy/chain')];
+ sandbox.reset();
+};
+
describe('proxy chain', function () {
let processors;
let chain;
+ let mockPushProcessors;
+ let sandboxSinon;
beforeEach(async () => {
+ // Create a new sandbox for each test
+ sandboxSinon = sinon.createSandbox();
+ // Initialize the mock push processors
+ mockPushProcessors = initMockPushProcessors(sandboxSinon);
+
// Re-import the processors module after clearing the cache
processors = await import('../src/proxy/processors');
// Mock the processors module
- sinon.stub(processors, 'pre').value(mockPreProcessors);
+ sandboxSinon.stub(processors, 'pre').value(mockPreProcessors);
- sinon.stub(processors, 'push').value(mockPushProcessors);
+ sandboxSinon.stub(processors, 'push').value(mockPushProcessors);
// Re-import the chain module after stubbing processors
- chain = (await import('../src/proxy/chain')).default;
+ chain = require('../src/proxy/chain').default;
chain.chainPluginLoader = new PluginLoader([]);
});
afterEach(() => {
// Clear the module from the cache after each test
- delete require.cache[require.resolve('../src/proxy/processors')];
- delete require.cache[require.resolve('../src/proxy/chain')];
- sinon.reset();
+ clearCache(sandboxSinon);
});
it('getChain should set pluginLoaded if loader is undefined', async function () {
@@ -179,6 +195,7 @@ describe('proxy chain', function () {
mockPushProcessors.writePack.resolves(continuingAction);
mockPushProcessors.preReceive.resolves(continuingAction);
mockPushProcessors.getDiff.resolves(continuingAction);
+ mockPushProcessors.gitleaks.resolves(continuingAction);
mockPushProcessors.clearBareClone.resolves(continuingAction);
mockPushProcessors.scanDiff.resolves(continuingAction);
mockPushProcessors.blockForAuth.resolves(continuingAction);
@@ -196,6 +213,7 @@ describe('proxy chain', function () {
expect(mockPushProcessors.writePack.called).to.be.true;
expect(mockPushProcessors.preReceive.called).to.be.true;
expect(mockPushProcessors.getDiff.called).to.be.true;
+ expect(mockPushProcessors.gitleaks.called).to.be.true;
expect(mockPushProcessors.clearBareClone.called).to.be.true;
expect(mockPushProcessors.scanDiff.called).to.be.true;
expect(mockPushProcessors.blockForAuth.called).to.be.true;
@@ -276,6 +294,7 @@ describe('proxy chain', function () {
});
mockPushProcessors.getDiff.resolves(action);
+ mockPushProcessors.gitleaks.resolves(action);
mockPushProcessors.clearBareClone.resolves(action);
mockPushProcessors.scanDiff.resolves(action);
mockPushProcessors.blockForAuth.resolves(action);
@@ -322,6 +341,7 @@ describe('proxy chain', function () {
});
mockPushProcessors.getDiff.resolves(action);
+ mockPushProcessors.gitleaks.resolves(action);
mockPushProcessors.clearBareClone.resolves(action);
mockPushProcessors.scanDiff.resolves(action);
mockPushProcessors.blockForAuth.resolves(action);
@@ -368,6 +388,7 @@ describe('proxy chain', function () {
});
mockPushProcessors.getDiff.resolves(action);
+ mockPushProcessors.gitleaks.resolves(action);
mockPushProcessors.clearBareClone.resolves(action);
mockPushProcessors.scanDiff.resolves(action);
mockPushProcessors.blockForAuth.resolves(action);
@@ -413,6 +434,7 @@ describe('proxy chain', function () {
});
mockPushProcessors.getDiff.resolves(action);
+ mockPushProcessors.gitleaks.resolves(action);
mockPushProcessors.clearBareClone.resolves(action);
mockPushProcessors.scanDiff.resolves(action);
mockPushProcessors.blockForAuth.resolves(action);
diff --git a/test/testAuthMethods.test.js b/test/testAuthMethods.test.js
new file mode 100644
index 000000000..013c79d8d
--- /dev/null
+++ b/test/testAuthMethods.test.js
@@ -0,0 +1,61 @@
+const chai = require('chai');
+const config = require('../src/config');
+const sinon = require('sinon');
+const proxyquire = require('proxyquire');
+
+chai.should();
+const expect = chai.expect;
+
+describe('auth methods', async () => {
+ it('should return a local auth method by default', async function () {
+ const authMethods = config.getAuthMethods();
+ expect(authMethods).to.have.lengthOf(1);
+ expect(authMethods[0].type).to.equal('local');
+ });
+
+ it('should return an error if no auth methods are enabled', async function () {
+ const newConfig = JSON.stringify({
+ authentication: [
+ { type: 'local', enabled: false },
+ { type: 'ActiveDirectory', enabled: false },
+ { type: 'openidconnect', enabled: false },
+ ],
+ });
+
+ const fsStub = {
+ existsSync: sinon.stub().returns(true),
+ readFileSync: sinon.stub().returns(newConfig),
+ };
+
+ const config = proxyquire('../src/config', {
+ fs: fsStub,
+ });
+
+ expect(() => config.getAuthMethods()).to.throw(Error, 'No authentication method enabled');
+ });
+
+ it('should return an array of enabled auth methods when overridden', async function () {
+ const newConfig = JSON.stringify({
+ authentication: [
+ { type: 'local', enabled: true },
+ { type: 'ActiveDirectory', enabled: true },
+ { type: 'openidconnect', enabled: true },
+ ],
+ });
+
+ const fsStub = {
+ existsSync: sinon.stub().returns(true),
+ readFileSync: sinon.stub().returns(newConfig),
+ };
+
+ const config = proxyquire('../src/config', {
+ fs: fsStub,
+ });
+
+ const authMethods = config.getAuthMethods();
+ expect(authMethods).to.have.lengthOf(3);
+ expect(authMethods[0].type).to.equal('local');
+ expect(authMethods[1].type).to.equal('ActiveDirectory');
+ expect(authMethods[2].type).to.equal('openidconnect');
+ })
+});
diff --git a/test/testConfig.test.js b/test/testConfig.test.js
index 9f5f45419..08d528e2d 100644
--- a/test/testConfig.test.js
+++ b/test/testConfig.test.js
@@ -11,11 +11,13 @@ describe('default configuration', function () {
it('should use default values if no user-settings.json file exists', function () {
const config = require('../src/config');
config.logConfiguration();
+ const enabledMethods = defaultSettings.authentication.filter(method => method.enabled);
- expect(config.getAuthentication()).to.be.eql(defaultSettings.authentication[0]);
+ expect(config.getAuthMethods()).to.deep.equal(enabledMethods);
expect(config.getDatabase()).to.be.eql(defaultSettings.sink[0]);
expect(config.getTempPasswordConfig()).to.be.eql(defaultSettings.tempPassword);
expect(config.getAuthorisedList()).to.be.eql(defaultSettings.authorisedList);
+ expect(config.getRateLimit()).to.be.eql(defaultSettings.rateLimit);
expect(config.getTLSKeyPemPath()).to.be.eql(defaultSettings.tls.key);
expect(config.getTLSCertPemPath()).to.be.eql(defaultSettings.tls.cert);
});
@@ -47,9 +49,10 @@ describe('user configuration', function () {
fs.writeFileSync(tempUserFile, JSON.stringify(user));
const config = require('../src/config');
+ const enabledMethods = defaultSettings.authentication.filter(method => method.enabled);
expect(config.getAuthorisedList()).to.be.eql(user.authorisedList);
- expect(config.getAuthentication()).to.be.eql(defaultSettings.authentication[0]);
+ expect(config.getAuthMethods()).to.deep.equal(enabledMethods);
expect(config.getDatabase()).to.be.eql(defaultSettings.sink[0]);
expect(config.getTempPasswordConfig()).to.be.eql(defaultSettings.tempPassword);
});
@@ -66,9 +69,13 @@ describe('user configuration', function () {
fs.writeFileSync(tempUserFile, JSON.stringify(user));
const config = require('../src/config');
+ const authMethods = config.getAuthMethods();
+ const googleAuth = authMethods.find(method => method.type === 'google');
- expect(config.getAuthentication()).to.be.eql(user.authentication[0]);
- expect(config.getAuthentication()).to.not.be.eql(defaultSettings.authentication[0]);
+ expect(googleAuth).to.not.be.undefined;
+ expect(googleAuth.enabled).to.be.true;
+ expect(config.getAuthMethods()).to.deep.include(user.authentication[0]);
+ expect(config.getAuthMethods()).to.not.be.eql(defaultSettings.authentication);
expect(config.getDatabase()).to.be.eql(defaultSettings.sink[0]);
expect(config.getTempPasswordConfig()).to.be.eql(defaultSettings.tempPassword);
});
@@ -85,10 +92,11 @@ describe('user configuration', function () {
fs.writeFileSync(tempUserFile, JSON.stringify(user));
const config = require('../src/config');
+ const enabledMethods = defaultSettings.authentication.filter(method => method.enabled);
expect(config.getDatabase()).to.be.eql(user.sink[0]);
expect(config.getDatabase()).to.not.be.eql(defaultSettings.sink[0]);
- expect(config.getAuthentication()).to.be.eql(defaultSettings.authentication[0]);
+ expect(config.getAuthMethods()).to.deep.equal(enabledMethods);
expect(config.getTempPasswordConfig()).to.be.eql(defaultSettings.tempPassword);
});
@@ -107,6 +115,21 @@ describe('user configuration', function () {
expect(config.getTLSCertPemPath()).to.be.eql(user.tls.cert);
});
+ it('should override default settings for rate limiting', function () {
+ const limitConfig = {
+ rateLimit: {
+ windowMs: 60000,
+ limit: 1500,
+ },
+ };
+ fs.writeFileSync(tempUserFile, JSON.stringify(limitConfig));
+
+ const config = require('../src/config');
+
+ expect(config.getRateLimit().windowMs).to.be.eql(limitConfig.rateLimit.windowMs);
+ expect(config.getRateLimit().limit).to.be.eql(limitConfig.rateLimit.limit);
+ });
+
afterEach(function () {
fs.rmSync(tempUserFile);
fs.rmdirSync(tempDir);
diff --git a/test/testLogin.test.js b/test/testLogin.test.js
index 812e4f755..107bb7256 100644
--- a/test/testLogin.test.js
+++ b/test/testLogin.test.js
@@ -43,7 +43,7 @@ describe('auth', async () => {
});
it('should now be able to access the user login metadata', async function () {
- const res = await chai.request(app).get('/api/auth/userLoggedIn').set('Cookie', `${cookie}`);
+ const res = await chai.request(app).get('/api/auth/me').set('Cookie', `${cookie}`);
res.should.have.status(200);
});
@@ -62,6 +62,22 @@ describe('auth', async () => {
res.should.have.status(401);
});
+
+ it('should fail to login with invalid username', async function () {
+ const res = await chai.request(app).post('/api/auth/login').send({
+ username: 'invalid',
+ password: 'admin',
+ });
+ res.should.have.status(401);
+ });
+
+ it('should fail to login with invalid password', async function () {
+ const res = await chai.request(app).post('/api/auth/login').send({
+ username: 'admin',
+ password: 'invalid',
+ });
+ res.should.have.status(401);
+ });
});
after(async function () {
diff --git a/tsconfig.json b/tsconfig.json
index 805153d01..a389ca8c7 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -8,6 +8,7 @@
"moduleResolution": "Node",
"strict": true,
"noEmit": true,
+ "declaration": true,
"skipLibCheck": true,
"isolatedModules": true,
"module": "CommonJS",
@@ -15,5 +16,6 @@
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true
},
- "include": ["src"]
+ "include": ["."],
+ "exclude": ["experimental/**", "plugins/**"]
}
diff --git a/tsconfig.publish.json b/tsconfig.publish.json
new file mode 100644
index 000000000..ef9be14f7
--- /dev/null
+++ b/tsconfig.publish.json
@@ -0,0 +1,15 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "noEmit": false,
+ "outDir": "./dist"
+ },
+ "exclude": [
+ "experimental/**",
+ "plugins/**",
+ "./dist/**",
+ "./src/ui",
+ "./src/**/*.jsx",
+ "./src/context.js"
+ ]
+}
diff --git a/website/docs/configuration/overview.mdx b/website/docs/configuration/overview.mdx
index 5493d54f6..274de5443 100644
--- a/website/docs/configuration/overview.mdx
+++ b/website/docs/configuration/overview.mdx
@@ -7,6 +7,7 @@ description: How to customise push protections and policies
On installation, GitProxy ships with an [out-of-the-box configuration](https://github.com/finos/git-proxy/blob/main/proxy.config.json). This is fine for
demonstration purposes but is likely not what you want to deploy into your environment.
+
### Customise configuration
To customise your GitProxy configuration, create a `proxy.config.json` in your current
@@ -44,8 +45,9 @@ npx -- @finos/git-proxy --config ./config.json
```
### Set ports with ENV variables
+
By default, GitProxy uses port 8000 to expose the Git Server and 8080 for the frontend application.
-The ports can be changed by setting the `GIT_PROXY_SERVER_PORT`, `GIT_PROXY_HTTPS_SERVER_PORT` (optional) and `GIT_PROXY_UI_PORT`
+The ports can be changed by setting the `GIT_PROXY_SERVER_PORT`, `GIT_PROXY_HTTPS_SERVER_PORT` (optional) and `GIT_PROXY_UI_PORT`
environment variables:
```
@@ -54,10 +56,10 @@ export GIT_PROXY_SERVER_PORT="9090"
export GIT_PROXY_HTTPS_SERVER_PORT="9443"
```
-Note that `GIT_PROXY_UI_PORT` is needed for both server and UI Node processes,
+Note that `GIT_PROXY_UI_PORT` is needed for both server and UI Node processes,
whereas `GIT_PROXY_SERVER_PORT` (and `GIT_PROXY_HTTPS_SERVER_PORT`) is only needed by the server process.
-By default, GitProxy CLI connects to GitProxy running on localhost and default port. This can be
+By default, GitProxy CLI connects to GitProxy running on localhost and default port. This can be
changed by setting the `GIT_PROXY_UI_HOST` and `GIT_PROXY_UI_PORT` environment variables:
```
@@ -79,5 +81,66 @@ To validate your configuration at a custom file location, run:
git-proxy --validate --config ./config.json
```
+### Configuration Sources
+
+GitProxy supports dynamic configuration loading from multiple sources. This feature allows you to manage your configuration from external sources and update it without restarting the service. Configuration sources can be files, HTTP endpoints, or Git repositories.
+
+To enable configuration sources, add the `configurationSources` section to your configuration:
+
+```json
+{
+ "configurationSources": {
+ "enabled": true,
+ "reloadIntervalSeconds": 60,
+ "merge": false,
+ "sources": [
+ {
+ "type": "file",
+ "enabled": true,
+ "path": "./external-config.json"
+ },
+ {
+ "type": "http",
+ "enabled": true,
+ "url": "http://config-service/git-proxy-config",
+ "headers": {},
+ "auth": {
+ "type": "bearer",
+ "token": "your-token"
+ }
+ },
+ {
+ "type": "git",
+ "enabled": true,
+ "repository": "https://git-server.com/project/git-proxy-config",
+ "branch": "main",
+ "path": "git-proxy/config.json",
+ "auth": {
+ "type": "ssh",
+ "privateKeyPath": "/path/to/.ssh/id_rsa"
+ }
+ }
+ ]
+ }
+}
+```
+
+The configuration options for `configurationSources` are:
+
+- `enabled`: Enable/disable dynamic configuration loading
+- `reloadIntervalSeconds`: How often to check for configuration updates (in seconds)
+- `merge`: When true, merges configurations from all enabled sources. When false, uses the last successful configuration load. This can be used to upload only partial configuration to external source
+- `sources`: Array of configuration sources to load from
+
+Each source can be one of three types:
+
+1. `file`: Load from a local JSON file
+2. `http`: Load from an HTTP endpoint
+3. `git`: Load from a Git repository
+When configuration changes are detected, GitProxy will:
+1. Validate the new configuration
+2. Stop existing services
+3. Apply the new configuration
+4. Restart services with the updated configuration
diff --git a/website/docs/configuration/reference.mdx b/website/docs/configuration/reference.mdx
index 6a7eceedf..3b8402305 100644
--- a/website/docs/configuration/reference.mdx
+++ b/website/docs/configuration/reference.mdx
@@ -7,11 +7,11 @@ description: JSON schema reference documentation for GitProxy
**Title:** GitProxy configuration file
-| | |
-| ------------------------- | ------------------------------------------------------- |
-| **Type** | `object` |
-| **Required** | No |
-| **Additional properties** | [[Not allowed]](# "Additional Properties not allowed.") |
+| | |
+| ------------------------- | ----------- |
+| **Type** | `object` |
+| **Required** | No |
+| **Additional properties** | Not allowed |
**Description:** Configuration for customizing git-proxy
@@ -63,11 +63,11 @@ description: JSON schema reference documentation for GitProxy
-| | |
-| ------------------------- | ------------------------------------------------------------------------- |
-| **Type** | `object` |
-| **Required** | No |
-| **Additional properties** | [[Any type: allowed]](# "Additional Properties of any type are allowed.") |
+| | |
+| ------------------------- | ---------------- |
+| **Type** | `object` |
+| **Required** | No |
+| **Additional properties** | Any type allowed |
**Description:** Third party APIs
@@ -80,11 +80,11 @@ description: JSON schema reference documentation for GitProxy
-| | |
-| ------------------------- | ------------------------------------------------------------------------- |
-| **Type** | `object` |
-| **Required** | No |
-| **Additional properties** | [[Any type: allowed]](# "Additional Properties of any type are allowed.") |
+| | |
+| ------------------------- | ---------------- |
+| **Type** | `object` |
+| **Required** | No |
+| **Additional properties** | Any type allowed |
**Description:** Enforce rules and patterns on commits including e-mail and message
@@ -97,11 +97,11 @@ description: JSON schema reference documentation for GitProxy
-| | |
-| ------------------------- | ------------------------------------------------------------------------- |
-| **Type** | `object` |
-| **Required** | No |
-| **Additional properties** | [[Any type: allowed]](# "Additional Properties of any type are allowed.") |
+| | |
+| ------------------------- | ---------------- |
+| **Type** | `object` |
+| **Required** | No |
+| **Additional properties** | Any type allowed |
**Description:** Customisable questions to add to attestation form
@@ -114,11 +114,11 @@ description: JSON schema reference documentation for GitProxy
-| | |
-| ------------------------- | ------------------------------------------------------------------------- |
-| **Type** | `object` |
-| **Required** | No |
-| **Additional properties** | [[Any type: allowed]](# "Additional Properties of any type are allowed.") |
+| | |
+| ------------------------- | ---------------- |
+| **Type** | `object` |
+| **Required** | No |
+| **Additional properties** | Any type allowed |
**Description:** Provide domains to use alternative to the defaults
@@ -127,7 +127,88 @@ description: JSON schema reference documentation for GitProxy
- 8. [Optional] Property GitProxy configuration file > privateOrganizations
+ 8. [Optional] Property GitProxy configuration file > rateLimit
+
+
+
+| | |
+| ------------------------- | ----------- |
+| **Type** | `object` |
+| **Required** | No |
+| **Additional properties** | Not allowed |
+
+**Description:** API Rate limiting configuration.
+
+
+
+ 8.1. [Required] Property GitProxy configuration file > rateLimit > windowMs
+
+
+
+| | |
+| ------------ | -------- |
+| **Type** | `number` |
+| **Required** | Yes |
+
+**Description:** How long to remember requests for, in milliseconds (default 10 mins).
+
+
+
+
+
+
+ 8.2. [Required] Property GitProxy configuration file > rateLimit > limit
+
+
+
+| | |
+| ------------ | -------- |
+| **Type** | `number` |
+| **Required** | Yes |
+
+**Description:** How many requests to allow (default 150).
+
+
+
+
+
+
+ 8.3. [Optional] Property GitProxy configuration file > rateLimit > statusCode
+
+
+
+| | |
+| ------------ | -------- |
+| **Type** | `number` |
+| **Required** | No |
+
+**Description:** HTTP status code after limit is reached (default is 429).
+
+
+
+
+
+
+ 8.4. [Optional] Property GitProxy configuration file > rateLimit > message
+
+
+
+| | |
+| ------------ | -------- |
+| **Type** | `string` |
+| **Required** | No |
+
+**Description:** Response to return after limit is reached.
+
+
+
+
+
+
+
+
+
+ 9. [Optional] Property GitProxy configuration file > privateOrganizations
@@ -143,7 +224,7 @@ description: JSON schema reference documentation for GitProxy
- 9. [Optional] Property GitProxy configuration file > urlShortener
+ 10. [Optional] Property GitProxy configuration file > urlShortener
@@ -159,7 +240,7 @@ description: JSON schema reference documentation for GitProxy
- 10. [Optional] Property GitProxy configuration file > contactEmail
+ 11. [Optional] Property GitProxy configuration file > contactEmail
@@ -175,7 +256,7 @@ description: JSON schema reference documentation for GitProxy
- 11. [Optional] Property GitProxy configuration file > csrfProtection
+ 12. [Optional] Property GitProxy configuration file > csrfProtection
@@ -191,7 +272,7 @@ description: JSON schema reference documentation for GitProxy
- 12. [Optional] Property GitProxy configuration file > plugins
+ 13. [Optional] Property GitProxy configuration file > plugins
@@ -206,7 +287,7 @@ description: JSON schema reference documentation for GitProxy
| ------------------------------- | ----------- |
| [plugins items](#plugins_items) | - |
-### 12.1. GitProxy configuration file > plugins > plugins items
+### 13.1. GitProxy configuration file > plugins > plugins items
| | |
| ------------ | -------- |
@@ -218,7 +299,7 @@ description: JSON schema reference documentation for GitProxy
- 13. [Optional] Property GitProxy configuration file > authorisedList
+ 14. [Optional] Property GitProxy configuration file > authorisedList
@@ -233,18 +314,18 @@ description: JSON schema reference documentation for GitProxy
| --------------------------------------- | ----------- |
| [authorisedRepo](#authorisedList_items) | - |
-### 13.1. GitProxy configuration file > authorisedList > authorisedRepo
+### 14.1. GitProxy configuration file > authorisedList > authorisedRepo
-| | |
-| ------------------------- | ------------------------------------------------------------------------- |
-| **Type** | `object` |
-| **Required** | No |
-| **Additional properties** | [[Any type: allowed]](# "Additional Properties of any type are allowed.") |
-| **Defined in** | #/definitions/authorisedRepo |
+| | |
+| ------------------------- | ---------------------------- |
+| **Type** | `object` |
+| **Required** | No |
+| **Additional properties** | Any type allowed |
+| **Defined in** | #/definitions/authorisedRepo |
- 13.1.1. [Required] Property GitProxy configuration file > authorisedList > authorisedList items > project
+ 14.1.1. [Required] Property GitProxy configuration file > authorisedList > authorisedList items > project
@@ -258,7 +339,7 @@ description: JSON schema reference documentation for GitProxy
- 13.1.2. [Required] Property GitProxy configuration file > authorisedList > authorisedList items > name
+ 14.1.2. [Required] Property GitProxy configuration file > authorisedList > authorisedList items > name
@@ -272,7 +353,7 @@ description: JSON schema reference documentation for GitProxy
- 13.1.3. [Required] Property GitProxy configuration file > authorisedList > authorisedList items > url
+ 14.1.3. [Required] Property GitProxy configuration file > authorisedList > authorisedList items > url
@@ -289,7 +370,7 @@ description: JSON schema reference documentation for GitProxy
- 14. [Optional] Property GitProxy configuration file > sink
+ 15. [Optional] Property GitProxy configuration file > sink
@@ -304,18 +385,18 @@ description: JSON schema reference documentation for GitProxy
| ------------------------------- | ----------- |
| [database](#sink_items) | - |
-### 14.1. GitProxy configuration file > sink > database
+### 15.1. GitProxy configuration file > sink > database
-| | |
-| ------------------------- | ------------------------------------------------------------------------- |
-| **Type** | `object` |
-| **Required** | No |
-| **Additional properties** | [[Any type: allowed]](# "Additional Properties of any type are allowed.") |
-| **Defined in** | #/definitions/database |
+| | |
+| ------------------------- | ---------------------- |
+| **Type** | `object` |
+| **Required** | No |
+| **Additional properties** | Any type allowed |
+| **Defined in** | #/definitions/database |
- 14.1.1. [Required] Property GitProxy configuration file > sink > sink items > type
+ 15.1.1. [Required] Property GitProxy configuration file > sink > sink items > type
@@ -329,7 +410,7 @@ description: JSON schema reference documentation for GitProxy
- 14.1.2. [Required] Property GitProxy configuration file > sink > sink items > enabled
+ 15.1.2. [Required] Property GitProxy configuration file > sink > sink items > enabled
@@ -343,7 +424,7 @@ description: JSON schema reference documentation for GitProxy
- 14.1.3. [Optional] Property GitProxy configuration file > sink > sink items > connectionString
+ 15.1.3. [Optional] Property GitProxy configuration file > sink > sink items > connectionString
@@ -357,30 +438,30 @@ description: JSON schema reference documentation for GitProxy
- 14.1.4. [Optional] Property GitProxy configuration file > sink > sink items > options
+ 15.1.4. [Optional] Property GitProxy configuration file > sink > sink items > options
-| | |
-| ------------------------- | ------------------------------------------------------------------------- |
-| **Type** | `object` |
-| **Required** | No |
-| **Additional properties** | [[Any type: allowed]](# "Additional Properties of any type are allowed.") |
+| | |
+| ------------------------- | ---------------- |
+| **Type** | `object` |
+| **Required** | No |
+| **Additional properties** | Any type allowed |
- 14.1.5. [Optional] Property GitProxy configuration file > sink > sink items > params
+ 15.1.5. [Optional] Property GitProxy configuration file > sink > sink items > params
-| | |
-| ------------------------- | ------------------------------------------------------------------------- |
-| **Type** | `object` |
-| **Required** | No |
-| **Additional properties** | [[Any type: allowed]](# "Additional Properties of any type are allowed.") |
+| | |
+| ------------------------- | ---------------- |
+| **Type** | `object` |
+| **Required** | No |
+| **Additional properties** | Any type allowed |
@@ -390,7 +471,7 @@ description: JSON schema reference documentation for GitProxy
- 15. [Optional] Property GitProxy configuration file > authentication
+ 16. [Optional] Property GitProxy configuration file > authentication
@@ -405,18 +486,18 @@ description: JSON schema reference documentation for GitProxy
| --------------------------------------- | ----------- |
| [authentication](#authentication_items) | - |
-### 15.1. GitProxy configuration file > authentication > authentication
+### 16.1. GitProxy configuration file > authentication > authentication
-| | |
-| ------------------------- | ------------------------------------------------------------------------- |
-| **Type** | `object` |
-| **Required** | No |
-| **Additional properties** | [[Any type: allowed]](# "Additional Properties of any type are allowed.") |
-| **Defined in** | #/definitions/authentication |
+| | |
+| ------------------------- | ---------------------------- |
+| **Type** | `object` |
+| **Required** | No |
+| **Additional properties** | Any type allowed |
+| **Defined in** | #/definitions/authentication |
- 15.1.1. [Required] Property GitProxy configuration file > authentication > authentication items > type
+ 16.1.1. [Required] Property GitProxy configuration file > authentication > authentication items > type
@@ -430,7 +511,7 @@ description: JSON schema reference documentation for GitProxy
- 15.1.2. [Required] Property GitProxy configuration file > authentication > authentication items > enabled
+ 16.1.2. [Required] Property GitProxy configuration file > authentication > authentication items > enabled
@@ -444,15 +525,15 @@ description: JSON schema reference documentation for GitProxy
- 15.1.3. [Optional] Property GitProxy configuration file > authentication > authentication items > options
+ 16.1.3. [Optional] Property GitProxy configuration file > authentication > authentication items > options
-| | |
-| ------------------------- | ------------------------------------------------------------------------- |
-| **Type** | `object` |
-| **Required** | No |
-| **Additional properties** | [[Any type: allowed]](# "Additional Properties of any type are allowed.") |
+| | |
+| ------------------------- | ---------------- |
+| **Type** | `object` |
+| **Required** | No |
+| **Additional properties** | Any type allowed |
@@ -462,21 +543,21 @@ description: JSON schema reference documentation for GitProxy
- 16. [Optional] Property GitProxy configuration file > tempPassword
+ 17. [Optional] Property GitProxy configuration file > tempPassword
-| | |
-| ------------------------- | ------------------------------------------------------------------------- |
-| **Type** | `object` |
-| **Required** | No |
-| **Additional properties** | [[Any type: allowed]](# "Additional Properties of any type are allowed.") |
+| | |
+| ------------------------- | ---------------- |
+| **Type** | `object` |
+| **Required** | No |
+| **Additional properties** | Any type allowed |
**Description:** Toggle the generation of temporary password for git-proxy admin user
- 16.1. [Optional] Property GitProxy configuration file > tempPassword > sendEmail
+ 17.1. [Optional] Property GitProxy configuration file > tempPassword > sendEmail
@@ -490,15 +571,15 @@ description: JSON schema reference documentation for GitProxy
- 16.2. [Optional] Property GitProxy configuration file > tempPassword > emailConfig
+ 17.2. [Optional] Property GitProxy configuration file > tempPassword > emailConfig
-| | |
-| ------------------------- | ------------------------------------------------------------------------- |
-| **Type** | `object` |
-| **Required** | No |
-| **Additional properties** | [[Any type: allowed]](# "Additional Properties of any type are allowed.") |
+| | |
+| ------------------------- | ---------------- |
+| **Type** | `object` |
+| **Required** | No |
+| **Additional properties** | Any type allowed |
**Description:** Generic object to configure nodemailer. For full type information, please see https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/nodemailer
@@ -508,5 +589,164 @@ description: JSON schema reference documentation for GitProxy
-----------------------------------------------------------------------------------------------------------------------------
-Generated using [json-schema-for-humans](https://github.com/coveooss/json-schema-for-humans) on 2024-10-22 at 16:45:32 +0100
+
+
+ 18. [Optional] Property GitProxy configuration file > tls
+
+
+
+| | |
+| ------------------------- | ---------------- |
+| **Type** | `object` |
+| **Required** | No |
+| **Additional properties** | Any type allowed |
+
+**Description:** TLS configuration for secure connections
+
+
+
+ 18.1. [Required] Property GitProxy configuration file > tls > enabled
+
+
+
+| | |
+| ------------ | --------- |
+| **Type** | `boolean` |
+| **Required** | Yes |
+
+
+
+
+
+
+ 18.2. [Required] Property GitProxy configuration file > tls > key
+
+
+
+| | |
+| ------------ | -------- |
+| **Type** | `string` |
+| **Required** | Yes |
+
+
+
+
+
+
+ 18.3. [Required] Property GitProxy configuration file > tls > cert
+
+
+
+| | |
+| ------------ | -------- |
+| **Type** | `string` |
+| **Required** | Yes |
+
+
+
+
+
+
+
+
+
+ 19. [Optional] Property GitProxy configuration file > configurationSources
+
+
+
+| | |
+| ------------------------- | ------------------------------------------------------- |
+| **Type** | `object` |
+| **Required** | No |
+| **Additional properties** | [[Not allowed]](# "Additional Properties not allowed.") |
+
+**Description:** Configuration for dynamic loading from external sources
+
+
+
+ 19.1. [Optional] Property configurationSources > enabled
+
+
+
+| | |
+| ------------ | --------- |
+| **Type** | `boolean` |
+| **Required** | No |
+
+**Description:** Enable/disable dynamic configuration loading
+
+
+
+
+
+
+ 19.2. [Optional] Property configurationSources > reloadIntervalSeconds
+
+
+
+| | |
+| ------------ | -------- |
+| **Type** | `number` |
+| **Required** | No |
+
+**Description:** How often to check for configuration updates (in seconds)
+
+
+
+
+
+
+ 19.3. [Optional] Property configurationSources > merge
+
+
+
+| | |
+| ------------ | --------- |
+| **Type** | `boolean` |
+| **Required** | No |
+
+**Description:** When true, merges configurations from all enabled sources. When false, uses the last successful configuration load
+
+
+
+
+
+
+ 19.4. [Optional] Property configurationSources > sources
+
+
+
+| | |
+| ------------ | ------- |
+| **Type** | `array` |
+| **Required** | No |
+
+**Description:** Array of configuration sources to load from
+
+Each item in the array must be an object with the following properties:
+
+- `type`: (Required) Type of configuration source (`"file"`, `"http"`, or `"git"`)
+- `enabled`: (Required) Whether this source is enabled
+- `path`: (Required for `file` type) Path to the configuration file
+- `url`: (Required for `http` type) URL of the configuration endpoint
+- `repository`: (Required for `git` type) Git repository URL
+- `branch`: (Optional for `git` type) Branch to use
+- `path`: (Required for `git` type) Path to configuration file in repository
+- `headers`: (Optional for `http` type) HTTP headers to include
+- `auth`: (Optional) Authentication configuration
+ - For `http` type:
+ - `type`: `"bearer"`
+ - `token`: Bearer token value
+ - For `git` type:
+ - `type`: `"ssh"`
+ - `privateKeyPath`: Path to SSH private key
+
+
+
+
+
+
+
+---
+
+Generated using [json-schema-for-humans](https://github.com/coveooss/json-schema-for-humans) on 2025-05-01 at 18:17:32 +0100
diff --git a/website/docs/quickstart/approve.mdx b/website/docs/quickstart/approve.mdx
index 8f01e96a4..ebcd59ced 100644
--- a/website/docs/quickstart/approve.mdx
+++ b/website/docs/quickstart/approve.mdx
@@ -21,7 +21,7 @@ All pushes that flow through GitProxy require an approval (authorisation). Until
Following on from [Push via GitProxy](/docs/quickstart/intercept#push-via-git-proxy), a unique & shareable link is generated:
```
-http://localhost:8080/admin/push/0000000000000000000000000000000000000000__79b4d8953cbc324bcc1eb53d6412ff89666c241f
+http://localhost:8080/dashboard/push/0000000000000000000000000000000000000000__79b4d8953cbc324bcc1eb53d6412ff89666c241f
```
The `ID` for your push corresponds to the last part of the URL:
@@ -174,7 +174,7 @@ Following on from [Push via GitProxy](/docs/quickstart/intercept#push-via-git-pr
remote: GitProxy has received your push â
remote:
remote: đ Shareable Link
-remote: http://localhost:8080/admin/push/000000__b12557
+remote: http://localhost:8080/dashboard/push/000000__b12557
```
Insert the URL directly into your web browser.
diff --git a/website/docs/quickstart/intercept.mdx b/website/docs/quickstart/intercept.mdx
index d3b5534bc..1ac8e6016 100644
--- a/website/docs/quickstart/intercept.mdx
+++ b/website/docs/quickstart/intercept.mdx
@@ -93,7 +93,7 @@ remote:
remote: GitProxy has received your push â
remote:
remote: đ Shareable Link
-remote: http://localhost:8080/admin/push/000000__b12557
+remote: http://localhost:8080/dashboard/push/000000__b12557
remote:
```
diff --git a/website/package.json b/website/package.json
index 446392c87..9456a83c4 100644
--- a/website/package.json
+++ b/website/package.json
@@ -13,13 +13,13 @@
"@docusaurus/core": "^3.7.0",
"@docusaurus/preset-classic": "^3.7.0",
"@docusaurus/plugin-google-gtag": "^3.7.0",
- "axios": "^1.8.4",
+ "axios": "^1.9.0",
"classnames": "^2.5.1",
"clsx": "^2.1.1",
- "eslint": "^9.23.0",
- "eslint-plugin-react": "^7.37.4",
- "react": "^19.0.0",
- "react-dom": "^19.0.0",
+ "eslint": "^9.27.0",
+ "eslint-plugin-react": "^7.37.5",
+ "react": "^19.1.0",
+ "react-dom": "^19.1.0",
"react-player": "^2.16.0",
"react-slick": "^0.30.3",
"react-social-media-embed": "^2.5.18",
diff --git a/website/yarn.lock b/website/yarn.lock
index 7cae312cd..ce259a41f 100644
--- a/website/yarn.lock
+++ b/website/yarn.lock
@@ -2832,24 +2832,24 @@
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0"
integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==
-"@eslint/config-array@^0.19.2":
- version "0.19.2"
- resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.2.tgz#3060b809e111abfc97adb0bb1172778b90cb46aa"
- integrity sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==
+"@eslint/config-array@^0.20.0":
+ version "0.20.0"
+ resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.20.0.tgz#7a1232e82376712d3340012a2f561a2764d1988f"
+ integrity sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==
dependencies:
"@eslint/object-schema" "^2.1.6"
debug "^4.3.1"
minimatch "^3.1.2"
-"@eslint/config-helpers@^0.2.0":
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.2.0.tgz#12dc8d65c31c4b6c3ebf0758db6601eb7692ce59"
- integrity sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ==
+"@eslint/config-helpers@^0.2.1":
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.2.1.tgz#26042c028d1beee5ce2235a7929b91c52651646d"
+ integrity sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==
-"@eslint/core@^0.12.0":
- version "0.12.0"
- resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.12.0.tgz#5f960c3d57728be9f6c65bd84aa6aa613078798e"
- integrity sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==
+"@eslint/core@^0.14.0":
+ version "0.14.0"
+ resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.14.0.tgz#326289380968eaf7e96f364e1e4cf8f3adf2d003"
+ integrity sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==
dependencies:
"@types/json-schema" "^7.0.15"
@@ -2868,22 +2868,22 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
-"@eslint/js@9.23.0":
- version "9.23.0"
- resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.23.0.tgz#c09ded4f3dc63b40b933bcaeb853fceddb64da30"
- integrity sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==
+"@eslint/js@9.27.0":
+ version "9.27.0"
+ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.27.0.tgz#181a23460877c484f6dd03890f4e3fa2fdeb8ff0"
+ integrity sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==
"@eslint/object-schema@^2.1.6":
version "2.1.6"
resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f"
integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==
-"@eslint/plugin-kit@^0.2.7":
- version "0.2.7"
- resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz#9901d52c136fb8f375906a73dcc382646c3b6a27"
- integrity sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==
+"@eslint/plugin-kit@^0.3.1":
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz#b71b037b2d4d68396df04a8c35a49481e5593067"
+ integrity sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==
dependencies:
- "@eslint/core" "^0.12.0"
+ "@eslint/core" "^0.14.0"
levn "^0.4.1"
"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0":
@@ -4132,10 +4132,10 @@ available-typed-arrays@^1.0.7:
dependencies:
possible-typed-array-names "^1.0.0"
-axios@^1.8.4:
- version "1.8.4"
- resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.4.tgz#78990bb4bc63d2cae072952d374835950a82f447"
- integrity sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==
+axios@^1.9.0:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-1.9.0.tgz#25534e3b72b54540077d33046f77e3b8d7081901"
+ integrity sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==
dependencies:
follow-redirects "^1.15.6"
form-data "^4.0.0"
@@ -4340,6 +4340,14 @@ call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1:
es-errors "^1.3.0"
function-bind "^1.1.2"
+call-bind-apply-helpers@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
+ integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
+ dependencies:
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+
call-bind@^1.0.5, call-bind@^1.0.7, call-bind@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c"
@@ -4358,6 +4366,14 @@ call-bound@^1.0.2, call-bound@^1.0.3:
call-bind-apply-helpers "^1.0.1"
get-intrinsic "^1.2.6"
+call-bound@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a"
+ integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==
+ dependencies:
+ call-bind-apply-helpers "^1.0.2"
+ get-intrinsic "^1.3.0"
+
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
@@ -5469,7 +5485,7 @@ es-module-lexer@^1.2.1:
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78"
integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==
-es-object-atoms@^1.0.0:
+es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1"
integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
@@ -5537,10 +5553,10 @@ escape-string-regexp@^5.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
-eslint-plugin-react@^7.37.4:
- version "7.37.4"
- resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz#1b6c80b6175b6ae4b26055ae4d55d04c414c7181"
- integrity sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ==
+eslint-plugin-react@^7.37.5:
+ version "7.37.5"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz#2975511472bdda1b272b34d779335c9b0e877065"
+ integrity sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==
dependencies:
array-includes "^3.1.8"
array.prototype.findlast "^1.2.5"
@@ -5552,7 +5568,7 @@ eslint-plugin-react@^7.37.4:
hasown "^2.0.2"
jsx-ast-utils "^2.4.1 || ^3.0.0"
minimatch "^3.1.2"
- object.entries "^1.1.8"
+ object.entries "^1.1.9"
object.fromentries "^2.0.8"
object.values "^1.2.1"
prop-types "^15.8.1"
@@ -5587,19 +5603,19 @@ eslint-visitor-keys@^4.2.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45"
integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==
-eslint@^9.23.0:
- version "9.23.0"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.23.0.tgz#b88f3ab6dc83bcb927fdb54407c69ffe5f2441a6"
- integrity sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==
+eslint@^9.27.0:
+ version "9.27.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.27.0.tgz#a587d3cd5b844b68df7898944323a702afe38979"
+ integrity sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==
dependencies:
"@eslint-community/eslint-utils" "^4.2.0"
"@eslint-community/regexpp" "^4.12.1"
- "@eslint/config-array" "^0.19.2"
- "@eslint/config-helpers" "^0.2.0"
- "@eslint/core" "^0.12.0"
+ "@eslint/config-array" "^0.20.0"
+ "@eslint/config-helpers" "^0.2.1"
+ "@eslint/core" "^0.14.0"
"@eslint/eslintrc" "^3.3.1"
- "@eslint/js" "9.23.0"
- "@eslint/plugin-kit" "^0.2.7"
+ "@eslint/js" "9.27.0"
+ "@eslint/plugin-kit" "^0.3.1"
"@humanfs/node" "^0.16.6"
"@humanwhocodes/module-importer" "^1.0.1"
"@humanwhocodes/retry" "^0.4.2"
@@ -6118,6 +6134,22 @@ get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@
hasown "^2.0.2"
math-intrinsics "^1.1.0"
+get-intrinsic@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
+ integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
+ dependencies:
+ call-bind-apply-helpers "^1.0.2"
+ es-define-property "^1.0.1"
+ es-errors "^1.3.0"
+ es-object-atoms "^1.1.1"
+ function-bind "^1.1.2"
+ get-proto "^1.0.1"
+ gopd "^1.2.0"
+ has-symbols "^1.1.0"
+ hasown "^2.0.2"
+ math-intrinsics "^1.1.0"
+
get-own-enumerable-property-symbols@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664"
@@ -8375,14 +8407,15 @@ object.assign@^4.1.4, object.assign@^4.1.7:
has-symbols "^1.1.0"
object-keys "^1.1.1"
-object.entries@^1.1.8:
- version "1.1.8"
- resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41"
- integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==
+object.entries@^1.1.9:
+ version "1.1.9"
+ resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.9.tgz#e4770a6a1444afb61bd39f984018b5bede25f8b3"
+ integrity sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==
dependencies:
- call-bind "^1.0.7"
+ call-bind "^1.0.8"
+ call-bound "^1.0.4"
define-properties "^1.2.1"
- es-object-atoms "^1.0.0"
+ es-object-atoms "^1.1.1"
object.fromentries@^2.0.8:
version "2.0.8"
@@ -9454,12 +9487,12 @@ react-dev-utils@^12.0.1:
strip-ansi "^6.0.1"
text-table "^0.2.0"
-react-dom@^19.0.0:
- version "19.0.0"
- resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.0.tgz#43446f1f01c65a4cd7f7588083e686a6726cfb57"
- integrity sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==
+react-dom@^19.1.0:
+ version "19.1.0"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.1.0.tgz#133558deca37fa1d682708df8904b25186793623"
+ integrity sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==
dependencies:
- scheduler "^0.25.0"
+ scheduler "^0.26.0"
react-error-overlay@^6.0.11:
version "6.0.11"
@@ -9601,10 +9634,10 @@ react-youtube@^10.1.0:
prop-types "15.8.1"
youtube-player "5.5.2"
-react@^19.0.0:
- version "19.0.0"
- resolved "https://registry.yarnpkg.com/react/-/react-19.0.0.tgz#6e1969251b9f108870aa4bff37a0ce9ddfaaabdd"
- integrity sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==
+react@^19.1.0:
+ version "19.1.0"
+ resolved "https://registry.yarnpkg.com/react/-/react-19.1.0.tgz#926864b6c48da7627f004795d6cce50e90793b75"
+ integrity sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==
readable-stream@^2.0.1:
version "2.3.8"
@@ -10021,10 +10054,10 @@ sax@^1.2.4:
resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f"
integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==
-scheduler@^0.25.0:
- version "0.25.0"
- resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.25.0.tgz#336cd9768e8cceebf52d3c80e3dcf5de23e7e015"
- integrity sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==
+scheduler@^0.26.0:
+ version "0.26.0"
+ resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.26.0.tgz#4ce8a8c2a2095f13ea11bf9a445be50c555d6337"
+ integrity sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==
schema-utils@2.7.0:
version "2.7.0"