diff --git a/.gitignore b/.gitignore index 6f23daa..17b1d29 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ wheels/ # Virtual environments .venv +data/*.csv diff --git a/app.py b/app.py index 7dba407..f9ca679 100644 --- a/app.py +++ b/app.py @@ -1,11 +1,15 @@ +import csv import os +import time +from datetime import datetime from typing import Annotated import questionary import typer from rich import print from rich.console import Console -from rich.prompt import Confirm, Prompt +from rich.progress import Progress, SpinnerColumn, TextColumn +from rich.prompt import Prompt from rich.table import Table from libwardenpy.db import get_connection @@ -76,6 +80,44 @@ def create( print("Username exists") +@app.command() +def export( + username: str, + password: Annotated[ + str, + typer.Option( + "--password", "-p", prompt=True, confirmation_prompt=True, hide_input=True + ), + ], +): + global authenticated, auth_data, data + data.username = username + data.master_password = password + auth_data.username = username + key = authenticate_user(get_connection(), data) + if key is not None: + auth_data.key = key + authenticated = True + passwords = list_passwords(get_connection(), auth_data) + if passwords is not None: + with Progress( + SpinnerColumn(), + TextColumn("[progress.description]{task.description}"), + transient=True, + ) as progress: + progress.add_task(description="Writing data to csv...", total=None) + time.sleep(1.5) + with open( + f"data/export-{datetime.today().date()}.csv", "w", newline="" + ) as csvfile: + writer = csv.writer( + csvfile, delimiter=",", quotechar="|", quoting=csv.QUOTE_MINIMAL + ) + for entry in passwords: + writer.writerow(entry) + print("Done!") + + def main() -> None: global authenticated if authenticated: @@ -86,13 +128,14 @@ def main() -> None: \ V V / (_| | | | (_| | __/ | | | __/ | | \_/\_/ \__,_|_| \__,_|\___|_| |_|_| |_| -- created by supun - type .help or ? for help and x or .exit to exit. 1.) Add a Entry [A] 2.) Search Entry [S] 3.) List Entries [L] 4.) Delete Entry [D] + + PRESS X TO QUITE """ print(banner) main_logic() @@ -136,7 +179,7 @@ def main_logic(): table.add_column("Site/Url", style="blue") table.add_column("Password", style="red") for item in passwords: - table.add_row(item[0], item[1].decode("utf-8")) + table.add_row(item[0], item[1]) console = Console() console.print(table) @@ -167,8 +210,9 @@ def main_logic(): qmark="> ", pointer=">", ).ask() - delete_the_entry: bool = Confirm.ask( - f"Are You sure You want to delete [bold red]{id}[/bold red]?" + delete_the_entry = Prompt.ask( + f"Are You sure You want to delete [bold red]{id}[/bold red] {'[y/N]'} ?", + default=False, ) if delete_the_entry: delete_passwod(get_connection(), auth_data, id) diff --git a/libwardenpy/configuration.py b/libwardenpy/configuration.py new file mode 100644 index 0000000..dcf0039 --- /dev/null +++ b/libwardenpy/configuration.py @@ -0,0 +1,7 @@ +from dataclasses import dataclass + + +@dataclass +class AppConfig: + EXPORT_DIRECTORY: str = "data/" + PASSWORD_LENGTH: int = 20 diff --git a/libwardenpy/funtionality.py b/libwardenpy/funtionality.py index 7173870..486101e 100644 --- a/libwardenpy/funtionality.py +++ b/libwardenpy/funtionality.py @@ -1,7 +1,7 @@ import secrets import sqlite3 from dataclasses import dataclass -from typing import List +from typing import Optional import argon2 from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 @@ -21,9 +21,9 @@ def __init__(self, username: str, master_password: str) -> None: @dataclass class AuthenticatedData: username: str - key: bytes | None + key: Optional[bytes] - def __init__(self, username: str, key: bytes | None) -> None: + def __init__(self, username: str, key: Optional[bytes]) -> None: self.username = username self.key = key @@ -66,7 +66,7 @@ def register_user(connection, data: UnAuthData) -> bool: ### verify the password hash matches given password and if matches create a new ### key for to encrypt the child items ( passwords, sites under given user) else ### if given user not found exit the program and if passwod doesnot match give a err -def authenticate_user(connection, data: UnAuthData) -> bytes | None: +def authenticate_user(connection, data: UnAuthData) -> Optional[bytes]: with connection as conn: cursor = conn.execute( "SELECT password_hash, salt FROM users WHERE username = ?", @@ -128,7 +128,7 @@ def add_password(connection, data: AuthenticatedData, entry: Entry) -> None: ### get the password for the site from sqlite database and decrept them ### and show them -def get_password(connection, data: AuthenticatedData, site: str) -> List | None: +def get_password(connection, data: AuthenticatedData, site: str) -> Optional[list]: if not data.key: return @@ -154,7 +154,7 @@ def get_password(connection, data: AuthenticatedData, site: str) -> List | None: ### get the passwords of all the sites from sqlite database and decrept them ### and show them -def list_passwords(connection, data: AuthenticatedData) -> List | None: +def list_passwords(connection, data: AuthenticatedData) -> Optional[list]: if not data.key: return @@ -171,7 +171,9 @@ def list_passwords(connection, data: AuthenticatedData) -> List | None: return [ ( site, - ChaCha20Poly1305(data.key).decrypt(nonce, encrypted_password, None), + ChaCha20Poly1305(data.key) + .decrypt(nonce, encrypted_password, None) + .decode("utf-8"), ) for site, encrypted_password, nonce in result ] diff --git a/libwardenpy/passgen.py b/libwardenpy/passgen.py index 881915c..3e11313 100644 --- a/libwardenpy/passgen.py +++ b/libwardenpy/passgen.py @@ -1,12 +1,16 @@ import secrets import string +from libwardenpy.configuration import AppConfig + +config = AppConfig() + ### this create a password with default length 14 and has digis uppercase and lowecase letters -def generate_password(lenght: int = 14) -> str: +def generate_password(lenght: int = config.PASSWORD_LENGTH) -> str: alphabet = string.ascii_letters + string.digits while True: - password = "".join(secrets.choice(alphabet) for _i in range(lenght)) + password = "".join(secrets.choice(alphabet) for _ in range(lenght)) if ( any(c.islower() for c in password) and any(c.isupper() for c in password) diff --git a/tests/test_functionality.py b/tests/test_functionality.py index 5a88463..b7973ac 100644 --- a/tests/test_functionality.py +++ b/tests/test_functionality.py @@ -139,8 +139,8 @@ def test_list_passwords(): passwords = list_passwords(conn, auth_data) assert passwords is not None assert len(passwords) == 2 - assert any(p[0] == "site1.com" and p[1].decode() == "pass1" for p in passwords) - assert any(p[0] == "site2.com" and p[1].decode() == "pass2" for p in passwords) + assert any(p[0] == "site1.com" and p[1] == "pass1" for p in passwords) + assert any(p[0] == "site2.com" and p[1] == "pass2" for p in passwords) def test_delete_password(): diff --git a/tests/test_password_generation.py b/tests/test_password_generation.py index 0e9f2d8..f5cd311 100644 --- a/tests/test_password_generation.py +++ b/tests/test_password_generation.py @@ -1,10 +1,11 @@ import random + from libwardenpy.passgen import generate_password def test_default_password_generation() -> None: default_password = generate_password() - assert len(default_password) == 14 + assert len(default_password) == 20 def test_password_generation_with_length() -> None: