Skip to content

Commit

Permalink
Typerandrich (#3)
Browse files Browse the repository at this point in the history
* remove manual implementations and remove migration and add it in to db

* add migrations here

* refactor some funtions and add rich and typer

* add some prompts

* add exprot funtion and add config currently do nothing
  • Loading branch information
deepattic authored Dec 1, 2024
1 parent 109e17e commit 28355be
Show file tree
Hide file tree
Showing 14 changed files with 408 additions and 166 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ wheels/

# Virtual environments
.venv
data/*.csv
236 changes: 143 additions & 93 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import argparse
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.progress import Progress, SpinnerColumn, TextColumn
from rich.prompt import Prompt
from rich.table import Table

from libwardenpy.colors import colored_string, print_colored
from libwardenpy.db import get_connection
from libwardenpy.funtionality import (
AuthenticatedData,
Expand All @@ -14,55 +24,102 @@
list_passwords,
register_user,
)
from libwardenpy.migrations import migrate_DB
from libwardenpy.passgen import generate_password

authenticated = False

data: UnAuthData = UnAuthData("", "")
auth_data: AuthenticatedData = AuthenticatedData("", b"")

app = typer.Typer()


@app.command()
def login(
username: Annotated[
str,
typer.Option("--username", "-u"),
],
password: Annotated[
str,
typer.Option("--password", "-p", 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
main()


def init_store(args) -> None:
@app.command()
def create(
username: str,
password: Annotated[
str,
typer.Option(
"--password", "-p", prompt=True, confirmation_prompt=True, hide_input=True
),
],
):
global data
migrate_DB()
USER_NAME_EXIST = False
data.username, data.master_password = username, password
with get_connection() as conn:
cursor = conn.execute("SELECT username FROM users;")
data.username = args.username
if (data.username,) in cursor.fetchall():
USER_NAME_EXIST = True
if not USER_NAME_EXIST:
tips_text1 = (
"create a strong and memorable password\n"
"guide: https://anonymousplanet.org/guide.html#appendix-a2-guidelines-for-passwords-and-passphrases\n"
cursor = conn.execute(
"SELECT COUNT(*) FROM users WHERE username = ?", (username,)
)
print_colored(tips_text1, "red")
data.master_password = input("Create Strong and Memorable Master Password: ")
register_user(get_connection(), data)
else:
print("Username exit")
exit()

if cursor.fetchone()[0] == 0:
if register_user(get_connection(), data):
print(f"User {username} registered successfully!")
else:
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, auth_data, data, pool
args = parse_arguments()
data.username = args.username
data.master_password = args.password
if args.password is not None and args.username is not None:
key = authenticate_user(get_connection(), data)
if key is not None:
auth_data.key = key
authenticated = True
elif args.username is not None and args.password is None:
data.master_password = input("Enter Master Password: ")
key = authenticate_user(get_connection(), data)
if key is not None:
authenticated = True
auth_data.key = key

auth_data.username = data.username
global authenticated
if authenticated:
banner = r"""
__ __ _ ______ __
Expand All @@ -71,106 +128,99 @@ 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()


def main_logic():
global auth_data
print(auth_data.key)
while True:
user_input = input("> ")
if user_input.upper() == ".CLEAR":
user_input = Prompt.ask("[bold green]> [/bold green]").upper()

if user_input == ".CLEAR":
os.system("clear")
if user_input == "?" or user_input.upper() == ".HELP":

if user_input in ("?", ".HELP"):
print(help_msg)
if (
user_input == "1"
or user_input.upper() == "A"
or user_input.upper() == ".ADD"
):

if user_input in ("1", "A", ".ADD"):
while True:
site = input(".add website_url > ").strip()
if not site:
print(colored_string("You Must Add a Website .^.", "RED"))
print("[bold red]You Must Add a Website .^.[/bold red]")
else:
break
site_pass = input(".add password (leave this blank for random password) > ")
if not site_pass:
site_pass = generate_password()
entry: Entry = Entry(site, site_pass)
add_password(get_connection(), auth_data, entry)
if (
user_input == "2"
or user_input.upper() == "S"
or user_input.upper() == ".SEARCH"
):

if user_input in ("2", "S", ".SEARCH"):
site = input(".search > ")
a = get_password(get_connection(), auth_data, site)
print(a)

if (
user_input == "3"
or user_input.upper() == "L"
or user_input.upper() == ".LIST"
):
if user_input in ("3", "L", ".LIST"):
passwords = list_passwords(get_connection(), auth_data)
if passwords is None:
print(f"No passwords for user {auth_data.username}")
print(passwords)
if user_input.upper() == "D" or user_input.upper() == ".DEL":
else:
table = Table(title="List of Passwords")
table.add_column("Site/Url", style="blue")
table.add_column("Password", style="red")
for item in passwords:
table.add_row(item[0], item[1])
console = Console()
console.print(table)

if user_input in ("D", "4", ".DEL"):
while True:
site = input(".search entry > ").strip()
if not site:
print(colored_string("You Must Add a Website .^.", "RED"))
print("[bold red]You Must Add a Website .^.[bold red]")
else:
break
list_of_entries = get_password(get_connection(), auth_data, site)

if list_of_entries is None:
print(f"no entries have {site}")
if list_of_entries is not None:
print(list_of_entries)
id = input("Give the id of the entry you want to delete > ")
id = str(id).strip()
delete_passwod(get_connection(), auth_data, id)
if user_input.upper() == "X" or user_input.upper() == ".EXIT":
table = Table(title="List of Entries")
table.add_column("id", style="bold blue")
table.add_column("Site/Url", style="green")
table.add_column("Password", style="red")
for item in list_of_entries:
table.add_row(str(item[0]), item[1], item[2].decode("utf-8"))
console = Console()
console.print(table)
choices = [str(id[0]) for id in list_of_entries]
id = questionary.select(
"Which entry do you want to delet?",
choices=choices,
qmark="> ",
pointer=">",
).ask()
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)

if user_input in ("X", ".EXIT"):
break


def parse_arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
subparser = parser.add_subparsers(
title="Commands",
)
parser.add_argument("-u", "--username", help="use the username given here")
parser.add_argument("-p", "--password", help="use the password given here")
parser.add_argument("-a", "--add", help="add password")

init_parser = subparser.add_parser(
"init", aliases="i", help="Inizialize password repo"
)
init_parser.add_argument(
"username", help="username for initialize the password store"
)
init_parser.set_defaults(func=init_store)
subparser.add_parser("new", help="Inizialize new password repo").set_defaults(
func=init_store
)

args = parser.parse_args()
if hasattr(args, "func"):
args.func(args)
return args


help_msg = """
.help, ? Show this menu
.clear Clear the screen
Expand All @@ -183,4 +233,4 @@ def parse_arguments() -> argparse.Namespace:


if __name__ == "__main__":
main()
app()
25 changes: 0 additions & 25 deletions libwardenpy/colors.py

This file was deleted.

7 changes: 7 additions & 0 deletions libwardenpy/configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from dataclasses import dataclass


@dataclass
class AppConfig:
EXPORT_DIRECTORY: str = "data/"
PASSWORD_LENGTH: int = 20
22 changes: 22 additions & 0 deletions libwardenpy/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,33 @@
def get_connection(DB_PATH="data/db.sqlite3"):
conn = sqlite3.connect(DB_PATH)
try:
conn.execute(
"""
CREATE TABLE IF NOT EXISTS users (
username TEXT PRIMARY KEY,
password_hash TEXT NOT NULL,
salt BLOB NOT NULL)
"""
)
conn.execute(
"""
CREATE TABLE IF NOT EXISTS passwords (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL,
site TEXT NOT NULL,
encrypted_password BLOB NOT NULL,
nonce BLOB NOT NULL,
FOREIGN KEY (username) REFERENCES users(username))
"""
)
yield conn

except Exception as e:
conn.rollback()
raise e

else:
conn.commit()

finally:
conn.close()
Loading

0 comments on commit 28355be

Please sign in to comment.