From f91bca2af497cc1a2b7c32e2bb57f8c3da85ada2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 21 Jul 2026 19:26:04 +0300 Subject: [PATCH] initial --- chat.db | Bin 0 -> 8192 bytes dbworker.py | 191 ++++++++++++++++++++ index.html | 461 +++++++++++++++++++++++++++++++++++++++++++++++++ index.py | 240 +++++++++++++++++++++++++ pyproject.toml | 2 + 5 files changed, 894 insertions(+) create mode 100644 chat.db create mode 100644 dbworker.py create mode 100644 index.html create mode 100644 index.py create mode 100644 pyproject.toml diff --git a/chat.db b/chat.db new file mode 100644 index 0000000000000000000000000000000000000000..0d5345da5a2e33f6af38efd7ccdeecec0e9e98ff GIT binary patch literal 8192 zcmeI#Pfx-y6aes+_$QJWym=z&5fU*Q6M+MX0|p{vZetAaVm8?3l#M?d3=bSIeg)5d z9zT?cL*ZaT;#HFO()P8lO_Tm^ZA!fD8^jz=9L*;TIzbqtD?$)Lk#!H#ci?c`ThCW% zwf-w2==|;DI6Q@84HPyA5C8!X009sH0T2KI5C8!X009u#eSsx~4^QK9{KEK}J}?}^ z^EAuwHaSv~Iac9_!WMasY^o#?J+k|xCMaB$lZkMv5TV8MXCy3-h|0>PYxZ}!H*|f& z^|$W%c6hI^Iisx?igK;N%3V_Dx+G!ulhNeQ-+X~$i{0mk7k~f=fB*=900@8p2!H?x zfB*=9z`qn&B7A^S%ho%H`)mZdV9#jc__?$8fTkX9$6$BpUsU>YzN)||Pk z`_oRZTyMHh)y_!BicHPC7b-J)9#Eo@nRAjJNEMdu8oe7!EnAF`%eB?2NzGh0-GKN*^JWhK literal 0 HcmV?d00001 diff --git a/dbworker.py b/dbworker.py new file mode 100644 index 0000000..9e77111 --- /dev/null +++ b/dbworker.py @@ -0,0 +1,191 @@ +import sqlalchemy +from sqlalchemy import create_engine, String, Integer, Text, DateTime +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session +from sqlalchemy.orm import sessionmaker +from datetime import datetime +import time +import os +from cryptography.fernet import Fernet +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC +import base64 + +# Encryption setup +SALT = b'salt_123456789' +PASSWORD = b'chat_secret_key_123' + +def get_cipher(): + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=32, + salt=SALT, + iterations=100000, + ) + key = base64.urlsafe_b64encode(kdf.derive(PASSWORD)) + return Fernet(key) + +class Base(DeclarativeBase): + pass + +class Messages(Base): + __tablename__ = "messages" + + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + ip: Mapped[str] = mapped_column(String(64), nullable=False) + content: Mapped[str] = mapped_column(Text, nullable=False) + timestamp: Mapped[int] = mapped_column(Integer, nullable=False) + +# MariaDB configuration +DB_USER = 'chat' +DB_PASSWORD = 'uqhyUb5eBGg3qad.' +DB_HOST = 'localhost' +DB_PORT = '3306' +DB_NAME = 'chat_db' + +# Create engine for MariaDB +DATABASE_URL = f"mariadb+mariadbconnector://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}" +# Alternative with pymysql (uncomment if needed): +# DATABASE_URL = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}" + +engine = create_engine( + DATABASE_URL, + echo=False, + pool_size=10, + max_overflow=20, + pool_pre_ping=True, + pool_recycle=3600 +) + +# Create tables +Base.metadata.create_all(engine) + +class DBfrontend(): + def __init__(self): + self.Session = sessionmaker(bind=engine) + self.cipher = get_cipher() + + def encrypt_message(self, message: str) -> str: + """Encrypt message before storing""" + return self.cipher.encrypt(message.encode()).decode() + + def decrypt_message(self, encrypted_message: str) -> str: + """Decrypt message after retrieval""" + try: + return self.cipher.decrypt(encrypted_message.encode()).decode() + except: + return "[Decryption failed]" + + def add_message(self, ip: str, content: str) -> int: + """Add a new message to the database and return its ID""" + with self.Session() as session: + encrypted_content = self.encrypt_message(content) + new_message = Messages( + ip=ip, + content=encrypted_content, + timestamp=int(time.time() * 1000) + ) + session.add(new_message) + session.commit() + return new_message.id + + def get_all_messages(self) -> list: + """Get all messages from the database""" + with self.Session() as session: + messages = session.query(Messages).all() + for msg in messages: + msg.content = self.decrypt_message(msg.content) + return messages + + def get_message_by_id(self, message_id: int) -> Messages | None: + """Get a specific message by its ID""" + with self.Session() as session: + message = session.query(Messages).get(message_id) + if message: + message.content = self.decrypt_message(message.content) + return message + + def get_messages_by_ip(self, ip: str) -> list: + """Get all messages from a specific IP address""" + with self.Session() as session: + messages = session.query(Messages).filter(Messages.ip == ip).all() + for msg in messages: + msg.content = self.decrypt_message(msg.content) + return messages + + def update_message(self, message_id: int, new_content: str) -> bool: + """Update the content of a message""" + with self.Session() as session: + message = session.query(Messages).get(message_id) + if message: + message.content = self.encrypt_message(new_content) + session.commit() + return True + return False + + def delete_message(self, message_id: int) -> bool: + """Delete a message by its ID""" + with self.Session() as session: + message = session.query(Messages).get(message_id) + if message: + session.delete(message) + session.commit() + return True + return False + + def delete_messages_by_ip(self, ip: str) -> int: + """Delete all messages from a specific IP address and return count""" + with self.Session() as session: + messages = session.query(Messages).filter(Messages.ip == ip).all() + count = len(messages) + for message in messages: + session.delete(message) + session.commit() + return count + + def get_message_count(self) -> int: + """Get total number of messages""" + with self.Session() as session: + return session.query(Messages).count() + + def get_latest_messages(self, limit: int = 10) -> list: + """Get the latest N messages""" + with self.Session() as session: + messages = session.query(Messages).order_by(Messages.id.desc()).limit(limit).all() + for msg in messages: + msg.content = self.decrypt_message(msg.content) + return messages + + def clear_all_messages(self) -> int: + """Delete all messages and return count""" + with self.Session() as session: + count = session.query(Messages).count() + session.query(Messages).delete() + session.commit() + return count + + def get_messages_by_date_range(self, start_timestamp: int, end_timestamp: int) -> list: + """Get messages within a timestamp range""" + with self.Session() as session: + messages = session.query(Messages).filter( + Messages.timestamp >= start_timestamp, + Messages.timestamp <= end_timestamp + ).order_by(Messages.timestamp.desc()).all() + for msg in messages: + msg.content = self.decrypt_message(msg.content) + return messages + +# Optional: Test connection function +def test_connection(): + """Test if MariaDB connection is working""" + try: + with engine.connect() as conn: + result = conn.execute(sqlalchemy.text("SELECT 1")) + print("✅ MariaDB connection successful!") + return True + except Exception as e: + print(f"❌ MariaDB connection failed: {e}") + return False + +# Run test if executed directly +if __name__ == "__main__": + test_connection() \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..54b90af --- /dev/null +++ b/index.html @@ -0,0 +1,461 @@ + + + + + + Secure Chat + + + +
+
+

🔒 Secure Chat

+ ● Encrypted +
+ +
+
Loading messages...
+
+ +
+ + +
+
+ +
+ + + + \ No newline at end of file diff --git a/index.py b/index.py new file mode 100644 index 0000000..4de45b4 --- /dev/null +++ b/index.py @@ -0,0 +1,240 @@ +from bottle import route, request, response, run, static_file +import hashlib +import time +import json +from cryptography.fernet import Fernet +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC +import base64 +import sqlalchemy +from sqlalchemy import create_engine, String, Integer, DateTime +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session +from sqlalchemy.orm import sessionmaker +from datetime import datetime + +# Encryption setup +SALT = b'salt_123456789' # In production, use a random salt +PASSWORD = b'chat_secret_key_123' # In production, use environment variable + +def get_cipher(): + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=32, + salt=SALT, + iterations=100000, + ) + key = base64.urlsafe_b64encode(kdf.derive(PASSWORD)) + return Fernet(key) + +class Base(DeclarativeBase): + pass + +class Messages(Base): + __tablename__ = "messages" + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + ip: Mapped[str] = mapped_column(String, nullable=False) + content: Mapped[str] = mapped_column(String, nullable=False) + timestamp: Mapped[int] = mapped_column(Integer, nullable=False) + +engine = create_engine("sqlite:///chat.db", echo=False) +Base.metadata.create_all(engine) + +class DBfrontend(): + def __init__(self): + self.Session = sessionmaker(bind=engine) + self.cipher = get_cipher() + + def encrypt_message(self, message: str) -> str: + """Encrypt message before storing""" + return self.cipher.encrypt(message.encode()).decode() + + def decrypt_message(self, encrypted_message: str) -> str: + """Decrypt message after retrieval""" + try: + return self.cipher.decrypt(encrypted_message.encode()).decode() + except: + return "[Decryption failed]" + + def add_message(self, ip: str, content: str) -> int: + """Add a new encrypted message to the database""" + with self.Session() as session: + encrypted_content = self.encrypt_message(content) + new_message = Messages( + ip=ip, + content=encrypted_content, + timestamp=int(time.time() * 1000) + ) + session.add(new_message) + session.commit() + return new_message.id + + def get_all_messages(self) -> list: + """Get all messages and decrypt them""" + with self.Session() as session: + messages = session.query(Messages).all() + for msg in messages: + msg.content = self.decrypt_message(msg.content) + return messages + + def get_message_by_id(self, message_id: int) -> Messages | None: + """Get a specific message by its ID""" + with self.Session() as session: + message = session.query(Messages).get(message_id) + if message: + message.content = self.decrypt_message(message.content) + return message + + def get_messages_by_ip(self, ip: str) -> list: + """Get all messages from a specific IP address""" + with self.Session() as session: + messages = session.query(Messages).filter(Messages.ip == ip).all() + for msg in messages: + msg.content = self.decrypt_message(msg.content) + return messages + + def update_message(self, message_id: int, new_content: str) -> bool: + """Update the content of a message""" + with self.Session() as session: + message = session.query(Messages).get(message_id) + if message: + message.content = self.encrypt_message(new_content) + session.commit() + return True + return False + + def delete_message(self, message_id: int) -> bool: + """Delete a message by its ID""" + with self.Session() as session: + message = session.query(Messages).get(message_id) + if message: + session.delete(message) + session.commit() + return True + return False + + def delete_messages_by_ip(self, ip: str) -> int: + """Delete all messages from a specific IP address""" + with self.Session() as session: + messages = session.query(Messages).filter(Messages.ip == ip).all() + count = len(messages) + for message in messages: + session.delete(message) + session.commit() + return count + + def get_message_count(self) -> int: + """Get total number of messages""" + with self.Session() as session: + return session.query(Messages).count() + + def get_latest_messages(self, limit: int = 10) -> list: + """Get the latest N messages""" + with self.Session() as session: + messages = session.query(Messages).order_by(Messages.id.desc()).limit(limit).all() + for msg in messages: + msg.content = self.decrypt_message(msg.content) + return messages + +# Initialize database +db = DBfrontend() + +# Routes +import os + +@route('/') +def serve_frontend(): + # Получаем путь к директории, где находится index.py + current_dir = os.path.dirname(os.path.abspath(__file__)) + return static_file('index.html', root=current_dir) + +@route('/api/messages', method='POST') +def post_message(): + ip = request.environ.get('REMOTE_ADDR', '127.0.0.1') + + try: + data = request.json + if not data or 'message' not in data: + response.status = 400 + return {'success': False, 'error': 'No message provided'} + + message = data.get('message', '').strip() + if not message: + response.status = 400 + return {'success': False, 'error': 'Empty message'} + + # Get IP hash for privacy + ip_hash = hashlib.sha256(('salt' + ip).encode('utf-8')).hexdigest()[:16] + + # Save message with encrypted content + message_id = db.add_message(ip_hash, message) + + response.content_type = 'application/json' + return { + 'success': True, + 'message_id': message_id, + 'ip': ip_hash + } + + except Exception as e: + response.status = 500 + return {'success': False, 'error': str(e)} + +@route('/api/messages', method='GET') +def get_messages(): + try: + limit = int(request.query.get('limit', 20)) + messages = db.get_latest_messages(limit) + + result = [] + for msg in messages: + result.append({ + 'id': msg.id, + 'ip': msg.ip, + 'content': msg.content, + 'timestamp': msg.timestamp + }) + + response.content_type = 'application/json' + return {'success': True, 'messages': result} + + except Exception as e: + response.status = 500 + return {'success': False, 'error': str(e)} + +@route('/api/messages/', method='PUT') +def update_message(message_id): + try: + data = request.json + if not data or 'message' not in data: + response.status = 400 + return {'success': False, 'error': 'No message provided'} + + new_content = data.get('message', '').strip() + if not new_content: + response.status = 400 + return {'success': False, 'error': 'Empty message'} + + success = db.update_message(int(message_id), new_content) + + response.content_type = 'application/json' + return {'success': success} + + except Exception as e: + response.status = 500 + return {'success': False, 'error': str(e)} + +@route('/api/messages/', method='DELETE') +def delete_message(message_id): + try: + success = db.delete_message(int(message_id)) + + response.content_type = 'application/json' + return {'success': success} + + except Exception as e: + response.status = 500 + return {'success': False, 'error': str(e)} + +if __name__ == '__main__': + run(host='localhost', port=8080, debug=True) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3351538 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[tool.pyright] +python.pythonPath = "~/myenv/bin/python" \ No newline at end of file