Skip to content

Commit 60fa4f2

Browse files
committed
feat: add forgot password and reset password functionality with corresponding routes and localization
1 parent 4cd1fce commit 60fa4f2

File tree

8 files changed

+658
-3
lines changed

8 files changed

+658
-3
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// @/i18n/locales/en/forgotPassword.ts
2+
export default {
3+
forgotPassword: {
4+
title: 'Reset Password',
5+
subtitle: 'Enter your email address and we\'ll send you a link to reset your password.',
6+
form: {
7+
email: {
8+
label: 'Email Address',
9+
placeholder: 'Enter your email address'
10+
}
11+
},
12+
buttons: {
13+
submit: 'Send Reset Link',
14+
loading: 'Sending...',
15+
backToLogin: 'Back to Login'
16+
},
17+
success: {
18+
title: 'Check Your Email',
19+
message: 'If an account with that email exists, we\'ve sent you a password reset link.',
20+
instruction: 'Please check your email and follow the instructions to reset your password.'
21+
},
22+
errors: {
23+
title: 'Error',
24+
networkError: 'Network error. Please check your connection and try again.',
25+
serverError: 'Server error. Please try again later.',
26+
invalidEmail: 'Please enter a valid email address.',
27+
serviceUnavailable: 'Password reset service is currently unavailable.',
28+
unknownError: 'An unexpected error occurred. Please try again.'
29+
}
30+
}
31+
}

services/frontend/src/i18n/locales/en/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import notFoundMessages from './notFound'
88
import adminUsersMessages from './adminUsers'
99
import sidebarMessages from './sidebar'
1010
import verifyEmailMessages from './verifyEmail'
11+
import forgotPasswordMessages from './forgotPassword'
12+
import resetPasswordMessages from './resetPassword'
1113

1214
export default {
1315
...commonMessages,
@@ -19,6 +21,8 @@ export default {
1921
...adminUsersMessages,
2022
...sidebarMessages,
2123
...verifyEmailMessages,
24+
...forgotPasswordMessages,
25+
...resetPasswordMessages,
2226
// If there are any top-level keys directly under 'en', they can be added here.
2327
// For example, if you had a global 'appName': 'My Application'
2428
// appName: 'DeployStack Application',
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// @/i18n/locales/en/resetPassword.ts
2+
export default {
3+
resetPassword: {
4+
title: 'Set New Password',
5+
subtitle: 'Enter your new password below.',
6+
form: {
7+
password: {
8+
label: 'New Password',
9+
placeholder: 'Enter your new password'
10+
},
11+
confirmPassword: {
12+
label: 'Confirm Password',
13+
placeholder: 'Confirm your new password'
14+
}
15+
},
16+
buttons: {
17+
submit: 'Reset Password',
18+
loading: 'Resetting...',
19+
backToLogin: 'Back to Login'
20+
},
21+
success: {
22+
title: 'Password Reset Successful',
23+
message: 'Your password has been successfully reset.',
24+
instruction: 'You can now log in with your new password.'
25+
},
26+
errors: {
27+
title: 'Error',
28+
invalidToken: 'This password reset link is invalid or has expired.',
29+
expiredToken: 'This password reset link has expired. Please request a new one.',
30+
weakPassword: 'Password must be at least 8 characters long.',
31+
passwordMismatch: 'Passwords do not match.',
32+
networkError: 'Network error. Please check your connection and try again.',
33+
serverError: 'Server error. Please try again later.',
34+
serviceUnavailable: 'Password reset service is currently unavailable.',
35+
unknownError: 'An unexpected error occurred. Please try again.'
36+
}
37+
}
38+
}

services/frontend/src/router/index.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ const routes = [
3131
component: () => import('../views/VerifyEmail.vue'),
3232
meta: { requiresSetup: true },
3333
},
34+
{
35+
path: '/forgot-password',
36+
name: 'ForgotPassword',
37+
component: () => import('../views/ForgotPassword.vue'),
38+
meta: { requiresSetup: true },
39+
},
40+
{
41+
path: '/reset-password',
42+
name: 'ResetPassword',
43+
component: () => import('../views/ResetPassword.vue'),
44+
meta: { requiresSetup: true },
45+
},
3446
{
3547
path: '/logout',
3648
name: 'Logout',
@@ -113,7 +125,7 @@ router.beforeEach(async (to, from, next) => {
113125
const databaseStore = useDatabaseStore()
114126

115127
// Define public routes that don't need user authentication checks
116-
const publicRoutes = ['Setup', 'Login', 'Register', 'VerifyEmail']
128+
const publicRoutes = ['Setup', 'Login', 'Register', 'VerifyEmail', 'ForgotPassword', 'ResetPassword']
117129
const isPublicRoute = publicRoutes.includes(to.name as string)
118130

119131
// Attempt to get current user status early

services/frontend/src/services/userService.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,4 +174,76 @@ export class UserService {
174174
this.clearCache();
175175
}
176176
}
177+
178+
/**
179+
* Request password reset for email users
180+
*/
181+
static async requestPasswordReset(email: string): Promise<{ success: boolean; message: string }> {
182+
try {
183+
const apiUrl = this.getApiUrl();
184+
const response = await fetch(`${apiUrl}/api/auth/email/forgot-password`, {
185+
method: 'POST',
186+
headers: {
187+
'Content-Type': 'application/json',
188+
},
189+
credentials: 'include',
190+
body: JSON.stringify({ email }),
191+
});
192+
193+
const data = await response.json();
194+
195+
if (response.ok) {
196+
return data;
197+
}
198+
199+
// Handle different error status codes
200+
if (response.status === 503) {
201+
throw new Error('SERVICE_UNAVAILABLE');
202+
}
203+
204+
throw new Error(`Password reset request failed with status: ${response.status}`);
205+
} catch (error) {
206+
console.error('Password reset request error:', error);
207+
throw error;
208+
}
209+
}
210+
211+
/**
212+
* Reset password using token
213+
*/
214+
static async resetPassword(token: string, newPassword: string): Promise<{ success: boolean; message: string }> {
215+
try {
216+
const apiUrl = this.getApiUrl();
217+
const response = await fetch(`${apiUrl}/api/auth/email/reset-password`, {
218+
method: 'POST',
219+
headers: {
220+
'Content-Type': 'application/json',
221+
},
222+
credentials: 'include',
223+
body: JSON.stringify({ token, new_password: newPassword }),
224+
});
225+
226+
const data = await response.json();
227+
228+
if (response.ok) {
229+
return data;
230+
}
231+
232+
// Handle different error status codes
233+
if (response.status === 400) {
234+
throw new Error('INVALID_TOKEN');
235+
}
236+
if (response.status === 403) {
237+
throw new Error('FORBIDDEN');
238+
}
239+
if (response.status === 503) {
240+
throw new Error('SERVICE_UNAVAILABLE');
241+
}
242+
243+
throw new Error(`Password reset failed with status: ${response.status}`);
244+
} catch (error) {
245+
console.error('Password reset error:', error);
246+
throw error;
247+
}
248+
}
177249
}

0 commit comments

Comments
 (0)