import crypto from "node:crypto";
const ALGORITHM = "aes-256-gcm";
const IV_LENGTH = 12;
const TAG_LENGTH = 16;
const COMMUNICATION_CHANNELS_OAUTH_STATE_TTL_MS = 5 * 60 * 1e3;
const HKDF_SALT = Buffer.from("open-mercato-channels-oauth-state-v1");
const HKDF_INFO = Buffer.from("communication_channels-oauth-state-cookie");
const COMMUNICATION_CHANNELS_OAUTH_STATE_COOKIE_NAME = "om_cc_oauth_state";
const DEFAULT_OAUTH_RETURN_URL = "/backend/profile/communication-channels";
class OAuthStateError extends Error {
  constructor(message, code) {
    super(message);
    this.code = code;
    this.name = "OAuthStateError";
  }
}
function isSafeOAuthReturnUrl(value) {
  if (typeof value !== "string") return false;
  if (value.length === 0 || value.length > 2048) return false;
  if (!value.startsWith("/") || value.startsWith("//")) return false;
  if (value.includes("\\")) return false;
  try {
    const base = new URL("https://open-mercato.local");
    const parsed = new URL(value, base);
    return parsed.origin === base.origin && parsed.pathname.startsWith("/");
  } catch {
    return false;
  }
}
function normalizeOAuthReturnUrl(value, fallback = DEFAULT_OAUTH_RETURN_URL) {
  return isSafeOAuthReturnUrl(value) ? value : fallback;
}
function deriveKey(secret) {
  return Buffer.from(crypto.hkdfSync("sha256", secret, HKDF_SALT, HKDF_INFO, 32));
}
function getSecret() {
  const dedicated = process.env.OM_HUB_OAUTH_STATE_KEY ?? process.env.KMS_MASTER_KEY;
  if (dedicated) return dedicated;
  if (process.env.NODE_ENV === "production") {
    throw new Error("[internal] OM_HUB_OAUTH_STATE_KEY or KMS_MASTER_KEY required in production");
  }
  const fallback = process.env.JWT_SECRET;
  if (!fallback) {
    throw new OAuthStateError(
      "OM_HUB_OAUTH_STATE_KEY (or fallback KMS_MASTER_KEY / JWT_SECRET) must be set",
      "missing_secret"
    );
  }
  return fallback;
}
function encryptOAuthState(payload) {
  const key = deriveKey(getSecret());
  const iv = crypto.randomBytes(IV_LENGTH);
  const json = JSON.stringify(payload);
  const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
  const ciphertext = Buffer.concat([cipher.update(json, "utf8"), cipher.final()]);
  const tag = cipher.getAuthTag();
  return Buffer.concat([iv, tag, ciphertext]).toString("base64url");
}
function decryptOAuthState(cookie) {
  try {
    const key = deriveKey(getSecret());
    const combined = Buffer.from(cookie, "base64url");
    if (combined.length < IV_LENGTH + TAG_LENGTH) return null;
    const iv = combined.subarray(0, IV_LENGTH);
    const tag = combined.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
    const ciphertext = combined.subarray(IV_LENGTH + TAG_LENGTH);
    const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
    decipher.setAuthTag(tag);
    const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
    return JSON.parse(decrypted);
  } catch {
    return null;
  }
}
function verifyOAuthState(input) {
  if (!input.cookie) {
    throw new OAuthStateError("Missing state cookie", "invalid_cookie");
  }
  const payload = decryptOAuthState(input.cookie);
  if (!payload) {
    throw new OAuthStateError("Invalid state cookie", "decrypt_failed");
  }
  const now = input.now ?? Date.now();
  if (payload.expiresAt < now) {
    throw new OAuthStateError("State cookie expired", "expired");
  }
  if (payload.userId !== input.expectedUserId) {
    throw new OAuthStateError("State cookie userId mismatch", "user_mismatch");
  }
  if (input.expectedProviderKey && payload.providerKey !== input.expectedProviderKey) {
    throw new OAuthStateError("State cookie providerKey mismatch", "invalid_cookie");
  }
  if (input.expectedState && payload.state !== input.expectedState) {
    throw new OAuthStateError("State cookie state nonce mismatch", "invalid_cookie");
  }
  return payload;
}
function createOAuthState(params) {
  const state = crypto.randomBytes(32).toString("base64url");
  const nonce = crypto.randomBytes(16).toString("base64url");
  const payload = {
    state,
    nonce,
    userId: params.userId,
    tenantId: params.tenantId,
    organizationId: params.organizationId ?? null,
    providerKey: params.providerKey,
    returnUrl: params.returnUrl,
    extra: params.extra,
    expiresAt: Date.now() + COMMUNICATION_CHANNELS_OAUTH_STATE_TTL_MS
  };
  const cookie = encryptOAuthState(payload);
  return { payload, cookie, stateParam: state };
}
export {
  COMMUNICATION_CHANNELS_OAUTH_STATE_COOKIE_NAME,
  COMMUNICATION_CHANNELS_OAUTH_STATE_TTL_MS,
  DEFAULT_OAUTH_RETURN_URL,
  OAuthStateError,
  createOAuthState,
  decryptOAuthState,
  encryptOAuthState,
  isSafeOAuthReturnUrl,
  normalizeOAuthReturnUrl,
  verifyOAuthState
};
//# sourceMappingURL=oauth-state.js.map
