Skip to content
88 changes: 88 additions & 0 deletions packages/git-proxy-cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,59 @@ async function reloadConfig() {
}
}

/**
* Create a new user
* @param {string} username The username for the new user
* @param {string} password The password for the new user
* @param {string} email The email for the new user
* @param {string} gitAccount The git account for the new user
* @param {boolean} [admin=false] Whether the user should be an admin (optional)
*/
async function createUser(username, password, email, gitAccount, admin = false) {
if (!fs.existsSync(GIT_PROXY_COOKIE_FILE)) {
console.error('Error: Create User: Authentication required');
process.exitCode = 1;
return;
}

try {
const cookies = JSON.parse(fs.readFileSync(GIT_PROXY_COOKIE_FILE, 'utf8'));

await axios.post(
`${baseUrl}/api/auth/create-user`,
{
username,
password,
email,
gitAccount,
admin,
},
{
headers: { Cookie: cookies },
},
);

console.log(`User '${username}' created successfully`);
} catch (error) {
let errorMessage = `Error: Create User: '${error.message}'`;
process.exitCode = 2;

if (error.response) {
switch (error.response.status) {
case 401:
errorMessage = 'Error: Create User: Authentication required';
process.exitCode = 3;
break;
case 400:
errorMessage = `Error: Create User: ${error.response.data.message}`;
process.exitCode = 4;
break;
}
}
console.error(errorMessage);
}
}

// Parsing command line arguments
yargs(hideBin(process.argv)) // eslint-disable-line @typescript-eslint/no-unused-expressions
.command({
Expand Down Expand Up @@ -468,6 +521,41 @@ yargs(hideBin(process.argv)) // eslint-disable-line @typescript-eslint/no-unused
description: 'Reload GitProxy configuration without restarting',
action: reloadConfig,
})
.command({
command: 'create-user',
describe: 'Create a new user',
builder: {
username: {
describe: 'Username for the new user',
demandOption: true,
type: 'string',
},
password: {
describe: 'Password for the new user',
demandOption: true,
type: 'string',
},
email: {
describe: 'Email for the new user',
demandOption: true,
type: 'string',
},
gitAccount: {
describe: 'Git account for the new user',
demandOption: true,
type: 'string',
},
admin: {
describe: 'Whether the user should be an admin (optional)',
demandOption: false,
type: 'boolean',
default: false,
},
},
handler(argv) {
createUser(argv.username, argv.password, argv.email, argv.gitAccount, argv.admin);
},
})
.demandCommand(1, 'You need at least one command before moving on')
.strict()
.help().argv;
130 changes: 130 additions & 0 deletions packages/git-proxy-cli/test/testCli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,136 @@ describe('test git-proxy-cli', function () {
});
});

// *** create user ***

describe('test git-proxy-cli :: create-user', function () {
before(async function () {
await helper.addUserToDb(TEST_USER, TEST_PASSWORD, TEST_EMAIL, TEST_GIT_ACCOUNT);
});

after(async function () {
await helper.removeUserFromDb(TEST_USER);
});

it('attempt to create user should fail when server is down', async function () {
try {
// start server -> login -> stop server
await helper.startServer(service);
await helper.runCli(`npx -- @finos/git-proxy-cli login --username admin --password admin`);
} finally {
await helper.closeServer(service.httpServer);
}

const cli = `npx -- @finos/git-proxy-cli create-user --username newuser --password newpass --email new@email.com --gitAccount newgit`;
const expectedExitCode = 2;
const expectedMessages = null;
const expectedErrorMessages = ['Error: Create User:'];
await helper.runCli(cli, expectedExitCode, expectedMessages, expectedErrorMessages);
});

it('attempt to create user should fail when not authenticated', async function () {
await helper.removeCookiesFile();

const cli = `npx -- @finos/git-proxy-cli create-user --username newuser --password newpass --email new@email.com --gitAccount newgit`;
const expectedExitCode = 1;
const expectedMessages = null;
const expectedErrorMessages = ['Error: Create User: Authentication required'];
await helper.runCli(cli, expectedExitCode, expectedMessages, expectedErrorMessages);
});

it('attempt to create user should fail when not admin', async function () {
try {
await helper.startServer(service);
await helper.runCli(
`npx -- @finos/git-proxy-cli login --username testuser --password testpassword`,
);

const cli = `npx -- @finos/git-proxy-cli create-user --username newuser --password newpass --email new@email.com --gitAccount newgit`;
const expectedExitCode = 3;
const expectedMessages = null;
const expectedErrorMessages = ['Error: Create User: Authentication required'];
await helper.runCli(cli, expectedExitCode, expectedMessages, expectedErrorMessages);
} finally {
await helper.closeServer(service.httpServer);
}
});

it('attempt to create user should fail with missing required fields', async function () {
try {
await helper.startServer(service);
await helper.runCli(`npx -- @finos/git-proxy-cli login --username admin --password admin`);

const cli = `npx -- @finos/git-proxy-cli create-user --username newuser --password "" --email new@email.com --gitAccount newgit`;
const expectedExitCode = 4;
const expectedMessages = null;
const expectedErrorMessages = ['Error: Create User: Missing required fields'];
await helper.runCli(cli, expectedExitCode, expectedMessages, expectedErrorMessages);
} finally {
await helper.closeServer(service.httpServer);
}
});

it('should successfully create a new user', async function () {
const uniqueUsername = `newuser_${Date.now()}`;
try {
await helper.startServer(service);
await helper.runCli(`npx -- @finos/git-proxy-cli login --username admin --password admin`);

const cli = `npx -- @finos/git-proxy-cli create-user --username ${uniqueUsername} --password newpass --email new@email.com --gitAccount newgit`;
const expectedExitCode = 0;
const expectedMessages = [`User '${uniqueUsername}' created successfully`];
const expectedErrorMessages = null;
await helper.runCli(cli, expectedExitCode, expectedMessages, expectedErrorMessages);

// Verify we can login with the new user
await helper.runCli(
`npx -- @finos/git-proxy-cli login --username ${uniqueUsername} --password newpass`,
0,
[`Login "${uniqueUsername}" <new@email.com>: OK`],
null,
);
} finally {
await helper.closeServer(service.httpServer);
// Clean up the created user
try {
await helper.removeUserFromDb(uniqueUsername);
} catch (error) {
// Ignore cleanup errors
}
}
});

it('should successfully create a new admin user', async function () {
const uniqueUsername = `newadmin_${Date.now()}`;
try {
await helper.startServer(service);
await helper.runCli(`npx -- @finos/git-proxy-cli login --username admin --password admin`);

const cli = `npx -- @finos/git-proxy-cli create-user --username ${uniqueUsername} --password newpass --email ${uniqueUsername}@email.com --gitAccount newgit --admin`;
const expectedExitCode = 0;
const expectedMessages = [`User '${uniqueUsername}' created successfully`];
const expectedErrorMessages = null;
await helper.runCli(cli, expectedExitCode, expectedMessages, expectedErrorMessages);

// Verify we can login with the new admin user
await helper.runCli(
`npx -- @finos/git-proxy-cli login --username ${uniqueUsername} --password newpass`,
0,
[`Login "${uniqueUsername}" <${uniqueUsername}@email.com> (admin): OK`],
null,
);
} finally {
await helper.closeServer(service.httpServer);
// Clean up the created user
try {
await helper.removeUserFromDb(uniqueUsername);
} catch (error) {
console.error('Error cleaning up user', error);
}
}
});
});

// *** tests require push in db ***

describe('test git-proxy-cli :: git push administration', function () {
Expand Down
31 changes: 31 additions & 0 deletions src/service/routes/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,37 @@ router.get('/me', async (req, res) => {
}
});

router.post('/create-user', async (req, res) => {
if (!req.user || !req.user.admin) {
return res.status(401).send({
message: 'You are not authorized to perform this action...',
});
}

try {
const { username, password, email, gitAccount, admin: isAdmin = false } = req.body;

if (!username || !password || !email || !gitAccount) {
return res.status(400).send({
message: 'Missing required fields: username, password, email, and gitAccount are required',
});
}

await db.createUser(username, password, email, gitAccount, isAdmin);
res.status(201).send({
message: 'User created successfully',
username,
});
} catch (error) {
console.error('Error creating user:', error);
res.status(400).send({
message: error.message || 'Failed to create user',
});
}
});

module.exports = router;

module.exports = {
router,
loginSuccessHandler,
Expand Down
Loading
Loading