ddmail-webapp 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ddmail_webapp/__init__.py +210 -0
- ddmail_webapp/auth.py +173 -0
- ddmail_webapp/forms.py +22 -0
- ddmail_webapp/models.py +117 -0
- ddmail_webapp/settings.py +1095 -0
- ddmail_webapp/static/contact_general_pubkey.asc +13 -0
- ddmail_webapp/static/contact_legal_pubkey.asc +13 -0
- ddmail_webapp/static/contact_security_pubkey.asc +13 -0
- ddmail_webapp/static/contact_support_pubkey.asc +13 -0
- ddmail_webapp/static/css/style.css +20 -0
- ddmail_webapp/static/mta-sts.txt +4 -0
- ddmail_webapp/static/robots.txt +4 -0
- ddmail_webapp/static/security.txt +15 -0
- ddmail_webapp/static/sitemap.xml +37 -0
- ddmail_webapp/templates/about.html +68 -0
- ddmail_webapp/templates/account_domain_demand.html +6 -0
- ddmail_webapp/templates/all.html +47 -0
- ddmail_webapp/templates/contact.html +31 -0
- ddmail_webapp/templates/help.html +108 -0
- ddmail_webapp/templates/i2p_hostname +1 -0
- ddmail_webapp/templates/login.html +24 -0
- ddmail_webapp/templates/main.html +6 -0
- ddmail_webapp/templates/message.html +6 -0
- ddmail_webapp/templates/pricing_and_payment.html +16 -0
- ddmail_webapp/templates/register.html +12 -0
- ddmail_webapp/templates/settings.html +4 -0
- ddmail_webapp/templates/settings_activate_openpgp_encryption.html +23 -0
- ddmail_webapp/templates/settings_add_alias.html +25 -0
- ddmail_webapp/templates/settings_add_domain.html +16 -0
- ddmail_webapp/templates/settings_add_email.html +19 -0
- ddmail_webapp/templates/settings_add_user_to_account.html +12 -0
- ddmail_webapp/templates/settings_added_user_to_account.html +9 -0
- ddmail_webapp/templates/settings_change_key_on_user.html +13 -0
- ddmail_webapp/templates/settings_change_password_on_email.html +18 -0
- ddmail_webapp/templates/settings_change_password_on_user.html +13 -0
- ddmail_webapp/templates/settings_deactivate_openpgp_encryption.html +17 -0
- ddmail_webapp/templates/settings_menu.html +46 -0
- ddmail_webapp/templates/settings_payment.html +18 -0
- ddmail_webapp/templates/settings_payment_token.html +8 -0
- ddmail_webapp/templates/settings_remove_account_user.html +15 -0
- ddmail_webapp/templates/settings_remove_alias.html +15 -0
- ddmail_webapp/templates/settings_remove_domain.html +15 -0
- ddmail_webapp/templates/settings_remove_email.html +15 -0
- ddmail_webapp/templates/settings_remove_openpgp_public_key.html +16 -0
- ddmail_webapp/templates/settings_show_account_users.html +10 -0
- ddmail_webapp/templates/settings_show_alias.html +10 -0
- ddmail_webapp/templates/settings_show_domains.html +17 -0
- ddmail_webapp/templates/settings_show_email.html +10 -0
- ddmail_webapp/templates/settings_show_emails_with_activated_openpgp.html +12 -0
- ddmail_webapp/templates/settings_show_openpgp_public_keys.html +10 -0
- ddmail_webapp/templates/settings_upload_openpgp_public_key.html +15 -0
- ddmail_webapp/templates/settings_usage_and_funds.html +16 -0
- ddmail_webapp/templates/terms.html +18 -0
- ddmail_webapp/templates/tor_hostname +1 -0
- ddmail_webapp/templates/user_created.html +8 -0
- ddmail_webapp/unauthenticated.py +78 -0
- ddmail_webapp/validators.py +168 -0
- ddmail_webapp/well_known.py +12 -0
- ddmail_webapp-0.0.1.dist-info/METADATA +103 -0
- ddmail_webapp-0.0.1.dist-info/RECORD +62 -0
- ddmail_webapp-0.0.1.dist-info/WHEEL +4 -0
- ddmail_webapp-0.0.1.dist-info/licenses/LICENSE +661 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import logging
|
|
4
|
+
import toml
|
|
5
|
+
from flask import Flask
|
|
6
|
+
from flask_wtf.csrf import CSRFProtect
|
|
7
|
+
from logging.config import dictConfig
|
|
8
|
+
from logging import FileHandler
|
|
9
|
+
|
|
10
|
+
def create_app(config_file=None, test_config=None):
|
|
11
|
+
"""Create and configure an instance of the Flask application ddmail."""
|
|
12
|
+
# Configure logging.
|
|
13
|
+
log_format = '[%(asctime)s] %(levelname)s in %(module)s %(funcName)s %(lineno)s: %(message)s'
|
|
14
|
+
dictConfig({
|
|
15
|
+
'version': 1,
|
|
16
|
+
'formatters': {'default': {
|
|
17
|
+
'format': log_format
|
|
18
|
+
}},
|
|
19
|
+
'handlers': {
|
|
20
|
+
'wsgi': {
|
|
21
|
+
'class': 'logging.StreamHandler',
|
|
22
|
+
'stream': 'ext://flask.logging.wsgi_errors_stream',
|
|
23
|
+
'formatter': 'default',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
'root': {
|
|
27
|
+
'level': 'INFO',
|
|
28
|
+
'handlers': ['wsgi']
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
app = Flask(__name__, instance_relative_config=True)
|
|
33
|
+
|
|
34
|
+
toml_config = None
|
|
35
|
+
|
|
36
|
+
# Check if config_file has been set.
|
|
37
|
+
if config_file is None:
|
|
38
|
+
print("Error: you need to set path to configuration file in toml format")
|
|
39
|
+
sys.exit(1)
|
|
40
|
+
|
|
41
|
+
# Parse toml config file.
|
|
42
|
+
with open(config_file, 'r') as f:
|
|
43
|
+
toml_config = toml.load(f)
|
|
44
|
+
|
|
45
|
+
# Set app configurations from toml config file.
|
|
46
|
+
mode = os.environ.get('MODE')
|
|
47
|
+
print("Running in MODE: " + mode)
|
|
48
|
+
|
|
49
|
+
# Apply configuration for the specific MODE.
|
|
50
|
+
if mode == "PRODUCTION":
|
|
51
|
+
app.config["SECRET_KEY"] = toml_config["PRODUCTION"]["SECRET_KEY"]
|
|
52
|
+
app.config['SQLALCHEMY_DATABASE_URI'] = toml_config["PRODUCTION"]['SQLALCHEMY_DATABASE_URI']
|
|
53
|
+
app.config["WTF_CSRF_SECRET_KEY"] = toml_config["PRODUCTION"]["WTF_CSRF_SECRET_KEY"]
|
|
54
|
+
|
|
55
|
+
# Configure services that ddmail_webapp depend on.
|
|
56
|
+
app.config["EMAIL_REMOVER_URL"] = toml_config["PRODUCTION"]["EMAIL_REMOVER_URL"]
|
|
57
|
+
app.config["EMAIL_REMOVER_PASSWORD"] = toml_config["PRODUCTION"]["EMAIL_REMOVER_PASSWORD"]
|
|
58
|
+
app.config["DMCP_KEYHANDLER_URL"] = toml_config["PRODUCTION"]["DMCP_KEYHANDLER_URL"]
|
|
59
|
+
app.config["DMCP_KEYHANDLER_PASSWORD"] = toml_config["PRODUCTION"]["DMCP_KEYHANDLER_PASSWORD"]
|
|
60
|
+
app.config["OPENPGP_KEYHANDLER_URL"] = toml_config["PRODUCTION"]["OPENPGP_KEYHANDLER_URL"]
|
|
61
|
+
app.config["OPENPGP_KEYHANDLER_PASSWORD"] = toml_config["PRODUCTION"]["OPENPGP_KEYHANDLER_PASSWORD"]
|
|
62
|
+
|
|
63
|
+
# Configure payment information.
|
|
64
|
+
app.config["PAYMENT_MAIL_NAME"] = toml_config["PRODUCTION"]["PAYMENT_MAIL_NAME"]
|
|
65
|
+
app.config["PAYMENT_MAIL_ADDRESS"] = toml_config["PRODUCTION"]["PAYMENT_MAIL_ADDRESS"]
|
|
66
|
+
app.config["PAYMENT_MAIL_POSTCODE"] = toml_config["PRODUCTION"]["PAYMENT_MAIL_POSTCODE"]
|
|
67
|
+
app.config["PAYMENT_MAIL_COUNTRY"] = toml_config["PRODUCTION"]["PAYMENT_MAIL_COUNTRY"]
|
|
68
|
+
|
|
69
|
+
# Configure dns record checked when adding account/own domains.
|
|
70
|
+
app.config["MX_RECORD_HOST"] = toml_config["PRODUCTION"]["MX_RECORD_HOST"]
|
|
71
|
+
app.config["MX_RECORD_PRIORITY"] = toml_config["PRODUCTION"]["MX_RECORD_PRIORITY"]
|
|
72
|
+
app.config["SPF_RECORD"] = toml_config["PRODUCTION"]["SPF_RECORD"]
|
|
73
|
+
app.config["DKIM_RECORD"] = toml_config["PRODUCTION"]["DKIM_RECORD"]
|
|
74
|
+
app.config["DMARC_RECORD"] = toml_config["PRODUCTION"]["DMARC_RECORD"]
|
|
75
|
+
|
|
76
|
+
# Configure logfile.
|
|
77
|
+
file_handler = FileHandler(filename=toml_config["PRODUCTION"]["LOGFILE"])
|
|
78
|
+
file_handler.setFormatter(logging.Formatter(log_format))
|
|
79
|
+
app.logger.addHandler(file_handler)
|
|
80
|
+
|
|
81
|
+
# Configure loglevel.
|
|
82
|
+
if toml_config["PRODUCTION"]["LOGLEVEL"] == "ERROR":
|
|
83
|
+
app.logger.setLevel(logging.ERROR)
|
|
84
|
+
elif toml_config["PRODUCTION"]["LOGLEVEL"] == "WARNING":
|
|
85
|
+
app.logger.setLevel(logging.WARNING)
|
|
86
|
+
elif toml_config["PRODUCTION"]["LOGLEVEL"] == "INFO":
|
|
87
|
+
app.logger.setLevel(logging.INFO)
|
|
88
|
+
elif toml_config["PRODUCTION"]["LOGLEVEL"] == "DEBUG":
|
|
89
|
+
app.logger.setLevel(logging.DEBUG)
|
|
90
|
+
else:
|
|
91
|
+
print("Error: you need to set LOGLEVEL to ERROR/WARNING/INFO/DEBUG")
|
|
92
|
+
sys.exit(1)
|
|
93
|
+
elif mode == "TESTING":
|
|
94
|
+
app.config["SECRET_KEY"] = toml_config["TESTING"]["SECRET_KEY"]
|
|
95
|
+
app.config['SQLALCHEMY_DATABASE_URI'] = toml_config["TESTING"]['SQLALCHEMY_DATABASE_URI']
|
|
96
|
+
app.config["WTF_CSRF_SECRET_KEY"] = toml_config["TESTING"]["WTF_CSRF_SECRET_KEY"]
|
|
97
|
+
|
|
98
|
+
# Configure services that ddmail_webapp depend on.
|
|
99
|
+
app.config["EMAIL_REMOVER_URL"] = toml_config["TESTING"]["EMAIL_REMOVER_URL"]
|
|
100
|
+
app.config["EMAIL_REMOVER_PASSWORD"] = toml_config["TESTING"]["EMAIL_REMOVER_PASSWORD"]
|
|
101
|
+
app.config["DMCP_KEYHANDLER_URL"] = toml_config["TESTING"]["DMCP_KEYHANDLER_URL"]
|
|
102
|
+
app.config["DMCP_KEYHANDLER_PASSWORD"] = toml_config["TESTING"]["DMCP_KEYHANDLER_PASSWORD"]
|
|
103
|
+
app.config["OPENPGP_KEYHANDLER_URL"] = toml_config["TESTING"]["OPENPGP_KEYHANDLER_URL"]
|
|
104
|
+
app.config["OPENPGP_KEYHANDLER_PASSWORD"] = toml_config["TESTING"]["OPENPGP_KEYHANDLER_PASSWORD"]
|
|
105
|
+
|
|
106
|
+
# Configure payment information.
|
|
107
|
+
app.config["PAYMENT_MAIL_NAME"] = toml_config["TESTING"]["PAYMENT_MAIL_NAME"]
|
|
108
|
+
app.config["PAYMENT_MAIL_ADDRESS"] = toml_config["TESTING"]["PAYMENT_MAIL_ADDRESS"]
|
|
109
|
+
app.config["PAYMENT_MAIL_POSTCODE"] = toml_config["TESTING"]["PAYMENT_MAIL_POSTCODE"]
|
|
110
|
+
app.config["PAYMENT_MAIL_COUNTRY"] = toml_config["TESTING"]["PAYMENT_MAIL_COUNTRY"]
|
|
111
|
+
|
|
112
|
+
# Configure dns record checked when adding account/own domains.
|
|
113
|
+
app.config["MX_RECORD_HOST"] = toml_config["TESTING"]["MX_RECORD_HOST"]
|
|
114
|
+
app.config["MX_RECORD_PRIORITY"] = toml_config["TESTING"]["MX_RECORD_PRIORITY"]
|
|
115
|
+
app.config["SPF_RECORD"] = toml_config["TESTING"]["SPF_RECORD"]
|
|
116
|
+
app.config["DKIM_RECORD"] = toml_config["TESTING"]["DKIM_RECORD"]
|
|
117
|
+
app.config["DMARC_RECORD"] = toml_config["TESTING"]["DMARC_RECORD"]
|
|
118
|
+
|
|
119
|
+
# Configure logfile.
|
|
120
|
+
file_handler = FileHandler(filename=toml_config["TESTING"]["LOGFILE"])
|
|
121
|
+
file_handler.setFormatter(logging.Formatter(log_format))
|
|
122
|
+
app.logger.addHandler(file_handler)
|
|
123
|
+
|
|
124
|
+
# Configure loglevel.
|
|
125
|
+
if toml_config["TESTING"]["LOGLEVEL"] == "ERROR":
|
|
126
|
+
app.logger.setLevel(logging.ERROR)
|
|
127
|
+
elif toml_config["TESTING"]["LOGLEVEL"] == "WARNING":
|
|
128
|
+
app.logger.setLevel(logging.WARNING)
|
|
129
|
+
elif toml_config["TESTING"]["LOGLEVEL"] == "INFO":
|
|
130
|
+
app.logger.setLevel(logging.INFO)
|
|
131
|
+
elif toml_config["TESTING"]["LOGLEVEL"] == "DEBUG":
|
|
132
|
+
app.logger.setLevel(logging.DEBUG)
|
|
133
|
+
else:
|
|
134
|
+
print("Error: you need to set LOGLEVEL to ERROR/WARNING/INFO/DEBUG")
|
|
135
|
+
sys.exit(1)
|
|
136
|
+
elif mode == "DEVELOPMENT":
|
|
137
|
+
app.config["SECRET_KEY"] = toml_config["DEVELOPMENT"]["SECRET_KEY"]
|
|
138
|
+
app.config['SQLALCHEMY_DATABASE_URI'] = toml_config["DEVELOPMENT"]['SQLALCHEMY_DATABASE_URI']
|
|
139
|
+
app.config["WTF_CSRF_SECRET_KEY"] = toml_config["DEVELOPMENT"]["WTF_CSRF_SECRET_KEY"]
|
|
140
|
+
|
|
141
|
+
# Configure services that ddmail_webapp depend on.
|
|
142
|
+
app.config["EMAIL_REMOVER_URL"] = toml_config["DEVELOPMENT"]["EMAIL_REMOVER_URL"]
|
|
143
|
+
app.config["EMAIL_REMOVER_PASSWORD"] = toml_config["DEVELOPMENT"]["EMAIL_REMOVER_PASSWORD"]
|
|
144
|
+
app.config["DMCP_KEYHANDLER_URL"] = toml_config["DEVELOPMENT"]["DMCP_KEYHANDLER_URL"]
|
|
145
|
+
app.config["DMCP_KEYHANDLER_PASSWORD"] = toml_config["DEVELOPMENT"]["DMCP_KEYHANDLER_PASSWORD"]
|
|
146
|
+
app.config["OPENPGP_KEYHANDLER_URL"] = toml_config["DEVELOPMENT"]["OPENPGP_KEYHANDLER_URL"]
|
|
147
|
+
app.config["OPENPGP_KEYHANDLER_PASSWORD"] = toml_config["DEVELOPMENT"]["OPENPGP_KEYHANDLER_PASSWORD"]
|
|
148
|
+
|
|
149
|
+
# Configure payment information.
|
|
150
|
+
app.config["PAYMENT_MAIL_NAME"] = toml_config["DEVELOPMENT"]["PAYMENT_MAIL_NAME"]
|
|
151
|
+
app.config["PAYMENT_MAIL_ADDRESS"] = toml_config["DEVELOPMENT"]["PAYMENT_MAIL_ADDRESS"]
|
|
152
|
+
app.config["PAYMENT_MAIL_POSTCODE"] = toml_config["DEVELOPMENT"]["PAYMENT_MAIL_POSTCODE"]
|
|
153
|
+
app.config["PAYMENT_MAIL_COUNTRY"] = toml_config["DEVELOPMENT"]["PAYMENT_MAIL_COUNTRY"]
|
|
154
|
+
|
|
155
|
+
# Configure dns record checked when adding account/own domains.
|
|
156
|
+
app.config["MX_RECORD_HOST"] = toml_config["DEVELOPMENT"]["MX_RECORD_HOST"]
|
|
157
|
+
app.config["MX_RECORD_PRIORITY"] = toml_config["DEVELOPMENT"]["MX_RECORD_PRIORITY"]
|
|
158
|
+
app.config["SPF_RECORD"] = toml_config["DEVELOPMENT"]["SPF_RECORD"]
|
|
159
|
+
app.config["DKIM_RECORD"] = toml_config["DEVELOPMENT"]["DKIM_RECORD"]
|
|
160
|
+
app.config["DMARC_RECORD"] = toml_config["DEVELOPMENT"]["DMARC_RECORD"]
|
|
161
|
+
|
|
162
|
+
# Configure logfile.
|
|
163
|
+
file_handler = FileHandler(filename=toml_config["DEVELOPMENT"]["LOGFILE"])
|
|
164
|
+
file_handler.setFormatter(logging.Formatter(log_format))
|
|
165
|
+
app.logger.addHandler(file_handler)
|
|
166
|
+
|
|
167
|
+
# Configure loglevel.
|
|
168
|
+
if toml_config["DEVELOPMENT"]["LOGLEVEL"] == "ERROR":
|
|
169
|
+
app.logger.setLevel(logging.ERROR)
|
|
170
|
+
elif toml_config["DEVELOPMENT"]["LOGLEVEL"] == "WARNING":
|
|
171
|
+
app.logger.setLevel(logging.WARNING)
|
|
172
|
+
elif toml_config["DEVELOPMENT"]["LOGLEVEL"] == "INFO":
|
|
173
|
+
app.logger.setLevel(logging.INFO)
|
|
174
|
+
elif toml_config["DEVELOPMENT"]["LOGLEVEL"] == "DEBUG":
|
|
175
|
+
app.logger.setLevel(logging.DEBUG)
|
|
176
|
+
else:
|
|
177
|
+
print("Error: you need to set LOGLEVEL to ERROR/WARNING/INFO/DEBUG")
|
|
178
|
+
sys.exit(1)
|
|
179
|
+
else:
|
|
180
|
+
print("Error: you need to set env variabel MODE to PRODUCTION/TESTING/DEVELOPMENT")
|
|
181
|
+
sys.exit(1)
|
|
182
|
+
|
|
183
|
+
app.secret_key = app.config["SECRET_KEY"]
|
|
184
|
+
app.WTF_CSRF_SECRET_KEY = app.config["WTF_CSRF_SECRET_KEY"]
|
|
185
|
+
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
|
186
|
+
#app.config['SQLALCHEMY_DATABASE_URI'] = app.config['SQLALCHEMY_DATABASE_URI']
|
|
187
|
+
|
|
188
|
+
csrf = CSRFProtect(app)
|
|
189
|
+
|
|
190
|
+
# Ensure the instance folder exists
|
|
191
|
+
try:
|
|
192
|
+
os.makedirs(app.instance_path)
|
|
193
|
+
except OSError:
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
# Import the database models.
|
|
197
|
+
from ddmail_webapp.models import db
|
|
198
|
+
db.init_app(app)
|
|
199
|
+
|
|
200
|
+
# Import forms
|
|
201
|
+
|
|
202
|
+
# Apply the blueprints to the app
|
|
203
|
+
from ddmail_webapp import auth, settings, unauthenticated, well_known
|
|
204
|
+
|
|
205
|
+
app.register_blueprint(auth.bp)
|
|
206
|
+
app.register_blueprint(settings.bp)
|
|
207
|
+
app.register_blueprint(unauthenticated.bp)
|
|
208
|
+
app.register_blueprint(well_known.bp)
|
|
209
|
+
|
|
210
|
+
return app
|
ddmail_webapp/auth.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from flask import Blueprint, request, render_template, session, redirect, url_for, current_app
|
|
2
|
+
from argon2 import PasswordHasher
|
|
3
|
+
from ddmail_webapp.models import db, Account, User, Authenticated
|
|
4
|
+
from ddmail_webapp.validators import is_username_allowed, is_password_allowed
|
|
5
|
+
import random
|
|
6
|
+
import string
|
|
7
|
+
import datetime
|
|
8
|
+
import secrets
|
|
9
|
+
|
|
10
|
+
bp = Blueprint("auth", __name__, url_prefix="/")
|
|
11
|
+
|
|
12
|
+
# Generate a token that is easy to write down on paper frpm screen.
|
|
13
|
+
def generate_token(length):
|
|
14
|
+
alphabet = string.ascii_uppercase + string.digits
|
|
15
|
+
while True:
|
|
16
|
+
token = ''.join(secrets.choice(alphabet) for i in range(length))
|
|
17
|
+
if (any(c.isupper() for c in token) and sum(c.isdigit() for c in token) >= 4):
|
|
18
|
+
break
|
|
19
|
+
return token
|
|
20
|
+
|
|
21
|
+
# Generate a password with digit, upparcase letters and lowercase letters.
|
|
22
|
+
def generate_password(length):
|
|
23
|
+
alphabet = string.ascii_letters + string.digits
|
|
24
|
+
while True:
|
|
25
|
+
password = ''.join(secrets.choice(alphabet) for i in range(length))
|
|
26
|
+
if (any(c.islower() for c in password) and any(c.isupper() for c in password) and sum(c.isdigit() for c in password) >= 3):
|
|
27
|
+
break
|
|
28
|
+
return password
|
|
29
|
+
|
|
30
|
+
# Check if a user is authenticated, if the user is authenticated the user id will be returned else None.
|
|
31
|
+
def is_athenticated(cookie):
|
|
32
|
+
# Validate the cookie
|
|
33
|
+
if is_password_allowed(cookie) != True:
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
# Try to find the cookie in the db.
|
|
37
|
+
authenticated = Authenticated.query.filter_by(cookie = cookie).first()
|
|
38
|
+
|
|
39
|
+
# Check if the cookie was in the authenticated table.
|
|
40
|
+
if authenticated == None:
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
# Get the cookie valid_to time in datetime object.
|
|
44
|
+
valid_to = datetime.datetime.strptime(str(authenticated.valid_to), '%Y-%m-%d %H:%M:%S')
|
|
45
|
+
|
|
46
|
+
# Get current time in datetime object.
|
|
47
|
+
now_time = datetime.datetime.now()
|
|
48
|
+
|
|
49
|
+
# Check if cookie is still valid.
|
|
50
|
+
if now_time > valid_to:
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
# Get the user object from db.
|
|
54
|
+
user_from_db = db.session.query(User).filter(User.id == authenticated.user_id).first()
|
|
55
|
+
|
|
56
|
+
# User is authenticated, return user object.
|
|
57
|
+
return user_from_db
|
|
58
|
+
|
|
59
|
+
@bp.route("/register", methods=['POST', 'GET'])
|
|
60
|
+
def register():
|
|
61
|
+
if request.method == 'GET':
|
|
62
|
+
return render_template('register.html')
|
|
63
|
+
if request.method == 'POST':
|
|
64
|
+
ph = PasswordHasher()
|
|
65
|
+
|
|
66
|
+
# Generate new account.
|
|
67
|
+
account = generate_token(12)
|
|
68
|
+
payment_token = generate_token(12)
|
|
69
|
+
|
|
70
|
+
# Add new org to the db.
|
|
71
|
+
new_account = Account(account=account, payment_token=payment_token, funds_in_sek=0, is_enabled=False, is_gratis=False, total_storage_space_g=0 ,created=datetime.datetime.now())
|
|
72
|
+
db.session.add(new_account)
|
|
73
|
+
db.session.commit()
|
|
74
|
+
|
|
75
|
+
# Generate all the user data.
|
|
76
|
+
user = generate_token(12)
|
|
77
|
+
cleartext_password = generate_password(24)
|
|
78
|
+
cleartext_password_key = generate_password(4096)
|
|
79
|
+
|
|
80
|
+
# Generate password hashes for password and password-key.
|
|
81
|
+
password_hash = ph.hash(cleartext_password)
|
|
82
|
+
password_key_hash = ph.hash(cleartext_password_key)
|
|
83
|
+
|
|
84
|
+
# Add the user data to the db.
|
|
85
|
+
new_user = User(account_id=new_account.id, user=user, password_hash=password_hash,password_key_hash=password_key_hash)
|
|
86
|
+
db.session.add(new_user)
|
|
87
|
+
db.session.commit()
|
|
88
|
+
|
|
89
|
+
# Give the data to the user.
|
|
90
|
+
current_app.logger.info("created new account: " + account + " with new user: " + user)
|
|
91
|
+
return render_template('user_created.html',account=new_account.account,user=user,cleartext_password=cleartext_password,cleartext_password_key=cleartext_password_key)
|
|
92
|
+
|
|
93
|
+
@bp.route("/login", methods=['POST', 'GET'])
|
|
94
|
+
def login():
|
|
95
|
+
current_user = None
|
|
96
|
+
|
|
97
|
+
if request.method == 'GET':
|
|
98
|
+
return render_template('login.html',current_user = current_user)
|
|
99
|
+
if request.method == 'POST':
|
|
100
|
+
ph = PasswordHasher()
|
|
101
|
+
|
|
102
|
+
# Get the data from the forms.
|
|
103
|
+
user_from_form = request.form["user"].strip()
|
|
104
|
+
cleartext_password_from_form = request.form["password"].strip()
|
|
105
|
+
file = request.files['key']
|
|
106
|
+
cleartext_password_key_from_form = file.read().strip().decode("utf-8")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# Check that form has data.
|
|
110
|
+
if not user_from_form or not cleartext_password_from_form or not cleartext_password_key_from_form:
|
|
111
|
+
# Login failed
|
|
112
|
+
current_app.logger.warning("failed login, data is missing")
|
|
113
|
+
return render_template('message.html',headline="Login error",message="Failed to login, wrong username and/or password and/or key.",current_user=current_user)
|
|
114
|
+
|
|
115
|
+
# Validate the form data.
|
|
116
|
+
if is_username_allowed(user_from_form) != True or is_password_allowed(cleartext_password_from_form) != True or is_password_allowed(cleartext_password_key_from_form) != True:
|
|
117
|
+
# Login failed.
|
|
118
|
+
current_app.logger.warning("failed login, validation failed")
|
|
119
|
+
return render_template('message.html',headline="Login error",message="Failed to login, wrong username and/or password and/or key.",current_user=current_user)
|
|
120
|
+
|
|
121
|
+
# Get the user data from db.
|
|
122
|
+
user_from_db = db.session.query(User).filter(User.user == user_from_form).first()
|
|
123
|
+
|
|
124
|
+
if not user_from_db:
|
|
125
|
+
# Login failed.
|
|
126
|
+
current_app.logger.warning("failed login for user " + user_from_form + " do not exsist in db")
|
|
127
|
+
return render_template('message.html',headline="Login error",message="Failed to login, wrong username and/or password and/or key.",current_user=current_user)
|
|
128
|
+
|
|
129
|
+
# Check password hash and password key hash.
|
|
130
|
+
try:
|
|
131
|
+
if ph.verify(user_from_db.password_hash, cleartext_password_from_form) == True and ph.verify(user_from_db.password_key_hash, cleartext_password_key_from_form) == True:
|
|
132
|
+
# Generate a secret random cookie.
|
|
133
|
+
cookie = generate_password(128)
|
|
134
|
+
|
|
135
|
+
# Sign the cookie and store it in the browser.
|
|
136
|
+
session["secret"] = cookie
|
|
137
|
+
|
|
138
|
+
# Store the cookie in the db together with expire time.
|
|
139
|
+
authenticated = Authenticated(cookie,user_from_db.id,datetime.datetime.now() + datetime.timedelta(hours=3))
|
|
140
|
+
db.session.add(authenticated)
|
|
141
|
+
db.session.commit()
|
|
142
|
+
|
|
143
|
+
current_app.logger.info("successful login for user " + user_from_db.user + " belonging to account " + user_from_db.account.account)
|
|
144
|
+
return redirect('/settings')
|
|
145
|
+
#return render_template('settings.html',current_user=current_user)
|
|
146
|
+
else:
|
|
147
|
+
# Login failed.
|
|
148
|
+
current_app.logger.warning("failed login for user " + user_from_db.user + " belonging to account " + user_from_db.account.account + " wrong password and/or key")
|
|
149
|
+
return render_template('message.html',headline="Login error",message="Failed to login, wrong username and/or password and/or key.",current_user=current_user)
|
|
150
|
+
except:
|
|
151
|
+
# Login failed.
|
|
152
|
+
current_app.logger.warning("failed login for user " + user_from_db.user + " belonging to account " + user_from_db.account.account + " wrong password and/or key")
|
|
153
|
+
return render_template('message.html',headline="Login error",message="Failed to login, wrong username and/or password and/or key.",current_user=current_user)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@bp.route("/logout")
|
|
157
|
+
def logout():
|
|
158
|
+
# Check if user is athenticated.
|
|
159
|
+
if "secret" in session:
|
|
160
|
+
current_user = is_athenticated(session["secret"])
|
|
161
|
+
current_app.logger.debug("secret is in session")
|
|
162
|
+
|
|
163
|
+
if current_user != None:
|
|
164
|
+
# Delete the cookie from db.
|
|
165
|
+
current_app.logger.debug("deleting user with id " + str(current_user.id) + " from authenticated in db")
|
|
166
|
+
db.session.query(Authenticated).filter(Authenticated.user_id == current_user.id).delete()
|
|
167
|
+
db.session.commit()
|
|
168
|
+
else:
|
|
169
|
+
current_app.logger.debug("secret is not in session")
|
|
170
|
+
current_user = None
|
|
171
|
+
|
|
172
|
+
session.clear()
|
|
173
|
+
return redirect('/')
|
ddmail_webapp/forms.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from flask_wtf import FlaskForm
|
|
2
|
+
from wtforms import StringField, PasswordField, validators
|
|
3
|
+
|
|
4
|
+
# Form modul for domain.
|
|
5
|
+
class DomainForm(FlaskForm):
|
|
6
|
+
domain = StringField('Domain', [validators.DataRequired(),validators.Length(min=4, max=200)])
|
|
7
|
+
|
|
8
|
+
# Form modul for email password.
|
|
9
|
+
class EmailPasswordForm(FlaskForm):
|
|
10
|
+
email_password = PasswordField('Email account current password', [validators.DataRequired(),validators.Length(min=24, max=24)])
|
|
11
|
+
|
|
12
|
+
# Form modul for email.
|
|
13
|
+
class EmailForm(FlaskForm):
|
|
14
|
+
email = StringField('Email', [validators.DataRequired(),validators.Length(min=1, max=200)])
|
|
15
|
+
domain = StringField('Domain', [validators.DataRequired(),validators.Length(min=4, max=200)])
|
|
16
|
+
|
|
17
|
+
# Form modul for alias.
|
|
18
|
+
class AliasForm(FlaskForm):
|
|
19
|
+
src = StringField('Source', [validators.DataRequired(),validators.Length(min=1, max=200)])
|
|
20
|
+
domain = StringField('Domain', [validators.DataRequired(),validators.Length(min=4, max=200)])
|
|
21
|
+
dst = StringField('Destination', [validators.DataRequired(),validators.Length(min=4, max=200)])
|
|
22
|
+
|
ddmail_webapp/models.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from flask_sqlalchemy import SQLAlchemy
|
|
2
|
+
from sqlalchemy import ForeignKey
|
|
3
|
+
from sqlalchemy.orm import relationship
|
|
4
|
+
|
|
5
|
+
db = SQLAlchemy()
|
|
6
|
+
|
|
7
|
+
# DB modul for accounts.
|
|
8
|
+
class Account(db.Model):
|
|
9
|
+
__tablename__ = 'accounts'
|
|
10
|
+
id = db.Column(db.Integer, primary_key=True)
|
|
11
|
+
account = db.Column(db.String(100), unique=True, nullable=False)
|
|
12
|
+
payment_token = db.Column(db.String(12), unique=True, nullable=False)
|
|
13
|
+
funds_in_sek = db.Column(db.Integer, nullable=False)
|
|
14
|
+
is_enabled = db.Column(db.Boolean, unique=False, nullable=False)
|
|
15
|
+
is_gratis = db.Column(db.Boolean, unique=False, nullable=False)
|
|
16
|
+
total_storage_space_g = db.Column(db.Integer, nullable=False)
|
|
17
|
+
created = db.Column(db.DateTime, nullable=False)
|
|
18
|
+
last_time_disabled = db.Column(db.DateTime, nullable=True)
|
|
19
|
+
|
|
20
|
+
aliases = relationship("Alias", back_populates="account")
|
|
21
|
+
emails = relationship("Email", back_populates="account")
|
|
22
|
+
account_domains = relationship("Account_domain", back_populates="account")
|
|
23
|
+
users = relationship("User", back_populates="account")
|
|
24
|
+
openpgp_public_keys = relationship("Openpgp_public_key", back_populates="account")
|
|
25
|
+
|
|
26
|
+
# DB modul for aliases.
|
|
27
|
+
class Alias(db.Model):
|
|
28
|
+
__tablename__ = 'aliases'
|
|
29
|
+
id = db.Column(db.Integer, primary_key=True, nullable=False)
|
|
30
|
+
account_id = db.mapped_column(ForeignKey("accounts.id"), nullable=False)
|
|
31
|
+
src_email = db.Column(db.String(200), unique=True, nullable=False)
|
|
32
|
+
src_account_domain_id = db.mapped_column(db.Integer, ForeignKey('account_domains.id'), nullable=True)
|
|
33
|
+
src_global_domain_id = db.mapped_column(db.Integer, ForeignKey('global_domains.id'), nullable=True)
|
|
34
|
+
dst_email_id = db.mapped_column(db.Integer, ForeignKey('emails.id'), nullable=False)
|
|
35
|
+
|
|
36
|
+
account = relationship("Account", back_populates="aliases")
|
|
37
|
+
email = relationship("Email", back_populates="aliases")
|
|
38
|
+
account_domain = relationship("Account_domain", back_populates="aliases")
|
|
39
|
+
global_domain = relationship("Global_domain", back_populates="aliases")
|
|
40
|
+
|
|
41
|
+
# DB modul for emails.
|
|
42
|
+
class Email(db.Model):
|
|
43
|
+
__tablename__ = 'emails'
|
|
44
|
+
id = db.Column(db.Integer, primary_key=True,nullable=False)
|
|
45
|
+
account_id = db.mapped_column(db.Integer, ForeignKey('accounts.id'),nullable=False)
|
|
46
|
+
account_domain_id = db.mapped_column(db.Integer, ForeignKey('account_domains.id'),nullable=True)
|
|
47
|
+
global_domain_id = db.mapped_column(db.Integer, ForeignKey('global_domains.id'),nullable=True)
|
|
48
|
+
openpgp_public_key_id = db.mapped_column(db.Integer, ForeignKey('openpgp_public_keys.id'),nullable=True)
|
|
49
|
+
email = db.Column(db.String(200), unique=True, nullable=False)
|
|
50
|
+
password_hash = db.Column(db.String(2096), nullable=False)
|
|
51
|
+
storage_space_mb = db.Column(db.Integer, nullable=False)
|
|
52
|
+
|
|
53
|
+
account = relationship("Account", back_populates="emails")
|
|
54
|
+
account_domain = relationship("Account_domain", back_populates="emails")
|
|
55
|
+
global_domain = relationship("Global_domain", back_populates="emails")
|
|
56
|
+
aliases = relationship("Alias", back_populates="email")
|
|
57
|
+
openpgp_public_key = relationship("Openpgp_public_key", back_populates="emails")
|
|
58
|
+
|
|
59
|
+
# DB modul for openpgp_public_keys.
|
|
60
|
+
class Openpgp_public_key(db.Model):
|
|
61
|
+
__tablename__ = 'openpgp_public_keys'
|
|
62
|
+
id = db.Column(db.Integer, primary_key=True,nullable=False)
|
|
63
|
+
account_id = db.mapped_column(db.Integer, ForeignKey('accounts.id'),nullable=False)
|
|
64
|
+
fingerprint = db.Column(db.String(200), unique=True, nullable=False)
|
|
65
|
+
|
|
66
|
+
account = relationship("Account", back_populates="openpgp_public_keys")
|
|
67
|
+
emails = relationship("Email", back_populates="openpgp_public_key")
|
|
68
|
+
|
|
69
|
+
# DB modul for account domains.
|
|
70
|
+
class Account_domain(db.Model):
|
|
71
|
+
__tablename__ = 'account_domains'
|
|
72
|
+
id = db.Column(db.Integer, primary_key=True, nullable=False)
|
|
73
|
+
account_id = db.mapped_column(db.Integer, ForeignKey('accounts.id'), nullable=False)
|
|
74
|
+
domain = db.Column(db.String(200), unique=True, nullable=False)
|
|
75
|
+
|
|
76
|
+
account = relationship("Account", back_populates="account_domains")
|
|
77
|
+
emails = relationship("Email", back_populates="account_domain")
|
|
78
|
+
aliases = relationship("Alias", back_populates="account_domain")
|
|
79
|
+
|
|
80
|
+
# DB modul for global domains.
|
|
81
|
+
class Global_domain(db.Model):
|
|
82
|
+
__tablename__ = 'global_domains'
|
|
83
|
+
id = db.Column(db.Integer, primary_key=True, nullable=False)
|
|
84
|
+
domain = db.Column(db.String(200), unique=True, nullable=False)
|
|
85
|
+
is_enabled = db.Column(db.Boolean, unique=False, nullable=False,default=True)
|
|
86
|
+
|
|
87
|
+
emails = relationship("Email", back_populates="global_domain")
|
|
88
|
+
aliases = relationship("Alias", back_populates="global_domain")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# DB modul for users.
|
|
92
|
+
class User(db.Model):
|
|
93
|
+
__tablename__ = 'users'
|
|
94
|
+
id = db.Column(db.Integer, primary_key=True, nullable=False)
|
|
95
|
+
account_id = db.mapped_column(db.Integer, ForeignKey('accounts.id'), nullable=False)
|
|
96
|
+
user = db.Column(db.String(100), unique=True, nullable=False)
|
|
97
|
+
password_hash = db.Column(db.String(200), unique=True, nullable=False)
|
|
98
|
+
password_key_hash = db.Column(db.String(200), unique=True, nullable=False)
|
|
99
|
+
|
|
100
|
+
account = relationship("Account", back_populates="users")
|
|
101
|
+
authenticated = relationship("Authenticated", back_populates="user")
|
|
102
|
+
|
|
103
|
+
# DB model for authenticated.
|
|
104
|
+
class Authenticated(db.Model):
|
|
105
|
+
__tablename__ = 'authenticateds'
|
|
106
|
+
id = db.Column(db.Integer, primary_key=True, nullable=False)
|
|
107
|
+
cookie = db.Column(db.String(12), unique=True, nullable=False)
|
|
108
|
+
user_id = db.mapped_column(db.Integer, ForeignKey('users.id'), nullable=False)
|
|
109
|
+
valid_to = db.Column(db.DateTime, nullable=False)
|
|
110
|
+
|
|
111
|
+
user = relationship("User", back_populates="authenticated")
|
|
112
|
+
|
|
113
|
+
def __init__(self,cookie,user_id,valid_to):
|
|
114
|
+
self.cookie = cookie
|
|
115
|
+
self.user_id = user_id
|
|
116
|
+
self.valid_to = valid_to
|
|
117
|
+
|