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(Webmaster): 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", "Webmaster/Package/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) 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 _package_conf(self): return self.get_config("USER_AUTH", {}) @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._package_conf.get("enable_login") is not True: abort("UserLoginDisabledError") def _signup_enabled(self): if self._package_conf.get("enable_signup") is not True: abort("UserSignupDisabledError") def _oauth_enabled(self): if self._package_conf.get("enable_oauth") is not True: abort("UserOAuthDisabledError") @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=user_not_authenticated, 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._package_conf.get("enable_signup"), oauth_enabled=self._package_conf.get("enable_oauth_login")) @menu("Logout", endpoint=endpoint_namespace % "logout", visible=user_authenticated, 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=[user_not_authenticated], 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: delivery = self._package_conf.get("password_reset_method") new_password = None if delivery.upper() == "TOKEN": token = user.set_temp_login() 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) mailer.send_template("reset-password.txt", method_=delivery, to=user.email, name=user.email, url=url, new_password=new_password) 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=user_authenticated, 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