diff --git a/reflex/reflex.py b/reflex/reflex.py index 0730ae441..315df528e 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -460,7 +460,10 @@ def makemigrations( @cli.command() def deploy( 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( config.app_name, @@ -539,6 +542,12 @@ def deploy( # Check if we are set up. prerequisites.check_initialized(frontend=True) 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: # Send a request to server to obtain necessary information # in preparation of a deployment. For example, diff --git a/reflex/utils/hosting.py b/reflex/utils/hosting.py index 1e05ea497..835fe428f 100644 --- a/reflex/utils/hosting.py +++ b/reflex/utils/hosting.py @@ -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]: """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.""" # 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 app_name: str = Field(..., min_length=1) # 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. """ # Check if the user is authenticated - if not (token := requires_authenticated()): + if not (token := requires_access_token()): raise Exception("not authenticated") try: @@ -551,15 +566,15 @@ def fetch_token(request_id: str) -> tuple[str, str]: access_token = (resp_json := resp.json()).get("access_token", "") invitation_code = resp_json.get("code", "") 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: - 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: - 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: - console.error(f"Server response format unexpected: {ke}") + console.debug(f"Server response format unexpected: {ke}") except Exception: - console.error("Unexpected errors: {ex}") + console.debug("Unexpected errors: {ex}") return access_token, invitation_code @@ -902,6 +917,18 @@ def validate_token_with_retries(access_token: str) -> bool: 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( pre_deploy_response: DeploymentPrepareResponse, 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.", 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: pre_deploy_response = prepare_deploy( app_name, diff --git a/tests/utils/test_hosting.py b/tests/utils/test_hosting.py index d1961f8c4..7f6e2fda3 100644 --- a/tests/utils/test_hosting.py +++ b/tests/utils/test_hosting.py @@ -194,7 +194,7 @@ def test_prepare_deploy_success(mocker): def test_deploy(mocker): 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(