Password Authentication

This commit is contained in:
DomySh
2022-06-13 16:12:52 +02:00
parent b53768b5d2
commit da28ee99be
20 changed files with 482 additions and 61 deletions

View File

@@ -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
View File

@@ -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

View File

@@ -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/

View File

@@ -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

View File

@@ -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.

View File

@@ -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
View File

@@ -0,0 +1,13 @@
version: '3.9'
services:
firewall:
restart: unless-stopped
build: .
ports:
- 80:80
environment:
- NGINX_PORT=80
volumes:
- /execute/db

View File

@@ -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

View File

@@ -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;

View File

@@ -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">

View File

@@ -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}

View File

@@ -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>
} }

View File

@@ -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>
</> </>
} }

View File

@@ -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>
); );

View File

@@ -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,

View File

@@ -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

View File

@@ -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>
} }