From f988e82dfbc0301cb757afb21b69c15a3cba0384 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 31 Jan 2025 19:12:29 -0600 Subject: [PATCH 1/5] feat: new signup via email --- gigalixir/__init__.py | 47 +++++------- gigalixir/signup.py | 162 ++++++++++++++++++++++++++++++++++++++++++ gigalixir/user.py | 11 --- setup.py | 2 +- 4 files changed, 179 insertions(+), 43 deletions(-) create mode 100644 gigalixir/signup.py diff --git a/gigalixir/__init__.py b/gigalixir/__init__.py index 058398e..614e4ab 100644 --- a/gigalixir/__init__.py +++ b/gigalixir/__init__.py @@ -5,26 +5,27 @@ from .openers.linux import LinuxOpener from .openers.darwin import DarwinOpener from .openers.windows import WindowsOpener -from . import observer as gigalixir_observer -from . import user as gigalixir_user -from . import mfa as gigalixir_mfa +from . import api_key as gigalixir_api_key +from . import api_session as gigalixir_api_session +from . import app_activity as gigalixir_app_activity from . import app as gigalixir_app +from . import canary as gigalixir_canary from . import config as gigalixir_config +from . import database as gigalixir_database +from . import domain as gigalixir_domain +from . import free_database as gigalixir_free_database +from . import invoice as gigalixir_invoice +from . import log_drain as gigalixir_log_drain +from . import mfa as gigalixir_mfa +from . import observer as gigalixir_observer +from . import payment_method as gigalixir_payment_method from . import permission as gigalixir_permission from . import release as gigalixir_release -from . import app_activity as gigalixir_app_activity -from . import api_key as gigalixir_api_key +from . import signup as gigalixir_signup from . import ssh_key as gigalixir_ssh_key -from . import log_drain as gigalixir_log_drain -from . import payment_method as gigalixir_payment_method -from . import domain as gigalixir_domain -from . import invoice as gigalixir_invoice from . import usage as gigalixir_usage -from . import database as gigalixir_database -from . import free_database as gigalixir_free_database -from . import canary as gigalixir_canary +from . import user as gigalixir_user from . import git -from . import api_session as gigalixir_api_session import click import getpass import stripe @@ -1063,30 +1064,14 @@ def current_running_usage(ctx): # @create.command() @cli.command() -@click.option('--email') -@click.option('-p', '--password') -@click.option('-y', '--accept_terms_of_service_and_privacy_policy', is_flag=True) @click.pass_context @report_errors -def signup(ctx, email, password, accept_terms_of_service_and_privacy_policy): +def signup(ctx): """ Sign up for a new account. """ - if not accept_terms_of_service_and_privacy_policy: - logging.getLogger("gigalixir-cli").info("GIGALIXIR Terms of Service: https://www.gigalixir.com/terms") - logging.getLogger("gigalixir-cli").info("GIGALIXIR Privacy Policy: https://www.gigalixir.com/privacy") - if not click.confirm('Do you accept the Terms of Service and Privacy Policy?'): - raise Exception("You must accept the Terms of Service and Privacy Policy to continue.") - - if email == None: - email = click.prompt('Email') - gigalixir_user.validate_email(ctx.obj['session'], email) - - if password == None: - password = click.prompt('Password', hide_input=True) - gigalixir_user.validate_password(ctx.obj['session'], password) + gigalixir_signup.by_email(ctx) - gigalixir_user.create(ctx.obj['session'], email, password, accept_terms_of_service_and_privacy_policy) @cli.command('signup:google') @click.option('-y', '--accept_terms_of_service_and_privacy_policy', is_flag=True) diff --git a/gigalixir/signup.py b/gigalixir/signup.py new file mode 100644 index 0000000..3627d79 --- /dev/null +++ b/gigalixir/signup.py @@ -0,0 +1,162 @@ +import click +import datetime +import re +import stripe + +def by_email(ctx): + session = ctx.obj['session'] + + print("Welcome, let's get you started!") + print("") + print("We require a *valid* email address to be on file in case we need to reach you about your applications.") + print("We promise to not abuse your email address.") + print("Don't believe us? Read our privacy policy: https://gigalixir.com/privacy-policy") + print("") + email = click.prompt('Email') + uuid = set_email(session, email) + + print("") + print("Please check your email for the confirmation code we have sent you.") + accepted = False + while not accepted: + code = click.prompt('Confirmation code') + accepted = confirm(session, uuid, code) + print("Thank you for your trust in us.") + print("") + + print("Now we need a password.") + accepted = False + while not accepted: + password = click.prompt('Password', hide_input=True) + accepted = set_password(session, uuid, password) + print("") + + print("Which tier would you like to sign up for?") + print("1. Free") + print("2. Standard") + tier = None + while not tier: + value = click.prompt('Tier', type=int) + if value == 1: + tier = "FREE" + elif value == 2: + tier = "STANDARD" + print("") + + if tier == "STANDARD": + accepted = False + while not accepted: + card_number = click.prompt("Enter credit card number", type=str, value_proc=validate_credit_card_number) + cvc = click.prompt("Enter CVV", type=str, value_proc=validate_cvv) + exp_month = click.prompt("Enter expiration month (MM)", type=str, value_proc=validate_exp_month) + exp_year = click.prompt("Enter expiration year (YYYY)", type=str, value_proc=validate_exp_year) + + stripe_token = stripe.Token.create(card={ "number": card_number, "exp_month": exp_month, "exp_year": exp_year, "cvc": cvc }) + + accepted = set_cc(session, uuid, stripe_token["id"]) + print("") + + print("You are about to signup for the %s tier." % tier) + print("By continuing, you agree to our Terms of Service and Privacy Policy.") + print(" Privacy Policy: https://gigalixir.com/privacy-policy") + print(" Terms of Service: https://gigalixir.com/terms-of-service") + if not click.confirm('Do you wish to proceed?'): + raise Exception("Signup cancelled.") + finalize(session, uuid, tier) + + print("Welcome to Gigalixir!") + print("Please sign in with 'gigalixir login' to get started.") + +def set_email(session, email): + r = session.post('/api/signup', json = { 'email': email }) + if r.status_code == 429: + raise Exception('Too many attempts. Please try again later.') + if r.status_code != 200: + raise Exception(r.text) + + return r.json()['data']['uuid'] + +def confirm(session, uuid, code): + r = session.post('/api/signup', json = { 'confirmation_code': code, 'uuid': uuid }) + if r.status_code == 429: + raise Exception('Too many attempts. Please try again later.') + + return r.status_code == 200 + +def set_password(session, uuid, password): + r = session.post('/api/signup', json = { 'password': password, 'uuid': uuid }) + if r.status_code != 200: + print(r.text) + return False + + return True + +def set_cc(session, uuid, stripe_token): + r = session.post('/api/signup', json = { 'stripe_token': stripe_token, 'uuid': uuid }) + if r.status_code == 429: + raise Exception('Too many attempts. Please try again later.') + + if r.status_code != 200: + print(r.text) + return False + + return True + +def finalize(session, uuid, tier): + r = session.post('/api/signup', json = { 'tier': tier, 'uuid': uuid }) + if r.status_code != 200: + raise Exception(r.text) + + + +## input validations +def luhn_check(card_number): + """Validate credit card number using Luhn algorithm.""" + digits = [int(d) for d in card_number] + checksum = 0 + + # Double every second digit from the right, subtracting 9 if >9 + for i, digit in enumerate(reversed(digits)): + if i % 2 == 1: + digit *= 2 + if digit > 9: + digit -= 9 + checksum += digit + + return checksum % 10 == 0 + +def validate_credit_card_number(card_number): + """Ensure card number is valid using regex and Luhn check.""" + card_number = card_number.replace(" ", "") # Allow spaces for readability + if not re.fullmatch(r"\d{13,19}", card_number): + raise click.BadParameter("Credit card number must be 13-19 digits long.") + if not luhn_check(card_number): + raise click.BadParameter("Invalid credit card number (failed Luhn check).") + return card_number + +def validate_cvv(cvv): + """Ensure CVV is numeric and correct length.""" + if not re.fullmatch(r"\d{3,4}", cvv): + raise click.BadParameter("CVV must be 3 or 4 digits.") + return cvv + +def validate_exp_month(exp_month): + """Ensure expiration month is valid (1-12).""" + try: + month = int(exp_month) + if 1 <= month <= 12: + return f"{month:02d}" # Ensure two-digit format + except ValueError: + pass + raise click.BadParameter("Expiration month must be a number between 1 and 12.") + +def validate_exp_year(exp_year): + """Ensure expiration year is in the future and valid.""" + current_year = datetime.datetime.now().year + try: + year = int(exp_year) + if current_year <= year <= current_year + 20: # Prevent unrealistic years + return str(year) + except ValueError: + pass + raise click.BadParameter(f"Expiration year must be {current_year} or later.") diff --git a/gigalixir/user.py b/gigalixir/user.py index 38d07d0..e99333f 100644 --- a/gigalixir/user.py +++ b/gigalixir/user.py @@ -59,17 +59,6 @@ def delete(session, email, password): raise Exception(r.text) logging.getLogger("gigalixir-cli").info('Account destroyed.') -def validate_email(session, email): - r = session.get('/api/validate_email', params = { - "email": email - }) - if r.status_code != 200: - raise Exception(r.text) - -def validate_password(session, password): - if len(password) < 4: - raise Exception("Password should be at least 4 characters.") - def logout(env): netrc.clear_netrc(env) diff --git a/setup.py b/setup.py index 53e42cd..c8aaf1c 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ url='https://github.com/gigalixir/gigalixir-cli', author='Tim Knight', author_email='tim@gigalixir.com', - version='1.13.1', + version='1.14.0', packages=find_packages(), include_package_data=True, install_requires=[ From 2efaa29b18c6d340bbd87b4d80844dedecedabf0 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 17 Feb 2025 06:57:38 -0600 Subject: [PATCH 2/5] feat: new signup via oauth --- gigalixir/__init__.py | 24 ++--- gigalixir/signup.py | 215 +++++++++++++++++++++++++++++------------- gigalixir/user.py | 47 ++++----- 3 files changed, 170 insertions(+), 116 deletions(-) diff --git a/gigalixir/__init__.py b/gigalixir/__init__.py index 614e4ab..724d0a9 100644 --- a/gigalixir/__init__.py +++ b/gigalixir/__init__.py @@ -564,24 +564,22 @@ def logout(ctx): @click.option('-e', '--email', prompt=True) @click.option('-p', '--password', prompt=True, hide_input=True, confirmation_prompt=False) @click.option('-t', '--mfa_token', prompt=False) # we handle prompting if needed, not always needed -@click.option('-y', '--yes', is_flag=True) @click.pass_context @report_errors -def login(ctx, email, password, yes, mfa_token): +def login(ctx, email, password, mfa_token): """ Login and receive an api key. """ - gigalixir_user.login(ctx.obj['session'], email, password, yes, ctx.obj['env'], mfa_token) + gigalixir_user.login(ctx.obj['session'], email, password, ctx.obj['env'], mfa_token) @cli.command(name='login:google') -@click.option('-y', '--yes', is_flag=True) @click.pass_context @report_errors -def google_login(ctx, yes): +def google_login(ctx): """ Login with Google and receive an api key. """ - gigalixir_user.oauth_login(ctx.obj['session'], yes, ctx.obj['env'], 'google') + gigalixir_user.oauth_login(ctx.obj['session'], ctx.obj['env'], 'google') # @get.command() @cli.command() @@ -1074,20 +1072,10 @@ def signup(ctx): @cli.command('signup:google') -@click.option('-y', '--accept_terms_of_service_and_privacy_policy', is_flag=True) @click.pass_context @report_errors -def google_signup(ctx, accept_terms_of_service_and_privacy_policy): - """ - Sign up for a new account using your google login. - """ - if not accept_terms_of_service_and_privacy_policy: - logging.getLogger("gigalixir-cli").info("GIGALIXIR Terms of Service: https://www.gigalixir.com/terms") - logging.getLogger("gigalixir-cli").info("GIGALIXIR Privacy Policy: https://www.gigalixir.com/privacy") - if not click.confirm('Do you accept the Terms of Service and Privacy Policy?'): - raise Exception("You must accept the Terms of Service and Privacy Policy to continue.") - - gigalixir_user.oauth_create(ctx.obj['session'], ctx.obj['env'], "google") +def google_signup(ctx): + gigalixir_signup.by_oauth(ctx, "google") @cli.command(name='ps:observer') @click.option('-a', '--app_name', envvar="GIGALIXIR_APP") diff --git a/gigalixir/signup.py b/gigalixir/signup.py index 3627d79..c2b440b 100644 --- a/gigalixir/signup.py +++ b/gigalixir/signup.py @@ -2,8 +2,11 @@ import datetime import re import stripe +from . import netrc +from . import user as gigalixir_user def by_email(ctx): + env = ctx.obj['env'] session = ctx.obj['session'] print("Welcome, let's get you started!") @@ -31,41 +34,43 @@ def by_email(ctx): accepted = set_password(session, uuid, password) print("") - print("Which tier would you like to sign up for?") - print("1. Free") - print("2. Standard") - tier = None - while not tier: - value = click.prompt('Tier', type=int) - if value == 1: - tier = "FREE" - elif value == 2: - tier = "STANDARD" + (promo, tier) = select_tier(session, uuid) + + confirm_and_complete(session, uuid, email, tier, promo, env) + +def by_oauth(ctx, provider): + env = ctx.obj['env'] + session = ctx.obj['session'] + + welcome_message() + + (oauth_session, url, uuid) = start_oauth(session, provider) + + email = gigalixir_user.oauth_process(session, provider, 'signup', url, oauth_session)['email'] + print("Thank you for your trust in us.") print("") - if tier == "STANDARD": - accepted = False - while not accepted: - card_number = click.prompt("Enter credit card number", type=str, value_proc=validate_credit_card_number) - cvc = click.prompt("Enter CVV", type=str, value_proc=validate_cvv) - exp_month = click.prompt("Enter expiration month (MM)", type=str, value_proc=validate_exp_month) - exp_year = click.prompt("Enter expiration year (YYYY)", type=str, value_proc=validate_exp_year) + (promo, tier) = select_tier(session, uuid) - stripe_token = stripe.Token.create(card={ "number": card_number, "exp_month": exp_month, "exp_year": exp_year, "cvc": cvc }) + confirm_and_complete(session, uuid, email, tier, promo, env) - accepted = set_cc(session, uuid, stripe_token["id"]) + +def welcome_message(): + print("Welcome, let's get you started!") print("") - print("You are about to signup for the %s tier." % tier) - print("By continuing, you agree to our Terms of Service and Privacy Policy.") - print(" Privacy Policy: https://gigalixir.com/privacy-policy") - print(" Terms of Service: https://gigalixir.com/terms-of-service") - if not click.confirm('Do you wish to proceed?'): - raise Exception("Signup cancelled.") - finalize(session, uuid, tier) + print("We require a *valid* email address to be on file in case we need to reach you about your applications.") + print("We promise to not abuse your email address.") + print("Don't believe us? Read our privacy policy: https://gigalixir.com/privacy-policy") + print("") - print("Welcome to Gigalixir!") - print("Please sign in with 'gigalixir login' to get started.") +def start_oauth(session, provider): + r = session.post('/api/signup', json = { 'oauth': provider }) + if r.status_code != 200: + raise Exception(r.text) + + data = r.json()['data'] + return (data['session'], data['url'], data['uuid']) def set_email(session, email): r = session.post('/api/signup', json = { 'email': email }) @@ -107,56 +112,132 @@ def finalize(session, uuid, tier): if r.status_code != 200: raise Exception(r.text) + data = r.json()['data'] + return (data['email'], data['key']) ## input validations def luhn_check(card_number): - """Validate credit card number using Luhn algorithm.""" - digits = [int(d) for d in card_number] - checksum = 0 + """Validate credit card number using Luhn algorithm.""" + digits = [int(d) for d in card_number] + checksum = 0 - # Double every second digit from the right, subtracting 9 if >9 - for i, digit in enumerate(reversed(digits)): - if i % 2 == 1: - digit *= 2 - if digit > 9: - digit -= 9 - checksum += digit + # Double every second digit from the right, subtracting 9 if >9 + for i, digit in enumerate(reversed(digits)): + if i % 2 == 1: + digit *= 2 + if digit > 9: + digit -= 9 + checksum += digit - return checksum % 10 == 0 + return checksum % 10 == 0 def validate_credit_card_number(card_number): - """Ensure card number is valid using regex and Luhn check.""" - card_number = card_number.replace(" ", "") # Allow spaces for readability - if not re.fullmatch(r"\d{13,19}", card_number): - raise click.BadParameter("Credit card number must be 13-19 digits long.") - if not luhn_check(card_number): - raise click.BadParameter("Invalid credit card number (failed Luhn check).") - return card_number + """Ensure card number is valid using regex and Luhn check.""" + card_number = card_number.replace(" ", "") # Allow spaces for readability + if not re.fullmatch(r"\d{13,19}", card_number): + raise click.BadParameter("Credit card number must be 13-19 digits long.") + if not luhn_check(card_number): + raise click.BadParameter("Invalid credit card number (failed Luhn check).") + return card_number def validate_cvv(cvv): - """Ensure CVV is numeric and correct length.""" - if not re.fullmatch(r"\d{3,4}", cvv): - raise click.BadParameter("CVV must be 3 or 4 digits.") - return cvv + """Ensure CVV is numeric and correct length.""" + if not re.fullmatch(r"\d{3,4}", cvv): + raise click.BadParameter("CVV must be 3 or 4 digits.") + return cvv def validate_exp_month(exp_month): - """Ensure expiration month is valid (1-12).""" - try: - month = int(exp_month) - if 1 <= month <= 12: - return f"{month:02d}" # Ensure two-digit format - except ValueError: - pass - raise click.BadParameter("Expiration month must be a number between 1 and 12.") + """Ensure expiration month is valid (1-12).""" + try: + month = int(exp_month) + if 1 <= month <= 12: + return f"{month:02d}" # Ensure two-digit format + except ValueError: + pass + raise click.BadParameter("Expiration month must be a number between 1 and 12.") def validate_exp_year(exp_year): - """Ensure expiration year is in the future and valid.""" - current_year = datetime.datetime.now().year - try: - year = int(exp_year) - if current_year <= year <= current_year + 20: # Prevent unrealistic years - return str(year) - except ValueError: - pass - raise click.BadParameter(f"Expiration year must be {current_year} or later.") + """Ensure expiration year is in the future and valid.""" + current_year = datetime.datetime.now().year + try: + year = int(exp_year) + if current_year <= year <= current_year + 20: # Prevent unrealistic years + return str(year) + except ValueError: + pass + raise click.BadParameter(f"Expiration year must be {current_year} or later.") + +def select_tier(session, uuid): + promo = add_promo_code(session, uuid) + print("") + + tier = None + if promo: + tier = "STANDARD" + else: + tier = prompt_tier() + print("") + + if tier == "STANDARD": + accepted = False + while not accepted: + card_number = click.prompt("Enter credit card number", type=str, value_proc=validate_credit_card_number) + cvc = click.prompt("Enter CVV", type=str, value_proc=validate_cvv) + exp_month = click.prompt("Enter expiration month (MM)", type=str, value_proc=validate_exp_month) + exp_year = click.prompt("Enter expiration year (YYYY)", type=str, value_proc=validate_exp_year) + + stripe_token = stripe.Token.create(card={ "number": card_number, "exp_month": exp_month, "exp_year": exp_year, "cvc": cvc }) + + accepted = set_cc(session, uuid, stripe_token["id"]) + print("") + + return (promo, tier) + +def add_promo_code(session, uuid): + while True: + promo = click.prompt('Promo code [enter to skip]', default=False, show_default=False, type=str) + print(promo) + if promo: + r = session.post('/api/signup', json = { 'promo_code': promo, 'uuid': uuid }) + + if r.status_code == 200: + return r.json()['data']['promo'] + + elif r.status_code == 429: + raise Exception('Too many attempts. Please try again later.') + + print("Invalid promo code") + else: + return None + +def prompt_tier(): + print("Which tier would you like to sign up for?") + print("1. Free") + print("2. Standard") + while True: + value = click.prompt('Tier', type=int) + if value == 1: + return "FREE" + elif value == 2: + return "STANDARD" + +def confirm_and_complete(session, uuid, email, tier, promo, env): + print("") + print("You are about to signup for the %s tier." % tier) + print(" Email: %s" % email) + + if promo: + print(" Promo: %s" % promo) + print("") + + print("By continuing, you agree to our Terms of Service and Privacy Policy.") + print(" Privacy Policy: https://gigalixir.com/privacy-policy") + print(" Terms of Service: https://gigalixir.com/terms-of-service") + print("") + if not click.confirm('Do you wish to proceed?'): + raise Exception("Signup cancelled.") + (email, key) = finalize(session, uuid, tier) + + print("Welcome to Gigalixir!") + netrc.update_netrc(email, key, env) diff --git a/gigalixir/user.py b/gigalixir/user.py index e99333f..03da3ec 100644 --- a/gigalixir/user.py +++ b/gigalixir/user.py @@ -21,12 +21,6 @@ def create(session, email, password, accept_terms_of_service_and_privacy_policy) logging.getLogger("gigalixir-cli").info('Created account for %s. Confirmation email sent.' % email) logging.getLogger("gigalixir-cli").info('Please check your email and click confirm before continuing.') -def oauth_create(session, env, provider): - r = session.post('/api/oauth/%s' % (provider), json = { 'signup': True }) - - oauth_process(session, provider, 'signup', r, False, env) - - def upgrade(session, card_number, card_exp_month, card_exp_year, card_cvc, promo_code): token = stripe.Token.create( card={ @@ -74,7 +68,7 @@ def change_password(session, current_password, new_password): data = json.loads(r.text)["data"] presenter.echo_json(data) -def login(session, email, password, yes, env, token): +def login(session, email, password, env, token): payload = {} if token: payload["mfa_token"] = token @@ -86,24 +80,29 @@ def login(session, email, password, yes, env, token): raise Exception("Sorry, we could not authenticate you. If you need to reset your password, run `gigalixir account:password:reset --email=%s`." % email) elif r.status_code == 303: token = click.prompt('Multi-factor Authentication Token') - login(session, email, password, yes, env, token) + login(session, email, password, env, token) else: raise Exception(r.text) else: key = json.loads(r.text)["data"]["key"] - complete_login(email, key, yes, env) + complete_login(email, key, env) -def oauth_login(session, yes, env, provider): +def oauth_login(session, env, provider): r = session.post('/api/oauth/%s' % (provider)) - oauth_process(session, provider, 'login', r, yes, env) - -def oauth_process(session, provider, action, r, yes, env): if r.status_code != 201: raise Exception(r.text) request_url = json.loads(r.text)["data"]["url"] oauth_session = json.loads(r.text)["data"]["session"] + + data = oauth_process(session, provider, 'login', request_url, oauth_session) + + data["email"] + data["key"] + complete_login(email, key, env) + +def oauth_process(session, provider, action, request_url, oauth_session): print('To', action, 'browse to', request_url) delay_time = 4 @@ -117,10 +116,7 @@ def oauth_process(session, provider, action, r, yes, env): delay_time -= 0.05 elif r.status_code == 200: - email = json.loads(r.text)["data"]["email"] - key = json.loads(r.text)["data"]["key"] - complete_login(email, key, yes, env) - return + return json.loads(r.text)["data"] else: error = json.loads(r.text)["errors"][""] @@ -170,17 +166,6 @@ def account(session): data = json.loads(r.text)["data"] presenter.echo_json(data) -def complete_login(email, key, yes, env): - if yes or click.confirm('Would you like us to save your api key to your ~/.netrc file?', default=True): - netrc.update_netrc(email, key, env) - logging.getLogger("gigalixir-cli").info('Logged in as %s.' % email) - else: - logging.getLogger("gigalixir-cli").warn('Please edit your ~/.netrc file manually. Many GIGALIXIR CLI commands may not work unless your ~/.netrc file contains your GIGALIXIR credentials.') - logging.getLogger("gigalixir-cli").info('Add the following:') - logging.getLogger("gigalixir-cli").info('') - logging.getLogger("gigalixir-cli").info('machine api.gigalixir.com') - logging.getLogger("gigalixir-cli").info('\tlogin %s' % email) - logging.getLogger("gigalixir-cli").info('\tpassword %s' % key) - logging.getLogger("gigalixir-cli").info('machine git.gigalixir.com') - logging.getLogger("gigalixir-cli").info('\tlogin %s' % email) - logging.getLogger("gigalixir-cli").info('\tpassword %s' % key) +def complete_login(email, key, env): + netrc.update_netrc(email, key, env) + logging.getLogger("gigalixir-cli").info('Logged in as %s.' % email) From 6230275981304d93f2db4747a10bfa9a4bfdf341 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 17 Feb 2025 07:15:00 -0600 Subject: [PATCH 3/5] feat: prompt login method --- gigalixir/__init__.py | 19 ++++++++++++++++--- gigalixir/user.py | 5 +++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/gigalixir/__init__.py b/gigalixir/__init__.py index 724d0a9..20711a0 100644 --- a/gigalixir/__init__.py +++ b/gigalixir/__init__.py @@ -222,7 +222,8 @@ def cli(ctx, env): host = "https://api.gigalixir.com" elif env == "dev": stripe.api_key = 'pk_test_6tMDkFKTz4N0wIFQZHuzOUyW' - host = "http://localhost:4000" + #host = "http://192.168.8.104:3200" + host = "http://192.168.0.162:3200" elif env == "test": stripe.api_key = 'pk_test_6tMDkFKTz4N0wIFQZHuzOUyW' @@ -561,8 +562,8 @@ def logout(ctx): gigalixir_user.logout(ctx.obj['env']) @cli.command() -@click.option('-e', '--email', prompt=True) -@click.option('-p', '--password', prompt=True, hide_input=True, confirmation_prompt=False) +@click.option('-e', '--email', prompt=False) +@click.option('-p', '--password', prompt=False) @click.option('-t', '--mfa_token', prompt=False) # we handle prompting if needed, not always needed @click.pass_context @report_errors @@ -570,6 +571,18 @@ def login(ctx, email, password, mfa_token): """ Login and receive an api key. """ + method = None + if not email and not password: + print("How would you like to log in?") + print("1. Email and password") + print("2. Google authentication") + while True: + value = click.prompt('Authentication', type=int) + if value == 1: + gigalixir_user.login(ctx.obj['session'], email, password, ctx.obj['env'], mfa_token) + elif value == 2: + gigalixir_user.oauth_login(ctx.obj['session'], ctx.obj['env'], 'google') + gigalixir_user.login(ctx.obj['session'], email, password, ctx.obj['env'], mfa_token) @cli.command(name='login:google') diff --git a/gigalixir/user.py b/gigalixir/user.py index 03da3ec..11dd139 100644 --- a/gigalixir/user.py +++ b/gigalixir/user.py @@ -69,6 +69,11 @@ def change_password(session, current_password, new_password): presenter.echo_json(data) def login(session, email, password, env, token): + if not email: + email = click.prompt('Email') + if not password: + password = click.prompt('Password', hide_input=True, confirmation_prompt=False) + payload = {} if token: payload["mfa_token"] = token From 374b8df0c4d90d2f4c044b8087dcc6ff7eeea6ec Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 17 Feb 2025 07:16:58 -0600 Subject: [PATCH 4/5] chore: restore (no unused) argument --- gigalixir/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gigalixir/__init__.py b/gigalixir/__init__.py index 20711a0..301768c 100644 --- a/gigalixir/__init__.py +++ b/gigalixir/__init__.py @@ -565,9 +565,10 @@ def logout(ctx): @click.option('-e', '--email', prompt=False) @click.option('-p', '--password', prompt=False) @click.option('-t', '--mfa_token', prompt=False) # we handle prompting if needed, not always needed +@click.option('-y', '--yes', is_flag=True) @click.pass_context @report_errors -def login(ctx, email, password, mfa_token): +def login(ctx, email, password, mfa_token, yes): """ Login and receive an api key. """ @@ -586,9 +587,10 @@ def login(ctx, email, password, mfa_token): gigalixir_user.login(ctx.obj['session'], email, password, ctx.obj['env'], mfa_token) @cli.command(name='login:google') +@click.option('-y', '--yes', is_flag=True) @click.pass_context @report_errors -def google_login(ctx): +def google_login(ctx, yes): """ Login with Google and receive an api key. """ From 84cf449d128b251fa203f74ea5bd91da9db99f53 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 17 Feb 2025 18:56:21 -0600 Subject: [PATCH 5/5] chore: remove dead code --- gigalixir/user.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/gigalixir/user.py b/gigalixir/user.py index 11dd139..e84ee4a 100644 --- a/gigalixir/user.py +++ b/gigalixir/user.py @@ -9,18 +9,6 @@ from six.moves.urllib.parse import quote from time import sleep -def create(session, email, password, accept_terms_of_service_and_privacy_policy): - r = session.post('/api/free_users', json = { - 'email': email, - 'password': password, - }) - if r.status_code != 200: - if r.status_code == 401: - raise auth.AuthException() - raise Exception(r.text) - logging.getLogger("gigalixir-cli").info('Created account for %s. Confirmation email sent.' % email) - logging.getLogger("gigalixir-cli").info('Please check your email and click confirm before continuing.') - def upgrade(session, card_number, card_exp_month, card_exp_year, card_cvc, promo_code): token = stripe.Token.create( card={