Skip to content

Commit

Permalink
v1.2.0 with multi-factor authentication.
Browse files Browse the repository at this point in the history
  • Loading branch information
jesseshieh committed Nov 8, 2020
1 parent e533e5e commit d343962
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 22 deletions.
4 changes: 2 additions & 2 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@
# built documents.
#
# The short X.Y version.
version = u'1.1.11'
version = u'1.2.0'
# The full version, including alpha/beta/rc tags.
release = u'1.1.11'
release = u'1.2.0'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
48 changes: 40 additions & 8 deletions gigalixir/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
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 app as gigalixir_app
from . import config as gigalixir_config
from . import permission as gigalixir_permission
Expand Down Expand Up @@ -481,16 +482,15 @@ def set_password(ctx, token, password):

# @update.command()
@cli.command(name='account:password:change')
@click.option('-e', '--email', prompt=True)
@click.option('-p', '--current_password', prompt=True, hide_input=True, confirmation_prompt=False)
@click.option('-n', '--new_password', prompt=True, hide_input=True, confirmation_prompt=False)
@click.pass_context
@report_errors
def change_password(ctx, email, current_password, new_password):
def change_password(ctx, current_password, new_password):
"""
Change password.
"""
gigalixir_user.change_password(ctx.obj['host'], email, current_password, new_password)
gigalixir_user.change_password(ctx.obj['host'], current_password, new_password)

@cli.command()
@click.pass_context
Expand All @@ -503,18 +503,48 @@ def account(ctx):

# @update.command()
@cli.command(name='account:api_key:reset')
@click.option('-e', '--email', prompt=True)
@click.option('-p', '--password', prompt=True, hide_input=True, confirmation_prompt=False)
@click.option('-y', '--yes', is_flag=True)
@click.pass_context
@report_errors
def reset_api_key(ctx, email, password, yes):
def reset_api_key(ctx, password, yes):
"""
Regenerate a replacement api key.
"""
gigalixir_api_key.regenerate(ctx.obj['host'], email, password, yes, ctx.obj['env'])
gigalixir_api_key.regenerate(ctx.obj['host'], password, yes, ctx.obj['env'])

@cli.command(name='account:mfa:activate')
@click.option('-y', '--yes', is_flag=True)
@click.pass_context
@report_errors
def mfa_activate(ctx, yes):
"""
Start the multi-factor authentication activation process.
"""
gigalixir_mfa.activate(ctx.obj['host'], yes)


@cli.command(name='account:mfa:deactivate')
@click.option('-y', '--yes', is_flag=True)
@click.pass_context
@report_errors
def mfa_deactivate(ctx, yes):
"""
Deactivate multi-factor authentication.
"""
if yes or click.confirm('Are you sure you want to deactivate multi-factor authentication?'):
gigalixir_mfa.deactivate(ctx.obj['host'])

@cli.command(name='account:mfa:recovery_codes:regenerate')
@click.option('-y', '--yes', is_flag=True)
@click.pass_context
@report_errors
def mfa_deactivate(ctx, yes):
"""
Regenerate multi-factor authentication recovery codes.
"""
gigalixir_mfa.regenerate_recovery_codes(ctx.obj['host'], yes)

@cli.command()
@click.pass_context
@report_errors
Expand All @@ -527,14 +557,15 @@ def logout(ctx):
@cli.command()
@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):
def login(ctx, email, password, yes, mfa_token):
"""
Login and receive an api key.
"""
gigalixir_user.login(ctx.obj['host'], email, password, yes, ctx.obj['env'])
gigalixir_user.login(ctx.obj['host'], email, password, yes, ctx.obj['env'], mfa_token)

# @get.command()
@cli.command()
Expand Down Expand Up @@ -1166,3 +1197,4 @@ def unset_canary(ctx, app_name, canary_name):
"""
gigalixir_canary.delete(ctx.obj['host'], app_name, canary_name)


8 changes: 5 additions & 3 deletions gigalixir/api_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@
import click
import logging

def regenerate(host, email, password, yes, env):
r = requests.post('%s/api/api_keys' % host, auth = (email, password))
def regenerate(host, password, yes, env):
r = requests.post('%s/api/api_keys/regenerate' % host, params={
'current_password': password
})
if r.status_code != 201:
if r.status_code == 401:
raise auth.AuthException()
raise Exception(r.text)
else:
key = json.loads(r.text)["data"]["key"]
if yes or click.confirm('Would you like to save your api key to your ~/%s file?' % netrc.netrc_name()):
netrc.update_netrc(email, key, env)
netrc.update_netrc(None, key, env)
else:
logging.getLogger("gigalixir-cli").info('Your api key is %s' % key)
logging.getLogger("gigalixir-cli").warn('Many GIGALIXIR CLI commands may not work unless you your ~/.netrc file contains your GIGALIXIR credentials.')
Expand Down
2 changes: 1 addition & 1 deletion gigalixir/auth.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class AuthException(Exception):
def __init__(self):
# Call the base class constructor with the parameters it needs
message = "Sorry, you do not have access to that app. Try passing the app name explicitly with the `-a` flag. If that doesn't work, try running `gigalixir login` or check your ~/.netrc file."
message = "Sorry, you do not have access to that app or account. Try passing the app name explicitly with the `-a` flag. If that doesn't work, try running `gigalixir login` or check your ~/.netrc file."
super(AuthException, self).__init__(message)
8 changes: 8 additions & 0 deletions gigalixir/netrc.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,19 @@ def update_netrc(email, key, env):
netrc_file, fname = get_netrc_file()

if env == 'prod':
if email is None:
# is it safe to assume the emails are the same for both?
(email, _, _) = netrc_file.hosts['api.gigalixir.com']
netrc_file.hosts['git.gigalixir.com'] = (email, None, key)
netrc_file.hosts['api.gigalixir.com'] = (email, None, key)
elif env == 'dev':
if email is None:
(email, _, _) = netrc_file.hosts['localhost']
netrc_file.hosts['localhost'] = (email, None, key)
elif env == 'test':
if email is None:
# is it safe to assume the emails are the same for both?
(email, _, _) = netrc_file.hosts['api.gigalixir.com']
netrc_file.hosts['git.gigalixir.com'] = (email, None, key)
netrc_file.hosts['api.gigalixir.com'] = (email, None, key)
else:
Expand Down
30 changes: 23 additions & 7 deletions gigalixir/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ def upgrade(host):
logging.getLogger("gigalixir-cli").info('Account upgraded.')

def delete(host, email, password):
r = requests.delete('%s/api/users' % host, auth = (quote(email.encode('utf-8')), quote(password.encode('utf-8'))), headers = {
r = requests.delete('%s/api/users/destroy' % host, headers = {
'Content-Type': 'application/json',
}, json = {
'email': email
'email': email,
'current_password': password
})
if r.status_code != 200:
if r.status_code == 401:
Expand All @@ -61,22 +62,37 @@ def validate_password(host, password):
def logout(env):
netrc.clear_netrc(env)

def change_password(host, email, current_password, new_password):
r = requests.patch('%s/api/users' % host, auth = (quote(email.encode('utf-8')), quote(current_password.encode('utf-8'))), json = {
def change_password(host, current_password, new_password):
r = requests.patch('%s/api/users/change_password' % host, headers = {
'Content-Type': 'application/json',
}, json = {
"current_password": current_password,
"new_password": new_password
})
if r.status_code != 200:
if r.status_code == 401:
raise auth.AuthException()
raise Exception(r.text)
data = json.loads(r.text)["data"]
presenter.echo_json(data)

def login(host, email, password, yes, env, token):
payload = {}
if token:
payload["mfa_token"] = token

r = requests.get('%s/api/login' % host, auth = (quote(email.encode('utf-8')), quote(password.encode('utf-8'))), headers = {
'Content-Type': 'application/json',
}, params=payload)

def login(host, email, password, yes, env):
r = requests.get('%s/api/login' % host, auth = (quote(email.encode('utf-8')), quote(password.encode('utf-8'))))
if r.status_code != 200:
if r.status_code == 401:
raise Exception("Sorry, we could not authenticate you. If you need to reset your password, run `gigalixir account:password:reset --email=%s`." % email)
raise Exception(r.text)
elif r.status_code == 303:
token = click.prompt('Multi-factor Authentication Token')
login(host, email, password, yes, env, token)
else:
raise Exception(r.text)
else:
key = json.loads(r.text)["data"]["key"]
if yes or click.confirm('Would you like us to save your api key to your ~/.netrc file?', default=True):
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
url='https://github.com/gigalixir/gigalixir-cli',
author='Jesse Shieh',
author_email='jesse@gigalixir.com',
version='1.1.11',
version='1.2.0',
packages=find_packages(),
include_package_data=True,
install_requires=[
Expand All @@ -14,6 +14,7 @@
'stripe~=1.51.0',
'rollbar~=0.13.11',
'pygments~=2.2.0',
'qrcode~=6.1',
],
entry_points='''
[console_scripts]
Expand Down

0 comments on commit d343962

Please sign in to comment.