nodejs-quickstart-structure 2.1.2 → 2.2.1

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.
Files changed (70) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +12 -17
  3. package/bin/index.js +1 -0
  4. package/lib/generator.js +1 -1
  5. package/lib/modules/app-setup.js +16 -0
  6. package/lib/modules/auth-setup.js +46 -4
  7. package/lib/prompts.js +49 -5
  8. package/package.json +1 -1
  9. package/templates/clean-architecture/js/src/infrastructure/config/env.js.ejs +12 -2
  10. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +27 -0
  11. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.spec.js.ejs +24 -0
  12. package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +5 -1
  13. package/templates/clean-architecture/ts/src/config/env.ts.ejs +12 -2
  14. package/templates/clean-architecture/ts/src/domain/user.ts.ejs +14 -0
  15. package/templates/clean-architecture/ts/src/index.ts.ejs +2 -0
  16. package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs +24 -0
  17. package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs +43 -45
  18. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs +5 -5
  19. package/templates/clean-architecture/ts/src/utils/httpCodes.ts +1 -0
  20. package/templates/common/.env.example.ejs +10 -0
  21. package/templates/common/README.md.ejs +65 -14
  22. package/templates/common/auth/js/controllers/authController.js.ejs +356 -13
  23. package/templates/common/auth/js/controllers/authController.spec.js.ejs +329 -53
  24. package/templates/common/auth/js/middleware/authMiddleware.js.ejs +10 -6
  25. package/templates/common/auth/js/routes/authRoutes.js.ejs +11 -0
  26. package/templates/common/auth/js/services/jwtService.spec.js.ejs +30 -0
  27. package/templates/common/auth/js/services/socialAuthService.js.ejs +175 -0
  28. package/templates/common/auth/js/services/socialAuthService.spec.js.ejs +192 -0
  29. package/templates/common/auth/js/usecases/SocialLoginUseCase.js.ejs +114 -0
  30. package/templates/common/auth/js/usecases/SocialLoginUseCase.spec.js.ejs +143 -0
  31. package/templates/common/auth/ts/controllers/authController.spec.ts.ejs +366 -64
  32. package/templates/common/auth/ts/controllers/authController.ts.ejs +370 -9
  33. package/templates/common/auth/ts/middleware/authMiddleware.ts.ejs +10 -6
  34. package/templates/common/auth/ts/routes/authRoutes.ts.ejs +11 -0
  35. package/templates/common/auth/ts/services/jwtService.spec.ts.ejs +18 -0
  36. package/templates/common/auth/ts/services/jwtService.ts.ejs +3 -3
  37. package/templates/common/auth/ts/services/socialAuthService.spec.ts.ejs +187 -0
  38. package/templates/common/auth/ts/services/socialAuthService.ts.ejs +189 -0
  39. package/templates/common/auth/ts/usecases/SocialLoginUseCase.spec.ts.ejs +143 -0
  40. package/templates/common/auth/ts/usecases/SocialLoginUseCase.ts.ejs +117 -0
  41. package/templates/common/database/js/models/User.js.ejs +13 -5
  42. package/templates/common/database/js/models/User.js.mongoose.ejs +15 -1
  43. package/templates/common/database/ts/models/User.ts.ejs +23 -7
  44. package/templates/common/database/ts/models/User.ts.mongoose.ejs +18 -2
  45. package/templates/common/docker-compose.yml.ejs +21 -0
  46. package/templates/common/ecosystem.config.js.ejs +10 -0
  47. package/templates/common/eslint.config.mjs.ejs +4 -1
  48. package/templates/common/jest.config.js.ejs +1 -1
  49. package/templates/common/kafka/ts/services/kafkaService.ts.ejs +1 -1
  50. package/templates/common/package.json.ejs +4 -0
  51. package/templates/common/src/tests/e2e/e2e.users.test.js.ejs +13 -1
  52. package/templates/common/src/tests/e2e/e2e.users.test.ts.ejs +13 -1
  53. package/templates/common/swagger.yml.ejs +62 -3
  54. package/templates/common/views/ejs/login.ejs.ejs +84 -0
  55. package/templates/common/views/ejs/signup.ejs.ejs +84 -0
  56. package/templates/common/views/pug/login.pug.ejs +78 -0
  57. package/templates/common/views/pug/signup.pug.ejs +78 -0
  58. package/templates/db/mysql/V1__Initial_Setup.sql.ejs +3 -1
  59. package/templates/db/postgres/V1__Initial_Setup.sql.ejs +3 -1
  60. package/templates/mvc/js/src/config/env.js.ejs +12 -2
  61. package/templates/mvc/js/src/controllers/userController.js.ejs +1 -1
  62. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.js.ejs +4 -3
  63. package/templates/mvc/js/src/index.js.ejs +2 -0
  64. package/templates/mvc/js/src/utils/httpCodes.js +1 -0
  65. package/templates/mvc/ts/src/config/env.ts.ejs +12 -2
  66. package/templates/mvc/ts/src/controllers/userController.ts.ejs +1 -0
  67. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.ts.ejs +5 -5
  68. package/templates/mvc/ts/src/index.ts.ejs +4 -1
  69. package/templates/mvc/ts/src/utils/httpCodes.ts +1 -0
  70. package/templates/clean-architecture/ts/src/domain/user.ts +0 -9
@@ -1,19 +1,28 @@
1
1
  const bcrypt = require('bcryptjs');
2
- <% if (architecture === 'MVC') { -%>
2
+ const crypto = require('crypto');
3
+ <%_ if (architecture === 'MVC') { _%>
3
4
  const User = require('../models/User');
4
5
  const JwtService = require('../services/jwtService');
5
- <% if (caching !== 'None') { -%>
6
+ <%_ if (socialAuth && socialAuth.filter(a => a !== 'None').length > 0) { _%>
7
+ const { SocialAuthService } = require('../services/socialAuthService');
8
+ <%_ } _%>
9
+ <%_ if (caching !== 'None') { _%>
6
10
  const cacheService = require('<% if (caching === "Redis") { %>../config/redisClient<% } else { %>../config/memoryCache<% } %>');
7
- <% } -%>
11
+ <%_ } _%>
8
12
  const logger = require('../utils/logger');
9
- <% } else { -%>
13
+ <%_ } else { _%>
10
14
  const User = require('../../../infrastructure/database/models/User');
11
15
  const JwtService = require('../../../infrastructure/auth/jwtService');
12
- <% if (caching !== 'None') { -%>
16
+ <%_ if (socialAuth && socialAuth.filter(a => a !== 'None').length > 0) { _%>
17
+ const { SocialLoginUseCase } = require('../../../usecases/auth/socialLoginUseCase');
18
+ const { GoogleProvider, GitHubProvider } = require('../../../infrastructure/auth/socialAuthService');
19
+ const UserRepository = require('../../../infrastructure/repositories/UserRepository');
20
+ <%_ } _%>
21
+ <%_ if (caching !== 'None') { _%>
13
22
  const cacheService = require('<% if (caching === "Redis") { %>../../../infrastructure/caching/redisClient<% } else { %>../../../infrastructure/caching/memoryCache<% } %>');
14
- <% } -%>
23
+ <%_ } _%>
15
24
  const logger = require('../../../infrastructure/log/logger');
16
- <% } -%>
25
+ <%_ } _%>
17
26
  const HTTP_STATUS = require('<% if (architecture === "MVC") { %>../utils/httpCodes<% } else { %>../../../utils/httpCodes<% } %>');
18
27
 
19
28
  class AuthController {
@@ -21,6 +30,27 @@ class AuthController {
21
30
  this.login = this.login.bind(this);
22
31
  this.refresh = this.refresh.bind(this);
23
32
  this.logout = this.logout.bind(this);
33
+ <% if (socialAuth && socialAuth.filter(a => a !== 'None').length > 0) { -%>
34
+ this.socialExchange = this.socialExchange.bind(this);
35
+ <% if (socialAuth.includes('Google')) { -%>
36
+ this.googleLogin = this.googleLogin.bind(this);
37
+ this.googleCallback = this.googleCallback.bind(this);
38
+ <% } -%>
39
+ <% if (socialAuth.includes('GitHub')) { -%>
40
+ this.githubLogin = this.githubLogin.bind(this);
41
+ this.githubCallback = this.githubCallback.bind(this);
42
+ <% } -%>
43
+ <% } -%>
44
+ this.setOAuthStateCookie = this.setOAuthStateCookie.bind(this);
45
+ }
46
+
47
+ setOAuthStateCookie(res, state) {
48
+ res.cookie('oauth_state', state, {
49
+ httpOnly: true,
50
+ secure: process.env.NODE_ENV === 'production',
51
+ sameSite: 'lax',
52
+ maxAge: 10 * 60 * 1000
53
+ });
24
54
  }
25
55
 
26
56
  async login(req, res, next) {
@@ -45,16 +75,16 @@ class AuthController {
45
75
  const refreshJti = JwtService.decodeToken(refreshToken)?.jti;
46
76
  const accessToken = JwtService.generateToken({ id: userId, email: user.email, sid: refreshJti });
47
77
 
48
- <%_ if (caching !== 'None') { -%>
78
+ <%_ if (caching !== 'None') { _%>
49
79
  const cacheKey = `refresh_tokens:${userId}`;
50
80
  const activeTokens = await cacheService.get(cacheKey) || [];
51
81
  activeTokens.push(refreshJti);
52
82
  await cacheService.set(cacheKey, activeTokens, 7 * 24 * 60 * 60);
53
- <% } else { %>
83
+ <%_ } else { _%>
54
84
  const activeTokens = JwtService.activeRefreshTokens.get(userId) || [];
55
85
  activeTokens.push(refreshJti);
56
86
  JwtService.activeRefreshTokens.set(userId, activeTokens);
57
- <%_ } -%>
87
+ <%_ } _%>
58
88
 
59
89
  res.json({ token: accessToken, accessToken, refreshToken });
60
90
  } catch (error) {
@@ -78,7 +108,7 @@ class AuthController {
78
108
  const userId = String(decoded.id);
79
109
  const incomingJti = decoded.jti;
80
110
 
81
- <%_ if (caching !== 'None') { -%>
111
+ <%_ if (caching !== 'None') { _%>
82
112
  const cacheKey = `refresh_tokens:${userId}`;
83
113
  let activeTokens = await cacheService.get(cacheKey) || [];
84
114
 
@@ -95,7 +125,7 @@ class AuthController {
95
125
 
96
126
  activeTokens.push(newRefreshJti);
97
127
  await cacheService.set(cacheKey, activeTokens, 7 * 24 * 60 * 60);
98
- <% } else { %>
128
+ <%_ } else { _%>
99
129
  let activeTokens = JwtService.activeRefreshTokens.get(userId) || [];
100
130
 
101
131
  if (!activeTokens.includes(incomingJti)) {
@@ -111,7 +141,7 @@ class AuthController {
111
141
 
112
142
  activeTokens.push(newRefreshJti);
113
143
  JwtService.activeRefreshTokens.set(userId, activeTokens);
114
- <%_ } -%>
144
+ <%_ } _%>
115
145
  res.json({ accessToken: newAccessToken, refreshToken: newRefreshToken });
116
146
  } catch (error) {
117
147
  logger.error('Refresh token error:', error);
@@ -165,6 +195,319 @@ class AuthController {
165
195
  next(error);
166
196
  }
167
197
  }
198
+
199
+ <% if (socialAuth && socialAuth.filter(a => a !== 'None').length > 0) { -%>
200
+ async socialExchange(req, res, next) {
201
+ try {
202
+ const { code, provider, redirectUri } = req.body;
203
+ if (!code || !provider) {
204
+ return res.status(HTTP_STATUS.BAD_REQUEST).json({ message: 'Code and provider are required' });
205
+ }
206
+
207
+ <% if (architecture === 'Clean Architecture') { -%>
208
+ let useCase;
209
+ const userRepository = new UserRepository();
210
+ <% if (socialAuth.includes('Google')) { -%>
211
+ if (provider === 'Google') useCase = new SocialLoginUseCase(new GoogleProvider(), userRepository);
212
+ <% } -%>
213
+ <% if (socialAuth.includes('GitHub')) { -%>
214
+ if (provider === 'GitHub') useCase = new SocialLoginUseCase(new GitHubProvider(), userRepository);
215
+ <% } -%>
216
+
217
+ if (!useCase) {
218
+ return res.status(HTTP_STATUS.BAD_REQUEST).json({ message: 'Invalid social provider' });
219
+ }
220
+
221
+ const { user, accessToken, refreshToken } = await useCase.execute(code, redirectUri);
222
+ const userId = String(user.id || user._id);
223
+ const refreshJti = JwtService.decodeToken(refreshToken)?.jti;
224
+
225
+ // Store refresh token
226
+ <% if (caching !== 'None') { -%>
227
+ const cacheKey = `refresh_tokens:${userId}`;
228
+ const activeTokens = await cacheService.get(cacheKey) || [];
229
+ activeTokens.push(refreshJti);
230
+ await cacheService.set(cacheKey, activeTokens, 7 * 24 * 60 * 60);
231
+ <% } else { -%>
232
+ const activeTokens = JwtService.activeRefreshTokens.get(userId) || [];
233
+ activeTokens.push(refreshJti);
234
+ JwtService.activeRefreshTokens.set(userId, activeTokens);
235
+ <% } -%>
236
+
237
+ res.json({ token: accessToken, accessToken, refreshToken });
238
+ <% } else { -%>
239
+ let profile;
240
+ <% if (socialAuth.includes('Google')) { -%>
241
+ if (provider === 'Google') {
242
+ profile = await SocialAuthService.getGoogleProfile(code, redirectUri);
243
+ }
244
+ <% } -%>
245
+ <% if (socialAuth.includes('GitHub')) { -%>
246
+ if (provider === 'GitHub') {
247
+ profile = await SocialAuthService.getGithubProfile(code);
248
+ }
249
+ <% } -%>
250
+
251
+ if (!profile) {
252
+ return res.status(HTTP_STATUS.BAD_REQUEST).json({ message: 'Invalid social provider' });
253
+ }
254
+
255
+ if (!profile || !profile.email) {
256
+ return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Failed to retrieve profile from provider' });
257
+ }
258
+
259
+ <%_ if (database === 'MongoDB' || database === 'None') { _%>
260
+ let user = await User.findOne({ email: profile.email });
261
+ <%_ } else { _%>
262
+ let user = await User.findOne({ where: { email: profile.email } });
263
+ <%_ } _%>
264
+
265
+ if (!user) {
266
+ // Create new user without password
267
+ <%_ if (database === 'MongoDB' || database === 'None') { _%>
268
+ user = await User.create({ email: profile.email, name: profile.name, password: null });
269
+ <%_ if (socialAuth.includes('Google')) { _%>
270
+ if (provider === 'Google') user.googleId = profile.id;
271
+ <%_ } _%>
272
+ <%_ if (socialAuth.includes('GitHub')) { _%>
273
+ if (provider === 'GitHub') user.githubId = profile.id;
274
+ <%_ } _%>
275
+ await user.save();
276
+ <%_ } else { _%>
277
+ user = await User.create({
278
+ email: profile.email,
279
+ name: profile.name,
280
+ password: null,
281
+ <%_ if (socialAuth.includes('Google')) { _%>
282
+ googleId: provider === 'Google' ? profile.id : null,
283
+ <%_ } _%>
284
+ <%_ if (socialAuth.includes('GitHub')) { _%>
285
+ githubId: provider === 'GitHub' ? profile.id : null,
286
+ <%_ } _%>
287
+ });
288
+ <%_ } _%>
289
+ }
290
+
291
+ const userId = String(user.id || user._id);
292
+
293
+ const refreshToken = JwtService.generateRefreshToken({ id: userId, email: user.email });
294
+ const refreshJti = JwtService.decodeToken(refreshToken)?.jti;
295
+ const accessToken = JwtService.generateToken({ id: userId, email: user.email, sid: refreshJti });
296
+
297
+ // Store refresh token
298
+ <%_ if (caching !== 'None') { -%>
299
+ const cacheKey = `refresh_tokens:${userId}`;
300
+ const activeTokens = await cacheService.get(cacheKey) || [];
301
+ activeTokens.push(refreshJti);
302
+ await cacheService.set(cacheKey, activeTokens, 7 * 24 * 60 * 60);
303
+ <%_ } else { -%>
304
+ const activeTokens = JwtService.activeRefreshTokens.get(userId) || [];
305
+ activeTokens.push(refreshJti);
306
+ JwtService.activeRefreshTokens.set(userId, activeTokens);
307
+ <%_ } _%>
308
+
309
+ res.json({ token: accessToken, accessToken, refreshToken });
310
+ <% } %>
311
+ } catch (error) {
312
+ logger.error('Social exchange error:', error);
313
+ next(error);
314
+ }
315
+ }
316
+ <% } -%>
317
+
318
+ <% if (socialAuth.includes('Google')) { -%>
319
+ async googleLogin(req, res) {
320
+ const rootUrl = 'https://accounts.google.com/o/oauth2/v2/auth';
321
+ const state = crypto.randomBytes(16).toString('hex');
322
+ this.setOAuthStateCookie(res, state);
323
+
324
+ const options = {
325
+ redirect_uri: process.env.GOOGLE_CALLBACK_URL || 'http://localhost:3000/api/auth/google/callback',
326
+ client_id: process.env.GOOGLE_CLIENT_ID,
327
+ access_type: 'offline',
328
+ response_type: 'code',
329
+ prompt: 'consent',
330
+ scope: [
331
+ 'https://www.googleapis.com/auth/userinfo.profile',
332
+ 'https://www.googleapis.com/auth/userinfo.email',
333
+ ].join(' '),
334
+ state: state
335
+ };
336
+ const qs = new URLSearchParams(options);
337
+ res.redirect(`${rootUrl}?${qs.toString()}`);
338
+ }
339
+
340
+ async googleCallback(req, res, _next) {
341
+ try {
342
+ const { code, state } = req.query;
343
+ const savedState = req.cookies?.oauth_state;
344
+ res.clearCookie('oauth_state');
345
+
346
+ if (!state || state !== savedState) {
347
+ return res.status(HTTP_STATUS.FORBIDDEN).json({ message: 'Invalid state parameter' });
348
+ }
349
+
350
+ const redirectUri = process.env.GOOGLE_CALLBACK_URL || 'http://localhost:3000/api/auth/google/callback';
351
+
352
+ <%_ if (architecture === 'Clean Architecture') { _%>
353
+ const useCase = new SocialLoginUseCase(new GoogleProvider(), new UserRepository());
354
+ const { user, accessToken, refreshToken } = await useCase.execute(code, redirectUri);
355
+ const userId = String(user.id || user._id);
356
+ const refreshJti = JwtService.decodeToken(refreshToken)?.jti;
357
+
358
+ // Store refresh token
359
+ <%_ if (caching !== 'None') { _%>
360
+ const cacheKey = `refresh_tokens:${userId}`;
361
+ const activeTokens = await cacheService.get(cacheKey) || [];
362
+ activeTokens.push(refreshJti);
363
+ await cacheService.set(cacheKey, activeTokens, 7 * 24 * 60 * 60);
364
+ <%_ } else { _%>
365
+ const activeTokens = JwtService.activeRefreshTokens.get(userId) || [];
366
+ activeTokens.push(refreshJti);
367
+ JwtService.activeRefreshTokens.set(userId, activeTokens);
368
+ <% } %>
369
+
370
+ res.cookie('accessToken', accessToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax' });
371
+ res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax' });
372
+ res.redirect('/');
373
+ <%_ } else { _%>
374
+ const profile = await SocialAuthService.getGoogleProfile(code, redirectUri);
375
+
376
+ <%_ if (database === 'MongoDB' || database === 'None') { -%>
377
+ let user = await User.findOne({ email: profile.email });
378
+ <%_ } else { -%>
379
+ let user = await User.findOne({ where: { email: profile.email } });
380
+ <%_ } -%>
381
+
382
+ if (!user) {
383
+ user = await User.create({
384
+ email: profile.email,
385
+ name: profile.name,
386
+ password: null,
387
+ googleId: profile.id
388
+ });
389
+ }
390
+
391
+ const userId = String(user.id || user._id);
392
+ const refreshToken = JwtService.generateRefreshToken({ id: userId, email: user.email });
393
+ const refreshJti = JwtService.decodeToken(refreshToken)?.jti;
394
+ const accessToken = JwtService.generateToken({ id: userId, email: user.email, sid: refreshJti });
395
+
396
+ // Store refresh token
397
+ <%_ if (caching !== 'None') { -%>
398
+ const cacheKey = `refresh_tokens:${userId}`;
399
+ const activeTokens = await cacheService.get(cacheKey) || [];
400
+ activeTokens.push(refreshJti);
401
+ await cacheService.set(cacheKey, activeTokens, 7 * 24 * 60 * 60);
402
+ <%_ } else { _%>
403
+ const activeTokens = JwtService.activeRefreshTokens.get(userId) || [];
404
+ activeTokens.push(refreshJti);
405
+ JwtService.activeRefreshTokens.set(userId, activeTokens);
406
+ <% } %>
407
+
408
+ res.cookie('accessToken', accessToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax' });
409
+ res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax' });
410
+ res.redirect('/');
411
+ <% } %>
412
+ } catch (error) {
413
+ logger.error('Google callback error:', error);
414
+ res.redirect('/login?error=social_auth_failed');
415
+ }
416
+ }
417
+ <% } -%>
418
+
419
+ <% if (socialAuth.includes('GitHub')) { -%>
420
+ async githubLogin(req, res) {
421
+ const rootUrl = 'https://github.com/login/oauth/authorize';
422
+ const state = crypto.randomBytes(16).toString('hex');
423
+ this.setOAuthStateCookie(res, state);
424
+
425
+ const options = {
426
+ client_id: process.env.GITHUB_CLIENT_ID,
427
+ redirect_uri: process.env.GITHUB_CALLBACK_URL || 'http://localhost:3000/api/auth/github/callback',
428
+ scope: 'user:email',
429
+ state: state
430
+ };
431
+ const qs = new URLSearchParams(options);
432
+ res.redirect(`${rootUrl}?${qs.toString()}`);
433
+ }
434
+
435
+ async githubCallback(req, res, _next) {
436
+ try {
437
+ const { code, state } = req.query;
438
+ const savedState = req.cookies?.oauth_state;
439
+ res.clearCookie('oauth_state');
440
+
441
+ if (!state || state !== savedState) {
442
+ return res.status(HTTP_STATUS.FORBIDDEN).json({ message: 'Invalid state parameter' });
443
+ }
444
+
445
+ <%_ if (architecture === 'Clean Architecture') { _%>
446
+ const useCase = new SocialLoginUseCase(new GitHubProvider(), new UserRepository());
447
+ const { user, accessToken, refreshToken } = await useCase.execute(code);
448
+ const userId = String(user.id || user._id);
449
+ const refreshJti = JwtService.decodeToken(refreshToken)?.jti;
450
+
451
+ // Store refresh token
452
+ <%_ if (caching !== 'None') { _%>
453
+ const cacheKey = `refresh_tokens:${userId}`;
454
+ const activeTokens = await cacheService.get(cacheKey) || [];
455
+ activeTokens.push(refreshJti);
456
+ await cacheService.set(cacheKey, activeTokens, 7 * 24 * 60 * 60);
457
+ <%_ } else { _%>
458
+ const activeTokens = JwtService.activeRefreshTokens.get(userId) || [];
459
+ activeTokens.push(refreshJti);
460
+ JwtService.activeRefreshTokens.set(userId, activeTokens);
461
+ <% } %>
462
+
463
+ res.cookie('accessToken', accessToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax' });
464
+ res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax' });
465
+ res.redirect('/');
466
+ <%_ } else { _%>
467
+ const profile = await SocialAuthService.getGithubProfile(code);
468
+
469
+ <%_ if (database === 'MongoDB' || database === 'None') { -%>
470
+ let user = await User.findOne({ email: profile.email });
471
+ <%_ } else { -%>
472
+ let user = await User.findOne({ where: { email: profile.email } });
473
+ <%_ } -%>
474
+
475
+ if (!user) {
476
+ user = await User.create({
477
+ email: profile.email,
478
+ name: profile.name,
479
+ password: null,
480
+ githubId: profile.id
481
+ });
482
+ }
483
+
484
+ const userId = String(user.id || user._id);
485
+ const refreshToken = JwtService.generateRefreshToken({ id: userId, email: user.email });
486
+ const refreshJti = JwtService.decodeToken(refreshToken)?.jti;
487
+ const accessToken = JwtService.generateToken({ id: userId, email: user.email, sid: refreshJti });
488
+
489
+ // Store refresh token
490
+ <%_ if (caching !== 'None') { -%>
491
+ const cacheKey = `refresh_tokens:${userId}`;
492
+ const activeTokens = await cacheService.get(cacheKey) || [];
493
+ activeTokens.push(refreshJti);
494
+ await cacheService.set(cacheKey, activeTokens, 7 * 24 * 60 * 60);
495
+ <%_ } else { -%>
496
+ const activeTokens = JwtService.activeRefreshTokens.get(userId) || [];
497
+ activeTokens.push(refreshJti);
498
+ JwtService.activeRefreshTokens.set(userId, activeTokens);
499
+ <% } %>
500
+
501
+ res.cookie('accessToken', accessToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax' });
502
+ res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax' });
503
+ res.redirect('/');
504
+ <%_ } _%>
505
+ } catch (error) {
506
+ logger.error('GitHub callback error:', error);
507
+ res.redirect('/login?error=social_auth_failed');
508
+ }
509
+ }
510
+ <% } -%>
168
511
  }
169
512
 
170
513
  module.exports = AuthController;