// ../../adapters/local-jwt/src/user-store.ts
import { mkdir, readFile, rename, writeFile } from "fs/promises";
import { dirname } from "path";
var InMemoryUserStore = class {
  byId = /* @__PURE__ */ new Map();
  async isEmpty() {
    return this.byId.size === 0;
  }
  async getByUsername(username) {
    const target = username.toLowerCase();
    for (const user of this.byId.values()) {
      if (user.username.toLowerCase() === target) return user;
    }
    return null;
  }
  async getById(id) {
    return this.byId.get(id) ?? null;
  }
  async put(user) {
    this.byId.set(user.id, user);
  }
  async remove(id) {
    this.byId.delete(id);
  }
  async list() {
    return [...this.byId.values()].sort((a, b) => a.username.localeCompare(b.username));
  }
};
var FileUserStore = class {
  constructor(path) {
    this.path = path;
  }
  path;
  async load() {
    try {
      const raw = await readFile(this.path, "utf8");
      const text = raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
      const parsed = JSON.parse(text);
      return Array.isArray(parsed.users) ? parsed.users : [];
    } catch (error) {
      if (error.code === "ENOENT") return [];
      throw error;
    }
  }
  async save(users) {
    await mkdir(dirname(this.path), { recursive: true });
    const tmp = `${this.path}.tmp`;
    await writeFile(tmp, JSON.stringify({ users }, null, 2), "utf8");
    await rename(tmp, this.path);
  }
  async isEmpty() {
    const users = await this.load();
    return users.length === 0;
  }
  async getByUsername(username) {
    const target = username.toLowerCase();
    const users = await this.load();
    return users.find((u) => u.username.toLowerCase() === target) ?? null;
  }
  async getById(id) {
    const users = await this.load();
    return users.find((u) => u.id === id) ?? null;
  }
  async put(user) {
    const users = await this.load();
    const idx = users.findIndex((u) => u.id === user.id);
    if (idx >= 0) users[idx] = user;
    else users.push(user);
    await this.save(users);
  }
  async remove(id) {
    const users = await this.load();
    const next = users.filter((u) => u.id !== id);
    if (next.length !== users.length) await this.save(next);
  }
  async list() {
    const users = await this.load();
    return [...users].sort((a, b) => a.username.localeCompare(b.username));
  }
};

// ../../adapters/local-jwt/src/password.ts
import { randomBytes, scrypt, timingSafeEqual } from "crypto";
import { promisify } from "util";
var scryptAsync = promisify(scrypt);
var SALT_BYTES = 16;
var HASH_BYTES = 64;
var SCRYPT_OPTIONS = { N: 1 << 15, r: 8, p: 1, maxmem: 64 * 1024 * 1024 };
var VERSION = "scrypt$1";
var WeakPasswordError = class extends Error {
  constructor(reason) {
    super(reason);
    this.name = "WeakPasswordError";
  }
};
async function hashPassword(password) {
  if (password === "") {
    throw new WeakPasswordError("password is required");
  }
  const salt = randomBytes(SALT_BYTES);
  const derived = await scryptAsync(password, salt, HASH_BYTES, SCRYPT_OPTIONS);
  return `${VERSION}$${salt.toString("base64")}$${derived.toString("base64")}`;
}
async function verifyPassword(password, encoded) {
  const parts = encoded.split("$");
  if (parts.length !== 4 || `${parts[0]}$${parts[1]}` !== VERSION) return false;
  const salt = Buffer.from(parts[2], "base64");
  const expected = Buffer.from(parts[3], "base64");
  if (salt.length === 0 || expected.length === 0) return false;
  let derived;
  try {
    derived = await scryptAsync(password, salt, expected.length, SCRYPT_OPTIONS);
  } catch {
    return false;
  }
  if (derived.length !== expected.length) return false;
  return timingSafeEqual(derived, expected);
}

// ../../adapters/local-jwt/src/jwt.ts
import { createHmac, timingSafeEqual as timingSafeEqual2 } from "crypto";
var HEADER = { alg: "HS256", typ: "JWT" };
var HEADER_B64 = base64UrlEncode(JSON.stringify(HEADER));
function signJwt(payload, secret) {
  const body = base64UrlEncode(JSON.stringify(payload));
  const signingInput = `${HEADER_B64}.${body}`;
  const signature = base64UrlBuffer(hmac(secret, signingInput));
  return `${signingInput}.${signature}`;
}
function verifyJwt(token, secret, nowSeconds = Math.floor(Date.now() / 1e3)) {
  const parts = token.split(".");
  if (parts.length !== 3) return { ok: false, reason: "malformed" };
  const [header, body, signature] = parts;
  if (header !== HEADER_B64) return { ok: false, reason: "malformed" };
  const expected = hmac(secret, `${header}.${body}`);
  const got = base64UrlDecode(signature);
  if (got.length !== expected.length || !timingSafeEqual2(got, expected)) {
    return { ok: false, reason: "bad-signature" };
  }
  let payload;
  try {
    const parsed = JSON.parse(base64UrlDecode(body).toString("utf8"));
    if (!isJwtPayload(parsed)) return { ok: false, reason: "malformed" };
    payload = parsed;
  } catch {
    return { ok: false, reason: "malformed" };
  }
  if (payload.exp <= nowSeconds) return { ok: false, reason: "expired" };
  return { ok: true, payload };
}
function hmac(secret, data) {
  return createHmac("sha256", secret).update(data).digest();
}
function base64UrlEncode(s) {
  return Buffer.from(s, "utf8").toString("base64").replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
}
function base64UrlBuffer(b) {
  return b.toString("base64").replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
}
function base64UrlDecode(s) {
  const padded = s + "=".repeat((4 - s.length % 4) % 4);
  return Buffer.from(padded.replace(/-/g, "+").replace(/_/g, "/"), "base64");
}
function isJwtPayload(value) {
  if (typeof value !== "object" || value === null) return false;
  const v = value;
  return typeof v["sub"] === "string" && Array.isArray(v["roles"]) && v["roles"].every((r) => typeof r === "string") && typeof v["lc"] === "number" && typeof v["iat"] === "number" && typeof v["exp"] === "number";
}

// ../../adapters/local-jwt/src/local-jwt.ts
import { randomUUID } from "crypto";
var BEARER_PREFIX = /^Bearer\s+/i;
function createLocalJwtAuth(config) {
  const ttl = config.accessTokenTtlSeconds ?? 600;
  const patTtl = config.patTtlSeconds ?? 60 * 60 * 24 * 90;
  const refreshGrace = config.refreshGraceSeconds ?? 60 * 60 * 24 * 30;
  const maxFails = config.maxFailedAttempts ?? 3;
  const lockoutMs = (config.lockoutSeconds ?? 60) * 1e3;
  const now = config.now ?? Date.now;
  const newId = config.newId ?? randomUUID;
  const users = config.users;
  function extractBearer(getHeader) {
    const raw = getHeader("authorization") ?? getHeader("Authorization");
    if (raw === void 0) return void 0;
    const match = BEARER_PREFIX.exec(raw);
    return match ? raw.slice(match[0].length).trim() : void 0;
  }
  function toPrincipal(user) {
    return {
      userId: user.id,
      username: user.username,
      roles: user.roles
    };
  }
  function issueToken(user) {
    const iat = Math.floor(now() / 1e3);
    const payload = {
      sub: user.id,
      roles: user.roles,
      lc: user.loginCount,
      iat,
      exp: iat + ttl
    };
    return signJwt(payload, config.signingSecret);
  }
  function toSummary(user) {
    return {
      id: user.id,
      username: user.username,
      ...user.displayName !== void 0 && { displayName: user.displayName },
      roles: user.roles,
      createdAt: user.createdAt,
      updatedAt: user.updatedAt,
      locked: user.lockedUntil !== void 0 && user.lockedUntil > now()
    };
  }
  function normalizeDisplayName(raw) {
    if (raw === void 0) return void 0;
    const trimmed = raw.trim();
    return trimmed === "" ? void 0 : trimmed;
  }
  const VALID_ROLES = ["author", "developer", "admin"];
  function isValidRoles(roles) {
    return Array.isArray(roles) && roles.length > 0 && roles.every((role) => VALID_ROLES.includes(role));
  }
  function sameRoles(a, b) {
    if (a.length !== b.length) return false;
    const sorted = [...b].sort();
    return [...a].sort().every((role, index) => role === sorted[index]);
  }
  async function adminCount() {
    const all = await users.list();
    return all.filter((user) => user.roles.includes("admin")).length;
  }
  const identity = {
    async authenticate(getHeader) {
      const token = extractBearer(getHeader);
      if (token === void 0) return null;
      const result = verifyJwt(token, config.signingSecret, Math.floor(now() / 1e3));
      if (!result.ok) return null;
      const user = await users.getById(result.payload.sub);
      if (user === null) return null;
      if (result.payload.pat === true) {
        if ((user.patVersion ?? 0) !== (result.payload.pv ?? 0)) return null;
      } else if (user.loginCount !== result.payload.lc) {
        return null;
      }
      return toPrincipal(user);
    }
  };
  async function consumeToken(token, graceSeconds = 0) {
    const nowSeconds = Math.floor(now() / 1e3);
    const result = verifyJwt(token, config.signingSecret, nowSeconds - graceSeconds);
    if (!result.ok) return null;
    const user = await users.getById(result.payload.sub);
    if (user === null) return null;
    if (user.loginCount !== result.payload.lc) return null;
    return user;
  }
  const endpoints = {
    async login({ username, password }) {
      if (typeof username !== "string" || typeof password !== "string") {
        return { ok: false, status: 400, error: "username and password are required" };
      }
      if (username.trim() === "" || password === "") {
        return { ok: false, status: 400, error: "username and password are required" };
      }
      if (await users.isEmpty()) {
        const created = {
          id: newId(),
          username: username.trim(),
          roles: ["admin"],
          passwordHash: await hashPassword(password),
          loginCount: 1,
          failedAttempts: 0,
          createdAt: now(),
          updatedAt: now()
        };
        await users.put(created);
        return {
          ok: true,
          value: {
            token: issueToken(created),
            principal: toPrincipal(created),
            firstRunBootstrap: true
          }
        };
      }
      const user = await users.getByUsername(username);
      if (user === null) {
        return { ok: false, status: 401, error: "invalid credentials" };
      }
      if (user.lockedUntil !== void 0 && user.lockedUntil > now()) {
        return { ok: false, status: 423, error: "account temporarily locked" };
      }
      const passwordOk = await verifyPassword(password, user.passwordHash);
      if (!passwordOk) {
        const failedAttempts = user.failedAttempts + 1;
        const next2 = {
          ...user,
          failedAttempts,
          ...failedAttempts >= maxFails && { lockedUntil: now() + lockoutMs },
          updatedAt: now()
        };
        await users.put(next2);
        return { ok: false, status: 401, error: "invalid credentials" };
      }
      const next = {
        id: user.id,
        username: user.username,
        ...user.displayName !== void 0 && { displayName: user.displayName },
        roles: user.roles,
        passwordHash: user.passwordHash,
        loginCount: user.loginCount + 1,
        failedAttempts: 0,
        ...user.patVersion !== void 0 && { patVersion: user.patVersion },
        createdAt: user.createdAt,
        updatedAt: now()
      };
      await users.put(next);
      return {
        ok: true,
        value: {
          token: issueToken(next),
          principal: toPrincipal(next),
          firstRunBootstrap: false
        }
      };
    },
    async refresh(token) {
      const user = await consumeToken(token, refreshGrace);
      if (user === null) {
        return { ok: false, status: 401, error: "invalid or expired token" };
      }
      return { ok: true, value: { token: issueToken(user), principal: toPrincipal(user) } };
    },
    async logout(token) {
      const user = await consumeToken(token);
      if (user !== null) {
        const next = {
          ...user,
          loginCount: user.loginCount + 1,
          updatedAt: now()
        };
        await users.put(next);
      }
      return { ok: true, value: { ok: true } };
    },
    async state() {
      return { firstRun: await users.isEmpty() };
    },
    async changePassword(userId, { currentPassword, newPassword }) {
      const user = await users.getById(userId);
      if (user === null) {
        return { ok: false, status: 401, error: "invalid credentials" };
      }
      if (typeof currentPassword !== "string" || typeof newPassword !== "string") {
        return { ok: false, status: 400, error: "currentPassword and newPassword are required" };
      }
      if (newPassword === "") {
        return { ok: false, status: 400, error: "password is required" };
      }
      if (!await verifyPassword(currentPassword, user.passwordHash)) {
        return { ok: false, status: 401, error: "invalid credentials" };
      }
      const next = {
        id: user.id,
        username: user.username,
        ...user.displayName !== void 0 && { displayName: user.displayName },
        roles: user.roles,
        passwordHash: await hashPassword(newPassword),
        loginCount: user.loginCount + 1,
        failedAttempts: 0,
        ...user.patVersion !== void 0 && { patVersion: user.patVersion },
        createdAt: user.createdAt,
        updatedAt: now()
      };
      await users.put(next);
      return { ok: true, value: { token: issueToken(next) } };
    },
    async createToken(userId) {
      const user = await users.getById(userId);
      if (user === null) return { ok: false, status: 404, error: "no such user" };
      const iat = Math.floor(now() / 1e3);
      const exp = iat + patTtl;
      const payload = {
        sub: user.id,
        roles: user.roles,
        lc: user.loginCount,
        iat,
        exp,
        pat: true,
        pv: user.patVersion ?? 0
      };
      return {
        ok: true,
        value: { token: signJwt(payload, config.signingSecret), expiresAt: exp * 1e3 }
      };
    },
    async revokeTokens(userId) {
      const user = await users.getById(userId);
      if (user === null) return { ok: false, status: 404, error: "no such user" };
      const next = {
        id: user.id,
        username: user.username,
        roles: user.roles,
        passwordHash: user.passwordHash,
        loginCount: user.loginCount,
        failedAttempts: user.failedAttempts,
        ...user.lockedUntil !== void 0 && { lockedUntil: user.lockedUntil },
        patVersion: (user.patVersion ?? 0) + 1,
        createdAt: user.createdAt,
        updatedAt: now()
      };
      await users.put(next);
      return { ok: true, value: { ok: true } };
    }
  };
  const userAdmin = {
    async listUsers() {
      const all = await users.list();
      return { ok: true, value: all.map(toSummary) };
    },
    async createUser({
      username,
      password,
      roles,
      displayName
    }) {
      if (typeof username !== "string" || username.trim() === "") {
        return { ok: false, status: 400, error: "username is required" };
      }
      if (typeof password !== "string" || password === "") {
        return { ok: false, status: 400, error: "password is required" };
      }
      if (!isValidRoles(roles)) {
        return {
          ok: false,
          status: 400,
          error: "roles must be a non-empty list of author|developer|admin"
        };
      }
      const handle = username.trim();
      if (await users.getByUsername(handle) !== null) {
        return { ok: false, status: 409, error: "username already exists" };
      }
      const cleanName = normalizeDisplayName(displayName);
      const created = {
        id: newId(),
        username: handle,
        ...cleanName !== void 0 && { displayName: cleanName },
        roles: [...roles],
        passwordHash: await hashPassword(password),
        loginCount: 0,
        // no session issued at create; first login bumps to 1 and mints the token
        failedAttempts: 0,
        createdAt: now(),
        updatedAt: now()
      };
      await users.put(created);
      return { ok: true, value: toSummary(created) };
    },
    async updateUser(callerId, targetId, { roles, displayName }) {
      const user = await users.getById(targetId);
      if (user === null) return { ok: false, status: 404, error: "no such user" };
      const nextDisplayName = displayName === void 0 ? user.displayName : normalizeDisplayName(displayName);
      let nextRoles = user.roles;
      let rolesChanged = false;
      if (roles !== void 0) {
        if (!isValidRoles(roles)) {
          return {
            ok: false,
            status: 400,
            error: "roles must be a non-empty list of author|developer|admin"
          };
        }
        if (!sameRoles(user.roles, roles)) {
          rolesChanged = true;
          if (targetId === callerId) {
            return { ok: false, status: 403, error: "cannot change your own role" };
          }
          const demotingAdmin = user.roles.includes("admin") && !roles.includes("admin");
          if (demotingAdmin && await adminCount() <= 1) {
            return { ok: false, status: 409, error: "cannot demote the last admin" };
          }
          nextRoles = [...roles];
        }
      }
      const next = {
        id: user.id,
        username: user.username,
        ...nextDisplayName !== void 0 && { displayName: nextDisplayName },
        roles: nextRoles,
        passwordHash: user.passwordHash,
        loginCount: rolesChanged ? user.loginCount + 1 : user.loginCount,
        failedAttempts: user.failedAttempts,
        ...user.lockedUntil !== void 0 && { lockedUntil: user.lockedUntil },
        ...user.patVersion !== void 0 && { patVersion: user.patVersion },
        createdAt: user.createdAt,
        updatedAt: now()
      };
      await users.put(next);
      return { ok: true, value: toSummary(next) };
    },
    async resetPassword(targetId, { newPassword }) {
      const user = await users.getById(targetId);
      if (user === null) return { ok: false, status: 404, error: "no such user" };
      if (typeof newPassword !== "string" || newPassword === "") {
        return { ok: false, status: 400, error: "password is required" };
      }
      const next = {
        id: user.id,
        username: user.username,
        ...user.displayName !== void 0 && { displayName: user.displayName },
        roles: user.roles,
        passwordHash: await hashPassword(newPassword),
        loginCount: user.loginCount + 1,
        failedAttempts: 0,
        ...user.patVersion !== void 0 && { patVersion: user.patVersion },
        createdAt: user.createdAt,
        updatedAt: now()
      };
      await users.put(next);
      return { ok: true, value: { ok: true } };
    },
    async deleteUser(callerId, targetId) {
      const user = await users.getById(targetId);
      if (user === null) return { ok: false, status: 404, error: "no such user" };
      if (targetId === callerId) {
        return { ok: false, status: 403, error: "cannot delete your own account" };
      }
      if (user.roles.includes("admin") && await adminCount() <= 1) {
        return { ok: false, status: 409, error: "cannot delete the last admin" };
      }
      await users.remove(targetId);
      return { ok: true, value: { ok: true } };
    }
  };
  return { identity, endpoints, userAdmin };
}
export {
  FileUserStore,
  InMemoryUserStore,
  WeakPasswordError,
  createLocalJwtAuth,
  hashPassword,
  signJwt,
  verifyJwt,
  verifyPassword
};
