Compare commits

...

5 Commits

8 changed files with 90 additions and 26 deletions

View File

@ -26,9 +26,12 @@ Aplikace sestává ze dvou (tří) modulů.
- [Docker](https://www.docker.com) - [Docker](https://www.docker.com)
- [Docker Compose](https://docs.docker.com/compose) - [Docker Compose](https://docs.docker.com/compose)
### Spuštění ### Spuštění s nginx
- `docker compose up --build -d` - `docker compose up --build -d`
### Spuštení s traefik
- `docker compose -f compose-traefik.yml up --build -d`
## TODO ## TODO
- [x] Umožnit smazání aktuální volby "popelnicí", místo nutnosti vybrat prázdnou položku v selectu - [x] Umožnit smazání aktuální volby "popelnicí", místo nutnosti vybrat prázdnou položku v selectu
- [x] Přívětivější možnost odhlašování - [x] Přívětivější možnost odhlašování

View File

@ -18,6 +18,6 @@ ENV NODE_ENV production
WORKDIR /app WORKDIR /app
COPY --from=builder /build . COPY --from=builder /build .
EXPOSE 3000
RUN yarn global add serve && yarn RUN yarn global add serve && yarn
CMD ["serve", "-s", "."] CMD ["serve", "-s", "."]

View File

@ -22,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 = () => { export const getQrUrl = (login: string) => {
return `${getBaseUrl()}/api/qr`; return `${getBaseUrl()}/api/qr?login=${login}`;
} }
export const getData = async () => { export const getData = async () => {

View File

@ -353,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} </div> <div>Částka: {myOrder.totalPrice} </div>
<img src={getQrUrl()} alt='QR kód' /> <img src={getQrUrl(auth.login)} 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>
} }

51
compose-traefik.yml Normal file
View File

@ -0,0 +1,51 @@
version: "3"
networks:
proxy:
name: traefik_proxy
services:
traefik:
image: "traefik:latest"
container_name: "traefik"
command:
# - "--log.level=DEBUG"
#- "--log.filePath=/log/traefik.log"
- "--accesslog=true"
#- "--accessLog.filePath=/log/access.log"
- "--api.insecure=false" # pokud chci dashboard
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
#- "--entryPoints.websecure.address=:443"
restart: unless-stopped
networks:
- proxy
ports:
- "${HTTP_PORT:-80}:80"
#- "443:443"
volumes:
#- "./traefik/log:/log"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "/etc/timezone:/etc/timezone:ro"
environment:
- "TZ=Europe/Prague"
server:
build:
context: ./server
networks:
- proxy
labels:
- 'traefik.enable=true'
- 'traefik.http.routers.server.rule=Host(`${DOMAIN:-localhost}`) && (PathPrefix(`/socket.io`) || PathPrefix(`/api`))'
client:
build:
context: ./client
ports:
- 3000:3000
networks:
- proxy
labels:
- 'traefik.enable=true'
- 'traefik.http.routers.client.rule=Host(`${DOMAIN:-localhost}`)'

View File

@ -13,4 +13,6 @@ COPY src ./src
RUN yarn install --frozen-lockfile RUN yarn install --frozen-lockfile
RUN yarn build RUN yarn build
EXPOSE 3001
CMD [ "node", "/app/dist/index.js" ] CMD [ "node", "/app/dist/index.js" ]

View File

@ -6,13 +6,16 @@ import jwt from 'jsonwebtoken';
* @param login přihlašovací jméno uživatele * @param login přihlašovací jméno uživatele
* @returns JWT token * @returns JWT token
*/ */
export function generateToken(login: string): string { export function generateToken(login?: string): 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");
} }
if (process.env.JWT_SECRET.length < 32) { if (process.env.JWT_SECRET.length < 32) {
throw Error("Proměnná prostředí JWT_SECRET musí být minimálně 32 znaků"); throw Error("Proměnná prostředí JWT_SECRET musí být minimálně 32 znaků");
} }
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 }, process.env.JWT_SECRET);
} }

View File

@ -38,29 +38,44 @@ const parseToken = (req: any) => {
// ----------- Metody nevyžadující token -------------- // ----------- Metody nevyžadující token --------------
app.get("/api/whoami",(req,res)=>{ app.get("/api/whoami", (req, res) => {
res.send(req.header('remote-user')); res.send(req.header('remote-user'));
}) })
app.post("/api/login", (req, res) => { app.post("/api/login", (req, res) => {
if (!req.body?.login) { // Autentizace pomocí trusted headers
const remoteUser = req.header('remote-user');
if (remoteUser && remoteUser.length > 0) {
res.status(200).json(generateToken(remoteUser));
return;
}
// Klasická autentizace loginem
if (!req.body?.login || req.body.login.trim().length === 0) {
throw Error("Nebyl předán login"); throw Error("Nebyl předán login");
} }
// TODO: je tohle hnusny?... bude to fungovat? lol // TODO zavést podmínky pro délku loginu (min i max)
if (req.header('remote-user')){ res.status(200).json(generateToken(req.body.login));
let username = req.header('remote-user') || "jmenonemahlavicku" });
res.status(200).json(generateToken(username));
} else { // TODO dočasné řešení - QR se zobrazuje přes <img>, nemáme sem jak dostat token
// TODO zavést podmínky pro délku loginu (min i max) app.get("/api/qr", (req, res) => {
res.status(200).json(generateToken(req.body.login)); // const login = getLogin(parseToken(req));
if (!req.query?.login) {
throw Error("Nebyl předán login");
} }
const img = getQr(req.query.login as string);
res.writeHead(200, {
'Content-Type': 'image/png',
'Content-Length': img.length
});
res.end(img);
}); });
// ---------------------------------------------------- // ----------------------------------------------------
/** Middleware ověřující JWT token */ /** Middleware ověřující JWT token */
app.use((req, res, next) => { app.use((req, res, next) => {
if (req.header('remote-user')){ if (req.header('remote-user')) {
console.log("Tvuj username: %s.", req.header('remote-user')); console.log("Tvuj username: %s.", req.header('remote-user'));
} }
if (!req.headers.authorization) { if (!req.headers.authorization) {
@ -181,16 +196,6 @@ app.post("/api/updateChoice", (req, res) => {
res.status(200).json(data); res.status(200).json(data);
}); });
app.get("/api/qr", (req, res) => {
const login = getLogin(parseToken(req));
const img = getQr(login);
res.writeHead(200, {
'Content-Type': 'image/png',
'Content-Length': img.length
});
res.end(img);
});
app.post("/api/updateNote", (req, res) => { app.post("/api/updateNote", (req, res) => {
const login = getLogin(parseToken(req)); const login = getLogin(parseToken(req));
if (req.body.note && req.body.note.length > 100) { if (req.body.note && req.body.note.length > 100) {