{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import crypto from 'crypto'\nimport { isAfter, addSeconds } from 'date-fns'\nimport { z } from 'zod'\nimport {\n  KenmonIdentifier,\n  KenmonReturnType,\n  KenmonMailer,\n  KenmonError,\n  KenmonInvalidPayloadError,\n} from 'kenmon'\nimport { generateSignature } from './signature'\n\n// OTP-specific error with reason discriminator\nexport type KenmonEmailOTPErrorReason =\n  | 'not-found'\n  | 'expired'\n  | 'invalid-code'\n  | 'already-used'\n  | 'email-mismatch'\n\nexport interface KenmonEmailOTP {\n  id: string\n  email: string\n  code: string\n  signature: string\n  expiresAt: Date\n  used: boolean\n}\n\nexport class KenmonEmailOTPError extends KenmonError {\n  readonly reason: KenmonEmailOTPErrorReason\n\n  constructor(reason: KenmonEmailOTPErrorReason) {\n    const messages: Record<KenmonEmailOTPErrorReason, string> = {\n      'not-found': 'OTP not found',\n      expired: 'OTP has expired',\n      'invalid-code': 'Invalid OTP code',\n      'already-used': 'OTP has already been used',\n      'email-mismatch': 'Email does not match OTP',\n    }\n\n    super(messages[reason])\n    this.name = 'KenmonOTPError'\n    this.reason = reason\n    Object.setPrototypeOf(this, KenmonEmailOTPError.prototype)\n  }\n}\n\n// Zod schemas for payload validation\nconst emailOTPPrepareDataSchema = z.object({\n  email: z.email('Invalid email address'),\n})\n\nconst emailOTPAuthenticateDataSchema = z.object({\n  email: z.email('Invalid email address'),\n  otpId: z.string().min(1, 'OTP ID is required'),\n  code: z.string().min(1, 'OTP code is required'),\n})\n\n// OTP Storage interface\nexport interface KenmonEmailOTPStorage {\n  createOTP(\n    email: string,\n    code: string,\n    expiresAt: Date,\n    signature: string,\n  ): Promise<KenmonEmailOTP>\n  getOTPById(id: string): Promise<KenmonEmailOTP | null>\n  markOTPAsUsed(id: string): Promise<void>\n}\n\n// EmailOTP Provider configuration\nexport interface KenmonEmailOTPProviderConfig {\n  mailer: KenmonMailer\n  otpStorage: KenmonEmailOTPStorage\n  otpTtl?: number // seconds, default 300 (5 minutes)\n  otpLength?: number // default 6\n  emailFrom: string\n  emailSubject?: (code: string, signature: string, otpTtl: number) => string\n  emailTextContent?: (code: string, signature: string, otpTtl: number) => string\n  emailHtmlContent?: (code: string, signature: string, otpTtl: number) => string\n}\n\nexport class KenmonEmailOTPAuthenticator {\n  readonly type = 'email-otp'\n\n  private mailer: KenmonMailer\n  private otpStorage: KenmonEmailOTPStorage\n  private otpTtl: number\n  private otpLength: number\n  private emailFrom: string\n  private emailSubject: (\n    code: string,\n    signature: string,\n    otpTtl: number,\n  ) => string\n  private emailTextContent: (\n    code: string,\n    signature: string,\n    otpTtl: number,\n  ) => string\n  private emailHtmlContent: (\n    code: string,\n    signature: string,\n    otpTtl: number,\n  ) => string\n\n  constructor(config: KenmonEmailOTPProviderConfig) {\n    this.mailer = config.mailer\n    this.otpStorage = config.otpStorage\n    this.otpTtl = config.otpTtl ?? 300 // 5 minutes default\n    this.otpLength = config.otpLength ?? 6\n    this.emailFrom = config.emailFrom\n\n    // Set default email subject\n    this.emailSubject =\n      config.emailSubject ??\n      ((code: string, signature: string, otpTtl: number) => {\n        return `Verify your email - ${signature}`\n      })\n\n    // Set default text content\n    this.emailTextContent =\n      config.emailTextContent ??\n      ((code: string, signature: string, otpTtl: number) => {\n        return `Your verification code is: ${code}\n\nRequest Signature: ${signature}\n⚠️ Verify this signature matches the one shown on the website before entering your code.\n\nThis code will expire in ${Math.floor(otpTtl / 60)} minutes.`\n      })\n\n    // Set default HTML content\n    this.emailHtmlContent =\n      config.emailHtmlContent ??\n      ((code: string, signature: string, otpTtl: number) => {\n        return `\n<div>\n  <p>Your verification code is:</p>\n  <h2 style=\"font-size: 32px; letter-spacing: 8px; font-weight: bold;\">${code}</h2>\n  <p><strong>Request Signature:</strong> ${signature}</p>\n  <p style=\"color: #666; font-size: 14px; margin-top: 8px;\">⚠️ Verify this signature matches the one shown on the website before entering your code.</p>\n  <p style=\"margin-top: 16px;\">This code will expire in ${Math.floor(otpTtl / 60)} minutes.</p>\n</div>\n      `.trim()\n      })\n  }\n\n  async sendOTP(\n    email: string,\n  ): Promise<KenmonReturnType<{ otpId: string; signature: string }>> {\n    // Validate email\n    const result = emailOTPPrepareDataSchema.safeParse({ email })\n    if (!result.success) {\n      return {\n        success: false,\n        error: new KenmonInvalidPayloadError(result.error.issues[0].message),\n      }\n    }\n\n    try {\n      // Generate OTP code\n      const code = this.generateOTPCode()\n\n      // Generate signature\n      const signature = generateSignature()\n\n      // Calculate expiry\n      const expiresAt = addSeconds(new Date(), this.otpTtl)\n\n      // Store OTP\n      const otp = await this.otpStorage.createOTP(\n        email,\n        code,\n        expiresAt,\n        signature,\n      )\n\n      // Generate email content\n      const subject = this.emailSubject(code, signature, this.otpTtl)\n      const textContent = this.emailTextContent(code, signature, this.otpTtl)\n      const htmlContent = this.emailHtmlContent(code, signature, this.otpTtl)\n\n      // Send email\n      await this.mailer.sendEmail({\n        from: this.emailFrom,\n        to: email,\n        subject,\n        textContent,\n        htmlContent,\n      })\n\n      return {\n        success: true,\n        data: { otpId: otp.id, signature: otp.signature },\n      }\n    } catch (error) {\n      return { success: false, error: error as Error }\n    }\n  }\n\n  async verifyOTP(payload: {\n    email: string\n    otpId: string\n    code: string\n  }): Promise<KenmonReturnType<KenmonIdentifier>> {\n    // Validate payload with Zod\n    const result = emailOTPAuthenticateDataSchema.safeParse(payload)\n    if (!result.success) {\n      return {\n        success: false,\n        error: new KenmonInvalidPayloadError(result.error.issues[0].message),\n      }\n    }\n\n    const { email, otpId, code } = result.data\n\n    try {\n      // Fetch OTP from storage\n      const otp = await this.otpStorage.getOTPById(otpId)\n      if (!otp) {\n        return { success: false, error: new KenmonEmailOTPError('not-found') }\n      }\n\n      // Verify OTP belongs to this email\n      if (otp.email !== email) {\n        return {\n          success: false,\n          error: new KenmonEmailOTPError('email-mismatch'),\n        }\n      }\n\n      // Check if OTP has been used\n      if (otp.used) {\n        return {\n          success: false,\n          error: new KenmonEmailOTPError('already-used'),\n        }\n      }\n\n      // Check if OTP has expired\n      if (isAfter(new Date(), otp.expiresAt)) {\n        return { success: false, error: new KenmonEmailOTPError('expired') }\n      }\n\n      // Verify OTP code\n      if (otp.code !== code) {\n        return {\n          success: false,\n          error: new KenmonEmailOTPError('invalid-code'),\n        }\n      }\n\n      // Mark OTP as used\n      await this.otpStorage.markOTPAsUsed(otpId)\n\n      // Return identifier\n      return {\n        success: true,\n        data: {\n          type: 'email-otp',\n          value: email,\n        },\n      }\n    } catch (error) {\n      return { success: false, error: error as Error }\n    }\n  }\n\n  private generateOTPCode(): string {\n    const digits = '0123456789'\n    let code = ''\n\n    for (let i = 0; i < this.otpLength; i++) {\n      code += digits[crypto.randomInt(0, 10)]\n    }\n\n    return code\n  }\n}\n"],"mappings":"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"}