Email auth for inviting members and editing permissions

This commit is contained in:
chrisw
2017-11-01 15:36:27 +00:00
parent 756bdd3181
commit c6ea90a7d8
11 changed files with 325 additions and 23 deletions

View File

@@ -63,6 +63,11 @@
right: -135px;
}
&-hint {
color: #6F777B;
padding-top: 5px;
}
}
}

View File

@@ -186,6 +186,14 @@ class PermissionsForm(Form):
manage_templates = BooleanField("Add and edit templates")
manage_service = BooleanField("Modify this service and its team")
manage_api_keys = BooleanField("Create and revoke API keys")
login_authentication = RadioField(
'Sign in using',
choices=[
('sms_auth', 'Text message code'),
('email_auth', 'Email link'),
],
validators=[DataRequired()]
)
class InviteUserForm(PermissionsForm):

View File

@@ -19,7 +19,7 @@ from app.main.forms import (
InviteUserForm,
PermissionsForm
)
from app import (user_api_client, service_api_client, invite_api_client)
from app import (user_api_client, current_service, service_api_client, invite_api_client)
from app.utils import user_has_permissions
@@ -38,6 +38,7 @@ def manage_users(service_id):
users = user_api_client.get_users_for_service(service_id=service_id)
invited_users = [invite for invite in invite_api_client.get_invites_for_service(service_id=service_id)
if invite.status != 'accepted']
return render_template(
'views/manage-users.html',
users=users,
@@ -51,7 +52,13 @@ def manage_users(service_id):
@user_has_permissions('manage_users', admin_override=True)
def invite_user(service_id):
form = InviteUserForm(invalid_email_address=current_user.email_address)
form = InviteUserForm(
invalid_email_address=current_user.email_address
)
service_has_email_auth = 'email_auth' in current_service['permissions']
if not service_has_email_auth:
form.login_authentication.data = 'sms_auth'
if form.validate_on_submit():
email_address = form.email_address.data
@@ -60,7 +67,8 @@ def invite_user(service_id):
current_user.id,
service_id,
email_address,
permissions
permissions,
form.login_authentication.data
)
flash('Invite sent to {}'.format(invited_user.email_address), 'default_with_tick')
@@ -68,7 +76,8 @@ def invite_user(service_id):
return render_template(
'views/invite-user.html',
form=form
form=form,
service_has_email_auth=service_has_email_auth
)
@@ -76,26 +85,35 @@ def invite_user(service_id):
@login_required
@user_has_permissions('manage_users', admin_override=True)
def edit_user_permissions(service_id, user_id):
service_has_email_auth = 'email_auth' in current_service['permissions']
# TODO we should probably using the service id here in the get user
# call as well. eg. /user/<user_id>?&service=service_id
user = user_api_client.get_user(user_id)
# Need to make the email address read only, or a disabled field?
# Do it through the template or the form class?
form = PermissionsForm(**{
role: user.has_permissions(permissions=permissions) for role, permissions in roles.items()
})
user_has_no_mobile_number = user.mobile_number is None
form = PermissionsForm(
**{role: user.has_permissions(permissions=permissions) for role, permissions in roles.items()},
login_authentication=user.auth_type
)
if form.validate_on_submit():
user_api_client.set_user_permissions(
user_id, service_id,
permissions=set(get_permissions_from_form(form)),
)
if service_has_email_auth:
user_api_client.update_user_attribute(
user_id,
auth_type=form.login_authentication.data
)
return redirect(url_for('.manage_users', service_id=service_id))
return render_template(
'views/edit-user-permissions.html',
user=user,
form=form
form=form,
service_has_email_auth=service_has_email_auth,
user_has_no_mobile_number=user_has_no_mobile_number
)

View File

@@ -12,12 +12,13 @@ class InviteApiClient(NotifyAdminAPIClient):
self.service_id = app.config['ADMIN_CLIENT_USER_NAME']
self.api_key = app.config['ADMIN_CLIENT_SECRET']
def create_invite(self, invite_from_id, service_id, email_address, permissions):
def create_invite(self, invite_from_id, service_id, email_address, permissions, auth_type):
data = {
'service': str(service_id),
'email_address': email_address,
'from_user': invite_from_id,
'permissions': permissions
'permissions': permissions,
'auth_type': auth_type
}
data = _attach_current_user(data)
resp = self.post(url='/service/{}/invite'.format(service_id), data=data)

View File

@@ -10,6 +10,7 @@ class User(UserMixin):
self._mobile_number = fields.get('mobile_number')
self._password_changed_at = fields.get('password_changed_at')
self._permissions = fields.get('permissions')
self._auth_type = fields.get('auth_type')
self._failed_login_count = fields.get('failed_login_count')
self._state = fields.get('state')
self.max_failed_login_count = max_failed_login_count
@@ -108,6 +109,14 @@ class User(UserMixin):
return set(self._permissions[service_id]) >= set(permissions)
return False
@property
def auth_type(self):
return self._auth_type
@auth_type.setter
def auth_type(self, auth_type):
self._auth_type = auth_type
@property
def failed_login_count(self):
return self._failed_login_count
@@ -155,6 +164,7 @@ class InvitedUser(object):
self.permissions = []
self.status = status
self.created_at = created_at
self.auth_type = auth_type
def has_permissions(self, permissions):
return set(self.permissions) > set(permissions)
@@ -164,10 +174,12 @@ class InvitedUser(object):
self.service,
self.from_user,
self.email_address,
self.auth_type,
self.status) == (other.id,
other.service,
other.from_user,
other.email_address,
other.auth_type,
other.status))
def serialize(self, permissions_as_string=False):
@@ -176,7 +188,8 @@ class InvitedUser(object):
'from_user': self.from_user,
'email_address': self.email_address,
'status': self.status,
'created_at': str(self.created_at)
'created_at': str(self.created_at),
'auth_type': self.auth_type
}
if permissions_as_string:
data['permissions'] = ','.join(self.permissions)

View File

@@ -6,7 +6,8 @@ from app.notify_client.models import User
ALLOWED_ATTRIBUTES = {
'name',
'email_address',
'mobile_number'
'mobile_number',
'auth_type',
}

View File

@@ -63,6 +63,15 @@
user.has_permissions(permissions=['manage_api_keys']),
'Access API keys'
) }}
{% if 'email_auth' in current_service['permissions'] %}
<div class="tick-cross-list-hint">
{% if user.auth_type == 'sms_auth' %}
Signs in with a text message code
{% else %}
Signs in with an email link
{% endif %}
</div>
{% endif %}
</div>
{% if current_user.has_permissions(['manage_users'], admin_override=True) %}
{% if current_user.id != user.id %}
@@ -104,6 +113,15 @@
user.has_permissions(permissions=['manage_api_keys']),
'Access API keys'
) }}
{% if 'email_auth' in current_service['permissions'] %}
<div class="tick-cross-list-hint">
{% if user.auth_type == 'sms_auth' %}
Signs in with a text message code
{% else %}
Signs in with an email link
{% endif %}
</div>
{% endif %}
</div>
<li class="tick-cross-list-edit-link">
{% if user.status == 'pending' and current_user.has_permissions(['manage_users']) %}

View File

@@ -1,4 +1,5 @@
{% from "components/checkbox.html" import checkbox %}
{% from "components/radios.html" import radios %}
<fieldset class="form-group">
<legend class="form-label">
@@ -20,3 +21,7 @@
<li>who the other team members are</li>
</ul>
</div>
{% if service_has_email_auth %}
{{ radios(form.login_authentication, disable=['sms_auth' if user_has_no_mobile_number]) }}
{% endif %}