@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.mjs
CHANGED
|
@@ -2,177 +2,10 @@
|
|
|
2
2
|
import bcrypt from "bcryptjs";
|
|
3
3
|
import jwt from "jsonwebtoken";
|
|
4
4
|
import { v4 as uuidv4 } from "uuid";
|
|
5
|
-
|
|
6
|
-
// src/storage/dynamodb.ts
|
|
7
|
-
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
8
|
-
import {
|
|
9
|
-
DynamoDBDocumentClient,
|
|
10
|
-
PutCommand,
|
|
11
|
-
GetCommand,
|
|
12
|
-
UpdateCommand,
|
|
13
|
-
DeleteCommand,
|
|
14
|
-
QueryCommand
|
|
15
|
-
} from "@aws-sdk/lib-dynamodb";
|
|
16
|
-
var DynamoDBStorageProvider = class {
|
|
17
|
-
constructor(config) {
|
|
18
|
-
const dbClient = new DynamoDBClient({ region: config.region });
|
|
19
|
-
this.client = DynamoDBDocumentClient.from(dbClient, {});
|
|
20
|
-
this.tableName = config.tableName;
|
|
21
|
-
}
|
|
22
|
-
async createUser(user) {
|
|
23
|
-
await this.client.send(
|
|
24
|
-
new PutCommand({
|
|
25
|
-
TableName: this.tableName,
|
|
26
|
-
Item: user,
|
|
27
|
-
ConditionExpression: "attribute_not_exists(email)"
|
|
28
|
-
})
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
async getUserByResetPasswordToken(resetPasswordToken) {
|
|
32
|
-
const response = await this.client.send(
|
|
33
|
-
new QueryCommand({
|
|
34
|
-
TableName: this.tableName,
|
|
35
|
-
IndexName: "ResetPasswordTokenIndex",
|
|
36
|
-
KeyConditionExpression: "resetPasswordToken = :token",
|
|
37
|
-
ExpressionAttributeValues: {
|
|
38
|
-
":token": resetPasswordToken
|
|
39
|
-
}
|
|
40
|
-
})
|
|
41
|
-
);
|
|
42
|
-
return response.Items?.[0];
|
|
43
|
-
}
|
|
44
|
-
async getUserByVerifyEmailToken(verifyEmailToken) {
|
|
45
|
-
const response = await this.client.send(
|
|
46
|
-
new QueryCommand({
|
|
47
|
-
TableName: this.tableName,
|
|
48
|
-
IndexName: "VerifyEmailTokenIndex",
|
|
49
|
-
KeyConditionExpression: "verificationToken = :token",
|
|
50
|
-
ExpressionAttributeValues: {
|
|
51
|
-
":token": verifyEmailToken
|
|
52
|
-
}
|
|
53
|
-
})
|
|
54
|
-
);
|
|
55
|
-
return response.Items?.[0];
|
|
56
|
-
}
|
|
57
|
-
async getUserById(id) {
|
|
58
|
-
const response = await this.client.send(
|
|
59
|
-
new GetCommand({
|
|
60
|
-
TableName: this.tableName,
|
|
61
|
-
Key: { id }
|
|
62
|
-
})
|
|
63
|
-
);
|
|
64
|
-
return response.Item;
|
|
65
|
-
}
|
|
66
|
-
async getUserByEmail(email) {
|
|
67
|
-
const response = await this.client.send(
|
|
68
|
-
new QueryCommand({
|
|
69
|
-
TableName: this.tableName,
|
|
70
|
-
IndexName: "EmailIndex",
|
|
71
|
-
KeyConditionExpression: "email = :email",
|
|
72
|
-
ExpressionAttributeValues: {
|
|
73
|
-
":email": email
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
);
|
|
77
|
-
return response.Items?.[0];
|
|
78
|
-
}
|
|
79
|
-
async updateUser(id, updates) {
|
|
80
|
-
const toSet = {};
|
|
81
|
-
const toRemove = [];
|
|
82
|
-
Object.entries(updates).forEach(([key, value]) => {
|
|
83
|
-
if (value === void 0) {
|
|
84
|
-
toRemove.push(key);
|
|
85
|
-
} else {
|
|
86
|
-
toSet[key] = value;
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
if (Object.keys(toSet).length === 0 && toRemove.length === 0) {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
const setPart = Object.keys(toSet).length > 0 ? `SET ${Object.keys(toSet).map((key) => `#${key} = :${key}`).join(", ")}` : "";
|
|
93
|
-
const removePart = toRemove.length > 0 ? `REMOVE ${toRemove.map((key) => `#${key}`).join(", ")}` : "";
|
|
94
|
-
const updateExpression = [setPart, removePart].filter(Boolean).join(" ");
|
|
95
|
-
const expressionAttributeNames = [...Object.keys(toSet), ...toRemove].reduce(
|
|
96
|
-
(acc, key) => ({ ...acc, [`#${key}`]: key }),
|
|
97
|
-
{}
|
|
98
|
-
);
|
|
99
|
-
const expressionAttributeValues = Object.entries(toSet).reduce(
|
|
100
|
-
(acc, [key, value]) => ({ ...acc, [`:${key}`]: value }),
|
|
101
|
-
{}
|
|
102
|
-
);
|
|
103
|
-
await this.client.send(
|
|
104
|
-
new UpdateCommand({
|
|
105
|
-
TableName: this.tableName,
|
|
106
|
-
Key: { id },
|
|
107
|
-
UpdateExpression: updateExpression,
|
|
108
|
-
ExpressionAttributeNames: expressionAttributeNames,
|
|
109
|
-
...Object.keys(expressionAttributeValues).length > 0 && {
|
|
110
|
-
ExpressionAttributeValues: expressionAttributeValues
|
|
111
|
-
}
|
|
112
|
-
})
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
async deleteUser(id) {
|
|
116
|
-
await this.client.send(
|
|
117
|
-
new DeleteCommand({
|
|
118
|
-
TableName: this.tableName,
|
|
119
|
-
Key: { id }
|
|
120
|
-
})
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
// src/storage/index.ts
|
|
126
|
-
function createStorageProvider(config) {
|
|
127
|
-
switch (config.type) {
|
|
128
|
-
case "dynamodb":
|
|
129
|
-
return new DynamoDBStorageProvider(config.options);
|
|
130
|
-
case "mongodb":
|
|
131
|
-
throw new Error("MongoDB storage provider not implemented yet");
|
|
132
|
-
case "postgres":
|
|
133
|
-
throw new Error("PostgreSQL storage provider not implemented yet");
|
|
134
|
-
default:
|
|
135
|
-
throw new Error(`Unsupported storage type: ${config.type}`);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
5
|
+
import crypto from "crypto";
|
|
138
6
|
|
|
139
7
|
// src/email/smtp.ts
|
|
140
8
|
import nodemailer from "nodemailer";
|
|
141
|
-
var SMTPEmailProvider = class {
|
|
142
|
-
constructor(from, config, templates) {
|
|
143
|
-
this.transporter = nodemailer.createTransport(config);
|
|
144
|
-
this.config = { from, templates };
|
|
145
|
-
}
|
|
146
|
-
async sendVerificationEmail(email, token) {
|
|
147
|
-
const { subject, html } = this.config.templates.verification;
|
|
148
|
-
await this.transporter.sendMail({
|
|
149
|
-
from: this.config.from,
|
|
150
|
-
to: email,
|
|
151
|
-
subject,
|
|
152
|
-
html: html(token)
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
async sendPasswordResetEmail(email, token) {
|
|
156
|
-
const { subject, html } = this.config.templates.resetPassword;
|
|
157
|
-
await this.transporter.sendMail({
|
|
158
|
-
from: this.config.from,
|
|
159
|
-
to: email,
|
|
160
|
-
subject,
|
|
161
|
-
html: html(token)
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
async verifyConnection() {
|
|
165
|
-
try {
|
|
166
|
-
await this.transporter.verify();
|
|
167
|
-
return true;
|
|
168
|
-
} catch (_error) {
|
|
169
|
-
return false;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
// src/email/ses.ts
|
|
175
|
-
import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2";
|
|
176
9
|
|
|
177
10
|
// src/email/defaultTemplates.ts
|
|
178
11
|
var defaultTemplates = {
|
|
@@ -231,7 +64,42 @@ var defaultTemplates = {
|
|
|
231
64
|
}
|
|
232
65
|
};
|
|
233
66
|
|
|
67
|
+
// src/email/smtp.ts
|
|
68
|
+
var SMTPEmailProvider = class {
|
|
69
|
+
constructor(from, config, templates) {
|
|
70
|
+
this.transporter = nodemailer.createTransport(config);
|
|
71
|
+
this.config = { from, templates: templates ?? defaultTemplates };
|
|
72
|
+
}
|
|
73
|
+
async sendVerificationEmail(email, token) {
|
|
74
|
+
const { subject, html } = this.config.templates.verification;
|
|
75
|
+
await this.transporter.sendMail({
|
|
76
|
+
from: this.config.from,
|
|
77
|
+
to: email,
|
|
78
|
+
subject,
|
|
79
|
+
html: html(token)
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async sendPasswordResetEmail(email, token) {
|
|
83
|
+
const { subject, html } = this.config.templates.resetPassword;
|
|
84
|
+
await this.transporter.sendMail({
|
|
85
|
+
from: this.config.from,
|
|
86
|
+
to: email,
|
|
87
|
+
subject,
|
|
88
|
+
html: html(token)
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
async verifyConnection() {
|
|
92
|
+
try {
|
|
93
|
+
await this.transporter.verify();
|
|
94
|
+
return true;
|
|
95
|
+
} catch (_error) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
234
101
|
// src/email/ses.ts
|
|
102
|
+
import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2";
|
|
235
103
|
var SESEmailProvider = class {
|
|
236
104
|
constructor(from, config, templates) {
|
|
237
105
|
this.client = new SESv2Client({
|
|
@@ -321,10 +189,10 @@ function createEmailProvider(config) {
|
|
|
321
189
|
var AuthService = class {
|
|
322
190
|
constructor(config) {
|
|
323
191
|
this.config = config;
|
|
324
|
-
this.storage =
|
|
192
|
+
this.storage = config.storage;
|
|
325
193
|
this.email = createEmailProvider(config.email);
|
|
326
194
|
}
|
|
327
|
-
|
|
195
|
+
generateAccessToken(user) {
|
|
328
196
|
return jwt.sign(
|
|
329
197
|
{
|
|
330
198
|
id: user.id,
|
|
@@ -340,31 +208,100 @@ var AuthService = class {
|
|
|
340
208
|
}
|
|
341
209
|
);
|
|
342
210
|
}
|
|
211
|
+
generateRefreshToken(sessionId, userId) {
|
|
212
|
+
const payload = {
|
|
213
|
+
sessionId,
|
|
214
|
+
userId,
|
|
215
|
+
type: "refresh"
|
|
216
|
+
};
|
|
217
|
+
return jwt.sign(payload, this.config.jwt.privateKey, {
|
|
218
|
+
algorithm: "RS256",
|
|
219
|
+
expiresIn: this.config.jwt.refreshTokenExpiresIn || "7d",
|
|
220
|
+
keyid: this.config.jwt.keyId
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
verifyRefreshToken(token) {
|
|
224
|
+
try {
|
|
225
|
+
const payload = jwt.verify(token, this.config.jwt.publicKey, {
|
|
226
|
+
algorithms: ["RS256"]
|
|
227
|
+
});
|
|
228
|
+
if (payload.type !== "refresh") {
|
|
229
|
+
throw new Error("Invalid token type");
|
|
230
|
+
}
|
|
231
|
+
return payload;
|
|
232
|
+
} catch (_error) {
|
|
233
|
+
throw new Error("Invalid refresh token");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
hashToken(token) {
|
|
237
|
+
return crypto.createHash("sha256").update(token).digest("hex");
|
|
238
|
+
}
|
|
239
|
+
getRefreshTokenExpiryMs() {
|
|
240
|
+
const expiresIn = this.config.jwt.refreshTokenExpiresIn || "7d";
|
|
241
|
+
const match = expiresIn.match(/^(\d+)([smhd])$/);
|
|
242
|
+
if (!match) {
|
|
243
|
+
return 7 * 24 * 60 * 60 * 1e3;
|
|
244
|
+
}
|
|
245
|
+
const value = parseInt(match[1], 10);
|
|
246
|
+
const unit = match[2];
|
|
247
|
+
const multipliers = {
|
|
248
|
+
s: 1e3,
|
|
249
|
+
m: 60 * 1e3,
|
|
250
|
+
h: 60 * 60 * 1e3,
|
|
251
|
+
d: 24 * 60 * 60 * 1e3
|
|
252
|
+
};
|
|
253
|
+
return value * multipliers[unit];
|
|
254
|
+
}
|
|
343
255
|
async hashPassword(password) {
|
|
344
256
|
return bcrypt.hash(password, 10);
|
|
345
257
|
}
|
|
346
|
-
async
|
|
347
|
-
const
|
|
258
|
+
async createSession(userId, options = {}) {
|
|
259
|
+
const now = Date.now();
|
|
260
|
+
const sessionId = this.config.idGeneration === "uuid" ? uuidv4() : void 0;
|
|
261
|
+
const tempSession = {
|
|
262
|
+
...sessionId ? { id: sessionId } : {},
|
|
263
|
+
userId,
|
|
264
|
+
refreshTokenHash: "",
|
|
265
|
+
userAgent: options.userAgent,
|
|
266
|
+
ipAddress: options.ipAddress,
|
|
267
|
+
deviceName: options.deviceName,
|
|
268
|
+
createdAt: now,
|
|
269
|
+
lastUsedAt: now,
|
|
270
|
+
expiresAt: now + this.getRefreshTokenExpiryMs()
|
|
271
|
+
};
|
|
272
|
+
const session = await this.storage.createSession(tempSession);
|
|
273
|
+
const refreshToken = this.generateRefreshToken(session.id, userId);
|
|
274
|
+
await this.storage.updateSession(session.id, {
|
|
275
|
+
refreshTokenHash: this.hashToken(refreshToken)
|
|
276
|
+
});
|
|
277
|
+
return {
|
|
278
|
+
session: { ...session, refreshTokenHash: this.hashToken(refreshToken) },
|
|
279
|
+
refreshToken
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
async signup(createUserInput, password, options = {}) {
|
|
283
|
+
const existingUser = await this.storage.getUserByEmail(createUserInput.email);
|
|
348
284
|
if (existingUser) {
|
|
349
285
|
throw new Error("User already exists");
|
|
350
286
|
}
|
|
351
287
|
const verificationToken = uuidv4();
|
|
288
|
+
const id = this.config.idGeneration === "uuid" ? uuidv4() : void 0;
|
|
352
289
|
const user = {
|
|
353
|
-
|
|
354
|
-
|
|
290
|
+
...createUserInput,
|
|
291
|
+
id,
|
|
355
292
|
passwordHash: await this.hashPassword(password),
|
|
356
|
-
roles,
|
|
357
293
|
isEmailVerified: false,
|
|
358
294
|
verificationToken,
|
|
359
295
|
createdAt: Date.now(),
|
|
360
296
|
updatedAt: Date.now()
|
|
361
297
|
};
|
|
362
|
-
await this.storage.createUser(user);
|
|
363
|
-
await this.email.sendVerificationEmail(email, verificationToken);
|
|
364
|
-
const
|
|
365
|
-
|
|
298
|
+
const createdUser = await this.storage.createUser(user);
|
|
299
|
+
await this.email.sendVerificationEmail(createUserInput.email, verificationToken);
|
|
300
|
+
const { refreshToken } = await this.createSession(createdUser.id, options);
|
|
301
|
+
const accessToken = this.generateAccessToken(createdUser);
|
|
302
|
+
return { user: createdUser, tokens: { accessToken, refreshToken } };
|
|
366
303
|
}
|
|
367
|
-
async signin(email, password) {
|
|
304
|
+
async signin(email, password, options = {}) {
|
|
368
305
|
const user = await this.storage.getUserByEmail(email);
|
|
369
306
|
if (!user) {
|
|
370
307
|
throw new Error("Invalid credentials");
|
|
@@ -376,8 +313,86 @@ var AuthService = class {
|
|
|
376
313
|
if (!user.isEmailVerified) {
|
|
377
314
|
throw new Error("Please verify your email before signing in");
|
|
378
315
|
}
|
|
379
|
-
const
|
|
380
|
-
|
|
316
|
+
const { refreshToken } = await this.createSession(user.id, options);
|
|
317
|
+
const accessToken = this.generateAccessToken(user);
|
|
318
|
+
return { user, tokens: { accessToken, refreshToken } };
|
|
319
|
+
}
|
|
320
|
+
async refreshAccessToken(refreshToken) {
|
|
321
|
+
const payload = this.verifyRefreshToken(refreshToken);
|
|
322
|
+
const session = await this.storage.getSessionById(payload.sessionId);
|
|
323
|
+
if (!session) {
|
|
324
|
+
throw new Error("Session not found");
|
|
325
|
+
}
|
|
326
|
+
const tokenHash = this.hashToken(refreshToken);
|
|
327
|
+
if (session.refreshTokenHash !== tokenHash) {
|
|
328
|
+
throw new Error("Invalid refresh token");
|
|
329
|
+
}
|
|
330
|
+
if (session.expiresAt < Date.now()) {
|
|
331
|
+
await this.storage.deleteSession(session.id);
|
|
332
|
+
throw new Error("Session expired");
|
|
333
|
+
}
|
|
334
|
+
const user = await this.storage.getUserById(session.userId);
|
|
335
|
+
if (!user) {
|
|
336
|
+
throw new Error("User not found");
|
|
337
|
+
}
|
|
338
|
+
const newRefreshToken = this.generateRefreshToken(session.id, user.id);
|
|
339
|
+
await this.storage.updateSession(session.id, {
|
|
340
|
+
refreshTokenHash: this.hashToken(newRefreshToken),
|
|
341
|
+
lastUsedAt: Date.now(),
|
|
342
|
+
expiresAt: Date.now() + this.getRefreshTokenExpiryMs()
|
|
343
|
+
});
|
|
344
|
+
const accessToken = this.generateAccessToken(user);
|
|
345
|
+
return { accessToken, refreshToken: newRefreshToken };
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Logout from current session only
|
|
349
|
+
*/
|
|
350
|
+
async logout(refreshToken) {
|
|
351
|
+
try {
|
|
352
|
+
const payload = this.verifyRefreshToken(refreshToken);
|
|
353
|
+
await this.storage.deleteSession(payload.sessionId);
|
|
354
|
+
} catch {
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Revoke a specific session by ID
|
|
359
|
+
*/
|
|
360
|
+
async revokeSession(userId, sessionId) {
|
|
361
|
+
const session = await this.storage.getSessionById(sessionId);
|
|
362
|
+
if (!session || session.userId !== userId) {
|
|
363
|
+
throw new Error("Session not found");
|
|
364
|
+
}
|
|
365
|
+
await this.storage.deleteSession(sessionId);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Revoke all sessions for a user (logout from all devices)
|
|
369
|
+
*/
|
|
370
|
+
async revokeAllSessions(userId) {
|
|
371
|
+
await this.storage.deleteAllUserSessions(userId);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Get all active sessions for a user
|
|
375
|
+
*/
|
|
376
|
+
async getSessions(userId, currentRefreshToken) {
|
|
377
|
+
const sessions = await this.storage.getSessionsByUserId(userId);
|
|
378
|
+
const now = Date.now();
|
|
379
|
+
let currentSessionId;
|
|
380
|
+
if (currentRefreshToken) {
|
|
381
|
+
try {
|
|
382
|
+
const payload = this.verifyRefreshToken(currentRefreshToken);
|
|
383
|
+
currentSessionId = payload.sessionId;
|
|
384
|
+
} catch {
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return sessions.filter((s) => s.expiresAt > now).map((session) => ({
|
|
388
|
+
id: session.id,
|
|
389
|
+
deviceName: session.deviceName,
|
|
390
|
+
userAgent: session.userAgent,
|
|
391
|
+
ipAddress: session.ipAddress,
|
|
392
|
+
createdAt: session.createdAt,
|
|
393
|
+
lastUsedAt: session.lastUsedAt,
|
|
394
|
+
isCurrent: session.id === currentSessionId
|
|
395
|
+
}));
|
|
381
396
|
}
|
|
382
397
|
async verifyEmail(token) {
|
|
383
398
|
const user = await this.storage.getUserByVerifyEmailToken(token);
|
|
@@ -420,6 +435,7 @@ var AuthService = class {
|
|
|
420
435
|
try {
|
|
421
436
|
return jwt.verify(token, this.config.jwt.publicKey, { algorithms: ["RS256"] });
|
|
422
437
|
} catch (_error) {
|
|
438
|
+
console.log("Token verification failed:", _error);
|
|
423
439
|
throw new Error("Invalid token");
|
|
424
440
|
}
|
|
425
441
|
}
|
|
@@ -430,6 +446,74 @@ var AuthService = class {
|
|
|
430
446
|
}
|
|
431
447
|
return requiredRoles.some((role) => user.roles.includes(role));
|
|
432
448
|
}
|
|
449
|
+
// ==================== User Management ====================
|
|
450
|
+
/**
|
|
451
|
+
* Get a user by ID
|
|
452
|
+
*/
|
|
453
|
+
async getUserById(id) {
|
|
454
|
+
return this.storage.getUserById(id);
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Get a user by email
|
|
458
|
+
*/
|
|
459
|
+
async getUserByEmail(email) {
|
|
460
|
+
return this.storage.getUserByEmail(email);
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Update a user's profile information.
|
|
464
|
+
* Supports predefined fields (email, firstName, lastName, roles, isEmailVerified)
|
|
465
|
+
* as well as any additional custom fields.
|
|
466
|
+
*/
|
|
467
|
+
async updateUser(id, updates) {
|
|
468
|
+
const user = await this.storage.getUserById(id);
|
|
469
|
+
if (!user) {
|
|
470
|
+
throw new Error("User not found");
|
|
471
|
+
}
|
|
472
|
+
if (updates.email !== void 0 && updates.email !== user.email) {
|
|
473
|
+
const existingUser = await this.storage.getUserByEmail(updates.email);
|
|
474
|
+
if (existingUser) {
|
|
475
|
+
throw new Error("Email already in use");
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
const { email, firstName, lastName, roles, isEmailVerified, ...additionalFields } = updates;
|
|
479
|
+
const mergedUpdates = {
|
|
480
|
+
updatedAt: Date.now(),
|
|
481
|
+
...additionalFields
|
|
482
|
+
};
|
|
483
|
+
if (email !== void 0) mergedUpdates.email = email;
|
|
484
|
+
if (firstName !== void 0) mergedUpdates.firstName = firstName;
|
|
485
|
+
if (lastName !== void 0) mergedUpdates.lastName = lastName;
|
|
486
|
+
if (roles !== void 0) mergedUpdates.roles = roles;
|
|
487
|
+
if (isEmailVerified !== void 0) mergedUpdates.isEmailVerified = isEmailVerified;
|
|
488
|
+
await this.storage.updateUser(id, mergedUpdates);
|
|
489
|
+
return { ...user, ...mergedUpdates };
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Delete a user and all their sessions
|
|
493
|
+
*/
|
|
494
|
+
async deleteUser(id) {
|
|
495
|
+
const user = await this.storage.getUserById(id);
|
|
496
|
+
if (!user) {
|
|
497
|
+
throw new Error("User not found");
|
|
498
|
+
}
|
|
499
|
+
await this.storage.deleteUser(id);
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Find users with filtering and pagination
|
|
503
|
+
*
|
|
504
|
+
* @example
|
|
505
|
+
* // Find all admins
|
|
506
|
+
* const { users } = await authService.findUsers({ hasAnyRole: ['ADMIN'] });
|
|
507
|
+
*
|
|
508
|
+
* // Find verified users with email containing '@company.com'
|
|
509
|
+
* const result = await authService.findUsers({
|
|
510
|
+
* emailContains: '@company.com',
|
|
511
|
+
* isEmailVerified: true
|
|
512
|
+
* }, { limit: 20 });
|
|
513
|
+
*/
|
|
514
|
+
async findUsers(filter, options) {
|
|
515
|
+
return this.storage.findUsers(filter, options);
|
|
516
|
+
}
|
|
433
517
|
};
|
|
434
518
|
|
|
435
519
|
// src/middleware/auth.ts
|
|
@@ -444,8 +528,7 @@ var AuthMiddleware = class {
|
|
|
444
528
|
if (!token) {
|
|
445
529
|
throw new Error("No token provided");
|
|
446
530
|
}
|
|
447
|
-
|
|
448
|
-
req.user = decoded;
|
|
531
|
+
req.user = await this.authService.verifyToken(token);
|
|
449
532
|
next();
|
|
450
533
|
} catch (_error) {
|
|
451
534
|
res.status(401).json({ error: "Unauthorized" });
|
|
@@ -477,12 +560,13 @@ var AuthMiddleware = class {
|
|
|
477
560
|
|
|
478
561
|
// src/types/index.ts
|
|
479
562
|
import { z } from "zod";
|
|
480
|
-
var UserRoleSchema = z.enum(["ADMIN", "USER", "GUEST"]);
|
|
481
563
|
var UserSchema = z.object({
|
|
482
|
-
id: z.string(),
|
|
564
|
+
id: z.union([z.string(), z.number()]),
|
|
483
565
|
email: z.string().email(),
|
|
566
|
+
firstName: z.string().optional(),
|
|
567
|
+
lastName: z.string().optional(),
|
|
484
568
|
passwordHash: z.string(),
|
|
485
|
-
roles: z.array(
|
|
569
|
+
roles: z.array(z.string()),
|
|
486
570
|
isEmailVerified: z.boolean(),
|
|
487
571
|
verificationToken: z.string().optional(),
|
|
488
572
|
resetPasswordToken: z.string().optional(),
|
|
@@ -490,10 +574,41 @@ var UserSchema = z.object({
|
|
|
490
574
|
createdAt: z.number(),
|
|
491
575
|
updatedAt: z.number()
|
|
492
576
|
});
|
|
577
|
+
var CreateUserSchema = UserSchema.omit({
|
|
578
|
+
id: true,
|
|
579
|
+
createdAt: true,
|
|
580
|
+
updatedAt: true
|
|
581
|
+
}).extend({
|
|
582
|
+
createdAt: z.number().optional(),
|
|
583
|
+
updatedAt: z.number().optional(),
|
|
584
|
+
id: z.union([z.string(), z.number()]).optional()
|
|
585
|
+
});
|
|
586
|
+
var SessionSchema = z.object({
|
|
587
|
+
id: z.union([z.string(), z.number()]),
|
|
588
|
+
userId: z.union([z.string(), z.number()]),
|
|
589
|
+
refreshTokenHash: z.string(),
|
|
590
|
+
userAgent: z.string().optional(),
|
|
591
|
+
ipAddress: z.string().optional(),
|
|
592
|
+
deviceName: z.string().optional(),
|
|
593
|
+
createdAt: z.number(),
|
|
594
|
+
lastUsedAt: z.number(),
|
|
595
|
+
expiresAt: z.number()
|
|
596
|
+
});
|
|
597
|
+
var CreateSessionSchema = SessionSchema.omit({
|
|
598
|
+
id: true,
|
|
599
|
+
createdAt: true,
|
|
600
|
+
lastUsedAt: true
|
|
601
|
+
}).extend({
|
|
602
|
+
createdAt: z.number().optional(),
|
|
603
|
+
lastUsedAt: z.number().optional(),
|
|
604
|
+
id: z.union([z.string(), z.number()]).optional()
|
|
605
|
+
});
|
|
493
606
|
export {
|
|
494
607
|
AuthMiddleware,
|
|
495
608
|
AuthService,
|
|
496
|
-
|
|
609
|
+
CreateSessionSchema,
|
|
610
|
+
CreateUserSchema,
|
|
611
|
+
SessionSchema,
|
|
497
612
|
UserSchema
|
|
498
613
|
};
|
|
499
614
|
//# sourceMappingURL=index.mjs.map
|