Ejemplo n.º 1
0
def delete_alias(alias: Alias, user: User):
    Alias.delete(alias.id)
    db.session.commit()

    # save deleted alias to either global or domain trash
    if alias.custom_domain_id:
        try:
            DomainDeletedAlias.create(user_id=user.id,
                                      email=alias.email,
                                      domain_id=alias.custom_domain_id)
            db.session.commit()
        except IntegrityError:
            LOG.error(
                "alias %s domain %s has been added before to DeletedAlias",
                alias.email,
                alias.custom_domain_id,
            )
            db.session.rollback()
    else:
        try:
            DeletedAlias.create(email=alias.email)
            db.session.commit()
        except IntegrityError:
            LOG.error("alias %s has been added before to DeletedAlias",
                      alias.email)
            db.session.rollback()
Ejemplo n.º 2
0
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()
Ejemplo n.º 3
0
def delete_alias(alias: Alias, user: User):
    email = alias.email
    Alias.delete(alias.id)
    db.session.commit()

    # try to save deleted alias
    try:
        DeletedAlias.create(email=email)
        db.session.commit()
    # this can happen when a previously deleted alias is re-created via catch-all or directory feature
    except IntegrityError:
        LOG.error("alias %s has been added before to DeletedAlias", email)
        db.session.rollback()
Ejemplo n.º 4
0
def migrate_domain_trash():
    """Move aliases from global trash to domain trash if applicable"""

    # ignore duplicate when insert
    # copied from https://github.com/sqlalchemy/sqlalchemy/issues/5374
    @compiles(Insert, "postgresql")
    def postgresql_on_conflict_do_nothing(insert, compiler, **kw):
        statement = compiler.visit_insert(insert, **kw)
        # IF we have a "RETURNING" clause, we must insert before it
        returning_position = statement.find("RETURNING")
        if returning_position >= 0:
            return (statement[:returning_position] +
                    "ON CONFLICT DO NOTHING " + statement[returning_position:])
        else:
            return statement + " ON CONFLICT DO NOTHING"

    sl_domains = [sl.domain for sl in SLDomain.all()]
    count = 0
    domain_deleted_aliases = []
    deleted_alias_ids = []
    for deleted_alias in DeletedAlias.yield_per_query():
        if count % 1000 == 0:
            LOG.d("process %s", count)

        count += 1

        alias_domain = get_email_domain_part(deleted_alias.email)
        if alias_domain not in sl_domains:
            custom_domain = CustomDomain.get_by(domain=alias_domain)
            if custom_domain:
                LOG.w("move %s to domain %s trash", deleted_alias,
                      custom_domain)
                domain_deleted_aliases.append(
                    DomainDeletedAlias(
                        user_id=custom_domain.user_id,
                        email=deleted_alias.email,
                        domain_id=custom_domain.id,
                        created_at=deleted_alias.created_at,
                    ))
                deleted_alias_ids.append(deleted_alias.id)

    LOG.d("create %s DomainDeletedAlias", len(domain_deleted_aliases))
    Session.bulk_save_objects(domain_deleted_aliases)

    LOG.d("delete %s DeletedAlias", len(deleted_alias_ids))
    DeletedAlias.filter(DeletedAlias.id.in_(deleted_alias_ids)).delete(
        synchronize_session=False)

    Session.commit()
Ejemplo n.º 5
0
def import_from_csv(batch_import: BatchImport, user: User, lines):
    reader = csv.DictReader(lines)

    for row in reader:
        try:
            full_alias = sanitize_email(row["alias"])
            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

        mailboxes = []

        if "mailboxes" in row:
            for mailbox_email in row["mailboxes"].split():
                mailbox_email = sanitize_email(mailbox_email)
                mailbox = Mailbox.get_by(email=mailbox_email)

                if not mailbox or not mailbox.verified or mailbox.user_id != user.id:
                    LOG.d("mailbox %s can't be used %s", mailbox, user)
                    continue

                mailboxes.append(mailbox.id)

        if len(mailboxes) == 0:
            mailboxes = [user.default_mailbox_id]

        alias = Alias.create(
            user_id=user.id,
            email=full_alias,
            note=note,
            mailbox_id=mailboxes[0],
            custom_domain_id=custom_domain.id,
            batch_import_id=batch_import.id,
        )
        db.session.commit()
        db.session.flush()
        LOG.d("Create %s", alias)

        for i in range(1, len(mailboxes)):
            alias_mailbox = AliasMailbox.create(
                alias_id=alias.id,
                mailbox_id=mailboxes[i],
            )
            db.session.commit()
            LOG.d("Create %s", alias_mailbox)
Ejemplo n.º 6
0
def test_add_alias_in_global_trash(flask_client):
    user = login(flask_client)
    Session.commit()

    another_user = User.create(
        email="[email protected]",
        password="******",
        name="Test User",
        activated=True,
        commit=True,
    )

    word = random_word()
    suffix = f".{word}@{EMAIL_DOMAIN}"
    alias_suffix = AliasSuffix(is_custom=False,
                               suffix=suffix,
                               is_premium=False,
                               domain=EMAIL_DOMAIN)
    signed_alias_suffix = signer.sign(alias_suffix.serialize()).decode()

    # delete an alias: alias should go the DeletedAlias
    alias = Alias.create(
        user_id=another_user.id,
        email=f"prefix{suffix}",
        mailbox_id=another_user.default_mailbox_id,
        commit=True,
    )

    assert DeletedAlias.count() == 0
    delete_alias(alias, another_user)
    assert DeletedAlias.count() == 1

    # create the same alias, should return error
    r = flask_client.post(
        url_for("dashboard.custom_alias"),
        data={
            "prefix": "prefix",
            "signed-alias-suffix": signed_alias_suffix,
            "mailboxes": [user.default_mailbox_id],
        },
        follow_redirects=True,
    )
    assert r.status_code == 200
    assert f"prefix{suffix} cannot be used" in r.get_data(True)
Ejemplo n.º 7
0
def migrate_domain_trash():
    """Move aliases from global trash to domain trash if applicable"""
    for deleted_alias in DeletedAlias.query.all():
        alias_domain = get_email_domain_part(deleted_alias.email)
        if not SLDomain.get_by(domain=alias_domain):
            custom_domain = CustomDomain.get_by(domain=alias_domain)
            if custom_domain:
                LOG.d("move %s to domain %s trash", deleted_alias,
                      custom_domain)
                db.session.add(
                    DomainDeletedAlias(
                        user_id=custom_domain.user_id,
                        email=deleted_alias.email,
                        domain_id=custom_domain.id,
                        created_at=deleted_alias.created_at,
                    ))
                DeletedAlias.delete(deleted_alias.id)

    db.session.commit()
Ejemplo n.º 8
0
def try_auto_create_directory(address: str) -> Optional[Alias]:
    """
    Try to create an alias with directory
    """
    # check if alias belongs to a directory, ie having directory/anything@EMAIL_DOMAIN format
    if email_belongs_to_alias_domains(address):
        # if there's no directory separator in the alias, no way to auto-create it
        if "/" not in address and "+" not in address and "#" not in address:
            return None

        # alias contains one of the 3 special directory separator: "/", "+" or "#"
        if "/" in address:
            sep = "/"
        elif "+" in address:
            sep = "+"
        else:
            sep = "#"

        directory_name = address[:address.find(sep)]
        LOG.d("directory_name %s", directory_name)

        directory = Directory.get_by(name=directory_name)
        if not directory:
            return None

        dir_user: User = directory.user

        if not dir_user.can_create_new_alias():
            send_cannot_create_directory_alias(dir_user, address,
                                               directory_name)
            return None

        # if alias has been deleted before, do not auto-create it
        if DeletedAlias.get_by(email=address):
            LOG.warning(
                "Alias %s was deleted before, cannot auto-create using directory %s, user %s",
                address,
                directory_name,
                dir_user,
            )
            return None

        LOG.d("create alias %s for directory %s", address, directory)

        alias = Alias.create(
            email=address,
            user_id=directory.user_id,
            directory_id=directory.id,
            mailbox_id=dir_user.default_mailbox_id,
        )

        db.session.commit()
        return alias
Ejemplo n.º 9
0
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)
Ejemplo n.º 10
0
def test_delete_alias(flask_client):
    user = User.create(email="[email protected]",
                       password="******",
                       name="Test User",
                       activated=True)
    db.session.commit()
    alias = Alias.create(user_id=user.id,
                         email="*****@*****.**",
                         mailbox_id=user.default_mailbox_id)
    db.session.commit()
    assert Alias.get_by(email="*****@*****.**")

    delete_alias(alias, user)
    assert Alias.get_by(email="*****@*****.**") is None
    assert DeletedAlias.get_by(email=alias.email)
Ejemplo n.º 11
0
def test_delete_alias_already_in_trash(flask_client):
    """delete an alias that's already in alias trash"""
    user = User.create(email="[email protected]",
                       password="******",
                       name="Test User",
                       activated=True)
    db.session.commit()
    alias = Alias.create(user_id=user.id,
                         email="*****@*****.**",
                         mailbox_id=user.default_mailbox_id)
    db.session.commit()

    # add the alias to global trash
    db.session.add(DeletedAlias(email=alias.email))
    db.session.commit()

    delete_alias(alias, user)
    assert Alias.get_by(email="*****@*****.**") is None
Ejemplo n.º 12
0
def try_auto_create_catch_all_domain(address: str) -> Optional[Alias]:
    """Try to create an alias with catch-all domain"""

    # try to create alias on-the-fly with custom-domain catch-all feature
    # check if alias is custom-domain alias and if the custom-domain has catch-all enabled
    alias_domain = get_email_domain_part(address)
    custom_domain = CustomDomain.get_by(domain=alias_domain)

    if not custom_domain:
        return None

    # custom_domain exists
    if not custom_domain.catch_all:
        return None

    # custom_domain has catch-all enabled
    domain_user: User = custom_domain.user

    if not domain_user.can_create_new_alias():
        send_cannot_create_domain_alias(domain_user, address, alias_domain)
        return None

    # if alias has been deleted before, do not auto-create it
    if DeletedAlias.get_by(email=address):
        LOG.warning(
            "Alias %s was deleted before, cannot auto-create using domain catch-all %s, user %s",
            address,
            custom_domain,
            domain_user,
        )
        return None

    LOG.d("create alias %s for domain %s", address, custom_domain)

    alias = Alias.create(
        email=address,
        user_id=custom_domain.user_id,
        custom_domain_id=custom_domain.id,
        automatic_creation=True,
        mailbox_id=domain_user.default_mailbox_id,
    )

    db.session.commit()
    return alias
Ejemplo n.º 13
0
def custom_alias():
    # check if user has the right to create custom alias
    if not current_user.can_create_new_alias():
        # notify admin
        LOG.error("user %s tries to create custom alias", current_user)
        flash("ony premium user can choose custom alias", "warning")
        return redirect(url_for("dashboard.index"))

    error = ""

    if request.method == "POST":
        if request.form.get("form-name") == "non-custom-domain-name":
            email_prefix = request.form.get("email-prefix")
            email_prefix = convert_to_id(email_prefix)
            email_suffix = request.form.get("email-suffix")

            if not email_prefix:
                error = "alias prefix cannot be empty"
            else:
                full_email = f"{email_prefix}.{email_suffix}@{EMAIL_DOMAIN}"
                # check if email already exists
                if GenEmail.get_by(email=full_email) or DeletedAlias.get_by(
                        email=full_email):
                    error = "email already chosen, please choose another one"
                else:
                    # create the new alias
                    LOG.d("create custom alias %s for user %s", full_email,
                          current_user)
                    gen_email = GenEmail.create(email=full_email,
                                                user_id=current_user.id)
                    db.session.commit()

                    flash(f"Alias {full_email} has been created", "success")
                    session[HIGHLIGHT_GEN_EMAIL_ID] = gen_email.id

                    return redirect(url_for("dashboard.index"))
        elif request.form.get("form-name") == "custom-domain-name":
            custom_domain_id = request.form.get("custom-domain-id")
            email = request.form.get("email")

            custom_domain = CustomDomain.get(custom_domain_id)

            if not custom_domain:
                flash("Unknown error. Refresh the page", "warning")
                return redirect(url_for("dashboard.custom_alias"))
            elif custom_domain.user_id != current_user.id:
                flash("Unknown error. Refresh the page", "warning")
                return redirect(url_for("dashboard.custom_alias"))
            elif not custom_domain.verified:
                flash("Unknown error. Refresh the page", "warning")
                return redirect(url_for("dashboard.custom_alias"))

            full_email = f"{email}@{custom_domain.domain}"

            if GenEmail.get_by(email=full_email):
                error = f"{full_email} already exist, please choose another one"
            else:
                LOG.d(
                    "create custom alias %s for custom domain %s",
                    full_email,
                    custom_domain.domain,
                )
                gen_email = GenEmail.create(
                    email=full_email,
                    user_id=current_user.id,
                    custom_domain_id=custom_domain.id,
                )
                db.session.commit()
                flash(f"Alias {full_email} has been created", "success")

                session[HIGHLIGHT_GEN_EMAIL_ID] = gen_email.id
                return redirect(url_for("dashboard.index"))

    email_suffix = random_word()
    return render_template(
        "dashboard/custom_alias.html",
        error=error,
        email_suffix=email_suffix,
        EMAIL_DOMAIN=EMAIL_DOMAIN,
        custom_domains=current_user.verified_custom_domains(),
    )
Ejemplo n.º 14
0
def index():
    query = request.args.get("query") or ""
    highlight_gen_email_id = None
    if request.args.get("highlight_gen_email_id"):
        highlight_gen_email_id = int(
            request.args.get("highlight_gen_email_id"))

    # User generates a new email
    if request.method == "POST":
        if request.form.get("form-name") == "trigger-email":
            gen_email_id = request.form.get("gen-email-id")
            gen_email = GenEmail.get(gen_email_id)

            LOG.d("trigger an email to %s", gen_email)
            email_utils.send_test_email_alias(gen_email.email,
                                              gen_email.user.name)

            flash(
                f"An email sent to {gen_email.email} is on its way, please check your inbox/spam folder",
                "success",
            )

        elif request.form.get("form-name") == "create-custom-email":
            if current_user.can_create_new_alias():
                return redirect(url_for("dashboard.custom_alias"))
            else:
                flash(f"You need to upgrade your plan to create new alias.",
                      "warning")

        elif request.form.get("form-name") == "create-random-email":
            if current_user.can_create_new_alias():
                scheme = int(
                    request.form.get("generator_scheme")
                    or current_user.alias_generator)
                if not scheme or not AliasGeneratorEnum.has_value(scheme):
                    scheme = current_user.alias_generator
                gen_email = GenEmail.create_new_random(user_id=current_user.id,
                                                       scheme=scheme)
                db.session.commit()

                LOG.d("generate new email %s for user %s", gen_email,
                      current_user)
                flash(f"Alias {gen_email.email} has been created", "success")

                return redirect(
                    url_for(
                        "dashboard.index",
                        highlight_gen_email_id=gen_email.id,
                        query=query,
                    ))
            else:
                flash(f"You need to upgrade your plan to create new alias.",
                      "warning")

        elif request.form.get("form-name") == "switch-email-forwarding":
            gen_email_id = request.form.get("gen-email-id")
            gen_email: GenEmail = GenEmail.get(gen_email_id)

            LOG.d("switch email forwarding for %s", gen_email)

            gen_email.enabled = not gen_email.enabled
            if gen_email.enabled:
                flash(f"Alias {gen_email.email} is enabled", "success")
            else:
                flash(f"Alias {gen_email.email} is disabled", "warning")

            db.session.commit()
            return redirect(
                url_for("dashboard.index",
                        highlight_gen_email_id=gen_email.id,
                        query=query))

        elif request.form.get("form-name") == "delete-email":
            gen_email_id = request.form.get("gen-email-id")
            gen_email: GenEmail = GenEmail.get(gen_email_id)

            LOG.d("delete gen email %s", gen_email)
            email = gen_email.email
            GenEmail.delete(gen_email.id)
            db.session.commit()
            flash(f"Alias {email} has been deleted", "success")

            # try to save deleted alias
            try:
                DeletedAlias.create(user_id=current_user.id, email=email)
                db.session.commit()
            # this can happen when a previously deleted alias is re-created via catch-all or directory feature
            except IntegrityError:
                LOG.error("alias %s has been added before to DeletedAlias",
                          email)
                db.session.rollback()

        return redirect(url_for("dashboard.index", query=query))

    client_users = (ClientUser.filter_by(user_id=current_user.id).options(
        joinedload(ClientUser.client)).options(joinedload(
            ClientUser.gen_email)).all())

    sorted(client_users, key=lambda cu: cu.client.name)

    return render_template(
        "dashboard/index.html",
        client_users=client_users,
        aliases=get_alias_info(current_user.id, query, highlight_gen_email_id),
        highlight_gen_email_id=highlight_gen_email_id,
        query=query,
        AliasGeneratorEnum=AliasGeneratorEnum,
    )
Ejemplo n.º 15
0
def custom_alias():
    # check if user has not exceeded the alias quota
    if not current_user.can_create_new_alias():
        LOG.warning("user %s tries to create custom alias", current_user)
        flash(
            "You have reached free plan limit, please upgrade to create new aliases",
            "warning",
        )
        return redirect(url_for("dashboard.index"))

    user_custom_domains = [
        cd.domain for cd in current_user.verified_custom_domains()
    ]
    suffixes = available_suffixes_more_info(current_user)
    at_least_a_premium_domain = False
    for suffix in suffixes:
        if not suffix.is_custom and suffix.is_premium:
            at_least_a_premium_domain = True
            break

    mailboxes = current_user.mailboxes()

    if request.method == "POST":
        alias_prefix = request.form.get("prefix").strip().lower().replace(
            " ", "")
        signed_suffix = request.form.get("suffix")
        mailbox_ids = request.form.getlist("mailboxes")
        alias_note = request.form.get("note")

        if not check_alias_prefix(alias_prefix):
            flash(
                "Only lowercase letters, numbers, dashes (-) and underscores (_) "
                "are currently supported for alias prefix. Cannot be more than 40 letters",
                "error",
            )
            return redirect(url_for("dashboard.custom_alias"))

        # 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_alias"))
            mailboxes.append(mailbox)

        if not mailboxes:
            flash("At least one mailbox must be selected", "error")
            return redirect(url_for("dashboard.custom_alias"))

        # hypothesis: user will click on the button in the 600 secs
        try:
            alias_suffix = signer.unsign(signed_suffix, max_age=600).decode()
        except SignatureExpired:
            LOG.warning("Alias creation time expired for %s", current_user)
            flash("Alias creation time is expired, please retry", "warning")
            return redirect(url_for("dashboard.custom_alias"))
        except Exception:
            LOG.warning("Alias suffix is tampered, user %s", current_user)
            flash("Unknown error, refresh the page", "error")
            return redirect(url_for("dashboard.custom_alias"))

        if verify_prefix_suffix(current_user, alias_prefix, alias_suffix):
            full_alias = alias_prefix + alias_suffix

            general_error_msg = f"{full_alias} cannot be used"

            if Alias.get_by(email=full_alias):
                alias = Alias.get_by(email=full_alias)
                if alias.user_id == current_user.id:
                    flash(f"You already have this alias {full_alias}", "error")
                else:
                    flash(general_error_msg, "error")
            elif DomainDeletedAlias.get_by(email=full_alias):
                domain_deleted_alias: DomainDeletedAlias = DomainDeletedAlias.get_by(
                    email=full_alias)
                custom_domain = domain_deleted_alias.domain
                if domain_deleted_alias.user_id == current_user.id:
                    flash(
                        f"You have deleted this alias before. You can restore it on "
                        f"{custom_domain.domain} 'Deleted Alias' page",
                        "error",
                    )
                else:
                    # should never happen as user can only choose their domains
                    LOG.exception(
                        "Deleted Alias %s does not belong to user %s",
                        domain_deleted_alias,
                    )

            elif DeletedAlias.get_by(email=full_alias):
                flash(general_error_msg, "error")

            else:
                custom_domain_id = None
                # get the custom_domain_id if alias is created with a custom domain
                if alias_suffix.startswith("@"):
                    alias_domain = alias_suffix[1:]
                    domain = CustomDomain.get_by(domain=alias_domain)

                    # check if the alias is currently in the domain trash
                    if domain and DomainDeletedAlias.get_by(
                            domain_id=domain.id, email=full_alias):
                        flash(
                            f"Alias {full_alias} is currently in the {domain.domain} trash. "
                            f"Please remove it from the trash in order to re-create it.",
                            "warning",
                        )
                        return redirect(url_for("dashboard.custom_alias"))

                    if domain:
                        custom_domain_id = domain.id

                try:
                    alias = Alias.create(
                        user_id=current_user.id,
                        email=full_alias,
                        note=alias_note,
                        mailbox_id=mailboxes[0].id,
                        custom_domain_id=custom_domain_id,
                    )
                    db.session.flush()
                except IntegrityError:
                    LOG.warning("Alias %s already exists", full_alias)
                    db.session.rollback()
                    flash("Unknown error, please retry", "error")
                    return redirect(url_for("dashboard.custom_alias"))

                for i in range(1, len(mailboxes)):
                    AliasMailbox.create(
                        alias_id=alias.id,
                        mailbox_id=mailboxes[i].id,
                    )

                db.session.commit()
                flash(f"Alias {full_alias} has been created", "success")

                return redirect(
                    url_for("dashboard.index", highlight_alias_id=alias.id))
        # only happen if the request has been "hacked"
        else:
            flash("something went wrong", "warning")

    return render_template(
        "dashboard/custom_alias.html",
        user_custom_domains=user_custom_domains,
        suffixes=suffixes,
        at_least_a_premium_domain=at_least_a_premium_domain,
        mailboxes=mailboxes,
    )
Ejemplo n.º 16
0
def new_custom_alias():
    """
    Currently used by Safari extension.
    Create a new custom alias
    Input:
        alias_prefix, for ex "www_groupon_com"
        alias_suffix, either [email protected] or @my-domain.com
        optional "hostname" in args
        optional "note"
    Output:
        201 if success
        409 if the alias already exists

    """
    LOG.warning("/alias/custom/new is obsolete")
    user: User = g.user
    if not user.can_create_new_alias():
        LOG.d("user %s cannot create any custom alias", user)
        return (
            jsonify(
                error=
                "You have reached the limitation of a free account with the maximum of "
                f"{MAX_NB_EMAIL_FREE_PLAN} aliases, please upgrade your plan to create more aliases"
            ),
            400,
        )

    hostname = request.args.get("hostname")

    data = request.get_json()
    if not data:
        return jsonify(error="request body cannot be empty"), 400

    alias_prefix = data.get("alias_prefix",
                            "").strip().lower().replace(" ", "")
    alias_suffix = data.get("alias_suffix",
                            "").strip().lower().replace(" ", "")
    note = data.get("note")
    alias_prefix = convert_to_id(alias_prefix)

    if not verify_prefix_suffix(user, alias_prefix, alias_suffix):
        return jsonify(error="wrong alias prefix or suffix"), 400

    full_alias = alias_prefix + alias_suffix
    if (Alias.get_by(email=full_alias) or DeletedAlias.get_by(email=full_alias)
            or DomainDeletedAlias.get_by(email=full_alias)):
        LOG.d("full alias already used %s", full_alias)
        return jsonify(error=f"alias {full_alias} already exists"), 409

    alias = Alias.create(user_id=user.id,
                         email=full_alias,
                         mailbox_id=user.default_mailbox_id,
                         note=note)

    if alias_suffix.startswith("@"):
        alias_domain = alias_suffix[1:]
        domain = CustomDomain.get_by(domain=alias_domain)
        if domain:
            LOG.d("set alias %s to domain %s", full_alias, domain)
            alias.custom_domain_id = domain.id

    db.session.commit()

    if hostname:
        AliasUsedOn.create(alias_id=alias.id,
                           hostname=hostname,
                           user_id=alias.user_id)
        db.session.commit()

    return jsonify(alias=full_alias,
                   **serialize_alias_info(get_alias_info(alias))), 201
Ejemplo n.º 17
0
def new_custom_alias_v3():
    """
    Create a new custom alias
    Same as v2 but accept a list of mailboxes as input
    Input:
        alias_prefix, for ex "www_groupon_com"
        signed_suffix, either [email protected] or @my-domain.com
        mailbox_ids: list of int
        optional "hostname" in args
        optional "note"
        optional "name"

    Output:
        201 if success
        409 if the alias already exists

    """
    user: User = g.user
    if not user.can_create_new_alias():
        LOG.d("user %s cannot create any custom alias", user)
        return (
            jsonify(
                error=
                "You have reached the limitation of a free account with the maximum of "
                f"{MAX_NB_EMAIL_FREE_PLAN} aliases, please upgrade your plan to create more aliases"
            ),
            400,
        )

    hostname = request.args.get("hostname")

    data = request.get_json()
    if not data:
        return jsonify(error="request body cannot be empty"), 400

    alias_prefix = data.get("alias_prefix",
                            "").strip().lower().replace(" ", "")
    signed_suffix = data.get("signed_suffix", "").strip()
    mailbox_ids = data.get("mailbox_ids")
    note = data.get("note")
    name = data.get("name")
    if name:
        name = name.replace("\n", "")
    alias_prefix = convert_to_id(alias_prefix)

    if not check_alias_prefix(alias_prefix):
        return jsonify(error="alias prefix invalid format or too long"), 400

    # 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 != user.id or not mailbox.verified:
            return jsonify(error="Errors with Mailbox"), 400
        mailboxes.append(mailbox)

    if not mailboxes:
        return jsonify(error="At least one mailbox must be selected"), 400

    # hypothesis: user will click on the button in the 600 secs
    try:
        alias_suffix = signer.unsign(signed_suffix, max_age=600).decode()
    except SignatureExpired:
        LOG.warning("Alias creation time expired for %s", user)
        return jsonify(
            error="Alias creation time is expired, please retry"), 412
    except Exception:
        LOG.warning("Alias suffix is tampered, user %s", user)
        return jsonify(error="Tampered suffix"), 400

    if not verify_prefix_suffix(user, alias_prefix, alias_suffix):
        return jsonify(error="wrong alias prefix or suffix"), 400

    full_alias = alias_prefix + alias_suffix
    if (Alias.get_by(email=full_alias) or DeletedAlias.get_by(email=full_alias)
            or DomainDeletedAlias.get_by(email=full_alias)):
        LOG.d("full alias already used %s", full_alias)
        return jsonify(error=f"alias {full_alias} already exists"), 409

    custom_domain_id = None
    if alias_suffix.startswith("@"):
        alias_domain = alias_suffix[1:]
        domain = CustomDomain.get_by(domain=alias_domain)
        if domain:
            custom_domain_id = domain.id

    alias = Alias.create(
        user_id=user.id,
        email=full_alias,
        note=note,
        name=name or None,
        mailbox_id=mailboxes[0].id,
        custom_domain_id=custom_domain_id,
    )
    db.session.flush()

    for i in range(1, len(mailboxes)):
        AliasMailbox.create(
            alias_id=alias.id,
            mailbox_id=mailboxes[i].id,
        )

    db.session.commit()

    if hostname:
        AliasUsedOn.create(alias_id=alias.id,
                           hostname=hostname,
                           user_id=alias.user_id)
        db.session.commit()

    return (
        jsonify(alias=full_alias,
                **serialize_alias_info_v2(get_alias_info_v2(alias))),
        201,
    )
Ejemplo n.º 18
0
def new_custom_alias_v2():
    """
    Create a new custom alias
    Same as v1 but signed_suffix is actually the suffix with signature, e.g.
    [email protected]
    Input:
        alias_prefix, for ex "www_groupon_com"
        signed_suffix, either [email protected] or @my-domain.com
        optional "hostname" in args
        optional "note"
    Output:
        201 if success
        409 if the alias already exists

    """
    user: User = g.user
    if not user.can_create_new_alias():
        LOG.d("user %s cannot create any custom alias", user)
        return (
            jsonify(
                error=
                "You have reached the limitation of a free account with the maximum of "
                f"{MAX_NB_EMAIL_FREE_PLAN} aliases, please upgrade your plan to create more aliases"
            ),
            400,
        )

    hostname = request.args.get("hostname")

    data = request.get_json()
    if not data:
        return jsonify(error="request body cannot be empty"), 400

    alias_prefix = data.get("alias_prefix",
                            "").strip().lower().replace(" ", "")
    signed_suffix = data.get("signed_suffix", "").strip()
    note = data.get("note")
    alias_prefix = convert_to_id(alias_prefix)

    # hypothesis: user will click on the button in the 600 secs
    try:
        alias_suffix = signer.unsign(signed_suffix, max_age=600).decode()
    except SignatureExpired:
        LOG.warning("Alias creation time expired for %s", user)
        return jsonify(
            error="Alias creation time is expired, please retry"), 412
    except Exception:
        LOG.warning("Alias suffix is tampered, user %s", user)
        return jsonify(error="Tampered suffix"), 400

    if not verify_prefix_suffix(user, alias_prefix, alias_suffix):
        return jsonify(error="wrong alias prefix or suffix"), 400

    full_alias = alias_prefix + alias_suffix
    if (Alias.get_by(email=full_alias) or DeletedAlias.get_by(email=full_alias)
            or DomainDeletedAlias.get_by(email=full_alias)):
        LOG.d("full alias already used %s", full_alias)
        return jsonify(error=f"alias {full_alias} already exists"), 409

    custom_domain_id = None
    if alias_suffix.startswith("@"):
        alias_domain = alias_suffix[1:]
        domain = CustomDomain.get_by(domain=alias_domain)

        # check if the alias is currently in the domain trash
        if domain and DomainDeletedAlias.get_by(domain_id=domain.id,
                                                email=full_alias):
            LOG.d(
                f"Alias {full_alias} is currently in the {domain.domain} trash. "
            )
            return jsonify(error=f"alias {full_alias} in domain trash"), 409

        if domain:
            custom_domain_id = domain.id

    alias = Alias.create(
        user_id=user.id,
        email=full_alias,
        mailbox_id=user.default_mailbox_id,
        note=note,
        custom_domain_id=custom_domain_id,
    )

    db.session.commit()

    if hostname:
        AliasUsedOn.create(alias_id=alias.id,
                           hostname=hostname,
                           user_id=alias.user_id)
        db.session.commit()

    return (
        jsonify(alias=full_alias,
                **serialize_alias_info_v2(get_alias_info_v2(alias))),
        201,
    )
Ejemplo n.º 19
0
def authorize():
    """
    Redirected from client when user clicks on "Login with Server".
    This is a GET request with the following field in url
    - client_id
    - (optional) state
    - response_type: must be code
    """
    oauth_client_id = request.args.get("client_id")
    state = request.args.get("state")
    scope = request.args.get("scope")
    redirect_uri = request.args.get("redirect_uri")
    response_mode = request.args.get("response_mode")
    nonce = request.args.get("nonce")

    try:
        response_types: [ResponseType] = get_response_types(request)
    except ValueError:
        return (
            "response_type must be code, token, id_token or certain combination of these."
            " Please see /.well-known/openid-configuration to see what response_type are supported ",
            400,
        )

    if set(response_types) not in SUPPORTED_OPENID_FLOWS:
        return (
            f"SimpleLogin only support the following OIDC flows: {SUPPORTED_OPENID_FLOWS_STR}",
            400,
        )

    if not redirect_uri:
        LOG.d("no redirect uri")
        return "redirect_uri must be set", 400

    client = Client.get_by(oauth_client_id=oauth_client_id)
    if not client:
        final_redirect_uri = (
            f"{redirect_uri}?error=invalid_client_id&client_id={oauth_client_id}"
        )
        return redirect(final_redirect_uri)

    # check if redirect_uri is valid
    # allow localhost by default
    hostname, scheme = get_host_name_and_scheme(redirect_uri)
    if hostname != "localhost" and hostname != "127.0.0.1":
        # support custom scheme for mobile app
        if scheme == "http":
            final_redirect_uri = f"{redirect_uri}?error=http_not_allowed"
            return redirect(final_redirect_uri)

        if not RedirectUri.get_by(client_id=client.id, uri=redirect_uri):
            final_redirect_uri = f"{redirect_uri}?error=unknown_redirect_uri"
            return redirect(final_redirect_uri)

    # redirect from client website
    if request.method == "GET":
        if current_user.is_authenticated:
            suggested_email, other_emails, email_suffix = None, [], None
            suggested_name, other_names = None, []

            # user has already allowed this client
            client_user: ClientUser = ClientUser.get_by(
                client_id=client.id, user_id=current_user.id)
            user_info = {}
            if client_user:
                LOG.debug("user %s has already allowed client %s",
                          current_user, client)
                user_info = client_user.get_user_info()
            else:
                suggested_email, other_emails = current_user.suggested_emails(
                    client.name)
                suggested_name, other_names = current_user.suggested_names()

                user_custom_domains = [
                    cd.domain for cd in current_user.verified_custom_domains()
                ]
                # List of (is_custom_domain, alias-suffix, time-signed alias-suffix)
                suffixes = available_suffixes(current_user)

            return render_template(
                "oauth/authorize.html",
                Scope=Scope,
                EMAIL_DOMAIN=EMAIL_DOMAIN,
                **locals(),
            )
        else:
            # after user logs in, redirect user back to this page
            return render_template(
                "oauth/authorize_nonlogin_user.html",
                client=client,
                next=request.url,
                Scope=Scope,
            )
    else:  # POST - user allows or denies
        if request.form.get("button") == "deny":
            LOG.debug("User %s denies Client %s", current_user, client)
            final_redirect_uri = f"{redirect_uri}?error=deny&state={state}"
            return redirect(final_redirect_uri)

        LOG.debug("User %s allows Client %s", current_user, client)
        client_user = ClientUser.get_by(client_id=client.id,
                                        user_id=current_user.id)

        # user has already allowed this client, user cannot change information
        if client_user:
            LOG.d("user %s has already allowed client %s", current_user,
                  client)
        else:
            alias_prefix = request.form.get("prefix")
            signed_suffix = request.form.get("suffix")

            alias = None

            # user creates a new alias, not using suggested alias
            if alias_prefix:
                # should never happen as this is checked on the front-end
                if not current_user.can_create_new_alias():
                    raise Exception(
                        f"User {current_user} cannot create custom email")

                # hypothesis: user will click on the button in the 600 secs
                try:
                    alias_suffix = signer.unsign(signed_suffix,
                                                 max_age=600).decode()
                except SignatureExpired:
                    LOG.warning("Alias creation time expired for %s",
                                current_user)
                    flash("Alias creation time is expired, please retry",
                          "warning")
                    return redirect(request.url)
                except Exception:
                    LOG.exception("Alias suffix is tampered, user %s",
                                  current_user)
                    flash("Unknown error, refresh the page", "error")
                    return redirect(request.url)

                user_custom_domains = [
                    cd.domain for cd in current_user.verified_custom_domains()
                ]

                from app.dashboard.views.custom_alias import verify_prefix_suffix

                if verify_prefix_suffix(current_user, alias_prefix,
                                        alias_suffix):
                    full_alias = alias_prefix + alias_suffix

                    if (Alias.get_by(email=full_alias)
                            or DeletedAlias.get_by(email=full_alias)
                            or DomainDeletedAlias.get_by(email=full_alias)):
                        LOG.exception("alias %s already used, very rare!",
                                      full_alias)
                        flash(f"Alias {full_alias} already used", "error")
                        return redirect(request.url)
                    else:
                        alias = Alias.create(
                            user_id=current_user.id,
                            email=full_alias,
                            mailbox_id=current_user.default_mailbox_id,
                        )

                        # get the custom_domain_id if alias is created with a custom domain
                        if alias_suffix.startswith("@"):
                            alias_domain = alias_suffix[1:]
                            domain = CustomDomain.get_by(domain=alias_domain)
                            if domain:
                                alias.custom_domain_id = domain.id

                        db.session.flush()
                        flash(f"Alias {full_alias} has been created",
                              "success")
                # only happen if the request has been "hacked"
                else:
                    flash("something went wrong", "warning")
                    return redirect(request.url)
            # User chooses one of the suggestions
            else:
                chosen_email = request.form.get("suggested-email")
                # todo: add some checks on chosen_email
                if chosen_email != current_user.email:
                    alias = Alias.get_by(email=chosen_email)
                    if not alias:
                        alias = Alias.create(
                            email=chosen_email,
                            user_id=current_user.id,
                            mailbox_id=current_user.default_mailbox_id,
                        )
                        db.session.flush()

            suggested_name = request.form.get("suggested-name")
            custom_name = request.form.get("custom-name")

            use_default_avatar = request.form.get("avatar-choice") == "default"

            client_user = ClientUser.create(client_id=client.id,
                                            user_id=current_user.id)
            if alias:
                client_user.alias_id = alias.id

            if custom_name:
                client_user.name = custom_name
            elif suggested_name != current_user.name:
                client_user.name = suggested_name

            if use_default_avatar:
                # use default avatar
                LOG.d("use default avatar for user %s client %s", current_user,
                      client)
                client_user.default_avatar = True

            db.session.flush()
            LOG.d("create client-user for client %s, user %s", client,
                  current_user)

        redirect_args = {}

        if state:
            redirect_args["state"] = state
        else:
            LOG.warning(
                "more security reason, state should be added. client %s",
                client)

        if scope:
            redirect_args["scope"] = scope

        auth_code = None
        if ResponseType.CODE in response_types:
            # Create authorization code
            auth_code = AuthorizationCode.create(
                client_id=client.id,
                user_id=current_user.id,
                code=random_string(),
                scope=scope,
                redirect_uri=redirect_uri,
                response_type=response_types_to_str(response_types),
            )
            db.session.add(auth_code)
            redirect_args["code"] = auth_code.code

        oauth_token = None
        if ResponseType.TOKEN in response_types:
            # create access-token
            oauth_token = OauthToken.create(
                client_id=client.id,
                user_id=current_user.id,
                scope=scope,
                redirect_uri=redirect_uri,
                access_token=generate_access_token(),
                response_type=response_types_to_str(response_types),
            )
            db.session.add(oauth_token)
            redirect_args["access_token"] = oauth_token.access_token

        if ResponseType.ID_TOKEN in response_types:
            redirect_args["id_token"] = make_id_token(
                client_user,
                nonce,
                oauth_token.access_token if oauth_token else None,
                auth_code.code if auth_code else None,
            )

        db.session.commit()

        # should all params appended the url using fragment (#) or query
        fragment = False

        if response_mode and response_mode == "fragment":
            fragment = True

        # if response_types contain "token" => implicit flow => should use fragment
        # except if client sets explicitly response_mode
        if not response_mode:
            if ResponseType.TOKEN in response_types:
                fragment = True

        # construct redirect_uri with redirect_args
        return redirect(construct_url(redirect_uri, redirect_args, fragment))
Ejemplo n.º 20
0
def custom_alias():
    # check if user has not exceeded the alias quota
    if not current_user.can_create_new_alias():
        # notify admin
        LOG.error("user %s tries to create custom alias", current_user)
        flash(
            "You have reached free plan limit, please upgrade to create new aliases",
            "warning",
        )
        return redirect(url_for("dashboard.index"))

    user_custom_domains = [cd.domain for cd in current_user.verified_custom_domains()]
    # List of (is_custom_domain, alias-suffix)
    suffixes = []

    # put custom domain first
    for alias_domain in user_custom_domains:
        suffixes.append((True, "@" + alias_domain))

    # then default domain
    for domain in ALIAS_DOMAINS:
        suffixes.append(
            (
                False,
                ("" if DISABLE_ALIAS_SUFFIX else "." + random_word()) + "@" + domain,
            )
        )

    if request.method == "POST":
        alias_prefix = request.form.get("prefix")
        alias_suffix = request.form.get("suffix")
        alias_note = request.form.get("note")

        if verify_prefix_suffix(
            current_user, alias_prefix, alias_suffix, user_custom_domains
        ):
            full_alias = alias_prefix + alias_suffix

            if GenEmail.get_by(email=full_alias) or DeletedAlias.get_by(
                email=full_alias
            ):
                LOG.d("full alias already used %s", full_alias)
                flash(
                    f"Alias {full_alias} already exists, please choose another one",
                    "warning",
                )
            else:
                gen_email = GenEmail.create(
                    user_id=current_user.id, email=full_alias, note=alias_note
                )

                # get the custom_domain_id if alias is created with a custom domain
                alias_domain = get_email_domain_part(full_alias)
                custom_domain = CustomDomain.get_by(domain=alias_domain)
                if custom_domain:
                    gen_email.custom_domain_id = custom_domain.id

                db.session.commit()
                flash(f"Alias {full_alias} has been created", "success")

                return redirect(
                    url_for("dashboard.index", highlight_gen_email_id=gen_email.id)
                )
        # only happen if the request has been "hacked"
        else:
            flash("something went wrong", "warning")

    return render_template("dashboard/custom_alias.html", **locals())
Ejemplo n.º 21
0
def authorize():
    """
    Redirected from client when user clicks on "Login with Server".
    This is a GET request with the following field in url
    - client_id
    - (optional) state
    - response_type: must be code
    """
    oauth_client_id = request.args.get("client_id")
    state = request.args.get("state")
    scope = request.args.get("scope")
    redirect_uri = request.args.get("redirect_uri")
    response_mode = request.args.get("response_mode")
    nonce = request.args.get("nonce")

    try:
        response_types: [ResponseType] = get_response_types(request)
    except ValueError:
        return (
            "response_type must be code, token, id_token or certain combination of these."
            " Please see /.well-known/openid-configuration to see what response_type are supported ",
            400,
        )

    if set(response_types) not in SUPPORTED_OPENID_FLOWS:
        return (
            f"SimpleLogin only support the following OIDC flows: {SUPPORTED_OPENID_FLOWS_STR}",
            400,
        )

    if not redirect_uri:
        LOG.d("no redirect uri")
        return "redirect_uri must be set", 400

    client = Client.get_by(oauth_client_id=oauth_client_id)
    if not client:
        final_redirect_uri = (
            f"{redirect_uri}?error=invalid_client_id&client_id={oauth_client_id}"
        )
        return redirect(final_redirect_uri)

    # check if redirect_uri is valid
    # allow localhost by default
    # allow any redirect_uri if the app isn't approved
    hostname, scheme = get_host_name_and_scheme(redirect_uri)
    if hostname != "localhost" and hostname != "127.0.0.1" and client.approved:
        # support custom scheme for mobile app
        if scheme == "http":
            final_redirect_uri = f"{redirect_uri}?error=http_not_allowed"
            return redirect(final_redirect_uri)

        if not RedirectUri.get_by(client_id=client.id, uri=redirect_uri):
            final_redirect_uri = f"{redirect_uri}?error=unknown_redirect_uri"
            return redirect(final_redirect_uri)

    # redirect from client website
    if request.method == "GET":
        if current_user.is_authenticated:
            suggested_email, other_emails, email_suffix = None, [], None
            suggested_name, other_names = None, []

            # user has already allowed this client
            client_user: ClientUser = ClientUser.get_by(
                client_id=client.id, user_id=current_user.id)
            user_info = {}
            if client_user:
                LOG.d("user %s has already allowed client %s", current_user,
                      client)
                user_info = client_user.get_user_info()

                # redirect user to the client page
                redirect_args = construct_redirect_args(
                    client,
                    client_user,
                    nonce,
                    redirect_uri,
                    response_types,
                    scope,
                    state,
                )
                fragment = get_fragment(response_mode, response_types)

                # construct redirect_uri with redirect_args
                return redirect(
                    construct_url(redirect_uri, redirect_args, fragment))
            else:
                suggested_email, other_emails = current_user.suggested_emails(
                    client.name)
                suggested_name, other_names = current_user.suggested_names()

                user_custom_domains = [
                    cd.domain for cd in current_user.verified_custom_domains()
                ]
                suffixes = get_available_suffixes(current_user)

            return render_template(
                "oauth/authorize.html",
                Scope=Scope,
                EMAIL_DOMAIN=EMAIL_DOMAIN,
                **locals(),
            )
        else:
            # after user logs in, redirect user back to this page
            return render_template(
                "oauth/authorize_nonlogin_user.html",
                client=client,
                next=request.url,
                Scope=Scope,
            )
    else:  # POST - user allows or denies
        if not current_user.is_authenticated or not current_user.is_active:
            LOG.i(
                "Attempt to validate a OAUth allow request by an unauthenticated user"
            )
            return redirect(url_for("auth.login", next=request.url))

        if request.form.get("button") == "deny":
            LOG.d("User %s denies Client %s", current_user, client)
            final_redirect_uri = f"{redirect_uri}?error=deny&state={state}"
            return redirect(final_redirect_uri)

        LOG.d("User %s allows Client %s", current_user, client)
        client_user = ClientUser.get_by(client_id=client.id,
                                        user_id=current_user.id)

        # user has already allowed this client, user cannot change information
        if client_user:
            LOG.d("user %s has already allowed client %s", current_user,
                  client)
        else:
            alias_prefix = request.form.get("prefix")
            signed_suffix = request.form.get("suffix")

            alias = None

            # user creates a new alias, not using suggested alias
            if alias_prefix:
                # should never happen as this is checked on the front-end
                if not current_user.can_create_new_alias():
                    raise Exception(
                        f"User {current_user} cannot create custom email")

                alias_prefix = alias_prefix.strip().lower().replace(" ", "")

                if not check_alias_prefix(alias_prefix):
                    flash(
                        "Only lowercase letters, numbers, dashes (-), dots (.) and underscores (_) "
                        "are currently supported for alias prefix. Cannot be more than 40 letters",
                        "error",
                    )
                    return redirect(request.url)

                # hypothesis: user will click on the button in the 600 secs
                try:
                    alias_suffix = signer.unsign(signed_suffix,
                                                 max_age=600).decode()
                except SignatureExpired:
                    LOG.w("Alias creation time expired for %s", current_user)
                    flash("Alias creation time is expired, please retry",
                          "warning")
                    return redirect(request.url)
                except Exception:
                    LOG.w("Alias suffix is tampered, user %s", current_user)
                    flash("Unknown error, refresh the page", "error")
                    return redirect(request.url)

                user_custom_domains = [
                    cd.domain for cd in current_user.verified_custom_domains()
                ]

                from app.dashboard.views.custom_alias import verify_prefix_suffix

                if verify_prefix_suffix(current_user, alias_prefix,
                                        alias_suffix):
                    full_alias = alias_prefix + alias_suffix

                    if (Alias.get_by(email=full_alias)
                            or DeletedAlias.get_by(email=full_alias)
                            or DomainDeletedAlias.get_by(email=full_alias)):
                        LOG.e("alias %s already used, very rare!", full_alias)
                        flash(f"Alias {full_alias} already used", "error")
                        return redirect(request.url)
                    else:
                        alias = Alias.create(
                            user_id=current_user.id,
                            email=full_alias,
                            mailbox_id=current_user.default_mailbox_id,
                        )

                        Session.flush()
                        flash(f"Alias {full_alias} has been created",
                              "success")
                # only happen if the request has been "hacked"
                else:
                    flash("something went wrong", "warning")
                    return redirect(request.url)
            # User chooses one of the suggestions
            else:
                chosen_email = request.form.get("suggested-email")
                # todo: add some checks on chosen_email
                if chosen_email != current_user.email:
                    alias = Alias.get_by(email=chosen_email)
                    if not alias:
                        alias = Alias.create(
                            email=chosen_email,
                            user_id=current_user.id,
                            mailbox_id=current_user.default_mailbox_id,
                        )
                        Session.flush()

            suggested_name = request.form.get("suggested-name")
            custom_name = request.form.get("custom-name")

            use_default_avatar = request.form.get("avatar-choice") == "default"

            client_user = ClientUser.create(client_id=client.id,
                                            user_id=current_user.id)
            if alias:
                client_user.alias_id = alias.id

            if custom_name:
                client_user.name = custom_name
            elif suggested_name != current_user.name:
                client_user.name = suggested_name

            if use_default_avatar:
                # use default avatar
                LOG.d("use default avatar for user %s client %s", current_user,
                      client)
                client_user.default_avatar = True

            Session.flush()
            LOG.d("create client-user for client %s, user %s", client,
                  current_user)

        redirect_args = construct_redirect_args(client, client_user, nonce,
                                                redirect_uri, response_types,
                                                scope, state)
        fragment = get_fragment(response_mode, response_types)

        # construct redirect_uri with redirect_args
        return redirect(construct_url(redirect_uri, redirect_args, fragment))
Ejemplo n.º 22
0
def authorize():
    """
    Redirected from client when user clicks on "Login with Server".
    This is a GET request with the following field in url
    - client_id
    - (optional) state
    - response_type: must be code
    """
    oauth_client_id = request.args.get("client_id")
    state = request.args.get("state")
    scope = request.args.get("scope")
    redirect_uri = request.args.get("redirect_uri")
    response_mode = request.args.get("response_mode")
    nonce = request.args.get("nonce")

    try:
        response_types: [ResponseType] = get_response_types(request)
    except ValueError:
        return (
            "response_type must be code, token, id_token or certain combination of these."
            " Please see /.well-known/openid-configuration to see what response_type are supported ",
            400,
        )

    if set(response_types) not in SUPPORTED_OPENID_FLOWS:
        return (
            f"SimpleLogin only support the following OIDC flows: {SUPPORTED_OPENID_FLOWS_STR}",
            400,
        )

    if not redirect_uri:
        LOG.d("no redirect uri")
        return "redirect_uri must be set", 400

    client = Client.get_by(oauth_client_id=oauth_client_id)
    if not client:
        final_redirect_uri = (
            f"{redirect_uri}?error=invalid_client_id&client_id={oauth_client_id}"
        )
        return redirect(final_redirect_uri)

    # check if redirect_uri is valid
    # allow localhost by default
    hostname, scheme = get_host_name_and_scheme(redirect_uri)
    if hostname != "localhost" and hostname != "127.0.0.1":
        # support custom scheme for mobile app
        if scheme == "http":
            final_redirect_uri = f"{redirect_uri}?error=http_not_allowed"
            return redirect(final_redirect_uri)

        if not RedirectUri.get_by(client_id=client.id, uri=redirect_uri):
            final_redirect_uri = f"{redirect_uri}?error=unknown_redirect_uri"
            return redirect(final_redirect_uri)

    # redirect from client website
    if request.method == "GET":
        if current_user.is_authenticated:
            suggested_email, other_emails, email_suffix = None, [], None
            suggested_name, other_names = None, []

            # user has already allowed this client
            client_user: ClientUser = ClientUser.get_by(
                client_id=client.id, user_id=current_user.id
            )
            user_info = {}
            if client_user:
                LOG.debug("user %s has already allowed client %s", current_user, client)
                user_info = client_user.get_user_info()
            else:
                suggested_email, other_emails = current_user.suggested_emails(
                    client.name
                )
                suggested_name, other_names = current_user.suggested_names()
                email_suffix = random_word()

            return render_template(
                "oauth/authorize.html",
                client=client,
                user_info=user_info,
                client_user=client_user,
                Scope=Scope,
                suggested_email=suggested_email,
                personal_email=current_user.email,
                suggested_name=suggested_name,
                other_names=other_names,
                other_emails=other_emails,
                email_suffix=email_suffix,
                EMAIL_DOMAIN=EMAIL_DOMAIN,
            )
        else:
            # after user logs in, redirect user back to this page
            return render_template(
                "oauth/authorize_nonlogin_user.html",
                client=client,
                next=request.url,
                Scope=Scope,
            )
    else:  # user allows or denies
        if request.form.get("button") == "deny":
            LOG.debug("User %s denies Client %s", current_user, client)
            final_redirect_uri = f"{redirect_uri}?error=deny&state={state}"
            return redirect(final_redirect_uri)

        LOG.debug("User %s allows Client %s", current_user, client)
        client_user = ClientUser.get_by(client_id=client.id, user_id=current_user.id)

        # user has already allowed this client, user cannot change information
        if client_user:
            LOG.d("user %s has already allowed client %s", current_user, client)
        else:
            email_suffix = request.form.get("email-suffix")
            custom_email_prefix = request.form.get("custom-email-prefix")
            chosen_email = request.form.get("suggested-email")

            suggested_name = request.form.get("suggested-name")
            custom_name = request.form.get("custom-name")

            use_default_avatar = request.form.get("avatar-choice") == "default"

            gen_email = None
            if custom_email_prefix:
                # check if user can generate custom email
                if not current_user.can_create_new_alias():
                    raise Exception(f"User {current_user} cannot create custom email")

                email = f"{convert_to_id(custom_email_prefix)}.{email_suffix}@{EMAIL_DOMAIN}"
                LOG.d("create custom email alias %s for user %s", email, current_user)

                if GenEmail.get_by(email=email) or DeletedAlias.get_by(email=email):
                    LOG.error("email %s already used, very rare!", email)
                    flash(f"alias {email} already used", "error")
                    return redirect(request.url)

                gen_email = GenEmail.create(email=email, user_id=current_user.id)
                db.session.flush()
            else:  # user picks an email from suggestion
                if chosen_email != current_user.email:
                    gen_email = GenEmail.get_by(email=chosen_email)
                    if not gen_email:
                        gen_email = GenEmail.create(
                            email=chosen_email, user_id=current_user.id
                        )
                        db.session.flush()

            client_user = ClientUser.create(
                client_id=client.id, user_id=current_user.id
            )
            if gen_email:
                client_user.gen_email_id = gen_email.id

            if custom_name:
                LOG.d(
                    "use custom name %s for user %s client %s",
                    custom_name,
                    current_user,
                    client,
                )
                client_user.name = custom_name
            elif suggested_name != current_user.name:
                LOG.d(
                    "use another name %s for user %s client %s",
                    custom_name,
                    current_user,
                    client,
                )
                client_user.name = suggested_name

            if use_default_avatar:
                # use default avatar
                LOG.d("use default avatar for user %s client %s", current_user, client)
                client_user.default_avatar = True

            db.session.flush()
            LOG.d("create client-user for client %s, user %s", client, current_user)

        redirect_args = {}

        if state:
            redirect_args["state"] = state
        else:
            LOG.warning(
                "more security reason, state should be added. client %s", client
            )

        if scope:
            redirect_args["scope"] = scope

        auth_code = None
        if ResponseType.CODE in response_types:
            # Create authorization code
            auth_code = AuthorizationCode.create(
                client_id=client.id,
                user_id=current_user.id,
                code=random_string(),
                scope=scope,
                redirect_uri=redirect_uri,
                response_type=response_types_to_str(response_types),
            )
            db.session.add(auth_code)
            redirect_args["code"] = auth_code.code

        oauth_token = None
        if ResponseType.TOKEN in response_types:
            # create access-token
            oauth_token = OauthToken.create(
                client_id=client.id,
                user_id=current_user.id,
                scope=scope,
                redirect_uri=redirect_uri,
                access_token=generate_access_token(),
                response_type=response_types_to_str(response_types),
            )
            db.session.add(oauth_token)
            redirect_args["access_token"] = oauth_token.access_token

        if ResponseType.ID_TOKEN in response_types:
            redirect_args["id_token"] = make_id_token(
                client_user,
                nonce,
                oauth_token.access_token if oauth_token else None,
                auth_code.code if auth_code else None,
            )

        db.session.commit()

        # should all params appended the url using fragment (#) or query
        fragment = False

        if response_mode and response_mode == "fragment":
            fragment = True

        # if response_types contain "token" => implicit flow => should use fragment
        # except if client sets explicitly response_mode
        if not response_mode:
            if ResponseType.TOKEN in response_types:
                fragment = True

        # construct redirect_uri with redirect_args
        return redirect(construct_url(redirect_uri, redirect_args, fragment))
Ejemplo n.º 23
0
def custom_alias():
    # check if user has not exceeded the alias quota
    if not current_user.can_create_new_alias():
        LOG.d("%s can't create new alias", current_user)
        flash(
            "You have reached free plan limit, please upgrade to create new aliases",
            "warning",
        )
        return redirect(url_for("dashboard.index"))

    user_custom_domains = [
        cd.domain for cd in current_user.verified_custom_domains()
    ]
    alias_suffixes = get_alias_suffixes(current_user)
    at_least_a_premium_domain = False
    for alias_suffix in alias_suffixes:
        if not alias_suffix.is_custom and alias_suffix.is_premium:
            at_least_a_premium_domain = True
            break

    alias_suffixes_with_signature = [
        (alias_suffix, signer.sign(alias_suffix.serialize()).decode())
        for alias_suffix in alias_suffixes
    ]

    mailboxes = current_user.mailboxes()

    if request.method == "POST":
        alias_prefix = request.form.get("prefix").strip().lower().replace(
            " ", "")
        signed_alias_suffix = request.form.get("signed-alias-suffix")
        mailbox_ids = request.form.getlist("mailboxes")
        alias_note = request.form.get("note")

        if not check_alias_prefix(alias_prefix):
            flash(
                "Only lowercase letters, numbers, dashes (-), dots (.) and underscores (_) "
                "are currently supported for alias prefix. Cannot be more than 40 letters",
                "error",
            )
            return redirect(request.url)

        # 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(request.url)
            mailboxes.append(mailbox)

        if not mailboxes:
            flash("At least one mailbox must be selected", "error")
            return redirect(request.url)

        # hypothesis: user will click on the button in the 600 secs
        try:
            signed_alias_suffix_decoded = signer.unsign(signed_alias_suffix,
                                                        max_age=600).decode()
            alias_suffix: AliasSuffix = AliasSuffix.deserialize(
                signed_alias_suffix_decoded)
        except SignatureExpired:
            LOG.w("Alias creation time expired for %s", current_user)
            flash("Alias creation time is expired, please retry", "warning")
            return redirect(request.url)
        except Exception:
            LOG.w("Alias suffix is tampered, user %s", current_user)
            flash("Unknown error, refresh the page", "error")
            return redirect(request.url)

        if verify_prefix_suffix(current_user, alias_prefix,
                                alias_suffix.suffix):
            full_alias = alias_prefix + alias_suffix.suffix

            if ".." in full_alias:
                flash("Your alias can't contain 2 consecutive dots (..)",
                      "error")
                return redirect(request.url)

            try:
                validate_email(full_alias,
                               check_deliverability=False,
                               allow_smtputf8=False)
            except EmailNotValidError as e:
                flash(str(e), "error")
                return redirect(request.url)

            general_error_msg = f"{full_alias} cannot be used"

            if Alias.get_by(email=full_alias):
                alias = Alias.get_by(email=full_alias)
                if alias.user_id == current_user.id:
                    flash(f"You already have this alias {full_alias}", "error")
                else:
                    flash(general_error_msg, "error")
            elif DomainDeletedAlias.get_by(email=full_alias):
                domain_deleted_alias: DomainDeletedAlias = DomainDeletedAlias.get_by(
                    email=full_alias)
                custom_domain = domain_deleted_alias.domain
                if domain_deleted_alias.user_id == current_user.id:
                    flash(
                        f"You have deleted this alias before. You can restore it on "
                        f"{custom_domain.domain} 'Deleted Alias' page",
                        "error",
                    )
                else:
                    # should never happen as user can only choose their domains
                    LOG.e(
                        "Deleted Alias %s does not belong to user %s",
                        domain_deleted_alias,
                    )

            elif DeletedAlias.get_by(email=full_alias):
                flash(general_error_msg, "error")

            else:
                try:
                    alias = Alias.create(
                        user_id=current_user.id,
                        email=full_alias,
                        note=alias_note,
                        mailbox_id=mailboxes[0].id,
                    )
                    Session.flush()
                except IntegrityError:
                    LOG.w("Alias %s already exists", full_alias)
                    Session.rollback()
                    flash("Unknown error, please retry", "error")
                    return redirect(url_for("dashboard.custom_alias"))

                for i in range(1, len(mailboxes)):
                    AliasMailbox.create(
                        alias_id=alias.id,
                        mailbox_id=mailboxes[i].id,
                    )

                Session.commit()
                flash(f"Alias {full_alias} has been created", "success")

                return redirect(
                    url_for("dashboard.index", highlight_alias_id=alias.id))
        # only happen if the request has been "hacked"
        else:
            flash("something went wrong", "warning")

    return render_template(
        "dashboard/custom_alias.html",
        user_custom_domains=user_custom_domains,
        alias_suffixes_with_signature=alias_suffixes_with_signature,
        at_least_a_premium_domain=at_least_a_premium_domain,
        mailboxes=mailboxes,
    )
Ejemplo n.º 24
0
def fake_data():
    LOG.d("create fake data")
    # Remove db if exist
    if os.path.exists("db.sqlite"):
        LOG.d("remove existing db file")
        os.remove("db.sqlite")

    # Create all tables
    db.create_all()

    # Create a user
    user = User.create(
        email="*****@*****.**",
        name="John Wick",
        password="******",
        activated=True,
        is_admin=True,
        otp_secret="base32secret3232",
        can_use_pgp=True,
    )
    db.session.commit()

    LifetimeCoupon.create(code="coupon", nb_used=10)
    db.session.commit()

    # Create a subscription for user
    Subscription.create(
        user_id=user.id,
        cancel_url="https://checkout.paddle.com/subscription/cancel?user=1234",
        update_url="https://checkout.paddle.com/subscription/update?user=1234",
        subscription_id="123",
        event_time=arrow.now(),
        next_bill_date=arrow.now().shift(days=10).date(),
        plan=PlanEnum.monthly,
    )
    db.session.commit()

    api_key = ApiKey.create(user_id=user.id, name="Chrome")
    api_key.code = "codeCH"

    api_key = ApiKey.create(user_id=user.id, name="Firefox")
    api_key.code = "codeFF"

    m1 = Mailbox.create(user_id=user.id, email="*****@*****.**", verified=True)
    db.session.commit()

    user.default_mailbox_id = m1.id

    GenEmail.create_new(user, "e1@", mailbox_id=m1.id)

    CustomDomain.create(user_id=user.id, domain="ab.cd", verified=True)
    CustomDomain.create(user_id=user.id,
                        domain="very-long-domain.com.net.org",
                        verified=True)
    db.session.commit()

    Directory.create(user_id=user.id, name="abcd")
    Directory.create(user_id=user.id, name="xyzt")
    db.session.commit()

    # Create a client
    client1 = Client.create_new(name="Demo", user_id=user.id)
    client1.oauth_client_id = "client-id"
    client1.oauth_client_secret = "client-secret"
    client1.published = True
    db.session.commit()

    RedirectUri.create(client_id=client1.id, uri="https://ab.com")

    client2 = Client.create_new(name="Demo 2", user_id=user.id)
    client2.oauth_client_id = "client-id2"
    client2.oauth_client_secret = "client-secret2"
    client2.published = True
    db.session.commit()

    DeletedAlias.create(user_id=user.id, email="*****@*****.**")
    DeletedAlias.create(user_id=user.id, email="*****@*****.**")
    db.session.commit()
Ejemplo n.º 25
0
def index():
    # after creating a gen email, it's helpful to highlight it
    highlight_gen_email_id = session.get(HIGHLIGHT_GEN_EMAIL_ID)

    # reset as it should not persist
    if highlight_gen_email_id:
        del session[HIGHLIGHT_GEN_EMAIL_ID]

    query = request.args.get("query") or ""

    # User generates a new email
    if request.method == "POST":
        if request.form.get("form-name") == "trigger-email":
            gen_email_id = request.form.get("gen-email-id")
            gen_email = GenEmail.get(gen_email_id)

            LOG.d("trigger an email to %s", gen_email)
            email_utils.send_test_email_alias(gen_email.email, gen_email.user.name)

            flash(
                f"An email sent to {gen_email.email} is on its way, please check your inbox/spam folder",
                "success",
            )

        elif request.form.get("form-name") == "create-custom-email":
            if current_user.can_create_new_alias():
                return redirect(url_for("dashboard.custom_alias"))
            else:
                flash(f"You need to upgrade your plan to create new alias.", "warning")

        elif request.form.get("form-name") == "create-random-email":
            if current_user.can_create_new_alias():
                scheme = int(
                    request.form.get("generator_scheme") or current_user.alias_generator
                )
                if not scheme or not AliasGeneratorEnum.has_value(scheme):
                    scheme = current_user.alias_generator
                gen_email = GenEmail.create_new_random(
                    user_id=current_user.id, scheme=scheme
                )
                db.session.commit()

                LOG.d("generate new email %s for user %s", gen_email, current_user)
                flash(f"Alias {gen_email.email} has been created", "success")
                session[HIGHLIGHT_GEN_EMAIL_ID] = gen_email.id
            else:
                flash(f"You need to upgrade your plan to create new alias.", "warning")

        elif request.form.get("form-name") == "switch-email-forwarding":
            gen_email_id = request.form.get("gen-email-id")
            gen_email: GenEmail = GenEmail.get(gen_email_id)

            LOG.d("switch email forwarding for %s", gen_email)

            gen_email.enabled = not gen_email.enabled
            if gen_email.enabled:
                flash(f"Alias {gen_email.email} is enabled", "success")
            else:
                flash(f"Alias {gen_email.email} is disabled", "warning")

            session[HIGHLIGHT_GEN_EMAIL_ID] = gen_email.id
            db.session.commit()

        elif request.form.get("form-name") == "delete-email":
            gen_email_id = request.form.get("gen-email-id")
            gen_email: GenEmail = GenEmail.get(gen_email_id)

            LOG.d("delete gen email %s", gen_email)
            email = gen_email.email
            GenEmail.delete(gen_email.id)

            # save deleted alias
            DeletedAlias.create(user_id=current_user.id, email=gen_email.email)

            db.session.commit()
            flash(f"Alias {email} has been deleted", "success")

        return redirect(url_for("dashboard.index", query=query))

    client_users = (
        ClientUser.filter_by(user_id=current_user.id)
        .options(joinedload(ClientUser.client))
        .options(joinedload(ClientUser.gen_email))
        .all()
    )

    sorted(client_users, key=lambda cu: cu.client.name)

    return render_template(
        "dashboard/index.html",
        client_users=client_users,
        aliases=get_alias_info(current_user.id, query, highlight_gen_email_id),
        highlight_gen_email_id=highlight_gen_email_id,
        query=query,
        AliasGeneratorEnum=AliasGeneratorEnum,
    )
Ejemplo n.º 26
0
def custom_alias():
    # check if user has not exceeded the alias quota
    if not current_user.can_create_new_alias():
        # notify admin
        LOG.error("user %s tries to create custom alias", current_user)
        flash(
            "You have reached free plan limit, please upgrade to create new aliases",
            "warning",
        )
        return redirect(url_for("dashboard.index"))

    user_custom_domains = [
        cd.domain for cd in current_user.verified_custom_domains()
    ]
    # List of (is_custom_domain, alias-suffix, time-signed alias-suffix)
    suffixes = available_suffixes(current_user)

    mailboxes = current_user.mailboxes()

    if request.method == "POST":
        alias_prefix = request.form.get("prefix").strip().lower()
        signed_suffix = request.form.get("suffix")
        mailbox_ids = request.form.getlist("mailboxes")
        alias_note = request.form.get("note")

        # 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_alias"))
            mailboxes.append(mailbox)

        if not mailboxes:
            flash("At least one mailbox must be selected", "error")
            return redirect(url_for("dashboard.custom_alias"))

        # hypothesis: user will click on the button in the 600 secs
        try:
            alias_suffix = signer.unsign(signed_suffix, max_age=600).decode()
        except SignatureExpired:
            LOG.warning("Alias creation time expired for %s", current_user)
            flash("Alias creation time is expired, please retry", "warning")
            return redirect(url_for("dashboard.custom_alias"))
        except Exception:
            LOG.error("Alias suffix is tampered, user %s", current_user)
            flash("Unknown error, refresh the page", "error")
            return redirect(url_for("dashboard.custom_alias"))

        if verify_prefix_suffix(current_user, alias_prefix, alias_suffix):
            full_alias = alias_prefix + alias_suffix

            if (Alias.get_by(email=full_alias)
                    or DeletedAlias.get_by(email=full_alias)
                    or DomainDeletedAlias.get_by(email=full_alias)):
                LOG.d("full alias already used %s", full_alias)
                flash(
                    f"Alias {full_alias} already exists, please choose another one",
                    "warning",
                )
            else:
                custom_domain_id = None
                # get the custom_domain_id if alias is created with a custom domain
                if alias_suffix.startswith("@"):
                    alias_domain = alias_suffix[1:]
                    domain = CustomDomain.get_by(domain=alias_domain)

                    # check if the alias is currently in the domain trash
                    if domain and DomainDeletedAlias.get_by(
                            domain_id=domain.id, email=full_alias):
                        flash(
                            f"Alias {full_alias} is currently in the {domain.domain} trash. "
                            f"Please remove it from the trash in order to re-create it.",
                            "warning",
                        )
                        return redirect(url_for("dashboard.custom_alias"))

                    if domain:
                        custom_domain_id = domain.id

                alias = Alias.create(
                    user_id=current_user.id,
                    email=full_alias,
                    note=alias_note,
                    mailbox_id=mailboxes[0].id,
                    custom_domain_id=custom_domain_id,
                )
                db.session.flush()

                for i in range(1, len(mailboxes)):
                    AliasMailbox.create(
                        alias_id=alias.id,
                        mailbox_id=mailboxes[i].id,
                    )

                db.session.commit()
                flash(f"Alias {full_alias} has been created", "success")

                return redirect(
                    url_for("dashboard.index", highlight_alias_id=alias.id))
        # only happen if the request has been "hacked"
        else:
            flash("something went wrong", "warning")

    return render_template(
        "dashboard/custom_alias.html",
        user_custom_domains=user_custom_domains,
        suffixes=suffixes,
        mailboxes=mailboxes,
    )
Ejemplo n.º 27
0
def fake_data():
    LOG.d("create fake data")
    # Remove db if exist
    if os.path.exists("db.sqlite"):
        LOG.d("remove existing db file")
        os.remove("db.sqlite")

    # Create all tables
    db.create_all()

    # Create a user
    user = User.create(
        email="*****@*****.**",
        name="John Wick",
        password="******",
        activated=True,
        is_admin=True,
        otp_secret="base32secret3232",
        intro_shown=True,
        fido_uuid=None,
    )
    db.session.commit()

    user.trial_end = None

    LifetimeCoupon.create(code="coupon", nb_used=10)
    db.session.commit()

    # Create a subscription for user
    Subscription.create(
        user_id=user.id,
        cancel_url="https://checkout.paddle.com/subscription/cancel?user=1234",
        update_url="https://checkout.paddle.com/subscription/update?user=1234",
        subscription_id="123",
        event_time=arrow.now(),
        next_bill_date=arrow.now().shift(days=10).date(),
        plan=PlanEnum.monthly,
    )
    db.session.commit()

    api_key = ApiKey.create(user_id=user.id, name="Chrome")
    api_key.code = "code"

    api_key = ApiKey.create(user_id=user.id, name="Firefox")
    api_key.code = "codeFF"

    m1 = Mailbox.create(
        user_id=user.id,
        email="*****@*****.**",
        verified=True,
        pgp_finger_print="fake fingerprint",
    )
    db.session.commit()

    for i in range(3):
        if i % 2 == 0:
            a = Alias.create(
                email=f"e{i}@{FIRST_ALIAS_DOMAIN}", user_id=user.id, mailbox_id=m1.id
            )
        else:
            a = Alias.create(
                email=f"e{i}@{FIRST_ALIAS_DOMAIN}",
                user_id=user.id,
                mailbox_id=user.default_mailbox_id,
            )
        db.session.commit()

        if i % 5 == 0:
            if i % 2 == 0:
                AliasMailbox.create(alias_id=a.id, mailbox_id=user.default_mailbox_id)
            else:
                AliasMailbox.create(alias_id=a.id, mailbox_id=m1.id)
        db.session.commit()

        # some aliases don't have any activity
        # if i % 3 != 0:
        #     contact = Contact.create(
        #         user_id=user.id,
        #         alias_id=a.id,
        #         website_email=f"contact{i}@example.com",
        #         reply_email=f"rep{i}@sl.local",
        #     )
        #     db.session.commit()
        #     for _ in range(3):
        #         EmailLog.create(user_id=user.id, contact_id=contact.id)
        #         db.session.commit()

        # have some disabled alias
        if i % 5 == 0:
            a.enabled = False
            db.session.commit()

    CustomDomain.create(user_id=user.id, domain="ab.cd", verified=True)
    CustomDomain.create(
        user_id=user.id, domain="very-long-domain.com.net.org", verified=True
    )
    db.session.commit()

    Directory.create(user_id=user.id, name="abcd")
    Directory.create(user_id=user.id, name="xyzt")
    db.session.commit()

    # Create a client
    client1 = Client.create_new(name="Demo", user_id=user.id)
    client1.oauth_client_id = "client-id"
    client1.oauth_client_secret = "client-secret"
    client1.published = True
    db.session.commit()

    RedirectUri.create(client_id=client1.id, uri="https://ab.com")

    client2 = Client.create_new(name="Demo 2", user_id=user.id)
    client2.oauth_client_id = "client-id2"
    client2.oauth_client_secret = "client-secret2"
    client2.published = True
    db.session.commit()

    ClientUser.create(user_id=user.id, client_id=client1.id, name="Fake Name")

    DeletedAlias.create(email="*****@*****.**")
    DeletedAlias.create(email="*****@*****.**")
    db.session.commit()

    referral = Referral.create(user_id=user.id, code="REFCODE", name="First referral")
    db.session.commit()

    for i in range(6):
        Notification.create(user_id=user.id, message=f"""Hey hey <b>{i}</b> """ * 10)
    db.session.commit()

    User.create(
        email="*****@*****.**",
        name="Winston",
        password="******",
        activated=True,
        referral_id=referral.id,
    )
    db.session.commit()

    for d in ["d1.localhost", "d2.localhost"]:
        PublicDomain.create(domain=d)
    db.session.commit()
Ejemplo n.º 28
0
def setting():
    form = SettingForm()
    promo_form = PromoCodeForm()

    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-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(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(f"Your profile has been updated", "success")

                if (form.email.data and form.email.data != current_user.email
                        and not pending_email):
                    new_email = form.email.data

                    # check if this email is not used by other user, or as alias
                    if (User.get_by(email=new_email)
                            or GenEmail.get_by(email=new_email)
                            or DeletedAlias.get_by(email=new_email)):
                        flash(f"Email {new_email} already used", "error")
                    elif new_email.endswith(EMAIL_DOMAIN):
                        flash(
                            "You cannot use alias as your personal inbox. Nice try though 😉",
                            "error",
                        )
                    else:
                        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",
                        )

        elif request.form.get("form-name") == "change-password":
            send_reset_password_email(current_user)

        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")

        elif request.form.get("form-name") == "delete-account":
            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")

        elif request.form.get("form-name") == "export-data":
            data = {
                "email": current_user.email,
                "name": current_user.name,
                "aliases": [],
                "apps": [],
                "custom_domains": [],
            }

            for alias in GenEmail.filter_by(
                    user_id=current_user.id).all():  # type: GenEmail
                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"
                },
            )

        return redirect(url_for("dashboard.setting"))

    return render_template(
        "dashboard/setting.html",
        form=form,
        PlanEnum=PlanEnum,
        promo_form=promo_form,
        pending_email=pending_email,
        AliasGeneratorEnum=AliasGeneratorEnum,
    )
Ejemplo n.º 29
0
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())