stackkit 0.2.7 → 0.2.9

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 (61) hide show
  1. package/README.md +91 -12
  2. package/dist/lib/fs/files.js +1 -1
  3. package/dist/lib/generation/code-generator.d.ts +2 -7
  4. package/dist/lib/generation/code-generator.js +120 -56
  5. package/dist/lib/utils/fs-helpers.d.ts +1 -1
  6. package/modules/auth/authjs/generator.json +16 -16
  7. package/modules/auth/better-auth/files/express/middlewares/authorize.ts +121 -41
  8. package/modules/auth/better-auth/files/express/modules/auth/auth.controller.ts +257 -0
  9. package/modules/auth/better-auth/files/express/modules/auth/auth.interface.ts +23 -0
  10. package/modules/auth/better-auth/files/express/modules/auth/auth.route.ts +22 -0
  11. package/modules/auth/better-auth/files/express/modules/auth/auth.service.ts +403 -0
  12. package/modules/auth/better-auth/files/express/templates/google-redirect.ejs +91 -0
  13. package/modules/auth/better-auth/files/express/templates/otp.ejs +87 -0
  14. package/modules/auth/better-auth/files/express/types/express.d.ts +6 -8
  15. package/modules/auth/better-auth/files/express/utils/cookie.ts +19 -0
  16. package/modules/auth/better-auth/files/express/utils/jwt.ts +34 -0
  17. package/modules/auth/better-auth/files/express/utils/token.ts +66 -0
  18. package/modules/auth/better-auth/files/nextjs/api/auth/[...all]/route.ts +1 -1
  19. package/modules/auth/better-auth/files/nextjs/lib/auth/auth-guards.ts +11 -1
  20. package/modules/auth/better-auth/files/nextjs/templates/email-otp.tsx +74 -0
  21. package/modules/auth/better-auth/files/shared/config/env.ts +113 -0
  22. package/modules/auth/better-auth/files/shared/lib/auth-client.ts +1 -1
  23. package/modules/auth/better-auth/files/shared/lib/auth.ts +151 -62
  24. package/modules/auth/better-auth/files/shared/prisma/schema.prisma +22 -11
  25. package/modules/auth/better-auth/files/shared/utils/email.ts +71 -0
  26. package/modules/auth/better-auth/generator.json +159 -81
  27. package/modules/database/mongoose/generator.json +18 -18
  28. package/modules/database/prisma/generator.json +44 -44
  29. package/package.json +1 -1
  30. package/templates/express/env.example +2 -2
  31. package/templates/express/eslint.config.mjs +7 -0
  32. package/templates/express/node_modules/.bin/acorn +17 -0
  33. package/templates/express/node_modules/.bin/eslint +17 -0
  34. package/templates/express/node_modules/.bin/tsc +17 -0
  35. package/templates/express/node_modules/.bin/tsserver +17 -0
  36. package/templates/express/node_modules/.bin/tsx +17 -0
  37. package/templates/express/package.json +12 -6
  38. package/templates/express/src/app.ts +15 -7
  39. package/templates/express/src/config/cors.ts +8 -7
  40. package/templates/express/src/config/env.ts +26 -5
  41. package/templates/express/src/config/logger.ts +2 -2
  42. package/templates/express/src/config/rate-limit.ts +2 -2
  43. package/templates/express/src/modules/health/health.controller.ts +13 -11
  44. package/templates/express/src/routes/index.ts +1 -6
  45. package/templates/express/src/server.ts +12 -12
  46. package/templates/express/src/shared/errors/app-error.ts +16 -0
  47. package/templates/express/src/shared/middlewares/error.middleware.ts +154 -12
  48. package/templates/express/src/shared/middlewares/not-found.middleware.ts +2 -1
  49. package/templates/express/src/shared/utils/catch-async.ts +11 -0
  50. package/templates/express/src/shared/utils/pagination.ts +6 -1
  51. package/templates/express/src/shared/utils/send-response.ts +25 -0
  52. package/templates/nextjs/lib/env.ts +19 -8
  53. package/modules/auth/better-auth/files/shared/lib/email/email-service.ts +0 -33
  54. package/modules/auth/better-auth/files/shared/lib/email/email-templates.ts +0 -89
  55. package/templates/express/eslint.config.cjs +0 -42
  56. package/templates/express/src/config/helmet.ts +0 -5
  57. package/templates/express/src/modules/health/health.service.ts +0 -6
  58. package/templates/express/src/shared/errors/error-codes.ts +0 -9
  59. package/templates/express/src/shared/logger/logger.ts +0 -20
  60. package/templates/express/src/shared/utils/async-handler.ts +0 -9
  61. package/templates/express/src/shared/utils/response.ts +0 -9
@@ -1,54 +1,134 @@
1
+ import { Role, UserStatus } from "@prisma/client";
1
2
  import { NextFunction, Request, Response } from "express";
2
- import { auth } from "../../modules/auth/auth";
3
+ import status from "http-status";
4
+ import { envVars } from "../../config/env";
5
+ import { prisma } from "../../database/prisma";
6
+ import { AppError } from "../errors/app-error";
7
+ import { cookieUtils } from "../utils/cookie";
8
+ import { jwtUtils } from "../utils/jwt";
3
9
 
4
- export enum UserRole {
5
- USER = "USER",
6
- ADMIN = "ADMIN"
7
- }
8
-
9
- const authorize = (...roles: UserRole[]) => {
10
- return async (req: Request, res: Response, next: NextFunction) => {
10
+ export const authorize =
11
+ (...authRoles: Role[]) =>
12
+ async (req: Request, res: Response, next: NextFunction) => {
11
13
  try {
12
- // get user session
13
- const session = await auth?.api.getSession({
14
- headers: req.headers as any,
15
- });
16
-
17
- if (!session) {
18
- return res.status(401).json({
19
- success: false,
20
- message: "You are not authorized!",
21
- });
14
+ //Session Token Verification
15
+ const sessionToken = cookieUtils.getCookie(
16
+ req,
17
+ "better-auth.session_token",
18
+ );
19
+
20
+ if (!sessionToken) {
21
+ throw new Error("Unauthorized access! No session token provided.");
22
22
  }
23
23
 
24
- if (!session.user.emailVerified) {
25
- return res.status(403).json({
26
- success: false,
27
- message: "Email verification required. Please verify your email!",
24
+ if (sessionToken) {
25
+ const sessionExists = await prisma.session.findFirst({
26
+ where: {
27
+ token: sessionToken,
28
+ expiresAt: {
29
+ gt: new Date(),
30
+ },
31
+ },
32
+ include: {
33
+ user: true,
34
+ },
28
35
  });
36
+
37
+ if (sessionExists && sessionExists.user) {
38
+ const user = sessionExists.user;
39
+
40
+ const now = new Date();
41
+ const expiresAt = new Date(sessionExists.expiresAt);
42
+ const createdAt = new Date(sessionExists.createdAt);
43
+
44
+ const sessionLifeTime = expiresAt.getTime() - createdAt.getTime();
45
+ const timeRemaining = expiresAt.getTime() - now.getTime();
46
+ const percentRemaining = (timeRemaining / sessionLifeTime) * 100;
47
+
48
+ if (percentRemaining < 20) {
49
+ res.setHeader("X-Session-Refresh", "true");
50
+ res.setHeader("X-Session-Expires-At", expiresAt.toISOString());
51
+ res.setHeader("X-Time-Remaining", timeRemaining.toString());
52
+
53
+ console.log("Session Expiring Soon!!");
54
+ }
55
+
56
+ if (
57
+ user.status === UserStatus.BLOCKED ||
58
+ user.status === UserStatus.DELETED
59
+ ) {
60
+ throw new AppError(
61
+ status.UNAUTHORIZED,
62
+ "Unauthorized access! User is not active.",
63
+ );
64
+ }
65
+
66
+ if (user.isDeleted) {
67
+ throw new AppError(
68
+ status.UNAUTHORIZED,
69
+ "Unauthorized access! User is deleted.",
70
+ );
71
+ }
72
+
73
+ if (authRoles.length > 0 && !authRoles.includes(user.role)) {
74
+ throw new AppError(
75
+ status.FORBIDDEN,
76
+ "Forbidden access! You do not have permission to access this resource.",
77
+ );
78
+ }
79
+
80
+ req.user = {
81
+ id: user.id,
82
+ name: user.name,
83
+ email: user.email,
84
+ role: user.role,
85
+ };
86
+ }
87
+
88
+ const accessToken = cookieUtils.getCookie(req, "accessToken");
89
+
90
+ if (!accessToken) {
91
+ throw new AppError(
92
+ status.UNAUTHORIZED,
93
+ "Unauthorized access! No access token provided.",
94
+ );
95
+ }
29
96
  }
30
97
 
31
- req.user = {
32
- id: session.user.id,
33
- email: session.user.email,
34
- name: session.user.name,
35
- role: session.user.role as string,
36
- emailVerified: session.user.emailVerified,
37
- };
38
-
39
- if (roles.length && !roles.includes(req.user.role as UserRole)) {
40
- return res.status(403).json({
41
- success: false,
42
- message:
43
- "Forbidden! You don't have permission to access this resources!",
44
- });
98
+ //Access Token Verification
99
+ const accessToken = cookieUtils.getCookie(req, "accessToken");
100
+
101
+ if (!accessToken) {
102
+ throw new AppError(
103
+ status.UNAUTHORIZED,
104
+ "Unauthorized access! No access token provided.",
105
+ );
106
+ }
107
+
108
+ const verifiedToken = jwtUtils.verifyToken(
109
+ accessToken,
110
+ envVars.ACCESS_TOKEN_SECRET,
111
+ );
112
+
113
+ if (!verifiedToken.success) {
114
+ throw new AppError(
115
+ status.UNAUTHORIZED,
116
+ "Unauthorized access! Invalid access token.",
117
+ );
118
+ }
119
+
120
+ if (
121
+ authRoles.length > 0 &&
122
+ !authRoles.includes(verifiedToken.data!.role as Role)
123
+ ) {
124
+ throw new AppError(
125
+ status.FORBIDDEN,
126
+ "Forbidden access! You do not have permission to access this resource.",
127
+ );
45
128
  }
46
129
 
47
130
  next();
48
- } catch (err) {
49
- next(err);
131
+ } catch (error: unknown) {
132
+ next(error);
50
133
  }
51
134
  };
52
- };
53
-
54
- export default authorize;
@@ -0,0 +1,257 @@
1
+ import { Request, Response } from "express";
2
+ import status from "http-status";
3
+ import { envVars } from "../../config/env";
4
+ import { auth } from "../../lib/auth";
5
+ import { AppError } from "../../shared/errors/app-error";
6
+ import { catchAsync } from "../../shared/utils/catch-async";
7
+ import { cookieUtils } from "../../shared/utils/cookie";
8
+ import { sendResponse } from "../../shared/utils/send-response";
9
+ import { tokenUtils } from "../../shared/utils/token";
10
+ import { authService } from "./auth.service";
11
+
12
+ const registerUser = catchAsync(async (req: Request, res: Response) => {
13
+ const payload = req.body;
14
+
15
+ const result = await authService.registerUser(payload);
16
+
17
+ const { accessToken, refreshToken, token, ...rest } = result;
18
+
19
+ tokenUtils.setAccessTokenCookie(res, accessToken);
20
+ tokenUtils.setRefreshTokenCookie(res, refreshToken);
21
+ tokenUtils.setBetterAuthSessionCookie(res, token as string);
22
+
23
+ sendResponse(res, {
24
+ status: status.CREATED,
25
+ success: true,
26
+ message: "User registered successfully",
27
+ data: {
28
+ token,
29
+ accessToken,
30
+ refreshToken,
31
+ ...rest,
32
+ },
33
+ });
34
+ });
35
+
36
+ const loginUser = catchAsync(
37
+ async (req: Request, res: Response) => {
38
+ const payload = req.body;
39
+ const result = await authService.loginUser(payload);
40
+ const { accessToken, refreshToken, token, ...rest } = result
41
+
42
+ tokenUtils.setAccessTokenCookie(res, accessToken);
43
+ tokenUtils.setRefreshTokenCookie(res, refreshToken);
44
+ tokenUtils.setBetterAuthSessionCookie(res, token);
45
+
46
+ sendResponse(res, {
47
+ status: status.OK,
48
+ success: true,
49
+ message: "User logged in successfully",
50
+ data: {
51
+ token,
52
+ accessToken,
53
+ refreshToken,
54
+ ...rest,
55
+ },
56
+ });
57
+ }
58
+ )
59
+
60
+ const getMe = catchAsync(
61
+ async (req: Request, res: Response) => {
62
+ const user = req.user;
63
+ const result = await authService.getMe(user);
64
+ sendResponse(res, {
65
+ status: status.OK,
66
+ success: true,
67
+ message: "User profile fetched successfully",
68
+ data: result,
69
+ });
70
+ }
71
+ )
72
+
73
+ const getNewToken = catchAsync(
74
+ async (req: Request, res: Response) => {
75
+ const refreshToken = req.cookies.refreshToken;
76
+ const betterAuthSessionToken = req.cookies["better-auth.session_token"];
77
+ if (!refreshToken) {
78
+ throw new AppError(status.UNAUTHORIZED, "Refresh token is missing");
79
+ }
80
+ const result = await authService.getNewToken(refreshToken, betterAuthSessionToken);
81
+
82
+ const { accessToken, refreshToken: newRefreshToken, sessionToken } = result;
83
+
84
+ tokenUtils.setAccessTokenCookie(res, accessToken);
85
+ tokenUtils.setRefreshTokenCookie(res, newRefreshToken);
86
+ tokenUtils.setBetterAuthSessionCookie(res, sessionToken);
87
+
88
+ sendResponse(res, {
89
+ status: status.OK,
90
+ success: true,
91
+ message: "New tokens generated successfully",
92
+ data: {
93
+ accessToken,
94
+ refreshToken: newRefreshToken,
95
+ sessionToken,
96
+ },
97
+ });
98
+ }
99
+ )
100
+
101
+ const changePassword = catchAsync(
102
+ async (req: Request, res: Response) => {
103
+ const payload = req.body;
104
+ const betterAuthSessionToken = req.cookies["better-auth.session_token"];
105
+
106
+ const result = await authService.changePassword(payload, betterAuthSessionToken);
107
+
108
+ const { accessToken, refreshToken, token } = result;
109
+
110
+ tokenUtils.setAccessTokenCookie(res, accessToken);
111
+ tokenUtils.setRefreshTokenCookie(res, refreshToken);
112
+ tokenUtils.setBetterAuthSessionCookie(res, token as string);
113
+
114
+ sendResponse(res, {
115
+ status: status.OK,
116
+ success: true,
117
+ message: "Password changed successfully",
118
+ data: result,
119
+ });
120
+ }
121
+ )
122
+
123
+ const logoutUser = catchAsync(
124
+ async (req: Request, res: Response) => {
125
+ const betterAuthSessionToken = req.cookies["better-auth.session_token"];
126
+ const result = await authService.logoutUser(betterAuthSessionToken);
127
+ cookieUtils.clearCookie(res, 'accessToken', {
128
+ httpOnly: true,
129
+ secure: true,
130
+ sameSite: "none",
131
+ });
132
+ cookieUtils.clearCookie(res, 'refreshToken', {
133
+ httpOnly: true,
134
+ secure: true,
135
+ sameSite: "none",
136
+ });
137
+ cookieUtils.clearCookie(res, 'better-auth.session_token', {
138
+ httpOnly: true,
139
+ secure: true,
140
+ sameSite: "none",
141
+ });
142
+
143
+ sendResponse(res, {
144
+ status: status.OK,
145
+ success: true,
146
+ message: "User logged out successfully",
147
+ data: result,
148
+ });
149
+ }
150
+ )
151
+
152
+ const verifyEmail = catchAsync(
153
+ async (req: Request, res: Response) => {
154
+ const { email, otp } = req.body;
155
+ await authService.verifyEmail(email, otp);
156
+
157
+ sendResponse(res, {
158
+ status: status.OK,
159
+ success: true,
160
+ message: "Email verified successfully",
161
+ });
162
+ }
163
+ )
164
+
165
+ const forgetPassword = catchAsync(
166
+ async (req: Request, res: Response) => {
167
+ const { email } = req.body;
168
+ await authService.forgetPassword(email);
169
+
170
+ sendResponse(res, {
171
+ status: status.OK,
172
+ success: true,
173
+ message: "Password reset OTP sent to email successfully",
174
+ });
175
+ }
176
+ )
177
+
178
+ const resetPassword = catchAsync(
179
+ async (req: Request, res: Response) => {
180
+ const { email, otp, newPassword } = req.body;
181
+ await authService.resetPassword(email, otp, newPassword);
182
+
183
+ sendResponse(res, {
184
+ status: status.OK,
185
+ success: true,
186
+ message: "Password reset successfully",
187
+ });
188
+ }
189
+ )
190
+
191
+ const googleLogin = catchAsync((req: Request, res: Response) => {
192
+ const redirectPath = req.query.redirect || "/dashboard";
193
+
194
+ const encodedRedirectPath = encodeURIComponent(redirectPath as string);
195
+
196
+ const callbackURL = `${envVars.BETTER_AUTH_URL}/api/v1/auth/google/success?redirect=${encodedRedirectPath}`;
197
+
198
+ res.render("googleRedirect", {
199
+ callbackURL : callbackURL,
200
+ betterAuthUrl : envVars.BETTER_AUTH_URL,
201
+ })
202
+ })
203
+
204
+ const googleLoginSuccess = catchAsync(async (req: Request, res: Response) => {
205
+ const redirectPath = req.query.redirect as string || "/dashboard";
206
+
207
+ const sessionToken = req.cookies["better-auth.session_token"];
208
+
209
+ if(!sessionToken){
210
+ return res.redirect(`${envVars.FRONTEND_URL}/login?error=oauth_failed`);
211
+ }
212
+
213
+ const session = await auth.api.getSession({
214
+ headers:{
215
+ "Cookie" : `better-auth.session_token=${sessionToken}`
216
+ }
217
+ })
218
+
219
+ if (!session) {
220
+ return res.redirect(`${envVars.FRONTEND_URL}/login?error=no_session_found`);
221
+ }
222
+
223
+ if(session && !session.user){
224
+ return res.redirect(`${envVars.FRONTEND_URL}/login?error=no_user_found`);
225
+ }
226
+
227
+ const result = await authService.googleLoginSuccess(session);
228
+
229
+ const {accessToken, refreshToken} = result;
230
+
231
+ tokenUtils.setAccessTokenCookie(res, accessToken);
232
+ tokenUtils.setRefreshTokenCookie(res, refreshToken);
233
+ const isValidRedirectPath = redirectPath.startsWith("/") && !redirectPath.startsWith("//");
234
+ const finalRedirectPath = isValidRedirectPath ? redirectPath : "/dashboard";
235
+
236
+ res.redirect(`${envVars.FRONTEND_URL}${finalRedirectPath}`);
237
+ })
238
+
239
+ const handleOAuthError = catchAsync((req: Request, res: Response) => {
240
+ const error = req.query.error as string || "oauth_failed";
241
+ res.redirect(`${envVars.FRONTEND_URL}/login?error=${error}`);
242
+ })
243
+
244
+ export const authController = {
245
+ registerUser,
246
+ loginUser,
247
+ getMe,
248
+ getNewToken,
249
+ changePassword,
250
+ logoutUser,
251
+ verifyEmail,
252
+ forgetPassword,
253
+ resetPassword,
254
+ googleLogin,
255
+ googleLoginSuccess,
256
+ handleOAuthError,
257
+ };
@@ -0,0 +1,23 @@
1
+ import { Role } from "@prisma/client";
2
+
3
+ export interface ILoginUserPayload {
4
+ email: string;
5
+ password: string;
6
+ }
7
+
8
+ export interface IRegisterUserPayload {
9
+ name: string;
10
+ email: string;
11
+ password: string;
12
+ }
13
+
14
+ export interface IChangePasswordPayload {
15
+ currentPassword: string;
16
+ newPassword: string;
17
+ }
18
+
19
+ export interface IRequestUser {
20
+ id: string;
21
+ role: Role | string;
22
+ email: string;
23
+ }
@@ -0,0 +1,22 @@
1
+ import { Role } from "@prisma/client";
2
+ import { Router } from "express";
3
+ import { authorize } from "../../shared/middlewares/authorize.middleware";
4
+ import { authController } from "./auth.controller";
5
+
6
+ const router = Router()
7
+
8
+ router.post("/register", authController.registerUser)
9
+ router.post("/login", authController.loginUser)
10
+ router.get("/me", authorize(Role.ADMIN, Role.USER, Role.SUPER_ADMIN), authController.getMe)
11
+ router.post("/refresh-token", authController.getNewToken)
12
+ router.post("/change-password", authorize(Role.ADMIN, Role.USER, Role.SUPER_ADMIN), authController.changePassword)
13
+ router.post("/logout", authorize(Role.ADMIN, Role.USER, Role.SUPER_ADMIN), authController.logoutUser)
14
+ router.post("/verify-email", authController.verifyEmail)
15
+ router.post("/forget-password", authController.forgetPassword)
16
+ router.post("/reset-password", authController.resetPassword)
17
+
18
+ router.get("/login/google", authController.googleLogin);
19
+ router.get("/google/success", authController.googleLoginSuccess);
20
+ router.get("/oauth/error", authController.handleOAuthError);
21
+
22
+ export const authRoutes = router;