Skip to content

Commit 6e86ccc

Browse files
authored
Merge pull request #1585 from master3395/v2.5.5-dev
Add regenerateTwoFASecret functionality and UI support
2 parents 614bf7c + 6aed317 commit 6e86ccc

File tree

4 files changed

+132
-0
lines changed

4 files changed

+132
-0
lines changed

userManagment/static/userManagment/userManagment.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,48 @@ app.controller('modifyUser', function ($scope, $http) {
180180
}
181181
};
182182

183+
$scope.regenerateSecret = function() {
184+
if (!$scope.accountUsername) {
185+
alert('Please select a user first.');
186+
return;
187+
}
188+
189+
if (!confirm('Are you sure you want to regenerate the 2FA secret? This will generate a new secret key and you will need to update your authenticator app.')) {
190+
return;
191+
}
192+
193+
var url = "/users/regenerateTwoFASecret";
194+
var data = {
195+
accountUsername: $scope.accountUsername
196+
};
197+
var config = {
198+
headers: {
199+
'X-CSRFToken': getCookie('csrftoken')
200+
}
201+
};
202+
203+
$http.post(url, data, config).then(function(response) {
204+
if (response.data.status === 1) {
205+
// Update the secret key and formatted version
206+
$scope.secretKey = response.data.secretKey;
207+
$scope.formattedSecretKey = response.data.secretKey.match(/.{1,4}/g).join(' ');
208+
209+
// Update the QR code with new provisioning URI
210+
qrCode.set({
211+
value: response.data.otpauth
212+
});
213+
214+
// Show success message
215+
alert('2FA secret has been successfully regenerated! Please update your authenticator app with the new QR code or secret key.');
216+
} else {
217+
alert('Error regenerating 2FA secret: ' + response.data.error_message);
218+
}
219+
}, function(error) {
220+
console.error('Error regenerating 2FA secret:', error);
221+
alert('Failed to regenerate 2FA secret. Please try again.');
222+
});
223+
};
224+
183225
// WebAuthn Functions
184226
$scope.loadWebAuthnData = function() {
185227
if (!$scope.accountUsername) return;

userManagment/templates/userManagment/modifyUser.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,14 @@ <h3 class="section-title">
319319
</div>
320320
<small class="text-muted mt-1 d-block">{% trans "Enter this key in your authenticator app if you cannot scan QR codes." %}</small>
321321
</div>
322+
323+
<!-- Regenerate Secret Button -->
324+
<div class="mt-3" ng-show="twofa">
325+
<button type="button" class="btn btn-warning" ng-click="regenerateSecret()" title="{% trans 'Generate a new 2FA secret key' %}">
326+
<i class="fa fa-refresh"></i> {% trans "Regenerate Secret" %}
327+
</button>
328+
<small class="text-muted d-block mt-1">{% trans "Warning: This will generate a new secret key. You'll need to update your authenticator app." %}</small>
329+
</div>
322330
</div>
323331
</div>
324332

userManagment/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,5 @@
4040
path('migrateUser', homeDirectoryViews.migrateUser, name='migrateUser'),
4141
path('userMigration', views.userMigration, name='userMigration'),
4242
path('disable2FA', views.disable2FA, name='disable2FA'),
43+
path('regenerateTwoFASecret', views.regenerateTwoFASecret, name='regenerateTwoFASecret'),
4344
]

userManagment/views.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,11 +504,22 @@ def saveModifications(request):
504504
user.lastName = lastName
505505
user.email = email
506506
user.type = 0
507+
508+
# Check if 2FA is being enabled (transition from 0 to 1)
509+
was_2fa_disabled = user.twoFA == 0
507510
user.twoFA = twofa
508511

509512
# If 2FA is being disabled, clear the secret key
510513
if twofa == 0:
511514
user.secretKey = 'None'
515+
# If 2FA is being enabled (transition from disabled to enabled), always generate a new secret
516+
elif twofa == 1 and was_2fa_disabled:
517+
import pyotp
518+
user.secretKey = pyotp.random_base32()
519+
520+
# Log the secret regeneration for security audit
521+
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
522+
logging.writeToFile(f'2FA secret auto-regenerated for user: {accountUsername} by admin: {val}')
512523

513524
if securityLevel == 'LOW':
514525
user.securityLevel = secMiddleware.LOW
@@ -1136,3 +1147,73 @@ def disable2FA(request):
11361147
data_ret = secure_error_response(e, 'Failed to disable 2FA')
11371148
json_data = json.dumps(data_ret)
11381149
return HttpResponse(json_data)
1150+
1151+
1152+
def regenerateTwoFASecret(request):
1153+
"""
1154+
Manually regenerate 2FA secret for a specific user
1155+
"""
1156+
try:
1157+
val = request.session['userID']
1158+
currentACL = ACLManager.loadedACL(val)
1159+
1160+
if currentACL['admin'] != 1:
1161+
data_ret = {'status': 0, 'error_message': 'Unauthorized access. Admin privileges required.'}
1162+
json_data = json.dumps(data_ret)
1163+
return HttpResponse(json_data)
1164+
1165+
if request.method == 'POST':
1166+
data = json.loads(request.body)
1167+
accountUsername = data.get('accountUsername')
1168+
1169+
if not accountUsername:
1170+
data_ret = {'status': 0, 'error_message': 'Username is required.'}
1171+
json_data = json.dumps(data_ret)
1172+
return HttpResponse(json_data)
1173+
1174+
try:
1175+
user = Administrator.objects.get(userName=accountUsername)
1176+
1177+
# Check if user has 2FA enabled
1178+
if not user.twoFA:
1179+
data_ret = {'status': 0, 'error_message': '2FA is not enabled for this user.'}
1180+
json_data = json.dumps(data_ret)
1181+
return HttpResponse(json_data)
1182+
1183+
# Generate new secret key
1184+
import pyotp
1185+
new_secret = pyotp.random_base32()
1186+
user.secretKey = new_secret
1187+
user.save()
1188+
1189+
# Generate new QR code provisioning URI
1190+
otpauth = pyotp.totp.TOTP(new_secret).provisioning_uri(user.email, issuer_name="CyberPanel")
1191+
1192+
# Log the secret regeneration for security audit
1193+
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
1194+
logging.writeToFile(f'2FA secret manually regenerated for user: {accountUsername} by admin: {val}')
1195+
1196+
data_ret = {
1197+
'status': 1,
1198+
'error_message': '2FA secret successfully regenerated.',
1199+
'message': f'Two-factor authentication secret has been regenerated for user {accountUsername}.',
1200+
'secretKey': new_secret,
1201+
'otpauth': otpauth
1202+
}
1203+
json_data = json.dumps(data_ret)
1204+
return HttpResponse(json_data)
1205+
1206+
except Administrator.DoesNotExist:
1207+
data_ret = {'status': 0, 'error_message': 'User not found.'}
1208+
json_data = json.dumps(data_ret)
1209+
return HttpResponse(json_data)
1210+
1211+
data_ret = {'status': 0, 'error_message': 'Invalid request method.'}
1212+
json_data = json.dumps(data_ret)
1213+
return HttpResponse(json_data)
1214+
1215+
except Exception as e:
1216+
secure_log_error(e, 'regenerateTwoFASecret', request.session.get('userID', 'Unknown'))
1217+
data_ret = secure_error_response(e, 'Failed to regenerate 2FA secret')
1218+
json_data = json.dumps(data_ret)
1219+
return HttpResponse(json_data)

0 commit comments

Comments
 (0)