From 2645a88e98a714ccc637fb2c3db68088d1704a43 Mon Sep 17 00:00:00 2001 From: Iyan Robles Date: Mon, 6 May 2024 02:38:44 +0200 Subject: [PATCH 1/7] Modified grafana configuration to change password properly --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 05085b3..9811d08 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -178,8 +178,8 @@ jobs: cp -r ./wiq_en3b/gatewayservice/ . echo -e "${{ secrets.CERTIFICATE }}" > gatewayservice/certificate/certificate.crt echo -e "${{ secrets.CERTIFICATE_KEY }}" > gatewayservice/certificate/certificatekey.key - echo -e "\n[security]\nadmin_user=${{ secrets.GRAFANA_USER }}\nadmin_password=${{ secrets.GRAFANA_PASSWORD }}" >> gatewayservice/monitoring/grafana/grafana.ini wget https://raw.githubusercontent.com/arquisoft/wiq_en3b/master/docker-compose.yml -O docker-compose.yml wget https://raw.githubusercontent.com/arquisoft/wiq_en3b/master/.env docker compose --profile prod down docker compose --profile prod up -d --pull always + docker exec grafana-wiq_en3b grafana-cli admin reset-admin-password ${{ secrets.GRAFANA_PASSWORD }} \ No newline at end of file From 713b58948789dba1a0c28662bf0cda0c0eb4911e Mon Sep 17 00:00:00 2001 From: Iyan Robles Date: Mon, 6 May 2024 02:50:19 +0200 Subject: [PATCH 2/7] Changed JWT key for one stored in secrets --- .github/workflows/release.yml | 6 ++++++ users/authservice/Dockerfile | 3 +++ users/authservice/src/controllers/auth-controller.ts | 4 +++- users/userservice/Dockerfile | 3 +++ users/userservice/src/utils/async-verification.ts | 4 +++- users/userservice/test/user-service.test.ts | 5 +++-- 6 files changed, 21 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9811d08..42eeade 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,12 +83,15 @@ jobs: - uses: actions/checkout@v4 - name: Publish to Registry uses: elgohr/Publish-Docker-Github-Action@v5 + env: + JWT_KEY: ${{ secrets.JWT_KEY }} with: name: arquisoft/wiq_en3b/authservice username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} registry: ghcr.io workdir: users/authservice + buildargs: JWT_KEY docker-push-userservice: name: Push user service Docker Image to GitHub Packages @@ -101,12 +104,15 @@ jobs: - uses: actions/checkout@v4 - name: Publish to Registry uses: elgohr/Publish-Docker-Github-Action@v5 + env: + JWT_KEY: ${{ secrets.JWT_KEY }} with: name: arquisoft/wiq_en3b/userservice username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} registry: ghcr.io workdir: users/userservice + buildargs: JWT_KEY docker-push-questionservice: name: Push question service Docker Image to GitHub Packages diff --git a/users/authservice/Dockerfile b/users/authservice/Dockerfile index 950f4d5..9693a4a 100644 --- a/users/authservice/Dockerfile +++ b/users/authservice/Dockerfile @@ -13,6 +13,9 @@ RUN npm install # Copy the app source code to the working directory COPY . . +ARG JWT_KEY='your-secret-key' +ENV JWT_SECRET_KEY=$JWT_KEY + # Expose the port the app runs on EXPOSE 8002 diff --git a/users/authservice/src/controllers/auth-controller.ts b/users/authservice/src/controllers/auth-controller.ts index 570197b..d16851d 100644 --- a/users/authservice/src/controllers/auth-controller.ts +++ b/users/authservice/src/controllers/auth-controller.ts @@ -6,6 +6,8 @@ const { validateRequiredFields } = require('kaw-users-utils'); import User from '../models/auth-model'; +const JWT_SECRET_KEY = process.env.JWT_SECRET_KEY ?? 'your-secret-key'; + const loginUser = async (req: Request, res: Response) => { try { validateRequiredFields(req, ['username', 'password']); @@ -28,7 +30,7 @@ const loginUser = async (req: Request, res: Response) => { // Check if the user exists and verify the password if (user && (await bcrypt.compare(password, user.password))) { // Generate a JWT token - const token = jwt.sign({ userId: user._id }, 'your-secret-key', { + const token = jwt.sign({ userId: user._id }, JWT_SECRET_KEY, { expiresIn: '1h', }); // Respond with the token and user information diff --git a/users/userservice/Dockerfile b/users/userservice/Dockerfile index 3d93183..033dbda 100644 --- a/users/userservice/Dockerfile +++ b/users/userservice/Dockerfile @@ -13,6 +13,9 @@ RUN npm install # Copy the app source code to the working directory COPY . . +ARG JWT_KEY='your-secret-key' +ENV JWT_SECRET_KEY=$JWT_KEY + # Expose the port the app runs on EXPOSE 8001 diff --git a/users/userservice/src/utils/async-verification.ts b/users/userservice/src/utils/async-verification.ts index 5d80313..ebc5ef6 100644 --- a/users/userservice/src/utils/async-verification.ts +++ b/users/userservice/src/utils/async-verification.ts @@ -1,8 +1,10 @@ import jwt from 'jsonwebtoken'; +const JWT_SECRET_KEY = process.env.JWT_SECRET_KEY ?? 'your-secret-key'; + const verifyJWT = async (token: string) => { return new Promise((resolve, reject) => { - jwt.verify(token, 'your-secret-key', (err, decoded) => { + jwt.verify(token, JWT_SECRET_KEY, (err, decoded) => { if (err) { reject(err); } else { diff --git a/users/userservice/test/user-service.test.ts b/users/userservice/test/user-service.test.ts index f86dc1e..a236f58 100644 --- a/users/userservice/test/user-service.test.ts +++ b/users/userservice/test/user-service.test.ts @@ -11,6 +11,7 @@ import { Request, Response } from 'express'; import { verifyJWT } from "../src/utils/async-verification"; let mongoServer: MongoMemoryServer; +const JWT_SECRET_KEY = process.env.JWT_SECRET_KEY ?? 'your-secret-key'; beforeAll(async () => { mongoServer = await MongoMemoryServer.create(); @@ -122,7 +123,7 @@ describe('User Service', () => { const user = await User.findOne({ username:'testuser' }); // Generates a temporary token for this and other tests - testToken = jwt.sign({ userId: user!._id }, 'your-secret-key', { + testToken = jwt.sign({ userId: user!._id }, JWT_SECRET_KEY, { expiresIn: '2m', }); @@ -237,7 +238,7 @@ describe('User Service', () => { }; const newUser = await User.findOne({ username:'highestscoreuser' }); // Generates a temporary token for this test - const testToken2 = jwt.sign({ userId: newUser!._id }, 'your-secret-key', { + const testToken2 = jwt.sign({ userId: newUser!._id }, JWT_SECRET_KEY, { expiresIn: '2m', }); From 3c0363bd3a6ce437be057ff6d499456823c82aa4 Mon Sep 17 00:00:00 2001 From: Iyan Robles Date: Mon, 6 May 2024 03:07:07 +0200 Subject: [PATCH 3/7] Changed goals and quality requirements for consistency --- docs/src/01_introduction_and_goals.adoc | 18 +++++++++--------- docs/src/10_quality_requirements.adoc | 22 +++++++++++----------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/src/01_introduction_and_goals.adoc b/docs/src/01_introduction_and_goals.adoc index 972f2c8..267bf12 100644 --- a/docs/src/01_introduction_and_goals.adoc +++ b/docs/src/01_introduction_and_goals.adoc @@ -95,23 +95,23 @@ A table with quality goals and concrete scenarios, ordered by priorities | Consider incorporating user feedback and usability testing to refine the user interface. | 2 -| Availability -| The application must be accessible 24 hours a day. While complete availability may be impossible over extended periods, minimizing downtime and making interruptions imperceptible to users is key. -| Implementing redundancy and failover mechanisms can help ensure continuous availability. - -| 3 | Accessibility -| Any user must be able to enjoy playing with our application despite any disability they could have. +| Any user must be able to enjoy playing with our application despite any disability they could have. | HTML standards must be followed to try to minimize the lack of Accessibility of our Application. Another important feature is the contrast of our web. All of these can be tested with Accessibility tools available on Internet. +| 3 +| Performance +| The software should have acceptable response time to provide a smooth user experience. | Basic optimization techniques can improve performance. + | 4 | Security -| The application should stick to industry best practices for security to protect against unauthorized access, data breaches, and other threats. +| The application should stick to industry best practices for security to protect against unauthorized access, data breaches, and other threats. | Regular security audits and implementing basic security controls are essential. | 5 -| Performance -| The software should have acceptable response time to provide a smooth user experience. | Basic optimization techniques can improve performance. +| Availability +| Minimizing downtime and making interruptions imperceptible to users is desirable, however, it is difficult to achieve and not a strict requirement. For a game application, continuous availability is not crucial. +| Implementing redundancy and failover mechanisms can enhance availability. |=== diff --git a/docs/src/10_quality_requirements.adoc b/docs/src/10_quality_requirements.adoc index 159dc48..bc74a68 100644 --- a/docs/src/10_quality_requirements.adoc +++ b/docs/src/10_quality_requirements.adoc @@ -96,28 +96,28 @@ Tabular or free form text. The application should meet usability standards. | High +| Adaptability +| If a user accesses the application from a different device than a PC, the application should be able to adapt to the different screen size, providing a satisfactory user experience. +| High + | Performance | When a user interacts with the system it should react within one second to ensure a smooth and responsive user experience. | High +| Accessibility +| In case a user with a visual impairment interacts with the web application, it should follow HTML standards to ensure accessibility. +All interactive elements must be navigable using screen readers, text alternatives should be provided for non-text content. +Additionally, the application's color contrast should be enough for people with color blindness. +| High + | Privacy | If a user provides personal information during the registration, the application should securely handle and store user-provided personal information. Encryption should be applied for data storage. -| High +| Medium | Security | If a malicious user attempts unauthorized access to the application, the application should implement robust authentication mechanisms, including secure password storage to prevent unauthorized access. -| High - -| Adaptability -| If a user accesses the application from a different device than a PC, the application should be able to adapt to the different screen size, providing a satisfactory user experience. -| Medium - -| Accessibility -| In case a user with a visual impairment interacts with the web application, it should follow HTML standards to ensure accessibility. -All interactive elements must be navigable using screen readers, text alternatives should be provided for non-text content. -Additionally, the application's color contrast should be enough for people with color blindness. | Medium | Availability From 5472d05729eb91c44a485882e1202fac8cba21c3 Mon Sep 17 00:00:00 2001 From: Iyan Robles Date: Mon, 6 May 2024 03:53:20 +0200 Subject: [PATCH 4/7] Fixed share tweet issue for hardcore mode --- webapp/public/assets/locales/en/translation.json | 2 +- webapp/public/assets/locales/es/translation.json | 2 +- webapp/public/assets/locales/fr/translation.json | 2 +- webapp/public/assets/locales/uk/translation.json | 2 +- webapp/src/components/FinalResult/FinalResult.js | 7 ++++--- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/webapp/public/assets/locales/en/translation.json b/webapp/public/assets/locales/en/translation.json index 756692a..8d42647 100644 --- a/webapp/public/assets/locales/en/translation.json +++ b/webapp/public/assets/locales/en/translation.json @@ -85,7 +85,7 @@ "play_again": "Play Again", "go_back_button": "Go Back", "xshare_button": "Share your results", - "tweetShare": "---- 🤔 Know and Win 🤔 ----%0A%0AI have correctly answered {{correct}}/{{total}} questions and earned {{points}} points 💪💪%0ACome and play this incredible trivia game and try to beat me!! 😎%0A" + "tweetShare": "---- 🤔 Know and Win 🤔 ----%0A%0AI have correctly answered {{xresult}} questions and earned {{points}} points 💪💪%0ACome and play this incredible trivia game and try to beat me!! 😎%0A" } }, "home": { diff --git a/webapp/public/assets/locales/es/translation.json b/webapp/public/assets/locales/es/translation.json index 975bac6..ade37c0 100644 --- a/webapp/public/assets/locales/es/translation.json +++ b/webapp/public/assets/locales/es/translation.json @@ -85,7 +85,7 @@ "play_again": "Jugar de nuevo", "go_back_button": "Volver", "xshare_button": "Comparte tu resultado", - "tweetShare": "---- 🤔 Saber y Ganar 🤔 ----%0A%0AHe respondido correctamente {{correct}}/{{total}} preguntas y he ganado {{points}} puntos 💪💪%0AVen y juega este increíble juego de preguntas e intenta vencerme!!😎%0A" + "tweetShare": "---- 🤔 Saber y Ganar 🤔 ----%0A%0AHe respondido correctamente {{xresult}} preguntas y he ganado {{points}} puntos 💪💪%0AVen y juega este increíble juego de preguntas e intenta vencerme!!😎%0A" } }, "home": { diff --git a/webapp/public/assets/locales/fr/translation.json b/webapp/public/assets/locales/fr/translation.json index 45516ca..133f3a4 100644 --- a/webapp/public/assets/locales/fr/translation.json +++ b/webapp/public/assets/locales/fr/translation.json @@ -82,7 +82,7 @@ "play_again": "Rejouer", "go_back_button": "Retour", "xshare_button": "Partagez vos résultats", - "tweetShare": "---- 🤔 Know and Win 🤔 ----%0A%0AJ'ai répondu correctement à {{correct}}/{{total}} questions et j'ai gagné {{points}} points 💪💪%0AViens jouer à ça jeu-questionnaire incroyable et essayez de me battre!! 😎%0A" + "tweetShare": "---- 🤔 Know and Win 🤔 ----%0A%0AJ'ai répondu correctement à {{xresult}} questions et j'ai gagné {{points}} points 💪💪%0AViens jouer à ça jeu-questionnaire incroyable et essayez de me battre!! 😎%0A" } }, "home": { diff --git a/webapp/public/assets/locales/uk/translation.json b/webapp/public/assets/locales/uk/translation.json index d54a8d5..1294ddb 100644 --- a/webapp/public/assets/locales/uk/translation.json +++ b/webapp/public/assets/locales/uk/translation.json @@ -82,7 +82,7 @@ "play_again": "Грати знову", "go_back_button": "Повернутися", "xshare_button": "Поділіться своїми результатами", - "tweetShare": "---- 🤔 Знай і перемагай 🤔 ----%0A%0A Я правильно відповів на {{correct}}/{{total}} запитань і заробив {{points}} балів 💪💪%0A Приходь і грай у це неймовірна дрібниця і спробуй мене перемогти 😎%0A" + "tweetShare": "---- 🤔 Знай і перемагай 🤔 ----%0A%0A Я правильно відповів на {{xresult}} запитань і заробив {{points}} балів 💪💪%0A Приходь і грай у це неймовірна дрібниця і спробуй мене перемогти 😎%0A" } }, "home": { diff --git a/webapp/src/components/FinalResult/FinalResult.js b/webapp/src/components/FinalResult/FinalResult.js index cebcb86..251eda7 100644 --- a/webapp/src/components/FinalResult/FinalResult.js +++ b/webapp/src/components/FinalResult/FinalResult.js @@ -8,9 +8,9 @@ const FinalResult = ({ result, quizLength, points, onPlayAgain, goBack }) => { const { t } = useTranslation() const tweetUrl = "https://twitter.com/intent/tweet" - + // https://developer.twitter.com/en/docs/twitter-for-websites/tweet-button/overview - let tweetToShare = tweetUrl + "?text=" + t("play.result.tweetShare",{correct: result, total: quizLength, points: points}) + let tweetToShare = tweetUrl + "?text=" + t("play.result.tweetShare",{xresult: quizLength === 0 ? result : result + "%2F" + quizLength, points: points}) tweetToShare += "&url=https://www.kawgame.xyz%0A" tweetToShare += "&hashtags=kaw,wikidata,know,win,game" @@ -24,7 +24,8 @@ const FinalResult = ({ result, quizLength, points, onPlayAgain, goBack }) => {

{t('play.result.points', { points: points })}

- + + From c4f3bcf07aa71d11ceb5719d7dab9525ee27236d Mon Sep 17 00:00:00 2001 From: Iyan Robles Date: Mon, 6 May 2024 05:04:29 +0200 Subject: [PATCH 5/7] Added tests --- users/authservice/test/auth-service.test.ts | 8 ++++++++ users/userservice/test/user-service.test.ts | 15 ++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/users/authservice/test/auth-service.test.ts b/users/authservice/test/auth-service.test.ts index ba1ee48..ddc1610 100644 --- a/users/authservice/test/auth-service.test.ts +++ b/users/authservice/test/auth-service.test.ts @@ -1,3 +1,5 @@ +import express from "express"; + const request = require('supertest'); import { MongoMemoryServer } from 'mongodb-memory-server'; import bcrypt from 'bcrypt'; @@ -64,6 +66,12 @@ describe('Auth Service', () => { }); expect(response.status).toBe(500); }); + // JWT token + it('JWT token should be properly initialized', async () => { + process.env.JWT_SECRET_KEY = 'just_a_different_key' + jest.resetModules(); + await import('../src/controllers/auth-controller'); + }); }); async function testWithoutDatabase(paramFunc : Function) { diff --git a/users/userservice/test/user-service.test.ts b/users/userservice/test/user-service.test.ts index a236f58..19ab5aa 100644 --- a/users/userservice/test/user-service.test.ts +++ b/users/userservice/test/user-service.test.ts @@ -11,7 +11,7 @@ import { Request, Response } from 'express'; import { verifyJWT } from "../src/utils/async-verification"; let mongoServer: MongoMemoryServer; -const JWT_SECRET_KEY = process.env.JWT_SECRET_KEY ?? 'your-secret-key'; +const JWT_SECRET_KEY = 'your-secret-key'; beforeAll(async () => { mongoServer = await MongoMemoryServer.create(); @@ -572,6 +572,19 @@ describe('User Service', () => { expect(() => validateProfileBody(mockRequest, user[0])).toThrow(); }); + + // JWT token + it('JWT token should be properly initialized', async () => { + jest.resetModules(); + let verification = await import('../src/utils/async-verification'); + let isTokenValid = await verification.verifyJWT(testToken); + expect(isTokenValid).not.toBeUndefined(); + + process.env.JWT_SECRET_KEY = 'just_a_different_key' + jest.resetModules(); + verification = await import('../src/utils/async-verification'); + await expect(verification.verifyJWT(testToken)).rejects.toThrow(); + }); }); async function testWithoutDatabase(paramFunc : Function) { From 8fb8faa55e7f6af0cd2d83de99cf727b2de135cf Mon Sep 17 00:00:00 2001 From: Iyan Robles Date: Mon, 6 May 2024 05:06:56 +0200 Subject: [PATCH 6/7] Removed unused import --- users/authservice/test/auth-service.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/users/authservice/test/auth-service.test.ts b/users/authservice/test/auth-service.test.ts index ddc1610..d29669c 100644 --- a/users/authservice/test/auth-service.test.ts +++ b/users/authservice/test/auth-service.test.ts @@ -1,5 +1,3 @@ -import express from "express"; - const request = require('supertest'); import { MongoMemoryServer } from 'mongodb-memory-server'; import bcrypt from 'bcrypt'; From 959e8a76dddf139e0abc044807e16b00ffa14864 Mon Sep 17 00:00:00 2001 From: Iyan Robles Date: Mon, 6 May 2024 05:24:09 +0200 Subject: [PATCH 7/7] Added more tests to gateway service --- gatewayservice/test/gateway-service.test.ts | 33 +++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/gatewayservice/test/gateway-service.test.ts b/gatewayservice/test/gateway-service.test.ts index 98f6f82..12d6aff 100644 --- a/gatewayservice/test/gateway-service.test.ts +++ b/gatewayservice/test/gateway-service.test.ts @@ -382,6 +382,39 @@ describe('Gateway Service', () => { expect(response.statusCode).toBe(500); }); + + // Test environment variables + it('URI constants should initialize properly to environment variables', async () => { + process.env.WEBAPP_ENDPOINT = 'totallydifferenturi'; + process.env.GATEWAY_ENDPOINT = 'totallydifferenturi'; + + jest.resetModules(); + await import('../src/app'); + }); + + // Test environment variable constants + it('constants should initialize properly to environment variables', async () => { + + let constants = await import('../src/utils/constants'); + + let OLD_AUTH_SERVICE_URL = constants.AUTH_SERVICE_URL; + let OLD_USER_SERVICE_URL = constants.USER_SERVICE_URL; + let OLD_HISTORY_SERVICE_URL = constants.HISTORY_SERVICE_URL; + let OLD_QUESTION_SERVICE_URL = constants.QUESTION_SERVICE_URL; + + process.env.AUTH_SERVICE_URL = 'totallydifferenturl'; + process.env.USER_SERVICE_URL = 'totallydifferenturl'; + process.env.HISTORY_SERVICE_URL = 'totallydifferenturl'; + process.env.QUESTION_SERVICE_URL = 'totallydifferenturl'; + jest.resetModules(); + constants = await import('../src/utils/constants'); + + expect(constants.AUTH_SERVICE_URL).not.toMatch(OLD_AUTH_SERVICE_URL); + expect(constants.USER_SERVICE_URL).not.toMatch(OLD_USER_SERVICE_URL); + expect(constants.HISTORY_SERVICE_URL).not.toMatch(OLD_HISTORY_SERVICE_URL); + expect(constants.QUESTION_SERVICE_URL).not.toMatch(OLD_QUESTION_SERVICE_URL); + + }); }); async function testWithoutServices(paramFunc: Function) {