From a727f02248b45f7695db684505643519c933f438 Mon Sep 17 00:00:00 2001
From: jjgancfer
Date: Tue, 27 Feb 2024 12:54:37 +0100
Subject: [PATCH 01/19] feat: add initial version of auth in webapp (untested)
---
webapp/src/components/auth/Auth.js | 16 ++++++++++++++++
webapp/src/components/auth/AuthUtils.js | 16 ++++++++++++++++
2 files changed, 32 insertions(+)
create mode 100644 webapp/src/components/auth/Auth.js
create mode 100644 webapp/src/components/auth/AuthUtils.js
diff --git a/webapp/src/components/auth/Auth.js b/webapp/src/components/auth/Auth.js
new file mode 100644
index 00000000..bf0b2468
--- /dev/null
+++ b/webapp/src/components/auth/Auth.js
@@ -0,0 +1,16 @@
+import React, { createContext, useState } from "react";
+
+export const authContext = createContext({
+ jwt: null,
+ setJwt: (newJwt) => {}
+});
+
+export function authProvider({children}) {
+
+ const [jwt, setJwt] = useState(null);
+
+ return
+ {children}
+
+}
+
diff --git a/webapp/src/components/auth/AuthUtils.js b/webapp/src/components/auth/AuthUtils.js
new file mode 100644
index 00000000..6c0e0e43
--- /dev/null
+++ b/webapp/src/components/auth/AuthUtils.js
@@ -0,0 +1,16 @@
+import { useContext } from "react";
+import { authContext } from "./Auth";
+import axios from "axios";
+
+export function isLoggedIn() {
+ const context = useContext(authContext);
+
+ return context.jwt != null;
+}
+
+export async function logIn(loginData) {
+ const requestAnswer = await axios.post(process.env.API_URL +
+ process.env.LOGIN_ENDPOINT, loginData);
+
+ // TODO: Compare response with the status code
+}
\ No newline at end of file
From 95227baa44a03fe1d12f2339434ea32aa3d92c29 Mon Sep 17 00:00:00 2001
From: jjgancfer
Date: Fri, 1 Mar 2024 20:25:19 +0100
Subject: [PATCH 02/19] chore: updated .gitignore to include .env
---
webapp/.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/webapp/.gitignore b/webapp/.gitignore
index 4d29575d..8692cf66 100644
--- a/webapp/.gitignore
+++ b/webapp/.gitignore
@@ -13,6 +13,7 @@
# misc
.DS_Store
+.env
.env.local
.env.development.local
.env.test.local
From a91f8f4ec6e69fccc4e3931ca520cc0affe5f3a4 Mon Sep 17 00:00:00 2001
From: jjgancfer
Date: Fri, 1 Mar 2024 20:26:07 +0100
Subject: [PATCH 03/19] feat: add tentatively working support for login (cannot
continue due to API bug)
---
webapp/.env | 1 -
webapp/src/components/auth/Auth.js | 2 +-
webapp/src/components/auth/AuthUtils.js | 17 +--
webapp/src/index.js | 1 -
webapp/src/pages/Login.jsx | 151 +++++++++++++-----------
5 files changed, 90 insertions(+), 82 deletions(-)
delete mode 100644 webapp/.env
diff --git a/webapp/.env b/webapp/.env
deleted file mode 100644
index c810bde7..00000000
--- a/webapp/.env
+++ /dev/null
@@ -1 +0,0 @@
-REACT_APP_API_ENDPOINT=http://localhost:8000
\ No newline at end of file
diff --git a/webapp/src/components/auth/Auth.js b/webapp/src/components/auth/Auth.js
index bf0b2468..20a53e9a 100644
--- a/webapp/src/components/auth/Auth.js
+++ b/webapp/src/components/auth/Auth.js
@@ -5,7 +5,7 @@ export const authContext = createContext({
setJwt: (newJwt) => {}
});
-export function authProvider({children}) {
+export function AuthProvider({children}) {
const [jwt, setJwt] = useState(null);
diff --git a/webapp/src/components/auth/AuthUtils.js b/webapp/src/components/auth/AuthUtils.js
index 6c0e0e43..7325ed0d 100644
--- a/webapp/src/components/auth/AuthUtils.js
+++ b/webapp/src/components/auth/AuthUtils.js
@@ -2,15 +2,18 @@ import { useContext } from "react";
import { authContext } from "./Auth";
import axios from "axios";
-export function isLoggedIn() {
+export function useLoggedState() {
const context = useContext(authContext);
-
return context.jwt != null;
}
-export async function logIn(loginData) {
- const requestAnswer = await axios.post(process.env.API_URL +
- process.env.LOGIN_ENDPOINT, loginData);
-
- // TODO: Compare response with the status code
+export async function logIn(loginData, onSuccess, onError) {
+ const url = process.env.REACT_APP_API_ENDPOINT + process.env.REACT_APP_LOGIN_ENDPOINT;
+ const requestAnswer = await axios.post(url, loginData);
+
+ return {
+ status: requestAnswer.status,
+ token: requestAnswer.data.token,
+ refreshToken: requestAnswer.data.refreshToken
+ };
}
\ No newline at end of file
diff --git a/webapp/src/index.js b/webapp/src/index.js
index 2a34dfff..798dfd4f 100644
--- a/webapp/src/index.js
+++ b/webapp/src/index.js
@@ -5,7 +5,6 @@ import reportWebVitals from './reportWebVitals';
import {createBrowserRouter, RouterProvider} from 'react-router-dom';
import router from 'components/Router';
import { ChakraProvider } from '@chakra-ui/react';
-
import "./i18n";
const root = ReactDOM.createRoot(document.querySelector("body"));
diff --git a/webapp/src/pages/Login.jsx b/webapp/src/pages/Login.jsx
index 8850e3e9..23ab5602 100644
--- a/webapp/src/pages/Login.jsx
+++ b/webapp/src/pages/Login.jsx
@@ -1,73 +1,80 @@
-import { Center } from "@chakra-ui/layout";
-import { Heading, Input, Button, InputGroup, Stack, InputLeftElement, chakra, Box, Avatar, FormControl, InputRightElement, Text } from "@chakra-ui/react";
-import axios, { HttpStatusCode } from "axios";
-import React, { useState } from "react";
-import { useTranslation } from "react-i18next";
-import { useNavigate } from "react-router-dom";
-import { FaLock, FaAddressCard } from "react-icons/fa";
-import ButtonEf from '../components/ButtonEf';
-import '../styles/AppView.css';
-
-export default function Login() {
-
- const [hasError, setHasError] = useState(false);
- const navigate = useNavigate();
- const { t } = useTranslation();
-
- const [showPassword, setShowPassword] = useState(false);
- const changeShowP = () => setShowPassword(!showPassword);
-
- const ChakraFaCardAlt = chakra(FaAddressCard);
- const ChakraFaLock = chakra(FaLock);
-
- const sendLogin = async () => {
- let data = {};
- let response = await axios.post(process.env.API_URL, data);
- if (response.status === HttpStatusCode.Accepted) {
- navigate("/home");
- } else {
- setHasError(true);
- }
- }
-
- return (
-
-
-
- { t("common.login")}
- {
- !hasError ?
- <>> :
-
- Error
-
- }
-
-
-
-
- }/>
-
-
-
-
-
- }/>
-
-
-
-
-
-
-
-
-
-
-
- );
+import { Center } from "@chakra-ui/layout";
+import { Heading, Input, Button, InputGroup, Stack, InputLeftElement, chakra, Box, Avatar, FormControl, InputRightElement, Text } from "@chakra-ui/react";
+import React, { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { useNavigate } from "react-router-dom";
+import { FaLock, FaAddressCard } from "react-icons/fa";
+import ButtonEf from '../components/ButtonEf';
+import '../styles/AppView.css';
+import {logIn} from "../components/auth/AuthUtils";
+import {HttpStatusCode} from "axios";
+
+export default function Login() {
+
+ const [hasError, setHasError] = useState(false);
+ const navigate = useNavigate();
+ const { t } = useTranslation();
+
+ const [showPassword, setShowPassword] = useState(false);
+ const changeShowP = () => setShowPassword(!showPassword);
+
+ const ChakraFaCardAlt = chakra(FaAddressCard);
+ const ChakraFaLock = chakra(FaLock);
+
+ const sendLogin = () => {
+ let data = {
+ "email": document.getElementById("user").value,
+ "password": document.getElementById("password").value
+ };
+ logIn(data).then(result => {
+ if (result.status === HttpStatusCode.Ok) {
+ localStorage.setItem('token', result.token);
+ localStorage.setItem('refreshToken', result.refreshToken);
+ navigate("/dashboard");
+ } else {
+ setHasError(true);
+ }
+ })
+ }
+
+ return (
+
+
+
+ { t("common.login")}
+ {
+ !hasError ?
+ <>> :
+
+ Error
+
+ }
+
+
+
+
+ }/>
+
+
+
+
+
+ }/>
+
+
+
+
+
+
+
+
+
+
+
+ );
}
\ No newline at end of file
From f3bc9761d4d9faff00e9b8ad63179804951bffd8 Mon Sep 17 00:00:00 2001
From: jjgancfer
Date: Sat, 2 Mar 2024 19:52:55 +0100
Subject: [PATCH 04/19] chore: some refactor, as well as minor error fix
---
webapp/src/components/auth/AuthUtils.js | 2 +-
webapp/src/i18n.js | 2 +-
webapp/src/index.js | 2 ++
webapp/src/pages/Login.jsx | 9 +++++++--
4 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/webapp/src/components/auth/AuthUtils.js b/webapp/src/components/auth/AuthUtils.js
index 7325ed0d..a882bfc9 100644
--- a/webapp/src/components/auth/AuthUtils.js
+++ b/webapp/src/components/auth/AuthUtils.js
@@ -14,6 +14,6 @@ export async function logIn(loginData, onSuccess, onError) {
return {
status: requestAnswer.status,
token: requestAnswer.data.token,
- refreshToken: requestAnswer.data.refreshToken
+ refreshToken: requestAnswer.data.refresh_Token
};
}
\ No newline at end of file
diff --git a/webapp/src/i18n.js b/webapp/src/i18n.js
index a57b1175..a9a6fdb1 100644
--- a/webapp/src/i18n.js
+++ b/webapp/src/i18n.js
@@ -9,5 +9,5 @@ export default i18n.use(Backend)
.use(initReactI18next)
.init({
fallbackLng: "en",
- debug: true
+ debug: false
})
\ No newline at end of file
diff --git a/webapp/src/index.js b/webapp/src/index.js
index 798dfd4f..fd2d1dc6 100644
--- a/webapp/src/index.js
+++ b/webapp/src/index.js
@@ -6,9 +6,11 @@ import {createBrowserRouter, RouterProvider} from 'react-router-dom';
import router from 'components/Router';
import { ChakraProvider } from '@chakra-ui/react';
import "./i18n";
+import axios from "axios";
const root = ReactDOM.createRoot(document.querySelector("body"));
const browserRouter = createBrowserRouter(router);
+axios.defaults.headers.post["Content-Type"] = "application/json";
root.render(
diff --git a/webapp/src/pages/Login.jsx b/webapp/src/pages/Login.jsx
index 23ab5602..19bbb365 100644
--- a/webapp/src/pages/Login.jsx
+++ b/webapp/src/pages/Login.jsx
@@ -6,16 +6,21 @@ import { useNavigate } from "react-router-dom";
import { FaLock, FaAddressCard } from "react-icons/fa";
import ButtonEf from '../components/ButtonEf';
import '../styles/AppView.css';
-import {logIn} from "../components/auth/AuthUtils";
+import {logIn, useLoggedState} from "../components/auth/AuthUtils";
import {HttpStatusCode} from "axios";
export default function Login() {
- const [hasError, setHasError] = useState(false);
const navigate = useNavigate();
+ if (useLoggedState()) {
+ navigate("/dashboard");
+ }
+
+ const [hasError, setHasError] = useState(false);
const { t } = useTranslation();
const [showPassword, setShowPassword] = useState(false);
+
const changeShowP = () => setShowPassword(!showPassword);
const ChakraFaCardAlt = chakra(FaAddressCard);
From 40090728f5a5fdbff4c0952e11fe25fbb325d0ca Mon Sep 17 00:00:00 2001
From: jjgancfer
Date: Sun, 3 Mar 2024 14:11:04 +0100
Subject: [PATCH 05/19] feat: add tentative support for JWT auth (routes are
yet to be protected though)
---
webapp/src/components/auth/Auth.js | 16 ----
webapp/src/components/auth/AuthUtils.js | 23 +++--
webapp/src/pages/Login.jsx | 38 ++++----
webapp/src/pages/Signup.jsx | 110 +++++++++++++-----------
4 files changed, 85 insertions(+), 102 deletions(-)
delete mode 100644 webapp/src/components/auth/Auth.js
diff --git a/webapp/src/components/auth/Auth.js b/webapp/src/components/auth/Auth.js
deleted file mode 100644
index 20a53e9a..00000000
--- a/webapp/src/components/auth/Auth.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import React, { createContext, useState } from "react";
-
-export const authContext = createContext({
- jwt: null,
- setJwt: (newJwt) => {}
-});
-
-export function AuthProvider({children}) {
-
- const [jwt, setJwt] = useState(null);
-
- return
- {children}
-
-}
-
diff --git a/webapp/src/components/auth/AuthUtils.js b/webapp/src/components/auth/AuthUtils.js
index a882bfc9..aa4ee624 100644
--- a/webapp/src/components/auth/AuthUtils.js
+++ b/webapp/src/components/auth/AuthUtils.js
@@ -1,19 +1,16 @@
-import { useContext } from "react";
-import { authContext } from "./Auth";
import axios from "axios";
-export function useLoggedState() {
- const context = useContext(authContext);
- return context.jwt != null;
+export function isUserLogged() {
+ return axios.defaults.headers.common["Authorization"] != null;
}
-export async function logIn(loginData, onSuccess, onError) {
- const url = process.env.REACT_APP_API_ENDPOINT + process.env.REACT_APP_LOGIN_ENDPOINT;
- const requestAnswer = await axios.post(url, loginData);
+export function saveToken(requestAnswer) {
+ axios.defaults.headers.common["Authorization"] = "Bearer " + requestAnswer.data.token;
+ localStorage.setItem("jwtToken", requestAnswer.data.token);
+ localStorage.setItem("jwtRefreshToken", requestAnswer.data.refresh_Token)
+}
- return {
- status: requestAnswer.status,
- token: requestAnswer.data.token,
- refreshToken: requestAnswer.data.refresh_Token
- };
+export function login(loginData) {
+ return axios.post(process.env.REACT_APP_API_ENDPOINT
+ + process.env.REACT_APP_LOGIN_ENDPOINT, loginData);
}
\ No newline at end of file
diff --git a/webapp/src/pages/Login.jsx b/webapp/src/pages/Login.jsx
index 19bbb365..9979b2dc 100644
--- a/webapp/src/pages/Login.jsx
+++ b/webapp/src/pages/Login.jsx
@@ -1,24 +1,36 @@
import { Center } from "@chakra-ui/layout";
import { Heading, Input, Button, InputGroup, Stack, InputLeftElement, chakra, Box, Avatar, FormControl, InputRightElement, Text } from "@chakra-ui/react";
-import React, { useState } from "react";
+import React, {useState} from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { FaLock, FaAddressCard } from "react-icons/fa";
import ButtonEf from '../components/ButtonEf';
import '../styles/AppView.css';
-import {logIn, useLoggedState} from "../components/auth/AuthUtils";
-import {HttpStatusCode} from "axios";
+import {isUserLogged, login, saveToken} from "../components/auth/AuthUtils";
export default function Login() {
const navigate = useNavigate();
- if (useLoggedState()) {
+ if (isUserLogged()) {
navigate("/dashboard");
}
const [hasError, setHasError] = useState(false);
const { t } = useTranslation();
+ const sendLogin = async () => {
+ const loginData = {
+ "email": document.getElementById("user").value,
+ "password": document.getElementById("password").value
+ };
+ login(loginData).then(requestAnswer => {
+ saveToken(requestAnswer);
+ navigate("/dashboard");
+ }).catch(err => {
+ setHasError(true);
+ });
+ }
+
const [showPassword, setShowPassword] = useState(false);
const changeShowP = () => setShowPassword(!showPassword);
@@ -26,22 +38,6 @@ export default function Login() {
const ChakraFaCardAlt = chakra(FaAddressCard);
const ChakraFaLock = chakra(FaLock);
- const sendLogin = () => {
- let data = {
- "email": document.getElementById("user").value,
- "password": document.getElementById("password").value
- };
- logIn(data).then(result => {
- if (result.status === HttpStatusCode.Ok) {
- localStorage.setItem('token', result.token);
- localStorage.setItem('refreshToken', result.refreshToken);
- navigate("/dashboard");
- } else {
- setHasError(true);
- }
- })
- }
-
return (
@@ -76,7 +72,7 @@ export default function Login() {
-
+
diff --git a/webapp/src/pages/Signup.jsx b/webapp/src/pages/Signup.jsx
index 8d3da600..4d1278a5 100644
--- a/webapp/src/pages/Signup.jsx
+++ b/webapp/src/pages/Signup.jsx
@@ -1,53 +1,59 @@
-import { Center } from "@chakra-ui/layout";
-import { Button, FormControl, FormLabel, Heading, Input, Text, Stack } from "@chakra-ui/react";
-import axios, { HttpStatusCode } from "axios";
-import React, { useState } from "react";
-import { useTranslation } from "react-i18next";
-import { useNavigate } from "react-router-dom";
-
-export default function Signup() {
-
- const [hasError, setHasError] = useState(false);
- const navigate = useNavigate();
- const { t } = useTranslation();
-
- const sendLogin = async () => {
- let data = {};
- let response = await axios.post(process.env.API_URL, data);
- if (response.status === HttpStatusCode.Accepted) {
- navigate("/home");
- } else {
- setHasError(true);
- }
- }
-
- return (
-
- { t("common.register")}
- {
- !hasError ?
- <>> :
-
- Error
-
- }
-
-
- { t("session.username") }
-
-
-
- { t("Correo electrónico") } {/* To be changed */}
-
-
-
- {t("session.password")}
-
-
-
-
-
- );
+import { Center } from "@chakra-ui/layout";
+import { Button, FormControl, FormLabel, Heading, Input, Text, Stack } from "@chakra-ui/react";
+import axios, { HttpStatusCode } from "axios";
+import React, { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { useNavigate } from "react-router-dom";
+import {saveToken} from "../components/auth/AuthUtils";
+
+export default function Signup() {
+
+ const [hasError, setHasError] = useState(false);
+ const navigate = useNavigate();
+ const { t } = useTranslation();
+
+ const sendRegister = async () => {
+ const petitionData = {
+ "user": document.getElementById("user").value,
+ "email": document.getElementById("email").value,
+ "password": document.getElementById("password").value
+ };
+
+ axios.post(process.env.REACT_APP_API_ENDPOINT
+ + process.env.REACT_APP_LOGIN_ENDPOINT, petitionData).then(() => {
+ navigate("/login");
+ }).catch(err => {
+ setHasError(true);
+ });
+ }
+
+ return (
+
+ { t("common.register")}
+ {
+ !hasError ?
+ <>> :
+
+ Error
+
+ }
+
+
+ { t("session.username") }
+
+
+
+ { t("Correo electrónico") } {/* To be changed */}
+
+
+
+ {t("session.password")}
+
+
+
+
+
+ );
}
\ No newline at end of file
From f27c266ce817513beea04076e887f17f79b03c71 Mon Sep 17 00:00:00 2001
From: jjgancfer
Date: Sun, 3 Mar 2024 14:21:53 +0100
Subject: [PATCH 06/19] chore: updated endpoint of signup request
---
webapp/src/pages/Signup.jsx | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/webapp/src/pages/Signup.jsx b/webapp/src/pages/Signup.jsx
index 4d1278a5..c4d98d26 100644
--- a/webapp/src/pages/Signup.jsx
+++ b/webapp/src/pages/Signup.jsx
@@ -1,10 +1,9 @@
import { Center } from "@chakra-ui/layout";
import { Button, FormControl, FormLabel, Heading, Input, Text, Stack } from "@chakra-ui/react";
-import axios, { HttpStatusCode } from "axios";
+import axios from "axios";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
-import {saveToken} from "../components/auth/AuthUtils";
export default function Signup() {
@@ -20,7 +19,7 @@ export default function Signup() {
};
axios.post(process.env.REACT_APP_API_ENDPOINT
- + process.env.REACT_APP_LOGIN_ENDPOINT, petitionData).then(() => {
+ + process.env.REACT_APP_SIGNUP_ENDPOINT, petitionData).then(() => {
navigate("/login");
}).catch(err => {
setHasError(true);
From 4b193d30c42e2b369d816ca637b55979e783c9b4 Mon Sep 17 00:00:00 2001
From: jjgancfer
Date: Tue, 5 Mar 2024 12:46:32 +0100
Subject: [PATCH 07/19] chore: rewrite to make use of async/await
---
webapp/src/components/auth/AuthUtils.js | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/webapp/src/components/auth/AuthUtils.js b/webapp/src/components/auth/AuthUtils.js
index aa4ee624..422ef22b 100644
--- a/webapp/src/components/auth/AuthUtils.js
+++ b/webapp/src/components/auth/AuthUtils.js
@@ -1,7 +1,7 @@
-import axios from "axios";
+import axios, { HttpStatusCode } from "axios";
export function isUserLogged() {
- return axios.defaults.headers.common["Authorization"] != null;
+ return localStorage.getItem("jwtToken") != undefined;
}
export function saveToken(requestAnswer) {
@@ -10,7 +10,13 @@ export function saveToken(requestAnswer) {
localStorage.setItem("jwtRefreshToken", requestAnswer.data.refresh_Token)
}
-export function login(loginData) {
- return axios.post(process.env.REACT_APP_API_ENDPOINT
+export async function login(loginData, onSuccess, onError) {
+ let requestAnswer = await axios.post(process.env.REACT_APP_API_ENDPOINT
+ process.env.REACT_APP_LOGIN_ENDPOINT, loginData);
+ if (HttpStatusCode.Ok === requestAnswer.status) {
+ saveToken(requestAnswer);
+ onSuccess();
+ } else {
+ onError();
+ }
}
\ No newline at end of file
From 4b1be6795d573ddd31c93c47554f33b986d60ed0 Mon Sep 17 00:00:00 2001
From: jjgancfer
Date: Tue, 5 Mar 2024 12:46:51 +0100
Subject: [PATCH 08/19] feat: add AuthUtils tests
---
webapp/src/tests/AuthUtils.test.js | 53 ++++++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
create mode 100644 webapp/src/tests/AuthUtils.test.js
diff --git a/webapp/src/tests/AuthUtils.test.js b/webapp/src/tests/AuthUtils.test.js
new file mode 100644
index 00000000..cad9ac5e
--- /dev/null
+++ b/webapp/src/tests/AuthUtils.test.js
@@ -0,0 +1,53 @@
+import MockAdapter from "axios-mock-adapter";
+import axios, { HttpStatusCode } from "axios";
+import { isUserLogged, login } from "components/auth/AuthUtils";
+
+const mockAxios = new MockAdapter(axios);
+
+describe("Auth Utils tests", () => {
+ describe("when the user is not authenticated", () => {
+
+ beforeEach(() => {
+ mockAxios.reset();
+ });
+
+ it("does not have a stored token", () => {
+ expect(isUserLogged()).not.toBe(true);
+ });
+
+ it("when logging in it is possible to do it", async () => {
+
+ // Mock response
+ mockAxios.onPost().replyOnce(HttpStatusCode.Ok, {
+ "token": "token",
+ "refresh_Token": "refreshToken"
+ });
+
+ // Test
+ const loginData = {
+ "email": "test@email.com",
+ "password": "test"
+ };
+
+ await login(loginData);
+
+ //Check the user is now logged in
+ expect(isUserLogged()).toBe(true);
+ expect(localStorage.getItem("jwtToken")).toBe("token");
+ expect(localStorage.getItem("jwtRefreshToken")).toBe("refreshToken");
+ });
+ });
+
+ describe("when the user is authenticated", () => {
+
+ beforeAll(() => {
+ localStorage.setItem("jwtToken", "token");
+ })
+
+ it("has a stored token", () => {
+ expect(isUserLogged()).toBe(true);
+ });
+ });
+});
+
+
From d73801a20fc545588b894d87388a19201b9da6a9 Mon Sep 17 00:00:00 2001
From: jjgancfer
Date: Tue, 5 Mar 2024 13:44:00 +0100
Subject: [PATCH 09/19] chore: restored some accidentally deleted code
---
webapp/src/components/auth/AuthUtils.js | 19 ++++++++++------
webapp/src/pages/Login.jsx | 29 +++++++++++++++----------
2 files changed, 29 insertions(+), 19 deletions(-)
diff --git a/webapp/src/components/auth/AuthUtils.js b/webapp/src/components/auth/AuthUtils.js
index 422ef22b..ffa02d9f 100644
--- a/webapp/src/components/auth/AuthUtils.js
+++ b/webapp/src/components/auth/AuthUtils.js
@@ -1,7 +1,8 @@
import axios, { HttpStatusCode } from "axios";
export function isUserLogged() {
- return localStorage.getItem("jwtToken") != undefined;
+ console.log(localStorage.getItem("jwtToken"))
+ return localStorage.getItem("jwtToken") !== null;
}
export function saveToken(requestAnswer) {
@@ -11,12 +12,16 @@ export function saveToken(requestAnswer) {
}
export async function login(loginData, onSuccess, onError) {
- let requestAnswer = await axios.post(process.env.REACT_APP_API_ENDPOINT
- + process.env.REACT_APP_LOGIN_ENDPOINT, loginData);
- if (HttpStatusCode.Ok === requestAnswer.status) {
- saveToken(requestAnswer);
- onSuccess();
- } else {
+ try {
+ let requestAnswer = await axios.post(process.env.REACT_APP_API_ENDPOINT
+ + process.env.REACT_APP_LOGIN_ENDPOINT, loginData);
+ if (HttpStatusCode.Ok === requestAnswer.status) {
+ saveToken(requestAnswer);
+ onSuccess();
+ } else {
+ onError();
+ }
+ } catch {
onError();
}
}
\ No newline at end of file
diff --git a/webapp/src/pages/Login.jsx b/webapp/src/pages/Login.jsx
index b6e4f605..7c2f224d 100644
--- a/webapp/src/pages/Login.jsx
+++ b/webapp/src/pages/Login.jsx
@@ -1,18 +1,25 @@
import { Center } from "@chakra-ui/layout";
import { Heading, Input, InputGroup, Stack, InputLeftElement, chakra, Box, Avatar, FormControl, InputRightElement, Text, IconButton } from "@chakra-ui/react";
import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons'
-import axios, { HttpStatusCode } from "axios";
-import React, { useState } from "react";
+import React, {useEffect, useState} from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { FaLock, FaAddressCard } from "react-icons/fa";
import ButtonEf from '../components/ButtonEf';
import '../styles/AppView.css';
+import {isUserLogged, login} from "../components/auth/AuthUtils";
export default function Login() {
- const [hasError, setHasError] = useState(false);
const navigate = useNavigate();
+ const navigateToDashboard = () => {
+ if (isUserLogged()) {
+ navigate("/dashboard");
+ }
+ }
+
+ useEffect(navigateToDashboard);
+ const [hasError, setHasError] = useState(false);
const { t } = useTranslation();
const [showPassword, setShowPassword] = useState(false);
@@ -22,13 +29,11 @@ export default function Login() {
const ChakraFaLock = chakra(FaLock);
const sendLogin = async () => {
- let data = {};
- let response = await axios.post(process.env.API_URL, data);
- if (response.status === HttpStatusCode.Accepted) {
- navigate("/home");
- } else {
- setHasError(true);
- }
+ const loginData = {
+ "email": document.getElementById("user").value,
+ "password": document.getElementById("password").value
+ };
+ await login(loginData, navigateToDashboard, () => setHasError(true));
}
return (
@@ -51,13 +56,13 @@ export default function Login() {
}/>
-
+
}/>
-
+
: } data-testid="togglePasswordButton"/>
From 282e81632312d1d5ad2b5105e6d6d191b44f6fe7 Mon Sep 17 00:00:00 2001
From: jjgancfer
Date: Tue, 5 Mar 2024 13:46:53 +0100
Subject: [PATCH 10/19] feat: solved tests not passing
---
webapp/src/tests/AuthUtils.test.js | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/webapp/src/tests/AuthUtils.test.js b/webapp/src/tests/AuthUtils.test.js
index cad9ac5e..118f1aed 100644
--- a/webapp/src/tests/AuthUtils.test.js
+++ b/webapp/src/tests/AuthUtils.test.js
@@ -17,11 +17,13 @@ describe("Auth Utils tests", () => {
it("when logging in it is possible to do it", async () => {
- // Mock response
+ // Mock axios and the onSuccess and onError functions
mockAxios.onPost().replyOnce(HttpStatusCode.Ok, {
"token": "token",
"refresh_Token": "refreshToken"
});
+ const mockOnSucess = jest.fn();
+ const mockOnError = jest.fn();
// Test
const loginData = {
@@ -29,7 +31,7 @@ describe("Auth Utils tests", () => {
"password": "test"
};
- await login(loginData);
+ await login(loginData, mockOnSucess, mockOnError);
//Check the user is now logged in
expect(isUserLogged()).toBe(true);
From d6beb384b9de50261e50a955d23c80f473ba17bc Mon Sep 17 00:00:00 2001
From: jjgancfer
Date: Tue, 5 Mar 2024 13:54:41 +0100
Subject: [PATCH 11/19] chore: rewrote stored data to include reception time
---
webapp/src/components/auth/AuthUtils.js | 10 ++++++----
webapp/src/tests/AuthUtils.test.js | 8 +++++---
2 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/webapp/src/components/auth/AuthUtils.js b/webapp/src/components/auth/AuthUtils.js
index ffa02d9f..1769422f 100644
--- a/webapp/src/components/auth/AuthUtils.js
+++ b/webapp/src/components/auth/AuthUtils.js
@@ -1,14 +1,16 @@
import axios, { HttpStatusCode } from "axios";
export function isUserLogged() {
- console.log(localStorage.getItem("jwtToken"))
- return localStorage.getItem("jwtToken") !== null;
+ return localStorage.getItem("authData") !== null;
}
export function saveToken(requestAnswer) {
axios.defaults.headers.common["Authorization"] = "Bearer " + requestAnswer.data.token;
- localStorage.setItem("jwtToken", requestAnswer.data.token);
- localStorage.setItem("jwtRefreshToken", requestAnswer.data.refresh_Token)
+ localStorage.setItem("authData", {
+ "jwtToken": requestAnswer.data.token,
+ "refreshToken": requestAnswer.data.refresh_Token,
+ "receivedOnUTC": Date.now()
+ });
}
export async function login(loginData, onSuccess, onError) {
diff --git a/webapp/src/tests/AuthUtils.test.js b/webapp/src/tests/AuthUtils.test.js
index 118f1aed..604baed5 100644
--- a/webapp/src/tests/AuthUtils.test.js
+++ b/webapp/src/tests/AuthUtils.test.js
@@ -35,15 +35,17 @@ describe("Auth Utils tests", () => {
//Check the user is now logged in
expect(isUserLogged()).toBe(true);
- expect(localStorage.getItem("jwtToken")).toBe("token");
- expect(localStorage.getItem("jwtRefreshToken")).toBe("refreshToken");
+ expect(localStorage.getItem("authData").jwtToken).toBe("token");
+ expect(localStorage.getItem("authData").jwtRefreshToken).toBe("refreshToken");
});
});
describe("when the user is authenticated", () => {
beforeAll(() => {
- localStorage.setItem("jwtToken", "token");
+ localStorage.setItem("authData", {
+ "token": "testToken"
+ });
})
it("has a stored token", () => {
From 072f9438f28a720378ad94d53edbaac84ef0a7de Mon Sep 17 00:00:00 2001
From: jjgancfer
Date: Tue, 5 Mar 2024 15:20:44 +0100
Subject: [PATCH 12/19] fix: fix tests not passing
---
webapp/src/components/auth/AuthUtils.js | 18 ++++++++++++------
webapp/src/tests/AuthUtils.test.js | 8 ++++----
2 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/webapp/src/components/auth/AuthUtils.js b/webapp/src/components/auth/AuthUtils.js
index 1769422f..296fb582 100644
--- a/webapp/src/components/auth/AuthUtils.js
+++ b/webapp/src/components/auth/AuthUtils.js
@@ -1,16 +1,22 @@
import axios, { HttpStatusCode } from "axios";
export function isUserLogged() {
- return localStorage.getItem("authData") !== null;
+ return getLoginData().jwtToken !== null;
}
export function saveToken(requestAnswer) {
axios.defaults.headers.common["Authorization"] = "Bearer " + requestAnswer.data.token;
- localStorage.setItem("authData", {
- "jwtToken": requestAnswer.data.token,
- "refreshToken": requestAnswer.data.refresh_Token,
- "receivedOnUTC": Date.now()
- });
+ sessionStorage.setItem("jwtToken", requestAnswer.data.token);
+ sessionStorage.setItem("jwtRefreshToken", requestAnswer.data.refresh_Token);
+ sessionStorage.setItem("jwtReceptionMillis", Date.now().toString());
+}
+
+export function getLoginData() {
+ return {
+ "jwtToken": sessionStorage.getItem("jwtToken"),
+ "jwtRefreshToken": sessionStorage.getItem("jwtRefreshToken"),
+ "jwtReceptionDate": new Date(sessionStorage.getItem("jwtReceptionMillis"))
+ };
}
export async function login(loginData, onSuccess, onError) {
diff --git a/webapp/src/tests/AuthUtils.test.js b/webapp/src/tests/AuthUtils.test.js
index 604baed5..c92c8371 100644
--- a/webapp/src/tests/AuthUtils.test.js
+++ b/webapp/src/tests/AuthUtils.test.js
@@ -1,6 +1,6 @@
import MockAdapter from "axios-mock-adapter";
import axios, { HttpStatusCode } from "axios";
-import { isUserLogged, login } from "components/auth/AuthUtils";
+import { isUserLogged, login} from "components/auth/AuthUtils";
const mockAxios = new MockAdapter(axios);
@@ -35,15 +35,15 @@ describe("Auth Utils tests", () => {
//Check the user is now logged in
expect(isUserLogged()).toBe(true);
- expect(localStorage.getItem("authData").jwtToken).toBe("token");
- expect(localStorage.getItem("authData").jwtRefreshToken).toBe("refreshToken");
+ expect(sessionStorage.getItem("jwtToken")).toBe("token");
+ expect(sessionStorage.getItem("jwtRefreshToken")).toBe("refreshToken");
});
});
describe("when the user is authenticated", () => {
beforeAll(() => {
- localStorage.setItem("authData", {
+ sessionStorage.setItem("authData", {
"token": "testToken"
});
})
From eac244f85c8fabbcf357d4037fdb5b389d003a1b Mon Sep 17 00:00:00 2001
From: jjgancfer
Date: Tue, 5 Mar 2024 16:16:09 +0100
Subject: [PATCH 13/19] chore: add new test that checks the token is saved
---
webapp/src/tests/AuthUtils.test.js | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
diff --git a/webapp/src/tests/AuthUtils.test.js b/webapp/src/tests/AuthUtils.test.js
index c92c8371..7053b41c 100644
--- a/webapp/src/tests/AuthUtils.test.js
+++ b/webapp/src/tests/AuthUtils.test.js
@@ -1,6 +1,6 @@
import MockAdapter from "axios-mock-adapter";
import axios, { HttpStatusCode } from "axios";
-import { isUserLogged, login} from "components/auth/AuthUtils";
+import {isUserLogged, login, saveToken} from "components/auth/AuthUtils";
const mockAxios = new MockAdapter(axios);
@@ -15,7 +15,7 @@ describe("Auth Utils tests", () => {
expect(isUserLogged()).not.toBe(true);
});
- it("when logging in it is possible to do it", async () => {
+ it("can log in", async () => {
// Mock axios and the onSuccess and onError functions
mockAxios.onPost().replyOnce(HttpStatusCode.Ok, {
@@ -35,8 +35,6 @@ describe("Auth Utils tests", () => {
//Check the user is now logged in
expect(isUserLogged()).toBe(true);
- expect(sessionStorage.getItem("jwtToken")).toBe("token");
- expect(sessionStorage.getItem("jwtRefreshToken")).toBe("refreshToken");
});
});
@@ -52,6 +50,24 @@ describe("Auth Utils tests", () => {
expect(isUserLogged()).toBe(true);
});
});
+
+ describe("saving the token", () => {
+ beforeAll(() => {
+ sessionStorage.clear();
+ });
+
+ it ("is saved", () => {
+ let mockResponse = {
+ "data": {
+ "token": "token",
+ "refresh_Token": "refreshToken"
+ }
+ };
+ saveToken(mockResponse);
+ expect(sessionStorage.getItem("jwtToken")).toBe(mockResponse.data.token);
+ expect(sessionStorage.getItem("jwtRefreshToken")).toBe(mockResponse.data.refresh_Token);
+ });
+ });
});
From 7a22e7f3df1d18708809a9fbe9ab4326e1df2b73 Mon Sep 17 00:00:00 2001
From: jjgancfer
Date: Tue, 5 Mar 2024 16:51:13 +0100
Subject: [PATCH 14/19] chore: update sonar config
---
sonar-project.properties | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sonar-project.properties b/sonar-project.properties
index 097e51c4..d274caae 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -11,7 +11,7 @@ sonar.language=js,java
sonar.projectName=wiq_en2b
sonar.coverage.exclusions=**/*.test.js,**/*.test.jsx
-sonar.sources=webapp/src/components,api/src/main/java
+sonar.sources=webapp/src/components,api/src/main/java,webapp/src/components/auth/,webapp/src/pages/
sonar.sourceEncoding=UTF-8
sonar.exclusions=node_modules/**
sonar.javascript.lcov.reportPaths=**/coverage/lcov.info
From 079cb0ae4b32df1af36faea1b431afd5a315f729 Mon Sep 17 00:00:00 2001
From: jjgancfer
Date: Tue, 5 Mar 2024 16:56:52 +0100
Subject: [PATCH 15/19] chore: update sonar config again
---
sonar-project.properties | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sonar-project.properties b/sonar-project.properties
index d274caae..7fe945d9 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -11,7 +11,7 @@ sonar.language=js,java
sonar.projectName=wiq_en2b
sonar.coverage.exclusions=**/*.test.js,**/*.test.jsx
-sonar.sources=webapp/src/components,api/src/main/java,webapp/src/components/auth/,webapp/src/pages/
+sonar.sources=webapp/src/components,api/src/main/java,webapp/src/pages/
sonar.sourceEncoding=UTF-8
sonar.exclusions=node_modules/**
sonar.javascript.lcov.reportPaths=**/coverage/lcov.info
From 420ea0d406fe10e6f520cfd642a85051e1189063 Mon Sep 17 00:00:00 2001
From: jjgancfer
Date: Tue, 5 Mar 2024 17:07:30 +0100
Subject: [PATCH 16/19] chore: minor rewrite due to old test code not being
replaced
---
webapp/src/tests/AuthUtils.test.js | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/webapp/src/tests/AuthUtils.test.js b/webapp/src/tests/AuthUtils.test.js
index 7053b41c..e98e5309 100644
--- a/webapp/src/tests/AuthUtils.test.js
+++ b/webapp/src/tests/AuthUtils.test.js
@@ -8,6 +8,7 @@ describe("Auth Utils tests", () => {
describe("when the user is not authenticated", () => {
beforeEach(() => {
+ sessionStorage.clear();
mockAxios.reset();
});
@@ -41,9 +42,11 @@ describe("Auth Utils tests", () => {
describe("when the user is authenticated", () => {
beforeAll(() => {
- sessionStorage.setItem("authData", {
- "token": "testToken"
- });
+ sessionStorage.setItem("jwtToken", "token");
+ })
+
+ afterEach(() => {
+ sessionStorage.clear();
})
it("has a stored token", () => {
@@ -52,7 +55,7 @@ describe("Auth Utils tests", () => {
});
describe("saving the token", () => {
- beforeAll(() => {
+ beforeEach(() => {
sessionStorage.clear();
});
From 75946c1b26fd713b8d2faa16ac9795c9939f083d Mon Sep 17 00:00:00 2001
From: jjgancfer
Date: Wed, 6 Mar 2024 19:21:54 +0100
Subject: [PATCH 17/19] chore: add key down listener to root page
---
webapp/src/pages/Root.jsx | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/webapp/src/pages/Root.jsx b/webapp/src/pages/Root.jsx
index df44626d..5459fef1 100644
--- a/webapp/src/pages/Root.jsx
+++ b/webapp/src/pages/Root.jsx
@@ -8,6 +8,9 @@ import ButtonEf from '../components/ButtonEf';
export default function Root() {
const navigate = useNavigate();
const { t } = useTranslation();
+ const signup = () => {
+ navigate("/signup");
+ }
return (
{t("session.welcome")}
navigate("/login")}/>
- navigate("/signup")} style={{ cursor: 'pointer' }}>{t("session.account")}
+ {t("session.account")}
);
From a7504699e77180c7ab296467cb35d132199ac789 Mon Sep 17 00:00:00 2001
From: "Dario G. Mori"
Date: Thu, 7 Mar 2024 11:24:45 +0100
Subject: [PATCH 18/19] chore: add webapp to deployment
---
docker-compose.yml | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/docker-compose.yml b/docker-compose.yml
index 600412c7..a7a27429 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -29,6 +29,16 @@ services:
ports:
- "8080:8080"
+ webapp:
+ container_name: webapp-${teamname:-defaultASW}
+ image: ghcr.io/arquisoft/wiq_en2a/webapp:latest
+ profiles: [ "dev", "prod" ]
+ build: ./webapp
+ networks:
+ - mynetwork
+ ports:
+ - "3000:3000"
+
volumes:
postgres_data:
From ba8819c275f6b5ebc0aa9231c6c2ba5c4faaf86b Mon Sep 17 00:00:00 2001
From: Gonzalo Alonso Fernandez
Date: Fri, 8 Mar 2024 11:03:44 +0100
Subject: [PATCH 19/19] fix: fixing the loginTests to work correctly.
---
webapp/src/tests/Login.test.js | 74 +++++++++++++++-------------------
1 file changed, 33 insertions(+), 41 deletions(-)
diff --git a/webapp/src/tests/Login.test.js b/webapp/src/tests/Login.test.js
index 1a20e7fc..0be238e5 100644
--- a/webapp/src/tests/Login.test.js
+++ b/webapp/src/tests/Login.test.js
@@ -1,64 +1,56 @@
import React from 'react';
-import { render, fireEvent, waitFor, getByTestId, getAllByTestId } from '@testing-library/react';
-import axios from 'axios';
+import { render, fireEvent, waitFor } from '@testing-library/react';
+import '@testing-library/jest-dom/extend-expect';
import { MemoryRouter } from 'react-router';
-import Signup from '../pages/Signup';
+import Login from '../pages/Login';
+import { login as mockLogin } from '../components/auth/AuthUtils';
-describe('Signup Component', () => {
+jest.mock('../components/auth/AuthUtils', () => ({
+ isUserLogged: jest.fn(),
+ login: jest.fn(),
+}));
+describe('Login Component', () => {
it('renders form elements correctly', () => {
- const { getByPlaceholderText } = render();
+ const { getByPlaceholderText, getByTestId } = render();
expect(getByPlaceholderText('session.email')).toBeInTheDocument();
- expect(getByPlaceholderText('session.username')).toBeInTheDocument();
expect(getByPlaceholderText('session.password')).toBeInTheDocument();
- expect(getByPlaceholderText('session.confirm_password')).toBeInTheDocument();
- expect(getByTestId(document.body, 'Sign up')).toBeInTheDocument();
+ expect(getByTestId('Login')).toBeInTheDocument();
});
- it('toggles password visibility', () => {
- const { getByPlaceholderText } = render();
+ it('toggles password visibility', () => {
+ const { getByLabelText, getByPlaceholderText } = render();
+
+ // Initially password should be hidden
const passwordInput = getByPlaceholderText('session.password');
- const confirmPasswordInput = getByPlaceholderText('session.confirm_password');
- const showPasswordButtons = getAllByTestId(document.body, 'show-confirm-password-button');
-
- fireEvent.click(showPasswordButtons[0]);
- fireEvent.click(showPasswordButtons[1]);
-
- expect(passwordInput.getAttribute('type')).toBe('text');
- expect(confirmPasswordInput.getAttribute('type')).toBe('text');
+ expect(passwordInput).toHaveAttribute('type', 'password');
+
+ // Click on the toggle password button
+ const toggleButton = getByLabelText('Shows or hides the password');
+ fireEvent.click(toggleButton);
+
+ // Password should now be visible
+ expect(passwordInput).toHaveAttribute('type', 'text');
});
- it('submits form data correctly', async () => {
- const axiosMock = jest.spyOn(axios, 'post');
- axiosMock.mockResolvedValueOnce({ status: 202 }); // Accepted status code
-
- // Render the Signup component
- const { getByPlaceholderText } = render();
-
- // Get form elements and submit button by their text and placeholder values
+ it('calls login function with correct credentials on submit', async () => {
+ const { getByPlaceholderText, getByTestId } = render(, { wrapper: MemoryRouter });
const emailInput = getByPlaceholderText('session.email');
- const usernameInput = getByPlaceholderText('session.username');
const passwordInput = getByPlaceholderText('session.password');
- const signUpButton = getByTestId(document.body, 'Sign up');
+ const loginButton = getByTestId('Login');
- // Fill out the form with valid data and submit it
fireEvent.change(emailInput, { target: { value: 'test@example.com' } });
- fireEvent.change(usernameInput, { target: { value: 'testuser' } });
- fireEvent.change(passwordInput, { target: { value: 'password' } });
- fireEvent.click(signUpButton);
+ fireEvent.change(passwordInput, { target: { value: 'password123' } });
+ fireEvent.click(loginButton);
- // Check if the form data was sent correctly
await waitFor(() => {
- expect(axiosMock).toHaveBeenCalledWith(process.env.API_URL, {
- email: 'test@example.com',
- username: 'testuser',
- password: 'password'
- });
- expect(axiosMock).toHaveBeenCalledTimes(1);
+ expect(mockLogin).toHaveBeenCalledWith(
+ { email: 'test@example.com', password: 'password123' },
+ expect.any(Function),
+ expect.any(Function)
+ );
});
-
- axiosMock.mockRestore();
});
});
\ No newline at end of file