You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ma...@apache.org on 2018/10/31 17:48:20 UTC

[airavata-django-portal] 06/09: AIRAVATA-2888 Resend email verification link form

This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit e1c22616d7ead16f5f45db10f8947038b2b1672c
Author: Marcus Christie <ma...@iu.edu>
AuthorDate: Fri Oct 26 17:03:45 2018 -0400

    AIRAVATA-2888 Resend email verification link form
---
 .../iam/admin/services/cpi/IamAdminServices-remote |  14 +
 .../iam/admin/services/cpi/IamAdminServices.py     | 467 +++++++++++++++++++++
 django_airavata/apps/auth/forms.py                 |  10 +
 django_airavata/apps/auth/iam_admin_client.py      |  12 +
 .../django_airavata_auth/verify_email.html         |  51 +++
 django_airavata/apps/auth/urls.py                  |   2 +
 django_airavata/apps/auth/views.py                 |  78 +++-
 django_airavata/utils.py                           |  10 +-
 8 files changed, 623 insertions(+), 21 deletions(-)

diff --git a/airavata/service/profile/iam/admin/services/cpi/IamAdminServices-remote b/airavata/service/profile/iam/admin/services/cpi/IamAdminServices-remote
index 683c411..c164ce1 100755
--- a/airavata/service/profile/iam/admin/services/cpi/IamAdminServices-remote
+++ b/airavata/service/profile/iam/admin/services/cpi/IamAdminServices-remote
@@ -30,6 +30,8 @@ if len(sys.argv) <= 1 or sys.argv[1] == '--help':
     print('  bool registerUser(AuthzToken authzToken, string username, string emailAddress, string firstName, string lastName, string newPassword)')
     print('  bool enableUser(AuthzToken authzToken, string username)')
     print('  bool isUserEnabled(AuthzToken authzToken, string username)')
+    print('  bool isUserExist(AuthzToken authzToken, string username)')
+    print('  UserProfile getUser(AuthzToken authzToken, string username)')
     print('  bool resetUserPassword(AuthzToken authzToken, string username, string newPassword)')
     print('   findUsers(AuthzToken authzToken, string email, string userId)')
     print('  void updateUserProfile(AuthzToken authzToken, UserProfile userDetails)')
@@ -151,6 +153,18 @@ elif cmd == 'isUserEnabled':
         sys.exit(1)
     pp.pprint(client.isUserEnabled(eval(args[0]), args[1],))
 
+elif cmd == 'isUserExist':
+    if len(args) != 2:
+        print('isUserExist requires 2 args')
+        sys.exit(1)
+    pp.pprint(client.isUserExist(eval(args[0]), args[1],))
+
+elif cmd == 'getUser':
+    if len(args) != 2:
+        print('getUser requires 2 args')
+        sys.exit(1)
+    pp.pprint(client.getUser(eval(args[0]), args[1],))
+
 elif cmd == 'resetUserPassword':
     if len(args) != 3:
         print('resetUserPassword requires 3 args')
diff --git a/airavata/service/profile/iam/admin/services/cpi/IamAdminServices.py b/airavata/service/profile/iam/admin/services/cpi/IamAdminServices.py
index 58b46fe..dae59b5 100644
--- a/airavata/service/profile/iam/admin/services/cpi/IamAdminServices.py
+++ b/airavata/service/profile/iam/admin/services/cpi/IamAdminServices.py
@@ -67,6 +67,22 @@ class Iface(object):
         """
         pass
 
+    def isUserExist(self, authzToken, username):
+        """
+        Parameters:
+         - authzToken
+         - username
+        """
+        pass
+
+    def getUser(self, authzToken, username):
+        """
+        Parameters:
+         - authzToken
+         - username
+        """
+        pass
+
     def resetUserPassword(self, authzToken, username, newPassword):
         """
         Parameters:
@@ -355,6 +371,80 @@ class Client(Iface):
             raise result.ae
         raise TApplicationException(TApplicationException.MISSING_RESULT, "isUserEnabled failed: unknown result")
 
+    def isUserExist(self, authzToken, username):
+        """
+        Parameters:
+         - authzToken
+         - username
+        """
+        self.send_isUserExist(authzToken, username)
+        return self.recv_isUserExist()
+
+    def send_isUserExist(self, authzToken, username):
+        self._oprot.writeMessageBegin('isUserExist', TMessageType.CALL, self._seqid)
+        args = isUserExist_args()
+        args.authzToken = authzToken
+        args.username = username
+        args.write(self._oprot)
+        self._oprot.writeMessageEnd()
+        self._oprot.trans.flush()
+
+    def recv_isUserExist(self):
+        iprot = self._iprot
+        (fname, mtype, rseqid) = iprot.readMessageBegin()
+        if mtype == TMessageType.EXCEPTION:
+            x = TApplicationException()
+            x.read(iprot)
+            iprot.readMessageEnd()
+            raise x
+        result = isUserExist_result()
+        result.read(iprot)
+        iprot.readMessageEnd()
+        if result.success is not None:
+            return result.success
+        if result.Idse is not None:
+            raise result.Idse
+        if result.ae is not None:
+            raise result.ae
+        raise TApplicationException(TApplicationException.MISSING_RESULT, "isUserExist failed: unknown result")
+
+    def getUser(self, authzToken, username):
+        """
+        Parameters:
+         - authzToken
+         - username
+        """
+        self.send_getUser(authzToken, username)
+        return self.recv_getUser()
+
+    def send_getUser(self, authzToken, username):
+        self._oprot.writeMessageBegin('getUser', TMessageType.CALL, self._seqid)
+        args = getUser_args()
+        args.authzToken = authzToken
+        args.username = username
+        args.write(self._oprot)
+        self._oprot.writeMessageEnd()
+        self._oprot.trans.flush()
+
+    def recv_getUser(self):
+        iprot = self._iprot
+        (fname, mtype, rseqid) = iprot.readMessageBegin()
+        if mtype == TMessageType.EXCEPTION:
+            x = TApplicationException()
+            x.read(iprot)
+            iprot.readMessageEnd()
+            raise x
+        result = getUser_result()
+        result.read(iprot)
+        iprot.readMessageEnd()
+        if result.success is not None:
+            return result.success
+        if result.Idse is not None:
+            raise result.Idse
+        if result.ae is not None:
+            raise result.ae
+        raise TApplicationException(TApplicationException.MISSING_RESULT, "getUser failed: unknown result")
+
     def resetUserPassword(self, authzToken, username, newPassword):
         """
         Parameters:
@@ -594,6 +684,8 @@ class Processor(Iface, TProcessor):
         self._processMap["registerUser"] = Processor.process_registerUser
         self._processMap["enableUser"] = Processor.process_enableUser
         self._processMap["isUserEnabled"] = Processor.process_isUserEnabled
+        self._processMap["isUserExist"] = Processor.process_isUserExist
+        self._processMap["getUser"] = Processor.process_getUser
         self._processMap["resetUserPassword"] = Processor.process_resetUserPassword
         self._processMap["findUsers"] = Processor.process_findUsers
         self._processMap["updateUserProfile"] = Processor.process_updateUserProfile
@@ -766,6 +858,56 @@ class Processor(Iface, TProcessor):
         oprot.writeMessageEnd()
         oprot.trans.flush()
 
+    def process_isUserExist(self, seqid, iprot, oprot):
+        args = isUserExist_args()
+        args.read(iprot)
+        iprot.readMessageEnd()
+        result = isUserExist_result()
+        try:
+            result.success = self._handler.isUserExist(args.authzToken, args.username)
+            msg_type = TMessageType.REPLY
+        except (TTransport.TTransportException, KeyboardInterrupt, SystemExit):
+            raise
+        except airavata.service.profile.iam.admin.services.cpi.error.ttypes.IamAdminServicesException as Idse:
+            msg_type = TMessageType.REPLY
+            result.Idse = Idse
+        except airavata.api.error.ttypes.AuthorizationException as ae:
+            msg_type = TMessageType.REPLY
+            result.ae = ae
+        except Exception as ex:
+            msg_type = TMessageType.EXCEPTION
+            logging.exception(ex)
+            result = TApplicationException(TApplicationException.INTERNAL_ERROR, 'Internal error')
+        oprot.writeMessageBegin("isUserExist", msg_type, seqid)
+        result.write(oprot)
+        oprot.writeMessageEnd()
+        oprot.trans.flush()
+
+    def process_getUser(self, seqid, iprot, oprot):
+        args = getUser_args()
+        args.read(iprot)
+        iprot.readMessageEnd()
+        result = getUser_result()
+        try:
+            result.success = self._handler.getUser(args.authzToken, args.username)
+            msg_type = TMessageType.REPLY
+        except (TTransport.TTransportException, KeyboardInterrupt, SystemExit):
+            raise
+        except airavata.service.profile.iam.admin.services.cpi.error.ttypes.IamAdminServicesException as Idse:
+            msg_type = TMessageType.REPLY
+            result.Idse = Idse
+        except airavata.api.error.ttypes.AuthorizationException as ae:
+            msg_type = TMessageType.REPLY
+            result.ae = ae
+        except Exception as ex:
+            msg_type = TMessageType.EXCEPTION
+            logging.exception(ex)
+            result = TApplicationException(TApplicationException.INTERNAL_ERROR, 'Internal error')
+        oprot.writeMessageBegin("getUser", msg_type, seqid)
+        result.write(oprot)
+        oprot.writeMessageEnd()
+        oprot.trans.flush()
+
     def process_resetUserPassword(self, seqid, iprot, oprot):
         args = resetUserPassword_args()
         args.read(iprot)
@@ -1935,6 +2077,331 @@ class isUserEnabled_result(object):
         return not (self == other)
 
 
+class isUserExist_args(object):
+    """
+    Attributes:
+     - authzToken
+     - username
+    """
+
+    thrift_spec = (
+        None,  # 0
+        (1, TType.STRUCT, 'authzToken', (airavata.model.security.ttypes.AuthzToken, airavata.model.security.ttypes.AuthzToken.thrift_spec), None, ),  # 1
+        (2, TType.STRING, 'username', 'UTF8', None, ),  # 2
+    )
+
+    def __init__(self, authzToken=None, username=None,):
+        self.authzToken = authzToken
+        self.username = username
+
+    def read(self, iprot):
+        if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
+            iprot._fast_decode(self, iprot, (self.__class__, self.thrift_spec))
+            return
+        iprot.readStructBegin()
+        while True:
+            (fname, ftype, fid) = iprot.readFieldBegin()
+            if ftype == TType.STOP:
+                break
+            if fid == 1:
+                if ftype == TType.STRUCT:
+                    self.authzToken = airavata.model.security.ttypes.AuthzToken()
+                    self.authzToken.read(iprot)
+                else:
+                    iprot.skip(ftype)
+            elif fid == 2:
+                if ftype == TType.STRING:
+                    self.username = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
+                else:
+                    iprot.skip(ftype)
+            else:
+                iprot.skip(ftype)
+            iprot.readFieldEnd()
+        iprot.readStructEnd()
+
+    def write(self, oprot):
+        if oprot._fast_encode is not None and self.thrift_spec is not None:
+            oprot.trans.write(oprot._fast_encode(self, (self.__class__, self.thrift_spec)))
+            return
+        oprot.writeStructBegin('isUserExist_args')
+        if self.authzToken is not None:
+            oprot.writeFieldBegin('authzToken', TType.STRUCT, 1)
+            self.authzToken.write(oprot)
+            oprot.writeFieldEnd()
+        if self.username is not None:
+            oprot.writeFieldBegin('username', TType.STRING, 2)
+            oprot.writeString(self.username.encode('utf-8') if sys.version_info[0] == 2 else self.username)
+            oprot.writeFieldEnd()
+        oprot.writeFieldStop()
+        oprot.writeStructEnd()
+
+    def validate(self):
+        if self.authzToken is None:
+            raise TProtocolException(message='Required field authzToken is unset!')
+        if self.username is None:
+            raise TProtocolException(message='Required field username is unset!')
+        return
+
+    def __repr__(self):
+        L = ['%s=%r' % (key, value)
+             for key, value in self.__dict__.items()]
+        return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+    def __eq__(self, other):
+        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        return not (self == other)
+
+
+class isUserExist_result(object):
+    """
+    Attributes:
+     - success
+     - Idse
+     - ae
+    """
+
+    thrift_spec = (
+        (0, TType.BOOL, 'success', None, None, ),  # 0
+        (1, TType.STRUCT, 'Idse', (airavata.service.profile.iam.admin.services.cpi.error.ttypes.IamAdminServicesException, airavata.service.profile.iam.admin.services.cpi.error.ttypes.IamAdminServicesException.thrift_spec), None, ),  # 1
+        (2, TType.STRUCT, 'ae', (airavata.api.error.ttypes.AuthorizationException, airavata.api.error.ttypes.AuthorizationException.thrift_spec), None, ),  # 2
+    )
+
+    def __init__(self, success=None, Idse=None, ae=None,):
+        self.success = success
+        self.Idse = Idse
+        self.ae = ae
+
+    def read(self, iprot):
+        if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
+            iprot._fast_decode(self, iprot, (self.__class__, self.thrift_spec))
+            return
+        iprot.readStructBegin()
+        while True:
+            (fname, ftype, fid) = iprot.readFieldBegin()
+            if ftype == TType.STOP:
+                break
+            if fid == 0:
+                if ftype == TType.BOOL:
+                    self.success = iprot.readBool()
+                else:
+                    iprot.skip(ftype)
+            elif fid == 1:
+                if ftype == TType.STRUCT:
+                    self.Idse = airavata.service.profile.iam.admin.services.cpi.error.ttypes.IamAdminServicesException()
+                    self.Idse.read(iprot)
+                else:
+                    iprot.skip(ftype)
+            elif fid == 2:
+                if ftype == TType.STRUCT:
+                    self.ae = airavata.api.error.ttypes.AuthorizationException()
+                    self.ae.read(iprot)
+                else:
+                    iprot.skip(ftype)
+            else:
+                iprot.skip(ftype)
+            iprot.readFieldEnd()
+        iprot.readStructEnd()
+
+    def write(self, oprot):
+        if oprot._fast_encode is not None and self.thrift_spec is not None:
+            oprot.trans.write(oprot._fast_encode(self, (self.__class__, self.thrift_spec)))
+            return
+        oprot.writeStructBegin('isUserExist_result')
+        if self.success is not None:
+            oprot.writeFieldBegin('success', TType.BOOL, 0)
+            oprot.writeBool(self.success)
+            oprot.writeFieldEnd()
+        if self.Idse is not None:
+            oprot.writeFieldBegin('Idse', TType.STRUCT, 1)
+            self.Idse.write(oprot)
+            oprot.writeFieldEnd()
+        if self.ae is not None:
+            oprot.writeFieldBegin('ae', TType.STRUCT, 2)
+            self.ae.write(oprot)
+            oprot.writeFieldEnd()
+        oprot.writeFieldStop()
+        oprot.writeStructEnd()
+
+    def validate(self):
+        return
+
+    def __repr__(self):
+        L = ['%s=%r' % (key, value)
+             for key, value in self.__dict__.items()]
+        return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+    def __eq__(self, other):
+        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        return not (self == other)
+
+
+class getUser_args(object):
+    """
+    Attributes:
+     - authzToken
+     - username
+    """
+
+    thrift_spec = (
+        None,  # 0
+        (1, TType.STRUCT, 'authzToken', (airavata.model.security.ttypes.AuthzToken, airavata.model.security.ttypes.AuthzToken.thrift_spec), None, ),  # 1
+        (2, TType.STRING, 'username', 'UTF8', None, ),  # 2
+    )
+
+    def __init__(self, authzToken=None, username=None,):
+        self.authzToken = authzToken
+        self.username = username
+
+    def read(self, iprot):
+        if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
+            iprot._fast_decode(self, iprot, (self.__class__, self.thrift_spec))
+            return
+        iprot.readStructBegin()
+        while True:
+            (fname, ftype, fid) = iprot.readFieldBegin()
+            if ftype == TType.STOP:
+                break
+            if fid == 1:
+                if ftype == TType.STRUCT:
+                    self.authzToken = airavata.model.security.ttypes.AuthzToken()
+                    self.authzToken.read(iprot)
+                else:
+                    iprot.skip(ftype)
+            elif fid == 2:
+                if ftype == TType.STRING:
+                    self.username = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
+                else:
+                    iprot.skip(ftype)
+            else:
+                iprot.skip(ftype)
+            iprot.readFieldEnd()
+        iprot.readStructEnd()
+
+    def write(self, oprot):
+        if oprot._fast_encode is not None and self.thrift_spec is not None:
+            oprot.trans.write(oprot._fast_encode(self, (self.__class__, self.thrift_spec)))
+            return
+        oprot.writeStructBegin('getUser_args')
+        if self.authzToken is not None:
+            oprot.writeFieldBegin('authzToken', TType.STRUCT, 1)
+            self.authzToken.write(oprot)
+            oprot.writeFieldEnd()
+        if self.username is not None:
+            oprot.writeFieldBegin('username', TType.STRING, 2)
+            oprot.writeString(self.username.encode('utf-8') if sys.version_info[0] == 2 else self.username)
+            oprot.writeFieldEnd()
+        oprot.writeFieldStop()
+        oprot.writeStructEnd()
+
+    def validate(self):
+        if self.authzToken is None:
+            raise TProtocolException(message='Required field authzToken is unset!')
+        if self.username is None:
+            raise TProtocolException(message='Required field username is unset!')
+        return
+
+    def __repr__(self):
+        L = ['%s=%r' % (key, value)
+             for key, value in self.__dict__.items()]
+        return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+    def __eq__(self, other):
+        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        return not (self == other)
+
+
+class getUser_result(object):
+    """
+    Attributes:
+     - success
+     - Idse
+     - ae
+    """
+
+    thrift_spec = (
+        (0, TType.STRUCT, 'success', (airavata.model.user.ttypes.UserProfile, airavata.model.user.ttypes.UserProfile.thrift_spec), None, ),  # 0
+        (1, TType.STRUCT, 'Idse', (airavata.service.profile.iam.admin.services.cpi.error.ttypes.IamAdminServicesException, airavata.service.profile.iam.admin.services.cpi.error.ttypes.IamAdminServicesException.thrift_spec), None, ),  # 1
+        (2, TType.STRUCT, 'ae', (airavata.api.error.ttypes.AuthorizationException, airavata.api.error.ttypes.AuthorizationException.thrift_spec), None, ),  # 2
+    )
+
+    def __init__(self, success=None, Idse=None, ae=None,):
+        self.success = success
+        self.Idse = Idse
+        self.ae = ae
+
+    def read(self, iprot):
+        if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
+            iprot._fast_decode(self, iprot, (self.__class__, self.thrift_spec))
+            return
+        iprot.readStructBegin()
+        while True:
+            (fname, ftype, fid) = iprot.readFieldBegin()
+            if ftype == TType.STOP:
+                break
+            if fid == 0:
+                if ftype == TType.STRUCT:
+                    self.success = airavata.model.user.ttypes.UserProfile()
+                    self.success.read(iprot)
+                else:
+                    iprot.skip(ftype)
+            elif fid == 1:
+                if ftype == TType.STRUCT:
+                    self.Idse = airavata.service.profile.iam.admin.services.cpi.error.ttypes.IamAdminServicesException()
+                    self.Idse.read(iprot)
+                else:
+                    iprot.skip(ftype)
+            elif fid == 2:
+                if ftype == TType.STRUCT:
+                    self.ae = airavata.api.error.ttypes.AuthorizationException()
+                    self.ae.read(iprot)
+                else:
+                    iprot.skip(ftype)
+            else:
+                iprot.skip(ftype)
+            iprot.readFieldEnd()
+        iprot.readStructEnd()
+
+    def write(self, oprot):
+        if oprot._fast_encode is not None and self.thrift_spec is not None:
+            oprot.trans.write(oprot._fast_encode(self, (self.__class__, self.thrift_spec)))
+            return
+        oprot.writeStructBegin('getUser_result')
+        if self.success is not None:
+            oprot.writeFieldBegin('success', TType.STRUCT, 0)
+            self.success.write(oprot)
+            oprot.writeFieldEnd()
+        if self.Idse is not None:
+            oprot.writeFieldBegin('Idse', TType.STRUCT, 1)
+            self.Idse.write(oprot)
+            oprot.writeFieldEnd()
+        if self.ae is not None:
+            oprot.writeFieldBegin('ae', TType.STRUCT, 2)
+            self.ae.write(oprot)
+            oprot.writeFieldEnd()
+        oprot.writeFieldStop()
+        oprot.writeStructEnd()
+
+    def validate(self):
+        return
+
+    def __repr__(self):
+        L = ['%s=%r' % (key, value)
+             for key, value in self.__dict__.items()]
+        return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+    def __eq__(self, other):
+        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        return not (self == other)
+
+
 class resetUserPassword_args(object):
     """
     Attributes:
diff --git a/django_airavata/apps/auth/forms.py b/django_airavata/apps/auth/forms.py
index 1077e58..9569af3 100644
--- a/django_airavata/apps/auth/forms.py
+++ b/django_airavata/apps/auth/forms.py
@@ -87,3 +87,13 @@ class CreateAccountForm(forms.Form):
             )
 
         return cleaned_data
+
+
+class ResendEmailVerificationLinkForm(forms.Form):
+    error_css_class = "is-invalid"
+    username = forms.CharField(
+        label='Username',
+        widget=forms.TextInput(attrs={'class': 'form-control',
+                                      'placeholder': 'Username'}),
+        min_length=6,
+        validators=[USERNAME_VALIDATOR])
diff --git a/django_airavata/apps/auth/iam_admin_client.py b/django_airavata/apps/auth/iam_admin_client.py
index 7c0aec1..6877d8a 100644
--- a/django_airavata/apps/auth/iam_admin_client.py
+++ b/django_airavata/apps/auth/iam_admin_client.py
@@ -39,3 +39,15 @@ def enable_user(username):
     with get_iam_admin_client() as iam_admin_client:
         authz_token = utils.get_service_account_authz_token()
         return iam_admin_client.enableUser(authz_token, username)
+
+
+def is_user_exist(username):
+    with get_iam_admin_client() as iam_admin_client:
+        authz_token = utils.get_service_account_authz_token()
+        return iam_admin_client.isUserExist(authz_token, username)
+
+
+def get_user(username):
+    with get_iam_admin_client() as iam_admin_client:
+        authz_token = utils.get_service_account_authz_token()
+        return iam_admin_client.getUser(authz_token, username)
diff --git a/django_airavata/apps/auth/templates/django_airavata_auth/verify_email.html b/django_airavata/apps/auth/templates/django_airavata_auth/verify_email.html
new file mode 100644
index 0000000..d737105
--- /dev/null
+++ b/django_airavata/apps/auth/templates/django_airavata_auth/verify_email.html
@@ -0,0 +1,51 @@
+{% extends 'base.html' %}
+
+{% block content %}
+
+<div class="container">
+  <div class="row">
+    <div class="col">
+      <div class="card">
+        <div class="card-body">
+          <h5 class="card-title">Resend Email Verification Link</h5>
+          <form action="{% url 'django_airavata_auth:resend_email_link' %}" method="post">
+            {% for error in form.non_field_errors %}
+            <div class="alert alert-danger" role="alert">
+              {{ error }}
+            </div>
+            {% endfor %}
+            {% csrf_token %}
+
+            {% for field in form %}
+            <div class="form-group">
+              <label for="{{ field.id_for_label }}">{{ field.label }}</label>
+              <input id="{{ field.id_for_label }}" type="{{ field.field.widget.input_type }}"
+                class="form-control{% if field.errors %} is-invalid{% endif %}" name="{{ field.name }}"
+                placeholder="{{ field.field.widget.attrs.placeholder }}"
+                {% if field.value %} value="{{ field.value }}" {% endif %}
+                {% if field.field.required %} required {% endif %} />
+              <div class="invalid-feedback">
+                {% if field.errors|length == 1 %}
+                  {{ field.errors|first| escape }}
+                {% else %}
+                  <ul>
+                    {% for error in field.errors %}
+                    <li>{{ error | escape }}</li>
+                    {% endfor %}
+                  </ul>
+                {% endif %}
+              </div>
+            </div>
+            {% endfor %}
+
+            <button type="submit" class="btn btn-primary btn-block">
+              Resend
+            </button>
+          </form>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+
+{% endblock %}
diff --git a/django_airavata/apps/auth/urls.py b/django_airavata/apps/auth/urls.py
index 5d54b76..66011f0 100644
--- a/django_airavata/apps/auth/urls.py
+++ b/django_airavata/apps/auth/urls.py
@@ -15,4 +15,6 @@ urlpatterns = [
     url(r'^create-account$', views.create_account, name='create_account'),
     url(r'^verify-email/(?P<code>[\w-]+)/$', views.verify_email,
         name="verify_email"),
+    url(r'^resend-email-link/', views.resend_email_link,
+        name="resend_email_link"),
 ]
diff --git a/django_airavata/apps/auth/views.py b/django_airavata/apps/auth/views.py
index 9626721..271881b 100644
--- a/django_airavata/apps/auth/views.py
+++ b/django_airavata/apps/auth/views.py
@@ -111,25 +111,8 @@ def create_account(request):
                     form.add_error(None, ValidationError(
                         "Failed to register user with IAM service"))
                 else:
-                    email_verification = models.EmailVerification(
-                        username=username)
-                    email_verification.save()
-
-                    verification_uri = request.build_absolute_uri(
-                        reverse(
-                            'django_airavata_auth:verify_email', kwargs={
-                                'code': email_verification.verification_code}))
-                    logger.debug(
-                        "verification_uri={}".format(verification_uri))
-
-                    # TODO: need a better template, customization
-                    # TODO: add email settings documentation to settings_local.py
-                    send_mail(
-                        'Please verify your email address',
-                        "Verification link: {}".format(verification_uri),
-                        "Django Portal <pg...@gmail.com>",
-                        ["{} {} <{}>".format(first_name, last_name, email)]
-                    )
+                    _create_and_send_email_verification_link(
+                        request, username, email, first_name, last_name)
                     # TODO: success message
                     return redirect(
                         reverse('django_airavata_auth:create_account'))
@@ -174,4 +157,59 @@ def verify_email(request, code):
     except ObjectDoesNotExist as e:
         # TODO: if doesn't exist, give user a form where they can enter their
         # username to resend verification code
-        pass
+        return redirect(reverse('django_airavata_auth:resend_email_link'))
+
+
+def resend_email_link(request):
+
+    # TODO: if the user is already verified their email, then redirect to login page with message
+    if request.method == 'POST':
+        form = forms.ResendEmailVerificationLinkForm(request.POST)
+        if form.is_valid():
+            try:
+                username = form.cleaned_data['username']
+                if iam_admin_client.is_user_exist(username):
+                    user_profile = iam_admin_client.get_user(username)
+                    _create_and_send_email_verification_link(
+                        request,
+                        username,
+                        user_profile.emails[0],
+                        user_profile.firstName,
+                        user_profile.lastName)
+                # TODO: success message
+                return redirect(
+                    reverse('django_airavata_auth:resend_email_link'))
+            except Exception as e:
+                logger.exception(
+                    "Failed to resend email verification link", exc_info=e)
+                form.add_error(None, ValidationError(str(e)))
+    else:
+        form = forms.ResendEmailVerificationLinkForm()
+    return render(request, 'django_airavata_auth/verify_email.html', {
+        'form': form
+    })
+
+
+def _create_and_send_email_verification_link(
+        request, username, email, first_name, last_name):
+
+    email_verification = models.EmailVerification(
+        username=username)
+    email_verification.save()
+
+    verification_uri = request.build_absolute_uri(
+        reverse(
+            'django_airavata_auth:verify_email', kwargs={
+                'code': email_verification.verification_code}))
+    logger.debug(
+        "verification_uri={}".format(verification_uri))
+
+    # TODO: need a better template, customization
+    # TODO: add email settings documentation to
+    # settings_local.py
+    send_mail(
+        'Please verify your email address',
+        "Verification link: {}".format(verification_uri),
+        "Django Portal <pg...@gmail.com>",
+        ["{} {} <{}>".format(first_name, last_name, email)]
+    )
diff --git a/django_airavata/utils.py b/django_airavata/utils.py
index 78748c9..e730bd2 100644
--- a/django_airavata/utils.py
+++ b/django_airavata/utils.py
@@ -30,6 +30,10 @@ class ThriftConnectionException(Exception):
     pass
 
 
+class ThriftClientException(Exception):
+    pass
+
+
 def get_unsecure_transport(hostname, port):
     # Create a socket to the Airavata Server
     transport = TSocket.TSocket(hostname, port)
@@ -165,12 +169,16 @@ def get_thrift_client(host, port, is_secure, client_generator):
             yield client
         except Exception as e:
             log.exception("Thrift client error occurred")
-            raise e
+            raise ThriftClientException(
+                "Thrift client error occurred: " + str(e)) from e
         finally:
             if transport.isOpen():
                 transport.close()
                 log.debug("Thrift connection closed to {}:{}, "
                           "secure={}".format(host, port, is_secure))
+    except ThriftClientException as tce:
+        # Allow thrift client errors to bubble up
+        raise tce
     except Exception as e:
         msg = "Failed to open thrift connection to {}:{}, secure={}".format(
             host, port, is_secure)