def get_or_create_contact(contact_from_header: str, mail_from: str, alias: Alias) -> Contact: """ contact_from_header is the RFC 2047 format FROM header """ # contact_from_header can be None, use mail_from in this case instead contact_from_header = contact_from_header or mail_from # force convert header to string, sometimes contact_from_header is Header object contact_from_header = str(contact_from_header) contact_name, contact_email = parseaddr_unicode(contact_from_header) if not contact_email: # From header is wrongly formatted, try with mail_from LOG.warning("From header is empty, parse mail_from %s %s", mail_from, alias) contact_name, contact_email = parseaddr_unicode(mail_from) if not contact_email: LOG.exception( "Cannot parse contact from from_header:%s, mail_from:%s", contact_from_header, mail_from, ) contact = Contact.get_by(alias_id=alias.id, website_email=contact_email) if contact: if contact.name != contact_name: LOG.d( "Update contact %s name %s to %s", contact, contact.name, contact_name, ) contact.name = contact_name db.session.commit() else: LOG.debug( "create contact for alias %s and contact %s", alias, contact_from_header, ) reply_email = generate_reply_email() try: contact = Contact.create( user_id=alias.user_id, alias_id=alias.id, website_email=contact_email, name=contact_name, reply_email=reply_email, ) db.session.commit() except IntegrityError: LOG.warning("Contact %s %s already exist", alias, contact_email) db.session.rollback() contact = Contact.get_by(alias_id=alias.id, website_email=contact_email) return contact
async def handle_DATA(self, server, session, envelope): LOG.debug(">>> New message <<<") LOG.debug("Mail from %s", envelope.mail_from) LOG.debug("Rcpt to %s", envelope.rcpt_tos) message_data = envelope.content.decode("utf8", errors="replace") if POSTFIX_SUBMISSION_TLS: smtp = SMTP(POSTFIX_SERVER, 587) smtp.starttls() else: smtp = SMTP(POSTFIX_SERVER, 25) msg = Parser(policy=SMTPUTF8).parsestr(message_data) for rcpt_to in envelope.rcpt_tos: # Reply case # recipient starts with "reply+" or "ra+" (ra=reverse-alias) prefix if rcpt_to.startswith("reply+") or rcpt_to.startswith("ra+"): LOG.debug("Reply phase") app = new_app() with app.app_context(): return handle_reply(envelope, smtp, msg, rcpt_to) else: # Forward case LOG.debug("Forward phase") app = new_app() with app.app_context(): return handle_forward(envelope, smtp, msg, rcpt_to)
async def handle_DATA(self, server, session, envelope): LOG.debug(">>> New message <<<") LOG.debug("Mail from %s", envelope.mail_from) LOG.debug("Rcpt to %s", envelope.rcpt_tos) message_data = envelope.content.decode("utf8", errors="replace") # Only when debug # LOG.debug("Message data:\n") # LOG.debug(message_data) # host IP, setup via Docker network smtp = SMTP(POSTFIX_SERVER, 25) msg = Parser(policy=SMTPUTF8).parsestr(message_data) rcpt_to = envelope.rcpt_tos[0].lower() # Reply case # reply+ or ra+ (reverse-alias) prefix if rcpt_to.startswith("reply+") or rcpt_to.startswith("ra+"): LOG.debug("Reply phase") app = new_app() with app.app_context(): return self.handle_reply(envelope, smtp, msg) else: # Forward case LOG.debug("Forward phase") app = new_app() with app.app_context(): return self.handle_forward(envelope, smtp, msg)
def register(): if current_user.is_authenticated: LOG.d("user is already authenticated, redirect to dashboard") flash("You are already logged in", "warning") return redirect(url_for("dashboard.index")) form = RegisterForm(request.form) next_url = request.args.get("next") if form.validate_on_submit(): email = form.email.data if not can_be_used_as_personal_email(email): flash( "You cannot use this email address as your personal inbox.", "error", ) else: user = User.filter_by(email=email).first() if user: flash(f"Email {form.email.data} already exists", "warning") else: LOG.debug("create user %s", form.email.data) user = User.create( email=form.email.data.lower(), name="", password=form.password.data, ) db.session.commit() send_activation_email(user, next_url) return render_template("auth/register_waiting_activation.html") return render_template("auth/register.html", form=form, next_url=next_url)
def notify_manual_sub_end(): for manual_sub in ManualSubscription.query.all(): need_reminder = False if arrow.now().shift(days=14) > manual_sub.end_at > arrow.now().shift( days=13): need_reminder = True elif arrow.now().shift(days=4) > manual_sub.end_at > arrow.now().shift( days=3): need_reminder = True if need_reminder: user = manual_sub.user LOG.debug("Remind user %s that their manual sub is ending soon", user) send_email( user.email, f"Your subscription will end soon {user.name}", render( "transactional/manual-subscription-end.txt", name=user.name, user=user, manual_sub=manual_sub, ), render( "transactional/manual-subscription-end.html", name=user.name, user=user, manual_sub=manual_sub, ), ) extend_subscription_url = URL + "/dashboard/coinbase_checkout" for coinbase_subscription in CoinbaseSubscription.query.all(): need_reminder = False if (arrow.now().shift(days=14) > coinbase_subscription.end_at > arrow.now().shift(days=13)): need_reminder = True elif (arrow.now().shift(days=4) > coinbase_subscription.end_at > arrow.now().shift(days=3)): need_reminder = True if need_reminder: user = coinbase_subscription.user LOG.debug( "Remind user %s that their coinbase subscription is ending soon", user) send_email( user.email, "Your SimpleLogin subscription will end soon", render( "transactional/coinbase/reminder-subscription.txt", coinbase_subscription=coinbase_subscription, extend_subscription_url=extend_subscription_url, ), render( "transactional/coinbase/reminder-subscription.html", coinbase_subscription=coinbase_subscription, extend_subscription_url=extend_subscription_url, ), )
def delete_alias(alias: Alias, user: User): """ Delete an alias and add it to either global or domain trash Should be used instead of Alias.delete, DomainDeletedAlias.create, DeletedAlias.create """ # save deleted alias to either global or domain trash if alias.custom_domain_id: if not DomainDeletedAlias.get_by( email=alias.email, domain_id=alias.custom_domain_id ): LOG.debug("add %s to domain %s trash", alias, alias.custom_domain_id) db.session.add( DomainDeletedAlias( user_id=user.id, email=alias.email, domain_id=alias.custom_domain_id ) ) db.session.commit() else: if not DeletedAlias.get_by(email=alias.email): LOG.d("add %s to global trash", alias) db.session.add(DeletedAlias(email=alias.email)) db.session.commit() Alias.query.filter(Alias.id == alias.id).delete() db.session.commit()
def mfa(): # passed from login page user_id = session[MFA_USER_ID] user = User.get(user_id) if not user.enable_otp: raise Exception( "Only user with MFA enabled should go to this page. %s", user) otp_token_form = OtpTokenForm() next_url = request.args.get("next") if otp_token_form.validate_on_submit(): totp = pyotp.TOTP(user.otp_secret) token = otp_token_form.token.data if totp.verify(token): del session[MFA_USER_ID] login_user(user) flash(f"Welcome back {user.name}!") # User comes to login page from another page if next_url: LOG.debug("redirect user to %s", next_url) return redirect(next_url) else: LOG.debug("redirect user to dashboard") return redirect(url_for("dashboard.index")) else: flash("Incorrect token", "warning") return render_template("auth/mfa.html", otp_token_form=otp_token_form)
def apple_process_payment(): """ Process payment Input: receipt_data: in body (optional) is_macapp: in body Output: 200 of the payment is successful, i.e. user is upgraded to premium """ LOG.debug("request for /apple/process_payment") user = g.user data = request.get_json() receipt_data = data.get("receipt_data") is_macapp = "is_macapp" in data if is_macapp: password = MACAPP_APPLE_API_SECRET else: password = APPLE_API_SECRET apple_sub = verify_receipt(receipt_data, user, password) if apple_sub: return jsonify(ok=True), 200 return jsonify(ok=False), 400
def notify_manual_sub_end(): for manual_sub in ManualSubscription.query.all(): need_reminder = False if arrow.now().shift(days=14) > manual_sub.end_at > arrow.now().shift(days=13): need_reminder = True elif arrow.now().shift(days=4) > manual_sub.end_at > arrow.now().shift(days=3): need_reminder = True if need_reminder: user = manual_sub.user LOG.debug("Remind user %s that their manual sub is ending soon", user) send_email( user.email, f"Your trial will end soon {user.name}", render( "transactional/manual-subscription-end.txt", name=user.name, user=user, manual_sub=manual_sub, ), render( "transactional/manual-subscription-end.html", name=user.name, user=user, manual_sub=manual_sub, ), )
def create_db(): if not database_exists(DB_URI): LOG.debug("db not exist, create database") create_database(DB_URI) # Create all tables # Use flask-migrate instead of db.create_all() flask_migrate.upgrade()
def generate_oauth_client_id(client_name) -> str: oauth_client_id = convert_to_id(client_name) + "-" + random_string() # check that the client does not exist yet if not Client.get_by(oauth_client_id=oauth_client_id): LOG.debug("generate oauth_client_id %s", oauth_client_id) return oauth_client_id # Rerun the function LOG.warning("client_id %s already exists, generate a new client_id", oauth_client_id) return generate_oauth_client_id(client_name)
def auth_activate(): """ User enters the activation code to confirm their account. Input: email code Output: 200: user account is now activated, user can login now 400: wrong email, code 410: wrong code too many times """ data = request.get_json() if not data: return jsonify(error="request body cannot be empty"), 400 email = sanitize_email(data.get("email")) code = data.get("code") user = User.get_by(email=email) # do not use a different message to avoid exposing existing email if not user or user.activated: # Trigger rate limiter g.deduct_limit = True return jsonify(error="Wrong email or code"), 400 account_activation = AccountActivation.get_by(user_id=user.id) if not account_activation: # Trigger rate limiter g.deduct_limit = True return jsonify(error="Wrong email or code"), 400 if account_activation.code != code: # decrement nb tries account_activation.tries -= 1 db.session.commit() # Trigger rate limiter g.deduct_limit = True if account_activation.tries == 0: AccountActivation.delete(account_activation.id) db.session.commit() return jsonify(error="Too many wrong tries"), 410 return jsonify(error="Wrong email or code"), 400 LOG.debug("activate user %s", user) user.activated = True AccountActivation.delete(account_activation.id) db.session.commit() return jsonify(msg="Account is activated, user can login now"), 200
def handle(envelope: Envelope, smtp: SMTP) -> str: """Return SMTP status""" # unsubscribe request if UNSUBSCRIBER and envelope.rcpt_tos == [UNSUBSCRIBER]: LOG.d("Handle unsubscribe request from %s", envelope.mail_from) return handle_unsubscribe(envelope) # emails sent to sender. Probably bounce emails if SENDER and envelope.rcpt_tos == [SENDER]: LOG.d("Handle email sent to sender from %s", envelope.mail_from) return handle_sender_email(envelope) # Whether it's necessary to apply greylisting if greylisting_needed(envelope.mail_from, envelope.rcpt_tos): LOG.warning("Grey listing applied for %s %s", envelope.mail_from, envelope.rcpt_tos) return "421 SL Retry later" # result of all deliveries # each element is a couple of whether the delivery is successful and the smtp status res: [(bool, str)] = [] for rcpt_to in envelope.rcpt_tos: msg = email.message_from_bytes(envelope.original_content) # Reply case # recipient starts with "reply+" or "ra+" (ra=reverse-alias) prefix if rcpt_to.startswith("reply+") or rcpt_to.startswith("ra+"): LOG.debug(">>> Reply phase %s(%s) -> %s", envelope.mail_from, msg["From"], rcpt_to) is_delivered, smtp_status = handle_reply(envelope, smtp, msg, rcpt_to) res.append((is_delivered, smtp_status)) else: # Forward case LOG.debug( ">>> Forward phase %s(%s) -> %s", envelope.mail_from, msg["From"], rcpt_to, ) for is_delivered, smtp_status in handle_forward( envelope, smtp, msg, rcpt_to): res.append((is_delivered, smtp_status)) for (is_success, smtp_status) in res: # Consider all deliveries successful if 1 delivery is successful if is_success: return smtp_status # Failed delivery for all, return the first failure return res[0][1]
def handle_batch_import(batch_import: BatchImport): user = batch_import.user batch_import.processed = True db.session.commit() LOG.debug("Start batch import for %s %s", batch_import, user) file_url = s3.get_url(batch_import.file.path) LOG.d("Download file %s from %s", batch_import.file, file_url) r = requests.get(file_url) lines = [l.decode() for l in r.iter_lines()] reader = csv.DictReader(lines) for row in reader: try: full_alias = row["alias"].lower().strip().replace(" ", "") note = row["note"] except KeyError: LOG.warning("Cannot parse row %s", row) continue alias_domain = get_email_domain_part(full_alias) custom_domain = CustomDomain.get_by(domain=alias_domain) if ( not custom_domain or not custom_domain.verified or custom_domain.user_id != user.id ): LOG.debug("domain %s can't be used %s", alias_domain, user) continue if ( Alias.get_by(email=full_alias) or DeletedAlias.get_by(email=full_alias) or DomainDeletedAlias.get_by(email=full_alias) ): LOG.d("alias already used %s", full_alias) continue alias = Alias.create( user_id=user.id, email=full_alias, note=note, mailbox_id=user.default_mailbox_id, custom_domain_id=custom_domain.id, batch_import_id=batch_import.id, ) db.session.commit() LOG.d("Create %s", alias)
def recovery_route(): # passed from login page user_id = session.get(MFA_USER_ID) # user access this page directly without passing by login page if not user_id: flash("Unknown error, redirect back to main page", "warning") return redirect(url_for("auth.login")) user = User.get(user_id) if not user.two_factor_authentication_enabled(): flash("Only user with MFA enabled should go to this page", "warning") return redirect(url_for("auth.login")) recovery_form = RecoveryForm() next_url = request.args.get("next") if recovery_form.validate_on_submit(): code = recovery_form.code.data recovery_code = RecoveryCode.get_by(user_id=user.id, code=code) if recovery_code: if recovery_code.used: # Trigger rate limiter g.deduct_limit = True flash("Code already used", "error") else: del session[MFA_USER_ID] login_user(user) flash(f"Welcome back!", "success") recovery_code.used = True recovery_code.used_at = arrow.now() db.session.commit() # User comes to login page from another page if next_url: LOG.debug("redirect user to %s", next_url) return redirect(next_url) else: LOG.debug("redirect user to dashboard") return redirect(url_for("dashboard.index")) else: # Trigger rate limiter g.deduct_limit = True flash("Incorrect code", "error") return render_template("auth/recovery.html", recovery_form=recovery_form)
def activate(): if current_user.is_authenticated: return ( render_template("auth/activate.html", error="You are already logged in"), 400, ) code = request.args.get("code") activation_code: ActivationCode = ActivationCode.get_by(code=code) if not activation_code: # Trigger rate limiter g.deduct_limit = True return ( render_template( "auth/activate.html", error="Activation code cannot be found" ), 400, ) if activation_code.is_expired(): return ( render_template( "auth/activate.html", error="Activation code was expired", show_resend_activation=True, ), 400, ) user = activation_code.user user.activated = True login_user(user) email_utils.send_welcome_email(user) # activation code is to be used only once ActivationCode.delete(activation_code.id) db.session.commit() flash("Your account has been activated", "success") # The activation link contains the original page, for ex authorize page if "next" in request.args: next_url = request.args.get("next") LOG.debug("redirect user to %s", next_url) return redirect(next_url) else: LOG.debug("redirect user to dashboard") return redirect(url_for("dashboard.index"))
def get_user_info(self) -> dict: """return user info according to client scope Return dict with key being scope name. For now all the fields are the same for all clients: { "client": "Demo", "email": "*****@*****.**", "email_verified": true, "id": 1, "name": "Son GM", "avatar_url": "http://s3..." } """ res = { "id": self.id, "client": self.client.name, "email_verified": True, "sub": str(self.id), } for scope in self.client.get_scopes(): if scope == Scope.NAME: if self.name: res[Scope.NAME.value] = self.name else: res[Scope.NAME.value] = self.user.name elif scope == Scope.AVATAR_URL: if self.user.profile_picture_id: if self.default_avatar: res[Scope.AVATAR_URL.value] = URL + "/static/default-avatar.png" else: res[Scope.AVATAR_URL.value] = self.user.profile_picture.get_url( AVATAR_URL_EXPIRATION ) else: res[Scope.AVATAR_URL.value] = None elif scope == Scope.EMAIL: # Use generated email if self.gen_email_id: LOG.debug( "Use gen email for user %s, client %s", self.user, self.client ) res[Scope.EMAIL.value] = self.gen_email.email # Use user original email else: res[Scope.EMAIL.value] = self.user.email return res
def after_request(res): # not logging /static call if (not request.path.startswith("/static") and not request.path.startswith("/admin/static") and not request.path.startswith("/_debug_toolbar")): LOG.debug( "%s %s %s %s %s", request.remote_addr, request.method, request.path, request.args, res.status_code, ) return res
async def handle_DATA(self, server, session, envelope: Envelope): LOG.debug( "===>> New message, mail from %s, rctp tos %s ", envelope.mail_from, envelope.rcpt_tos, ) if POSTFIX_SUBMISSION_TLS: smtp = SMTP(POSTFIX_SERVER, 587) smtp.starttls() else: smtp = SMTP(POSTFIX_SERVER, 25) app = new_app() with app.app_context(): return handle(envelope, smtp)
def should_disable(alias: Alias) -> bool: """Disable an alias if it has more than 5 bounces in the last 24h""" # Bypass the bounce rule if alias.cannot_be_disabled: LOG.warning("%s cannot be disabled", alias) return False yesterday = arrow.now().shift(days=-1) nb_bounced_last_24h = ( db.session.query(EmailLog) .join(Contact, EmailLog.contact_id == Contact.id) .filter( EmailLog.bounced.is_(True), EmailLog.is_reply.is_(False), EmailLog.created_at > yesterday, ) .filter(Contact.alias_id == alias.id) .count() ) # if more than 10 bounces in 24h -> disable alias if nb_bounced_last_24h > 10: LOG.debug("more than 10 bounces in the last 24h on %s", alias) return True # if between 5-10 bounces but has bounces last week -> disable alias elif nb_bounced_last_24h > 5: one_week_ago = arrow.now().shift(days=-8) nb_bounced_7d_1d = ( db.session.query(EmailLog) .join(Contact, EmailLog.contact_id == Contact.id) .filter( EmailLog.bounced.is_(True), EmailLog.is_reply.is_(False), EmailLog.created_at > one_week_ago, EmailLog.created_at < yesterday, ) .filter(Contact.alias_id == alias.id) .count() ) if nb_bounced_7d_1d > 1: LOG.debug( "more than 5 bounces in the last 24h and more than 1 bounces in the last 7 days on %s", alias, ) return True return False
def mfa(): # passed from login page user_id = session.get(MFA_USER_ID) # user access this page directly without passing by login page if not user_id: flash("Unknown error, redirect back to main page", "warning") return redirect(url_for("auth.login")) user = User.get(user_id) if not (user and user.enable_otp): flash("Only user with MFA enabled should go to this page", "warning") return redirect(url_for("auth.login")) otp_token_form = OtpTokenForm() next_url = request.args.get("next") if otp_token_form.validate_on_submit(): totp = pyotp.TOTP(user.otp_secret) token = otp_token_form.token.data if totp.verify(token): del session[MFA_USER_ID] login_user(user) flash(f"Welcome back {user.name}!") # User comes to login page from another page if next_url: LOG.debug("redirect user to %s", next_url) return redirect(next_url) else: LOG.debug("redirect user to dashboard") return redirect(url_for("dashboard.index")) else: flash("Incorrect token", "warning") return render_template( "auth/mfa.html", otp_token_form=otp_token_form, enable_fido=(user.fido_enabled()), )
def login(): next_url = request.args.get("next") if current_user.is_authenticated: if next_url: LOG.debug("user is already authenticated, redirect to %s", next_url) return redirect(next_url) else: LOG.d("user is already authenticated, redirect to dashboard") return redirect(url_for("dashboard.index")) form = LoginForm(request.form) show_resend_activation = False if form.validate_on_submit(): user = User.filter_by(email=sanitize_email(form.email.data)).first() if not user or not user.check_password(form.password.data): # Trigger rate limiter g.deduct_limit = True form.password.data = None flash("Email or password incorrect", "error") elif user.disabled: flash( "Your account is disabled. Please contact SimpleLogin team to re-enable your account.", "error", ) elif not user.activated: show_resend_activation = True flash( "Please check your inbox for the activation email. You can also have this email re-sent", "error", ) else: return after_login(user, next_url) return render_template( "auth/login.html", form=form, next_url=next_url, show_resend_activation=show_resend_activation, )
def auth_register(): """ User signs up - will need to activate their account with an activation code. Input: email password Output: 200: user needs to confirm their account """ data = request.get_json() if not data: return jsonify(error="request body cannot be empty"), 400 email = data.get("email").strip().lower() password = data.get("password") if DISABLE_REGISTRATION: return jsonify(error="registration is closed"), 400 if not email_domain_can_be_used_as_mailbox(email) or personal_email_already_used( email ): return jsonify(error=f"cannot use {email} as personal inbox"), 400 if not password or len(password) < 8: return jsonify(error="password too short"), 400 LOG.debug("create user %s", email) user = User.create(email=email, name="", password=password) db.session.flush() # create activation code code = "".join([str(random.randint(0, 9)) for _ in range(6)]) AccountActivation.create(user_id=user.id, code=code) db.session.commit() send_email( email, f"Just one more step to join SimpleLogin", render("transactional/code-activation.txt", code=code), render("transactional/code-activation.html", code=code), ) return jsonify(msg="User needs to confirm their account"), 200
def get_or_create_forward_email( website_from_header: str, gen_email: GenEmail ) -> ForwardEmail: """ website_from_header can be the full-form email, i.e. "First Last <*****@*****.**>" """ website_email = get_email_part(website_from_header) forward_email = ForwardEmail.get_by( gen_email_id=gen_email.id, website_email=website_email ) if forward_email: # update the website_from if needed if forward_email.website_from != website_from_header: LOG.d("Update From header for %s", forward_email) forward_email.website_from = website_from_header db.session.commit() else: LOG.debug( "create forward email for alias %s and website email %s", gen_email, website_from_header, ) # generate a reply_email, make sure it is unique # not use while loop to avoid infinite loop reply_email = f"reply+{random_string(30)}@{EMAIL_DOMAIN}" for _ in range(1000): if not ForwardEmail.get_by(reply_email=reply_email): # found! break reply_email = f"reply+{random_string(30)}@{EMAIL_DOMAIN}" forward_email = ForwardEmail.create( gen_email_id=gen_email.id, website_email=website_email, website_from=website_from_header, reply_email=reply_email, ) db.session.commit() return forward_email
def register(): if current_user.is_authenticated: LOG.d("user is already authenticated, redirect to dashboard") flash("You are already logged in", "warning") return redirect(url_for("dashboard.index")) if config.DISABLE_REGISTRATION: flash("Registration is closed", "error") return redirect(url_for("auth.login")) form = RegisterForm(request.form) next_url = request.args.get("next") if form.validate_on_submit(): email = form.email.data.strip().lower() if not email_domain_can_be_used_as_mailbox(email): flash("You cannot use this email address as your personal inbox.", "error") else: if personal_email_already_used(email): flash(f"Email {email} already used", "error") else: LOG.debug("create user %s", form.email.data) user = User.create( email=email, name="", password=form.password.data, referral=get_referral(), ) db.session.commit() try: send_activation_email(user, next_url) except: flash("Invalid email, are you sure the email is correct?", "error") return redirect(url_for("auth.register")) return render_template("auth/register_waiting_activation.html") return render_template("auth/register.html", form=form, next_url=next_url)
def generate_email(scheme: int = AliasGeneratorEnum.word.value, in_hex: bool = False) -> str: """generate an email address that does not exist before :param scheme: int, value of AliasGeneratorEnum, indicate how the email is generated :type in_hex: bool, if the generate scheme is uuid, is hex favorable? """ if scheme == AliasGeneratorEnum.uuid.value: name = uuid.uuid4().hex if in_hex else uuid.uuid4().__str__() random_email = name + "@" + EMAIL_DOMAIN else: random_email = random_words() + "@" + EMAIL_DOMAIN # check that the client does not exist yet if not GenEmail.get_by(email=random_email) and not DeletedAlias.get_by( email=random_email): LOG.debug("generate email %s", random_email) return random_email # Rerun the function LOG.warning("email %s already exists, generate a new email", random_email) return generate_email(scheme=scheme, in_hex=in_hex)
def after_login(user, next_url): """ Redirect to the correct page after login. If user enables MFA: redirect user to MFA page Otherwise redirect to dashboard page if no next_url """ if user.fido_enabled(): # Use the same session for FIDO so that we can easily # switch between these two 2FA option session[MFA_USER_ID] = user.id if next_url: return redirect(url_for("auth.fido", next=next_url)) else: return redirect(url_for("auth.fido")) elif user.enable_otp: session[MFA_USER_ID] = user.id if next_url: return redirect(url_for("auth.mfa", next=next_url)) else: return redirect(url_for("auth.mfa")) else: LOG.debug("log user %s in", user) login_user(user) # User comes to login page from another page if next_url: LOG.debug("redirect user to %s", next_url) return redirect(next_url) else: LOG.debug("redirect user to dashboard") return redirect(url_for("dashboard.index"))
def enter_sudo(): password_check_form = LoginForm() if password_check_form.validate_on_submit(): password = password_check_form.password.data if current_user.check_password(password): session["sudo_time"] = int(time()) # User comes to sudo page from another page next_url = request.args.get("next") if next_url: LOG.debug("redirect user to %s", next_url) return redirect(next_url) else: LOG.debug("redirect user to dashboard") return redirect(url_for("dashboard.index")) else: flash("Incorrect password", "warning") return render_template("dashboard/enter_sudo.html", password_check_form=password_check_form)
def batch_import_route(): # only for users who have custom domains if not current_user.verified_custom_domains(): flash("Alias batch import is only available for custom domains", "warning") batch_imports = BatchImport.query.filter_by(user_id=current_user.id).all() if request.method == "POST": alias_file = request.files["alias-file"] file_path = random_string(20) + ".csv" file = File.create(user_id=current_user.id, path=file_path) s3.upload_from_bytesio(file_path, alias_file) db.session.flush() LOG.d("upload file %s to s3 at %s", file, file_path) bi = BatchImport.create(user_id=current_user.id, file_id=file.id) db.session.flush() LOG.debug("Add a batch import job %s for %s", bi, current_user) # Schedule batch import job Job.create( name=JOB_BATCH_IMPORT, payload={"batch_import_id": bi.id}, run_at=arrow.now(), ) db.session.commit() flash( "The file has been uploaded successfully and the import will start shortly", "success", ) return redirect(url_for("dashboard.batch_import_route")) return render_template("dashboard/batch_import.html", batch_imports=batch_imports)
def login(): if current_user.is_authenticated: LOG.d("user is already authenticated, redirect to dashboard") return redirect(url_for("dashboard.index")) form = LoginForm(request.form) next_url = request.args.get("next") show_resend_activation = False if form.validate_on_submit(): user = User.filter_by(email=form.email.data).first() if not user: flash("Email or password incorrect", "error") elif not user.check_password(form.password.data): flash("Email or password incorrect", "error") elif not user.activated: show_resend_activation = True flash( "Please check your inbox for the activation email. You can also have this email re-sent", "error", ) else: LOG.debug("log user %s in", user) login_user(user) # User comes to login page from another page if next_url: LOG.debug("redirect user to %s", next_url) return redirect(next_url) else: LOG.debug("redirect user to dashboard") return redirect(url_for("dashboard.index")) return render_template( "auth/login.html", form=form, next_url=next_url, show_resend_activation=show_resend_activation, )