Password Authentication
This commit is contained in:
@@ -18,6 +18,8 @@ docker-compose.yml
|
|||||||
/backend/db/firegex.db
|
/backend/db/firegex.db
|
||||||
/backend/db/firegex.db-journal
|
/backend/db/firegex.db-journal
|
||||||
|
|
||||||
|
/backend/proxy/proxy
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
**/.env.local
|
**/.env.local
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
/backend/db/firegex.db
|
/backend/db/firegex.db
|
||||||
/backend/db/firegex.db-journal
|
/backend/db/firegex.db-journal
|
||||||
|
/backend/proxy/proxy
|
||||||
# production
|
# production
|
||||||
/frontend/build
|
/frontend/build
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ RUN npm run build
|
|||||||
#Building main conteiner
|
#Building main conteiner
|
||||||
FROM python:slim-buster
|
FROM python:slim-buster
|
||||||
|
|
||||||
RUN apt-get update && apt-get -y install curl supervisor gettext-base build-essential libboost-dev nginx
|
RUN apt-get update && apt-get -y install curl supervisor gettext-base build-essential libboost-dev nginx libboost-regex-dev libboost-system-dev
|
||||||
RUN curl -sL https://deb.nodesource.com/setup_16.x | bash
|
RUN curl -sL https://deb.nodesource.com/setup_16.x | bash
|
||||||
RUN apt-get install nodejs
|
RUN apt-get install nodejs
|
||||||
|
|
||||||
@@ -31,6 +31,9 @@ COPY ./config/supervisord.conf /etc/supervisor/supervisord.conf
|
|||||||
COPY ./config/nginx.conf /tmp/nginx.conf
|
COPY ./config/nginx.conf /tmp/nginx.conf
|
||||||
COPY ./config/start_nginx.sh /tmp/start_nginx.sh
|
COPY ./config/start_nginx.sh /tmp/start_nginx.sh
|
||||||
|
|
||||||
|
RUN c++ -O3 -o proxy/proxy proxy/proxy.cpp -pthread -lboost_system -lboost_regex
|
||||||
|
RUN chmod ug+x /execute/proxy/proxy
|
||||||
|
|
||||||
#Copy react app in the main container
|
#Copy react app in the main container
|
||||||
COPY --from=frontend /app/build/ ./frontend/
|
COPY --from=frontend /app/build/ ./frontend/
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,8 @@
|
|||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
1. custom windows docker-compose
|
1. back and frontend password
|
||||||
2. backend checks and errors
|
2. compile c++ -O3
|
||||||
3. back and frontend password
|
|
||||||
4. compile c++ -O3
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Documentation
|
# Documentation
|
||||||
|
|||||||
163
backend/app.py
163
backend/app.py
@@ -1,5 +1,8 @@
|
|||||||
import sqlite3, random, string, subprocess, sys, threading, os
|
import sqlite3, random, string, subprocess, sys, threading, os, bcrypt, secrets, time
|
||||||
from flask import Flask, jsonify, request, abort
|
from flask import Flask, jsonify, request, abort, session
|
||||||
|
from functools import wraps
|
||||||
|
from flask_cors import CORS
|
||||||
|
from kthread import KThread
|
||||||
|
|
||||||
|
|
||||||
class SQLite():
|
class SQLite():
|
||||||
@@ -54,15 +57,150 @@ def gen_internal_port():
|
|||||||
break
|
break
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# DB init
|
# DB init
|
||||||
db = SQLite('firegex')
|
db = SQLite('firegex')
|
||||||
db.connect()
|
db.connect()
|
||||||
|
|
||||||
|
|
||||||
|
class KeyValueStorage:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get(self, key):
|
||||||
|
q = db.query('SELECT value FROM keys_values WHERE key = ?', (key,))
|
||||||
|
if len(q) == 0:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return q[0][0]
|
||||||
|
|
||||||
|
def put(self, key, value):
|
||||||
|
if self.get(key) is None:
|
||||||
|
db.query('INSERT INTO keys_values (key, value) VALUES (?, ?);', (key,str(value)))
|
||||||
|
else:
|
||||||
|
db.query('UPDATE keys_values SET value=? WHERE key = ?;', (str(value), key))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
conf = KeyValueStorage()
|
||||||
|
proxy_table = {}
|
||||||
|
|
||||||
DEBUG = len(sys.argv) > 1 and sys.argv[1] == "DEBUG"
|
DEBUG = len(sys.argv) > 1 and sys.argv[1] == "DEBUG"
|
||||||
|
|
||||||
|
def is_loggined():
|
||||||
|
if DEBUG: return True
|
||||||
|
return True if session.get("loggined") else False
|
||||||
|
|
||||||
|
def login_required(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
if is_loggined() or DEBUG:
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
return abort(401)
|
||||||
|
|
||||||
|
return decorated_function
|
||||||
|
|
||||||
|
def get_service_data(id):
|
||||||
|
q = db.query('SELECT * FROM services WHERE service_id=?;',(id,))
|
||||||
|
if len(q) == 0: return None
|
||||||
|
srv = q[0]
|
||||||
|
return {
|
||||||
|
'id': srv[1],
|
||||||
|
'status': srv[0],
|
||||||
|
'public_port': srv[3],
|
||||||
|
'internal_port': srv[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
def service_manager(id):
|
||||||
|
data = get_service_data(id)
|
||||||
|
if data is None: return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.before_first_request
|
||||||
|
def before_first_request():
|
||||||
|
|
||||||
|
|
||||||
|
services = [
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
for srv in services:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
app.config['SECRET_KEY'] = secrets.token_hex(32)
|
||||||
|
if DEBUG:
|
||||||
|
app.config["STATUS"] = "run"
|
||||||
|
elif conf.get("password") is None:
|
||||||
|
app.config["STATUS"] = "init"
|
||||||
|
else:
|
||||||
|
app.config["STATUS"] = "run"
|
||||||
|
|
||||||
|
@app.route("/api/status")
|
||||||
|
def get_status():
|
||||||
|
return {
|
||||||
|
"status":app.config["STATUS"],
|
||||||
|
"loggined": is_loggined()
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.route("/api/login", methods = ['POST'])
|
||||||
|
def login():
|
||||||
|
if not conf.get("password"): return abort(404)
|
||||||
|
req = request.get_json(force = True)
|
||||||
|
if not "password" in req or not isinstance(req["password"],str):
|
||||||
|
return abort(400)
|
||||||
|
if req["password"] == "":
|
||||||
|
return {"status":"Cannot insert an empty password!"}
|
||||||
|
time.sleep(.3) # No bruteforce :)
|
||||||
|
if bcrypt.checkpw(req["password"].encode(), conf.get("password").encode()):
|
||||||
|
session["loggined"] = True
|
||||||
|
return { "status":"ok" }
|
||||||
|
return {"status":"Wrong password!"}
|
||||||
|
|
||||||
|
@app.route("/api/logout")
|
||||||
|
def logout():
|
||||||
|
session["loggined"] = False
|
||||||
|
return { "status":"ok" }
|
||||||
|
|
||||||
|
@app.route('/api/change-password', methods = ['POST'])
|
||||||
|
@login_required
|
||||||
|
def change_password():
|
||||||
|
req = request.get_json(force = True)
|
||||||
|
if not "password" in req or not isinstance(req["password"],str):
|
||||||
|
return abort(400)
|
||||||
|
if req["password"] == "":
|
||||||
|
return {"status":"Cannot insert an empty password!"}
|
||||||
|
if req["expire"]:
|
||||||
|
app.config['SECRET_KEY'] = secrets.token_hex(32)
|
||||||
|
session["loggined"] = True
|
||||||
|
hash_psw = bcrypt.hashpw(req["password"].encode(), bcrypt.gensalt())
|
||||||
|
conf.put("password",hash_psw.decode())
|
||||||
|
return {"status":"ok"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/set-password', methods = ['POST'])
|
||||||
|
def set_password():
|
||||||
|
if conf.get("password"): return abort(404)
|
||||||
|
req = request.get_json(force = True)
|
||||||
|
if not "password" in req or not isinstance(req["password"],str):
|
||||||
|
return abort(400)
|
||||||
|
if req["password"] == "":
|
||||||
|
return {"status":"Cannot insert an empty password!"}
|
||||||
|
|
||||||
|
hash_psw = bcrypt.hashpw(req["password"].encode(), bcrypt.gensalt())
|
||||||
|
conf.put("password",hash_psw.decode())
|
||||||
|
app.config["STATUS"] = "run"
|
||||||
|
session["loggined"] = True
|
||||||
|
return {"status":"ok"}
|
||||||
|
|
||||||
@app.route('/api/general-stats')
|
@app.route('/api/general-stats')
|
||||||
|
@login_required
|
||||||
def get_general_stats():
|
def get_general_stats():
|
||||||
n_services = db.query('''
|
n_services = db.query('''
|
||||||
SELECT COUNT (*) FROM services;
|
SELECT COUNT (*) FROM services;
|
||||||
@@ -81,6 +219,7 @@ def get_general_stats():
|
|||||||
}
|
}
|
||||||
|
|
||||||
@app.route('/api/services')
|
@app.route('/api/services')
|
||||||
|
@login_required
|
||||||
def get_services():
|
def get_services():
|
||||||
res = []
|
res = []
|
||||||
for i in db.query('SELECT * FROM services;'):
|
for i in db.query('SELECT * FROM services;'):
|
||||||
@@ -101,6 +240,7 @@ def get_services():
|
|||||||
|
|
||||||
|
|
||||||
@app.route('/api/service/<serv>')
|
@app.route('/api/service/<serv>')
|
||||||
|
@login_required
|
||||||
def get_service(serv):
|
def get_service(serv):
|
||||||
q = db.query('SELECT * FROM services WHERE service_id = ?;', (serv,))
|
q = db.query('SELECT * FROM services WHERE service_id = ?;', (serv,))
|
||||||
if len(q) != 0:
|
if len(q) != 0:
|
||||||
@@ -119,6 +259,7 @@ def get_service(serv):
|
|||||||
return abort(404)
|
return abort(404)
|
||||||
|
|
||||||
@app.route('/api/service/<serv>/stop')
|
@app.route('/api/service/<serv>/stop')
|
||||||
|
@login_required
|
||||||
def get_service_stop(serv):
|
def get_service_stop(serv):
|
||||||
db.query('''
|
db.query('''
|
||||||
UPDATE services SET status = 'stop' WHERE service_id = ?;
|
UPDATE services SET status = 'stop' WHERE service_id = ?;
|
||||||
@@ -129,6 +270,7 @@ def get_service_stop(serv):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@app.route('/api/service/<serv>/pause')
|
@app.route('/api/service/<serv>/pause')
|
||||||
|
@login_required
|
||||||
def get_service_pause(serv):
|
def get_service_pause(serv):
|
||||||
db.query('''
|
db.query('''
|
||||||
UPDATE services SET status = 'pause' WHERE service_id = ?;
|
UPDATE services SET status = 'pause' WHERE service_id = ?;
|
||||||
@@ -139,6 +281,7 @@ def get_service_pause(serv):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@app.route('/api/service/<serv>/start')
|
@app.route('/api/service/<serv>/start')
|
||||||
|
@login_required
|
||||||
def get_service_start(serv):
|
def get_service_start(serv):
|
||||||
db.query('''
|
db.query('''
|
||||||
UPDATE services SET status = 'wait' WHERE service_id = ?;
|
UPDATE services SET status = 'wait' WHERE service_id = ?;
|
||||||
@@ -149,6 +292,7 @@ def get_service_start(serv):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@app.route('/api/service/<serv>/delete')
|
@app.route('/api/service/<serv>/delete')
|
||||||
|
@login_required
|
||||||
def get_service_delete(serv):
|
def get_service_delete(serv):
|
||||||
db.query('DELETE FROM services WHERE service_id = ?;', (serv,))
|
db.query('DELETE FROM services WHERE service_id = ?;', (serv,))
|
||||||
db.query('DELETE FROM regexes WHERE service_id = ?;', (serv,))
|
db.query('DELETE FROM regexes WHERE service_id = ?;', (serv,))
|
||||||
@@ -159,6 +303,7 @@ def get_service_delete(serv):
|
|||||||
|
|
||||||
|
|
||||||
@app.route('/api/service/<serv>/regen-port')
|
@app.route('/api/service/<serv>/regen-port')
|
||||||
|
@login_required
|
||||||
def get_regen_port(serv):
|
def get_regen_port(serv):
|
||||||
db.query('UPDATE services SET internal_port = ? WHERE service_id = ?;', (gen_internal_port(), serv))
|
db.query('UPDATE services SET internal_port = ? WHERE service_id = ?;', (gen_internal_port(), serv))
|
||||||
return {
|
return {
|
||||||
@@ -167,6 +312,7 @@ def get_regen_port(serv):
|
|||||||
|
|
||||||
|
|
||||||
@app.route('/api/service/<serv>/regexes')
|
@app.route('/api/service/<serv>/regexes')
|
||||||
|
@login_required
|
||||||
def get_service_regexes(serv):
|
def get_service_regexes(serv):
|
||||||
return jsonify([
|
return jsonify([
|
||||||
{
|
{
|
||||||
@@ -181,6 +327,7 @@ def get_service_regexes(serv):
|
|||||||
|
|
||||||
|
|
||||||
@app.route('/api/regex/<int:regex_id>')
|
@app.route('/api/regex/<int:regex_id>')
|
||||||
|
@login_required
|
||||||
def get_regex_id(regex_id):
|
def get_regex_id(regex_id):
|
||||||
q = db.query('SELECT * FROM regexes WHERE regex_id = ?;', (regex_id,))
|
q = db.query('SELECT * FROM regexes WHERE regex_id = ?;', (regex_id,))
|
||||||
if len(q) != 0:
|
if len(q) != 0:
|
||||||
@@ -197,6 +344,7 @@ def get_regex_id(regex_id):
|
|||||||
|
|
||||||
|
|
||||||
@app.route('/api/regex/<int:regex_id>/delete')
|
@app.route('/api/regex/<int:regex_id>/delete')
|
||||||
|
@login_required
|
||||||
def get_regex_delete(regex_id):
|
def get_regex_delete(regex_id):
|
||||||
db.query('DELETE FROM regexes WHERE regex_id = ?;', (regex_id,))
|
db.query('DELETE FROM regexes WHERE regex_id = ?;', (regex_id,))
|
||||||
|
|
||||||
@@ -206,6 +354,7 @@ def get_regex_delete(regex_id):
|
|||||||
|
|
||||||
|
|
||||||
@app.route('/api/regexes/add', methods = ['POST'])
|
@app.route('/api/regexes/add', methods = ['POST'])
|
||||||
|
@login_required
|
||||||
def post_regexes_add():
|
def post_regexes_add():
|
||||||
req = request.get_json(force = True)
|
req = request.get_json(force = True)
|
||||||
|
|
||||||
@@ -219,6 +368,7 @@ def post_regexes_add():
|
|||||||
|
|
||||||
|
|
||||||
@app.route('/api/services/add', methods = ['POST'])
|
@app.route('/api/services/add', methods = ['POST'])
|
||||||
|
@login_required
|
||||||
def post_services_add():
|
def post_services_add():
|
||||||
req = request.get_json(force = True)
|
req = request.get_json(force = True)
|
||||||
serv_id = from_name_get_id(req['name'])
|
serv_id = from_name_get_id(req['name'])
|
||||||
@@ -233,8 +383,7 @@ def post_services_add():
|
|||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
from flask_cors import CORS
|
CORS(app, resources={r"/api/*": {"origins": "*"}}, supports_credentials=True )
|
||||||
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
db.check_integrity({
|
db.check_integrity({
|
||||||
@@ -254,10 +403,14 @@ if __name__ == '__main__':
|
|||||||
'regex_id': 'INTEGER PRIMARY KEY',
|
'regex_id': 'INTEGER PRIMARY KEY',
|
||||||
'FOREIGN KEY (service_id)':'REFERENCES services (service_id)'
|
'FOREIGN KEY (service_id)':'REFERENCES services (service_id)'
|
||||||
},
|
},
|
||||||
|
'keys_values': {
|
||||||
|
'key': 'VARCHAR(100) PRIMARY KEY',
|
||||||
|
'value': 'VARCHAR(100) NOT NULL',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
app.run(host="0.0.0.0", port=8080 ,debug=True)
|
app.run(host="0.0.0.0", port=8080 ,debug=True)
|
||||||
else:
|
else:
|
||||||
subprocess.run(["uwsgi","--socket","./uwsgi.sock","--master","--module","app:app"])
|
subprocess.run(["uwsgi","--socket","./uwsgi.sock","--master","--module","app:app", "--enable-threads"])
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -1,9 +1,5 @@
|
|||||||
click==8.1.3
|
Flask
|
||||||
colorama==0.4.4
|
|
||||||
Flask==2.1.2
|
|
||||||
itsdangerous==2.1.2
|
|
||||||
Jinja2==3.1.2
|
|
||||||
MarkupSafe==2.1.1
|
|
||||||
Werkzeug==2.1.2
|
|
||||||
uwsgi
|
uwsgi
|
||||||
flask-cors
|
flask-cors
|
||||||
|
bcrypt
|
||||||
|
kthread
|
||||||
13
docker-compose.test.yml
Executable file
13
docker-compose.test.yml
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
version: '3.9'
|
||||||
|
|
||||||
|
services:
|
||||||
|
firewall:
|
||||||
|
restart: unless-stopped
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
environment:
|
||||||
|
- NGINX_PORT=80
|
||||||
|
volumes:
|
||||||
|
- /execute/db
|
||||||
|
|
||||||
@@ -4,11 +4,9 @@ services:
|
|||||||
firewall:
|
firewall:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
build: .
|
build: .
|
||||||
#network_mode: "host"
|
network_mode: "host"
|
||||||
ports:
|
|
||||||
- 80:80
|
|
||||||
environment:
|
environment:
|
||||||
- NGINX_PORT=80
|
- NGINX_PORT=4444
|
||||||
volumes:
|
volumes:
|
||||||
- /execute/db
|
- /execute/db
|
||||||
|
|
||||||
@@ -1,17 +1,142 @@
|
|||||||
import React from 'react';
|
import { Button, Group, Loader, LoadingOverlay, Notification, Space, TextInput, Title } from '@mantine/core';
|
||||||
|
import { useForm } from '@mantine/hooks';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { ImCross } from 'react-icons/im';
|
||||||
import { Navigate, Outlet, Route, Routes } from 'react-router-dom';
|
import { Navigate, Outlet, Route, Routes } from 'react-router-dom';
|
||||||
import MainLayout from './components/MainLayout';
|
import MainLayout from './components/MainLayout';
|
||||||
|
import { PasswordSend, ServerStatusResponse, update_freq } from './js/models';
|
||||||
|
import { getstatus, login, setpassword } from './js/utils';
|
||||||
import HomePage from './pages/HomePage';
|
import HomePage from './pages/HomePage';
|
||||||
import ServiceDetails from './pages/ServiceDetails';
|
import ServiceDetails from './pages/ServiceDetails';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return <Routes>
|
|
||||||
<Route element={<MainLayout><Outlet /></MainLayout>}>
|
const [loading, setLoading] = useState(true);
|
||||||
<Route index element={<HomePage />} />
|
const [systemStatus, setSystemStatus] = useState<ServerStatusResponse>({status:"", loggined:false})
|
||||||
<Route path=":srv_id" element={<ServiceDetails />} />
|
const [reqError, setReqError] = useState<undefined|string>()
|
||||||
<Route path="*" element={<Navigate to="/" />} />
|
const [error, setError] = useState<string|null>()
|
||||||
</Route>
|
const [loadinBtn, setLoadingBtn] = useState(false);
|
||||||
</Routes>
|
|
||||||
|
const getStatus = () =>{
|
||||||
|
getstatus().then( res =>{
|
||||||
|
setSystemStatus(res)
|
||||||
|
setReqError(undefined)
|
||||||
|
setLoading(false)
|
||||||
|
}).catch(err=>{
|
||||||
|
setReqError(err.toString())
|
||||||
|
setLoading(false)
|
||||||
|
setTimeout(getStatus, update_freq)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(getStatus,[])
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
initialValues: {
|
||||||
|
password:"",
|
||||||
|
},
|
||||||
|
validationRules:{
|
||||||
|
password: (value) => value !== ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
if (loading){
|
||||||
|
return <LoadingOverlay visible/>
|
||||||
|
}else if (reqError){
|
||||||
|
return <div className='center-flex-row' style={{padding:"100px"}}>
|
||||||
|
<Title order={1} align="center">Errore nel caricamento del firewall! 🔥</Title>
|
||||||
|
<Space h="md" />
|
||||||
|
<Title order={4} align="center">Errore nella comunicazione con il backend</Title>
|
||||||
|
<Space h="md" />
|
||||||
|
Errore: {reqError}
|
||||||
|
<Space h="xl" />
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
|
}else if (systemStatus.status === "init"){
|
||||||
|
|
||||||
|
const submitRequest = async (values:PasswordSend) => {
|
||||||
|
setLoadingBtn(true)
|
||||||
|
await setpassword(values).then(res => {
|
||||||
|
if(!res){
|
||||||
|
setSystemStatus({loggined:true, status:"run"})
|
||||||
|
}else{
|
||||||
|
setError(res)
|
||||||
|
}
|
||||||
|
}).catch( err => setError(err.toString()))
|
||||||
|
setLoadingBtn(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return <div className='center-flex-row' style={{padding:"100px"}}>
|
||||||
|
<Title order={3} align="center">Choose the password for access to the firewall 🔒</Title>
|
||||||
|
<Space h="xl" />
|
||||||
|
<form onSubmit={form.onSubmit(submitRequest)} style={{width:"80%"}}>
|
||||||
|
<TextInput
|
||||||
|
label="Password"
|
||||||
|
placeholder="$3cr3t"
|
||||||
|
{...form.getInputProps('password')}
|
||||||
|
/>
|
||||||
|
<Group position="right" mt="md">
|
||||||
|
<Button loading={loadinBtn} type="submit">Set Password</Button>
|
||||||
|
</Group>
|
||||||
|
</form>
|
||||||
|
<Space h="xl" />
|
||||||
|
{error?<>
|
||||||
|
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
|
||||||
|
Error: {error}
|
||||||
|
</Notification><Space h="md" /></>:null}
|
||||||
|
</div>
|
||||||
|
}else if (systemStatus.status === "run" && !systemStatus.loggined){
|
||||||
|
const submitRequest = async (values:PasswordSend) => {
|
||||||
|
setLoadingBtn(true)
|
||||||
|
await login(values).then(res => {
|
||||||
|
if(!res){
|
||||||
|
setSystemStatus({...systemStatus, loggined:true})
|
||||||
|
}else{
|
||||||
|
setError(res)
|
||||||
|
}
|
||||||
|
}).catch( err => setError(err.toString()))
|
||||||
|
setLoadingBtn(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return <div className='center-flex-row' style={{padding:"100px"}}>
|
||||||
|
<Title order={2} align="center">Welcome to Firegex 🔥</Title>
|
||||||
|
<Space h="xl" />
|
||||||
|
<Title order={2} align="center">Before you use the firewall, insert a password 🔒</Title>
|
||||||
|
<Space h="xl" />
|
||||||
|
<form onSubmit={form.onSubmit(submitRequest)} style={{width:"80%"}}>
|
||||||
|
<TextInput
|
||||||
|
label="Password"
|
||||||
|
placeholder="$3cr3t"
|
||||||
|
{...form.getInputProps('password')}
|
||||||
|
/>
|
||||||
|
<Group position="right" mt="md">
|
||||||
|
<Button loading={loadinBtn} type="submit">Login</Button>
|
||||||
|
</Group>
|
||||||
|
</form>
|
||||||
|
<Space h="xl" />
|
||||||
|
{error?<>
|
||||||
|
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
|
||||||
|
Error: {error}
|
||||||
|
</Notification><Space h="md" /></>:null}
|
||||||
|
</div>
|
||||||
|
}else if (systemStatus.status === "run" && systemStatus.loggined){
|
||||||
|
return <Routes>
|
||||||
|
<Route element={<MainLayout><Outlet /></MainLayout>}>
|
||||||
|
<Route index element={<HomePage />} />
|
||||||
|
<Route path=":srv_id" element={<ServiceDetails />} />
|
||||||
|
<Route path="*" element={<Navigate to="/" />} />
|
||||||
|
</Route>
|
||||||
|
</Routes>
|
||||||
|
}else{
|
||||||
|
return <div className='center-flex-row' style={{padding:"100px"}}>
|
||||||
|
<Title order={1} align="center">Errore nel caricamento del firewall! 🔥</Title>
|
||||||
|
<Space h="md" />
|
||||||
|
<Title order={4} align="center">Errore nella comunicazione con il backend</Title>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ function AddNewRegex({ closePopup, service }:{ closePopup:()=>void, service:stri
|
|||||||
|
|
||||||
return <form onSubmit={form.onSubmit(submitRequest)}>
|
return <form onSubmit={form.onSubmit(submitRequest)}>
|
||||||
<TextInput
|
<TextInput
|
||||||
required
|
|
||||||
label="Regex"
|
label="Regex"
|
||||||
placeholder="[A-Z0-9]{31}="
|
placeholder="[A-Z0-9]{31}="
|
||||||
{...form.getInputProps('regex')}
|
{...form.getInputProps('regex')}
|
||||||
@@ -93,14 +92,12 @@ function AddNewRegex({ closePopup, service }:{ closePopup:()=>void, service:stri
|
|||||||
data={['C -> S', 'S -> C', 'C <-> S']}
|
data={['C -> S', 'S -> C', 'C <-> S']}
|
||||||
label="Choose the source of the packets to filter"
|
label="Choose the source of the packets to filter"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
required
|
|
||||||
{...form.getInputProps('mode')}
|
{...form.getInputProps('mode')}
|
||||||
/>
|
/>
|
||||||
<Space h="md" />
|
<Space h="md" />
|
||||||
<FilterTypeSelector
|
<FilterTypeSelector
|
||||||
size="md"
|
size="md"
|
||||||
color="gray"
|
color="gray"
|
||||||
required
|
|
||||||
{...form.getInputProps('type')}
|
{...form.getInputProps('type')}
|
||||||
/>
|
/>
|
||||||
<Group position="right" mt="md">
|
<Group position="right" mt="md">
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ function AddNewService({ closePopup }:{ closePopup:()=>void }) {
|
|||||||
|
|
||||||
return <form onSubmit={form.onSubmit(submitRequest)}>
|
return <form onSubmit={form.onSubmit(submitRequest)}>
|
||||||
<TextInput
|
<TextInput
|
||||||
required
|
|
||||||
label="Service name"
|
label="Service name"
|
||||||
placeholder="Challenge 01"
|
placeholder="Challenge 01"
|
||||||
{...form.getInputProps('name')}
|
{...form.getInputProps('name')}
|
||||||
@@ -49,7 +48,6 @@ function AddNewService({ closePopup }:{ closePopup:()=>void }) {
|
|||||||
<Space h="md" />
|
<Space h="md" />
|
||||||
|
|
||||||
<NumberInput
|
<NumberInput
|
||||||
required
|
|
||||||
placeholder="8080"
|
placeholder="8080"
|
||||||
min={1}
|
min={1}
|
||||||
max={65535}
|
max={65535}
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { ActionIcon, Badge, Image, Modal } from '@mantine/core';
|
import { ActionIcon, Badge, Button, Divider, Group, Image, Menu, Modal, Notification, Space, Switch, TextInput } from '@mantine/core';
|
||||||
import style from "./Header.module.scss";
|
import style from "./Header.module.scss";
|
||||||
import { errorNotify, generalstats } from '../../js/utils';
|
import { changepassword, errorNotify, generalstats, logout, okNotify } from '../../js/utils';
|
||||||
import { GeneralStats, update_freq } from '../../js/models';
|
import { ChangePassword, GeneralStats, update_freq } from '../../js/models';
|
||||||
import { BsPlusLg } from "react-icons/bs"
|
import { BsPlusLg } from "react-icons/bs"
|
||||||
import { AiFillHome } from "react-icons/ai"
|
import { AiFillHome } from "react-icons/ai"
|
||||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||||
import AddNewRegex from '../AddNewRegex';
|
import AddNewRegex from '../AddNewRegex';
|
||||||
import AddNewService from '../AddNewService';
|
import AddNewService from '../AddNewService';
|
||||||
|
import { MdSettings } from 'react-icons/md';
|
||||||
|
import { FaLock } from 'react-icons/fa';
|
||||||
|
import { ImCross, ImExit } from 'react-icons/im';
|
||||||
|
import { useForm } from '@mantine/hooks';
|
||||||
|
|
||||||
|
|
||||||
function Header() {
|
function Header() {
|
||||||
@@ -30,9 +34,50 @@ function Header() {
|
|||||||
const updater = setInterval(updateInfo, update_freq)
|
const updater = setInterval(updateInfo, update_freq)
|
||||||
return () => { clearInterval(updater) }
|
return () => { clearInterval(updater) }
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const logout_action = () => {
|
||||||
|
logout().then(r => {
|
||||||
|
window.location.reload()
|
||||||
|
}).catch(r => {
|
||||||
|
errorNotify("Logout failed!",`Error: ${r}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
initialValues: {
|
||||||
|
password:"",
|
||||||
|
expire:true
|
||||||
|
},
|
||||||
|
validationRules:{
|
||||||
|
password: (value) => value !== ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const [loadingBtn, setLoadingBtn] = useState(false)
|
||||||
|
const [error, setError] = useState<null|string>(null)
|
||||||
|
const [changePasswordModal, setChangePasswordModal] = useState(false);
|
||||||
|
|
||||||
|
const submitRequest = async (values:ChangePassword) => {
|
||||||
|
setLoadingBtn(true)
|
||||||
|
await changepassword(values).then(res => {
|
||||||
|
if(!res){
|
||||||
|
okNotify("Password change done!","The password of the firewall has been changed!")
|
||||||
|
setChangePasswordModal(false)
|
||||||
|
form.reset()
|
||||||
|
}else{
|
||||||
|
setError(res)
|
||||||
|
}
|
||||||
|
}).catch( err => setError(err.toString()))
|
||||||
|
setLoadingBtn(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const {srv_id} = useParams()
|
const {srv_id} = useParams()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const closeModal = () => {setOpen(false);}
|
const closeModal = () => {setOpen(false);}
|
||||||
|
|
||||||
@@ -45,6 +90,13 @@ function Header() {
|
|||||||
<Badge style={{marginLeft:"10px"}} size="lg" color="yellow" variant="filled">Filtered Connections: {generalStats.closed}</Badge>
|
<Badge style={{marginLeft:"10px"}} size="lg" color="yellow" variant="filled">Filtered Connections: {generalStats.closed}</Badge>
|
||||||
<Badge style={{marginLeft:"10px"}} size="lg" color="violet" variant="filled">Regexes: {generalStats.regexes}</Badge>
|
<Badge style={{marginLeft:"10px"}} size="lg" color="violet" variant="filled">Regexes: {generalStats.regexes}</Badge>
|
||||||
<div style={{marginLeft:"20px"}}></div>
|
<div style={{marginLeft:"20px"}}></div>
|
||||||
|
<Menu>
|
||||||
|
<Menu.Label>Firewall Access</Menu.Label>
|
||||||
|
<Menu.Item icon={<ImExit size={14} />} onClick={logout_action}>Logout</Menu.Item>
|
||||||
|
<Divider />
|
||||||
|
<Menu.Item color="red" icon={<FaLock size={14} />} onClick={() => setChangePasswordModal(true)}>Change Password</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
<div style={{marginLeft:"20px"}}></div>
|
||||||
{ location.pathname !== "/"?
|
{ location.pathname !== "/"?
|
||||||
<ActionIcon color="teal" style={{marginRight:"10px"}}
|
<ActionIcon color="teal" style={{marginRight:"10px"}}
|
||||||
size="xl" radius="md" variant="filled"
|
size="xl" radius="md" variant="filled"
|
||||||
@@ -61,8 +113,31 @@ function Header() {
|
|||||||
<AddNewService closePopup={closeModal} />
|
<AddNewService closePopup={closeModal} />
|
||||||
</Modal>
|
</Modal>
|
||||||
}
|
}
|
||||||
|
<Modal size="xl" title="Change Firewall Password" opened={changePasswordModal} onClose={()=>setChangePasswordModal(false)} closeOnClickOutside={false} centered>
|
||||||
|
|
||||||
|
<form onSubmit={form.onSubmit(submitRequest)}>
|
||||||
|
<Space h="md" />
|
||||||
|
<TextInput
|
||||||
|
label="New Password"
|
||||||
|
placeholder="$3cr3t"
|
||||||
|
{...form.getInputProps('password')}
|
||||||
|
/>
|
||||||
|
<Space h="md" />
|
||||||
|
<Switch
|
||||||
|
label="Expire the login status to all connections"
|
||||||
|
{...form.getInputProps('expire', { type: 'checkbox' })}
|
||||||
|
/>
|
||||||
|
<Space h="md" />
|
||||||
|
<Group position="right" mt="md">
|
||||||
|
<Button loading={loadingBtn} type="submit">Change Password</Button>
|
||||||
|
</Group>
|
||||||
|
</form>
|
||||||
|
<Space h="xl" />
|
||||||
|
{error?<>
|
||||||
|
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
|
||||||
|
Error: {error}
|
||||||
|
</Notification><Space h="md" /></>:null}
|
||||||
|
</Modal>
|
||||||
<div style={{marginLeft:"40px"}}></div>
|
<div style={{marginLeft:"40px"}}></div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { Container, MantineProvider, Space } from '@mantine/core';
|
|
||||||
import { NotificationsProvider } from '@mantine/notifications';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Container, Space } from '@mantine/core';
|
||||||
import Footer from './Footer';
|
import Footer from './Footer';
|
||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
|
|
||||||
function MainLayout({ children }:{ children:any }) {
|
function MainLayout({ children }:{ children:any }) {
|
||||||
return <>
|
return <>
|
||||||
<MantineProvider theme={{ colorScheme: 'dark' }} withGlobalStyles withNormalizeCSS>
|
|
||||||
<NotificationsProvider>
|
|
||||||
<Header />
|
<Header />
|
||||||
<Space h="xl" />
|
<Space h="xl" />
|
||||||
<Container size="xl" style={{minHeight:"57.5vh"}}>
|
<Container size="xl" style={{minHeight:"57.5vh"}}>
|
||||||
@@ -15,8 +13,7 @@ function MainLayout({ children }:{ children:any }) {
|
|||||||
</Container>
|
</Container>
|
||||||
<Space h="xl" />
|
<Space h="xl" />
|
||||||
<Footer />
|
<Footer />
|
||||||
</NotificationsProvider>
|
|
||||||
</MantineProvider>
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,18 @@ import ReactDOM from 'react-dom/client';
|
|||||||
import { BrowserRouter } from "react-router-dom"
|
import { BrowserRouter } from "react-router-dom"
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
import { MantineProvider } from '@mantine/core';
|
||||||
|
import { NotificationsProvider } from '@mantine/notifications';
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(
|
const root = ReactDOM.createRoot(
|
||||||
document.getElementById('root') as HTMLElement
|
document.getElementById('root') as HTMLElement
|
||||||
);
|
);
|
||||||
root.render(
|
root.render(
|
||||||
<BrowserRouter>
|
<MantineProvider theme={{ colorScheme: 'dark' }} withGlobalStyles withNormalizeCSS>
|
||||||
<App />
|
<NotificationsProvider>
|
||||||
</BrowserRouter>
|
<BrowserRouter>
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
|
</NotificationsProvider>
|
||||||
|
</MantineProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,6 +28,20 @@ export type ServerResponse = {
|
|||||||
status:string
|
status:string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ServerStatusResponse = {
|
||||||
|
status:string,
|
||||||
|
loggined:boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PasswordSend = {
|
||||||
|
password:string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ChangePassword = {
|
||||||
|
password:string,
|
||||||
|
expire:boolean
|
||||||
|
}
|
||||||
|
|
||||||
export type RegexFilter = {
|
export type RegexFilter = {
|
||||||
id:number,
|
id:number,
|
||||||
service_id:string,
|
service_id:string,
|
||||||
|
|||||||
@@ -1,27 +1,52 @@
|
|||||||
import { showNotification } from "@mantine/notifications";
|
import { showNotification } from "@mantine/notifications";
|
||||||
import { ImCross } from "react-icons/im";
|
import { ImCross } from "react-icons/im";
|
||||||
import { TiTick } from "react-icons/ti"
|
import { TiTick } from "react-icons/ti"
|
||||||
import { GeneralStats, Service, ServiceAddForm, ServerResponse, RegexFilter, notification_time, RegexAddForm } from "./models";
|
import { GeneralStats, Service, ServiceAddForm, ServerResponse, RegexFilter, notification_time, RegexAddForm, ServerStatusResponse, PasswordSend, ChangePassword } from "./models";
|
||||||
|
|
||||||
var Buffer = require('buffer').Buffer
|
var Buffer = require('buffer').Buffer
|
||||||
|
|
||||||
const custom_url = ""//"http://127.0.0.1:8080"
|
const DEBUG = true
|
||||||
|
|
||||||
|
const custom_url = DEBUG?"http://127.0.0.1:8080":""
|
||||||
|
|
||||||
export async function getapi(path:string):Promise<any>{
|
export async function getapi(path:string):Promise<any>{
|
||||||
return await fetch(`${custom_url}/api/${path}`).then( res => res.json() )
|
return await new Promise((resolve, reject) => {
|
||||||
|
fetch(`${custom_url}/api/${path}`,{credentials: "same-origin"})
|
||||||
|
.then(res => {
|
||||||
|
if(res.status == 401) window.location.reload()
|
||||||
|
if(!res.ok) reject(res.statusText)
|
||||||
|
res.json().then( res => resolve(res) ).catch( err => reject(err))
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postapi(path:string,data:any):Promise<any>{
|
export async function postapi(path:string,data:any):Promise<any>{
|
||||||
return await fetch(`${custom_url}/api/${path}`, {
|
return await new Promise((resolve, reject) => {
|
||||||
method: 'POST',
|
fetch(`${custom_url}/api/${path}`, {
|
||||||
cache: 'no-cache',
|
method: 'POST',
|
||||||
headers: {
|
credentials: "same-origin",
|
||||||
'Content-Type': 'application/json'
|
cache: 'no-cache',
|
||||||
},
|
headers: {
|
||||||
body: JSON.stringify(data)
|
'Content-Type': 'application/json'
|
||||||
}).then(res => res.json());
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
}).then(res => {
|
||||||
|
if(res.status == 401) window.location.reload()
|
||||||
|
if(!res.ok) reject(res.statusText)
|
||||||
|
res.json().then( res => resolve(res) ).catch( err => reject(err))
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getstatus(){
|
||||||
|
return await getapi(`status`) as ServerStatusResponse;
|
||||||
|
}
|
||||||
|
|
||||||
export async function generalstats(){
|
export async function generalstats(){
|
||||||
return await getapi("general-stats") as GeneralStats;
|
return await getapi("general-stats") as GeneralStats;
|
||||||
@@ -35,6 +60,26 @@ export async function serviceinfo(service_id:string){
|
|||||||
return await getapi(`service/${service_id}`) as Service;
|
return await getapi(`service/${service_id}`) as Service;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function logout(){
|
||||||
|
const { status } = await getapi(`logout`) as ServerResponse;
|
||||||
|
return status === "ok"?undefined:status
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setpassword(data:PasswordSend) {
|
||||||
|
const { status } = await postapi("set-password",data) as ServerResponse;
|
||||||
|
return status === "ok"?undefined:status
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function changepassword(data:ChangePassword) {
|
||||||
|
const { status } = await postapi("change-password",data) as ServerResponse;
|
||||||
|
return status === "ok"?undefined:status
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function login(data:PasswordSend) {
|
||||||
|
const { status } = await postapi("login",data) as ServerResponse;
|
||||||
|
return status === "ok"?undefined:status
|
||||||
|
}
|
||||||
|
|
||||||
export async function deleteregex(regex_id:number){
|
export async function deleteregex(regex_id:number){
|
||||||
const { status } = await getapi(`regex/${regex_id}/delete`) as ServerResponse;
|
const { status } = await getapi(`regex/${regex_id}/delete`) as ServerResponse;
|
||||||
return status === "ok"?undefined:status
|
return status === "ok"?undefined:status
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ function HomePage() {
|
|||||||
<LoadingOverlay visible={loader} />
|
<LoadingOverlay visible={loader} />
|
||||||
{services.length > 0?services.map( srv => <ServiceRow service={srv} key={srv.id} onClick={()=>{
|
{services.length > 0?services.map( srv => <ServiceRow service={srv} key={srv.id} onClick={()=>{
|
||||||
navigator("/"+srv.id)
|
navigator("/"+srv.id)
|
||||||
}} />):<><Space h="xl"/> <Title className='center-flex' order={3}>No services found! Add one clicking the button above</Title></>}
|
}} />):<><Space h="xl"/> <Title className='center-flex' order={3}>No services found! Add one clicking the add button above</Title></>}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user