Skip to content

Commit 62f065f

Browse files
feat: login logout email added
1 parent 7b331ad commit 62f065f

13 files changed

Lines changed: 345 additions & 92 deletions

File tree

src/models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ class User(db.Model, UserMixin):
9090
receive_email_alerts = db.Column(db.Boolean, default=True)
9191

9292

93-
class EmailPassword(db.Model):
94-
__tablename__ = "EmailPassword"
93+
class SmptEamilPasswordConfig(db.Model):
94+
__tablename__ = "SmptEamilPasswordConfig"
9595
id = db.Column(db.Integer, primary_key=True)
9696
email = db.Column(db.String(150), unique=True, nullable=False)
9797
password = db.Column(db.String(150), nullable=False)

src/routes/auth.py

Lines changed: 64 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
33
from flask_sqlalchemy import SQLAlchemy
44
from werkzeug.security import generate_password_hash, check_password_hash
5+
6+
7+
from flask import Flask, render_template, request, jsonify
8+
import subprocess
9+
510
from src.scripts.email_me import send_email
611

712
from src.config import app, db
8-
from src.models import User, EmailPassword, DashboardSettings
13+
from src.models import User, SmptEamilPasswordConfig, DashboardSettings
14+
from src.utils import read_html_file
915

1016
auth_bp = blueprints.Blueprint('auth', __name__)
1117

@@ -64,7 +70,8 @@ def login():
6470
# Get Admin Emails with Alerts Enabled:
6571
admin_email_address = get_email_addresses(user_level='admin', receive_email_alerts=True)
6672
if admin_email_address:
67-
send_email(admin_email_address, 'Login Alert', f'{user.username} logged in to the system.')
73+
login_body = read_html_file("src/templates/email_templates/login.html")
74+
send_email(admin_email_address, 'Login Alert', login_body, is_html=True)
6875

6976
return redirect(url_for('dashboard'))
7077
flash('Invalid username or password', 'danger')
@@ -114,30 +121,13 @@ def protected():
114121

115122
@app.route('/logout')
116123
def logout():
124+
receiver_email = get_email_addresses(user_level='admin', receive_email_alerts=True)
125+
if receiver_email:
126+
logout_body = read_html_file("src/templates/email_templates/logout.html")
127+
send_email(receiver_email, 'Logout Alert', logout_body, is_html=True)
117128
logout_user()
118129
return redirect(url_for('login'))
119130

120-
121-
# def admin_required(f):
122-
# """Decorator to ensure the current user is an admin."""
123-
# @wraps(f)
124-
# def decorated_function(*args, **kwargs):
125-
# if not current_user.is_authenticated or current_user.user_level != 'admin':
126-
# flash('Access denied. Admins only.')
127-
# return redirect(url_for('login'))
128-
# return f(*args, **kwargs)
129-
# return decorated_function
130-
131-
# def user_required(f):
132-
# """Decorator to ensure the current user is a regular user."""
133-
# @wraps(f)
134-
# def decorated_function(*args, **kwargs):
135-
# if not current_user.is_authenticated or current_user.user_level != 'user':
136-
# flash('Access denied. Users only.')
137-
# return redirect(url_for('login'))
138-
# return f(*args, **kwargs)
139-
# return decorated_function
140-
141131
@app.route('/users')
142132
@login_required
143133
def view_users():
@@ -190,28 +180,36 @@ def delete_user(username):
190180

191181
@app.route("/update-email-password", methods=["GET", "POST"])
192182
@login_required
193-
def update_email_password():
194-
email_password = EmailPassword.query.first()
183+
def update_smpt_email_password():
184+
smtp_config = SmptEamilPasswordConfig.query.first()
195185

196186
if request.method == "POST":
197187
new_email = request.form.get("email")
198188
new_password = request.form.get("password")
199189

200-
if new_email:
201-
email_password.email = new_email
202-
if new_password:
203-
email_password.password = new_password
204-
190+
if not new_email or not new_password:
191+
flash("Please provide email and password.", "danger")
192+
return redirect(url_for("update_smpt_email_password"))
193+
194+
195+
if not smtp_config:
196+
smtp_config = SmptEamilPasswordConfig(email=new_email, password=new_password)
197+
db.session.add(smtp_config)
198+
else:
199+
smtp_config.email = new_email
200+
smtp_config.password = new_password
201+
205202
db.session.commit()
206203
flash("Email and password updated successfully!", "success")
207-
return redirect(url_for("update_email_password"))
204+
return redirect(url_for("update_smpt_email_password"))
208205

209-
return render_template("update_email_password.html", email_password=email_password)
206+
return render_template("update_smpt_email_password.html", smtp_config=smtp_config)
210207

211208
@app.route("/send_email", methods=["GET", "POST"])
212209
@login_required
213210
def send_email_page():
214211
dasboard_settings = DashboardSettings.query.first()
212+
receiver_email = get_email_addresses(user_level='admin', receive_email_alerts=True)
215213
if dasboard_settings:
216214
enable_alerts = dasboard_settings.enable_alerts
217215
if request.method == "POST":
@@ -225,18 +223,24 @@ def send_email_page():
225223
flash("Please provide recipient, subject, and body.", "danger")
226224
return redirect(url_for('send_email_page'))
227225

226+
print("Priority:", priority)
227+
print("receiver_email:", receiver_email)
228+
228229
# on high priority, send to all users or admin users even the receive_email_alerts is False
229230
if priority == "high" and receiver_email == "all_users":
231+
print("Sending to all users")
230232
receiver_email = get_email_addresses(fetch_all_users=True)
231233
elif priority == "high" and receiver_email == "admin_users":
234+
print("Sending to admin users")
232235
receiver_email = get_email_addresses(user_level='admin', fetch_all_users=True)
233236

234237
# priority is low, send to users with receive_email_alerts is True
235238
if priority == "low" and receiver_email == "all_users":
239+
print("Sending to all users with receive_email_alerts=True")
236240
receiver_email = get_email_addresses(receive_email_alerts=True)
237241
elif priority == "low" and receiver_email == "admin_users":
238-
receiver_email = get_email_addresses(user_level='admin', receive_email_alerts=True)
239-
242+
print("Sending to admin users with receive_email_alerts=True")
243+
receiver_email = get_email_addresses(user_level='admin', receive_email_alerts=True)
240244

241245
if not receiver_email:
242246
flash("No users found to send email to.", "danger")
@@ -248,15 +252,34 @@ def send_email_page():
248252
attachment_path = f"/tmp/{attachment.filename}"
249253
attachment.save(attachment_path)
250254
try:
251-
respone = send_email(receiver_email, subject, body, attachment_path)
252-
if respone and respone["status"] == "success":
253-
flash(respone['message'], "success")
254-
elif respone and respone["status"] == "failed":
255-
flash(respone['message'], "danger")
256-
return redirect(url_for(respone["type"]))
255+
respose = send_email(receiver_email, subject, body, attachment_path)
256+
print(respose)
257+
if respose and respose.get("status") == "success":
258+
flash(respose.get("message"), "success")
257259
except Exception as e:
258260
flash(f"Failed to send email: {str(e)}", "danger")
259261

260262
return redirect(url_for('send_email_page'))
261263

262-
return render_template("send_email.html", enable_alerts=enable_alerts)
264+
return render_template("send_email.html", enable_alerts=enable_alerts)
265+
266+
@app.route('/terminal', methods=['GET', 'POST'])
267+
@login_required
268+
def terminal():
269+
if current_user.user_level != 'admin':
270+
flash("Your account does not have permission to view this page.", "danger")
271+
return render_template("error/permission_denied.html")
272+
if request.method == 'POST':
273+
command = request.form.get('command')
274+
if command:
275+
try:
276+
# Run the command and capture the output
277+
output = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT, universal_newlines=True)
278+
except subprocess.CalledProcessError as e:
279+
# If the command fails, capture the error output
280+
output = e.output
281+
return jsonify(output=output)
282+
return render_template('terminal.html')
283+
284+
if __name__ == '__main__':
285+
app.run(debug=True)

src/scripts/email_me.py

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,57 +4,53 @@
44
from email.mime.text import MIMEText
55
from email.mime.base import MIMEBase
66
from email import encoders
7-
from dotenv import load_dotenv
8-
7+
from flask import redirect, url_for, flash
98
from src.config import app
10-
from src.models import EmailPassword, DashboardSettings
11-
# Load environment variables from .env file
12-
load_dotenv()
13-
14-
# Get email credentials from .env
15-
EMAIL_ADDRESS = os.getenv("EMAIL_ADDRESS")
16-
EMAIL_PASSWORD = os.getenv("EMAIL_PASSWORD")
17-
9+
from src.models import SmptEamilPasswordConfig, DashboardSettings
1810

19-
def send_email(receiver_email, subject, body, attachment_path=None):
11+
def send_email(receiver_email, subject, body, attachment_path=None, is_html=False):
2012

2113
if isinstance(receiver_email, str):
2214
receiver_email = [receiver_email] # Convert single address to list
2315

2416
with app.app_context():
25-
dasboard_settings = DashboardSettings.query.first()
26-
if dasboard_settings:
27-
enable_alerts = dasboard_settings.enable_alerts
17+
dashboard_settings = DashboardSettings.query.first()
18+
if dashboard_settings:
19+
enable_alerts = dashboard_settings.enable_alerts
2820
if not enable_alerts:
2921
print("Email alerts are disabled. Please enable them in the settings.")
30-
return {
31-
"message": "Email alerts are disabled. Please enable them in the settings.",
32-
"status": "failed",
33-
"type": "general_settings",
34-
}
35-
email_password = EmailPassword.query.first()
22+
flash("Email alerts are disabled. Please enable them in the settings.", "danger")
23+
return redirect(url_for('general_settings'))
24+
25+
email_password = SmptEamilPasswordConfig.query.first()
3626
if not email_password:
37-
print("Email credentials not found. Please set EMAIL_ADDRESS and EMAIL_PASSWORD environment variables.")
38-
return {
39-
"message": "Email credentials not found. Please set EMAIL_ADDRESS and EMAIL_PASSWORD environment variables.",
40-
"status": "failed",
41-
"type": "update_email_password",
42-
}
27+
print("SMTP email credentials not found. Please set EMAIL_ADDRESS and EMAIL_PASSWORD environment variables.")
28+
flash("SMTP email credentials not found. Please set EMAIL_ADDRESS and EMAIL_PASSWORD environment variables.", "danger")
29+
return redirect(url_for('update_smpt_email_password'))
30+
31+
EMAIL_ADDRESS = email_password.email
32+
EMAIL_PASSWORD = email_password.password
4333

34+
print(f"Sending email to {receiver_email}")
4435

4536
for email in receiver_email:
4637
try:
4738
# Create a multipart message
4839
msg = MIMEMultipart()
4940
msg['From'] = EMAIL_ADDRESS
5041
msg['To'] = email
51-
msg['Subject'] = subject
42+
msg['Subject'] = "SystemGuard Alert:" + subject
5243
# Append message to the body
5344
append_message = "This is an automated email from the SystemGuard application. Please do not reply to this email."
54-
full_body = body + "\n\n" + append_message
55-
56-
# Attach the body with the msg instance
57-
msg.attach(MIMEText(full_body, 'plain'))
45+
46+
if is_html:
47+
# If the email is HTML, append the message in HTML format
48+
full_body = body + f"<p>{append_message}</p>"
49+
msg.attach(MIMEText(full_body, 'html'))
50+
else:
51+
# If the email is plain text, append the message in plain text format
52+
full_body = body + "\n\n" + append_message
53+
msg.attach(MIMEText(full_body, 'plain'))
5854

5955
# Attach a file if provided
6056
if attachment_path:
@@ -86,7 +82,6 @@ def send_email(receiver_email, subject, body, attachment_path=None):
8682

8783
except Exception as e:
8884
return {
89-
"message": f"failed to send email to {email}. Error: {str(e)}",
85+
"message": f"Failed to send email to {email}. Error: {str(e)}",
9086
"status": "failed",
9187
}
92-

src/static/css/terminal.css

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
body {
2+
font-family: monospace;
3+
background-color: #000;
4+
color: #0f0;
5+
margin: 0;
6+
padding: 20px;
7+
}
8+
9+
#terminal {
10+
background-color: #000;
11+
color: #0f0;
12+
padding: 10px;
13+
border: 1px solid #0f0;
14+
height: 400px;
15+
overflow-y: auto;
16+
margin-top: 50px;
17+
}
18+
19+
#command {
20+
width: 100%;
21+
background-color: #000;
22+
color: #0f0;
23+
border: none;
24+
outline: none;
25+
font-size: 16px;
26+
}
File renamed without changes.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Login Notification</title>
8+
<style>
9+
.alert-container {
10+
max-width: 600px;
11+
margin: 50px auto;
12+
background-color: #f8f9fa;
13+
border: 1px solid #ced4da;
14+
border-radius: 10px;
15+
padding: 20px;
16+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
17+
}
18+
19+
.alert-header {
20+
font-size: 24px;
21+
color: #343a40;
22+
margin-bottom: 10px;
23+
text-align: center;
24+
}
25+
26+
.alert-body {
27+
font-size: 16px;
28+
color: #495057;
29+
margin-bottom: 20px;
30+
text-align: center;
31+
}
32+
33+
.alert-footer {
34+
text-align: center;
35+
}
36+
37+
.alert-footer a {
38+
text-decoration: none;
39+
color: #007bff;
40+
font-weight: bold;
41+
}
42+
</style>
43+
</head>
44+
45+
<body>
46+
<div class="alert-container">
47+
<div class="alert-header">
48+
<i class="fas fa-info-circle"></i> Login Notification
49+
</div>
50+
<div class="alert-body">
51+
Hello {{ username }},<br><br>
52+
We noticed a login to your account on {{ login_time }} from the IP address {{ ip_address }}.<br><br>
53+
If this was not you, please reset your password immediately.
54+
</div>
55+
<div class="alert-footer">
56+
<a href="{{ url_for('reset_password') }}">Reset Password</a>
57+
</div>
58+
</div>
59+
</body>
60+
61+
</html>

0 commit comments

Comments
 (0)