create-nexwire 4.0.0 → 4.1.0

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 (175) hide show
  1. package/README.md +5 -4
  2. package/express/.env +1 -1
  3. package/express/README.md +13 -7
  4. package/express/package.json +2 -2
  5. package/express/src/config/session.ts +1 -1
  6. package/express/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
  7. package/express/src/database/migrations/sqlite/meta/_journal.json +1 -1
  8. package/express/src/database/schema.ts +3 -3
  9. package/express/src/framework/cache/cache.ts +82 -81
  10. package/express/src/framework/database/seed.ts +1 -1
  11. package/express/src/framework/facade.ts +1 -24
  12. package/express/src/framework/http/app.ts +5 -5
  13. package/express/src/framework/http/openapi.ts +1 -1
  14. package/express/src/framework/http/router.ts +3 -18
  15. package/express/src/framework/http/types.ts +19 -0
  16. package/express/src/framework/http/validation.ts +18 -18
  17. package/express/src/framework/maker-cli/middleware/core.mjs +41 -47
  18. package/express/src/framework/maker-cli/module/core.mjs +920 -684
  19. package/express/src/framework/maker-cli/module/index.mjs +202 -140
  20. package/express/src/framework/maker-cli/runtime/core.mjs +1 -2
  21. package/express/src/framework/maker-cli/stubs/controller/openapi.ts.stub +40 -14
  22. package/express/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +40 -15
  23. package/express/src/framework/maker-cli/stubs/controller/plain.ts.stub +42 -16
  24. package/express/src/framework/maker-cli/stubs/controller/standalone.ts.stub +47 -0
  25. package/express/src/framework/maker-cli/stubs/facade/name.ts.stub +12 -0
  26. package/express/src/framework/maker-cli/stubs/helper/name.ts.stub +14 -0
  27. package/express/src/framework/maker-cli/stubs/middleware/name.local.ts.stub +15 -0
  28. package/express/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +9 -8
  29. package/express/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +9 -8
  30. package/express/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +9 -8
  31. package/express/src/framework/maker-cli/stubs/notification/route.api.ts.stub +50 -50
  32. package/express/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +11 -11
  33. package/express/src/framework/maker-cli/stubs/route/api.ts.stub +93 -79
  34. package/express/src/framework/maker-cli/stubs/route/plain.ts.stub +23 -10
  35. package/express/src/framework/maker-cli/stubs/route/standalone.api.ts.stub +113 -0
  36. package/express/src/framework/maker-cli/stubs/route/standalone.plain.ts.stub +37 -0
  37. package/express/src/framework/maker-cli/stubs/{controller/schema.plain.ts.stub → schema/name.plain.ts.stub} +13 -13
  38. package/express/src/framework/maker-cli/stubs/{controller/schema.ts.stub → schema/name.ts.stub} +32 -32
  39. package/express/src/framework/maker-cli/stubs/service/name.plain.ts.stub +67 -0
  40. package/express/src/framework/maker-cli/stubs/service/name.ts.stub +65 -0
  41. package/express/src/framework/maker-cli/stubs/type/name.ts.stub +22 -0
  42. package/express/src/framework/maker-cli/utils/naming.mjs +56 -13
  43. package/express/src/framework/modules/discover.ts +57 -54
  44. package/express/src/framework/notification/index.ts +1 -1
  45. package/express/src/framework/realtime/broadcast.ts +46 -46
  46. package/express/src/framework/server.ts +2 -1
  47. package/express/src/framework/session/session.ts +102 -98
  48. package/express/src/framework/support/mail.ts +48 -48
  49. package/express/src/framework/support/status-codes.ts +75 -0
  50. package/express/src/middlewares/auth-middleware.ts +1 -1
  51. package/express/src/modules/auth/__tests__/forgetpass.test.ts +200 -0
  52. package/express/src/modules/auth/__tests__/login.test.ts +171 -0
  53. package/express/src/modules/auth/__tests__/logout.test.ts +60 -0
  54. package/express/src/modules/auth/__tests__/logoutalldevices.test.ts +49 -0
  55. package/express/src/modules/auth/__tests__/me.test.ts +91 -0
  56. package/express/src/modules/auth/__tests__/refreshtoken.test.ts +305 -0
  57. package/express/src/modules/auth/__tests__/register.test.ts +291 -0
  58. package/express/src/modules/auth/__tests__/resetpass.test.ts +206 -0
  59. package/express/src/modules/auth/__tests__/verifyemail.test.ts +183 -0
  60. package/express/src/modules/auth/controllers/auth.ts +254 -0
  61. package/express/src/modules/auth/controllers/{role.controller.ts → role.ts} +25 -15
  62. package/{hono/src/modules/auth/database/models/notifications.ts → express/src/modules/auth/database/models/notification.ts} +1 -1
  63. package/express/src/modules/auth/database/models/role.ts +1 -1
  64. package/express/src/modules/auth/database/models/user.ts +1 -1
  65. package/express/src/modules/auth/facade.ts +12 -0
  66. package/express/src/modules/auth/{controllers/auth.helpers.ts → helpers/auth.ts} +3 -4
  67. package/express/src/modules/auth/jobs/registeruser.ts +23 -23
  68. package/express/src/modules/auth/routes/{api.ts → index.ts} +150 -150
  69. package/express/src/modules/auth/routes/role.ts +33 -33
  70. package/{hono/src/modules/auth/controllers/auth.schema.ts → express/src/modules/auth/schemas/auth.ts} +102 -102
  71. package/express/src/modules/auth/services/forgetpassword.ts +66 -0
  72. package/express/src/modules/auth/services/login.ts +63 -0
  73. package/express/src/modules/auth/services/logout.ts +25 -0
  74. package/express/src/modules/auth/services/logoutalldevices.ts +19 -0
  75. package/express/src/modules/auth/services/me.ts +31 -0
  76. package/express/src/modules/auth/services/refreshtoken.ts +110 -0
  77. package/express/src/modules/auth/services/register.ts +141 -0
  78. package/express/src/modules/auth/services/resetpassword.ts +69 -0
  79. package/express/src/modules/auth/services/verifyemail.ts +77 -0
  80. package/express/src/modules/auth/types/auth.ts +31 -0
  81. package/express/src/resources/src/assets/images/logo.png +0 -0
  82. package/express/src/resources/src/pages/auth/forgetPassword.vue +1 -0
  83. package/express/src/resources/src/pages/auth/login.vue +1 -1
  84. package/express/src/storage/database/nexwire.sqlite +0 -0
  85. package/hono/.env +1 -1
  86. package/hono/README.md +13 -7
  87. package/hono/package.json +1 -1
  88. package/hono/src/config/session.ts +1 -1
  89. package/hono/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
  90. package/hono/src/database/migrations/sqlite/meta/_journal.json +1 -1
  91. package/hono/src/database/schema.ts +3 -3
  92. package/hono/src/framework/cache/cache.ts +82 -81
  93. package/hono/src/framework/database/seed.ts +1 -1
  94. package/hono/src/framework/http/app.ts +5 -5
  95. package/hono/src/framework/http/validation.ts +21 -21
  96. package/hono/src/framework/maker-cli/middleware/core.mjs +40 -46
  97. package/hono/src/framework/maker-cli/module/core.mjs +401 -168
  98. package/hono/src/framework/maker-cli/module/index.mjs +73 -11
  99. package/hono/src/framework/maker-cli/stubs/controller/openapi.ts.stub +28 -16
  100. package/hono/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +28 -17
  101. package/hono/src/framework/maker-cli/stubs/controller/plain.ts.stub +28 -16
  102. package/hono/src/framework/maker-cli/stubs/controller/standalone.ts.stub +56 -0
  103. package/hono/src/framework/maker-cli/stubs/facade/name.ts.stub +12 -0
  104. package/hono/src/framework/maker-cli/stubs/helper/name.ts.stub +14 -0
  105. package/hono/src/framework/maker-cli/stubs/middleware/name.local.ts.stub +15 -0
  106. package/hono/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +9 -8
  107. package/hono/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +9 -8
  108. package/hono/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +9 -8
  109. package/hono/src/framework/maker-cli/stubs/notification/route.api.ts.stub +49 -49
  110. package/hono/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +11 -11
  111. package/hono/src/framework/maker-cli/stubs/route/api.ts.stub +93 -79
  112. package/hono/src/framework/maker-cli/stubs/route/plain.ts.stub +23 -10
  113. package/hono/src/framework/maker-cli/stubs/route/standalone.api.ts.stub +112 -0
  114. package/hono/src/framework/maker-cli/stubs/route/standalone.plain.ts.stub +37 -0
  115. package/hono/src/framework/maker-cli/stubs/{controller/schema.plain.ts.stub → schema/name.plain.ts.stub} +13 -13
  116. package/hono/src/framework/maker-cli/stubs/{controller/schema.ts.stub → schema/name.ts.stub} +32 -32
  117. package/hono/src/framework/maker-cli/stubs/service/name.plain.ts.stub +67 -0
  118. package/hono/src/framework/maker-cli/stubs/service/name.ts.stub +65 -0
  119. package/hono/src/framework/maker-cli/stubs/type/name.ts.stub +22 -0
  120. package/hono/src/framework/maker-cli/utils/naming.mjs +56 -13
  121. package/hono/src/framework/modules/discover.ts +57 -54
  122. package/hono/src/framework/notification/index.ts +1 -1
  123. package/hono/src/framework/realtime/broadcast.ts +46 -46
  124. package/hono/src/framework/server.ts +1 -1
  125. package/hono/src/framework/session/session.ts +121 -116
  126. package/hono/src/middlewares/auth-middleware.ts +1 -1
  127. package/hono/src/modules/auth/__tests__/forgetpass.test.ts +170 -0
  128. package/hono/src/modules/auth/__tests__/login.test.ts +154 -0
  129. package/hono/src/modules/auth/__tests__/logout.test.ts +53 -0
  130. package/hono/src/modules/auth/__tests__/logoutalldevices.test.ts +34 -0
  131. package/hono/src/modules/auth/__tests__/me.test.ts +87 -0
  132. package/hono/src/modules/auth/__tests__/refreshtoken.test.ts +305 -0
  133. package/hono/src/modules/auth/__tests__/register.test.ts +258 -0
  134. package/hono/src/modules/auth/__tests__/resetpass.test.ts +214 -0
  135. package/hono/src/modules/auth/__tests__/verifyemail.test.ts +189 -0
  136. package/hono/src/modules/auth/controllers/auth.ts +238 -0
  137. package/hono/src/modules/auth/controllers/{role.controller.ts → role.ts} +25 -25
  138. package/{express/src/modules/auth/database/models/notifications.ts → hono/src/modules/auth/database/models/notification.ts} +1 -1
  139. package/hono/src/modules/auth/database/models/role.ts +1 -1
  140. package/hono/src/modules/auth/database/models/user.ts +1 -1
  141. package/hono/src/modules/auth/facade.ts +12 -0
  142. package/hono/src/modules/auth/{controllers/auth.helpers.ts → helpers/auth.ts} +5 -6
  143. package/hono/src/modules/auth/jobs/registeruser.ts +23 -23
  144. package/hono/src/modules/auth/routes/{api.ts → index.ts} +150 -150
  145. package/hono/src/modules/auth/routes/role.ts +33 -33
  146. package/{express/src/modules/auth/controllers/auth.schema.ts → hono/src/modules/auth/schemas/auth.ts} +102 -102
  147. package/hono/src/modules/auth/services/forgetpassword.ts +58 -0
  148. package/hono/src/modules/auth/services/login.ts +61 -0
  149. package/hono/src/modules/auth/services/logout.ts +24 -0
  150. package/hono/src/modules/auth/services/logoutalldevices.ts +19 -0
  151. package/hono/src/modules/auth/services/me.ts +31 -0
  152. package/hono/src/modules/auth/services/refreshtoken.ts +110 -0
  153. package/hono/src/modules/auth/services/register.ts +132 -0
  154. package/hono/src/modules/auth/services/resetpassword.ts +69 -0
  155. package/hono/src/modules/auth/services/verifyemail.ts +77 -0
  156. package/hono/src/modules/auth/types/auth.ts +31 -0
  157. package/hono/src/resources/src/assets/images/logo.png +0 -0
  158. package/hono/src/storage/database/nexwire.sqlite +0 -0
  159. package/package.json +1 -1
  160. package/express/src/framework/maker-cli/stubs/example/console.ts.stub +0 -34
  161. package/express/src/framework/maker-cli/stubs/example/controller.ts.stub +0 -418
  162. package/express/src/framework/maker-cli/stubs/example/job.ts.stub +0 -75
  163. package/express/src/framework/maker-cli/stubs/example/route.api.ts.stub +0 -207
  164. package/express/src/framework/maker-cli/stubs/example/route.plain.ts.stub +0 -18
  165. package/express/src/framework/maker-cli/stubs/example/schema.plain.ts.stub +0 -42
  166. package/express/src/framework/maker-cli/stubs/example/schema.ts.stub +0 -42
  167. package/express/src/modules/auth/controllers/auth.controller.ts +0 -358
  168. package/hono/src/framework/maker-cli/stubs/example/console.ts.stub +0 -34
  169. package/hono/src/framework/maker-cli/stubs/example/controller.ts.stub +0 -505
  170. package/hono/src/framework/maker-cli/stubs/example/job.ts.stub +0 -75
  171. package/hono/src/framework/maker-cli/stubs/example/route.api.ts.stub +0 -207
  172. package/hono/src/framework/maker-cli/stubs/example/route.plain.ts.stub +0 -18
  173. package/hono/src/framework/maker-cli/stubs/example/schema.plain.ts.stub +0 -42
  174. package/hono/src/framework/maker-cli/stubs/example/schema.ts.stub +0 -42
  175. package/hono/src/modules/auth/controllers/auth.controller.ts +0 -413
@@ -1,413 +0,0 @@
1
- import { and, eq, gt, lt } from "drizzle-orm";
2
- import type { Handler } from "hono";
3
- import { authConfig, jwtConfig } from "@/config/index.js";
4
- import { cookie, db, dispatchEvent, HttpStatusCodes, jwt, password, urls } from "@/framework/facade.js";
5
- import { roles } from "@/modules/auth/database/models/role.js";
6
- import { emailVerificationTokens, passwordResetTokens, refreshTokens, users } from "@/modules/auth/database/models/user.js";
7
- import {
8
- hashEmailVerificationToken,
9
- hashResetToken,
10
- issueTokens,
11
- makeEmailVerificationToken,
12
- makeResetToken,
13
- revokeCurrentRefreshToken,
14
- sanitizeUser
15
- } from "./auth.helpers.js";
16
-
17
- /**
18
- * Why: Creates a new user, issues tokens, and triggers signup side effects.
19
- * When: Used on first-time account creation.
20
- * Where: POST auth register route.
21
- */
22
- export const register: Handler = async (c: any) => {
23
- try {
24
- const body = c.req.valid("json");
25
- const defaultRole = await db.query.roles.findFirst({
26
- where: eq(roles.name, "user")
27
- });
28
- const existingUser = await db.query.users.findFirst({
29
- where: eq(users.email, body.email)
30
- });
31
-
32
- if (existingUser) {
33
- return c.json({ message: "Email already exists" }, HttpStatusCodes.UNPROCESSABLE_ENTITY);
34
- }
35
-
36
- const insertResult = await db.insert(users).values({
37
- name: body.name,
38
- email: body.email,
39
- password: await password.hashPassword(body.password),
40
- roleId: defaultRole?.id ?? null
41
- });
42
-
43
- const insertedId = Number(
44
- (insertResult as any)[0]?.insertId ?? (insertResult as any).insertId ?? (insertResult as any).lastInsertRowid
45
- );
46
- if (!insertedId) {
47
- throw new Error("Failed to resolve inserted user id");
48
- }
49
-
50
- const user = await db.query.users.findFirst({
51
- where: eq(users.id, insertedId),
52
- with: { role: true }
53
- });
54
-
55
- if (!user) throw new Error("Inserted user not found");
56
-
57
- if (authConfig.requireEmailVerification) {
58
- const plainToken = makeEmailVerificationToken();
59
- const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000);
60
-
61
- await db.delete(emailVerificationTokens).where(eq(emailVerificationTokens.email, user.email));
62
- await db.insert(emailVerificationTokens).values({
63
- email: user.email,
64
- token: hashEmailVerificationToken(plainToken),
65
- expiresAt,
66
- createdAt: new Date()
67
- });
68
-
69
- const verifyUrl = urls.url(`/verify-email?token=${plainToken}&email=${encodeURIComponent(user.email)}`);
70
- await dispatchEvent("user:verify-email", { email: user.email, name: user.name, verifyUrl }, { queue: "mail" });
71
-
72
- return c.json({ message: "User registered successfully. Please verify your email before logging in." }, HttpStatusCodes.CREATED);
73
- }
74
-
75
- await revokeCurrentRefreshToken(c);
76
- const tokens = await issueTokens(c, user, { remember: !!body.remember });
77
- await dispatchEvent(
78
- "user:signup",
79
- {
80
- userId: user.id,
81
- email: user.email,
82
- name: user.name,
83
- password: body.password
84
- },
85
- { queue: "mail" }
86
- );
87
-
88
- return c.json(
89
- {
90
- message: "User registered successfully",
91
- data: {
92
- user: sanitizeUser(user),
93
- access_token: tokens.accessToken,
94
- refresh_token: tokens.refreshToken,
95
- token_type: "Bearer"
96
- }
97
- },
98
- HttpStatusCodes.CREATED
99
- );
100
- } catch (error) {
101
- console.error("Register error:", error);
102
- return c.json({ message: "Failed to register user" }, HttpStatusCodes.INTERNAL_SERVER_ERROR);
103
- }
104
- };
105
-
106
- /**
107
- * Why: Authenticates user credentials and rotates active login cookies/tokens.
108
- * When: Used whenever a user signs in.
109
- * Where: POST auth login route.
110
- */
111
- export const login: Handler = async (c: any) => {
112
- try {
113
- const body = c.req.valid("json");
114
- const user = await db.query.users.findFirst({ where: eq(users.email, body.email), with: { role: true } });
115
-
116
- if (!user || !(await password.verifyPassword(body.password, user.password))) {
117
- return c.json({ message: "Invalid credentials" }, HttpStatusCodes.UNAUTHORIZED);
118
- }
119
-
120
- if (authConfig.requireEmailVerification && !user.emailVerifiedAt) {
121
- return c.json({ message: "Please verify your email before logging in" }, HttpStatusCodes.FORBIDDEN);
122
- }
123
-
124
- await revokeCurrentRefreshToken(c);
125
- const tokens = await issueTokens(c, user, { remember: !!body.remember });
126
-
127
- return c.json(
128
- {
129
- message: "User logged in successfully",
130
- data: {
131
- user: sanitizeUser(user),
132
- access_token: tokens.accessToken,
133
- refresh_token: tokens.refreshToken,
134
- token_type: "Bearer"
135
- }
136
- },
137
- HttpStatusCodes.OK
138
- );
139
- } catch (error) {
140
- console.error("Login error:", error);
141
- return c.json({ message: "Failed to login" }, HttpStatusCodes.INTERNAL_SERVER_ERROR);
142
- }
143
- };
144
-
145
- /**
146
- * Why: Returns the currently authenticated user profile.
147
- * When: Used by clients to bootstrap session/user state.
148
- * Where: GET auth me route.
149
- */
150
- export const me: Handler = async (c: any) => {
151
- try {
152
- const auth = c.get("auth");
153
- const user = await db.query.users.findFirst({
154
- where: eq(users.id, auth.id),
155
- with: { role: true }
156
- });
157
-
158
- if (!user) return c.json({ message: "User not found" }, HttpStatusCodes.NOT_FOUND);
159
-
160
- return c.json(
161
- {
162
- message: "Authenticated user fetched successfully",
163
- data: sanitizeUser(user)
164
- },
165
- HttpStatusCodes.OK
166
- );
167
- } catch (error) {
168
- console.error("Me error:", error);
169
- return c.json({ message: "Failed to fetch user" }, HttpStatusCodes.INTERNAL_SERVER_ERROR);
170
- }
171
- };
172
-
173
- /**
174
- * Why: Revokes current refresh token and clears auth cookies.
175
- * When: Used when the active device logs out.
176
- * Where: POST auth logout route.
177
- */
178
- export const logout: Handler = async (c: any) => {
179
- try {
180
- await revokeCurrentRefreshToken(c);
181
- cookie.deleteAuth(c);
182
- cookie.deleteRefresh(c);
183
- return c.json({ message: "Logged out successfully" }, HttpStatusCodes.OK);
184
- } catch (error) {
185
- console.error("Logout error:", error);
186
- cookie.deleteAuth(c);
187
- cookie.deleteRefresh(c);
188
- return c.json({ message: "Failed to logout" }, HttpStatusCodes.INTERNAL_SERVER_ERROR);
189
- }
190
- };
191
-
192
- /**
193
- * Why: Starts password reset flow and queues email delivery event.
194
- * When: Used when user requests a forgot-password link.
195
- * Where: POST auth forgot-password route.
196
- */
197
- export const forgotPassword: Handler = async (c: any) => {
198
- try {
199
- const body = c.req.valid("json");
200
- await db
201
- .delete(passwordResetTokens)
202
- .where(and(eq(passwordResetTokens.email, body.email), lt(passwordResetTokens.expiresAt, new Date())));
203
-
204
- const user = await db.query.users.findFirst({
205
- where: eq(users.email, body.email)
206
- });
207
-
208
- if (!user) return c.json({ message: "Reset link has been sent" }, HttpStatusCodes.OK);
209
-
210
- const plainToken = makeResetToken();
211
- const expiresAt = new Date(Date.now() + 15 * 60 * 1000);
212
-
213
- await db.delete(passwordResetTokens).where(eq(passwordResetTokens.email, user.email));
214
- await db.insert(passwordResetTokens).values({
215
- email: user.email,
216
- token: hashResetToken(plainToken),
217
- expiresAt,
218
- createdAt: new Date()
219
- });
220
-
221
- const resetUrl = urls.url(`/reset-password?token=${plainToken}&email=${encodeURIComponent(user.email)}`);
222
- await dispatchEvent("user:forget-password", { email: user.email, name: user.name, resetUrl }, { queue: "mail" });
223
-
224
- return c.json({ message: "Reset link passed" }, HttpStatusCodes.OK);
225
- } catch (error) {
226
- console.error("Forgot password error:", error);
227
- return c.json({ message: "Failed to process forgot password request" }, HttpStatusCodes.INTERNAL_SERVER_ERROR);
228
- }
229
- };
230
-
231
- /**
232
- * Why: Verifies reset token and persists the new password.
233
- * When: Used after user submits reset token + new password.
234
- * Where: POST auth reset-password route.
235
- */
236
- export const resetPassword: Handler = async (c: any) => {
237
- try {
238
- const body = c.req.valid("json");
239
- const record = await db.query.passwordResetTokens.findFirst({
240
- where: and(
241
- eq(passwordResetTokens.email, body.email),
242
- eq(passwordResetTokens.token, hashResetToken(body.token)),
243
- gt(passwordResetTokens.expiresAt, new Date())
244
- )
245
- });
246
-
247
- if (!record) {
248
- return c.json({ message: "Invalid or expired reset token" }, HttpStatusCodes.UNPROCESSABLE_ENTITY);
249
- }
250
-
251
- await db
252
- .update(users)
253
- .set({
254
- password: await password.hashPassword(body.password),
255
- updatedAt: new Date()
256
- })
257
- .where(eq(users.email, body.email));
258
-
259
- await db.delete(passwordResetTokens).where(eq(passwordResetTokens.email, body.email));
260
-
261
- const user = await db.query.users.findFirst({
262
- where: eq(users.email, body.email)
263
- });
264
- if (user) await db.update(refreshTokens).set({ revoked: 1 }).where(eq(refreshTokens.userId, user.id));
265
-
266
- return c.json({ message: "Password reset successfully" }, HttpStatusCodes.OK);
267
- } catch (error) {
268
- console.error("Reset password error:", error);
269
- return c.json({ message: "Failed to reset password" }, HttpStatusCodes.INTERNAL_SERVER_ERROR);
270
- }
271
- };
272
-
273
- /**
274
- * Why: Validates email verification token and marks user as verified.
275
- * When: Used when user opens verification link from inbox.
276
- * Where: POST auth verify-email route.
277
- */
278
- export const verifyEmail: Handler = async (c: any) => {
279
- try {
280
- const body = c.req.valid("json");
281
-
282
- if (!authConfig.requireEmailVerification) {
283
- return c.json({ message: "Email verification is not required" }, HttpStatusCodes.OK);
284
- }
285
-
286
- const record = await db.query.emailVerificationTokens.findFirst({
287
- where: and(
288
- eq(emailVerificationTokens.email, body.email),
289
- eq(emailVerificationTokens.token, hashEmailVerificationToken(body.token)),
290
- gt(emailVerificationTokens.expiresAt, new Date())
291
- )
292
- });
293
-
294
- if (!record) {
295
- return c.json({ message: "Invalid or expired verification token" }, HttpStatusCodes.UNPROCESSABLE_ENTITY);
296
- }
297
-
298
- await db.update(users).set({ emailVerifiedAt: new Date(), updatedAt: new Date() }).where(eq(users.email, body.email));
299
- await db.delete(emailVerificationTokens).where(eq(emailVerificationTokens.email, body.email));
300
-
301
- return c.json({ message: "Email verified successfully" }, HttpStatusCodes.OK);
302
- } catch (error) {
303
- console.error("Verify email error:", error);
304
- return c.json({ message: "Failed to verify email" }, HttpStatusCodes.INTERNAL_SERVER_ERROR);
305
- }
306
- };
307
-
308
- /**
309
- * Why: Rotates refresh token and reissues access credentials.
310
- * When: Used when access token expires but refresh token is still valid.
311
- * Where: POST auth refresh-token route.
312
- */
313
- export const refreshToken: Handler = async (c: any) => {
314
- try {
315
- const body = c.req.valid("json");
316
- const payload = await jwt.verifyToken(body.refresh_token, "refresh");
317
-
318
- if (!payload?.jti) return c.json({ message: "Invalid refresh token" }, HttpStatusCodes.UNAUTHORIZED);
319
-
320
- const storedToken = await db.query.refreshTokens.findFirst({
321
- where: eq(refreshTokens.jti, payload.jti as string)
322
- });
323
-
324
- if (!storedToken || storedToken.revoked === 1) {
325
- return c.json({ message: "Refresh token revoked" }, HttpStatusCodes.UNAUTHORIZED);
326
- }
327
-
328
- if (storedToken.expiresAt.getTime() < Date.now()) {
329
- await db.delete(refreshTokens).where(eq(refreshTokens.id, storedToken.id));
330
- return c.json({ message: "Refresh token expired" }, HttpStatusCodes.UNAUTHORIZED);
331
- }
332
-
333
- const user = await db.query.users.findFirst({
334
- where: eq(users.id, payload.id as number),
335
- with: { role: true }
336
- });
337
- if (!user) return c.json({ message: "User not found" }, HttpStatusCodes.UNAUTHORIZED);
338
-
339
- const remember = !!payload.remember;
340
- const refreshExpiry = remember ? jwtConfig.refreshRememberExpirySeconds : undefined;
341
- const accessToken = await jwt.generateToken(
342
- {
343
- id: user.id,
344
- email: user.email,
345
- roleId: user.role?.id,
346
- role: user.role?.name,
347
- remember
348
- },
349
- "access"
350
- );
351
- const newRefreshToken = await jwt.generateToken(
352
- {
353
- id: user.id,
354
- email: user.email,
355
- roleId: user.role?.id,
356
- role: user.role?.name,
357
- remember
358
- },
359
- "refresh",
360
- refreshExpiry
361
- );
362
-
363
- await db
364
- .update(refreshTokens)
365
- .set({
366
- jti: newRefreshToken.jti as string,
367
- expiresAt: new Date(newRefreshToken.exp * 1000),
368
- revoked: 0
369
- })
370
- .where(eq(refreshTokens.id, storedToken.id));
371
-
372
- await cookie.setAuth(c, accessToken.token);
373
- await cookie.setRefresh(c, newRefreshToken.token, refreshExpiry);
374
-
375
- return c.json(
376
- {
377
- message: "Token refreshed successfully",
378
- data: {
379
- user: sanitizeUser(user),
380
- access_token: accessToken.token,
381
- refresh_token: newRefreshToken.token,
382
- token_type: "Bearer"
383
- }
384
- },
385
- HttpStatusCodes.OK
386
- );
387
- } catch (error) {
388
- console.error("Refresh token error:", error);
389
- return c.json({ message: "Invalid or expired refresh token" }, HttpStatusCodes.UNAUTHORIZED);
390
- }
391
- };
392
-
393
- /**
394
- * Why: Revokes all refresh tokens for account-wide logout.
395
- * When: Used for "logout from all devices" security action.
396
- * Where: POST auth logout-all-devices route.
397
- */
398
- export const logoutAllDevices: Handler = async (c: any) => {
399
- try {
400
- const auth = c.get("auth");
401
-
402
- if (!auth) return c.json({ message: "Unauthorized" }, HttpStatusCodes.UNAUTHORIZED);
403
-
404
- await db.delete(refreshTokens).where(eq(refreshTokens.userId, auth.id));
405
- cookie.deleteAuth(c);
406
- cookie.deleteRefresh(c);
407
-
408
- return c.json({ message: "Logged out from all devices successfully" }, HttpStatusCodes.OK);
409
- } catch (error) {
410
- console.error("Logout all devices error:", error);
411
- return c.json({ message: "Failed to logout from all devices" }, HttpStatusCodes.INTERNAL_SERVER_ERROR);
412
- }
413
- };