mirror of
https://github.com/simple-login/app.git
synced 2025-09-07 23:25:00 +08:00
User can add/delete/verify mailbox
This commit is contained in:
parent
821372fdfd
commit
8a531f6c86
6 changed files with 268 additions and 0 deletions
|
@ -14,4 +14,5 @@ from .views import (
|
|||
domain_detail,
|
||||
lifetime_licence,
|
||||
directory,
|
||||
mailbox,
|
||||
)
|
||||
|
|
111
app/dashboard/templates/dashboard/mailbox.html
Normal file
111
app/dashboard/templates/dashboard/mailbox.html
Normal file
|
@ -0,0 +1,111 @@
|
|||
{% extends 'default.html' %}
|
||||
{% set active_page = "mailbox" %}
|
||||
|
||||
{% block title %}
|
||||
Mailboxes
|
||||
{% endblock %}
|
||||
|
||||
{% block default_content %}
|
||||
<div class="row">
|
||||
<div class="col-md-8 offset-md-2">
|
||||
<h1 class="h3"> Mailboxes </h1>
|
||||
|
||||
{% if not current_user.is_premium() %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
This feature is only available in premium plan.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="alert alert-primary" role="alert">
|
||||
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>
|
||||
|
||||
By default, all aliases are owned by your email <b>{{ current_user.email }}</b>. <br><br>
|
||||
|
||||
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.
|
||||
</div>
|
||||
|
||||
{% for mailbox in mailboxes %}
|
||||
<div class="card" style="max-width: 50rem">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">
|
||||
{{ 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 %}
|
||||
|
||||
</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.
|
||||
</h6>
|
||||
</div>
|
||||
|
||||
<div class="card-footer p-0">
|
||||
<div class="row">
|
||||
<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">
|
||||
Delete
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if mailboxs|length > 0 %}
|
||||
<hr>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" class="mt-6">
|
||||
{{ new_mailbox_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="create">
|
||||
|
||||
<div class="font-weight-bold">Email</div>
|
||||
<div class="small-text">
|
||||
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") }}
|
||||
{{ render_field_errors(new_mailbox_form.email) }}
|
||||
<button class="btn btn-lg btn-success mt-2">Create</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script>
|
||||
$(".delete-mailbox").on("click", function (e) {
|
||||
let mailbox = $(this).parent().find(".mailbox").val();
|
||||
notie.confirm({
|
||||
text: `All aliases owned by this mailbox <b>${mailbox}</b> will be also deleted, ` +
|
||||
" please confirm.",
|
||||
cancelCallback: () => {
|
||||
// nothing to do
|
||||
},
|
||||
submitCallback: () => {
|
||||
$(this).closest("form").submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
129
app/dashboard/views/mailbox.py
Normal file
129
app/dashboard/views/mailbox.py
Normal file
|
@ -0,0 +1,129 @@
|
|||
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 EMAIL_DOMAIN, ALIAS_DOMAINS, FLASK_SECRET, URL
|
||||
from app.dashboard.base import dashboard_bp
|
||||
from app.email_utils import (
|
||||
send_email,
|
||||
render,
|
||||
can_be_used_as_personal_email,
|
||||
email_already_used,
|
||||
)
|
||||
from app.extensions import db
|
||||
from app.log import LOG
|
||||
from app.models import Mailbox
|
||||
|
||||
|
||||
class NewMailboxForm(FlaskForm):
|
||||
email = EmailField(
|
||||
"email", validators=[validators.DataRequired(), validators.Email()]
|
||||
)
|
||||
|
||||
|
||||
@dashboard_bp.route("/mailbox", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def mailbox_route():
|
||||
mailboxes = Mailbox.query.filter_by(user_id=current_user.id).all()
|
||||
|
||||
new_mailbox_form = NewMailboxForm()
|
||||
|
||||
if request.method == "POST":
|
||||
if request.form.get("form-name") == "delete":
|
||||
mailbox_id = request.form.get("mailbox-id")
|
||||
mailbox = Mailbox.get(mailbox_id)
|
||||
|
||||
if not mailbox or mailbox.user_id != current_user.id:
|
||||
flash("Unknown error. Refresh the page", "warning")
|
||||
return redirect(url_for("dashboard.mailbox_route"))
|
||||
|
||||
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"))
|
||||
|
||||
elif request.form.get("form-name") == "create":
|
||||
if not current_user.is_premium():
|
||||
flash("Only premium plan can add additional mailbox", "warning")
|
||||
return redirect(url_for("dashboard.mailbox_route"))
|
||||
|
||||
if new_mailbox_form.validate():
|
||||
mailbox_email = new_mailbox_form.email.data.lower()
|
||||
|
||||
if email_already_used(mailbox_email):
|
||||
flash(f"{mailbox_email} already used", "error")
|
||||
elif not can_be_used_as_personal_email(mailbox_email):
|
||||
flash(
|
||||
f"You cannot use {mailbox_email}.", "error",
|
||||
)
|
||||
else:
|
||||
new_mailbox = Mailbox.create(
|
||||
email=mailbox_email, user_id=current_user.id
|
||||
)
|
||||
db.session.commit()
|
||||
|
||||
s = Signer(FLASK_SECRET)
|
||||
mailbox_id_signed = s.sign(str(new_mailbox.id)).decode()
|
||||
verification_url = (
|
||||
URL
|
||||
+ "/dashboard/mailbox_verify"
|
||||
+ f"?mailbox_id={mailbox_id_signed}"
|
||||
)
|
||||
send_email(
|
||||
current_user.email,
|
||||
f"Please confirm your email {mailbox_email}",
|
||||
render(
|
||||
"transactional/verify-mailbox.txt",
|
||||
user=current_user,
|
||||
link=verification_url,
|
||||
mailbox_email=mailbox_email,
|
||||
),
|
||||
render(
|
||||
"transactional/verify-mailbox.html",
|
||||
user=current_user,
|
||||
link=verification_url,
|
||||
mailbox_email=mailbox_email,
|
||||
),
|
||||
)
|
||||
|
||||
flash(
|
||||
f"You are going to receive an email to confirm {mailbox_email}.",
|
||||
"success",
|
||||
)
|
||||
|
||||
return redirect(url_for("dashboard.mailbox_route"))
|
||||
|
||||
return render_template(
|
||||
"dashboard/mailbox.html",
|
||||
mailboxes=mailboxes,
|
||||
new_mailbox_form=new_mailbox_form,
|
||||
EMAIL_DOMAIN=EMAIL_DOMAIN,
|
||||
ALIAS_DOMAINS=ALIAS_DOMAINS,
|
||||
)
|
||||
|
||||
|
||||
@dashboard_bp.route("/mailbox_verify")
|
||||
def mailbox_verify():
|
||||
s = Signer(FLASK_SECRET)
|
||||
mailbox_id = request.args.get("mailbox_id")
|
||||
|
||||
try:
|
||||
r_id = int(s.unsign(mailbox_id))
|
||||
except BadSignature:
|
||||
flash("Invalid link. Please delete and re-add your mailbox", "error")
|
||||
else:
|
||||
mailbox = Mailbox.get(r_id)
|
||||
mailbox.verified = True
|
||||
db.session.commit()
|
||||
|
||||
LOG.d("Mailbox %s is verified", mailbox)
|
||||
flash(
|
||||
f"The {mailbox.email} is now verified, you can start creating alias with it",
|
||||
"success",
|
||||
)
|
||||
return redirect(url_for("dashboard.mailbox_route"))
|
9
templates/emails/transactional/verify-mailbox.html
Normal file
9
templates/emails/transactional/verify-mailbox.html
Normal file
|
@ -0,0 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
{{ render_text("Hi " + user.name) }}
|
||||
{{ render_text("You have added "+ mailbox_email +" as an additional mailbox.") }}
|
||||
{{ render_text("To confirm, please click on the button below or use this link: <br> " + link) }}
|
||||
{{ render_button("Confirm email", link) }}
|
||||
{% endblock %}
|
||||
|
10
templates/emails/transactional/verify-mailbox.txt
Normal file
10
templates/emails/transactional/verify-mailbox.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
Hi {{user.name}}
|
||||
|
||||
You have added {{mailbox_email}} as an additional mailbox.
|
||||
|
||||
To confirm, please click on this link:
|
||||
|
||||
{{link}}
|
||||
|
||||
Regards,
|
||||
Son - SimpleLogin founder.
|
|
@ -39,6 +39,14 @@
|
|||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('dashboard.mailbox_route') }}"
|
||||
class="nav-link {{ 'active' if active_page == 'mailbox' }}">
|
||||
<i class="fe fe-inbox"></i> Mailboxes
|
||||
<span class="badge badge-info" style="font-size: .5rem; top: 5px">Beta</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!--
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('discover.index') }}"
|
||||
|
|
Loading…
Add table
Reference in a new issue