[REF-1042] Hosting CLI: check the user selected app name (#2102)

This commit is contained in:
Martin Xu 2023-11-02 14:34:10 -07:00 committed by GitHub
parent 63a3a1baf6
commit 4a526620ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 57 additions and 9 deletions

View File

@ -460,7 +460,10 @@ def makemigrations(
@cli.command() @cli.command()
def deploy( def deploy(
key: Optional[str] = typer.Option( key: Optional[str] = typer.Option(
None, "-k", "--deployment-key", help="The name of the deployment." None,
"-k",
"--deployment-key",
help="The name of the deployment. Domain name safe characters only.",
), ),
app_name: str = typer.Option( app_name: str = typer.Option(
config.app_name, config.app_name,
@ -539,6 +542,12 @@ def deploy(
# Check if we are set up. # Check if we are set up.
prerequisites.check_initialized(frontend=True) prerequisites.check_initialized(frontend=True)
enabled_regions = None enabled_regions = None
# If there is already a key, then it is passed in from CLI option in the non-interactive mode
if key is not None and not hosting.is_valid_deployment_key(key):
console.error(
f"Deployment key {key} is not valid. Please use only domain name safe characters."
)
raise typer.Exit(1)
try: try:
# Send a request to server to obtain necessary information # Send a request to server to obtain necessary information
# in preparation of a deployment. For example, # in preparation of a deployment. For example,

View File

@ -148,6 +148,21 @@ def save_token_to_config(token: str, code: str | None = None):
) )
def requires_access_token() -> str:
"""Fetch the access token from the existing config if applicable.
Returns:
The access token. If not found, return empty string for it instead.
"""
# Check if the user is authenticated
access_token, _ = get_existing_access_token()
if not access_token:
console.debug("No access token found from the existing config.")
return access_token
def authenticated_token() -> tuple[str, str]: def authenticated_token() -> tuple[str, str]:
"""Fetch the access token from the existing config if applicable and validate it. """Fetch the access token from the existing config if applicable and validate it.
@ -339,7 +354,7 @@ class DeploymentsPostParam(Base):
"""Params for hosted instance deployment POST request.""" """Params for hosted instance deployment POST request."""
# Key is the name of the deployment, it becomes part of the URL # Key is the name of the deployment, it becomes part of the URL
key: str = Field(..., regex=r"^[a-zA-Z0-9-]+$") key: str = Field(..., regex=r"^[a-z0-9-]+$")
# Name of the app # Name of the app
app_name: str = Field(..., min_length=1) app_name: str = Field(..., min_length=1)
# json encoded list of regions to deploy to # json encoded list of regions to deploy to
@ -414,7 +429,7 @@ def deploy(
The response containing the URL of the site to be deployed if successful, None otherwise. The response containing the URL of the site to be deployed if successful, None otherwise.
""" """
# Check if the user is authenticated # Check if the user is authenticated
if not (token := requires_authenticated()): if not (token := requires_access_token()):
raise Exception("not authenticated") raise Exception("not authenticated")
try: try:
@ -551,15 +566,15 @@ def fetch_token(request_id: str) -> tuple[str, str]:
access_token = (resp_json := resp.json()).get("access_token", "") access_token = (resp_json := resp.json()).get("access_token", "")
invitation_code = resp_json.get("code", "") invitation_code = resp_json.get("code", "")
except httpx.RequestError as re: except httpx.RequestError as re:
console.error(f"Unable to fetch token due to request error: {re}") console.debug(f"Unable to fetch token due to request error: {re}")
except httpx.HTTPError as he: except httpx.HTTPError as he:
console.error(f"Unable to fetch token due to {he}") console.debug(f"Unable to fetch token due to {he}")
except json.JSONDecodeError as jde: except json.JSONDecodeError as jde:
console.error(f"Server did not respond with valid json: {jde}") console.debug(f"Server did not respond with valid json: {jde}")
except KeyError as ke: except KeyError as ke:
console.error(f"Server response format unexpected: {ke}") console.debug(f"Server response format unexpected: {ke}")
except Exception: except Exception:
console.error("Unexpected errors: {ex}") console.debug("Unexpected errors: {ex}")
return access_token, invitation_code return access_token, invitation_code
@ -902,6 +917,18 @@ def validate_token_with_retries(access_token: str) -> bool:
return False return False
def is_valid_deployment_key(key: str):
"""Helper function to check if the deployment key is valid. Must be a domain name safe string.
Args:
key: The deployment key to check.
Returns:
True if the key contains only domain name safe characters, False otherwise.
"""
return re.match(r"^[a-zA-Z0-9-]*$", key)
def interactive_get_deployment_key_from_user_input( def interactive_get_deployment_key_from_user_input(
pre_deploy_response: DeploymentPrepareResponse, pre_deploy_response: DeploymentPrepareResponse,
app_name: str, app_name: str,
@ -940,6 +967,18 @@ def interactive_get_deployment_key_from_user_input(
f"Choose a name for your deployed app. Enter to use default.", f"Choose a name for your deployed app. Enter to use default.",
default=key_candidate, default=key_candidate,
): ):
if not is_valid_deployment_key(key_input):
console.error(
"Invalid key input, should only contain domain name safe characters: letters, digits, or hyphens."
)
continue
elif any(x.isupper() for x in key_input):
key_input = key_input.lower()
console.info(
f"Domain name is case insensitive, automatically converting to all lower cases: {key_input}"
)
try: try:
pre_deploy_response = prepare_deploy( pre_deploy_response = prepare_deploy(
app_name, app_name,

View File

@ -194,7 +194,7 @@ def test_prepare_deploy_success(mocker):
def test_deploy(mocker): def test_deploy(mocker):
mocker.patch( mocker.patch(
"reflex.utils.hosting.requires_authenticated", return_value="fake_token" "reflex.utils.hosting.requires_access_token", return_value="fake_token"
) )
mocker.patch("builtins.open") mocker.patch("builtins.open")
mocker.patch( mocker.patch(