Compare commits

...

39 Commits

Author SHA1 Message Date
aitzol 4f0097ac1b dockerizazioa 2023-12-15 23:21:56 +01:00
Aitzol 645edec125 dockerizazioa 2023-12-15 13:11:40 +01:00
aitzol 253c5c2491 lastlogin attribute 2023-11-29 23:30:29 +01:00
aitzol 48be235fde devices, ip attributes 2023-11-29 23:10:24 +01:00
Aitzol 5f3bbd0ff4 aldaketa izen ematean 2023-11-28 09:42:46 +01:00
aitzol 249b0009a3 itzulpenak 2023-11-27 15:57:50 +01:00
aitzol 5225f72ee0 itzulpenak 2023-11-27 15:38:45 +01:00
Aitzol de1315db33 itzulpenak 2023-11-27 13:34:20 +01:00
aitzol 6c2a65221c v0.0.3 2023-11-27 05:43:19 +01:00
aitzol 407fa6351d v0.0.3 2023-11-26 18:49:05 +01:00
aitzol 490e9bb80c v0.0.3 2023-11-26 18:14:50 +01:00
aitzol c20a8a0b2c v0.0.3 2023-11-25 16:21:11 +01:00
aitzol 05821df546 2fa-2.0 2023-11-25 15:58:37 +01:00
aitzol 3a88b7db3b 2fa-1.4 2023-11-24 23:31:01 +01:00
Aitzol a53f2aa274 2fa-1.3 2023-11-24 20:25:35 +01:00
Aitzol 92bc2bbdbf 2fa-1.3 2023-11-24 20:06:38 +01:00
aitzol e2d0e7a95e 2fa-1.2 2023-11-24 08:15:40 +01:00
aitzol 9b4bd725da 2fa-1.2 2023-11-24 08:12:00 +01:00
aitzol b783617335 2fa-1.1 2023-11-24 07:46:08 +01:00
aitzol 4ea6831be2 2fa-1.0 2023-11-23 23:14:42 +01:00
aitzol b8645473a7 2fa-1.0 2023-11-23 23:11:03 +01:00
aitzol 9cc6bf6290 2fa-1.0 2023-11-23 23:03:44 +01:00
Aitzol 4fe1bec588 2fa-0.9 2023-11-22 20:45:37 +01:00
aitzol f2ffabad3e 2fa-0.9 2023-11-22 11:33:36 +01:00
aitzol 7e7c599530 2fa-0.9 2023-11-22 08:26:22 +01:00
aitzol 7a7edc1f92 2fa-0.8 2023-11-20 13:01:32 +01:00
aitzol 078af33e77 2fa-0.8 2023-11-20 11:45:49 +01:00
aitzol 94563bfefc 2fa-0.7 2023-11-18 23:03:40 +01:00
aitzol da0114f2f8 2fa-0.7 2023-11-18 20:47:09 +01:00
aitzol cbb7ed4498 2fa-0.7 2023-11-18 20:45:37 +01:00
aitzol 4d2cf64f19 2fa-0.7 2023-11-18 20:44:06 +01:00
Aitzol fc224038a3 2fa-0.6 2023-11-17 13:17:16 +01:00
Aitzol 7553c6a443 2fa-0.5 2023-11-16 13:21:09 +01:00
Aitzol a16cb64606 2fa-0.5 2023-11-16 13:17:59 +01:00
aitzol e6c8abe026 2fa-0.5 2023-11-16 05:35:57 +01:00
Aitzol d477a40859 2fa-0.4 2023-11-15 11:02:02 +01:00
aitzol bd0680d9a2 2fa-0.3 2023-11-14 05:43:56 +01:00
aitzol 05e130d03a 2fa-0.2 2023-11-10 13:12:15 +01:00
aitzol be7650b777 2fa-0.1 2023-11-06 13:07:43 +01:00
28 changed files with 1034 additions and 229 deletions

12
.gitignore vendored
View File

@ -1,8 +1,16 @@
/settings.ini
/settings.ini.example.original
/uwsgi.ini
/oharrak.txt
/.env
session
libs/__pycache__
__pycache__
*.db
!.empty
*.swp
*.swo
*~
session
libs/__pycache__
!static/tmp
static/tmp/*
!static/tmp/.gitkeep

View File

@ -1,6 +1,8 @@
FROM python:3-alpine
ARG UID_
ENV UID_=1000
ARG USER_=admin
ARG UID_=1000
RUN apk add --no-cache --upgrade bash
ADD --chown=$UID_:$UID_ . /www
@ -16,4 +18,7 @@ RUN set -e; \
pip install -r requirements.txt; \
apk del .build-deps;
RUN adduser -S -D $USER_ -u $UID_
USER $USER_
ENTRYPOINT ["./start.sh"]

View File

@ -52,7 +52,7 @@ Konfiguraketa fitxategia sortu:
edo
docker build -t aitzol/ldap-webui:latest . --build-arg UID_=$UID
docker build -t aitzol/ldap-webui:latest . --build-arg UID_=$UID --build-arg USER_=$USER
#### Edukiontzia sortu

59
_2fa.tpl Normal file
View File

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<title>{{ str['welcome'] }}</title>
<link rel="stylesheet" href="{{ url('static', filename='style.css') }}">
</head>
<body>
<main>
<h1>{{ str['2fa-title'] }}</h1>
% if data['secureAuth'] == True:
<div class="info">{{ str['2fa-info-1'] }}</div>
<div class="qr-code">
<img src="{{'static/tmp/'+data['authCode']+'.png'}}" />
</div>
<form name="disable2faForm" method="post" action="/disable_2fa">
<label for="code">{{ str['code'] }}</label>
<input id="" name="code" type="text" value="{{data['authCode']}}" readonly>
<div class="form-buttons">
<a href="/user"><button class="green" type="button">{{ str['back'] }}</button></a>
<button class="green" type="submit">{{ str['disable'] }}</button>
</div>
</form>
% else:
<div class="info">{{ str['2fa-info-2'] }}</div>
<form name="enable2faForm" method="post" action="/enable_2fa">
<!--<input id="token" name="token" type="text" value="{{data['authCode']}}" readonly>-->
<div class="form-buttons">
<a href="/user"><button class="green" type="button">{{ str['back'] }}</button></a>
<button class="green" type="submit">{{ str['enable'] }}</button>
</div>
</form>
%end
%for type, text, animation in get('alerts', []):
<div class="alerts {{ animation }}">
<div class="alert {{ type }}">{{ text }}</div>
</div>
%end
</main>
</body>
</html>

297
app.py
View File

@ -19,28 +19,32 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
import bottle
from bottle import get, post, static_file, request, route, template
from bottle import get, post, static_file, request, route, template, error
from bottle import SimpleTemplate
from bottle.ext import beaker
#from bottle.ext import beaker
from beaker.middleware import SessionMiddleware
from configparser import ConfigParser
from ldap3 import Server, Connection, ALL
from ldap3 import SIMPLE, SUBTREE, MODIFY_REPLACE, MODIFY_ADD, ALL_ATTRIBUTES
from ldap3 import SIMPLE, SUBTREE, MODIFY_REPLACE, MODIFY_ADD, MODIFY_DELETE, ALL_ATTRIBUTES
from ldap3.core.exceptions import LDAPBindError, LDAPConstraintViolationResult, \
LDAPInvalidCredentialsResult, LDAPUserNameIsMandatoryError, \
LDAPSocketOpenError, LDAPExceptionError, LDAPAttributeOrValueExistsResult
LDAPSocketOpenError, LDAPExceptionError, LDAPAttributeOrValueExistsResult, \
LDAPNoSuchAttributeResult
import logging
from os import getenv, environ, path
from os import getenv, environ, path, remove
from libs import flist, slist
from libs.localization import *
from libs.helper import tools
import random
from user_agents import parse as ua_parse
from datetime import datetime
import cryptocode
import base64
BASE_DIR = path.dirname(__file__)
LOG = logging.getLogger(__name__)
LOG_FORMAT = '%(asctime)s %(levelname)s: %(message)s'
VERSION = '0.0.2'
VERSION = '0.0.3'
@get('/')
def get_index():
@ -52,7 +56,7 @@ def get_index():
@get('/user')
def get_index():
try:
print(newSession().get())
print('SESSION:',newSession().get())
return user_tpl(data=newSession().get(), str=i18n.str)
except Exception as e:
return index_tpl(str=i18n.str)
@ -100,7 +104,6 @@ def get_index():
except Exception as e:
return index_tpl(str=i18n.str)
@get('/logout')
def get_index():
@ -117,13 +120,29 @@ def get_index():
return index_tpl(alerts=[('success', i18n.msg[0], 'fadeOut')], str=i18n.str)
@get('/_2fa')
def get_index():
try:
reload(newSession().get()['username'], None, None)
return _2fa_tpl(data=newSession().get(), str=i18n.str)
except Exception as e:
return index_tpl(str=i18n.str)
@get('/enable_2fa')
@get('/disable_2fa')
def get_index():
try:
return user_tpl(data=newSession().get(), str=i18n.str)
except Exception as e:
return index_tpl(str=i18n.str)
@post('/user')
def post_user():
form = request.forms.getunicode
def error(msg):
return index_tpl(alerts=[('error', msg, 'fadeOut')], str=i18n.str)
if len(form('username')) < 3:
return error(i18n.msg[2])
elif not tools.input_validation(form('username')):
@ -137,9 +156,59 @@ def post_user():
except Error as e:
LOG.warning("Unsuccessful attempt to login %s: %s" % (form('username'), e))
return error(str(e))
try:
#if(check_2fa_step1(form('username'))):
if(newSession().get()['secureAuth']):
# encrypt and store the credentials
key = tools.key()
data = ';'.join([form('username'),form('password'),newSession().get()['authCode']])
data_enc = cryptocode.encrypt(data, key)
data_to_url = base64.urlsafe_b64encode(str.encode(data_enc))
memo.data = data_enc
memo.key = key
logout(form('username'))
return index_tpl(two_factor_authentication=True, path=data_to_url, str=i18n.str)
except Error as e:
#On error force disable 2fa
add_auth_attribute_step1(form('username'), None, action='disable')
LOG.debug("Two factor authentication has been impossible.")
return error(i18n.msg[29])
return user_tpl(alerts=[('success', '%s %s' % (i18n.msg[1], form('username').capitalize()), 'fadeOut' )], data=newSession().get(), str=i18n.str)
@post('/user/<path>')
def post_user_step2(path):
form = request.forms.getunicode
def error(msg):
return index_tpl(alerts=[('error', msg, 'fadeOut')], str=i18n.str)
try:
# decrypt url
path = base64.urlsafe_b64decode(path)
path = cryptocode.decrypt(path.decode('utf-8'), memo.key)
data = path.split(';')
username = data[0]
password = data[1]
secret = data[2]
except:
newSession()
return error(i18n.msg[27])
#if not tools._2fa_validation(form('token'), newSession().get()['authCode']):
if not tools._2fa_validation(form('token'), secret):
return error(i18n.msg[6])
else:
try:
login(username, password)
except Error as e:
LOG.warning("Unsuccessful attempt to login %s: %s" % (username, e))
return error(str(e))
return user_tpl(alerts=[('success', '%s %s' % (i18n.msg[1], newSession().get()['username']), 'fadeOut' )], data=newSession().get(), str=i18n.str)
@post('/signup')
def post_signup():
@ -162,7 +231,11 @@ def post_signup():
def error(msg):
return signup_tpl(alerts=[('error', msg, 'fadeOut')], str=i18n.str)
if not tools.code_is_valid(form('invite_code'), db):
try:
if not tools.code_is_valid(form('invite_code'), db):
return(error(i18n.msg[6]))
except Exception as e:
LOG.error(e)
return(error(i18n.msg[6]))
if len(form('username')) < 3:
@ -189,7 +262,7 @@ def post_signup():
return error(i18n.msg[15])
if not tools.pwd_validation(form('password')):
return error(i18n.msg[8]) #mezua ALDATU egin behar da
return error(i18n.msg[8])
elif form('password') != form('confirm-password'):
return error(i18n.msg[7])
@ -260,11 +333,53 @@ def post_edit_email():
try:
edit_email(username, old_email, form('email').lower())
except Error as e:
LOG.warning("Unsuccessful attempt to change email addres for %s: %s" % (username, e))
LOG.warning("Unsuccessful attempt to change email address for %s: %s" % (username, e))
return error(str(e))
return user_tpl(alerts=[('success', i18n.msg[16], 'fadeOut' )], data=newSession().get(), str=i18n.str)
@post('/enable_2fa')
def post_enable_2fa():
def error(msg):
return _2fa_tpl(alerts=[('error', msg, 'fadeOut')], data=newSession().get(), str=i18n.str)
try:
if(not newSession().get()['secureAuth']):
try:
username=newSession().get()['username']
add_auth_attribute_step1(username, tools.gen_secret(), action='enable')
except Error as e:
reload(newSession().get()['username'], None, None)
LOG.warning(e)
return error(i18n.msg[30])
except Error as e:
LOG.warning(e)
return index_tpl(alerts=[('error', e, 'fadeOut')], str=i18n.str)
return _2fa_tpl(alerts=[('success', i18n.msg[31], 'fadeOut')], data=newSession().get(), str=i18n.str)
@post('/disable_2fa')
def post_disable_2fa():
def error(msg):
return _2fa_tpl(alerts=[('error', msg, 'fadeOut')], data=newSession().get(), str=i18n.str)
try:
if(newSession().get()['secureAuth']):
try:
username=newSession().get()['username']
add_auth_attribute_step1(username, None, action='disable')
except Error as e:
reload(newSession().get()['username'], None, None)
LOG.warning(e)
return error(str(e))
except Error as e:
LOG.warning(e)
return index_tpl(alerts=[('error', e, 'fadeOut')], str=i18n.str)
return _2fa_tpl(alerts=[('error', i18n.msg[32], 'fadeOut')], data=newSession().get(), str=i18n.str)
@post('/change_pwd')
def post_change_pwd():
form = request.forms.getunicode
@ -323,6 +438,14 @@ def post_delete():
def serve_static(filename):
return static_file(filename, root=path.join(BASE_DIR, 'static'))
@get("/static/fonts/<filepath:re:.*\\.(eot|otf|svg|ttf|woff|woff2?)>")
def font(filepath):
return static_file(filepath, root="static/fonts")
@get("/static/tmp/<filepath:re:.*\\.(png|svg)>")
def font(filepath):
return static_file(filepath, root="static/tmp")
def index_tpl(**kwargs):
return template('index', **kwargs)
@ -347,6 +470,9 @@ def delete_tpl(**kwargs):
def logs_tpl(**kwargs):
return template('logs', **kwargs)
def _2fa_tpl(**kwargs):
return template('_2fa', **kwargs)
def connect_ldap(conf, **kwargs):
server = Server(host=conf['host'],
port=conf.getint('port', None),
@ -355,8 +481,14 @@ def connect_ldap(conf, **kwargs):
return Connection(server, raise_exceptions=True, **kwargs)
@error(404)
@error(405)
def error40x(error):
return index_tpl(str=i18n.str)
#LOGIN
def login(username, password):
n = N
for key in (key for key in CONF.sections()
if key == 'ldap' or key.startswith('ldap:')):
@ -371,7 +503,6 @@ def login(username, password):
continue
else:
raise e
break
def login_user(conf, *args):
@ -402,8 +533,11 @@ def login_user_ldap(conf, username, password):
if is_trusted_device(conf, user_dn):
newSession().set(get_user_data(user_dn, c))
#update timestamp + ip address
update_login_info(conf, user_dn)
update_login_info(conf, user_dn)
LOG.debug("%s logged in to %s" % (username, conf['base']))
#generate qr if it doenst exists when 2fa enable
if(newSession().get()['secureAuth']):
tools.gen_qr(newSession().get()['authCode'])
#LOGOUT
def logout(username):
@ -520,8 +654,10 @@ def register(conf, username, firstname, surname, password, email, isFake, device
ts = datetime.now().strftime('%Y%m%d%H%M%S')+'Z'
attributes = {'gidNumber': '501', 'uidNumber': uidNumber, 'homeDirectory': directory, 'givenName':
firstname, 'sn': surname, 'uid' : username, 'mail': email, 'active': False, 'fakeCn': isFake,
'devices':device, 'ip':request.environ.get('HTTP_X_REAL_IP', request.remote_addr), 'lastLogin': ts}
new_user_dn = "cn="+firstname+" "+surname+" - "+username+",cn=users,"+conf['base']
'devices':device, 'ip':request.environ.get('HTTP_X_REAL_IP', request.remote_addr), 'lastLogin': ts,
'secureAuth': False}
#new_user_dn = "cn="+firstname+" "+surname+" - "+username+",cn=users,"+conf['base']
new_user_dn = "cn="+firstname+" "+surname+",cn=users,"+conf['base']
c.add(dn=new_user_dn,object_class=OBJECT_CLASS, attributes=attributes)
#create/change user password
c.extend.standard.modify_password(new_user_dn, '', password)
@ -631,7 +767,6 @@ def new_email_address(conf, *args):
LOG.error('{}: {!s}'.format(e.__class__.__name__, e))
raise Error(i18n.msg[23])
def update_email_address(conf, username, old_email, new_email):
if(old_email == new_email):
raise Error(i18n.msg[15])
@ -645,6 +780,75 @@ def update_email_address(conf, username, old_email, new_email):
c.modify(user_dn, {'mail': [( MODIFY_REPLACE, new_email_addresses )]})
newSession().set(get_user_data(user_dn, c))
# ADD AUTHCODE ATTRIBUTE - 2FA
def add_auth_attribute_step1(username, code, action):
changed = []
for key in (key for key in CONF.sections()
if key == 'ldap' or key.startswith('ldap:')):
LOG.debug("Adding secureAuth attribute %s to %s" % (key, username))
try:
add_auth_attribute_step2(CONF[key], username, code, action)
changed.append(key)
LOG.debug("%s has activated 2FA authentication on %s" % (username, key))
except Error as e:
for key in reversed(changed):
LOG.info("Reverting 2FA activation in %s for %s due to errors" % (key, username))
try:
new_email_address(CONF[key], username, new_email, old_email)
except Error as e2:
LOG.error('{}: {!s}'.format(e.__class__.__name__, e2))
raise e
def add_auth_attribute_step2(conf, *args):
try:
add_auth_attribute_step3(conf, *args)
except (LDAPBindError, LDAPInvalidCredentialsResult, LDAPUserNameIsMandatoryError):
raise Error(i18n.msg[26])
except LDAPConstraintViolationResult as e:
# Extract useful part of the error message (for Samba 4 / AD).
msg = e.message.split('check_password_restrictions: ')[-1].capitalize()
raise Error(msg)
except LDAPSocketOpenError as e:
LOG.error('{}: {!s}'.format(e.__class__.__name__, e))
raise Error(i18n.msg[23])
except LDAPNoSuchAttributeResult as e:
LOG.error('{}: {!s}'.format(e.__class__.__name__, e))
raise Error(i18n.msg[33])
except LDAPExceptionError as e:
LOG.error('{}: {!s}'.format(e.__class__.__name__, e))
raise Error(i18n.msg[23])
def add_auth_attribute_step3(conf, username, code, action):
#set current LDAP
superUser = SuperUsers(conf)
with connect_ldap(conf, user=superUser.admin_dn, password=superUser.admin_pwd) as c:
user_dn = find_user_dn(conf, c, username)
if(action == 'enable'):
c.modify(user_dn,{'authCode': [(MODIFY_ADD, [code])]})
c.modify(user_dn,{'secureAuth': [MODIFY_REPLACE, [True]]})
elif(action == 'disable'):
c.modify(user_dn,{'authCode': [(MODIFY_DELETE, [])]})
c.modify(user_dn,{'secureAuth': [MODIFY_REPLACE, [False]]})
#remove file
try:
remove('static/tmp/'+newSession().get()['authCode']+'.png')
except OSError as e:
LOG.warning(str(e))
#raise Error(e)
pass
newSession().set(get_user_data(user_dn, c))
reload=add_auth_attribute_step1
#CHANGE PASSWORD
def change_passwords(username, old_pass, new_pass):
changed = []
@ -805,9 +1009,17 @@ def get_user_email_array(user_dn, conn, old_email, new_email):
emails[i] = new_email
return(emails)
def check_secure_auth(user_dn, conn):
search_filter = '(objectClass=*)'
conn.search(user_dn, search_filter, attributes=['secureAuth'])
status = conn.entries[0].secureAuth
return(status)
def get_user_data(user_dn, conn):
search_filter = '(objectClass=*)'
conn.search(user_dn, search_filter, attributes=['active','fakeCn','givenName','sn','uid','mail','devices','ip','lastLogin'])
conn.search(user_dn, search_filter,
attributes=['active','fakeCn','givenName','sn','uid','mail','devices','ip','lastLogin','secureAuth',
'authCode'])
data = []
data.append(conn.entries[0].active.values[0])
data.append(conn.entries[0].fakeCn.values[0])
@ -816,11 +1028,20 @@ def get_user_data(user_dn, conn):
data.append(conn.entries[0].uid.values[0])
data.append(conn.entries[0].mail.values[0])
data.append(conn.entries[0].devices.values)
data.append(conn.entries[0].ip.values[0])
if(conn.entries[0].ip):
data.append(conn.entries[0].ip.values[0])
else:
data.append(request.environ.get('HTTP_X_REAL_IP', request.remote_addr))
#ts = conn.entries[0].lastLogin.values[0]
#ts = datetime.strptime(ts, '%Y-%m-%d %H:%M:%S%z')
#ts = datetime.strftime(t, '%Y-%m-%d %H:%M:%S')
data.append(str(conn.entries[0].lastLogin.values[0])[:-6])
if(conn.entries[0].lastLogin):
data.append(str(conn.entries[0].lastLogin.values[0])[:-6])
else:
data.append(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
data.append(conn.entries[0].secureAuth.values[0])
if(conn.entries[0].authCode):
data.append(conn.entries[0].authCode.values[0])
return(data)
@ -860,18 +1081,10 @@ def is_trusted_device(conf, user_dn):
if not find_device(user_dn, c, d):
OBJECT_CLASS = ['top', 'inetOrgPerson', 'posixAccount', 'accountsManagement']
c.modify(user_dn, {'devices': [( MODIFY_ADD, d )] })
'''
if find_device(user_dn, c, 'unknown'):
OBJECT_CLASS = ['top', 'inetOrgPerson', 'posixAccount', 'accountsManagement']
c.modify(user_dn, {'devices': [( MODIFY_REPLACE, d )] })
else:
OBJECT_CLASS = ['top', 'inetOrgPerson', 'posixAccount', 'accountsManagement']
c.modify(user_dn, {'devices': [( MODIFY_ADD, d )] })
'''
c.unbind()
return True
except Exception as e:
print(e)
LOG.warning(e)
return True
def update_login_info(conf, user_dn):
@ -885,6 +1098,14 @@ def update_login_info(conf, user_dn):
class Error(Exception):
pass
# TEMPORAL MEMORY
class tMemory(object):
def __init__(self):
self.data = None
self.key = None
memo = tMemory()
#SESSIONS MANAGEMENT
def newSession():
@ -893,11 +1114,11 @@ def newSession():
def __init__(self):
super(Session, self).__init__()
self.data = bottle.request.environ.get('beaker.session')
self.lang = self.get_lang()
self.sid = self.data.id
#localization
self.lang = self.get_lang()
global i18n
i18n = LocalizeTo(self.lang, CONF)
i18n = LocalizeTo(self.lang, CONF)
def get_lang(self):
if 'HTTP_ACCEPT_LANGUAGE' in bottle.request.environ:
@ -922,6 +1143,11 @@ def newSession():
self.devices = data[6]
self.ip = data[7]
self.lastLogin = data[8]
self.secureAuth = data[9]
try:
self.authCode = data[10]
except:
self.authCode = None
self.data['active'] = self.active
self.data['fakeCn'] = self.fakeCn
@ -932,6 +1158,10 @@ def newSession():
self.data['devices'] = self.devices
self.data['ip'] = self.ip
self.data['lastLogin'] = self.lastLogin
self.data['secureAuth'] = self.secureAuth
self.data['authCode'] = self.authCode
self.data['id'] = self.sid
def close(self):
self.data.pop('username')
@ -969,7 +1199,8 @@ class SuperUsers(object):
superUser = SuperUsers(CONF['ldap:0'])
app = beaker.middleware.SessionMiddleware(bottle.app(), session_opts)
#app = beaker.middleware.SessionMiddleware(bottle.app(), session_opts)
app = SessionMiddleware(bottle.app(), session_opts)
bottle.TEMPLATE_PATH = [BASE_DIR]

Binary file not shown.

View File

@ -14,13 +14,22 @@
<body>
<main>
<h1>{{ str['login'] }}</h1>
%try:
%if two_factor_authentication:
<form method="post" action="/user/{{path}}">
<label for="token">{{ str['token'] }}</label>
<input id="token" name="token" value="" type="text" required autofocus>
%end
%except:
<form method="post" action="/user">
<label for="username">{{ str['usrn'] }}</label>
<input id="username" name="username" value="{{ get('username', '') }}" type="text" required autofocus>
<form method="post" action="/user">
<label for="username">{{ str['usrn'] }}</label>
<input id="username" name="username" value="{{ get('username', '') }}" type="text" required autofocus>
<label for="password">{{ str['pwd'] }}</label>
<input id="password" name="password" type="password" pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*_=+-]).{8,24}$" oninvalid="setCustomValidity('{{ str['pwd-pattern'] }}')" oninput="setCustomValidity('')" required>
<label for="password">{{ str['pwd'] }}</label>
<input id="password" name="password" type="password" pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*_=+-]).{8,24}$" oninvalid="setCustomValidity('{{ str['pwd-pattern'] }}')" oninput="setCustomValidity('')" required>
%end
<button class="green" type="submit">{{str['login']}}</button>
<a href="/signup">{{ str['or-sign-up'] }}</a>

View File

@ -2,6 +2,11 @@
import sqlite3
import re
from onetimepass import valid_totp
from secrets import choice
import segno
from os import path
import uuid
class Tools():
@ -42,5 +47,30 @@ class Tools():
def pwd_validation(self, e):
regex = r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!#%*?&]{8,18}$'
return(bool(re.fullmatch(regex, e)))
# 2FA
def gen_qr(self, secret):
if(not path.isfile('static/tmp/'+secret+'.png')):
qrcode = segno.make(secret, micro=False)
qrcode.save('static/tmp/'+secret+'.png', scale=10)
def gen_secret(self): # Function to return a random string with length 16.
secret = ''
while len(secret) < 16:
secret += choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567')
self.gen_qr(secret)
return secret
def _2fa_validation(self, otp, secret):
authenticated = valid_totp(otp, secret)
if authenticated:
print('Correct otp, Authenticated!')
return True
elif not authenticated:
print('Wrong otp, please try again.')
return False
def key(self):
return uuid.uuid4().hex
tools = Tools()

View File

@ -40,6 +40,13 @@ class LocalizeTo(object):
str_25 = _("Last login")
str_26 = _("Devices")
str_27 = _("show")
str_28 = _("Two factor authentication")
str_29 = _("Token")
str_30 = _("Disable")
str_31 = _("Enable")
str_32 = _("You must then use the key created by this QR code to log in. To get the key you must use a OTP or 2FA mobile app.")
str_33 = _("Two-step authentication is additional security. By enabling this you will be asked at the login, in addition to the user and password, a key you will need to create using QR code.")
str_34 = _("Code")
#messages
msg_00 = _("The session was closed.")
@ -71,11 +78,19 @@ class LocalizeTo(object):
msg_26 = _("Forgot your password? Please try again.")
msg_27 = _("The session has expired.")
msg_28 = _("Registration is currently closed. We apologize for the inconvenience.")
msg_29 = _("Two factor authentication has been impossible.")
msg_30 = _("Two factor authentication has been restored.")
msg_31 = _("Two factor authentication has been enabled.")
msg_32 = _("Two factor authentication has been disabled.")
msg_33 = _("Two factor authentication was already disabled.")
self.str = {'usr':str_00, 'usrn':str_01, 'fn':str_02, 'sn':str_03, 'pwd':str_04, 'old-pwd':str_05,
'new-pwd':str_06, 'conf-pwd':str_07, 'email':str_08, 'edit':str_09, 'login':str_10, 'log-out':str_11,
'del':str_12, 'sign-up':str_13, 'back':str_14, 'update':str_15, 'or-sign-in':str_16,
'or-sign-up':str_17, 'inv-code':str_18, 'edit-fn':str_19, 'edit-email':str_20, 'ch-pwd':str_21,
'del-account':str_22, 'welcome':str_23, 'logs':str_24, 'last-login':str_25, 'devices':str_26,
'show':str_27, 'pwd-pattern': msg_08}
self.msg = (msg_00, msg_01, msg_02, msg_03, msg_04, msg_05, msg_06, msg_07, msg_08, msg_09, msg_10, msg_11, msg_12, msg_13, msg_14, msg_15, msg_16, msg_17, msg_18, msg_19, msg_20, msg_21, msg_22, msg_23, msg_24, msg_25, msg_26, msg_27, msg_28)
'show':str_27, '2fa-title':str_28, 'token':str_29, 'disable':str_30, 'enable':str_31, '2fa-info-1':str_32,
'2fa-info-2':str_33, 'code':str_34, 'pwd-pattern': msg_08}
self.msg = (msg_00, msg_01, msg_02, msg_03, msg_04, msg_05, msg_06, msg_07, msg_08, msg_09, msg_10, msg_11,
msg_12, msg_13, msg_14, msg_15, msg_16, msg_17, msg_18, msg_19, msg_20, msg_21, msg_22, msg_23,
msg_24, msg_25, msg_26, msg_27, msg_28, msg_29, msg_30, msg_31, msg_32, msg_33)

View File

@ -6,243 +6,285 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Project-Id-Version: 0.0.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-25 16:52+0200\n"
"POT-Creation-Date: 2023-11-27 15:52+0100\n"
"PO-Revision-Date: 2023-04-07 13:28+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.2.2\n"
#: libs/localization.py:15
msgid "User"
msgstr "User"
msgstr ""
#: libs/localization.py:16
msgid "Username"
msgstr "Username"
msgstr ""
#: libs/localization.py:17
msgid "Firstname"
msgstr "Firstname"
msgstr ""
#: libs/localization.py:18
msgid "Surname"
msgstr "Surname"
msgstr ""
#: libs/localization.py:19
msgid "Password"
msgstr "Password"
msgstr ""
#: libs/localization.py:20
msgid "Old password"
msgstr "Old password"
msgstr ""
#: libs/localization.py:21
msgid "New password"
msgstr "New password"
msgstr ""
#: libs/localization.py:22
msgid "Confirm password"
msgstr "Confirm password"
msgstr ""
#: libs/localization.py:23
msgid "Email"
msgstr "Email"
msgstr ""
#: libs/localization.py:24
msgid "edit"
msgstr "edit"
msgstr ""
#: libs/localization.py:25
msgid "Login"
msgstr "Login"
msgstr ""
#: libs/localization.py:26
msgid "Logout"
msgstr "Logout"
msgstr ""
#: libs/localization.py:27
msgid "Delete"
msgstr "Delete"
msgstr ""
#: libs/localization.py:28
msgid "Sign Up"
msgstr "Sign Up"
msgstr ""
#: libs/localization.py:29
msgid "Back"
msgstr "Back"
msgstr ""
#: libs/localization.py:30
msgid "Update"
msgstr "Update"
msgstr ""
#: libs/localization.py:31
msgid "Or Sign In"
msgstr "Or Sign In"
msgstr ""
#: libs/localization.py:32
msgid "Or Sign Up"
msgstr "Or Sign Up"
msgstr ""
#: libs/localization.py:33
msgid "Invite code"
msgstr "Invite code"
msgstr ""
#: libs/localization.py:34
msgid "Edit your fullname"
msgstr "Edit your fullname"
msgstr ""
#: libs/localization.py:35
msgid "Edit your email"
msgstr "Edit your email"
msgstr ""
#: libs/localization.py:36
msgid "Change your password"
msgstr "Change your password"
msgstr ""
#: libs/localization.py:37
msgid "Delete your account"
msgstr "Delete your account"
msgstr ""
#: libs/localization.py:38 libs/localization.py:46
#: libs/localization.py:38 libs/localization.py:53
msgid "Welcome"
msgstr "Welcome"
msgstr ""
#: libs/localization.py:39
msgid "Logs"
msgstr "Logs"
msgstr ""
#: libs/localization.py:40
msgid "Last login"
msgstr "Last login"
msgstr ""
#: libs/localization.py:41
msgid "Devices"
msgstr "Devices"
msgstr ""
#: libs/localization.py:42
msgid "show"
msgstr "show"
msgstr ""
#: libs/localization.py:43
msgid "Two factor authentication"
msgstr ""
#: libs/localization.py:44
msgid "Token"
msgstr ""
#: libs/localization.py:45
msgid "The session was closed."
msgstr "The session was closed."
msgid "Disable"
msgstr ""
#: libs/localization.py:46
msgid "Enable"
msgstr ""
#: libs/localization.py:47
msgid "Username must be at least 3 characters long!"
msgstr "Username must be at least 3 characters long!"
msgid "You must then use the key created by this QR code to log in. To get the key you must use a OTP or 2FA mobile app."
msgstr ""
#: libs/localization.py:48
msgid "Not allowed characters for the username field."
msgstr "Not allowed characters for the username field."
msgid "Two-step authentication is additional security. By enabling this you will be asked at the login, in addition to the user and password, a key you will need to create using QR code."
msgstr ""
#: libs/localization.py:49
msgid "Not allowed characters for the firstname field."
msgstr "Not allowed characters for the firstname field."
#: libs/localization.py:50
msgid "Not allowed characters for the surname field."
msgstr "Not allowed characters for the surname field."
#: libs/localization.py:51
msgid "The code is invalid or has expired."
msgstr "The code is invalid or has expired."
msgid "Code"
msgstr ""
#: libs/localization.py:52
msgid "Passwords do not match!"
msgstr "Passwords do not match!"
#: libs/localization.py:53
msgid ""
"The password must contain at least 8 characters, at least one number, "
"a capital letter and a special character."
msgid "The session was closed."
msgstr ""
"The password must contain at least 8 characters, at least one number, "
"a capital letter and a special character."
#: libs/localization.py:54
msgid "Congratulations, your account has been created!"
msgstr "Congratulations, your account has been created!"
msgid "Username must be at least 3 characters long!"
msgstr ""
#: libs/localization.py:55
msgid "Your first and last name have not been changed."
msgstr "Your first and last name have not been changed."
msgid "Not allowed characters for the username field."
msgstr ""
#: libs/localization.py:56
msgid "Your firstname is a bit short, don't you think?"
msgstr "Your firstname is a bit short, don't you think?"
msgid "Not allowed characters for the firstname field."
msgstr ""
#: libs/localization.py:57
msgid "Your surname is a bit short, don't you think?"
msgstr "Your surname is a bit short, don't you think?"
msgid "Not allowed characters for the surname field."
msgstr ""
#: libs/localization.py:58
msgid "Your first and last name have been successfully updated."
msgstr "Your first and last name have been successfully updated."
msgid "The code is invalid or has expired."
msgstr ""
#: libs/localization.py:59
msgid "Invalid email address. Please try again."
msgstr "Invalid email address. Please try again."
msgid "Passwords do not match!"
msgstr ""
#: libs/localization.py:60
msgid "Email address has not been changed."
msgstr "Email address has not been changed."
msgid "The password must contain at least 8 characters, at least one number, a capital letter and a special character."
msgstr ""
#: libs/localization.py:61
msgid "Your email has been successfully updated."
msgstr "Your email has been successfully updated."
msgid "Congratulations, your account has been created!"
msgstr ""
#: libs/localization.py:62
msgid "The password entered is the same as the current password."
msgstr "The password entered is the same as the current password."
msgid "Your first and last name have not been changed."
msgstr ""
#: libs/localization.py:63
msgid "Password has been changed!"
msgstr "Password has been changed!"
msgid "Your firstname is a bit short, don't you think?"
msgstr ""
#: libs/localization.py:64
msgid "Please, type your username for account deletion."
msgstr "Please, type your username for account deletion."
msgid "Your surname is a bit short, don't you think?"
msgstr ""
#: libs/localization.py:65
msgid "Account successfully deleted!"
msgstr "Account successfully deleted!"
msgid "Your first and last name have been successfully updated."
msgstr ""
#: libs/localization.py:66
msgid "Username or password is incorrect!"
msgstr "Username or password is incorrect!"
msgid "Invalid email address. Please try again."
msgstr ""
#: libs/localization.py:67
msgid "Unable to connect to the remote server."
msgstr "Unable to connect to the remote server."
msgid "Email address has not been changed."
msgstr ""
#: libs/localization.py:68
msgid ""
"Encountered an unexpected error while communicating with the remote server."
msgid "Your email has been successfully updated."
msgstr ""
"Encountered an unexpected error while communicating with the remote server."
#: libs/localization.py:69
msgid "User already exists."
msgstr "User already exists."
msgid "The password entered is the same as the current password."
msgstr ""
#: libs/localization.py:70
msgid "Email already exists."
msgstr "Email already exists."
msgid "Password has been changed!"
msgstr ""
#: libs/localization.py:71
msgid "Forgot your password? Please try again."
msgstr "Forgot your password? Please try again."
msgid "Please, type your username for account deletion."
msgstr ""
#: libs/localization.py:72
msgid "The session has expired."
msgstr "The session has expired."
msgid "Account successfully deleted!"
msgstr ""
#: libs/localization.py:73
msgid "Username or password is incorrect!"
msgstr ""
#: libs/localization.py:74
msgid "Unable to connect to the remote server."
msgstr ""
#: libs/localization.py:75
msgid "Encountered an unexpected error while communicating with the remote server."
msgstr ""
#: libs/localization.py:76
msgid "User already exists."
msgstr ""
#: libs/localization.py:77
msgid "Email already exists."
msgstr ""
#: libs/localization.py:78
msgid "Forgot your password? Please try again."
msgstr ""
#: libs/localization.py:79
msgid "The session has expired."
msgstr ""
#: libs/localization.py:80
msgid "Registration is currently closed. We apologize for the inconvenience."
msgstr "Registration is currently closed. We apologize for the inconvenience."
msgstr ""
#: libs/localization.py:81
msgid "Two factor authentication has been impossible."
msgstr ""
#: libs/localization.py:82
msgid "Two factor authentication has been restored."
msgstr ""
#: libs/localization.py:83
msgid "Two factor authentication has been enabled."
msgstr ""
#: libs/localization.py:84
msgid "Two factor authentication has been disabled."
msgstr ""
#: libs/localization.py:85
msgid "Two factor authentication was already disabled."
msgstr ""

Binary file not shown.

View File

@ -1,21 +1,21 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <aitzol@lainoa.eus>, 2022.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Project-Id-Version: 0.0.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-07 17:20+0200\n"
"PO-Revision-Date: 2023-04-07 13:28+0200\n"
"Last-Translator: Aitzol Berasategi <aitzol@lainoa.eus>\n"
"Language-Team: LANGUAGE <EN@en.org>\n"
"POT-Creation-Date: 2022-04-25 16:52+0200\n"
"PO-Revision-Date: 2023-11-27 15:37+0100\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.3\n"
"X-Generator: Poedit 3.2.2\n"
#: libs/localization.py:15
msgid "User"
@ -109,7 +109,7 @@ msgstr "Change your password"
msgid "Delete your account"
msgstr "Delete your account"
#: libs/localization.py:38 libs/localization.py:46
#: libs/localization.py:38 libs/localization.py:53
msgid "Welcome"
msgstr "Welcome"
@ -129,120 +129,162 @@ msgstr "Devices"
msgid "show"
msgstr "show"
#: libs/localization.py:43
msgid "Two factor authentication"
msgstr "Two factor authentication"
#: libs/localization.py:44
msgid "Token"
msgstr "Token"
#: libs/localization.py:45
msgid "Disable"
msgstr "Disable"
#: libs/localization.py:46
msgid "Enable"
msgstr "Enable"
#: libs/localization.py:47
msgid "You must then use the key created by this QR code to log in. To get the key you must use a OTP or 2FA mobile app."
msgstr "You must then use the key created by this QR code to log in. To get the key you must use a OTP or 2FA mobile app."
#: libs/localization.py:48
msgid "Two-step authentication is additional security. By enabling this you will be asked at the login, in addition to the user and password, a key you will need to create using QR code."
msgstr "Two-step authentication is additional security. By enabling this you will be asked at the login, in addition to the user and password, a key you will need to create using QR code."
#: libs/localization.py:49
msgid "Code"
msgstr "Code"
#: libs/localization.py:52
msgid "The session was closed."
msgstr "The session was closed."
#: libs/localization.py:47
#: libs/localization.py:54
msgid "Username must be at least 3 characters long!"
msgstr "Username must be at least 3 characters long!"
#: libs/localization.py:48
#: libs/localization.py:55
msgid "Not allowed characters for the username field."
msgstr "Not allowed characters for the username field."
#: libs/localization.py:49
#: libs/localization.py:56
msgid "Not allowed characters for the firstname field."
msgstr "Not allowed characters for the firstname field."
#: libs/localization.py:50
#: libs/localization.py:57
msgid "Not allowed characters for the surname field."
msgstr "Not allowed characters for the surname field."
#: libs/localization.py:51
#: libs/localization.py:58
msgid "The code is invalid or has expired."
msgstr "The code is invalid or has expired."
#: libs/localization.py:52
#: libs/localization.py:59
msgid "Passwords do not match!"
msgstr "Passwords do not match!"
#: libs/localization.py:53
msgid ""
"The password must contain at least 8 characters, at least one number, "
"a capital letter and a special character."
msgstr ""
"The password must contain at least 8 characters, at least one number, "
"a capital letter and a special character."
#: libs/localization.py:60
msgid "The password must contain at least 8 characters, at least one number, a capital letter and a special character."
msgstr "The password must contain at least 8 characters, at least one number, a capital letter and a special character."
#: libs/localization.py:54
#: libs/localization.py:61
msgid "Congratulations, your account has been created!"
msgstr "Congratulations, your account has been created!"
#: libs/localization.py:55
#: libs/localization.py:62
msgid "Your first and last name have not been changed."
msgstr "Your first and last name have not been changed."
#: libs/localization.py:56
#: libs/localization.py:63
msgid "Your firstname is a bit short, don't you think?"
msgstr "Your firstname is a bit short, don't you think?"
#: libs/localization.py:57
#: libs/localization.py:64
msgid "Your surname is a bit short, don't you think?"
msgstr "Your surname is a bit short, don't you think?"
#: libs/localization.py:58
#: libs/localization.py:65
msgid "Your first and last name have been successfully updated."
msgstr "Your first and last name have been successfully updated."
#: libs/localization.py:59
#: libs/localization.py:66
msgid "Invalid email address. Please try again."
msgstr "Invalid email address. Please try again."
#: libs/localization.py:60
#: libs/localization.py:67
msgid "Email address has not been changed."
msgstr "Email address has not been changed."
#: libs/localization.py:61
#: libs/localization.py:68
msgid "Your email has been successfully updated."
msgstr "Your email has been successfully updated."
#: libs/localization.py:62
#: libs/localization.py:69
msgid "The password entered is the same as the current password."
msgstr "The password entered is the same as the current password."
#: libs/localization.py:63
#: libs/localization.py:70
msgid "Password has been changed!"
msgstr "Password has been changed!"
#: libs/localization.py:64
#: libs/localization.py:71
msgid "Please, type your username for account deletion."
msgstr "Please, type your username for account deletion."
#: libs/localization.py:65
#: libs/localization.py:72
msgid "Account successfully deleted!"
msgstr "Account successfully deleted!"
#: libs/localization.py:66
#: libs/localization.py:73
msgid "Username or password is incorrect!"
msgstr "Username or password is incorrect!"
#: libs/localization.py:67
#: libs/localization.py:74
msgid "Unable to connect to the remote server."
msgstr "Unable to connect to the remote server."
#: libs/localization.py:68
msgid ""
"Encountered an unexpected error while communicating with the remote server."
msgstr ""
"Encountered an unexpected error while communicating with the remote server."
#: libs/localization.py:75
msgid "Encountered an unexpected error while communicating with the remote server."
msgstr "Encountered an unexpected error while communicating with the remote server."
#: libs/localization.py:69
#: libs/localization.py:76
msgid "User already exists."
msgstr "User already exists."
#: libs/localization.py:70
#: libs/localization.py:77
msgid "Email already exists."
msgstr "Email already exists."
#: libs/localization.py:71
#: libs/localization.py:78
msgid "Forgot your password? Please try again."
msgstr "Forgot your password? Please try again."
#: libs/localization.py:72
#: libs/localization.py:79
msgid "The session has expired."
msgstr "The session has expired."
#: libs/localization.py:73
#: libs/localization.py:80
msgid "Registration is currently closed. We apologize for the inconvenience."
msgstr "Registration is currently closed. We apologize for the inconvenience."
#: libs/localization.py:81
msgid "Two factor authentication has been impossible."
msgstr "Two factor authentication has been impossible."
#: libs/localization.py:82
msgid "Two factor authentication has been restored."
msgstr "Two factor authentication has been restored."
#: libs/localization.py:83
msgid "Two factor authentication has been enabled."
msgstr "Two factor authentication has been enabled."
#: libs/localization.py:84
msgid "Two factor authentication has been disabled."
msgstr "Two factor authentication has been disabled."
#: libs/localization.py:85
msgid "Two factor authentication was already disabled."
msgstr "Two factor authentication was already disabled."

Binary file not shown.

View File

@ -5,17 +5,17 @@
#
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Project-Id-Version: 0.0.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-07 17:23+0200\n"
"PO-Revision-Date: 2023-04-07 13:27+0200\n"
"PO-Revision-Date: 2023-11-27 15:36+0100\n"
"Last-Translator: Aitzol Berasategi <aitzol@lainoa.eus>\n"
"Language-Team: LANGUAGE <EU@eu.org>\n"
"Language: eu\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.3\n"
"X-Generator: Poedit 3.2.2\n"
#: libs/localization.py:15
msgid "User"
@ -109,7 +109,7 @@ msgstr "Pasahitza eguneratu"
msgid "Delete your account"
msgstr "Kontua ezabatu"
#: libs/localization.py:38 libs/localization.py:46
#: libs/localization.py:38 libs/localization.py:53
msgid "Welcome"
msgstr "Ongi etorri"
@ -129,119 +129,162 @@ msgstr "Gailuak"
msgid "show"
msgstr "ikusi"
#: libs/localization.py:43
msgid "Two factor authentication"
msgstr "Bi urratseko autentifikazioa"
#: libs/localization.py:44
msgid "Token"
msgstr "Gakoa"
#: libs/localization.py:45
msgid "Disable"
msgstr "Desgaitu"
#: libs/localization.py:46
msgid "Enable"
msgstr "Gaitu"
#: libs/localization.py:47
msgid "You must then use the key created by this QR code to log in. To get the key you must use a OTP or 2FA mobile app."
msgstr "Aurrerantzean QR kode honen bidez sortutako gakoa erabili beharko duzu saioa hasteko. Gakoa lortzeko mugikorrerako OTP edo 2FA aplikazio bat erabili beharko duzu."
#: libs/localization.py:48
msgid "Two-step authentication is additional security. By enabling this you will be asked at the login, in addition to the user and password, a key you will need to create using QR code."
msgstr "Bi urratseko autentifikazioa segurtasun gehigarri bat da. Hau gaituz saio hasieran erabiltzaile eta pasahitzaz gain QR kode bidez sortu beharko duzun gako bat eskatuko zaizu."
#: libs/localization.py:49
msgid "Code"
msgstr "Kodea"
#: libs/localization.py:52
msgid "The session was closed."
msgstr "Saioa itxi da."
#: libs/localization.py:47
#: libs/localization.py:54
msgid "Username must be at least 3 characters long!"
msgstr "Erabiltzaile izenak gutxienez 3 karaktere izan behar ditu!"
#: libs/localization.py:48
#: libs/localization.py:55
msgid "Not allowed characters for the username field."
msgstr "Erabiltzaile-izenaren eremurako onartzen ez diren karaktereak."
#: libs/localization.py:49
#: libs/localization.py:56
msgid "Not allowed characters for the firstname field."
msgstr "Izenaren eremurako onartzen ez diren karaktereak."
#: libs/localization.py:50
#: libs/localization.py:57
msgid "Not allowed characters for the surname field."
msgstr "Abizenaren eremurako onartzen ez diren karaktereak."
#: libs/localization.py:51
#: libs/localization.py:58
msgid "The code is invalid or has expired."
msgstr "Kodea baliogabea da edo iraungi egin da."
#: libs/localization.py:52
#: libs/localization.py:59
msgid "Passwords do not match!"
msgstr "Pasahitzak ez datoz bat!"
#: libs/localization.py:53
msgid ""
"The password must contain at least 8 characters, at least one number, "
"a capital letter and a special character."
msgstr ""
"Pasahitzak gutxienez 8 karaktere izan behar ditu, zenbaki bat, hizki larri bat "
"eta karaktere berezi bat."
#: libs/localization.py:60
msgid "The password must contain at least 8 characters, at least one number, a capital letter and a special character."
msgstr "Pasahitzak gutxienez 8 karaktere izan behar ditu, zenbaki bat, hizki larri bat eta karaktere berezi bat."
#: libs/localization.py:54
#: libs/localization.py:61
msgid "Congratulations, your account has been created!"
msgstr "Zorionak, zure kontua sortu da!"
#: libs/localization.py:55
#: libs/localization.py:62
msgid "Your first and last name have not been changed."
msgstr "Zure izen-abizenak ez dira aldatu."
#: libs/localization.py:56
#: libs/localization.py:63
msgid "Your firstname is a bit short, don't you think?"
msgstr "Zure izena labur-xamarra da, ez duzu uste?"
#: libs/localization.py:57
#: libs/localization.py:64
msgid "Your surname is a bit short, don't you think?"
msgstr "Zure abizena labur-xamarra da, ez duzu uste?"
#: libs/localization.py:58
#: libs/localization.py:65
msgid "Your first and last name have been successfully updated."
msgstr "Zure izen-abizenak ongi eguneratu dira."
#: libs/localization.py:59
#: libs/localization.py:66
msgid "Invalid email address. Please try again."
msgstr "Baliogabeko email helbidea. Saia zaitez berriz, mesedez."
#: libs/localization.py:60
#: libs/localization.py:67
msgid "Email address has not been changed."
msgstr "Email helbidea ez da aldatu."
#: libs/localization.py:61
#: libs/localization.py:68
msgid "Your email has been successfully updated."
msgstr "Zure emaila ongi eguneratu da."
#: libs/localization.py:62
#: libs/localization.py:69
msgid "The password entered is the same as the current password."
msgstr "Sartutako pasahitza egungo pasahitzaren berdina da."
#: libs/localization.py:63
#: libs/localization.py:70
msgid "Password has been changed!"
msgstr "Pasahitza eguneratua izan da!"
#: libs/localization.py:64
#: libs/localization.py:71
msgid "Please, type your username for account deletion."
msgstr "Kontua ezabatzeko idatzi zure erabiltzaile izena, mesedez."
#: libs/localization.py:65
#: libs/localization.py:72
msgid "Account successfully deleted!"
msgstr "Kontua ongi ezabatu da!"
#: libs/localization.py:66
#: libs/localization.py:73
msgid "Username or password is incorrect!"
msgstr "Erabiltzaile izena edo pasahitza okerrak dira!"
#: libs/localization.py:67
#: libs/localization.py:74
msgid "Unable to connect to the remote server."
msgstr "Ezinezkoa urruneko zerbitzara konektatzea."
#: libs/localization.py:68
msgid ""
"Encountered an unexpected error while communicating with the remote server."
#: libs/localization.py:75
msgid "Encountered an unexpected error while communicating with the remote server."
msgstr "Ezusteko errore bat gertatu da urruneko zerbitzariarekin komunikatzean."
#: libs/localization.py:69
#: libs/localization.py:76
msgid "User already exists."
msgstr "Erabiltzaile hori existitzen da."
#: libs/localization.py:70
#: libs/localization.py:77
msgid "Email already exists."
msgstr "Email hori existitzen da."
#: libs/localization.py:71
#: libs/localization.py:78
msgid "Forgot your password? Please try again."
msgstr "Zure pasahitza ahaztu duzu? Saia zeitez berriz, mesedez."
#: libs/localization.py:72
#: libs/localization.py:79
msgid "The session has expired."
msgstr "Saioa iraungi egin da."
#: libs/localization.py:73
#: libs/localization.py:80
msgid "Registration is currently closed. We apologize for the inconvenience."
msgstr "Izen-ematea itxita dago une honetan. Barkatu eragozpenak."
#: libs/localization.py:81
msgid "Two factor authentication has been impossible."
msgstr "Ezinezkoa izan da bi urratseko autentifikazioa burutzea."
#: libs/localization.py:82
msgid "Two factor authentication has been restored."
msgstr "Bi urratseko autentifikazioa birgaitua izan da."
#: libs/localization.py:83
msgid "Two factor authentication has been enabled."
msgstr "Bi urratseko autentifikazioa gaitua izan da."
#: libs/localization.py:84
msgid "Two factor authentication has been disabled."
msgstr "Bi urratseko autentifikazioa desgaitua izan da."
#: libs/localization.py:85
msgid "Two factor authentication was already disabled."
msgstr "Bi urratseko autentifikazioa desgaiturik zegoen."

View File

@ -1,3 +1,4 @@
Beaker>=1.12.1
bottle>=0.12.19
bottle-beaker>=0.1.3
ldap3>=2.9.1
@ -5,3 +6,6 @@ uwsgi>=2.0.21
pyyaml>=6.0
ua-parser>=0.16.1
user-agents>=2.2.0
cryptocode==0.1
onetimepass==1.0.1
segno==1.5.3

View File

@ -1,4 +1,9 @@
#!/bin/bash
##############################
## Erabilera: ##
## sudo chmod +x start.sh ##
## ./start.sh $UID ##
##############################
if [ ! -f settings.ini ]; then
cp settings.ini.example settings.ini
fi
@ -6,6 +11,11 @@ fi
if [[ $# -gt 0 ]]; then
UID_=$1
echo $UID_
else
UID_=$UID
fi
export LDAP_ADMIN_PASSWORD=admin
export LDAP_READONLY_PASSWORD=readonly
uwsgi --http :9090 --enable-threads --uid $UID_ --wsgi-file app.py

View File

@ -0,0 +1,67 @@
/*
* https://remixicon.com
* https://github.com/Remix-Design/RemixIcon
* Copyright RemixIcon.com
* Released under the Apache License Version 2.0
*/
@font-face {
font-family: "remixicon";
src: url('remixicon.eot?t=1700036445706'); /* IE9*/
src: url('remixicon.eot?t=1700036445706#iefix') format('embedded-opentype'), /* IE6-IE8 */
url("remixicon.woff2?t=1700036445706") format("woff2"),
url("remixicon.woff?t=1700036445706") format("woff"),
url('remixicon.ttf?t=1700036445706') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
url('remixicon.svg?t=1700036445706#remixicon') format('svg'); /* iOS 4.1- */
font-display: swap;
}
[class^="ri-"], [class*="ri-"] {
font-family: 'remixicon' !important;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.ri-lg { font-size: 1.3333em; line-height: 0.75em; vertical-align: -.0667em; }
.ri-xl { font-size: 1.5em; line-height: 0.6666em; vertical-align: -.075em; }
.ri-xxs { font-size: .5em; }
.ri-xs { font-size: .75em; }
.ri-sm { font-size: .875em }
.ri-1x { font-size: 1em; }
.ri-2x { font-size: 2em; }
.ri-3x { font-size: 3em; }
.ri-4x { font-size: 4em; }
.ri-5x { font-size: 5em; }
.ri-6x { font-size: 6em; }
.ri-7x { font-size: 7em; }
.ri-8x { font-size: 8em; }
.ri-9x { font-size: 9em; }
.ri-10x { font-size: 10em; }
.ri-fw { text-align: center; width: 1.25em; }
.ri-pencil-line:before { content: "\efe0"; }
.ri-pencil-fill:before { content: "\efdf"; }
.ri-settings-3-line:before { content: "\f0e6"; }
.ri-settings-3-fill:before { content: "\f0e5"; }
.ri-user-line:before { content: "\f264"; }
.ri-user-fill:before { content: "\f25f"; }
.ri-account-circle-line:before { content: "\ea09"; }
.ri-account-circle-fill:before { content: "\ea08"; }
.ri-delete-bin-line:before { content: "\ec2a"; }
.ri-delete-bin-fill:before { content: "\ec29"; }
.ri-toggle-line:before { content: "\f219"; }
.ri-toggle-fill:before { content: "\f218"; }
.ri-history-line:before { content: "\ee17"; }
.ri-history-fill:before { content: "\ee16"; }
.ri-mail-line:before { content: "\eef6"; }
.ri-mail-fill:before { content: "\eef3"; }
.ri-home-line:before { content: "\ee2b"; }
.ri-home-fill:before { content: "\ee26"; }
.ri-send-plane-line:before { content: "\f0da"; }
.ri-send-plane-fill:before { content: "\f0d9"; }
.ri-chat-3-line:before { content: "\eb51"; }
.ri-chat-3-fill:before { content: "\eb50"; }
.ri-file-line:before { content: "\eceb"; }
.ri-file-fill:before { content: "\ece0"; }

BIN
static/fonts/remixicon.eot Normal file

Binary file not shown.

View File

@ -0,0 +1,69 @@
/*
* https://remixicon.com
* https://github.com/Remix-Design/RemixIcon
* Copyright RemixIcon.com
* Released under the Apache License Version 2.0
*/
@font-face {
font-family: "remixicon";
src: url('remixicon.eot?t=1700036445706'); /* IE9*/
src: url('remixicon.eot?t=1700036445706#iefix') format('embedded-opentype'), /* IE6-IE8 */
url("remixicon.woff2?t=1700036445706") format("woff2"),
url("remixicon.woff?t=1700036445706") format("woff"),
url('remixicon.ttf?t=1700036445706') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
url('remixicon.svg?t=1700036445706#remixicon') format('svg'); /* iOS 4.1- */
font-display: swap;
}
[class^="ri-"], [class*="ri-"] {
font-family: 'remixicon' !important;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
:global {
.ri-lg { font-size: 1.3333em; line-height: 0.75em; vertical-align: -.0667em; }
.ri-xl { font-size: 1.5em; line-height: 0.6666em; vertical-align: -.075em; }
.ri-xxs { font-size: .5em; }
.ri-xs { font-size: .75em; }
.ri-sm { font-size: .875em }
.ri-1x { font-size: 1em; }
.ri-2x { font-size: 2em; }
.ri-3x { font-size: 3em; }
.ri-4x { font-size: 4em; }
.ri-5x { font-size: 5em; }
.ri-6x { font-size: 6em; }
.ri-7x { font-size: 7em; }
.ri-8x { font-size: 8em; }
.ri-9x { font-size: 9em; }
.ri-10x { font-size: 10em; }
.ri-fw { text-align: center; width: 1.25em; }
.ri-pencil-line:before { content: "\efe0"; }
.ri-pencil-fill:before { content: "\efdf"; }
.ri-settings-3-line:before { content: "\f0e6"; }
.ri-settings-3-fill:before { content: "\f0e5"; }
.ri-user-line:before { content: "\f264"; }
.ri-user-fill:before { content: "\f25f"; }
.ri-account-circle-line:before { content: "\ea09"; }
.ri-account-circle-fill:before { content: "\ea08"; }
.ri-delete-bin-line:before { content: "\ec2a"; }
.ri-delete-bin-fill:before { content: "\ec29"; }
.ri-toggle-line:before { content: "\f219"; }
.ri-toggle-fill:before { content: "\f218"; }
.ri-history-line:before { content: "\ee17"; }
.ri-history-fill:before { content: "\ee16"; }
.ri-mail-line:before { content: "\eef6"; }
.ri-mail-fill:before { content: "\eef3"; }
.ri-home-line:before { content: "\ee2b"; }
.ri-home-fill:before { content: "\ee26"; }
.ri-send-plane-line:before { content: "\f0da"; }
.ri-send-plane-fill:before { content: "\f0d9"; }
.ri-chat-3-line:before { content: "\eb51"; }
.ri-chat-3-fill:before { content: "\eb50"; }
.ri-file-line:before { content: "\eceb"; }
.ri-file-fill:before { content: "\ece0"; }
}

View File

@ -0,0 +1,56 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<font id="remixicon" horiz-adv-x="1200">
<font-face font-family="remixicon"
units-per-em="1200" ascent="1020"
descent="180" />
<missing-glyph horiz-adv-x="0" /><glyph glyph-name="pencil-line"
unicode="&#xEFE0;"
horiz-adv-x="1200" d="M787.1306249999999 536.4213125000001L716.420625 607.1323125000001L250.7356249999999 141.4458125000001V70.7358125H321.4461249999999L787.1306249999999 536.4213125000001zM857.8406249999999 607.1323125000001L928.550625 677.8428125L857.8406249999999 748.5533125L787.1306249999999 677.8428125L857.8406249999999 607.1323125000001zM362.8676249999999 -29.2641875H150.7356249999999V182.8658125L822.4856249999998 854.6193125C842.015625 874.1458124999999 873.670625 874.1458124999999 893.195625 854.6193125L1034.620625 713.1983124999999C1054.145625 693.6718125 1054.145625 662.0138125000001 1034.620625 642.4873124999999L362.8676249999999 -29.2641875z" /><glyph glyph-name="pencil-fill"
unicode="&#xEFDF;"
horiz-adv-x="1200" d="M645.7106249999999 677.8415000000001L857.8406249999999 465.7120000000001L362.8676249999999 -29.263H150.7356249999999V182.8670000000002L645.7106249999999 677.8415000000001zM716.420625 748.5525000000001L822.4856249999998 854.6185000000002C842.015625 874.1445000000001 873.670625 874.1445000000001 893.195625 854.6185000000002L1034.620625 713.1970000000001C1054.145625 693.6710000000002 1054.145625 662.0125000000002 1034.620625 642.4865000000001L928.550625 536.4205000000002L716.420625 748.5525000000001z" /><glyph glyph-name="settings-3-line"
unicode="&#xF0E6;"
horiz-adv-x="1200" d="M166.98725 170.0005605357185C145.37475 207.4355605357185 129.15275 246.5005605357186 118.08075 286.3055605357186C166.70425 311.1155605357186 200.00025 361.6705605357185 200.00025 420.0005605357186C200.00025 478.2855605357185 166.75425 528.8105605357184 118.19075 553.6400605357185C140.52675 634.1850605357185 182.9427500000001 709.1970605357185 243.2512500000001 770.5100605357186C289.0492500000001 740.8025605357186 349.48175 737.2435605357184 400.00025 766.4105605357186C450.51875 795.5775605357185 477.65275 849.6930605357186 474.82425 904.2085605357186C558.0792500000001 925.7805605357185 644.2492500000001 925.0085605357186 725.16925 904.0795605357184C722.3892500000001 849.6075605357184 749.51925 795.5545605357186 799.9992500000001 766.4105605357186C850.5192500000002 737.2445605357186 910.94925 740.8025605357186 956.74425 770.5070605357184C985.67925 741.0175605357185 1011.39925 707.4350605357185 1033.01425 670.0000605357186C1054.6242499999998 632.5655605357185 1070.84925 593.4980605357184 1081.91925 553.6965605357184C1033.29425 528.8870605357185 999.99925 478.3305605357185 999.99925 420.0005605357186C999.99925 361.7105605357185 1033.24425 311.1905605357186 1081.80925 286.3605605357186C1059.47425 205.8155605357186 1017.0592500000002 130.8055605357186 956.74925 69.4905605357185C910.94925 99.1955605357186 850.5192500000002 102.7555605357186 799.9992500000001 73.5905605357186C749.4792500000001 44.4255605357185 722.3492500000001 -9.6944394642816 725.17425 -64.2094394642815C641.9242500000001 -85.7794394642814 555.7542500000001 -85.0094394642815 474.83075 -64.0794394642814C477.60975 -9.6044394642815 450.47875 44.4455605357186 400.00025 73.5905605357186C349.48325 102.7555605357186 289.05375 99.2005605357186 243.25625 69.4955605357184C214.3227500000001 98.9855605357186 188.60025 132.5655605357186 166.98725 170.0005605357185zM450.00025 160.1905605357185C504.5692500000001 128.6855605357186 543.7592500000001 78.8755605357184 562.52925 21.7155605357185C587.44425 19.3455605357184 612.52425 19.3105605357186 637.44425 21.6355605357185C656.20425 78.8255605357186 695.40425 128.6705605357186 749.9992500000001 160.1905605357185C804.59425 191.7155605357186 867.3642500000001 200.7355605357186 926.2742500000002 188.3905605357185C940.74425 208.8105605357185 953.2542500000002 230.5505605357186 963.65925 253.3105605357186C923.5442500000001 298.1455605357185 899.9992500000001 356.9905605357186 899.9992500000001 420.0005605357186C899.9992500000001 483.0105605357185 923.5442500000001 541.8505605357184 963.65925 586.6855605357187C958.4542500000002 597.9855605357186 952.70425 609.0995605357184 946.4092500000002 620.0000605357186C940.11425 630.9010605357184 933.3642500000002 641.4365605357184 926.17925 651.5895605357184C867.2992500000001 639.2725605357184 804.5692500000001 648.3035605357186 749.9992500000001 679.8080605357186C695.4292500000001 711.3130605357185 656.24425 761.1265605357185 637.4692500000001 818.2830605357185C612.55925 820.6535605357185 587.4742500000001 820.6875605357186 562.55925 818.3645605357185C543.79425 761.1750605357186 504.59425 711.3285605357185 450.00025 679.8080605357186C395.40425 648.2870605357184 332.63675 639.2635605357184 273.72825 651.6090605357185C259.25675 631.1900605357184 246.7457500000001 609.4520605357186 236.3422500000001 586.6920605357184C276.45475 541.8550605357184 300.00025 483.0105605357185 300.00025 420.0005605357186C300.00025 356.9905605357186 276.45575 298.1505605357185 236.3422500000001 253.3155605357186C241.54775 242.0155605357185 247.2962500000001 230.9005605357185 253.59025 220.0005605357185C259.88375 209.1005605357186 266.63425 198.5655605357186 273.82025 188.4105605357185C332.70275 200.7255605357186 395.43325 191.6955605357186 450.00025 160.1905605357185zM599.9992500000001 270.0005605357186C517.15925 270.0005605357186 450.00025 337.1555605357185 450.00025 420.0005605357186C450.00025 502.8405605357185 517.15925 570.0000605357186 599.9992500000001 570.0000605357186C682.84425 570.0000605357186 749.9992500000001 502.8405605357185 749.9992500000001 420.0005605357186C749.9992500000001 337.1555605357185 682.84425 270.0005605357186 599.9992500000001 270.0005605357186zM599.9992500000001 370.0005605357186C627.6142500000001 370.0005605357186 649.9992500000001 392.3855605357186 649.9992500000001 420.0005605357186C649.9992500000001 447.6155605357186 627.6142500000001 470.0005605357185 599.9992500000001 470.0005605357185C572.3842500000001 470.0005605357185 549.9992500000001 447.6155605357186 549.9992500000001 420.0005605357186C549.9992500000001 392.3855605357186 572.3842500000001 370.0005605357186 599.9992500000001 370.0005605357186z" /><glyph glyph-name="settings-3-fill"
unicode="&#xF0E5;"
horiz-adv-x="1200" d="M497.71675 909.4311193331952C564.39625 923.3891193331953 634.05125 923.8296193331952 702.26125 909.5686193331952C711.11125 851.5911193331953 745.25625 798.0151193331952 800.00125 766.4096193331952C854.74125 734.8046193331952 918.21625 732.0191193331952 972.84625 753.3461193331952C1019.30625 701.4021193331953 1053.7512499999998 640.8596193331953 1075.00125 576.1351193331952C1029.27625 539.4821193331952 1000.00125 483.1611193331952 1000.00125 420.0011193331952C1000.00125 356.8011193331953 1029.3112499999997 300.4511193331953 1075.08125 263.8011193331952C1064.51625 231.7811193331952 1050.5362499999999 200.3511193331954 1033.01125 170.0011193331954C1015.48625 139.6461193331952 995.2612500000002 111.8261193331953 972.81125 86.6661193331952C918.18625 107.9811193331953 854.73125 105.1861193331953 800.00125 73.5911193331954C745.30125 42.0111193331952 711.1662500000001 -11.5088806668047 702.28125 -69.4338806668047C635.60625 -83.3888806668046 565.95125 -83.8288806668047 497.73775 -69.5688806668047C488.89025 -11.5938806668048 454.74175 41.9861193331953 399.99975 73.5911193331954C345.25825 105.1961193331952 281.78575 107.9811193331953 227.15225 86.6511193331953C180.69475 138.5961193331952 146.24925 199.1411193331954 124.99825 263.8661193331953C170.72225 300.5161193331953 200.00025 356.8411193331953 200.00025 420.0011193331952C200.00025 483.1961193331953 170.68825 539.5476193331954 124.91875 576.1991193331953C135.48275 608.2201193331953 149.46325 639.6466193331953 166.98725 669.9996193331953C184.51175 700.3526193331952 204.73775 728.1731193331952 227.18675 753.3326193331952C281.81275 732.0206193331952 345.2697500000001 734.8111193331952 399.99975 766.4096193331952C454.69825 797.9901193331953 488.8362500000001 851.5061193331952 497.71675 909.4311193331952zM600.00125 270.0011193331952C682.84125 270.0011193331952 750.00125 337.1561193331952 750.00125 420.0011193331952C750.00125 502.8411193331953 682.84125 569.9996193331951 600.00125 569.9996193331951C517.15625 569.9996193331951 449.99975 502.8411193331953 449.99975 420.0011193331952C449.99975 337.1561193331952 517.15625 270.0011193331952 600.00125 270.0011193331952z" /><glyph glyph-name="user-line"
unicode="&#xF264;"
horiz-adv-x="1200" d="M200 -105C200 115.915 379.086 295 600 295C820.915 295 1000 115.915 1000 -105H900C900 60.6850000000001 765.6850000000001 195 600 195C434.3145 195 300 60.6850000000001 300 -105H200zM600 345C434.25 345 300 479.25 300 645C300 810.75 434.25 945 600 945C765.75 945 900 810.75 900 645C900 479.25 765.75 345 600 345zM600 445C710.5 445 800 534.5 800 645C800 755.5 710.5 845 600 845C489.4999999999999 845 400 755.5 400 645C400 534.5 489.4999999999999 445 600 445z" /><glyph glyph-name="user-fill"
unicode="&#xF25F;"
horiz-adv-x="1200" d="M200 -105C200 115.915 379.086 295 600 295C820.915 295 1000 115.915 1000 -105H200zM600 345C434.25 345 300 479.25 300 645C300 810.75 434.25 945 600 945C765.75 945 900 810.75 900 645C900 479.25 765.75 345 600 345z" /><glyph glyph-name="account-circle-line"
unicode="&#xEA09;"
horiz-adv-x="1200" d="M600 920C876.14 920 1100 696.1424999999999 1100 420C1100 143.86 876.14 -80 600 -80C323.8575 -80 100 143.86 100 420C100 696.1424999999999 323.8575 920 600 920zM607.985 220C506.215 220 414.591 176.565 350.6380000000001 107.2199999999999C419.0195 52.63 505.7 20 600 20C698.475 20 788.635 55.585 858.3299999999999 114.595C794.78 179.6299999999999 706.095 220 607.985 220zM600 820C379.086 820 200 640.914 200 420C200 329.4699999999999 230.075 245.9649999999999 280.7785 178.93C362.8195 265.7950000000001 479.072 320 607.985 320C732.205 320 844.665 269.67 926.09 188.29C972.63 253.665 1000 333.635 1000 420C1000 640.914 820.915 820 600 820zM600 770C710.4549999999999 770 800 680.457 800 570C800 459.5450000000001 710.4549999999999 370 600 370C489.543 370 400 459.5450000000001 400 570C400 680.457 489.543 770 600 770zM600 670C544.77 670 500 625.2284999999999 500 570C500 514.77 544.77 470 600 470C655.23 470 700 514.77 700 570C700 625.2284999999999 655.23 670 600 670z" /><glyph glyph-name="account-circle-fill"
unicode="&#xEA08;"
horiz-adv-x="1200" d="M600 920C876 920 1100 696 1100 420C1100 144 876 -80 600 -80C324 -80 100 144 100 420C100 696 324 920 600 920zM301.166 249.1850000000001C374.5415 139.655 484.7555 70 607.985 70C731.215 70 841.4300000000001 139.655 914.805 249.1850000000001C834.4250000000001 324.14 726.5600000000001 370 607.985 370C489.4105 370 381.5475 324.14 301.166 249.1850000000001zM600 470C682.845 470 750 537.1575 750 620C750 702.8425 682.845 770 600 770C517.155 770 450 702.8425 450 620C450 537.1575 517.155 470 600 470z" /><glyph glyph-name="delete-bin-line"
unicode="&#xEC2A;"
horiz-adv-x="1200" d="M850 720H1100V620H1000V-30C1000 -57.615 977.6149999999998 -80 950 -80H250C222.386 -80 200 -57.615 200 -30V620H100V720H350V870C350 897.614 372.386 920 400 920H800C827.6149999999999 920 850 897.614 850 870V720zM900 620H300V20H900V620zM450 470H550V170H450V470zM650 470H750V170H650V470zM450 820V720H750V820H450z" /><glyph glyph-name="delete-bin-fill"
unicode="&#xEC29;"
horiz-adv-x="1200" d="M850 720H1100V620H1000V-30C1000 -57.615 977.6149999999998 -80 950 -80H250C222.386 -80 200 -57.615 200 -30V620H100V720H350V870C350 897.614 372.386 920 400 920H800C827.6149999999999 920 850 897.614 850 870V720zM450 470V170H550V470H450zM650 470V170H750V470H650zM450 820V720H750V820H450z" /><glyph glyph-name="toggle-line"
unicode="&#xF219;"
horiz-adv-x="1200" d="M400 670C261.929 670 150 558.0709999999999 150 420C150 281.93 261.929 170 400 170H800C938.07 170 1050 281.93 1050 420C1050 558.0709999999999 938.07 670 800 670H400zM400 770H800C993.3 770 1150 613.2995000000001 1150 420C1150 226.7000000000001 993.3 70 800 70H400C206.7005 70 50 226.7000000000001 50 420C50 613.2995000000001 206.7005 770 400 770zM400 270C317.1575 270 250 337.155 250 420C250 502.845 317.1575 570 400 570C482.8425 570 550 502.845 550 420C550 337.155 482.8425 270 400 270z" /><glyph glyph-name="toggle-fill"
unicode="&#xF218;"
horiz-adv-x="1200" d="M400 770H800C993.3 770 1150 613.2995000000001 1150 420C1150 226.7000000000001 993.3 70 800 70H400C206.7005 70 50 226.7000000000001 50 420C50 613.2995000000001 206.7005 770 400 770zM800 270C882.845 270 950 337.155 950 420C950 502.845 882.845 570 800 570C717.155 570 650 502.845 650 420C650 337.155 717.155 270 800 270z" /><glyph glyph-name="history-line"
unicode="&#xEE17;"
horiz-adv-x="1200" d="M600 920C876.14 920 1100 696.1424999999999 1100 420C1100 143.86 876.14 -80 600 -80C323.8575 -80 100 143.86 100 420H200C200 199.085 379.086 20 600 20C820.915 20 1000 199.085 1000 420C1000 640.914 820.915 820 600 820C462.511 820 341.2235 750.633 269.2255 644.988L400 645V545H100V845H200L199.9945 720.0409999999999C291.2170000000001 841.4625 436.4365 920 600 920zM650 670L649.99 440.75L812.13 278.5800000000001L741.42 207.87L549.99 399.35L550 670H650z" /><glyph glyph-name="history-fill"
unicode="&#xEE16;"
horiz-adv-x="1200" d="M600 920C876.14 920 1100 696.1424999999999 1100 420C1100 143.86 876.14 -80 600 -80C323.8575 -80 100 143.86 100 420H200C200 199.085 379.086 20 600 20C820.915 20 1000 199.085 1000 420C1000 640.914 820.915 820 600 820C476.807 820 366.6215 764.3085 293.246 676.7284999999999L400 570H100V870L222.328 747.6759999999999C314.0010000000001 853.2455 449.205 920 600 920zM650 670L649.99 440.75L812.13 278.5800000000001L741.42 207.87L549.99 399.35L550 670H650z" /><glyph glyph-name="mail-line"
unicode="&#xEEF6;"
horiz-adv-x="1200" d="M150 870H1050C1077.615 870 1100 847.614 1100 820V20C1100 -7.615 1077.615 -30 1050 -30H150C122.386 -30 100 -7.615 100 20V820C100 847.614 122.386 870 150 870zM1000 658.104L603.59 303.1L200 659.203V70H1000V658.104zM225.573 770L603.095 436.9L975.05 770H225.573z" /><glyph glyph-name="mail-fill"
unicode="&#xEEF3;"
horiz-adv-x="1200" d="M150 870H1050C1077.615 870 1100 847.614 1100 820V20C1100 -7.615 1077.615 -30 1050 -30H150C122.386 -30 100 -7.615 100 20V820C100 847.614 122.386 870 150 870zM603.0300000000001 435.855L282.361 708.115L217.639 631.885L603.655 304.145L982.72 632.192L917.28 707.808L603.0300000000001 435.855z" /><glyph glyph-name="home-line"
unicode="&#xEE2B;"
horiz-adv-x="1200" d="M1050 1.6726250000001C1050 -25.9423749999998 1027.615 -48.327375 1000 -48.327375H200C172.386 -48.327375 150 -25.9423749999998 150 1.6726250000001V527.216625C150 542.646125 157.1235 557.211125 169.303 566.684125L569.3050000000001 877.7951250000001C587.36 891.838125 612.64 891.838125 630.6949999999999 877.7951250000001L1030.6950000000002 566.684125C1042.875 557.211125 1050 542.646125 1050 527.216625V1.6726250000001zM950 51.6726250000001V502.7621250000001L600 774.984625L250 502.7621250000001V51.6726250000001H950z" /><glyph glyph-name="home-fill"
unicode="&#xEE26;"
horiz-adv-x="1200" d="M1050 1.6726250000001C1050 -25.9423749999998 1027.615 -48.327375 1000 -48.327375H200C172.386 -48.327375 150 -25.9423749999998 150 1.6726250000001V527.216625C150 542.646125 157.1235 557.211125 169.303 566.684125L569.3050000000001 877.7951250000001C587.36 891.838125 612.64 891.838125 630.6949999999999 877.7951250000001L1030.6950000000002 566.684125C1042.875 557.211125 1050 542.646125 1050 527.216625V1.6726250000001z" /><glyph glyph-name="send-plane-line"
unicode="&#xF0DA;"
horiz-adv-x="1200" d="M1103.5500131470606 890.3025092883845L830.8900131470604 -64.0094907116156C823.3400131470604 -90.4344907116155 807.1100131470604 -91.6344907116156 795.0350131470605 -67.4794907116156L567.2200131470604 388.1455092883844L113.3630131470604 569.6900092883843C87.8790131470604 579.8835092883844 88.1945131470604 595.1385092883843 115.0650131470604 604.0955092883844L1069.3750131470604 922.1990092883844C1095.8000131470603 931.0070092883844 1110.9550131470605 916.2165092883844 1103.5500131470606 890.3025092883845zM968.9850131470604 783.3255092883844L357.8285131470604 579.6070092883842L639.6600131470605 466.8755092883844L791.6950131470604 162.8055092883844L968.9850131470604 783.3255092883844z" /><glyph glyph-name="send-plane-fill"
unicode="&#xF0D9;"
horiz-adv-x="1200" d="M114.3534685470316 572.2351730569304C88.2264685470316 580.9441730569303 88.0204685470316 594.9956730569303 114.8909685470316 603.9526730569304L1069.2029685470316 922.0561730569304C1095.6229685470316 930.8641730569304 1110.7779685470316 916.0736730569304 1103.3779685470317 890.1596730569304L830.7179685470315 -64.1523269430696C823.1679685470315 -90.5773269430696 807.9329685470316 -91.4923269430695 796.7679685470316 -66.3723269430696L617.0479685470316 338.0026730569304L917.0479685470316 738.0041730569304L517.0479685470316 438.0026730569304L114.3534685470316 572.2351730569304z" /><glyph glyph-name="chat-3-line"
unicode="&#xEB51;"
horiz-adv-x="1200" d="M364.5585 -21.21L100 -80L158.7905 184.56C121.272 254.7200000000001 100 334.875 100 420C100 696.1424999999999 323.8575 920 600 920C876.14 920 1100 696.1424999999999 1100 420C1100 143.86 876.14 -80 600 -80C514.875 -80 434.72 -58.73 364.5585 -21.21zM379.0375 84.4500000000001L411.714 66.9749999999999C469.124 36.2750000000001 533.275 20 600 20C820.915 20 1000 199.085 1000 420C1000 640.914 820.915 820 600 820C379.086 820 200 640.914 200 420C200 353.275 216.2745 289.125 246.9745 231.7149999999999L264.4480000000001 199.04L231.708 51.71L379.0375 84.4500000000001z" /><glyph glyph-name="chat-3-fill"
unicode="&#xEB50;"
horiz-adv-x="1200" d="M364.5585 -21.21L100 -80L158.7905 184.56C121.272 254.7200000000001 100 334.875 100 420C100 696.1424999999999 323.8575 920 600 920C876.14 920 1100 696.1424999999999 1100 420C1100 143.86 876.14 -80 600 -80C514.875 -80 434.72 -58.73 364.5585 -21.21z" /><glyph glyph-name="file-line"
unicode="&#xECEB;"
horiz-adv-x="1200" d="M450 919.841V920H999.8900000000002C1027.565 920 1050 897.2345 1050 870.41V-30.4099999999999C1050 -57.7999999999999 1027.7549999999999 -80 1000.33 -80H199.67C172.238 -80 150 -57.5050000000001 150 -29.6600000000001V620L450 919.841zM291.459 620H450V778.457L291.459 620zM550 820V570C550 542.386 527.615 520 500 520H250V20H950V820H550z" /><glyph glyph-name="file-fill"
unicode="&#xECE0;"
horiz-adv-x="1200" d="M150 620L450.1595 920H999.8900000000002C1027.565 920 1050 897.2345 1050 870.41V-30.4099999999999C1050 -57.7999999999999 1027.7549999999999 -80 1000.33 -80H199.67C172.238 -80 150 -57.5050000000001 150 -29.6600000000001V620zM500 845L225 570H500V845z" /></font></defs></svg>

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

BIN
static/fonts/remixicon.ttf Normal file

Binary file not shown.

BIN
static/fonts/remixicon.woff Normal file

Binary file not shown.

Binary file not shown.

View File

@ -194,6 +194,22 @@ button.red:hover{
animation-name: fadeOut;
}
/**/
.qr-code {
margin: 0 auto;
width: max-content;
text-align: center;
}
/**/
.info {
margin: 0 auto;
max-width: 24rem;
padding: 0 2.5rem 0 2.5rem;
text-align: justify;
}
/**/
.grid-container {
display: grid;
@ -216,10 +232,91 @@ button.red:hover{
text-transform: capitalize;
}
.grid-item input{
display: unset;
max-width: max-content;
}
/**/
@media only screen and (max-width: 480px) {
form {
border: 0;
}
.info {
max-width: 16rem;
}
}
@media only screen and (min-width: 481px) and (max-width: 760px) {
.info {
max-width: 22rem;
}
}
/*
* https://remixicon.com
* https://github.com/Remix-Design/RemixIcon
* Copyright RemixIcon.com
* Released under the Apache License Version 2.0
*/
@font-face {
font-family: "remixicon";
src: url('fonts/remixicon.eot?t=1700036445706'); /* IE9*/
src: url('fonts/remixicon.eot?t=1700036445706#iefix') format('embedded-opentype'), /* IE6-IE8 */
url("fonts/remixicon.woff2?t=1700036445706") format("woff2"),
url("fonts/remixicon.woff?t=1700036445706") format("woff"),
url('fonts/remixicon.ttf?t=1700036445706') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
url('fonts/remixicon.svg?t=1700036445706#remixicon') format('svg'); /* iOS 4.1- */
font-display: swap;
}
[class^="ri-"], [class*="ri-"] {
font-family: 'remixicon' !important;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.ri-lg { font-size: 1.3333em; line-height: 0.75em; vertical-align: -.0667em; }
.ri-xl { font-size: 1.5em; line-height: 0.6666em; vertical-align: -.075em; }
.ri-xxs { font-size: .5em; }
.ri-xs { font-size: .75em; }
.ri-sm { font-size: .875em }
.ri-1x { font-size: 1em; }
.ri-2x { font-size: 2em; }
.ri-3x { font-size: 3em; }
.ri-4x { font-size: 4em; }
.ri-5x { font-size: 5em; }
.ri-6x { font-size: 6em; }
.ri-7x { font-size: 7em; }
.ri-8x { font-size: 8em; }
.ri-9x { font-size: 9em; }
.ri-10x { font-size: 10em; }
.ri-fw { text-align: center; width: 1.25em; }
.ri-home-line:before { content: "\ee2b"; }
.ri-home-fill:before { content: "\ee26"; }
.ri-mail-line:before { content: "\eef6"; }
.ri-mail-fill:before { content: "\eef3"; }
.ri-send-plane-line:before { content: "\f0da"; }
.ri-send-plane-fill:before { content: "\f0d9"; }
.ri-chat-3-line:before { content: "\eb51"; }
.ri-chat-3-fill:before { content: "\eb50"; }
.ri-pencil-line:before { content: "\efe0"; }
.ri-pencil-fill:before { content: "\efdf"; }
.ri-file-line:before { content: "\eceb"; }
.ri-file-fill:before { content: "\ece0"; }
.ri-settings-3-line:before { content: "\f0e6"; }
.ri-settings-3-fill:before { content: "\f0e5"; }
.ri-user-line:before { content: "\f264"; }
.ri-user-fill:before { content: "\f25f"; }
.ri-account-circle-line:before { content: "\ea09"; }
.ri-account-circle-fill:before { content: "\ea08"; }
.ri-delete-bin-line:before { content: "\ec2a"; }
.ri-delete-bin-fill:before { content: "\ec29"; }
.ri-toggle-line:before { content: "\f219"; }
.ri-toggle-fill:before { content: "\f218"; }
.ri-history-line:before { content: "\ee17"; }
.ri-history-fill:before { content: "\ee16"; }}

0
static/tmp/.gitkeep Normal file
View File

View File

@ -51,6 +51,23 @@
<a href="/change_pwd">{{ str['edit'] }}</a>
</div>
<!-- 2FA -->
<div class="grid-item">
<div class="account">
<h5>2FA</h5>
<p>{{ str['2fa-title'] }}</p>
</div>
</div>
<div class="grid-item">
<!--<a href="/_2fa">{{'Kendu' if data['secureAuth'] == True else "Ezarri"}}</a>-->
% if data['secureAuth'] == True:
<a href="/_2fa"><i class='ri-toggle-fill'></i></a>
% else:
<a href="/_2fa"><i class='ri-toggle-line'></i></a>
%end
</div>
<div class="grid-item">
<div class="account">
<h5>{{ str['logs'] }}</h5>
@ -61,7 +78,6 @@
<a href="/logs">{{ str['show'] }}</a>
</div>
<div class="account">
<a href="/logout"><button class="green" type="button">{{ str['log-out'] }}</button></a>
<a href="/delete"><button class="red" type="button">{{ str['del'] }}</button></a>

View File

@ -4,5 +4,6 @@
http = :8080
chdir = %v
wsgi-file = %v/app.py
enable-thread = true
processes = 1
threads = 2