Skip to content

Commit 2137b12

Browse files
authored
Merge branch 'main' into 1163-dont-forward-unknown-repos
2 parents 437e1cc + 94d807d commit 2137b12

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+565
-296
lines changed

cypress.config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,13 @@ module.exports = defineConfig({
44
e2e: {
55
baseUrl: process.env.CYPRESS_BASE_URL || 'http://localhost:3000',
66
chromeWebSecurity: false, // Required for OIDC testing
7+
setupNodeEvents(on, config) {
8+
on("task", {
9+
log(message) {
10+
console.log(message);
11+
return null;
12+
}
13+
})
14+
}
715
},
816
});

cypress/e2e/repo.cy.js

Lines changed: 74 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,84 @@
11
describe('Repo', () => {
2-
beforeEach(() => {
2+
let repoName;
3+
let cloneURL;
4+
let csrfToken;
5+
let cookies;
6+
let repoId;
7+
8+
before(() => {
39
cy.login('admin', 'admin');
410

5-
cy.visit('/dashboard/repo');
11+
// Create a new repo
12+
cy.getCSRFToken().then((csrfToken) => {
13+
repoName = `${Date.now()}`;
14+
cloneURL = `http://localhost:8000/github.com/cypress-test/${repoName}.git`;
15+
16+
cy.request({
17+
method: 'POST',
18+
url: 'http://localhost:8080/api/v1/repo',
19+
body: {
20+
project: 'cypress-test',
21+
name: repoName,
22+
url: `https://github.com/cypress-test/${repoName}.git`
23+
},
24+
headers: {
25+
cookie: cookies?.join('; ') || '',
26+
'X-CSRF-TOKEN': csrfToken
27+
}
28+
}).then((res) => {
29+
expect(res.status).to.eq(200);
30+
repoId = res.body._id;
31+
});
32+
});
33+
});
634

7-
// prevent failures on 404 request and uncaught promises
35+
it('Opens tooltip with correct content and can copy', () => {
36+
cy.visit('/dashboard/repo');
837
cy.on('uncaught:exception', () => false);
38+
39+
const tooltipQuery = 'div[role="tooltip"]';
40+
41+
// Check the tooltip isn't open to start with
42+
cy.get(tooltipQuery)
43+
.should('not.exist');
44+
45+
// Find the repo's Code button and click it
46+
cy.get(`a[href="/dashboard/repo/${repoId}"]`)
47+
.closest('tr')
48+
.find('span')
49+
.contains('Code')
50+
.should('exist')
51+
.click();
52+
53+
// Check tooltip is open and contains the correct clone URL
54+
cy.get(tooltipQuery)
55+
.should('exist')
56+
.find('span')
57+
.contains(cloneURL)
58+
.should('exist')
59+
.parent()
60+
.find('span')
61+
.next()
62+
.get('svg.octicon-copy')
63+
.should('exist')
64+
.click()
65+
.get('svg.octicon-copy')
66+
.should('not.exist')
67+
.get('svg.octicon-check')
68+
.should('exist');
969
});
1070

11-
describe('Code button for repo row', () => {
12-
it('Opens tooltip with correct content and can copy', () => {
13-
const cloneURLRegex = /http:\/\/localhost:8000\/(?:[^\/]+\/).+\.git/;
14-
const tooltipQuery = 'div[role="tooltip"]';
15-
16-
cy
17-
// tooltip isn't open to start with
18-
.get(tooltipQuery)
19-
.should('not.exist');
20-
21-
cy
22-
// find a table row for a repo (any will do)
23-
.get('table#RepoListTable>tbody>tr')
24-
// find the nearby span containing Code we can click to open the tooltip
25-
.find('span')
26-
.contains('Code')
27-
.should('exist')
28-
.click();
29-
30-
cy
31-
// find the newly opened tooltip
32-
.get(tooltipQuery)
33-
.should('exist')
34-
.find('span')
35-
// check it contains the url we expect
36-
.contains(cloneURLRegex)
37-
.should('exist')
38-
.parent()
39-
// find the adjacent span that contains the svg
40-
.find('span')
41-
.next()
42-
// check it has the copy icon first and click it
43-
.get('svg.octicon-copy')
44-
.should('exist')
45-
.click()
46-
// check the icon has changed to the check icon
47-
.get('svg.octicon-copy')
48-
.should('not.exist')
49-
.get('svg.octicon-check')
50-
.should('exist');
51-
52-
// failed to successfully check the clipboard
71+
after(() => {
72+
// Delete the repo
73+
cy.getCSRFToken().then((csrfToken) => {
74+
cy.request({
75+
method: 'DELETE',
76+
url: `http://localhost:8080/api/v1/repo/${repoName}/delete`,
77+
headers: {
78+
cookie: cookies?.join('; ') || '',
79+
'X-CSRF-TOKEN': csrfToken
80+
}
81+
});
5382
});
5483
});
5584
});

cypress/support/commands.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,25 @@ Cypress.Commands.add('login', (username, password) => {
3939
cy.url().should('include', '/dashboard/repo');
4040
});
4141
});
42+
43+
Cypress.Commands.add('getCSRFToken', () => {
44+
return cy.request('GET', 'http://localhost:8080/api/v1/repo').then((res) => {
45+
let cookies = res.headers['set-cookie'];
46+
47+
if (typeof cookies === 'string') {
48+
cookies = [cookies];
49+
}
50+
51+
if (!cookies) {
52+
throw new Error('No cookies found in response');
53+
}
54+
55+
const csrfCookie = cookies.find(c => c.startsWith('csrf='));
56+
if (!csrfCookie) {
57+
throw new Error('No CSRF cookie found in response headers');
58+
}
59+
60+
const token = csrfCookie.split('=')[1].split(';')[0];
61+
return cy.wrap(decodeURIComponent(token));
62+
});
63+
});

package-lock.json

Lines changed: 50 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"axios": "^1.11.0",
4747
"bcryptjs": "^3.0.2",
4848
"bit-mask": "^1.0.2",
49-
"classnames": "2.5.1",
49+
"clsx": "^2.1.1",
5050
"concurrently": "^9.2.0",
5151
"connect-mongo": "^5.1.0",
5252
"cors": "^2.8.5",

src/constants/languageColors.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// src/constants/languageColors.ts
2-
31
export const languageColors: Record<string, string> = {
42
'1C Enterprise': '#814CCC',
53
'2-Dimensional Array': '#38761D',

src/routes.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ import User from './ui/views/User/User';
2525
import UserList from './ui/views/UserList/UserList';
2626
import RepoDetails from './ui/views/RepoDetails/RepoDetails';
2727
import RepoList from './ui/views/RepoList/RepoList';
28+
import SettingsView from './ui/views/Settings/Settings';
2829

2930
import { RepoIcon } from '@primer/octicons-react';
30-
import { Group, AccountCircle, Dashboard } from '@material-ui/icons';
31+
import { Group, AccountCircle, Dashboard, Settings } from '@material-ui/icons';
32+
3133
import { Route } from './types/models';
3234

3335
const dashboardRoutes: Route[] = [
@@ -97,6 +99,18 @@ const dashboardRoutes: Route[] = [
9799
layout: '/dashboard',
98100
visible: false,
99101
},
102+
{
103+
path: '/admin/settings',
104+
name: 'Settings',
105+
icon: Settings,
106+
component: (props) =>
107+
<RouteGuard
108+
component={SettingsView}
109+
fullRoutePath={`/dashboard/admin/settings`}
110+
/>,
111+
layout: '/dashboard',
112+
visible: true,
113+
},
100114
];
101115

102116
export default dashboardRoutes;

src/service/passport/jwtAuthHandler.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,6 @@ const jwtAuthHandler = (overrideConfig = null) => {
1717
return next();
1818
}
1919

20-
if (req.isAuthenticated()) {
21-
return next();
22-
}
23-
2420
const token = req.header("Authorization");
2521
if (!token) {
2622
return res.status(401).send("No token provided\n");
@@ -30,11 +26,15 @@ const jwtAuthHandler = (overrideConfig = null) => {
3026
const audience = expectedAudience || clientID;
3127

3228
if (!authorityURL) {
33-
return res.status(500).send("OIDC authority URL is not configured\n");
29+
return res.status(500).send({
30+
message: "JWT handler: authority URL is not configured\n"
31+
});
3432
}
3533

3634
if (!clientID) {
37-
return res.status(500).send("OIDC client ID is not configured\n");
35+
return res.status(500).send({
36+
message: "JWT handler: client ID is not configured\n"
37+
});
3838
}
3939

4040
const tokenParts = token.split(" ");

src/ui/components/Card/Card.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import classNames from 'classnames';
2+
import clsx from 'clsx';
33
import { makeStyles } from '@material-ui/core/styles';
44
import styles from '../../assets/jss/material-dashboard-react/components/cardStyle';
55

@@ -23,7 +23,7 @@ const Card: React.FC<CardProps> = ({
2323
}) => {
2424
const classes = useStyles();
2525

26-
const cardClasses = classNames({
26+
const cardClasses = clsx({
2727
[classes.card]: true,
2828
[classes.cardPlain]: plain,
2929
[classes.cardProfile]: profile,

0 commit comments

Comments
 (0)