@xcelsior/auth 1.1.0 → 1.1.1
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 +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +24 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +24 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/services/auth.ts +25 -20
package/dist/index.d.mts
CHANGED
|
@@ -285,7 +285,9 @@ declare class AuthService {
|
|
|
285
285
|
private generateRefreshToken;
|
|
286
286
|
private verifyRefreshToken;
|
|
287
287
|
private hashToken;
|
|
288
|
-
|
|
288
|
+
/** Return current time as epoch seconds */
|
|
289
|
+
private now;
|
|
290
|
+
private getRefreshTokenExpirySecs;
|
|
289
291
|
private hashPassword;
|
|
290
292
|
private createSession;
|
|
291
293
|
signup(createUserInput: CreateUserInput, password: string, options?: LoginOptions): Promise<{
|
package/dist/index.d.ts
CHANGED
|
@@ -285,7 +285,9 @@ declare class AuthService {
|
|
|
285
285
|
private generateRefreshToken;
|
|
286
286
|
private verifyRefreshToken;
|
|
287
287
|
private hashToken;
|
|
288
|
-
|
|
288
|
+
/** Return current time as epoch seconds */
|
|
289
|
+
private now;
|
|
290
|
+
private getRefreshTokenExpirySecs;
|
|
289
291
|
private hashPassword;
|
|
290
292
|
private createSession;
|
|
291
293
|
signup(createUserInput: CreateUserInput, password: string, options?: LoginOptions): Promise<{
|
package/dist/index.js
CHANGED
|
@@ -277,19 +277,23 @@ var AuthService = class {
|
|
|
277
277
|
hashToken(token) {
|
|
278
278
|
return import_crypto.default.createHash("sha256").update(token).digest("hex");
|
|
279
279
|
}
|
|
280
|
-
|
|
280
|
+
/** Return current time as epoch seconds */
|
|
281
|
+
now() {
|
|
282
|
+
return Math.floor(Date.now() / 1e3);
|
|
283
|
+
}
|
|
284
|
+
getRefreshTokenExpirySecs() {
|
|
281
285
|
const expiresIn = this.config.jwt.refreshTokenExpiresIn || "7d";
|
|
282
286
|
const match = expiresIn.match(/^(\d+)([smhd])$/);
|
|
283
287
|
if (!match) {
|
|
284
|
-
return 7 * 24 * 60 * 60
|
|
288
|
+
return 7 * 24 * 60 * 60;
|
|
285
289
|
}
|
|
286
290
|
const value = parseInt(match[1], 10);
|
|
287
291
|
const unit = match[2];
|
|
288
292
|
const multipliers = {
|
|
289
|
-
s:
|
|
290
|
-
m: 60
|
|
291
|
-
h: 60 * 60
|
|
292
|
-
d: 24 * 60 * 60
|
|
293
|
+
s: 1,
|
|
294
|
+
m: 60,
|
|
295
|
+
h: 60 * 60,
|
|
296
|
+
d: 24 * 60 * 60
|
|
293
297
|
};
|
|
294
298
|
return value * multipliers[unit];
|
|
295
299
|
}
|
|
@@ -297,7 +301,7 @@ var AuthService = class {
|
|
|
297
301
|
return import_bcryptjs.default.hash(password, 10);
|
|
298
302
|
}
|
|
299
303
|
async createSession(userId, options = {}) {
|
|
300
|
-
const now =
|
|
304
|
+
const now = this.now();
|
|
301
305
|
const sessionId = this.config.idGeneration === "uuid" ? (0, import_uuid.v4)() : void 0;
|
|
302
306
|
const tempSession = {
|
|
303
307
|
...sessionId ? { id: sessionId } : {},
|
|
@@ -308,7 +312,7 @@ var AuthService = class {
|
|
|
308
312
|
deviceName: options.deviceName,
|
|
309
313
|
createdAt: now,
|
|
310
314
|
lastUsedAt: now,
|
|
311
|
-
expiresAt: now + this.
|
|
315
|
+
expiresAt: now + this.getRefreshTokenExpirySecs()
|
|
312
316
|
};
|
|
313
317
|
const session = await this.storage.createSession(tempSession);
|
|
314
318
|
const refreshToken = this.generateRefreshToken(session.id, userId);
|
|
@@ -333,8 +337,8 @@ var AuthService = class {
|
|
|
333
337
|
passwordHash: await this.hashPassword(password),
|
|
334
338
|
isEmailVerified: false,
|
|
335
339
|
verificationToken,
|
|
336
|
-
createdAt:
|
|
337
|
-
updatedAt:
|
|
340
|
+
createdAt: this.now(),
|
|
341
|
+
updatedAt: this.now()
|
|
338
342
|
};
|
|
339
343
|
const createdUser = await this.storage.createUser(user);
|
|
340
344
|
await this.email.sendVerificationEmail(createUserInput.email, verificationToken);
|
|
@@ -368,7 +372,7 @@ var AuthService = class {
|
|
|
368
372
|
if (session.refreshTokenHash !== tokenHash) {
|
|
369
373
|
throw new Error("Invalid refresh token");
|
|
370
374
|
}
|
|
371
|
-
if (session.expiresAt <
|
|
375
|
+
if (session.expiresAt < this.now()) {
|
|
372
376
|
await this.storage.deleteSession(session.id);
|
|
373
377
|
throw new Error("Session expired");
|
|
374
378
|
}
|
|
@@ -379,8 +383,8 @@ var AuthService = class {
|
|
|
379
383
|
const newRefreshToken = this.generateRefreshToken(session.id, user.id);
|
|
380
384
|
await this.storage.updateSession(session.id, {
|
|
381
385
|
refreshTokenHash: this.hashToken(newRefreshToken),
|
|
382
|
-
lastUsedAt:
|
|
383
|
-
expiresAt:
|
|
386
|
+
lastUsedAt: this.now(),
|
|
387
|
+
expiresAt: this.now() + this.getRefreshTokenExpirySecs()
|
|
384
388
|
});
|
|
385
389
|
const accessToken = this.generateAccessToken(user);
|
|
386
390
|
return { accessToken, refreshToken: newRefreshToken };
|
|
@@ -416,7 +420,7 @@ var AuthService = class {
|
|
|
416
420
|
*/
|
|
417
421
|
async getSessions(userId, currentRefreshToken) {
|
|
418
422
|
const sessions = await this.storage.getSessionsByUserId(userId);
|
|
419
|
-
const now =
|
|
423
|
+
const now = this.now();
|
|
420
424
|
let currentSessionId;
|
|
421
425
|
if (currentRefreshToken) {
|
|
422
426
|
try {
|
|
@@ -443,7 +447,7 @@ var AuthService = class {
|
|
|
443
447
|
await this.storage.updateUser(user.id, {
|
|
444
448
|
isEmailVerified: true,
|
|
445
449
|
verificationToken: void 0,
|
|
446
|
-
updatedAt:
|
|
450
|
+
updatedAt: this.now()
|
|
447
451
|
});
|
|
448
452
|
}
|
|
449
453
|
async initiatePasswordReset(email) {
|
|
@@ -452,24 +456,24 @@ var AuthService = class {
|
|
|
452
456
|
throw new Error("User not found");
|
|
453
457
|
}
|
|
454
458
|
const resetToken = (0, import_uuid.v4)();
|
|
455
|
-
const resetExpires =
|
|
459
|
+
const resetExpires = this.now() + 3600;
|
|
456
460
|
await this.storage.updateUser(user.id, {
|
|
457
461
|
resetPasswordToken: resetToken,
|
|
458
462
|
resetPasswordExpires: resetExpires,
|
|
459
|
-
updatedAt:
|
|
463
|
+
updatedAt: this.now()
|
|
460
464
|
});
|
|
461
465
|
await this.email.sendPasswordResetEmail(email, resetToken);
|
|
462
466
|
}
|
|
463
467
|
async resetPassword(token, newPassword) {
|
|
464
468
|
const user = await this.storage.getUserByResetPasswordToken(token);
|
|
465
|
-
if (!user || !user.resetPasswordToken || user.resetPasswordToken !== token || !user.resetPasswordExpires || user.resetPasswordExpires <
|
|
469
|
+
if (!user || !user.resetPasswordToken || user.resetPasswordToken !== token || !user.resetPasswordExpires || user.resetPasswordExpires < this.now()) {
|
|
466
470
|
throw new Error("Invalid or expired reset token");
|
|
467
471
|
}
|
|
468
472
|
await this.storage.updateUser(user.id, {
|
|
469
473
|
passwordHash: await this.hashPassword(newPassword),
|
|
470
474
|
resetPasswordToken: void 0,
|
|
471
475
|
resetPasswordExpires: void 0,
|
|
472
|
-
updatedAt:
|
|
476
|
+
updatedAt: this.now()
|
|
473
477
|
});
|
|
474
478
|
}
|
|
475
479
|
async verifyToken(token) {
|
|
@@ -518,7 +522,7 @@ var AuthService = class {
|
|
|
518
522
|
}
|
|
519
523
|
const { email, firstName, lastName, roles, isEmailVerified, ...additionalFields } = updates;
|
|
520
524
|
const mergedUpdates = {
|
|
521
|
-
updatedAt:
|
|
525
|
+
updatedAt: this.now(),
|
|
522
526
|
...additionalFields
|
|
523
527
|
};
|
|
524
528
|
if (email !== void 0) mergedUpdates.email = email;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/services/auth.ts","../src/email/smtp.ts","../src/email/defaultTemplates.ts","../src/email/ses.ts","../src/email/index.ts","../src/middleware/auth.ts","../src/types/index.ts"],"sourcesContent":["export * from './services/auth';\nexport * from './middleware/auth';\nexport * from './types';\nexport * from './storage';\n","import bcrypt from 'bcryptjs';\nimport jwt from 'jsonwebtoken';\nimport { v4 as uuidv4 } from 'uuid';\nimport crypto from 'crypto';\nimport type {\n AuthConfig,\n CreateSessionInput,\n CreateUserInput,\n Session,\n SessionId,\n SessionInfo,\n User,\n UserId,\n} from '../types';\nimport type {\n FindUsersOptions,\n FindUsersResult,\n IStorageProvider,\n UserFilter,\n} from '../storage/types';\nimport type { IEmailProvider } from '../email/types';\nimport { createEmailProvider } from '../email';\n\nexport interface AuthTokens {\n accessToken: string;\n refreshToken: string;\n}\n\nexport interface LoginOptions {\n userAgent?: string;\n ipAddress?: string;\n deviceName?: string;\n}\n\ninterface RefreshTokenPayload {\n sessionId: SessionId;\n userId: UserId;\n type: 'refresh';\n}\n\n/** Allowed fields for user updates */\nexport interface UserUpdateInput {\n email?: string;\n firstName?: string;\n lastName?: string;\n roles?: string[];\n isEmailVerified?: boolean;\n [key: string]: unknown;\n}\n\nexport class AuthService {\n private storage: IStorageProvider;\n private email: IEmailProvider;\n private config: AuthConfig;\n\n constructor(config: AuthConfig) {\n this.config = config;\n this.storage = config.storage;\n this.email = createEmailProvider(config.email);\n }\n\n private generateAccessToken(user: User): string {\n // @ts-ignore\n return jwt.sign(\n {\n id: user.id,\n email: user.email,\n roles: user.roles,\n isEmailVerified: user.isEmailVerified,\n },\n this.config.jwt.privateKey,\n {\n algorithm: 'RS256',\n expiresIn: this.config.jwt.expiresIn,\n keyid: this.config.jwt.keyId,\n }\n );\n }\n\n private generateRefreshToken(sessionId: SessionId, userId: UserId): string {\n const payload: RefreshTokenPayload = {\n sessionId,\n userId,\n type: 'refresh',\n };\n\n // @ts-ignore\n return jwt.sign(payload, this.config.jwt.privateKey, {\n algorithm: 'RS256',\n expiresIn: this.config.jwt.refreshTokenExpiresIn || '7d',\n keyid: this.config.jwt.keyId,\n });\n }\n\n private verifyRefreshToken(token: string): RefreshTokenPayload {\n try {\n const payload = jwt.verify(token, this.config.jwt.publicKey, {\n algorithms: ['RS256'],\n }) as RefreshTokenPayload;\n\n if (payload.type !== 'refresh') {\n throw new Error('Invalid token type');\n }\n\n return payload;\n } catch (_error) {\n throw new Error('Invalid refresh token');\n }\n }\n\n private hashToken(token: string): string {\n return crypto.createHash('sha256').update(token).digest('hex');\n }\n\n private getRefreshTokenExpiryMs(): number {\n const expiresIn = this.config.jwt.refreshTokenExpiresIn || '7d';\n const match = expiresIn.match(/^(\\d+)([smhd])$/);\n if (!match) {\n return 7 * 24 * 60 * 60 * 1000; // Default 7 days\n }\n\n const value = parseInt(match[1], 10);\n const unit = match[2];\n const multipliers: Record<string, number> = {\n s: 1000,\n m: 60 * 1000,\n h: 60 * 60 * 1000,\n d: 24 * 60 * 60 * 1000,\n };\n\n return value * multipliers[unit];\n }\n\n private async hashPassword(password: string): Promise<string> {\n return bcrypt.hash(password, 10);\n }\n\n private async createSession(\n userId: UserId,\n options: LoginOptions = {}\n ): Promise<{ session: Session; refreshToken: string }> {\n const now = Date.now();\n const sessionId = this.config.idGeneration === 'uuid' ? uuidv4() : undefined;\n\n const tempSession: CreateSessionInput = {\n ...(sessionId ? { id: sessionId } : {}),\n userId,\n refreshTokenHash: '',\n userAgent: options.userAgent,\n ipAddress: options.ipAddress,\n deviceName: options.deviceName,\n createdAt: now,\n lastUsedAt: now,\n expiresAt: now + this.getRefreshTokenExpiryMs(),\n };\n\n const session = await this.storage.createSession(tempSession);\n const refreshToken = this.generateRefreshToken(session.id, userId);\n await this.storage.updateSession(session.id, {\n refreshTokenHash: this.hashToken(refreshToken),\n });\n\n return {\n session: { ...session, refreshTokenHash: this.hashToken(refreshToken) },\n refreshToken,\n };\n }\n\n async signup(\n createUserInput: CreateUserInput,\n password: string,\n options: LoginOptions = {}\n ): Promise<{ user: User; tokens: AuthTokens }> {\n const existingUser = await this.storage.getUserByEmail(createUserInput.email);\n if (existingUser) {\n throw new Error('User already exists');\n }\n\n const verificationToken = uuidv4();\n const id = this.config.idGeneration === 'uuid' ? uuidv4() : undefined;\n const user = {\n ...createUserInput,\n id,\n passwordHash: await this.hashPassword(password),\n isEmailVerified: false,\n verificationToken,\n createdAt: Date.now(),\n updatedAt: Date.now(),\n };\n\n const createdUser = await this.storage.createUser(user);\n await this.email.sendVerificationEmail(createUserInput.email, verificationToken);\n\n const { refreshToken } = await this.createSession(createdUser.id!, options);\n const accessToken = this.generateAccessToken(createdUser);\n return { user: createdUser, tokens: { accessToken, refreshToken } };\n }\n\n async signin(\n email: string,\n password: string,\n options: LoginOptions = {}\n ): Promise<{ user: User; tokens: AuthTokens }> {\n const user = await this.storage.getUserByEmail(email);\n if (!user) {\n throw new Error('Invalid credentials');\n }\n\n const isValidPassword = await bcrypt.compare(password, user.passwordHash);\n if (!isValidPassword) {\n throw new Error('Invalid credentials');\n }\n\n if (!user.isEmailVerified) {\n throw new Error('Please verify your email before signing in');\n }\n\n const { refreshToken } = await this.createSession(user.id, options);\n const accessToken = this.generateAccessToken(user);\n return { user, tokens: { accessToken, refreshToken } };\n }\n\n async refreshAccessToken(refreshToken: string): Promise<AuthTokens> {\n const payload = this.verifyRefreshToken(refreshToken);\n\n const session = await this.storage.getSessionById(payload.sessionId);\n if (!session) {\n throw new Error('Session not found');\n }\n\n // Verify token hash matches\n const tokenHash = this.hashToken(refreshToken);\n if (session.refreshTokenHash !== tokenHash) {\n throw new Error('Invalid refresh token');\n }\n\n // Check if session is expired\n if (session.expiresAt < Date.now()) {\n await this.storage.deleteSession(session.id!);\n throw new Error('Session expired');\n }\n\n const user = await this.storage.getUserById(session.userId);\n if (!user) {\n throw new Error('User not found');\n }\n\n // Generate new refresh token (rotation)\n const newRefreshToken = this.generateRefreshToken(session.id, user.id);\n\n // Update session with new token hash and last used time\n await this.storage.updateSession(session.id, {\n refreshTokenHash: this.hashToken(newRefreshToken),\n lastUsedAt: Date.now(),\n expiresAt: Date.now() + this.getRefreshTokenExpiryMs(),\n });\n\n const accessToken = this.generateAccessToken(user);\n return { accessToken, refreshToken: newRefreshToken };\n }\n\n /**\n * Logout from current session only\n */\n async logout(refreshToken: string): Promise<void> {\n try {\n const payload = this.verifyRefreshToken(refreshToken);\n await this.storage.deleteSession(payload.sessionId);\n } catch {\n // Token invalid - already logged out or expired\n }\n }\n\n /**\n * Revoke a specific session by ID\n */\n async revokeSession(userId: UserId, sessionId: SessionId): Promise<void> {\n const session = await this.storage.getSessionById(sessionId);\n if (!session || session.userId !== userId) {\n throw new Error('Session not found');\n }\n await this.storage.deleteSession(sessionId);\n }\n\n /**\n * Revoke all sessions for a user (logout from all devices)\n */\n async revokeAllSessions(userId: UserId): Promise<void> {\n await this.storage.deleteAllUserSessions(userId);\n }\n\n /**\n * Get all active sessions for a user\n */\n async getSessions(userId: UserId, currentRefreshToken?: string): Promise<SessionInfo[]> {\n const sessions = await this.storage.getSessionsByUserId(userId);\n const now = Date.now();\n\n let currentSessionId: SessionId | undefined;\n if (currentRefreshToken) {\n try {\n const payload = this.verifyRefreshToken(currentRefreshToken);\n currentSessionId = payload.sessionId;\n } catch {\n // Invalid token, no current session to mark\n }\n }\n\n return sessions\n .filter((s) => s.expiresAt > now)\n .map((session) => ({\n id: session.id!,\n deviceName: session.deviceName,\n userAgent: session.userAgent,\n ipAddress: session.ipAddress,\n createdAt: session.createdAt,\n lastUsedAt: session.lastUsedAt,\n isCurrent: session.id === currentSessionId,\n }));\n }\n\n async verifyEmail(token: string): Promise<void> {\n const user = await this.storage.getUserByVerifyEmailToken(token);\n if (!user || user.verificationToken !== token) {\n throw new Error('Invalid verification token');\n }\n\n await this.storage.updateUser(user.id!, {\n isEmailVerified: true,\n verificationToken: undefined,\n updatedAt: Date.now(),\n });\n }\n\n async initiatePasswordReset(email: string): Promise<void> {\n const user = await this.storage.getUserByEmail(email);\n if (!user) {\n throw new Error('User not found');\n }\n\n const resetToken = uuidv4();\n const resetExpires = Date.now() + 3600000; // 1 hour\n\n await this.storage.updateUser(user.id!, {\n resetPasswordToken: resetToken,\n resetPasswordExpires: resetExpires,\n updatedAt: Date.now(),\n });\n\n await this.email.sendPasswordResetEmail(email, resetToken);\n }\n\n async resetPassword(token: string, newPassword: string): Promise<void> {\n const user = await this.storage.getUserByResetPasswordToken(token);\n if (\n !user ||\n !user.resetPasswordToken ||\n user.resetPasswordToken !== token ||\n !user.resetPasswordExpires ||\n user.resetPasswordExpires < Date.now()\n ) {\n throw new Error('Invalid or expired reset token');\n }\n\n await this.storage.updateUser(user.id!, {\n passwordHash: await this.hashPassword(newPassword),\n resetPasswordToken: undefined,\n resetPasswordExpires: undefined,\n updatedAt: Date.now(),\n });\n }\n\n async verifyToken(\n token: string\n ): Promise<{ id: UserId; email: string; roles: string[]; isEmailVerified: boolean }> {\n try {\n return jwt.verify(token, this.config.jwt.publicKey, { algorithms: ['RS256'] }) as {\n id: UserId;\n email: string;\n roles: string[];\n isEmailVerified: boolean;\n };\n } catch (_error) {\n console.log('Token verification failed:', _error);\n throw new Error('Invalid token');\n }\n }\n\n async hasRole(userId: UserId, requiredRoles: string[]): Promise<boolean> {\n const user = await this.storage.getUserById(userId);\n if (!user) {\n return false;\n }\n\n return requiredRoles.some((role) => user.roles.includes(role));\n }\n\n // ==================== User Management ====================\n\n /**\n * Get a user by ID\n */\n async getUserById(id: UserId): Promise<User | null> {\n return this.storage.getUserById(id);\n }\n\n /**\n * Get a user by email\n */\n async getUserByEmail(email: string): Promise<User | null> {\n return this.storage.getUserByEmail(email);\n }\n\n /**\n * Update a user's profile information.\n * Supports predefined fields (email, firstName, lastName, roles, isEmailVerified)\n * as well as any additional custom fields.\n */\n async updateUser(id: UserId, updates: UserUpdateInput): Promise<User> {\n const user = await this.storage.getUserById(id);\n if (!user) {\n throw new Error('User not found');\n }\n\n if (updates.email !== undefined && updates.email !== user.email) {\n const existingUser = await this.storage.getUserByEmail(updates.email);\n if (existingUser) {\n throw new Error('Email already in use');\n }\n }\n\n const { email, firstName, lastName, roles, isEmailVerified, ...additionalFields } = updates;\n const mergedUpdates: Record<string, unknown> = {\n updatedAt: Date.now(),\n ...additionalFields,\n };\n\n if (email !== undefined) mergedUpdates.email = email;\n if (firstName !== undefined) mergedUpdates.firstName = firstName;\n if (lastName !== undefined) mergedUpdates.lastName = lastName;\n if (roles !== undefined) mergedUpdates.roles = roles;\n if (isEmailVerified !== undefined) mergedUpdates.isEmailVerified = isEmailVerified;\n\n await this.storage.updateUser(id, mergedUpdates);\n\n return { ...user, ...mergedUpdates } as User;\n }\n\n /**\n * Delete a user and all their sessions\n */\n async deleteUser(id: UserId): Promise<void> {\n const user = await this.storage.getUserById(id);\n if (!user) {\n throw new Error('User not found');\n }\n await this.storage.deleteUser(id);\n }\n\n /**\n * Find users with filtering and pagination\n *\n * @example\n * // Find all admins\n * const { users } = await authService.findUsers({ hasAnyRole: ['ADMIN'] });\n *\n * // Find verified users with email containing '@company.com'\n * const result = await authService.findUsers({\n * emailContains: '@company.com',\n * isEmailVerified: true\n * }, { limit: 20 });\n */\n async findUsers(filter: UserFilter, options?: FindUsersOptions): Promise<FindUsersResult> {\n return this.storage.findUsers(filter, options);\n }\n}\n","import nodemailer from 'nodemailer';\nimport type { IEmailProvider, SMTPConfig, EmailTemplates } from './types';\nimport { defaultTemplates } from './defaultTemplates';\n\nexport class SMTPEmailProvider implements IEmailProvider {\n private transporter: nodemailer.Transporter;\n private config: { from: string; templates: EmailTemplates };\n\n constructor(from: string, config: SMTPConfig, templates?: EmailTemplates) {\n this.transporter = nodemailer.createTransport(config);\n this.config = { from, templates: templates ?? defaultTemplates };\n }\n\n async sendVerificationEmail(email: string, token: string): Promise<void> {\n const { subject, html } = this.config.templates.verification;\n\n await this.transporter.sendMail({\n from: this.config.from,\n to: email,\n subject,\n html: html(token),\n });\n }\n\n async sendPasswordResetEmail(email: string, token: string): Promise<void> {\n const { subject, html } = this.config.templates.resetPassword;\n\n await this.transporter.sendMail({\n from: this.config.from,\n to: email,\n subject,\n html: html(token),\n });\n }\n\n async verifyConnection(): Promise<boolean> {\n try {\n await this.transporter.verify();\n return true;\n } catch (_error) {\n return false;\n }\n }\n}\n","import type { EmailTemplates } from './types';\n\nexport const defaultTemplates: EmailTemplates = {\n verification: {\n subject: 'Verify your email address',\n html: (token: string) => `\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>Verify your email address</title>\n <style>\n body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; }\n .button { display: inline-block; padding: 12px 24px; background-color: #007bff; color: white; text-decoration: none; border-radius: 4px; margin: 20px 0; }\n .footer { margin-top: 30px; font-size: 0.9em; color: #666; }\n </style>\n</head>\n<body>\n <h1>Verify your email address</h1>\n <p>Thank you for signing up! Please click the button below to verify your email address:</p>\n <a href=\"${token}\" class=\"button\">Verify Email Address</a>\n <p>Or copy and paste this link in your browser:</p>\n <p>${token}</p>\n <div class=\"footer\">\n <p>If you didn't create an account, you can safely ignore this email.</p>\n </div>\n</body>\n</html>`,\n },\n resetPassword: {\n subject: 'Reset your password',\n html: (token: string) => `\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>Reset your password</title>\n <style>\n body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; }\n .button { display: inline-block; padding: 12px 24px; background-color: #007bff; color: white; text-decoration: none; border-radius: 4px; margin: 20px 0; }\n .footer { margin-top: 30px; font-size: 0.9em; color: #666; }\n </style>\n</head>\n<body>\n <h1>Reset your password</h1>\n <p>We received a request to reset your password. Click the button below to create a new password:</p>\n <a href=\"${token}\" class=\"button\">Reset Password</a>\n <p>Or copy and paste this link in your browser:</p>\n <p>${token}</p>\n <div class=\"footer\">\n <p>If you didn't request a password reset, you can safely ignore this email.</p>\n <p>This link will expire in 24 hours.</p>\n </div>\n</body>\n</html>`,\n },\n};\n","import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2';\nimport type { IEmailProvider, SESConfig, EmailTemplates } from './types';\nimport { defaultTemplates } from './defaultTemplates';\n\nexport class SESEmailProvider implements IEmailProvider {\n private client: SESv2Client;\n private config: { from: string; templates: EmailTemplates; sourceArn?: string };\n\n constructor(from: string, config: SESConfig, templates?: EmailTemplates) {\n this.client = new SESv2Client({\n region: config.region,\n credentials: config.credentials,\n });\n this.config = {\n from,\n templates: templates ?? defaultTemplates,\n sourceArn: config.sourceArn,\n };\n }\n\n private async sendEmail(to: string, subject: string, html: string): Promise<void> {\n const command = new SendEmailCommand({\n FromEmailAddress: this.config.from,\n FromEmailAddressIdentityArn: this.config.sourceArn,\n Destination: {\n ToAddresses: [to],\n },\n Content: {\n Simple: {\n Subject: {\n Data: subject,\n Charset: 'UTF-8',\n },\n Body: {\n Html: {\n Data: html,\n Charset: 'UTF-8',\n },\n },\n },\n },\n });\n\n await this.client.send(command);\n }\n\n async sendVerificationEmail(email: string, token: string): Promise<void> {\n const { subject, html } = this.config.templates.verification;\n await this.sendEmail(email, subject, html(token));\n }\n\n async sendPasswordResetEmail(email: string, token: string): Promise<void> {\n const { subject, html } = this.config.templates.resetPassword;\n await this.sendEmail(email, subject, html(token));\n }\n\n async verifyConnection(): Promise<boolean> {\n try {\n // SES doesn't have a direct verify method, so we'll check if we can describe our sending status\n await this.client.send(\n new SendEmailCommand({\n FromEmailAddress: this.config.from,\n FromEmailAddressIdentityArn: this.config.sourceArn,\n Destination: {\n ToAddresses: [this.config.from], // Send to ourselves as a test\n },\n Content: {\n Simple: {\n Subject: { Data: 'Test Connection', Charset: 'UTF-8' },\n Body: { Text: { Data: 'Test', Charset: 'UTF-8' } },\n },\n },\n })\n );\n return true;\n } catch (_error) {\n return false;\n }\n }\n}\n","import type { IEmailProvider, EmailConfig, SMTPConfig, SESConfig } from './types';\nimport { SMTPEmailProvider } from './smtp';\nimport { SESEmailProvider } from './ses';\n\nexport function createEmailProvider(config: EmailConfig): IEmailProvider {\n switch (config.type) {\n case 'smtp':\n return new SMTPEmailProvider(\n config.from,\n config.options as SMTPConfig,\n config.templates\n );\n case 'ses':\n return new SESEmailProvider(config.from, config.options as SESConfig, config.templates);\n default:\n throw new Error(`Unsupported email provider type: ${config.type}`);\n }\n}\n\nexport * from './types';\nexport * from './smtp';\nexport * from './ses';\n","import type { AuthService } from '../services/auth';\n\nexport class AuthMiddleware {\n private authService: AuthService;\n\n constructor(authService: AuthService) {\n this.authService = authService;\n }\n\n verifyToken() {\n return async (req: any, res: any, next: any) => {\n try {\n const token = req.headers.authorization?.split(' ')[1];\n if (!token) {\n throw new Error('No token provided');\n }\n\n req.user = await this.authService.verifyToken(token);\n next();\n } catch (_error) {\n res.status(401).json({ error: 'Unauthorized' });\n }\n };\n }\n\n requireRoles(roles: string[]) {\n return async (req: any, res: any, next: any) => {\n try {\n const hasRole = await this.authService.hasRole(req.user.id, roles);\n if (!hasRole) {\n throw new Error('Insufficient permissions');\n }\n next();\n } catch (_error) {\n res.status(403).json({ error: 'Forbidden' });\n }\n };\n }\n\n requireEmailVerified() {\n return (req: any, res: any, next: any) => {\n if (!req.user.isEmailVerified) {\n return res.status(403).json({ error: 'Email not verified' });\n }\n next();\n };\n }\n}\n","import { z } from 'zod';\nimport type { IStorageProvider } from '../storage/types';\nimport type { EmailConfig } from '../email/types';\n\n/** ID can be either auto-incremented number or UUID string */\nexport type UserId = string | number;\nexport type SessionId = string | number;\n\nexport const UserSchema = z.object({\n id: z.union([z.string(), z.number()]),\n email: z.string().email(),\n firstName: z.string().optional(),\n lastName: z.string().optional(),\n passwordHash: z.string(),\n roles: z.array(z.string()),\n isEmailVerified: z.boolean(),\n verificationToken: z.string().optional(),\n resetPasswordToken: z.string().optional(),\n resetPasswordExpires: z.number().optional(),\n createdAt: z.number(),\n updatedAt: z.number(),\n});\n\nexport const CreateUserSchema = UserSchema.omit({\n id: true,\n createdAt: true,\n updatedAt: true,\n}).extend({\n createdAt: z.number().optional(),\n updatedAt: z.number().optional(),\n id: z.union([z.string(), z.number()]).optional(),\n});\n\nexport type User = z.infer<typeof UserSchema>;\nexport type CreateUserInput = z.infer<typeof CreateUserSchema>;\n\nexport const SessionSchema = z.object({\n id: z.union([z.string(), z.number()]),\n userId: z.union([z.string(), z.number()]),\n refreshTokenHash: z.string(),\n userAgent: z.string().optional(),\n ipAddress: z.string().optional(),\n deviceName: z.string().optional(),\n createdAt: z.number(),\n lastUsedAt: z.number(),\n expiresAt: z.number(),\n});\n\nexport const CreateSessionSchema = SessionSchema.omit({\n id: true,\n createdAt: true,\n lastUsedAt: true,\n}).extend({\n createdAt: z.number().optional(),\n lastUsedAt: z.number().optional(),\n id: z.union([z.string(), z.number()]).optional(),\n});\nexport type Session = z.infer<typeof SessionSchema>;\nexport type CreateSessionInput = z.infer<typeof CreateSessionSchema>;\n\n/** Session info returned to clients (without sensitive data) */\nexport interface SessionInfo {\n id: SessionId;\n deviceName?: string;\n userAgent?: string;\n ipAddress?: string;\n createdAt: number;\n lastUsedAt: number;\n isCurrent: boolean;\n}\n\nexport interface AuthConfig {\n jwt: {\n privateKey: string;\n publicKey: string;\n keyId: string;\n expiresIn: string;\n refreshTokenExpiresIn?: string;\n };\n storage: IStorageProvider;\n idGeneration: 'uuid' | 'increment';\n email: EmailConfig;\n}\n"],"mappings":"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","names":["nodemailer","jwt","crypto","bcrypt","uuidv4"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/services/auth.ts","../src/email/smtp.ts","../src/email/defaultTemplates.ts","../src/email/ses.ts","../src/email/index.ts","../src/middleware/auth.ts","../src/types/index.ts"],"sourcesContent":["export * from './services/auth';\nexport * from './middleware/auth';\nexport * from './types';\nexport * from './storage';\n","import bcrypt from 'bcryptjs';\nimport jwt from 'jsonwebtoken';\nimport { v4 as uuidv4 } from 'uuid';\nimport crypto from 'crypto';\nimport type {\n AuthConfig,\n CreateSessionInput,\n CreateUserInput,\n Session,\n SessionId,\n SessionInfo,\n User,\n UserId,\n} from '../types';\nimport type {\n FindUsersOptions,\n FindUsersResult,\n IStorageProvider,\n UserFilter,\n} from '../storage/types';\nimport type { IEmailProvider } from '../email/types';\nimport { createEmailProvider } from '../email';\n\nexport interface AuthTokens {\n accessToken: string;\n refreshToken: string;\n}\n\nexport interface LoginOptions {\n userAgent?: string;\n ipAddress?: string;\n deviceName?: string;\n}\n\ninterface RefreshTokenPayload {\n sessionId: SessionId;\n userId: UserId;\n type: 'refresh';\n}\n\n/** Allowed fields for user updates */\nexport interface UserUpdateInput {\n email?: string;\n firstName?: string;\n lastName?: string;\n roles?: string[];\n isEmailVerified?: boolean;\n [key: string]: unknown;\n}\n\nexport class AuthService {\n private storage: IStorageProvider;\n private email: IEmailProvider;\n private config: AuthConfig;\n\n constructor(config: AuthConfig) {\n this.config = config;\n this.storage = config.storage;\n this.email = createEmailProvider(config.email);\n }\n\n private generateAccessToken(user: User): string {\n // @ts-ignore\n return jwt.sign(\n {\n id: user.id,\n email: user.email,\n roles: user.roles,\n isEmailVerified: user.isEmailVerified,\n },\n this.config.jwt.privateKey,\n {\n algorithm: 'RS256',\n expiresIn: this.config.jwt.expiresIn,\n keyid: this.config.jwt.keyId,\n }\n );\n }\n\n private generateRefreshToken(sessionId: SessionId, userId: UserId): string {\n const payload: RefreshTokenPayload = {\n sessionId,\n userId,\n type: 'refresh',\n };\n\n // @ts-ignore\n return jwt.sign(payload, this.config.jwt.privateKey, {\n algorithm: 'RS256',\n expiresIn: this.config.jwt.refreshTokenExpiresIn || '7d',\n keyid: this.config.jwt.keyId,\n });\n }\n\n private verifyRefreshToken(token: string): RefreshTokenPayload {\n try {\n const payload = jwt.verify(token, this.config.jwt.publicKey, {\n algorithms: ['RS256'],\n }) as RefreshTokenPayload;\n\n if (payload.type !== 'refresh') {\n throw new Error('Invalid token type');\n }\n\n return payload;\n } catch (_error) {\n throw new Error('Invalid refresh token');\n }\n }\n\n private hashToken(token: string): string {\n return crypto.createHash('sha256').update(token).digest('hex');\n }\n\n /** Return current time as epoch seconds */\n private now(): number {\n return Math.floor(Date.now() / 1000);\n }\n\n private getRefreshTokenExpirySecs(): number {\n const expiresIn = this.config.jwt.refreshTokenExpiresIn || '7d';\n const match = expiresIn.match(/^(\\d+)([smhd])$/);\n if (!match) {\n return 7 * 24 * 60 * 60; // Default 7 days in seconds\n }\n\n const value = parseInt(match[1], 10);\n const unit = match[2];\n const multipliers: Record<string, number> = {\n s: 1,\n m: 60,\n h: 60 * 60,\n d: 24 * 60 * 60,\n };\n\n return value * multipliers[unit];\n }\n\n private async hashPassword(password: string): Promise<string> {\n return bcrypt.hash(password, 10);\n }\n\n private async createSession(\n userId: UserId,\n options: LoginOptions = {}\n ): Promise<{ session: Session; refreshToken: string }> {\n const now = this.now();\n const sessionId = this.config.idGeneration === 'uuid' ? uuidv4() : undefined;\n\n const tempSession: CreateSessionInput = {\n ...(sessionId ? { id: sessionId } : {}),\n userId,\n refreshTokenHash: '',\n userAgent: options.userAgent,\n ipAddress: options.ipAddress,\n deviceName: options.deviceName,\n createdAt: now,\n lastUsedAt: now,\n expiresAt: now + this.getRefreshTokenExpirySecs(),\n };\n\n const session = await this.storage.createSession(tempSession);\n const refreshToken = this.generateRefreshToken(session.id, userId);\n await this.storage.updateSession(session.id, {\n refreshTokenHash: this.hashToken(refreshToken),\n });\n\n return {\n session: { ...session, refreshTokenHash: this.hashToken(refreshToken) },\n refreshToken,\n };\n }\n\n async signup(\n createUserInput: CreateUserInput,\n password: string,\n options: LoginOptions = {}\n ): Promise<{ user: User; tokens: AuthTokens }> {\n const existingUser = await this.storage.getUserByEmail(createUserInput.email);\n if (existingUser) {\n throw new Error('User already exists');\n }\n\n const verificationToken = uuidv4();\n const id = this.config.idGeneration === 'uuid' ? uuidv4() : undefined;\n const user = {\n ...createUserInput,\n id,\n passwordHash: await this.hashPassword(password),\n isEmailVerified: false,\n verificationToken,\n createdAt: this.now(),\n updatedAt: this.now(),\n };\n\n const createdUser = await this.storage.createUser(user);\n await this.email.sendVerificationEmail(createUserInput.email, verificationToken);\n\n const { refreshToken } = await this.createSession(createdUser.id!, options);\n const accessToken = this.generateAccessToken(createdUser);\n return { user: createdUser, tokens: { accessToken, refreshToken } };\n }\n\n async signin(\n email: string,\n password: string,\n options: LoginOptions = {}\n ): Promise<{ user: User; tokens: AuthTokens }> {\n const user = await this.storage.getUserByEmail(email);\n if (!user) {\n throw new Error('Invalid credentials');\n }\n\n const isValidPassword = await bcrypt.compare(password, user.passwordHash);\n if (!isValidPassword) {\n throw new Error('Invalid credentials');\n }\n\n if (!user.isEmailVerified) {\n throw new Error('Please verify your email before signing in');\n }\n\n const { refreshToken } = await this.createSession(user.id, options);\n const accessToken = this.generateAccessToken(user);\n return { user, tokens: { accessToken, refreshToken } };\n }\n\n async refreshAccessToken(refreshToken: string): Promise<AuthTokens> {\n const payload = this.verifyRefreshToken(refreshToken);\n\n const session = await this.storage.getSessionById(payload.sessionId);\n if (!session) {\n throw new Error('Session not found');\n }\n\n // Verify token hash matches\n const tokenHash = this.hashToken(refreshToken);\n if (session.refreshTokenHash !== tokenHash) {\n throw new Error('Invalid refresh token');\n }\n\n // Check if session is expired\n if (session.expiresAt < this.now()) {\n await this.storage.deleteSession(session.id!);\n throw new Error('Session expired');\n }\n\n const user = await this.storage.getUserById(session.userId);\n if (!user) {\n throw new Error('User not found');\n }\n\n // Generate new refresh token (rotation)\n const newRefreshToken = this.generateRefreshToken(session.id, user.id);\n\n // Update session with new token hash and last used time\n await this.storage.updateSession(session.id, {\n refreshTokenHash: this.hashToken(newRefreshToken),\n lastUsedAt: this.now(),\n expiresAt: this.now() + this.getRefreshTokenExpirySecs(),\n });\n\n const accessToken = this.generateAccessToken(user);\n return { accessToken, refreshToken: newRefreshToken };\n }\n\n /**\n * Logout from current session only\n */\n async logout(refreshToken: string): Promise<void> {\n try {\n const payload = this.verifyRefreshToken(refreshToken);\n await this.storage.deleteSession(payload.sessionId);\n } catch {\n // Token invalid - already logged out or expired\n }\n }\n\n /**\n * Revoke a specific session by ID\n */\n async revokeSession(userId: UserId, sessionId: SessionId): Promise<void> {\n const session = await this.storage.getSessionById(sessionId);\n if (!session || session.userId !== userId) {\n throw new Error('Session not found');\n }\n await this.storage.deleteSession(sessionId);\n }\n\n /**\n * Revoke all sessions for a user (logout from all devices)\n */\n async revokeAllSessions(userId: UserId): Promise<void> {\n await this.storage.deleteAllUserSessions(userId);\n }\n\n /**\n * Get all active sessions for a user\n */\n async getSessions(userId: UserId, currentRefreshToken?: string): Promise<SessionInfo[]> {\n const sessions = await this.storage.getSessionsByUserId(userId);\n const now = this.now();\n\n let currentSessionId: SessionId | undefined;\n if (currentRefreshToken) {\n try {\n const payload = this.verifyRefreshToken(currentRefreshToken);\n currentSessionId = payload.sessionId;\n } catch {\n // Invalid token, no current session to mark\n }\n }\n\n return sessions\n .filter((s) => s.expiresAt > now)\n .map((session) => ({\n id: session.id!,\n deviceName: session.deviceName,\n userAgent: session.userAgent,\n ipAddress: session.ipAddress,\n createdAt: session.createdAt,\n lastUsedAt: session.lastUsedAt,\n isCurrent: session.id === currentSessionId,\n }));\n }\n\n async verifyEmail(token: string): Promise<void> {\n const user = await this.storage.getUserByVerifyEmailToken(token);\n if (!user || user.verificationToken !== token) {\n throw new Error('Invalid verification token');\n }\n\n await this.storage.updateUser(user.id!, {\n isEmailVerified: true,\n verificationToken: undefined,\n updatedAt: this.now(),\n });\n }\n\n async initiatePasswordReset(email: string): Promise<void> {\n const user = await this.storage.getUserByEmail(email);\n if (!user) {\n throw new Error('User not found');\n }\n\n const resetToken = uuidv4();\n const resetExpires = this.now() + 3600; // 1 hour in seconds\n\n await this.storage.updateUser(user.id!, {\n resetPasswordToken: resetToken,\n resetPasswordExpires: resetExpires,\n updatedAt: this.now(),\n });\n\n await this.email.sendPasswordResetEmail(email, resetToken);\n }\n\n async resetPassword(token: string, newPassword: string): Promise<void> {\n const user = await this.storage.getUserByResetPasswordToken(token);\n if (\n !user ||\n !user.resetPasswordToken ||\n user.resetPasswordToken !== token ||\n !user.resetPasswordExpires ||\n user.resetPasswordExpires < this.now()\n ) {\n throw new Error('Invalid or expired reset token');\n }\n\n await this.storage.updateUser(user.id!, {\n passwordHash: await this.hashPassword(newPassword),\n resetPasswordToken: undefined,\n resetPasswordExpires: undefined,\n updatedAt: this.now(),\n });\n }\n\n async verifyToken(\n token: string\n ): Promise<{ id: UserId; email: string; roles: string[]; isEmailVerified: boolean }> {\n try {\n return jwt.verify(token, this.config.jwt.publicKey, { algorithms: ['RS256'] }) as {\n id: UserId;\n email: string;\n roles: string[];\n isEmailVerified: boolean;\n };\n } catch (_error) {\n console.log('Token verification failed:', _error);\n throw new Error('Invalid token');\n }\n }\n\n async hasRole(userId: UserId, requiredRoles: string[]): Promise<boolean> {\n const user = await this.storage.getUserById(userId);\n if (!user) {\n return false;\n }\n\n return requiredRoles.some((role) => user.roles.includes(role));\n }\n\n // ==================== User Management ====================\n\n /**\n * Get a user by ID\n */\n async getUserById(id: UserId): Promise<User | null> {\n return this.storage.getUserById(id);\n }\n\n /**\n * Get a user by email\n */\n async getUserByEmail(email: string): Promise<User | null> {\n return this.storage.getUserByEmail(email);\n }\n\n /**\n * Update a user's profile information.\n * Supports predefined fields (email, firstName, lastName, roles, isEmailVerified)\n * as well as any additional custom fields.\n */\n async updateUser(id: UserId, updates: UserUpdateInput): Promise<User> {\n const user = await this.storage.getUserById(id);\n if (!user) {\n throw new Error('User not found');\n }\n\n if (updates.email !== undefined && updates.email !== user.email) {\n const existingUser = await this.storage.getUserByEmail(updates.email);\n if (existingUser) {\n throw new Error('Email already in use');\n }\n }\n\n const { email, firstName, lastName, roles, isEmailVerified, ...additionalFields } = updates;\n const mergedUpdates: Record<string, unknown> = {\n updatedAt: this.now(),\n ...additionalFields,\n };\n\n if (email !== undefined) mergedUpdates.email = email;\n if (firstName !== undefined) mergedUpdates.firstName = firstName;\n if (lastName !== undefined) mergedUpdates.lastName = lastName;\n if (roles !== undefined) mergedUpdates.roles = roles;\n if (isEmailVerified !== undefined) mergedUpdates.isEmailVerified = isEmailVerified;\n\n await this.storage.updateUser(id, mergedUpdates);\n\n return { ...user, ...mergedUpdates } as User;\n }\n\n /**\n * Delete a user and all their sessions\n */\n async deleteUser(id: UserId): Promise<void> {\n const user = await this.storage.getUserById(id);\n if (!user) {\n throw new Error('User not found');\n }\n await this.storage.deleteUser(id);\n }\n\n /**\n * Find users with filtering and pagination\n *\n * @example\n * // Find all admins\n * const { users } = await authService.findUsers({ hasAnyRole: ['ADMIN'] });\n *\n * // Find verified users with email containing '@company.com'\n * const result = await authService.findUsers({\n * emailContains: '@company.com',\n * isEmailVerified: true\n * }, { limit: 20 });\n */\n async findUsers(filter: UserFilter, options?: FindUsersOptions): Promise<FindUsersResult> {\n return this.storage.findUsers(filter, options);\n }\n}\n","import nodemailer from 'nodemailer';\nimport type { IEmailProvider, SMTPConfig, EmailTemplates } from './types';\nimport { defaultTemplates } from './defaultTemplates';\n\nexport class SMTPEmailProvider implements IEmailProvider {\n private transporter: nodemailer.Transporter;\n private config: { from: string; templates: EmailTemplates };\n\n constructor(from: string, config: SMTPConfig, templates?: EmailTemplates) {\n this.transporter = nodemailer.createTransport(config);\n this.config = { from, templates: templates ?? defaultTemplates };\n }\n\n async sendVerificationEmail(email: string, token: string): Promise<void> {\n const { subject, html } = this.config.templates.verification;\n\n await this.transporter.sendMail({\n from: this.config.from,\n to: email,\n subject,\n html: html(token),\n });\n }\n\n async sendPasswordResetEmail(email: string, token: string): Promise<void> {\n const { subject, html } = this.config.templates.resetPassword;\n\n await this.transporter.sendMail({\n from: this.config.from,\n to: email,\n subject,\n html: html(token),\n });\n }\n\n async verifyConnection(): Promise<boolean> {\n try {\n await this.transporter.verify();\n return true;\n } catch (_error) {\n return false;\n }\n }\n}\n","import type { EmailTemplates } from './types';\n\nexport const defaultTemplates: EmailTemplates = {\n verification: {\n subject: 'Verify your email address',\n html: (token: string) => `\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>Verify your email address</title>\n <style>\n body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; }\n .button { display: inline-block; padding: 12px 24px; background-color: #007bff; color: white; text-decoration: none; border-radius: 4px; margin: 20px 0; }\n .footer { margin-top: 30px; font-size: 0.9em; color: #666; }\n </style>\n</head>\n<body>\n <h1>Verify your email address</h1>\n <p>Thank you for signing up! Please click the button below to verify your email address:</p>\n <a href=\"${token}\" class=\"button\">Verify Email Address</a>\n <p>Or copy and paste this link in your browser:</p>\n <p>${token}</p>\n <div class=\"footer\">\n <p>If you didn't create an account, you can safely ignore this email.</p>\n </div>\n</body>\n</html>`,\n },\n resetPassword: {\n subject: 'Reset your password',\n html: (token: string) => `\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>Reset your password</title>\n <style>\n body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; }\n .button { display: inline-block; padding: 12px 24px; background-color: #007bff; color: white; text-decoration: none; border-radius: 4px; margin: 20px 0; }\n .footer { margin-top: 30px; font-size: 0.9em; color: #666; }\n </style>\n</head>\n<body>\n <h1>Reset your password</h1>\n <p>We received a request to reset your password. Click the button below to create a new password:</p>\n <a href=\"${token}\" class=\"button\">Reset Password</a>\n <p>Or copy and paste this link in your browser:</p>\n <p>${token}</p>\n <div class=\"footer\">\n <p>If you didn't request a password reset, you can safely ignore this email.</p>\n <p>This link will expire in 24 hours.</p>\n </div>\n</body>\n</html>`,\n },\n};\n","import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2';\nimport type { IEmailProvider, SESConfig, EmailTemplates } from './types';\nimport { defaultTemplates } from './defaultTemplates';\n\nexport class SESEmailProvider implements IEmailProvider {\n private client: SESv2Client;\n private config: { from: string; templates: EmailTemplates; sourceArn?: string };\n\n constructor(from: string, config: SESConfig, templates?: EmailTemplates) {\n this.client = new SESv2Client({\n region: config.region,\n credentials: config.credentials,\n });\n this.config = {\n from,\n templates: templates ?? defaultTemplates,\n sourceArn: config.sourceArn,\n };\n }\n\n private async sendEmail(to: string, subject: string, html: string): Promise<void> {\n const command = new SendEmailCommand({\n FromEmailAddress: this.config.from,\n FromEmailAddressIdentityArn: this.config.sourceArn,\n Destination: {\n ToAddresses: [to],\n },\n Content: {\n Simple: {\n Subject: {\n Data: subject,\n Charset: 'UTF-8',\n },\n Body: {\n Html: {\n Data: html,\n Charset: 'UTF-8',\n },\n },\n },\n },\n });\n\n await this.client.send(command);\n }\n\n async sendVerificationEmail(email: string, token: string): Promise<void> {\n const { subject, html } = this.config.templates.verification;\n await this.sendEmail(email, subject, html(token));\n }\n\n async sendPasswordResetEmail(email: string, token: string): Promise<void> {\n const { subject, html } = this.config.templates.resetPassword;\n await this.sendEmail(email, subject, html(token));\n }\n\n async verifyConnection(): Promise<boolean> {\n try {\n // SES doesn't have a direct verify method, so we'll check if we can describe our sending status\n await this.client.send(\n new SendEmailCommand({\n FromEmailAddress: this.config.from,\n FromEmailAddressIdentityArn: this.config.sourceArn,\n Destination: {\n ToAddresses: [this.config.from], // Send to ourselves as a test\n },\n Content: {\n Simple: {\n Subject: { Data: 'Test Connection', Charset: 'UTF-8' },\n Body: { Text: { Data: 'Test', Charset: 'UTF-8' } },\n },\n },\n })\n );\n return true;\n } catch (_error) {\n return false;\n }\n }\n}\n","import type { IEmailProvider, EmailConfig, SMTPConfig, SESConfig } from './types';\nimport { SMTPEmailProvider } from './smtp';\nimport { SESEmailProvider } from './ses';\n\nexport function createEmailProvider(config: EmailConfig): IEmailProvider {\n switch (config.type) {\n case 'smtp':\n return new SMTPEmailProvider(\n config.from,\n config.options as SMTPConfig,\n config.templates\n );\n case 'ses':\n return new SESEmailProvider(config.from, config.options as SESConfig, config.templates);\n default:\n throw new Error(`Unsupported email provider type: ${config.type}`);\n }\n}\n\nexport * from './types';\nexport * from './smtp';\nexport * from './ses';\n","import type { AuthService } from '../services/auth';\n\nexport class AuthMiddleware {\n private authService: AuthService;\n\n constructor(authService: AuthService) {\n this.authService = authService;\n }\n\n verifyToken() {\n return async (req: any, res: any, next: any) => {\n try {\n const token = req.headers.authorization?.split(' ')[1];\n if (!token) {\n throw new Error('No token provided');\n }\n\n req.user = await this.authService.verifyToken(token);\n next();\n } catch (_error) {\n res.status(401).json({ error: 'Unauthorized' });\n }\n };\n }\n\n requireRoles(roles: string[]) {\n return async (req: any, res: any, next: any) => {\n try {\n const hasRole = await this.authService.hasRole(req.user.id, roles);\n if (!hasRole) {\n throw new Error('Insufficient permissions');\n }\n next();\n } catch (_error) {\n res.status(403).json({ error: 'Forbidden' });\n }\n };\n }\n\n requireEmailVerified() {\n return (req: any, res: any, next: any) => {\n if (!req.user.isEmailVerified) {\n return res.status(403).json({ error: 'Email not verified' });\n }\n next();\n };\n }\n}\n","import { z } from 'zod';\nimport type { IStorageProvider } from '../storage/types';\nimport type { EmailConfig } from '../email/types';\n\n/** ID can be either auto-incremented number or UUID string */\nexport type UserId = string | number;\nexport type SessionId = string | number;\n\nexport const UserSchema = z.object({\n id: z.union([z.string(), z.number()]),\n email: z.string().email(),\n firstName: z.string().optional(),\n lastName: z.string().optional(),\n passwordHash: z.string(),\n roles: z.array(z.string()),\n isEmailVerified: z.boolean(),\n verificationToken: z.string().optional(),\n resetPasswordToken: z.string().optional(),\n resetPasswordExpires: z.number().optional(),\n createdAt: z.number(),\n updatedAt: z.number(),\n});\n\nexport const CreateUserSchema = UserSchema.omit({\n id: true,\n createdAt: true,\n updatedAt: true,\n}).extend({\n createdAt: z.number().optional(),\n updatedAt: z.number().optional(),\n id: z.union([z.string(), z.number()]).optional(),\n});\n\nexport type User = z.infer<typeof UserSchema>;\nexport type CreateUserInput = z.infer<typeof CreateUserSchema>;\n\nexport const SessionSchema = z.object({\n id: z.union([z.string(), z.number()]),\n userId: z.union([z.string(), z.number()]),\n refreshTokenHash: z.string(),\n userAgent: z.string().optional(),\n ipAddress: z.string().optional(),\n deviceName: z.string().optional(),\n createdAt: z.number(),\n lastUsedAt: z.number(),\n expiresAt: z.number(),\n});\n\nexport const CreateSessionSchema = SessionSchema.omit({\n id: true,\n createdAt: true,\n lastUsedAt: true,\n}).extend({\n createdAt: z.number().optional(),\n lastUsedAt: z.number().optional(),\n id: z.union([z.string(), z.number()]).optional(),\n});\nexport type Session = z.infer<typeof SessionSchema>;\nexport type CreateSessionInput = z.infer<typeof CreateSessionSchema>;\n\n/** Session info returned to clients (without sensitive data) */\nexport interface SessionInfo {\n id: SessionId;\n deviceName?: string;\n userAgent?: string;\n ipAddress?: string;\n createdAt: number;\n lastUsedAt: number;\n isCurrent: boolean;\n}\n\nexport interface AuthConfig {\n jwt: {\n privateKey: string;\n publicKey: string;\n keyId: string;\n expiresIn: string;\n refreshTokenExpiresIn?: string;\n };\n storage: IStorageProvider;\n idGeneration: 'uuid' | 'increment';\n email: EmailConfig;\n}\n"],"mappings":"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","names":["nodemailer","jwt","crypto","bcrypt","uuidv4"]}
|
package/dist/index.mjs
CHANGED
|
@@ -236,19 +236,23 @@ var AuthService = class {
|
|
|
236
236
|
hashToken(token) {
|
|
237
237
|
return crypto.createHash("sha256").update(token).digest("hex");
|
|
238
238
|
}
|
|
239
|
-
|
|
239
|
+
/** Return current time as epoch seconds */
|
|
240
|
+
now() {
|
|
241
|
+
return Math.floor(Date.now() / 1e3);
|
|
242
|
+
}
|
|
243
|
+
getRefreshTokenExpirySecs() {
|
|
240
244
|
const expiresIn = this.config.jwt.refreshTokenExpiresIn || "7d";
|
|
241
245
|
const match = expiresIn.match(/^(\d+)([smhd])$/);
|
|
242
246
|
if (!match) {
|
|
243
|
-
return 7 * 24 * 60 * 60
|
|
247
|
+
return 7 * 24 * 60 * 60;
|
|
244
248
|
}
|
|
245
249
|
const value = parseInt(match[1], 10);
|
|
246
250
|
const unit = match[2];
|
|
247
251
|
const multipliers = {
|
|
248
|
-
s:
|
|
249
|
-
m: 60
|
|
250
|
-
h: 60 * 60
|
|
251
|
-
d: 24 * 60 * 60
|
|
252
|
+
s: 1,
|
|
253
|
+
m: 60,
|
|
254
|
+
h: 60 * 60,
|
|
255
|
+
d: 24 * 60 * 60
|
|
252
256
|
};
|
|
253
257
|
return value * multipliers[unit];
|
|
254
258
|
}
|
|
@@ -256,7 +260,7 @@ var AuthService = class {
|
|
|
256
260
|
return bcrypt.hash(password, 10);
|
|
257
261
|
}
|
|
258
262
|
async createSession(userId, options = {}) {
|
|
259
|
-
const now =
|
|
263
|
+
const now = this.now();
|
|
260
264
|
const sessionId = this.config.idGeneration === "uuid" ? uuidv4() : void 0;
|
|
261
265
|
const tempSession = {
|
|
262
266
|
...sessionId ? { id: sessionId } : {},
|
|
@@ -267,7 +271,7 @@ var AuthService = class {
|
|
|
267
271
|
deviceName: options.deviceName,
|
|
268
272
|
createdAt: now,
|
|
269
273
|
lastUsedAt: now,
|
|
270
|
-
expiresAt: now + this.
|
|
274
|
+
expiresAt: now + this.getRefreshTokenExpirySecs()
|
|
271
275
|
};
|
|
272
276
|
const session = await this.storage.createSession(tempSession);
|
|
273
277
|
const refreshToken = this.generateRefreshToken(session.id, userId);
|
|
@@ -292,8 +296,8 @@ var AuthService = class {
|
|
|
292
296
|
passwordHash: await this.hashPassword(password),
|
|
293
297
|
isEmailVerified: false,
|
|
294
298
|
verificationToken,
|
|
295
|
-
createdAt:
|
|
296
|
-
updatedAt:
|
|
299
|
+
createdAt: this.now(),
|
|
300
|
+
updatedAt: this.now()
|
|
297
301
|
};
|
|
298
302
|
const createdUser = await this.storage.createUser(user);
|
|
299
303
|
await this.email.sendVerificationEmail(createUserInput.email, verificationToken);
|
|
@@ -327,7 +331,7 @@ var AuthService = class {
|
|
|
327
331
|
if (session.refreshTokenHash !== tokenHash) {
|
|
328
332
|
throw new Error("Invalid refresh token");
|
|
329
333
|
}
|
|
330
|
-
if (session.expiresAt <
|
|
334
|
+
if (session.expiresAt < this.now()) {
|
|
331
335
|
await this.storage.deleteSession(session.id);
|
|
332
336
|
throw new Error("Session expired");
|
|
333
337
|
}
|
|
@@ -338,8 +342,8 @@ var AuthService = class {
|
|
|
338
342
|
const newRefreshToken = this.generateRefreshToken(session.id, user.id);
|
|
339
343
|
await this.storage.updateSession(session.id, {
|
|
340
344
|
refreshTokenHash: this.hashToken(newRefreshToken),
|
|
341
|
-
lastUsedAt:
|
|
342
|
-
expiresAt:
|
|
345
|
+
lastUsedAt: this.now(),
|
|
346
|
+
expiresAt: this.now() + this.getRefreshTokenExpirySecs()
|
|
343
347
|
});
|
|
344
348
|
const accessToken = this.generateAccessToken(user);
|
|
345
349
|
return { accessToken, refreshToken: newRefreshToken };
|
|
@@ -375,7 +379,7 @@ var AuthService = class {
|
|
|
375
379
|
*/
|
|
376
380
|
async getSessions(userId, currentRefreshToken) {
|
|
377
381
|
const sessions = await this.storage.getSessionsByUserId(userId);
|
|
378
|
-
const now =
|
|
382
|
+
const now = this.now();
|
|
379
383
|
let currentSessionId;
|
|
380
384
|
if (currentRefreshToken) {
|
|
381
385
|
try {
|
|
@@ -402,7 +406,7 @@ var AuthService = class {
|
|
|
402
406
|
await this.storage.updateUser(user.id, {
|
|
403
407
|
isEmailVerified: true,
|
|
404
408
|
verificationToken: void 0,
|
|
405
|
-
updatedAt:
|
|
409
|
+
updatedAt: this.now()
|
|
406
410
|
});
|
|
407
411
|
}
|
|
408
412
|
async initiatePasswordReset(email) {
|
|
@@ -411,24 +415,24 @@ var AuthService = class {
|
|
|
411
415
|
throw new Error("User not found");
|
|
412
416
|
}
|
|
413
417
|
const resetToken = uuidv4();
|
|
414
|
-
const resetExpires =
|
|
418
|
+
const resetExpires = this.now() + 3600;
|
|
415
419
|
await this.storage.updateUser(user.id, {
|
|
416
420
|
resetPasswordToken: resetToken,
|
|
417
421
|
resetPasswordExpires: resetExpires,
|
|
418
|
-
updatedAt:
|
|
422
|
+
updatedAt: this.now()
|
|
419
423
|
});
|
|
420
424
|
await this.email.sendPasswordResetEmail(email, resetToken);
|
|
421
425
|
}
|
|
422
426
|
async resetPassword(token, newPassword) {
|
|
423
427
|
const user = await this.storage.getUserByResetPasswordToken(token);
|
|
424
|
-
if (!user || !user.resetPasswordToken || user.resetPasswordToken !== token || !user.resetPasswordExpires || user.resetPasswordExpires <
|
|
428
|
+
if (!user || !user.resetPasswordToken || user.resetPasswordToken !== token || !user.resetPasswordExpires || user.resetPasswordExpires < this.now()) {
|
|
425
429
|
throw new Error("Invalid or expired reset token");
|
|
426
430
|
}
|
|
427
431
|
await this.storage.updateUser(user.id, {
|
|
428
432
|
passwordHash: await this.hashPassword(newPassword),
|
|
429
433
|
resetPasswordToken: void 0,
|
|
430
434
|
resetPasswordExpires: void 0,
|
|
431
|
-
updatedAt:
|
|
435
|
+
updatedAt: this.now()
|
|
432
436
|
});
|
|
433
437
|
}
|
|
434
438
|
async verifyToken(token) {
|
|
@@ -477,7 +481,7 @@ var AuthService = class {
|
|
|
477
481
|
}
|
|
478
482
|
const { email, firstName, lastName, roles, isEmailVerified, ...additionalFields } = updates;
|
|
479
483
|
const mergedUpdates = {
|
|
480
|
-
updatedAt:
|
|
484
|
+
updatedAt: this.now(),
|
|
481
485
|
...additionalFields
|
|
482
486
|
};
|
|
483
487
|
if (email !== void 0) mergedUpdates.email = email;
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/services/auth.ts","../src/email/smtp.ts","../src/email/defaultTemplates.ts","../src/email/ses.ts","../src/email/index.ts","../src/middleware/auth.ts","../src/types/index.ts"],"sourcesContent":["import bcrypt from 'bcryptjs';\nimport jwt from 'jsonwebtoken';\nimport { v4 as uuidv4 } from 'uuid';\nimport crypto from 'crypto';\nimport type {\n AuthConfig,\n CreateSessionInput,\n CreateUserInput,\n Session,\n SessionId,\n SessionInfo,\n User,\n UserId,\n} from '../types';\nimport type {\n FindUsersOptions,\n FindUsersResult,\n IStorageProvider,\n UserFilter,\n} from '../storage/types';\nimport type { IEmailProvider } from '../email/types';\nimport { createEmailProvider } from '../email';\n\nexport interface AuthTokens {\n accessToken: string;\n refreshToken: string;\n}\n\nexport interface LoginOptions {\n userAgent?: string;\n ipAddress?: string;\n deviceName?: string;\n}\n\ninterface RefreshTokenPayload {\n sessionId: SessionId;\n userId: UserId;\n type: 'refresh';\n}\n\n/** Allowed fields for user updates */\nexport interface UserUpdateInput {\n email?: string;\n firstName?: string;\n lastName?: string;\n roles?: string[];\n isEmailVerified?: boolean;\n [key: string]: unknown;\n}\n\nexport class AuthService {\n private storage: IStorageProvider;\n private email: IEmailProvider;\n private config: AuthConfig;\n\n constructor(config: AuthConfig) {\n this.config = config;\n this.storage = config.storage;\n this.email = createEmailProvider(config.email);\n }\n\n private generateAccessToken(user: User): string {\n // @ts-ignore\n return jwt.sign(\n {\n id: user.id,\n email: user.email,\n roles: user.roles,\n isEmailVerified: user.isEmailVerified,\n },\n this.config.jwt.privateKey,\n {\n algorithm: 'RS256',\n expiresIn: this.config.jwt.expiresIn,\n keyid: this.config.jwt.keyId,\n }\n );\n }\n\n private generateRefreshToken(sessionId: SessionId, userId: UserId): string {\n const payload: RefreshTokenPayload = {\n sessionId,\n userId,\n type: 'refresh',\n };\n\n // @ts-ignore\n return jwt.sign(payload, this.config.jwt.privateKey, {\n algorithm: 'RS256',\n expiresIn: this.config.jwt.refreshTokenExpiresIn || '7d',\n keyid: this.config.jwt.keyId,\n });\n }\n\n private verifyRefreshToken(token: string): RefreshTokenPayload {\n try {\n const payload = jwt.verify(token, this.config.jwt.publicKey, {\n algorithms: ['RS256'],\n }) as RefreshTokenPayload;\n\n if (payload.type !== 'refresh') {\n throw new Error('Invalid token type');\n }\n\n return payload;\n } catch (_error) {\n throw new Error('Invalid refresh token');\n }\n }\n\n private hashToken(token: string): string {\n return crypto.createHash('sha256').update(token).digest('hex');\n }\n\n private getRefreshTokenExpiryMs(): number {\n const expiresIn = this.config.jwt.refreshTokenExpiresIn || '7d';\n const match = expiresIn.match(/^(\\d+)([smhd])$/);\n if (!match) {\n return 7 * 24 * 60 * 60 * 1000; // Default 7 days\n }\n\n const value = parseInt(match[1], 10);\n const unit = match[2];\n const multipliers: Record<string, number> = {\n s: 1000,\n m: 60 * 1000,\n h: 60 * 60 * 1000,\n d: 24 * 60 * 60 * 1000,\n };\n\n return value * multipliers[unit];\n }\n\n private async hashPassword(password: string): Promise<string> {\n return bcrypt.hash(password, 10);\n }\n\n private async createSession(\n userId: UserId,\n options: LoginOptions = {}\n ): Promise<{ session: Session; refreshToken: string }> {\n const now = Date.now();\n const sessionId = this.config.idGeneration === 'uuid' ? uuidv4() : undefined;\n\n const tempSession: CreateSessionInput = {\n ...(sessionId ? { id: sessionId } : {}),\n userId,\n refreshTokenHash: '',\n userAgent: options.userAgent,\n ipAddress: options.ipAddress,\n deviceName: options.deviceName,\n createdAt: now,\n lastUsedAt: now,\n expiresAt: now + this.getRefreshTokenExpiryMs(),\n };\n\n const session = await this.storage.createSession(tempSession);\n const refreshToken = this.generateRefreshToken(session.id, userId);\n await this.storage.updateSession(session.id, {\n refreshTokenHash: this.hashToken(refreshToken),\n });\n\n return {\n session: { ...session, refreshTokenHash: this.hashToken(refreshToken) },\n refreshToken,\n };\n }\n\n async signup(\n createUserInput: CreateUserInput,\n password: string,\n options: LoginOptions = {}\n ): Promise<{ user: User; tokens: AuthTokens }> {\n const existingUser = await this.storage.getUserByEmail(createUserInput.email);\n if (existingUser) {\n throw new Error('User already exists');\n }\n\n const verificationToken = uuidv4();\n const id = this.config.idGeneration === 'uuid' ? uuidv4() : undefined;\n const user = {\n ...createUserInput,\n id,\n passwordHash: await this.hashPassword(password),\n isEmailVerified: false,\n verificationToken,\n createdAt: Date.now(),\n updatedAt: Date.now(),\n };\n\n const createdUser = await this.storage.createUser(user);\n await this.email.sendVerificationEmail(createUserInput.email, verificationToken);\n\n const { refreshToken } = await this.createSession(createdUser.id!, options);\n const accessToken = this.generateAccessToken(createdUser);\n return { user: createdUser, tokens: { accessToken, refreshToken } };\n }\n\n async signin(\n email: string,\n password: string,\n options: LoginOptions = {}\n ): Promise<{ user: User; tokens: AuthTokens }> {\n const user = await this.storage.getUserByEmail(email);\n if (!user) {\n throw new Error('Invalid credentials');\n }\n\n const isValidPassword = await bcrypt.compare(password, user.passwordHash);\n if (!isValidPassword) {\n throw new Error('Invalid credentials');\n }\n\n if (!user.isEmailVerified) {\n throw new Error('Please verify your email before signing in');\n }\n\n const { refreshToken } = await this.createSession(user.id, options);\n const accessToken = this.generateAccessToken(user);\n return { user, tokens: { accessToken, refreshToken } };\n }\n\n async refreshAccessToken(refreshToken: string): Promise<AuthTokens> {\n const payload = this.verifyRefreshToken(refreshToken);\n\n const session = await this.storage.getSessionById(payload.sessionId);\n if (!session) {\n throw new Error('Session not found');\n }\n\n // Verify token hash matches\n const tokenHash = this.hashToken(refreshToken);\n if (session.refreshTokenHash !== tokenHash) {\n throw new Error('Invalid refresh token');\n }\n\n // Check if session is expired\n if (session.expiresAt < Date.now()) {\n await this.storage.deleteSession(session.id!);\n throw new Error('Session expired');\n }\n\n const user = await this.storage.getUserById(session.userId);\n if (!user) {\n throw new Error('User not found');\n }\n\n // Generate new refresh token (rotation)\n const newRefreshToken = this.generateRefreshToken(session.id, user.id);\n\n // Update session with new token hash and last used time\n await this.storage.updateSession(session.id, {\n refreshTokenHash: this.hashToken(newRefreshToken),\n lastUsedAt: Date.now(),\n expiresAt: Date.now() + this.getRefreshTokenExpiryMs(),\n });\n\n const accessToken = this.generateAccessToken(user);\n return { accessToken, refreshToken: newRefreshToken };\n }\n\n /**\n * Logout from current session only\n */\n async logout(refreshToken: string): Promise<void> {\n try {\n const payload = this.verifyRefreshToken(refreshToken);\n await this.storage.deleteSession(payload.sessionId);\n } catch {\n // Token invalid - already logged out or expired\n }\n }\n\n /**\n * Revoke a specific session by ID\n */\n async revokeSession(userId: UserId, sessionId: SessionId): Promise<void> {\n const session = await this.storage.getSessionById(sessionId);\n if (!session || session.userId !== userId) {\n throw new Error('Session not found');\n }\n await this.storage.deleteSession(sessionId);\n }\n\n /**\n * Revoke all sessions for a user (logout from all devices)\n */\n async revokeAllSessions(userId: UserId): Promise<void> {\n await this.storage.deleteAllUserSessions(userId);\n }\n\n /**\n * Get all active sessions for a user\n */\n async getSessions(userId: UserId, currentRefreshToken?: string): Promise<SessionInfo[]> {\n const sessions = await this.storage.getSessionsByUserId(userId);\n const now = Date.now();\n\n let currentSessionId: SessionId | undefined;\n if (currentRefreshToken) {\n try {\n const payload = this.verifyRefreshToken(currentRefreshToken);\n currentSessionId = payload.sessionId;\n } catch {\n // Invalid token, no current session to mark\n }\n }\n\n return sessions\n .filter((s) => s.expiresAt > now)\n .map((session) => ({\n id: session.id!,\n deviceName: session.deviceName,\n userAgent: session.userAgent,\n ipAddress: session.ipAddress,\n createdAt: session.createdAt,\n lastUsedAt: session.lastUsedAt,\n isCurrent: session.id === currentSessionId,\n }));\n }\n\n async verifyEmail(token: string): Promise<void> {\n const user = await this.storage.getUserByVerifyEmailToken(token);\n if (!user || user.verificationToken !== token) {\n throw new Error('Invalid verification token');\n }\n\n await this.storage.updateUser(user.id!, {\n isEmailVerified: true,\n verificationToken: undefined,\n updatedAt: Date.now(),\n });\n }\n\n async initiatePasswordReset(email: string): Promise<void> {\n const user = await this.storage.getUserByEmail(email);\n if (!user) {\n throw new Error('User not found');\n }\n\n const resetToken = uuidv4();\n const resetExpires = Date.now() + 3600000; // 1 hour\n\n await this.storage.updateUser(user.id!, {\n resetPasswordToken: resetToken,\n resetPasswordExpires: resetExpires,\n updatedAt: Date.now(),\n });\n\n await this.email.sendPasswordResetEmail(email, resetToken);\n }\n\n async resetPassword(token: string, newPassword: string): Promise<void> {\n const user = await this.storage.getUserByResetPasswordToken(token);\n if (\n !user ||\n !user.resetPasswordToken ||\n user.resetPasswordToken !== token ||\n !user.resetPasswordExpires ||\n user.resetPasswordExpires < Date.now()\n ) {\n throw new Error('Invalid or expired reset token');\n }\n\n await this.storage.updateUser(user.id!, {\n passwordHash: await this.hashPassword(newPassword),\n resetPasswordToken: undefined,\n resetPasswordExpires: undefined,\n updatedAt: Date.now(),\n });\n }\n\n async verifyToken(\n token: string\n ): Promise<{ id: UserId; email: string; roles: string[]; isEmailVerified: boolean }> {\n try {\n return jwt.verify(token, this.config.jwt.publicKey, { algorithms: ['RS256'] }) as {\n id: UserId;\n email: string;\n roles: string[];\n isEmailVerified: boolean;\n };\n } catch (_error) {\n console.log('Token verification failed:', _error);\n throw new Error('Invalid token');\n }\n }\n\n async hasRole(userId: UserId, requiredRoles: string[]): Promise<boolean> {\n const user = await this.storage.getUserById(userId);\n if (!user) {\n return false;\n }\n\n return requiredRoles.some((role) => user.roles.includes(role));\n }\n\n // ==================== User Management ====================\n\n /**\n * Get a user by ID\n */\n async getUserById(id: UserId): Promise<User | null> {\n return this.storage.getUserById(id);\n }\n\n /**\n * Get a user by email\n */\n async getUserByEmail(email: string): Promise<User | null> {\n return this.storage.getUserByEmail(email);\n }\n\n /**\n * Update a user's profile information.\n * Supports predefined fields (email, firstName, lastName, roles, isEmailVerified)\n * as well as any additional custom fields.\n */\n async updateUser(id: UserId, updates: UserUpdateInput): Promise<User> {\n const user = await this.storage.getUserById(id);\n if (!user) {\n throw new Error('User not found');\n }\n\n if (updates.email !== undefined && updates.email !== user.email) {\n const existingUser = await this.storage.getUserByEmail(updates.email);\n if (existingUser) {\n throw new Error('Email already in use');\n }\n }\n\n const { email, firstName, lastName, roles, isEmailVerified, ...additionalFields } = updates;\n const mergedUpdates: Record<string, unknown> = {\n updatedAt: Date.now(),\n ...additionalFields,\n };\n\n if (email !== undefined) mergedUpdates.email = email;\n if (firstName !== undefined) mergedUpdates.firstName = firstName;\n if (lastName !== undefined) mergedUpdates.lastName = lastName;\n if (roles !== undefined) mergedUpdates.roles = roles;\n if (isEmailVerified !== undefined) mergedUpdates.isEmailVerified = isEmailVerified;\n\n await this.storage.updateUser(id, mergedUpdates);\n\n return { ...user, ...mergedUpdates } as User;\n }\n\n /**\n * Delete a user and all their sessions\n */\n async deleteUser(id: UserId): Promise<void> {\n const user = await this.storage.getUserById(id);\n if (!user) {\n throw new Error('User not found');\n }\n await this.storage.deleteUser(id);\n }\n\n /**\n * Find users with filtering and pagination\n *\n * @example\n * // Find all admins\n * const { users } = await authService.findUsers({ hasAnyRole: ['ADMIN'] });\n *\n * // Find verified users with email containing '@company.com'\n * const result = await authService.findUsers({\n * emailContains: '@company.com',\n * isEmailVerified: true\n * }, { limit: 20 });\n */\n async findUsers(filter: UserFilter, options?: FindUsersOptions): Promise<FindUsersResult> {\n return this.storage.findUsers(filter, options);\n }\n}\n","import nodemailer from 'nodemailer';\nimport type { IEmailProvider, SMTPConfig, EmailTemplates } from './types';\nimport { defaultTemplates } from './defaultTemplates';\n\nexport class SMTPEmailProvider implements IEmailProvider {\n private transporter: nodemailer.Transporter;\n private config: { from: string; templates: EmailTemplates };\n\n constructor(from: string, config: SMTPConfig, templates?: EmailTemplates) {\n this.transporter = nodemailer.createTransport(config);\n this.config = { from, templates: templates ?? defaultTemplates };\n }\n\n async sendVerificationEmail(email: string, token: string): Promise<void> {\n const { subject, html } = this.config.templates.verification;\n\n await this.transporter.sendMail({\n from: this.config.from,\n to: email,\n subject,\n html: html(token),\n });\n }\n\n async sendPasswordResetEmail(email: string, token: string): Promise<void> {\n const { subject, html } = this.config.templates.resetPassword;\n\n await this.transporter.sendMail({\n from: this.config.from,\n to: email,\n subject,\n html: html(token),\n });\n }\n\n async verifyConnection(): Promise<boolean> {\n try {\n await this.transporter.verify();\n return true;\n } catch (_error) {\n return false;\n }\n }\n}\n","import type { EmailTemplates } from './types';\n\nexport const defaultTemplates: EmailTemplates = {\n verification: {\n subject: 'Verify your email address',\n html: (token: string) => `\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>Verify your email address</title>\n <style>\n body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; }\n .button { display: inline-block; padding: 12px 24px; background-color: #007bff; color: white; text-decoration: none; border-radius: 4px; margin: 20px 0; }\n .footer { margin-top: 30px; font-size: 0.9em; color: #666; }\n </style>\n</head>\n<body>\n <h1>Verify your email address</h1>\n <p>Thank you for signing up! Please click the button below to verify your email address:</p>\n <a href=\"${token}\" class=\"button\">Verify Email Address</a>\n <p>Or copy and paste this link in your browser:</p>\n <p>${token}</p>\n <div class=\"footer\">\n <p>If you didn't create an account, you can safely ignore this email.</p>\n </div>\n</body>\n</html>`,\n },\n resetPassword: {\n subject: 'Reset your password',\n html: (token: string) => `\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>Reset your password</title>\n <style>\n body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; }\n .button { display: inline-block; padding: 12px 24px; background-color: #007bff; color: white; text-decoration: none; border-radius: 4px; margin: 20px 0; }\n .footer { margin-top: 30px; font-size: 0.9em; color: #666; }\n </style>\n</head>\n<body>\n <h1>Reset your password</h1>\n <p>We received a request to reset your password. Click the button below to create a new password:</p>\n <a href=\"${token}\" class=\"button\">Reset Password</a>\n <p>Or copy and paste this link in your browser:</p>\n <p>${token}</p>\n <div class=\"footer\">\n <p>If you didn't request a password reset, you can safely ignore this email.</p>\n <p>This link will expire in 24 hours.</p>\n </div>\n</body>\n</html>`,\n },\n};\n","import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2';\nimport type { IEmailProvider, SESConfig, EmailTemplates } from './types';\nimport { defaultTemplates } from './defaultTemplates';\n\nexport class SESEmailProvider implements IEmailProvider {\n private client: SESv2Client;\n private config: { from: string; templates: EmailTemplates; sourceArn?: string };\n\n constructor(from: string, config: SESConfig, templates?: EmailTemplates) {\n this.client = new SESv2Client({\n region: config.region,\n credentials: config.credentials,\n });\n this.config = {\n from,\n templates: templates ?? defaultTemplates,\n sourceArn: config.sourceArn,\n };\n }\n\n private async sendEmail(to: string, subject: string, html: string): Promise<void> {\n const command = new SendEmailCommand({\n FromEmailAddress: this.config.from,\n FromEmailAddressIdentityArn: this.config.sourceArn,\n Destination: {\n ToAddresses: [to],\n },\n Content: {\n Simple: {\n Subject: {\n Data: subject,\n Charset: 'UTF-8',\n },\n Body: {\n Html: {\n Data: html,\n Charset: 'UTF-8',\n },\n },\n },\n },\n });\n\n await this.client.send(command);\n }\n\n async sendVerificationEmail(email: string, token: string): Promise<void> {\n const { subject, html } = this.config.templates.verification;\n await this.sendEmail(email, subject, html(token));\n }\n\n async sendPasswordResetEmail(email: string, token: string): Promise<void> {\n const { subject, html } = this.config.templates.resetPassword;\n await this.sendEmail(email, subject, html(token));\n }\n\n async verifyConnection(): Promise<boolean> {\n try {\n // SES doesn't have a direct verify method, so we'll check if we can describe our sending status\n await this.client.send(\n new SendEmailCommand({\n FromEmailAddress: this.config.from,\n FromEmailAddressIdentityArn: this.config.sourceArn,\n Destination: {\n ToAddresses: [this.config.from], // Send to ourselves as a test\n },\n Content: {\n Simple: {\n Subject: { Data: 'Test Connection', Charset: 'UTF-8' },\n Body: { Text: { Data: 'Test', Charset: 'UTF-8' } },\n },\n },\n })\n );\n return true;\n } catch (_error) {\n return false;\n }\n }\n}\n","import type { IEmailProvider, EmailConfig, SMTPConfig, SESConfig } from './types';\nimport { SMTPEmailProvider } from './smtp';\nimport { SESEmailProvider } from './ses';\n\nexport function createEmailProvider(config: EmailConfig): IEmailProvider {\n switch (config.type) {\n case 'smtp':\n return new SMTPEmailProvider(\n config.from,\n config.options as SMTPConfig,\n config.templates\n );\n case 'ses':\n return new SESEmailProvider(config.from, config.options as SESConfig, config.templates);\n default:\n throw new Error(`Unsupported email provider type: ${config.type}`);\n }\n}\n\nexport * from './types';\nexport * from './smtp';\nexport * from './ses';\n","import type { AuthService } from '../services/auth';\n\nexport class AuthMiddleware {\n private authService: AuthService;\n\n constructor(authService: AuthService) {\n this.authService = authService;\n }\n\n verifyToken() {\n return async (req: any, res: any, next: any) => {\n try {\n const token = req.headers.authorization?.split(' ')[1];\n if (!token) {\n throw new Error('No token provided');\n }\n\n req.user = await this.authService.verifyToken(token);\n next();\n } catch (_error) {\n res.status(401).json({ error: 'Unauthorized' });\n }\n };\n }\n\n requireRoles(roles: string[]) {\n return async (req: any, res: any, next: any) => {\n try {\n const hasRole = await this.authService.hasRole(req.user.id, roles);\n if (!hasRole) {\n throw new Error('Insufficient permissions');\n }\n next();\n } catch (_error) {\n res.status(403).json({ error: 'Forbidden' });\n }\n };\n }\n\n requireEmailVerified() {\n return (req: any, res: any, next: any) => {\n if (!req.user.isEmailVerified) {\n return res.status(403).json({ error: 'Email not verified' });\n }\n next();\n };\n }\n}\n","import { z } from 'zod';\nimport type { IStorageProvider } from '../storage/types';\nimport type { EmailConfig } from '../email/types';\n\n/** ID can be either auto-incremented number or UUID string */\nexport type UserId = string | number;\nexport type SessionId = string | number;\n\nexport const UserSchema = z.object({\n id: z.union([z.string(), z.number()]),\n email: z.string().email(),\n firstName: z.string().optional(),\n lastName: z.string().optional(),\n passwordHash: z.string(),\n roles: z.array(z.string()),\n isEmailVerified: z.boolean(),\n verificationToken: z.string().optional(),\n resetPasswordToken: z.string().optional(),\n resetPasswordExpires: z.number().optional(),\n createdAt: z.number(),\n updatedAt: z.number(),\n});\n\nexport const CreateUserSchema = UserSchema.omit({\n id: true,\n createdAt: true,\n updatedAt: true,\n}).extend({\n createdAt: z.number().optional(),\n updatedAt: z.number().optional(),\n id: z.union([z.string(), z.number()]).optional(),\n});\n\nexport type User = z.infer<typeof UserSchema>;\nexport type CreateUserInput = z.infer<typeof CreateUserSchema>;\n\nexport const SessionSchema = z.object({\n id: z.union([z.string(), z.number()]),\n userId: z.union([z.string(), z.number()]),\n refreshTokenHash: z.string(),\n userAgent: z.string().optional(),\n ipAddress: z.string().optional(),\n deviceName: z.string().optional(),\n createdAt: z.number(),\n lastUsedAt: z.number(),\n expiresAt: z.number(),\n});\n\nexport const CreateSessionSchema = SessionSchema.omit({\n id: true,\n createdAt: true,\n lastUsedAt: true,\n}).extend({\n createdAt: z.number().optional(),\n lastUsedAt: z.number().optional(),\n id: z.union([z.string(), z.number()]).optional(),\n});\nexport type Session = z.infer<typeof SessionSchema>;\nexport type CreateSessionInput = z.infer<typeof CreateSessionSchema>;\n\n/** Session info returned to clients (without sensitive data) */\nexport interface SessionInfo {\n id: SessionId;\n deviceName?: string;\n userAgent?: string;\n ipAddress?: string;\n createdAt: number;\n lastUsedAt: number;\n isCurrent: boolean;\n}\n\nexport interface AuthConfig {\n jwt: {\n privateKey: string;\n publicKey: string;\n keyId: string;\n expiresIn: string;\n refreshTokenExpiresIn?: string;\n };\n storage: IStorageProvider;\n idGeneration: 'uuid' | 'increment';\n email: EmailConfig;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/services/auth.ts","../src/email/smtp.ts","../src/email/defaultTemplates.ts","../src/email/ses.ts","../src/email/index.ts","../src/middleware/auth.ts","../src/types/index.ts"],"sourcesContent":["import bcrypt from 'bcryptjs';\nimport jwt from 'jsonwebtoken';\nimport { v4 as uuidv4 } from 'uuid';\nimport crypto from 'crypto';\nimport type {\n AuthConfig,\n CreateSessionInput,\n CreateUserInput,\n Session,\n SessionId,\n SessionInfo,\n User,\n UserId,\n} from '../types';\nimport type {\n FindUsersOptions,\n FindUsersResult,\n IStorageProvider,\n UserFilter,\n} from '../storage/types';\nimport type { IEmailProvider } from '../email/types';\nimport { createEmailProvider } from '../email';\n\nexport interface AuthTokens {\n accessToken: string;\n refreshToken: string;\n}\n\nexport interface LoginOptions {\n userAgent?: string;\n ipAddress?: string;\n deviceName?: string;\n}\n\ninterface RefreshTokenPayload {\n sessionId: SessionId;\n userId: UserId;\n type: 'refresh';\n}\n\n/** Allowed fields for user updates */\nexport interface UserUpdateInput {\n email?: string;\n firstName?: string;\n lastName?: string;\n roles?: string[];\n isEmailVerified?: boolean;\n [key: string]: unknown;\n}\n\nexport class AuthService {\n private storage: IStorageProvider;\n private email: IEmailProvider;\n private config: AuthConfig;\n\n constructor(config: AuthConfig) {\n this.config = config;\n this.storage = config.storage;\n this.email = createEmailProvider(config.email);\n }\n\n private generateAccessToken(user: User): string {\n // @ts-ignore\n return jwt.sign(\n {\n id: user.id,\n email: user.email,\n roles: user.roles,\n isEmailVerified: user.isEmailVerified,\n },\n this.config.jwt.privateKey,\n {\n algorithm: 'RS256',\n expiresIn: this.config.jwt.expiresIn,\n keyid: this.config.jwt.keyId,\n }\n );\n }\n\n private generateRefreshToken(sessionId: SessionId, userId: UserId): string {\n const payload: RefreshTokenPayload = {\n sessionId,\n userId,\n type: 'refresh',\n };\n\n // @ts-ignore\n return jwt.sign(payload, this.config.jwt.privateKey, {\n algorithm: 'RS256',\n expiresIn: this.config.jwt.refreshTokenExpiresIn || '7d',\n keyid: this.config.jwt.keyId,\n });\n }\n\n private verifyRefreshToken(token: string): RefreshTokenPayload {\n try {\n const payload = jwt.verify(token, this.config.jwt.publicKey, {\n algorithms: ['RS256'],\n }) as RefreshTokenPayload;\n\n if (payload.type !== 'refresh') {\n throw new Error('Invalid token type');\n }\n\n return payload;\n } catch (_error) {\n throw new Error('Invalid refresh token');\n }\n }\n\n private hashToken(token: string): string {\n return crypto.createHash('sha256').update(token).digest('hex');\n }\n\n /** Return current time as epoch seconds */\n private now(): number {\n return Math.floor(Date.now() / 1000);\n }\n\n private getRefreshTokenExpirySecs(): number {\n const expiresIn = this.config.jwt.refreshTokenExpiresIn || '7d';\n const match = expiresIn.match(/^(\\d+)([smhd])$/);\n if (!match) {\n return 7 * 24 * 60 * 60; // Default 7 days in seconds\n }\n\n const value = parseInt(match[1], 10);\n const unit = match[2];\n const multipliers: Record<string, number> = {\n s: 1,\n m: 60,\n h: 60 * 60,\n d: 24 * 60 * 60,\n };\n\n return value * multipliers[unit];\n }\n\n private async hashPassword(password: string): Promise<string> {\n return bcrypt.hash(password, 10);\n }\n\n private async createSession(\n userId: UserId,\n options: LoginOptions = {}\n ): Promise<{ session: Session; refreshToken: string }> {\n const now = this.now();\n const sessionId = this.config.idGeneration === 'uuid' ? uuidv4() : undefined;\n\n const tempSession: CreateSessionInput = {\n ...(sessionId ? { id: sessionId } : {}),\n userId,\n refreshTokenHash: '',\n userAgent: options.userAgent,\n ipAddress: options.ipAddress,\n deviceName: options.deviceName,\n createdAt: now,\n lastUsedAt: now,\n expiresAt: now + this.getRefreshTokenExpirySecs(),\n };\n\n const session = await this.storage.createSession(tempSession);\n const refreshToken = this.generateRefreshToken(session.id, userId);\n await this.storage.updateSession(session.id, {\n refreshTokenHash: this.hashToken(refreshToken),\n });\n\n return {\n session: { ...session, refreshTokenHash: this.hashToken(refreshToken) },\n refreshToken,\n };\n }\n\n async signup(\n createUserInput: CreateUserInput,\n password: string,\n options: LoginOptions = {}\n ): Promise<{ user: User; tokens: AuthTokens }> {\n const existingUser = await this.storage.getUserByEmail(createUserInput.email);\n if (existingUser) {\n throw new Error('User already exists');\n }\n\n const verificationToken = uuidv4();\n const id = this.config.idGeneration === 'uuid' ? uuidv4() : undefined;\n const user = {\n ...createUserInput,\n id,\n passwordHash: await this.hashPassword(password),\n isEmailVerified: false,\n verificationToken,\n createdAt: this.now(),\n updatedAt: this.now(),\n };\n\n const createdUser = await this.storage.createUser(user);\n await this.email.sendVerificationEmail(createUserInput.email, verificationToken);\n\n const { refreshToken } = await this.createSession(createdUser.id!, options);\n const accessToken = this.generateAccessToken(createdUser);\n return { user: createdUser, tokens: { accessToken, refreshToken } };\n }\n\n async signin(\n email: string,\n password: string,\n options: LoginOptions = {}\n ): Promise<{ user: User; tokens: AuthTokens }> {\n const user = await this.storage.getUserByEmail(email);\n if (!user) {\n throw new Error('Invalid credentials');\n }\n\n const isValidPassword = await bcrypt.compare(password, user.passwordHash);\n if (!isValidPassword) {\n throw new Error('Invalid credentials');\n }\n\n if (!user.isEmailVerified) {\n throw new Error('Please verify your email before signing in');\n }\n\n const { refreshToken } = await this.createSession(user.id, options);\n const accessToken = this.generateAccessToken(user);\n return { user, tokens: { accessToken, refreshToken } };\n }\n\n async refreshAccessToken(refreshToken: string): Promise<AuthTokens> {\n const payload = this.verifyRefreshToken(refreshToken);\n\n const session = await this.storage.getSessionById(payload.sessionId);\n if (!session) {\n throw new Error('Session not found');\n }\n\n // Verify token hash matches\n const tokenHash = this.hashToken(refreshToken);\n if (session.refreshTokenHash !== tokenHash) {\n throw new Error('Invalid refresh token');\n }\n\n // Check if session is expired\n if (session.expiresAt < this.now()) {\n await this.storage.deleteSession(session.id!);\n throw new Error('Session expired');\n }\n\n const user = await this.storage.getUserById(session.userId);\n if (!user) {\n throw new Error('User not found');\n }\n\n // Generate new refresh token (rotation)\n const newRefreshToken = this.generateRefreshToken(session.id, user.id);\n\n // Update session with new token hash and last used time\n await this.storage.updateSession(session.id, {\n refreshTokenHash: this.hashToken(newRefreshToken),\n lastUsedAt: this.now(),\n expiresAt: this.now() + this.getRefreshTokenExpirySecs(),\n });\n\n const accessToken = this.generateAccessToken(user);\n return { accessToken, refreshToken: newRefreshToken };\n }\n\n /**\n * Logout from current session only\n */\n async logout(refreshToken: string): Promise<void> {\n try {\n const payload = this.verifyRefreshToken(refreshToken);\n await this.storage.deleteSession(payload.sessionId);\n } catch {\n // Token invalid - already logged out or expired\n }\n }\n\n /**\n * Revoke a specific session by ID\n */\n async revokeSession(userId: UserId, sessionId: SessionId): Promise<void> {\n const session = await this.storage.getSessionById(sessionId);\n if (!session || session.userId !== userId) {\n throw new Error('Session not found');\n }\n await this.storage.deleteSession(sessionId);\n }\n\n /**\n * Revoke all sessions for a user (logout from all devices)\n */\n async revokeAllSessions(userId: UserId): Promise<void> {\n await this.storage.deleteAllUserSessions(userId);\n }\n\n /**\n * Get all active sessions for a user\n */\n async getSessions(userId: UserId, currentRefreshToken?: string): Promise<SessionInfo[]> {\n const sessions = await this.storage.getSessionsByUserId(userId);\n const now = this.now();\n\n let currentSessionId: SessionId | undefined;\n if (currentRefreshToken) {\n try {\n const payload = this.verifyRefreshToken(currentRefreshToken);\n currentSessionId = payload.sessionId;\n } catch {\n // Invalid token, no current session to mark\n }\n }\n\n return sessions\n .filter((s) => s.expiresAt > now)\n .map((session) => ({\n id: session.id!,\n deviceName: session.deviceName,\n userAgent: session.userAgent,\n ipAddress: session.ipAddress,\n createdAt: session.createdAt,\n lastUsedAt: session.lastUsedAt,\n isCurrent: session.id === currentSessionId,\n }));\n }\n\n async verifyEmail(token: string): Promise<void> {\n const user = await this.storage.getUserByVerifyEmailToken(token);\n if (!user || user.verificationToken !== token) {\n throw new Error('Invalid verification token');\n }\n\n await this.storage.updateUser(user.id!, {\n isEmailVerified: true,\n verificationToken: undefined,\n updatedAt: this.now(),\n });\n }\n\n async initiatePasswordReset(email: string): Promise<void> {\n const user = await this.storage.getUserByEmail(email);\n if (!user) {\n throw new Error('User not found');\n }\n\n const resetToken = uuidv4();\n const resetExpires = this.now() + 3600; // 1 hour in seconds\n\n await this.storage.updateUser(user.id!, {\n resetPasswordToken: resetToken,\n resetPasswordExpires: resetExpires,\n updatedAt: this.now(),\n });\n\n await this.email.sendPasswordResetEmail(email, resetToken);\n }\n\n async resetPassword(token: string, newPassword: string): Promise<void> {\n const user = await this.storage.getUserByResetPasswordToken(token);\n if (\n !user ||\n !user.resetPasswordToken ||\n user.resetPasswordToken !== token ||\n !user.resetPasswordExpires ||\n user.resetPasswordExpires < this.now()\n ) {\n throw new Error('Invalid or expired reset token');\n }\n\n await this.storage.updateUser(user.id!, {\n passwordHash: await this.hashPassword(newPassword),\n resetPasswordToken: undefined,\n resetPasswordExpires: undefined,\n updatedAt: this.now(),\n });\n }\n\n async verifyToken(\n token: string\n ): Promise<{ id: UserId; email: string; roles: string[]; isEmailVerified: boolean }> {\n try {\n return jwt.verify(token, this.config.jwt.publicKey, { algorithms: ['RS256'] }) as {\n id: UserId;\n email: string;\n roles: string[];\n isEmailVerified: boolean;\n };\n } catch (_error) {\n console.log('Token verification failed:', _error);\n throw new Error('Invalid token');\n }\n }\n\n async hasRole(userId: UserId, requiredRoles: string[]): Promise<boolean> {\n const user = await this.storage.getUserById(userId);\n if (!user) {\n return false;\n }\n\n return requiredRoles.some((role) => user.roles.includes(role));\n }\n\n // ==================== User Management ====================\n\n /**\n * Get a user by ID\n */\n async getUserById(id: UserId): Promise<User | null> {\n return this.storage.getUserById(id);\n }\n\n /**\n * Get a user by email\n */\n async getUserByEmail(email: string): Promise<User | null> {\n return this.storage.getUserByEmail(email);\n }\n\n /**\n * Update a user's profile information.\n * Supports predefined fields (email, firstName, lastName, roles, isEmailVerified)\n * as well as any additional custom fields.\n */\n async updateUser(id: UserId, updates: UserUpdateInput): Promise<User> {\n const user = await this.storage.getUserById(id);\n if (!user) {\n throw new Error('User not found');\n }\n\n if (updates.email !== undefined && updates.email !== user.email) {\n const existingUser = await this.storage.getUserByEmail(updates.email);\n if (existingUser) {\n throw new Error('Email already in use');\n }\n }\n\n const { email, firstName, lastName, roles, isEmailVerified, ...additionalFields } = updates;\n const mergedUpdates: Record<string, unknown> = {\n updatedAt: this.now(),\n ...additionalFields,\n };\n\n if (email !== undefined) mergedUpdates.email = email;\n if (firstName !== undefined) mergedUpdates.firstName = firstName;\n if (lastName !== undefined) mergedUpdates.lastName = lastName;\n if (roles !== undefined) mergedUpdates.roles = roles;\n if (isEmailVerified !== undefined) mergedUpdates.isEmailVerified = isEmailVerified;\n\n await this.storage.updateUser(id, mergedUpdates);\n\n return { ...user, ...mergedUpdates } as User;\n }\n\n /**\n * Delete a user and all their sessions\n */\n async deleteUser(id: UserId): Promise<void> {\n const user = await this.storage.getUserById(id);\n if (!user) {\n throw new Error('User not found');\n }\n await this.storage.deleteUser(id);\n }\n\n /**\n * Find users with filtering and pagination\n *\n * @example\n * // Find all admins\n * const { users } = await authService.findUsers({ hasAnyRole: ['ADMIN'] });\n *\n * // Find verified users with email containing '@company.com'\n * const result = await authService.findUsers({\n * emailContains: '@company.com',\n * isEmailVerified: true\n * }, { limit: 20 });\n */\n async findUsers(filter: UserFilter, options?: FindUsersOptions): Promise<FindUsersResult> {\n return this.storage.findUsers(filter, options);\n }\n}\n","import nodemailer from 'nodemailer';\nimport type { IEmailProvider, SMTPConfig, EmailTemplates } from './types';\nimport { defaultTemplates } from './defaultTemplates';\n\nexport class SMTPEmailProvider implements IEmailProvider {\n private transporter: nodemailer.Transporter;\n private config: { from: string; templates: EmailTemplates };\n\n constructor(from: string, config: SMTPConfig, templates?: EmailTemplates) {\n this.transporter = nodemailer.createTransport(config);\n this.config = { from, templates: templates ?? defaultTemplates };\n }\n\n async sendVerificationEmail(email: string, token: string): Promise<void> {\n const { subject, html } = this.config.templates.verification;\n\n await this.transporter.sendMail({\n from: this.config.from,\n to: email,\n subject,\n html: html(token),\n });\n }\n\n async sendPasswordResetEmail(email: string, token: string): Promise<void> {\n const { subject, html } = this.config.templates.resetPassword;\n\n await this.transporter.sendMail({\n from: this.config.from,\n to: email,\n subject,\n html: html(token),\n });\n }\n\n async verifyConnection(): Promise<boolean> {\n try {\n await this.transporter.verify();\n return true;\n } catch (_error) {\n return false;\n }\n }\n}\n","import type { EmailTemplates } from './types';\n\nexport const defaultTemplates: EmailTemplates = {\n verification: {\n subject: 'Verify your email address',\n html: (token: string) => `\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>Verify your email address</title>\n <style>\n body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; }\n .button { display: inline-block; padding: 12px 24px; background-color: #007bff; color: white; text-decoration: none; border-radius: 4px; margin: 20px 0; }\n .footer { margin-top: 30px; font-size: 0.9em; color: #666; }\n </style>\n</head>\n<body>\n <h1>Verify your email address</h1>\n <p>Thank you for signing up! Please click the button below to verify your email address:</p>\n <a href=\"${token}\" class=\"button\">Verify Email Address</a>\n <p>Or copy and paste this link in your browser:</p>\n <p>${token}</p>\n <div class=\"footer\">\n <p>If you didn't create an account, you can safely ignore this email.</p>\n </div>\n</body>\n</html>`,\n },\n resetPassword: {\n subject: 'Reset your password',\n html: (token: string) => `\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>Reset your password</title>\n <style>\n body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; }\n .button { display: inline-block; padding: 12px 24px; background-color: #007bff; color: white; text-decoration: none; border-radius: 4px; margin: 20px 0; }\n .footer { margin-top: 30px; font-size: 0.9em; color: #666; }\n </style>\n</head>\n<body>\n <h1>Reset your password</h1>\n <p>We received a request to reset your password. Click the button below to create a new password:</p>\n <a href=\"${token}\" class=\"button\">Reset Password</a>\n <p>Or copy and paste this link in your browser:</p>\n <p>${token}</p>\n <div class=\"footer\">\n <p>If you didn't request a password reset, you can safely ignore this email.</p>\n <p>This link will expire in 24 hours.</p>\n </div>\n</body>\n</html>`,\n },\n};\n","import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2';\nimport type { IEmailProvider, SESConfig, EmailTemplates } from './types';\nimport { defaultTemplates } from './defaultTemplates';\n\nexport class SESEmailProvider implements IEmailProvider {\n private client: SESv2Client;\n private config: { from: string; templates: EmailTemplates; sourceArn?: string };\n\n constructor(from: string, config: SESConfig, templates?: EmailTemplates) {\n this.client = new SESv2Client({\n region: config.region,\n credentials: config.credentials,\n });\n this.config = {\n from,\n templates: templates ?? defaultTemplates,\n sourceArn: config.sourceArn,\n };\n }\n\n private async sendEmail(to: string, subject: string, html: string): Promise<void> {\n const command = new SendEmailCommand({\n FromEmailAddress: this.config.from,\n FromEmailAddressIdentityArn: this.config.sourceArn,\n Destination: {\n ToAddresses: [to],\n },\n Content: {\n Simple: {\n Subject: {\n Data: subject,\n Charset: 'UTF-8',\n },\n Body: {\n Html: {\n Data: html,\n Charset: 'UTF-8',\n },\n },\n },\n },\n });\n\n await this.client.send(command);\n }\n\n async sendVerificationEmail(email: string, token: string): Promise<void> {\n const { subject, html } = this.config.templates.verification;\n await this.sendEmail(email, subject, html(token));\n }\n\n async sendPasswordResetEmail(email: string, token: string): Promise<void> {\n const { subject, html } = this.config.templates.resetPassword;\n await this.sendEmail(email, subject, html(token));\n }\n\n async verifyConnection(): Promise<boolean> {\n try {\n // SES doesn't have a direct verify method, so we'll check if we can describe our sending status\n await this.client.send(\n new SendEmailCommand({\n FromEmailAddress: this.config.from,\n FromEmailAddressIdentityArn: this.config.sourceArn,\n Destination: {\n ToAddresses: [this.config.from], // Send to ourselves as a test\n },\n Content: {\n Simple: {\n Subject: { Data: 'Test Connection', Charset: 'UTF-8' },\n Body: { Text: { Data: 'Test', Charset: 'UTF-8' } },\n },\n },\n })\n );\n return true;\n } catch (_error) {\n return false;\n }\n }\n}\n","import type { IEmailProvider, EmailConfig, SMTPConfig, SESConfig } from './types';\nimport { SMTPEmailProvider } from './smtp';\nimport { SESEmailProvider } from './ses';\n\nexport function createEmailProvider(config: EmailConfig): IEmailProvider {\n switch (config.type) {\n case 'smtp':\n return new SMTPEmailProvider(\n config.from,\n config.options as SMTPConfig,\n config.templates\n );\n case 'ses':\n return new SESEmailProvider(config.from, config.options as SESConfig, config.templates);\n default:\n throw new Error(`Unsupported email provider type: ${config.type}`);\n }\n}\n\nexport * from './types';\nexport * from './smtp';\nexport * from './ses';\n","import type { AuthService } from '../services/auth';\n\nexport class AuthMiddleware {\n private authService: AuthService;\n\n constructor(authService: AuthService) {\n this.authService = authService;\n }\n\n verifyToken() {\n return async (req: any, res: any, next: any) => {\n try {\n const token = req.headers.authorization?.split(' ')[1];\n if (!token) {\n throw new Error('No token provided');\n }\n\n req.user = await this.authService.verifyToken(token);\n next();\n } catch (_error) {\n res.status(401).json({ error: 'Unauthorized' });\n }\n };\n }\n\n requireRoles(roles: string[]) {\n return async (req: any, res: any, next: any) => {\n try {\n const hasRole = await this.authService.hasRole(req.user.id, roles);\n if (!hasRole) {\n throw new Error('Insufficient permissions');\n }\n next();\n } catch (_error) {\n res.status(403).json({ error: 'Forbidden' });\n }\n };\n }\n\n requireEmailVerified() {\n return (req: any, res: any, next: any) => {\n if (!req.user.isEmailVerified) {\n return res.status(403).json({ error: 'Email not verified' });\n }\n next();\n };\n }\n}\n","import { z } from 'zod';\nimport type { IStorageProvider } from '../storage/types';\nimport type { EmailConfig } from '../email/types';\n\n/** ID can be either auto-incremented number or UUID string */\nexport type UserId = string | number;\nexport type SessionId = string | number;\n\nexport const UserSchema = z.object({\n id: z.union([z.string(), z.number()]),\n email: z.string().email(),\n firstName: z.string().optional(),\n lastName: z.string().optional(),\n passwordHash: z.string(),\n roles: z.array(z.string()),\n isEmailVerified: z.boolean(),\n verificationToken: z.string().optional(),\n resetPasswordToken: z.string().optional(),\n resetPasswordExpires: z.number().optional(),\n createdAt: z.number(),\n updatedAt: z.number(),\n});\n\nexport const CreateUserSchema = UserSchema.omit({\n id: true,\n createdAt: true,\n updatedAt: true,\n}).extend({\n createdAt: z.number().optional(),\n updatedAt: z.number().optional(),\n id: z.union([z.string(), z.number()]).optional(),\n});\n\nexport type User = z.infer<typeof UserSchema>;\nexport type CreateUserInput = z.infer<typeof CreateUserSchema>;\n\nexport const SessionSchema = z.object({\n id: z.union([z.string(), z.number()]),\n userId: z.union([z.string(), z.number()]),\n refreshTokenHash: z.string(),\n userAgent: z.string().optional(),\n ipAddress: z.string().optional(),\n deviceName: z.string().optional(),\n createdAt: z.number(),\n lastUsedAt: z.number(),\n expiresAt: z.number(),\n});\n\nexport const CreateSessionSchema = SessionSchema.omit({\n id: true,\n createdAt: true,\n lastUsedAt: true,\n}).extend({\n createdAt: z.number().optional(),\n lastUsedAt: z.number().optional(),\n id: z.union([z.string(), z.number()]).optional(),\n});\nexport type Session = z.infer<typeof SessionSchema>;\nexport type CreateSessionInput = z.infer<typeof CreateSessionSchema>;\n\n/** Session info returned to clients (without sensitive data) */\nexport interface SessionInfo {\n id: SessionId;\n deviceName?: string;\n userAgent?: string;\n ipAddress?: string;\n createdAt: number;\n lastUsedAt: number;\n isCurrent: boolean;\n}\n\nexport interface AuthConfig {\n jwt: {\n privateKey: string;\n publicKey: string;\n keyId: string;\n expiresIn: string;\n refreshTokenExpiresIn?: string;\n };\n storage: IStorageProvider;\n idGeneration: 'uuid' | 'increment';\n email: EmailConfig;\n}\n"],"mappings":";AAAA,OAAO,YAAY;AACnB,OAAO,SAAS;AAChB,SAAS,MAAM,cAAc;AAC7B,OAAO,YAAY;;;ACHnB,OAAO,gBAAgB;;;ACEhB,IAAM,mBAAmC;AAAA,EAC5C,cAAc;AAAA,IACV,SAAS;AAAA,IACT,MAAM,CAAC,UAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,aAepB,KAAK;AAAA;AAAA,OAEX,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMR;AAAA,EACA,eAAe;AAAA,IACX,SAAS;AAAA,IACT,MAAM,CAAC,UAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,aAepB,KAAK;AAAA;AAAA,OAEX,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOR;AACJ;;;ADpDO,IAAM,oBAAN,MAAkD;AAAA,EAIrD,YAAY,MAAc,QAAoB,WAA4B;AACtE,SAAK,cAAc,WAAW,gBAAgB,MAAM;AACpD,SAAK,SAAS,EAAE,MAAM,WAAW,aAAa,iBAAiB;AAAA,EACnE;AAAA,EAEA,MAAM,sBAAsB,OAAe,OAA8B;AACrE,UAAM,EAAE,SAAS,KAAK,IAAI,KAAK,OAAO,UAAU;AAEhD,UAAM,KAAK,YAAY,SAAS;AAAA,MAC5B,MAAM,KAAK,OAAO;AAAA,MAClB,IAAI;AAAA,MACJ;AAAA,MACA,MAAM,KAAK,KAAK;AAAA,IACpB,CAAC;AAAA,EACL;AAAA,EAEA,MAAM,uBAAuB,OAAe,OAA8B;AACtE,UAAM,EAAE,SAAS,KAAK,IAAI,KAAK,OAAO,UAAU;AAEhD,UAAM,KAAK,YAAY,SAAS;AAAA,MAC5B,MAAM,KAAK,OAAO;AAAA,MAClB,IAAI;AAAA,MACJ;AAAA,MACA,MAAM,KAAK,KAAK;AAAA,IACpB,CAAC;AAAA,EACL;AAAA,EAEA,MAAM,mBAAqC;AACvC,QAAI;AACA,YAAM,KAAK,YAAY,OAAO;AAC9B,aAAO;AAAA,IACX,SAAS,QAAQ;AACb,aAAO;AAAA,IACX;AAAA,EACJ;AACJ;;;AE3CA,SAAS,aAAa,wBAAwB;AAIvC,IAAM,mBAAN,MAAiD;AAAA,EAIpD,YAAY,MAAc,QAAmB,WAA4B;AACrE,SAAK,SAAS,IAAI,YAAY;AAAA,MAC1B,QAAQ,OAAO;AAAA,MACf,aAAa,OAAO;AAAA,IACxB,CAAC;AACD,SAAK,SAAS;AAAA,MACV;AAAA,MACA,WAAW,aAAa;AAAA,MACxB,WAAW,OAAO;AAAA,IACtB;AAAA,EACJ;AAAA,EAEA,MAAc,UAAU,IAAY,SAAiB,MAA6B;AAC9E,UAAM,UAAU,IAAI,iBAAiB;AAAA,MACjC,kBAAkB,KAAK,OAAO;AAAA,MAC9B,6BAA6B,KAAK,OAAO;AAAA,MACzC,aAAa;AAAA,QACT,aAAa,CAAC,EAAE;AAAA,MACpB;AAAA,MACA,SAAS;AAAA,QACL,QAAQ;AAAA,UACJ,SAAS;AAAA,YACL,MAAM;AAAA,YACN,SAAS;AAAA,UACb;AAAA,UACA,MAAM;AAAA,YACF,MAAM;AAAA,cACF,MAAM;AAAA,cACN,SAAS;AAAA,YACb;AAAA,UACJ;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ,CAAC;AAED,UAAM,KAAK,OAAO,KAAK,OAAO;AAAA,EAClC;AAAA,EAEA,MAAM,sBAAsB,OAAe,OAA8B;AACrE,UAAM,EAAE,SAAS,KAAK,IAAI,KAAK,OAAO,UAAU;AAChD,UAAM,KAAK,UAAU,OAAO,SAAS,KAAK,KAAK,CAAC;AAAA,EACpD;AAAA,EAEA,MAAM,uBAAuB,OAAe,OAA8B;AACtE,UAAM,EAAE,SAAS,KAAK,IAAI,KAAK,OAAO,UAAU;AAChD,UAAM,KAAK,UAAU,OAAO,SAAS,KAAK,KAAK,CAAC;AAAA,EACpD;AAAA,EAEA,MAAM,mBAAqC;AACvC,QAAI;AAEA,YAAM,KAAK,OAAO;AAAA,QACd,IAAI,iBAAiB;AAAA,UACjB,kBAAkB,KAAK,OAAO;AAAA,UAC9B,6BAA6B,KAAK,OAAO;AAAA,UACzC,aAAa;AAAA,YACT,aAAa,CAAC,KAAK,OAAO,IAAI;AAAA;AAAA,UAClC;AAAA,UACA,SAAS;AAAA,YACL,QAAQ;AAAA,cACJ,SAAS,EAAE,MAAM,mBAAmB,SAAS,QAAQ;AAAA,cACrD,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,SAAS,QAAQ,EAAE;AAAA,YACrD;AAAA,UACJ;AAAA,QACJ,CAAC;AAAA,MACL;AACA,aAAO;AAAA,IACX,SAAS,QAAQ;AACb,aAAO;AAAA,IACX;AAAA,EACJ;AACJ;;;AC3EO,SAAS,oBAAoB,QAAqC;AACrE,UAAQ,OAAO,MAAM;AAAA,IACjB,KAAK;AACD,aAAO,IAAI;AAAA,QACP,OAAO;AAAA,QACP,OAAO;AAAA,QACP,OAAO;AAAA,MACX;AAAA,IACJ,KAAK;AACD,aAAO,IAAI,iBAAiB,OAAO,MAAM,OAAO,SAAsB,OAAO,SAAS;AAAA,IAC1F;AACI,YAAM,IAAI,MAAM,oCAAoC,OAAO,IAAI,EAAE;AAAA,EACzE;AACJ;;;AJiCO,IAAM,cAAN,MAAkB;AAAA,EAKrB,YAAY,QAAoB;AAC5B,SAAK,SAAS;AACd,SAAK,UAAU,OAAO;AACtB,SAAK,QAAQ,oBAAoB,OAAO,KAAK;AAAA,EACjD;AAAA,EAEQ,oBAAoB,MAAoB;AAE5C,WAAO,IAAI;AAAA,MACP;AAAA,QACI,IAAI,KAAK;AAAA,QACT,OAAO,KAAK;AAAA,QACZ,OAAO,KAAK;AAAA,QACZ,iBAAiB,KAAK;AAAA,MAC1B;AAAA,MACA,KAAK,OAAO,IAAI;AAAA,MAChB;AAAA,QACI,WAAW;AAAA,QACX,WAAW,KAAK,OAAO,IAAI;AAAA,QAC3B,OAAO,KAAK,OAAO,IAAI;AAAA,MAC3B;AAAA,IACJ;AAAA,EACJ;AAAA,EAEQ,qBAAqB,WAAsB,QAAwB;AACvE,UAAM,UAA+B;AAAA,MACjC;AAAA,MACA;AAAA,MACA,MAAM;AAAA,IACV;AAGA,WAAO,IAAI,KAAK,SAAS,KAAK,OAAO,IAAI,YAAY;AAAA,MACjD,WAAW;AAAA,MACX,WAAW,KAAK,OAAO,IAAI,yBAAyB;AAAA,MACpD,OAAO,KAAK,OAAO,IAAI;AAAA,IAC3B,CAAC;AAAA,EACL;AAAA,EAEQ,mBAAmB,OAAoC;AAC3D,QAAI;AACA,YAAM,UAAU,IAAI,OAAO,OAAO,KAAK,OAAO,IAAI,WAAW;AAAA,QACzD,YAAY,CAAC,OAAO;AAAA,MACxB,CAAC;AAED,UAAI,QAAQ,SAAS,WAAW;AAC5B,cAAM,IAAI,MAAM,oBAAoB;AAAA,MACxC;AAEA,aAAO;AAAA,IACX,SAAS,QAAQ;AACb,YAAM,IAAI,MAAM,uBAAuB;AAAA,IAC3C;AAAA,EACJ;AAAA,EAEQ,UAAU,OAAuB;AACrC,WAAO,OAAO,WAAW,QAAQ,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK;AAAA,EACjE;AAAA;AAAA,EAGQ,MAAc;AAClB,WAAO,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,EACvC;AAAA,EAEQ,4BAAoC;AACxC,UAAM,YAAY,KAAK,OAAO,IAAI,yBAAyB;AAC3D,UAAM,QAAQ,UAAU,MAAM,iBAAiB;AAC/C,QAAI,CAAC,OAAO;AACR,aAAO,IAAI,KAAK,KAAK;AAAA,IACzB;AAEA,UAAM,QAAQ,SAAS,MAAM,CAAC,GAAG,EAAE;AACnC,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,cAAsC;AAAA,MACxC,GAAG;AAAA,MACH,GAAG;AAAA,MACH,GAAG,KAAK;AAAA,MACR,GAAG,KAAK,KAAK;AAAA,IACjB;AAEA,WAAO,QAAQ,YAAY,IAAI;AAAA,EACnC;AAAA,EAEA,MAAc,aAAa,UAAmC;AAC1D,WAAO,OAAO,KAAK,UAAU,EAAE;AAAA,EACnC;AAAA,EAEA,MAAc,cACV,QACA,UAAwB,CAAC,GAC0B;AACnD,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,YAAY,KAAK,OAAO,iBAAiB,SAAS,OAAO,IAAI;AAEnE,UAAM,cAAkC;AAAA,MACpC,GAAI,YAAY,EAAE,IAAI,UAAU,IAAI,CAAC;AAAA,MACrC;AAAA,MACA,kBAAkB;AAAA,MAClB,WAAW,QAAQ;AAAA,MACnB,WAAW,QAAQ;AAAA,MACnB,YAAY,QAAQ;AAAA,MACpB,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,WAAW,MAAM,KAAK,0BAA0B;AAAA,IACpD;AAEA,UAAM,UAAU,MAAM,KAAK,QAAQ,cAAc,WAAW;AAC5D,UAAM,eAAe,KAAK,qBAAqB,QAAQ,IAAI,MAAM;AACjE,UAAM,KAAK,QAAQ,cAAc,QAAQ,IAAI;AAAA,MACzC,kBAAkB,KAAK,UAAU,YAAY;AAAA,IACjD,CAAC;AAED,WAAO;AAAA,MACH,SAAS,EAAE,GAAG,SAAS,kBAAkB,KAAK,UAAU,YAAY,EAAE;AAAA,MACtE;AAAA,IACJ;AAAA,EACJ;AAAA,EAEA,MAAM,OACF,iBACA,UACA,UAAwB,CAAC,GACkB;AAC3C,UAAM,eAAe,MAAM,KAAK,QAAQ,eAAe,gBAAgB,KAAK;AAC5E,QAAI,cAAc;AACd,YAAM,IAAI,MAAM,qBAAqB;AAAA,IACzC;AAEA,UAAM,oBAAoB,OAAO;AACjC,UAAM,KAAK,KAAK,OAAO,iBAAiB,SAAS,OAAO,IAAI;AAC5D,UAAM,OAAO;AAAA,MACT,GAAG;AAAA,MACH;AAAA,MACA,cAAc,MAAM,KAAK,aAAa,QAAQ;AAAA,MAC9C,iBAAiB;AAAA,MACjB;AAAA,MACA,WAAW,KAAK,IAAI;AAAA,MACpB,WAAW,KAAK,IAAI;AAAA,IACxB;AAEA,UAAM,cAAc,MAAM,KAAK,QAAQ,WAAW,IAAI;AACtD,UAAM,KAAK,MAAM,sBAAsB,gBAAgB,OAAO,iBAAiB;AAE/E,UAAM,EAAE,aAAa,IAAI,MAAM,KAAK,cAAc,YAAY,IAAK,OAAO;AAC1E,UAAM,cAAc,KAAK,oBAAoB,WAAW;AACxD,WAAO,EAAE,MAAM,aAAa,QAAQ,EAAE,aAAa,aAAa,EAAE;AAAA,EACtE;AAAA,EAEA,MAAM,OACF,OACA,UACA,UAAwB,CAAC,GACkB;AAC3C,UAAM,OAAO,MAAM,KAAK,QAAQ,eAAe,KAAK;AACpD,QAAI,CAAC,MAAM;AACP,YAAM,IAAI,MAAM,qBAAqB;AAAA,IACzC;AAEA,UAAM,kBAAkB,MAAM,OAAO,QAAQ,UAAU,KAAK,YAAY;AACxE,QAAI,CAAC,iBAAiB;AAClB,YAAM,IAAI,MAAM,qBAAqB;AAAA,IACzC;AAEA,QAAI,CAAC,KAAK,iBAAiB;AACvB,YAAM,IAAI,MAAM,4CAA4C;AAAA,IAChE;AAEA,UAAM,EAAE,aAAa,IAAI,MAAM,KAAK,cAAc,KAAK,IAAI,OAAO;AAClE,UAAM,cAAc,KAAK,oBAAoB,IAAI;AACjD,WAAO,EAAE,MAAM,QAAQ,EAAE,aAAa,aAAa,EAAE;AAAA,EACzD;AAAA,EAEA,MAAM,mBAAmB,cAA2C;AAChE,UAAM,UAAU,KAAK,mBAAmB,YAAY;AAEpD,UAAM,UAAU,MAAM,KAAK,QAAQ,eAAe,QAAQ,SAAS;AACnE,QAAI,CAAC,SAAS;AACV,YAAM,IAAI,MAAM,mBAAmB;AAAA,IACvC;AAGA,UAAM,YAAY,KAAK,UAAU,YAAY;AAC7C,QAAI,QAAQ,qBAAqB,WAAW;AACxC,YAAM,IAAI,MAAM,uBAAuB;AAAA,IAC3C;AAGA,QAAI,QAAQ,YAAY,KAAK,IAAI,GAAG;AAChC,YAAM,KAAK,QAAQ,cAAc,QAAQ,EAAG;AAC5C,YAAM,IAAI,MAAM,iBAAiB;AAAA,IACrC;AAEA,UAAM,OAAO,MAAM,KAAK,QAAQ,YAAY,QAAQ,MAAM;AAC1D,QAAI,CAAC,MAAM;AACP,YAAM,IAAI,MAAM,gBAAgB;AAAA,IACpC;AAGA,UAAM,kBAAkB,KAAK,qBAAqB,QAAQ,IAAI,KAAK,EAAE;AAGrE,UAAM,KAAK,QAAQ,cAAc,QAAQ,IAAI;AAAA,MACzC,kBAAkB,KAAK,UAAU,eAAe;AAAA,MAChD,YAAY,KAAK,IAAI;AAAA,MACrB,WAAW,KAAK,IAAI,IAAI,KAAK,0BAA0B;AAAA,IAC3D,CAAC;AAED,UAAM,cAAc,KAAK,oBAAoB,IAAI;AACjD,WAAO,EAAE,aAAa,cAAc,gBAAgB;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,cAAqC;AAC9C,QAAI;AACA,YAAM,UAAU,KAAK,mBAAmB,YAAY;AACpD,YAAM,KAAK,QAAQ,cAAc,QAAQ,SAAS;AAAA,IACtD,QAAQ;AAAA,IAER;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAc,QAAgB,WAAqC;AACrE,UAAM,UAAU,MAAM,KAAK,QAAQ,eAAe,SAAS;AAC3D,QAAI,CAAC,WAAW,QAAQ,WAAW,QAAQ;AACvC,YAAM,IAAI,MAAM,mBAAmB;AAAA,IACvC;AACA,UAAM,KAAK,QAAQ,cAAc,SAAS;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBAAkB,QAA+B;AACnD,UAAM,KAAK,QAAQ,sBAAsB,MAAM;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,QAAgB,qBAAsD;AACpF,UAAM,WAAW,MAAM,KAAK,QAAQ,oBAAoB,MAAM;AAC9D,UAAM,MAAM,KAAK,IAAI;AAErB,QAAI;AACJ,QAAI,qBAAqB;AACrB,UAAI;AACA,cAAM,UAAU,KAAK,mBAAmB,mBAAmB;AAC3D,2BAAmB,QAAQ;AAAA,MAC/B,QAAQ;AAAA,MAER;AAAA,IACJ;AAEA,WAAO,SACF,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,EAC/B,IAAI,CAAC,aAAa;AAAA,MACf,IAAI,QAAQ;AAAA,MACZ,YAAY,QAAQ;AAAA,MACpB,WAAW,QAAQ;AAAA,MACnB,WAAW,QAAQ;AAAA,MACnB,WAAW,QAAQ;AAAA,MACnB,YAAY,QAAQ;AAAA,MACpB,WAAW,QAAQ,OAAO;AAAA,IAC9B,EAAE;AAAA,EACV;AAAA,EAEA,MAAM,YAAY,OAA8B;AAC5C,UAAM,OAAO,MAAM,KAAK,QAAQ,0BAA0B,KAAK;AAC/D,QAAI,CAAC,QAAQ,KAAK,sBAAsB,OAAO;AAC3C,YAAM,IAAI,MAAM,4BAA4B;AAAA,IAChD;AAEA,UAAM,KAAK,QAAQ,WAAW,KAAK,IAAK;AAAA,MACpC,iBAAiB;AAAA,MACjB,mBAAmB;AAAA,MACnB,WAAW,KAAK,IAAI;AAAA,IACxB,CAAC;AAAA,EACL;AAAA,EAEA,MAAM,sBAAsB,OAA8B;AACtD,UAAM,OAAO,MAAM,KAAK,QAAQ,eAAe,KAAK;AACpD,QAAI,CAAC,MAAM;AACP,YAAM,IAAI,MAAM,gBAAgB;AAAA,IACpC;AAEA,UAAM,aAAa,OAAO;AAC1B,UAAM,eAAe,KAAK,IAAI,IAAI;AAElC,UAAM,KAAK,QAAQ,WAAW,KAAK,IAAK;AAAA,MACpC,oBAAoB;AAAA,MACpB,sBAAsB;AAAA,MACtB,WAAW,KAAK,IAAI;AAAA,IACxB,CAAC;AAED,UAAM,KAAK,MAAM,uBAAuB,OAAO,UAAU;AAAA,EAC7D;AAAA,EAEA,MAAM,cAAc,OAAe,aAAoC;AACnE,UAAM,OAAO,MAAM,KAAK,QAAQ,4BAA4B,KAAK;AACjE,QACI,CAAC,QACD,CAAC,KAAK,sBACN,KAAK,uBAAuB,SAC5B,CAAC,KAAK,wBACN,KAAK,uBAAuB,KAAK,IAAI,GACvC;AACE,YAAM,IAAI,MAAM,gCAAgC;AAAA,IACpD;AAEA,UAAM,KAAK,QAAQ,WAAW,KAAK,IAAK;AAAA,MACpC,cAAc,MAAM,KAAK,aAAa,WAAW;AAAA,MACjD,oBAAoB;AAAA,MACpB,sBAAsB;AAAA,MACtB,WAAW,KAAK,IAAI;AAAA,IACxB,CAAC;AAAA,EACL;AAAA,EAEA,MAAM,YACF,OACiF;AACjF,QAAI;AACA,aAAO,IAAI,OAAO,OAAO,KAAK,OAAO,IAAI,WAAW,EAAE,YAAY,CAAC,OAAO,EAAE,CAAC;AAAA,IAMjF,SAAS,QAAQ;AACb,cAAQ,IAAI,8BAA8B,MAAM;AAChD,YAAM,IAAI,MAAM,eAAe;AAAA,IACnC;AAAA,EACJ;AAAA,EAEA,MAAM,QAAQ,QAAgB,eAA2C;AACrE,UAAM,OAAO,MAAM,KAAK,QAAQ,YAAY,MAAM;AAClD,QAAI,CAAC,MAAM;AACP,aAAO;AAAA,IACX;AAEA,WAAO,cAAc,KAAK,CAAC,SAAS,KAAK,MAAM,SAAS,IAAI,CAAC;AAAA,EACjE;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,YAAY,IAAkC;AAChD,WAAO,KAAK,QAAQ,YAAY,EAAE;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe,OAAqC;AACtD,WAAO,KAAK,QAAQ,eAAe,KAAK;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,WAAW,IAAY,SAAyC;AAClE,UAAM,OAAO,MAAM,KAAK,QAAQ,YAAY,EAAE;AAC9C,QAAI,CAAC,MAAM;AACP,YAAM,IAAI,MAAM,gBAAgB;AAAA,IACpC;AAEA,QAAI,QAAQ,UAAU,UAAa,QAAQ,UAAU,KAAK,OAAO;AAC7D,YAAM,eAAe,MAAM,KAAK,QAAQ,eAAe,QAAQ,KAAK;AACpE,UAAI,cAAc;AACd,cAAM,IAAI,MAAM,sBAAsB;AAAA,MAC1C;AAAA,IACJ;AAEA,UAAM,EAAE,OAAO,WAAW,UAAU,OAAO,iBAAiB,GAAG,iBAAiB,IAAI;AACpF,UAAM,gBAAyC;AAAA,MAC3C,WAAW,KAAK,IAAI;AAAA,MACpB,GAAG;AAAA,IACP;AAEA,QAAI,UAAU,OAAW,eAAc,QAAQ;AAC/C,QAAI,cAAc,OAAW,eAAc,YAAY;AACvD,QAAI,aAAa,OAAW,eAAc,WAAW;AACrD,QAAI,UAAU,OAAW,eAAc,QAAQ;AAC/C,QAAI,oBAAoB,OAAW,eAAc,kBAAkB;AAEnE,UAAM,KAAK,QAAQ,WAAW,IAAI,aAAa;AAE/C,WAAO,EAAE,GAAG,MAAM,GAAG,cAAc;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAW,IAA2B;AACxC,UAAM,OAAO,MAAM,KAAK,QAAQ,YAAY,EAAE;AAC9C,QAAI,CAAC,MAAM;AACP,YAAM,IAAI,MAAM,gBAAgB;AAAA,IACpC;AACA,UAAM,KAAK,QAAQ,WAAW,EAAE;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,UAAU,QAAoB,SAAsD;AACtF,WAAO,KAAK,QAAQ,UAAU,QAAQ,OAAO;AAAA,EACjD;AACJ;;;AK9dO,IAAM,iBAAN,MAAqB;AAAA,EAGxB,YAAY,aAA0B;AAClC,SAAK,cAAc;AAAA,EACvB;AAAA,EAEA,cAAc;AACV,WAAO,OAAO,KAAU,KAAU,SAAc;AAC5C,UAAI;AACA,cAAM,QAAQ,IAAI,QAAQ,eAAe,MAAM,GAAG,EAAE,CAAC;AACrD,YAAI,CAAC,OAAO;AACR,gBAAM,IAAI,MAAM,mBAAmB;AAAA,QACvC;AAEA,YAAI,OAAO,MAAM,KAAK,YAAY,YAAY,KAAK;AACnD,aAAK;AAAA,MACT,SAAS,QAAQ;AACb,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,eAAe,CAAC;AAAA,MAClD;AAAA,IACJ;AAAA,EACJ;AAAA,EAEA,aAAa,OAAiB;AAC1B,WAAO,OAAO,KAAU,KAAU,SAAc;AAC5C,UAAI;AACA,cAAM,UAAU,MAAM,KAAK,YAAY,QAAQ,IAAI,KAAK,IAAI,KAAK;AACjE,YAAI,CAAC,SAAS;AACV,gBAAM,IAAI,MAAM,0BAA0B;AAAA,QAC9C;AACA,aAAK;AAAA,MACT,SAAS,QAAQ;AACb,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,YAAY,CAAC;AAAA,MAC/C;AAAA,IACJ;AAAA,EACJ;AAAA,EAEA,uBAAuB;AACnB,WAAO,CAAC,KAAU,KAAU,SAAc;AACtC,UAAI,CAAC,IAAI,KAAK,iBAAiB;AAC3B,eAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,qBAAqB,CAAC;AAAA,MAC/D;AACA,WAAK;AAAA,IACT;AAAA,EACJ;AACJ;;;AC/CA,SAAS,SAAS;AAQX,IAAM,aAAa,EAAE,OAAO;AAAA,EAC/B,IAAI,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC;AAAA,EACpC,OAAO,EAAE,OAAO,EAAE,MAAM;AAAA,EACxB,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,cAAc,EAAE,OAAO;AAAA,EACvB,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA,EACzB,iBAAiB,EAAE,QAAQ;AAAA,EAC3B,mBAAmB,EAAE,OAAO,EAAE,SAAS;AAAA,EACvC,oBAAoB,EAAE,OAAO,EAAE,SAAS;AAAA,EACxC,sBAAsB,EAAE,OAAO,EAAE,SAAS;AAAA,EAC1C,WAAW,EAAE,OAAO;AAAA,EACpB,WAAW,EAAE,OAAO;AACxB,CAAC;AAEM,IAAM,mBAAmB,WAAW,KAAK;AAAA,EAC5C,IAAI;AAAA,EACJ,WAAW;AAAA,EACX,WAAW;AACf,CAAC,EAAE,OAAO;AAAA,EACN,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,IAAI,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,SAAS;AACnD,CAAC;AAKM,IAAM,gBAAgB,EAAE,OAAO;AAAA,EAClC,IAAI,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC;AAAA,EACpC,QAAQ,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC;AAAA,EACxC,kBAAkB,EAAE,OAAO;AAAA,EAC3B,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,EAChC,WAAW,EAAE,OAAO;AAAA,EACpB,YAAY,EAAE,OAAO;AAAA,EACrB,WAAW,EAAE,OAAO;AACxB,CAAC;AAEM,IAAM,sBAAsB,cAAc,KAAK;AAAA,EAClD,IAAI;AAAA,EACJ,WAAW;AAAA,EACX,YAAY;AAChB,CAAC,EAAE,OAAO;AAAA,EACN,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,EAChC,IAAI,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,SAAS;AACnD,CAAC;","names":[]}
|
package/package.json
CHANGED
package/src/services/auth.ts
CHANGED
|
@@ -112,20 +112,25 @@ export class AuthService {
|
|
|
112
112
|
return crypto.createHash('sha256').update(token).digest('hex');
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
/** Return current time as epoch seconds */
|
|
116
|
+
private now(): number {
|
|
117
|
+
return Math.floor(Date.now() / 1000);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private getRefreshTokenExpirySecs(): number {
|
|
116
121
|
const expiresIn = this.config.jwt.refreshTokenExpiresIn || '7d';
|
|
117
122
|
const match = expiresIn.match(/^(\d+)([smhd])$/);
|
|
118
123
|
if (!match) {
|
|
119
|
-
return 7 * 24 * 60 * 60
|
|
124
|
+
return 7 * 24 * 60 * 60; // Default 7 days in seconds
|
|
120
125
|
}
|
|
121
126
|
|
|
122
127
|
const value = parseInt(match[1], 10);
|
|
123
128
|
const unit = match[2];
|
|
124
129
|
const multipliers: Record<string, number> = {
|
|
125
|
-
s:
|
|
126
|
-
m: 60
|
|
127
|
-
h: 60 * 60
|
|
128
|
-
d: 24 * 60 * 60
|
|
130
|
+
s: 1,
|
|
131
|
+
m: 60,
|
|
132
|
+
h: 60 * 60,
|
|
133
|
+
d: 24 * 60 * 60,
|
|
129
134
|
};
|
|
130
135
|
|
|
131
136
|
return value * multipliers[unit];
|
|
@@ -139,7 +144,7 @@ export class AuthService {
|
|
|
139
144
|
userId: UserId,
|
|
140
145
|
options: LoginOptions = {}
|
|
141
146
|
): Promise<{ session: Session; refreshToken: string }> {
|
|
142
|
-
const now =
|
|
147
|
+
const now = this.now();
|
|
143
148
|
const sessionId = this.config.idGeneration === 'uuid' ? uuidv4() : undefined;
|
|
144
149
|
|
|
145
150
|
const tempSession: CreateSessionInput = {
|
|
@@ -151,7 +156,7 @@ export class AuthService {
|
|
|
151
156
|
deviceName: options.deviceName,
|
|
152
157
|
createdAt: now,
|
|
153
158
|
lastUsedAt: now,
|
|
154
|
-
expiresAt: now + this.
|
|
159
|
+
expiresAt: now + this.getRefreshTokenExpirySecs(),
|
|
155
160
|
};
|
|
156
161
|
|
|
157
162
|
const session = await this.storage.createSession(tempSession);
|
|
@@ -184,8 +189,8 @@ export class AuthService {
|
|
|
184
189
|
passwordHash: await this.hashPassword(password),
|
|
185
190
|
isEmailVerified: false,
|
|
186
191
|
verificationToken,
|
|
187
|
-
createdAt:
|
|
188
|
-
updatedAt:
|
|
192
|
+
createdAt: this.now(),
|
|
193
|
+
updatedAt: this.now(),
|
|
189
194
|
};
|
|
190
195
|
|
|
191
196
|
const createdUser = await this.storage.createUser(user);
|
|
@@ -235,7 +240,7 @@ export class AuthService {
|
|
|
235
240
|
}
|
|
236
241
|
|
|
237
242
|
// Check if session is expired
|
|
238
|
-
if (session.expiresAt <
|
|
243
|
+
if (session.expiresAt < this.now()) {
|
|
239
244
|
await this.storage.deleteSession(session.id!);
|
|
240
245
|
throw new Error('Session expired');
|
|
241
246
|
}
|
|
@@ -251,8 +256,8 @@ export class AuthService {
|
|
|
251
256
|
// Update session with new token hash and last used time
|
|
252
257
|
await this.storage.updateSession(session.id, {
|
|
253
258
|
refreshTokenHash: this.hashToken(newRefreshToken),
|
|
254
|
-
lastUsedAt:
|
|
255
|
-
expiresAt:
|
|
259
|
+
lastUsedAt: this.now(),
|
|
260
|
+
expiresAt: this.now() + this.getRefreshTokenExpirySecs(),
|
|
256
261
|
});
|
|
257
262
|
|
|
258
263
|
const accessToken = this.generateAccessToken(user);
|
|
@@ -294,7 +299,7 @@ export class AuthService {
|
|
|
294
299
|
*/
|
|
295
300
|
async getSessions(userId: UserId, currentRefreshToken?: string): Promise<SessionInfo[]> {
|
|
296
301
|
const sessions = await this.storage.getSessionsByUserId(userId);
|
|
297
|
-
const now =
|
|
302
|
+
const now = this.now();
|
|
298
303
|
|
|
299
304
|
let currentSessionId: SessionId | undefined;
|
|
300
305
|
if (currentRefreshToken) {
|
|
@@ -328,7 +333,7 @@ export class AuthService {
|
|
|
328
333
|
await this.storage.updateUser(user.id!, {
|
|
329
334
|
isEmailVerified: true,
|
|
330
335
|
verificationToken: undefined,
|
|
331
|
-
updatedAt:
|
|
336
|
+
updatedAt: this.now(),
|
|
332
337
|
});
|
|
333
338
|
}
|
|
334
339
|
|
|
@@ -339,12 +344,12 @@ export class AuthService {
|
|
|
339
344
|
}
|
|
340
345
|
|
|
341
346
|
const resetToken = uuidv4();
|
|
342
|
-
const resetExpires =
|
|
347
|
+
const resetExpires = this.now() + 3600; // 1 hour in seconds
|
|
343
348
|
|
|
344
349
|
await this.storage.updateUser(user.id!, {
|
|
345
350
|
resetPasswordToken: resetToken,
|
|
346
351
|
resetPasswordExpires: resetExpires,
|
|
347
|
-
updatedAt:
|
|
352
|
+
updatedAt: this.now(),
|
|
348
353
|
});
|
|
349
354
|
|
|
350
355
|
await this.email.sendPasswordResetEmail(email, resetToken);
|
|
@@ -357,7 +362,7 @@ export class AuthService {
|
|
|
357
362
|
!user.resetPasswordToken ||
|
|
358
363
|
user.resetPasswordToken !== token ||
|
|
359
364
|
!user.resetPasswordExpires ||
|
|
360
|
-
user.resetPasswordExpires <
|
|
365
|
+
user.resetPasswordExpires < this.now()
|
|
361
366
|
) {
|
|
362
367
|
throw new Error('Invalid or expired reset token');
|
|
363
368
|
}
|
|
@@ -366,7 +371,7 @@ export class AuthService {
|
|
|
366
371
|
passwordHash: await this.hashPassword(newPassword),
|
|
367
372
|
resetPasswordToken: undefined,
|
|
368
373
|
resetPasswordExpires: undefined,
|
|
369
|
-
updatedAt:
|
|
374
|
+
updatedAt: this.now(),
|
|
370
375
|
});
|
|
371
376
|
}
|
|
372
377
|
|
|
@@ -431,7 +436,7 @@ export class AuthService {
|
|
|
431
436
|
|
|
432
437
|
const { email, firstName, lastName, roles, isEmailVerified, ...additionalFields } = updates;
|
|
433
438
|
const mergedUpdates: Record<string, unknown> = {
|
|
434
|
-
updatedAt:
|
|
439
|
+
updatedAt: this.now(),
|
|
435
440
|
...additionalFields,
|
|
436
441
|
};
|
|
437
442
|
|