import io import string import time from random import SystemRandom from typing import Self, Type import pydantic import telebot from sqlalchemy.future import Engine from .. import cryptography, database FORBIDDEN_CHARS = frozenset("`\n") PUNCTUATION = frozenset(string.punctuation).difference(FORBIDDEN_CHARS) PASSWORD_CHARS = tuple( frozenset(string.ascii_letters + string.digits).difference(FORBIDDEN_CHARS) | PUNCTUATION ) Message = telebot.types.Message class _Account(pydantic.BaseModel): name: str login: str passwd: str @classmethod def from_tuple(cls: Type[Self], tuple_: tuple[str, str, str]) -> Self: return cls(name=tuple_[0], login=tuple_[1], passwd=tuple_[2]) def as_tuple(self: Self) -> tuple[str, str, str]: return (self.name, self.login, self.passwd) class _Accounts(pydantic.BaseModel): accounts: list[_Account] = pydantic.Field(default_factory=list) def _accounts_list_to_json(accounts: list[tuple[str, str, str]]) -> str: accounts = _Accounts(accounts=[_Account.from_tuple(i) for i in accounts]) return accounts.json() def json_to_accounts(json_: str) -> list[tuple[str, str, str]]: accounts = _Accounts.parse_raw(json_) return [i.as_tuple() for i in accounts.accounts] def send_tmp_message( bot: telebot.TeleBot, chat_id: telebot.types.Message, text: str, timeout: int = 5 ) -> None: bot_mes = bot.send_message(chat_id, text, parse_mode="MarkdownV2") time.sleep(timeout) bot.delete_message(chat_id, bot_mes.id) def base_handler( bot: telebot.TeleBot, mes: Message, prev_mes: Message | None = None ) -> None: bot.delete_message(mes.chat.id, mes.id) if prev_mes is not None: bot.delete_message(prev_mes.chat.id, prev_mes.id) def get_all_accounts( engine: Engine, user_id: int, master_pass: str ) -> list[tuple[str, str, str]]: accounts: list[tuple[str, str, str]] = [] for account_name in database.get.get_accounts(engine, user_id): salt, enc_login, enc_passwd = database.get.get_account_info( engine, user_id, account_name ) login, passwd = cryptography.other_accounts.decrypt_account_info( enc_login, enc_passwd, master_pass, salt ) accounts.append((account_name, login, passwd)) return accounts def accounts_to_json(accounts: list[tuple[str, str, str]]) -> io.StringIO: file = io.StringIO(_accounts_list_to_json(accounts)) file.name = "passwords.json" return file def _base_check(val: str, /) -> bool: "Returns false if finds new lines or backtick (`)" return not any(i in FORBIDDEN_CHARS for i in val) def check_account_name(name: str) -> bool: "Returns true if account name is valid" return _base_check(name) def check_login(login: str) -> bool: "Returns true if login is valid" return _base_check(login) def check_passwd(passwd: str) -> bool: "Returns true if password is valid" return _base_check(passwd) def check_account(name: str, login: str, passwd: str) -> bool: """Runs checks for account name, login and password""" return check_account_name(name) and check_login(login) and check_passwd(passwd) def _check_gened_password(passwd: str, /) -> bool: """Retuns true if generated password is valid, false otherwise. Password is valid if there is at least one lowercase character, uppercase character and one punctuation character""" return ( any(c.islower() for c in passwd) and any(c.isupper() for c in passwd) and any(c.isdigit() for c in passwd) and any(c in PUNCTUATION for c in passwd) ) def gen_passwd() -> str: """Generates password of length 32""" choices = SystemRandom().choices while True: passwd = "".join(choices(PASSWORD_CHARS, k=32)) if _check_gened_password(passwd): return passwd