docs: def auth(view, **kwargs):
      """
      This plugin allow user to login to application
  
      kwargs:
          - signin_view
          - signout_view
          - template_dir
          - menu:
              - name
              - group_name
              - ...
  
          @plugin(user.login, model=model.User)
          class MyAccount(Juice):
              pass
  
      """
  
      endpoint_namespace = view.__name__ + ":%s"
      view_name = view.__name__
      UserModel = kwargs.pop("model")
      User = UserModel.User
  
      login_view = endpoint_namespace % "login"
      on_signin_view = kwargs.get("signin_view", "Index:index")
      on_signout_view = kwargs.get("signout_view", "Index:index")
      template_dir = kwargs.get("template_dir", "Juice/Plugin/User/Account")
      template_page = template_dir + "/%s.html"
  
      login_manager = LoginManager()
      login_manager.login_view = login_view
      login_manager.login_message_category = "error"
      init_app(login_manager.init_app)
  
      menu_context = view
      _menu = kwargs.get("menu", {})
      if _menu:
          @menu(**_menu)
          class UserAccountMenu(object): pass
          menu_context = UserAccountMenu
  
      @login_manager.user_loader
      def load_user(userid):
          return User.get(userid)
  
      View.g(__USER_AUTH_ENABLED__=True)
  
      class Auth(object):
          decorators = view.decorators + [login_required]
  
          SESSION_KEY_SET_EMAIL_DATA = "set_email_tmp_data"
          TEMP_DATA_KEY = "login_tmp_data"
  
          @property
          def tmp_data(self):
              return session[self.TEMP_DATA_KEY]
  
          @tmp_data.setter
          def tmp_data(self, data):
              session[self.TEMP_DATA_KEY] = data
  
          def _login_enabled(self):
              if self.get_config("USER_AUTH_ALLOW_LOGIN") is not True:
                  abort("UserLoginDisabledError")
  
          def _signup_enabled(self):
              if self.get_config("USER_AUTH_ALLOW_SIGNUP") is not True:
                  abort("UserSignupDisabledError")
  
          def _oauth_enabled(self):
              if self.get_config("USER_AUTH_ALLOW_OAUTH") is not True:
                  abort("UserOAuthDisabledError")
  
          def _send_reset_password(self, user):
              delivery = self.get_config("USER_AUTH_PASSWORD_RESET_METHOD")
              token_reset_ttl = self.get_config("USER_AUTH_TOKEN_RESET_TTL", 60)
              new_password = None
              if delivery.upper() == "TOKEN":
                  token = user.set_temp_login(token_reset_ttl)
                  url = url_for(endpoint_namespace % "reset_password",
                                token=token,
                                _external=True)
              else:
                  new_password = user.set_password(password=None, random=True)
                  url = url_for(endpoint_namespace % "login", _external=True)
  
              mail.send(template="reset-password.txt",
                           method_=delivery,
                           to=user.email,
                           name=user.email,
                           url=url,
                           new_password=new_password)
  
  
          @classmethod
          def login_user(cls, user):
              login_user(user)
              now = datetime.datetime.now()
              user.update(last_login=now, last_visited=now)
  
          @menu("Login",
                endpoint=endpoint_namespace % "login",
                visible_with_auth_user=False,
                extends=menu_context)
          @template(template_page % "login",
                    endpoint_namespace=endpoint_namespace)
          @route("login/",
                 methods=["GET", "POST"],
                 endpoint=endpoint_namespace % "login")
          @no_login_required
          def login(self):
              """ Login page """
  
              self._login_enabled()
              logout_user()
              self.tmp_data = None
              self.meta_tags(title="Login")
  
              if request.method == "POST":
                  email = request.form.get("email").strip()
                  password = request.form.get("password").strip()
  
                  if not email or not password:
                      flash("Email or Password is empty", "error")
                      return redirect(url_for(login_view, next=request.form.get("next")))
  
                  user = User.get_by_email(email)
                  if user and user.password_hash and user.password_matched(password):
                      self.login_user(user)
                      return redirect(request.form.get("next") or url_for(on_signin_view))
                  else:
                      flash("Email or Password is invalid", "error")
                      return redirect(url_for(login_view, next=request.form.get("next")))
  
              return dict(login_url_next=request.args.get("next", ""),
                          login_url_default=url_for(on_signin_view),
                          signup_enabled=self.get_config("USER_AUTH_ALLOW_SIGNUP"),
                          oauth_enabled=self.get_config("USER_AUTH_ALLOW_LOGIN"))
  
          @menu("Logout",
                endpoint=endpoint_namespace % "logout",
                visible_with_auth_user=True,
                order=100,
                extends=menu_context)
          @route("logout/",
                 endpoint=endpoint_namespace % "logout")
          @no_login_required
          def logout(self):
              logout_user()
              return redirect(url_for(on_signout_view or login_view))
  
          @menu("Signup",
                endpoint=endpoint_namespace % "signup",
                visible_with_auth_user=False,
                extends=menu_context)
          @template(template_page % "signup",
                    endpoint_namespace=endpoint_namespace)
          @route("signup/",
                 methods=["GET", "POST"],
                 endpoint=endpoint_namespace % "signup")
          @no_login_required
          def signup(self):
              """
              For Email Signup
              :return:
              """
              self._login_enabled()
              self._signup_enabled()
              self.meta_tags(title="Signup")
  
              if request.method == "POST":
                  # reCaptcha
                  if not recaptcha.verify():
                      flash("Invalid Security code", "error")
                      return redirect(url_for(endpoint_namespace % "signup",
                                              next=request.form.get("next")))
                  try:
                      name = request.form.get("name")
                      email = request.form.get("email")
                      password = request.form.get("password")
                      password2 = request.form.get("password2")
                      profile_image_url = request.form.get("profile_image_url", None)
  
                      if not name:
                          raise UserError("Name is required")
                      elif not utils.is_valid_email(email):
                          raise UserError("Invalid email address '%s'" % email)
                      elif not password.strip() or password.strip() != password2.strip():
                          raise UserError("Passwords don't match")
                      elif not utils.is_valid_password(password):
                          raise UserError("Invalid password")
                      else:
                          new_account = User.new(email=email,
                                          password=password.strip(),
                                          first_name=name,
                                          profile_image_url=profile_image_url,
                                          signup_method="email")
  
                          self.login_user(new_account)
                          return redirect(request.form.get("next") or url_for(on_signin_view))
                  except ApplicationError as ex:
                      flash(ex.message, "error")
                  return redirect(url_for(endpoint_namespace % "signup",
                                          next=request.form.get("next")))
  
              logout_user()
              return dict(login_url_next=request.args.get("next", ""))
  
          @route("lost-password/",
                 methods=["GET", "POST"],
                 endpoint=endpoint_namespace % "lost_password")
          @template(template_page % "lost_password",
                    endpoint_namespace=endpoint_namespace)
          @no_login_required
          def lost_password(self):
              self._login_enabled()
              logout_user()
  
              self.meta_tags(title="Lost Password")
  
              if request.method == "POST":
                  email = request.form.get("email")
                  user = User.get_by_email(email)
                  if user:
                      self._send_reset_password(user)
                      flash("A new password has been sent to '%s'" % email, "success")
                  else:
                      flash("Invalid email address", "error")
                  return redirect(url_for(login_view))
              else:
                  return {}
  
  
          @menu("Account Settings",
                endpoint=endpoint_namespace % "account_settings",
                order=99,
                visible_with_auth_user=True,
                extends=menu_context)
          @template(template_page % "account_settings",
                    endpoint_namespace=endpoint_namespace)
          @route("account-settings",
                 methods=["GET", "POST"],
                 endpoint=endpoint_namespace % "account_settings")
          @fresh_login_required
          def account_settings(self):
              self.meta_tags(title="Account Settings")
  
              if request.method == "POST":
                  action = request.form.get("action")
                  try:
                      action = action.lower()
                      #
                      if action == "info":
                          first_name = request.form.get("first_name").strip()
                          last_name = request.form.get("last_name", "").strip()
  
                          data = {
                              "first_name": first_name,
                              "last_name": last_name
                          }
                          current_user.update(**data)
                          flash("Account info updated successfully!", "success")
                      #
                      elif action == "login":
                          confirm_password = request.form.get("confirm-password").strip()
                          if current_user.password_matched(confirm_password):
                              self.change_login_handler()
                              flash("Login Info updated successfully!", "success")
                          else:
                              flash("Invalid password", "error")
                      #
                      elif action == "password":
                          confirm_password = request.form.get("confirm-password").strip()
                          if current_user.password_matched(confirm_password):
                              self.change_password_handler()
                              flash("Password updated successfully!", "success")
                          else:
                              flash("Invalid password", "error")
  
                      elif action == "profile-photo":
                          file = request.files.get("file")
                          if file:
                              prefix = "profile-photos/%s/" % current_user.id
                              extensions = ["jpg", "jpeg", "png", "gif"]
                              my_photo = storage.upload(file,
                                                        prefix=prefix,
                                                        allowed_extensions=extensions)
                              if my_photo:
                                  url = my_photo.url
                                  current_user.update(profile_image_url=url)
                                  flash("Profile Image updated successfully!", "success")
                      else:
                          raise UserError("Invalid action")
  
                  except Exception as e:
                      flash(e.message, "error")
  
                  return redirect(url_for(endpoint_namespace % "account_settings"))
  
              return {}
  
          @classmethod
          def change_login_handler(cls, user_context=None, email=None):
              if not user_context:
                  user_context = current_user
              if not email:
                  email = request.form.get("email").strip()
  
              if not utils.is_valid_email(email):
                  raise UserWarning("Invalid email address '%s'" % email)
              else:
                  if email != user_context.email and User.get_by_email(email):
                      raise UserWarning("Email exists already '%s'" % email)
                  elif email != user_context.email:
                      user_context.update(email=email)
                      return True
              return False
  
          @classmethod
          def change_password_handler(cls, user_context=None, password=None,
                                      password2=None):
              if not user_context:
                  user_context = current_user
              if not password:
                  password = request.form.get("password").strip()
              if not password2:
                  password2 = request.form.get("password2").strip()
  
              if password:
                  if password != password2:
                      raise UserWarning("Password don't match")
                  elif not utils.is_valid_password(password):
                      raise UserWarning("Invalid password")
                  else:
                      user_context.set_password(password)
                      return True
              else:
                  raise UserWarning("Password is empty")
  
  
          # OAUTH Login
          @route("oauth-login/<provider>",
                 methods=["GET", "POST"],
                 endpoint=endpoint_namespace % "oauth_login")
          @template(template_page % "oauth_login",
                    endpoint_namespace=endpoint_namespace)
          @no_login_required
          def oauth_login(self, provider):
              """ Login via oauth providers """
  
              self._login_enabled()
              self._oauth_enabled()
  
              provider = provider.lower()
              result = oauth.login(provider)
              response = oauth.response
              popup_js_custom = {
                  "action": "",
                  "url": ""
              }
  
              if result:
                  if result.error:
                      pass
  
                  elif result.user:
                      result.user.update()
  
                      oauth_user = result.user
                      user = User.get_by_oauth(provider=provider,
                                               provider_user_id=oauth_user.id)
                      if not user:
                          if oauth_user.email and User.get_by_email(oauth_user.email):
                              flash("Account already exists with this email '%s'. "
                                          "Try to login or retrieve your password " % oauth_user.email, "error")
  
                              popup_js_custom.update({
                                  "action": "redirect",
                                  "url": url_for(login_view, next=request.form.get("next"))
                              })
  
                          else:
                              tmp_data = {
                                  "is_oauth": True,
                                  "provider": provider,
                                  "id": oauth_user.id,
                                  "name": oauth_user.name,
                                  "picture": oauth_user.picture,
                                  "first_name": oauth_user.first_name,
                                  "last_name": oauth_user.last_name,
                                  "email": oauth_user.email,
                                  "link": oauth_user.link
                              }
                              if not oauth_user.email:
                                  self.tmp_data = tmp_data
  
                                  popup_js_custom.update({
                                      "action": "redirect",
                                      "url": url_for(endpoint_namespace % "setup_login")
                                  })
  
                              else:
                                  try:
                                      picture = oauth_user.picture
                                      user = User.new(email=oauth_user.email,
                                                      name=oauth_user.name,
                                                      signup_method=provider,
                                                      profile_image_url=picture
                                                      )
                                      user.add_oauth(provider,
                                                     oauth_user.provider_id,
                                                     name=oauth_user.name,
                                                     email=oauth_user.email,
                                                     profile_image_url=oauth_user.picture,
                                                     link=oauth_user.link)
                                  except ModelError as e:
                                      flash(e.message, "error")
                                      popup_js_custom.update({
                                          "action": "redirect",
                                          "url": url_for(endpoint_namespace % "login")
                                      })
                      if user:
                          self.login_user(user)
  
                      return dict(popup_js=result.popup_js(custom=popup_js_custom),
                                  template_=template_page % "oauth_login")
              return response
  
          @template(template_page % "setup_login",
                    endpoint_namespace=endpoint_namespace)
          @route("setup-login/", methods=["GET", "POST"], 
                 endpoint=endpoint_namespace % "setup_login")
          def setup_login(self):
              """
              Allows to setup a email password if it's not provided specially
              coming from oauth-login
              :return:
              """
              self._login_enabled()
              self.meta_tags(title="Setup  Login")
  
              # Only user without email can set email
              if current_user.is_authenticated() and current_user.email:
                  return redirect(url_for(endpoint_namespace % "account_settings"))
  
              if self.tmp_data:
                  if request.method == "POST":
                      if not self.tmp_data["is_oauth"]:
                          return redirect(endpoint_namespace % "login")
  
                      try:
                          email = request.form.get("email")
                          password = request.form.get("password")
                          password2 = request.form.get("password2")
  
                          if not utils.is_valid_email(email):
                              raise UserError("Invalid email address '%s'" % email)
                          elif User.get_by_email(email):
                              raise UserError("An account exists already with this email address '%s' " % email)
                          elif not password.strip() or password.strip() != password2.strip():
                              raise UserError("Passwords don't match")
                          elif not utils.is_valid_password(password):
                              raise UserError("Invalid password")
                          else:
                              user = User.new(email=email,
                                              password=password.strip(),
                                              name=self.tmp_data["name"],
                                              profile_image_url=self.tmp_data["picture"],
                                              signup_method=self.tmp_data["provider"])
  
                              user.add_oauth(self.tmp_data["provider"],
                                             self.tmp_data["id"],
                                             name=self.tmp_data["name"],
                                             email=email,
                                             profile_image_url=self.tmp_data["picture"],
                                             link=self.tmp_data["link"])
  
                              self.login_user(user)
                              self.tmp_data = None
  
                          return redirect(request.form.get("next") or url_for(on_signin_view))
                      except ApplicationError as ex:
                          flash(ex.message, "error")
                          return redirect(url_for(endpoint_namespace % "login"))
  
                  return dict(provider=self.tmp_data)
  
              else:
                  return redirect(url_for(endpoint_namespace % "login"))
  
          @route("reset-password/<token>",
                 methods=["GET", "POST"],
                 endpoint=endpoint_namespace % "reset_password")
          @template(template_page % "reset_password",
                    endpoint_namespace=endpoint_namespace)
          @no_login_required
          def reset_password(self, token):
              self._login_enabled()
              logout_user()
  
              self.meta_tags(title="Reset Password")
              user = User.get_by_temp_login(token)
              if user:
                  if not user.has_temp_login:
                      return redirect(url_for(on_signin_view))
                  if request.method == "POST":
                      try:
                          self.change_password_handler(user_context=user)
                          user.clear_temp_login()
                          flash("Password updated successfully!", "success")
                          return redirect(url_for(on_signin_view))
                      except Exception as ex:
                          flash("Error: %s" % ex.message, "error")
                          return redirect(url_for(endpoint_namespace % "reset_password",
                                                  token=token))
                  else:
                      return dict(token=token)
              else:
                  abort(404, "Invalid token")
  
          @route("oauth-connect", methods=["POST"], 
                 endpoint="%s:oauth_connect" % endpoint_namespace)
          def oauth_connect(self):
              """ To login via social """
              email = request.form.get("email").strip()
              name = request.form.get("name").strip()
              provider = request.form.get("provider").strip()
              provider_user_id = request.form.get("provider_user_id").strip()
              image_url = request.form.get("image_url").strip()
              next = request.form.get("next", "")
              try:
                  current_user.oauth_connect(provider=provider,
                                           provider_user_id=provider_user_id,
                                           email=email,
                                           name=name,
                                           image_url=image_url)
              except Exception as ex:
                  flash("Unable to link your account", "error")
  
              return redirect(url_for(endpoint_namespace % "account_settings"))
  
      return Auth