@feardread/fear 1.1.6 → 1.1.7
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.
- package/FEAR.js +76 -63
- package/FEARServer.js +3 -4
- package/controllers/auth/index.js +239 -3
- package/controllers/payment.js +5 -185
- package/libs/emailer/info.js +22 -34
- package/libs/emailer/smtp.js +511 -65
- package/libs/passport/index.js +137 -0
- package/libs/passport.js +22 -0
- package/libs/paypal/index.js +82 -0
- package/libs/stripe/index.js +306 -0
- package/models/address.js +1 -1
- package/models/order.js +8 -11
- package/models/payment.js +2 -4
- package/package.json +1 -1
- package/routes/address.js +1 -0
- package/routes/auth.js +6 -0
- package/routes/mail.js +10 -165
- package/routes/order.js +3 -2
- package/routes/payment.js +1 -5
- package/routes/paypal.js +12 -0
- package/routes/stripe.js +27 -0
- package/libs/passport/passport.js +0 -109
package/FEAR.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
2
|
const fs = require("fs");
|
|
3
3
|
const express = require("express");
|
|
4
|
+
const session = require('express-session');
|
|
4
5
|
const compression = require("compression");
|
|
5
6
|
const cookieParser = require("cookie-parser");
|
|
6
7
|
const fileUpload = require("express-fileupload");
|
|
7
8
|
const cors = require("cors");
|
|
9
|
+
const MongoStore = require('connect-mongo');
|
|
10
|
+
const passport = require('passport');
|
|
8
11
|
|
|
9
12
|
module.exports = FEAR = (() => {
|
|
10
13
|
// Private constants
|
|
@@ -14,7 +17,7 @@ module.exports = FEAR = (() => {
|
|
|
14
17
|
const AGENT_ROUTE_PATH = '/fear/api/agent';
|
|
15
18
|
|
|
16
19
|
// Constructor function
|
|
17
|
-
const FEAR = function() {
|
|
20
|
+
const FEAR = function (config) {
|
|
18
21
|
this.app = express();
|
|
19
22
|
this.Router = express.Router;
|
|
20
23
|
this.server = null;
|
|
@@ -29,100 +32,67 @@ module.exports = FEAR = (() => {
|
|
|
29
32
|
this.logo = null;
|
|
30
33
|
this.origins = [];
|
|
31
34
|
this.corsConfig = null;
|
|
35
|
+
this.stripe = null;
|
|
36
|
+
this.paypal = null;
|
|
37
|
+
this.passport = null;
|
|
38
|
+
this.mailer = null;
|
|
39
|
+
|
|
32
40
|
|
|
33
|
-
// Initialize
|
|
34
41
|
this.setupEnvironment();
|
|
35
42
|
this.setupDependencies();
|
|
43
|
+
if (this.env.ADD_PAYMENTS) this.setupProcessors();
|
|
44
|
+
this.setupMailer();
|
|
36
45
|
this.setupMiddleware();
|
|
37
46
|
this.corsConfig = this.getCorsConfig();
|
|
38
47
|
this.setupRoutes();
|
|
39
48
|
};
|
|
40
49
|
|
|
41
|
-
|
|
50
|
+
|
|
42
51
|
FEAR.prototype = {
|
|
43
52
|
constructor: FEAR,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return this.agentService;
|
|
53
|
+
getStripe() {
|
|
54
|
+
return this.stripe;
|
|
55
|
+
},
|
|
56
|
+
getPassport() {
|
|
57
|
+
return this.passport;
|
|
50
58
|
},
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Clear global FearRouter
|
|
54
|
-
*/
|
|
55
59
|
clearGlobal() {
|
|
56
60
|
delete global.FearRouter;
|
|
57
61
|
return this;
|
|
58
62
|
},
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Get Express app instance
|
|
62
|
-
*/
|
|
63
63
|
getApp() {
|
|
64
64
|
return this.app;
|
|
65
65
|
},
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Get logger instance
|
|
69
|
-
*/
|
|
70
66
|
getLogger() {
|
|
71
67
|
return this.logger;
|
|
72
68
|
},
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Get database instance
|
|
76
|
-
*/
|
|
77
69
|
getDatabase() {
|
|
78
70
|
return this.db;
|
|
79
71
|
},
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Get environment configuration
|
|
83
|
-
*/
|
|
84
72
|
getEnvironment() {
|
|
85
73
|
return this.env;
|
|
86
74
|
},
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Get cloud service instance
|
|
90
|
-
*/
|
|
91
75
|
getCloud() {
|
|
92
76
|
return this.cloud;
|
|
93
77
|
},
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Get Express Router
|
|
97
|
-
*/
|
|
98
78
|
getRouter() {
|
|
99
79
|
return this.Router;
|
|
100
80
|
},
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Get validator instance
|
|
104
|
-
*/
|
|
105
81
|
getValidator() {
|
|
106
82
|
return this.validator;
|
|
107
83
|
},
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Get handler instance
|
|
111
|
-
*/
|
|
112
84
|
getHandler() {
|
|
113
85
|
return this.handler;
|
|
114
86
|
},
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
87
|
+
getPaypal() {
|
|
88
|
+
return this.paypal;
|
|
89
|
+
},
|
|
90
|
+
getMailer() {
|
|
91
|
+
return this.mailer;
|
|
92
|
+
},
|
|
119
93
|
getCorsConfigValue() {
|
|
120
94
|
return this.corsConfig;
|
|
121
95
|
},
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Setup environment variables from .env file
|
|
125
|
-
*/
|
|
126
96
|
setupEnvironment() {
|
|
127
97
|
const envResult = require("dotenv").config({ path: ".env" });
|
|
128
98
|
|
|
@@ -138,6 +108,7 @@ module.exports = FEAR = (() => {
|
|
|
138
108
|
*/
|
|
139
109
|
setupDependencies() {
|
|
140
110
|
this.logger = require("./libs/logger");
|
|
111
|
+
this.passport = require('./libs/passport');
|
|
141
112
|
this.morgan = require("./libs/logger/morgan");
|
|
142
113
|
this.cloud = require("./libs/cloud");
|
|
143
114
|
this.db = require("./libs/db");
|
|
@@ -147,26 +118,66 @@ module.exports = FEAR = (() => {
|
|
|
147
118
|
this.origins = this.getAllowedOrigins();
|
|
148
119
|
},
|
|
149
120
|
|
|
121
|
+
setupProcessors() {
|
|
122
|
+
const StripeHandler = require("./libs/stripe");
|
|
123
|
+
const PayPal = require('./libs/paypal');
|
|
124
|
+
|
|
125
|
+
if ( !this.env.PAYPAL_CLIENT_ID|| !this.env.STRIPE_API_KEY ) {
|
|
126
|
+
this.logger.warn('Missing environment values in .env file')
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this.stripe = new StripeHandler(this);
|
|
130
|
+
this.paypal = new PayPal(this);
|
|
131
|
+
},
|
|
132
|
+
|
|
150
133
|
/**
|
|
151
134
|
* Setup Express middleware
|
|
152
135
|
*/
|
|
153
136
|
setupMiddleware() {
|
|
154
137
|
this.app.set("PORT", this.env.NODE_PORT || DEFAULT_PORT);
|
|
155
|
-
|
|
156
|
-
this.app.use(this.morgan);
|
|
157
138
|
this.app.use(express.json({ limit: DEFAULT_JSON_LIMIT }));
|
|
139
|
+
this.app.use(express.urlencoded({ extended: true }));
|
|
140
|
+
this.app.use(session({
|
|
141
|
+
secret: process.env.SESSION_SECRET,
|
|
142
|
+
resave: false,
|
|
143
|
+
saveUninitialized: false,
|
|
144
|
+
//store: MongoStore.create({
|
|
145
|
+
//mongoUrl: process.env.DB_LINK,
|
|
146
|
+
//touchAfter: 24 * 3600 // lazy session update (24 hours)
|
|
147
|
+
//}),
|
|
148
|
+
cookie: {
|
|
149
|
+
maxAge: 1000 * 60 * 60 * 24 * 7, // 1 week
|
|
150
|
+
httpOnly: true,
|
|
151
|
+
secure: process.env.NODE_ENV === 'production' // HTTPS only in production
|
|
152
|
+
}
|
|
153
|
+
}));
|
|
154
|
+
this.app.use(this.morgan);
|
|
158
155
|
this.app.use(compression());
|
|
159
156
|
this.app.use(fileUpload());
|
|
160
157
|
this.app.use(cookieParser());
|
|
158
|
+
this.app.use(passport.initialize());
|
|
159
|
+
this.app.use(passport.session());
|
|
161
160
|
|
|
162
|
-
|
|
161
|
+
require('./libs/passport');
|
|
163
162
|
this.app.use((req, res, next) => {
|
|
164
163
|
this.logger.info(`FEAR API Query :: ${req.url}`);
|
|
165
|
-
res.locals.user = req.user;
|
|
164
|
+
res.locals.user = req.user || null;
|
|
166
165
|
next();
|
|
167
166
|
});
|
|
168
167
|
},
|
|
169
168
|
|
|
169
|
+
setupMailer() {
|
|
170
|
+
const mailService = (this.env.NODE_ENV === 'production') ? 'mailgun' : 'google';
|
|
171
|
+
const mailinfo = require('./libs/emailer/info');
|
|
172
|
+
const smtp = require('./libs/emailer/smtp')
|
|
173
|
+
|
|
174
|
+
mailinfo.service = mailService;
|
|
175
|
+
this.mailinfo = mailinfo;
|
|
176
|
+
|
|
177
|
+
if ( !this.mailer ) {
|
|
178
|
+
this.mailer = new smtp(this);
|
|
179
|
+
}
|
|
180
|
+
},
|
|
170
181
|
/**
|
|
171
182
|
* Parse allowed origins from environment
|
|
172
183
|
*/
|
|
@@ -287,6 +298,8 @@ module.exports = FEAR = (() => {
|
|
|
287
298
|
router.getHandler = () => this.handler;
|
|
288
299
|
router.getValidator = () => this.validator;
|
|
289
300
|
router.getAiAgent = () => this.agentService;
|
|
301
|
+
router.getStripe = () => this.stripe;
|
|
302
|
+
router.getPaypal = () => this.paypal;
|
|
290
303
|
//router.getAgentWebInterface = () => this.agentWebInterface;
|
|
291
304
|
|
|
292
305
|
return router;
|
|
@@ -314,14 +327,14 @@ module.exports = FEAR = (() => {
|
|
|
314
327
|
this.logger.error(`Failed to start server on port ${serverPort}:`, err);
|
|
315
328
|
return reject(err);
|
|
316
329
|
}
|
|
317
|
-
|
|
330
|
+
|
|
318
331
|
this.logger.info(`FEAR server started on port ${serverPort}`);
|
|
319
|
-
|
|
332
|
+
|
|
320
333
|
// Log agent interface status
|
|
321
334
|
if (this.agentWebInterface) {
|
|
322
335
|
this.logger.info(`Agent Web Interface available at ${AGENT_ROUTE_PATH}`);
|
|
323
336
|
}
|
|
324
|
-
|
|
337
|
+
|
|
325
338
|
resolve(this.server);
|
|
326
339
|
});
|
|
327
340
|
});
|
|
@@ -347,7 +360,7 @@ module.exports = FEAR = (() => {
|
|
|
347
360
|
}
|
|
348
361
|
|
|
349
362
|
// Shutdown agent web interface
|
|
350
|
-
const agentShutdown = this.agentWebInterface &&
|
|
363
|
+
const agentShutdown = this.agentWebInterface &&
|
|
351
364
|
typeof this.agentWebInterface.shutdown === 'function' ?
|
|
352
365
|
this.agentWebInterface.shutdown() :
|
|
353
366
|
Promise.resolve();
|
|
@@ -397,8 +410,8 @@ module.exports = FEAR = (() => {
|
|
|
397
410
|
getDatabase: () => this.db,
|
|
398
411
|
getEnvironment: () => this.env,
|
|
399
412
|
getCloud: () => this.cloud,
|
|
400
|
-
|
|
401
|
-
|
|
413
|
+
getStripe: () => this.stripe,
|
|
414
|
+
initProcessors: this.setupProcessors
|
|
402
415
|
};
|
|
403
416
|
return this;
|
|
404
417
|
}
|
package/FEARServer.js
CHANGED
|
@@ -8,7 +8,7 @@ const FearServer = (function () {
|
|
|
8
8
|
// Private constants
|
|
9
9
|
const DEFAULT_PATHS = {
|
|
10
10
|
root: path.resolve(),
|
|
11
|
-
app: '/backend/dashboard/build',
|
|
11
|
+
app: '/backend/dashboard/build',
|
|
12
12
|
build: 'backend/dashboard/build'
|
|
13
13
|
};
|
|
14
14
|
|
|
@@ -184,11 +184,11 @@ const FearServer = (function () {
|
|
|
184
184
|
/**
|
|
185
185
|
* Initialize FEAR application
|
|
186
186
|
*/
|
|
187
|
-
initialize(paths = DEFAULT_PATHS) {
|
|
187
|
+
initialize(paths = DEFAULT_PATHS, ADD_PAYMENTS = false) {
|
|
188
188
|
try {
|
|
189
189
|
// Import FEAR after dotenv is configured
|
|
190
190
|
const FearFactory = require("./FEAR");
|
|
191
|
-
this.fear = new FearFactory();
|
|
191
|
+
this.fear = new FearFactory({ADD_PAYMENTS});
|
|
192
192
|
this.Router = this.fear.Router;
|
|
193
193
|
|
|
194
194
|
this.setupStaticFiles(paths.root, paths.app, paths.build, paths.basePath);
|
|
@@ -212,7 +212,6 @@ const FearServer = (function () {
|
|
|
212
212
|
if (this.fear.logo) {
|
|
213
213
|
logger.warn(this.fear.logo);
|
|
214
214
|
}
|
|
215
|
-
|
|
216
215
|
// Initialize database connection
|
|
217
216
|
return this.initializeDatabase()
|
|
218
217
|
.then(() => {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const User = require("../../models/user");
|
|
2
2
|
const TokenService = require('./token');
|
|
3
|
-
const handler = require('../../libs/handler');
|
|
4
3
|
const validator = require('../../libs/validator');
|
|
5
4
|
const logger = require('../../libs/logger');
|
|
6
5
|
|
|
@@ -361,7 +360,7 @@ exports.isAuthorized = (req, res, next) => {
|
|
|
361
360
|
let token;
|
|
362
361
|
|
|
363
362
|
// Check for token in cookies first, then Authorization header
|
|
364
|
-
if (req.cookies?.jwt) {
|
|
363
|
+
if (req && req.cookies?.jwt) {
|
|
365
364
|
token = req.cookies.jwt;
|
|
366
365
|
} else if (req.headers.authorization?.startsWith('Bearer ')) {
|
|
367
366
|
token = req.headers.authorization.split(' ')[1];
|
|
@@ -505,12 +504,249 @@ exports.optionalAuth = (req, res, next) => {
|
|
|
505
504
|
next();
|
|
506
505
|
}
|
|
507
506
|
};
|
|
507
|
+
/**
|
|
508
|
+
* POST /fear/api/auth/google
|
|
509
|
+
* @summary Authenticate or register user via Google OAuth
|
|
510
|
+
* @description Handles Google OAuth login/registration. Creates new user if doesn't exist, or logs in existing user
|
|
511
|
+
* @tags authentication, oauth
|
|
512
|
+
*/
|
|
513
|
+
exports.googleAuth = (req, res) => {
|
|
514
|
+
const { googleToken, googleId, email, firstName, lastName, avatar } = req.body;
|
|
515
|
+
|
|
516
|
+
// Validate required fields
|
|
517
|
+
if (!email || !googleId) {
|
|
518
|
+
return response.error(res, 400, "Google ID and email are required");
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Validate email format (assuming validator has email method)
|
|
522
|
+
const emailValidation = validator.input.email ? validator.input.email(email) : { isValid: true };
|
|
523
|
+
if (!emailValidation.isValid) {
|
|
524
|
+
return response.error(res, 400, "Invalid email format");
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
logger.info('Google authentication attempt for:', email);
|
|
528
|
+
|
|
529
|
+
// Find user by email or googleId
|
|
530
|
+
User.findOne({
|
|
531
|
+
$or: [
|
|
532
|
+
{ email: email.toLowerCase() },
|
|
533
|
+
{ 'oauth.google.id': googleId }
|
|
534
|
+
],
|
|
535
|
+
status: { $ne: 'deleted' }
|
|
536
|
+
})
|
|
537
|
+
.then(user => {
|
|
538
|
+
// If user exists - login flow
|
|
539
|
+
if (user) {
|
|
540
|
+
// Check account status
|
|
541
|
+
if (user.status === 'suspended') {
|
|
542
|
+
return response.error(res, 403, "Your account has been suspended. Please contact support.");
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (user.status === 'inactive') {
|
|
546
|
+
return response.error(res, 403, "Your account is inactive. Please contact support to reactivate.");
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Update Google OAuth info if not already set
|
|
550
|
+
if (!user.oauth || !user.oauth.google || !user.oauth.google.id) {
|
|
551
|
+
user.oauth = user.oauth || {};
|
|
552
|
+
user.oauth.google = {
|
|
553
|
+
id: googleId,
|
|
554
|
+
email: email.toLowerCase(),
|
|
555
|
+
connectedAt: new Date()
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Update last login info
|
|
560
|
+
user.lastLoginAt = new Date();
|
|
561
|
+
user.lastLoginIP = req.ip || req.connection.remoteAddress;
|
|
562
|
+
|
|
563
|
+
// Update avatar if provided and user doesn't have one
|
|
564
|
+
if (avatar && !user.avatar) {
|
|
565
|
+
user.avatar = avatar;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return user.save()
|
|
569
|
+
.then(savedUser => {
|
|
570
|
+
logger.info('Google login successful for:', savedUser.email);
|
|
571
|
+
|
|
572
|
+
// Generate token and send response
|
|
573
|
+
const token = TokenService.generateToken(savedUser);
|
|
574
|
+
return response.success(res, savedUser, token, 200, "Google login successful");
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// User doesn't exist - registration flow
|
|
579
|
+
const userData = {
|
|
580
|
+
email: email.toLowerCase(),
|
|
581
|
+
firstName: firstName?.trim() || 'User',
|
|
582
|
+
lastName: lastName?.trim() || '',
|
|
583
|
+
displayName: firstName && lastName ? `${firstName} ${lastName}`.trim() : email.split('@')[0],
|
|
584
|
+
avatar: avatar || undefined,
|
|
585
|
+
role: 'user',
|
|
586
|
+
status: 'active',
|
|
587
|
+
oauth: {
|
|
588
|
+
google: {
|
|
589
|
+
id: googleId,
|
|
590
|
+
email: email.toLowerCase(),
|
|
591
|
+
connectedAt: new Date()
|
|
592
|
+
}
|
|
593
|
+
},
|
|
594
|
+
isEmailVerified: true, // Google emails are already verified
|
|
595
|
+
lastLoginAt: new Date(),
|
|
596
|
+
lastLoginIP: req.ip || req.connection.remoteAddress
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
// Create new user (no password required for OAuth users)
|
|
600
|
+
return User.create(userData)
|
|
601
|
+
.then(newUser => {
|
|
602
|
+
logger.info('New user registered via Google:', newUser.email);
|
|
603
|
+
|
|
604
|
+
// Generate token and send response
|
|
605
|
+
const token = TokenService.generateToken(newUser);
|
|
606
|
+
return response.success(res, newUser, token, 201, "Google registration successful");
|
|
607
|
+
});
|
|
608
|
+
})
|
|
609
|
+
.catch(error => {
|
|
610
|
+
logger.error('Google authentication error:', error);
|
|
611
|
+
|
|
612
|
+
// Handle duplicate key error
|
|
613
|
+
if (error.code === 11000) {
|
|
614
|
+
return response.error(res, 409, "User with this email already exists");
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Handle validation errors
|
|
618
|
+
if (error.name === 'ValidationError') {
|
|
619
|
+
const messages = Object.values(error.errors).map(err => err.message);
|
|
620
|
+
return response.error(res, 400, messages.join(', '));
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
return response.error(res, 500, "Google authentication failed", error.message);
|
|
624
|
+
});
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* POST /fear/api/auth/google/link
|
|
629
|
+
* @summary Link Google account to existing authenticated user
|
|
630
|
+
* @description Connects a Google OAuth account to the currently logged-in user
|
|
631
|
+
* @tags authentication, oauth
|
|
632
|
+
*/
|
|
633
|
+
exports.linkGoogleAccount = (req, res) => {
|
|
634
|
+
const { googleId, email } = req.body;
|
|
635
|
+
const userId = req.user._id; // Set by isAuthorized middleware
|
|
636
|
+
|
|
637
|
+
if (!googleId || !email) {
|
|
638
|
+
return response.error(res, 400, "Google ID and email are required");
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Check if Google account is already linked to another user
|
|
642
|
+
User.findOne({
|
|
643
|
+
'oauth.google.id': googleId,
|
|
644
|
+
_id: { $ne: userId }
|
|
645
|
+
})
|
|
646
|
+
.then(existingUser => {
|
|
647
|
+
if (existingUser) {
|
|
648
|
+
return response.error(res, 409, "This Google account is already linked to another user");
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Get current user
|
|
652
|
+
return User.findById(userId);
|
|
653
|
+
})
|
|
654
|
+
.then(user => {
|
|
655
|
+
if (!user) {
|
|
656
|
+
return response.error(res, 404, "User not found");
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Update user with Google OAuth info
|
|
660
|
+
user.oauth = user.oauth || {};
|
|
661
|
+
user.oauth.google = {
|
|
662
|
+
id: googleId,
|
|
663
|
+
email: email.toLowerCase(),
|
|
664
|
+
connectedAt: new Date()
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
// Verify email if it matches
|
|
668
|
+
if (!user.isEmailVerified && email.toLowerCase() === user.email) {
|
|
669
|
+
user.isEmailVerified = true;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
return user.save();
|
|
673
|
+
})
|
|
674
|
+
.then(user => {
|
|
675
|
+
if (!user) return; // Already handled
|
|
676
|
+
|
|
677
|
+
logger.info('Google account linked for user:', user.email);
|
|
678
|
+
|
|
679
|
+
return res.status(200).json({
|
|
680
|
+
success: true,
|
|
681
|
+
message: "Google account linked successfully",
|
|
682
|
+
data: { user: user.toJSON() }
|
|
683
|
+
});
|
|
684
|
+
})
|
|
685
|
+
.catch(error => {
|
|
686
|
+
logger.error('Google account linking error:', error);
|
|
687
|
+
return response.error(res, 500, "Failed to link Google account", error.message);
|
|
688
|
+
});
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* DELETE /fear/api/auth/google/unlink
|
|
693
|
+
* @summary Unlink Google account from authenticated user
|
|
694
|
+
* @description Removes Google OAuth connection from the user's account
|
|
695
|
+
* @tags authentication, oauth
|
|
696
|
+
*/
|
|
697
|
+
exports.unlinkGoogleAccount = (req, res) => {
|
|
698
|
+
const userId = req.user._id;
|
|
508
699
|
|
|
700
|
+
User.findById(userId)
|
|
701
|
+
.select('+password')
|
|
702
|
+
.then(user => {
|
|
703
|
+
if (!user) {
|
|
704
|
+
return response.error(res, 404, "User not found");
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// Check if user has a password set (don't allow unlinking if it's their only auth method)
|
|
708
|
+
if (!user.password && user.oauth?.google) {
|
|
709
|
+
return response.error(
|
|
710
|
+
res,
|
|
711
|
+
400,
|
|
712
|
+
"Cannot unlink Google account. Please set a password first to maintain access to your account."
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Check if Google account is linked
|
|
717
|
+
if (!user.oauth || !user.oauth.google) {
|
|
718
|
+
return response.error(res, 400, "No Google account is linked to this user");
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Remove Google OAuth info
|
|
722
|
+
user.oauth.google = undefined;
|
|
723
|
+
|
|
724
|
+
return user.save();
|
|
725
|
+
})
|
|
726
|
+
.then(user => {
|
|
727
|
+
if (!user) return; // Already handled
|
|
728
|
+
|
|
729
|
+
logger.info('Google account unlinked for user:', user.email);
|
|
730
|
+
|
|
731
|
+
return res.status(200).json({
|
|
732
|
+
success: true,
|
|
733
|
+
message: "Google account unlinked successfully",
|
|
734
|
+
data: { user: user.toJSON() }
|
|
735
|
+
});
|
|
736
|
+
})
|
|
737
|
+
.catch(error => {
|
|
738
|
+
logger.error('Google account unlinking error:', error);
|
|
739
|
+
return response.error(res, 500, "Failed to unlink Google account", error.message);
|
|
740
|
+
});
|
|
741
|
+
};
|
|
509
742
|
// Export TokenService and other utilities for use in other modules
|
|
510
743
|
exports.AuthResponse = response;
|
|
511
744
|
|
|
745
|
+
|
|
512
746
|
module.exports = {
|
|
513
747
|
login: exports.login,
|
|
514
748
|
register: exports.register,
|
|
515
|
-
logout: exports.logout
|
|
749
|
+
logout: exports.logout,
|
|
750
|
+
isAuthorized: exports.isAuthorized,
|
|
751
|
+
googleAuth: exports.googleAuth,
|
|
516
752
|
}
|