import { Unauthorized, AuthInvalidated } from "../../../zero-protocol/src/error-kind-enum.js";
import { ZeroCache } from "../../../zero-protocol/src/error-origin-enum.js";
import { isProtocolError, ProtocolError } from "../../../zero-protocol/src/error.js";
function isProvidedAuth(wireAuth) {
  return wireAuth !== void 0 && wireAuth !== "";
}
function authEquals(a, b) {
  if (a === b) {
    return true;
  }
  if (!a || !b) {
    return false;
  }
  return a.type === b.type && a.raw === b.raw;
}
class AuthSessionImpl {
  id;
  #lc;
  #validateLegacyJWT;
  #auth = void 0;
  #boundUserID;
  #revision = 0;
  constructor(lc, clientGroupID, validateLegacyJWT) {
    this.id = clientGroupID;
    this.#lc = lc;
    this.#validateLegacyJWT = validateLegacyJWT;
  }
  get auth() {
    return this.#auth;
  }
  get revision() {
    return this.#revision;
  }
  clear() {
    const lc = this.#lc.withContext(
      "boundUserID",
      this.#boundUserID ?? "unknown"
    );
    lc.debug?.(`Clearing auth session`);
    this.#auth = void 0;
    this.#boundUserID = void 0;
    this.#revision = 0;
  }
  async update(userID, wireAuth) {
    try {
      const lc = this.#lc.withContext("newUserID", userID);
      if (this.#boundUserID && this.#boundUserID !== userID) {
        return {
          ok: false,
          error: {
            kind: Unauthorized,
            message: "Client groups are pinned to a single user. Connection userID does not match existing client group userID.",
            origin: ZeroCache
          }
        };
      }
      const previousAuth = this.#auth;
      const hasProvidedAuth = isProvidedAuth(wireAuth);
      let nextAuth = previousAuth;
      if (previousAuth) {
        lc.debug?.(`Attempting to update auth from previous value`);
      } else {
        lc.debug?.(`Attempting to initialize auth`);
      }
      if (!hasProvidedAuth && previousAuth) {
        return {
          ok: false,
          error: {
            kind: Unauthorized,
            message: "No token provided. An unauthenticated client cannot connect to an authenticated client group.",
            origin: ZeroCache
          }
        };
      }
      if (!hasProvidedAuth) {
        nextAuth = void 0;
        lc.debug?.(`Cleared auth`);
      } else if (this.#validateLegacyJWT !== void 0) {
        const verifiedToken = await this.#validateLegacyJWT(wireAuth, { userID });
        nextAuth = pickToken(this.#lc, this.#auth, verifiedToken);
        lc.debug?.(`Updated auth with JWT`);
      } else {
        if (this.#auth?.type === "jwt") {
          throw new Error(
            "Cannot change auth type from legacy to opaque token"
          );
        }
        nextAuth = {
          type: "opaque",
          raw: wireAuth
        };
        lc.debug?.(`Updated auth with opaque token`);
      }
      this.#auth = nextAuth;
      this.#boundUserID ??= userID;
      if (!authEquals(previousAuth, nextAuth)) {
        this.#revision++;
      }
    } catch (e) {
      if (isProtocolError(e)) {
        return {
          ok: false,
          error: e.errorBody
        };
      }
      return {
        ok: false,
        error: {
          kind: AuthInvalidated,
          message: `Failed to decode auth token: ${String(e)}`,
          origin: ZeroCache
        }
      };
    }
    return { ok: true };
  }
}
function pickToken(lc, previousToken, newToken) {
  if (newToken === null) {
    return void 0;
  }
  if (previousToken?.type && newToken?.type && previousToken?.type !== newToken?.type) {
    throw new ProtocolError({
      kind: Unauthorized,
      message: "Token type cannot change. Client groups are pinned to a single token type.",
      origin: ZeroCache
    });
  }
  if (previousToken === void 0) {
    lc.debug?.(`No previous token, using new token`);
    return newToken;
  }
  if (newToken?.type === "opaque") {
    return newToken;
  }
  if (previousToken.type === "opaque") {
    throw new ProtocolError({
      kind: Unauthorized,
      message: "Token type cannot change from opaque to JWT. Client groups are pinned to a single token type.",
      origin: ZeroCache
    });
  }
  if (newToken) {
    if (previousToken.decoded.sub !== newToken.decoded.sub) {
      throw new ProtocolError({
        kind: Unauthorized,
        message: "The user id in the new token does not match the previous token. Client groups are pinned to a single user.",
        origin: ZeroCache
      });
    }
    if (previousToken.decoded.iat === void 0) {
      lc.debug?.(`No issued at time for the existing token, using new token`);
      return newToken;
    }
    if (newToken.decoded.iat === void 0) {
      throw new ProtocolError({
        kind: Unauthorized,
        message: "The new token does not have an issued at time but the prior token does. Tokens for a client group must either all have issued at times or all not have issued at times",
        origin: ZeroCache
      });
    }
    if (previousToken.decoded.iat < newToken.decoded.iat) {
      lc.debug?.(`New token is newer, using it`);
      return newToken;
    }
    lc.debug?.(`New token is older or the same, using existing token`);
    return previousToken;
  }
  throw new ProtocolError({
    kind: Unauthorized,
    message: "No token provided. An unauthenticated client cannot connect to an authenticated client group.",
    origin: ZeroCache
  });
}
export {
  AuthSessionImpl,
  pickToken
};
//# sourceMappingURL=auth.js.map
