diff --git a/README.md b/README.md index 13cafa1..7b1bae8 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Aplikace sestává ze dvou (tří) modulů. - [ ] Popsat nginx - [x] Popsat závislosti, co je nutné provést před vývojem a postup spuštění pro vývoj - [x] Popsat dostupné env -- [ ] Přesunout autentizaci na server (JWT?) +- [x] Přesunout autentizaci na server (JWT?) - [x] Zavést .env.template a přidat .env do .gitignore - [x] Zkrášlit dialog pro vyplnění čísla účtu, vypadá mizerně - [ ] Podpora pro notifikace v externích systémech (Gotify, Discord, MS Teams) diff --git a/client/package.json b/client/package.json index 4f4b7d7..305a85c 100644 --- a/client/package.json +++ b/client/package.json @@ -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" } -} +} \ No newline at end of file diff --git a/client/src/Api.ts b/client/src/Api.ts index 219ae63..ff89e73 100644 --- a/client/src/Api.ts +++ b/client/src/Api.ts @@ -1,10 +1,14 @@ import { PizzaOrder } from "./Types"; -import { getBaseUrl } from "./Utils"; +import { getBaseUrl, getToken } from "./Utils"; async function request( url: string, config: RequestInit = {} ): Promise { + 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: (url: string, body: TBody) => request(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('/api/pizza'); } -export const createPizzaDay = async (creator) => { - return await api.post('/api/createPizzaDay', JSON.stringify({ creator })); +export const createPizzaDay = async () => { + return await api.post('/api/createPizzaDay', undefined); } -export const deletePizzaDay = async (login) => { - return await api.post('/api/deletePizzaDay', JSON.stringify({ login })); +export const deletePizzaDay = async () => { + return await api.post('/api/deletePizzaDay', undefined); } -export const lockPizzaDay = async (login) => { - return await api.post('/api/lockPizzaDay', JSON.stringify({ login })); +export const lockPizzaDay = async () => { + return await api.post('/api/lockPizzaDay', undefined); } -export const unlockPizzaDay = async (login) => { - return await api.post('/api/unlockPizzaDay', JSON.stringify({ login })); +export const unlockPizzaDay = async () => { + return await api.post('/api/unlockPizzaDay', undefined); } -export const finishOrder = async (login) => { - return await api.post('/api/finishOrder', JSON.stringify({ login })); +export const finishOrder = async () => { + return await api.post('/api/finishOrder', undefined); } -export const finishDelivery = async (login, bankAccount, bankAccountHolder) => { - return await api.post('/api/finishDelivery', JSON.stringify({ login, bankAccount, bankAccountHolder })); +export const finishDelivery = async (bankAccount, bankAccountHolder) => { + return await api.post('/api/finishDelivery', JSON.stringify({ bankAccount, bankAccountHolder })); } -export const updateChoice = async (name: string, choice: number | null) => { - return await api.post('/api/updateChoice', JSON.stringify({ name, choice })); +export const updateChoice = async (choice: number | null) => { + return await api.post('/api/updateChoice', JSON.stringify({ choice })); } -export const addPizza = async (login: string, pizzaIndex: number, pizzaSizeIndex: number) => { - return await api.post('/api/addPizza', JSON.stringify({ login, pizzaIndex, pizzaSizeIndex })); +export const addPizza = async (pizzaIndex: number, pizzaSizeIndex: number) => { + return await api.post('/api/addPizza', JSON.stringify({ pizzaIndex, pizzaSizeIndex })); } -export const removePizza = async (login: string, pizzaOrder: PizzaOrder) => { - return await api.post('/api/removePizza', JSON.stringify({ login, pizzaOrder })); +export const removePizza = async (pizzaOrder: PizzaOrder) => { + return await api.post('/api/removePizza', JSON.stringify({ pizzaOrder })); } -export const updateNote = async (login: string, note?: string) => { - return await api.post('/api/updateNote', JSON.stringify({ login, note })); -} \ No newline at end of file +export const updateNote = async (note?: string) => { + return await api.post('/api/updateNote', JSON.stringify({ note })); +} + +export const login = async (login: string) => { + return await api.post('/api/login', JSON.stringify({ login })); +} diff --git a/client/src/App.tsx b/client/src/App.tsx index dafa7e6..745f1ed 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -31,8 +31,11 @@ function App() { const choiceRef = useRef(null); const poznamkaRef = useRef(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) => { 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() { Poslední změny:
    -
  • Nová žárovka zatím funguje
  • -
  • Funkční generování a zobrazení QR kódů pro Pizza day
  • -
  • Možnost zadat k Pizza day objednávce poznámku
  • -
  • Zbavení se Food API, přepsání a zahrnutí parseru do serveru
  • +
  • Zavedení JWT, přesun autentizace na server

Dnes je {data.date}

@@ -260,7 +256,7 @@ function App() {

Pro dnešní den není aktuálně založen Pizza day.

} @@ -279,10 +275,10 @@ function App() { data.pizzaDay.creator === auth.login && <> } @@ -295,13 +291,13 @@ function App() { {data.pizzaDay.creator === auth.login && <> {/* */} } @@ -314,10 +310,10 @@ function App() { {data.pizzaDay.creator === auth.login &&
} @@ -357,7 +353,7 @@ function App() {

QR platba

Částka: {myOrder.totalPrice} Kč
- QR kód + QR kód

Generování QR kódů je v experimentální fázi - doporučujeme si překontrolovat údaje před odesláním platby.

} diff --git a/client/src/Login.tsx b/client/src/Login.tsx index 64fb1b5..c5228ae 100644 --- a/client/src/Login.tsx +++ b/client/src/Login.tsx @@ -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(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]); diff --git a/client/src/Utils.tsx b/client/src/Utils.tsx index a019057..bfece64 100644 --- a/client/src/Utils.tsx +++ b/client/src/Utils.tsx @@ -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); } \ No newline at end of file diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx index caa361c..01f24aa 100644 --- a/client/src/components/Header.tsx +++ b/client/src/components/Header.tsx @@ -83,7 +83,7 @@ export default function Header() { diff --git a/client/src/context/auth.tsx b/client/src/context/auth.tsx index aeb9e75..7eb654c 100644 --- a/client/src/context/auth.tsx +++ b/client/src/context/auth.tsx @@ -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(); + const [token, setToken] = useState(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, } } diff --git a/client/yarn.lock b/client/yarn.lock index 8b64f2c..7453f71 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -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" diff --git a/server/.env.template b/server/.env.template index 099e9ce..b6c4389 100644 --- a/server/.env.template +++ b/server/.env.template @@ -1,8 +1,14 @@ +# Secret pro podepisování JWT tokenů. Minimální délka 32 znaků. +# JWT_SECRET='CHANGE_ME' + # Zapne režim mockování obědových menu. # Vhodné pro vývoj o víkendech, svátcích a dalších dnech, pro které podniky nenabízejí obědové menu. # V tomto režimu vrací server vždy falešné datum (pracovní den) a pevně nadefinovanou, smyšlenou nabídku jídel. # MOCK_DATA=true +# Secret pro podepisování JWT tokenů. Minimální délka 32 znaků. +# JWT_SECRET='CHANGE_ME' + # Určuje servery Gotify a příslušné klíče API. # Formát je pole objektů, kde každý objekt obsahuje adresu serveru a pole klíčů API. # To je užitečné pro odesílání upozornění na různé servery Gotify s různými klíči API. diff --git a/server/package.json b/server/package.json index c8727f0..24f06b6 100644 --- a/server/package.json +++ b/server/package.json @@ -10,6 +10,7 @@ }, "devDependencies": { "@types/express": "^4.17.17", + "@types/jsonwebtoken": "^9.0.2", "@types/node": "^20.2.5", "@types/request-promise": "^4.1.48", "ts-node": "^10.9.1", @@ -21,6 +22,7 @@ "cors": "^2.8.5", "dotenv": "^16.1.3", "express": "^4.18.2", + "jsonwebtoken": "^9.0.0", "simple-json-db": "^2.0.0", "socket.io": "^4.6.1" } diff --git a/server/src/auth.ts b/server/src/auth.ts new file mode 100644 index 0000000..85c837b --- /dev/null +++ b/server/src/auth.ts @@ -0,0 +1,50 @@ +import jwt from 'jsonwebtoken'; + +/** + * Vygeneruje a vrátí podepsaný JWT token pro daný login. + * + * @param login přihlašovací jméno uživatele + * @returns JWT token + */ +export function generateToken(login: string): string { + if (!process.env.JWT_SECRET) { + throw Error("Není vyplněna proměnná prostředí JWT_SECRET"); + } + if (process.env.JWT_SECRET.length < 32) { + throw Error("Proměnná prostředí JWT_SECRET musí být minimálně 32 znaků"); + } + return jwt.sign({ login }, process.env.JWT_SECRET); +} + +/** + * Vrátí true, pokud je předaný JWT token platný. + * + * @param token JWT token + */ +export function verify(token: string): boolean { + if (!process.env.JWT_SECRET) { + throw Error("Není vyplněna proměnná prostředí JWT_SECRET"); + } + try { + jwt.verify(token, process.env.JWT_SECRET); + return true; + } catch (err) { + return false; + } +} + +/** + * Vrátí login z daného JWT tokenu, pokud je token platný. + * + * @param token JWT token + */ +export function getLogin(token?: string): string { + if (!process.env.JWT_SECRET) { + throw Error("Není vyplněna proměnná prostředí JWT_SECRET"); + } + if (!token) { + throw Error("Nebyl předán token"); + } + const payload: any = jwt.verify(token, process.env.JWT_SECRET); + return payload.login; +} \ No newline at end of file diff --git a/server/src/index.ts b/server/src/index.ts index 3993e3d..99cf5c9 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -9,6 +9,7 @@ import path from 'path'; import { getMenuSladovnicka, getMenuTechTower, getMenuUMotliku } from "./restaurants"; import { getQr } from "./qr"; import { Restaurants } from "./types"; +import { generateToken, getLogin, verify } from "./auth"; const ENVIRONMENT = process.env.NODE_ENV || 'production' dotenv.config({ path: path.resolve(__dirname, `../.env.${ENVIRONMENT}`) }); @@ -29,6 +30,37 @@ app.use(cors({ origin: '*' })); +const parseToken = (req: any) => { + if (req?.headers?.authorization) { + return req.headers.authorization.split(' ')[1]; + } +} + +// ----------- Metody nevyžadující token -------------- + +app.post("/api/login", (req, res) => { + if (!req.body?.login) { + throw Error("Nebyl předán login"); + } + // TODO zavést podmínky pro délku loginu (min i max) + const token = generateToken(req.body.login); + res.status(200).json(token); +}); + +// ---------------------------------------------------- + +/** Middleware ověřující JWT token */ +app.use((req, res, next) => { + if (!req.headers.authorization) { + return res.status(401).json({ error: 'Nebyl předán autentizační token' }); + } + const token = req.headers.authorization.split(' ')[1]; + if (!verify(token)) { + return res.status(403).json({ error: 'Neplatný autentizační token' }); + } + next(); +}); + /** Vrátí data pro aktuální den. */ app.get("/api/data", (req, res) => { res.status(200).json(getData()); @@ -56,27 +88,21 @@ app.get("/api/pizza", (req, res) => { /** Založí pizza day pro aktuální den, za předpokladu že dosud neexistuje. */ app.post("/api/createPizzaDay", (req, res) => { - if (!req.body?.creator) { - throw Error("Nebyl předán název zakládajícího"); - } - const data = createPizzaDay(req.body.creator); + const login = getLogin(parseToken(req)); + const data = createPizzaDay(login); res.status(200).json(data); io.emit("message", data); }); /** Smaže pizza day pro aktuální den, za předpokladu že existuje. */ app.post("/api/deletePizzaDay", (req, res) => { - if (!req.body?.login) { - throw Error("Nebyl předán login uživatele"); - } - const data = deletePizzaDay(req.body.login); + const login = getLogin(parseToken(req)); + const data = deletePizzaDay(login); io.emit("message", data); }); app.post("/api/addPizza", (req, res) => { - if (!req.body?.login) { - throw Error("Nebyl předán login"); - } + const login = getLogin(parseToken(req)); if (isNaN(req.body?.pizzaIndex)) { throw Error("Nebyl předán index pizzy"); } @@ -92,74 +118,60 @@ app.post("/api/addPizza", (req, res) => { if (!pizzy[pizzaIndex].sizes[pizzaSizeIndex]) { throw Error("Neplatný index velikosti pizzy: " + pizzaSizeIndex); } - const data = addPizzaOrder(req.body.login, pizzy[pizzaIndex], pizzy[pizzaIndex].sizes[pizzaSizeIndex]); + const data = addPizzaOrder(login, pizzy[pizzaIndex], pizzy[pizzaIndex].sizes[pizzaSizeIndex]); io.emit("message", data); res.status(200).json({}); }) }); app.post("/api/removePizza", (req, res) => { - if (!req.body?.login) { - throw Error("Nebyl předán login"); - } + const login = getLogin(parseToken(req)); if (!req.body?.pizzaOrder) { throw Error("Nebyla předána objednávka"); } - const data = removePizzaOrder(req.body.login, req.body?.pizzaOrder); + const data = removePizzaOrder(login, req.body?.pizzaOrder); io.emit("message", data); res.status(200).json({}); }); app.post("/api/lockPizzaDay", (req, res) => { - if (!req.body?.login) { - throw Error("Nebyl předán login"); - } - const data = lockPizzaDay(req.body.login); + const login = getLogin(parseToken(req)); + const data = lockPizzaDay(login); io.emit("message", data); res.status(200).json({}); }); app.post("/api/unlockPizzaDay", (req, res) => { - if (!req.body?.login) { - throw Error("Nebyl předán login"); - } - const data = unlockPizzaDay(req.body.login); + const login = getLogin(parseToken(req)); + const data = unlockPizzaDay(login); io.emit("message", data); res.status(200).json({}); }); app.post("/api/finishOrder", (req, res) => { - if (!req.body?.login) { - throw Error("Nebyl předán login"); - } - const data = finishPizzaOrder(req.body.login); + const login = getLogin(parseToken(req)); + const data = finishPizzaOrder(login); io.emit("message", data); res.status(200).json({}); }); app.post("/api/finishDelivery", (req, res) => { - if (!req.body?.login) { - throw Error("Nebyl předán login"); - } - const data = finishPizzaDelivery(req.body.login, req.body.bankAccount, req.body.bankAccountHolder); + const login = getLogin(parseToken(req)); + const data = finishPizzaDelivery(login, req.body.bankAccount, req.body.bankAccountHolder); io.emit("message", data); res.status(200).json({}); }); app.post("/api/updateChoice", (req, res) => { - if (!req.body.hasOwnProperty('name')) { - res.status(400).json({}); - } - const data = updateChoice(req.body.name, req.body.choice); + const login = getLogin(parseToken(req)); + const data = updateChoice(login, req.body.choice); io.emit("message", data); res.status(200).json(data); }); app.get("/api/qr", (req, res) => { - if (!req.query?.login || typeof req.query.login !== 'string') { - throw Error("Nebyl předán login"); - } - const img = getQr(req.query.login); + const login = getLogin(parseToken(req)); + const img = getQr(login); res.writeHead(200, { 'Content-Type': 'image/png', 'Content-Length': img.length @@ -168,13 +180,11 @@ app.get("/api/qr", (req, res) => { }); app.post("/api/updateNote", (req, res) => { - if (!req.body.login) { - throw Error("Nebyl předán login"); - } + const login = getLogin(parseToken(req)); if (req.body.note && req.body.note.length > 100) { throw Error("Poznámka může mít maximálně 100 znaků"); } - const data = updateNote(req.body.login, req.body.note); + const data = updateNote(login, req.body.note); io.emit("message", data); res.status(200).json(data); }); diff --git a/server/src/service.ts b/server/src/service.ts index 6711358..683124a 100644 --- a/server/src/service.ts +++ b/server/src/service.ts @@ -218,6 +218,7 @@ export function finishPizzaDelivery(login: string, bankAccount?: string, bankAcc clientData.pizzaDay.state = PizzaDayState.DELIVERED; // Vygenerujeme QR kód, pokud k tomu máme data + // TODO berka je potřeba počkat na resolve promises z generateQr a až poté volat save do DB if (bankAccount?.length && bankAccountHolder?.length) { for (const order of clientData.pizzaDay.orders) { if (order.customer !== login) { // zatím platí creator = objednávající, a pro toho nemá QR kód smysl diff --git a/server/yarn.lock b/server/yarn.lock index fb094ec..c91ca07 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -109,6 +109,13 @@ "@types/qs" "*" "@types/serve-static" "*" +"@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" @@ -243,6 +250,11 @@ boolbase@^1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" @@ -413,6 +425,13 @@ dotenv@^16.1.3: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.1.4.tgz#67ac1a10cd9c25f5ba604e4e08bc77c0ebe0ca8c" integrity sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw== +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -612,6 +631,45 @@ ipaddr.js@1.9.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +jsonwebtoken@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d" + integrity sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw== + dependencies: + jws "^3.2.2" + lodash "^4.17.21" + ms "^2.1.1" + semver "^7.3.8" + +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" @@ -659,7 +717,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3: +ms@2.1.3, ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -753,7 +811,7 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" -safe-buffer@5.2.1: +safe-buffer@5.2.1, safe-buffer@^5.0.1: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -763,6 +821,13 @@ safe-buffer@5.2.1: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +semver@^7.3.8: + version "7.5.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" + integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ== + dependencies: + lru-cache "^6.0.0" + send@0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -905,6 +970,11 @@ ws@~8.11.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"