create-warlock 4.0.29 → 4.0.30

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 (51) hide show
  1. package/package.json +1 -1
  2. package/templates/warlock/package.json +7 -7
  3. package/templates/warlock/src/app/auth/controllers/forgot-password.controller.ts +46 -0
  4. package/templates/warlock/src/app/auth/controllers/login.controller.ts +31 -0
  5. package/templates/warlock/src/app/auth/controllers/logout-all.controller.ts +16 -0
  6. package/templates/warlock/src/app/auth/controllers/logout.controller.ts +16 -0
  7. package/templates/warlock/src/app/auth/controllers/me.controller.ts +13 -0
  8. package/templates/warlock/src/app/auth/controllers/refresh-token.controller.ts +31 -0
  9. package/templates/warlock/src/app/auth/controllers/reset-password.controller.ts +25 -0
  10. package/templates/warlock/src/app/auth/main.ts +9 -0
  11. package/templates/warlock/src/app/auth/models/otp/index.ts +1 -0
  12. package/templates/warlock/src/app/auth/models/otp/migrations/22-12-2025_10-30-20.otp-migration.ts +22 -0
  13. package/templates/warlock/src/app/auth/models/otp/otp.model.ts +75 -0
  14. package/templates/warlock/src/app/auth/requests/login.request.ts +10 -0
  15. package/templates/warlock/src/app/auth/requests/reset-password.request.ts +11 -0
  16. package/templates/warlock/src/app/auth/routes.ts +22 -0
  17. package/templates/warlock/src/app/auth/services/auth.service.ts +47 -0
  18. package/templates/warlock/src/app/auth/services/otp.service.ts +174 -0
  19. package/templates/warlock/src/app/auth/services/reset-password.service.ts +35 -0
  20. package/templates/warlock/src/app/auth/utils/auth-error-code.ts +6 -0
  21. package/templates/warlock/src/app/auth/utils/locales.ts +89 -0
  22. package/templates/warlock/src/app/auth/utils/types.ts +14 -0
  23. package/templates/warlock/src/app/shared/services/scheduler.service.ts +3 -0
  24. package/templates/warlock/src/app/shared/utils/locales.ts +728 -0
  25. package/templates/warlock/src/app/users/commands/hello-world.command.ts +8 -0
  26. package/templates/warlock/src/app/users/controllers/get-users.controller.ts +10 -0
  27. package/templates/warlock/src/app/users/main.ts +0 -0
  28. package/templates/warlock/src/app/users/models/user/index.ts +1 -0
  29. package/templates/warlock/src/app/users/models/user/migrations/11-12-2025_23-58-03-user.migration.ts +14 -0
  30. package/templates/warlock/src/app/users/models/user/user.model.ts +46 -0
  31. package/templates/warlock/src/app/users/repositories/users-repository.ts +66 -0
  32. package/templates/warlock/src/app/users/repositories/users.repository.ts +27 -0
  33. package/templates/warlock/src/app/users/routes.ts +4 -0
  34. package/templates/warlock/src/app/users/services/get-new-customers.ts +5 -0
  35. package/templates/warlock/src/app/users/services/get-users.service.ts +5 -0
  36. package/templates/warlock/src/app/users/services/list-users.service.ts +7 -0
  37. package/templates/warlock/src/app/users/services/login-social.ts +19 -0
  38. package/templates/warlock/src/app/utils/output.ts +5 -0
  39. package/templates/warlock/src/app/utils/router.ts +30 -0
  40. package/templates/warlock/src/config/app.ts +12 -0
  41. package/templates/warlock/src/config/auth.ts +18 -0
  42. package/templates/warlock/src/config/cache.ts +60 -0
  43. package/templates/warlock/src/config/database.ts +19 -0
  44. package/templates/warlock/src/config/google.ts +22 -0
  45. package/templates/warlock/src/config/http.ts +23 -0
  46. package/templates/warlock/src/config/log.ts +22 -0
  47. package/templates/warlock/src/config/mail.ts +16 -0
  48. package/templates/warlock/src/config/notifications.ts +11 -0
  49. package/templates/warlock/src/config/storage.ts +21 -0
  50. package/templates/warlock/src/config/tests.ts +5 -0
  51. package/templates/warlock/src/config/validation.ts +7 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-warlock",
3
- "version": "4.0.29",
3
+ "version": "4.0.30",
4
4
  "main": "./esm/index.js",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -27,13 +27,13 @@
27
27
  "@mongez/reinforcements": "^2.3.12",
28
28
  "@mongez/localization": "^3.2.1",
29
29
  "@mongez/supportive-is": "^2.0.4",
30
- "@warlock.js/auth": "4.0.29",
31
- "@warlock.js/cache": "4.0.29",
32
- "@warlock.js/cascade": "4.0.29",
33
- "@warlock.js/scheduler": "4.0.29",
34
- "@warlock.js/core": "4.0.29",
35
- "@warlock.js/logger": "4.0.29",
36
- "@warlock.js/seal": "4.0.29",
30
+ "@warlock.js/auth": "4.0.30",
31
+ "@warlock.js/cache": "4.0.30",
32
+ "@warlock.js/cascade": "4.0.30",
33
+ "@warlock.js/scheduler": "4.0.30",
34
+ "@warlock.js/core": "4.0.30",
35
+ "@warlock.js/logger": "4.0.30",
36
+ "@warlock.js/seal": "4.0.30",
37
37
  "dayjs": "^1.11.13"
38
38
  },
39
39
  "devDependencies": {
@@ -0,0 +1,46 @@
1
+ import { t, v, type Request, type RequestHandler, type Response } from "@warlock.js/core";
2
+ import { usersRepository } from "app/users/repositories/users.repository";
3
+ import { createOtpService } from "../services/otp.service";
4
+
5
+ /**
6
+ * Forgot password controller
7
+ * POST /auth/forgot-password
8
+ */
9
+ export const forgotPassword: RequestHandler = async (request: Request, response: Response) => {
10
+ const { email } = request.validated();
11
+
12
+ // Find user by email (silent fail for security)
13
+ const user = await usersRepository.first({ email });
14
+
15
+ if (!user) {
16
+ return response.notFound({
17
+ error: t("auth.userNotFound"),
18
+ });
19
+ }
20
+
21
+ // Create OTP
22
+ const otp = await createOtpService({
23
+ target: email,
24
+ channel: "email",
25
+ type: "password-reset",
26
+ userId: user.id,
27
+ userType: user.userType,
28
+ });
29
+
30
+ // TODO: Send email with OTP code
31
+ // await sendPasswordResetEmail(user, otp.get("code"));
32
+ console.log(`[DEV] Password reset OTP for ${email}: ${otp.get("code")}`);
33
+
34
+ // Always return success for security (don't reveal if email exists)
35
+ return response.success({
36
+ message: t("auth.otpSent"),
37
+ });
38
+ };
39
+
40
+ forgotPassword.description = "Request password reset";
41
+
42
+ forgotPassword.validation = {
43
+ schema: v.object({
44
+ email: v.string().email().required(),
45
+ }),
46
+ };
@@ -0,0 +1,31 @@
1
+ import { t, type RequestHandler, type Response } from "@warlock.js/core";
2
+ import { loginSchema, type LoginRequest } from "../requests/login.request";
3
+ import { loginService } from "../services/auth.service";
4
+
5
+ /**
6
+ * Login controller
7
+ * POST /auth/login
8
+ */
9
+ export const login: RequestHandler = async (request: LoginRequest, response: Response) => {
10
+ const result = await loginService(request.validated(), {
11
+ userAgent: request.userAgent,
12
+ ip: request.ip,
13
+ });
14
+
15
+ if (!result) {
16
+ return response.unauthorized({
17
+ error: t("auth.invalidCredentials"),
18
+ });
19
+ }
20
+
21
+ return response.success({
22
+ user: result.user,
23
+ ...result.tokens,
24
+ });
25
+ };
26
+
27
+ login.description = "User Login";
28
+
29
+ login.validation = {
30
+ schema: loginSchema,
31
+ };
@@ -0,0 +1,16 @@
1
+ import { t, type Request, type RequestHandler, type Response } from "@warlock.js/core";
2
+ import { logoutAllService } from "../services/auth.service";
3
+
4
+ /**
5
+ * Logout from all devices controller
6
+ * POST /auth/logout-all
7
+ */
8
+ export const logoutAll: RequestHandler = async (request: Request, response: Response) => {
9
+ await logoutAllService(request.user);
10
+
11
+ return response.success({
12
+ message: t("auth.loggedOutAll"),
13
+ });
14
+ };
15
+
16
+ logoutAll.description = "Logout from all devices";
@@ -0,0 +1,16 @@
1
+ import { t, type Request, type RequestHandler, type Response } from "@warlock.js/core";
2
+ import { logoutService } from "../services/auth.service";
3
+
4
+ /**
5
+ * Logout controller
6
+ * POST /auth/logout
7
+ */
8
+ export const logout: RequestHandler = async (request: Request, response: Response) => {
9
+ await logoutService(request.user);
10
+
11
+ return response.success({
12
+ message: t("auth.loggedOut"),
13
+ });
14
+ };
15
+
16
+ logout.description = "User Logout";
@@ -0,0 +1,13 @@
1
+ import { type Request, type RequestHandler, type Response } from "@warlock.js/core";
2
+
3
+ /**
4
+ * Get current user controller
5
+ * GET /auth/me
6
+ */
7
+ export const me: RequestHandler = async (request: Request, response: Response) => {
8
+ return response.success({
9
+ user: request.user,
10
+ });
11
+ };
12
+
13
+ me.description = "Get Current User";
@@ -0,0 +1,31 @@
1
+ import { t, v, type Request, type RequestHandler, type Response } from "@warlock.js/core";
2
+ import { refreshTokensService } from "../services/auth.service";
3
+
4
+ /**
5
+ * Refresh token controller
6
+ * POST /auth/refresh-token
7
+ */
8
+ export const refreshToken: RequestHandler = async (request: Request, response: Response) => {
9
+ const token = request.input("refreshToken");
10
+
11
+ const result = await refreshTokensService(token, {
12
+ userAgent: request.userAgent,
13
+ ip: request.ip,
14
+ });
15
+
16
+ if (!result) {
17
+ return response.unauthorized({
18
+ error: t("auth.invalidRefreshToken"),
19
+ });
20
+ }
21
+
22
+ return response.success(result);
23
+ };
24
+
25
+ refreshToken.description = "Refresh Access Token";
26
+
27
+ refreshToken.validation = {
28
+ schema: v.object({
29
+ refreshToken: v.string().required(),
30
+ }),
31
+ };
@@ -0,0 +1,25 @@
1
+ import { t, type Response } from "@warlock.js/core";
2
+ import { resetPasswordSchema, type ResetPasswordRequest } from "../requests/reset-password.request";
3
+ import { resetPasswordService } from "../services/reset-password.service";
4
+
5
+ /**
6
+ * Reset password controller
7
+ */
8
+ export const resetPasswordController = async (
9
+ request: ResetPasswordRequest,
10
+ response: Response,
11
+ ) => {
12
+ const { email, code, newPassword } = request.validated();
13
+
14
+ await resetPasswordService(email, code, newPassword);
15
+
16
+ return response.success({
17
+ message: t("auth.passwordResetSuccess"),
18
+ });
19
+ };
20
+
21
+ resetPasswordController.description = "Reset password with OTP";
22
+
23
+ resetPasswordController.validation = {
24
+ schema: resetPasswordSchema,
25
+ };
@@ -0,0 +1,9 @@
1
+ import { onceConnected } from "@warlock.js/cascade";
2
+ import { Job } from "@warlock.js/scheduler";
3
+ import { scheduler } from "app/shared/services/scheduler.service";
4
+ import { cleanupExpiredOtpsService } from "./services/otp.service";
5
+
6
+ onceConnected(() => {
7
+ const cleanupJob = new Job("cleanup-expired-otps", cleanupExpiredOtpsService).everyHour();
8
+ scheduler.addJob(cleanupJob);
9
+ });
@@ -0,0 +1 @@
1
+ export * from "./otp.model";
@@ -0,0 +1,22 @@
1
+ import { migrationOffice } from "@warlock.js/cascade";
2
+ import { OTP } from "../otp.model";
3
+
4
+ const otpBlueprint = OTP.blueprint();
5
+
6
+ export default migrationOffice.register({
7
+ name: "otp",
8
+ createdAt: "22-12-2025_10-30-20",
9
+ blueprint: otpBlueprint,
10
+ up: async () => {
11
+ await otpBlueprint.index("code");
12
+ await otpBlueprint.index(["target", "type"]);
13
+ await otpBlueprint.index("expiresAt");
14
+ await otpBlueprint.index("userId");
15
+ },
16
+ down: async () => {
17
+ await otpBlueprint.dropIndex("code");
18
+ await otpBlueprint.dropIndex("target", "type");
19
+ await otpBlueprint.dropIndex("expiresAt");
20
+ await otpBlueprint.dropIndex("userId");
21
+ },
22
+ });
@@ -0,0 +1,75 @@
1
+ import { Model, type Casts, type Document } from "@warlock.js/cascade";
2
+
3
+ export class OTP extends Model {
4
+ /**
5
+ * Collection name
6
+ */
7
+ public static collection = "otps";
8
+
9
+ /**
10
+ * Default value for model data
11
+ */
12
+ public defaultValue: Document = {
13
+ attempts: 0,
14
+ maxAttempts: 5,
15
+ };
16
+
17
+ /**
18
+ * Cast data types before saving
19
+ */
20
+ protected casts: Casts = {
21
+ code: "string",
22
+ type: "string",
23
+ target: "string",
24
+ channel: "string",
25
+ userId: "number",
26
+ userType: "string",
27
+ expiresAt: "date",
28
+ usedAt: "date",
29
+ attempts: "number",
30
+ maxAttempts: "number",
31
+ metadata: "object",
32
+ };
33
+
34
+ /**
35
+ * Check if OTP is valid (not expired, not used, not max attempts)
36
+ */
37
+ public get isValid(): boolean {
38
+ if (this.get("usedAt")) return false;
39
+ if (this.get("attempts") >= this.get("maxAttempts")) return false;
40
+ if (new Date() > new Date(this.get("expiresAt"))) return false;
41
+ return true;
42
+ }
43
+
44
+ /**
45
+ * Check if OTP is expired
46
+ */
47
+ public get isExpired(): boolean {
48
+ return new Date() > new Date(this.get("expiresAt"));
49
+ }
50
+
51
+ /**
52
+ * Check if max attempts exceeded
53
+ */
54
+ public get isMaxAttemptsExceeded(): boolean {
55
+ return this.get("attempts") >= this.get("maxAttempts");
56
+ }
57
+
58
+ /**
59
+ * Mark OTP as used
60
+ */
61
+ public async markAsUsed(): Promise<this> {
62
+ return this.save({
63
+ usedAt: new Date(),
64
+ });
65
+ }
66
+
67
+ /**
68
+ * Increment failed attempt
69
+ */
70
+ public async incrementAttempt(): Promise<this> {
71
+ return this.save({
72
+ attempts: this.get("attempts") + 1,
73
+ });
74
+ }
75
+ }
@@ -0,0 +1,10 @@
1
+ import { v, type Infer, type Request } from "@warlock.js/core";
2
+
3
+ export const loginSchema = v.object({
4
+ email: v.string().email().required(),
5
+ password: v.string().required(),
6
+ });
7
+
8
+ export type LoginSchema = Infer<typeof loginSchema>;
9
+
10
+ export type LoginRequest = Request<undefined, LoginSchema>;
@@ -0,0 +1,11 @@
1
+ import { v, type Infer, type Request } from "@warlock.js/core";
2
+
3
+ export const resetPasswordSchema = v.object({
4
+ email: v.string().email().required(),
5
+ code: v.string().required(),
6
+ newPassword: v.string().min(8).required(),
7
+ });
8
+
9
+ export type ResetPasswordSchema = Infer<typeof resetPasswordSchema>;
10
+
11
+ export type ResetPasswordRequest = Request<undefined, ResetPasswordSchema>;
@@ -0,0 +1,22 @@
1
+ import { router } from "@warlock.js/core";
2
+ import { guarded } from "app/utils/router";
3
+ import { forgotPassword } from "./controllers/forgot-password.controller";
4
+ import { login } from "./controllers/login.controller";
5
+ import { logoutAll } from "./controllers/logout-all.controller";
6
+ import { logout } from "./controllers/logout.controller";
7
+ import { me } from "./controllers/me.controller";
8
+ import { refreshToken } from "./controllers/refresh-token.controller";
9
+ import { resetPasswordController } from "./controllers/reset-password.controller";
10
+
11
+ // Auth routes
12
+ router.prefix("/auth", () => {
13
+ router.post("/login", login);
14
+ router.post("/refresh-token", refreshToken);
15
+ router.post("/forgot-password", forgotPassword);
16
+ router.post("/reset-password", resetPasswordController);
17
+ guarded(() => {
18
+ router.post("/logout", logout);
19
+ router.post("/logout-all", logoutAll);
20
+ router.get("/me", me);
21
+ });
22
+ });
@@ -0,0 +1,47 @@
1
+ import type { Auth } from "@warlock.js/auth";
2
+ import { authService, type DeviceInfo, type TokenPair } from "@warlock.js/auth";
3
+ import { User } from "app/users/models/user";
4
+
5
+ export type LoginCredentials = {
6
+ email: string;
7
+ password: string;
8
+ };
9
+
10
+ export type LoginResult = {
11
+ user: Auth;
12
+ tokens: TokenPair;
13
+ };
14
+
15
+ /**
16
+ * Login with email and password
17
+ */
18
+ export async function loginService(
19
+ credentials: LoginCredentials,
20
+ deviceInfo?: DeviceInfo,
21
+ ): Promise<LoginResult | null> {
22
+ return authService.login(User, credentials, deviceInfo);
23
+ }
24
+
25
+ /**
26
+ * Logout user
27
+ */
28
+ export async function logoutService(user: Auth): Promise<void> {
29
+ return authService.logout(user);
30
+ }
31
+
32
+ /**
33
+ * Logout from all devices
34
+ */
35
+ export async function logoutAllService(user: Auth): Promise<void> {
36
+ return authService.revokeAllTokens(user);
37
+ }
38
+
39
+ /**
40
+ * Refresh tokens
41
+ */
42
+ export async function refreshTokensService(
43
+ refreshToken: string,
44
+ deviceInfo?: DeviceInfo,
45
+ ): Promise<TokenPair | null> {
46
+ return authService.refreshTokens(refreshToken, deviceInfo);
47
+ }
@@ -0,0 +1,174 @@
1
+ import type { Duration } from "@warlock.js/auth";
2
+ import { ForbiddenError, t } from "@warlock.js/core";
3
+ import { OTP } from "../models/otp";
4
+ import { AuthErrorCode } from "../utils/auth-error-code";
5
+ import type { OTPChannel, OTPType } from "../utils/types";
6
+
7
+ // Default OTP expiration
8
+ const DEFAULT_OTP_EXPIRATION: Duration = { minutes: 15 };
9
+
10
+ // Parse duration to milliseconds
11
+ function parseDurationToMs(duration: Duration): number {
12
+ let ms = 0;
13
+ if (duration.milliseconds) ms += duration.milliseconds;
14
+ if (duration.seconds) ms += duration.seconds * 1000;
15
+ if (duration.minutes) ms += duration.minutes * 60 * 1000;
16
+ if (duration.hours) ms += duration.hours * 60 * 60 * 1000;
17
+ if (duration.days) ms += duration.days * 24 * 60 * 60 * 1000;
18
+ if (duration.weeks) ms += duration.weeks * 7 * 24 * 60 * 60 * 1000;
19
+ return ms;
20
+ }
21
+
22
+ export type CreateOTPOptions = {
23
+ target: string;
24
+ channel: OTPChannel;
25
+ type: OTPType;
26
+ userId?: number;
27
+ userType?: string;
28
+ expiresIn?: Duration;
29
+ length?: number;
30
+ alphanumeric?: boolean;
31
+ maxAttempts?: number;
32
+ metadata?: object;
33
+ };
34
+
35
+ /**
36
+ * Generate OTP code
37
+ */
38
+ function generateCode(length: number = 6, alphanumeric: boolean = false): string {
39
+ if (alphanumeric) {
40
+ // Generate alphanumeric code
41
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
42
+ let code = "";
43
+ for (let i = 0; i < length; i++) {
44
+ code += chars.charAt(Math.floor(Math.random() * chars.length));
45
+ }
46
+ return code;
47
+ }
48
+ // Generate numeric code
49
+ const min = Math.pow(10, length - 1);
50
+ const max = Math.pow(10, length) - 1;
51
+ return Math.floor(min + Math.random() * (max - min + 1)).toString();
52
+ }
53
+
54
+ /**
55
+ * Create a new OTP
56
+ */
57
+ export async function createOtpService(options: CreateOTPOptions): Promise<OTP> {
58
+ const {
59
+ target,
60
+ channel,
61
+ type,
62
+ userId,
63
+ userType,
64
+ expiresIn = DEFAULT_OTP_EXPIRATION,
65
+ length = 6,
66
+ alphanumeric = false,
67
+ maxAttempts = 5,
68
+ metadata,
69
+ } = options;
70
+
71
+ // Invalidate any existing unused OTPs for this target+type
72
+ await cleanupOtpService(target, type);
73
+
74
+ const code = generateCode(length, alphanumeric);
75
+ const expiresAt = new Date(Date.now() + parseDurationToMs(expiresIn));
76
+
77
+ return OTP.create({
78
+ code,
79
+ type,
80
+ target,
81
+ channel,
82
+ userId,
83
+ userType,
84
+ expiresAt,
85
+ maxAttempts,
86
+ metadata,
87
+ });
88
+ }
89
+
90
+ /**
91
+ * Verify an OTP
92
+ */
93
+ export async function verifyOtpService(
94
+ code: string,
95
+ target: string,
96
+ type: OTPType,
97
+ ): Promise<OTP | never> {
98
+ const otp = await OTP.first({
99
+ code,
100
+ target,
101
+ type,
102
+ usedAt: null,
103
+ });
104
+
105
+ if (!otp) {
106
+ throw new ForbiddenError(t("auth.missingOtp"), {
107
+ errorCode: AuthErrorCode.OTP_NOT_FOUND,
108
+ });
109
+ }
110
+
111
+ if (otp.isExpired) {
112
+ throw new ForbiddenError(t("auth.otpExpired"), {
113
+ errorCode: AuthErrorCode.OTP_EXPIRED,
114
+ });
115
+ }
116
+
117
+ if (otp.isMaxAttemptsExceeded) {
118
+ throw new ForbiddenError(t("auth.otpMaxAttempts"), {
119
+ errorCode: AuthErrorCode.OTP_MAX_ATTEMPTS,
120
+ });
121
+ }
122
+
123
+ // Verify code matches
124
+ if (otp.get("code") !== code) {
125
+ await otp.incrementAttempt();
126
+ throw new ForbiddenError(t("auth.otpInvalid"), {
127
+ errorCode: AuthErrorCode.OTP_INVALID,
128
+ });
129
+ }
130
+
131
+ // Mark as used
132
+ await otp.markAsUsed();
133
+
134
+ return otp;
135
+ }
136
+
137
+ export async function cleanupOtpService(target: string, type: OTPType): Promise<void> {
138
+ await OTP.delete({
139
+ target,
140
+ type,
141
+ usedAt: null,
142
+ });
143
+ }
144
+
145
+ /**
146
+ * Resend OTP (invalidate old and create new)
147
+ */
148
+ export async function resendOtpService(
149
+ target: string,
150
+ type: OTPType,
151
+ channel: OTPChannel,
152
+ options?: Partial<CreateOTPOptions>,
153
+ ): Promise<OTP> {
154
+ await cleanupOtpService(target, type);
155
+ return createOtpService({
156
+ target,
157
+ type,
158
+ channel,
159
+ ...options,
160
+ });
161
+ }
162
+
163
+ /**
164
+ * Cleanup expired OTPs
165
+ */
166
+ export async function cleanupExpiredOtpsService(): Promise<number> {
167
+ const expiredOtps = await OTP.aggregate().where("expiresAt", "<", new Date()).get();
168
+
169
+ for (const otp of expiredOtps) {
170
+ await otp.destroy();
171
+ }
172
+
173
+ return expiredOtps.length;
174
+ }
@@ -0,0 +1,35 @@
1
+ import { authService } from "@warlock.js/auth";
2
+ import { BadRequestError, t } from "@warlock.js/core";
3
+ import { User } from "app/users/models/user";
4
+ import { AuthErrorCode } from "../utils/auth-error-code";
5
+ import { verifyOtpService } from "./otp.service";
6
+
7
+ /**
8
+ * Reset user password using OTP verification
9
+ */
10
+ export async function resetPasswordService(
11
+ email: string,
12
+ code: string,
13
+ newPassword: string,
14
+ ): Promise<User> {
15
+ // Verify OTP
16
+ const otp = await verifyOtpService(code, email, "password-reset");
17
+
18
+ // Find user
19
+ const user = await User.find(otp.get("userId"));
20
+
21
+ if (!user) {
22
+ throw new BadRequestError(t("auth.otpInvalid"), {
23
+ errorCode: AuthErrorCode.OTP_INVALID,
24
+ });
25
+ }
26
+
27
+ // Update password
28
+ await user.save({ password: newPassword });
29
+
30
+ // Revoke all tokens (force re-login)
31
+ // Or make it an option through user decision.
32
+ await authService.revokeAllTokens(user);
33
+
34
+ return user;
35
+ }
@@ -0,0 +1,6 @@
1
+ export enum AuthErrorCode {
2
+ OTP_NOT_FOUND = "OTX001", // OTP Not Found
3
+ OTP_EXPIRED = "OTX002", // OTP Expired
4
+ OTP_MAX_ATTEMPTS = "OTX003", // OTP Max Attempts
5
+ OTP_INVALID = "OTX004", // OTP Invalid
6
+ }