Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v0.2.0 #93

Merged
merged 27 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a727f02
feat: add initial version of auth in webapp (untested)
jjgancfer Feb 27, 2024
2ce39de
Merge remote-tracking branch 'origin/feat/webapp/new-login' into feat…
jjgancfer Mar 1, 2024
95227ba
chore: updated .gitignore to include .env
jjgancfer Mar 1, 2024
a91f8f4
feat: add tentatively working support for login (cannot continue due …
jjgancfer Mar 1, 2024
f3bc976
chore: some refactor, as well as minor error fix
jjgancfer Mar 2, 2024
c622d8a
Merge remote-tracking branch 'origin/fix/cors-configuration' into fea…
jjgancfer Mar 3, 2024
4009072
feat: add tentative support for JWT auth (routes are yet to be protec…
jjgancfer Mar 3, 2024
f27c266
chore: updated endpoint of signup request
jjgancfer Mar 3, 2024
64ee43c
Merge branch 'develop' into feat/webapp/jwt-auth
jjgancfer Mar 5, 2024
4b193d3
chore: rewrite to make use of async/await
jjgancfer Mar 5, 2024
4b1be67
feat: add AuthUtils tests
jjgancfer Mar 5, 2024
d73801a
chore: restored some accidentally deleted code
jjgancfer Mar 5, 2024
282e816
feat: solved tests not passing
jjgancfer Mar 5, 2024
d6beb38
chore: rewrote stored data to include reception time
jjgancfer Mar 5, 2024
072f943
fix: fix tests not passing
jjgancfer Mar 5, 2024
eac244f
chore: add new test that checks the token is saved
jjgancfer Mar 5, 2024
9c6f75a
Merge branch 'develop' into feat/webapp/jwt-auth
jjgancfer Mar 5, 2024
7a22e7f
chore: update sonar config
jjgancfer Mar 5, 2024
079cb0a
chore: update sonar config again
jjgancfer Mar 5, 2024
420ea0d
chore: minor rewrite due to old test code not being replaced
jjgancfer Mar 5, 2024
396380c
Merge remote-tracking branch 'origin/develop' into feat/webapp/jwt-auth
Toto-hitori Mar 6, 2024
75946c1
chore: add key down listener to root page
jjgancfer Mar 6, 2024
d5aebf5
Merge pull request #83 from Arquisoft/feat/webapp/jwt-auth
gony02 Mar 7, 2024
f379c41
Merge branch 'master' into develop
Toto-hitori Mar 7, 2024
a750469
chore: add webapp to deployment
Toto-hitori Mar 7, 2024
ba8819c
fix: fixing the loginTests to work correctly.
gony02 Mar 8, 2024
c81dd57
Merge pull request #94 from Arquisoft/fix-loginTests
Toto-hitori Mar 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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/pages/
sonar.sourceEncoding=UTF-8
sonar.exclusions=node_modules/**,**/quizapi/commons/utils/**,**/quizapi/commons/exceptions/**,**/quizapi/auth/jwt/**,**/quizapi/**/dtos/**
sonar.javascript.lcov.reportPaths=**/coverage/lcov.info
Expand Down
1 change: 0 additions & 1 deletion webapp/.env

This file was deleted.

1 change: 1 addition & 0 deletions webapp/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
Expand Down
35 changes: 35 additions & 0 deletions webapp/src/components/auth/AuthUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import axios, { HttpStatusCode } from "axios";

export function isUserLogged() {
return getLoginData().jwtToken !== null;
}

export function saveToken(requestAnswer) {
axios.defaults.headers.common["Authorization"] = "Bearer " + requestAnswer.data.token;
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) {
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();
}
}
2 changes: 1 addition & 1 deletion webapp/src/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ export default i18n.use(Backend)
.use(initReactI18next)
.init({
fallbackLng: "en",
debug: true
debug: false
})
3 changes: 2 additions & 1 deletion webapp/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import reportWebVitals from './reportWebVitals';
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(
<ChakraProvider>
<React.StrictMode>
Expand Down
147 changes: 76 additions & 71 deletions webapp/src/pages/Login.jsx
Original file line number Diff line number Diff line change
@@ -1,72 +1,77 @@
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 { 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 (
<Center display={"flex"} flexDirection={"column"} w={"100wh"} h={"100vh"}
bg={"blue.50"} justifyContent={"center"} alignItems={"center"}>
<Stack flexDir={"column"} mb="2" justifyContent="center" alignItems={"center"}>
<Avatar bg="blue.500" />
<Heading as="h2" color="blue.400">{ t("common.login")}</Heading>
{
!hasError ?
<></> :
<Center bgColor={"#FFA98A"} margin={"1vh 0vw"} padding={"1vh 0vw"}
color={"#FF0500"} border={"0.1875em solid #FF0500"}
borderRadius={"0.75em"} maxW={"100%"} minW={"30%"}>
<Text>{t("error.login")}</Text>
</Center>
}
<Box minW={{md: "400px"}}>
<Stack spacing={4} p="1rem" backgroundColor="whiteAlpha.900" boxShadow="md">
<FormControl>
<InputGroup>
<InputLeftElement children={<ChakraFaCardAlt color="gray.300" />}/>
<Input type="text" placeholder={t("session.email")} />
</InputGroup>
</FormControl>
<FormControl>
<InputGroup>
<InputLeftElement children={<ChakraFaLock color="gray.300" />}/>
<Input type={showPassword ? "text" : "password"} placeholder={t("session.password")}/>
<InputRightElement>
<IconButton h="1.75rem" size="sm" onClick={changeShowP} aria-label='Shows or hides the password' icon={showPassword ? <ViewOffIcon/> : <ViewIcon/>} data-testid="togglePasswordButton"/>
</InputRightElement>
</InputGroup>
</FormControl>
<ButtonEf dataTestId={"Login"} variant={"solid"} colorScheme={"blue"} text={t("common.login")} onClick={sendLogin}/>
</Stack>
</Box>
</Stack>
</Center>
);
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 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 navigate = useNavigate();
const navigateToDashboard = () => {
if (isUserLogged()) {
navigate("/dashboard");
}
}

useEffect(navigateToDashboard);
const [hasError, setHasError] = useState(false);
const { t } = useTranslation();

const [showPassword, setShowPassword] = useState(false);
const changeShowP = () => setShowPassword(!showPassword);

const ChakraFaCardAlt = chakra(FaAddressCard);
const ChakraFaLock = chakra(FaLock);

const sendLogin = async () => {
const loginData = {
"email": document.getElementById("user").value,
"password": document.getElementById("password").value
};
await login(loginData, navigateToDashboard, () => setHasError(true));
}

return (
<Center display={"flex"} flexDirection={"column"} w={"100wh"} h={"100vh"}
bg={"blue.50"} justifyContent={"center"} alignItems={"center"}>
<Stack flexDir={"column"} mb="2" justifyContent="center" alignItems={"center"}>
<Avatar bg="blue.500" />
<Heading as="h2" color="blue.400">{ t("common.login")}</Heading>
{
!hasError ?
<></> :
<Center bgColor={"#FFA98A"} margin={"1vh 0vw"} padding={"1vh 0vw"}
color={"#FF0500"} border={"0.1875em solid #FF0500"}
borderRadius={"0.75em"} maxW={"100%"} minW={"30%"}>
<Text>{t("error.login")}</Text>
</Center>
}
<Box minW={{md: "400px"}}>
<Stack spacing={4} p="1rem" backgroundColor="whiteAlpha.900" boxShadow="md">
<FormControl>
<InputGroup>
<InputLeftElement children={<ChakraFaCardAlt color="gray.300" />}/>
<Input type="text" id={"user"} placeholder={t("session.email")} />
</InputGroup>
</FormControl>
<FormControl>
<InputGroup>
<InputLeftElement children={<ChakraFaLock color="gray.300" />}/>
<Input type={showPassword ? "text" : "password"} id={"password"} placeholder={t("session.password")}/>
<InputRightElement>
<IconButton h="1.75rem" size="sm" onClick={changeShowP} aria-label='Shows or hides the password' icon={showPassword ? <ViewOffIcon/> : <ViewIcon/>} data-testid="togglePasswordButton"/>
</InputRightElement>
</InputGroup>
</FormControl>
<ButtonEf dataTestId={"Login"} variant={"solid"} colorScheme={"blue"} text={t("common.login")} onClick={sendLogin}/>
</Stack>
</Box>
</Stack>
</Center>
);
}
5 changes: 4 additions & 1 deletion webapp/src/pages/Root.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import ButtonEf from '../components/ButtonEf';
export default function Root() {
const navigate = useNavigate();
const { t } = useTranslation();
const signup = () => {
navigate("/signup");
}

return (
<Center display={"flex"} flexDirection={"column"} w={"100wh"} h={"100vh"}
Expand All @@ -16,7 +19,7 @@ export default function Root() {
<p>{t("session.welcome")}</p>
<Stack spacing={4} p="3rem">
<ButtonEf dataTestId={"Login"} variant={"solid"} colorScheme={"blue"} text={t("common.login")} onClick={() => navigate("/login")}/>
<p onClick={() => navigate("/signup")} style={{ cursor: 'pointer' }}>{t("session.account")}</p>
<p onClick={signup} onKeyDown={signup} style={{ cursor: 'pointer' }}>{t("session.account")}</p>
</Stack>
</Center>
);
Expand Down
76 changes: 76 additions & 0 deletions webapp/src/tests/AuthUtils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import MockAdapter from "axios-mock-adapter";
import axios, { HttpStatusCode } from "axios";
import {isUserLogged, login, saveToken} from "components/auth/AuthUtils";

const mockAxios = new MockAdapter(axios);

describe("Auth Utils tests", () => {
describe("when the user is not authenticated", () => {

beforeEach(() => {
sessionStorage.clear();
mockAxios.reset();
});

it("does not have a stored token", () => {
expect(isUserLogged()).not.toBe(true);
});

it("can log in", async () => {

// 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 = {
"email": "[email protected]",
"password": "test"
};

await login(loginData, mockOnSucess, mockOnError);

//Check the user is now logged in
expect(isUserLogged()).toBe(true);
});
});

describe("when the user is authenticated", () => {

beforeAll(() => {
sessionStorage.setItem("jwtToken", "token");
})

afterEach(() => {
sessionStorage.clear();
})

it("has a stored token", () => {
expect(isUserLogged()).toBe(true);
});
});

describe("saving the token", () => {
beforeEach(() => {
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);
});
});
});


Loading