Skip to content

Commit a5bfc50

Browse files
committed
Split auth into multiple subparsers
1 parent 625e285 commit a5bfc50

7 files changed

Lines changed: 301 additions & 46 deletions

File tree

ethpm_cli/_utils/input.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from ethpm_cli._utils.logger import cli_logger
2+
3+
4+
def parse_bool_flag(question: str) -> bool:
5+
while True:
6+
response = input(f"{question} (y/n) ")
7+
if response.lower() == "y":
8+
return True
9+
elif response.lower() == "n":
10+
return False
11+
else:
12+
cli_logger.info(f"Invalid response: {response}.")
13+
continue

ethpm_cli/commands/auth.py

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,104 @@
1+
import json
12
from pathlib import Path
2-
import tempfile
33
from typing import Any, Dict
44

55
import eth_keyfile
66
from eth_typing import Address
7-
from eth_utils import to_bytes
7+
from eth_utils import add_0x_prefix, to_bytes
88

9+
from ethpm_cli._utils.filesystem import atomic_replace
10+
from ethpm_cli._utils.input import parse_bool_flag
11+
from ethpm_cli._utils.logger import cli_logger
912
from ethpm_cli._utils.xdg import get_xdg_ethpmcli_root
1013
from ethpm_cli.constants import KEYFILE_PATH
1114
from ethpm_cli.exceptions import AuthorizationError
1215

16+
PRIVATE_KEY_WARNING = (
17+
"Please be careful when using your private key, it is a sensitive piece of information.\n",
18+
"~ ~ ~ Not your keys, not your crypto. ~ ~ ~\n",
19+
"ethPM doesn't save your private key, but it is best practice to re-use an already encrypted ",
20+
"keyfile and link it with `ethpm auth link` rather than regenerating a new encrypted keyfile.",
21+
)
22+
23+
24+
def link_keyfile(keyfile_path: Path) -> None:
25+
if valid_keyfile_exists():
26+
xdg_keyfile = get_keyfile_path()
27+
cli_logger.info(
28+
f"Keyfile detected at {xdg_keyfile}. Please use `ethpm auth unlink` to delete this "
29+
"keyfile before linking a new one."
30+
)
31+
else:
32+
import_keyfile(keyfile_path)
33+
address = get_authorized_address()
34+
cli_logger.info(
35+
f"Keyfile stored for address: {address}\n"
36+
"It's now available for use when its password is passed in with the "
37+
"`--keyfile-password` flag."
38+
)
39+
40+
41+
def unlink_keyfile() -> None:
42+
if not valid_keyfile_exists():
43+
cli_logger.info("Unable to unlink keyfile: empty keyfile found.")
44+
else:
45+
keyfile_path = get_keyfile_path()
46+
address = get_authorized_address()
47+
keyfile_path.write_text("")
48+
cli_logger.info(f"Keyfile removed for address: {address}")
49+
50+
51+
def init_keyfile() -> None:
52+
if valid_keyfile_exists():
53+
cli_logger.info(
54+
f"Keyfile detected. Please use `ethpm auth unlink` to delete this "
55+
"keyfile before initializing a new one."
56+
)
57+
return
58+
59+
cli_logger.info(PRIVATE_KEY_WARNING)
60+
agreement = parse_bool_flag(
61+
"Are you sure you want to proceed with initializing a keyfile? "
62+
)
63+
if not agreement:
64+
cli_logger.info("Aborting keyfile initialization.")
65+
return
66+
67+
private_key = to_bytes(text=input("Please enter your 32-length private key: "))
68+
validate_private_key_length(private_key)
69+
password = to_bytes(
70+
text=input(
71+
"Please enter a password to encrypt your keyfile with (Don't forget this password!): "
72+
)
73+
)
74+
keyfile_json = eth_keyfile.create_keyfile_json(private_key, password)
75+
ethpm_xdg_root = get_xdg_ethpmcli_root()
76+
ethpm_cli_keyfile_path = ethpm_xdg_root / KEYFILE_PATH
77+
with atomic_replace(ethpm_cli_keyfile_path) as file:
78+
file.write(json.dumps(keyfile_json))
79+
address = get_authorized_address()
80+
cli_logger.info(f"Encrypted keyfile saved for address: {address}")
81+
82+
83+
def valid_keyfile_exists() -> bool:
84+
try:
85+
get_authorized_address()
86+
except AuthorizationError:
87+
return False
88+
return True
89+
90+
91+
def validate_private_key_length(private_key: str) -> None:
92+
if len(private_key) != 32:
93+
raise AuthorizationError(f"{private_key} is not 32 long")
94+
1395

1496
def import_keyfile(keyfile_path: Path) -> None:
1597
validate_keyfile(keyfile_path)
1698
ethpm_xdg_root = get_xdg_ethpmcli_root()
1799
ethpm_cli_keyfile_path = ethpm_xdg_root / KEYFILE_PATH
18-
tmp_keyfile = Path(tempfile.NamedTemporaryFile().name)
19-
tmp_keyfile.write_text(keyfile_path.read_text())
20-
tmp_keyfile.replace(ethpm_cli_keyfile_path)
100+
with atomic_replace(ethpm_cli_keyfile_path) as file:
101+
file.write(keyfile_path.read_text())
21102

22103

23104
def get_keyfile_path() -> Path:
@@ -49,7 +130,7 @@ def get_authorized_address() -> Address:
49130
Returns the address associated with stored keyfile. No password required.
50131
"""
51132
keyfile = get_keyfile_data()
52-
return keyfile["address"]
133+
return add_0x_prefix(keyfile["address"])
53134

54135

55136
def get_authorized_private_key(password: str) -> str:

ethpm_cli/commands/manifest.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from ethpm.validation.package import validate_package_name
1212
from web3 import Web3
1313

14+
from ethpm_cli._utils.input import parse_bool_flag
1415
from ethpm_cli._utils.logger import cli_logger
1516
from ethpm_cli._utils.shellart import bold_blue
1617
from ethpm_cli._utils.solc import (
@@ -450,18 +451,6 @@ def gen_links() -> Optional[Callable[..., Manifest]]:
450451
return None
451452

452453

453-
def parse_bool_flag(question: str) -> bool:
454-
while True:
455-
response = input(f"{question} (y/n) ")
456-
if response.lower() == "y":
457-
return True
458-
elif response.lower() == "n":
459-
return False
460-
else:
461-
cli_logger.info(f"Invalid response: {response}.")
462-
continue
463-
464-
465454
def write_manifest_to_disk(manifest: Manifest, project_dir: Path) -> None:
466455
while True:
467456
filename = input("Please enter a filename for your manifest. ")

ethpm_cli/commands/package.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def resolve_install_uri(args: Namespace) -> ResolvedInstallURI:
9393
)
9494
registry_address = None
9595
elif registry_backend.can_translate_uri(args.uri):
96-
registry_address, _, _, _, _ = parse_registry_uri(args.uri)
96+
registry_address, _, _, _, _, _ = parse_registry_uri(args.uri)
9797
manifest_uri = registry_backend.fetch_uri_contents(args.uri)
9898
else:
9999
manifest_uri = args.uri

ethpm_cli/config.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from ethpm_cli._utils.ipfs import get_ipfs_backend
1717
from ethpm_cli._utils.logger import cli_logger
1818
from ethpm_cli._utils.xdg import get_xdg_ethpmcli_root
19-
from ethpm_cli.commands.auth import get_authorized_private_key, import_keyfile
19+
from ethpm_cli.commands.auth import get_authorized_private_key
2020
from ethpm_cli.constants import (
2121
ETHPM_DIR_ENV_VAR,
2222
ETHPM_PACKAGES_DIR,
@@ -63,16 +63,14 @@ def __init__(self, args: Namespace) -> None:
6363
else:
6464
chain_id = 1
6565

66-
if "keyfile_path" in args and args.keyfile_path:
67-
import_keyfile(args.keyfile_path)
68-
66+
# setup auth
6967
if "keyfile_password" in args and args.keyfile_password:
7068
self.private_key = get_authorized_private_key(args.keyfile_password)
7169
self.w3 = setup_w3(chain_id, self.private_key)
7270

7371
# Setup xdg ethpm dir
7472
self.xdg_ethpmcli_root = get_xdg_ethpmcli_root()
75-
setup_xdg_ethpm_dir(self.xdg_ethpmcli_root, self.w3)
73+
validate_xdg_ethpm_dir(self.xdg_ethpmcli_root, self.w3)
7674

7775
# Setup projects dir
7876
if "project_dir" in args and args.project_dir:
@@ -114,7 +112,7 @@ def setup_w3(chain_id: int, private_key: str = None) -> Web3:
114112
return w3
115113

116114

117-
def setup_xdg_ethpm_dir(xdg_ethpmcli_root: Path, w3: Web3) -> None:
115+
def validate_xdg_ethpm_dir(xdg_ethpmcli_root: Path, w3: Web3) -> None:
118116
if not xdg_ethpmcli_root.is_dir():
119117
initialize_xdg_ethpm_dir(xdg_ethpmcli_root, w3)
120118

0 commit comments

Comments
 (0)