From 8a75c98c9add1d8f6ed2358a6af72d2e1c649f00 Mon Sep 17 00:00:00 2001 From: Martin Berka Date: Sun, 30 Jul 2023 23:36:18 +0200 Subject: [PATCH] =?UTF-8?q?Z=C3=A1klad=20zobrazov=C3=A1n=C3=AD=20ov=C4=9B?= =?UTF-8?q?=C5=99en=C3=BDch=20u=C5=BEivatel=C5=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/App.css | 5 +++++ client/src/App.tsx | 24 +++++++++++++++++------- server/src/auth.ts | 21 +++++++++++++++++++-- server/src/index.ts | 9 +++++---- server/src/service.ts | 35 +++++++++++++++++++++++++++-------- types/Types.ts | 7 ++++++- 6 files changed, 79 insertions(+), 22 deletions(-) diff --git a/client/src/App.css b/client/src/App.css index fa663fd..954d2ef 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -98,4 +98,9 @@ .select-search-container { margin: auto; +} + +.trusted-icon { + color: rgb(0, 89, 255); + margin-right: 10px; } \ No newline at end of file diff --git a/client/src/App.tsx b/client/src/App.tsx index 7e46d58..18269a1 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -12,9 +12,9 @@ import SelectSearch, { SelectedOptionValue } from 'react-select-search'; import 'react-select-search/style.css'; import './App.css'; import { SelectSearchOption } from 'react-select-search'; -import { faTrashCan } from '@fortawesome/free-regular-svg-icons'; +import { faCircleCheck, faTrashCan } from '@fortawesome/free-regular-svg-icons'; import { useBank } from './context/bank'; -import { ClientData, Restaurants, Food, Pizza, Order, Locations, PizzaOrder, PizzaDayState } from './types'; +import { ClientData, Restaurants, Food, Pizza, Order, Locations, PizzaOrder, PizzaDayState, FoodChoices } from './types'; import Footer from './components/Footer'; @@ -111,7 +111,7 @@ function App() { } else { setFoodChoiceList(undefined); } - }, [choiceRef.current?.value]) + }, [choiceRef.current?.value, food]) const doAddChoice = async (event: React.ChangeEvent) => { const index = Object.values(Locations).indexOf(event.target.value as unknown as Locations); @@ -257,8 +257,13 @@ function App() { Poslední změny:
    -
  • (Trochu) přehlednější zobrazení tabulky
  • +
  • (Trochu) přehlednější zobrazení tabulky +
      +
    • Je to pořád ošklivý :(
    • +
    +
  • (Opět) možnost vybrat jen jednu variantu
  • +
  • "Blue checkmark" pro uživatele přihlášené přes AD

Dnes je {data.date}

@@ -300,11 +305,16 @@ function App() { - {locationLoginList.map((entry: [string, number[]], index) => { + {locationLoginList.map((entry: [string, FoodChoices], index) => { const login = entry[0]; - const userChoices = entry[1]; + const userPayload = entry[1]; + const userChoices = userPayload?.options; + const trusted = userPayload?.trusted || false; return {userChoices?.length && food ?
+ {trusted && + + } {login} {login === auth.login && { doRemoveChoices(locationKey); @@ -312,7 +322,7 @@ function App() {
    - {userChoices.map(foodIndex => { + {userChoices?.map(foodIndex => { const locationsKey = Object.keys(Locations)[Number(locationKey)] const restaurantKey = Object.keys(Restaurants).indexOf(locationsKey); const restaurant = Object.values(Restaurants)[restaurantKey]; diff --git a/server/src/auth.ts b/server/src/auth.ts index 4dcf0f0..d382ca3 100644 --- a/server/src/auth.ts +++ b/server/src/auth.ts @@ -4,9 +4,10 @@ import jwt from 'jsonwebtoken'; * Vygeneruje a vrátí podepsaný JWT token pro daný login. * * @param login přihlašovací jméno uživatele + * @param trusted příznak, zda se jedná o ověřeného uživatele * @returns JWT token */ -export function generateToken(login?: string): string { +export function generateToken(login?: string, trusted?: boolean): string { if (!process.env.JWT_SECRET) { throw Error("Není vyplněna proměnná prostředí JWT_SECRET"); } @@ -16,7 +17,7 @@ export function generateToken(login?: string): string { if (!login || login.trim().length === 0) { throw Error("Nebyl předán login"); } - return jwt.sign({ login }, process.env.JWT_SECRET); + return jwt.sign({ login, trusted: trusted || false }, process.env.JWT_SECRET); } /** @@ -50,4 +51,20 @@ export function getLogin(token?: string): string { } const payload: any = jwt.verify(token, process.env.JWT_SECRET); return payload.login; +} + +/** + * Vrátí zda je uživatel používající daný token ověřený, pokud je token platný. + * + * @param token JWT token + */ +export function getTrusted(token?: string): boolean { + 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.trusted || false; } \ No newline at end of file diff --git a/server/src/index.ts b/server/src/index.ts index 5374c6d..50e8beb 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -8,7 +8,7 @@ import dotenv from 'dotenv'; import path from 'path'; import { getMenuSladovnicka, getMenuTechTower, getMenuUMotliku } from "./restaurants"; import { getQr } from "./qr"; -import { generateToken, getLogin, verify } from "./auth"; +import { generateToken, getLogin, getTrusted, verify } from "./auth"; import { Locations, Restaurants } from "../../types"; const ENVIRONMENT = process.env.NODE_ENV || 'production'; @@ -62,7 +62,7 @@ app.post("/api/login", (req, res) => { // Autentizace pomocí trusted headers const remoteUser = req.header('remote-user'); if (remoteUser && remoteUser.length > 0) { - res.status(200).json(generateToken(remoteUser)); + res.status(200).json(generateToken(remoteUser, true)); return; } // Klasická autentizace loginem @@ -70,7 +70,7 @@ app.post("/api/login", (req, res) => { throw Error("Nebyl předán login"); } // TODO zavést podmínky pro délku loginu (min i max) - res.status(200).json(generateToken(req.body.login)); + res.status(200).json(generateToken(req.body.login, false)); }); // TODO dočasné řešení - QR se zobrazuje přes , nemáme sem jak dostat token @@ -207,8 +207,9 @@ app.post("/api/finishDelivery", (req, res) => { app.post("/api/addChoice", (req, res) => { const login = getLogin(parseToken(req)); + const trusted = getTrusted(parseToken(req)); if (req.body.locationIndex > -1) { - const data = addChoice(login, req.body.locationIndex, req.body.foodIndex); + const data = addChoice(login, trusted, req.body.locationIndex, req.body.foodIndex); io.emit("message", data); res.status(200).json(data); } diff --git a/server/src/service.ts b/server/src/service.ts index 7e4d1ef..a454a72 100644 --- a/server/src/service.ts +++ b/server/src/service.ts @@ -249,6 +249,7 @@ export function initIfNeeded() { export function removeChoices(login: string, location: Locations) { const today = formatDate(getToday()); let data: ClientData = db.get(today); + // TODO zajistit, že neověřený uživatel se stejným loginem nemůže mazat volby ověřeného if (location in data.choices) { if (login in data.choices[location]) { delete data.choices[location][login] @@ -273,11 +274,12 @@ export function removeChoices(login: string, location: Locations) { export function removeChoice(login: string, location: Locations, foodIndex: number) { const today = formatDate(getToday()); let data: ClientData = db.get(today); + // TODO řešit ověření uživatele if (location in data.choices) { if (login in data.choices[location]) { - const index = data.choices[location][login].indexOf(foodIndex); + const index = data.choices[location][login].options.indexOf(foodIndex); if (index > -1) { - data.choices[location][login].splice(index, 1) + data.choices[location][login].options.splice(index, 1) db.set(today, data); } } @@ -309,24 +311,41 @@ function removeChoiceIfPresent(login: string) { * @param login login uživatele * @param location vybrané "umístění" * @param foodIndex volitelný index jídla v daném umístění + * @param trusted příznak, zda se jedná o ověřeného uživatele * @returns aktuální data */ -export function addChoice(login: string, location: Locations, foodIndex?: number) { +export function addChoice(login: string, trusted: boolean, location: Locations, foodIndex?: number) { initIfNeeded(); + const today = formatDate(getToday()); + let data: ClientData = db.get(today); + // Ověření, že se neověřený užívatel nepokouší přepsat údaje ověřeného + const locations = Object.values(data?.choices); + let found = false; + if (!trusted) { + for (const location of locations) { + if (Object.keys(location).includes(login) && location[login].trusted) { + found = true; + } + } + } + if (!trusted && found) { + throw Error("Nelze změnit volbu ověřeného uživatele"); + } // Pokud měníme pouze lokaci, mažeme případné předchozí if (foodIndex == null) { removeChoiceIfPresent(login); } - const today = formatDate(getToday()); - let data: ClientData = db.get(today); if (!(location in data.choices)) { data.choices[location] = {}; } if (!(login in data.choices[location])) { - data.choices[location][login] = []; + data.choices[location][login] = { + trusted, + options: [] + }; } - if (foodIndex != null && !data.choices[location][login].includes(foodIndex)) { - data.choices[location][login].push(foodIndex); + if (foodIndex != null && !data.choices[location][login].options.includes(foodIndex)) { + data.choices[location][login].options.push(foodIndex); } db.set(today, data); return data; diff --git a/types/Types.ts b/types/Types.ts index f259b36..03fdf9e 100644 --- a/types/Types.ts +++ b/types/Types.ts @@ -5,9 +5,14 @@ export enum Restaurants { TECHTOWER = 'techTower', } +export interface FoodChoices { + trusted: boolean, + options: number[] +} + export interface Choices { [location: string]: { - [login: string]: number[] + [login: string]: FoodChoices }, }