// src/lib/crypto.ts
import crypto2 from "crypto";
import jwt from "jsonwebtoken";
function generateKeyPairES256() {
  const keyId = crypto2.randomUUID();
  const { privateKey, publicKey } = crypto2.generateKeyPairSync("ec", {
    namedCurve: "P-256",
    // ES256
    publicKeyEncoding: {
      type: "spki",
      format: "der"
    },
    privateKeyEncoding: {
      type: "pkcs8",
      format: "der"
    }
  });
  const privateKeyB64 = privateKey.toString("base64");
  const publicKeyB64 = publicKey.toString("base64");
  const fingerprint = crypto2.createHash("sha256").update(publicKey).digest("hex");
  return {
    privateKey: privateKeyB64,
    publicKey: publicKeyB64,
    keyId,
    fingerprint,
    algorithm: "ES256"
  };
}
function generateKeyPairRS256() {
  const keyId = crypto2.randomUUID();
  const { privateKey, publicKey } = crypto2.generateKeyPairSync("rsa", {
    modulusLength: 2048,
    publicKeyEncoding: {
      type: "spki",
      format: "der"
    },
    privateKeyEncoding: {
      type: "pkcs8",
      format: "der"
    }
  });
  const privateKeyB64 = privateKey.toString("base64");
  const publicKeyB64 = publicKey.toString("base64");
  const fingerprint = crypto2.createHash("sha256").update(publicKey).digest("hex");
  return {
    privateKey: privateKeyB64,
    publicKey: publicKeyB64,
    keyId,
    fingerprint,
    algorithm: "RS256"
  };
}
function generateKeyPair(algorithm = "ES256") {
  return algorithm === "ES256" ? generateKeyPairES256() : generateKeyPairRS256();
}
function generateClientToken(payload, privateKeyB64, algorithm, options) {
  try {
    const privateKeyDER = Buffer.from(privateKeyB64, "base64");
    const privateKeyObject = crypto2.createPrivateKey({
      key: privateKeyDER,
      format: "der",
      type: "pkcs8"
    });
    const privateKeyPEM = privateKeyObject.export({
      type: "pkcs8",
      format: "pem"
    });
    const signOptions = {
      algorithm,
      issuer: options?.issuer || "spfn-client",
      expiresIn: options?.expiresIn ?? "15m"
      // Default to 15 minutes
    };
    return jwt.sign(payload, privateKeyPEM, signOptions);
  } catch (error) {
    throw new Error(
      `Failed to generate client token: ${error instanceof Error ? error.message : "Unknown error"}`
    );
  }
}
function verifyClientToken(token, publicKeyB64, algorithm) {
  try {
    const publicKeyDER = Buffer.from(publicKeyB64, "base64");
    const publicKeyObject = crypto2.createPublicKey({
      key: publicKeyDER,
      format: "der",
      type: "spki"
    });
    return jwt.verify(token, publicKeyObject, {
      algorithms: [algorithm],
      issuer: "spfn-client"
    });
  } catch (error) {
    throw new Error(
      `Failed to verify token: ${error instanceof Error ? error.message : "Unknown error"}`
    );
  }
}
function getKeySize(publicKeyB64) {
  const keyDER = Buffer.from(publicKeyB64, "base64");
  return {
    bytes: keyDER.length,
    base64Length: publicKeyB64.length
  };
}
function shouldRotateKey(createdAt, rotationDays = 90) {
  const now = /* @__PURE__ */ new Date();
  const ageInDays = Math.floor(
    (now.getTime() - createdAt.getTime()) / (1e3 * 60 * 60 * 24)
  );
  const daysRemaining = Math.max(0, rotationDays - ageInDays);
  return {
    shouldRotate: daysRemaining <= 7,
    // Warn 7 days before expiry
    daysRemaining
  };
}

// src/lib/session.ts
import * as jose from "jose";
function calculateEntropy(str) {
  const len = str.length;
  const frequencies = /* @__PURE__ */ new Map();
  for (const char of str) {
    frequencies.set(char, (frequencies.get(char) || 0) + 1);
  }
  let entropy = 0;
  for (const count of frequencies.values()) {
    const probability = count / len;
    entropy -= probability * Math.log2(probability);
  }
  return entropy;
}
async function getSessionSecret() {
  const secret = process.env.SPFN_AUTH_SESSION_SECRET || // New prefixed version (recommended)
  process.env.SESSION_SECRET;
  if (!secret) {
    throw new Error("SPFN_AUTH_SESSION_SECRET environment variable is not set");
  }
  if (secret.length < 32) {
    throw new Error("SPFN_AUTH_SESSION_SECRET must be at least 32 characters long");
  }
  const encoder = new TextEncoder();
  const data = encoder.encode(secret);
  const hashBuffer = await crypto.subtle.digest("SHA-256", data);
  return new Uint8Array(hashBuffer);
}
async function sealSession(data, ttl = 60 * 60 * 24 * 7) {
  const secret = await getSessionSecret();
  return await new jose.EncryptJWT({ data }).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).setIssuedAt().setExpirationTime(`${ttl}s`).setIssuer("spfn-auth").setAudience("spfn-client").encrypt(secret);
}
async function unsealSession(jwt2) {
  const secret = await getSessionSecret();
  try {
    const { payload } = await jose.jwtDecrypt(jwt2, secret, {
      issuer: "spfn-auth",
      audience: "spfn-client"
    });
    return payload.data;
  } catch (err) {
    if (err instanceof jose.errors.JWTExpired) {
      throw new Error("Session expired");
    }
    if (err instanceof jose.errors.JWEDecryptionFailed) {
      throw new Error("Invalid session");
    }
    if (err instanceof jose.errors.JWTClaimValidationFailed) {
      throw new Error("Session validation failed");
    }
    throw new Error("Failed to unseal session");
  }
}
async function getSessionInfo(jwt2) {
  const secret = await getSessionSecret();
  try {
    const { payload } = await jose.jwtDecrypt(jwt2, secret);
    return {
      issuedAt: new Date(payload.iat * 1e3),
      expiresAt: new Date(payload.exp * 1e3),
      issuer: payload.iss || "",
      audience: Array.isArray(payload.aud) ? payload.aud[0] : payload.aud || ""
    };
  } catch (err) {
    if (process.env.NODE_ENV !== "production") {
      console.warn("[Session] Failed to get session info:", err instanceof Error ? err.message : "Unknown error");
    }
    return null;
  }
}
async function shouldRefreshSession(jwt2, thresholdHours = 24) {
  const info = await getSessionInfo(jwt2);
  if (!info) {
    return true;
  }
  const hoursRemaining = (info.expiresAt.getTime() - Date.now()) / (1e3 * 60 * 60);
  return hoursRemaining < thresholdHours;
}
function validateSessionSecret() {
  try {
    const secret = process.env.SPFN_AUTH_SESSION_SECRET || // New prefixed version (recommended)
    process.env.SESSION_SECRET;
    if (!secret) {
      return { valid: false, error: "SPFN_AUTH_SESSION_SECRET is not set" };
    }
    const length = secret.length;
    const uniqueChars = new Set(secret).size;
    const entropy = calculateEntropy(secret);
    if (length < 32) {
      return {
        valid: false,
        error: `SPFN_AUTH_SESSION_SECRET too short (${length} chars, minimum 32)`,
        details: { length, uniqueChars, entropy }
      };
    }
    if (uniqueChars < 16) {
      return {
        valid: false,
        error: `SPFN_AUTH_SESSION_SECRET has low diversity (${uniqueChars} unique chars, minimum 16)`,
        details: { length, uniqueChars, entropy }
      };
    }
    if (entropy < 3.5) {
      return {
        valid: false,
        error: `SPFN_AUTH_SESSION_SECRET has low entropy (${entropy.toFixed(2)} bits/char, minimum 3.5). Use a more random secret.`,
        details: { length, uniqueChars, entropy }
      };
    }
    return {
      valid: true,
      details: { length, uniqueChars, entropy }
    };
  } catch (err) {
    return { valid: false, error: "Failed to validate SPFN_AUTH_SESSION_SECRET" };
  }
}

// src/lib/config.ts
var COOKIE_NAMES = {
  /** Encrypted session data (userId, privateKey, keyId, algorithm) */
  SESSION: "spfn_session",
  /** Current key ID (for key rotation) */
  SESSION_KEY_ID: "spfn_session_key_id"
};
function parseDuration(duration) {
  if (typeof duration === "number") {
    return duration;
  }
  const match = duration.match(/^(\d+)([dhms]?)$/);
  if (!match) {
    throw new Error(`Invalid duration format: ${duration}. Use format like '30d', '12h', '45m', '3600s', or plain number.`);
  }
  const value = parseInt(match[1], 10);
  const unit = match[2] || "s";
  switch (unit) {
    case "d":
      return value * 24 * 60 * 60;
    case "h":
      return value * 60 * 60;
    case "m":
      return value * 60;
    case "s":
      return value;
    default:
      throw new Error(`Unknown duration unit: ${unit}`);
  }
}
var globalConfig = {
  sessionTtl: "7d"
  // Default: 7 days
};
function configureAuth(config) {
  globalConfig = {
    ...globalConfig,
    ...config
  };
}
function getAuthConfig() {
  return { ...globalConfig };
}
function getSessionTtl(override) {
  if (override !== void 0) {
    return parseDuration(override);
  }
  if (globalConfig.sessionTtl !== void 0) {
    return parseDuration(globalConfig.sessionTtl);
  }
  const envTtl = process.env.SPFN_AUTH_SESSION_TTL;
  if (envTtl) {
    return parseDuration(envTtl);
  }
  return 7 * 24 * 60 * 60;
}
export {
  COOKIE_NAMES,
  configureAuth,
  generateClientToken,
  generateKeyPair,
  generateKeyPairES256,
  generateKeyPairRS256,
  getAuthConfig,
  getKeySize,
  getSessionInfo,
  getSessionTtl,
  parseDuration,
  sealSession,
  shouldRefreshSession,
  shouldRotateKey,
  unsealSession,
  validateSessionSecret,
  verifyClientToken
};
//# sourceMappingURL=index.js.map