@meridianjs/auth 0.1.9 → 0.1.10
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 +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +54 -1
- package/dist/index.mjs +55 -2
- package/package.json +1 -1
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
|
-
"
|
|
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
|
-
"
|
|
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"] });
|