diff --git a/api/db.py b/api/db.py index 23d43c9..3acdbd4 100644 --- a/api/db.py +++ b/api/db.py @@ -1,9 +1,13 @@ -from fastapi import APIRouter, HTTPException +from fastapi import APIRouter, HTTPException, Depends, Header import logging import db +import api_security as auth from pydantic import BaseModel +from api import tokens + + #Logging logging.basicConfig( @@ -42,8 +46,11 @@ class Item_weight(BaseModel): class Item_piece(BaseModel): pieces: int +class Item_duration(BaseModel): + duration: str + @router.post("/insert", status_code=201) -def insert(item_name: Item_name, item_weight: Item_weight, item_pieces: Item_piece): +def insert(item_name: Item_name, item_weight: Item_weight, item_pieces: Item_piece, token: str = Depends(tokens.check_token)): name = item_name.name_of_clothes.upper() weight = item_weight.weight pieces = item_pieces.pieces @@ -54,7 +61,7 @@ def insert(item_name: Item_name, item_weight: Item_weight, item_pieces: Item_pie @router.put("/update/{clothe_name}/pieces", status_code=202) -def update_pieces(clothe_name: str, item_pieces: Item_piece): +def update_pieces(clothe_name: str, item_pieces: Item_piece, token: str = Depends(tokens.check_token)): name = clothe_name.upper() pieces = item_pieces.pieces if db.check_clothe(name) != None: @@ -68,7 +75,7 @@ def update_pieces(clothe_name: str, item_pieces: Item_piece): @router.put("/update/{clothe_name}/weight", status_code=202) -def update_weight(clothe_name: str, item: Item_weight): +def update_weight(clothe_name: str, item: Item_weight, token: str = Depends(tokens.check_token)): name = clothe_name.upper() weight = item.weight if db.check_clothe(name) != None: @@ -82,7 +89,7 @@ def update_weight(clothe_name: str, item: Item_weight): @router.delete("/{clothe_name}/delete", status_code=204) -def delete(clothe_name: str): +def delete(clothe_name: str, token: str = Depends(tokens.check_token)): name = clothe_name.upper() if db.check_clothe(name) != None: db.delete(name) @@ -90,4 +97,6 @@ def delete(clothe_name: str): return {"message": "Item was deleted"} else: logging.error(f"Item {name} not found") - raise HTTPException(status_code=404, detail=f"Item {name} not found") \ No newline at end of file + raise HTTPException(status_code=404, detail=f"Item {name} not found") + + diff --git a/api/tokens.py b/api/tokens.py new file mode 100644 index 0000000..faf2cd5 --- /dev/null +++ b/api/tokens.py @@ -0,0 +1,60 @@ +from fastapi import APIRouter, HTTPException, Depends, Header +import logging +import db +import api_security as auth +from pydantic import BaseModel + + + +def check_token(token:str = Header(...)): + if not auth.check_token(token): + raise HTTPException(status_code=401, detail="Invalid token") + return token + +def check_admin_token(admin_token: str = Header(...)): + if not auth.check_admin_token(admin_token): + raise HTTPException(status_code=401, detail="No admin token") + return admin_token + + +#Logging + +logging.basicConfig( + filename='api.log', # Log to a file + level=logging.DEBUG, # Log everything (DEBUG and above) + format='%(asctime)s %(levelname)s %(message)s' +) + +logging.info('Application started') +try: + logging.debug('Debug info here') +except Exception as e: + logging.exception('An error occurred') + +router = APIRouter(prefix="/tokens", tags=["tokens"]) + + +class Item_duration(BaseModel): + duration: str | int + +class Item_token(BaseModel): + token: str + +@router.get("/help") +def help(): + return{"duration": "string or int"} + + +@router.post("/create") +def create_token(duration: Item_duration, admin_token: str = Depends(check_admin_token)): + new_token = auth.generate_token(duration.duration) + return {"new token": new_token} + +@router.delete("/delete") +def delete(token: Item_token, admin_token: str = Depends(check_admin_token)): + auth.delete_token(token.token) + return {"messages": "token deleted"} + +@router.get("/test") +def test(token: str = Depends(check_token)): + return {"message": "you can authenticate", "status": "ok"} \ No newline at end of file diff --git a/api_security.py b/api_security.py new file mode 100644 index 0000000..c9825eb --- /dev/null +++ b/api_security.py @@ -0,0 +1,140 @@ +import time +from dotenv import load_dotenv +import db +import os +from datetime import datetime, timedelta +import logging +from argon2 import PasswordHasher +import random +import string + + +#Logging + +logging.basicConfig( + filename='api.log', # Log to a file + level=logging.DEBUG, # Log everything (DEBUG and above) + format='%(asctime)s %(levelname)s %(message)s' +) + +logging.info('Application started') +try: + logging.debug('Debug info here') +except Exception as e: + logging.exception('An error occurred') + +load_dotenv() +db_name = os.getenv("DB_NAME") +db_psswd = os.getenv("DB_PASSWORD") +db_user = os.getenv("DB_USER") +db_port = os.getenv("DB_PORT") or "3306" +db_host = os.getenv("DB_HOST") +if db_host == None: + logging.error("no hostname provided") + os._exit(1) + +db.connect(db_user, db_psswd, db_host, int(db_port), db_name) + + +def generate_token(duration): + ph = PasswordHasher() + token = "".join(random.choices(string.ascii_lowercase + string.digits, k=32)) + print(token) + hash = ph.hash(token) + end_date = enddate(duration) + time = datetime.now() + cursor = db.connection.cursor() + executed = "INSERT INTO tokens (tokens, time, end_date) VALUES (?, ?, ?)" + executed_data = (hash, time, end_date) + cursor.execute(executed, executed_data) + db.connection.commit() + logging.info(f"Token till {end_date} created") + return token + + +def enddate(duration): + if duration == "week": + return datetime.now() + timedelta(days=7) + if duration == "day": + return datetime.now() + timedelta(days=1) + if duration == "year": + return datetime.now() + timedelta(days=365) + if isinstance(duration, int): + return datetime.now() + timedelta(days=duration) + if duration == "forever": + return datetime.now() + timedelta(days=4000) + raise ValueError(f"Invalid duration: {duration}") + + +def check_token(token): + ph = PasswordHasher() + cursor = db.connection.cursor() + valid = False + cursor.execute("SELECT tokens FROM tokens") + hashes = [row[0] for row in cursor.fetchall()] + for hash in hashes: + try: + valid = ph.verify(hash, token) + if valid == True: + return valid + except Exception as e: + logging.error("Token not the same") + logging.error(e) + + +def check_validaty(): + cursor = db.connection.cursor() + cursor.execute("SELECT end_date FROM tokens") + end_dates = [row[0] for row in cursor.fetchall()] + for end_date in end_dates: + end_date_dt = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S") + if datetime.now() > end_date_dt: + cursor.execute("DELETE FROM tokens WHERE end_date = ?", (end_date,)) + + +def admin_token(): + ph = PasswordHasher() + token = "".join(random.choices(string.ascii_lowercase + string.digits, k=64)) + hash = ph.hash(token) + time = datetime.now() + end_date = enddate("forever") + cursor = db.connection.cursor() + executed = "INSERT INTO tokens (tokens, time, end_date) VALUES (?, ?, ?)" + executed_data = (hash, time, end_date) + cursor.execute(executed, executed_data) + db.connection.commit() + logging.info(f"Token till {end_date} created") + return token + + +def check_admin_token(token): + valid = False + if len(token) == 64: + ph = PasswordHasher() + cursor = db.connection.cursor() + cursor.execute("SELECT tokens FROM tokens") + hashes = [row[0] for row in cursor.fetchall()] + for hash in hashes: + try: + valid = ph.verify(hash, token) + if valid == True: + return valid + except Exception as e: + logging.info(e) + + +def delete_token(token): + if not len(token) == 64: + ph = PasswordHasher() + cursor = db.connection.cursor() + cursor.execute("SELECT tokens FROM tokens") + hashes = [row[0] for row in cursor.fetchall()] + for hash in hashes: + try: + valid = ph.verify(hash, token) + if valid == True: + cursor.execute("DELETE FROM tokens WHERE tokens = ?", (hash,)) + db.connection.commit() + logging.critical("token deleted") + except Exception as e: + logging.error(e) diff --git a/api_server.py b/api_server.py index c30f77e..c7ac25f 100644 --- a/api_server.py +++ b/api_server.py @@ -1,7 +1,8 @@ from fastapi import FastAPI -from api import health, db, query +from api import health, db, query, tokens -app =FastAPI() +app = FastAPI() app.include_router(health.router) app.include_router(db.router) -app.include_router(query.router) \ No newline at end of file +app.include_router(query.router) +app.include_router(tokens.router) \ No newline at end of file diff --git a/main.py b/main.py index 258097c..e0495a5 100644 --- a/main.py +++ b/main.py @@ -2,12 +2,15 @@ import logging import subprocess import sys import os +import api_security as auth +import time - -open("api.log", "w").close() -open("app.log", "w").close() -open("api_server.log", "w").close() - +try: + os.remove("api.log") + os.remove("api_server.log") + os.remove("app.log") +except Exception as e: + logging.error(e) #Logging logging.basicConfig( filename='app.log', # Log to a file @@ -37,3 +40,11 @@ cli_process = subprocess.Popen([sys.executable, "cli.py"]) api_process.wait() cli_process.wait() +# automatic durataion validation check +while True: + time.sleep(300) + try: + auth.check_validaty() + + except Exception as e: + logging.info(e) diff --git a/requirements.txt b/requirements.txt index ca76f24..b24feb2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ mariadb == 1.1.14 python-dotenv == 1.2.3 fastapi[standard] == 0.141.1 +argon2-cffi == 25.1.0 \ No newline at end of file diff --git a/start.py b/start.py index abca631..cf57cb4 100644 --- a/start.py +++ b/start.py @@ -2,6 +2,7 @@ from dotenv import load_dotenv import db import os import logging +import api_security as auth #Logging @@ -32,13 +33,13 @@ db.connect(db_user, db_psswd, db_host, int(db_port), db_name) def new_table(name): cursor = db.connection.cursor() if name == "tokens": - executed = "CREATE TABLE tokens (tokens VARCHAR(50) PRIMARY KEY, `time` VARCHAR(50), duration VARCHAR(50))" + executed = "CREATE TABLE tokens (tokens VARCHAR(255) PRIMARY KEY, `time` VARCHAR(255), end_date VARCHAR(255))" cursor.execute(executed) db.connection.commit() logging.info("Table 'tokens' created") if name == "luggage": - executed = "CREATE TABLE luggage (clothes VARCHAR(50) PRIMARY KEY, `weight` DECIMAL(20,2), pieces INT(11))" + executed = "CREATE TABLE luggage (clothes VARCHAR(50) PRIMARY KEY, weight DECIMAL(20,2), pieces INT(11))" cursor.execute(executed) db.connection.commit() logging.info("Table 'luggage' created") @@ -46,4 +47,5 @@ def new_table(name): new_table("tokens") new_table("luggage") +print(auth.admin_token())