@feardread/fear 1.1.7 → 1.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -742,7 +742,6 @@ exports.unlinkGoogleAccount = (req, res) => {
742
742
  // Export TokenService and other utilities for use in other modules
743
743
  exports.AuthResponse = response;
744
744
 
745
-
746
745
  module.exports = {
747
746
  login: exports.login,
748
747
  register: exports.register,
@@ -0,0 +1,237 @@
1
+ const crypto = require('crypto');
2
+ const User = require('../../models/user');
3
+ //const { validateMongoDbId } = require('../utils/validateMongoDbId');
4
+
5
+ // Response helper
6
+ const response = {
7
+ success: (res, data, statusCode = 200, message = "Operation successful") => {
8
+ return res.status(statusCode).json({
9
+ success: true,
10
+ message,
11
+ data
12
+ });
13
+ },
14
+
15
+ error: (res, statusCode, message, error = null) => {
16
+ const responseObj = { success: false, message };
17
+ if (error && process.env.NODE_ENV === "development") {
18
+ responseObj.error = error;
19
+ }
20
+ return res.status(statusCode).json(responseObj);
21
+ }
22
+ };
23
+
24
+ // Update password for authenticated user
25
+ exports.updatePassword = (req, res) => {
26
+ const { _id } = req.user;
27
+ const { currentPassword, newPassword } = req.body;
28
+
29
+ //validateMongoDbId(_id);
30
+
31
+ if (!currentPassword || !newPassword) {
32
+ return response.error(res, 400, 'Current password and new password are required');
33
+ }
34
+
35
+ User.findById(_id)
36
+ .then((user) => {
37
+ if (!user) {
38
+ return response.error(res, 404, 'User not found');
39
+ }
40
+
41
+ // Verify current password
42
+ return user.isPasswordMatched(currentPassword)
43
+ .then((isMatch) => {
44
+ if (!isMatch) {
45
+ return response.error(res, 401, 'Current password is incorrect');
46
+ }
47
+
48
+ user.password = newPassword;
49
+ return user.save();
50
+ })
51
+ .then((updatedUser) => {
52
+ // Send confirmation email
53
+ const mailService = req.app.get('mailService');
54
+
55
+ return mailService.sendEmail({
56
+ to: updatedUser.email,
57
+ subject: 'Password Changed Successfully',
58
+ html: mailService.templates.passwordResetSuccessTemplate(updatedUser.email),
59
+ text: mailService.templates.generatePlainText({ email: updatedUser.email }, 'passwordResetSuccess')
60
+ })
61
+ .then(() => {
62
+ const userResponse = updatedUser.toJSON();
63
+ return response.success(res, { user: userResponse }, 200, 'Password updated successfully');
64
+ })
65
+ .catch((emailError) => {
66
+ // Log error but don't fail the password update
67
+ console.error('Failed to send confirmation email:', emailError);
68
+ const userResponse = updatedUser.toJSON();
69
+ return response.success(res, { user: userResponse }, 200, 'Password updated successfully');
70
+ });
71
+ });
72
+ })
73
+ .catch((error) => {
74
+ return response.error(res, 500, 'Error updating password', error.message);
75
+ });
76
+ };
77
+
78
+ // Request password reset token
79
+ exports.forgotPasswordToken = (req, res) => {
80
+ const { email } = req.body;
81
+
82
+ if (!email) {
83
+ return response.error(res, 400, 'Email is required');
84
+ }
85
+
86
+ User.findOne({ email })
87
+ .then((user) => {
88
+ if (!user) {
89
+ return response.error(res, 404, 'User not found with this email');
90
+ }
91
+
92
+ // Create reset token
93
+ return user.createPasswordResetToken()
94
+ .then((token) => {
95
+ return user.save()
96
+ .then(() => ({ user, token }));
97
+ });
98
+ })
99
+ .then(({ user, token }) => {
100
+ // Construct reset URL
101
+ const resetURL = `${req.protocol}://${req.get('host')}/reset-password/${token}`;
102
+
103
+ // Send email
104
+ const mailService = req.app.get('mailService');
105
+
106
+ return mailService.sendEmail({
107
+ to: user.email,
108
+ subject: 'Password Reset Request',
109
+ html: mailService.templates.passwordResetTemplate(resetURL, user.email),
110
+ text: mailService.templates.generatePlainText({ resetURL, email: user.email }, 'passwordReset')
111
+ })
112
+ .then(() => {
113
+ return response.success(
114
+ res,
115
+ {
116
+ message: 'Password reset link sent to email',
117
+ expiresIn: '10 minutes'
118
+ },
119
+ 200,
120
+ 'Password reset email sent successfully'
121
+ );
122
+ })
123
+ .catch((emailError) => {
124
+ // Clear reset token if email fails
125
+ user.passwordResetToken = undefined;
126
+ user.passwordResetExpires = undefined;
127
+ return user.save()
128
+ .then(() => {
129
+ return response.error(res, 500, 'Error sending password reset email', emailError.message);
130
+ });
131
+ });
132
+ })
133
+ .catch((error) => {
134
+ return response.error(res, 500, 'Error processing password reset request', error.message);
135
+ });
136
+ };
137
+
138
+ // Reset password with token
139
+ exports.resetPassword = (req, res) => {
140
+ const { password } = req.body;
141
+ const { token } = req.params;
142
+
143
+ if (!password) {
144
+ return response.error(res, 400, 'New password is required');
145
+ }
146
+
147
+ if (!token) {
148
+ return response.error(res, 400, 'Reset token is required');
149
+ }
150
+
151
+ const hashedToken = crypto.createHash("sha256").update(token).digest("hex");
152
+
153
+ User.findOne({
154
+ passwordResetToken: hashedToken,
155
+ passwordResetExpires: { $gt: Date.now() }
156
+ })
157
+ .then((user) => {
158
+ if (!user) {
159
+ return response.error(res, 400, 'Token expired or invalid. Please try again');
160
+ }
161
+
162
+ // Update password and clear reset token
163
+ user.password = password;
164
+ user.passwordResetToken = undefined;
165
+ user.passwordResetExpires = undefined;
166
+
167
+ return user.save();
168
+ })
169
+ .then((updatedUser) => {
170
+ // Send confirmation email
171
+ const mailService = req.app.get('mailService');
172
+
173
+ return mailService.sendEmail({
174
+ to: updatedUser.email,
175
+ subject: 'Password Changed Successfully',
176
+ html: mailService.templates.passwordResetSuccessTemplate(updatedUser.email),
177
+ text: mailService.templates.generatePlainText({ email: updatedUser.email }, 'passwordResetSuccess')
178
+ })
179
+ .then(() => {
180
+ const userResponse = updatedUser.toJSON();
181
+ return response.success(
182
+ res,
183
+ { user: userResponse },
184
+ 200,
185
+ 'Password reset successfully'
186
+ );
187
+ })
188
+ .catch((emailError) => {
189
+ // Log error but don't fail the password reset
190
+ console.error('Failed to send confirmation email:', emailError);
191
+ const userResponse = updatedUser.toJSON();
192
+ return response.success(
193
+ res,
194
+ { user: userResponse },
195
+ 200,
196
+ 'Password reset successfully'
197
+ );
198
+ });
199
+ })
200
+ .catch((error) => {
201
+ return response.error(res, 500, 'Error resetting password', error.message);
202
+ });
203
+ };
204
+
205
+ // Verify reset token validity (optional)
206
+ exports.verifyResetToken = (req, res) => {
207
+ const { token } = req.params;
208
+
209
+ if (!token) {
210
+ return response.error(res, 400, 'Reset token is required');
211
+ }
212
+
213
+ const hashedToken = crypto.createHash("sha256").update(token).digest("hex");
214
+
215
+ User.findOne({
216
+ passwordResetToken: hashedToken,
217
+ passwordResetExpires: { $gt: Date.now() }
218
+ })
219
+ .then((user) => {
220
+ if (!user) {
221
+ return response.error(res, 400, 'Token expired or invalid');
222
+ }
223
+
224
+ return response.success(
225
+ res,
226
+ {
227
+ valid: true,
228
+ email: user.email.replace(/(.{2})(.*)(@.*)/, '$1***$3') // Partially masked email
229
+ },
230
+ 200,
231
+ 'Token is valid'
232
+ );
233
+ })
234
+ .catch((error) => {
235
+ return response.error(res, 500, 'Error verifying token', error.message);
236
+ });
237
+ };
@@ -207,7 +207,7 @@ module.exports = function (fear) {
207
207
  padding: 0;
208
208
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
209
209
  line-height: 1.6;
210
- color: #333;
210
+ color: #242424;
211
211
  background-color: #f4f4f4;
212
212
  }
213
213
  .container {
@@ -219,7 +219,7 @@ module.exports = function (fear) {
219
219
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
220
220
  }
221
221
  .header {
222
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
222
+ background: linear-gradient(135deg, #ea6666 0%, #410182 100%);
223
223
  color: white;
224
224
  padding: 30px;
225
225
  text-align: center;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feardread/fear",
3
- "version": "1.1.7",
3
+ "version": "1.1.8",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/routes/auth.js CHANGED
@@ -7,9 +7,9 @@ module.exports = (fear) => {
7
7
  const validator = fear.getValidator();
8
8
  //const passport = fear.getPassport();
9
9
 
10
- router.post("/login", handler.async(Auth.login))
11
- router.post("/logout", handler.async(Auth.logout))
12
- router.post("/register", handler.async(Auth.register))
10
+ router.post("/login", handler.async(Auth.login));
11
+ router.post("/logout", handler.async(Auth.logout));
12
+ router.post("/register", handler.async(Auth.register));
13
13
 
14
14
  router.post('/google', handler.async(Auth.googleAuth));
15
15
  router.post('/google/link',handler.async(Auth.linkGoogleAccount));
@@ -0,0 +1,17 @@
1
+ const Password = require("../controllers/auth/password");
2
+
3
+ module.exports = (fear) => {
4
+ const router = fear.createRouter();
5
+ const handler = fear.getHandler();
6
+
7
+ // Forgot password - sends reset email
8
+ router.post('/forgot-password', Password.forgotPasswordToken);
9
+ // Reset password with token
10
+ router.post('/reset-password/:token', Password.resetPassword);
11
+ // Update password (requires authentication) TODO: Add auth middleware
12
+ router.put('/update-password', Password.updatePassword);
13
+ // Verify reset token validity (optional - for frontend validation)
14
+ router.get('/verify-reset-token/:token', Password.verifyResetToken);
15
+
16
+ return router;
17
+ };