diff --git a/packages/git-proxy-cli/index.js b/packages/git-proxy-cli/index.js index 142a58a33..6ee1d0539 100755 --- a/packages/git-proxy-cli/index.js +++ b/packages/git-proxy-cli/index.js @@ -330,6 +330,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')); + + const response = 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({ @@ -465,6 +518,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; diff --git a/packages/git-proxy-cli/test/testCli.test.js b/packages/git-proxy-cli/test/testCli.test.js index fbfce0fe3..897dde9c2 100644 --- a/packages/git-proxy-cli/test/testCli.test.js +++ b/packages/git-proxy-cli/test/testCli.test.js @@ -483,6 +483,114 @@ describe('test git-proxy-cli', function () { }); }); + // *** create user *** + + describe('test git-proxy-cli :: create-user', function () { + 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 --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 () { + 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 newpass --email new@email.com --gitAccount newgit`; + const expectedExitCode = 0; + const expectedMessages = ["User 'newuser' 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 newuser --password newpass`, + 0, + [`Login "newuser" : OK`], + null, + ); + } finally { + await helper.closeServer(service.httpServer); + } + }); + + it('should successfully create a new admin user', 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 newadmin --password newpass --email newadmin@email.com --gitAccount newgit --admin`; + const expectedExitCode = 0; + const expectedMessages = ["User 'newadmin' 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 newadmin --password newpass`, + 0, + [`Login "newadmin" (admin): OK`], + null, + ); + } finally { + await helper.closeServer(service.httpServer); + } + }); + }); + // *** tests require push in db *** describe('test git-proxy-cli :: git push administration', function () { diff --git a/src/service/routes/auth.js b/src/service/routes/auth.js index 2d9bceb70..e3398e59e 100644 --- a/src/service/routes/auth.js +++ b/src/service/routes/auth.js @@ -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 diff --git a/test/testLogin.test.js b/test/testLogin.test.js index dea0cfc75..4cafc6e35 100644 --- a/test/testLogin.test.js +++ b/test/testLogin.test.js @@ -122,6 +122,165 @@ describe('auth', async () => { }); }); + describe('test create user', async function () { + beforeEach(async function () { + await db.deleteUser('newuser'); + await db.deleteUser('nonadmin'); + }); + + it('should fail to create user when not authenticated', async function () { + const res = await chai.request(app).post('/api/auth/create-user').send({ + username: 'newuser', + password: 'newpass', + email: 'new@email.com', + gitAccount: 'newgit', + }); + + res.should.have.status(401); + res.body.should.have + .property('message') + .eql('You are not authorized to perform this action...'); + }); + + it('should fail to create user when not admin', async function () { + await db.deleteUser('nonadmin'); + await db.createUser('nonadmin', 'nonadmin', 'nonadmin@test.com', 'nonadmin', false); + + // First login as non-admin user + const loginRes = await chai.request(app).post('/api/auth/login').send({ + username: 'nonadmin', + password: 'nonadmin', + }); + + loginRes.should.have.status(200); + + let nonAdminCookie; + // Get the connect cooie + loginRes.headers['set-cookie'].forEach((x) => { + if (x.startsWith('connect')) { + nonAdminCookie = x.split(';')[0]; + } + }); + + console.log('nonAdminCookie', nonAdminCookie); + + const res = await chai + .request(app) + .post('/api/auth/create-user') + .set('Cookie', nonAdminCookie) + .send({ + username: 'newuser', + password: 'newpass', + email: 'new@email.com', + gitAccount: 'newgit', + }); + + res.should.have.status(401); + res.body.should.have + .property('message') + .eql('You are not authorized to perform this action...'); + }); + + it('should fail to create user with missing required fields', async function () { + // First login as admin + const loginRes = await chai.request(app).post('/api/auth/login').send({ + username: 'admin', + password: 'admin', + }); + + const adminCookie = loginRes.headers['set-cookie'][0].split(';')[0]; + + const res = await chai + .request(app) + .post('/api/auth/create-user') + .set('Cookie', adminCookie) + .send({ + username: 'newuser', + // missing password + email: 'new@email.com', + gitAccount: 'newgit', + }); + + res.should.have.status(400); + res.body.should.have + .property('message') + .eql('Missing required fields: username, password, email, and gitAccount are required'); + }); + + it('should successfully create a new user', async function () { + // First login as admin + const loginRes = await chai.request(app).post('/api/auth/login').send({ + username: 'admin', + password: 'admin', + }); + + const adminCookie = loginRes.headers['set-cookie'][0].split(';')[0]; + + const res = await chai + .request(app) + .post('/api/auth/create-user') + .set('Cookie', adminCookie) + .send({ + username: 'newuser', + password: 'newpass', + email: 'new@email.com', + gitAccount: 'newgit', + admin: false, + }); + + res.should.have.status(201); + res.body.should.have.property('message').eql('User created successfully'); + res.body.should.have.property('username').eql('newuser'); + + // Verify we can login with the new user + const newUserLoginRes = await chai.request(app).post('/api/auth/login').send({ + username: 'newuser', + password: 'newpass', + }); + + newUserLoginRes.should.have.status(200); + }); + + it('should fail to create user when username already exists', async function () { + // First login as admin + const loginRes = await chai.request(app).post('/api/auth/login').send({ + username: 'admin', + password: 'admin', + }); + + const adminCookie = loginRes.headers['set-cookie'][0].split(';')[0]; + + const res = await chai + .request(app) + .post('/api/auth/create-user') + .set('Cookie', adminCookie) + .send({ + username: 'newuser', + password: 'newpass', + email: 'new@email.com', + gitAccount: 'newgit', + admin: false, + }); + + res.should.have.status(201); + + // Verify we can login with the new user + const failCreateRes = await chai + .request(app) + .post('/api/auth/create-user') + .set('Cookie', adminCookie) + .send({ + username: 'newuser', + password: 'newpass', + email: 'new@email.com', + gitAccount: 'newgit', + admin: false, + }); + + failCreateRes.should.have.status(400); + }); + }); + after(async function () { await service.httpServer.close(); });