def setting(): form = SettingForm() promo_form = PromoCodeForm() change_email_form = ChangeEmailForm() email_change = EmailChange.get_by(user_id=current_user.id) if email_change: pending_email = email_change.new_email else: pending_email = None if request.method == "POST": if request.form.get("form-name") == "update-email": if change_email_form.validate(): # whether user can proceed with the email update new_email_valid = True if (sanitize_email(change_email_form.email.data) != current_user.email and not pending_email): new_email = sanitize_email(change_email_form.email.data) # check if this email is not already used if personal_email_already_used(new_email) or Alias.get_by( email=new_email): flash(f"Email {new_email} already used", "error") new_email_valid = False elif not email_can_be_used_as_mailbox(new_email): flash( "You cannot use this email address as your personal inbox.", "error", ) new_email_valid = False # a pending email change with the same email exists from another user elif EmailChange.get_by(new_email=new_email): other_email_change: EmailChange = EmailChange.get_by( new_email=new_email) LOG.warning( "Another user has a pending %s with the same email address. Current user:%s", other_email_change, current_user, ) if other_email_change.is_expired(): LOG.d("delete the expired email change %s", other_email_change) EmailChange.delete(other_email_change.id) db.session.commit() else: flash( "You cannot use this email address as your personal inbox.", "error", ) new_email_valid = False if new_email_valid: email_change = EmailChange.create( user_id=current_user.id, code=random_string( 60), # todo: make sure the code is unique new_email=new_email, ) db.session.commit() send_change_email_confirmation(current_user, email_change) flash( "A confirmation email is on the way, please check your inbox", "success", ) return redirect(url_for("dashboard.setting")) if request.form.get("form-name") == "update-profile": if form.validate(): profile_updated = False # update user info if form.name.data != current_user.name: current_user.name = form.name.data db.session.commit() profile_updated = True if form.profile_picture.data: file_path = random_string(30) file = File.create(user_id=current_user.id, path=file_path) s3.upload_from_bytesio( file_path, BytesIO(form.profile_picture.data.read())) db.session.flush() LOG.d("upload file %s to s3", file) current_user.profile_picture_id = file.id db.session.commit() profile_updated = True if profile_updated: flash("Your profile has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "change-password": flash( "You are going to receive an email containing instructions to change your password", "success", ) send_reset_password_email(current_user) return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "notification-preference": choose = request.form.get("notification") if choose == "on": current_user.notification = True else: current_user.notification = False db.session.commit() flash("Your notification preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "delete-account": sub: Subscription = current_user.get_subscription() # user who has canceled can also re-subscribe if sub and not sub.cancelled: flash("Please cancel your current subscription first", "warning") return redirect(url_for("dashboard.setting")) # Schedule delete account job LOG.warning("schedule delete account job for %s", current_user) Job.create( name=JOB_DELETE_ACCOUNT, payload={"user_id": current_user.id}, run_at=arrow.now(), commit=True, ) flash( "Your account deletion has been scheduled. " "You'll receive an email when the deletion is finished", "success", ) return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "change-alias-generator": scheme = int(request.form.get("alias-generator-scheme")) if AliasGeneratorEnum.has_value(scheme): current_user.alias_generator = scheme db.session.commit() flash("Your preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get( "form-name") == "change-random-alias-default-domain": default_domain = request.form.get("random-alias-default-domain") if default_domain: sl_domain: SLDomain = SLDomain.get_by(domain=default_domain) if sl_domain: if sl_domain.premium_only and not current_user.is_premium( ): flash("You cannot use this domain", "error") return redirect(url_for("dashboard.setting")) current_user.default_alias_public_domain_id = sl_domain.id current_user.default_alias_custom_domain_id = None else: custom_domain = CustomDomain.get_by(domain=default_domain) if custom_domain: # sanity check if (custom_domain.user_id != current_user.id or not custom_domain.verified): LOG.exception("%s cannot use domain %s", current_user, default_domain) else: current_user.default_alias_custom_domain_id = ( custom_domain.id) current_user.default_alias_public_domain_id = None else: current_user.default_alias_custom_domain_id = None current_user.default_alias_public_domain_id = None db.session.commit() flash("Your preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "random-alias-suffix": scheme = int(request.form.get("random-alias-suffix-generator")) if AliasSuffixEnum.has_value(scheme): current_user.random_alias_suffix = scheme db.session.commit() flash("Your preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "change-sender-format": sender_format = int(request.form.get("sender-format")) if SenderFormatEnum.has_value(sender_format): current_user.sender_format = sender_format current_user.sender_format_updated_at = arrow.now() db.session.commit() flash("Your sender format preference has been updated", "success") db.session.commit() return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "replace-ra": choose = request.form.get("replace-ra") if choose == "on": current_user.replace_reverse_alias = True else: current_user.replace_reverse_alias = False db.session.commit() flash("Your preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "sender-in-ra": choose = request.form.get("enable") if choose == "on": current_user.include_sender_in_reverse_alias = True else: current_user.include_sender_in_reverse_alias = False db.session.commit() flash("Your preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "expand-alias-info": choose = request.form.get("enable") if choose == "on": current_user.expand_alias_info = True else: current_user.expand_alias_info = False db.session.commit() flash("Your preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "export-data": return redirect(url_for("api.export_data")) elif request.form.get("form-name") == "export-alias": return redirect(url_for("api.export_aliases")) manual_sub = ManualSubscription.get_by(user_id=current_user.id) apple_sub = AppleSubscription.get_by(user_id=current_user.id) coinbase_sub = CoinbaseSubscription.get_by(user_id=current_user.id) return render_template( "dashboard/setting.html", form=form, PlanEnum=PlanEnum, SenderFormatEnum=SenderFormatEnum, promo_form=promo_form, change_email_form=change_email_form, pending_email=pending_email, AliasGeneratorEnum=AliasGeneratorEnum, manual_sub=manual_sub, apple_sub=apple_sub, coinbase_sub=coinbase_sub, FIRST_ALIAS_DOMAIN=FIRST_ALIAS_DOMAIN, ALIAS_RAND_SUFFIX_LENGTH=ALIAS_RANDOM_SUFFIX_LENGTH, )
def setting(): form = SettingForm() promo_form = PromoCodeForm() change_email_form = ChangeEmailForm() email_change = EmailChange.get_by(user_id=current_user.id) if email_change: pending_email = email_change.new_email else: pending_email = None if request.method == "POST": if request.form.get("form-name") == "update-email": if change_email_form.validate(): # whether user can proceed with the email update new_email_valid = True if (change_email_form.email.data.lower().strip() != current_user.email and not pending_email): new_email = change_email_form.email.data.strip().lower() # check if this email is not already used if personal_email_already_used(new_email) or Alias.get_by( email=new_email): flash(f"Email {new_email} already used", "error") new_email_valid = False elif not email_can_be_used_as_mailbox(new_email): flash( "You cannot use this email address as your personal inbox.", "error", ) new_email_valid = False # a pending email change with the same email exists from another user elif EmailChange.get_by(new_email=new_email): other_email_change: EmailChange = EmailChange.get_by( new_email=new_email) LOG.warning( "Another user has a pending %s with the same email address. Current user:%s", other_email_change, current_user, ) if other_email_change.is_expired(): LOG.d("delete the expired email change %s", other_email_change) EmailChange.delete(other_email_change.id) db.session.commit() else: flash( "You cannot use this email address as your personal inbox.", "error", ) new_email_valid = False if new_email_valid: email_change = EmailChange.create( user_id=current_user.id, code=random_string( 60), # todo: make sure the code is unique new_email=new_email, ) db.session.commit() send_change_email_confirmation(current_user, email_change) flash( "A confirmation email is on the way, please check your inbox", "success", ) return redirect(url_for("dashboard.setting")) if request.form.get("form-name") == "update-profile": if form.validate(): profile_updated = False # update user info if form.name.data != current_user.name: current_user.name = form.name.data db.session.commit() profile_updated = True if form.profile_picture.data: file_path = random_string(30) file = File.create(user_id=current_user.id, path=file_path) s3.upload_from_bytesio( file_path, BytesIO(form.profile_picture.data.read())) db.session.flush() LOG.d("upload file %s to s3", file) current_user.profile_picture_id = file.id db.session.commit() profile_updated = True if profile_updated: flash("Your profile has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "change-password": flash( "You are going to receive an email containing instructions to change your password", "success", ) send_reset_password_email(current_user) return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "notification-preference": choose = request.form.get("notification") if choose == "on": current_user.notification = True else: current_user.notification = False db.session.commit() flash("Your notification preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "delete-account": LOG.warning("Delete account %s", current_user) User.delete(current_user.id) db.session.commit() flash("Your account has been deleted", "success") logout_user() return redirect(url_for("auth.register")) elif request.form.get("form-name") == "change-alias-generator": scheme = int(request.form.get("alias-generator-scheme")) if AliasGeneratorEnum.has_value(scheme): current_user.alias_generator = scheme db.session.commit() flash("Your preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get( "form-name") == "change-random-alias-default-domain": default_domain = request.form.get("random-alias-default-domain") if default_domain: sl_domain: SLDomain = SLDomain.get_by(domain=default_domain) if sl_domain: if sl_domain.premium_only and not current_user.is_premium( ): flash("You cannot use this domain", "error") return redirect(url_for("dashboard.setting")) # make sure only default_random_alias_domain_id or default_random_alias_public_domain_id is set current_user.default_random_alias_public_domain_id = sl_domain.id current_user.default_random_alias_domain_id = None else: custom_domain = CustomDomain.get_by(domain=default_domain) if custom_domain: # sanity check if (custom_domain.user_id != current_user.id or not custom_domain.verified): LOG.exception("%s cannot use domain %s", current_user, default_domain) else: # make sure only default_random_alias_domain_id or # default_random_alias_public_domain_id is set current_user.default_random_alias_domain_id = ( custom_domain.id) current_user.default_random_alias_public_domain_id = None else: current_user.default_random_alias_domain_id = None current_user.default_random_alias_public_domain_id = None db.session.commit() flash("Your preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "change-sender-format": sender_format = int(request.form.get("sender-format")) if SenderFormatEnum.has_value(sender_format): current_user.sender_format = sender_format db.session.commit() flash("Your sender format preference has been updated", "success") db.session.commit() return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "replace-ra": choose = request.form.get("replace-ra") if choose == "on": current_user.replace_reverse_alias = True else: current_user.replace_reverse_alias = False db.session.commit() flash("Your preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "sender-in-ra": choose = request.form.get("enable") if choose == "on": current_user.include_sender_in_reverse_alias = True else: current_user.include_sender_in_reverse_alias = False db.session.commit() flash("Your preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "export-data": data = { "email": current_user.email, "name": current_user.name, "aliases": [], "apps": [], "custom_domains": [], } for alias in Alias.filter_by( user_id=current_user.id).all(): # type: Alias data["aliases"].append( dict(email=alias.email, enabled=alias.enabled)) for custom_domain in CustomDomain.filter_by( user_id=current_user.id).all(): data["custom_domains"].append(custom_domain.domain) for app in Client.filter_by( user_id=current_user.id): # type: Client data["apps"].append( dict(name=app.name, home_url=app.home_url, published=app.published)) return Response( json.dumps(data), mimetype="text/json", headers={ "Content-Disposition": "attachment;filename=data.json" }, ) elif request.form.get("form-name") == "export-alias": data = [["alias", "note", "enabled"]] for alias in Alias.filter_by( user_id=current_user.id).all(): # type: Alias data.append([alias.email, alias.note, alias.enabled]) si = StringIO() cw = csv.writer(si) cw.writerows(data) output = make_response(si.getvalue()) output.headers[ "Content-Disposition"] = "attachment; filename=aliases.csv" output.headers["Content-type"] = "text/csv" return output manual_sub = ManualSubscription.get_by(user_id=current_user.id) return render_template( "dashboard/setting.html", form=form, PlanEnum=PlanEnum, SenderFormatEnum=SenderFormatEnum, promo_form=promo_form, change_email_form=change_email_form, pending_email=pending_email, AliasGeneratorEnum=AliasGeneratorEnum, manual_sub=manual_sub, FIRST_ALIAS_DOMAIN=FIRST_ALIAS_DOMAIN, )
def custom_domain(): custom_domains = CustomDomain.query.filter_by( user_id=current_user.id).all() mailboxes = current_user.mailboxes() new_custom_domain_form = NewCustomDomainForm() errors = {} if request.method == "POST": if request.form.get("form-name") == "create": if not current_user.is_premium(): flash("Only premium plan can add custom domain", "warning") return redirect(url_for("dashboard.custom_domain")) if new_custom_domain_form.validate(): new_domain = new_custom_domain_form.domain.data.lower().strip() if new_domain.startswith("http://"): new_domain = new_domain[len("http://"):] if new_domain.startswith("https://"): new_domain = new_domain[len("https://"):] if new_domain in ALIAS_DOMAINS: flash("A custom domain cannot be a built-in domain.", "error") elif CustomDomain.get_by(domain=new_domain): flash(f"{new_domain} already added", "warning") elif get_email_domain_part(current_user.email) == new_domain: flash( "You cannot add a domain that you are currently using for your personal email. " "Please change your personal email to your real email", "error", ) else: new_custom_domain = CustomDomain.create( domain=new_domain, user_id=current_user.id) db.session.commit() mailbox_ids = request.form.getlist("mailbox_ids") if mailbox_ids: # check if mailbox is not tempered with mailboxes = [] for mailbox_id in mailbox_ids: mailbox = Mailbox.get(mailbox_id) if (not mailbox or mailbox.user_id != current_user.id or not mailbox.verified): flash("Something went wrong, please retry", "warning") return redirect( url_for("dashboard.custom_domain")) mailboxes.append(mailbox) for mailbox in mailboxes: DomainMailbox.create( domain_id=new_custom_domain.id, mailbox_id=mailbox.id) db.session.commit() flash(f"New domain {new_custom_domain.domain} is created", "success") return redirect( url_for( "dashboard.domain_detail_dns", custom_domain_id=new_custom_domain.id, )) elif request.form.get("form-name") == "update": domain_id = request.form.get("domain-id") domain = CustomDomain.get(domain_id) if not domain or domain.user_id != current_user.id: flash("Unknown error. Refresh the page", "warning") return redirect(url_for("dashboard.custom_domain")) mailbox_ids = request.form.getlist("mailbox_ids") # check if mailbox is not tempered with mailboxes = [] for mailbox_id in mailbox_ids: mailbox = Mailbox.get(mailbox_id) if (not mailbox or mailbox.user_id != current_user.id or not mailbox.verified): flash("Something went wrong, please retry", "warning") return redirect(url_for("dashboard.custom_domain")) mailboxes.append(mailbox) if not mailboxes: flash("You must select at least 1 mailbox", "warning") return redirect(url_for("dashboard.custom_domain")) # first remove all existing domain-mailboxes links DomainMailbox.query.filter_by(domain_id=domain.id).delete() db.session.flush() for mailbox in mailboxes: DomainMailbox.create(domain_id=domain.id, mailbox_id=mailbox.id) db.session.commit() flash(f"Domain {domain.domain} has been updated", "success") return redirect(url_for("dashboard.custom_domain")) return render_template( "dashboard/custom_domain.html", custom_domains=custom_domains, new_custom_domain_form=new_custom_domain_form, EMAIL_SERVERS_WITH_PRIORITY=EMAIL_SERVERS_WITH_PRIORITY, errors=errors, mailboxes=mailboxes, )
def mailbox_detail_route(mailbox_id): mailbox = Mailbox.get(mailbox_id) if not mailbox or mailbox.user_id != current_user.id: flash("You cannot see this page", "warning") return redirect(url_for("dashboard.index")) change_email_form = ChangeEmailForm() if mailbox.new_email: pending_email = mailbox.new_email else: pending_email = None if request.method == "POST": if (request.form.get("form-name") == "update-email" and change_email_form.validate_on_submit()): new_email = change_email_form.email.data.lower().strip() if new_email != mailbox.email and not pending_email: # check if this email is not already used if mailbox_already_used( new_email, current_user) or Alias.get_by(email=new_email): flash(f"Email {new_email} already used", "error") elif not email_can_be_used_as_mailbox(new_email): flash("You cannot use this email address as your mailbox", "error") else: mailbox.new_email = new_email db.session.commit() try: verify_mailbox_change(current_user, mailbox, new_email) except SMTPRecipientsRefused: flash( f"Incorrect mailbox, please recheck {mailbox.email}", "error", ) else: flash( f"You are going to receive an email to confirm {new_email}.", "success", ) return redirect( url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)) elif request.form.get("form-name") == "force-spf": if not ENFORCE_SPF: flash("SPF enforcement globally not enabled", "error") return redirect(url_for("dashboard.index")) mailbox.force_spf = (True if request.form.get("spf-status") == "on" else False) db.session.commit() flash( "SPF enforcement was " + "enabled" if request.form.get("spf-status") else "disabled" + " successfully", "success", ) return redirect( url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)) elif request.form.get("form-name") == "add-authorized-address": address = request.form.get("email").lower().strip().replace( " ", "") if AuthorizedAddress.get_by(mailbox_id=mailbox.id, email=address): flash(f"{address} already added", "error") else: AuthorizedAddress.create( user_id=current_user.id, mailbox_id=mailbox.id, email=address, commit=True, ) flash(f"{address} added as authorized address", "success") return redirect( url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)) elif request.form.get("form-name") == "delete-authorized-address": authorized_address_id = request.form.get("authorized-address-id") authorized_address: AuthorizedAddress = AuthorizedAddress.get( authorized_address_id) if not authorized_address or authorized_address.mailbox_id != mailbox.id: flash("Unknown error. Refresh the page", "warning") else: address = authorized_address.email AuthorizedAddress.delete(authorized_address_id) db.session.commit() flash(f"{address} has been deleted", "success") return redirect( url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)) elif request.form.get("form-name") == "pgp": if request.form.get("action") == "save": if not current_user.is_premium(): flash("Only premium plan can add PGP Key", "warning") return redirect( url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)) mailbox.pgp_public_key = request.form.get("pgp") try: mailbox.pgp_finger_print = load_public_key_and_check( mailbox.pgp_public_key) except PGPException: flash("Cannot add the public key, please verify it", "error") else: db.session.commit() flash("Your PGP public key is saved successfully", "success") return redirect( url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)) elif request.form.get("action") == "remove": # Free user can decide to remove their added PGP key mailbox.pgp_public_key = None mailbox.pgp_finger_print = None mailbox.disable_pgp = False db.session.commit() flash("Your PGP public key is removed successfully", "success") return redirect( url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)) elif request.form.get("form-name") == "toggle-pgp": if request.form.get("pgp-enabled") == "on": mailbox.disable_pgp = False flash(f"PGP is enabled on {mailbox.email}", "success") else: mailbox.disable_pgp = True flash(f"PGP is disabled on {mailbox.email}", "info") db.session.commit() return redirect( url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)) elif request.form.get("form-name") == "generic-subject": if request.form.get("action") == "save": if not mailbox.pgp_enabled(): flash( "Generic subject can only be used on PGP-enabled mailbox", "error", ) return redirect( url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)) mailbox.generic_subject = request.form.get("generic-subject") db.session.commit() flash("Generic subject for PGP-encrypted email is enabled", "success") return redirect( url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)) elif request.form.get("action") == "remove": mailbox.generic_subject = None db.session.commit() flash("Generic subject for PGP-encrypted email is disabled", "success") return redirect( url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)) spf_available = ENFORCE_SPF return render_template("dashboard/mailbox_detail.html", **locals())
def directory(): dirs = (Directory.query.filter_by(user_id=current_user.id).order_by( Directory.created_at.desc()).all()) mailboxes = current_user.mailboxes() new_dir_form = NewDirForm() if request.method == "POST": if request.form.get("form-name") == "delete": dir_id = request.form.get("dir-id") dir = Directory.get(dir_id) if not dir: flash("Unknown error. Refresh the page", "warning") return redirect(url_for("dashboard.directory")) elif dir.user_id != current_user.id: flash("You cannot delete this directory", "warning") return redirect(url_for("dashboard.directory")) name = dir.name Directory.delete(dir_id) db.session.commit() flash(f"Directory {name} has been deleted", "success") return redirect(url_for("dashboard.directory")) elif request.form.get("form-name") == "update": dir_id = request.form.get("dir-id") dir = Directory.get(dir_id) if not dir or dir.user_id != current_user.id: flash("Unknown error. Refresh the page", "warning") return redirect(url_for("dashboard.directory")) mailbox_ids = request.form.getlist("mailbox_ids") # check if mailbox is not tempered with mailboxes = [] for mailbox_id in mailbox_ids: mailbox = Mailbox.get(mailbox_id) if (not mailbox or mailbox.user_id != current_user.id or not mailbox.verified): flash("Something went wrong, please retry", "warning") return redirect(url_for("dashboard.directory")) mailboxes.append(mailbox) if not mailboxes: flash("You must select at least 1 mailbox", "warning") return redirect(url_for("dashboard.directory")) # first remove all existing directory-mailboxes links DirectoryMailbox.query.filter_by(directory_id=dir.id).delete() db.session.flush() for mailbox in mailboxes: DirectoryMailbox.create(directory_id=dir.id, mailbox_id=mailbox.id) db.session.commit() flash(f"Directory {dir.name} has been updated", "success") return redirect(url_for("dashboard.directory")) elif request.form.get("form-name") == "create": if not current_user.is_premium(): flash("Only premium plan can add directory", "warning") return redirect(url_for("dashboard.directory")) if current_user.nb_directory() >= MAX_NB_DIRECTORY: flash( f"You cannot have more than {MAX_NB_DIRECTORY} directories", "warning", ) return redirect(url_for("dashboard.directory")) if new_dir_form.validate(): new_dir_name = new_dir_form.name.data.lower() if Directory.get_by(name=new_dir_name): flash(f"{new_dir_name} already added", "warning") elif new_dir_name == "reply": flash( "directory name cannot be *reply*, please choose another name", "warning", ) else: new_dir = Directory.create(name=new_dir_name, user_id=current_user.id) db.session.commit() mailbox_ids = request.form.getlist("mailbox_ids") if mailbox_ids: # check if mailbox is not tempered with mailboxes = [] for mailbox_id in mailbox_ids: mailbox = Mailbox.get(mailbox_id) if (not mailbox or mailbox.user_id != current_user.id or not mailbox.verified): flash("Something went wrong, please retry", "warning") return redirect(url_for("dashboard.directory")) mailboxes.append(mailbox) for mailbox in mailboxes: DirectoryMailbox.create(directory_id=new_dir.id, mailbox_id=mailbox.id) db.session.commit() flash(f"Directory {new_dir.name} is created", "success") return redirect(url_for("dashboard.directory")) return render_template( "dashboard/directory.html", dirs=dirs, new_dir_form=new_dir_form, mailboxes=mailboxes, EMAIL_DOMAIN=EMAIL_DOMAIN, ALIAS_DOMAINS=ALIAS_DOMAINS, )
def mailbox_route(): mailboxes = (Mailbox.query.filter_by(user_id=current_user.id).order_by( Mailbox.created_at.desc()).all()) new_mailbox_form = NewMailboxForm() if request.method == "POST": if request.form.get("form-name") == "delete": mailbox_id = request.form.get("mailbox-id") mailbox = Mailbox.get(mailbox_id) if not mailbox or mailbox.user_id != current_user.id: flash("Unknown error. Refresh the page", "warning") return redirect(url_for("dashboard.mailbox_route")) if mailbox.id == current_user.default_mailbox_id: flash("You cannot delete default mailbox", "error") return redirect(url_for("dashboard.mailbox_route")) LOG.d("Schedule deleting %s", mailbox) Thread(target=delete_mailbox, args=(mailbox.id, )).start() flash( f"Mailbox {mailbox.email} scheduled for deletion." f"You will receive a confirmation email when the deletion is finished", "success", ) return redirect(url_for("dashboard.mailbox_route")) if request.form.get("form-name") == "set-default": mailbox_id = request.form.get("mailbox-id") mailbox = Mailbox.get(mailbox_id) if not mailbox or mailbox.user_id != current_user.id: flash("Unknown error. Refresh the page", "warning") return redirect(url_for("dashboard.mailbox_route")) if mailbox.id == current_user.default_mailbox_id: flash("This mailbox is already default one", "error") return redirect(url_for("dashboard.mailbox_route")) if not mailbox.verified: flash("Cannot set unverified mailbox as default", "error") return redirect(url_for("dashboard.mailbox_route")) current_user.default_mailbox_id = mailbox.id db.session.commit() flash(f"Mailbox {mailbox.email} is set as Default Mailbox", "success") return redirect(url_for("dashboard.mailbox_route")) elif request.form.get("form-name") == "create": if not current_user.is_premium(): flash("Only premium plan can add additional mailbox", "warning") return redirect(url_for("dashboard.mailbox_route")) if new_mailbox_form.validate(): mailbox_email = ( new_mailbox_form.email.data.lower().strip().replace( " ", "")) if not is_valid_email(mailbox_email): flash(f"{mailbox_email} invalid", "error") elif mailbox_already_used(mailbox_email, current_user): flash(f"{mailbox_email} already used", "error") elif not email_can_be_used_as_mailbox(mailbox_email): flash(f"You cannot use {mailbox_email}.", "error") else: new_mailbox = Mailbox.create(email=mailbox_email, user_id=current_user.id) db.session.commit() send_verification_email(current_user, new_mailbox) flash( f"You are going to receive an email to confirm {mailbox_email}.", "success", ) return redirect( url_for("dashboard.mailbox_detail_route", mailbox_id=new_mailbox.id)) return render_template( "dashboard/mailbox.html", mailboxes=mailboxes, new_mailbox_form=new_mailbox_form, )
def directory(): dirs = ( Directory.query.filter_by(user_id=current_user.id) .order_by(Directory.created_at.desc()) .all() ) new_dir_form = NewDirForm() if request.method == "POST": if request.form.get("form-name") == "delete": dir_id = request.form.get("dir-id") dir = Directory.get(dir_id) if not dir: flash("Unknown error. Refresh the page", "warning") return redirect(url_for("dashboard.directory")) elif dir.user_id != current_user.id: flash("You cannot delete this directory", "warning") return redirect(url_for("dashboard.directory")) name = dir.name Directory.delete(dir_id) db.session.commit() flash(f"Directory {name} has been deleted", "success") return redirect(url_for("dashboard.directory")) elif request.form.get("form-name") == "create": if not current_user.is_premium(): flash("Only premium plan can add directory", "warning") return redirect(url_for("dashboard.directory")) if current_user.nb_directory() >= MAX_NB_DIRECTORY: flash( f"You cannot have more than {MAX_NB_DIRECTORY} directories", "warning", ) return redirect(url_for("dashboard.directory")) if new_dir_form.validate(): new_dir_name = new_dir_form.name.data.lower() if Directory.get_by(name=new_dir_name): flash(f"{new_dir_name} already added", "warning") elif new_dir_name == "reply": flash( "directory name cannot be *reply*, please choose another name", "warning", ) else: new_dir = Directory.create( name=new_dir_name, user_id=current_user.id ) db.session.commit() flash(f"Directory {new_dir.name} is created", "success") return redirect(url_for("dashboard.directory")) return render_template( "dashboard/directory.html", dirs=dirs, new_dir_form=new_dir_form, EMAIL_DOMAIN=EMAIL_DOMAIN, ALIAS_DOMAINS=ALIAS_DOMAINS, )
def mailbox_route(): mailboxes = (Mailbox.query.filter_by(user_id=current_user.id).order_by( Mailbox.created_at.desc()).all()) new_mailbox_form = NewMailboxForm() if request.method == "POST": if request.form.get("form-name") == "delete": mailbox_id = request.form.get("mailbox-id") mailbox = Mailbox.get(mailbox_id) if not mailbox or mailbox.user_id != current_user.id: flash("Unknown error. Refresh the page", "warning") return redirect(url_for("dashboard.mailbox_route")) if mailbox.id == current_user.default_mailbox_id: flash("You cannot delete default mailbox", "error") return redirect(url_for("dashboard.mailbox_route")) email = mailbox.email Mailbox.delete(mailbox_id) db.session.commit() flash(f"Mailbox {email} has been deleted", "success") return redirect(url_for("dashboard.mailbox_route")) if request.form.get("form-name") == "set-default": mailbox_id = request.form.get("mailbox-id") mailbox = Mailbox.get(mailbox_id) if not mailbox or mailbox.user_id != current_user.id: flash("Unknown error. Refresh the page", "warning") return redirect(url_for("dashboard.mailbox_route")) if mailbox.id == current_user.default_mailbox_id: flash("This mailbox is already default one", "error") return redirect(url_for("dashboard.mailbox_route")) if not mailbox.verified: flash("Cannot set unverified mailbox as default", "error") return redirect(url_for("dashboard.mailbox_route")) current_user.default_mailbox_id = mailbox.id db.session.commit() flash(f"Mailbox {mailbox.email} is set as Default Mailbox", "success") return redirect(url_for("dashboard.mailbox_route")) elif request.form.get("form-name") == "create": if not current_user.is_premium(): flash("Only premium plan can add additional mailbox", "warning") return redirect(url_for("dashboard.mailbox_route")) if new_mailbox_form.validate(): mailbox_email = new_mailbox_form.email.data.lower() if mailbox_already_used(mailbox_email, current_user): flash(f"{mailbox_email} already used", "error") elif not email_domain_can_be_used_as_mailbox(mailbox_email): flash(f"You cannot use {mailbox_email}.", "error") else: new_mailbox = Mailbox.create(email=mailbox_email, user_id=current_user.id) db.session.commit() s = Signer(MAILBOX_SECRET) mailbox_id_signed = s.sign(str(new_mailbox.id)).decode() verification_url = (URL + "/dashboard/mailbox_verify" + f"?mailbox_id={mailbox_id_signed}") send_email( mailbox_email, f"Please confirm your email {mailbox_email}", render( "transactional/verify-mailbox.txt", user=current_user, link=verification_url, mailbox_email=mailbox_email, ), render( "transactional/verify-mailbox.html", user=current_user, link=verification_url, mailbox_email=mailbox_email, ), ) flash( f"You are going to receive an email to confirm {mailbox_email}.", "success", ) return redirect( url_for("dashboard.mailbox_detail_route", mailbox_id=new_mailbox.id)) return render_template( "dashboard/mailbox.html", mailboxes=mailboxes, new_mailbox_form=new_mailbox_form, EMAIL_DOMAIN=EMAIL_DOMAIN, ALIAS_DOMAINS=ALIAS_DOMAINS, )
def subdomain_route(): if not current_user.subdomain_is_available(): flash("Unknown error, redirect to the home page", "error") return redirect(url_for("dashboard.index")) sl_domains = SLDomain.filter_by(can_use_subdomain=True).all() subdomains = CustomDomain.filter_by( user_id=current_user.id, is_sl_subdomain=True ).all() errors = {} if request.method == "POST": if request.form.get("form-name") == "create": if not current_user.is_premium(): flash("Only premium plan can add subdomain", "warning") return redirect(request.url) if current_user.subdomain_quota <= 0: flash( f"You can't create more than {MAX_NB_SUBDOMAIN} subdomains", "error" ) return redirect(request.url) subdomain = request.form.get("subdomain").lower().strip() domain = request.form.get("domain").lower().strip() if len(subdomain) < 3: flash("Subdomain must have at least 3 characters", "error") return redirect(request.url) if re.fullmatch(_SUBDOMAIN_PATTERN, subdomain) is None: flash( "Subdomain can only contain lowercase letters, numbers and dashes (-)", "error", ) return redirect(request.url) if subdomain.endswith("-"): flash("Subdomain can't end with dash (-)", "error") return redirect(request.url) if domain not in [sl_domain.domain for sl_domain in sl_domains]: LOG.e("Domain %s is tampered by %s", domain, current_user) flash("Unknown error, refresh the page", "error") return redirect(request.url) full_domain = f"{subdomain}.{domain}" if CustomDomain.get_by(domain=full_domain): flash(f"{full_domain} already used", "error") elif Mailbox.filter( Mailbox.verified.is_(True), Mailbox.email.endswith(f"@{full_domain}"), ).first(): flash(f"{full_domain} already used in a SimpleLogin mailbox", "error") else: try: new_custom_domain = CustomDomain.create( is_sl_subdomain=True, catch_all=True, # by default catch-all is enabled domain=full_domain, user_id=current_user.id, verified=True, dkim_verified=False, # wildcard DNS does not work for DKIM spf_verified=True, dmarc_verified=False, # wildcard DNS does not work for DMARC ownership_verified=True, commit=True, ) except SubdomainInTrashError: flash( f"{full_domain} has been used before and cannot be reused", "error", ) else: flash( f"New subdomain {new_custom_domain.domain} is created", "success", ) return redirect( url_for( "dashboard.domain_detail", custom_domain_id=new_custom_domain.id, ) ) return render_template( "dashboard/subdomain.html", sl_domains=sl_domains, errors=errors, subdomains=subdomains, )
def domain_detail_dns(custom_domain_id): # only premium user can see custom domain if not current_user.is_premium(): flash("Only premium user can add custom domains", "warning") return redirect(url_for("dashboard.index")) custom_domain = CustomDomain.get(custom_domain_id) if not custom_domain or custom_domain.user_id != current_user.id: flash("You cannot see this page", "warning") return redirect(url_for("dashboard.index")) mx_ok = spf_ok = dkim_ok = True mx_errors = spf_errors = dkim_errors = [] if request.method == "POST": if request.form.get("form-name") == "check-mx": mx_domains = get_mx_domains(custom_domain.domain) if sorted(mx_domains) != sorted(EMAIL_SERVERS_WITH_PRIORITY): mx_ok = False # build mx_errors to show to user mx_errors = [ f"{priority} {domain}" for (priority, domain) in mx_domains ] else: flash( "Your domain is verified. Now it can be used to create custom alias", "success", ) custom_domain.verified = True db.session.commit() return redirect( url_for("dashboard.domain_detail_dns", custom_domain_id=custom_domain.id)) elif request.form.get("form-name") == "check-spf": spf_domains = get_spf_domain(custom_domain.domain) if EMAIL_DOMAIN in spf_domains: custom_domain.spf_verified = True db.session.commit() flash("The SPF is setup correctly", "success") return redirect( url_for("dashboard.domain_detail_dns", custom_domain_id=custom_domain.id)) else: flash(f"{EMAIL_DOMAIN} is not included in your SPF record.", "warning") spf_ok = False spf_errors = get_txt_record(custom_domain.domain) elif request.form.get("form-name") == "check-dkim": dkim_record = get_dkim_record(custom_domain.domain) correct_dkim_record = f"v=DKIM1; k=rsa; p={DKIM_DNS_VALUE}" if dkim_record == correct_dkim_record: flash("The DKIM is setup correctly.", "success") custom_domain.dkim_verified = True db.session.commit() return redirect( url_for("dashboard.domain_detail_dns", custom_domain_id=custom_domain.id)) else: dkim_ok = False dkim_errors = get_txt_record( f"dkim._domainkey.{custom_domain.domain}") spf_record = f"v=spf1 include:{EMAIL_DOMAIN} -all" dkim_record = f"v=DKIM1; k=rsa; p={DKIM_DNS_VALUE}" return render_template( "dashboard/domain_detail/dns.html", EMAIL_SERVERS_WITH_PRIORITY=EMAIL_SERVERS_WITH_PRIORITY, **locals(), )
def custom_domain(): # only premium user can add custom domain if not current_user.is_premium(): flash("Only premium user can add custom domains", "warning") return redirect(url_for("dashboard.index")) custom_domains = CustomDomain.query.filter_by(user_id=current_user.id).all() new_custom_domain_form = NewCustomDomainForm() errors = {} if request.method == "POST": if request.form.get("form-name") == "delete": custom_domain_id = request.form.get("custom-domain-id") custom_domain = CustomDomain.get(custom_domain_id) if not custom_domain: flash("Unknown error. Refresh the page", "warning") return redirect(url_for("dashboard.custom_domain")) elif custom_domain.user_id != current_user.id: flash("You cannot delete this domain", "warning") return redirect(url_for("dashboard.custom_domain")) name = custom_domain.domain CustomDomain.delete(custom_domain_id) db.session.commit() flash(f"Domain {name} has been deleted successfully", "success") return redirect(url_for("dashboard.custom_domain")) elif request.form.get("form-name") == "create": if new_custom_domain_form.validate(): new_custom_domain = CustomDomain.create( domain=new_custom_domain_form.domain.data, user_id=current_user.id ) db.session.commit() flash( f"New domain {new_custom_domain.domain} has been created successfully", "success", ) return redirect(url_for("dashboard.custom_domain")) elif request.form.get("form-name") == "check-domain": custom_domain_id = request.form.get("custom-domain-id") custom_domain = CustomDomain.get(custom_domain_id) if not custom_domain: flash("Unknown error. Refresh the page", "warning") return redirect(url_for("dashboard.custom_domain")) elif custom_domain.user_id != current_user.id: flash("You cannot delete this domain", "warning") return redirect(url_for("dashboard.custom_domain")) else: spf_domains = get_spf_domain(custom_domain.domain) for email_server in EMAIL_SERVERS: email_server = email_server[:-1] # remove the trailing . if email_server not in spf_domains: flash( f"{email_server} is not included in your SPF record.", "warning", ) mx_domains = get_mx_domains(custom_domain.domain) if mx_domains != EMAIL_SERVERS: errors[ custom_domain.id ] = f"""Your DNS is not correctly set. The MX record we obtain is: {",".join(mx_domains)}""" else: flash( "Your domain is verified. Now it can be used to create custom alias", "success", ) custom_domain.verified = True db.session.commit() return render_template( "dashboard/custom_domain.html", custom_domains=custom_domains, new_custom_domain_form=new_custom_domain_form, EMAIL_SERVERS_WITH_PRIORITY=EMAIL_SERVERS_WITH_PRIORITY, errors=errors, )
def mailbox_detail_route(mailbox_id): mailbox = Mailbox.get(mailbox_id) if not mailbox or mailbox.user_id != current_user.id: flash("You cannot see this page", "warning") return redirect(url_for("dashboard.index")) change_email_form = ChangeEmailForm() if mailbox.new_email: pending_email = mailbox.new_email else: pending_email = None if request.method == "POST": if ( request.form.get("form-name") == "update-email" and change_email_form.validate_on_submit() ): new_email = change_email_form.email.data if new_email != mailbox.email and not pending_email: # check if this email is not already used if ( mailbox_already_used(new_email, current_user) or Alias.get_by(email=new_email) or DeletedAlias.get_by(email=new_email) ): flash(f"Email {new_email} already used", "error") elif not email_domain_can_be_used_as_mailbox(new_email): flash("You cannot use this email address as your mailbox", "error") else: mailbox.new_email = new_email db.session.commit() s = Signer(MAILBOX_SECRET) mailbox_id_signed = s.sign(str(mailbox.id)).decode() verification_url = ( URL + "/dashboard/mailbox/confirm_change" + f"?mailbox_id={mailbox_id_signed}" ) try: send_email( new_email, f"Confirm mailbox change on SimpleLogin", render( "transactional/verify-mailbox-change.txt", user=current_user, link=verification_url, mailbox_email=mailbox.email, mailbox_new_email=new_email, ), render( "transactional/verify-mailbox-change.html", user=current_user, link=verification_url, mailbox_email=mailbox.email, mailbox_new_email=new_email, ), ) except SMTPRecipientsRefused: flash( f"Incorrect mailbox, please recheck {mailbox.email}", "error", ) else: flash( f"You are going to receive an email to confirm {new_email}.", "success", ) return redirect( url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id) ) elif request.form.get("form-name") == "force-spf": if not ENFORCE_SPF: flash("SPF enforcement globally not enabled", "error") return redirect(url_for("dashboard.index")) mailbox.force_spf = ( True if request.form.get("spf-status") == "on" else False ) db.session.commit() flash( "SPF enforcement was " + "enabled" if request.form.get("spf-status") else "disabled" + " succesfully", "success", ) return redirect( url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id) ) elif request.form.get("form-name") == "pgp": if request.form.get("action") == "save": if not current_user.is_premium(): flash("Only premium plan can add PGP Key", "warning") return redirect( url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id) ) mailbox.pgp_public_key = request.form.get("pgp") try: mailbox.pgp_finger_print = load_public_key(mailbox.pgp_public_key) except PGPException: flash("Cannot add the public key, please verify it", "error") else: db.session.commit() flash("Your PGP public key is saved successfully", "success") return redirect( url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id) ) elif request.form.get("action") == "remove": # Free user can decide to remove their added PGP key mailbox.pgp_public_key = None mailbox.pgp_finger_print = None db.session.commit() flash("Your PGP public key is removed successfully", "success") return redirect( url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id) ) spf_available = ENFORCE_SPF return render_template("dashboard/mailbox_detail.html", **locals())