Skip to content

Commit bcfb0f7

Browse files
dunaj-devclaude
andcommitted
Add account password change functionality
- Add backend function to update email account passwords - Add API endpoint for password updates (/api/accounts/:email/password) - Create password change modal in Accounts page - Add change password button to email accounts table - Add password update validation - Add translations for all new features - Improve UI with proper button labels and icons 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 247f29b commit bcfb0f7

File tree

6 files changed

+308
-94
lines changed

6 files changed

+308
-94
lines changed

backend/dockerMailserver.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,20 @@ async function addAccount(email, password) {
150150
}
151151
}
152152

153+
// Function to update an email account password
154+
async function updateAccountPassword(email, password) {
155+
try {
156+
debugLog(`Updating password for account: ${email}`);
157+
await execSetup(`email update ${email} ${password}`);
158+
debugLog(`Password updated for account: ${email}`);
159+
return { success: true, email };
160+
} catch (error) {
161+
console.error('Error updating account password:', error);
162+
debugLog('Account password update error:', error);
163+
throw new Error('Unable to update email account password');
164+
}
165+
}
166+
153167
// Function to delete an email account
154168
async function deleteAccount(email) {
155169
try {
@@ -310,6 +324,7 @@ function formatMemorySize(bytes) {
310324
module.exports = {
311325
getAccounts,
312326
addAccount,
327+
updateAccountPassword,
313328
deleteAccount,
314329
getAliases,
315330
addAlias,

backend/index.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,26 @@ app.delete('/api/accounts/:email', async (req, res) => {
6060
}
6161
});
6262

63+
// Endpoint for updating an email account password
64+
app.put('/api/accounts/:email/password', async (req, res) => {
65+
try {
66+
const { email } = req.params;
67+
const { password } = req.body;
68+
69+
if (!email) {
70+
return res.status(400).json({ error: 'Email is required' });
71+
}
72+
if (!password) {
73+
return res.status(400).json({ error: 'Password is required' });
74+
}
75+
76+
await dockerMailserver.updateAccountPassword(email, password);
77+
res.json({ message: 'Password updated successfully', email });
78+
} catch (error) {
79+
res.status(500).json({ error: 'Unable to update password' });
80+
}
81+
});
82+
6383
// Endpoint for retrieving aliases
6484
app.get('/api/aliases', async (req, res) => {
6585
try {

frontend/src/locales/en/translation.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,15 @@
3333
"existingAccounts": "Existing Accounts",
3434
"email": "Email Address",
3535
"password": "Password",
36+
"newPassword": "New Password",
3637
"confirmPassword": "Confirm Password",
3738
"status": "Status",
3839
"storage": "Storage Usage",
3940
"aliases": "Aliases",
4041
"actions": "Actions",
4142
"addAccount": "Add Account",
43+
"changePassword": "Change Password",
44+
"updatePassword": "Update Password",
4245
"noAccounts": "No email accounts. Add your first account.",
4346
"emailRequired": "Email is required",
4447
"invalidEmail": "Invalid email format",
@@ -47,8 +50,10 @@
4750
"passwordsNotMatch": "Passwords do not match",
4851
"accountCreated": "Account created successfully!",
4952
"accountDeleted": "Account deleted successfully!",
53+
"passwordUpdated": "Password updated successfully!",
5054
"cannotCreateAccount": "Unable to create account. Please try again.",
5155
"cannotDeleteAccount": "Unable to delete account. Please try again.",
56+
"cannotUpdatePassword": "Unable to update password. Please try again.",
5257
"confirmDelete": "Are you sure you want to delete account {{email}}?",
5358
"status": {
5459
"active": "Active",
@@ -93,14 +98,16 @@
9398
"common": {
9499
"loading": "Loading...",
95100
"error": "Error",
96-
"success": "Success"
101+
"success": "Success",
102+
"cancel": "Cancel"
97103
},
98104
"api": {
99105
"errors": {
100106
"fetchServerStatus": "Error fetching server status",
101107
"fetchAccounts": "Error fetching accounts",
102108
"addAccount": "Error adding account",
103109
"deleteAccount": "Error deleting account",
110+
"updatePassword": "Error updating password",
104111
"fetchAliases": "Error fetching aliases",
105112
"addAlias": "Error adding alias",
106113
"deleteAlias": "Error deleting alias"

frontend/src/locales/pl/translation.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,15 @@
3333
"existingAccounts": "Istniejące Konta",
3434
"email": "Adres Email",
3535
"password": "Hasło",
36+
"newPassword": "Nowe Hasło",
3637
"confirmPassword": "Potwierdź Hasło",
3738
"status": "Status",
3839
"storage": "Użycie Dysku",
3940
"aliases": "Aliasy",
4041
"actions": "Akcje",
4142
"addAccount": "Dodaj Konto",
43+
"changePassword": "Zmień Hasło",
44+
"updatePassword": "Aktualizuj Hasło",
4245
"noAccounts": "Brak kont email. Dodaj pierwsze konto.",
4346
"emailRequired": "Email jest wymagany",
4447
"invalidEmail": "Nieprawidłowy format email",
@@ -47,8 +50,10 @@
4750
"passwordsNotMatch": "Hasła nie są takie same",
4851
"accountCreated": "Konto zostało pomyślnie utworzone!",
4952
"accountDeleted": "Konto zostało pomyślnie usunięte!",
53+
"passwordUpdated": "Hasło zostało pomyślnie zaktualizowane!",
5054
"cannotCreateAccount": "Nie można utworzyć konta. Spróbuj ponownie.",
5155
"cannotDeleteAccount": "Nie można usunąć konta. Spróbuj ponownie.",
56+
"cannotUpdatePassword": "Nie można zaktualizować hasła. Spróbuj ponownie.",
5257
"confirmDelete": "Czy na pewno chcesz usunąć konto {{email}}?",
5358
"status": {
5459
"active": "Aktywne",
@@ -93,14 +98,16 @@
9398
"common": {
9499
"loading": "Ładowanie...",
95100
"error": "Błąd",
96-
"success": "Sukces"
101+
"success": "Sukces",
102+
"cancel": "Anuluj"
97103
},
98104
"api": {
99105
"errors": {
100106
"fetchServerStatus": "Błąd podczas pobierania statusu serwera",
101107
"fetchAccounts": "Błąd podczas pobierania kont",
102108
"addAccount": "Błąd podczas dodawania konta",
103109
"deleteAccount": "Błąd podczas usuwania konta",
110+
"updatePassword": "Błąd podczas aktualizacji hasła",
104111
"fetchAliases": "Błąd podczas pobierania aliasów",
105112
"addAlias": "Błąd podczas dodawania aliasu",
106113
"deleteAlias": "Błąd podczas usuwania aliasu"

frontend/src/pages/Accounts.js

Lines changed: 162 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useState, useEffect } from 'react';
22
import { useTranslation } from 'react-i18next';
3-
import { getAccounts, addAccount, deleteAccount } from '../services/api';
3+
import { getAccounts, addAccount, deleteAccount, updateAccountPassword } from '../services/api';
44
import {
55
AlertMessage,
66
Button,
@@ -22,6 +22,15 @@ const Accounts = () => {
2222
});
2323
const [formErrors, setFormErrors] = useState({});
2424
const [successMessage, setSuccessMessage] = useState('');
25+
26+
// State for password change modal
27+
const [showPasswordModal, setShowPasswordModal] = useState(false);
28+
const [selectedAccount, setSelectedAccount] = useState(null);
29+
const [passwordFormData, setPasswordFormData] = useState({
30+
newPassword: '',
31+
confirmPassword: ''
32+
});
33+
const [passwordFormErrors, setPasswordFormErrors] = useState({});
2534

2635
useEffect(() => {
2736
fetchAccounts();
@@ -116,6 +125,78 @@ const Accounts = () => {
116125
}
117126
}
118127
};
128+
129+
// Open password change modal for an account
130+
const handleChangePassword = (account) => {
131+
setSelectedAccount(account);
132+
setPasswordFormData({
133+
newPassword: '',
134+
confirmPassword: ''
135+
});
136+
setPasswordFormErrors({});
137+
setShowPasswordModal(true);
138+
};
139+
140+
// Close password change modal
141+
const handleClosePasswordModal = () => {
142+
setShowPasswordModal(false);
143+
setSelectedAccount(null);
144+
};
145+
146+
// Handle input changes for password change form
147+
const handlePasswordInputChange = (e) => {
148+
const { name, value } = e.target;
149+
setPasswordFormData({
150+
...passwordFormData,
151+
[name]: value
152+
});
153+
154+
// Clear the error for this field while typing
155+
if (passwordFormErrors[name]) {
156+
setPasswordFormErrors({
157+
...passwordFormErrors,
158+
[name]: null
159+
});
160+
}
161+
};
162+
163+
// Validate password change form
164+
const validatePasswordForm = () => {
165+
const errors = {};
166+
167+
if (!passwordFormData.newPassword) {
168+
errors.newPassword = 'accounts.passwordRequired';
169+
} else if (passwordFormData.newPassword.length < 8) {
170+
errors.newPassword = 'accounts.passwordLength';
171+
}
172+
173+
if (passwordFormData.newPassword !== passwordFormData.confirmPassword) {
174+
errors.confirmPassword = 'accounts.passwordsNotMatch';
175+
}
176+
177+
setPasswordFormErrors(errors);
178+
return Object.keys(errors).length === 0;
179+
};
180+
181+
// Submit password change
182+
const handleSubmitPasswordChange = async (e) => {
183+
e.preventDefault();
184+
setError(null);
185+
setSuccessMessage('');
186+
187+
if (!validatePasswordForm()) {
188+
return;
189+
}
190+
191+
try {
192+
await updateAccountPassword(selectedAccount.email, passwordFormData.newPassword);
193+
setSuccessMessage('accounts.passwordUpdated');
194+
handleClosePasswordModal(); // Close the modal
195+
} catch (err) {
196+
console.error(t('api.errors.updatePassword'), err);
197+
setError('api.errors.updatePassword');
198+
}
199+
};
119200

120201
// Column definitions for accounts table
121202
const columns = [
@@ -139,12 +220,23 @@ const Accounts = () => {
139220
},
140221
{ key: 'actions', label: 'accounts.actions',
141222
render: (account) => (
142-
<Button
143-
variant="danger"
144-
size="sm"
145-
icon="trash"
146-
onClick={() => handleDelete(account.email)}
147-
/>
223+
<div className="d-flex">
224+
<Button
225+
variant="primary"
226+
size="sm"
227+
icon="key"
228+
title={t('accounts.changePassword')}
229+
onClick={() => handleChangePassword(account)}
230+
className="me-2"
231+
/>
232+
<Button
233+
variant="danger"
234+
size="sm"
235+
icon="trash"
236+
title={t('accounts.confirmDelete', { email: account.email })}
237+
onClick={() => handleDelete(account.email)}
238+
/>
239+
</div>
148240
)
149241
}
150242
];
@@ -215,6 +307,69 @@ const Accounts = () => {
215307
</Card>
216308
</div>
217309
</div>
310+
311+
{/* Password Change Modal */}
312+
{showPasswordModal && selectedAccount && (
313+
<div className="modal show d-block" tabIndex="-1">
314+
<div className="modal-dialog">
315+
<div className="modal-content">
316+
<div className="modal-header">
317+
<h5 className="modal-title">
318+
{t('accounts.changePassword')} - {selectedAccount.email}
319+
</h5>
320+
<button
321+
type="button"
322+
className="btn-close"
323+
onClick={handleClosePasswordModal}
324+
aria-label="Close"
325+
></button>
326+
</div>
327+
<div className="modal-body">
328+
<form onSubmit={handleSubmitPasswordChange}>
329+
<FormField
330+
type="password"
331+
id="newPassword"
332+
name="newPassword"
333+
label="accounts.newPassword"
334+
value={passwordFormData.newPassword}
335+
onChange={handlePasswordInputChange}
336+
error={passwordFormErrors.newPassword}
337+
required
338+
/>
339+
340+
<FormField
341+
type="password"
342+
id="confirmPasswordModal"
343+
name="confirmPassword"
344+
label="accounts.confirmPassword"
345+
value={passwordFormData.confirmPassword}
346+
onChange={handlePasswordInputChange}
347+
error={passwordFormErrors.confirmPassword}
348+
required
349+
/>
350+
351+
<div className="modal-footer">
352+
<button
353+
type="button"
354+
className="btn btn-secondary"
355+
onClick={handleClosePasswordModal}
356+
>
357+
{t('common.cancel')}
358+
</button>
359+
<button
360+
type="submit"
361+
className="btn btn-primary"
362+
>
363+
{t('accounts.updatePassword')}
364+
</button>
365+
</div>
366+
</form>
367+
</div>
368+
</div>
369+
</div>
370+
<div className="modal-backdrop show"></div>
371+
</div>
372+
)}
218373
</div>
219374
);
220375
};

0 commit comments

Comments
 (0)