Add environment variable in frontend and ssl

This commit is contained in:
Ricardo Paes
2022-02-14 08:27:05 -03:00
parent 7a4e908421
commit ac84639687
30 changed files with 207 additions and 77 deletions

View File

@@ -1,7 +1,19 @@
# MYSQL
MYSQL_ROOT_PASSWORD=strongpassword MYSQL_ROOT_PASSWORD=strongpassword
MYSQL_DATABASE=whaticket MYSQL_DATABASE=whaticket
TZ=America/Fortaleza
# BACKEND
BACKEND_PORT=8080
BACKEND_SERVER_NAME=api.mydomain.com
BACKEND_URL=https://api.mydomain.com/
JWT_SECRET=3123123213123 JWT_SECRET=3123123213123
JWT_REFRESH_SECRET=75756756756 JWT_REFRESH_SECRET=75756756756
# FRONTEND
FRONTEND_PORT=3000 FRONTEND_PORT=3000
TZ=America/Fortaleza FRONTEND_SERVER_NAME=myapp.mydomain.com
FRONTEND_URL=https://myapp.mydomain.com/
# BROWSERLESS
MAX_CONCURRENT_SESSIONS=1 MAX_CONCURRENT_SESSIONS=1

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.docker/data/ .docker/data/
ssl/
.env .env

View File

@@ -16,9 +16,11 @@ services:
- DB_NAME=${MYSQL_DATABASE:-whaticket} - DB_NAME=${MYSQL_DATABASE:-whaticket}
- JWT_SECRET=${JWT_SECRET:-3123123213123} - JWT_SECRET=${JWT_SECRET:-3123123213123}
- JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET:-75756756756} - JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET:-75756756756}
- BACKEND_URL=${BACKEND_URL:-http://localhost:3000/api} - BACKEND_URL=${BACKEND_URL:-http://localhost:8080}
- FRONTEND_URL=${BACKEND_URL:-http://localhost:3000} - FRONTEND_URL=${FRONTEND_URL:-http://localhost:3000}
- CHROME_ARGS=--no-sandbox --disable-setuid-sandbox - CHROME_ARGS=--no-sandbox --disable-setuid-sandbox
ports:
- ${BACKEND_PORT:-8080}:3000
networks: networks:
- whaticket - whaticket
@@ -29,7 +31,12 @@ services:
context: ./frontend context: ./frontend
dockerfile: ./Dockerfile dockerfile: ./Dockerfile
environment: environment:
- URL_BACKEND=http://backend:3000/ - URL_BACKEND=backend:3000
- REACT_APP_BACKEND_URL=${BACKEND_URL:-http://localhost:8080/}
- FRONTEND_SERVER_NAME=${FRONTEND_SERVER_NAME}
- BACKEND_SERVER_NAME=${BACKEND_SERVER_NAME}
volumes:
- ./ssl/:/etc/nginx/ssl/
networks: networks:
- whaticket - whaticket

View File

@@ -0,0 +1,27 @@
_writeFrontendEnvVars() {
ENV_JSON="$(jq --compact-output --null-input 'env | with_entries(select(.key | startswith("REACT_APP_")))')"
ENV_JSON_ESCAPED="$(printf "%s" "${ENV_JSON}" | sed -e 's/[\&/]/\\&/g')"
sed -i "s/<noscript id=\"env-insertion-point\"><\/noscript>/<script>var ENV=${ENV_JSON_ESCAPED}<\/script>/g" ${PUBLIC_HTML}index.html
}
_writeNginxEnvVars() {
dockerize -template /etc/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf
}
_addSslConfig() {
SSL_CERTIFICATE=/etc/nginx/ssl/${1}/fullchain.pem;
SSL_CERTIFICATE_KEY=/etc/nginx/ssl/${1}/privkey.pem;
FILE_CONF=/etc/nginx/sites.d/${1}.conf
if [ -f ${SSL_CERTIFICATE} ] && [ -f ${SSL_CERTIFICATE_KEY} ]; then
echo 'include "include.d/ssl.conf";' >> ${FILE_CONF};
echo "ssl_certificate ${SSL_CERTIFICATE};" >> ${FILE_CONF};
echo "ssl_certificate_key ${SSL_CERTIFICATE_KEY};" >> ${FILE_CONF};
fi;
}
_writeFrontendEnvVars;
_writeNginxEnvVars;
_addSslConfig 'backend'
_addSslConfig 'frontend'

View File

@@ -1,4 +1,18 @@
server { upstream backend {
server_name _; server {{ .Env.URL_BACKEND }};
include include.d/spa.conf; }
server {
listen 80;
index index.html;
root /var/www/public/;
server_name {{ default .Env.FRONTEND_SERVER_NAME "_" }};
include sites.d/frontend.conf;
}
server {
listen 80;
server_name {{ default .Env.BACKEND_SERVER_NAME "_" }};
include sites.d/backend.conf;
} }

View File

@@ -0,0 +1,45 @@
#############################################################################
# Configuration file for Let's Encrypt ACME Challenge location
# This file is already included in listen_xxx.conf files.
# Do NOT include it separately!
#############################################################################
#
# This config enables to access /.well-known/acme-challenge/xxxxxxxxxxx
# on all our sites (HTTP), including all subdomains.
# This is required by ACME Challenge (webroot authentication).
# You can check that this location is working by placing ping.txt here:
# /var/www/letsencrypt/.well-known/acme-challenge/ping.txt
# And pointing your browser to:
# http://xxx.domain.tld/.well-known/acme-challenge/ping.txt
#
# Sources:
# https://community.letsencrypt.org/t/howto-easy-cert-generation-and-renewal-with-nginx/3491
#
#############################################################################
# Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx)
# We use ^~ here, so that we don't check other regexes (for speed-up). We actually MUST cancel
# other regex checks, because in our other config files have regex rule that denies access to files with dotted names.
location ^~ /.well-known/acme-challenge/ {
# Set correct content type. According to this:
# https://community.letsencrypt.org/t/using-the-webroot-domain-verification-method/1445/29
# Current specification requires "text/plain" or no content header at all.
# It seems that "text/plain" is a safe option.
default_type "text/plain";
# This directory must be the same as in /etc/letsencrypt/cli.ini
# as "webroot-path" parameter. Also don't forget to set "authenticator" parameter
# there to "webroot".
# Do NOT use alias, use root! Target directory is located here:
# /var/www/common/letsencrypt/.well-known/acme-challenge/
root /var/www/letsencrypt;
autoindex on;
}
# Hide /acme-challenge subdirectory and return 404 on all requests.
# It is somewhat more secure than letting Nginx return 403.
# Ending slash is important!
location = /.well-known/acme-challenge/ {
return 404;
}

View File

@@ -1,16 +0,0 @@
# X-Frame-Options is to prevent from clickJacking attack
add_header X-Frame-Options SAMEORIGIN;
# disable content-type sniffing on some browsers.
add_header X-Content-Type-Options nosniff;
# This header enables the Cross-site scripting (XSS) filter
add_header X-XSS-Protection "1; mode=block";
# This will enforce HTTP browsing into HTTPS and avoid ssl stripping attack
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
add_header Referrer-Policy "no-referrer-when-downgrade";
# Enables response header of "Vary: Accept-Encoding"
gzip_vary on;

View File

@@ -1,28 +1,16 @@
listen 80; # X-Frame-Options is to prevent from clickJacking attack
index index.html; add_header X-Frame-Options SAMEORIGIN;
root /var/www/public/;
location /{{ default .Env.FOLDER_BACKEND "api" }}/ { # disable content-type sniffing on some browsers.
proxy_pass {{ .Env.URL_BACKEND }}; add_header X-Content-Type-Options nosniff;
}
location /socket.io/ { # This header enables the Cross-site scripting (XSS) filter
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; add_header X-XSS-Protection "1; mode=block";
proxy_set_header Host $host;
proxy_pass {{ .Env.URL_BACKEND }}socket.io/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location / { # This will enforce HTTP browsing into HTTPS and avoid ssl stripping attack
try_files $uri $uri/ /index.html; add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
include include.d/nocache.conf;
}
location /static { add_header Referrer-Policy "no-referrer-when-downgrade";
alias /var/www/public/static/;
include include.d/allcache.conf;
}
include include.d/root.conf; # Enables response header of "Vary: Accept-Encoding"
gzip_vary on;

View File

@@ -0,0 +1,2 @@
listen 443 ssl http2;
listen [::]:443 ssl http2;

View File

@@ -0,0 +1,11 @@
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;
}

View File

@@ -0,0 +1,11 @@
location / {
try_files $uri $uri/ /index.html;
include include.d/nocache.conf;
}
location /static {
alias /var/www/public/static/;
include include.d/allcache.conf;
}
include "include.d/spa.conf";

View File

@@ -5,20 +5,21 @@ RUN npm install
COPY .env* ./ COPY .env* ./
COPY src/ ./src/ COPY src/ ./src/
COPY public/ ./public/ COPY public/ ./public/
RUN echo "REACT_APP_BACKEND_URL=/api/" > .env.production
RUN npm run build RUN npm run build
FROM nginx:alpine FROM nginx:alpine
RUN apk add --no-cache jq openssl
RUN apk add --no-cache openssl
ENV DOCKERIZE_VERSION v0.6.1 ENV DOCKERIZE_VERSION v0.6.1
RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
&& tar -C /usr/local/bin -xzvf dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ && tar -C /usr/local/bin -xzvf dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
&& rm dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz && rm dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz
ENV PUBLIC_HTML=/var/www/public/
COPY .docker/nginx /etc/nginx/ COPY .docker/nginx /etc/nginx/
COPY --from=build-deps /usr/src/app/build /var/www/public/ COPY --from=build-deps /usr/src/app/build ${PUBLIC_HTML}
EXPOSE 80 EXPOSE 80
RUN echo "dockerize -template /etc/nginx/include.d/spa.conf:/etc/nginx/include.d/spa.conf" > /docker-entrypoint.d/01-change-url-backend.sh \ COPY .docker/add-env-vars.sh /docker-entrypoint.d/01-add-env-vars.sh
&& chmod +x /docker-entrypoint.d/01-change-url-backend.sh RUN chmod +x /docker-entrypoint.d/01-add-env-vars.sh

View File

@@ -15,6 +15,7 @@
name="viewport" name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width" content="minimum-scale=1, initial-scale=1, width=device-width"
/> />
<noscript id="env-insertion-point"></noscript>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect, useReducer, useRef } from "react"; import React, { useState, useEffect, useReducer, useRef } from "react";
import { isSameDay, parseISO, format } from "date-fns"; import { isSameDay, parseISO, format } from "date-fns";
import openSocket from "socket.io-client"; import openSocket from "../../services/socket-io";
import clsx from "clsx"; import clsx from "clsx";
import { green } from "@material-ui/core/colors"; import { green } from "@material-ui/core/colors";
@@ -358,7 +358,7 @@ const MessagesList = ({ ticketId, isGroup }) => {
}, [pageNumber, ticketId]); }, [pageNumber, ticketId]);
useEffect(() => { useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket();
socket.on("connect", () => socket.emit("joinChatBox", ticketId)); socket.on("connect", () => socket.emit("joinChatBox", ticketId));

View File

@@ -2,7 +2,7 @@ import React, { useState, useRef, useEffect, useContext } from "react";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import { format } from "date-fns"; import { format } from "date-fns";
import openSocket from "socket.io-client"; import openSocket from "../../services/socket-io";
import useSound from "use-sound"; import useSound from "use-sound";
import Popover from "@material-ui/core/Popover"; import Popover from "@material-ui/core/Popover";
@@ -78,7 +78,7 @@ const NotificationsPopOver = () => {
}, [ticketIdUrl]); }, [ticketIdUrl]);
useEffect(() => { useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket();
socket.on("connect", () => socket.emit("joinNotification")); socket.on("connect", () => socket.emit("joinNotification"));

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import QRCode from "qrcode.react"; import QRCode from "qrcode.react";
import openSocket from "socket.io-client"; import openSocket from "../../services/socket-io";
import toastError from "../../errors/toastError"; import toastError from "../../errors/toastError";
import { Dialog, DialogContent, Paper, Typography } from "@material-ui/core"; import { Dialog, DialogContent, Paper, Typography } from "@material-ui/core";
@@ -26,7 +26,7 @@ const QrcodeModal = ({ open, onClose, whatsAppId }) => {
useEffect(() => { useEffect(() => {
if (!whatsAppId) return; if (!whatsAppId) return;
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket();
socket.on("whatsappSession", data => { socket.on("whatsappSession", data => {
if (data.action === "update" && data.session.id === whatsAppId) { if (data.action === "update" && data.session.id === whatsAppId) {

View File

@@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
import { useParams, useHistory } from "react-router-dom"; import { useParams, useHistory } from "react-router-dom";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import openSocket from "socket.io-client"; import openSocket from "../../services/socket-io";
import clsx from "clsx"; import clsx from "clsx";
import { Paper, makeStyles } from "@material-ui/core"; import { Paper, makeStyles } from "@material-ui/core";
@@ -104,7 +104,7 @@ const Ticket = () => {
}, [ticketId, history]); }, [ticketId, history]);
useEffect(() => { useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket();
socket.on("connect", () => socket.emit("joinChatBox", ticketId)); socket.on("connect", () => socket.emit("joinChatBox", ticketId));

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect, useReducer, useContext } from "react"; import React, { useState, useEffect, useReducer, useContext } from "react";
import openSocket from "socket.io-client"; import openSocket from "../../services/socket-io";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import List from "@material-ui/core/List"; import List from "@material-ui/core/List";
@@ -182,7 +182,7 @@ const reducer = (state, action) => {
}, [tickets, status, searchParam]); }, [tickets, status, searchParam]);
useEffect(() => { useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket();
const shouldUpdateTicket = ticket => const shouldUpdateTicket = ticket =>
(!ticket.userId || ticket.userId === user?.id || showAll) && (!ticket.userId || ticket.userId === user?.id || showAll) &&

16
frontend/src/config.js Normal file
View File

@@ -0,0 +1,16 @@
function getConfig(name, defaultValue=null) {
// If inside a docker container, use window.ENV
if( window.ENV !== undefined ) {
return window.ENV[name] || defaultValue;
}
return process.env[name] || defaultValue;
}
export function getBackendUrl() {
return getConfig('REACT_APP_BACKEND_URL');
}
export function getHoursCloseTicketsAuto() {
return getConfig('REACT_APP_HOURS_CLOSE_TICKETS_AUTO');
}

View File

@@ -1,6 +1,6 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import openSocket from "socket.io-client"; import openSocket from "../../services/socket-io";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
@@ -71,7 +71,7 @@ const useAuth = () => {
}, []); }, []);
useEffect(() => { useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket();
socket.on("user", data => { socket.on("user", data => {
if (data.action === "update" && data.user.id === user.id) { if (data.action === "update" && data.user.id === user.id) {

View File

@@ -1,4 +1,5 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { getHoursCloseTicketsAuto } from "../../config";
import toastError from "../../errors/toastError"; import toastError from "../../errors/toastError";
import api from "../../services/api"; import api from "../../services/api";
@@ -35,7 +36,7 @@ const useTickets = ({
}) })
setTickets(data.tickets) setTickets(data.tickets)
let horasFecharAutomaticamente = process.env.REACT_APP_HOURS_CLOSE_TICKETS_AUTO let horasFecharAutomaticamente = getHoursCloseTicketsAuto();
if (status === "open" && horasFecharAutomaticamente && horasFecharAutomaticamente !== "" && if (status === "open" && horasFecharAutomaticamente && horasFecharAutomaticamente !== "" &&
horasFecharAutomaticamente !== "0" && Number(horasFecharAutomaticamente) > 0) { horasFecharAutomaticamente !== "0" && Number(horasFecharAutomaticamente) > 0) {

View File

@@ -1,5 +1,5 @@
import { useState, useEffect, useReducer } from "react"; import { useState, useEffect, useReducer } from "react";
import openSocket from "socket.io-client"; import openSocket from "../../services/socket-io";
import toastError from "../../errors/toastError"; import toastError from "../../errors/toastError";
import api from "../../services/api"; import api from "../../services/api";
@@ -73,7 +73,7 @@ const useWhatsApps = () => {
}, []); }, []);
useEffect(() => { useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket();
socket.on("whatsapp", data => { socket.on("whatsapp", data => {
if (data.action === "update") { if (data.action === "update") {

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect, useReducer, useContext } from "react"; import React, { useState, useEffect, useReducer, useContext } from "react";
import openSocket from "socket.io-client"; import openSocket from "../../services/socket-io";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
@@ -130,7 +130,7 @@ const Contacts = () => {
}, [searchParam, pageNumber]); }, [searchParam, pageNumber]);
useEffect(() => { useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket();
socket.on("contact", (data) => { socket.on("contact", (data) => {
if (data.action === "update" || data.action === "create") { if (data.action === "update" || data.action === "create") {

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useReducer, useState } from "react"; import React, { useEffect, useReducer, useState } from "react";
import openSocket from "socket.io-client"; import openSocket from "../../services/socket-io";
import { import {
Button, Button,
@@ -111,7 +111,7 @@ const Queues = () => {
}, []); }, []);
useEffect(() => { useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket();
socket.on("queue", (data) => { socket.on("queue", (data) => {
if (data.action === "update" || data.action === "create") { if (data.action === "update" || data.action === "create") {

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect, useReducer } from "react"; import React, { useState, useEffect, useReducer } from "react";
import openSocket from "socket.io-client"; import openSocket from "../../services/socket-io";
import { import {
Button, Button,
@@ -122,7 +122,7 @@ const QuickAnswers = () => {
}, [searchParam, pageNumber]); }, [searchParam, pageNumber]);
useEffect(() => { useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket();
socket.on("quickAnswer", (data) => { socket.on("quickAnswer", (data) => {
if (data.action === "update" || data.action === "create") { if (data.action === "update" || data.action === "create") {

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import openSocket from "socket.io-client"; import openSocket from "../../services/socket-io";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import Paper from "@material-ui/core/Paper"; import Paper from "@material-ui/core/Paper";
@@ -55,7 +55,7 @@ const Settings = () => {
}, []); }, []);
useEffect(() => { useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket();
socket.on("settings", data => { socket.on("settings", data => {
if (data.action === "update") { if (data.action === "update") {

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect, useReducer } from "react"; import React, { useState, useEffect, useReducer } from "react";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import openSocket from "socket.io-client"; import openSocket from "../../services/socket-io";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import Paper from "@material-ui/core/Paper"; import Paper from "@material-ui/core/Paper";
@@ -122,7 +122,7 @@ const Users = () => {
}, [searchParam, pageNumber]); }, [searchParam, pageNumber]);
useEffect(() => { useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket();
socket.on("user", (data) => { socket.on("user", (data) => {
if (data.action === "update" || data.action === "create") { if (data.action === "update" || data.action === "create") {

View File

@@ -1,7 +1,8 @@
import axios from "axios"; import axios from "axios";
import { getBackendUrl } from "../config";
const api = axios.create({ const api = axios.create({
baseURL: process.env.REACT_APP_BACKEND_URL, baseURL: getBackendUrl(),
withCredentials: true, withCredentials: true,
}); });

View File

@@ -0,0 +1,8 @@
import openSocket from "socket.io-client";
import { getBackendUrl } from "../config";
function connectToSocket() {
return openSocket(getBackendUrl());
}
export default connectToSocket;