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("ony premium user can choose custom alias", "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") 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): 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) db.session.commit() flash(f"Alias {full_alias} has been created", "success") session[HIGHLIGHT_GEN_EMAIL_ID] = gen_email.id return redirect(url_for("dashboard.index")) # only happen if the request has been "hacked" else: flash("something went wrong", "warning") return render_template("dashboard/custom_alias.html", **locals())
def batch_import_route(): # only for users who have custom domains if not current_user.verified_custom_domains(): flash("Alias batch import is only available for custom domains", "warning") if current_user.disable_import: flash( "you cannot use the import feature, please contact SimpleLogin team", "error", ) return redirect(url_for("dashboard.index")) batch_imports = BatchImport.filter_by(user_id=current_user.id).all() if request.method == "POST": alias_file = request.files["alias-file"] file_path = random_string(20) + ".csv" file = File.create(user_id=current_user.id, path=file_path) s3.upload_from_bytesio(file_path, alias_file) Session.flush() LOG.d("upload file %s to s3 at %s", file, file_path) bi = BatchImport.create(user_id=current_user.id, file_id=file.id) Session.flush() LOG.d("Add a batch import job %s for %s", bi, current_user) # Schedule batch import job Job.create( name=JOB_BATCH_IMPORT, payload={"batch_import_id": bi.id}, run_at=arrow.now(), ) Session.commit() flash( "The file has been uploaded successfully and the import will start shortly", "success", ) return redirect(url_for("dashboard.batch_import_route")) return render_template("dashboard/batch_import.html", batch_imports=batch_imports)
def 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, )
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))
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())
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))
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(), )
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, )
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, )