Základ zobrazování ověřených uživatelů
This commit is contained in:
parent
028186c8ea
commit
8a75c98c9a
@ -99,3 +99,8 @@
|
|||||||
.select-search-container {
|
.select-search-container {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.trusted-icon {
|
||||||
|
color: rgb(0, 89, 255);
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
@ -12,9 +12,9 @@ import SelectSearch, { SelectedOptionValue } from 'react-select-search';
|
|||||||
import 'react-select-search/style.css';
|
import 'react-select-search/style.css';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import { SelectSearchOption } from 'react-select-search';
|
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 { 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';
|
import Footer from './components/Footer';
|
||||||
|
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ function App() {
|
|||||||
} else {
|
} else {
|
||||||
setFoodChoiceList(undefined);
|
setFoodChoiceList(undefined);
|
||||||
}
|
}
|
||||||
}, [choiceRef.current?.value])
|
}, [choiceRef.current?.value, food])
|
||||||
|
|
||||||
const doAddChoice = async (event: React.ChangeEvent<HTMLSelectElement>) => {
|
const doAddChoice = 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);
|
||||||
@ -257,8 +257,13 @@ function App() {
|
|||||||
<Alert variant={'primary'}>
|
<Alert variant={'primary'}>
|
||||||
Poslední změny:
|
Poslední změny:
|
||||||
<ul>
|
<ul>
|
||||||
<li>(Trochu) přehlednější zobrazení tabulky</li>
|
<li>(Trochu) přehlednější zobrazení tabulky
|
||||||
|
<ul>
|
||||||
|
<li>Je to pořád ošklivý :(</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
<li>(Opět) možnost vybrat jen jednu variantu</li>
|
<li>(Opět) možnost vybrat jen jednu variantu</li>
|
||||||
|
<li>"Blue checkmark" pro uživatele přihlášené přes AD</li>
|
||||||
</ul>
|
</ul>
|
||||||
</Alert>
|
</Alert>
|
||||||
<h1 className='title'>Dnes je {data.date}</h1>
|
<h1 className='title'>Dnes je {data.date}</h1>
|
||||||
@ -300,11 +305,16 @@ function App() {
|
|||||||
<td className='p-0'>
|
<td className='p-0'>
|
||||||
<Table>
|
<Table>
|
||||||
<tbody>
|
<tbody>
|
||||||
{locationLoginList.map((entry: [string, number[]], index) => {
|
{locationLoginList.map((entry: [string, FoodChoices], index) => {
|
||||||
const login = entry[0];
|
const login = entry[0];
|
||||||
const userChoices = entry[1];
|
const userPayload = entry[1];
|
||||||
|
const userChoices = userPayload?.options;
|
||||||
|
const trusted = userPayload?.trusted || false;
|
||||||
return <tr key={index}>
|
return <tr key={index}>
|
||||||
<td className='text-nowrap'>
|
<td className='text-nowrap'>
|
||||||
|
{trusted && <span className='trusted-icon'>
|
||||||
|
<FontAwesomeIcon title='Uživatel ověřený doménovým přihlášením' icon={faCircleCheck} style={{ cursor: "help" }} />
|
||||||
|
</span>}
|
||||||
{login}
|
{login}
|
||||||
{login === auth.login && <FontAwesomeIcon onClick={() => {
|
{login === auth.login && <FontAwesomeIcon onClick={() => {
|
||||||
doRemoveChoices(locationKey);
|
doRemoveChoices(locationKey);
|
||||||
@ -312,7 +322,7 @@ function App() {
|
|||||||
</td>
|
</td>
|
||||||
{userChoices?.length && food ? <td className='w-100'>
|
{userChoices?.length && food ? <td className='w-100'>
|
||||||
<ul>
|
<ul>
|
||||||
{userChoices.map(foodIndex => {
|
{userChoices?.map(foodIndex => {
|
||||||
const locationsKey = Object.keys(Locations)[Number(locationKey)]
|
const locationsKey = Object.keys(Locations)[Number(locationKey)]
|
||||||
const restaurantKey = Object.keys(Restaurants).indexOf(locationsKey);
|
const restaurantKey = Object.keys(Restaurants).indexOf(locationsKey);
|
||||||
const restaurant = Object.values(Restaurants)[restaurantKey];
|
const restaurant = Object.values(Restaurants)[restaurantKey];
|
||||||
|
@ -4,9 +4,10 @@ import jwt from 'jsonwebtoken';
|
|||||||
* Vygeneruje a vrátí podepsaný JWT token pro daný login.
|
* Vygeneruje a vrátí podepsaný JWT token pro daný login.
|
||||||
*
|
*
|
||||||
* @param login přihlašovací jméno uživatele
|
* @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
|
* @returns JWT token
|
||||||
*/
|
*/
|
||||||
export function generateToken(login?: string): string {
|
export function generateToken(login?: string, trusted?: boolean): string {
|
||||||
if (!process.env.JWT_SECRET) {
|
if (!process.env.JWT_SECRET) {
|
||||||
throw Error("Není vyplněna proměnná prostředí 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) {
|
if (!login || login.trim().length === 0) {
|
||||||
throw Error("Nebyl předán login");
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,3 +52,19 @@ export function getLogin(token?: string): string {
|
|||||||
const payload: any = jwt.verify(token, process.env.JWT_SECRET);
|
const payload: any = jwt.verify(token, process.env.JWT_SECRET);
|
||||||
return payload.login;
|
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;
|
||||||
|
}
|
@ -8,7 +8,7 @@ import dotenv from 'dotenv';
|
|||||||
import path from 'path';
|
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 { generateToken, getLogin, verify } from "./auth";
|
import { generateToken, getLogin, getTrusted, verify } from "./auth";
|
||||||
import { Locations, Restaurants } from "../../types";
|
import { Locations, Restaurants } from "../../types";
|
||||||
|
|
||||||
const ENVIRONMENT = process.env.NODE_ENV || 'production';
|
const ENVIRONMENT = process.env.NODE_ENV || 'production';
|
||||||
@ -62,7 +62,7 @@ app.post("/api/login", (req, res) => {
|
|||||||
// Autentizace pomocí trusted headers
|
// Autentizace pomocí trusted headers
|
||||||
const remoteUser = req.header('remote-user');
|
const remoteUser = req.header('remote-user');
|
||||||
if (remoteUser && remoteUser.length > 0) {
|
if (remoteUser && remoteUser.length > 0) {
|
||||||
res.status(200).json(generateToken(remoteUser));
|
res.status(200).json(generateToken(remoteUser, true));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Klasická autentizace loginem
|
// Klasická autentizace loginem
|
||||||
@ -70,7 +70,7 @@ app.post("/api/login", (req, res) => {
|
|||||||
throw Error("Nebyl předán login");
|
throw Error("Nebyl předán login");
|
||||||
}
|
}
|
||||||
// TODO zavést podmínky pro délku loginu (min i max)
|
// 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 <img>, nemáme sem jak dostat token
|
// TODO dočasné řešení - QR se zobrazuje přes <img>, nemáme sem jak dostat token
|
||||||
@ -207,8 +207,9 @@ app.post("/api/finishDelivery", (req, res) => {
|
|||||||
|
|
||||||
app.post("/api/addChoice", (req, res) => {
|
app.post("/api/addChoice", (req, res) => {
|
||||||
const login = getLogin(parseToken(req));
|
const login = getLogin(parseToken(req));
|
||||||
|
const trusted = getTrusted(parseToken(req));
|
||||||
if (req.body.locationIndex > -1) {
|
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);
|
io.emit("message", data);
|
||||||
res.status(200).json(data);
|
res.status(200).json(data);
|
||||||
}
|
}
|
||||||
|
@ -249,6 +249,7 @@ export function initIfNeeded() {
|
|||||||
export function removeChoices(login: string, location: Locations) {
|
export function removeChoices(login: string, location: Locations) {
|
||||||
const today = formatDate(getToday());
|
const today = formatDate(getToday());
|
||||||
let data: ClientData = db.get(today);
|
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 (location in data.choices) {
|
||||||
if (login in data.choices[location]) {
|
if (login in data.choices[location]) {
|
||||||
delete data.choices[location][login]
|
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) {
|
export function removeChoice(login: string, location: Locations, foodIndex: number) {
|
||||||
const today = formatDate(getToday());
|
const today = formatDate(getToday());
|
||||||
let data: ClientData = db.get(today);
|
let data: ClientData = db.get(today);
|
||||||
|
// TODO řešit ověření uživatele
|
||||||
if (location in data.choices) {
|
if (location in data.choices) {
|
||||||
if (login in data.choices[location]) {
|
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) {
|
if (index > -1) {
|
||||||
data.choices[location][login].splice(index, 1)
|
data.choices[location][login].options.splice(index, 1)
|
||||||
db.set(today, data);
|
db.set(today, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -309,24 +311,41 @@ function removeChoiceIfPresent(login: string) {
|
|||||||
* @param login login uživatele
|
* @param login login uživatele
|
||||||
* @param location vybrané "umístění"
|
* @param location vybrané "umístění"
|
||||||
* @param foodIndex volitelný index jídla v daném 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
|
* @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();
|
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í
|
// Pokud měníme pouze lokaci, mažeme případné předchozí
|
||||||
if (foodIndex == null) {
|
if (foodIndex == null) {
|
||||||
removeChoiceIfPresent(login);
|
removeChoiceIfPresent(login);
|
||||||
}
|
}
|
||||||
const today = formatDate(getToday());
|
|
||||||
let data: ClientData = db.get(today);
|
|
||||||
if (!(location in data.choices)) {
|
if (!(location in data.choices)) {
|
||||||
data.choices[location] = {};
|
data.choices[location] = {};
|
||||||
}
|
}
|
||||||
if (!(login in 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)) {
|
if (foodIndex != null && !data.choices[location][login].options.includes(foodIndex)) {
|
||||||
data.choices[location][login].push(foodIndex);
|
data.choices[location][login].options.push(foodIndex);
|
||||||
}
|
}
|
||||||
db.set(today, data);
|
db.set(today, data);
|
||||||
return data;
|
return data;
|
||||||
|
@ -5,9 +5,14 @@ export enum Restaurants {
|
|||||||
TECHTOWER = 'techTower',
|
TECHTOWER = 'techTower',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FoodChoices {
|
||||||
|
trusted: boolean,
|
||||||
|
options: number[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface Choices {
|
export interface Choices {
|
||||||
[location: string]: {
|
[location: string]: {
|
||||||
[login: string]: number[]
|
[login: string]: FoodChoices
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user