@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 CHANGED
@@ -285,7 +285,9 @@ declare class AuthService {
285
285
  private generateRefreshToken;
286
286
  private verifyRefreshToken;
287
287
  private hashToken;
288
- private getRefreshTokenExpiryMs;
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
- private getRefreshTokenExpiryMs;
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
- getRefreshTokenExpiryMs() {
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 * 1e3;
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: 1e3,
290
- m: 60 * 1e3,
291
- h: 60 * 60 * 1e3,
292
- d: 24 * 60 * 60 * 1e3
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 = Date.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.getRefreshTokenExpiryMs()
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: Date.now(),
337
- updatedAt: Date.now()
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 < Date.now()) {
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: Date.now(),
383
- expiresAt: Date.now() + this.getRefreshTokenExpiryMs()
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 = Date.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: Date.now()
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 = Date.now() + 36e5;
459
+ const resetExpires = this.now() + 3600;
456
460
  await this.storage.updateUser(user.id, {
457
461
  resetPasswordToken: resetToken,
458
462
  resetPasswordExpires: resetExpires,
459
- updatedAt: Date.now()
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 < Date.now()) {
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: Date.now()
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: Date.now(),
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
- getRefreshTokenExpiryMs() {
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 * 1e3;
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: 1e3,
249
- m: 60 * 1e3,
250
- h: 60 * 60 * 1e3,
251
- d: 24 * 60 * 60 * 1e3
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 = Date.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.getRefreshTokenExpiryMs()
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: Date.now(),
296
- updatedAt: Date.now()
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 < Date.now()) {
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: Date.now(),
342
- expiresAt: Date.now() + this.getRefreshTokenExpiryMs()
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 = Date.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: Date.now()
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 = Date.now() + 36e5;
418
+ const resetExpires = this.now() + 3600;
415
419
  await this.storage.updateUser(user.id, {
416
420
  resetPasswordToken: resetToken,
417
421
  resetPasswordExpires: resetExpires,
418
- updatedAt: Date.now()
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 < Date.now()) {
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: Date.now()
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: Date.now(),
484
+ updatedAt: this.now(),
481
485
  ...additionalFields
482
486
  };
483
487
  if (email !== void 0) mergedUpdates.email = email;
@@ -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":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcelsior/auth",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Reusable serverless authentication system with RBAC and configurable email notifications",
5
5
  "exports": {
6
6
  ".": {
@@ -112,20 +112,25 @@ export class AuthService {
112
112
  return crypto.createHash('sha256').update(token).digest('hex');
113
113
  }
114
114
 
115
- private getRefreshTokenExpiryMs(): number {
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 * 1000; // Default 7 days
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: 1000,
126
- m: 60 * 1000,
127
- h: 60 * 60 * 1000,
128
- d: 24 * 60 * 60 * 1000,
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 = Date.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.getRefreshTokenExpiryMs(),
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: Date.now(),
188
- updatedAt: Date.now(),
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 < Date.now()) {
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: Date.now(),
255
- expiresAt: Date.now() + this.getRefreshTokenExpiryMs(),
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 = Date.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: Date.now(),
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 = Date.now() + 3600000; // 1 hour
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: Date.now(),
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 < Date.now()
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: Date.now(),
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: Date.now(),
439
+ updatedAt: this.now(),
435
440
  ...additionalFields,
436
441
  };
437
442