Přesun autentizace na server
This commit is contained in:
parent
47fbe4173d
commit
e81c7d09a3
@ -54,7 +54,7 @@ Aplikace sestává ze dvou (tří) modulů.
|
|||||||
- [ ] Popsat nginx
|
- [ ] 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 závislosti, co je nutné provést před vývojem a postup spuštění pro vývoj
|
||||||
- [x] Popsat dostupné env
|
- [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] Zavést .env.template a přidat .env do .gitignore
|
||||||
- [x] Zkrášlit dialog pro vyplnění čísla účtu, vypadá mizerně
|
- [x] Zkrášlit dialog pro vyplnění čísla účtu, vypadá mizerně
|
||||||
- [ ] Podpora pro notifikace v externích systémech (Gotify, Discord, MS Teams)
|
- [ ] Podpora pro notifikace v externích systémech (Gotify, Discord, MS Teams)
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-bootstrap": "^2.7.2",
|
"react-bootstrap": "^2.7.2",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-jwt": "^1.2.0",
|
||||||
"react-modal": "^3.16.1",
|
"react-modal": "^3.16.1",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"react-select-search": "^4.1.6",
|
"react-select-search": "^4.1.6",
|
||||||
@ -54,4 +55,4 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "^2.8.8"
|
"prettier": "^2.8.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,10 +1,14 @@
|
|||||||
import { PizzaOrder } from "./Types";
|
import { PizzaOrder } from "./Types";
|
||||||
import { getBaseUrl } from "./Utils";
|
import { getBaseUrl, getToken } from "./Utils";
|
||||||
|
|
||||||
async function request<TResponse>(
|
async function request<TResponse>(
|
||||||
url: string,
|
url: string,
|
||||||
config: RequestInit = {}
|
config: RequestInit = {}
|
||||||
): Promise<TResponse> {
|
): Promise<TResponse> {
|
||||||
|
if (!config.headers) {
|
||||||
|
config.headers = {};
|
||||||
|
}
|
||||||
|
config.headers["Authorization"] = `Bearer ${getToken()}`;
|
||||||
return fetch(getBaseUrl() + url, config).then(response => {
|
return fetch(getBaseUrl() + url, config).then(response => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(response.statusText);
|
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' } }),
|
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) => {
|
export const getQrUrl = () => {
|
||||||
return `${getBaseUrl()}/api/qr?login=${login}`;
|
return `${getBaseUrl()}/api/qr`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getData = async () => {
|
export const getData = async () => {
|
||||||
@ -34,42 +38,46 @@ export const getPizzy = async () => {
|
|||||||
return await api.get<any>('/api/pizza');
|
return await api.get<any>('/api/pizza');
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createPizzaDay = async (creator) => {
|
export const createPizzaDay = async () => {
|
||||||
return await api.post<any, any>('/api/createPizzaDay', JSON.stringify({ creator }));
|
return await api.post<any, any>('/api/createPizzaDay', undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deletePizzaDay = async (login) => {
|
export const deletePizzaDay = async () => {
|
||||||
return await api.post<any, any>('/api/deletePizzaDay', JSON.stringify({ login }));
|
return await api.post<any, any>('/api/deletePizzaDay', undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const lockPizzaDay = async (login) => {
|
export const lockPizzaDay = async () => {
|
||||||
return await api.post<any, any>('/api/lockPizzaDay', JSON.stringify({ login }));
|
return await api.post<any, any>('/api/lockPizzaDay', undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const unlockPizzaDay = async (login) => {
|
export const unlockPizzaDay = async () => {
|
||||||
return await api.post<any, any>('/api/unlockPizzaDay', JSON.stringify({ login }));
|
return await api.post<any, any>('/api/unlockPizzaDay', undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const finishOrder = async (login) => {
|
export const finishOrder = async () => {
|
||||||
return await api.post<any, any>('/api/finishOrder', JSON.stringify({ login }));
|
return await api.post<any, any>('/api/finishOrder', undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const finishDelivery = async (login, bankAccount, bankAccountHolder) => {
|
export const finishDelivery = async (bankAccount, bankAccountHolder) => {
|
||||||
return await api.post<any, any>('/api/finishDelivery', JSON.stringify({ login, bankAccount, bankAccountHolder }));
|
return await api.post<any, any>('/api/finishDelivery', JSON.stringify({ bankAccount, bankAccountHolder }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateChoice = async (name: string, choice: number | null) => {
|
export const updateChoice = async (choice: number | null) => {
|
||||||
return await api.post<any, any>('/api/updateChoice', JSON.stringify({ name, choice }));
|
return await api.post<any, any>('/api/updateChoice', JSON.stringify({ choice }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addPizza = async (login: string, pizzaIndex: number, pizzaSizeIndex: number) => {
|
export const addPizza = async (pizzaIndex: number, pizzaSizeIndex: number) => {
|
||||||
return await api.post<any, any>('/api/addPizza', JSON.stringify({ login, pizzaIndex, pizzaSizeIndex }));
|
return await api.post<any, any>('/api/addPizza', JSON.stringify({ pizzaIndex, pizzaSizeIndex }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const removePizza = async (login: string, pizzaOrder: PizzaOrder) => {
|
export const removePizza = async (pizzaOrder: PizzaOrder) => {
|
||||||
return await api.post<any, any>('/api/removePizza', JSON.stringify({ login, pizzaOrder }));
|
return await api.post<any, any>('/api/removePizza', JSON.stringify({ pizzaOrder }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateNote = async (login: string, note?: string) => {
|
export const updateNote = async (note?: string) => {
|
||||||
return await api.post<any, any>('/api/updateNote', JSON.stringify({ login, note }));
|
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 choiceRef = useRef<HTMLSelectElement>(null);
|
||||||
const poznamkaRef = useRef<HTMLInputElement>(null);
|
const poznamkaRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
// Prvotní načtení aktuálního stavu
|
// Načtení dat po přihlášení
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!auth || !auth.login) {
|
||||||
|
return
|
||||||
|
}
|
||||||
getPizzy().then(pizzy => {
|
getPizzy().then(pizzy => {
|
||||||
setPizzy(pizzy);
|
setPizzy(pizzy);
|
||||||
});
|
});
|
||||||
@ -42,7 +45,7 @@ function App() {
|
|||||||
getFood().then(food => {
|
getFood().then(food => {
|
||||||
setFood(food);
|
setFood(food);
|
||||||
})
|
})
|
||||||
}, []);
|
}, [auth, auth?.login]);
|
||||||
|
|
||||||
// Registrace socket eventů
|
// Registrace socket eventů
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -91,13 +94,13 @@ function App() {
|
|||||||
const changeChoice = async (event: React.ChangeEvent<HTMLSelectElement>) => {
|
const changeChoice = async (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
const index = Object.values(Locations).indexOf(event.target.value as unknown as Locations);
|
const index = Object.values(Locations).indexOf(event.target.value as unknown as Locations);
|
||||||
if (auth?.login) {
|
if (auth?.login) {
|
||||||
await updateChoice(auth.login, index > -1 ? index : null);
|
await updateChoice(index > -1 ? index : null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeChoice = async (key: string) => {
|
const removeChoice = async (key: string) => {
|
||||||
if (auth?.login) {
|
if (auth?.login) {
|
||||||
await updateChoice(auth.login, null);
|
await updateChoice(null);
|
||||||
if (choiceRef?.current?.value) {
|
if (choiceRef?.current?.value) {
|
||||||
choiceRef.current.value = "";
|
choiceRef.current.value = "";
|
||||||
}
|
}
|
||||||
@ -126,24 +129,20 @@ function App() {
|
|||||||
const s = value.split('|');
|
const s = value.split('|');
|
||||||
const pizzaIndex = Number.parseInt(s[0]);
|
const pizzaIndex = Number.parseInt(s[0]);
|
||||||
const pizzaSizeIndex = Number.parseInt(s[1]);
|
const pizzaSizeIndex = Number.parseInt(s[1]);
|
||||||
await addPizza(auth.login, pizzaIndex, pizzaSizeIndex);
|
await addPizza(pizzaIndex, pizzaSizeIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePizzaDelete = async (pizzaOrder: PizzaOrder) => {
|
const handlePizzaDelete = async (pizzaOrder: PizzaOrder) => {
|
||||||
if (auth?.login) {
|
await removePizza(pizzaOrder);
|
||||||
await removePizza(auth?.login, pizzaOrder);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePoznamkaChange = async () => {
|
const handlePoznamkaChange = async () => {
|
||||||
if (auth?.login) {
|
if (poznamkaRef.current?.value && poznamkaRef.current.value.length > 100) {
|
||||||
if (poznamkaRef.current?.value && poznamkaRef.current.value.length > 100) {
|
alert("Poznámka může mít maximálně 100 znaků");
|
||||||
alert("Poznámka může mít maximálně 100 znaků");
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
updateNote(auth.login, poznamkaRef.current?.value);
|
|
||||||
}
|
}
|
||||||
|
updateNote(poznamkaRef.current?.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// const addToCart = async () => {
|
// const addToCart = async () => {
|
||||||
@ -205,10 +204,7 @@ function App() {
|
|||||||
<Alert variant={'primary'}>
|
<Alert variant={'primary'}>
|
||||||
Poslední změny:
|
Poslední změny:
|
||||||
<ul>
|
<ul>
|
||||||
<li>Nová žárovka zatím funguje</li>
|
<li>Zavedení JWT, přesun autentizace na server</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>
|
|
||||||
</ul>
|
</ul>
|
||||||
</Alert>
|
</Alert>
|
||||||
<h1 className='title'>Dnes je {data.date}</h1>
|
<h1 className='title'>Dnes je {data.date}</h1>
|
||||||
@ -260,7 +256,7 @@ function App() {
|
|||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
<p>Pro dnešní den není aktuálně založen Pizza day.</p>
|
<p>Pro dnešní den není aktuálně založen Pizza day.</p>
|
||||||
<Button onClick={async () => {
|
<Button onClick={async () => {
|
||||||
await createPizzaDay(auth.login);
|
await createPizzaDay();
|
||||||
}}>Založit Pizza day</Button>
|
}}>Založit Pizza day</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -279,10 +275,10 @@ function App() {
|
|||||||
data.pizzaDay.creator === auth.login &&
|
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 () => {
|
<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>
|
}}>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 () => {
|
<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>
|
}}>Uzamknout</Button>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
@ -295,13 +291,13 @@ function App() {
|
|||||||
{data.pizzaDay.creator === auth.login &&
|
{data.pizzaDay.creator === auth.login &&
|
||||||
<>
|
<>
|
||||||
<Button className='danger mb-3' title="Umožní znovu editovat objednávky." onClick={async () => {
|
<Button className='danger mb-3' title="Umožní znovu editovat objednávky." onClick={async () => {
|
||||||
await unlockPizzaDay(auth.login);
|
await unlockPizzaDay();
|
||||||
}}>Odemknout</Button>
|
}}>Odemknout</Button>
|
||||||
{/* <Button className='danger mb-3' style={{ marginLeft: '20px' }} onClick={async () => {
|
{/* <Button className='danger mb-3' style={{ marginLeft: '20px' }} onClick={async () => {
|
||||||
await addToCart();
|
await addToCart();
|
||||||
}}>Přidat vše do košíku</Button> */}
|
}}>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 () => {
|
<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>
|
}}>Objednáno</Button>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
@ -314,10 +310,10 @@ function App() {
|
|||||||
{data.pizzaDay.creator === auth.login &&
|
{data.pizzaDay.creator === auth.login &&
|
||||||
<div>
|
<div>
|
||||||
<Button className='danger mb-3' title="Vrátí stav do předchozího kroku (před objednáním)." onClick={async () => {
|
<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>
|
}}>Vrátit do "uzamčeno"</Button>
|
||||||
<Button className='danger mb-3' style={{ marginLeft: '20px' }} title="Nastaví stav na 'Doručeno' - koncový stav." onClick={async () => {
|
<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>
|
}}>Doručeno</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -357,7 +353,7 @@ function App() {
|
|||||||
<div className='qr-code'>
|
<div className='qr-code'>
|
||||||
<h3>QR platba</h3>
|
<h3>QR platba</h3>
|
||||||
<div>Částka: {myOrder.totalPrice} Kč</div>
|
<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>
|
<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>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React, { useCallback, useRef } from 'react';
|
import React, { useCallback, useRef } from 'react';
|
||||||
import { Button } from 'react-bootstrap';
|
import { Button } from 'react-bootstrap';
|
||||||
import { useAuth } from './context/auth';
|
import { useAuth } from './context/auth';
|
||||||
|
import { login } from './Api';
|
||||||
import './Login.css';
|
import './Login.css';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -10,10 +11,14 @@ export default function Login() {
|
|||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
const loginRef = useRef<HTMLInputElement>(null);
|
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
|
const length = loginRef?.current?.value && loginRef?.current?.value.length && loginRef.current.value.replace(/\s/g, '').length
|
||||||
if (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]);
|
}, [auth]);
|
||||||
|
|
||||||
|
@ -10,29 +10,29 @@ export const getBaseUrl = (): string => {
|
|||||||
return 'http://127.0.0.1:3001';
|
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) => {
|
export const storeToken = (token: string) => {
|
||||||
localStorage.setItem(LOGIN_KEY, login);
|
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 => {
|
export const getToken = (): string | null => {
|
||||||
return localStorage.getItem(LOGIN_KEY);
|
return localStorage.getItem(TOKEN_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Odstraní login z local storage, pokud tam je.
|
* Odstraní token z local storage, pokud tam je.
|
||||||
*/
|
*/
|
||||||
export const deleteLogin = () => {
|
export const deleteToken = () => {
|
||||||
localStorage.removeItem(LOGIN_KEY);
|
localStorage.removeItem(TOKEN_KEY);
|
||||||
}
|
}
|
@ -83,7 +83,7 @@ export default function Header() {
|
|||||||
<Nav className="nav">
|
<Nav className="nav">
|
||||||
<NavDropdown align="end" title={auth?.login} id="basic-nav-dropdown">
|
<NavDropdown align="end" title={auth?.login} id="basic-nav-dropdown">
|
||||||
<NavDropdown.Item onClick={openBankSettings}>Nastavit číslo účtu</NavDropdown.Item>
|
<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>
|
</NavDropdown>
|
||||||
</Nav>
|
</Nav>
|
||||||
</Navbar.Collapse>
|
</Navbar.Collapse>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import React, { ReactNode, useContext, useState } from "react"
|
import React, { ReactNode, useContext, useState } from "react"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
|
import { useJwt } from "react-jwt";
|
||||||
const LOGIN_KEY = 'login';
|
import { deleteToken, getToken, storeToken } from "../Utils";
|
||||||
|
|
||||||
export type AuthContextProps = {
|
export type AuthContextProps = {
|
||||||
login?: string,
|
login?: string,
|
||||||
setLogin: (name: string) => void,
|
setToken: (name: string) => void,
|
||||||
clearLogin: () => void,
|
logout: () => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContextProps = {
|
type ContextProps = {
|
||||||
@ -26,33 +26,33 @@ export const useAuth = () => {
|
|||||||
|
|
||||||
function useProvideAuth(): AuthContextProps {
|
function useProvideAuth(): AuthContextProps {
|
||||||
const [loginName, setLoginName] = useState<string | undefined>();
|
const [loginName, setLoginName] = useState<string | undefined>();
|
||||||
|
const [token, setToken] = useState<string | null>(getToken());
|
||||||
|
const { decodedToken } = useJwt(token || '');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const login = localStorage.getItem(LOGIN_KEY);
|
if (token && token.length > 0) {
|
||||||
if (login) {
|
storeToken(token);
|
||||||
setLogin(login);
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (loginName) {
|
|
||||||
localStorage.setItem(LOGIN_KEY, loginName)
|
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem(LOGIN_KEY);
|
deleteToken();
|
||||||
}
|
}
|
||||||
}, [loginName]);
|
}, [token]);
|
||||||
|
|
||||||
function setLogin(login: string) {
|
useEffect(() => {
|
||||||
setLoginName(login);
|
if (decodedToken) {
|
||||||
}
|
setLoginName((decodedToken as any).login);
|
||||||
|
} else {
|
||||||
|
setLoginName(undefined);
|
||||||
|
}
|
||||||
|
}, [decodedToken]);
|
||||||
|
|
||||||
function clearLogin() {
|
function logout() {
|
||||||
|
setToken(null);
|
||||||
setLoginName(undefined);
|
setLoginName(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
login: loginName,
|
login: loginName,
|
||||||
setLogin,
|
setToken,
|
||||||
clearLogin
|
logout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2168,6 +2168,13 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||||
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
|
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@*":
|
"@types/mime@*":
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
|
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"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
|
||||||
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
|
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:
|
react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4:
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||||
|
@ -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.
|
# 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.
|
# 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.
|
# 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
|
# 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.
|
# 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.
|
# 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.
|
# To je užitečné pro odesílání upozornění na různé servery Gotify s různými klíči API.
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
|
"@types/jsonwebtoken": "^9.0.2",
|
||||||
"@types/node": "^20.2.5",
|
"@types/node": "^20.2.5",
|
||||||
"@types/request-promise": "^4.1.48",
|
"@types/request-promise": "^4.1.48",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
@ -21,6 +22,7 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.1.3",
|
"dotenv": "^16.1.3",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"jsonwebtoken": "^9.0.0",
|
||||||
"simple-json-db": "^2.0.0",
|
"simple-json-db": "^2.0.0",
|
||||||
"socket.io": "^4.6.1"
|
"socket.io": "^4.6.1"
|
||||||
}
|
}
|
||||||
|
50
server/src/auth.ts
Normal file
50
server/src/auth.ts
Normal file
@ -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;
|
||||||
|
}
|
@ -9,6 +9,7 @@ import path from 'path';
|
|||||||
import { getMenuSladovnicka, getMenuTechTower, getMenuUMotliku } from "./restaurants";
|
import { getMenuSladovnicka, getMenuTechTower, getMenuUMotliku } from "./restaurants";
|
||||||
import { getQr } from "./qr";
|
import { getQr } from "./qr";
|
||||||
import { Restaurants } from "./types";
|
import { Restaurants } from "./types";
|
||||||
|
import { generateToken, getLogin, verify } from "./auth";
|
||||||
|
|
||||||
const ENVIRONMENT = process.env.NODE_ENV || 'production'
|
const ENVIRONMENT = process.env.NODE_ENV || 'production'
|
||||||
dotenv.config({ path: path.resolve(__dirname, `../.env.${ENVIRONMENT}`) });
|
dotenv.config({ path: path.resolve(__dirname, `../.env.${ENVIRONMENT}`) });
|
||||||
@ -29,6 +30,37 @@ app.use(cors({
|
|||||||
origin: '*'
|
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. */
|
/** Vrátí data pro aktuální den. */
|
||||||
app.get("/api/data", (req, res) => {
|
app.get("/api/data", (req, res) => {
|
||||||
res.status(200).json(getData());
|
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. */
|
/** Založí pizza day pro aktuální den, za předpokladu že dosud neexistuje. */
|
||||||
app.post("/api/createPizzaDay", (req, res) => {
|
app.post("/api/createPizzaDay", (req, res) => {
|
||||||
if (!req.body?.creator) {
|
const login = getLogin(parseToken(req));
|
||||||
throw Error("Nebyl předán název zakládajícího");
|
const data = createPizzaDay(login);
|
||||||
}
|
|
||||||
const data = createPizzaDay(req.body.creator);
|
|
||||||
res.status(200).json(data);
|
res.status(200).json(data);
|
||||||
io.emit("message", data);
|
io.emit("message", data);
|
||||||
});
|
});
|
||||||
|
|
||||||
/** Smaže pizza day pro aktuální den, za předpokladu že existuje. */
|
/** Smaže pizza day pro aktuální den, za předpokladu že existuje. */
|
||||||
app.post("/api/deletePizzaDay", (req, res) => {
|
app.post("/api/deletePizzaDay", (req, res) => {
|
||||||
if (!req.body?.login) {
|
const login = getLogin(parseToken(req));
|
||||||
throw Error("Nebyl předán login uživatele");
|
const data = deletePizzaDay(login);
|
||||||
}
|
|
||||||
const data = deletePizzaDay(req.body.login);
|
|
||||||
io.emit("message", data);
|
io.emit("message", data);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/api/addPizza", (req, res) => {
|
app.post("/api/addPizza", (req, res) => {
|
||||||
if (!req.body?.login) {
|
const login = getLogin(parseToken(req));
|
||||||
throw Error("Nebyl předán login");
|
|
||||||
}
|
|
||||||
if (isNaN(req.body?.pizzaIndex)) {
|
if (isNaN(req.body?.pizzaIndex)) {
|
||||||
throw Error("Nebyl předán index pizzy");
|
throw Error("Nebyl předán index pizzy");
|
||||||
}
|
}
|
||||||
@ -92,74 +118,60 @@ app.post("/api/addPizza", (req, res) => {
|
|||||||
if (!pizzy[pizzaIndex].sizes[pizzaSizeIndex]) {
|
if (!pizzy[pizzaIndex].sizes[pizzaSizeIndex]) {
|
||||||
throw Error("Neplatný index velikosti pizzy: " + 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);
|
io.emit("message", data);
|
||||||
res.status(200).json({});
|
res.status(200).json({});
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/api/removePizza", (req, res) => {
|
app.post("/api/removePizza", (req, res) => {
|
||||||
if (!req.body?.login) {
|
const login = getLogin(parseToken(req));
|
||||||
throw Error("Nebyl předán login");
|
|
||||||
}
|
|
||||||
if (!req.body?.pizzaOrder) {
|
if (!req.body?.pizzaOrder) {
|
||||||
throw Error("Nebyla předána objednávka");
|
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);
|
io.emit("message", data);
|
||||||
res.status(200).json({});
|
res.status(200).json({});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/api/lockPizzaDay", (req, res) => {
|
app.post("/api/lockPizzaDay", (req, res) => {
|
||||||
if (!req.body?.login) {
|
const login = getLogin(parseToken(req));
|
||||||
throw Error("Nebyl předán login");
|
const data = lockPizzaDay(login);
|
||||||
}
|
|
||||||
const data = lockPizzaDay(req.body.login);
|
|
||||||
io.emit("message", data);
|
io.emit("message", data);
|
||||||
res.status(200).json({});
|
res.status(200).json({});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/api/unlockPizzaDay", (req, res) => {
|
app.post("/api/unlockPizzaDay", (req, res) => {
|
||||||
if (!req.body?.login) {
|
const login = getLogin(parseToken(req));
|
||||||
throw Error("Nebyl předán login");
|
const data = unlockPizzaDay(login);
|
||||||
}
|
|
||||||
const data = unlockPizzaDay(req.body.login);
|
|
||||||
io.emit("message", data);
|
io.emit("message", data);
|
||||||
res.status(200).json({});
|
res.status(200).json({});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/api/finishOrder", (req, res) => {
|
app.post("/api/finishOrder", (req, res) => {
|
||||||
if (!req.body?.login) {
|
const login = getLogin(parseToken(req));
|
||||||
throw Error("Nebyl předán login");
|
const data = finishPizzaOrder(login);
|
||||||
}
|
|
||||||
const data = finishPizzaOrder(req.body.login);
|
|
||||||
io.emit("message", data);
|
io.emit("message", data);
|
||||||
res.status(200).json({});
|
res.status(200).json({});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/api/finishDelivery", (req, res) => {
|
app.post("/api/finishDelivery", (req, res) => {
|
||||||
if (!req.body?.login) {
|
const login = getLogin(parseToken(req));
|
||||||
throw Error("Nebyl předán login");
|
const data = finishPizzaDelivery(login, req.body.bankAccount, req.body.bankAccountHolder);
|
||||||
}
|
|
||||||
const data = finishPizzaDelivery(req.body.login, req.body.bankAccount, req.body.bankAccountHolder);
|
|
||||||
io.emit("message", data);
|
io.emit("message", data);
|
||||||
res.status(200).json({});
|
res.status(200).json({});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/api/updateChoice", (req, res) => {
|
app.post("/api/updateChoice", (req, res) => {
|
||||||
if (!req.body.hasOwnProperty('name')) {
|
const login = getLogin(parseToken(req));
|
||||||
res.status(400).json({});
|
const data = updateChoice(login, req.body.choice);
|
||||||
}
|
|
||||||
const data = updateChoice(req.body.name, req.body.choice);
|
|
||||||
io.emit("message", data);
|
io.emit("message", data);
|
||||||
res.status(200).json(data);
|
res.status(200).json(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get("/api/qr", (req, res) => {
|
app.get("/api/qr", (req, res) => {
|
||||||
if (!req.query?.login || typeof req.query.login !== 'string') {
|
const login = getLogin(parseToken(req));
|
||||||
throw Error("Nebyl předán login");
|
const img = getQr(login);
|
||||||
}
|
|
||||||
const img = getQr(req.query.login);
|
|
||||||
res.writeHead(200, {
|
res.writeHead(200, {
|
||||||
'Content-Type': 'image/png',
|
'Content-Type': 'image/png',
|
||||||
'Content-Length': img.length
|
'Content-Length': img.length
|
||||||
@ -168,13 +180,11 @@ app.get("/api/qr", (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.post("/api/updateNote", (req, res) => {
|
app.post("/api/updateNote", (req, res) => {
|
||||||
if (!req.body.login) {
|
const login = getLogin(parseToken(req));
|
||||||
throw Error("Nebyl předán login");
|
|
||||||
}
|
|
||||||
if (req.body.note && req.body.note.length > 100) {
|
if (req.body.note && req.body.note.length > 100) {
|
||||||
throw Error("Poznámka může mít maximálně 100 znaků");
|
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);
|
io.emit("message", data);
|
||||||
res.status(200).json(data);
|
res.status(200).json(data);
|
||||||
});
|
});
|
||||||
|
@ -218,6 +218,7 @@ export function finishPizzaDelivery(login: string, bankAccount?: string, bankAcc
|
|||||||
clientData.pizzaDay.state = PizzaDayState.DELIVERED;
|
clientData.pizzaDay.state = PizzaDayState.DELIVERED;
|
||||||
|
|
||||||
// Vygenerujeme QR kód, pokud k tomu máme data
|
// 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) {
|
if (bankAccount?.length && bankAccountHolder?.length) {
|
||||||
for (const order of clientData.pizzaDay.orders) {
|
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
|
if (order.customer !== login) { // zatím platí creator = objednávající, a pro toho nemá QR kód smysl
|
||||||
|
@ -109,6 +109,13 @@
|
|||||||
"@types/qs" "*"
|
"@types/qs" "*"
|
||||||
"@types/serve-static" "*"
|
"@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@*":
|
"@types/mime@*":
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
|
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"
|
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
|
||||||
integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
|
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:
|
bytes@3.1.2:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
|
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"
|
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.1.4.tgz#67ac1a10cd9c25f5ba604e4e08bc77c0ebe0ca8c"
|
||||||
integrity sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw==
|
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:
|
ee-first@1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
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"
|
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
|
||||||
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
|
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:
|
make-error@^1.1.1:
|
||||||
version "1.3.6"
|
version "1.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
|
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"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||||
|
|
||||||
ms@2.1.3:
|
ms@2.1.3, ms@^2.1.1:
|
||||||
version "2.1.3"
|
version "2.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||||
@ -753,7 +811,7 @@ raw-body@2.5.1:
|
|||||||
iconv-lite "0.4.24"
|
iconv-lite "0.4.24"
|
||||||
unpipe "1.0.0"
|
unpipe "1.0.0"
|
||||||
|
|
||||||
safe-buffer@5.2.1:
|
safe-buffer@5.2.1, safe-buffer@^5.0.1:
|
||||||
version "5.2.1"
|
version "5.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
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"
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
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:
|
send@0.18.0:
|
||||||
version "0.18.0"
|
version "0.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
|
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"
|
resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143"
|
||||||
integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==
|
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:
|
yn@3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user