import { g as getHeader, c as createError, eS as isApiToken, eT as verifyApiToken, eU as hasScope, aG as requireFeature, dA as hashPassword, b as getDatabaseManager, ae as getAdminScopesForRole, e2 as matchesScope } from '../nitro/nitro.mjs';
import { randomBytes } from 'node:crypto';

function getDb() {
  return getDatabaseManager();
}
const SCIM_USER_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:User";
const SCIM_LIST_SCHEMA = "urn:ietf:params:scim:api:messages:2.0:ListResponse";
const SCIM_ERROR_SCHEMA = "urn:ietf:params:scim:api:messages:2.0:Error";
function scimUserToResource(row, baseUrl) {
  var _a, _b, _c;
  const isActive = row.is_active === 1 || row.is_active === true;
  return {
    schemas: [SCIM_USER_SCHEMA],
    id: String(row.id),
    externalId: (_a = row.scim_external_id) != null ? _a : null,
    userName: row.email || row.username,
    name: {
      givenName: (_b = row.first_name) != null ? _b : void 0,
      familyName: (_c = row.last_name) != null ? _c : void 0,
      formatted: [row.first_name, row.last_name].filter(Boolean).join(" ") || void 0
    },
    emails: [{ value: row.email, primary: true, type: "work" }],
    active: isActive,
    meta: {
      resourceType: "User",
      created: row.created_at,
      lastModified: row.updated_at,
      location: `${baseUrl}/api/scim/v2/Users/${row.id}`
    }
  };
}
function scimError(status, detail, scimType) {
  return {
    schemas: [SCIM_ERROR_SCHEMA],
    status: String(status),
    detail,
    scimType
  };
}
function pickPrimaryEmail(emails) {
  var _a;
  if (!Array.isArray(emails) || emails.length === 0) {
    return null;
  }
  const primary = emails.find((e) => e.primary && typeof e.value === "string");
  if (primary == null ? void 0 : primary.value) {
    return primary.value;
  }
  const first = emails.find((e) => typeof e.value === "string");
  return (_a = first == null ? void 0 : first.value) != null ? _a : null;
}
function findByScimExternalId(externalId) {
  const db = getDb();
  const p = (i) => db.getPlaceholder(i);
  return db.queryOne(
    `SELECT * FROM admin_users WHERE scim_external_id = ${p(1)}`,
    [externalId]
  );
}
function findByEmail(email) {
  const db = getDb();
  const p = (i) => db.getPlaceholder(i);
  return db.queryOne(`SELECT * FROM admin_users WHERE email = ${p(1)}`, [email]);
}
function findByUsername(username) {
  const db = getDb();
  const p = (i) => db.getPlaceholder(i);
  return db.queryOne(`SELECT * FROM admin_users WHERE username = ${p(1)}`, [
    username
  ]);
}
async function generateUsername(seed) {
  const sanitized = seed.toLowerCase().replace(/[^a-z0-9._-]+/g, ".").replace(/^[._-]+|[._-]+$/g, "").slice(0, 32) || "user";
  for (let i = 0; i < 8; i++) {
    const candidate = i === 0 ? sanitized : `${sanitized}.${i}`;
    if (!await findByUsername(candidate)) {
      return candidate;
    }
  }
  return `${sanitized}.${randomBytes(3).toString("hex")}`;
}
async function scimCreateUser(payload, baseUrl) {
  var _a, _b, _c, _d, _e, _f;
  const email = (_a = pickPrimaryEmail(payload.emails)) != null ? _a : payload.userName;
  if (!email) {
    throw new Error("Email is required (provide emails[].value or userName)");
  }
  const externalId = (_b = payload.externalId) != null ? _b : null;
  if (externalId) {
    const existing = await findByScimExternalId(externalId);
    if (existing) {
      throw new Error("User with this externalId already exists");
    }
  }
  const byEmail = await findByEmail(email);
  if (byEmail) {
    throw new Error("User with this email already exists");
  }
  const username = await generateUsername(payload.userName || email.split("@")[0]);
  const randomPassword = randomBytes(24).toString("base64url");
  const hashed = await hashPassword(randomPassword);
  const isActive = payload.active === false ? false : true;
  const db = getDb();
  const dialect = db.getDialect();
  const isActiveValue = dialect === "postgres" ? isActive : isActive ? 1 : 0;
  const externalOnlyValue = dialect === "postgres" ? true : 1;
  const values = [
    username,
    email,
    hashed,
    (_d = (_c = payload.name) == null ? void 0 : _c.givenName) != null ? _d : null,
    (_f = (_e = payload.name) == null ? void 0 : _e.familyName) != null ? _f : null,
    "editor",
    isActiveValue,
    externalOnlyValue,
    externalId
  ];
  let userId;
  if (dialect === "postgres") {
    const result = await db.execute(
      `INSERT INTO admin_users
        (username, email, password, first_name, last_name, role, is_active, external_only, scim_external_id)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
       RETURNING id`,
      values
    );
    userId = Number(result.lastInsertRowid);
  } else {
    const result = await db.execute(
      `INSERT INTO admin_users
        (username, email, password, first_name, last_name, role, is_active, external_only, scim_external_id)
       VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
      values
    );
    userId = Number(result.lastInsertRowid);
  }
  const p = (i) => db.getPlaceholder(i);
  const created = await db.queryOne(
    `SELECT * FROM admin_users WHERE id = ${p(1)}`,
    [userId]
  );
  if (!created) {
    throw new Error("Failed to load created SCIM user");
  }
  return {
    resource: scimUserToResource(created, baseUrl),
    created: true
  };
}
async function scimGetUser(id, baseUrl) {
  const db = getDb();
  const p = (i) => db.getPlaceholder(i);
  const row = await db.queryOne(`SELECT * FROM admin_users WHERE id = ${p(1)}`, [
    id
  ]);
  return row ? scimUserToResource(row, baseUrl) : null;
}
function parseSimpleFilter(filter) {
  if (!filter) {
    return {};
  }
  const eqMatch = /^\s*(userName|emails\.value|externalId|email)\s+eq\s+"([^"]+)"\s*$/.exec(filter);
  if (!eqMatch) {
    return {};
  }
  const [, key, value] = eqMatch;
  if (key === "externalId") {
    return { externalId: value };
  }
  return { email: value };
}
async function scimListUsers(query, baseUrl) {
  var _a;
  const db = getDb();
  const p = (i) => db.getPlaceholder(i);
  const startIndex = Math.max(1, Math.trunc(Number(query.startIndex) || 1));
  const count = Math.max(0, Math.min(200, Math.trunc(Number(query.count) || 100)));
  const filter = parseSimpleFilter(query.filter);
  let where = "";
  const params = [];
  if (filter.email) {
    where = `WHERE email = ${p(1)}`;
    params.push(filter.email);
  } else if (filter.externalId) {
    where = `WHERE scim_external_id = ${p(1)}`;
    params.push(filter.externalId);
  }
  const totalRow = await db.queryOne(
    `SELECT COUNT(*) as total FROM admin_users ${where}`,
    params
  );
  const total = Number((_a = totalRow == null ? void 0 : totalRow.total) != null ? _a : 0);
  const offset = startIndex - 1;
  const limitParam = p(params.length + 1);
  const offsetParam = p(params.length + 2);
  const rows = await db.query(
    `SELECT * FROM admin_users ${where} ORDER BY id ASC LIMIT ${limitParam} OFFSET ${offsetParam}`,
    [...params, count, offset]
  );
  return {
    schemas: [SCIM_LIST_SCHEMA],
    totalResults: total,
    startIndex,
    itemsPerPage: rows.length,
    Resources: rows.map((row) => scimUserToResource(row, baseUrl))
  };
}
async function scimReplaceUser(id, payload, baseUrl) {
  var _a, _b, _c, _d, _e, _f;
  const db = getDb();
  const dialect = db.getDialect();
  const p = (i) => db.getPlaceholder(i);
  const existing = await db.queryOne(
    `SELECT * FROM admin_users WHERE id = ${p(1)}`,
    [id]
  );
  if (!existing) {
    throw new Error("User not found");
  }
  const email = (_b = (_a = pickPrimaryEmail(payload.emails)) != null ? _a : payload.userName) != null ? _b : existing.email;
  const givenName = (_d = (_c = payload.name) == null ? void 0 : _c.givenName) != null ? _d : existing.first_name;
  const familyName = (_f = (_e = payload.name) == null ? void 0 : _e.familyName) != null ? _f : existing.last_name;
  const isActive = payload.active === void 0 ? existing.is_active : payload.active;
  const isActiveValue = dialect === "postgres" ? Boolean(isActive) : isActive ? 1 : 0;
  const deactivatedAtSql = Boolean(isActive) ? "NULL" : "CURRENT_TIMESTAMP";
  const externalId = payload.externalId === void 0 ? existing.scim_external_id : payload.externalId;
  if (email !== existing.email) {
    const conflict = await db.queryOne(
      `SELECT id FROM admin_users WHERE email = ${p(1)} AND id <> ${p(2)}`,
      [email, id]
    );
    if (conflict) {
      throw new Error("User with this email already exists");
    }
  }
  if (typeof externalId === "string" && externalId.length > 0 && externalId !== existing.scim_external_id) {
    const conflict = await db.queryOne(
      `SELECT id FROM admin_users WHERE scim_external_id = ${p(1)} AND id <> ${p(2)}`,
      [externalId, id]
    );
    if (conflict) {
      throw new Error("User with this externalId already exists");
    }
  }
  await db.execute(
    `UPDATE admin_users
     SET email = ${p(1)},
         first_name = ${p(2)},
         last_name = ${p(3)},
         is_active = ${p(4)},
         scim_external_id = ${p(5)},
         deactivated_at = ${deactivatedAtSql},
         updated_at = CURRENT_TIMESTAMP
     WHERE id = ${p(6)}`,
    [email, givenName, familyName, isActiveValue, externalId, id]
  );
  const refreshed = await db.queryOne(
    `SELECT * FROM admin_users WHERE id = ${p(1)}`,
    [id]
  );
  if (!refreshed) {
    throw new Error("Failed to reload user after replace");
  }
  return scimUserToResource(refreshed, baseUrl);
}
async function scimPatchUser(id, payload, baseUrl) {
  var _a, _b, _c;
  if (!Array.isArray(payload.Operations) || payload.Operations.length === 0) {
    throw new Error("PATCH body must contain Operations[]");
  }
  const db = getDb();
  const dialect = db.getDialect();
  const p = (i) => db.getPlaceholder(i);
  const existing = await db.queryOne(
    `SELECT * FROM admin_users WHERE id = ${p(1)}`,
    [id]
  );
  if (!existing) {
    throw new Error("User not found");
  }
  let active = null;
  let email = null;
  let givenName = null;
  let familyName = null;
  for (const op of payload.Operations) {
    const opName = ((_a = op.op) != null ? _a : "").toLowerCase();
    if (opName !== "replace" && opName !== "add") {
      continue;
    }
    if (!op.path && op.value && typeof op.value === "object") {
      const value = op.value;
      if (typeof value.active === "boolean") {
        active = value.active;
      }
      const e = pickPrimaryEmail(value.emails);
      if (e) {
        email = e;
      }
      if ((_b = value.name) == null ? void 0 : _b.givenName) {
        givenName = value.name.givenName;
      }
      if ((_c = value.name) == null ? void 0 : _c.familyName) {
        familyName = value.name.familyName;
      }
      continue;
    }
    if (op.path === "active" && typeof op.value === "boolean") {
      active = op.value;
    }
    if (op.path === "userName" && typeof op.value === "string") {
      email = op.value;
    }
    if (op.path === "name.givenName" && typeof op.value === "string") {
      givenName = op.value;
    }
    if (op.path === "name.familyName" && typeof op.value === "string") {
      familyName = op.value;
    }
  }
  if (email !== null && email !== existing.email) {
    const conflict = await db.queryOne(
      `SELECT id FROM admin_users WHERE email = ${p(1)} AND id <> ${p(2)}`,
      [email, id]
    );
    if (conflict) {
      throw new Error("User with this email already exists");
    }
  }
  const updates = [];
  const values = [];
  let idx = 1;
  if (active !== null) {
    updates.push(`is_active = ${p(idx++)}`);
    values.push(dialect === "postgres" ? active : active ? 1 : 0);
    if (!active) {
      updates.push(`deactivated_at = CURRENT_TIMESTAMP`);
    } else {
      updates.push(`deactivated_at = NULL`);
    }
  }
  if (email !== null) {
    updates.push(`email = ${p(idx++)}`);
    values.push(email);
  }
  if (givenName !== null) {
    updates.push(`first_name = ${p(idx++)}`);
    values.push(givenName);
  }
  if (familyName !== null) {
    updates.push(`last_name = ${p(idx++)}`);
    values.push(familyName);
  }
  if (updates.length > 0) {
    updates.push("updated_at = CURRENT_TIMESTAMP");
    values.push(id);
    await db.execute(`UPDATE admin_users SET ${updates.join(", ")} WHERE id = ${p(idx)}`, values);
  }
  const refreshed = await db.queryOne(
    `SELECT * FROM admin_users WHERE id = ${p(1)}`,
    [id]
  );
  if (!refreshed) {
    throw new Error("Failed to reload user after patch");
  }
  return scimUserToResource(refreshed, baseUrl);
}
async function scimDeleteUser(id) {
  const db = getDb();
  const dialect = db.getDialect();
  const p = (i) => db.getPlaceholder(i);
  const existing = await db.queryOne(
    `SELECT * FROM admin_users WHERE id = ${p(1)}`,
    [id]
  );
  if (!existing) {
    return false;
  }
  const isActiveExisting = existing.is_active === 1 || existing.is_active === true;
  if (isActiveExisting && await roleHasWildcardScope(existing.role)) {
    const activeClause = dialect === "postgres" ? "is_active = TRUE" : "is_active = 1";
    const activeAdmins = await db.query(
      `SELECT role FROM admin_users WHERE ${activeClause}`
    );
    let ownerCount = 0;
    for (const row of activeAdmins) {
      if (await roleHasWildcardScope(row.role)) {
        ownerCount += 1;
      }
    }
    if (ownerCount <= 1) {
      throw new Error("Cannot delete the last active wildcard admin role");
    }
  }
  await db.execute(
    `UPDATE admin_users
     SET is_active = ${p(1)},
         deactivated_at = CURRENT_TIMESTAMP,
         updated_at = CURRENT_TIMESTAMP
     WHERE id = ${p(2)}`,
    [dialect === "postgres" ? false : 0, id]
  );
  return true;
}
async function roleHasWildcardScope(role) {
  if (!role) {
    return false;
  }
  const scopes = await getAdminScopesForRole(role);
  return matchesScope(scopes, "*");
}
async function requireScimAuth(event, requiredScope) {
  var _a;
  const authHeader = getHeader(event, "authorization");
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
    throw createError({
      statusCode: 401,
      statusMessage: "Unauthorized",
      data: scimError(401, "Missing Bearer token")
    });
  }
  const token = authHeader.slice("Bearer ".length).trim();
  if (!isApiToken(token)) {
    throw createError({
      statusCode: 401,
      statusMessage: "Unauthorized",
      data: scimError(401, "Token is not a valid API token")
    });
  }
  const ip = (_a = event.node.req.socket) == null ? void 0 : _a.remoteAddress;
  const payload = await verifyApiToken(token, ip);
  if (!payload) {
    throw createError({
      statusCode: 401,
      statusMessage: "Unauthorized",
      data: scimError(401, "Invalid API token")
    });
  }
  if (!hasScope(payload.scopes, requiredScope)) {
    throw createError({
      statusCode: 403,
      statusMessage: "Forbidden",
      data: scimError(403, `Token is missing required scope: ${requiredScope}`)
    });
  }
  await requireFeature("enterprise.scim", event);
}

export { scimDeleteUser as a, scimGetUser as b, scimPatchUser as c, scimReplaceUser as d, scimListUsers as e, scimCreateUser as f, requireScimAuth as r, scimError as s };
//# sourceMappingURL=scim.mjs.map
