Skip to content

Commit e251f2e

Browse files
feat: change_password feature added
1 parent 92df725 commit e251f2e

12 files changed

+120
-7
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ All significant changes to this project will be documented here. The format foll
1212
- Flash messages are now displayed on the dashboard.
1313
- Flash messages are configurable in the admin dashboard.
1414
- Audit Logs are now displayed on the dashboard and can be exported to CSV.
15+
- Password now can be changed from the dashboard.
1516

1617

1718
## Version 2.0.0

src/blueprints/admin.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ def dashboard():
142142
system_settings = Settings.query.first()
143143
audit_logs = AuditLog.query.filter_by(user_id=current_user.id).order_by(AuditLog.timestamp.desc()).all()
144144
if current_user.user_level == 'admin':
145+
146+
if not current_user.password_changed:
147+
flash('You need to change your password first to continue.', 'warning')
148+
return render_template('change_password.html', settings=system_settings)
149+
145150
total_users = User.query.count() # Count total users
146151
active_sessions = len(session) # This is a basic approach. You may want to track sessions differently.
147152

@@ -164,7 +169,8 @@ def dashboard():
164169
cpu_core=cpu_core,
165170
cpu_util=cpu_utilization,
166171
audit_logs=audit_logs,
167-
settings=system_settings)
172+
settings=system_settings,
173+
current_user=current_user)
168174
elif current_user.user_level == 'customer':
169175
return render_template('customer_dashboard.html', audit_logs=audit_logs)
170176

src/blueprints/auth.py

+39-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
1+
from datetime import datetime
22
from flask import render_template, request, redirect, url_for, Blueprint, flash
33

4-
from flask_login import login_user, logout_user
4+
from flask_login import login_user, logout_user, current_user, login_required
55

66
from src.config import app, db, bcrypt, login_manager
77
from src.models import User, log_action, Settings
@@ -19,9 +19,11 @@ def register():
1919
# Check if this is the first user
2020
if User.query.count() == 0:
2121
user_level = 'admin' # Make the first user an admin
22+
password_changed = False
2223
else:
2324
user_level = 'customer' # Default to 'customer' for all other users
24-
user = User(fname=fname, lname=lname, username=username, email=email, password=password, user_level=user_level)
25+
password_changed = True
26+
user = User(fname=fname, lname=lname, username=username, email=email, password=password, user_level=user_level, password_changed=password_changed)
2527
db.session.add(user)
2628
db.session.commit()
2729
return redirect(url_for('auth.login'))
@@ -37,12 +39,46 @@ def login():
3739
if user and bcrypt.check_password_hash(user.password, password):
3840
login_user(user)
3941
log_action(user.id, user.username, 'Login', f'User {user.username} logged in.')
42+
43+
# Check if the admin needs to change their password
44+
if user.user_level == 'admin' and not user.password_changed:
45+
flash('You need to change your password first to continue.', 'warning')
46+
return redirect(url_for('auth.change_password'))
47+
4048
flash(f'Login successful as {user.username}', 'success')
4149
return redirect(url_for('admin.dashboard'))
4250
else:
4351
flash('Login failed. Please check your credentials.', 'danger')
4452
return render_template('login.html', settings=system_settings)
4553

54+
@auth_bp.route("/change-password", methods=['GET', 'POST'])
55+
@login_required
56+
def change_password():
57+
system_settings = Settings.query.first()
58+
if request.method == 'POST':
59+
current_password = request.form.get('current_password')
60+
new_password = request.form.get('new_password')
61+
user = User.query.get(current_user.id)
62+
63+
if user and bcrypt.check_password_hash(user.password, current_password):
64+
user.password = bcrypt.generate_password_hash(new_password).decode('utf-8')
65+
user.password_changed = True # Set the flag to True
66+
user.last_password_change=datetime.utcnow()
67+
db.session.commit()
68+
flash('Password updated successfully!', 'success')
69+
70+
return redirect(url_for('auth.change_password'))
71+
else:
72+
flash('Current password is incorrect.', 'danger')
73+
74+
# if current user password_changed false, flash a message
75+
if not current_user.password_changed:
76+
flash('You need to change your password first to continue.', 'warning')
77+
return render_template('change_password.html', settings=system_settings)
78+
79+
return render_template('change_password.html', settings=system_settings)
80+
81+
4682
@auth_bp.route("/logout")
4783
def logout():
4884
logout_user()

src/blueprints/decorators.py

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from flask import flash, render_template
2+
from flask_login import current_user
3+
from src.config import db
4+
from src.models import Settings
5+
6+
def require_password_change(func):
7+
def wrapper(*args, **kwargs):
8+
# Check if the user is authenticated and if a password change is required
9+
if current_user.is_authenticated and not current_user.password_changed:
10+
flash('You need to change your password first to continue.', 'warning')
11+
system_settings = Settings.query.first() # Ensure to load system settings if needed
12+
return render_template('change_password.html', settings=system_settings)
13+
return func(*args, **kwargs)
14+
return wrapper

src/blueprints/gold_calculator.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
import logging
33

44
from flask import render_template, request, session, redirect, url_for, flash, Blueprint
5-
from flask_login import current_user
5+
from flask_login import current_user, login_required, login_remembered
66
from src.calculators import GoldCalculator
77

88
from src.config import db
99
from src.models import Settings, GoldTransaction, JewellerDetails, log_action
1010
from src.blueprints.helper import get_currency_symbol
11+
from src.blueprints.decorators import require_password_change
1112

1213
gold_calculator_bp = Blueprint('gold_calculator', __name__)
1314

@@ -16,6 +17,7 @@
1617
def gold_calculator():
1718
system_settings = Settings.query.first()
1819
jeweller_details = JewellerDetails.query.first()
20+
1921
if not system_settings.is_gold_calculator_enabled:
2022
return redirect(url_for('additional.permission_denied'))
2123
if request.method == 'POST':

src/config.py

+7
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,10 @@ def page_not_found(e):
2828
@app.errorhandler(500)
2929
def internal_server_error(e):
3030
return render_template('500.html'), 500
31+
32+
# Custom filter to format dates
33+
@app.template_filter('format_datetime')
34+
def format_datetime(value, format='%Y-%m-%d %H:%M:%S'):
35+
if value is not None:
36+
return value.strftime(format)
37+
return ''

src/models.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ class User(db.Model, UserMixin):
3333
username = db.Column(db.String(20), unique=True, nullable=False)
3434
email = db.Column(db.String(120), unique=True, nullable=False)
3535
password = db.Column(db.String(60), nullable=False)
36+
last_password_change = db.Column(db.DateTime, default=datetime.utcnow)
3637
user_level = db.Column(db.String(10), nullable=False, default='customer')
38+
password_changed = db.Column(db.Boolean, default=False)
3739

3840
def __repr__(self):
3941
return f"User('{self.username}', '{self.email}', '{self.user_level}')"
@@ -126,7 +128,8 @@ def __init__(self, jeweller_name, jeweller_address, jeweller_contact, jeweller_e
126128
username='admin',
127129
email='admin@gmail.com',
128130
password=generate_password_hash('admin'),
129-
user_level='admin'
131+
user_level='admin',
132+
last_password_change=datetime.utcnow()
130133
)
131134
db.session.add(admin_user)
132135
db.session.commit()

src/static/css/admin_dashboard.css

+12
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@
3131
margin-bottom: 30px;
3232
}
3333

34+
.card p:last-of-type {
35+
font-size: 0.9em; /* Reduced font size */
36+
color: #666; /* Light grey color for less emphasis */
37+
}
38+
39+
.card h3 {
40+
font-size: 1.3em; /* Adjusted heading size if necessary */
41+
margin-bottom: 8px;
42+
}
43+
3444
.card {
3545
background-color: #ffffff;
3646
padding: 20px;
@@ -128,3 +138,5 @@
128138
.disabled-link:focus {
129139
outline: none;
130140
}
141+
142+

src/static/css/change_password.css

Whitespace-only changes.

src/templates/admin_dashboard.html

+7
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ <h3>CPU Core</h3>
4141
<h3>CPU Utilization</h3>
4242
<p>{{ cpu_util }}</p>
4343
</div>
44+
<!-- last password change -->
45+
<div class="card">
46+
<i class="fas fa-key card-icon"></i>
47+
<h3>Last Password Change</h3>
48+
<p>{{ current_user.last_password_change | format_datetime('%Y-%m-%d %H:%M:%S') }}</p>
49+
</div>
4450
</div>
4551

4652
<div class="admin-tools">
@@ -51,6 +57,7 @@ <h3><i class="fas fa-tools"></i> Admin Tools</h3>
5157
<a href="{{ url_for('admin.settings') }}" class="btn btn-large"><i class="fas fa-cog"></i> System Settings</a>
5258
<a href="{{ url_for('admin.audit_log') }}" class="btn btn-large"><i class="fas fa-clipboard-list"></i> Audit Logs</a>
5359
<a href="{{ url_for('admin.update_jeweller_details') }}" class="btn btn-large"><i class="fas fa-cog"></i>Jewellers Config Settings</a>
60+
<a href="{{ url_for('auth.change_password') }}" class="btn btn-large"><i class="fas fa-key"></i> Change Password</a>
5461
</div>
5562
</div>
5663
<div class="admin-dashboard">

src/templates/change_password.html

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{% extends "base/base.html" %}
2+
3+
{% block title %}Change Password{% endblock %}
4+
{% block styles %}
5+
<link rel="stylesheet" href="{{ url_for('static', filename='css/change_password.css') }}">
6+
{% endblock %}
7+
8+
{% block content %}
9+
<div class="container">
10+
<h2>Change Password</h2>
11+
<!-- Display flash messages -->
12+
{% include "ext/flash_message.html" %}
13+
<form method="POST" action="{{ url_for('auth.change_password') }}">
14+
<div class="form-group">
15+
<label for="current_password">Current Password</label>
16+
<input type="password" class="form-control" id="current_password" name="current_password" required>
17+
</div>
18+
<div class="form-group">
19+
<label for="new_password">New Password</label>
20+
<input type="password" class="form-control" id="new_password" name="new_password" required>
21+
</div>
22+
<button type="submit" class="btn btn-primary">Change Password</button>
23+
</form>
24+
</div>
25+
{% endblock %}

src/templates/history.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ <h1>Transaction History</h1>
4747
<td>{{ transaction.service_charge }}</td>
4848
<td>{{ transaction.tax }}</td>
4949
<td>{{ transaction.total }}</td>
50-
<td>{{ transaction.timestamp }}</td>
50+
<td>{{ transaction.timestamp | format_datetime('%Y-%m-%d %H:%M:%S') }}</td>
5151
</tr>
5252
{% endfor %}
5353
</table>

0 commit comments

Comments
 (0)