@xcelsior/auth 1.0.0 → 1.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.
- package/.turbo/turbo-build.log +13 -13
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test.log +1 -1
- package/dist/index.d.mts +263 -34
- package/dist/index.d.ts +263 -34
- package/dist/index.js +306 -182
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +303 -188
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -9
- package/src/email/smtp.ts +3 -2
- package/src/email/types.ts +1 -1
- package/src/index.ts +1 -0
- package/src/middleware/auth.ts +2 -4
- package/src/services/auth.ts +338 -27
- package/src/storage/index.ts +0 -17
- package/src/storage/types.ts +49 -24
- package/src/types/index.ts +57 -6
- package/src/storage/dynamodb.ts +0 -153
package/dist/index.js
CHANGED
|
@@ -32,7 +32,9 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
AuthMiddleware: () => AuthMiddleware,
|
|
34
34
|
AuthService: () => AuthService,
|
|
35
|
-
|
|
35
|
+
CreateSessionSchema: () => CreateSessionSchema,
|
|
36
|
+
CreateUserSchema: () => CreateUserSchema,
|
|
37
|
+
SessionSchema: () => SessionSchema,
|
|
36
38
|
UserSchema: () => UserSchema
|
|
37
39
|
});
|
|
38
40
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -41,170 +43,10 @@ module.exports = __toCommonJS(index_exports);
|
|
|
41
43
|
var import_bcryptjs = __toESM(require("bcryptjs"));
|
|
42
44
|
var import_jsonwebtoken = __toESM(require("jsonwebtoken"));
|
|
43
45
|
var import_uuid = require("uuid");
|
|
44
|
-
|
|
45
|
-
// src/storage/dynamodb.ts
|
|
46
|
-
var import_client_dynamodb = require("@aws-sdk/client-dynamodb");
|
|
47
|
-
var import_lib_dynamodb = require("@aws-sdk/lib-dynamodb");
|
|
48
|
-
var DynamoDBStorageProvider = class {
|
|
49
|
-
constructor(config) {
|
|
50
|
-
const dbClient = new import_client_dynamodb.DynamoDBClient({ region: config.region });
|
|
51
|
-
this.client = import_lib_dynamodb.DynamoDBDocumentClient.from(dbClient, {});
|
|
52
|
-
this.tableName = config.tableName;
|
|
53
|
-
}
|
|
54
|
-
async createUser(user) {
|
|
55
|
-
await this.client.send(
|
|
56
|
-
new import_lib_dynamodb.PutCommand({
|
|
57
|
-
TableName: this.tableName,
|
|
58
|
-
Item: user,
|
|
59
|
-
ConditionExpression: "attribute_not_exists(email)"
|
|
60
|
-
})
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
async getUserByResetPasswordToken(resetPasswordToken) {
|
|
64
|
-
const response = await this.client.send(
|
|
65
|
-
new import_lib_dynamodb.QueryCommand({
|
|
66
|
-
TableName: this.tableName,
|
|
67
|
-
IndexName: "ResetPasswordTokenIndex",
|
|
68
|
-
KeyConditionExpression: "resetPasswordToken = :token",
|
|
69
|
-
ExpressionAttributeValues: {
|
|
70
|
-
":token": resetPasswordToken
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
);
|
|
74
|
-
return response.Items?.[0];
|
|
75
|
-
}
|
|
76
|
-
async getUserByVerifyEmailToken(verifyEmailToken) {
|
|
77
|
-
const response = await this.client.send(
|
|
78
|
-
new import_lib_dynamodb.QueryCommand({
|
|
79
|
-
TableName: this.tableName,
|
|
80
|
-
IndexName: "VerifyEmailTokenIndex",
|
|
81
|
-
KeyConditionExpression: "verificationToken = :token",
|
|
82
|
-
ExpressionAttributeValues: {
|
|
83
|
-
":token": verifyEmailToken
|
|
84
|
-
}
|
|
85
|
-
})
|
|
86
|
-
);
|
|
87
|
-
return response.Items?.[0];
|
|
88
|
-
}
|
|
89
|
-
async getUserById(id) {
|
|
90
|
-
const response = await this.client.send(
|
|
91
|
-
new import_lib_dynamodb.GetCommand({
|
|
92
|
-
TableName: this.tableName,
|
|
93
|
-
Key: { id }
|
|
94
|
-
})
|
|
95
|
-
);
|
|
96
|
-
return response.Item;
|
|
97
|
-
}
|
|
98
|
-
async getUserByEmail(email) {
|
|
99
|
-
const response = await this.client.send(
|
|
100
|
-
new import_lib_dynamodb.QueryCommand({
|
|
101
|
-
TableName: this.tableName,
|
|
102
|
-
IndexName: "EmailIndex",
|
|
103
|
-
KeyConditionExpression: "email = :email",
|
|
104
|
-
ExpressionAttributeValues: {
|
|
105
|
-
":email": email
|
|
106
|
-
}
|
|
107
|
-
})
|
|
108
|
-
);
|
|
109
|
-
return response.Items?.[0];
|
|
110
|
-
}
|
|
111
|
-
async updateUser(id, updates) {
|
|
112
|
-
const toSet = {};
|
|
113
|
-
const toRemove = [];
|
|
114
|
-
Object.entries(updates).forEach(([key, value]) => {
|
|
115
|
-
if (value === void 0) {
|
|
116
|
-
toRemove.push(key);
|
|
117
|
-
} else {
|
|
118
|
-
toSet[key] = value;
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
if (Object.keys(toSet).length === 0 && toRemove.length === 0) {
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
const setPart = Object.keys(toSet).length > 0 ? `SET ${Object.keys(toSet).map((key) => `#${key} = :${key}`).join(", ")}` : "";
|
|
125
|
-
const removePart = toRemove.length > 0 ? `REMOVE ${toRemove.map((key) => `#${key}`).join(", ")}` : "";
|
|
126
|
-
const updateExpression = [setPart, removePart].filter(Boolean).join(" ");
|
|
127
|
-
const expressionAttributeNames = [...Object.keys(toSet), ...toRemove].reduce(
|
|
128
|
-
(acc, key) => ({ ...acc, [`#${key}`]: key }),
|
|
129
|
-
{}
|
|
130
|
-
);
|
|
131
|
-
const expressionAttributeValues = Object.entries(toSet).reduce(
|
|
132
|
-
(acc, [key, value]) => ({ ...acc, [`:${key}`]: value }),
|
|
133
|
-
{}
|
|
134
|
-
);
|
|
135
|
-
await this.client.send(
|
|
136
|
-
new import_lib_dynamodb.UpdateCommand({
|
|
137
|
-
TableName: this.tableName,
|
|
138
|
-
Key: { id },
|
|
139
|
-
UpdateExpression: updateExpression,
|
|
140
|
-
ExpressionAttributeNames: expressionAttributeNames,
|
|
141
|
-
...Object.keys(expressionAttributeValues).length > 0 && {
|
|
142
|
-
ExpressionAttributeValues: expressionAttributeValues
|
|
143
|
-
}
|
|
144
|
-
})
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
async deleteUser(id) {
|
|
148
|
-
await this.client.send(
|
|
149
|
-
new import_lib_dynamodb.DeleteCommand({
|
|
150
|
-
TableName: this.tableName,
|
|
151
|
-
Key: { id }
|
|
152
|
-
})
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
// src/storage/index.ts
|
|
158
|
-
function createStorageProvider(config) {
|
|
159
|
-
switch (config.type) {
|
|
160
|
-
case "dynamodb":
|
|
161
|
-
return new DynamoDBStorageProvider(config.options);
|
|
162
|
-
case "mongodb":
|
|
163
|
-
throw new Error("MongoDB storage provider not implemented yet");
|
|
164
|
-
case "postgres":
|
|
165
|
-
throw new Error("PostgreSQL storage provider not implemented yet");
|
|
166
|
-
default:
|
|
167
|
-
throw new Error(`Unsupported storage type: ${config.type}`);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
46
|
+
var import_crypto = __toESM(require("crypto"));
|
|
170
47
|
|
|
171
48
|
// src/email/smtp.ts
|
|
172
49
|
var import_nodemailer = __toESM(require("nodemailer"));
|
|
173
|
-
var SMTPEmailProvider = class {
|
|
174
|
-
constructor(from, config, templates) {
|
|
175
|
-
this.transporter = import_nodemailer.default.createTransport(config);
|
|
176
|
-
this.config = { from, templates };
|
|
177
|
-
}
|
|
178
|
-
async sendVerificationEmail(email, token) {
|
|
179
|
-
const { subject, html } = this.config.templates.verification;
|
|
180
|
-
await this.transporter.sendMail({
|
|
181
|
-
from: this.config.from,
|
|
182
|
-
to: email,
|
|
183
|
-
subject,
|
|
184
|
-
html: html(token)
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
async sendPasswordResetEmail(email, token) {
|
|
188
|
-
const { subject, html } = this.config.templates.resetPassword;
|
|
189
|
-
await this.transporter.sendMail({
|
|
190
|
-
from: this.config.from,
|
|
191
|
-
to: email,
|
|
192
|
-
subject,
|
|
193
|
-
html: html(token)
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
async verifyConnection() {
|
|
197
|
-
try {
|
|
198
|
-
await this.transporter.verify();
|
|
199
|
-
return true;
|
|
200
|
-
} catch (_error) {
|
|
201
|
-
return false;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
// src/email/ses.ts
|
|
207
|
-
var import_client_sesv2 = require("@aws-sdk/client-sesv2");
|
|
208
50
|
|
|
209
51
|
// src/email/defaultTemplates.ts
|
|
210
52
|
var defaultTemplates = {
|
|
@@ -263,7 +105,42 @@ var defaultTemplates = {
|
|
|
263
105
|
}
|
|
264
106
|
};
|
|
265
107
|
|
|
108
|
+
// src/email/smtp.ts
|
|
109
|
+
var SMTPEmailProvider = class {
|
|
110
|
+
constructor(from, config, templates) {
|
|
111
|
+
this.transporter = import_nodemailer.default.createTransport(config);
|
|
112
|
+
this.config = { from, templates: templates ?? defaultTemplates };
|
|
113
|
+
}
|
|
114
|
+
async sendVerificationEmail(email, token) {
|
|
115
|
+
const { subject, html } = this.config.templates.verification;
|
|
116
|
+
await this.transporter.sendMail({
|
|
117
|
+
from: this.config.from,
|
|
118
|
+
to: email,
|
|
119
|
+
subject,
|
|
120
|
+
html: html(token)
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
async sendPasswordResetEmail(email, token) {
|
|
124
|
+
const { subject, html } = this.config.templates.resetPassword;
|
|
125
|
+
await this.transporter.sendMail({
|
|
126
|
+
from: this.config.from,
|
|
127
|
+
to: email,
|
|
128
|
+
subject,
|
|
129
|
+
html: html(token)
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
async verifyConnection() {
|
|
133
|
+
try {
|
|
134
|
+
await this.transporter.verify();
|
|
135
|
+
return true;
|
|
136
|
+
} catch (_error) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
266
142
|
// src/email/ses.ts
|
|
143
|
+
var import_client_sesv2 = require("@aws-sdk/client-sesv2");
|
|
267
144
|
var SESEmailProvider = class {
|
|
268
145
|
constructor(from, config, templates) {
|
|
269
146
|
this.client = new import_client_sesv2.SESv2Client({
|
|
@@ -353,10 +230,10 @@ function createEmailProvider(config) {
|
|
|
353
230
|
var AuthService = class {
|
|
354
231
|
constructor(config) {
|
|
355
232
|
this.config = config;
|
|
356
|
-
this.storage =
|
|
233
|
+
this.storage = config.storage;
|
|
357
234
|
this.email = createEmailProvider(config.email);
|
|
358
235
|
}
|
|
359
|
-
|
|
236
|
+
generateAccessToken(user) {
|
|
360
237
|
return import_jsonwebtoken.default.sign(
|
|
361
238
|
{
|
|
362
239
|
id: user.id,
|
|
@@ -372,31 +249,100 @@ var AuthService = class {
|
|
|
372
249
|
}
|
|
373
250
|
);
|
|
374
251
|
}
|
|
252
|
+
generateRefreshToken(sessionId, userId) {
|
|
253
|
+
const payload = {
|
|
254
|
+
sessionId,
|
|
255
|
+
userId,
|
|
256
|
+
type: "refresh"
|
|
257
|
+
};
|
|
258
|
+
return import_jsonwebtoken.default.sign(payload, this.config.jwt.privateKey, {
|
|
259
|
+
algorithm: "RS256",
|
|
260
|
+
expiresIn: this.config.jwt.refreshTokenExpiresIn || "7d",
|
|
261
|
+
keyid: this.config.jwt.keyId
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
verifyRefreshToken(token) {
|
|
265
|
+
try {
|
|
266
|
+
const payload = import_jsonwebtoken.default.verify(token, this.config.jwt.publicKey, {
|
|
267
|
+
algorithms: ["RS256"]
|
|
268
|
+
});
|
|
269
|
+
if (payload.type !== "refresh") {
|
|
270
|
+
throw new Error("Invalid token type");
|
|
271
|
+
}
|
|
272
|
+
return payload;
|
|
273
|
+
} catch (_error) {
|
|
274
|
+
throw new Error("Invalid refresh token");
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
hashToken(token) {
|
|
278
|
+
return import_crypto.default.createHash("sha256").update(token).digest("hex");
|
|
279
|
+
}
|
|
280
|
+
getRefreshTokenExpiryMs() {
|
|
281
|
+
const expiresIn = this.config.jwt.refreshTokenExpiresIn || "7d";
|
|
282
|
+
const match = expiresIn.match(/^(\d+)([smhd])$/);
|
|
283
|
+
if (!match) {
|
|
284
|
+
return 7 * 24 * 60 * 60 * 1e3;
|
|
285
|
+
}
|
|
286
|
+
const value = parseInt(match[1], 10);
|
|
287
|
+
const unit = match[2];
|
|
288
|
+
const multipliers = {
|
|
289
|
+
s: 1e3,
|
|
290
|
+
m: 60 * 1e3,
|
|
291
|
+
h: 60 * 60 * 1e3,
|
|
292
|
+
d: 24 * 60 * 60 * 1e3
|
|
293
|
+
};
|
|
294
|
+
return value * multipliers[unit];
|
|
295
|
+
}
|
|
375
296
|
async hashPassword(password) {
|
|
376
297
|
return import_bcryptjs.default.hash(password, 10);
|
|
377
298
|
}
|
|
378
|
-
async
|
|
379
|
-
const
|
|
299
|
+
async createSession(userId, options = {}) {
|
|
300
|
+
const now = Date.now();
|
|
301
|
+
const sessionId = this.config.idGeneration === "uuid" ? (0, import_uuid.v4)() : void 0;
|
|
302
|
+
const tempSession = {
|
|
303
|
+
...sessionId ? { id: sessionId } : {},
|
|
304
|
+
userId,
|
|
305
|
+
refreshTokenHash: "",
|
|
306
|
+
userAgent: options.userAgent,
|
|
307
|
+
ipAddress: options.ipAddress,
|
|
308
|
+
deviceName: options.deviceName,
|
|
309
|
+
createdAt: now,
|
|
310
|
+
lastUsedAt: now,
|
|
311
|
+
expiresAt: now + this.getRefreshTokenExpiryMs()
|
|
312
|
+
};
|
|
313
|
+
const session = await this.storage.createSession(tempSession);
|
|
314
|
+
const refreshToken = this.generateRefreshToken(session.id, userId);
|
|
315
|
+
await this.storage.updateSession(session.id, {
|
|
316
|
+
refreshTokenHash: this.hashToken(refreshToken)
|
|
317
|
+
});
|
|
318
|
+
return {
|
|
319
|
+
session: { ...session, refreshTokenHash: this.hashToken(refreshToken) },
|
|
320
|
+
refreshToken
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
async signup(createUserInput, password, options = {}) {
|
|
324
|
+
const existingUser = await this.storage.getUserByEmail(createUserInput.email);
|
|
380
325
|
if (existingUser) {
|
|
381
326
|
throw new Error("User already exists");
|
|
382
327
|
}
|
|
383
328
|
const verificationToken = (0, import_uuid.v4)();
|
|
329
|
+
const id = this.config.idGeneration === "uuid" ? (0, import_uuid.v4)() : void 0;
|
|
384
330
|
const user = {
|
|
385
|
-
|
|
386
|
-
|
|
331
|
+
...createUserInput,
|
|
332
|
+
id,
|
|
387
333
|
passwordHash: await this.hashPassword(password),
|
|
388
|
-
roles,
|
|
389
334
|
isEmailVerified: false,
|
|
390
335
|
verificationToken,
|
|
391
336
|
createdAt: Date.now(),
|
|
392
337
|
updatedAt: Date.now()
|
|
393
338
|
};
|
|
394
|
-
await this.storage.createUser(user);
|
|
395
|
-
await this.email.sendVerificationEmail(email, verificationToken);
|
|
396
|
-
const
|
|
397
|
-
|
|
339
|
+
const createdUser = await this.storage.createUser(user);
|
|
340
|
+
await this.email.sendVerificationEmail(createUserInput.email, verificationToken);
|
|
341
|
+
const { refreshToken } = await this.createSession(createdUser.id, options);
|
|
342
|
+
const accessToken = this.generateAccessToken(createdUser);
|
|
343
|
+
return { user: createdUser, tokens: { accessToken, refreshToken } };
|
|
398
344
|
}
|
|
399
|
-
async signin(email, password) {
|
|
345
|
+
async signin(email, password, options = {}) {
|
|
400
346
|
const user = await this.storage.getUserByEmail(email);
|
|
401
347
|
if (!user) {
|
|
402
348
|
throw new Error("Invalid credentials");
|
|
@@ -408,8 +354,86 @@ var AuthService = class {
|
|
|
408
354
|
if (!user.isEmailVerified) {
|
|
409
355
|
throw new Error("Please verify your email before signing in");
|
|
410
356
|
}
|
|
411
|
-
const
|
|
412
|
-
|
|
357
|
+
const { refreshToken } = await this.createSession(user.id, options);
|
|
358
|
+
const accessToken = this.generateAccessToken(user);
|
|
359
|
+
return { user, tokens: { accessToken, refreshToken } };
|
|
360
|
+
}
|
|
361
|
+
async refreshAccessToken(refreshToken) {
|
|
362
|
+
const payload = this.verifyRefreshToken(refreshToken);
|
|
363
|
+
const session = await this.storage.getSessionById(payload.sessionId);
|
|
364
|
+
if (!session) {
|
|
365
|
+
throw new Error("Session not found");
|
|
366
|
+
}
|
|
367
|
+
const tokenHash = this.hashToken(refreshToken);
|
|
368
|
+
if (session.refreshTokenHash !== tokenHash) {
|
|
369
|
+
throw new Error("Invalid refresh token");
|
|
370
|
+
}
|
|
371
|
+
if (session.expiresAt < Date.now()) {
|
|
372
|
+
await this.storage.deleteSession(session.id);
|
|
373
|
+
throw new Error("Session expired");
|
|
374
|
+
}
|
|
375
|
+
const user = await this.storage.getUserById(session.userId);
|
|
376
|
+
if (!user) {
|
|
377
|
+
throw new Error("User not found");
|
|
378
|
+
}
|
|
379
|
+
const newRefreshToken = this.generateRefreshToken(session.id, user.id);
|
|
380
|
+
await this.storage.updateSession(session.id, {
|
|
381
|
+
refreshTokenHash: this.hashToken(newRefreshToken),
|
|
382
|
+
lastUsedAt: Date.now(),
|
|
383
|
+
expiresAt: Date.now() + this.getRefreshTokenExpiryMs()
|
|
384
|
+
});
|
|
385
|
+
const accessToken = this.generateAccessToken(user);
|
|
386
|
+
return { accessToken, refreshToken: newRefreshToken };
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Logout from current session only
|
|
390
|
+
*/
|
|
391
|
+
async logout(refreshToken) {
|
|
392
|
+
try {
|
|
393
|
+
const payload = this.verifyRefreshToken(refreshToken);
|
|
394
|
+
await this.storage.deleteSession(payload.sessionId);
|
|
395
|
+
} catch {
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Revoke a specific session by ID
|
|
400
|
+
*/
|
|
401
|
+
async revokeSession(userId, sessionId) {
|
|
402
|
+
const session = await this.storage.getSessionById(sessionId);
|
|
403
|
+
if (!session || session.userId !== userId) {
|
|
404
|
+
throw new Error("Session not found");
|
|
405
|
+
}
|
|
406
|
+
await this.storage.deleteSession(sessionId);
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Revoke all sessions for a user (logout from all devices)
|
|
410
|
+
*/
|
|
411
|
+
async revokeAllSessions(userId) {
|
|
412
|
+
await this.storage.deleteAllUserSessions(userId);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Get all active sessions for a user
|
|
416
|
+
*/
|
|
417
|
+
async getSessions(userId, currentRefreshToken) {
|
|
418
|
+
const sessions = await this.storage.getSessionsByUserId(userId);
|
|
419
|
+
const now = Date.now();
|
|
420
|
+
let currentSessionId;
|
|
421
|
+
if (currentRefreshToken) {
|
|
422
|
+
try {
|
|
423
|
+
const payload = this.verifyRefreshToken(currentRefreshToken);
|
|
424
|
+
currentSessionId = payload.sessionId;
|
|
425
|
+
} catch {
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return sessions.filter((s) => s.expiresAt > now).map((session) => ({
|
|
429
|
+
id: session.id,
|
|
430
|
+
deviceName: session.deviceName,
|
|
431
|
+
userAgent: session.userAgent,
|
|
432
|
+
ipAddress: session.ipAddress,
|
|
433
|
+
createdAt: session.createdAt,
|
|
434
|
+
lastUsedAt: session.lastUsedAt,
|
|
435
|
+
isCurrent: session.id === currentSessionId
|
|
436
|
+
}));
|
|
413
437
|
}
|
|
414
438
|
async verifyEmail(token) {
|
|
415
439
|
const user = await this.storage.getUserByVerifyEmailToken(token);
|
|
@@ -452,6 +476,7 @@ var AuthService = class {
|
|
|
452
476
|
try {
|
|
453
477
|
return import_jsonwebtoken.default.verify(token, this.config.jwt.publicKey, { algorithms: ["RS256"] });
|
|
454
478
|
} catch (_error) {
|
|
479
|
+
console.log("Token verification failed:", _error);
|
|
455
480
|
throw new Error("Invalid token");
|
|
456
481
|
}
|
|
457
482
|
}
|
|
@@ -462,6 +487,74 @@ var AuthService = class {
|
|
|
462
487
|
}
|
|
463
488
|
return requiredRoles.some((role) => user.roles.includes(role));
|
|
464
489
|
}
|
|
490
|
+
// ==================== User Management ====================
|
|
491
|
+
/**
|
|
492
|
+
* Get a user by ID
|
|
493
|
+
*/
|
|
494
|
+
async getUserById(id) {
|
|
495
|
+
return this.storage.getUserById(id);
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Get a user by email
|
|
499
|
+
*/
|
|
500
|
+
async getUserByEmail(email) {
|
|
501
|
+
return this.storage.getUserByEmail(email);
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Update a user's profile information.
|
|
505
|
+
* Supports predefined fields (email, firstName, lastName, roles, isEmailVerified)
|
|
506
|
+
* as well as any additional custom fields.
|
|
507
|
+
*/
|
|
508
|
+
async updateUser(id, updates) {
|
|
509
|
+
const user = await this.storage.getUserById(id);
|
|
510
|
+
if (!user) {
|
|
511
|
+
throw new Error("User not found");
|
|
512
|
+
}
|
|
513
|
+
if (updates.email !== void 0 && updates.email !== user.email) {
|
|
514
|
+
const existingUser = await this.storage.getUserByEmail(updates.email);
|
|
515
|
+
if (existingUser) {
|
|
516
|
+
throw new Error("Email already in use");
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
const { email, firstName, lastName, roles, isEmailVerified, ...additionalFields } = updates;
|
|
520
|
+
const mergedUpdates = {
|
|
521
|
+
updatedAt: Date.now(),
|
|
522
|
+
...additionalFields
|
|
523
|
+
};
|
|
524
|
+
if (email !== void 0) mergedUpdates.email = email;
|
|
525
|
+
if (firstName !== void 0) mergedUpdates.firstName = firstName;
|
|
526
|
+
if (lastName !== void 0) mergedUpdates.lastName = lastName;
|
|
527
|
+
if (roles !== void 0) mergedUpdates.roles = roles;
|
|
528
|
+
if (isEmailVerified !== void 0) mergedUpdates.isEmailVerified = isEmailVerified;
|
|
529
|
+
await this.storage.updateUser(id, mergedUpdates);
|
|
530
|
+
return { ...user, ...mergedUpdates };
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Delete a user and all their sessions
|
|
534
|
+
*/
|
|
535
|
+
async deleteUser(id) {
|
|
536
|
+
const user = await this.storage.getUserById(id);
|
|
537
|
+
if (!user) {
|
|
538
|
+
throw new Error("User not found");
|
|
539
|
+
}
|
|
540
|
+
await this.storage.deleteUser(id);
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Find users with filtering and pagination
|
|
544
|
+
*
|
|
545
|
+
* @example
|
|
546
|
+
* // Find all admins
|
|
547
|
+
* const { users } = await authService.findUsers({ hasAnyRole: ['ADMIN'] });
|
|
548
|
+
*
|
|
549
|
+
* // Find verified users with email containing '@company.com'
|
|
550
|
+
* const result = await authService.findUsers({
|
|
551
|
+
* emailContains: '@company.com',
|
|
552
|
+
* isEmailVerified: true
|
|
553
|
+
* }, { limit: 20 });
|
|
554
|
+
*/
|
|
555
|
+
async findUsers(filter, options) {
|
|
556
|
+
return this.storage.findUsers(filter, options);
|
|
557
|
+
}
|
|
465
558
|
};
|
|
466
559
|
|
|
467
560
|
// src/middleware/auth.ts
|
|
@@ -476,8 +569,7 @@ var AuthMiddleware = class {
|
|
|
476
569
|
if (!token) {
|
|
477
570
|
throw new Error("No token provided");
|
|
478
571
|
}
|
|
479
|
-
|
|
480
|
-
req.user = decoded;
|
|
572
|
+
req.user = await this.authService.verifyToken(token);
|
|
481
573
|
next();
|
|
482
574
|
} catch (_error) {
|
|
483
575
|
res.status(401).json({ error: "Unauthorized" });
|
|
@@ -509,12 +601,13 @@ var AuthMiddleware = class {
|
|
|
509
601
|
|
|
510
602
|
// src/types/index.ts
|
|
511
603
|
var import_zod = require("zod");
|
|
512
|
-
var UserRoleSchema = import_zod.z.enum(["ADMIN", "USER", "GUEST"]);
|
|
513
604
|
var UserSchema = import_zod.z.object({
|
|
514
|
-
id: import_zod.z.string(),
|
|
605
|
+
id: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]),
|
|
515
606
|
email: import_zod.z.string().email(),
|
|
607
|
+
firstName: import_zod.z.string().optional(),
|
|
608
|
+
lastName: import_zod.z.string().optional(),
|
|
516
609
|
passwordHash: import_zod.z.string(),
|
|
517
|
-
roles: import_zod.z.array(
|
|
610
|
+
roles: import_zod.z.array(import_zod.z.string()),
|
|
518
611
|
isEmailVerified: import_zod.z.boolean(),
|
|
519
612
|
verificationToken: import_zod.z.string().optional(),
|
|
520
613
|
resetPasswordToken: import_zod.z.string().optional(),
|
|
@@ -522,11 +615,42 @@ var UserSchema = import_zod.z.object({
|
|
|
522
615
|
createdAt: import_zod.z.number(),
|
|
523
616
|
updatedAt: import_zod.z.number()
|
|
524
617
|
});
|
|
618
|
+
var CreateUserSchema = UserSchema.omit({
|
|
619
|
+
id: true,
|
|
620
|
+
createdAt: true,
|
|
621
|
+
updatedAt: true
|
|
622
|
+
}).extend({
|
|
623
|
+
createdAt: import_zod.z.number().optional(),
|
|
624
|
+
updatedAt: import_zod.z.number().optional(),
|
|
625
|
+
id: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]).optional()
|
|
626
|
+
});
|
|
627
|
+
var SessionSchema = import_zod.z.object({
|
|
628
|
+
id: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]),
|
|
629
|
+
userId: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]),
|
|
630
|
+
refreshTokenHash: import_zod.z.string(),
|
|
631
|
+
userAgent: import_zod.z.string().optional(),
|
|
632
|
+
ipAddress: import_zod.z.string().optional(),
|
|
633
|
+
deviceName: import_zod.z.string().optional(),
|
|
634
|
+
createdAt: import_zod.z.number(),
|
|
635
|
+
lastUsedAt: import_zod.z.number(),
|
|
636
|
+
expiresAt: import_zod.z.number()
|
|
637
|
+
});
|
|
638
|
+
var CreateSessionSchema = SessionSchema.omit({
|
|
639
|
+
id: true,
|
|
640
|
+
createdAt: true,
|
|
641
|
+
lastUsedAt: true
|
|
642
|
+
}).extend({
|
|
643
|
+
createdAt: import_zod.z.number().optional(),
|
|
644
|
+
lastUsedAt: import_zod.z.number().optional(),
|
|
645
|
+
id: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]).optional()
|
|
646
|
+
});
|
|
525
647
|
// Annotate the CommonJS export names for ESM import in node:
|
|
526
648
|
0 && (module.exports = {
|
|
527
649
|
AuthMiddleware,
|
|
528
650
|
AuthService,
|
|
529
|
-
|
|
651
|
+
CreateSessionSchema,
|
|
652
|
+
CreateUserSchema,
|
|
653
|
+
SessionSchema,
|
|
530
654
|
UserSchema
|
|
531
655
|
});
|
|
532
656
|
//# sourceMappingURL=index.js.map
|