Skip to content

Commit 9302c5d

Browse files
committed
fix: cli CSRF protection and session cookie management
1 parent 367ef88 commit 9302c5d

File tree

1 file changed

+29
-72
lines changed

1 file changed

+29
-72
lines changed

packages/git-proxy-cli/index.ts

Lines changed: 29 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,13 @@ import util from 'util';
88
import { CommitData, PushData } from '@finos/git-proxy/types';
99
import { PushQuery } from '@finos/git-proxy/db';
1010
import { serverConfig } from '@finos/git-proxy/config/env';
11+
import {
12+
ensureAuthCookie,
13+
getCliCookies,
14+
getCliPostRequestConfig,
15+
GIT_PROXY_COOKIE_FILE,
16+
} from './utils';
1117

12-
const GIT_PROXY_COOKIE_FILE = 'git-proxy-cookie';
1318
// GitProxy UI HOST and PORT (configurable via environment variable)
1419
const { GIT_PROXY_UI_HOST: uiHost = 'http://localhost' } = process.env;
1520
const { GIT_PROXY_UI_PORT: uiPort } = serverConfig;
@@ -24,26 +29,23 @@ axios.defaults.timeout = 30000;
2429
*/
2530
async function login(username: string, password: string) {
2631
try {
32+
const config = await getCliPostRequestConfig(baseUrl);
2733
let response = await axios.post(
2834
`${baseUrl}/api/auth/login`,
2935
{
3036
username,
3137
password,
3238
},
33-
{
34-
headers: { 'Content-Type': 'application/json' },
35-
withCredentials: true,
36-
},
39+
config,
3740
);
3841
const cookies = response.headers['set-cookie'];
42+
fs.writeFileSync(GIT_PROXY_COOKIE_FILE, cookies ? cookies.join('; ') : '');
3943

4044
response = await axios.get(`${baseUrl}/api/auth/profile`, {
4145
headers: { Cookie: cookies },
4246
withCredentials: true,
4347
});
4448

45-
fs.writeFileSync(GIT_PROXY_COOKIE_FILE, JSON.stringify(cookies), 'utf8');
46-
4749
const user = `"${response.data.username}" <${response.data.email}>`;
4850
const isAdmin = response.data.admin ? ' (admin)' : '';
4951
console.log(`Login ${user}${isAdmin}: OK`);
@@ -80,15 +82,9 @@ async function login(username: string, password: string) {
8082
* given attribute and status.
8183
*/
8284
async function getGitPushes(filters: Partial<PushQuery>) {
83-
if (!fs.existsSync(GIT_PROXY_COOKIE_FILE)) {
84-
console.error('Error: List: Authentication required');
85-
process.exitCode = 1;
86-
return;
87-
}
88-
85+
if (!ensureAuthCookie()) return;
8986
try {
90-
const cookies = JSON.parse(fs.readFileSync(GIT_PROXY_COOKIE_FILE, 'utf8'));
91-
87+
const cookies = getCliCookies();
9288
const response = await axios.get(`${baseUrl}/api/v1/push/`, {
9389
headers: { Cookie: cookies },
9490
params: filters,
@@ -164,15 +160,9 @@ async function getGitPushes(filters: Partial<PushQuery>) {
164160
* @param {string} id The ID of the git push to authorise
165161
*/
166162
async function authoriseGitPush(id: string) {
167-
if (!fs.existsSync(GIT_PROXY_COOKIE_FILE)) {
168-
console.error('Error: Authorise: Authentication required');
169-
process.exitCode = 1;
170-
return;
171-
}
172-
163+
if (!ensureAuthCookie()) return;
173164
try {
174-
const cookies = JSON.parse(fs.readFileSync(GIT_PROXY_COOKIE_FILE, 'utf8'));
175-
165+
const cookies = getCliCookies();
176166
await axios.get(`${baseUrl}/api/v1/push/${id}`, {
177167
headers: { Cookie: cookies },
178168
});
@@ -220,15 +210,9 @@ async function authoriseGitPush(id: string) {
220210
* @param {string} id The ID of the git push to reject
221211
*/
222212
async function rejectGitPush(id: string) {
223-
if (!fs.existsSync(GIT_PROXY_COOKIE_FILE)) {
224-
console.error('Error: Reject: Authentication required');
225-
process.exitCode = 1;
226-
return;
227-
}
228-
213+
if (!ensureAuthCookie()) return;
229214
try {
230-
const cookies = JSON.parse(fs.readFileSync(GIT_PROXY_COOKIE_FILE, 'utf8'));
231-
215+
const cookies = getCliCookies();
232216
await axios.get(`${baseUrl}/api/v1/push/${id}`, {
233217
headers: { Cookie: cookies },
234218
});
@@ -267,15 +251,9 @@ async function rejectGitPush(id: string) {
267251
* @param {string} id The ID of the git push to cancel
268252
*/
269253
async function cancelGitPush(id: string) {
270-
if (!fs.existsSync(GIT_PROXY_COOKIE_FILE)) {
271-
console.error('Error: Cancel: Authentication required');
272-
process.exitCode = 1;
273-
return;
274-
}
275-
254+
if (!ensureAuthCookie()) return;
276255
try {
277-
const cookies = JSON.parse(fs.readFileSync(GIT_PROXY_COOKIE_FILE, 'utf8'));
278-
256+
const cookies = getCliCookies();
279257
await axios.get(`${baseUrl}/api/v1/push/${id}`, {
280258
headers: { Cookie: cookies },
281259
});
@@ -315,23 +293,18 @@ async function cancelGitPush(id: string) {
315293
async function logout() {
316294
if (fs.existsSync(GIT_PROXY_COOKIE_FILE)) {
317295
try {
318-
const cookies = JSON.parse(fs.readFileSync(GIT_PROXY_COOKIE_FILE, 'utf8'));
319-
fs.writeFileSync(GIT_PROXY_COOKIE_FILE, '*** logged out ***', 'utf8');
320-
fs.unlinkSync(GIT_PROXY_COOKIE_FILE);
296+
const config = await getCliPostRequestConfig(baseUrl);
297+
await axios.post(`${baseUrl}/api/auth/logout`, {}, config);
321298

322-
await axios.post(
323-
`${baseUrl}/api/auth/logout`,
324-
{},
325-
{
326-
headers: { Cookie: cookies },
327-
},
328-
);
299+
console.log('Logged out successfully.');
300+
fs.unlinkSync(GIT_PROXY_COOKIE_FILE);
329301
} catch (error: any) {
330-
console.log(`Warning: Logout: '${error.message}'`);
302+
console.error(`Error: Logout: '${error.message}'`);
303+
process.exitCode = 2;
331304
}
305+
} else {
306+
console.error('Not logged in.');
332307
}
333-
334-
console.log('Logout: OK');
335308
}
336309

337310
/**
@@ -340,29 +313,13 @@ async function logout() {
340313
* @param {string} keyPath Path to the public key file
341314
*/
342315
async function addSSHKey(username: string, keyPath: string) {
343-
console.log('Add SSH key', { username, keyPath });
344-
if (!fs.existsSync(GIT_PROXY_COOKIE_FILE)) {
345-
console.error('Error: SSH key: Authentication required');
346-
process.exitCode = 1;
347-
return;
348-
}
349-
316+
if (!ensureAuthCookie()) return;
350317
try {
351-
const cookies = JSON.parse(fs.readFileSync(GIT_PROXY_COOKIE_FILE, 'utf8'));
352318
const publicKey = fs.readFileSync(keyPath, 'utf8').trim();
319+
const config = await getCliPostRequestConfig(baseUrl);
353320

354321
console.log('Adding SSH key', { username, publicKey });
355-
await axios.post(
356-
`${baseUrl}/api/v1/user/${username}/ssh-keys`,
357-
{ publicKey },
358-
{
359-
headers: {
360-
Cookie: cookies,
361-
'Content-Type': 'application/json',
362-
},
363-
withCredentials: true,
364-
},
365-
);
322+
await axios.post(`${baseUrl}/api/v1/user/${username}/ssh-keys`, { publicKey }, config);
366323

367324
console.log(`SSH key added successfully for user ${username}`);
368325
} catch (error: any) {
@@ -372,7 +329,7 @@ async function addSSHKey(username: string, keyPath: string) {
372329
if (error.response) {
373330
switch (error.response.status) {
374331
case 401:
375-
errorMessage = `Error: SSH key: Authentication required: '${error.response.data.message}'`;
332+
errorMessage = `Error: SSH key: Authentication required: '${error.message}'`;
376333
process.exitCode = 3;
377334
break;
378335
case 404:

0 commit comments

Comments
 (0)