diff --git a/.gitignore b/.gitignore index c5788e3..4032a9b 100644 --- a/.gitignore +++ b/.gitignore @@ -135,3 +135,6 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + +#screenshots +tests/cypress/screenshots \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 10af2e8..a5aabc7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "editor.tabSize": 2, - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true } diff --git a/app/src/utils/api.js b/app/src/utils/api.js index c5f2353..a0bbef5 100644 --- a/app/src/utils/api.js +++ b/app/src/utils/api.js @@ -7,5 +7,4 @@ const api = axios.create({ }, withCredentials: true, }) - export default api diff --git a/server/package.json b/server/package.json index 7562dae..584ac14 100644 --- a/server/package.json +++ b/server/package.json @@ -20,6 +20,7 @@ "bcrypt": "^5.1.1", "commander": "^11.0.0", "connect-mongo": "^5.0.0", + "cookie-parser": "^1.4.6", "dockerode": "^3.3.5", "dotenv": "^16.4.1", "express": "^4.18.2", @@ -27,6 +28,7 @@ "express-session": "^1.17.3", "express-validator": "^7.0.1", "jsonwebtoken": "^9.0.1", + "lusca": "^1.7.0", "minimist": "^1.2.8", "mongodb": "^6.3.0", "mongoose": "^8.0.3", diff --git a/server/src/api/routes/index.js b/server/src/api/routes/index.js index 2cfd5ec..cc79728 100644 --- a/server/src/api/routes/index.js +++ b/server/src/api/routes/index.js @@ -1,5 +1,7 @@ import { Router } from 'express' import passport from 'passport' +import lusca from 'lusca' +import cookieParser from 'cookie-parser' import sessions from './sessionRouter.js' import quests from './questRouter.js' import commands from './commandRouter.js' @@ -10,7 +12,7 @@ import config from '../../config.js' import leaderboardController from '../controllers/leaderboardController.js' const router = Router() - +router.use(cookieParser()) if (!config.isProduction) { router.get('/', (req, res) => { res.send('Hello API!') @@ -21,17 +23,27 @@ if (!config.isProduction) { }) } -router.use('/auth', authRouter) -router.use('/quests', quests) -router.use('/users', userRouter) - -router.use('/sessions', authRequired, sessions) router.use( '/commands', passport.authenticate('jwt', { session: false }), authRequired, commands ) +router.use('/auth', authRouter) +router.use('/sessions', authRequired, sessions) +router.use( + lusca({ + csrf: { + cookie: { name: '_csrf' }, + header: 'x-csrf-token', + secret: config.secret, + }, + hsts: { maxAge: 31536000, includeSubDomains: true, preload: true }, + }) +) + +router.use('/quests', quests) +router.use('/users', userRouter) router.get('/survey', (req, res) => { if (!config.surveyUrl) { diff --git a/server/src/config.js b/server/src/config.js index 2f7b7c8..cff3cbe 100644 --- a/server/src/config.js +++ b/server/src/config.js @@ -23,46 +23,58 @@ function getUrl(key) { return '' } -const config = { - host: get('HOST', 'localhost'), - port: get('PORT', 3000), - db: get('MONGO_URL', 'mongodb://localhost:27017/odyssey-test'), - secret: get('SECRET_KEY', ''), - isProduction: process.env.NODE_ENV === 'production', - containerExpiry: get('EXPIRY', 1000 * 60 * 60), - sessionMaxAge: get('SESSION_MAX_AGE', 1000 * 60 * 60 * 24 * 7), - google: { - clientID: get('GOOGLE_CLIENT_ID', ''), - clientSecret: get('GOOGLE_CLIENT_SECRET', ''), - }, - github: { - clientID: get('GITHUB_CLIENT_ID', ''), - clientSecret: get('GITHUB_CLIENT_SECRET', ''), - }, - trustedProxies: getTrustProxies('TRUSTED_PROXIES'), - surveyUrl: getUrl('SURVEY_URL', ''), - bugReportUrl: getUrl('BUG_REPORT_URL', ''), +function createConfig() { + const isProduction = process.env.NODE_ENV === 'production' + const host = get('HOST', 'localhost') + const port = Number(get('PORT', 3000)) + const baseUrl = get('BASE_URL', `http://${host}:${port}`) + const url = new URL(baseUrl) + return { + host, + port, + baseUrl, + domain: url.hostname, + protocol: url.protocol, + backendUrl: get('BACKEND_URL', baseUrl), + db: get('MONGO_URL', 'mongodb://localhost:27017/odyssey-test'), + secret: get('SECRET_KEY', ''), + isProduction: process.env.NODE_ENV === 'production', + containerExpiry: get('EXPIRY', 1000 * 60 * 60), + sessionMaxAge: get('SESSION_MAX_AGE', 1000 * 60 * 60 * 24 * 7), + google: { + clientID: get('GOOGLE_CLIENT_ID', ''), + clientSecret: get('GOOGLE_CLIENT_SECRET', ''), + }, + github: { + clientID: get('GITHUB_CLIENT_ID', ''), + clientSecret: get('GITHUB_CLIENT_SECRET', ''), + }, + trustedProxies: getTrustProxies('TRUSTED_PROXIES'), + surveyUrl: getUrl('SURVEY_URL', ''), + bugReportUrl: getUrl('BUG_REPORT_URL', ''), - docker: { - network: get('DOCKER_NETWORK', 'host'), - defaultImage: get('QUEST_IMAGE', 'linuxodyssey/quest-base'), - imagePrefix: get('DOCKER_PREFIX', 'linuxodyssey/quest-'), - mountQuest: get('MOUNT_QUEST', ''), - hostPwd: get('HOST_PWD', ''), - }, + docker: { + network: get('DOCKER_NETWORK', 'host'), + defaultImage: get('QUEST_IMAGE', 'linuxodyssey/quest-base'), + imagePrefix: get('DOCKER_PREFIX', 'linuxodyssey/quest-'), + mountQuest: get('MOUNT_QUEST', ''), + hostPwd: get('HOST_PWD', ''), + }, - log: { - path: get('LOG_PATH', '../logs'), - }, + log: { + path: get('LOG_PATH', '../logs'), + }, + testing: { + enabled: !isProduction && process.env.TESTING === 'true', + username: get('TESTING_USERNAME', ''), + password: get('TESTING_PASSWORD', ''), + }, + } } -config.baseUrl = get('BASE_URL', `http://${config.host}:${config.port}`) -config.backendUrl = get('BACKEND_URL', config.baseUrl) -config.testing = { - enabled: !config.isProduction && process.env.TESTING === 'true', - username: get('TESTING_USERNAME', ''), - password: get('TESTING_PASSWORD', ''), -} +const config = createConfig() + +console.log(config) export function getQuestImage(id) { return `${config.docker.imagePrefix}${id}` diff --git a/server/src/middleware/session.js b/server/src/middleware/session.js index 38ce0a2..a6f90e3 100644 --- a/server/src/middleware/session.js +++ b/server/src/middleware/session.js @@ -9,9 +9,10 @@ export default session({ saveUninitialized: false, rolling: true, cookie: { + domain: config.domain, httpOnly: true, - secure: false, - sameSite: 'lax', + secure: config.protocol === 'https:', + sameSite: 'strict', maxAge: config.sessionMaxAge, }, store: MongoStore.create({ diff --git a/yarn.lock b/yarn.lock index 81241b8..b3ddac4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3146,6 +3146,16 @@ __metadata: languageName: node linkType: hard +"cookie-parser@npm:^1.4.6": + version: 1.4.6 + resolution: "cookie-parser@npm:1.4.6" + dependencies: + cookie: "npm:0.4.1" + cookie-signature: "npm:1.0.6" + checksum: 10c0/9c2ade5459290802cd472a2d2a6e46fbd7de3e8514e02bfed5edfde892d77733c7f89d9d2015f752a9087680429b416972d7aba748bf6824e21eb680c8556383 + languageName: node + linkType: hard + "cookie-signature@npm:1.0.6": version: 1.0.6 resolution: "cookie-signature@npm:1.0.6" @@ -3160,6 +3170,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:0.4.1": + version: 0.4.1 + resolution: "cookie@npm:0.4.1" + checksum: 10c0/4d7bc798df3d0f34035977949cd6b7d05bbab47d7dcb868667f460b578a550cd20dec923832b8a3a107ef35aba091a3975e14f79efacf6e39282dc0fed6db4a1 + languageName: node + linkType: hard + "cookie@npm:0.5.0": version: 0.5.0 resolution: "cookie@npm:0.5.0" @@ -6709,6 +6726,15 @@ __metadata: languageName: node linkType: hard +"lusca@npm:^1.7.0": + version: 1.7.0 + resolution: "lusca@npm:1.7.0" + dependencies: + tsscmp: "npm:^1.0.5" + checksum: 10c0/73b49c9289bbdff73298421bdafc0633f958d97bef51a0fe747bbd1ec5a8c6bb2ff9ada37e84d3a8e23703ae2db1fcf8e3d1fbea366559e3b7261467c66fbe2e + languageName: node + linkType: hard + "luxon@npm:^3.2.1": version: 3.4.4 resolution: "luxon@npm:3.4.4" @@ -8413,6 +8439,7 @@ __metadata: commander: "npm:^11.0.0" concurrently: "npm:^8.2.2" connect-mongo: "npm:^5.0.0" + cookie-parser: "npm:^1.4.6" dockerode: "npm:^3.3.5" dotenv: "npm:^16.4.1" express: "npm:^4.18.2" @@ -8421,6 +8448,7 @@ __metadata: express-validator: "npm:^7.0.1" jest: "npm:^29.6.1" jsonwebtoken: "npm:^9.0.1" + lusca: "npm:^1.7.0" minimist: "npm:^1.2.8" mongodb: "npm:^6.3.0" mongoose: "npm:^8.0.3" @@ -9354,6 +9382,13 @@ __metadata: languageName: node linkType: hard +"tsscmp@npm:^1.0.5": + version: 1.0.6 + resolution: "tsscmp@npm:1.0.6" + checksum: 10c0/2f79a9455e7e3e8071995f98cdf3487ccfc91b760bec21a9abb4d90519557eafaa37246e87c92fa8bf3fef8fd30cfd0cc3c4212bb929baa9fb62494bfa4d24b2 + languageName: node + linkType: hard + "tunnel-agent@npm:^0.6.0": version: 0.6.0 resolution: "tunnel-agent@npm:0.6.0"