@meridianjs/auth 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -72,6 +72,20 @@ declare class AuthModuleService extends AuthModuleService_base {
72
72
  last_name?: string;
73
73
  role?: UserRole;
74
74
  }): Promise<AuthResult>;
75
+ /** Set (or reset) password for a user. Used after OTP verification. */
76
+ setPassword(userId: string, newPassword: string): Promise<void>;
77
+ /**
78
+ * Generate a password reset token for the given email.
79
+ * Returns the token and user info so the caller can emit an event / send an email.
80
+ * Returns null (instead of throwing) when the email isn't found — prevents enumeration.
81
+ */
82
+ requestPasswordReset(email: string): Promise<{
83
+ token: string;
84
+ userId: string;
85
+ email: string;
86
+ } | null>;
87
+ /** Validate a password reset token and set the new password. */
88
+ resetPassword(token: string, newPassword: string): Promise<void>;
75
89
  /** Verify a JWT and return its decoded payload. Throws if invalid or expired. */
76
90
  verifyToken(token: string, secret: string): JwtPayload;
77
91
  /** Resolve permissions for a given app_role_id — gracefully degrades if module not loaded. */
package/dist/index.d.ts CHANGED
@@ -72,6 +72,20 @@ declare class AuthModuleService extends AuthModuleService_base {
72
72
  last_name?: string;
73
73
  role?: UserRole;
74
74
  }): Promise<AuthResult>;
75
+ /** Set (or reset) password for a user. Used after OTP verification. */
76
+ setPassword(userId: string, newPassword: string): Promise<void>;
77
+ /**
78
+ * Generate a password reset token for the given email.
79
+ * Returns the token and user info so the caller can emit an event / send an email.
80
+ * Returns null (instead of throwing) when the email isn't found — prevents enumeration.
81
+ */
82
+ requestPasswordReset(email: string): Promise<{
83
+ token: string;
84
+ userId: string;
85
+ email: string;
86
+ } | null>;
87
+ /** Validate a password reset token and set the new password. */
88
+ resetPassword(token: string, newPassword: string): Promise<void>;
75
89
  /** Verify a JWT and return its decoded payload. Throws if invalid or expired. */
76
90
  verifyToken(token: string, secret: string): JwtPayload;
77
91
  /** Resolve permissions for a given app_role_id — gracefully degrades if module not loaded. */
package/dist/index.js CHANGED
@@ -49,6 +49,7 @@ var import_crypto = require("crypto");
49
49
  var BCRYPT_ROUNDS = 12;
50
50
  var JWT_EXPIRES_IN = "7d";
51
51
  var JWT_EXPIRES_MS = 7 * 24 * 60 * 60 * 1e3;
52
+ var RESET_TOKEN_EXPIRES_MS = 30 * 60 * 1e3;
52
53
  var AuthModuleService = class extends (0, import_framework_utils.MeridianService)({}) {
53
54
  container;
54
55
  constructor(container) {
@@ -140,7 +141,7 @@ var AuthModuleService = class extends (0, import_framework_utils.MeridianService
140
141
  if (existingByEmail) {
141
142
  throw Object.assign(
142
143
  new Error(
143
- "An account with this email already exists. Please sign in with your password. You can link Google sign-in from your account settings afterwards."
144
+ "GOOGLE_NOT_LINKED: Your Google account is not connected yet. Please sign in with your email and password, then connect Google from your Profile settings to use Google sign-in."
144
145
  ),
145
146
  { status: 409 }
146
147
  );
@@ -153,6 +154,10 @@ var AuthModuleService = class extends (0, import_framework_utils.MeridianService
153
154
  if (!user.is_active) {
154
155
  throw Object.assign(new Error("Account deactivated"), { status: 403 });
155
156
  }
157
+ if (!user.avatar_url && input.picture) {
158
+ await userService.updateUser(user.id, { avatar_url: input.picture }).catch(() => {
159
+ });
160
+ }
156
161
  await userService.recordLogin(user.id).catch(() => {
157
162
  });
158
163
  const permissions2 = await this.resolvePermissions(user.app_role_id);
@@ -187,7 +192,9 @@ var AuthModuleService = class extends (0, import_framework_utils.MeridianService
187
192
  last_name: input.lastName ?? null,
188
193
  role,
189
194
  is_active: true,
195
+ has_password: false,
190
196
  google_id: input.googleId,
197
+ avatar_url: input.picture ?? null,
191
198
  ...invite?.app_role_id ? { app_role_id: invite.app_role_id } : {}
192
199
  });
193
200
  if (invite) {
@@ -236,6 +243,52 @@ var AuthModuleService = class extends (0, import_framework_utils.MeridianService
236
243
  token
237
244
  };
238
245
  }
246
+ /** Set (or reset) password for a user. Used after OTP verification. */
247
+ async setPassword(userId, newPassword) {
248
+ const userService = this.container.resolve("userModuleService");
249
+ const password_hash = await import_bcrypt.default.hash(newPassword, BCRYPT_ROUNDS);
250
+ await userService.updateUser(userId, { password_hash, has_password: true });
251
+ }
252
+ /**
253
+ * Generate a password reset token for the given email.
254
+ * Returns the token and user info so the caller can emit an event / send an email.
255
+ * Returns null (instead of throwing) when the email isn't found — prevents enumeration.
256
+ */
257
+ async requestPasswordReset(email) {
258
+ const userService = this.container.resolve("userModuleService");
259
+ const config = this.container.resolve("config");
260
+ const user = await userService.retrieveUserByEmail(email.toLowerCase().trim());
261
+ if (!user || user.deleted_at || !user.is_active) return null;
262
+ const resetToken = (0, import_crypto.randomBytes)(32).toString("hex");
263
+ const payload = { sub: user.id, purpose: "password_reset", jti: resetToken };
264
+ const signedToken = import_jsonwebtoken.default.sign(payload, config.projectConfig.jwtSecret, { expiresIn: "30m" });
265
+ return { token: signedToken, userId: user.id, email: user.email };
266
+ }
267
+ /** Validate a password reset token and set the new password. */
268
+ async resetPassword(token, newPassword) {
269
+ const config = this.container.resolve("config");
270
+ const userService = this.container.resolve("userModuleService");
271
+ let payload;
272
+ try {
273
+ payload = import_jsonwebtoken.default.verify(token, config.projectConfig.jwtSecret, { algorithms: ["HS256"] });
274
+ } catch (err) {
275
+ if (err.name === "TokenExpiredError") {
276
+ throw Object.assign(new Error("Reset link has expired. Please request a new one."), { status: 400 });
277
+ }
278
+ throw Object.assign(new Error("Invalid reset link"), { status: 400 });
279
+ }
280
+ if (payload.purpose !== "password_reset") {
281
+ throw Object.assign(new Error("Invalid reset link"), { status: 400 });
282
+ }
283
+ const user = await userService.retrieveUser(payload.sub);
284
+ if (!user || user.deleted_at || !user.is_active) {
285
+ throw Object.assign(new Error("Account not found or inactive"), { status: 400 });
286
+ }
287
+ const password_hash = await import_bcrypt.default.hash(newPassword, BCRYPT_ROUNDS);
288
+ await userService.updateUser(payload.sub, { password_hash });
289
+ await userService.revokeAllUserSessions(payload.sub).catch(() => {
290
+ });
291
+ }
239
292
  /** Verify a JWT and return its decoded payload. Throws if invalid or expired. */
240
293
  verifyToken(token, secret) {
241
294
  return import_jsonwebtoken.default.verify(token, secret, { algorithms: ["HS256"] });
package/dist/index.mjs CHANGED
@@ -5,10 +5,11 @@ import { Module } from "@meridianjs/framework-utils";
5
5
  import { MeridianService } from "@meridianjs/framework-utils";
6
6
  import bcrypt from "bcrypt";
7
7
  import jwt from "jsonwebtoken";
8
- import { randomUUID } from "crypto";
8
+ import { randomBytes, randomUUID } from "crypto";
9
9
  var BCRYPT_ROUNDS = 12;
10
10
  var JWT_EXPIRES_IN = "7d";
11
11
  var JWT_EXPIRES_MS = 7 * 24 * 60 * 60 * 1e3;
12
+ var RESET_TOKEN_EXPIRES_MS = 30 * 60 * 1e3;
12
13
  var AuthModuleService = class extends MeridianService({}) {
13
14
  container;
14
15
  constructor(container) {
@@ -100,7 +101,7 @@ var AuthModuleService = class extends MeridianService({}) {
100
101
  if (existingByEmail) {
101
102
  throw Object.assign(
102
103
  new Error(
103
- "An account with this email already exists. Please sign in with your password. You can link Google sign-in from your account settings afterwards."
104
+ "GOOGLE_NOT_LINKED: Your Google account is not connected yet. Please sign in with your email and password, then connect Google from your Profile settings to use Google sign-in."
104
105
  ),
105
106
  { status: 409 }
106
107
  );
@@ -113,6 +114,10 @@ var AuthModuleService = class extends MeridianService({}) {
113
114
  if (!user.is_active) {
114
115
  throw Object.assign(new Error("Account deactivated"), { status: 403 });
115
116
  }
117
+ if (!user.avatar_url && input.picture) {
118
+ await userService.updateUser(user.id, { avatar_url: input.picture }).catch(() => {
119
+ });
120
+ }
116
121
  await userService.recordLogin(user.id).catch(() => {
117
122
  });
118
123
  const permissions2 = await this.resolvePermissions(user.app_role_id);
@@ -147,7 +152,9 @@ var AuthModuleService = class extends MeridianService({}) {
147
152
  last_name: input.lastName ?? null,
148
153
  role,
149
154
  is_active: true,
155
+ has_password: false,
150
156
  google_id: input.googleId,
157
+ avatar_url: input.picture ?? null,
151
158
  ...invite?.app_role_id ? { app_role_id: invite.app_role_id } : {}
152
159
  });
153
160
  if (invite) {
@@ -196,6 +203,52 @@ var AuthModuleService = class extends MeridianService({}) {
196
203
  token
197
204
  };
198
205
  }
206
+ /** Set (or reset) password for a user. Used after OTP verification. */
207
+ async setPassword(userId, newPassword) {
208
+ const userService = this.container.resolve("userModuleService");
209
+ const password_hash = await bcrypt.hash(newPassword, BCRYPT_ROUNDS);
210
+ await userService.updateUser(userId, { password_hash, has_password: true });
211
+ }
212
+ /**
213
+ * Generate a password reset token for the given email.
214
+ * Returns the token and user info so the caller can emit an event / send an email.
215
+ * Returns null (instead of throwing) when the email isn't found — prevents enumeration.
216
+ */
217
+ async requestPasswordReset(email) {
218
+ const userService = this.container.resolve("userModuleService");
219
+ const config = this.container.resolve("config");
220
+ const user = await userService.retrieveUserByEmail(email.toLowerCase().trim());
221
+ if (!user || user.deleted_at || !user.is_active) return null;
222
+ const resetToken = randomBytes(32).toString("hex");
223
+ const payload = { sub: user.id, purpose: "password_reset", jti: resetToken };
224
+ const signedToken = jwt.sign(payload, config.projectConfig.jwtSecret, { expiresIn: "30m" });
225
+ return { token: signedToken, userId: user.id, email: user.email };
226
+ }
227
+ /** Validate a password reset token and set the new password. */
228
+ async resetPassword(token, newPassword) {
229
+ const config = this.container.resolve("config");
230
+ const userService = this.container.resolve("userModuleService");
231
+ let payload;
232
+ try {
233
+ payload = jwt.verify(token, config.projectConfig.jwtSecret, { algorithms: ["HS256"] });
234
+ } catch (err) {
235
+ if (err.name === "TokenExpiredError") {
236
+ throw Object.assign(new Error("Reset link has expired. Please request a new one."), { status: 400 });
237
+ }
238
+ throw Object.assign(new Error("Invalid reset link"), { status: 400 });
239
+ }
240
+ if (payload.purpose !== "password_reset") {
241
+ throw Object.assign(new Error("Invalid reset link"), { status: 400 });
242
+ }
243
+ const user = await userService.retrieveUser(payload.sub);
244
+ if (!user || user.deleted_at || !user.is_active) {
245
+ throw Object.assign(new Error("Account not found or inactive"), { status: 400 });
246
+ }
247
+ const password_hash = await bcrypt.hash(newPassword, BCRYPT_ROUNDS);
248
+ await userService.updateUser(payload.sub, { password_hash });
249
+ await userService.revokeAllUserSessions(payload.sub).catch(() => {
250
+ });
251
+ }
199
252
  /** Verify a JWT and return its decoded payload. Throws if invalid or expired. */
200
253
  verifyToken(token, secret) {
201
254
  return jwt.verify(token, secret, { algorithms: ["HS256"] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meridianjs/auth",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Meridian auth module — JWT authentication and middleware",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",