vesta-web 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vesta/__init__.py +486 -0
- vesta/db/UNIAUTH.sql +58 -0
- vesta/db/db_service.py +253 -0
- vesta/emptyProject/.gitignore +16 -0
- vesta/emptyProject/.gitlab-ci.yml +12 -0
- vesta/emptyProject/CONTRIBUTING.md +45 -0
- vesta/emptyProject/LICENSE.md +3 -0
- vesta/emptyProject/README.md +44 -0
- vesta/emptyProject/crons/exemple.py +13 -0
- vesta/emptyProject/db/schema.sql +0 -0
- vesta/emptyProject/install.sh +17 -0
- vesta/emptyProject/mailing/mailReset.html +1 -0
- vesta/emptyProject/mailing/mailVerif.html +1 -0
- vesta/emptyProject/misc/nginx_local +15 -0
- vesta/emptyProject/misc/nginx_prod +29 -0
- vesta/emptyProject/misc/nginx_prod_ws +29 -0
- vesta/emptyProject/misc/vesta.service +11 -0
- vesta/emptyProject/requirements.txt +0 -0
- vesta/emptyProject/server_static.ini +6 -0
- vesta/emptyProject/server_static.py +15 -0
- vesta/emptyProject/server_static_orm.ini +13 -0
- vesta/emptyProject/server_static_orm.py +15 -0
- vesta/emptyProject/server_vesta.ini +27 -0
- vesta/emptyProject/server_vesta.py +17 -0
- vesta/emptyProject/server_vesta_ws.ini +31 -0
- vesta/emptyProject/server_vesta_ws.py +56 -0
- vesta/emptyProject/static/home/auth.css +101 -0
- vesta/emptyProject/static/home/auth.html +38 -0
- vesta/emptyProject/static/home/auth.js +159 -0
- vesta/emptyProject/static/home/reset.html +33 -0
- vesta/emptyProject/static/home/verif.html +33 -0
- vesta/emptyProject/static/main.html +14 -0
- vesta/emptyProject/static/main.mjs +9 -0
- vesta/emptyProject/static/mobileUiManifest.mjs +3 -0
- vesta/emptyProject/static/style.css +0 -0
- vesta/emptyProject/static/translations/en.mjs +2 -0
- vesta/emptyProject/static/translations/fr.mjs +2 -0
- vesta/emptyProject/static/translations/translation.mjs +51 -0
- vesta/emptyProject/static/ws/onMessage.mjs +21 -0
- vesta/emptyProject/tests/example/foo.py +7 -0
- vesta/http/baseServer.py +257 -0
- vesta/http/error.py +5 -0
- vesta/http/redirect.py +5 -0
- vesta/http/response.py +85 -0
- vesta/mailing/mailing_service.py +126 -0
- vesta/scripts/initDB.py +52 -0
- vesta/scripts/install.py +76 -0
- vesta/scripts/testsRun.py +83 -0
- vesta/scripts/utils.py +84 -0
- vesta/scripts/vesta.py +225 -0
- vesta_web-1.1.0.dist-info/METADATA +55 -0
- vesta_web-1.1.0.dist-info/RECORD +55 -0
- vesta_web-1.1.0.dist-info/WHEEL +4 -0
- vesta_web-1.1.0.dist-info/entry_points.txt +2 -0
- vesta_web-1.1.0.dist-info/licenses/LICENSE.md +5 -0
vesta/__init__.py
ADDED
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
"""
|
|
2
|
+
------------------------------------------------------------------------------------------------------------------------
|
|
3
|
+
|
|
4
|
+
__.--~~.,-.__ Welcome to VESTA
|
|
5
|
+
`~-._.-(`-.__`-.
|
|
6
|
+
\ `~~` Vesta is a strongly opinionated and minimalist Framework.
|
|
7
|
+
.--./ \ It bundles a lot of different features:
|
|
8
|
+
/# \ \.--. - a http server
|
|
9
|
+
\ / /# \ - a websocket server
|
|
10
|
+
'--' \ / - a mailing server
|
|
11
|
+
'--' - some tooling
|
|
12
|
+
- an HTML templating library
|
|
13
|
+
- a reactive frontend library
|
|
14
|
+
- a unique authentification system called uniauth (think google account)
|
|
15
|
+
|
|
16
|
+
________________________________________________________________________________________________________________________
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# stdlibs
|
|
20
|
+
import datetime
|
|
21
|
+
import json
|
|
22
|
+
import inspect
|
|
23
|
+
import asyncio
|
|
24
|
+
import threading
|
|
25
|
+
import os
|
|
26
|
+
import time
|
|
27
|
+
|
|
28
|
+
import fastwsgi
|
|
29
|
+
import bcrypt
|
|
30
|
+
import jwt
|
|
31
|
+
from configparser import ConfigParser
|
|
32
|
+
import websockets
|
|
33
|
+
|
|
34
|
+
# in house modules
|
|
35
|
+
from vesta.db import db_service as db
|
|
36
|
+
from vesta.mailing import mailing_service as mailing
|
|
37
|
+
|
|
38
|
+
# OTP imports
|
|
39
|
+
import random as rand
|
|
40
|
+
import math
|
|
41
|
+
|
|
42
|
+
from vesta.http import baseServer as server
|
|
43
|
+
from vesta.http import error as HTTPError
|
|
44
|
+
from vesta.http import redirect as HTTPRedirect
|
|
45
|
+
from vesta.http import response as Response
|
|
46
|
+
server = server.BaseServer
|
|
47
|
+
Response = Response.Response
|
|
48
|
+
HTTPRedirect = HTTPRedirect.HTTPRedirect
|
|
49
|
+
HTTPError = HTTPError.HTTPError
|
|
50
|
+
from colorama import Fore, Style
|
|
51
|
+
from colorama import init as colorama_init
|
|
52
|
+
colorama_init()
|
|
53
|
+
|
|
54
|
+
class Server(server):
|
|
55
|
+
features = {}
|
|
56
|
+
|
|
57
|
+
def __init__(self, path, configFile, noStart=False):
|
|
58
|
+
print(Fore.GREEN,"[INFO] starting Vesta server...")
|
|
59
|
+
self.path = path
|
|
60
|
+
|
|
61
|
+
self.importConf(configFile)
|
|
62
|
+
self.db = db.DB(user=self.config.get('DB', 'DB_USER'), password=self.config.get('DB', 'DB_PASSWORD'),
|
|
63
|
+
host=self.config.get('DB', 'DB_HOST'), port=int(self.config.get('DB', 'DB_PORT')),
|
|
64
|
+
db=self.config.get('DB', 'DB_NAME'))
|
|
65
|
+
print(Fore.GREEN,"[INFO] successfully connected to postgresql!")
|
|
66
|
+
self.uniauth = db.DB(user=self.config.get('DB', 'DB_USER'), password=self.config.get('DB', 'DB_PASSWORD'),
|
|
67
|
+
host=self.config.get('UNIAUTH', 'DB_HOST'),
|
|
68
|
+
port=int(self.config.get('UNIAUTH', 'DB_PORT')), db=self.config.get('UNIAUTH', 'DB_NAME'))
|
|
69
|
+
print(Fore.GREEN,"[INFO] successfully connected to uniauth!")
|
|
70
|
+
|
|
71
|
+
if not self.config.getboolean("server", "DEBUG"):
|
|
72
|
+
self.noreply = mailing.Mailing(self.config.get('MAILING', 'MAILING_HOST'),
|
|
73
|
+
self.config.get('MAILING', 'MAILING_PORT'),
|
|
74
|
+
"noreply@carbonlab.dev", self.config.get('MAILING', 'NOREPLY_PASSWORD'),
|
|
75
|
+
self.config.get("server", "SERVICE_NAME"), self.path)
|
|
76
|
+
print(Fore.GREEN,"[INFO] successfully connected to the mailing service!")
|
|
77
|
+
|
|
78
|
+
if noStart:
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
self.start()
|
|
82
|
+
|
|
83
|
+
#-----------------------UNIAUTH RELATED METHODS-----------------------------
|
|
84
|
+
|
|
85
|
+
@server.expose
|
|
86
|
+
def auth(self, parrain=None, ref=None):
|
|
87
|
+
return (open(self.path + "/static/home/auth.html").read() )
|
|
88
|
+
|
|
89
|
+
@server.expose
|
|
90
|
+
def reset(self, email):
|
|
91
|
+
return open(self.path + "/static/home/reset.html").read()
|
|
92
|
+
|
|
93
|
+
@server.expose
|
|
94
|
+
def verif(self, ref=None):
|
|
95
|
+
self.checkJwt(verif=True)
|
|
96
|
+
return open(self.path + "/static/home/verif.html").read()
|
|
97
|
+
|
|
98
|
+
@server.expose
|
|
99
|
+
def resendVerif(self):
|
|
100
|
+
user = self.getUser()
|
|
101
|
+
self.sendVerification(user)
|
|
102
|
+
|
|
103
|
+
@server.expose
|
|
104
|
+
def login(self, email, password, parrain=None):
|
|
105
|
+
if 'email' and 'password':
|
|
106
|
+
password = password.encode('utf-8') # converting to bytes array
|
|
107
|
+
account = self.uniauth.getUserCredentials(email)
|
|
108
|
+
# If account exists in accounts table
|
|
109
|
+
if account:
|
|
110
|
+
msg = self.connect(account, password)
|
|
111
|
+
else:
|
|
112
|
+
msg = self.register(email, password, parrain)
|
|
113
|
+
else:
|
|
114
|
+
msg = 'please give an email and a password'
|
|
115
|
+
|
|
116
|
+
return msg
|
|
117
|
+
|
|
118
|
+
@server.expose
|
|
119
|
+
def changePasswordVerif(self,mail, code, password):
|
|
120
|
+
id = self.uniauth.getSomething("account", mail,"email")["id"]
|
|
121
|
+
if not id :
|
|
122
|
+
return "no account found for " + mail
|
|
123
|
+
|
|
124
|
+
password = password.encode('utf-8')
|
|
125
|
+
actual = self.uniauth.getSomething("reset_code", id)
|
|
126
|
+
if actual and str(actual["code"]) == code and actual["expiration"] > datetime.datetime.now():
|
|
127
|
+
self.changePassword(id, password)
|
|
128
|
+
return "ok"
|
|
129
|
+
else:
|
|
130
|
+
return "Code erroné"
|
|
131
|
+
|
|
132
|
+
@server.expose
|
|
133
|
+
def passwordReset(self, email):
|
|
134
|
+
account = self.uniauth.getUserCredentials(email)
|
|
135
|
+
# If account exists in accounts table
|
|
136
|
+
if account:
|
|
137
|
+
OTP = self.generateOTP(12)
|
|
138
|
+
expiration = datetime.datetime.now() + datetime.timedelta(hours=1)
|
|
139
|
+
self.uniauth.insertReplaceDict("reset_code", {"id": account["id"], "code": OTP, "expiration": expiration})
|
|
140
|
+
if not self.config.getboolean("server", "DEBUG"):
|
|
141
|
+
self.noreply.sendTemplate('mailReset.html', email, "Reset your password","Your reset code: "+OTP, OTP)
|
|
142
|
+
else:
|
|
143
|
+
print("RESET OTP : ", OTP)
|
|
144
|
+
return "ok"
|
|
145
|
+
else:
|
|
146
|
+
return "no account found for " + email
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@server.expose
|
|
150
|
+
def signup(self, code):
|
|
151
|
+
user = self.getUser()
|
|
152
|
+
actual = self.uniauth.getSomething("verif_code", user)
|
|
153
|
+
if str(actual["code"]) == code and actual["expiration"] > datetime.datetime.now():
|
|
154
|
+
self.uniauth.edit("account", user, "verified", True)
|
|
155
|
+
self.createJwt(user, True)
|
|
156
|
+
self.onLogin(user)
|
|
157
|
+
return "ok"
|
|
158
|
+
else:
|
|
159
|
+
return "Code erroné"
|
|
160
|
+
|
|
161
|
+
@server.expose
|
|
162
|
+
def logout(self):
|
|
163
|
+
token = self.getJWT()
|
|
164
|
+
self.response.del_cookie('JWT')
|
|
165
|
+
self.response.del_cookie('auth')
|
|
166
|
+
raise HTTPRedirect(self.response, "/auth")
|
|
167
|
+
|
|
168
|
+
@server.expose
|
|
169
|
+
def goodbye(self): #delete account
|
|
170
|
+
token = self.getJWT()
|
|
171
|
+
info = jwt.decode(token, self.config.get('security', 'SECRET_KEY'), algorithms=['HS256'])
|
|
172
|
+
self.response.del_cookie('JWT')
|
|
173
|
+
self.response.del_cookie('auth')
|
|
174
|
+
self.uniauth.deleteSomething("account", info['username'])
|
|
175
|
+
self.uniauth.deleteSomething("verif_code", info['username'])
|
|
176
|
+
return 'ok'
|
|
177
|
+
|
|
178
|
+
def createJwt(self, uid, verified):
|
|
179
|
+
payload = {
|
|
180
|
+
'username': uid,
|
|
181
|
+
'verified': verified,
|
|
182
|
+
'exp': datetime.datetime.utcnow() + datetime.timedelta(days=1)
|
|
183
|
+
}
|
|
184
|
+
token = jwt.encode(payload, self.config.get('security', 'SECRET_KEY'), algorithm='HS256')
|
|
185
|
+
|
|
186
|
+
self.response.set_cookie('JWT', token, exp={"value": 100, "unit": "days"}, httponly=True, samesite='Strict',
|
|
187
|
+
secure=True)
|
|
188
|
+
self.response.set_cookie('auth', "true", exp={"value": 100, "unit": "days"}, samesite='Strict')
|
|
189
|
+
|
|
190
|
+
return token
|
|
191
|
+
|
|
192
|
+
def checkJwt(self, verif=False):
|
|
193
|
+
try:
|
|
194
|
+
token = self.getJWT()
|
|
195
|
+
except:
|
|
196
|
+
raise HTTPRedirect(self.response, "/auth")
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
info = jwt.decode(token, self.config.get('security', 'SECRET_KEY'), algorithms=['HS256'])
|
|
200
|
+
if not info['verified'] and not verif:
|
|
201
|
+
raise HTTPRedirect(self.response, "/verif")
|
|
202
|
+
elif verif and info['verified']:
|
|
203
|
+
raise HTTPRedirect(self.response, self.config.get("server", "DEFAULT_ENDPOINT"))
|
|
204
|
+
except (jwt.ExpiredSignatureError, jwt.DecodeError):
|
|
205
|
+
self.logout()
|
|
206
|
+
return info
|
|
207
|
+
|
|
208
|
+
def getJWT(self):
|
|
209
|
+
token = self.response.cookies['JWT']
|
|
210
|
+
return token
|
|
211
|
+
|
|
212
|
+
def getUser(self):
|
|
213
|
+
info = self.checkJwt()
|
|
214
|
+
return info['username']
|
|
215
|
+
|
|
216
|
+
def sendVerification(self, uid, mail=''):
|
|
217
|
+
if mail == '':
|
|
218
|
+
mail = self.uniauth.getUser(uid, target="email")["email"]
|
|
219
|
+
OTP = self.generateOTP()
|
|
220
|
+
expiration = datetime.datetime.now() + datetime.timedelta(hours=1)
|
|
221
|
+
self.uniauth.insertReplaceDict("verif_code", {"id": uid, "code": OTP, "expiration": expiration})
|
|
222
|
+
if not self.config.getboolean("server", "DEBUG"):
|
|
223
|
+
self.noreply.sendConfirmation(mail, OTP)
|
|
224
|
+
else:
|
|
225
|
+
print("OTP : ", OTP)
|
|
226
|
+
|
|
227
|
+
def generateOTP(self,n=6):
|
|
228
|
+
digits = "0123456789"
|
|
229
|
+
OTP = ""
|
|
230
|
+
|
|
231
|
+
for i in range(n):
|
|
232
|
+
OTP += digits[math.floor(rand.random() * 10)]
|
|
233
|
+
|
|
234
|
+
return OTP
|
|
235
|
+
|
|
236
|
+
def register(self, username, password, parrain):
|
|
237
|
+
salt = bcrypt.gensalt()
|
|
238
|
+
hash = bcrypt.hashpw(password, salt)
|
|
239
|
+
uid = self.uniauth.createAccount(username, hash, parrain)
|
|
240
|
+
self.createJwt(uid, False)
|
|
241
|
+
self.sendVerification(uid=uid, mail=username)
|
|
242
|
+
pending = self.uniauth.getSomething('pendingmembership', username, 'email')
|
|
243
|
+
if pending:
|
|
244
|
+
self.uniauth.insertDict('membership', {'account': uid, 'company': pending['company']})
|
|
245
|
+
self.uniauth.deleteSomething('pendingmembership', pending['id'])
|
|
246
|
+
return "verif"
|
|
247
|
+
|
|
248
|
+
def changePassword(self, uid, password):
|
|
249
|
+
salt = bcrypt.gensalt()
|
|
250
|
+
hash = bcrypt.hashpw(password, salt)
|
|
251
|
+
self.uniauth.edit("account",uid, 'password', hash)
|
|
252
|
+
|
|
253
|
+
def connect(self, account, password):
|
|
254
|
+
result = bcrypt.checkpw(password, account["password"])
|
|
255
|
+
if result:
|
|
256
|
+
self.createJwt(account['id'], account["verified"])
|
|
257
|
+
self.onLogin(account['id'])
|
|
258
|
+
return "ok"
|
|
259
|
+
else:
|
|
260
|
+
return "invalid email or password"
|
|
261
|
+
|
|
262
|
+
def onLogin(self, uid):
|
|
263
|
+
pass
|
|
264
|
+
|
|
265
|
+
def onStart(self):
|
|
266
|
+
pass
|
|
267
|
+
|
|
268
|
+
#--------------------------GENERAL USE METHODS------------------------------
|
|
269
|
+
|
|
270
|
+
def parseAcceptLanguage(self, acceptLanguage):
|
|
271
|
+
languages = acceptLanguage.split(",")
|
|
272
|
+
locale_q_pairs = []
|
|
273
|
+
|
|
274
|
+
for language in languages:
|
|
275
|
+
if language.split(";")[0] == language:
|
|
276
|
+
# no q => q = 1
|
|
277
|
+
locale_q_pairs.append((language.strip(), "1"))
|
|
278
|
+
else:
|
|
279
|
+
locale = language.split(";")[0].strip()
|
|
280
|
+
q = language.split(";")[1].split("=")[1]
|
|
281
|
+
locale_q_pairs.append((locale, q))
|
|
282
|
+
|
|
283
|
+
return locale_q_pairs
|
|
284
|
+
|
|
285
|
+
def importConf(self, configFile):
|
|
286
|
+
self.config = ConfigParser()
|
|
287
|
+
try:
|
|
288
|
+
self.config.read(self.path + configFile)
|
|
289
|
+
print(Fore.GREEN,"[INFO] Vesta - config at " + self.path + configFile + " loaded")
|
|
290
|
+
except Exception:
|
|
291
|
+
print(Fore.RED,"[ERROR] Vesta - Please create a config file")
|
|
292
|
+
|
|
293
|
+
def start(self):
|
|
294
|
+
self.fileCache = {}
|
|
295
|
+
|
|
296
|
+
if self.features.get("websockets"):
|
|
297
|
+
self.id = 1 # TODO give a different id to each server to allow them to contact eachother
|
|
298
|
+
self.pool = {}
|
|
299
|
+
self.waiting_clients = {}
|
|
300
|
+
self.currentWaiting = 0
|
|
301
|
+
self.stop_event = asyncio.Event()
|
|
302
|
+
websocket_thread = threading.Thread(target=self.startWebSockets)
|
|
303
|
+
websocket_thread.start()
|
|
304
|
+
print(Fore.GREEN,"[INFO] Vesta - WS server started")
|
|
305
|
+
|
|
306
|
+
if self.features.get("errors"):
|
|
307
|
+
for code, page in self.features["errors"].items():
|
|
308
|
+
Response.ERROR_PAGES[code] = self.path + page
|
|
309
|
+
|
|
310
|
+
self.onStart()
|
|
311
|
+
|
|
312
|
+
fastwsgi.server.nowait = 1
|
|
313
|
+
fastwsgi.server.hook_sigint = 1
|
|
314
|
+
|
|
315
|
+
print(Fore.GREEN,"[INFO] Vesta - server running on PID:", os.getpid())
|
|
316
|
+
fastwsgi.server.init(app=self.onrequest, host=self.config.get('server', 'IP'),
|
|
317
|
+
port=int(self.config.get('server', 'PORT')))
|
|
318
|
+
while True:
|
|
319
|
+
code = fastwsgi.server.run()
|
|
320
|
+
if code != 0:
|
|
321
|
+
break
|
|
322
|
+
time.sleep(0)
|
|
323
|
+
self.close()
|
|
324
|
+
|
|
325
|
+
def close(self):
|
|
326
|
+
print(Fore.GREEN,"[INFO] SIGTERM/SIGINT received")
|
|
327
|
+
|
|
328
|
+
fastwsgi.server.close()
|
|
329
|
+
if self.features.get("websockets"):
|
|
330
|
+
self.closeWebSockets()
|
|
331
|
+
|
|
332
|
+
self.clean()
|
|
333
|
+
print(Fore.GREEN,"[INFO] SERVER STOPPED")
|
|
334
|
+
exit()
|
|
335
|
+
|
|
336
|
+
def startWebSockets(self):
|
|
337
|
+
server = threading.Thread(target=self.runWebsockets, daemon=True)
|
|
338
|
+
server.start()
|
|
339
|
+
|
|
340
|
+
async def handle_message(self,websocket):
|
|
341
|
+
pass
|
|
342
|
+
|
|
343
|
+
def runWebsockets(self):
|
|
344
|
+
"""
|
|
345
|
+
Asynchronously runs the WebSocket server.
|
|
346
|
+
"""
|
|
347
|
+
try:
|
|
348
|
+
loop = asyncio.new_event_loop()
|
|
349
|
+
asyncio.set_event_loop(loop)
|
|
350
|
+
|
|
351
|
+
async def _run_server():
|
|
352
|
+
async with websockets.serve(
|
|
353
|
+
self.handle_message,
|
|
354
|
+
self.config.get("server", "IP"),
|
|
355
|
+
int(self.config.get("NOTIFICATION", "PORT"))
|
|
356
|
+
):
|
|
357
|
+
await asyncio.Future()
|
|
358
|
+
|
|
359
|
+
loop.run_until_complete(_run_server())
|
|
360
|
+
loop.run_forever()
|
|
361
|
+
loop.close()
|
|
362
|
+
|
|
363
|
+
except Exception as e:
|
|
364
|
+
print(Fore.RED,"[ERROR] Vesta - exception in ws server:", e)
|
|
365
|
+
|
|
366
|
+
def file(self,path):
|
|
367
|
+
file = self.fileCache.get(path)
|
|
368
|
+
if file:
|
|
369
|
+
return file
|
|
370
|
+
else:
|
|
371
|
+
file = open(path)
|
|
372
|
+
content = file.read()
|
|
373
|
+
file.close()
|
|
374
|
+
self.fileCache[path] = content
|
|
375
|
+
return content
|
|
376
|
+
|
|
377
|
+
def closeWebSockets(self):
|
|
378
|
+
for client, ws in self.pool.items():
|
|
379
|
+
# Close the websocket connection
|
|
380
|
+
asyncio.run_coroutine_threadsafe(ws.close(), asyncio.get_event_loop())
|
|
381
|
+
self.stop_event.set()
|
|
382
|
+
print(Fore.GREEN,"[INFO] WS server closed")
|
|
383
|
+
|
|
384
|
+
def clean(self):
|
|
385
|
+
pass
|
|
386
|
+
|
|
387
|
+
def stop(self):
|
|
388
|
+
fastwsgi.server.close()
|
|
389
|
+
for client, ws in self.pool.items():
|
|
390
|
+
self.db.deleteSomething("active_client", client)
|
|
391
|
+
print(Fore.GREEN,"[INFO] cleaned database")
|
|
392
|
+
|
|
393
|
+
# --------------------------------WEBSOCKETS--------------------------------
|
|
394
|
+
@server.expose
|
|
395
|
+
def config(self):
|
|
396
|
+
if not self.features.get("websockets"):
|
|
397
|
+
raise HTTPError(self.response, 404)
|
|
398
|
+
|
|
399
|
+
if self.config.getboolean("server", "DEBUG"):
|
|
400
|
+
url = self.config.get("NOTIFICATION", "DEBUG_URL")
|
|
401
|
+
else:
|
|
402
|
+
url = self.config.get("NOTIFICATION", "URL")
|
|
403
|
+
|
|
404
|
+
doc= f"""const WEBSOCKETS = "{url}";\n """ + "export {WEBSOCKETS}"
|
|
405
|
+
|
|
406
|
+
self.response.type = "application/javascript"
|
|
407
|
+
self.response.headers = [('Content-Type', 'application/javascript')]
|
|
408
|
+
return doc
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
@server.expose
|
|
412
|
+
def authWS(self, connectionId):
|
|
413
|
+
account_id = self.getUser()
|
|
414
|
+
if int(self.waiting_clients[int(connectionId)]["uid"]) != account_id:
|
|
415
|
+
raise HTTPError(self.response, 403)
|
|
416
|
+
|
|
417
|
+
connection = self.db.insertDict("active_client", {"userid": account_id, "server": self.id}, True)
|
|
418
|
+
self.pool[connection] = self.waiting_clients[int(connectionId)]["connection"]
|
|
419
|
+
del self.waiting_clients[int(connectionId)]
|
|
420
|
+
self.onWSAuth(account_id)
|
|
421
|
+
return str(connection)
|
|
422
|
+
|
|
423
|
+
def onWSAuth(self,uid):
|
|
424
|
+
pass
|
|
425
|
+
|
|
426
|
+
async def sendNotificationAsync(self, account, content, exclude=None):
|
|
427
|
+
message = {"type": "notif", "content": content}
|
|
428
|
+
|
|
429
|
+
clients = self.db.getAll("active_client", account, "userid")
|
|
430
|
+
clients_to_remove = []
|
|
431
|
+
|
|
432
|
+
for client in clients:
|
|
433
|
+
#TODO handle multi server
|
|
434
|
+
if self.pool.get(client["id"]):
|
|
435
|
+
websocket = self.pool[client["id"]]
|
|
436
|
+
|
|
437
|
+
if exclude and websocket == exclude:
|
|
438
|
+
continue
|
|
439
|
+
|
|
440
|
+
try:
|
|
441
|
+
if hasattr(websocket, 'closed') and websocket.closed:
|
|
442
|
+
print(f"[INFO] Client {client['id']} already closed (closed)")
|
|
443
|
+
clients_to_remove.append(client["id"])
|
|
444
|
+
continue
|
|
445
|
+
elif hasattr(websocket, 'state') and websocket.state.name in ['CLOSED', 'CLOSING']:
|
|
446
|
+
print(f"[INFO] Client {client['id']} already closed (state: {websocket.state.name})")
|
|
447
|
+
clients_to_remove.append(client["id"])
|
|
448
|
+
continue
|
|
449
|
+
|
|
450
|
+
await websocket.send(json.dumps(message))
|
|
451
|
+
except Exception as e:
|
|
452
|
+
clients_to_remove.append(client["id"])
|
|
453
|
+
else:
|
|
454
|
+
clients_to_remove.append(client["id"])
|
|
455
|
+
|
|
456
|
+
for client_id in clients_to_remove:
|
|
457
|
+
print(f"[INFO] Cleaning client {client_id}")
|
|
458
|
+
if client_id in self.pool:
|
|
459
|
+
del self.pool[client_id]
|
|
460
|
+
self.db.deleteSomething("active_client", client_id)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def checkWSAuth(self, ws, clientID):
|
|
464
|
+
if self.pool.get(clientID) == ws:
|
|
465
|
+
return True
|
|
466
|
+
return False
|
|
467
|
+
|
|
468
|
+
def sendNotification(self, account, content):
|
|
469
|
+
message = {"type": "notif", "content": content}
|
|
470
|
+
|
|
471
|
+
async def ws_send(message):
|
|
472
|
+
await websocket.send(message)
|
|
473
|
+
|
|
474
|
+
clients = self.db.getAll("active_client", account, "userid")
|
|
475
|
+
for client in clients:
|
|
476
|
+
#TODO handle multi server
|
|
477
|
+
if self.pool.get(client["id"]):
|
|
478
|
+
websocket = self.pool[client["id"]]
|
|
479
|
+
try:
|
|
480
|
+
asyncio.run(ws_send(json.dumps(message)))
|
|
481
|
+
except Exception as e:
|
|
482
|
+
print(Fore.RED,"[ERROR] Vesta - exception sending a message '", message,"' on a ws", e)
|
|
483
|
+
del self.pool[client["id"]]
|
|
484
|
+
self.db.deleteSomething("active_client", client["id"])
|
|
485
|
+
else:
|
|
486
|
+
self.db.deleteSomething("active_client", client["id"])
|
vesta/db/UNIAUTH.sql
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
create table account (
|
|
2
|
+
id bigserial NOT NULL PRIMARY KEY,
|
|
3
|
+
email varchar(319),
|
|
4
|
+
password bytea not null,
|
|
5
|
+
inscription date,
|
|
6
|
+
verified boolean,
|
|
7
|
+
parrain int
|
|
8
|
+
);
|
|
9
|
+
ALTER TABLE account
|
|
10
|
+
ADD CONSTRAINT PARRAIN_CONSTRAINT
|
|
11
|
+
FOREIGN KEY (parrain)
|
|
12
|
+
REFERENCES account (id)
|
|
13
|
+
ON UPDATE CASCADE;
|
|
14
|
+
|
|
15
|
+
create table verif_code (
|
|
16
|
+
id integer NOT NULL PRIMARY KEY,
|
|
17
|
+
code varchar(6) NOT NULL,
|
|
18
|
+
expiration timestamp NOT NULL
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
create table if not exists reset_code (
|
|
22
|
+
id integer NOT NULL PRIMARY KEY,
|
|
23
|
+
code varchar(12) NOT NULL,
|
|
24
|
+
expiration timestamp NOT NULL
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
create table companies (
|
|
28
|
+
id bigserial NOT NULL PRIMARY KEY,
|
|
29
|
+
name varchar(22) NOT NULL
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
create table membership (
|
|
33
|
+
id bigserial NOT NULL PRIMARY KEY,
|
|
34
|
+
account integer NOT NULL,
|
|
35
|
+
company integer NOT NULL
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
ALTER TABLE membership
|
|
39
|
+
ADD CONSTRAINT MEM_USER_CONSTRAINT FOREIGN KEY (account) REFERENCES account (id) ON UPDATE CASCADE;
|
|
40
|
+
ALTER TABLE membership
|
|
41
|
+
ADD CONSTRAINT MEM_COMPANY_CONSTRAINT FOREIGN KEY (company) REFERENCES companies (id) ON UPDATE CASCADE;
|
|
42
|
+
|
|
43
|
+
create table pendingMembership (
|
|
44
|
+
id bigserial NOT NULL PRIMARY KEY,
|
|
45
|
+
email varchar(319),
|
|
46
|
+
company integer NOT NULL
|
|
47
|
+
);
|
|
48
|
+
ALTER TABLE pendingMembership
|
|
49
|
+
ADD CONSTRAINT PEND_MEM_COMPANY_CONSTRAINT FOREIGN KEY (company) REFERENCES companies (id) ON UPDATE CASCADE;
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
create table if not exists unibridge(
|
|
53
|
+
id bigserial NOT NULL PRIMARY KEY,
|
|
54
|
+
source varchar(64) NOT NULL,
|
|
55
|
+
name varchar(256) NOT NULL,
|
|
56
|
+
related_table varchar(256),
|
|
57
|
+
value jsonb DEFAULT '{}'
|
|
58
|
+
)
|