Přesun autentizace na server
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
"react": "^18.2.0",
|
||||
"react-bootstrap": "^2.7.2",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-jwt": "^1.2.0",
|
||||
"react-modal": "^3.16.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-select-search": "^4.1.6",
|
||||
@@ -54,4 +55,4 @@
|
||||
"devDependencies": {
|
||||
"prettier": "^2.8.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
import { PizzaOrder } from "./Types";
|
||||
import { getBaseUrl } from "./Utils";
|
||||
import { getBaseUrl, getToken } from "./Utils";
|
||||
|
||||
async function request<TResponse>(
|
||||
url: string,
|
||||
config: RequestInit = {}
|
||||
): Promise<TResponse> {
|
||||
if (!config.headers) {
|
||||
config.headers = {};
|
||||
}
|
||||
config.headers["Authorization"] = `Bearer ${getToken()}`;
|
||||
return fetch(getBaseUrl() + url, config).then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText);
|
||||
@@ -18,8 +22,8 @@ const api = {
|
||||
post: <TBody extends BodyInit, TResponse>(url: string, body: TBody) => request<TResponse>(url, { method: 'POST', body, headers: { 'Content-Type': 'application/json' } }),
|
||||
}
|
||||
|
||||
export const getQrUrl = (login: string) => {
|
||||
return `${getBaseUrl()}/api/qr?login=${login}`;
|
||||
export const getQrUrl = () => {
|
||||
return `${getBaseUrl()}/api/qr`;
|
||||
}
|
||||
|
||||
export const getData = async () => {
|
||||
@@ -34,42 +38,46 @@ export const getPizzy = async () => {
|
||||
return await api.get<any>('/api/pizza');
|
||||
}
|
||||
|
||||
export const createPizzaDay = async (creator) => {
|
||||
return await api.post<any, any>('/api/createPizzaDay', JSON.stringify({ creator }));
|
||||
export const createPizzaDay = async () => {
|
||||
return await api.post<any, any>('/api/createPizzaDay', undefined);
|
||||
}
|
||||
|
||||
export const deletePizzaDay = async (login) => {
|
||||
return await api.post<any, any>('/api/deletePizzaDay', JSON.stringify({ login }));
|
||||
export const deletePizzaDay = async () => {
|
||||
return await api.post<any, any>('/api/deletePizzaDay', undefined);
|
||||
}
|
||||
|
||||
export const lockPizzaDay = async (login) => {
|
||||
return await api.post<any, any>('/api/lockPizzaDay', JSON.stringify({ login }));
|
||||
export const lockPizzaDay = async () => {
|
||||
return await api.post<any, any>('/api/lockPizzaDay', undefined);
|
||||
}
|
||||
|
||||
export const unlockPizzaDay = async (login) => {
|
||||
return await api.post<any, any>('/api/unlockPizzaDay', JSON.stringify({ login }));
|
||||
export const unlockPizzaDay = async () => {
|
||||
return await api.post<any, any>('/api/unlockPizzaDay', undefined);
|
||||
}
|
||||
|
||||
export const finishOrder = async (login) => {
|
||||
return await api.post<any, any>('/api/finishOrder', JSON.stringify({ login }));
|
||||
export const finishOrder = async () => {
|
||||
return await api.post<any, any>('/api/finishOrder', undefined);
|
||||
}
|
||||
|
||||
export const finishDelivery = async (login, bankAccount, bankAccountHolder) => {
|
||||
return await api.post<any, any>('/api/finishDelivery', JSON.stringify({ login, bankAccount, bankAccountHolder }));
|
||||
export const finishDelivery = async (bankAccount, bankAccountHolder) => {
|
||||
return await api.post<any, any>('/api/finishDelivery', JSON.stringify({ bankAccount, bankAccountHolder }));
|
||||
}
|
||||
|
||||
export const updateChoice = async (name: string, choice: number | null) => {
|
||||
return await api.post<any, any>('/api/updateChoice', JSON.stringify({ name, choice }));
|
||||
export const updateChoice = async (choice: number | null) => {
|
||||
return await api.post<any, any>('/api/updateChoice', JSON.stringify({ choice }));
|
||||
}
|
||||
|
||||
export const addPizza = async (login: string, pizzaIndex: number, pizzaSizeIndex: number) => {
|
||||
return await api.post<any, any>('/api/addPizza', JSON.stringify({ login, pizzaIndex, pizzaSizeIndex }));
|
||||
export const addPizza = async (pizzaIndex: number, pizzaSizeIndex: number) => {
|
||||
return await api.post<any, any>('/api/addPizza', JSON.stringify({ pizzaIndex, pizzaSizeIndex }));
|
||||
}
|
||||
|
||||
export const removePizza = async (login: string, pizzaOrder: PizzaOrder) => {
|
||||
return await api.post<any, any>('/api/removePizza', JSON.stringify({ login, pizzaOrder }));
|
||||
export const removePizza = async (pizzaOrder: PizzaOrder) => {
|
||||
return await api.post<any, any>('/api/removePizza', JSON.stringify({ pizzaOrder }));
|
||||
}
|
||||
|
||||
export const updateNote = async (login: string, note?: string) => {
|
||||
return await api.post<any, any>('/api/updateNote', JSON.stringify({ login, note }));
|
||||
}
|
||||
export const updateNote = async (note?: string) => {
|
||||
return await api.post<any, any>('/api/updateNote', JSON.stringify({ note }));
|
||||
}
|
||||
|
||||
export const login = async (login: string) => {
|
||||
return await api.post<any, any>('/api/login', JSON.stringify({ login }));
|
||||
}
|
||||
|
||||
@@ -31,8 +31,11 @@ function App() {
|
||||
const choiceRef = useRef<HTMLSelectElement>(null);
|
||||
const poznamkaRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// Prvotní načtení aktuálního stavu
|
||||
// Načtení dat po přihlášení
|
||||
useEffect(() => {
|
||||
if (!auth || !auth.login) {
|
||||
return
|
||||
}
|
||||
getPizzy().then(pizzy => {
|
||||
setPizzy(pizzy);
|
||||
});
|
||||
@@ -42,7 +45,7 @@ function App() {
|
||||
getFood().then(food => {
|
||||
setFood(food);
|
||||
})
|
||||
}, []);
|
||||
}, [auth, auth?.login]);
|
||||
|
||||
// Registrace socket eventů
|
||||
useEffect(() => {
|
||||
@@ -91,13 +94,13 @@ function App() {
|
||||
const changeChoice = async (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const index = Object.values(Locations).indexOf(event.target.value as unknown as Locations);
|
||||
if (auth?.login) {
|
||||
await updateChoice(auth.login, index > -1 ? index : null);
|
||||
await updateChoice(index > -1 ? index : null);
|
||||
}
|
||||
}
|
||||
|
||||
const removeChoice = async (key: string) => {
|
||||
if (auth?.login) {
|
||||
await updateChoice(auth.login, null);
|
||||
await updateChoice(null);
|
||||
if (choiceRef?.current?.value) {
|
||||
choiceRef.current.value = "";
|
||||
}
|
||||
@@ -126,24 +129,20 @@ function App() {
|
||||
const s = value.split('|');
|
||||
const pizzaIndex = Number.parseInt(s[0]);
|
||||
const pizzaSizeIndex = Number.parseInt(s[1]);
|
||||
await addPizza(auth.login, pizzaIndex, pizzaSizeIndex);
|
||||
await addPizza(pizzaIndex, pizzaSizeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
const handlePizzaDelete = async (pizzaOrder: PizzaOrder) => {
|
||||
if (auth?.login) {
|
||||
await removePizza(auth?.login, pizzaOrder);
|
||||
}
|
||||
await removePizza(pizzaOrder);
|
||||
}
|
||||
|
||||
const handlePoznamkaChange = async () => {
|
||||
if (auth?.login) {
|
||||
if (poznamkaRef.current?.value && poznamkaRef.current.value.length > 100) {
|
||||
alert("Poznámka může mít maximálně 100 znaků");
|
||||
return;
|
||||
}
|
||||
updateNote(auth.login, poznamkaRef.current?.value);
|
||||
if (poznamkaRef.current?.value && poznamkaRef.current.value.length > 100) {
|
||||
alert("Poznámka může mít maximálně 100 znaků");
|
||||
return;
|
||||
}
|
||||
updateNote(poznamkaRef.current?.value);
|
||||
}
|
||||
|
||||
// const addToCart = async () => {
|
||||
@@ -205,10 +204,7 @@ function App() {
|
||||
<Alert variant={'primary'}>
|
||||
Poslední změny:
|
||||
<ul>
|
||||
<li>Nová žárovka zatím funguje</li>
|
||||
<li>Funkční generování a zobrazení QR kódů pro Pizza day</li>
|
||||
<li>Možnost zadat k Pizza day objednávce poznámku</li>
|
||||
<li>Zbavení se Food API, přepsání a zahrnutí parseru do serveru</li>
|
||||
<li>Zavedení JWT, přesun autentizace na server</li>
|
||||
</ul>
|
||||
</Alert>
|
||||
<h1 className='title'>Dnes je {data.date}</h1>
|
||||
@@ -260,7 +256,7 @@ function App() {
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<p>Pro dnešní den není aktuálně založen Pizza day.</p>
|
||||
<Button onClick={async () => {
|
||||
await createPizzaDay(auth.login);
|
||||
await createPizzaDay();
|
||||
}}>Založit Pizza day</Button>
|
||||
</div>
|
||||
}
|
||||
@@ -279,10 +275,10 @@ function App() {
|
||||
data.pizzaDay.creator === auth.login &&
|
||||
<>
|
||||
<Button className='danger mb-3' title="Smaže kompletně pizza day, včetně dosud zadaných objednávek." onClick={async () => {
|
||||
await deletePizzaDay(auth.login);
|
||||
await deletePizzaDay();
|
||||
}}>Smazat Pizza day</Button>
|
||||
<Button className='mb-3' style={{ marginLeft: '20px' }} title={noOrders ? "Nelze uzamknout - neexistuje žádná objednávka" : "Zamezí přidávat/odebírat objednávky. Použij před samotným objednáním, aby již nemohlo docházet ke změnám."} disabled={noOrders} onClick={async () => {
|
||||
await lockPizzaDay(auth.login);
|
||||
await lockPizzaDay();
|
||||
}}>Uzamknout</Button>
|
||||
</>
|
||||
}
|
||||
@@ -295,13 +291,13 @@ function App() {
|
||||
{data.pizzaDay.creator === auth.login &&
|
||||
<>
|
||||
<Button className='danger mb-3' title="Umožní znovu editovat objednávky." onClick={async () => {
|
||||
await unlockPizzaDay(auth.login);
|
||||
await unlockPizzaDay();
|
||||
}}>Odemknout</Button>
|
||||
{/* <Button className='danger mb-3' style={{ marginLeft: '20px' }} onClick={async () => {
|
||||
await addToCart();
|
||||
}}>Přidat vše do košíku</Button> */}
|
||||
<Button className='danger mb-3' style={{ marginLeft: '20px' }} title={noOrders ? "Nelze objednat - neexistuje žádná objednávka" : "Použij po objednání. Objednávky zůstanou zamčeny."} disabled={noOrders} onClick={async () => {
|
||||
await finishOrder(auth.login);
|
||||
await finishOrder();
|
||||
}}>Objednáno</Button>
|
||||
</>
|
||||
}
|
||||
@@ -314,10 +310,10 @@ function App() {
|
||||
{data.pizzaDay.creator === auth.login &&
|
||||
<div>
|
||||
<Button className='danger mb-3' title="Vrátí stav do předchozího kroku (před objednáním)." onClick={async () => {
|
||||
await lockPizzaDay(auth.login);
|
||||
await lockPizzaDay();
|
||||
}}>Vrátit do "uzamčeno"</Button>
|
||||
<Button className='danger mb-3' style={{ marginLeft: '20px' }} title="Nastaví stav na 'Doručeno' - koncový stav." onClick={async () => {
|
||||
await finishDelivery(auth.login, bank?.bankAccount, bank?.holderName);
|
||||
await finishDelivery(bank?.bankAccount, bank?.holderName);
|
||||
}}>Doručeno</Button>
|
||||
</div>
|
||||
}
|
||||
@@ -357,7 +353,7 @@ function App() {
|
||||
<div className='qr-code'>
|
||||
<h3>QR platba</h3>
|
||||
<div>Částka: {myOrder.totalPrice} Kč</div>
|
||||
<img src={getQrUrl(auth.login)} alt='QR kód' />
|
||||
<img src={getQrUrl()} alt='QR kód' />
|
||||
<p>Generování QR kódů je v experimentální fázi - doporučujeme si překontrolovat údaje před odesláním platby.</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import { useAuth } from './context/auth';
|
||||
import { login } from './Api';
|
||||
import './Login.css';
|
||||
|
||||
/**
|
||||
@@ -10,10 +11,14 @@ export default function Login() {
|
||||
const auth = useAuth();
|
||||
const loginRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const doLogin = useCallback(() => {
|
||||
const doLogin = useCallback(async () => {
|
||||
const length = loginRef?.current?.value && loginRef?.current?.value.length && loginRef.current.value.replace(/\s/g, '').length
|
||||
if (length) {
|
||||
auth?.setLogin(loginRef.current.value);
|
||||
// TODO odchytávat cokoliv mimo 200
|
||||
const token = await login(loginRef.current.value);
|
||||
if (token) {
|
||||
auth?.setToken(token);
|
||||
}
|
||||
}
|
||||
}, [auth]);
|
||||
|
||||
|
||||
@@ -10,29 +10,29 @@ export const getBaseUrl = (): string => {
|
||||
return 'http://127.0.0.1:3001';
|
||||
}
|
||||
|
||||
const LOGIN_KEY = "login";
|
||||
const TOKEN_KEY = "token";
|
||||
|
||||
/**
|
||||
* Uloží login do local storage prohlížeče.
|
||||
* Uloží token do local storage prohlížeče.
|
||||
*
|
||||
* @param login login
|
||||
* @param token token
|
||||
*/
|
||||
export const storeLogin = (login: string) => {
|
||||
localStorage.setItem(LOGIN_KEY, login);
|
||||
export const storeToken = (token: string) => {
|
||||
localStorage.setItem(TOKEN_KEY, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrátí login z local storage, pokud tam je.
|
||||
* Vrátí token z local storage, pokud tam je.
|
||||
*
|
||||
* @returns login nebo null
|
||||
* @returns token nebo null
|
||||
*/
|
||||
export const getLogin = (): string | null => {
|
||||
return localStorage.getItem(LOGIN_KEY);
|
||||
export const getToken = (): string | null => {
|
||||
return localStorage.getItem(TOKEN_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Odstraní login z local storage, pokud tam je.
|
||||
* Odstraní token z local storage, pokud tam je.
|
||||
*/
|
||||
export const deleteLogin = () => {
|
||||
localStorage.removeItem(LOGIN_KEY);
|
||||
export const deleteToken = () => {
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
}
|
||||
@@ -83,7 +83,7 @@ export default function Header() {
|
||||
<Nav className="nav">
|
||||
<NavDropdown align="end" title={auth?.login} id="basic-nav-dropdown">
|
||||
<NavDropdown.Item onClick={openBankSettings}>Nastavit číslo účtu</NavDropdown.Item>
|
||||
<NavDropdown.Item onClick={auth?.clearLogin}>Odhlásit se</NavDropdown.Item>
|
||||
<NavDropdown.Item onClick={auth?.logout}>Odhlásit se</NavDropdown.Item>
|
||||
</NavDropdown>
|
||||
</Nav>
|
||||
</Navbar.Collapse>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React, { ReactNode, useContext, useState } from "react"
|
||||
import { useEffect } from "react"
|
||||
|
||||
const LOGIN_KEY = 'login';
|
||||
import { useJwt } from "react-jwt";
|
||||
import { deleteToken, getToken, storeToken } from "../Utils";
|
||||
|
||||
export type AuthContextProps = {
|
||||
login?: string,
|
||||
setLogin: (name: string) => void,
|
||||
clearLogin: () => void,
|
||||
setToken: (name: string) => void,
|
||||
logout: () => void,
|
||||
}
|
||||
|
||||
type ContextProps = {
|
||||
@@ -26,33 +26,33 @@ export const useAuth = () => {
|
||||
|
||||
function useProvideAuth(): AuthContextProps {
|
||||
const [loginName, setLoginName] = useState<string | undefined>();
|
||||
const [token, setToken] = useState<string | null>(getToken());
|
||||
const { decodedToken } = useJwt(token || '');
|
||||
|
||||
useEffect(() => {
|
||||
const login = localStorage.getItem(LOGIN_KEY);
|
||||
if (login) {
|
||||
setLogin(login);
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (loginName) {
|
||||
localStorage.setItem(LOGIN_KEY, loginName)
|
||||
if (token && token.length > 0) {
|
||||
storeToken(token);
|
||||
} else {
|
||||
localStorage.removeItem(LOGIN_KEY);
|
||||
deleteToken();
|
||||
}
|
||||
}, [loginName]);
|
||||
}, [token]);
|
||||
|
||||
function setLogin(login: string) {
|
||||
setLoginName(login);
|
||||
}
|
||||
useEffect(() => {
|
||||
if (decodedToken) {
|
||||
setLoginName((decodedToken as any).login);
|
||||
} else {
|
||||
setLoginName(undefined);
|
||||
}
|
||||
}, [decodedToken]);
|
||||
|
||||
function clearLogin() {
|
||||
function logout() {
|
||||
setToken(null);
|
||||
setLoginName(undefined);
|
||||
}
|
||||
|
||||
return {
|
||||
login: loginName,
|
||||
setLogin,
|
||||
clearLogin
|
||||
setToken,
|
||||
logout,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2168,6 +2168,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
|
||||
|
||||
"@types/jsonwebtoken@^9.0.2":
|
||||
version "9.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#9eeb56c76dd555039be2a3972218de5bd3b8d83e"
|
||||
integrity sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/mime@*":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
|
||||
@@ -7809,6 +7816,13 @@ react-is@^18.0.0:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
|
||||
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
|
||||
|
||||
react-jwt@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-jwt/-/react-jwt-1.2.0.tgz#985c507dbbc0980606719a0d78c2a164282d0569"
|
||||
integrity sha512-HmEaS63CaqxHPIWoLh68KpGacXX7tAiWS2YIREVDosc2m4hTYoMp23Oz1lRM3MivT8DGibwTFIg5k4HNLfMv1w==
|
||||
optionalDependencies:
|
||||
fsevents "^2.3.2"
|
||||
|
||||
react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||
|
||||
Reference in New Issue
Block a user