Skip to content
Snippets Groups Projects
Commit 61c664e8 authored by Leander Tolksdorf's avatar Leander Tolksdorf
Browse files

add login and logout feature

parent 50e4c015
No related branches found
No related tags found
No related merge requests found
type StaffLoginParameters = {
email: string;
password: string;
};
export async function staffLogin({ email, password }: StaffLoginParameters) {
const response = await fetch("/api/login", {
method: "POST",
body: JSON.stringify({ email: email, password: password }),
headers: {
"Content-Type": "application/json",
},
});
return response.json();
}
export async function getCurrentUser() {
const response = await fetch("/api/user", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
return response;
}
import { Container, Nav, Navbar } from "react-bootstrap"; import { Container, Nav, Navbar } from "react-bootstrap";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Outlet, useLocation, NavLink } from "react-router-dom"; import { Outlet, useLocation, NavLink, useNavigate } from "react-router-dom";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { useEffect } from "react";
import { getCurrentUser } from "../api/user";
function StaffLayout() { function StaffLayout() {
const { t } = useTranslation(); const { t } = useTranslation();
const {pathname} = useLocation(); const { pathname } = useLocation();
return ( const navigate = useNavigate();
<div className="h-100 w-100">
<Helmet>
<title>{t(`routes.${pathname}`)}</title>
</Helmet>
<Navbar sticky="top" className="bg-secondary" variant="dark" expand="lg">
<Container>
<Navbar.Brand className="fw-bold" style={{minWidth: "200px"}}>{t(`routes.${pathname}`)}</Navbar.Brand>
<Navbar.Toggle />
<Navbar.Collapse>
<Nav className="mr-auto d-flex justify-content-between w-100">
<div className="d-flex">
<Nav activeKey={pathname}>
<Nav.Link as={NavLink} to="/staff/overview">{t("staffNav.buttonBoatOverview")}</Nav.Link>
<Nav.Link as={NavLink} to="/staff/manage">{t("staffNav.buttonManageBoats")}</Nav.Link>
<Nav.Link as={NavLink} to="/staff/boattypes">{t("staffNav.buttonBoatTypes")}</Nav.Link>
<Nav.Link as={NavLink} to="/staff/sports">{t("staffNav.buttonSports")}</Nav.Link>
<Nav.Link as={NavLink} to="/staff/statistics">{t("staffNav.buttonStatistics")}</Nav.Link>
</Nav>
</div>
<div>
<Nav.Link>{t("staffNav.buttonLogout")}</Nav.Link>
</div> async function validateCurrentUser() {
</Nav> try {
</Navbar.Collapse> const response = await getCurrentUser();
</Container> if (response.status !== 200) {
</Navbar> navigate("/login");
<Outlet /> }
</div> } catch (e) {}
); }
async function logout() {
try {
const response = await fetch("/api/logout", {
method: "POST",
});
navigate("/");
} catch (e) {
console.log(e);
}
}
useEffect(() => {
validateCurrentUser();
}, []);
return (
<div className="h-100 w-100">
<Helmet>
<title>{t(`routes.${pathname}`)}</title>
</Helmet>
<Navbar sticky="top" className="bg-secondary" variant="dark" expand="lg">
<Container>
<Navbar.Brand className="fw-bold" style={{ minWidth: "200px" }}>
{t(`routes.${pathname}`)}
</Navbar.Brand>
<Navbar.Toggle />
<Navbar.Collapse>
<Nav className="mr-auto d-flex justify-content-between w-100">
<div className="d-flex">
<Nav activeKey={pathname}>
<Nav.Link as={NavLink} to="/staff/overview">
{t("staffNav.buttonBoatOverview")}
</Nav.Link>
<Nav.Link as={NavLink} to="/staff/manage">
{t("staffNav.buttonManageBoats")}
</Nav.Link>
<Nav.Link as={NavLink} to="/staff/boattypes">
{t("staffNav.buttonBoatTypes")}
</Nav.Link>
<Nav.Link as={NavLink} to="/staff/sports">
{t("staffNav.buttonSports")}
</Nav.Link>
<Nav.Link as={NavLink} to="/staff/statistics">
{t("staffNav.buttonStatistics")}
</Nav.Link>
</Nav>
</div>
<div>
<Nav.Link
onClick={() => {
logout();
}}
>
{t("staffNav.buttonLogout")}
</Nav.Link>
</div>
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
<Outlet />
</div>
);
} }
export default StaffLayout; export default StaffLayout;
import { useEffect } from "react";
import { Button, Col, Container, Form, Row } from "react-bootstrap"; import { Button, Col, Container, Form, Row } from "react-bootstrap";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import validator from "validator"; import validator from "validator";
import { staffLogin } from "../../api/auth";
import { getCurrentUser } from "../../api/user";
import Divider from "../../components/layout/Divider"; import Divider from "../../components/layout/Divider";
import Modal from "../../components/Modal"; import Modal from "../../components/Modal";
...@@ -11,94 +15,113 @@ type FormData = { ...@@ -11,94 +15,113 @@ type FormData = {
}; };
function Login() { function Login() {
const { const {
control, control,
handleSubmit, handleSubmit,
formState: { errors }, formState: { errors },
}= useForm<FormData>({ } = useForm<FormData>({
mode: "onBlur"}); mode: "onBlur",
const { t } = useTranslation(); });
const { t } = useTranslation();
const navigate = useNavigate();
const onSubmit = (data: FormData) => { const onSubmit = async (data: FormData) => {
try { try {
/** const response = await staffLogin(data);
* Logic to handle authentication. Should be separated into a service in another file. if (response.success) {
* await login(data); navigate("/staff", { replace: true });
*
*
* navigate("/boatManager");
*/
} catch (e) {
alert("error");
} }
}; } catch (e) {
// TODO: Error handling
}
};
return ( async function validateCurrentUser() {
<Container className="position-absolute top-50 start-50 translate-middle"> try {
<Row> const response = await getCurrentUser();
<Col xs={{ span: 10, offset: 1 }}> if (response.status == 200) {
<Modal> navigate("/staff");
<h1 className="text-center p-1">{t("staffLogin.title")}</h1> }
<p className="text-center">{t("staffLogin.subtitle")}</p> } catch (e) {}
<Divider /> }
<Form onSubmit={handleSubmit(onSubmit)}>
<Controller useEffect(() => {
name="email" validateCurrentUser();
control={control} }, []);
defaultValue=""
render={({ field }) => (
<div className="mb-2">
<Form.Label>{t("staffLogin.labelEmail")}</Form.Label>
<Form.Control type="email" {...field} isInvalid={!!errors.email}/>
<Form.Control.Feedback type="invalid">
{errors.email?.message}
</Form.Control.Feedback>
</div>
)}
rules={{
required: {
value: true,
message: t("staffLogin.messages.required", {
val: t("staffLogin.labelEmail"),
}),
},
validate: (value: string) =>
validator.isEmail(value) ||
t("staffLogin.messages.invalidEmail").toString(),
}}
/>
<Controller
name="password"
control={control}
defaultValue=""
render={({ field }) => (
<div className="mb-2">
<Form.Label>{t("staffLogin.labelPassword")}</Form.Label>
<Form.Control type="password" {...field} isInvalid={!!errors.password}/>
<Form.Control.Feedback type="invalid">
{errors.password?.message}
</Form.Control.Feedback>
</div>
)}
rules={{
required: {
value: true,
message: t("staffLogin.messages.required", {
val: t("staffLogin.labelPassword"),
}),
}
}}
/>
<Button type="submit" variant="secondary" className="mt-2 w-100">
{t("staffLogin.buttonSignIn")}
</Button>
</Form>
</Modal>
</Col>
</Row>
</Container>
);
return (
<Container className="position-absolute top-50 start-50 translate-middle">
<Row>
<Col xs={{ span: 10, offset: 1 }}>
<Modal>
<h1 className="text-center p-1">{t("staffLogin.title")}</h1>
<p className="text-center">{t("staffLogin.subtitle")}</p>
<Divider />
<Form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="email"
control={control}
defaultValue=""
render={({ field }) => (
<div className="mb-2">
<Form.Label>{t("staffLogin.labelEmail")}</Form.Label>
<Form.Control
type="email"
{...field}
isInvalid={!!errors.email}
/>
<Form.Control.Feedback type="invalid">
{errors.email?.message}
</Form.Control.Feedback>
</div>
)}
rules={{
required: {
value: true,
message: t("staffLogin.messages.required", {
val: t("staffLogin.labelEmail"),
}),
},
validate: (value: string) =>
validator.isEmail(value) ||
t("staffLogin.messages.invalidEmail").toString(),
}}
/>
<Controller
name="password"
control={control}
defaultValue=""
render={({ field }) => (
<div className="mb-2">
<Form.Label>{t("staffLogin.labelPassword")}</Form.Label>
<Form.Control
type="password"
{...field}
isInvalid={!!errors.password}
/>
<Form.Control.Feedback type="invalid">
{errors.password?.message}
</Form.Control.Feedback>
</div>
)}
rules={{
required: {
value: true,
message: t("staffLogin.messages.required", {
val: t("staffLogin.labelPassword"),
}),
},
}}
/>
<Button type="submit" variant="secondary" className="mt-2 w-100">
{t("staffLogin.buttonSignIn")}
</Button>
</Form>
</Modal>
</Col>
</Row>
</Container>
);
} }
export default Login; export default Login;
...@@ -30,7 +30,13 @@ const authLoginController = async (req: Request, res: Response) => { ...@@ -30,7 +30,13 @@ const authLoginController = async (req: Request, res: Response) => {
const token = jwt.sign(payload, envVars.JWT_SECRET); const token = jwt.sign(payload, envVars.JWT_SECRET);
return res.cookie("token", token).json({ success: true }); return res
.cookie("token", token, {
httpOnly: true,
secure: process.env.NOVE_ENV === "production",
path: "/",
})
.json({ success: true });
} else { } else {
return res.status(401).json({ success: false, error: "invalidPassword" }); return res.status(401).json({ success: false, error: "invalidPassword" });
} }
...@@ -39,9 +45,20 @@ const authLoginController = async (req: Request, res: Response) => { ...@@ -39,9 +45,20 @@ const authLoginController = async (req: Request, res: Response) => {
return res.status(500).json({ success: false, error: "serverError" }); return res.status(500).json({ success: false, error: "serverError" });
} }
}; };
const authLogOutController = async (req: Request, res: Response) => {
return res
.cookie("token", null, {
httpOnly: true,
secure: process.env.NOVE_ENV === "production",
path: "/",
expires: new Date(0),
})
.json({ success: true });
};
const authControllers = { const authControllers = {
authLoginController, authLoginController,
authLogOutController,
}; };
export default authControllers; export default authControllers;
...@@ -13,5 +13,6 @@ authRouter.post( ...@@ -13,5 +13,6 @@ authRouter.post(
handleValidationResult, handleValidationResult,
authControllers.authLoginController authControllers.authLoginController
); );
authRouter.post("/api/logout/", authControllers.authLogOutController);
export default authRouter; export default authRouter;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment