From 192e8b4b0b66e41783265b362025c331c322f089 Mon Sep 17 00:00:00 2001 From: Antonio de la Rosa Date: Fri, 10 Jan 2025 22:59:51 +0100 Subject: [PATCH] Added keyutils and fixes in autologin --- parameciofast/libraries/db/simplequery.py | 19 +++- parameciofast/libraries/keyutils.py | 86 +++++++++++++++++++ parameciofast/modules/fastadmin/app.py | 52 +++++++++-- .../modules/fastadmin/templates/login.phtml | 8 +- 4 files changed, 155 insertions(+), 10 deletions(-) create mode 100644 parameciofast/libraries/keyutils.py diff --git a/parameciofast/libraries/db/simplequery.py b/parameciofast/libraries/db/simplequery.py index 2268b91..7c8aaad 100644 --- a/parameciofast/libraries/db/simplequery.py +++ b/parameciofast/libraries/db/simplequery.py @@ -1,7 +1,7 @@ # A more simple set for make queries -def insert(model, dict_values, db): +def insert(model, db, dict_values): final_values={} @@ -25,3 +25,20 @@ def insert(model, dict_values, db): return success + +def select(model, db, dict_fields=[], where_sql='', limit='', dict_values=[]): + + if len(dict_fields)==0: + dict_fields=['`'+field+'`' for field in model.fields.keys()] + + str_fields=", ".join(dict_fields) + + str_query='select {} from {} {} limit 1'.format(str_fields, model.name, where_sql) + + arr_result=[] + + with db.query(str_query, dict_values) as cursor: + + arr_result=cursor.fetchall() + + return arr_result diff --git a/parameciofast/libraries/keyutils.py b/parameciofast/libraries/keyutils.py new file mode 100644 index 0000000..7b0abda --- /dev/null +++ b/parameciofast/libraries/keyutils.py @@ -0,0 +1,86 @@ +""" +Paramecio2fm is a series of wrappers for Flask, mako and others and construct a simple headless cms. + +Copyright (C) 2023 Antonio de la Rosa Caballero + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +""" + +from hashlib import sha512, sha256 +from base64 import b64encode +from os import urandom +import string +import secrets + +# Functions for create random strings usando urandom + +def create_key_encrypt(n=10): + """ Simple function for create a random string + + Simple function for create a random string based in sha512 + + Args: + n (int): size of string random bytes (view urandom function in Python3 Help) + """ + + return sha512(urandom(n)).hexdigest() + +def create_key_encrypt_256(n=10): + + """ Simple function for create a random string + + Simple function for create a random string based in sha256 + + Args: + n (int): size of string random bytes (view urandom function in Python3 Help) + """ + + return sha256(urandom(n)).hexdigest() + +def create_key(n=10): + + """ Simple function for create a random string + + Simple function for create a random string based in urandom function and base64 encoding + + Args: + n (int): size of string random bytes (view urandom function in Python3 Help) + """ + + rand_bytes=urandom(n) + + return b64encode(rand_bytes).decode('utf-8')[0:-2] + +def create_simple_password(n=14): + + """ Based in python3 documentation for create passwords using secrets module + + https://docs.python.org/3/library/secrets.html + + Args: + n (int): Number of random elements of the password + + """ + + password='' + + alphabet=string.ascii_letters+string.digits+string.punctuation + + while True: + password=''.join(secrets.choice(alphabet) for i in range(n)) + if (any(c.islower() for c in password) and any(c.isupper() for c in password) and sum(c.isdigit() for c in password) >= 3): + break + + return password + diff --git a/parameciofast/modules/fastadmin/app.py b/parameciofast/modules/fastadmin/app.py index 4f76878..b7c0330 100644 --- a/parameciofast/modules/fastadmin/app.py +++ b/parameciofast/modules/fastadmin/app.py @@ -12,6 +12,8 @@ from parameciofast.libraries.fastutils import ResponseData from parameciofast.libraries.db import simplequery from settings import config from parameciofast.libraries.datetime import now, format_local_strtime, timestamp_to_datetime, obtain_timestamp +from parameciofast.libraries.keyutils import create_key_encrypt, create_key +from time import time env=env_theme(__file__) t=PTemplate(env, app.url_path_for) @@ -28,15 +30,16 @@ seconds_login=300 if hasattr(config, 'seconds_login'): seconds_login=config.seconds_login +cookie_name='paramecio_session' -#useradmin.create_forms() - -#useradmin.safe_query=True +if hasattr(config, 'cookie_name'): + cookie_name=config.cookie_name @admin_app.get('/', response_class=HTMLResponse) def home_admin(request: Request, paramecio_session: Annotated[str | None, Cookie(description='Cookie for validate into the admin site. The cookie name can change in you settings/config.py')] = None, remote_address: Annotated[str | None, Header()] = None): if not request.session.get('login_admin', None): + return RedirectResponse(app.url_path_for('login_admin')) return "Hello world!" @@ -47,6 +50,22 @@ def login_admin(request: Request): db=WebModel.connection() + if cookie_name+'_remember' in request.cookies: + + arr_user=simplequery.select(usermodel, db, dict_fields=['id', 'username'], where_sql='WHERE token_login=%s', dict_values=[request.cookies[cookie_name+'_remember']]) + + if len(arr_user)>0: + now_str=now() + date_now=format_local_strtime('YYYY-MM-DD HH:mm:ss', now_str) + + db.query('update useradmin set last_login=%s WHERE id=%s', [date_now, arr_user[0]['id']]) + + request.session['login_admin']=True + + db.close() + + return RedirectResponse(app.url_path_for('home_admin')) + with db.query('select count(id) as num_users from useradmin', []) as cursor: num_users=cursor.fetchone()['num_users'] @@ -85,7 +104,7 @@ class ResponseDataLogin(ResponseData): no_login: bool @admin_app.post('/login') -def check_login_admin(user: UserAdmin, request: Request) -> ResponseDataLogin: +def check_login_admin(user: UserAdmin, request: Request, response: Response) -> ResponseDataLogin: db=WebModel.connection() @@ -106,6 +125,22 @@ def check_login_admin(user: UserAdmin, request: Request) -> ResponseDataLogin: if usermodel.fields['password'].verify(user.password, result['password']): + remember_key='' + + if user.remember_login==True: + # Send cookies + + remember_key=create_key_encrypt() + + timestamp=int(time())+315360000 + + response.set_cookie(key=cookie_name+'_remember', value=remember_key, expires=timestamp, max_age=315360000, httponly=True, path=config.application_root) + + now_str=now() + date_now=format_local_strtime('YYYY-MM-DD HH:mm:ss', now_str) + + db.query('update useradmin set token_login=%s, last_login=%s WHERE id=%s', [remember_key, date_now, result['id']]) + request.session['login_admin']=True error=0 message='' @@ -156,7 +191,7 @@ def signup_insert_admin(user: UserSignup, request: Request) -> ResponseData: if not error: - if simplequery.insert(usermodel, dict(user), db): + if simplequery.insert(usermodel, db, dict(user)): error=0 message="User added!" @@ -168,10 +203,15 @@ def signup_insert_admin(user: UserSignup, request: Request) -> ResponseData: @admin_app.get('/logout') def logout_admin(request: Request) -> RedirectResponse: + response=RedirectResponse(app.url_path_for('login_admin')) + if 'login_admin' in request.session: del request.session['login_admin'] - return RedirectResponse(app.url_path_for('login_admin')) + if cookie_name+'_remember' in request.cookies: + response.delete_cookie(cookie_name+'_remember', path=config.application_root) + + return response def check_login_tries(request, db): diff --git a/parameciofast/modules/fastadmin/templates/login.phtml b/parameciofast/modules/fastadmin/templates/login.phtml index 7cb5852..ec42901 100644 --- a/parameciofast/modules/fastadmin/templates/login.phtml +++ b/parameciofast/modules/fastadmin/templates/login.phtml @@ -44,8 +44,8 @@
- - + +
@@ -100,7 +100,9 @@ $('#login_submit').prop('disabled', true); - data_form={'username': $('#username_form').val(), 'password': $('#password_form').val(), 'csrf_token': $("#csrf_token").val(), 'remember_login': 0}; + const is_checked=document.getElementById('remember_login_form').checked; + + data_form={'username': $('#username_form').val(), 'password': $('#password_form').val(), 'csrf_token': $("#csrf_token").val(), 'remember_login': is_checked}; $.ajax({ url: "${url_for('check_login_admin')}",