commit
e8e0923de7
|
@ -5,7 +5,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block single_content %}
|
||||
<div class="text-center">
|
||||
<div class="text-center bg-white p-5" style="max-width: 50rem">
|
||||
<h1>
|
||||
An email to validate your email is on its way.
|
||||
</h1>
|
||||
|
|
|
@ -16,4 +16,5 @@ from .views import (
|
|||
directory,
|
||||
mailbox,
|
||||
deleted_alias,
|
||||
mailbox_detail,
|
||||
)
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
|
||||
|
||||
<div class="col-sm-6 p-1">
|
||||
<select class="form-control" name="suffix">
|
||||
<select class="form-control custom-select" name="suffix">
|
||||
{% for suffix in suffixes %}
|
||||
<option value="{{ suffix[1] }}">
|
||||
{% if suffix[0] %}
|
||||
|
@ -54,10 +54,10 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{% if mailboxes|length > 1 %}
|
||||
{% if mailboxes|length > 1 or current_user.full_mailbox %}
|
||||
<div class="row mb-2">
|
||||
<div class="col p-1">
|
||||
<select class="form-control" name="mailbox">
|
||||
<select class="form-control custom-select" name="mailbox">
|
||||
{% for mailbox in mailboxes %}
|
||||
<option value="{{ mailbox }}">
|
||||
{{ mailbox }}
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
<input type="hidden" name="form-name" value="delete">
|
||||
<input type="hidden" class="dir-name" value="{{ dir.name }}">
|
||||
<input type="hidden" name="dir-id" value="{{ dir.id }}">
|
||||
<span class="card-link btn btn-link float-right delete-dir">
|
||||
<span class="card-link btn btn-link float-right text-danger delete-dir">
|
||||
Delete
|
||||
</span>
|
||||
</form>
|
||||
|
@ -85,8 +85,7 @@
|
|||
|
||||
{{ new_dir_form.name(class="form-control", placeholder="my-directory",
|
||||
pattern="[0-9a-z-_]{3,}",
|
||||
title="Only letter, number, dash (-), underscore (_) can be used. Directory name must be at least 3 characters.",
|
||||
autofocus="1") }}
|
||||
title="Only letter, number, dash (-), underscore (_) can be used. Directory name must be at least 3 characters.") }}
|
||||
{{ render_field_errors(new_dir_form.name) }}
|
||||
<button class="btn btn-lg btn-success mt-2">Create</button>
|
||||
</form>
|
||||
|
|
|
@ -134,12 +134,6 @@
|
|||
|
||||
<hr class="my-2">
|
||||
|
||||
{% if alias_info.mailbox != None %}
|
||||
<div class="small-text">
|
||||
Owned by <b>{{ alias_info.mailbox.email }}</b> mailbox
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<p class="small-text">
|
||||
Created {{ gen_email.created_at | dt }}
|
||||
{% if alias_info.highlight %}
|
||||
|
@ -157,10 +151,42 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
{% if current_user.full_mailbox and mailboxes|length > 1 %}
|
||||
<form method="post">
|
||||
<div class="small-text mt-2">Current mailbox</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-10">
|
||||
<select class="form-control form-control-sm custom-select" name="mailbox">
|
||||
{% for mailbox in mailboxes %}
|
||||
<option value="{{ mailbox }}" {% if mailbox == alias_info.mailbox.email %} selected {% endif %}>
|
||||
{{ mailbox }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-2">
|
||||
<input type="hidden" name="form-name" value="set-mailbox">
|
||||
<input type="hidden" name="gen-email-id" value="{{ gen_email.id }}">
|
||||
|
||||
<button class="btn btn-sm btn-outline-info w-100">
|
||||
Update
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
{% elif alias_info.mailbox != None and alias_info.mailbox.email != current_user.email %}
|
||||
<div class="small-text">
|
||||
Owned by <b>{{ alias_info.mailbox.email }}</b> mailbox
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<form method="post">
|
||||
<div class="row mt-2">
|
||||
|
||||
<div class="col-10">
|
||||
<div class="col-lg-10">
|
||||
<textarea
|
||||
name="note"
|
||||
class="form-control"
|
||||
|
@ -168,11 +194,11 @@
|
|||
placeholder="Alias Note.">{{ gen_email.note or "" }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="col-2">
|
||||
<div class="col-lg-2">
|
||||
<input type="hidden" name="form-name" value="set-note">
|
||||
<input type="hidden" name="gen-email-id" value="{{ gen_email.id }}">
|
||||
|
||||
<button class="btn btn-sm btn-outline-success">
|
||||
<button class="btn btn-sm btn-outline-success w-100">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -20,13 +20,15 @@
|
|||
A <em>mailbox</em> is just another personal email address. When creating a new alias, you could choose the
|
||||
mailbox that <em>owns</em> this alias, i.e: <br>
|
||||
- all emails sent to this alias will be forwarded to this mailbox <br>
|
||||
- from this mailbox, you can reply/send emails from the alias. <br>
|
||||
- from this mailbox, you can reply/send emails from the alias. <br><br>
|
||||
|
||||
By default, all aliases are owned by your email <b>{{ current_user.email }}</b>. <br><br>
|
||||
{% if current_user.full_mailbox %}
|
||||
When you signed up, a mailbox is automatically created with your email <b>{{ current_user.email }}</b>
|
||||
<br><br>
|
||||
{% endif %}
|
||||
|
||||
The mailbox doesn't have to be your email: it can be your friend's email
|
||||
if you want to create aliases for your buddy. <br>
|
||||
They just need to validate this mailbox when they receive the activation email.
|
||||
if you want to create aliases for your buddy.
|
||||
</div>
|
||||
|
||||
{% for mailbox in mailboxes %}
|
||||
|
@ -41,22 +43,46 @@
|
|||
🚫
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if mailbox.id == current_user.default_mailbox_id %}
|
||||
<div class="badge badge-primary float-right" data-toggle="tooltip"
|
||||
title="When a new random alias is created, it belongs to the default mailbox">Default Mailbox
|
||||
</div>
|
||||
{% endif %}
|
||||
</h5>
|
||||
|
||||
<h6 class="card-subtitle mb-2 text-muted">
|
||||
Created {{ mailbox.created_at | dt }} <br>
|
||||
<span class="font-weight-bold">{{ mailbox.nb_alias() }}</span> aliases.
|
||||
<span class="font-weight-bold">{{ mailbox.nb_alias() }}</span> aliases. <br>
|
||||
|
||||
</h6>
|
||||
|
||||
<a href="{{ url_for('dashboard.mailbox_detail_route', mailbox_id=mailbox.id) }}">Edit ➡</a>
|
||||
</div>
|
||||
|
||||
<div class="card-footer p-0">
|
||||
<div class="row">
|
||||
{% if mailbox.verified and current_user.full_mailbox %}
|
||||
<div class="col">
|
||||
<form method="post">
|
||||
<input type="hidden" name="form-name" value="set-default">
|
||||
<input type="hidden" class="mailbox" value="{{ mailbox.email }}">
|
||||
<input type="hidden" name="mailbox-id" value="{{ mailbox.id }}">
|
||||
<button class="card-link btn btn-link
|
||||
{% if mailbox.id == current_user.default_mailbox_id %} disabled {% endif %}"
|
||||
>
|
||||
Set As Default Mailbox
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col">
|
||||
<form method="post">
|
||||
<input type="hidden" name="form-name" value="delete">
|
||||
<input type="hidden" class="mailbox" value="{{ mailbox.email }}">
|
||||
<input type="hidden" name="mailbox-id" value="{{ mailbox.id }}">
|
||||
<span class="card-link btn btn-link float-right delete-mailbox">
|
||||
<span class="card-link btn btn-link text-danger float-right delete-mailbox
|
||||
{% if mailbox.id == current_user.default_mailbox_id %} disabled {% endif %}">
|
||||
Delete
|
||||
</span>
|
||||
</form>
|
||||
|
@ -81,8 +107,7 @@
|
|||
A verification email will be sent to this email to make sure you have access to this email.
|
||||
</div>
|
||||
|
||||
{{ new_mailbox_form.email(class="form-control", placeholder="email@example.com",
|
||||
autofocus="1") }}
|
||||
{{ new_mailbox_form.email(class="form-control", placeholder="email@example.com") }}
|
||||
{{ render_field_errors(new_mailbox_form.email) }}
|
||||
<button class="btn btn-lg btn-success mt-2">Create</button>
|
||||
</form>
|
||||
|
|
58
app/dashboard/templates/dashboard/mailbox_detail.html
Normal file
58
app/dashboard/templates/dashboard/mailbox_detail.html
Normal file
|
@ -0,0 +1,58 @@
|
|||
{% extends 'default.html' %}
|
||||
|
||||
{% set active_page = "mailbox" %}
|
||||
|
||||
{% block title %}
|
||||
Mailbox {{ mailbox.email }}
|
||||
{% endblock %}
|
||||
|
||||
{% block default_content %}
|
||||
|
||||
<div class="col-md-8 offset-md-2 pb-3">
|
||||
<h1 class="h3">{{ mailbox.email }}
|
||||
{% if mailbox.verified %}
|
||||
<span class="cursor" data-toggle="tooltip" data-original-title="Mailbox Verified">✅</span>
|
||||
{% else %}
|
||||
<span class="cursor" data-toggle="tooltip" data-original-title="Mailbox Not Verified">
|
||||
🚫
|
||||
</span>
|
||||
{% endif %}
|
||||
</h1>
|
||||
|
||||
<!-- Change email -->
|
||||
<div class="card">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="form-name" value="update-email">
|
||||
{{ change_email_form.csrf_token }}
|
||||
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
Change Mailbox Address
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Address</label>
|
||||
|
||||
<!-- Not allow user to change mailbox if there's a pending change -->
|
||||
{{ change_email_form.email(class="form-control", value=mailbox.email, readonly=pending_email != None) }}
|
||||
{{ render_field_errors(change_email_form.email) }}
|
||||
|
||||
{% if pending_email %}
|
||||
<div class="mt-2">
|
||||
<span class="text-danger">Pending change: {{ pending_email }}</span>
|
||||
<a href="{{ url_for('dashboard.cancel_mailbox_change_route', mailbox_id=mailbox.id) }}"
|
||||
class="btn btn-secondary btn-sm">
|
||||
Cancel mailbox change
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<button class="btn btn-primary">Change</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- END Change email -->
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -43,9 +43,7 @@ def custom_alias():
|
|||
)
|
||||
)
|
||||
|
||||
mailboxes = [current_user.email]
|
||||
for mailbox in Mailbox.query.filter_by(user_id=current_user.id, verified=True):
|
||||
mailboxes.append(mailbox.email)
|
||||
mailboxes = current_user.mailboxes()
|
||||
|
||||
if request.method == "POST":
|
||||
alias_prefix = request.form.get("prefix")
|
||||
|
@ -85,7 +83,8 @@ def custom_alias():
|
|||
LOG.d("Set alias %s domain to %s", full_alias, custom_domain)
|
||||
gen_email.custom_domain_id = custom_domain.id
|
||||
|
||||
if mailbox_email != current_user.email:
|
||||
# assign alias to a mailbox
|
||||
if current_user.full_mailbox or mailbox_email != current_user.email:
|
||||
mailbox = Mailbox.get_by(email=mailbox_email)
|
||||
gen_email.mailbox_id = mailbox.id
|
||||
LOG.d("Set alias %s mailbox to %s", full_alias, mailbox)
|
||||
|
|
|
@ -74,6 +74,16 @@ def index():
|
|||
gen_email = GenEmail.create_new_random(
|
||||
user_id=current_user.id, scheme=scheme
|
||||
)
|
||||
|
||||
if current_user.full_mailbox:
|
||||
if not current_user.default_mailbox_id:
|
||||
LOG.error(
|
||||
"Full mailbox User %s does not have default mailbox ",
|
||||
current_user,
|
||||
)
|
||||
else:
|
||||
gen_email.mailbox_id = current_user.default_mailbox_id
|
||||
|
||||
db.session.commit()
|
||||
|
||||
LOG.d("generate new email %s for user %s", gen_email, current_user)
|
||||
|
@ -142,6 +152,31 @@ def index():
|
|||
)
|
||||
)
|
||||
|
||||
elif request.form.get("form-name") == "set-mailbox":
|
||||
gen_email_id = request.form.get("gen-email-id")
|
||||
gen_email: GenEmail = GenEmail.get(gen_email_id)
|
||||
mailbox_email = request.form.get("mailbox")
|
||||
|
||||
mailbox = Mailbox.get_by(email=mailbox_email)
|
||||
if not mailbox or mailbox.user_id != current_user.id:
|
||||
flash("Something went wrong, please retry", "warning")
|
||||
else:
|
||||
gen_email.mailbox_id = mailbox.id
|
||||
db.session.commit()
|
||||
LOG.d("Set alias %s mailbox to %s", gen_email, mailbox)
|
||||
|
||||
flash(
|
||||
f"Update mailbox for {gen_email.email} to {mailbox_email}",
|
||||
"success",
|
||||
)
|
||||
return redirect(
|
||||
url_for(
|
||||
"dashboard.index",
|
||||
highlight_gen_email_id=gen_email.id,
|
||||
query=query,
|
||||
)
|
||||
)
|
||||
|
||||
return redirect(url_for("dashboard.index", query=query))
|
||||
|
||||
client_users = (
|
||||
|
@ -153,6 +188,8 @@ def index():
|
|||
|
||||
sorted(client_users, key=lambda cu: cu.client.name)
|
||||
|
||||
mailboxes = current_user.mailboxes()
|
||||
|
||||
return render_template(
|
||||
"dashboard/index.html",
|
||||
client_users=client_users,
|
||||
|
@ -160,6 +197,7 @@ def index():
|
|||
highlight_gen_email_id=highlight_gen_email_id,
|
||||
query=query,
|
||||
AliasGeneratorEnum=AliasGeneratorEnum,
|
||||
mailboxes=mailboxes,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ class NewMailboxForm(FlaskForm):
|
|||
@dashboard_bp.route("/mailbox", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def mailbox_route():
|
||||
if not current_user.can_use_multiple_mailbox:
|
||||
if not current_user.can_use_multiple_mailbox and not current_user.full_mailbox:
|
||||
flash("You don't have access to this page, redirect to home page", "warning")
|
||||
return redirect(url_for("dashboard.index"))
|
||||
|
||||
|
@ -44,11 +44,36 @@ def mailbox_route():
|
|||
flash("Unknown error. Refresh the page", "warning")
|
||||
return redirect(url_for("dashboard.mailbox_route"))
|
||||
|
||||
if mailbox.id == current_user.default_mailbox_id:
|
||||
flash("You cannot delete default mailbox", "error")
|
||||
return redirect(url_for("dashboard.mailbox_route"))
|
||||
|
||||
email = mailbox.email
|
||||
Mailbox.delete(mailbox_id)
|
||||
db.session.commit()
|
||||
flash(f"Mailbox {email} has been deleted", "success")
|
||||
|
||||
return redirect(url_for("dashboard.mailbox_route"))
|
||||
if request.form.get("form-name") == "set-default":
|
||||
mailbox_id = request.form.get("mailbox-id")
|
||||
mailbox = Mailbox.get(mailbox_id)
|
||||
|
||||
if not mailbox or mailbox.user_id != current_user.id:
|
||||
flash("Unknown error. Refresh the page", "warning")
|
||||
return redirect(url_for("dashboard.mailbox_route"))
|
||||
|
||||
if mailbox.id == current_user.default_mailbox_id:
|
||||
flash("This mailbox is already default one", "error")
|
||||
return redirect(url_for("dashboard.mailbox_route"))
|
||||
|
||||
if not mailbox.verified:
|
||||
flash("Cannot set unverified mailbox as default", "error")
|
||||
return redirect(url_for("dashboard.mailbox_route"))
|
||||
|
||||
current_user.default_mailbox_id = mailbox.id
|
||||
db.session.commit()
|
||||
flash(f"Mailbox {mailbox.email} is set as Default Mailbox", "success")
|
||||
|
||||
return redirect(url_for("dashboard.mailbox_route"))
|
||||
|
||||
elif request.form.get("form-name") == "create":
|
||||
|
|
150
app/dashboard/views/mailbox_detail.py
Normal file
150
app/dashboard/views/mailbox_detail.py
Normal file
|
@ -0,0 +1,150 @@
|
|||
from flask import render_template, request, redirect, url_for, flash
|
||||
from flask_login import login_required, current_user
|
||||
from flask_wtf import FlaskForm
|
||||
from itsdangerous import Signer, BadSignature
|
||||
from wtforms import validators
|
||||
from wtforms.fields.html5 import EmailField
|
||||
|
||||
from app.config import FLASK_SECRET
|
||||
from app.config import URL
|
||||
from app.dashboard.base import dashboard_bp
|
||||
from app.email_utils import can_be_used_as_personal_email, email_already_used
|
||||
from app.email_utils import (
|
||||
send_email,
|
||||
render,
|
||||
)
|
||||
from app.extensions import db
|
||||
from app.log import LOG
|
||||
from app.models import (
|
||||
GenEmail,
|
||||
DeletedAlias,
|
||||
)
|
||||
from app.models import Mailbox
|
||||
|
||||
|
||||
class ChangeEmailForm(FlaskForm):
|
||||
email = EmailField(
|
||||
"email", validators=[validators.DataRequired(), validators.Email()]
|
||||
)
|
||||
|
||||
|
||||
@dashboard_bp.route("/mailbox/<int:mailbox_id>/", methods=["GET", "POST"])
|
||||
@login_required
|
||||
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 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 (
|
||||
email_already_used(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 not can_be_used_as_personal_email(new_email):
|
||||
flash(
|
||||
"You cannot use this email address as your mailbox", "error",
|
||||
)
|
||||
else:
|
||||
mailbox.new_email = new_email
|
||||
db.session.commit()
|
||||
|
||||
s = Signer(FLASK_SECRET)
|
||||
mailbox_id_signed = s.sign(str(mailbox.id)).decode()
|
||||
verification_url = (
|
||||
URL
|
||||
+ "/dashboard/mailbox/confirm_change"
|
||||
+ f"?mailbox_id={mailbox_id_signed}"
|
||||
)
|
||||
|
||||
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,
|
||||
),
|
||||
)
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
return render_template("dashboard/mailbox_detail.html", **locals(),)
|
||||
|
||||
|
||||
@dashboard_bp.route(
|
||||
"/mailbox/<int:mailbox_id>/cancel_email_change", methods=["GET", "POST"]
|
||||
)
|
||||
@login_required
|
||||
def cancel_mailbox_change_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"))
|
||||
|
||||
if mailbox.new_email:
|
||||
mailbox.new_email = None
|
||||
db.session.commit()
|
||||
flash("Your mailbox change is cancelled", "success")
|
||||
return redirect(
|
||||
url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)
|
||||
)
|
||||
else:
|
||||
flash("You have no pending mailbox change", "warning")
|
||||
return redirect(
|
||||
url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)
|
||||
)
|
||||
|
||||
|
||||
@dashboard_bp.route("/mailbox/confirm_change")
|
||||
def mailbox_confirm_change_route():
|
||||
s = Signer(FLASK_SECRET)
|
||||
mailbox_id = request.args.get("mailbox_id")
|
||||
|
||||
try:
|
||||
r_id = int(s.unsign(mailbox_id))
|
||||
except BadSignature:
|
||||
flash("Invalid link", "error")
|
||||
else:
|
||||
mailbox = Mailbox.get(r_id)
|
||||
mailbox.email = mailbox.new_email
|
||||
mailbox.new_email = None
|
||||
|
||||
# mark mailbox as verified if the change request is sent from an unverified mailbox
|
||||
mailbox.verified = True
|
||||
db.session.commit()
|
||||
|
||||
LOG.d("Mailbox change %s is verified", mailbox)
|
||||
flash(
|
||||
f"The {mailbox.email} is updated", "success",
|
||||
)
|
||||
return redirect(
|
||||
url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox.id)
|
||||
)
|
|
@ -185,6 +185,8 @@ def send_email(
|
|||
)
|
||||
return
|
||||
|
||||
LOG.d("send email to %s, subject %s", to_email, subject)
|
||||
|
||||
# host IP, setup via Docker network
|
||||
smtp = SMTP(POSTFIX_SERVER, 25)
|
||||
|
||||
|
@ -214,11 +216,9 @@ def send_email(
|
|||
msg["To"] = to_email
|
||||
|
||||
msg_id_header = make_msgid()
|
||||
LOG.d("message-id %s", msg_id_header)
|
||||
msg["Message-ID"] = msg_id_header
|
||||
|
||||
date_header = formatdate()
|
||||
LOG.d("Date header: %s", date_header)
|
||||
msg["Date"] = date_header
|
||||
|
||||
# add DKIM
|
||||
|
|
|
@ -137,6 +137,19 @@ class User(db.Model, ModelMixin, UserMixin):
|
|||
db.Boolean, default=False, nullable=False, server_default="0"
|
||||
)
|
||||
|
||||
# only use mailbox instead of default to user email
|
||||
# this requires a migration before to:
|
||||
# 1. create default mailbox for the user email address
|
||||
# 2. assign existing aliases to this default mailbox
|
||||
full_mailbox = db.Column(
|
||||
db.Boolean, default=False, nullable=False, server_default="0"
|
||||
)
|
||||
|
||||
# the mailbox used when create random alias
|
||||
default_mailbox_id = db.Column(
|
||||
db.ForeignKey("mailbox.id"), nullable=True, default=None
|
||||
)
|
||||
|
||||
profile_picture = db.relationship(File)
|
||||
|
||||
@classmethod
|
||||
|
@ -154,6 +167,14 @@ class User(db.Model, ModelMixin, UserMixin):
|
|||
GenEmail.create_new(user.id, prefix="my-first-alias")
|
||||
db.session.flush()
|
||||
|
||||
# todo: uncomment when all existing users are full_mailbox
|
||||
# to run just after migrating all existing user to full mailbox
|
||||
# so new users are automatically full-mailbox
|
||||
# mb = Mailbox.create(user_id=user.id, email=user.email, verified=True)
|
||||
# db.session.flush()
|
||||
# user.full_mailbox = True
|
||||
# user.default_mailbox_id = mb.id
|
||||
|
||||
# Schedule onboarding emails
|
||||
Job.create(
|
||||
name=JOB_ONBOARDING_1,
|
||||
|
@ -270,6 +291,18 @@ class User(db.Model, ModelMixin, UserMixin):
|
|||
def verified_custom_domains(self):
|
||||
return CustomDomain.query.filter_by(user_id=self.id, verified=True).all()
|
||||
|
||||
def mailboxes(self) -> [str]:
|
||||
"""list of mailbox emails that user own"""
|
||||
if self.full_mailbox:
|
||||
mailboxes = []
|
||||
else:
|
||||
mailboxes = [self.email]
|
||||
|
||||
for mailbox in Mailbox.query.filter_by(user_id=self.id, verified=True):
|
||||
mailboxes.append(mailbox.email)
|
||||
|
||||
return mailboxes
|
||||
|
||||
def __repr__(self):
|
||||
return f"<User {self.id} {self.name} {self.email}>"
|
||||
|
||||
|
@ -491,7 +524,7 @@ class GenEmail(db.Model, ModelMixin):
|
|||
mailbox = db.relationship("Mailbox")
|
||||
|
||||
@classmethod
|
||||
def create_new(cls, user_id, prefix, note=None):
|
||||
def create_new(cls, user_id, prefix, note=None, mailbox_id=None):
|
||||
if not prefix:
|
||||
raise Exception("alias prefix cannot be empty")
|
||||
|
||||
|
@ -503,7 +536,9 @@ class GenEmail(db.Model, ModelMixin):
|
|||
if not cls.get_by(email=email):
|
||||
break
|
||||
|
||||
return GenEmail.create(user_id=user_id, email=email, note=note)
|
||||
return GenEmail.create(
|
||||
user_id=user_id, email=email, note=note, mailbox_id=mailbox_id
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def create_new_random(
|
||||
|
@ -828,7 +863,8 @@ class Mailbox(db.Model, ModelMixin):
|
|||
email = db.Column(db.String(256), unique=True, nullable=False)
|
||||
verified = db.Column(db.Boolean, default=False, nullable=False)
|
||||
|
||||
user = db.relationship(User)
|
||||
# used when user wants to update mailbox email
|
||||
new_email = db.Column(db.String(256), unique=True)
|
||||
|
||||
def nb_alias(self):
|
||||
return GenEmail.filter_by(mailbox_id=self.id).count()
|
||||
|
|
|
@ -106,7 +106,7 @@
|
|||
|
||||
<div class="col-sm-6"
|
||||
style="padding-left: 5px">
|
||||
<select class="form-control" name="suffix">
|
||||
<select class="form-control custom-select" name="suffix">
|
||||
{% for suffix in suffixes %}
|
||||
<option value="{{ suffix[1] }}">
|
||||
{% if suffix[0] %}
|
||||
|
|
33
migrations/versions/2020_022314_903ec5f566e8_.py
Normal file
33
migrations/versions/2020_022314_903ec5f566e8_.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 903ec5f566e8
|
||||
Revises: 3fa3a648c8e7
|
||||
Create Date: 2020-02-23 14:11:46.332532
|
||||
|
||||
"""
|
||||
import sqlalchemy_utils
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '903ec5f566e8'
|
||||
down_revision = '3fa3a648c8e7'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('mailbox', sa.Column('new_email', sa.String(length=256), nullable=True))
|
||||
op.create_unique_constraint(None, 'mailbox', ['new_email'])
|
||||
op.add_column('users', sa.Column('full_mailbox', sa.Boolean(), server_default='0', nullable=False))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('users', 'full_mailbox')
|
||||
op.drop_constraint(None, 'mailbox', type_='unique')
|
||||
op.drop_column('mailbox', 'new_email')
|
||||
# ### end Alembic commands ###
|
31
migrations/versions/2020_022316_f580030d9beb_.py
Normal file
31
migrations/versions/2020_022316_f580030d9beb_.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: f580030d9beb
|
||||
Revises: 903ec5f566e8
|
||||
Create Date: 2020-02-23 16:03:46.064813
|
||||
|
||||
"""
|
||||
import sqlalchemy_utils
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'f580030d9beb'
|
||||
down_revision = '903ec5f566e8'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('users', sa.Column('default_mailbox_id', sa.Integer(), nullable=True))
|
||||
op.create_foreign_key(None, 'users', 'mailbox', ['default_mailbox_id'], ['id'])
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(None, 'users', type_='foreignkey')
|
||||
op.drop_column('users', 'default_mailbox_id')
|
||||
# ### end Alembic commands ###
|
17
server.py
17
server.py
|
@ -132,6 +132,7 @@ def fake_data():
|
|||
is_admin=True,
|
||||
otp_secret="base32secret3232",
|
||||
can_use_multiple_mailbox=True,
|
||||
full_mailbox=True,
|
||||
)
|
||||
db.session.commit()
|
||||
|
||||
|
@ -156,9 +157,15 @@ def fake_data():
|
|||
api_key = ApiKey.create(user_id=user.id, name="Firefox")
|
||||
api_key.code = "codeFF"
|
||||
|
||||
GenEmail.create_new(user.id, "e1@")
|
||||
GenEmail.create_new(user.id, "e2@")
|
||||
GenEmail.create_new(user.id, "e3@")
|
||||
m1 = Mailbox.create(user_id=user.id, email="m1@cd.ef", verified=True)
|
||||
m2 = Mailbox.create(user_id=user.id, email="m2@zt.com", verified=False)
|
||||
m3 = Mailbox.create(user_id=user.id, email="m3@cd.ef", verified=True)
|
||||
db.session.commit()
|
||||
|
||||
user.default_mailbox_id = m1.id
|
||||
|
||||
GenEmail.create_new(user.id, "e1@", mailbox_id=m1.id)
|
||||
GenEmail.create_new(user.id, "e2@", mailbox_id=m3.id)
|
||||
|
||||
CustomDomain.create(user_id=user.id, domain="ab.cd", verified=True)
|
||||
CustomDomain.create(
|
||||
|
@ -185,10 +192,6 @@ def fake_data():
|
|||
client2.published = True
|
||||
db.session.commit()
|
||||
|
||||
Mailbox.create(user_id=user.id, email="ab@cd.ef", verified=True)
|
||||
Mailbox.create(user_id=user.id, email="xy@zt.com", verified=False)
|
||||
db.session.commit()
|
||||
|
||||
DeletedAlias.create(user_id=user.id, email="d1@ab.cd")
|
||||
DeletedAlias.create(user_id=user.id, email="d2@ab.cd")
|
||||
db.session.commit()
|
||||
|
|
18
shell.py
18
shell.py
|
@ -40,6 +40,24 @@ def send_safari_extension_newsletter():
|
|||
)
|
||||
|
||||
|
||||
def convert_user_full_mailbox(user):
|
||||
# create a default mailbox
|
||||
default_mb = Mailbox.get_by(user_id=user.id, email=user.email)
|
||||
if not default_mb:
|
||||
LOG.d("create default mailbox for user %s", user)
|
||||
default_mb = Mailbox.create(user_id=user.id, email=user.email, verified=True)
|
||||
db.session.commit()
|
||||
|
||||
# assign existing alias to this mailbox
|
||||
for gen_email in GenEmail.query.filter_by(user_id=user.id):
|
||||
gen_email.mailbox_id = default_mb.id
|
||||
|
||||
# finally set user to full_mailbox
|
||||
user.full_mailbox = True
|
||||
user.default_mailbox_id = default_mb.id
|
||||
db.session.commit()
|
||||
|
||||
|
||||
app = create_app()
|
||||
|
||||
with app.app_context():
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
and create your <b>business emails</b> using email alias. This is cheaper and more convenient than buying a dedicated solution like GSuite. By the way, all our business emails are actually aliases.') }}
|
||||
|
||||
{% if user.in_trial() %}
|
||||
{{ render_text('You can use all premium features like <em>custom domain</em> or <em>alias directory</em> during the <b>trial period</b>. Your trial will end ' + user.trial_end.humanize() + ".") }}
|
||||
{{ render_text('You can use all premium features like <em>custom domain</em> or <em>alias directory</em> during the <b>trial period</b>. Your trial will end ' + user.trial_end.humanize() + ". All aliases you create during the trial will continue to work normally when the trial ends.") }}
|
||||
{% endif %}
|
||||
|
||||
{{ render_text('In the next coming days, you are going to receive some onboarding emails to quickly present SimpleLogin features. If you don\'t want to receive these emails, you can disable them on <a href="https://app.simplelogin.io/dashboard/setting#notification">Settings</a>.') }}
|
||||
|
|
|
@ -22,6 +22,7 @@ and create your business emails backed by your personal email! By the way, all o
|
|||
{% if user.in_trial() %}
|
||||
You can use all premium features like custom domain or alias directory during the trial period.
|
||||
Your trial will end {{ user.trial_end.humanize() }}.
|
||||
All aliases you create during the trial will continue to work normally when the trial ends.
|
||||
{% endif %}
|
||||
|
||||
If there's anything that's bugging you, even the smallest of issues that could be done better, I want to hear about it - so hit the reply button.
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
{{ render_text("When the trial ends:") }}
|
||||
|
||||
{{ render_text("- All aliases/domains/directories you have created are <b>kept</b> and continue working.") }}
|
||||
{{ render_text("- All aliases/domains/directories you have created are <b>kept</b> and continue working normally.") }}
|
||||
{{ render_text("- You cannot create new aliases if you exceed the free plan limit, i.e. have more than 5 aliases.") }}
|
||||
{{ render_text("- As features like <b>catch-all</b> or <b>directory</b> allow you to create aliases on-the-fly, those aliases cannot be automatically created if you have more than 5 aliases.") }}
|
||||
{{ render_text("- You cannot add new domain or directory.") }}
|
||||
|
|
11
templates/emails/transactional/verify-mailbox-change.html
Normal file
11
templates/emails/transactional/verify-mailbox-change.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
{{ render_text("Hi " + user.name) }}
|
||||
{{ render_text("You recently requested to change mailbox <b>"+ mailbox_email +"</b> to <b>" + mailbox_new_email + "</b>.") }}
|
||||
{{ render_text("To confirm, please click on the button below.") }}
|
||||
{{ render_button("Confirm mailbox change", link) }}
|
||||
{{ render_text('Thanks, <br />SimpleLogin Team.') }}
|
||||
{{ raw_url(link) }}
|
||||
{% endblock %}
|
||||
|
10
templates/emails/transactional/verify-mailbox-change.txt
Normal file
10
templates/emails/transactional/verify-mailbox-change.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
Hi {{user.name}}
|
||||
|
||||
You recently requested to change mailbox {{mailbox_email}} to {{mailbox_new_email}}
|
||||
|
||||
To confirm, please click on this link:
|
||||
|
||||
{{link}}
|
||||
|
||||
Regards,
|
||||
Son - SimpleLogin founder.
|
|
@ -29,7 +29,7 @@
|
|||
</a>
|
||||
</li>
|
||||
|
||||
{% if current_user.can_use_multiple_mailbox %}
|
||||
{% if current_user.can_use_multiple_mailbox or current_user.full_mailbox %}
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('dashboard.mailbox_route') }}"
|
||||
class="nav-link {{ 'active' if active_page == 'mailbox' }}">
|
||||
|
|
Loading…
Reference in a new issue