From 151a57e33ab2375adb6209183a581a15d3d7896b Mon Sep 17 00:00:00 2001 From: Quentin AUBERT Date: Thu, 1 Aug 2024 17:04:03 +0200 Subject: [PATCH 001/123] wip #722 - fix ok but 1 test KO --- daikoku/app/controllers/ApiController.scala | 2 + daikoku/app/jobs/OtoroshiVerifierJob.scala | 996 ++++++----- daikoku/app/utils/ApiService.scala | 99 +- daikoku/build.sbt | 2 +- daikoku/test/daikoku/ApiControllerSpec.scala | 366 +++- daikoku/test/daikoku/otoroshi.json | 1581 +++++++++++++++--- daikoku/test/daikoku/suites.scala | 15 + 7 files changed, 2318 insertions(+), 743 deletions(-) diff --git a/daikoku/app/controllers/ApiController.scala b/daikoku/app/controllers/ApiController.scala index d9fc50863..01d7587f1 100644 --- a/daikoku/app/controllers/ApiController.scala +++ b/daikoku/app/controllers/ApiController.scala @@ -5095,9 +5095,11 @@ class ApiController( _ <- EitherT.liftF( env.dataStore.usagePlanRepo.forTenant(ctx.tenant).save(updatedPlan) ) + log1 = logger.warn("######### APICONTROLLER ####### SYNC BEGIN") _ <- EitherT.liftF( otoroshiSynchronisator.verify(Json.obj("api" -> api.id.value)) ) + log = logger.warn("######### APICONTROLLER ####### SYNC ENDED ??????") _ <- runDemandUpdate(oldPlan, updatedPlan, api) //FIXME: attention, peut etre il y en a qui sont blocked de base _ <- EitherT.liftF( diff --git a/daikoku/app/jobs/OtoroshiVerifierJob.scala b/daikoku/app/jobs/OtoroshiVerifierJob.scala index e0d8b08b6..7af0c2eed 100644 --- a/daikoku/app/jobs/OtoroshiVerifierJob.scala +++ b/daikoku/app/jobs/OtoroshiVerifierJob.scala @@ -7,15 +7,14 @@ import cats.data.EitherT import cats.syntax.option._ import controllers.AppError import fr.maif.otoroshi.daikoku.audit.{ApiKeyRotationEvent, JobEvent} -import fr.maif.otoroshi.daikoku.domain.NotificationAction.{ - OtoroshiSyncApiError, - OtoroshiSyncSubscriptionError -} +import fr.maif.otoroshi.daikoku.domain.NotificationAction.{OtoroshiSyncApiError, OtoroshiSyncSubscriptionError} import fr.maif.otoroshi.daikoku.domain._ import fr.maif.otoroshi.daikoku.domain.json.ApiSubscriptionyRotationFormat import fr.maif.otoroshi.daikoku.env.Env import fr.maif.otoroshi.daikoku.logger.AppLogger import fr.maif.otoroshi.daikoku.utils._ +import fr.maif.otoroshi.daikoku.utils.future.EnhancedObject +import org.apache.pekko.http.scaladsl.util.FastFuture import org.joda.time.DateTime import play.api.Logger import play.api.i18n.MessagesApi @@ -70,6 +69,7 @@ class OtoroshiVerifierJob( ) { private val logger = Logger("OtoroshiVerifierJob") + private val synclogger = Logger("APIkey Synchronizer") private val ref = new AtomicReference[Cancellable]() @@ -92,6 +92,47 @@ class OtoroshiVerifierJob( // lastTeams = Map.empty ) + def getListFromMeta( + key: String, + metadata: Map[String, String] + ): Set[String] = { + metadata + .get(key) + .map(_.split('|').toSeq.map(_.trim).toSet) + .getOrElse(Set.empty) + } + + def mergeMetaValue( + key: String, + meta1: Map[String, String], + meta2: Map[String, String] + ): String = { + val list1 = getListFromMeta(key, meta1) + val list2 = getListFromMeta(key, meta2) + (list1 ++ list2).mkString(" | ") + } + + case class SyncInformation( + parent: ApiSubscription, + childs: Seq[ApiSubscription], + team: Team, + parentApi: Api, + apk: ActualOtoroshiApiKey, + otoroshiSettings: OtoroshiSettings, + tenant: Tenant, + tenantAdminTeam: Team + ) + + case class ComputedInformation( + parent: ApiSubscription, + childs: Seq[ApiSubscription], + apk: ActualOtoroshiApiKey, + computedApk: ActualOtoroshiApiKey, + otoroshiSettings: OtoroshiSettings, + tenant: Tenant, + tenantAdminTeam: Team + ) + def start(): Unit = { if ( !env.config.otoroshiSyncByCron && env.config.otoroshiSyncMaster && ref @@ -195,6 +236,343 @@ class OtoroshiVerifierJob( } } + private def computeAPIKey( + infos: SyncInformation + ): Future[ComputedInformation] = { + synclogger.info(s"begin apk computing for ${infos.parent.id}/${infos.parent.apiKey.clientName} with ${infos.childs.size} child.s") + (infos.childs :+ infos.parent) + .map(subscription => { + for { + api <- EitherT.fromOptionF( + env.dataStore.apiRepo + .forAllTenant() + .findOneNotDeleted( + Json.obj( + "_id" -> subscription.api.value, + "state" -> ApiState.publishedJsonFilter + ) + ), + sendErrorNotification( + NotificationAction.OtoroshiSyncSubscriptionError( + subscription, + "API does not exist anymore" + ), + infos.tenantAdminTeam.id, + infos.tenant.id + ) + ) + plan <- EitherT.fromOptionF[Future, Unit, UsagePlan]( + env.dataStore.usagePlanRepo + .forTenant(infos.tenant) + .findById(subscription.plan), + sendErrorNotification( + NotificationAction.OtoroshiSyncSubscriptionError( + subscription, + "Usage plan does not exist anymore" + ), + api.team, + infos.tenant.id + ) + ) + user <- + EitherT + .fromOptionF( + env.dataStore.userRepo.findById(subscription.by), + () + ) + } yield { + val ctx: Map[String, String] = Map( + "user.id" -> user.id.value, + "user.name" -> user.name, + "user.email" -> user.email, + "api.id" -> infos.parent.api.value, + "api.name" -> infos.parentApi.name, + "team.id" -> infos.parent.team.value, + "team.name" -> infos.team.name, + "tenant.id" -> infos.tenant.id.value, + "tenant.name" -> infos.tenant.name, + "client.id" -> infos.apk.clientId, + "client.name" -> infos.apk.clientName + ) ++ infos.team.metadata + .map(t => ("team.metadata." + t._1, t._2)) ++ + user.metadata.map(t => ("user.metadata." + t._1, t._2)) + + // ******************** + // process new tags + // ******************** + val planTags = plan.otoroshiTarget + .flatMap(_.apikeyCustomization.tags.asOpt[Set[String]]) + .getOrElse(Set.empty[String]) + + val tagsFromDk = + getListFromMeta("daikoku__tags", infos.apk.metadata) + val newTagsFromDk = + planTags.map(OtoroshiTarget.processValue(_, ctx)) + + //todo: unnecessary ?? + //val newTags: Set[String] = apk.tags.diff(tagsFromDk) ++ newTagsFromDk + + // ******************** + // process new metadata + // ******************** + val planMeta = plan.otoroshiTarget + .map(_.apikeyCustomization.metadata.as[Map[String, String]]) + .getOrElse(Map.empty[String, String]) + + val metaFromDk = infos.apk.metadata + .get("daikoku__metadata") + .map( + _.split('|').toSeq + .map(_.trim) + .map(key => key -> infos.apk.metadata.get(key).orNull) + ) + .getOrElse(planMeta.map { + case (a, b) => + a -> OtoroshiTarget.processValue(b, ctx) + }) + .toMap + + val customMetaFromSub = subscription.customMetadata + .flatMap(_.asOpt[Map[String, String]]) + .getOrElse(Map.empty[String, String]) + + val newMetaFromDk = (planMeta ++ customMetaFromSub).map { + case (a, b) => a -> OtoroshiTarget.processValue(b, ctx) + } + val newMeta = infos.apk.metadata + .removedAll(metaFromDk.keys) ++ newMetaFromDk ++ Map( + "daikoku__metadata" -> newMetaFromDk.keys + .mkString(" | "), + "daikoku__tags" -> newTagsFromDk.mkString(" | ") + ) + + // ******************** + // process new metadata + // ******************** + + infos.apk.copy( + tags = newTagsFromDk, + metadata = newMeta, + constrainedServicesOnly = plan.otoroshiTarget + .exists(_.apikeyCustomization.constrainedServicesOnly), + allowClientIdOnly = + plan.otoroshiTarget.exists(_.apikeyCustomization.clientIdOnly), + restrictions = plan.otoroshiTarget + .map(_.apikeyCustomization.restrictions) + .getOrElse(ApiKeyRestrictions()), + throttlingQuota = subscription.customMaxPerSecond + .orElse(plan.maxRequestPerSecond) + .getOrElse(infos.apk.throttlingQuota), + dailyQuota = subscription.customMaxPerDay + .orElse(plan.maxRequestPerDay) + .getOrElse(infos.apk.dailyQuota), + monthlyQuota = subscription.customMaxPerMonth + .orElse(plan.maxRequestPerMonth) + .getOrElse(infos.apk.monthlyQuota), + authorizedEntities = plan.otoroshiTarget + .flatMap(_.authorizedEntities) + .getOrElse(AuthorizedEntities()), + readOnly = subscription.customReadOnly + .orElse( + plan.otoroshiTarget + .map(_.apikeyCustomization.readOnly) + ) + .getOrElse(infos.apk.readOnly), + rotation = infos.apk.rotation + .map(r => + r.copy(enabled = + r.enabled || subscription.rotation + .exists(_.enabled) || plan.autoRotation + .exists(e => e) + ) + ) + .orElse( + subscription.rotation.map(r => + ApiKeyRotation( + enabled = + r.enabled || plan.autoRotation.exists(e => e), + rotationEvery = r.rotationEvery, + gracePeriod = r.gracePeriod + ) + ) + ) + .orElse( + plan.autoRotation + .map(enabled => ApiKeyRotation(enabled = enabled)) + ) + ) + + } + }) + .foldLeft(infos.apk.copy( + authorizedEntities = AuthorizedEntities(), + tags = Set.empty, + metadata = Map.empty, + restrictions = ApiKeyRestrictions(), + ).future)((_apikey1, either) => { + either.value.flatMap { + case Left(_) => _apikey1 + case Right(apikey2) => + _apikey1.map(apikey1 => + apikey1.copy( + tags = apikey1.tags ++ apikey2.tags, + metadata = apikey1.metadata ++ + apikey2.metadata ++ + Map( + "daikoku__metadata" -> mergeMetaValue( + "daikoku__metadata", + apikey1.metadata, + apikey2.metadata + ), + "daikoku__tags" -> mergeMetaValue( + "daikoku__tags", + apikey1.metadata, + apikey2.metadata + ) + ), + restrictions = ApiKeyRestrictions( + enabled = + apikey1.restrictions.enabled && apikey2.restrictions.enabled, + allowLast = + apikey1.restrictions.allowLast || apikey2.restrictions.allowLast, + allowed = + apikey1.restrictions.allowed ++ apikey2.restrictions.allowed, + forbidden = + apikey1.restrictions.forbidden ++ apikey2.restrictions.forbidden, + notFound = + apikey1.restrictions.notFound ++ apikey2.restrictions.notFound + ), + authorizedEntities = AuthorizedEntities( + groups = + apikey1.authorizedEntities.groups | apikey2.authorizedEntities.groups, + services = + apikey1.authorizedEntities.services | apikey2.authorizedEntities.services, + routes = + apikey1.authorizedEntities.routes | apikey2.authorizedEntities.routes + ) + ) + ) + } + }) + .map(computedApk => { + synclogger.info(s"apk computing ended for ${computedApk.clientName}") + ComputedInformation( + parent = infos.parent, + childs = infos.childs, + apk = infos.apk, + computedApk = computedApk, + otoroshiSettings = infos.otoroshiSettings, + tenant = infos.tenant, + tenantAdminTeam = infos.tenantAdminTeam + )} + ) + + } + + private def updateOtoroshiApk(infos: ComputedInformation): Future[Unit] = { + if (infos.apk != infos.computedApk) { + synclogger.info(s"update otoroshi APIkey ${infos.computedApk.clientName}") + client + .updateApiKey(infos.computedApk)(infos.otoroshiSettings) + .map { + case Right(_) if infos.apk.asOtoroshiApiKey != infos.parent.apiKey => + synclogger.info( + s"Successfully updated api key: ${infos.apk.clientId} - ${infos.apk.clientName} on ${infos.otoroshiSettings.host}" + ) + env.dataStore.apiSubscriptionRepo + .forTenant(infos.tenant) + .updateManyByQuery( + Json.obj( + "_id" -> Json.obj( + "$in" -> JsArray( + (infos.childs :+ infos.parent) + .map(_.id.asJson) + ) + ) + ), + Json.obj( + "$set" -> Json.obj( + "apiKey" -> infos.computedApk.asOtoroshiApiKey.asJson, + "tags" -> Some(infos.computedApk.tags), + "metadata" -> (infos.computedApk.metadata.filterNot(i => + i._1.startsWith("daikoku_") + ) -- infos.parent.customMetadata //FIXME: je penses que ca n'enleve que les customMetadata de la parent + .flatMap(_.asOpt[Map[String, String]]) + .getOrElse(Map.empty[String, String]) + .keys).view.mapValues(i => JsString(i)).toSeq + ) + ) + ) + .flatMap(_ => + env.dataStore.notificationRepo + .forTenant(infos.tenant) + .save( + Notification( + id = NotificationId(IdGenerator.token(32)), + tenant = infos.parent.tenant, + team = Some(infos.parent.team), + sender = jobUser.asNotificationSender, + date = DateTime.now(), + notificationType = NotificationType.AcceptOnly, + status = NotificationStatus.Pending(), + action = NotificationAction.ApiKeyRefresh( + infos.parent.id.value, + infos.parent.api.value, + infos.parent.plan.value + ) + ) + ) + ) + .map(_ => + JobEvent("subscription desync from otoroshi to daikoku") + .logJobEvent( + infos.tenant, + jobUser, + Json.obj( + "subscription" -> infos.parent.id.asJson + ) + ) + ) + case Right(_) => + synclogger.info( + s"Successfully updated api key metadata: ${infos.apk.clientId} - ${infos.apk.clientName} on ${infos.otoroshiSettings.host}" + ) + env.dataStore.apiSubscriptionRepo + .forTenant(infos.tenant) + .updateManyByQuery( + Json.obj( + "_id" -> Json.obj( + "$in" -> JsArray( + (infos.childs :+ infos.parent) + .map(_.id.asJson) + ) + ) + ), + Json.obj( + "$set" -> Json.obj( + "tags" -> Some(infos.computedApk.tags), + "metadata" -> (infos.computedApk.metadata.filterNot(i => + i._1.startsWith("daikoku_") + ) -- infos.parent.customMetadata + .flatMap(_.asOpt[Map[String, String]]) + .getOrElse(Map.empty[String, String]) + .keys).view.mapValues(i => JsString(i)).toSeq + ) + ) + ) + case Left(e) => + synclogger.error( + s"Error while updating api key metadata: ${infos.apk.clientId} - ${infos.apk.clientName} on ${infos.otoroshiSettings.host}" + ) + synclogger.error(e.getErrorMessage()) + } + } else { + FastFuture.successful(synclogger.info( + s"No need to update api key: ${infos.apk.clientName} on ${infos.otoroshiSettings.host}" + )) + } + } + private def verifyIfOtoroshiGroupsStillExists( query: JsObject = Json.obj() ): Future[Done] = { @@ -264,6 +642,7 @@ class OtoroshiVerifierJob( .map(_ => ()) } + logger.info("Verifying if otoroshi groups still exists") val par = 10 env.dataStore.apiRepo .forAllTenant() @@ -326,47 +705,30 @@ class OtoroshiVerifierJob( * @param query to find some subscriptions and sync its * @return just future of Unit */ + private def synchronizeApikeys( - query: JsObject = Json.obj("parent" -> JsNull) - ): Future[Unit] = { + query: JsObject = Json.obj("parent" -> JsNull) + ): Future[Done] = { import cats.implicits._ - def getListFromMeta( - key: String, - metadata: Map[String, String] - ): Set[String] = { - metadata - .get(key) - .map(_.split('|').toSeq.map(_.trim).toSet) - .getOrElse(Set.empty) - } - - def mergeMetaValue( - key: String, - meta1: Map[String, String], - meta2: Map[String, String] - ): String = { - val list1 = getListFromMeta(key, meta1) - val list2 = getListFromMeta(key, meta2) - (list1 ++ list2).mkString(" | ") - } + synclogger.info(s"begin APIkeys synchronisation -- query: ${Json.stringify(query)}") - for { - allSubscriptions <- + val r = for { + //Get all "base" subscriptions from provided query + allSubscriptions <- EitherT.liftF[Future, AppError, Seq[ApiSubscription]]( env.dataStore.apiSubscriptionRepo .forAllTenant() .findNotDeleted(query) - //Get just parent sub (childs will be processed after) - //FIXME: Rewrite the following 2 request in 1 with a proper sql request - adminApis <- + ) + //Get admin API + adminApi <- EitherT.fromOptionF[Future, AppError, Api]( env.dataStore.apiRepo .forAllTenant() - .find( - Json.obj( - "visibility" -> ApiVisibility.AdminOnly.name - ) - ) - subscriptions <- + .findOne(Json.obj("visibility" -> ApiVisibility.AdminOnly.name)), + AppError.EntityNotFound("Admin API") + ) + //Get all Parent subscriptions based on allSubscription filtering all adminAPI subs + subscriptions <- EitherT.liftF[Future, AppError, Seq[ApiSubscription]]( env.dataStore.apiSubscriptionRepo .forAllTenant() .findNotDeleted( @@ -384,436 +746,165 @@ class OtoroshiVerifierJob( ) ) .map( - _.filterNot(sub => - adminApis.flatMap(_.possibleUsagePlans).contains(sub.plan) - ) - ) - } yield { - subscriptions.map(subscription => { - for { - aggregatedSubscriptions <- EitherT.right[Unit]( - env.dataStore.apiSubscriptionRepo - .forAllTenant() - .findNotDeleted(Json.obj("parent" -> subscription.id.asJson)) - ) - tenant <- EitherT.fromOptionF( - env.dataStore.tenantRepo.findByIdNotDeleted(subscription.tenant), - sendErrorNotification( - NotificationAction.OtoroshiSyncSubscriptionError( - subscription, - "Tenant does not exist anymore" - ), - subscription.team, //todo: to super admins ??? - subscription.tenant - ) - ) - tenantAdminTeam <- EitherT.fromOptionF( - env.dataStore.teamRepo - .forTenant(tenant) - .findOne(Json.obj("type" -> "Admin")), - () - ) - parentApi <- EitherT.fromOptionF( - env.dataStore.apiRepo - .forAllTenant() - .findOneNotDeleted( - Json.obj( - "_id" -> subscription.api.value, - "state" -> ApiState.publishedJsonFilter - ) - ), - sendErrorNotification( - NotificationAction.OtoroshiSyncSubscriptionError( - subscription, - "API does not exist anymore" - ), - tenantAdminTeam.id, - subscription.tenant - ) + _.filterNot(sub => adminApi.possibleUsagePlans.contains(sub.plan)) ) - plan <- EitherT.fromOptionF[Future, Unit, UsagePlan]( - env.dataStore.usagePlanRepo - .forTenant(tenant) - .findById(subscription.plan), - sendErrorNotification( - NotificationAction.OtoroshiSyncSubscriptionError( - subscription, - "Usage plan does not exist anymore" - ), - parentApi.team, - parentApi.tenant - ) - ) - otoroshiTarget <- EitherT.fromOption[Future]( - plan.otoroshiTarget, - sendErrorNotification( - NotificationAction.OtoroshiSyncSubscriptionError( - subscription, - "No Otoroshi target specified" - ), - parentApi.team, - parentApi.tenant - ) - ) - otoroshiSettings <- EitherT.fromOption[Future]( - tenant.otoroshiSettings - .find(_.id == otoroshiTarget.otoroshiSettings), - Seq(parentApi.team, tenantAdminTeam.id) - .map(team => - sendErrorNotification( - NotificationAction.OtoroshiSyncSubscriptionError( - subscription, - "Otoroshi settings does not exist anymore" - ), - team, - parentApi.tenant - ) - ) - .reduce((_, _) => ()) - ) - //todo: if otoroshi apikey doesn't exist => rebuild it !!! - apk <- EitherT( - client.getApikey(subscription.apiKey.clientId)(otoroshiSettings) - ).leftMap(e => - sendErrorNotification( - NotificationAction.OtoroshiSyncSubscriptionError( - subscription, - s"Unable to fetch apikey from otoroshi: ${Json - .stringify(AppError.toJson(e))}" - ), - parentApi.team, - parentApi.tenant, - Some(otoroshiSettings.host) - ) - ) - team <- EitherT.fromOptionF( - env.dataStore.teamRepo - .forTenant(tenant) - .findById(subscription.team), - () - ) - newApk <- EitherT( - Future - .sequence( - (aggregatedSubscriptions :+ subscription) - .map(sub => { - for { - api <- EitherT.fromOptionF( - env.dataStore.apiRepo - .forAllTenant() - .findOneNotDeleted( - Json.obj( - "_id" -> sub.api.value, - "state" -> ApiState.publishedJsonFilter - ) - ), - sendErrorNotification( - NotificationAction.OtoroshiSyncSubscriptionError( - sub, - "API does not exist anymore" - ), - tenantAdminTeam.id, - tenant.id - ) - ) - plan <- EitherT.fromOptionF[Future, Unit, UsagePlan]( - env.dataStore.usagePlanRepo - .forTenant(tenant) - .findById(sub.plan), - sendErrorNotification( - NotificationAction.OtoroshiSyncSubscriptionError( - sub, - "Usage plan does not exist anymore" - ), - api.team, - tenant.id - ) - ) - user <- - EitherT - .fromOptionF( - env.dataStore.userRepo.findById(sub.by), - () - ) - } yield { - val ctx: Map[String, String] = Map( - "user.id" -> user.id.value, - "user.name" -> user.name, - "user.email" -> user.email, - "api.id" -> parentApi.id.value, - "api.name" -> parentApi.name, - "team.id" -> team.id.value, - "team.name" -> team.name, - "tenant.id" -> tenant.id.value, - "tenant.name" -> tenant.name, - "client.id" -> apk.clientId, - "client.name" -> apk.clientName - ) ++ team.metadata - .map(t => ("team.metadata." + t._1, t._2)) ++ - user.metadata.map(t => ("user.metadata." + t._1, t._2)) + ) - // ******************** - // process new tags - // ******************** - val planTags = plan.otoroshiTarget - .flatMap(_.apikeyCustomization.tags.asOpt[Set[String]]) - .getOrElse(Set.empty[String]) + } yield subscriptions - val tagsFromDk = - getListFromMeta("daikoku__tags", apk.metadata) - val newTagsFromDk = - planTags.map(OtoroshiTarget.processValue(_, ctx)) + val _allParentSubscriptions = r.leftMap(_ => Seq.empty[ApiSubscription]).merge - //todo: unnecessary ?? - //val newTags: Set[String] = apk.tags.diff(tagsFromDk) ++ newTagsFromDk + Source + .futureSource(_allParentSubscriptions.map(Source(_))) + .mapAsync(1)(subscription => { - // ******************** - // process new metadata - // ******************** - val planMeta = otoroshiTarget.apikeyCustomization.metadata - .asOpt[Map[String, String]] - .getOrElse(Map.empty[String, String]) + synclogger.info(s"sync parent sub ${subscription.apiKey.clientName}") + val either = + for { + childs <- EitherT.liftF( + env.dataStore.apiSubscriptionRepo + .forAllTenant() + .findNotDeleted(Json.obj("parent" -> subscription.id.asJson)) + ) - val metaFromDk = apk.metadata - .get("daikoku__metadata") - .map( - _.split('|').toSeq - .map(_.trim) - .map(key => key -> apk.metadata.get(key).orNull) - ) - .getOrElse(planMeta.map { - case (a, b) => - a -> OtoroshiTarget.processValue(b, ctx) - }) - .toMap + //get tenant + tenant <- EitherT.fromOptionF( + env.dataStore.tenantRepo.findByIdNotDeleted(subscription.tenant), + sendErrorNotification( + NotificationAction.OtoroshiSyncSubscriptionError( + subscription, + "Tenant does not exist anymore" + ), + subscription.team, //todo: to super admins ??? + subscription.tenant + ) + ) + //get tenant team admin + tenantAdminTeam <- EitherT.fromOptionF( + env.dataStore.teamRepo + .forTenant(tenant) + .findOne(Json.obj("type" -> "Admin")), + () //todo: send mail or log error + ) - val customMetaFromSub = sub.customMetadata - .flatMap(_.asOpt[Map[String, String]]) - .getOrElse(Map.empty[String, String]) + //GET parent API + parentApi <- EitherT.fromOptionF( + env.dataStore.apiRepo + .forAllTenant() + .findOneNotDeleted( + Json.obj( + "_id" -> subscription.api.value, + "state" -> ApiState.publishedJsonFilter + ) + ), + sendErrorNotification( + NotificationAction.OtoroshiSyncSubscriptionError( + subscription, + "API does not exist anymore" + ), + tenantAdminTeam.id, + subscription.tenant + ) + ) - val newMetaFromDk = (planMeta ++ customMetaFromSub).map { - case (a, b) => a -> OtoroshiTarget.processValue(b, ctx) - } - val newMeta = apk.metadata - .removedAll(metaFromDk.keys) ++ newMetaFromDk ++ Map( - "daikoku__metadata" -> newMetaFromDk.keys - .mkString(" | "), - "daikoku__tags" -> newTagsFromDk.mkString(" | ") - ) + //Get parent plan + plan <- EitherT.fromOptionF[Future, Unit, UsagePlan]( + env.dataStore.usagePlanRepo + .forTenant(tenant) + .findById(subscription.plan), + sendErrorNotification( + NotificationAction.OtoroshiSyncSubscriptionError( + subscription, + "Usage plan does not exist anymore" + ), + parentApi.team, + parentApi.tenant + ) + ) - // ******************** - // process new metadata - // ******************** + //get ototoshi target from parent plan + otoroshiTarget <- EitherT.fromOption[Future]( + plan.otoroshiTarget, + sendErrorNotification( + NotificationAction.OtoroshiSyncSubscriptionError( + subscription, + "No Otoroshi target specified" + ), + parentApi.team, + parentApi.tenant + ) + ) - apk.copy( - tags = newTagsFromDk, - metadata = newMeta, - constrainedServicesOnly = - otoroshiTarget.apikeyCustomization.constrainedServicesOnly, - allowClientIdOnly = - otoroshiTarget.apikeyCustomization.clientIdOnly, - restrictions = - otoroshiTarget.apikeyCustomization.restrictions, - throttlingQuota = subscription.customMaxPerSecond - .orElse(plan.maxRequestPerSecond) - .getOrElse(apk.throttlingQuota), - dailyQuota = subscription.customMaxPerDay - .orElse(plan.maxRequestPerDay) - .getOrElse(apk.dailyQuota), - monthlyQuota = subscription.customMaxPerMonth - .orElse(plan.maxRequestPerMonth) - .getOrElse(apk.monthlyQuota), - authorizedEntities = otoroshiTarget.authorizedEntities - .getOrElse(AuthorizedEntities()), - readOnly = subscription.customReadOnly - .orElse( - plan.otoroshiTarget - .map(_.apikeyCustomization.readOnly) - ) - .getOrElse(apk.readOnly), - rotation = apk.rotation - .map(r => - r.copy(enabled = - r.enabled || subscription.rotation - .exists(_.enabled) || plan.autoRotation - .exists(e => e) - ) - ) - .orElse( - subscription.rotation.map(r => - ApiKeyRotation( - enabled = - r.enabled || plan.autoRotation.exists(e => e), - rotationEvery = r.rotationEvery, - gracePeriod = r.gracePeriod - ) - ) - ) - .orElse( - plan.autoRotation - .map(enabled => ApiKeyRotation(enabled = enabled)) - ) - ) + //get otoroshi settings from parent plan + otoroshiSettings <- EitherT.fromOption[Future]( + tenant.otoroshiSettings + .find(_.id == otoroshiTarget.otoroshiSettings), + Seq(parentApi.team, tenantAdminTeam.id) + .map(team => + sendErrorNotification( + NotificationAction.OtoroshiSyncSubscriptionError( + subscription, + "Otoroshi settings does not exist anymore" + ), + team, + parentApi.tenant + ) + ) + .reduce((_, _) => ()) + ) - } - }) - .map(_.value) - ) - .map(x => - x.reduce((either1, either2) => { - (either1, either2) match { - case (Right(apikey1), Right(apikey2)) => - Right( - apikey1.copy( - tags = apikey1.tags ++ apikey2.tags, - metadata = apikey1.metadata ++ - apikey2.metadata ++ - Map( - "daikoku__metadata" -> mergeMetaValue( - "daikoku__metadata", - apikey1.metadata, - apikey2.metadata - ), - "daikoku__tags" -> mergeMetaValue( - "daikoku__tags", - apikey1.metadata, - apikey2.metadata - ) - ), - restrictions = ApiKeyRestrictions( - enabled = - apikey1.restrictions.enabled && apikey2.restrictions.enabled, - allowLast = - apikey1.restrictions.allowLast || apikey2.restrictions.allowLast, - allowed = - apikey1.restrictions.allowed ++ apikey2.restrictions.allowed, - forbidden = - apikey1.restrictions.forbidden ++ apikey2.restrictions.forbidden, - notFound = - apikey1.restrictions.notFound ++ apikey2.restrictions.notFound - ), - authorizedEntities = AuthorizedEntities( - groups = - apikey1.authorizedEntities.groups | apikey2.authorizedEntities.groups, - services = - apikey1.authorizedEntities.services | apikey2.authorizedEntities.services, - routes = - apikey1.authorizedEntities.routes | apikey2.authorizedEntities.routes - ) - ) - ) - case (Left(_), Right(apikey)) => Right(apikey) - case (Right(apikey), Left(_)) => Right(apikey) - case (Left(_), Left(_)) => Left(()) - } - }) + // get previous apikey from otoroshi + apk <- EitherT( + client.getApikey(subscription.apiKey.clientId)(otoroshiSettings) + ).leftMap(e => + sendErrorNotification( + NotificationAction.OtoroshiSyncSubscriptionError( + subscription, + s"Unable to fetch apikey from otoroshi: ${ + Json + .stringify(AppError.toJson(e)) + }" + ), + parentApi.team, + parentApi.tenant, + Some(otoroshiSettings.host) ) - ) - } yield { - if (newApk != apk) { - client - .updateApiKey(newApk)(otoroshiSettings) - .andThen { - case Success(_) - if apk.asOtoroshiApiKey != subscription.apiKey => - logger.info( - s"Successfully updated api key: ${apk.clientId} - ${apk.clientName} on ${otoroshiSettings.host}" - ) - env.dataStore.apiSubscriptionRepo - .forTenant(subscription.tenant) - .updateManyByQuery( - Json.obj( - "_id" -> Json.obj( - "$in" -> JsArray( - (aggregatedSubscriptions :+ subscription) - .map(_.id.asJson) - ) - ) - ), - Json.obj( - "$set" -> Json.obj( - "apiKey" -> newApk.asOtoroshiApiKey.asJson, - "tags" -> Some(newApk.tags), - "metadata" -> (newApk.metadata.filterNot(i => - i._1.startsWith("daikoku_") - ) -- subscription.customMetadata - .flatMap(_.asOpt[Map[String, String]]) - .getOrElse(Map.empty[String, String]) - .keys).view.mapValues(i => JsString(i)).toSeq - ) - ) - ) - .flatMap(_ => - env.dataStore.notificationRepo - .forTenant(subscription.tenant) - .save( - Notification( - id = NotificationId(IdGenerator.token(32)), - tenant = subscription.tenant, - team = Some(subscription.team), - sender = jobUser.asNotificationSender, - date = DateTime.now(), - notificationType = NotificationType.AcceptOnly, - status = NotificationStatus.Pending(), - action = NotificationAction.ApiKeyRefresh( - subscription.id.value, - subscription.api.value, - subscription.plan.value - ) - ) - ) - ) - .map(_ => - JobEvent("subscription desync from otoroshi to daikoku") - .logJobEvent( - tenant, - jobUser, - Json.obj( - "subscription" -> subscription.id.asJson - ) - ) - ) - case Success(_) => - logger.info( - s"Successfully updated api key metadata: ${apk.clientId} - ${apk.clientName} on ${otoroshiSettings.host}" - ) - env.dataStore.apiSubscriptionRepo - .forTenant(subscription.tenant) - .updateManyByQuery( - Json.obj( - "_id" -> Json.obj( - "$in" -> JsArray( - (aggregatedSubscriptions :+ subscription) - .map(_.id.asJson) - ) - ) - ), - Json.obj( - "$set" -> Json.obj( - "tags" -> Some(newApk.tags), - "metadata" -> (newApk.metadata.filterNot(i => - i._1.startsWith("daikoku_") - ) -- subscription.customMetadata - .flatMap(_.asOpt[Map[String, String]]) - .getOrElse(Map.empty[String, String]) - .keys).view.mapValues(i => JsString(i)).toSeq - ) - ) - ) - case Failure(e) => - logger.error( - s"Error while updating api key metadata: ${apk.clientId} - ${apk.clientName} on ${otoroshiSettings.host}", - e - ) - } - } else { - logger.info( - s"No need to update api key: ${apk.clientName} on ${otoroshiSettings.host}" + ) + + // get subscription team + team <- EitherT.fromOptionF( + env.dataStore.teamRepo + .forTenant(tenant) + .findById(subscription.team), + () + ) + } yield { + synclogger.info(s"get sync information ended - ${apk.clientName}") + SyncInformation( + parent = subscription, + childs = childs, + apk = apk, + otoroshiSettings = otoroshiSettings, + tenant = tenant, + team = team, + parentApi = parentApi, + tenantAdminTeam = tenantAdminTeam ) } - } + + either.value }) - } + .mapAsync(1) { + case Left(_) => FastFuture.successful(None) //do nothing + case Right(informations) => + computeAPIKey(informations).map(_.some) //Future[Option[apk]] + } + .mapAsync(1) { + case Some(computedInfos) => updateOtoroshiApk(computedInfos) + case None => FastFuture.successful(()) + } + .runWith(Sink.ignore)(env.defaultMaterializer) + } def checkRotation(query: JsObject = Json.obj("parent" -> JsNull)) = { @@ -1062,6 +1153,7 @@ class OtoroshiVerifierJob( def verify(query: JsObject = Json.obj()): Future[Unit] = { logger.info("Verifying sync between daikoku and otoroshi") + Future .sequence( Seq( @@ -1070,6 +1162,6 @@ class OtoroshiVerifierJob( synchronizeApikeys(query) ) ) - .map(_ => ()) + .map(_ => logger.info("Sync verification ended")) } } diff --git a/daikoku/app/utils/ApiService.scala b/daikoku/app/utils/ApiService.scala index 4342a4222..3c3e83519 100644 --- a/daikoku/app/utils/ApiService.scala +++ b/daikoku/app/utils/ApiService.scala @@ -425,57 +425,66 @@ class ApiService( } def updateSubscription( - tenant: Tenant, - subscription: ApiSubscription, - plan: UsagePlan - ): Future[Either[AppError, JsObject]] = { + tenant: Tenant, + subscription: ApiSubscription, + plan: UsagePlan + ): Future[Either[AppError, JsObject]] = { import cats.implicits._ - plan.otoroshiTarget.map(_.otoroshiSettings).flatMap { id => + val maybeTarget = plan.otoroshiTarget.map(_.otoroshiSettings).flatMap { id => tenant.otoroshiSettings.find(_.id == id) - } match { - case None => Future.successful(Left(OtoroshiSettingsNotFound)) - case Some(otoSettings) => - implicit val otoroshiSettings: OtoroshiSettings = otoSettings - plan.otoroshiTarget.flatMap(_.authorizedEntities) match { - case None => Future.successful(Left(ApiNotLinked)) - case Some(authorizedEntities) if authorizedEntities.isEmpty => - Future.successful(Left(ApiNotLinked)) - case Some(authorizedEntities) => - val r: EitherT[Future, AppError, JsObject] = for { - apiKey <- EitherT( - otoroshiClient.getApikey(subscription.apiKey.clientId) - ) - _ <- EitherT.liftF( - otoroshiClient.updateApiKey( - apiKey.copy( - authorizedEntities = authorizedEntities, - throttlingQuota = subscription.customMaxPerSecond - .getOrElse(apiKey.throttlingQuota), - dailyQuota = - subscription.customMaxPerDay.getOrElse(apiKey.dailyQuota), - monthlyQuota = subscription.customMaxPerMonth - .getOrElse(apiKey.monthlyQuota), - metadata = apiKey.metadata ++ subscription.customMetadata - .flatMap(_.asOpt[Map[String, String]]) - .getOrElse(Map.empty[String, String]), - readOnly = - subscription.customReadOnly.getOrElse(apiKey.readOnly) - ) - ) - ) - _ <- EitherT.liftF( - env.dataStore.apiSubscriptionRepo - .forTenant(tenant.id) - .save(subscription) - ) - } yield subscription.asSafeJson.as[JsObject] - - r.value - } } + + val r = for { + otoSettings <- EitherT.fromOption[Future][AppError, OtoroshiSettings](maybeTarget, AppError.OtoroshiSettingsNotFound) + authorizedEntities <- EitherT.fromOption[Future][AppError, AuthorizedEntities](plan.otoroshiTarget.flatMap(_.authorizedEntities), AppError.ApiNotLinked) + _ <- EitherT.cond[Future][AppError, Unit](!authorizedEntities.isEmpty, (), AppError.ApiNotLinked) + apiKey <- EitherT[Future, AppError, ActualOtoroshiApiKey](otoroshiClient.getApikey(subscription.apiKey.clientId)(otoSettings)) + + aggregatedSubs <- EitherT.liftF[Future, AppError, Seq[ApiSubscription]](env.dataStore.apiSubscriptionRepo.forTenant(tenant) + .findNotDeleted(Json.obj("parent" -> subscription.id.asJson))) + aggregatedPlan <- EitherT.liftF[Future, AppError, Seq[UsagePlan]](env.dataStore.usagePlanRepo.forTenant(tenant) + .findNotDeleted(Json.obj("_id" -> Json.obj("$in" -> JsArray(aggregatedSubs.map(_.plan.asJson)))))) + aggregatedAuthorizedEntities <- EitherT.pure[Future, AppError](aggregatedPlan + .map(_.otoroshiTarget.flatMap(_.authorizedEntities).getOrElse(AuthorizedEntities()))) + + _authorizedEntities = aggregatedAuthorizedEntities.fold(authorizedEntities)((acc, curr) => { + AuthorizedEntities( + services = acc.services ++ curr.services, + groups = acc.groups ++ curr.groups, + routes = acc.routes ++ curr.routes + ) + }) + + _ <- EitherT[Future, AppError, ActualOtoroshiApiKey](otoroshiClient.updateApiKey( + apiKey.copy( + authorizedEntities = _authorizedEntities, + throttlingQuota = subscription.customMaxPerSecond + .getOrElse(apiKey.throttlingQuota), + dailyQuota = + subscription.customMaxPerDay.getOrElse(apiKey.dailyQuota), + monthlyQuota = subscription.customMaxPerMonth + .getOrElse(apiKey.monthlyQuota), + metadata = apiKey.metadata ++ subscription.customMetadata + .flatMap(_.asOpt[Map[String, String]]) + .getOrElse(Map.empty[String, String]), + readOnly = + subscription.customReadOnly.getOrElse(apiKey.readOnly) + ) + )(otoSettings) + ) + _ <- EitherT.liftF[Future, AppError, Boolean]( + env.dataStore.apiSubscriptionRepo + .forTenant(tenant.id) + .save(subscription) + ) + } yield subscription.asSafeJson.as[JsObject] + + r.value } + + def deleteApiKey( tenant: Tenant, subscription: ApiSubscription, diff --git a/daikoku/build.sbt b/daikoku/build.sbt index 235091819..0d1f5b2de 100644 --- a/daikoku/build.sbt +++ b/daikoku/build.sbt @@ -59,7 +59,7 @@ libraryDependencies ++= Seq( "com.themillhousegroup" %% "scoup" % "1.0.0" % Test, "org.wiremock" % "wiremock" % wiremockVersion % Test, // "org.wiremock" % "wiremock-jre8" % wiremockVersion % Test, -// "org.testcontainers" % "testcontainers" % "1.17.5" % Test, + "org.testcontainers" % "testcontainers" % "1.20.0" % Test, "com.dimafeng" %% "testcontainers-scala-scalatest" % "0.40.14" % Test, "com.dimafeng" %% "testcontainers-scala-postgresql" % "0.40.14" % Test, "org.apache.commons" % "commons-lang3" % "3.13.0", diff --git a/daikoku/test/daikoku/ApiControllerSpec.scala b/daikoku/test/daikoku/ApiControllerSpec.scala index a49d64492..6fb861267 100644 --- a/daikoku/test/daikoku/ApiControllerSpec.scala +++ b/daikoku/test/daikoku/ApiControllerSpec.scala @@ -1,56 +1,59 @@ package fr.maif.otoroshi.daikoku.tests import cats.implicits.catsSyntaxOptionId +import com.dimafeng.testcontainers.GenericContainer.FileSystemBind +import com.dimafeng.testcontainers.{ForAllTestContainer, GenericContainer} import com.github.tomakehurst.wiremock.WireMockServer import com.github.tomakehurst.wiremock.client.WireMock import com.github.tomakehurst.wiremock.client.WireMock._ import com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig import controllers.AppError import controllers.AppError.SubscriptionAggregationDisabled -import fr.maif.otoroshi.daikoku.domain.NotificationAction.{ - ApiAccess, - ApiSubscriptionDemand -} +import fr.maif.otoroshi.daikoku.domain.NotificationAction.{ApiAccess, ApiSubscriptionDemand} import fr.maif.otoroshi.daikoku.domain.NotificationType.AcceptOrReject import fr.maif.otoroshi.daikoku.domain.TeamPermission.Administrator import fr.maif.otoroshi.daikoku.domain.UsagePlan._ import fr.maif.otoroshi.daikoku.domain.UsagePlanVisibility.{Private, Public} import fr.maif.otoroshi.daikoku.domain._ -import fr.maif.otoroshi.daikoku.domain.json.{ - ApiFormat, - SeqApiSubscriptionFormat -} +import fr.maif.otoroshi.daikoku.domain.json.{ApiFormat, SeqApiSubscriptionFormat} import fr.maif.otoroshi.daikoku.logger.AppLogger import fr.maif.otoroshi.daikoku.tests.utils.DaikokuSpecHelper import fr.maif.otoroshi.daikoku.utils.IdGenerator import org.joda.time.DateTime import org.scalatest.concurrent.IntegrationPatience +import org.scalatest.time.Span.convertSpanToDuration +import org.scalatest.time.SpanSugar.convertIntToGrainOfTime import org.scalatest.{BeforeAndAfter, BeforeAndAfterEach} import org.scalatestplus.play.PlaySpec +import org.testcontainers.containers.BindMode import play.api.http.Status import play.api.libs.json._ +import scala.concurrent.duration.FiniteDuration +import scala.concurrent.{Await, Future} import scala.util.Random class ApiControllerSpec() extends PlaySpec with DaikokuSpecHelper with IntegrationPatience - with BeforeAndAfterEach - with BeforeAndAfter { -// with ForAllTestContainer { + with BeforeAndAfterEach + with BeforeAndAfter + with ForAllTestContainer { val pwd = System.getProperty("user.dir"); - lazy val wireMockServer = new WireMockServer(wireMockConfig().port(stubPort)) - /*override val container = GenericContainer( - "maif/otoroshi:dev", + lazy val wireMockServer = new WireMockServer(wireMockConfig() + .port(stubPort)) + + override val container = GenericContainer( + "maif/otoroshi", exposedPorts = Seq(8080), fileSystemBind = Seq( FileSystemBind(s"$pwd/test/daikoku/otoroshi.json", "/home/user/otoroshi.json", BindMode.READ_ONLY)), env = Map("APP_IMPORT_FROM" -> "/home/user/otoroshi.json") - )*/ + ) before { wireMockServer.start() @@ -73,7 +76,8 @@ class ApiControllerSpec() // )))), // users = Seq(userAdmin), // teams = Seq(teamConsumer), -// apis = Seq(defaultApi) +// apis = Seq(defaultApi.api), +// usagePlans = defaultApi.plans // ) // // //plans @@ -93,6 +97,7 @@ class ApiControllerSpec() // Json.obj("plan" -> id.toString, "teams" -> Json.arr(teamConsumer.id.asJson)) // ) // )(tenant, session) +// AppLogger.info(Json.prettyPrint(resp.json)) // resp.status mustBe 200 // // @@ -4646,7 +4651,6 @@ class ApiControllerSpec() resp.status mustBe 409 } - "not be enabled on plan when aggregation on tenant is disabled" in { setupEnvBlocking( tenants = Seq(tenant), @@ -5138,5 +5142,333 @@ class ApiControllerSpec() String ] mustBe "The subscribed plan has another otoroshi of the parent plan" } + "update aggregated APIkey do not erase authorizedEntities" in { + val parentPlan = FreeWithoutQuotas( + id = UsagePlanId("parent.dev"), + tenant = tenant.id, + billingDuration = BillingDuration(1, BillingTimeUnit.Month), + currency = Currency("EUR"), + customName = None, + customDescription = None, + otoroshiTarget = Some( + OtoroshiTarget( + containerizedOtoroshi, + Some( + AuthorizedEntities( + routes = Set(OtoroshiRouteId(parentRouteId)) + ) + ) + ) + ), + allowMultipleKeys = Some(false), + subscriptionProcess = Seq.empty, + integrationProcess = IntegrationProcess.ApiKey, + autoRotation = Some(false), + aggregationApiKeysSecurity = Some(true) + ) + val childPlan = FreeWithoutQuotas( + id = UsagePlanId("child.dev"), + tenant = tenant.id, + billingDuration = BillingDuration(1, BillingTimeUnit.Month), + currency = Currency("EUR"), + customName = None, + customDescription = None, + otoroshiTarget = Some( + OtoroshiTarget( + containerizedOtoroshi, + Some( + AuthorizedEntities( + routes = Set(OtoroshiRouteId(childRouteId)) + ) + ) + ) + ), + allowMultipleKeys = Some(false), + subscriptionProcess = Seq.empty, + integrationProcess = IntegrationProcess.ApiKey, + autoRotation = Some(false), + aggregationApiKeysSecurity = Some(true) + ) + + + val parentApi = defaultApi.api.copy( + id = ApiId("parent-id"), + name = "parent API", + team = teamOwnerId, + possibleUsagePlans = Seq(UsagePlanId("parent.dev")), + defaultUsagePlan = UsagePlanId("parent.dev").some + ) + val childApi = defaultApi.api.copy( + id = ApiId("child-id"), + name = "child API", + team = teamOwnerId, + possibleUsagePlans = Seq(UsagePlanId("child.dev")), + defaultUsagePlan = UsagePlanId("child.dev").some, + ) + + val parentSub = ApiSubscription( + id = ApiSubscriptionId("parent_sub"), + tenant = tenant.id, + apiKey = parentApiKey, + plan = parentPlan.id, + createdAt = DateTime.now(), + team = teamConsumerId, + api = parentApi.id, + by = userTeamAdminId, + customName = None, + rotation = None, + integrationToken = "parent_token" + ) + val childSub = ApiSubscription( + id = ApiSubscriptionId("child_sub"), + tenant = tenant.id, + apiKey = parentApiKey, + plan = childPlan.id, + createdAt = DateTime.now(), + team = teamConsumerId, + api = childApi.id, + by = userTeamAdminId, + customName = None, + rotation = None, + integrationToken = "child_token", + parent = Some(parentSub.id) + ) + + + setupEnvBlocking( + tenants = Seq(tenant.copy( + aggregationApiKeysSecurity = Some(true), + otoroshiSettings = Set( + OtoroshiSettings( + id = containerizedOtoroshi, + url = s"http://otoroshi.oto.tools:${container.mappedPort(8080)}", + host = "otoroshi-api.oto.tools", + clientSecret = otoroshiAdminApiKey.clientSecret, + clientId = otoroshiAdminApiKey.clientId + ), + ))), + users = Seq(userAdmin), + teams = Seq(teamOwner, teamConsumer), + usagePlans = Seq(parentPlan, childPlan), + apis = Seq(parentApi, childApi), + subscriptions = Seq(parentSub, childSub)) + + +// AppLogger.info(container.logs) + val session = loginWithBlocking(userAdmin, tenant) + val resp = httpJsonCallBlocking( + path = + s"/api/teams/${teamOwnerId.value}/subscriptions/${parentSub.id.value}", + method = "PUT", + body = parentSub.copy( + customMetadata = Json.obj("foo"-> "bar").some + ).asJson.some + )(tenant, session) + + resp.status mustBe 200 + + // test otoroshi key + val respVerif = httpJsonCallBlocking( + path = s"/api/apikeys/${parentSub.apiKey.clientId}", + baseUrl = "http://otoroshi-api.oto.tools", + headers = Map( + "Otoroshi-Client-Id" -> otoroshiAdminApiKey.clientId, + "Otoroshi-Client-Secret" -> otoroshiAdminApiKey.clientSecret, + "Host" -> "otoroshi-api.oto.tools" + ), + port = container.mappedPort(8080) + )(tenant, session) + + val authorizations = (respVerif.json \ "authorizations").as[JsArray] + val strings = authorizations.value.map(value => (value \ "id").as[String]) + strings.size mustBe 2 + strings.contains(childRouteId) mustBe true + strings.contains(parentRouteId) mustBe true + } + "update plan in aggregated APIkey do not erase authorizedEntities" in { + val parentPlan = FreeWithoutQuotas( + id = UsagePlanId("parent.dev"), + tenant = tenant.id, + billingDuration = BillingDuration(1, BillingTimeUnit.Month), + currency = Currency("EUR"), + customName = None, + customDescription = None, + otoroshiTarget = Some( + OtoroshiTarget( + containerizedOtoroshi, + Some( + AuthorizedEntities( + routes = Set(OtoroshiRouteId(parentRouteId)) + ) + ) + ) + ), + allowMultipleKeys = Some(false), + subscriptionProcess = Seq.empty, + integrationProcess = IntegrationProcess.ApiKey, + autoRotation = Some(false), + aggregationApiKeysSecurity = Some(true) + ) + val childPlan = FreeWithoutQuotas( + id = UsagePlanId("child.dev"), + tenant = tenant.id, + billingDuration = BillingDuration(1, BillingTimeUnit.Month), + currency = Currency("EUR"), + customName = None, + customDescription = None, + otoroshiTarget = Some( + OtoroshiTarget( + containerizedOtoroshi, + Some( + AuthorizedEntities( + routes = Set(OtoroshiRouteId(childRouteId)) + ) + ) + ) + ), + allowMultipleKeys = Some(false), + subscriptionProcess = Seq.empty, + integrationProcess = IntegrationProcess.ApiKey, + autoRotation = Some(false), + aggregationApiKeysSecurity = Some(true) + ) + + + val parentApi = defaultApi.api.copy( + id = ApiId("parent-id"), + name = "parent API", + team = teamOwnerId, + possibleUsagePlans = Seq(UsagePlanId("parent.dev")), + defaultUsagePlan = UsagePlanId("parent.dev").some + ) + val childApi = defaultApi.api.copy( + id = ApiId("child-id"), + name = "child API", + team = teamOwnerId, + possibleUsagePlans = Seq(UsagePlanId("child.dev")), + defaultUsagePlan = UsagePlanId("child.dev").some, + ) + + val parentSub = ApiSubscription( + id = ApiSubscriptionId("parent_sub"), + tenant = tenant.id, + apiKey = parentApiKey, + plan = parentPlan.id, + createdAt = DateTime.now(), + team = teamConsumerId, + api = parentApi.id, + by = userTeamAdminId, + customName = None, + rotation = None, + integrationToken = "parent_token" + ) + val childSub = ApiSubscription( + id = ApiSubscriptionId("child_sub"), + tenant = tenant.id, + apiKey = parentApiKey, + plan = childPlan.id, + createdAt = DateTime.now(), + team = teamConsumerId, + api = childApi.id, + by = userTeamAdminId, + customName = None, + rotation = None, + integrationToken = "child_token", + parent = Some(parentSub.id) + ) + + + setupEnvBlocking( + tenants = Seq(tenant.copy( + aggregationApiKeysSecurity = Some(true), + otoroshiSettings = Set( + OtoroshiSettings( + id = containerizedOtoroshi, + url = s"http://otoroshi.oto.tools:${container.mappedPort(8080)}", + host = "otoroshi-api.oto.tools", + clientSecret = otoroshiAdminApiKey.clientSecret, + clientId = otoroshiAdminApiKey.clientId + ), + ))), + users = Seq(userAdmin), + teams = Seq(teamOwner, teamConsumer, defaultAdminTeam), + usagePlans = Seq(parentPlan, childPlan), + apis = Seq(parentApi, childApi), + subscriptions = Seq(parentSub, childSub)) + + +// AppLogger.info(container.logs) + val session = loginWithBlocking(userAdmin, tenant) + + + val resp = httpJsonCallBlocking( + path = + s"/api/teams/${teamOwnerId.value}/apis/${parentApi.id.value}/${parentApi.currentVersion.value}/plan/${parentPlan.id.value}", + method = "PUT", + body = parentPlan.copy( + otoroshiTarget = OtoroshiTarget( + containerizedOtoroshi, + Some( + AuthorizedEntities( + routes = Set(OtoroshiRouteId(otherRouteId)) + ) + ) + ).some + ).asJson.some + )(tenant, session) + + resp.status mustBe 200 + + val respVerif = httpJsonCallBlocking( + path = s"/api/apikeys/${parentSub.apiKey.clientId}", + baseUrl = "http://otoroshi-api.oto.tools", + headers = Map( + "Otoroshi-Client-Id" -> otoroshiAdminApiKey.clientId, + "Otoroshi-Client-Secret" -> otoroshiAdminApiKey.clientSecret, + "Host" -> "otoroshi-api.oto.tools" + ), + port = container.mappedPort(8080) + )(tenant, session) + + AppLogger.info(Json.prettyPrint(respVerif.json)) + val authorizations = (respVerif.json \ "authorizations").as[JsArray] + val strings = authorizations.value.map(value => (value \ "id").as[String]) + strings.size mustBe 2 + strings.contains(otherRouteId) mustBe true + strings.contains(childRouteId) mustBe true + + + httpJsonCallBlocking( + path = + s"/api/teams/${teamOwnerId.value}/apis/${childApi.id.value}/${childApi.currentVersion.value}/plan/${childPlan.id.value}", + method = "PUT", + body = childPlan.copy( + otoroshiTarget = OtoroshiTarget( + containerizedOtoroshi, + Some( + AuthorizedEntities( + routes = Set(OtoroshiRouteId(parentRouteId), OtoroshiRouteId(childRouteId)) + ) + ) + ).some + ).asJson.some + )(tenant, session) + + val respVerif2 = httpJsonCallBlocking( + path = s"/api/apikeys/${parentSub.apiKey.clientId}", + baseUrl = "http://otoroshi-api.oto.tools", + headers = Map( + "Otoroshi-Client-Id" -> otoroshiAdminApiKey.clientId, + "Otoroshi-Client-Secret" -> otoroshiAdminApiKey.clientSecret, + "Host" -> "otoroshi-api.oto.tools" + ), + port = container.mappedPort(8080) + )(tenant, session) + + AppLogger.info(Json.prettyPrint(respVerif2.json)) + val authorizations2 = (respVerif2.json \ "authorizations").as[JsArray] + val strings2 = authorizations2.value.map(value => (value \ "id").as[String]) + strings2.size mustBe 3 + } } } diff --git a/daikoku/test/daikoku/otoroshi.json b/daikoku/test/daikoku/otoroshi.json index 056cc066c..12a67bdb7 100644 --- a/daikoku/test/daikoku/otoroshi.json +++ b/daikoku/test/daikoku/otoroshi.json @@ -1,11 +1,11 @@ { "label": "Otoroshi export", - "dateRaw": 1669037982223, - "date": "2022-11-21 01:39:42", + "dateRaw": 1722331630114, + "date": "2024-07-30 11:27:10", "stats": { - "calls": 257, - "dataIn": 23154, - "dataOut": 209856 + "calls": 164, + "dataIn": 19447, + "dataOut": 2000425 }, "config": { "tags": [], @@ -20,6 +20,8 @@ "lines": [ "prod" ], + "anonymousReporting": false, + "initWithNewEngine": true, "maintenanceMode": false, "enableEmbeddedMetrics": true, "streamEntityOnly": true, @@ -62,7 +64,11 @@ "trustAll": false }, "securityProtocol": "PLAINTEXT", - "saslConfig": null + "saslConfig": { + "username": "foo", + "password": "bar", + "mechanism": "PLAIN" + } }, "backOfficeAuthRef": null, "mailerSettings": { @@ -71,8 +77,8 @@ "cleverSettings": null, "maxWebhookSize": 100, "middleFingers": false, - "maxLogsSize": 1000, - "otoroshiId": "c9923093a-4987-48cc-bc11-c64a168db016", + "maxLogsSize": 0, + "otoroshiId": "181795b00-9748-4007-92a0-c74b2938bce8", "snowMonkeyConfig": { "enabled": false, "outageStrategy": "OneServicePerGroup", @@ -138,7 +144,8 @@ "randomIfNotFound": false, "includeJdkCaServer": true, "includeJdkCaClient": true, - "trustedCAsServer": [] + "trustedCAsServer": [], + "bannedAlpnProtocols": {} }, "quotasSettings": { "enabled": false, @@ -157,7 +164,7 @@ "debug": false, "debug_headers": false, "domains": [ - "*-next-gen.oto.tools" + "*" ], "routing_strategy": "tree" }, @@ -170,7 +177,26 @@ }, "excluded": [] }, + "wasmoSettings": { + "settings": { + "url": "http://localhost:5001", + "clientId": "admin-api-apikey-id", + "clientSecret": "admin-api-apikey-secret", + "legacyAuth": false, + "pluginsFilter": "*", + "tlsConfig": null + }, + "tlsConfig": { + "certs": [], + "trustedCerts": [], + "mtls": false, + "loose": false, + "trustAll": false + } + }, "metadata": {}, + "env": {}, + "extensions": {}, "templates": { "route": {}, "service": {}, @@ -200,32 +226,9 @@ ] }, "username": "admin@otoroshi.io", - "password": "$2a$10$FuFhEPQcHbs1HIdfW46QR.9SPgoB2V4sXwXn99BP1lKNDdVrLUAH6", + "password": "$2a$10$qRrexFJEHj0FYqu9ccIDf.btT.Cf3ri8W5BGRWizvtqhrifUY75y.", "label": "Otoroshi Admin", - "createdAt": 1669036666915, - "type": "SIMPLE", - "metadata": {}, - "tags": [], - "rights": [ - { - "tenant": "*:rw", - "teams": [ - "*:rw" - ] - } - ] - }, - { - "_loc": { - "tenant": "default", - "teams": [ - "*" - ] - }, - "username": "test@otoroshi.io", - "password": "$2a$10$pV5kY6ZNX1rj5lW2z3NVMeUkovuLSRnanIgg/dQWsGqqqaTrqe7dS", - "label": "tester", - "createdAt": 1669037036665, + "createdAt": 1715604023631, "type": "SIMPLE", "metadata": {}, "tags": [], @@ -236,7 +239,8 @@ "*:rw" ] } - ] + ], + "adminEntityValidators": {} } ], "serviceGroups": [ @@ -247,9 +251,9 @@ "default" ] }, - "id": "admin-api-group", - "name": "Otoroshi Admin Api group", - "description": "No description", + "id": "default", + "name": "default-group", + "description": "The default service group", "tags": [], "metadata": {} }, @@ -260,9 +264,9 @@ "default" ] }, - "id": "12345", - "name": "test_group", - "description": "Group named test_group", + "id": "admin-api-group", + "name": "Otoroshi Admin Api group", + "description": "No description", "tags": [], "metadata": {} }, @@ -273,9 +277,9 @@ "default" ] }, - "id": "default", - "name": "default-group", - "description": "The default service group", + "id": "group_dev_574c57dd-ab79-48a1-a810-22ba214b25f5", + "name": "aggregated-group", + "description": "group for aggregagtion", "tags": [], "metadata": {} } @@ -289,13 +293,19 @@ ] }, "clientId": "admin-api-apikey-id", - "clientSecret": "password", + "clientSecret": "admin-api-apikey-secret", "clientName": "Otoroshi Backoffice ApiKey", "description": "The apikey use by the Otoroshi UI", "authorizedGroup": "admin-api-group", "authorizedEntities": [ "group_admin-api-group" ], + "authorizations": [ + { + "kind": "group", + "id": "admin-api-group" + } + ], "enabled": true, "readOnly": false, "allowClientIdOnly": false, @@ -327,13 +337,24 @@ "default" ] }, - "clientId": "QCIjTr9YDEL2nBAH", - "clientSecret": "mzf3cibdCqA6KIKcLkmAXECE7whs1uH3wiLIa6y8ZyAyElIGIIWGvfag2IUyWZ6z", - "clientName": "default-apikey", - "description": "the default apikey", - "authorizedGroup": "default", + "clientId": "5w24yl2ly3dlnn92", + "clientSecret": "8iwm9fhbns0rmybnyul5evq9l1o4dxza0rh7rt4flay69jolw3okbz1owfl6w2db", + "clientName": "daikoku_test_parent_key", + "description": "", + "authorizedGroup": null, "authorizedEntities": [ - "group_default" + "route_route_d74ea8b27-b8be-4177-82d9-c50722416c50", + "route_route_8ce030cbd-6c07-43d4-9c61-4a330ae0975d" + ], + "authorizations": [ + { + "kind": "route", + "id": "route_d74ea8b27-b8be-4177-82d9-c50722416c50" + }, + { + "kind": "route", + "id": "route_8ce030cbd-6c07-43d4-9c61-4a330ae0975d" + } ], "enabled": true, "readOnly": false, @@ -368,22 +389,22 @@ "default" ] }, - "id": "service_12345", - "groupId": "12345", + "id": "ZSTWnemHcCDi4yA6h7qJz0wCP9glqeWnCMG9KQrDi17hcapQ5mpN15cWr68y4vs8", + "groupId": "3mXM0GtZBsNQEEBOfBZw6KteRXfdEmqorGOXckAgWZ2WDZpRiIAr4DMgoxIb2BQ3", "groups": [ - "12345" + "3mXM0GtZBsNQEEBOfBZw6KteRXfdEmqorGOXckAgWZ2WDZpRiIAr4DMgoxIb2BQ3" ], - "name": "test", - "description": "a test service", + "name": "[MIGRATED TO ROUTE, PENDING DELETION] my-service", + "description": "", "env": "prod", - "domain": "oto.tools", - "subdomain": "test", + "domain": "opunmaif.fr", + "subdomain": "orc", "targetsLoadBalancing": { "type": "RoundRobin" }, "targets": [ { - "host": "mirror.otoroshi.io", + "host": "app-4c897a2e-b753-43e7-9755-e41ddd0637b3.opunmaif.io", "scheme": "https", "weight": 1, "mtlsConfig": { @@ -402,28 +423,28 @@ "ipAddress": null } ], - "root": "/", - "matchingRoot": null, + "root": "/api", + "matchingRoot": "/api", "stripPath": true, "localHost": "localhost:8080", "localScheme": "http", "redirectToLocal": false, - "enabled": true, + "enabled": false, "userFacing": false, "privateApp": false, "forceHttps": false, "logAnalyticsOnServer": false, - "useAkkaHttpClient": false, + "useAkkaHttpClient": true, "useNewWSClient": false, "tcpUdpTunneling": false, "detectApiKeySooner": false, "maintenanceMode": false, "buildMode": false, "strictlyPrivate": false, - "enforceSecureCommunication": false, + "enforceSecureCommunication": true, "sendInfoToken": true, "sendStateChallenge": true, - "sendOtoroshiHeadersBack": false, + "sendOtoroshiHeadersBack": true, "readOnly": false, "xForwardedHeaders": false, "overrideHost": true, @@ -434,15 +455,16 @@ "stateRequestName": null, "stateResponseName": null }, - "secComTtl": 30000, - "secComVersion": 1, + "secComTtl": 120000, + "secComVersion": "V1", "secComInfoTokenVersion": "Legacy", - "secComExcludedPatterns": [], + "secComExcludedPatterns": [ + "/ruxitagentjs_.*", + "/rb_.*" + ], "securityExcludedPatterns": [], "publicPatterns": [], - "privatePatterns": [ - "/.*" - ], + "privatePatterns": [], "additionalHeaders": {}, "additionalHeadersOut": {}, "missingOnlyHeadersIn": {}, @@ -460,7 +482,10 @@ }, "healthCheck": { "enabled": false, - "url": "/" + "url": "/", + "timeout": 5000, + "healthyStatuses": [], + "unhealthyStatuses": [] }, "clientConfig": { "useCircuitBreaker": true, @@ -500,14 +525,33 @@ "chunkedThreshold": 102400, "compressionLevel": 5 }, - "metadata": {}, + "metadata": { + "converted_to_route_by": "admin@otoroshi.io - admin@otoroshi.io", + "service": "api", + "converted_to_route_at": "2024-05-16 02:36:03", + "env": "prod", + "product": "orc" + }, "tags": [], "chaosConfig": { "enabled": false, - "largeRequestFaultConfig": null, - "largeResponseFaultConfig": null, - "latencyInjectionFaultConfig": null, - "badResponsesFaultConfig": null + "largeRequestFaultConfig": { + "ratio": 0.2, + "additionalRequestSize": 0 + }, + "largeResponseFaultConfig": { + "ratio": 0.2, + "additionalResponseSize": 0 + }, + "latencyInjectionFaultConfig": { + "ratio": 0.2, + "from": 0, + "to": 0 + }, + "badResponsesFaultConfig": { + "ratio": 0.2, + "responses": [] + } }, "jwtVerifier": { "type": "ref", @@ -519,7 +563,7 @@ "secComSettings": { "type": "HSAlgoSettings", "size": 512, - "secret": "${config.app.claim.sharedKey}", + "secret": "Qu0EMIbWZsDTuuFUilDLG4APYqVTEw8Zgd3UpvqsbKoQlLkhqzkngZxJ2TKAsVoBOQKSc4stOf9B8htpgP2iGF896LPCFFmZTPT3GQg0QYtRuz5lbvS0Em9gxcUBzHfx", "base64": false }, "secComUseSameAlgo": true, @@ -556,11 +600,21 @@ "code": 303, "to": "https://www.otoroshi.io" }, - "authConfigRef": null, + "authConfigRef": "confidential-apps", "clientValidatorRef": null, "transformerRef": null, "transformerRefs": [], - "transformerConfig": {}, + "transformerConfig": { + "AccessLog": { + "enabled": false, + "methods": [ + "POST" + ], + "statuses": [ + 201 + ] + } + }, "apiKeyConstraints": { "basicAuth": { "enabled": true, @@ -572,6 +626,12 @@ "clientIdHeaderName": null, "clientSecretHeaderName": null }, + "otoBearerAuth": { + "enabled": true, + "headerName": null, + "queryName": null, + "cookieName": null + }, "clientIdAuth": { "enabled": true, "headerName": null, @@ -637,23 +697,23 @@ "default" ] }, - "id": "admin-api-service", - "groupId": "admin-api-group", + "id": "4dMCocfui1hLmlqxCI7Sx8D4TV1KrUczAzKDqb2RsarpJSUHLhddQAHBArSmoIVj", + "groupId": "0ZISHDNhJOHOP0IflvU0voYhLAVsqyej5wxqnOheuCM7Gk8SzppbyDDbSI1aEc5n", "groups": [ - "admin-api-group" + "0ZISHDNhJOHOP0IflvU0voYhLAVsqyej5wxqnOheuCM7Gk8SzppbyDDbSI1aEc5n" ], - "name": "otoroshi-admin-api", + "name": "my-service", "description": "", - "env": "prod", - "domain": "oto.tools", - "subdomain": "otoroshi-api", + "env": "preprod", + "domain": "opunmaif.fr", + "subdomain": "nio", "targetsLoadBalancing": { "type": "RoundRobin" }, "targets": [ { - "host": "127.0.0.1:8080", - "scheme": "http", + "host": "mirror.otoroshi.io", + "scheme": "https", "weight": 1, "mtlsConfig": { "certs": [], @@ -674,13 +734,13 @@ "root": "/", "matchingRoot": null, "stripPath": true, - "localHost": "127.0.0.1:8080", + "localHost": "localhost:8080", "localScheme": "http", "redirectToLocal": false, "enabled": true, "userFacing": false, - "privateApp": false, - "forceHttps": false, + "privateApp": true, + "forceHttps": true, "logAnalyticsOnServer": false, "useAkkaHttpClient": true, "useNewWSClient": false, @@ -692,11 +752,11 @@ "enforceSecureCommunication": true, "sendInfoToken": true, "sendStateChallenge": true, - "sendOtoroshiHeadersBack": true, + "sendOtoroshiHeadersBack": false, "readOnly": false, "xForwardedHeaders": false, "overrideHost": true, - "allowHttp10": true, + "allowHttp10": false, "letsEncrypt": false, "secComHeaders": { "claimRequestName": null, @@ -704,18 +764,22 @@ "stateResponseName": null }, "secComTtl": 30000, - "secComVersion": 1, + "secComVersion": "V1", "secComInfoTokenVersion": "Legacy", - "secComExcludedPatterns": [], + "secComExcludedPatterns": [ + "/ruxitagentjs_.*", + "/rb_.*" + ], "securityExcludedPatterns": [], "publicPatterns": [ - "/health", - "/metrics" + "/.*" ], - "privatePatterns": [], - "additionalHeaders": { - "Host": "otoroshi-admin-internal-api.oto.tools" - }, + "privatePatterns": [ + "/api/.*", + "/_healthCheck", + "/assets/swagger/swagger.json" + ], + "additionalHeaders": {}, "additionalHeadersOut": {}, "missingOnlyHeadersIn": {}, "missingOnlyHeadersOut": {}, @@ -732,16 +796,19 @@ }, "healthCheck": { "enabled": false, - "url": "/" + "url": "/", + "timeout": 5000, + "healthyStatuses": [], + "unhealthyStatuses": [] }, "clientConfig": { "useCircuitBreaker": true, - "retries": 1, - "maxErrors": 20, + "retries": 3, + "maxErrors": 60, "retryInitialDelay": 50, "backoffFactor": 2, - "callTimeout": 30000, - "callAndStreamTimeout": 120000, + "callTimeout": 60000, + "callAndStreamTimeout": 1200000, "connectionTimeout": 10000, "idleTimeout": 60000, "globalTimeout": 30000, @@ -772,14 +839,31 @@ "chunkedThreshold": 102400, "compressionLevel": 5 }, - "metadata": {}, + "metadata": { + "service": "nio", + "env": "preprod", + "product": "nio" + }, "tags": [], "chaosConfig": { "enabled": false, - "largeRequestFaultConfig": null, - "largeResponseFaultConfig": null, - "latencyInjectionFaultConfig": null, - "badResponsesFaultConfig": null + "largeRequestFaultConfig": { + "ratio": 0.2, + "additionalRequestSize": 0 + }, + "largeResponseFaultConfig": { + "ratio": 0.2, + "additionalResponseSize": 0 + }, + "latencyInjectionFaultConfig": { + "ratio": 0.2, + "from": 0, + "to": 0 + }, + "badResponsesFaultConfig": { + "ratio": 0.2, + "responses": [] + } }, "jwtVerifier": { "type": "ref", @@ -791,7 +875,7 @@ "secComSettings": { "type": "HSAlgoSettings", "size": 512, - "secret": "${config.app.claim.sharedKey}", + "secret": "eE1YY5jEr1p7FH1otSFPJhyiKapPCmqtTYw14oervekiWAPUE3xIPuxz3ZLPwuleIgZrQoOiMmJx919JdsTJA6BHXeksuoI5h0f1PI57SshEGwMVTF02RK7Gamw8hAaW", "base64": false }, "secComUseSameAlgo": true, @@ -828,11 +912,20 @@ "code": 303, "to": "https://www.otoroshi.io" }, - "authConfigRef": null, + "authConfigRef": "confidential-apps", "clientValidatorRef": null, "transformerRef": null, "transformerRefs": [], - "transformerConfig": {}, + "transformerConfig": { + "KafkaAccessLog": { + "enabled": true, + "topic": "otoroshi-access-log-nio", + "statuses": [], + "paths": [], + "methods": [], + "identities": [] + } + }, "apiKeyConstraints": { "basicAuth": { "enabled": true, @@ -844,6 +937,12 @@ "clientIdHeaderName": null, "clientSecretHeaderName": null }, + "otoBearerAuth": { + "enabled": true, + "headerName": null, + "queryName": null, + "cookieName": null + }, "clientIdAuth": { "enabled": true, "headerName": null, @@ -896,52 +995,11 @@ "config": {}, "excluded": [] }, - "hosts": [ - "otoroshi-api.oto.tools" - ], + "hosts": [], "paths": [], "handleLegacyDomain": true, "issueCert": false, "issueCertCA": null - } - ], - "errorTemplates": [], - "jwtVerifiers": [], - "authConfigs": [], - "certificates": [ - { - "_loc": { - "tenant": "default", - "teams": [ - "default" - ] - }, - "id": "otoroshi-client", - "domain": "Otoroshi Default Client Certificate", - "name": "Otoroshi Default Client Certificate", - "description": "Otoroshi client certificate (auto-generated)", - "chain": "-----BEGIN CERTIFICATE-----\nMIIDuDCCAqCgAwIBAgIIPMazde7BgS8wDQYJKoZIhvcNAQELBQAwajE1MDMGA1UE\nAwwsT3Rvcm9zaGkgRGVmYXVsdCBJbnRlcm1lZGlhdGUgQ0EgQ2VydGlmaWNhdGUx\nHjAcBgNVBAsMFU90b3Jvc2hpIENlcnRpZmljYXRlczERMA8GA1UECgwIT3Rvcm9z\naGkwHhcNMjIxMTIxMTMxNzU0WhcNMjMxMTIxMTMxNzU0WjBhMSwwKgYDVQQDDCNP\ndG9yb3NoaSBEZWZhdWx0IENsaWVudCBDZXJ0aWZpY2F0ZTEeMBwGA1UECwwVT3Rv\ncm9zaGkgQ2VydGlmaWNhdGVzMREwDwYDVQQKDAhPdG9yb3NoaTCCASIwDQYJKoZI\nhvcNAQEBBQADggEPADCCAQoCggEBALEJWTsWwVLKhnuEQdKuP1KuV2xW9jfIgdKI\nZfByPW+TdBddcF7RD1yqaK0cMjhOVkatBKdSpv3wX2a7R/Fg4SqJcq7SBnemJaY6\nqO9eX2Uku2Y/vX99K4FG337+FED50Gz8H5yJH/hBR9vfRVSfVKOBcHYDMXaAvWrX\nmKIKOJZ2aXnAG3m1CzavI4mSwzx3hV+b3RA1grF+XKCHaodjx16rLau1QIzdmmty\nAlG+lRobrFTOQswPjRGsFoS96+GK/Aj6YDJRc1W3DYdfoE0mYjO86WTeOZT5ddCL\nYbki3r+BL9OF4GpO7sl+Q9RwNa9+Q8jOzk5ahzJiQUGtT8pOfXMCAwEAAaNrMGkw\nDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUH\nAwIGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFCdJVgUF6ZxtLLJNsHQXpBRmWL0CMAkG\nA1UdEQQCMAAwDQYJKoZIhvcNAQELBQADggEBAKtNnVvtLd3gbxLxgOxY25GLscfE\ntDMpIWvWJhI6WwVYic7Utj/24S24SYPYHHyEJCEu1VSjsaPIXf19TKuXbch0kfSl\na8jMEZDsHVYK8OgIhnoZOvga+etSU8AdrjT/nbIDbo2L2plvMIxFSgYCO8tg/9wj\nQRDVRMYSk+pknWJPl8BTy5fpmJPvZ6fQMiC5txzQdnBJEh5skrjJTzp8XT/0wQby\n6OP65rHZ8uvXFB/Wt6wblcMDneFYGS+uYuFOFz5xSQU4PWrhpPMehVs6doIKHMrN\na/g+Edwp+yrUqvvfFT8l9r/sHrXYEjJDqBWZmWufTP2V3MILhq21JE/C5TM=\n-----END CERTIFICATE-----\n\n-----BEGIN CERTIFICATE-----\nMIIDtDCCApygAwIBAgIISNRU1CRzbwYwDQYJKoZIhvcNAQELBQAwYjEtMCsGA1UE\nAwwkT3Rvcm9zaGkgRGVmYXVsdCBSb290IENBIENlcnRpZmljYXRlMR4wHAYDVQQL\nDBVPdG9yb3NoaSBDZXJ0aWZpY2F0ZXMxETAPBgNVBAoMCE90b3Jvc2hpMB4XDTIy\nMTEyMTEzMTc1NFoXDTMyMTExODEzMTc1NFowajE1MDMGA1UEAwwsT3Rvcm9zaGkg\nRGVmYXVsdCBJbnRlcm1lZGlhdGUgQ0EgQ2VydGlmaWNhdGUxHjAcBgNVBAsMFU90\nb3Jvc2hpIENlcnRpZmljYXRlczERMA8GA1UECgwIT3Rvcm9zaGkwggEiMA0GCSqG\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCyyEf9RZVWoRsBZi5Sc9yLUBWMn6KhCUkh\newvEqeLuOXinN/w21wD1xy2fBkABmhXoDb2xbc1vhdskrUWQdqYfG275XzyJqGp5\n2dA5Q/L/3kPS2AAVTDkaGUXvUpObv7r7JzLolq2MRLhJIL2V04bI6sfe1npk8Imp\nwcaFVoADhliB2Jx88VWAu/Te8R9wjG3apTI7xXy45JzZzaelppMFo/9OrHpeYSeC\n2iTcbFUgFgVvM6yMd++dN6ApKvqkmD6DMha1dgrWkETDg70mciaeWqR9dDRvLEp4\n4O85a7TwhTLU6XTR63dzKjFkt0YpR8TO5+cOvQdVPyxixX3ge+BPAgMBAAGjZjBk\nMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaA\nFGUiVFsfuJ0DNULdrjx1yc5Amr+AMB0GA1UdDgQWBBQnSVYFBemcbSyyTbB0F6QU\nZli9AjANBgkqhkiG9w0BAQsFAAOCAQEAs8p3xMvR4/P2c767RmqnO9eoqCpvtCzT\nM20BMrnLu8SBfSQ+s0c++/7LiIB91Z6gF6ZOWcKs8aGUnr5HM/2eqF7gLrGa3tp/\nwQZH+bG2mqtWQRvqG+fhGc1xkG7XvEoSh1EVd1tk12MgYMQu7rqj6Xn643aZ69iv\n16ZilV6H7ua+ew9nsff37BPz/tUB35yevo8tJeWZlcZC2utyNOjruWJL51VPV41x\nYck2fm2c+oxx+QqjVJLk/bgTv/RyLT+TGVdQV1fU+1E1mlwu7xHpQEXAQ8OxfZ1q\ni+Xe1sfGlGykkQrh5A4RxIfwgT9Z5h/4HNRJ4GmgNmwGQ8qtStLQnQ==\n-----END CERTIFICATE-----\n\n-----BEGIN CERTIFICATE-----\nMIIEcjCCA1qgAwIBAgIJAJch6aKrM1lzMA0GCSqGSIb3DQEBCwUAMGIxLTArBgNV\nBAMMJE90b3Jvc2hpIERlZmF1bHQgUm9vdCBDQSBDZXJ0aWZpY2F0ZTEeMBwGA1UE\nCwwVT3Rvcm9zaGkgQ2VydGlmaWNhdGVzMREwDwYDVQQKDAhPdG9yb3NoaTAeFw0y\nMjExMjExMzE3NTNaFw0zMjExMTgxMzE3NTNaMGIxLTArBgNVBAMMJE90b3Jvc2hp\nIERlZmF1bHQgUm9vdCBDQSBDZXJ0aWZpY2F0ZTEeMBwGA1UECwwVT3Rvcm9zaGkg\nQ2VydGlmaWNhdGVzMREwDwYDVQQKDAhPdG9yb3NoaTCCASIwDQYJKoZIhvcNAQEB\nBQADggEPADCCAQoCggEBAOgA/esqsN/lzJ2Mn/22ShU5qtRk2XpVxkbdDJpRT4Rv\n49Vm7iU0lOU7nyhMyCKaXzDdbPMHGWK5epUsd3E2n/L0kbqj3Q03leSDx2tkPQ9i\nBNiBNdjuybNo7bA+G8GmtI+zvjKyba19mzFpeNWHuMiL2lbh8BoNUWGKBFuRwvJ+\nIVPRrrl9RMT6QnnxtRTPc5oUZ/Jmzkl7Twy/LiJZMrA5nkDX4V4v7yzQR25XrboA\n+HrXg/qomgjw/H6Wfjfbdnt5/Y6YbCt0j25PCwnDDyMrQT4TLxWjPcmoVCJm6IgT\npqiYVGOvDVbe8qaEWkPwH/2nANBnMcG9eG2+MVjLByUCAwEAAaOCASkwggElMA8G\nA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB8GA1UdIwQYMBaAFGUiVFsf\nuJ0DNULdrjx1yc5Amr+AMB0GA1UdDgQWBBRlIlRbH7idAzVC3a48dcnOQJq/gDCB\nwQYIKwYBBQUHAQEEgbQwgbEwZQYIKwYBBQUHMAKGWWh0dHA6Ly9vdG9yb3NoaS1h\ncGkub3RvLnRvb2xzOjgwODAvLndlbGwta25vd24vb3Rvcm9zaGkvY2VydGlmaWNh\ndGVzLzEwODkwMjQyMjU4ODI0ODEyOTE1MEgGCCsGAQUFBzABhjxodHRwOi8vb3Rv\ncm9zaGktYXBpLm90by50b29sczo4MDgwLy53ZWxsLWtub3duL290b3Jvc2hpL29j\nc3AwDQYJKoZIhvcNAQELBQADggEBACZrpgx4fJ3iuvwJWAUZKZu68VSc2POmm2xm\nFpppDTxJHgYNJD9fWPRj9Fwk/gNnKbjCRSp1YA3+3o60Jz+MFWgV3hfdExMYmiCf\n6mMY7M8D3cqJ2Gf1TBK278nf+etcMUBRHLsvWnbB9Q4UFZBw6uQNh76I1inWDaFn\nBbBZi3y6IfzFThh33s6vRRnduWVZ3eDDw/E9GuaXdXAuLoC3WspzmM1vlduaqTaI\nxj4kbWzNk8mVsQ15h4IbSLnBpg1zdPFQyjWtVHxQZqwdAijlB4EzfyEB/kOAK24M\n6wim3SKjers/9ZyVLOWf5r5mnb855e/Yaeg9lMrnwfy/iME15ZE=\n-----END CERTIFICATE-----\n", - "caRef": null, - "privateKey": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCxCVk7FsFSyoZ7\nhEHSrj9SrldsVvY3yIHSiGXwcj1vk3QXXXBe0Q9cqmitHDI4TlZGrQSnUqb98F9m\nu0fxYOEqiXKu0gZ3piWmOqjvXl9lJLtmP71/fSuBRt9+/hRA+dBs/B+ciR/4QUfb\n30VUn1SjgXB2AzF2gL1q15iiCjiWdml5wBt5tQs2ryOJksM8d4Vfm90QNYKxflyg\nh2qHY8deqy2rtUCM3ZprcgJRvpUaG6xUzkLMD40RrBaEvevhivwI+mAyUXNVtw2H\nX6BNJmIzvOlk3jmU+XXQi2G5It6/gS/TheBqTu7JfkPUcDWvfkPIzs5OWocyYkFB\nrU/KTn1zAgMBAAECggEAAIQjOm5Dsrh2sDPhyqA6CCu23lDftAkzKqHw+YYPemUl\neZTl4hjjQXp578PORQL6tA+3MKhpESSgxT72shnbgnkBpsgOZzF1S92LGJJS4QjT\nSD/H1meZAsCJZyWYV3SlI2xirA3BL1GRUV4dzapCMTcuaKMZA1I/l5Oh5jyll5Ff\n5u0JUW/aft0hldhjccSNqPBk3Z+cWMKYtdaW7IxTR2UI1ZSM7GWNccHM49vdGEFK\n3NSRdLS/fP+xa+oaRaRUt4vrwzCzcu6zz0qfliJpYzzy1NiFgjB+0lpF8unyzsSe\n/bQFkE6V/CFkLC+O7uRyViraqoz4xj4H9NF9KT1TFQKBgQDnsN7ATv/BXfYc+Dt7\nSrTVdbw/ze5qo8oSKdn+VOKPaUfoP0mXhy8xZ+lh4RTSF76qj/K1y7FesJcc2/DK\nFdfBTXRQMlPlobGBWP9q+twqC1nAUF3sZSeCvq0sZ3pjnKHRk96dMRAs5UaBurK4\nIb4KidiIpr4GTLnjTzC8uW9urQKBgQDDnHuBJdVt3PljSA+aNIsjk1dtiipfnw5N\nqYd/iy0PTpkx/fCXEM3HvM72BENvk34H5sHUrw26bTHT0rvDTBczkpSFGHuWj3s9\n6sb/gR/xQxMc6SA94kWhjCp4BucQYRLevp3W17Av6MZwgnQqMmQNq3dC+81XerjR\ndVnm7VPAnwKBgQDHs0PMLktzuDFyNuPOGU5Uzu47cJyXfiWmEjy7Yx8EzZIVNLeR\nzAt5Yj9fBkqT67OEAB8LY062dy9IyKp7BOi6zK/8j+SQgRYmjzdYVnF+K9poUN0j\n7OxpXUCWjXqIcXFo2zN9+dQfXl+vYeS5oRvUqYvh/Ra9B1USACfaA1tw4QKBgGvd\nn3FBFqdFgjQQKaJkNRHCDyT1WpZuFCoZQQHBEnDta6bYnFIBTyhEYm/hO/qL/6SF\nx/7rJQvXaPGgtC9mhyjGPKYEMSp+JnynOWLtplqZHTIFD+VwH+uMamLTk5lBnnE7\n9bfiKmLQ3pRuK3aVYPz0v4gtaDdEEiRWrOOdnWCLAoGATtqsOXQigW2ab2TP5aBv\nubUDyBfyd1K15AvMKbnWFEE92W41j1A0h+JMWfD3sHy5SvrMYozdDBQy0jKn3XIA\nD6unBRkzEaeXeNYKQKJclZ6nvczKz1+ach7ClYYc/8bHLV23gsCNowYrKO92vtEx\ny4hJMUHPCmOiPzRfiDrfm8g=\n-----END PRIVATE KEY-----\n", - "selfSigned": false, - "ca": false, - "valid": true, - "exposed": true, - "revoked": false, - "autoRenew": true, - "letsEncrypt": false, - "subject": "OU=Otoroshi Certificates, O=Otoroshi, CN=Otoroshi Default Client Certificate", - "from": 1669036674000, - "to": 1700572674000, - "client": true, - "keypair": false, - "sans": [], - "certType": "client", - "password": null, - "metadata": { - "csr": "{\"hosts\":[],\"key\":{\"algo\":\"rsa\",\"size\":2048},\"name\":{},\"subject\":\"CN=Otoroshi Default Client Certificate, OU=Otoroshi Certificates, O=Otoroshi\",\"client\":true,\"ca\":false,\"duration\":31536000000,\"signatureAlg\":\"SHA256WithRSAEncryption\",\"digestAlg\":\"SHA-256\"}" - }, - "tags": [] }, { "_loc": { @@ -950,64 +1008,473 @@ "default" ] }, - "id": "otoroshi-jwt-signing", - "domain": "Otoroshi Default Jwt Signing Keypair", - "name": "Otoroshi Default Jwt Signing Keypair", - "description": "Otoroshi jwt signing keypair (auto-generated)", - "chain": "-----BEGIN CERTIFICATE-----\nMIIDrzCCApegAwIBAgIIbz9bxKSftaUwDQYJKoZIhvcNAQELBQAwajE1MDMGA1UE\nAwwsT3Rvcm9zaGkgRGVmYXVsdCBJbnRlcm1lZGlhdGUgQ0EgQ2VydGlmaWNhdGUx\nHjAcBgNVBAsMFU90b3Jvc2hpIENlcnRpZmljYXRlczERMA8GA1UECgwIT3Rvcm9z\naGkwHhcNMjIxMTIxMTMxNzU0WhcNMjMxMTIxMTMxNzU0WjBiMS0wKwYDVQQDDCRP\ndG9yb3NoaSBEZWZhdWx0IEp3dCBTaWduaW5nIEtleXBhaXIxHjAcBgNVBAsMFU90\nb3Jvc2hpIENlcnRpZmljYXRlczERMA8GA1UECgwIT3Rvcm9zaGkwggEiMA0GCSqG\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCoLmCGpDFHG/9VziW0L3K7MNDGy1V/HpJS\ntFTSJhbya8N6HbH6/hpY5woJM7x0yFPqRN8OZBb6FoG5ZnkqgEMx/MpjOHwY+HVj\n4KzjVjr5YHtB6jgvAtXD66bCuNSTSLu8WkU4V1BVnNRbLnuOg6Wjk732kBVbhleO\nVwSTikGT7maZ103Baf2uzuPUerYaTZ5P6AILnrfGMXjQYNlWeRewDtrgFoRmPXeS\nx2ngdP0FCw6uh1jtdAWgrTovukKuPqsIAtq9lNxIzfulU1VLB+apkf4/7QqTIgoj\nDlTtIeDTeQtvo2PDPzMZ4a1o5wvbA5Cv26Ek2VDA4YBcbCmt/mM5AgMBAAGjYTBf\nMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUF\nBwMBMB8GA1UdIwQYMBaAFCdJVgUF6ZxtLLJNsHQXpBRmWL0CMAkGA1UdEQQCMAAw\nDQYJKoZIhvcNAQELBQADggEBALHLNVQW/7kvYmO1wqXqjQA6JEeqXo8P+aLdopRo\neEJB3hLwP7kUJeZ6Fic/qZM69WxRJhGdPcryI4ATdahJ2rydU8Ytxl8niDxRqm8c\nVj0V/3BnlIwksuq5JnRg0RNIniXE6DIHO8kgDW0VMv1jIpJmvlBaE5A9b8ZKWqo2\nCbpr6hQ64bf/r0d3DNc71IIMks4iE5CK9jAoQEghSh7yDeWCTbOTpWsIOZmZCVie\n6cyEiFPNOqltd2SUWEDRqnp2t2L/9+Nnk5Gu2tOyAjZhTwZ7z5XGJWqbQNq+u3Ka\nY/CRI7l+qbV7pN0jAwsZq9Rv3FRxSJZWSjaNH8wUkvMGnkI=\n-----END CERTIFICATE-----\n\n-----BEGIN CERTIFICATE-----\nMIIDtDCCApygAwIBAgIISNRU1CRzbwYwDQYJKoZIhvcNAQELBQAwYjEtMCsGA1UE\nAwwkT3Rvcm9zaGkgRGVmYXVsdCBSb290IENBIENlcnRpZmljYXRlMR4wHAYDVQQL\nDBVPdG9yb3NoaSBDZXJ0aWZpY2F0ZXMxETAPBgNVBAoMCE90b3Jvc2hpMB4XDTIy\nMTEyMTEzMTc1NFoXDTMyMTExODEzMTc1NFowajE1MDMGA1UEAwwsT3Rvcm9zaGkg\nRGVmYXVsdCBJbnRlcm1lZGlhdGUgQ0EgQ2VydGlmaWNhdGUxHjAcBgNVBAsMFU90\nb3Jvc2hpIENlcnRpZmljYXRlczERMA8GA1UECgwIT3Rvcm9zaGkwggEiMA0GCSqG\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCyyEf9RZVWoRsBZi5Sc9yLUBWMn6KhCUkh\newvEqeLuOXinN/w21wD1xy2fBkABmhXoDb2xbc1vhdskrUWQdqYfG275XzyJqGp5\n2dA5Q/L/3kPS2AAVTDkaGUXvUpObv7r7JzLolq2MRLhJIL2V04bI6sfe1npk8Imp\nwcaFVoADhliB2Jx88VWAu/Te8R9wjG3apTI7xXy45JzZzaelppMFo/9OrHpeYSeC\n2iTcbFUgFgVvM6yMd++dN6ApKvqkmD6DMha1dgrWkETDg70mciaeWqR9dDRvLEp4\n4O85a7TwhTLU6XTR63dzKjFkt0YpR8TO5+cOvQdVPyxixX3ge+BPAgMBAAGjZjBk\nMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaA\nFGUiVFsfuJ0DNULdrjx1yc5Amr+AMB0GA1UdDgQWBBQnSVYFBemcbSyyTbB0F6QU\nZli9AjANBgkqhkiG9w0BAQsFAAOCAQEAs8p3xMvR4/P2c767RmqnO9eoqCpvtCzT\nM20BMrnLu8SBfSQ+s0c++/7LiIB91Z6gF6ZOWcKs8aGUnr5HM/2eqF7gLrGa3tp/\nwQZH+bG2mqtWQRvqG+fhGc1xkG7XvEoSh1EVd1tk12MgYMQu7rqj6Xn643aZ69iv\n16ZilV6H7ua+ew9nsff37BPz/tUB35yevo8tJeWZlcZC2utyNOjruWJL51VPV41x\nYck2fm2c+oxx+QqjVJLk/bgTv/RyLT+TGVdQV1fU+1E1mlwu7xHpQEXAQ8OxfZ1q\ni+Xe1sfGlGykkQrh5A4RxIfwgT9Z5h/4HNRJ4GmgNmwGQ8qtStLQnQ==\n-----END CERTIFICATE-----\n\n-----BEGIN CERTIFICATE-----\nMIIEcjCCA1qgAwIBAgIJAJch6aKrM1lzMA0GCSqGSIb3DQEBCwUAMGIxLTArBgNV\nBAMMJE90b3Jvc2hpIERlZmF1bHQgUm9vdCBDQSBDZXJ0aWZpY2F0ZTEeMBwGA1UE\nCwwVT3Rvcm9zaGkgQ2VydGlmaWNhdGVzMREwDwYDVQQKDAhPdG9yb3NoaTAeFw0y\nMjExMjExMzE3NTNaFw0zMjExMTgxMzE3NTNaMGIxLTArBgNVBAMMJE90b3Jvc2hp\nIERlZmF1bHQgUm9vdCBDQSBDZXJ0aWZpY2F0ZTEeMBwGA1UECwwVT3Rvcm9zaGkg\nQ2VydGlmaWNhdGVzMREwDwYDVQQKDAhPdG9yb3NoaTCCASIwDQYJKoZIhvcNAQEB\nBQADggEPADCCAQoCggEBAOgA/esqsN/lzJ2Mn/22ShU5qtRk2XpVxkbdDJpRT4Rv\n49Vm7iU0lOU7nyhMyCKaXzDdbPMHGWK5epUsd3E2n/L0kbqj3Q03leSDx2tkPQ9i\nBNiBNdjuybNo7bA+G8GmtI+zvjKyba19mzFpeNWHuMiL2lbh8BoNUWGKBFuRwvJ+\nIVPRrrl9RMT6QnnxtRTPc5oUZ/Jmzkl7Twy/LiJZMrA5nkDX4V4v7yzQR25XrboA\n+HrXg/qomgjw/H6Wfjfbdnt5/Y6YbCt0j25PCwnDDyMrQT4TLxWjPcmoVCJm6IgT\npqiYVGOvDVbe8qaEWkPwH/2nANBnMcG9eG2+MVjLByUCAwEAAaOCASkwggElMA8G\nA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB8GA1UdIwQYMBaAFGUiVFsf\nuJ0DNULdrjx1yc5Amr+AMB0GA1UdDgQWBBRlIlRbH7idAzVC3a48dcnOQJq/gDCB\nwQYIKwYBBQUHAQEEgbQwgbEwZQYIKwYBBQUHMAKGWWh0dHA6Ly9vdG9yb3NoaS1h\ncGkub3RvLnRvb2xzOjgwODAvLndlbGwta25vd24vb3Rvcm9zaGkvY2VydGlmaWNh\ndGVzLzEwODkwMjQyMjU4ODI0ODEyOTE1MEgGCCsGAQUFBzABhjxodHRwOi8vb3Rv\ncm9zaGktYXBpLm90by50b29sczo4MDgwLy53ZWxsLWtub3duL290b3Jvc2hpL29j\nc3AwDQYJKoZIhvcNAQELBQADggEBACZrpgx4fJ3iuvwJWAUZKZu68VSc2POmm2xm\nFpppDTxJHgYNJD9fWPRj9Fwk/gNnKbjCRSp1YA3+3o60Jz+MFWgV3hfdExMYmiCf\n6mMY7M8D3cqJ2Gf1TBK278nf+etcMUBRHLsvWnbB9Q4UFZBw6uQNh76I1inWDaFn\nBbBZi3y6IfzFThh33s6vRRnduWVZ3eDDw/E9GuaXdXAuLoC3WspzmM1vlduaqTaI\nxj4kbWzNk8mVsQ15h4IbSLnBpg1zdPFQyjWtVHxQZqwdAijlB4EzfyEB/kOAK24M\n6wim3SKjers/9ZyVLOWf5r5mnb855e/Yaeg9lMrnwfy/iME15ZE=\n-----END CERTIFICATE-----\n", - "caRef": null, - "privateKey": "-----BEGIN PRIVATE KEY-----\nMIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQCoLmCGpDFHG/9V\nziW0L3K7MNDGy1V/HpJStFTSJhbya8N6HbH6/hpY5woJM7x0yFPqRN8OZBb6FoG5\nZnkqgEMx/MpjOHwY+HVj4KzjVjr5YHtB6jgvAtXD66bCuNSTSLu8WkU4V1BVnNRb\nLnuOg6Wjk732kBVbhleOVwSTikGT7maZ103Baf2uzuPUerYaTZ5P6AILnrfGMXjQ\nYNlWeRewDtrgFoRmPXeSx2ngdP0FCw6uh1jtdAWgrTovukKuPqsIAtq9lNxIzful\nU1VLB+apkf4/7QqTIgojDlTtIeDTeQtvo2PDPzMZ4a1o5wvbA5Cv26Ek2VDA4YBc\nbCmt/mM5AgMBAAECgf8UGC/ZgneCqcOwPrs+3MBT0xh//XtVPiVdKfqEQ0dWkZYr\nzTUaeRaIxF6u+VDM9DirjkNJDYtadeEAhB3mnjKDoVXJi8JT3MXUlPYZKU0Q7GTu\nISMOxHapbJFbGPvmNLcKmU1FuXhyh2PlU4ZC6QFs5xIWHuhoe7d7Jq7vOg9zWel9\nN92SLR6AOygyj0lT/NijD7xceV6XVI9/5bDNC8OdT3Bm1QfKFIM69hlNtnTlPfDq\nqAZ9tAAJe3YwavVbLt1gtC5S9Vs4NTpI4CJWQfo2NOqYj2tmYVc3DB6SFL8tJOju\nFWGDnsHe9WRine990gKzQ56cwXxzC8uyoBP7NbECgYEA7XlEyAsbxVJBOPsOHlGK\nFIA7+M2ILTs18Mhzs7DptHnA1qZf/ZIqnSnKYVnROI3n32hcoBSdKyNGM5p5qc9u\nZrC1OiIH+EAdus2nGvjhnvrAuKtxmyTwqVCyZ9rDnjIyaBQsBHovAE7kBkvXt+mc\ngUA6kr+ICiyWdz0GoqzaZakCgYEAtU05lmYxvarBO0cncHnJSEpB8kvnX0WrcCdm\ngq9N7pXxHr729aTKwtcdaltdFV2naJSoAML3LaoyybJpV2jzbkSq60D6YjzYIwjP\nGC70pL9NWwA3oRD91WNnWj7OaxemC1FTBx5XdZgiWhooezwvWwVi2xKnjHVobgD5\nInMAaxECgYAspD9p26y8y1m3fHqK+IkckX60pu6Ski7xtL0LapaEdt7pex1QbcnV\nAaYp5tiN6R9NwDCpZzakhV0NFl0IrxXx+AYZh4w3/tWX6FEhoOfJGxpfnNsebzNO\noPzoyRc9BfKz3hboQI/VXv8+r8M9r4zcP1bDf33za65de/tjWVs1AQKBgBd/cM1L\nZXr9nJnAOR5cyneyGCX9LHmun++jS6f4bJNVn7Cqc3uW5iimlMtqGABi6QWm7rda\njYq/mmGF3B1WHubVw4lGHK2K0UCJLb4mp9KKcM4wkwUvHJcBkS2jIkHDCORCoA42\nIoO9OgaPiVufi0uywQM/wbN0rBpFygRq/0zhAoGBAIGDzsXfQ/Py2WhL1zRBMKzq\nY/SKK2j1aRjaMUcOIyaSu9nlfCOsrhyxFHNCwx9M9YyqyLGr0IeDkTuTjqS0Tfo+\nxRESbWhr6PmKDk3kSudvMCPrPqG25aKerzmrmLEV2lEYAsE9KsfD+sRy71MfLJQt\nPjxZIptZydTtn7FQebrE\n-----END PRIVATE KEY-----\n", - "selfSigned": false, - "ca": false, - "valid": true, - "exposed": true, - "revoked": false, - "autoRenew": true, - "letsEncrypt": false, - "subject": "OU=Otoroshi Certificates, O=Otoroshi, CN=Otoroshi Default Jwt Signing Keypair", - "from": 1669036674000, - "to": 1700572674000, - "client": false, - "keypair": true, - "sans": [], - "certType": "keypair", - "password": null, - "metadata": { - "csr": "{\"hosts\":[],\"key\":{\"algo\":\"rsa\",\"size\":2048},\"name\":{},\"subject\":\"CN=Otoroshi Default Jwt Signing Keypair, OU=Otoroshi Certificates, O=Otoroshi\",\"client\":false,\"ca\":false,\"duration\":31536000000,\"signatureAlg\":\"SHA256WithRSAEncryption\",\"digestAlg\":\"SHA-256\"}" + "id": "KAyMY0iHPF7aljWqNkJx3mehRiwJtwKt2ZUybPZDZMIfOJDDJZiXvnrzlQlXyaAc", + "groupId": "VVHIgho2keCtVHaDR3l9Xkq34t6XnXnIcdf20i5NqEJmAt8uF5Tio9hSBWbgIo6O", + "groups": [ + "VVHIgho2keCtVHaDR3l9Xkq34t6XnXnIcdf20i5NqEJmAt8uF5Tio9hSBWbgIo6O" + ], + "name": "[MIGRATED TO ROUTE, PENDING DELETION] my-service", + "description": "", + "env": "prod", + "domain": "opunmaif.fr", + "subdomain": "nio", + "targetsLoadBalancing": { + "type": "RoundRobin" }, - "tags": [] - }, - { - "_loc": { - "tenant": "default", - "teams": [ - "default" - ] + "targets": [ + { + "host": "app-d85f65bf-e7ae-41c1-a3b5-408e74b07dac.opunmaif.io", + "scheme": "https", + "weight": 1, + "mtlsConfig": { + "certs": [], + "trustedCerts": [], + "mtls": false, + "loose": false, + "trustAll": false + }, + "tags": [], + "metadata": {}, + "protocol": "HTTP/1.1", + "predicate": { + "type": "AlwaysMatch" + }, + "ipAddress": null + } + ], + "root": "/", + "matchingRoot": null, + "stripPath": true, + "localHost": "localhost:8080", + "localScheme": "http", + "redirectToLocal": false, + "enabled": false, + "userFacing": false, + "privateApp": true, + "forceHttps": true, + "logAnalyticsOnServer": false, + "useAkkaHttpClient": true, + "useNewWSClient": false, + "tcpUdpTunneling": false, + "detectApiKeySooner": false, + "maintenanceMode": false, + "buildMode": false, + "strictlyPrivate": false, + "enforceSecureCommunication": true, + "sendInfoToken": true, + "sendStateChallenge": true, + "sendOtoroshiHeadersBack": false, + "readOnly": false, + "xForwardedHeaders": false, + "overrideHost": true, + "allowHttp10": false, + "letsEncrypt": false, + "secComHeaders": { + "claimRequestName": null, + "stateRequestName": null, + "stateResponseName": null }, - "id": "otoroshi-root-ca", - "domain": "Otoroshi Default Root CA Certificate", - "name": "Otoroshi Default Root CA Certificate", - "description": "Otoroshi root CA (auto-generated)", - "chain": "-----BEGIN CERTIFICATE-----\nMIIEcjCCA1qgAwIBAgIJAJch6aKrM1lzMA0GCSqGSIb3DQEBCwUAMGIxLTArBgNV\nBAMMJE90b3Jvc2hpIERlZmF1bHQgUm9vdCBDQSBDZXJ0aWZpY2F0ZTEeMBwGA1UE\nCwwVT3Rvcm9zaGkgQ2VydGlmaWNhdGVzMREwDwYDVQQKDAhPdG9yb3NoaTAeFw0y\nMjExMjExMzE3NTNaFw0zMjExMTgxMzE3NTNaMGIxLTArBgNVBAMMJE90b3Jvc2hp\nIERlZmF1bHQgUm9vdCBDQSBDZXJ0aWZpY2F0ZTEeMBwGA1UECwwVT3Rvcm9zaGkg\nQ2VydGlmaWNhdGVzMREwDwYDVQQKDAhPdG9yb3NoaTCCASIwDQYJKoZIhvcNAQEB\nBQADggEPADCCAQoCggEBAOgA/esqsN/lzJ2Mn/22ShU5qtRk2XpVxkbdDJpRT4Rv\n49Vm7iU0lOU7nyhMyCKaXzDdbPMHGWK5epUsd3E2n/L0kbqj3Q03leSDx2tkPQ9i\nBNiBNdjuybNo7bA+G8GmtI+zvjKyba19mzFpeNWHuMiL2lbh8BoNUWGKBFuRwvJ+\nIVPRrrl9RMT6QnnxtRTPc5oUZ/Jmzkl7Twy/LiJZMrA5nkDX4V4v7yzQR25XrboA\n+HrXg/qomgjw/H6Wfjfbdnt5/Y6YbCt0j25PCwnDDyMrQT4TLxWjPcmoVCJm6IgT\npqiYVGOvDVbe8qaEWkPwH/2nANBnMcG9eG2+MVjLByUCAwEAAaOCASkwggElMA8G\nA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB8GA1UdIwQYMBaAFGUiVFsf\nuJ0DNULdrjx1yc5Amr+AMB0GA1UdDgQWBBRlIlRbH7idAzVC3a48dcnOQJq/gDCB\nwQYIKwYBBQUHAQEEgbQwgbEwZQYIKwYBBQUHMAKGWWh0dHA6Ly9vdG9yb3NoaS1h\ncGkub3RvLnRvb2xzOjgwODAvLndlbGwta25vd24vb3Rvcm9zaGkvY2VydGlmaWNh\ndGVzLzEwODkwMjQyMjU4ODI0ODEyOTE1MEgGCCsGAQUFBzABhjxodHRwOi8vb3Rv\ncm9zaGktYXBpLm90by50b29sczo4MDgwLy53ZWxsLWtub3duL290b3Jvc2hpL29j\nc3AwDQYJKoZIhvcNAQELBQADggEBACZrpgx4fJ3iuvwJWAUZKZu68VSc2POmm2xm\nFpppDTxJHgYNJD9fWPRj9Fwk/gNnKbjCRSp1YA3+3o60Jz+MFWgV3hfdExMYmiCf\n6mMY7M8D3cqJ2Gf1TBK278nf+etcMUBRHLsvWnbB9Q4UFZBw6uQNh76I1inWDaFn\nBbBZi3y6IfzFThh33s6vRRnduWVZ3eDDw/E9GuaXdXAuLoC3WspzmM1vlduaqTaI\nxj4kbWzNk8mVsQ15h4IbSLnBpg1zdPFQyjWtVHxQZqwdAijlB4EzfyEB/kOAK24M\n6wim3SKjers/9ZyVLOWf5r5mnb855e/Yaeg9lMrnwfy/iME15ZE=\n-----END CERTIFICATE-----\n", + "secComTtl": 30000, + "secComVersion": "V1", + "secComInfoTokenVersion": "Legacy", + "secComExcludedPatterns": [ + "/ruxitagentjs_.*", + "/rb_.*" + ], + "securityExcludedPatterns": [], + "publicPatterns": [ + "/.*" + ], + "privatePatterns": [ + "/api/.*", + "/_healthCheck" + ], + "additionalHeaders": {}, + "additionalHeadersOut": {}, + "missingOnlyHeadersIn": {}, + "missingOnlyHeadersOut": {}, + "removeHeadersIn": [], + "removeHeadersOut": [], + "headersVerification": {}, + "matchingHeaders": {}, + "ipFiltering": { + "whitelist": [], + "blacklist": [] + }, + "api": { + "exposeApi": false + }, + "healthCheck": { + "enabled": false, + "url": "/", + "timeout": 5000, + "healthyStatuses": [], + "unhealthyStatuses": [] + }, + "clientConfig": { + "useCircuitBreaker": true, + "retries": 3, + "maxErrors": 60, + "retryInitialDelay": 50, + "backoffFactor": 2, + "callTimeout": 60000, + "callAndStreamTimeout": 72000000, + "connectionTimeout": 10000, + "idleTimeout": 60000, + "globalTimeout": 18000, + "sampleInterval": 2000, + "proxy": {}, + "customTimeouts": [], + "cacheConnectionSettings": { + "enabled": false, + "queueSize": 2048 + } + }, + "canary": { + "enabled": false, + "traffic": 0.2, + "targets": [], + "root": "/" + }, + "gzip": { + "enabled": false, + "excludedPatterns": [], + "whiteList": [ + "text/*", + "application/javascript", + "application/json" + ], + "blackList": [], + "bufferSize": 8192, + "chunkedThreshold": 102400, + "compressionLevel": 5 + }, + "metadata": { + "converted_to_route_by": "admin@otoroshi.io - admin@otoroshi.io", + "service": "nio", + "converted_to_route_at": "2024-05-13 04:14:12", + "env": "prod", + "product": "nio" + }, + "tags": [], + "chaosConfig": { + "enabled": false, + "largeRequestFaultConfig": { + "ratio": 0.2, + "additionalRequestSize": 0 + }, + "largeResponseFaultConfig": { + "ratio": 0.2, + "additionalResponseSize": 0 + }, + "latencyInjectionFaultConfig": { + "ratio": 0.2, + "from": 0, + "to": 0 + }, + "badResponsesFaultConfig": { + "ratio": 0.2, + "responses": [] + } + }, + "jwtVerifier": { + "type": "ref", + "ids": [], + "id": null, + "enabled": false, + "excludedPatterns": [] + }, + "secComSettings": { + "type": "HSAlgoSettings", + "size": 512, + "secret": "Wh9cYrNCQl8wUUdJPdUsDrnSVP9Y5PRqElopegNoTX0Sy5BiV7k4mo4m1ew4gwdDNkUwHJk5shU5AujW55D0lV30dp8OOVhdGlwBko4kSforYgUIuPTVy3Jgd8BfXQLj", + "base64": false + }, + "secComUseSameAlgo": true, + "secComAlgoChallengeOtoToBack": { + "type": "HSAlgoSettings", + "size": 512, + "secret": "secret", + "base64": false + }, + "secComAlgoChallengeBackToOto": { + "type": "HSAlgoSettings", + "size": 512, + "secret": "secret", + "base64": false + }, + "secComAlgoInfoToken": { + "type": "HSAlgoSettings", + "size": 512, + "secret": "secret", + "base64": false + }, + "cors": { + "enabled": false, + "allowOrigin": "*", + "exposeHeaders": [], + "allowHeaders": [], + "allowMethods": [], + "excludedPatterns": [], + "maxAge": null, + "allowCredentials": true + }, + "redirection": { + "enabled": false, + "code": 303, + "to": "https://www.otoroshi.io" + }, + "authConfigRef": "confidential-apps", + "clientValidatorRef": null, + "transformerRef": null, + "transformerRefs": [], + "transformerConfig": { + "KafkaAccessLog": { + "enabled": true, + "topic": "otoroshi-access-log-nio", + "statuses": [], + "paths": [], + "methods": [], + "identities": [] + } + }, + "apiKeyConstraints": { + "basicAuth": { + "enabled": true, + "headerName": null, + "queryName": null + }, + "customHeadersAuth": { + "enabled": true, + "clientIdHeaderName": null, + "clientSecretHeaderName": null + }, + "otoBearerAuth": { + "enabled": true, + "headerName": null, + "queryName": null, + "cookieName": null + }, + "clientIdAuth": { + "enabled": true, + "headerName": null, + "queryName": null + }, + "jwtAuth": { + "enabled": true, + "secretSigned": true, + "keyPairSigned": true, + "includeRequestAttributes": false, + "maxJwtLifespanSecs": null, + "headerName": null, + "queryName": null, + "cookieName": null + }, + "routing": { + "noneTagIn": [], + "oneTagIn": [], + "allTagsIn": [], + "noneMetaIn": {}, + "oneMetaIn": {}, + "allMetaIn": {}, + "noneMetaKeysIn": [], + "oneMetaKeyIn": [], + "allMetaKeysIn": [] + } + }, + "restrictions": { + "enabled": false, + "allowLast": true, + "allowed": [], + "forbidden": [], + "notFound": [] + }, + "accessValidator": { + "enabled": false, + "refs": [], + "config": {}, + "excludedPatterns": [] + }, + "preRouting": { + "enabled": false, + "refs": [], + "config": {}, + "excludedPatterns": [] + }, + "plugins": { + "enabled": false, + "refs": [], + "config": {}, + "excluded": [] + }, + "hosts": [], + "paths": [], + "handleLegacyDomain": true, + "issueCert": false, + "issueCertCA": null + } + ], + "errorTemplates": [], + "jwtVerifiers": [], + "authConfigs": [ + { + "_loc": { + "tenant": "default", + "teams": [ + "default" + ] + }, + "type": "oauth2", + "id": "CfVNaKQPdU5IqbxqemoFyjhWNYj4EvluLWtl2eQNqDYP3seIxP3MYCHWBZUusl6X", + "name": "auth0", + "desc": "new oauth config", + "clientSideSessionEnabled": true, + "sessionMaxAge": 86400, + "userValidators": [], + "clientId": "ty4FtdvLumT9iFRcQ5Oy6dqmeCNNFJFM", + "clientSecret": "Ts0vcdejBbt8Co32YDZPQNSHvXEhUHtW0ncpYMylqOcf7cM_-qDtNf2hWakb7vQL", + "authorizeUrl": "https://komainu-demo.eu.auth0.com/authorize", + "tokenUrl": "https://komainu-demo.eu.auth0.com/oauth/token", + "userInfoUrl": "https://komainu-demo.eu.auth0.com/userinfo", + "introspectionUrl": "http://localhost:8082/token/introspect", + "loginUrl": "https://komainu-demo.eu.auth0.com/authorize", + "logoutUrl": "https://komainu-demo.eu.auth0.com/oidc/logout", + "scope": "openid profile offline_access name given_name family_name nickname email email_verified picture created_at identities phone address", + "claims": "", + "useCookie": false, + "useJson": false, + "pkce": { + "enabled": false, + "algorithm": "S256" + }, + "noWildcardRedirectURI": false, + "readProfileFromToken": false, + "accessTokenField": "access_token", + "jwtVerifier": { + "type": "JWKSAlgoSettings", + "url": "https://komainu-demo.eu.auth0.com/.well-known/jwks.json", + "timeout": 2000, + "headers": {}, + "ttl": 3600000, + "kty": "RSA", + "proxy": null, + "tls_config": { + "certs": [], + "trustedCerts": [], + "mtls": false, + "loose": false, + "trustAll": false + }, + "mtlsConfig": { + "certs": [], + "trustedCerts": [], + "mtls": false, + "loose": false, + "trustAll": false + } + }, + "nameField": "name", + "emailField": "email", + "apiKeyMetaField": "apkMeta", + "apiKeyTagsField": "apkTags", + "otoroshiDataField": "daikokuAdmin", + "callbackUrl": "http://privateapps.oto.tools:8080/privateapps/generic/callback", + "oidConfig": "https://komainu-demo.eu.auth0.com/.well-known/openid-configuration", + "mtlsConfig": { + "certs": [], + "trustedCerts": [], + "mtls": false, + "loose": false, + "trustAll": false + }, + "proxy": null, + "extraMetadata": {}, + "metadata": {}, + "tags": [], + "refreshTokens": false, + "sessionCookieValues": { + "httpOnly": true, + "secure": false + }, + "superAdmins": false, + "rightsOverride": {}, + "dataOverride": { + "quentin.aubert@maif.fr": { + "daikokuAdmin": true + } + }, + "otoroshiRightsField": "otoroshi_rights", + "adminEntityValidatorsOverride": {} + } + ], + "certificates": [ + { + "_loc": { + "tenant": "default", + "teams": [ + "default" + ] + }, + "id": "otoroshi-client", + "domain": "Otoroshi Default Client Certificate", + "name": "Otoroshi Default Client Certificate", + "description": "Otoroshi client certificate (auto-generated)", + "chain": "-----BEGIN CERTIFICATE-----\nMIIEfzCCA2egAwIBAgIIZ3nGcsq30SswDQYJKoZIhvcNAQELBQAwajE1MDMGA1UE\nAwwsT3Rvcm9zaGkgRGVmYXVsdCBJbnRlcm1lZGlhdGUgQ0EgQ2VydGlmaWNhdGUx\nHjAcBgNVBAsMFU90b3Jvc2hpIENlcnRpZmljYXRlczERMA8GA1UECgwIT3Rvcm9z\naGkwHhcNMjQwNTEzMTI0MDMxWhcNMjUwNTEzMTI0MDMxWjBhMSwwKgYDVQQDDCNP\ndG9yb3NoaSBEZWZhdWx0IENsaWVudCBDZXJ0aWZpY2F0ZTEeMBwGA1UECwwVT3Rv\ncm9zaGkgQ2VydGlmaWNhdGVzMREwDwYDVQQKDAhPdG9yb3NoaTCCASIwDQYJKoZI\nhvcNAQEBBQADggEPADCCAQoCggEBAPyu8zPrlQM6SWKuvrD19Zm4G2DB88NC9LWr\nW/zxKQhPlxnKo4JNfgSdWvPMosX6JL7cAwxEYQmwKlXxmCH5PFygf9RDGoZHvz7I\nFmBxN95hqQ08WMT3ltPegeStP7HTFLMIrzy4EaYCxRqCDhPBKPjYSm6dzg9ifWtz\nGnoUQVWx7tBLYx2XwkBozxNpNi7ZrOexgyrE66Mg2JrM37y7YPiv4YrwW5q34tZf\njZ4IivT6KX2FLcdhlSoEUFCdhBxDF2fmiWklOoVjBL1uPSz26+usq/1ay8tfpHP5\nfKYVrh7hEYyeDrrLYMOGs7TmVcRDAPKbr+RahGZqOSlevlEwXzUCAwEAAaOCATAw\nggEsMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsG\nAQUFBwMCBggrBgEFBQcDATAfBgNVHSMEGDAWgBS4QwI1e7QM+El9SEMtz3eNaEIw\naDAJBgNVHREEAjAAMIHABggrBgEFBQcBAQSBszCBsDBkBggrBgEFBQcwAoZYaHR0\ncDovL290b3Jvc2hpLWFwaS5vdG8udG9vbHM6OTk5OS8ud2VsbC1rbm93bi9vdG9y\nb3NoaS9jZXJ0aWZpY2F0ZXMvNzQ1NjIwODg1NDQxODE4MjQ0MzBIBggrBgEFBQcw\nAYY8aHR0cDovL290b3Jvc2hpLWFwaS5vdG8udG9vbHM6OTk5OS8ud2VsbC1rbm93\nbi9vdG9yb3NoaS9vY3NwMA0GCSqGSIb3DQEBCwUAA4IBAQBY57t8UyB2QoGlnzdD\nvFtVrSqNqqtTXxzy8XWHZ8OQVeKyIYq8+Q6Vw/IkhqxL1JPFWHD8ATdwziX/DEkt\n0SUwCSvDFxD6xahB+Uh8EfVvqWzqpyXVabpPI/FpC8btbj12wfh5OAz+UpdpvbAs\nzjfeKtIGHWDb/GSa1Rp9dFJQRRAaHWBFV4kLNoNAfGL6wnTze80e9F1wrfUi0263\noach/LIWm9SWaSZSZQ87LELLp5u5ZqZHyx4Fv585Fxp0hBreB0Z1GEjTqTsFjnag\n8eIe+i3qGPxBFDMEeZ9B+66OaMrWPDwIx0Ikfu4y4Ko3ijqIJp+Mw9l1fiE23ROf\ndVjs\n-----END CERTIFICATE-----\n\n-----BEGIN CERTIFICATE-----\nMIIDtTCCAp2gAwIBAgIJAJI/0IpO0bFHMA0GCSqGSIb3DQEBCwUAMGIxLTArBgNV\nBAMMJE90b3Jvc2hpIERlZmF1bHQgUm9vdCBDQSBDZXJ0aWZpY2F0ZTEeMBwGA1UE\nCwwVT3Rvcm9zaGkgQ2VydGlmaWNhdGVzMREwDwYDVQQKDAhPdG9yb3NoaTAeFw0y\nNDA1MTMxMjQwMzBaFw0zNDA1MTExMjQwMzBaMGoxNTAzBgNVBAMMLE90b3Jvc2hp\nIERlZmF1bHQgSW50ZXJtZWRpYXRlIENBIENlcnRpZmljYXRlMR4wHAYDVQQLDBVP\ndG9yb3NoaSBDZXJ0aWZpY2F0ZXMxETAPBgNVBAoMCE90b3Jvc2hpMIIBIjANBgkq\nhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtnT7t0ZsEcW4nVg3vGfLx0en8smZ5rcs\nSP8UWvQGXrbPs0vX/tDty2IabSIw6YrddRfBVoPr6IgFn6ZkVx6OxVPL2siftsUR\n0ha77Ie2wbMcEot3utfBllypG6lUBxvQvTBpJUmtHbI/ZSK9z1PVzvyetIPlN2ow\nvBVGeuxLuOINNIGVv1ByyPx2lTIo2kkhuU+XUufnF9CEYDRaMt7CgJjeE1SWs6nR\nwqA8V0BJnUHkcw5QPqTqPeetN3UUHw+hvzJjWK9wRK7o2oYGDxPFLv19qu8apOyD\nnlTkXtSRQqS7i5K6WPeHFEu8qEaUqgFq4z4SelTpjRAQjkFGPG6YowIDAQABo2Yw\nZDASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAfBgNVHSMEGDAW\ngBRIIm+c4aGHU0/jJ+HiwRZKdZ8NOjAdBgNVHQ4EFgQUuEMCNXu0DPhJfUhDLc93\njWhCMGgwDQYJKoZIhvcNAQELBQADggEBAE8L95TZRPx6JZ4FLRAeFvalBOp+c9C+\n1w0yubOVGVrHGdCvbu5VE7c5OhUDXNU/lC86VF7d5UhW07XnmiKXyZGxbVY/MMnm\nDQbCRd2yXD9g7KQIFQTWURBj516nh1ugR4k3hEcjPTAtcMMbLD2xga6cAkA7NWwX\nODSjNLSAvefzJLpGAMjISJeLKnTCHqdpgoWKroZ8RzVeOj2V45CnIGocX852291N\nnKqsdfHCxcxx34wp1km7aiJqYG1vEaXw1e7sW8E57pVcSmOcqXHwbP7Ebk729PI8\nnoPerKLn/AN8iL34t3CtkIpBQmQsVyMfhqpY8qBcjZOjYgmTaaNwe1E=\n-----END CERTIFICATE-----\n\n-----BEGIN CERTIFICATE-----\nMIIEcDCCA1igAwIBAgIILMmQ5szf0skwDQYJKoZIhvcNAQELBQAwYjEtMCsGA1UE\nAwwkT3Rvcm9zaGkgRGVmYXVsdCBSb290IENBIENlcnRpZmljYXRlMR4wHAYDVQQL\nDBVPdG9yb3NoaSBDZXJ0aWZpY2F0ZXMxETAPBgNVBAoMCE90b3Jvc2hpMB4XDTI0\nMDUxMzEyNDAzMFoXDTM0MDUxMTEyNDAzMFowYjEtMCsGA1UEAwwkT3Rvcm9zaGkg\nRGVmYXVsdCBSb290IENBIENlcnRpZmljYXRlMR4wHAYDVQQLDBVPdG9yb3NoaSBD\nZXJ0aWZpY2F0ZXMxETAPBgNVBAoMCE90b3Jvc2hpMIIBIjANBgkqhkiG9w0BAQEF\nAAOCAQ8AMIIBCgKCAQEAunpALvuym5JJNFD5rVDBEUVWn+cHYpBsPDhwpezuVgTF\nm/vzAlHBNKuBd/JhKyfekR/vByesxZVuKUFi6whbvdYaENQqWzqNtCHPhB8zcgoF\nQnXMGQqSIlk7NL8fY4r3d5+rKZ535S5zrHunibby4tp4Bt9Z6miP7/Kds2+hrPgt\nWt+lq+OHeHo8brn9Y9jXArO112U3SpaqD+kuRmWa4CsSdVUHSMHSzUt2v7zV/m1k\nDl0eJqAi5q9GN4wrG3ES6zC66caZFLk2o3/uN6zakln0xM2LjjdHH/7D83qF9Itx\nh091jZ9XP/+71accjrEs3InVuKhwmb4gNlFkeJzO+QIDAQABo4IBKDCCASQwDwYD\nVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHwYDVR0jBBgwFoAUSCJvnOGh\nh1NP4yfh4sEWSnWfDTowHQYDVR0OBBYEFEgib5zhoYdTT+Mn4eLBFkp1nw06MIHA\nBggrBgEFBQcBAQSBszCBsDBkBggrBgEFBQcwAoZYaHR0cDovL290b3Jvc2hpLWFw\naS5vdG8udG9vbHM6OTk5OS8ud2VsbC1rbm93bi9vdG9yb3NoaS9jZXJ0aWZpY2F0\nZXMvMzIyNzI2OTkyODk0MTc2OTQxNzBIBggrBgEFBQcwAYY8aHR0cDovL290b3Jv\nc2hpLWFwaS5vdG8udG9vbHM6OTk5OS8ud2VsbC1rbm93bi9vdG9yb3NoaS9vY3Nw\nMA0GCSqGSIb3DQEBCwUAA4IBAQCWAAxgeZyFyay78wYqm14vsaziPd16xSiTz57X\nxFzQ0g8Ysc+Z/9FwJmAiSr2tdksKSk2AETrNpGchj30xVknKcpuOtmcyOUZl7QBk\nVWJHDcUudeBPJssMNauTfamzaDbekpRR3rNkzJBK8eg7p1kzokPku4vqVZ3crPgV\nC9lZzkOGTiBmRxUuUV+RupQg7EfMxT9FdIuAycxLdhvZQ3k3fWHo73UOK3PKBhAs\nusOxUTUDWRCSpzihNoAwulc5GwnLRMaNRQeUlJLADQfdZ5k6qbPdmim7sElGU7JK\ndd+6mZ0gYv8vZOUmiSUNTsY2TUkM3TizSmI5IIfSZcxr46GQ\n-----END CERTIFICATE-----\n", "caRef": null, - "privateKey": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDoAP3rKrDf5cyd\njJ/9tkoVOarUZNl6VcZG3QyaUU+Eb+PVZu4lNJTlO58oTMgiml8w3WzzBxliuXqV\nLHdxNp/y9JG6o90NN5Xkg8drZD0PYgTYgTXY7smzaO2wPhvBprSPs74ysm2tfZsx\naXjVh7jIi9pW4fAaDVFhigRbkcLyfiFT0a65fUTE+kJ58bUUz3OaFGfyZs5Je08M\nvy4iWTKwOZ5A1+FeL+8s0EduV626APh614P6qJoI8Px+ln4323Z7ef2OmGwrdI9u\nTwsJww8jK0E+Ey8Voz3JqFQiZuiIE6aomFRjrw1W3vKmhFpD8B/9pwDQZzHBvXht\nvjFYywclAgMBAAECggEALxWYcsQovl/gXW8fPzniIhmggldvTaUfxtzwxFbRKQMe\nPX6UwUdBGp61h9PBYEhtHjkp9c6Qh9k1uBWcrgTUhFW/R54e7CJGUOivratbuwX9\nDsV2XK7lj3wxlO2d63i+N1XWuWo92RJfTzFlHQzc4xKC/tP+ywK96+o7RSw5YMX2\nX/C9q/C6eyECr6Z8Chn2nZGBjBViC5CHsQ444uRWGCZ8EBWewSu8hFZH57Wr0Dsb\nEVUQKrocVgEIGoDuYhcajRG11eTEchqzSN5umPdb6ae336jOKg0tSxOeBN0XgqU8\nyZV+K/XMyeZQ37Vxp2K7dWQ0F6snfVxxUJthWFLaFwKBgQD6TDC/0H0KxH2F129L\ncQE8/Euia622SKiXPGA1fkzzDf5BMIaeYnUEVLYCqDOiUcQVOKWr0bv4CTqZm1xB\nqAoakC4BY9PQm8Fyjg84ZjjoVXrL0tX4D05uJdziZlad7kHglqCaPW+1DVUggylx\n6llee8Bl7KwebsXxHZtJzNd8mwKBgQDtShtfly6fuiXWoottKRWFUX6vpl35Xzrr\nIq8jRYd69Fb6vLGPOe2j6A2PimonGydQtchNIV1JXz1lI4022sH/NfFcSVwY7oTm\n5TOLKmNDuPAl0tqiF1Rt27DcnaQYN1rHKT30utGSelkF/+8MP+fKWWayX7VHargx\nDgvxeXNnPwKBgAXhrHOVbprGKzzt5wuayDCqrQxQA3D5HB0RqiEXqq87cPIPNdqa\n7a0AYJaHZU83JKhrAFmdQBGABgR+Bhj03efp42rc8alBP7Z1aB4nKMcs2wnXFngR\nxAIKmbooNXjYlGu2nmeBchKLEakF6/9lRlxWBBthCFpeuAAGdhOx4I5pAoGBAIPx\nFgF0Ffe40PtkzHQ5SP8ljnTq5AwaeK9wTMQnECs9G/Q14eFLC6l9CbVL+LSf8598\nRNEmgTGgJIpNV2MuBnr7KqbvH6e5UOr2fU+flSx99AKEGQ/SOx8O7mvtt+XDOdXO\nR+YBzzBMmZ2tbuV3z1IDnggHEcyRObQFJHzz1EiPAoGASOVzpB5awyWQwQfEBw+y\nPjZB2nSwX0dQL9ZqLFcIw1rSoO3IMDA7d1LQuU5cUdnQfIN1yIXtyXw6jQgwQbHN\nT0XZR3WCkuSb6Qkxsnt1E88D7PatZbW3kyTisChBiIwQn9IRaao8Keh69LFDNgYG\nn0XHUMu1fp9ypAJJZYQhyk8=\n-----END PRIVATE KEY-----\n", - "selfSigned": true, - "ca": true, + "privateKey": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD8rvMz65UDOkli\nrr6w9fWZuBtgwfPDQvS1q1v88SkIT5cZyqOCTX4EnVrzzKLF+iS+3AMMRGEJsCpV\n8Zgh+TxcoH/UQxqGR78+yBZgcTfeYakNPFjE95bT3oHkrT+x0xSzCK88uBGmAsUa\ngg4TwSj42Epunc4PYn1rcxp6FEFVse7QS2Mdl8JAaM8TaTYu2aznsYMqxOujINia\nzN+8u2D4r+GK8Fuat+LWX42eCIr0+il9hS3HYZUqBFBQnYQcQxdn5olpJTqFYwS9\nbj0s9uvrrKv9WsvLX6Rz+XymFa4e4RGMng66y2DDhrO05lXEQwDym6/kWoRmajkp\nXr5RMF81AgMBAAECggEAKoV0Vr3jpxXlaI5Yd6YETPhu3wL0dtzrGjE6ex/IFGZ0\nWno/trs8pCEov5MlpyQ3j8itWKCfJca38RuFSt5KupmluT4LliI3vCRmo5cZ3DnZ\n/Wi01XyGrKOqfyVqbQ3E03X4K0MccS/RJGtTXB6quumG6QN0PCGKUo0dnQKQlg9U\nM5YHt4YPspMUOhnxht0fkqztSdacCBQCMpW+6D7xq5Y0gqfswHv1SqhSKor3Uqa2\nXWEsHJCmhA5s6Q3g9ywmZ7q78WXh9w7EF+ay4fpfjpj53YiaBWp4UrKkrHqHoWvI\ntDOX6jpnNDyz2dt2Z4cI9Aw2V2VmQBUwfB0o911bhwKBgQD+1CUAKiAmwi8Ubyt+\nY/PjSWMMo8qSs097lytyZEZWq4ebRVZ0lQ2lLxPSwJoXMSZY77llgYgHyDRIgN3z\nWjsPlwKiZIW9mjE5PIXpdiExg4ehRhVW/1j8X9ck+Wd+8IDKvq5F0Iy4I+XU/soG\nbY8Obj5P6xQFuK0SxOdqddYl7wKBgQD92Ef31ddsfvwvfVI8IcpaDUBE81wfMO2e\nRjGooXPetRsuKzi5ntQQQjPQPzYeThqc2IE/NWjCrGJo6/3GVNYw+YVIrpP3UfCN\nbeDGvXTEs8g6KYZAvgpmXCPvfl3Xz2Rk14Q8+2opIArnK8SVXwybti/Mr/blPm+Y\nW/SRt8KRGwKBgQDPXyxNGfrYD6XoZx9p10UdbRchrhinkSvZU4eTq25iEWxlOuPE\nM5fLGKh1FS9QODsQnOSuad6dFpsVdWkpJAB0C1ulc1Ayn0v3W/1No0gX1wWEhFfR\nWDK+DfAyspM6hdUJKGsBfVlGcFAUKNOWr4l/IM+skYzVqxeM8pev1vzSGQKBgAse\nyXBS/t/y5CtUayDVPh8Zy4H5w0ReiVpQquY/RGsyNeGc5RxEl/eqXsi1rDJBq6rJ\nVUgml9NHZ/w+slxY5RU6KRuZAkRoY4hekuVB8XaYhaH+pbH6YUxApxZby9KjdDty\nsYlWXj00+SVNZoxAoLXFkmx+HOvIA22CQcGMckqXAoGAA6DCoWdH0OkdQF5/a4be\nu6OqhwvlEXFn/7uV9a6UAXo0k+OQdNfZFtCpngguzd31pLj263fh+XzFGb+z2fc/\nHpolKx2FfV3oU840j564sLXPsaFCy85B6+Mz3Goxpeqw36OHnYpTYrMR3bWApvy7\njKcN00mIjBZ3kBa2/pBAGZ0=\n-----END PRIVATE KEY-----\n", + "selfSigned": false, + "ca": false, "valid": true, "exposed": true, "revoked": false, "autoRenew": true, "letsEncrypt": false, - "subject": "OU=Otoroshi Certificates, O=Otoroshi, CN=Otoroshi Default Root CA Certificate", - "from": 1669036673000, - "to": 1984396673000, - "client": false, + "subject": "OU=Otoroshi Certificates, O=Otoroshi, CN=Otoroshi Default Client Certificate", + "from": 1715604031000, + "to": 1747140031000, + "client": true, "keypair": false, "sans": [], - "certType": "ca", + "certType": "client", "password": null, "metadata": { - "csr": "{\"hosts\":[],\"key\":{\"algo\":\"rsa\",\"size\":2048},\"name\":{},\"subject\":\"CN=Otoroshi Default Root CA Certificate, OU=Otoroshi Certificates, O=Otoroshi\",\"client\":false,\"ca\":true,\"duration\":315360000000,\"signatureAlg\":\"SHA256WithRSAEncryption\",\"digestAlg\":\"SHA-256\"}" + "csr": "{\"hosts\":[],\"key\":{\"algo\":\"rsa\",\"size\":2048},\"name\":{},\"subject\":\"CN=Otoroshi Default Client Certificate, OU=Otoroshi Certificates, O=Otoroshi\",\"client\":true,\"ca\":false,\"duration\":31536000000,\"signatureAlg\":\"SHA256WithRSAEncryption\",\"digestAlg\":\"SHA-256\"}" + }, + "tags": [] + }, + { + "_loc": { + "tenant": "default", + "teams": [ + "default" + ] + }, + "id": "otoroshi-wildcard", + "domain": "*.oto.tools", + "name": "Otoroshi Default Wildcard Certificate", + "description": "Otoroshi wildcard certificate (auto-generated)", + "chain": "-----BEGIN CERTIFICATE-----\nMIIEnDCCA4SgAwIBAgIJAPVM7CTRvwUbMA0GCSqGSIb3DQEBCwUAMGoxNTAzBgNV\nBAMMLE90b3Jvc2hpIERlZmF1bHQgSW50ZXJtZWRpYXRlIENBIENlcnRpZmljYXRl\nMR4wHAYDVQQLDBVPdG9yb3NoaSBDZXJ0aWZpY2F0ZXMxETAPBgNVBAoMCE90b3Jv\nc2hpMB4XDTI0MDUxMzEyNDAzMVoXDTI1MDUxMzEyNDAzMVoweTEUMBIGA1UEAwwL\nKi5vdG8udG9vbHMxLjAsBgNVBAQMJU90b3Jvc2hpIERlZmF1bHQgV2lsZGNhcmQg\nQ2VydGlmaWNhdGUxHjAcBgNVBAsMFU90b3Jvc2hpIENlcnRpZmljYXRlczERMA8G\nA1UECgwIT3Rvcm9zaGkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDS\nZkJUIXVmuF+wBMl8csvMYfVt9+e1Qyl8JiUu3pIwu1d54/FJWyGM22nx+yjFutA9\nAeoLr2Pt931E4lhoSKdYAdc9pDZH7hRGou9GwLl47DisX6r1xU5e0JZ7x8xXhSz9\nme76AiLhG/WcGpEXXU2l+Cog8iFgWP9liY8ohdxCKiqPu9a2Csw3ZElX1c/cHWFB\ne4lEXCIlRNCJO+psSHOUkQDjpo+pldW5kYvg9rIE00Z2zi4kTrDYuwh85g86D3F8\nckrOlsbXb2e9KRn1nsceDy3uKaMh99K9CvM97oqbAoY/+G7PHi1k3kR37AktA/+L\nbUKV/GvF87ZSM1E309QXAgMBAAGjggE0MIIBMDAMBgNVHRMBAf8EAjAAMA4GA1Ud\nDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBS4QwI1\ne7QM+El9SEMtz3eNaEIwaDAWBgNVHREEDzANggsqLm90by50b29sczCBwQYIKwYB\nBQUHAQEEgbQwgbEwZQYIKwYBBQUHMAKGWWh0dHA6Ly9vdG9yb3NoaS1hcGkub3Rv\nLnRvb2xzOjk5OTkvLndlbGwta25vd24vb3Rvcm9zaGkvY2VydGlmaWNhdGVzLzE3\nNjc1NzYyMjgwNDA0Mjg4Nzk1MEgGCCsGAQUFBzABhjxodHRwOi8vb3Rvcm9zaGkt\nYXBpLm90by50b29sczo5OTk5Ly53ZWxsLWtub3duL290b3Jvc2hpL29jc3AwDQYJ\nKoZIhvcNAQELBQADggEBAJaxXDhiGPoJLvGEWf21jt1+UZzlylj5fqnA1+k9kc+k\ne0ej5HZUrAx6iX+pz1gQ8DY4TlEEfaRITiOLAQ9yOZOKpmFSjpbxm+qaWPApl9Q7\nIkUJkctS+wMAjCVDn5hwkhwGGqRt70KIolDZ+3LB2l9vYNF23KePjDe9GjinGfWE\nrFgNQBi4C/CBjFLkPC3z3oJW0oDKlVv0D7KXwkVNeyMs0DbHCKkM14EGiBNsMevI\n+1OKR9NUCEXe49DwbnPFxiBKNMLlTUXD9E1i/NU74vpt6ys9ot5F8wbmQcDr0k8j\n5v+oFSOOPO7FY24fo7WFIt3LUb+b7OA3OlOozK/hX0g=\n-----END CERTIFICATE-----\n\n-----BEGIN CERTIFICATE-----\nMIIDtTCCAp2gAwIBAgIJAJI/0IpO0bFHMA0GCSqGSIb3DQEBCwUAMGIxLTArBgNV\nBAMMJE90b3Jvc2hpIERlZmF1bHQgUm9vdCBDQSBDZXJ0aWZpY2F0ZTEeMBwGA1UE\nCwwVT3Rvcm9zaGkgQ2VydGlmaWNhdGVzMREwDwYDVQQKDAhPdG9yb3NoaTAeFw0y\nNDA1MTMxMjQwMzBaFw0zNDA1MTExMjQwMzBaMGoxNTAzBgNVBAMMLE90b3Jvc2hp\nIERlZmF1bHQgSW50ZXJtZWRpYXRlIENBIENlcnRpZmljYXRlMR4wHAYDVQQLDBVP\ndG9yb3NoaSBDZXJ0aWZpY2F0ZXMxETAPBgNVBAoMCE90b3Jvc2hpMIIBIjANBgkq\nhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtnT7t0ZsEcW4nVg3vGfLx0en8smZ5rcs\nSP8UWvQGXrbPs0vX/tDty2IabSIw6YrddRfBVoPr6IgFn6ZkVx6OxVPL2siftsUR\n0ha77Ie2wbMcEot3utfBllypG6lUBxvQvTBpJUmtHbI/ZSK9z1PVzvyetIPlN2ow\nvBVGeuxLuOINNIGVv1ByyPx2lTIo2kkhuU+XUufnF9CEYDRaMt7CgJjeE1SWs6nR\nwqA8V0BJnUHkcw5QPqTqPeetN3UUHw+hvzJjWK9wRK7o2oYGDxPFLv19qu8apOyD\nnlTkXtSRQqS7i5K6WPeHFEu8qEaUqgFq4z4SelTpjRAQjkFGPG6YowIDAQABo2Yw\nZDASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAfBgNVHSMEGDAW\ngBRIIm+c4aGHU0/jJ+HiwRZKdZ8NOjAdBgNVHQ4EFgQUuEMCNXu0DPhJfUhDLc93\njWhCMGgwDQYJKoZIhvcNAQELBQADggEBAE8L95TZRPx6JZ4FLRAeFvalBOp+c9C+\n1w0yubOVGVrHGdCvbu5VE7c5OhUDXNU/lC86VF7d5UhW07XnmiKXyZGxbVY/MMnm\nDQbCRd2yXD9g7KQIFQTWURBj516nh1ugR4k3hEcjPTAtcMMbLD2xga6cAkA7NWwX\nODSjNLSAvefzJLpGAMjISJeLKnTCHqdpgoWKroZ8RzVeOj2V45CnIGocX852291N\nnKqsdfHCxcxx34wp1km7aiJqYG1vEaXw1e7sW8E57pVcSmOcqXHwbP7Ebk729PI8\nnoPerKLn/AN8iL34t3CtkIpBQmQsVyMfhqpY8qBcjZOjYgmTaaNwe1E=\n-----END CERTIFICATE-----\n\n-----BEGIN CERTIFICATE-----\nMIIEcDCCA1igAwIBAgIILMmQ5szf0skwDQYJKoZIhvcNAQELBQAwYjEtMCsGA1UE\nAwwkT3Rvcm9zaGkgRGVmYXVsdCBSb290IENBIENlcnRpZmljYXRlMR4wHAYDVQQL\nDBVPdG9yb3NoaSBDZXJ0aWZpY2F0ZXMxETAPBgNVBAoMCE90b3Jvc2hpMB4XDTI0\nMDUxMzEyNDAzMFoXDTM0MDUxMTEyNDAzMFowYjEtMCsGA1UEAwwkT3Rvcm9zaGkg\nRGVmYXVsdCBSb290IENBIENlcnRpZmljYXRlMR4wHAYDVQQLDBVPdG9yb3NoaSBD\nZXJ0aWZpY2F0ZXMxETAPBgNVBAoMCE90b3Jvc2hpMIIBIjANBgkqhkiG9w0BAQEF\nAAOCAQ8AMIIBCgKCAQEAunpALvuym5JJNFD5rVDBEUVWn+cHYpBsPDhwpezuVgTF\nm/vzAlHBNKuBd/JhKyfekR/vByesxZVuKUFi6whbvdYaENQqWzqNtCHPhB8zcgoF\nQnXMGQqSIlk7NL8fY4r3d5+rKZ535S5zrHunibby4tp4Bt9Z6miP7/Kds2+hrPgt\nWt+lq+OHeHo8brn9Y9jXArO112U3SpaqD+kuRmWa4CsSdVUHSMHSzUt2v7zV/m1k\nDl0eJqAi5q9GN4wrG3ES6zC66caZFLk2o3/uN6zakln0xM2LjjdHH/7D83qF9Itx\nh091jZ9XP/+71accjrEs3InVuKhwmb4gNlFkeJzO+QIDAQABo4IBKDCCASQwDwYD\nVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHwYDVR0jBBgwFoAUSCJvnOGh\nh1NP4yfh4sEWSnWfDTowHQYDVR0OBBYEFEgib5zhoYdTT+Mn4eLBFkp1nw06MIHA\nBggrBgEFBQcBAQSBszCBsDBkBggrBgEFBQcwAoZYaHR0cDovL290b3Jvc2hpLWFw\naS5vdG8udG9vbHM6OTk5OS8ud2VsbC1rbm93bi9vdG9yb3NoaS9jZXJ0aWZpY2F0\nZXMvMzIyNzI2OTkyODk0MTc2OTQxNzBIBggrBgEFBQcwAYY8aHR0cDovL290b3Jv\nc2hpLWFwaS5vdG8udG9vbHM6OTk5OS8ud2VsbC1rbm93bi9vdG9yb3NoaS9vY3Nw\nMA0GCSqGSIb3DQEBCwUAA4IBAQCWAAxgeZyFyay78wYqm14vsaziPd16xSiTz57X\nxFzQ0g8Ysc+Z/9FwJmAiSr2tdksKSk2AETrNpGchj30xVknKcpuOtmcyOUZl7QBk\nVWJHDcUudeBPJssMNauTfamzaDbekpRR3rNkzJBK8eg7p1kzokPku4vqVZ3crPgV\nC9lZzkOGTiBmRxUuUV+RupQg7EfMxT9FdIuAycxLdhvZQ3k3fWHo73UOK3PKBhAs\nusOxUTUDWRCSpzihNoAwulc5GwnLRMaNRQeUlJLADQfdZ5k6qbPdmim7sElGU7JK\ndd+6mZ0gYv8vZOUmiSUNTsY2TUkM3TizSmI5IIfSZcxr46GQ\n-----END CERTIFICATE-----\n", + "caRef": null, + "privateKey": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDSZkJUIXVmuF+w\nBMl8csvMYfVt9+e1Qyl8JiUu3pIwu1d54/FJWyGM22nx+yjFutA9AeoLr2Pt931E\n4lhoSKdYAdc9pDZH7hRGou9GwLl47DisX6r1xU5e0JZ7x8xXhSz9me76AiLhG/Wc\nGpEXXU2l+Cog8iFgWP9liY8ohdxCKiqPu9a2Csw3ZElX1c/cHWFBe4lEXCIlRNCJ\nO+psSHOUkQDjpo+pldW5kYvg9rIE00Z2zi4kTrDYuwh85g86D3F8ckrOlsbXb2e9\nKRn1nsceDy3uKaMh99K9CvM97oqbAoY/+G7PHi1k3kR37AktA/+LbUKV/GvF87ZS\nM1E309QXAgMBAAECggEAAOjgKfv0mtKXI532t/BN3QJpal6w7fogFUFGX4wwZW+B\nVGjmDbRq9ZZfkMyCnvhiCy2hVFQ72ys4jzEJVlXK0mTdt6/AKby/nMSSNHSbfWwT\nJiKayodZgaKDRrcsfD3LEhjPtZGQ+yaWQkh3yfCu3ncS5jkC0I+fkzV1greLqu5f\ni5qIhrbkKYAzab/f9ywgZEBaEf9EP35673nmZjj8AvHRXCEXfyw5lYPH5iJNectO\nzxLKKv4kRpBvG/OjVzMseoRrr0HqhClldRDKwS/svi5xICET9cpJOwwfYWd7SUY8\nWZVis6DXz4rkHAeaZsEfWG72OPH/VoaR1tMqSc0LsQKBgQD06zFit0vdRTmmaFqG\nLMI0fQgBootj8ZNAlbo9Dmyv5ILNjwSWTNjVGgrLf8PnnUbNDEip58RTg7sj9Wfx\nn26LsDwJzatrJ5BKBVTvXX4vUTUsqlx7gjBQk2WPn3RrX2TxOvNn5Wf34Mlme3IJ\nsef0h22WfN3JIquJPMvpPCEABwKBgQDb6z3b0ipftahQOqlFW/P0Qp3nL19jDPB3\npafV+p1/dMgKUhmNpMyDpN5VC0Ut3bbLSZbzG6+EB1iPgWnXYU/kfMfl+hpCM9Lg\n74BoDIveMRH6H5iJ+HcQZGuqargSZzrf2mCSa2++RxDIAaezxK/4rZ+uDlBJqJol\nXZPGV8BncQKBgG6kCQ1VTJpK+X4pSiuu8JSRnxpN+bZOnXzhpt6KM6NKEaUW5LWZ\nRaMPPq0rrK5nKGLdF75ZiyFHMzVNWYWXxnKoTJcJKFUh7FbkDbiMfcKWZvUkiN7q\nszTTQWAeYpNW5tzrCqZ2xf7+tzzqq1uh2O1oeKMxCWghl2hJ5Lwag7XlAoGBAKyQ\nZYS04B0j7QOvVB2hrsaO81px2LZfc91etlj4g1d86m72xyhXFUeDOhafdlQsMoBH\nqVPv+IRwQzSwBr5DlK4kn7K80pf6FFH+fNKobaQW+xU6b7ZrqfnJ4pXhkbiXGiKH\nN9oMxa0YFHnsmow8HRzUGIU6pBN4YiRJzmgHeWlxAoGBAJH5hmzjwVuqDTK90fPo\nUZy0ro76cC4U2onFxUvbP9KibuflJuRbxmDU0ChBtEChq/6k1Mh3U9zDkXnMG4/k\n6S5DQz8TnF5Llt8XGsm+gJQaG3lJavfouqd//jljHA8DlfbZp90yNukTKtDSHCLl\nSdDoLMjJuNHyzW63P93wiwZI\n-----END PRIVATE KEY-----\n", + "selfSigned": false, + "ca": false, + "valid": true, + "exposed": true, + "revoked": false, + "autoRenew": true, + "letsEncrypt": false, + "subject": "SURNAME=Otoroshi Default Wildcard Certificate, OU=Otoroshi Certificates, O=Otoroshi, CN=*.oto.tools", + "from": 1715604031000, + "to": 1747140031000, + "client": false, + "keypair": false, + "sans": [ + "*.oto.tools" + ], + "certType": "certificate", + "password": null, + "metadata": { + "csr": "{\"hosts\":[\"*.oto.tools\"],\"key\":{\"algo\":\"rsa\",\"size\":2048},\"name\":{},\"subject\":\"CN=*.oto.tools, SN=Otoroshi Default Wildcard Certificate, OU=Otoroshi Certificates, O=Otoroshi\",\"client\":false,\"ca\":false,\"duration\":31536000000,\"signatureAlg\":\"SHA256WithRSAEncryption\",\"digestAlg\":\"SHA-256\"}" }, "tags": [] }, @@ -1022,9 +1489,9 @@ "domain": "Otoroshi Default Intermediate CA Certificate", "name": "Otoroshi Default Intermediate CA Certificate", "description": "Otoroshi intermediate CA (auto-generated)", - "chain": "-----BEGIN CERTIFICATE-----\nMIIDtDCCApygAwIBAgIISNRU1CRzbwYwDQYJKoZIhvcNAQELBQAwYjEtMCsGA1UE\nAwwkT3Rvcm9zaGkgRGVmYXVsdCBSb290IENBIENlcnRpZmljYXRlMR4wHAYDVQQL\nDBVPdG9yb3NoaSBDZXJ0aWZpY2F0ZXMxETAPBgNVBAoMCE90b3Jvc2hpMB4XDTIy\nMTEyMTEzMTc1NFoXDTMyMTExODEzMTc1NFowajE1MDMGA1UEAwwsT3Rvcm9zaGkg\nRGVmYXVsdCBJbnRlcm1lZGlhdGUgQ0EgQ2VydGlmaWNhdGUxHjAcBgNVBAsMFU90\nb3Jvc2hpIENlcnRpZmljYXRlczERMA8GA1UECgwIT3Rvcm9zaGkwggEiMA0GCSqG\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCyyEf9RZVWoRsBZi5Sc9yLUBWMn6KhCUkh\newvEqeLuOXinN/w21wD1xy2fBkABmhXoDb2xbc1vhdskrUWQdqYfG275XzyJqGp5\n2dA5Q/L/3kPS2AAVTDkaGUXvUpObv7r7JzLolq2MRLhJIL2V04bI6sfe1npk8Imp\nwcaFVoADhliB2Jx88VWAu/Te8R9wjG3apTI7xXy45JzZzaelppMFo/9OrHpeYSeC\n2iTcbFUgFgVvM6yMd++dN6ApKvqkmD6DMha1dgrWkETDg70mciaeWqR9dDRvLEp4\n4O85a7TwhTLU6XTR63dzKjFkt0YpR8TO5+cOvQdVPyxixX3ge+BPAgMBAAGjZjBk\nMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaA\nFGUiVFsfuJ0DNULdrjx1yc5Amr+AMB0GA1UdDgQWBBQnSVYFBemcbSyyTbB0F6QU\nZli9AjANBgkqhkiG9w0BAQsFAAOCAQEAs8p3xMvR4/P2c767RmqnO9eoqCpvtCzT\nM20BMrnLu8SBfSQ+s0c++/7LiIB91Z6gF6ZOWcKs8aGUnr5HM/2eqF7gLrGa3tp/\nwQZH+bG2mqtWQRvqG+fhGc1xkG7XvEoSh1EVd1tk12MgYMQu7rqj6Xn643aZ69iv\n16ZilV6H7ua+ew9nsff37BPz/tUB35yevo8tJeWZlcZC2utyNOjruWJL51VPV41x\nYck2fm2c+oxx+QqjVJLk/bgTv/RyLT+TGVdQV1fU+1E1mlwu7xHpQEXAQ8OxfZ1q\ni+Xe1sfGlGykkQrh5A4RxIfwgT9Z5h/4HNRJ4GmgNmwGQ8qtStLQnQ==\n-----END CERTIFICATE-----\n\n-----BEGIN CERTIFICATE-----\nMIIEcjCCA1qgAwIBAgIJAJch6aKrM1lzMA0GCSqGSIb3DQEBCwUAMGIxLTArBgNV\nBAMMJE90b3Jvc2hpIERlZmF1bHQgUm9vdCBDQSBDZXJ0aWZpY2F0ZTEeMBwGA1UE\nCwwVT3Rvcm9zaGkgQ2VydGlmaWNhdGVzMREwDwYDVQQKDAhPdG9yb3NoaTAeFw0y\nMjExMjExMzE3NTNaFw0zMjExMTgxMzE3NTNaMGIxLTArBgNVBAMMJE90b3Jvc2hp\nIERlZmF1bHQgUm9vdCBDQSBDZXJ0aWZpY2F0ZTEeMBwGA1UECwwVT3Rvcm9zaGkg\nQ2VydGlmaWNhdGVzMREwDwYDVQQKDAhPdG9yb3NoaTCCASIwDQYJKoZIhvcNAQEB\nBQADggEPADCCAQoCggEBAOgA/esqsN/lzJ2Mn/22ShU5qtRk2XpVxkbdDJpRT4Rv\n49Vm7iU0lOU7nyhMyCKaXzDdbPMHGWK5epUsd3E2n/L0kbqj3Q03leSDx2tkPQ9i\nBNiBNdjuybNo7bA+G8GmtI+zvjKyba19mzFpeNWHuMiL2lbh8BoNUWGKBFuRwvJ+\nIVPRrrl9RMT6QnnxtRTPc5oUZ/Jmzkl7Twy/LiJZMrA5nkDX4V4v7yzQR25XrboA\n+HrXg/qomgjw/H6Wfjfbdnt5/Y6YbCt0j25PCwnDDyMrQT4TLxWjPcmoVCJm6IgT\npqiYVGOvDVbe8qaEWkPwH/2nANBnMcG9eG2+MVjLByUCAwEAAaOCASkwggElMA8G\nA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB8GA1UdIwQYMBaAFGUiVFsf\nuJ0DNULdrjx1yc5Amr+AMB0GA1UdDgQWBBRlIlRbH7idAzVC3a48dcnOQJq/gDCB\nwQYIKwYBBQUHAQEEgbQwgbEwZQYIKwYBBQUHMAKGWWh0dHA6Ly9vdG9yb3NoaS1h\ncGkub3RvLnRvb2xzOjgwODAvLndlbGwta25vd24vb3Rvcm9zaGkvY2VydGlmaWNh\ndGVzLzEwODkwMjQyMjU4ODI0ODEyOTE1MEgGCCsGAQUFBzABhjxodHRwOi8vb3Rv\ncm9zaGktYXBpLm90by50b29sczo4MDgwLy53ZWxsLWtub3duL290b3Jvc2hpL29j\nc3AwDQYJKoZIhvcNAQELBQADggEBACZrpgx4fJ3iuvwJWAUZKZu68VSc2POmm2xm\nFpppDTxJHgYNJD9fWPRj9Fwk/gNnKbjCRSp1YA3+3o60Jz+MFWgV3hfdExMYmiCf\n6mMY7M8D3cqJ2Gf1TBK278nf+etcMUBRHLsvWnbB9Q4UFZBw6uQNh76I1inWDaFn\nBbBZi3y6IfzFThh33s6vRRnduWVZ3eDDw/E9GuaXdXAuLoC3WspzmM1vlduaqTaI\nxj4kbWzNk8mVsQ15h4IbSLnBpg1zdPFQyjWtVHxQZqwdAijlB4EzfyEB/kOAK24M\n6wim3SKjers/9ZyVLOWf5r5mnb855e/Yaeg9lMrnwfy/iME15ZE=\n-----END CERTIFICATE-----\n\n", + "chain": "-----BEGIN CERTIFICATE-----\nMIIDtTCCAp2gAwIBAgIJAJI/0IpO0bFHMA0GCSqGSIb3DQEBCwUAMGIxLTArBgNV\nBAMMJE90b3Jvc2hpIERlZmF1bHQgUm9vdCBDQSBDZXJ0aWZpY2F0ZTEeMBwGA1UE\nCwwVT3Rvcm9zaGkgQ2VydGlmaWNhdGVzMREwDwYDVQQKDAhPdG9yb3NoaTAeFw0y\nNDA1MTMxMjQwMzBaFw0zNDA1MTExMjQwMzBaMGoxNTAzBgNVBAMMLE90b3Jvc2hp\nIERlZmF1bHQgSW50ZXJtZWRpYXRlIENBIENlcnRpZmljYXRlMR4wHAYDVQQLDBVP\ndG9yb3NoaSBDZXJ0aWZpY2F0ZXMxETAPBgNVBAoMCE90b3Jvc2hpMIIBIjANBgkq\nhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtnT7t0ZsEcW4nVg3vGfLx0en8smZ5rcs\nSP8UWvQGXrbPs0vX/tDty2IabSIw6YrddRfBVoPr6IgFn6ZkVx6OxVPL2siftsUR\n0ha77Ie2wbMcEot3utfBllypG6lUBxvQvTBpJUmtHbI/ZSK9z1PVzvyetIPlN2ow\nvBVGeuxLuOINNIGVv1ByyPx2lTIo2kkhuU+XUufnF9CEYDRaMt7CgJjeE1SWs6nR\nwqA8V0BJnUHkcw5QPqTqPeetN3UUHw+hvzJjWK9wRK7o2oYGDxPFLv19qu8apOyD\nnlTkXtSRQqS7i5K6WPeHFEu8qEaUqgFq4z4SelTpjRAQjkFGPG6YowIDAQABo2Yw\nZDASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAfBgNVHSMEGDAW\ngBRIIm+c4aGHU0/jJ+HiwRZKdZ8NOjAdBgNVHQ4EFgQUuEMCNXu0DPhJfUhDLc93\njWhCMGgwDQYJKoZIhvcNAQELBQADggEBAE8L95TZRPx6JZ4FLRAeFvalBOp+c9C+\n1w0yubOVGVrHGdCvbu5VE7c5OhUDXNU/lC86VF7d5UhW07XnmiKXyZGxbVY/MMnm\nDQbCRd2yXD9g7KQIFQTWURBj516nh1ugR4k3hEcjPTAtcMMbLD2xga6cAkA7NWwX\nODSjNLSAvefzJLpGAMjISJeLKnTCHqdpgoWKroZ8RzVeOj2V45CnIGocX852291N\nnKqsdfHCxcxx34wp1km7aiJqYG1vEaXw1e7sW8E57pVcSmOcqXHwbP7Ebk729PI8\nnoPerKLn/AN8iL34t3CtkIpBQmQsVyMfhqpY8qBcjZOjYgmTaaNwe1E=\n-----END CERTIFICATE-----\n\n-----BEGIN CERTIFICATE-----\nMIIEcDCCA1igAwIBAgIILMmQ5szf0skwDQYJKoZIhvcNAQELBQAwYjEtMCsGA1UE\nAwwkT3Rvcm9zaGkgRGVmYXVsdCBSb290IENBIENlcnRpZmljYXRlMR4wHAYDVQQL\nDBVPdG9yb3NoaSBDZXJ0aWZpY2F0ZXMxETAPBgNVBAoMCE90b3Jvc2hpMB4XDTI0\nMDUxMzEyNDAzMFoXDTM0MDUxMTEyNDAzMFowYjEtMCsGA1UEAwwkT3Rvcm9zaGkg\nRGVmYXVsdCBSb290IENBIENlcnRpZmljYXRlMR4wHAYDVQQLDBVPdG9yb3NoaSBD\nZXJ0aWZpY2F0ZXMxETAPBgNVBAoMCE90b3Jvc2hpMIIBIjANBgkqhkiG9w0BAQEF\nAAOCAQ8AMIIBCgKCAQEAunpALvuym5JJNFD5rVDBEUVWn+cHYpBsPDhwpezuVgTF\nm/vzAlHBNKuBd/JhKyfekR/vByesxZVuKUFi6whbvdYaENQqWzqNtCHPhB8zcgoF\nQnXMGQqSIlk7NL8fY4r3d5+rKZ535S5zrHunibby4tp4Bt9Z6miP7/Kds2+hrPgt\nWt+lq+OHeHo8brn9Y9jXArO112U3SpaqD+kuRmWa4CsSdVUHSMHSzUt2v7zV/m1k\nDl0eJqAi5q9GN4wrG3ES6zC66caZFLk2o3/uN6zakln0xM2LjjdHH/7D83qF9Itx\nh091jZ9XP/+71accjrEs3InVuKhwmb4gNlFkeJzO+QIDAQABo4IBKDCCASQwDwYD\nVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHwYDVR0jBBgwFoAUSCJvnOGh\nh1NP4yfh4sEWSnWfDTowHQYDVR0OBBYEFEgib5zhoYdTT+Mn4eLBFkp1nw06MIHA\nBggrBgEFBQcBAQSBszCBsDBkBggrBgEFBQcwAoZYaHR0cDovL290b3Jvc2hpLWFw\naS5vdG8udG9vbHM6OTk5OS8ud2VsbC1rbm93bi9vdG9yb3NoaS9jZXJ0aWZpY2F0\nZXMvMzIyNzI2OTkyODk0MTc2OTQxNzBIBggrBgEFBQcwAYY8aHR0cDovL290b3Jv\nc2hpLWFwaS5vdG8udG9vbHM6OTk5OS8ud2VsbC1rbm93bi9vdG9yb3NoaS9vY3Nw\nMA0GCSqGSIb3DQEBCwUAA4IBAQCWAAxgeZyFyay78wYqm14vsaziPd16xSiTz57X\nxFzQ0g8Ysc+Z/9FwJmAiSr2tdksKSk2AETrNpGchj30xVknKcpuOtmcyOUZl7QBk\nVWJHDcUudeBPJssMNauTfamzaDbekpRR3rNkzJBK8eg7p1kzokPku4vqVZ3crPgV\nC9lZzkOGTiBmRxUuUV+RupQg7EfMxT9FdIuAycxLdhvZQ3k3fWHo73UOK3PKBhAs\nusOxUTUDWRCSpzihNoAwulc5GwnLRMaNRQeUlJLADQfdZ5k6qbPdmim7sElGU7JK\ndd+6mZ0gYv8vZOUmiSUNTsY2TUkM3TizSmI5IIfSZcxr46GQ\n-----END CERTIFICATE-----\n\n", "caRef": null, - "privateKey": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCyyEf9RZVWoRsB\nZi5Sc9yLUBWMn6KhCUkhewvEqeLuOXinN/w21wD1xy2fBkABmhXoDb2xbc1vhdsk\nrUWQdqYfG275XzyJqGp52dA5Q/L/3kPS2AAVTDkaGUXvUpObv7r7JzLolq2MRLhJ\nIL2V04bI6sfe1npk8ImpwcaFVoADhliB2Jx88VWAu/Te8R9wjG3apTI7xXy45JzZ\nzaelppMFo/9OrHpeYSeC2iTcbFUgFgVvM6yMd++dN6ApKvqkmD6DMha1dgrWkETD\ng70mciaeWqR9dDRvLEp44O85a7TwhTLU6XTR63dzKjFkt0YpR8TO5+cOvQdVPyxi\nxX3ge+BPAgMBAAECggEAEc+UJG5HN28rYpNMcBhs01AyJWVN7V8gcv5wKLKIEhWV\nqIsSTGBVDyc9q0cTH1W7cvfTexamTcL3FwtQD9lSKW0xPBS50jj+Co+hh+L2FXmO\nJjYwo+6JDcmQxwvh+rU6KoU4NCW2um+qMFbbrE57cg65xMB3RxETld7hKS156V/R\nKRo0RpeDzar8sNfKxZSKhdM2gWClitWKHyvjjHPoU8OABm0gBwcheDjhamy2ugpn\nNHTK7ZNgFWCabTMOrKrU16813EEIlSL4ZlhZsY+D5MzEkuBFY+YgBTmmnSWfKSY3\njGx7IlSaRtIMN2rNvRvDqpv0mBCfsbMSQ+u81a8JMQKBgQDqf9Ti09xGY+S+jPGn\nwAGa524b4oEJh+5h7x9KIqB+8B/UeFAY0zgWAXoafn6bm0LBf/t6QOIj5an7h240\nIlONMK43O3Fk88QXFxnJijG+BqrHWcKOlic8V5uh/QGI4A38NpLMVyxldoNQEKms\nMxwJY4jbHkCExFRlOS+vcp7dVwKBgQDDLKkOwOQIDDlXByCxeFewyLjlyLIBRK5c\nS6Vvux1qtgwnoAKmyCobh3pnUjMouiU0N4Wy7YJccxsAbewlShmo3zyRgnPQV+Us\nU/d1M6vTRQvVyC9D5TGZve0gHROz5UfwNkZdPlWmnT30IXjLaJCgn+NK/N464Mvh\n00jY67tByQKBgApa5r21eU6qbplzg+BSfD+o5JXapLQVCpPMiWcurEOuutkZDwBp\no1plakztNQ4OBx6gViyJ3PKU27K9wkIPGf1sHuVVfKZmlUwnuv3JWnT3umbfMdY2\nDzl87OR0VbMDodp3VngriUxZtWa9taBjCWPov0xdw5+ZQ9jMc6ydtSI1AoGARUFY\nr9KEV9K2mgn+cNrqCuH+shLYDxkm4bs59f6ofpAgmgLuTe4i+ufE6FrDqoT65J97\nArXClzmE8E8RB15W4pRsMpnerKQzvtoAqgbuxqXugCprkyUFx68cMFQBBMmsEDHC\nIB4ZWGjdbf5elZcCEu0JQzedR7kyv2wwokBrjvkCgYBc0eJuq94AjFayNyuPj8DO\n4gU76DsgGqdMHertC8uLEjrZ/5qF5YT02PF+UbyR0rVy9zgJ5AVcXERoDOTYiLzm\nnSkNu9aCb5353MPeZLhXH1RWCatmd8xTZzI9bR6qfO9xtskJnLRFD64/EgMmudGp\nIovFlULNYXttlbyjhVAefw==\n-----END PRIVATE KEY-----\n", + "privateKey": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC2dPu3RmwRxbid\nWDe8Z8vHR6fyyZnmtyxI/xRa9AZets+zS9f+0O3LYhptIjDpit11F8FWg+voiAWf\npmRXHo7FU8vayJ+2xRHSFrvsh7bBsxwSi3e618GWXKkbqVQHG9C9MGklSa0dsj9l\nIr3PU9XO/J60g+U3ajC8FUZ67Eu44g00gZW/UHLI/HaVMijaSSG5T5dS5+cX0IRg\nNFoy3sKAmN4TVJazqdHCoDxXQEmdQeRzDlA+pOo95603dRQfD6G/MmNYr3BEruja\nhgYPE8Uu/X2q7xqk7IOeVORe1JFCpLuLkrpY94cUS7yoRpSqAWrjPhJ6VOmNEBCO\nQUY8bpijAgMBAAECggEABP94Wo8WWgqQRmn4SgZFkKocU9ngUSaEXD/RFNq7q12E\nwJA3OT/nKVu3quJaGMp9XX4cl5wsNhN/yRRpZV+Ijqej5mbVMuJpyqBlgwCGsv5Y\nPLqqlN/g15emn6hIRC5wzdp3ak1IJH1RjavhdHUVXsLHdwGxoaDA9236DOmGjcQd\nvgCPUzhXJaLQHrF8uL8e9NZSje6QXxPPtTYgvS0q6IRbpdMydSU2inpdFd6GIWgT\nJXtCtMDMXN95m/RPZRYppmk6D/FSt8K4NkoyFRi884jbJ5lbVkPhFi1L6sLPFFC/\nbrt9HEc2Il+Z5PyAZ10gu6cntbi3LZbJlE79wD0KoQKBgQD7gej26Qdlu8MuOmcd\nmKuH1CmwP/RQpRD5doPzV0ZKLaiowXxEDa1WJHfLsODcjfjDPqCZOyRgw+moObgX\ngVcD8KajYZ/P1qOVzGK+vM1jdmSCEO9a6tajtGwCiXDOu3/RortTvx42ASAvw2xK\nVP1PFGCAl7aq/PGlr31zSkyZAwKBgQC5t1HvaFtafNCl5ndRi7rLmP/51zxP1yTh\nnxS/0Wm0fuxlSgYo0T3O+LvPO5GxappNlieCBm9bfmUxslLkevLi4vxrPvLO+i3r\nWiW1+qbQbB6Sw/FLUPUY9Nh1nH12CPysyY04NRC4OYRFdWTdFo1ZP+SeHZgtglza\nVHuEQhxf4QKBgBK6Sa6ZC4P3x0DZ+HOrupoSokgNexPGNs1clda42PMNE1H9llM6\n8X1ImeMA+L1zlOJ092BxlvPdorh0E9EsQF56vO3NhyjiwquU3IV3aZPObQ2CVVBf\nzDvEY4aM1WmPbKUfiPNjd8xtqQfldVrTmUkaK1Vn9qIk05mSx7SeA7YhAoGBAKYB\nJH9Ao+/1g7fXky5UHUuiXqUf7KxdXn1C6pQvhzItBffgPDM+Heak1ZP+5lNVO8pA\nft4uDFoXWDMc+BV9hSCjzwe6Q+YZIPSwJudMvtGs44NDHKu7GOsO6XHvyQG6NMnq\ne44TePeDiiVgMyGg6AYwI5XmVEHRtlf+jk4BnkVhAoGBALmhqq4uQEQhrC+4yxDb\nfWtY8jmBV/gYxZVGuqHLNy4LsOcDIfh5Fg53vwsvcNBaxLOIDoao285n5GPvGzTY\nvkVNkuOsny+3cVFHsE01PA6VLY35Ao58wfkUtaDazeah8w0/5bRPb2eusbZ5Jr2K\n//fpP84Fv48BqbK45+fVph+J\n-----END PRIVATE KEY-----\n", "selfSigned": false, "ca": true, "valid": true, @@ -1033,8 +1500,8 @@ "autoRenew": true, "letsEncrypt": false, "subject": "OU=Otoroshi Certificates, O=Otoroshi, CN=Otoroshi Default Intermediate CA Certificate", - "from": 1669036674000, - "to": 1984396674000, + "from": 1715604030000, + "to": 2030964030000, "client": false, "keypair": false, "sans": [], @@ -1052,13 +1519,47 @@ "default" ] }, - "id": "otoroshi-wildcard", - "domain": "*.oto.tools", - "name": "Otoroshi Default Wildcard Certificate", - "description": "Otoroshi wildcard certificate (auto-generated)", - "chain": "-----BEGIN CERTIFICATE-----\nMIID1DCCArygAwIBAgIJAN9aOSrUkVopMA0GCSqGSIb3DQEBCwUAMGoxNTAzBgNV\nBAMMLE90b3Jvc2hpIERlZmF1bHQgSW50ZXJtZWRpYXRlIENBIENlcnRpZmljYXRl\nMR4wHAYDVQQLDBVPdG9yb3NoaSBDZXJ0aWZpY2F0ZXMxETAPBgNVBAoMCE90b3Jv\nc2hpMB4XDTIyMTEyMTEzMTc1NFoXDTIzMTEyMTEzMTc1NFoweTEUMBIGA1UEAwwL\nKi5vdG8udG9vbHMxLjAsBgNVBAQMJU90b3Jvc2hpIERlZmF1bHQgV2lsZGNhcmQg\nQ2VydGlmaWNhdGUxHjAcBgNVBAsMFU90b3Jvc2hpIENlcnRpZmljYXRlczERMA8G\nA1UECgwIT3Rvcm9zaGkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDx\no4Qhie0V3BEKJBZX/ZKP5F4sT8ceoGUfXfQYXyzLiJPllN+8fIkxGVC1iERKvMnJ\nUQVi2a85J2kGd9lEL6AKBI6eCP++9NMgmCfmuaEYeZis1muQ7MTCQ5Sm1Mdd+XB2\ncw0v4rQ/Bz3lHwuDGB6l7LRMeP9gNmIbTOfYXgv7z0lCotVpM+jbUaHynVUGLrc3\nK6nFCJTWG48Y1Ielk/1OGvotinurcRNxJPZ6+r6S+wx97eJki1zRjo+l6jfBfkSn\n9fawE5KpAVXBNVhdhq+evuyYW0RyoOxyV3sb1PJ8vlqOosU9sgetfOirXoINljno\ni/U83vsEokS8I1Bomr29AgMBAAGjbjBsMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/\nBAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFCdJVgUF6Zxt\nLLJNsHQXpBRmWL0CMBYGA1UdEQQPMA2CCyoub3RvLnRvb2xzMA0GCSqGSIb3DQEB\nCwUAA4IBAQA9DDhUSuqsQC6v1HhsZELpHDcPglFx5s6EbVQ5oC0UCEHkTsW2Gkeg\nGHUXR7HtrnhAxTtEl2EevztfcqBQotStOOKaXvY9YyZV46W4dHqgAuns8jMIC0nu\n4lwAfClntzewvM42Z9oJ2tcyC8O6/lgQFD6l9dyXa7sWzG4hD41s3p/ILycRLQez\nsq4x2cGxcmgnFxgj6VCxqX6h0T9jRRFTivFn4twlYvpzkr7fBNg9cw0EroYVT34m\n5o3ObD/iA+TVQzPVv9OIRC2EqJScsxbrlZfGoYIYgP4L9B9tD4o4NBtvdD5RIUxq\nv9aSZWOHkE5Z4eVEdd+ilZOyK+p1UORu\n-----END CERTIFICATE-----\n\n-----BEGIN CERTIFICATE-----\nMIIDtDCCApygAwIBAgIISNRU1CRzbwYwDQYJKoZIhvcNAQELBQAwYjEtMCsGA1UE\nAwwkT3Rvcm9zaGkgRGVmYXVsdCBSb290IENBIENlcnRpZmljYXRlMR4wHAYDVQQL\nDBVPdG9yb3NoaSBDZXJ0aWZpY2F0ZXMxETAPBgNVBAoMCE90b3Jvc2hpMB4XDTIy\nMTEyMTEzMTc1NFoXDTMyMTExODEzMTc1NFowajE1MDMGA1UEAwwsT3Rvcm9zaGkg\nRGVmYXVsdCBJbnRlcm1lZGlhdGUgQ0EgQ2VydGlmaWNhdGUxHjAcBgNVBAsMFU90\nb3Jvc2hpIENlcnRpZmljYXRlczERMA8GA1UECgwIT3Rvcm9zaGkwggEiMA0GCSqG\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCyyEf9RZVWoRsBZi5Sc9yLUBWMn6KhCUkh\newvEqeLuOXinN/w21wD1xy2fBkABmhXoDb2xbc1vhdskrUWQdqYfG275XzyJqGp5\n2dA5Q/L/3kPS2AAVTDkaGUXvUpObv7r7JzLolq2MRLhJIL2V04bI6sfe1npk8Imp\nwcaFVoADhliB2Jx88VWAu/Te8R9wjG3apTI7xXy45JzZzaelppMFo/9OrHpeYSeC\n2iTcbFUgFgVvM6yMd++dN6ApKvqkmD6DMha1dgrWkETDg70mciaeWqR9dDRvLEp4\n4O85a7TwhTLU6XTR63dzKjFkt0YpR8TO5+cOvQdVPyxixX3ge+BPAgMBAAGjZjBk\nMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaA\nFGUiVFsfuJ0DNULdrjx1yc5Amr+AMB0GA1UdDgQWBBQnSVYFBemcbSyyTbB0F6QU\nZli9AjANBgkqhkiG9w0BAQsFAAOCAQEAs8p3xMvR4/P2c767RmqnO9eoqCpvtCzT\nM20BMrnLu8SBfSQ+s0c++/7LiIB91Z6gF6ZOWcKs8aGUnr5HM/2eqF7gLrGa3tp/\nwQZH+bG2mqtWQRvqG+fhGc1xkG7XvEoSh1EVd1tk12MgYMQu7rqj6Xn643aZ69iv\n16ZilV6H7ua+ew9nsff37BPz/tUB35yevo8tJeWZlcZC2utyNOjruWJL51VPV41x\nYck2fm2c+oxx+QqjVJLk/bgTv/RyLT+TGVdQV1fU+1E1mlwu7xHpQEXAQ8OxfZ1q\ni+Xe1sfGlGykkQrh5A4RxIfwgT9Z5h/4HNRJ4GmgNmwGQ8qtStLQnQ==\n-----END CERTIFICATE-----\n\n-----BEGIN CERTIFICATE-----\nMIIEcjCCA1qgAwIBAgIJAJch6aKrM1lzMA0GCSqGSIb3DQEBCwUAMGIxLTArBgNV\nBAMMJE90b3Jvc2hpIERlZmF1bHQgUm9vdCBDQSBDZXJ0aWZpY2F0ZTEeMBwGA1UE\nCwwVT3Rvcm9zaGkgQ2VydGlmaWNhdGVzMREwDwYDVQQKDAhPdG9yb3NoaTAeFw0y\nMjExMjExMzE3NTNaFw0zMjExMTgxMzE3NTNaMGIxLTArBgNVBAMMJE90b3Jvc2hp\nIERlZmF1bHQgUm9vdCBDQSBDZXJ0aWZpY2F0ZTEeMBwGA1UECwwVT3Rvcm9zaGkg\nQ2VydGlmaWNhdGVzMREwDwYDVQQKDAhPdG9yb3NoaTCCASIwDQYJKoZIhvcNAQEB\nBQADggEPADCCAQoCggEBAOgA/esqsN/lzJ2Mn/22ShU5qtRk2XpVxkbdDJpRT4Rv\n49Vm7iU0lOU7nyhMyCKaXzDdbPMHGWK5epUsd3E2n/L0kbqj3Q03leSDx2tkPQ9i\nBNiBNdjuybNo7bA+G8GmtI+zvjKyba19mzFpeNWHuMiL2lbh8BoNUWGKBFuRwvJ+\nIVPRrrl9RMT6QnnxtRTPc5oUZ/Jmzkl7Twy/LiJZMrA5nkDX4V4v7yzQR25XrboA\n+HrXg/qomgjw/H6Wfjfbdnt5/Y6YbCt0j25PCwnDDyMrQT4TLxWjPcmoVCJm6IgT\npqiYVGOvDVbe8qaEWkPwH/2nANBnMcG9eG2+MVjLByUCAwEAAaOCASkwggElMA8G\nA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB8GA1UdIwQYMBaAFGUiVFsf\nuJ0DNULdrjx1yc5Amr+AMB0GA1UdDgQWBBRlIlRbH7idAzVC3a48dcnOQJq/gDCB\nwQYIKwYBBQUHAQEEgbQwgbEwZQYIKwYBBQUHMAKGWWh0dHA6Ly9vdG9yb3NoaS1h\ncGkub3RvLnRvb2xzOjgwODAvLndlbGwta25vd24vb3Rvcm9zaGkvY2VydGlmaWNh\ndGVzLzEwODkwMjQyMjU4ODI0ODEyOTE1MEgGCCsGAQUFBzABhjxodHRwOi8vb3Rv\ncm9zaGktYXBpLm90by50b29sczo4MDgwLy53ZWxsLWtub3duL290b3Jvc2hpL29j\nc3AwDQYJKoZIhvcNAQELBQADggEBACZrpgx4fJ3iuvwJWAUZKZu68VSc2POmm2xm\nFpppDTxJHgYNJD9fWPRj9Fwk/gNnKbjCRSp1YA3+3o60Jz+MFWgV3hfdExMYmiCf\n6mMY7M8D3cqJ2Gf1TBK278nf+etcMUBRHLsvWnbB9Q4UFZBw6uQNh76I1inWDaFn\nBbBZi3y6IfzFThh33s6vRRnduWVZ3eDDw/E9GuaXdXAuLoC3WspzmM1vlduaqTaI\nxj4kbWzNk8mVsQ15h4IbSLnBpg1zdPFQyjWtVHxQZqwdAijlB4EzfyEB/kOAK24M\n6wim3SKjers/9ZyVLOWf5r5mnb855e/Yaeg9lMrnwfy/iME15ZE=\n-----END CERTIFICATE-----\n", + "id": "otoroshi-root-ca", + "domain": "Otoroshi Default Root CA Certificate", + "name": "Otoroshi Default Root CA Certificate", + "description": "Otoroshi root CA (auto-generated)", + "chain": "-----BEGIN CERTIFICATE-----\nMIIEcDCCA1igAwIBAgIILMmQ5szf0skwDQYJKoZIhvcNAQELBQAwYjEtMCsGA1UE\nAwwkT3Rvcm9zaGkgRGVmYXVsdCBSb290IENBIENlcnRpZmljYXRlMR4wHAYDVQQL\nDBVPdG9yb3NoaSBDZXJ0aWZpY2F0ZXMxETAPBgNVBAoMCE90b3Jvc2hpMB4XDTI0\nMDUxMzEyNDAzMFoXDTM0MDUxMTEyNDAzMFowYjEtMCsGA1UEAwwkT3Rvcm9zaGkg\nRGVmYXVsdCBSb290IENBIENlcnRpZmljYXRlMR4wHAYDVQQLDBVPdG9yb3NoaSBD\nZXJ0aWZpY2F0ZXMxETAPBgNVBAoMCE90b3Jvc2hpMIIBIjANBgkqhkiG9w0BAQEF\nAAOCAQ8AMIIBCgKCAQEAunpALvuym5JJNFD5rVDBEUVWn+cHYpBsPDhwpezuVgTF\nm/vzAlHBNKuBd/JhKyfekR/vByesxZVuKUFi6whbvdYaENQqWzqNtCHPhB8zcgoF\nQnXMGQqSIlk7NL8fY4r3d5+rKZ535S5zrHunibby4tp4Bt9Z6miP7/Kds2+hrPgt\nWt+lq+OHeHo8brn9Y9jXArO112U3SpaqD+kuRmWa4CsSdVUHSMHSzUt2v7zV/m1k\nDl0eJqAi5q9GN4wrG3ES6zC66caZFLk2o3/uN6zakln0xM2LjjdHH/7D83qF9Itx\nh091jZ9XP/+71accjrEs3InVuKhwmb4gNlFkeJzO+QIDAQABo4IBKDCCASQwDwYD\nVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHwYDVR0jBBgwFoAUSCJvnOGh\nh1NP4yfh4sEWSnWfDTowHQYDVR0OBBYEFEgib5zhoYdTT+Mn4eLBFkp1nw06MIHA\nBggrBgEFBQcBAQSBszCBsDBkBggrBgEFBQcwAoZYaHR0cDovL290b3Jvc2hpLWFw\naS5vdG8udG9vbHM6OTk5OS8ud2VsbC1rbm93bi9vdG9yb3NoaS9jZXJ0aWZpY2F0\nZXMvMzIyNzI2OTkyODk0MTc2OTQxNzBIBggrBgEFBQcwAYY8aHR0cDovL290b3Jv\nc2hpLWFwaS5vdG8udG9vbHM6OTk5OS8ud2VsbC1rbm93bi9vdG9yb3NoaS9vY3Nw\nMA0GCSqGSIb3DQEBCwUAA4IBAQCWAAxgeZyFyay78wYqm14vsaziPd16xSiTz57X\nxFzQ0g8Ysc+Z/9FwJmAiSr2tdksKSk2AETrNpGchj30xVknKcpuOtmcyOUZl7QBk\nVWJHDcUudeBPJssMNauTfamzaDbekpRR3rNkzJBK8eg7p1kzokPku4vqVZ3crPgV\nC9lZzkOGTiBmRxUuUV+RupQg7EfMxT9FdIuAycxLdhvZQ3k3fWHo73UOK3PKBhAs\nusOxUTUDWRCSpzihNoAwulc5GwnLRMaNRQeUlJLADQfdZ5k6qbPdmim7sElGU7JK\ndd+6mZ0gYv8vZOUmiSUNTsY2TUkM3TizSmI5IIfSZcxr46GQ\n-----END CERTIFICATE-----\n", "caRef": null, - "privateKey": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDxo4Qhie0V3BEK\nJBZX/ZKP5F4sT8ceoGUfXfQYXyzLiJPllN+8fIkxGVC1iERKvMnJUQVi2a85J2kG\nd9lEL6AKBI6eCP++9NMgmCfmuaEYeZis1muQ7MTCQ5Sm1Mdd+XB2cw0v4rQ/Bz3l\nHwuDGB6l7LRMeP9gNmIbTOfYXgv7z0lCotVpM+jbUaHynVUGLrc3K6nFCJTWG48Y\n1Ielk/1OGvotinurcRNxJPZ6+r6S+wx97eJki1zRjo+l6jfBfkSn9fawE5KpAVXB\nNVhdhq+evuyYW0RyoOxyV3sb1PJ8vlqOosU9sgetfOirXoINljnoi/U83vsEokS8\nI1Bomr29AgMBAAECggEAM1PZgEpM+yS41d+eg2hQlpIa0uzLTh0h/cy2SesFl+JW\nECFUeTQ27CAFfdoJL5r0fffg3gOBbfGKJJ6Wnx5Cr34SUU12Dbjxk+3eI+twZcOG\ng8g0Jx+0F9PFIr717mdK1U5REcflI7JFGoNCrcEU2YfzMH9PN7+1jTkD0nj/sKgI\nrHV+gntllocqLtu+nb5vgAJ8Oz0/DInfaDLk1AQ7st6jwv7CpXkiFJY9mui5JOdg\nuNVLRrSgvH6TZSxXDxGztsby829hd2/7N4q75aqSgfvryu6zYsMw94vdXs9hpijn\nyDPIDbfTJxSkYNK9uF8VAqxhE3yFoTbNFzTmeAh+PQKBgQD6S+VUa6p76Fnxg3fI\n7U9QEcFN1+9ars8T3Kyl+ELp7e55XQiLzXKK70X30bhi6nX1ftN2gcuKsD1YMkNa\nka9V7DBhht8QOXuZeMy7itE9yh7ZcsN/bCZcYVz13BSEm8oa0wbpD27uGGw9N5IT\ndrna6gN6auh2adSuyNNg+e/Y0wKBgQD3JR2NfuPh1YMa7V+YGLTKPSgUNlXHR0LY\npvmKIFd/tMvIAcNts71CPwmsWNq2xEKQRXFT/3BL6+PbO63ZBfCbkULy4pVZsBkY\nxbcqFW+BmTgo8OqSVdsFRG96qaSUP2RtBi+riAX+ellWJ0Jj+fvYytVHYyMOIEjO\n3aGXLrD1LwKBgFS3d4mZX3zM6hSKsPJ7EAtD1y+n1MXWwRQx7uXGH6/Z7ot5Z7kZ\n8pj/36i/fS9NuFs9cAl+aQwqFrlRWea/xJ7KcJ2x7rIpIqKRdopLtAK79LEY3P61\n9k3yTmqQ8QjpSgDGSGJ97Mf90DTwKCsaVlG4PzEiGkhqDaiTJKiAMCQtAoGAMVJn\n5nAXXF927a3HsRvlIKM512bMkZuIHKKeFFHGBId5T64CaWdAcvMPpCSLaYQbYJ1r\nM3870vr9myf61t/Xl8RSnxRXB/Ib4BS9LBnhCfUg/xKtTYzyy8dPlCQ84xUPnq5o\nXwwjXhjesVHebwNZnndEPx3qxgvtW9NhR0GbMSMCgYEAgqSVUxn2/eejeaMwksLr\nhULK5NL/zwGzUlsLp0Y9xaAAghbBEHBKzgTxe0NI4laV6dAuT6aWEPv1U8R1Q1i3\nXgFOVfhhYc3cwpkJkvPM5JFrb9sOu8m1C9v3hlRb8+x/WHPD9VH+BBGBVXCrVt52\nEO9LqmmxQ3jCE/loMHdIKZI=\n-----END PRIVATE KEY-----\n", + "privateKey": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC6ekAu+7Kbkkk0\nUPmtUMERRVaf5wdikGw8OHCl7O5WBMWb+/MCUcE0q4F38mErJ96RH+8HJ6zFlW4p\nQWLrCFu91hoQ1CpbOo20Ic+EHzNyCgVCdcwZCpIiWTs0vx9jivd3n6spnnflLnOs\ne6eJtvLi2ngG31nqaI/v8p2zb6Gs+C1a36Wr44d4ejxuuf1j2NcCs7XXZTdKlqoP\n6S5GZZrgKxJ1VQdIwdLNS3a/vNX+bWQOXR4moCLmr0Y3jCsbcRLrMLrpxpkUuTaj\nf+43rNqSWfTEzYuON0cf/sPzeoX0i3GHT3WNn1c//7vVpxyOsSzcidW4qHCZviA2\nUWR4nM75AgMBAAECggEAGpmaszkDKs7ss1JDs1XcHH8UEr4tXwFz9wkkBH8uJkYK\n/Cut5+xKj51RXA9Ojg3jYTc5bP9bVVnx+1+Q4S7xizxRvonNOvM4yHO0OsgKmh9U\nzbyo7v+DhfPNjsIek5/8metESq0ItD/yjy2aYfofXn3EdlKA7H+a3KLynOh1GwuT\nQzSBxMjLgIXh/Lx7VvyXi49dHrAme7GTp8F5d3cUYO+TrWFd0M48JeRP3nITL1f4\nI/INyhcyBg90EuymceQ7yz0dC6iD54KpYHUkVOxKw2dRaMHY1c3IT8VjqQ0Cc9A+\n8HU3P3LNyPbKx8TCyB7OptxFSBOa9bjVgsLwUf7bQwKBgQDsGZfRmcQRYiNylUrv\nZBAG9GTITydolk3iUaxLno/yzGaVH/ZO42wtFvCF6unS70C5DbNs1Js8yHpF+bdp\n+DYvYi5MxCaAvBrmcewkl8DcuzRkeI7D7+UmQFm96SXowJf6zilWhkKdKnwtgJCo\nsHMtaAFEP7n4JdR7wYGiQzqHqwKBgQDKMfAotPzojsN9QLo1S9FzluOMy+ns4sDg\nkkTnHvC64D7hLU5P1reIb258X585n9AcOXA8BEo0cXEJbqaWpmwXyOYpgFjeDveW\nosvVugd3ZJjQjXCPvLNBraLEK2tlxMssxFhBfLOYsg2GIRIOW881SabybgypE/Np\ngmw+H43P6wKBgG8F8fBsYFjUT1r4fU5pPWgn0KSpJBz4YROF2IGOxJ4mJC8H4qli\nJGJnoR84QlAglczvdmo85+sDUwxIy0kXoVNS1fbfXkO3MOcgtyzV96lUluSQFIJq\n8tSz+f9l/Z+fwVf9Tk8Tv4tsiQxUorHQl7jIaaAQX4EjOPIhIraM6p8VAoGABzgp\nPwrXTOZsDHjXLTf7zojArzJIs/CUum/+jdII/mMrW5bmiOs9ul1gMIipgsE2CQvz\n+fCdRaoXQyL0R8TBb0f5D9pJUgRuyq4dYRA3XAf3fNrP/hHaxKmSejaiqwuPxZVt\nIUl2kCkrWnKr1l9o0ppCn71WLmNL+aRygPVXdukCgYEAnur5QESnGbAB5nhSQbkW\nIAmUhL42/BbR74jw6+SaZPy9qj+q5WdBU2zMrX9GovlCXMqRcAiXwbFxkalt+qhN\nwCcKIvAttbALLghWaBQ9KBiuYRoKeD9dvgRt8v21V/Jv8/CJWk7dX6S6CS5hDNhy\nF0NlD1j9zL0OFVorWSb8eNg=\n-----END PRIVATE KEY-----\n", + "selfSigned": true, + "ca": true, + "valid": true, + "exposed": true, + "revoked": false, + "autoRenew": true, + "letsEncrypt": false, + "subject": "OU=Otoroshi Certificates, O=Otoroshi, CN=Otoroshi Default Root CA Certificate", + "from": 1715604030000, + "to": 2030964030000, + "client": false, + "keypair": false, + "sans": [], + "certType": "ca", + "password": null, + "metadata": { + "csr": "{\"hosts\":[],\"key\":{\"algo\":\"rsa\",\"size\":2048},\"name\":{},\"subject\":\"CN=Otoroshi Default Root CA Certificate, OU=Otoroshi Certificates, O=Otoroshi\",\"client\":false,\"ca\":true,\"duration\":315360000000,\"signatureAlg\":\"SHA256WithRSAEncryption\",\"digestAlg\":\"SHA-256\"}" + }, + "tags": [] + }, + { + "_loc": { + "tenant": "default", + "teams": [ + "default" + ] + }, + "id": "otoroshi-jwt-signing", + "domain": "Otoroshi Default Jwt Signing Keypair", + "name": "Otoroshi Default Jwt Signing Keypair", + "description": "Otoroshi jwt signing keypair (auto-generated)", + "chain": "-----BEGIN CERTIFICATE-----\nMIIEdjCCA16gAwIBAgIIGNNuea+cmjcwDQYJKoZIhvcNAQELBQAwajE1MDMGA1UE\nAwwsT3Rvcm9zaGkgRGVmYXVsdCBJbnRlcm1lZGlhdGUgQ0EgQ2VydGlmaWNhdGUx\nHjAcBgNVBAsMFU90b3Jvc2hpIENlcnRpZmljYXRlczERMA8GA1UECgwIT3Rvcm9z\naGkwHhcNMjQwNTEzMTI0MDMxWhcNMjUwNTEzMTI0MDMxWjBiMS0wKwYDVQQDDCRP\ndG9yb3NoaSBEZWZhdWx0IEp3dCBTaWduaW5nIEtleXBhaXIxHjAcBgNVBAsMFU90\nb3Jvc2hpIENlcnRpZmljYXRlczERMA8GA1UECgwIT3Rvcm9zaGkwggEiMA0GCSqG\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2ZwwVGRVqM75JUoTcMVipsQPLnR6wzNgy\nymluPAIxOFYQ78hJUQqF097OxRS65OJpYj7pW4pMPIc26aFlBdXik3nmcucuuga9\nFhXt2CGgiro+0rGIa9DHbNEpLFdvG9qCdozTINgJCpFzyKupS9O5oO4P5tfVlEog\nXxGPjzGiyuInCdFc39b97YXpFIo0VO8B2qlXJLvzK3I/zQlVT4fSwKuKytVb5sRU\nndC2FFMdon9NRYVP5lgVh5oXflAKH1LTiMPB+6hlXqjkiw6BeoL+7TVVxFgoXOR9\nHoLj1L7WEuNG28OCbcecrge2gxPHLEEHGsBDVXNpHuVFh3SmSo/ZAgMBAAGjggEm\nMIIBIjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr\nBgEFBQcDATAfBgNVHSMEGDAWgBS4QwI1e7QM+El9SEMtz3eNaEIwaDAJBgNVHREE\nAjAAMIHABggrBgEFBQcBAQSBszCBsDBkBggrBgEFBQcwAoZYaHR0cDovL290b3Jv\nc2hpLWFwaS5vdG8udG9vbHM6OTk5OS8ud2VsbC1rbm93bi9vdG9yb3NoaS9jZXJ0\naWZpY2F0ZXMvMTc4ODg5NDk0NTkxMjU5Mjk1MTBIBggrBgEFBQcwAYY8aHR0cDov\nL290b3Jvc2hpLWFwaS5vdG8udG9vbHM6OTk5OS8ud2VsbC1rbm93bi9vdG9yb3No\naS9vY3NwMA0GCSqGSIb3DQEBCwUAA4IBAQBs41g9qLUZA9WrtXdNG4f9DTuB1iPz\nvVvI/YPxheDuo89QAsNLLgb3nfRaYNjZGc6DkFQK0EUYMKEsFzScjcCadNk9XXcj\nxGTpuLsTcDXf98ZUBJbLmKSs8CxvK8q3swV18E1GPnhQpKSNqJJQGIrVtchY6uuP\nFmVy8tgSqSRhlNoGSoD9iwhp4tB8Mi31QzYRj5vX2XhpW8n6Zf/C9woxVraUnxEa\nghMg161HnI2KxNCMlvX9DrbXk7FXZm2wqTY+rsDfaVSsMHi2hpXbE5co9JwteNTL\n53DXlixZdYA7I2hrJ8ISi/wwteYaqAVyZg3cZs+srVwRolVYthFzUdJO\n-----END CERTIFICATE-----\n\n-----BEGIN CERTIFICATE-----\nMIIDtTCCAp2gAwIBAgIJAJI/0IpO0bFHMA0GCSqGSIb3DQEBCwUAMGIxLTArBgNV\nBAMMJE90b3Jvc2hpIERlZmF1bHQgUm9vdCBDQSBDZXJ0aWZpY2F0ZTEeMBwGA1UE\nCwwVT3Rvcm9zaGkgQ2VydGlmaWNhdGVzMREwDwYDVQQKDAhPdG9yb3NoaTAeFw0y\nNDA1MTMxMjQwMzBaFw0zNDA1MTExMjQwMzBaMGoxNTAzBgNVBAMMLE90b3Jvc2hp\nIERlZmF1bHQgSW50ZXJtZWRpYXRlIENBIENlcnRpZmljYXRlMR4wHAYDVQQLDBVP\ndG9yb3NoaSBDZXJ0aWZpY2F0ZXMxETAPBgNVBAoMCE90b3Jvc2hpMIIBIjANBgkq\nhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtnT7t0ZsEcW4nVg3vGfLx0en8smZ5rcs\nSP8UWvQGXrbPs0vX/tDty2IabSIw6YrddRfBVoPr6IgFn6ZkVx6OxVPL2siftsUR\n0ha77Ie2wbMcEot3utfBllypG6lUBxvQvTBpJUmtHbI/ZSK9z1PVzvyetIPlN2ow\nvBVGeuxLuOINNIGVv1ByyPx2lTIo2kkhuU+XUufnF9CEYDRaMt7CgJjeE1SWs6nR\nwqA8V0BJnUHkcw5QPqTqPeetN3UUHw+hvzJjWK9wRK7o2oYGDxPFLv19qu8apOyD\nnlTkXtSRQqS7i5K6WPeHFEu8qEaUqgFq4z4SelTpjRAQjkFGPG6YowIDAQABo2Yw\nZDASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAfBgNVHSMEGDAW\ngBRIIm+c4aGHU0/jJ+HiwRZKdZ8NOjAdBgNVHQ4EFgQUuEMCNXu0DPhJfUhDLc93\njWhCMGgwDQYJKoZIhvcNAQELBQADggEBAE8L95TZRPx6JZ4FLRAeFvalBOp+c9C+\n1w0yubOVGVrHGdCvbu5VE7c5OhUDXNU/lC86VF7d5UhW07XnmiKXyZGxbVY/MMnm\nDQbCRd2yXD9g7KQIFQTWURBj516nh1ugR4k3hEcjPTAtcMMbLD2xga6cAkA7NWwX\nODSjNLSAvefzJLpGAMjISJeLKnTCHqdpgoWKroZ8RzVeOj2V45CnIGocX852291N\nnKqsdfHCxcxx34wp1km7aiJqYG1vEaXw1e7sW8E57pVcSmOcqXHwbP7Ebk729PI8\nnoPerKLn/AN8iL34t3CtkIpBQmQsVyMfhqpY8qBcjZOjYgmTaaNwe1E=\n-----END CERTIFICATE-----\n\n-----BEGIN CERTIFICATE-----\nMIIEcDCCA1igAwIBAgIILMmQ5szf0skwDQYJKoZIhvcNAQELBQAwYjEtMCsGA1UE\nAwwkT3Rvcm9zaGkgRGVmYXVsdCBSb290IENBIENlcnRpZmljYXRlMR4wHAYDVQQL\nDBVPdG9yb3NoaSBDZXJ0aWZpY2F0ZXMxETAPBgNVBAoMCE90b3Jvc2hpMB4XDTI0\nMDUxMzEyNDAzMFoXDTM0MDUxMTEyNDAzMFowYjEtMCsGA1UEAwwkT3Rvcm9zaGkg\nRGVmYXVsdCBSb290IENBIENlcnRpZmljYXRlMR4wHAYDVQQLDBVPdG9yb3NoaSBD\nZXJ0aWZpY2F0ZXMxETAPBgNVBAoMCE90b3Jvc2hpMIIBIjANBgkqhkiG9w0BAQEF\nAAOCAQ8AMIIBCgKCAQEAunpALvuym5JJNFD5rVDBEUVWn+cHYpBsPDhwpezuVgTF\nm/vzAlHBNKuBd/JhKyfekR/vByesxZVuKUFi6whbvdYaENQqWzqNtCHPhB8zcgoF\nQnXMGQqSIlk7NL8fY4r3d5+rKZ535S5zrHunibby4tp4Bt9Z6miP7/Kds2+hrPgt\nWt+lq+OHeHo8brn9Y9jXArO112U3SpaqD+kuRmWa4CsSdVUHSMHSzUt2v7zV/m1k\nDl0eJqAi5q9GN4wrG3ES6zC66caZFLk2o3/uN6zakln0xM2LjjdHH/7D83qF9Itx\nh091jZ9XP/+71accjrEs3InVuKhwmb4gNlFkeJzO+QIDAQABo4IBKDCCASQwDwYD\nVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHwYDVR0jBBgwFoAUSCJvnOGh\nh1NP4yfh4sEWSnWfDTowHQYDVR0OBBYEFEgib5zhoYdTT+Mn4eLBFkp1nw06MIHA\nBggrBgEFBQcBAQSBszCBsDBkBggrBgEFBQcwAoZYaHR0cDovL290b3Jvc2hpLWFw\naS5vdG8udG9vbHM6OTk5OS8ud2VsbC1rbm93bi9vdG9yb3NoaS9jZXJ0aWZpY2F0\nZXMvMzIyNzI2OTkyODk0MTc2OTQxNzBIBggrBgEFBQcwAYY8aHR0cDovL290b3Jv\nc2hpLWFwaS5vdG8udG9vbHM6OTk5OS8ud2VsbC1rbm93bi9vdG9yb3NoaS9vY3Nw\nMA0GCSqGSIb3DQEBCwUAA4IBAQCWAAxgeZyFyay78wYqm14vsaziPd16xSiTz57X\nxFzQ0g8Ysc+Z/9FwJmAiSr2tdksKSk2AETrNpGchj30xVknKcpuOtmcyOUZl7QBk\nVWJHDcUudeBPJssMNauTfamzaDbekpRR3rNkzJBK8eg7p1kzokPku4vqVZ3crPgV\nC9lZzkOGTiBmRxUuUV+RupQg7EfMxT9FdIuAycxLdhvZQ3k3fWHo73UOK3PKBhAs\nusOxUTUDWRCSpzihNoAwulc5GwnLRMaNRQeUlJLADQfdZ5k6qbPdmim7sElGU7JK\ndd+6mZ0gYv8vZOUmiSUNTsY2TUkM3TizSmI5IIfSZcxr46GQ\n-----END CERTIFICATE-----\n", + "caRef": null, + "privateKey": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC2ZwwVGRVqM75J\nUoTcMVipsQPLnR6wzNgyymluPAIxOFYQ78hJUQqF097OxRS65OJpYj7pW4pMPIc2\n6aFlBdXik3nmcucuuga9FhXt2CGgiro+0rGIa9DHbNEpLFdvG9qCdozTINgJCpFz\nyKupS9O5oO4P5tfVlEogXxGPjzGiyuInCdFc39b97YXpFIo0VO8B2qlXJLvzK3I/\nzQlVT4fSwKuKytVb5sRUndC2FFMdon9NRYVP5lgVh5oXflAKH1LTiMPB+6hlXqjk\niw6BeoL+7TVVxFgoXOR9HoLj1L7WEuNG28OCbcecrge2gxPHLEEHGsBDVXNpHuVF\nh3SmSo/ZAgMBAAECggEAM2fbSd7l4CZxDG0LMJtfp4NR1IKVcbJpg/bzu7OB0qlT\n6DYLtk32rzzE4HU+oGV9QcIEhPPdiBrO6LTzXWtFmjOB+WaMrOiUmZmt2Hg/EeXH\nE4mBqzbngiyyze3HPnMVJAuRKLdq6zFlSMJXTsEmDHzEWwrt7S17rp/cbR8oF/6n\n6Kurs4+SvkTnK1v2IHXsBUrkVVE988bWeZhRf3Gd09dPfCmEQdHlR2yv8raMt+cu\nNT2CzXxNYNYQZG7+/93X3P0lKYMMoPe0rdcQRxW2qpqkrcmo2SQyyxOCARRvZIIW\nadIuSnN+3yiHrv7FAhtLB9YlRly1IdGLg6dYd/+luQKBgQDbmtDLKu8yKW51kC73\nnrDpvAkDeQOed9hleiLqLqdwFtx7dS6cs2WeQUDvCegiDHMOEblMo8nNHksVMkVn\nvkHen7px+9lQfTGgK8Y+8AnGzPV5SbCBoDx2m4CjgBiL2JMEBBxUmLDrNvTQ7MaS\ncSmgO6y0S1zsMFpK0NAg9OwMrwKBgQDUodnFXf+Gj7LakMspdkm6U7jX2Fd1LPmX\niRyXvTEgOVbSbHkJvIREFCrMjsgn4qTPVptiS31jASsJwOOXVxSJ3k0x1KgmHtYb\naWh/3ogUUI2aKqYONh1TTqpcEZ7dPLzKDkOv03QMWFCj4BKpL6QHkSwqyXSyJ1mc\naFAmKC2d9wKBgB6LAr0vJIOKhaVuP0oLVTm9AFfuTdWNf5hli1Oc14bcdx/opWn/\n/YMsOjbE3vdVWe88AdSp9xhDPVgeXM7YBFtNReYyjAAI2Y0YaYM5aXKv8PhEinsM\n6Tvp1tNmroNMxO3LEJGnhvu/8f7Y36lgedSiWBvfW3YyJ2y+bCQZTHdbAoGAJVFa\nlQcQR5+EtlNfNKGsAuWatVck0k3YaD4KJJ9xtcQadsfO3mSVmhUoBcqxWj6uyizb\nDH9PPEcCNK4Dw5Xm5uN7DNnTwQz8ascvLfHyVA5nK718c1YQA4T05T+6b/2u7I9g\nVcV4wonlbT1wSP0xfpp98+xzbKj3UzoDhw7aIrUCgYBbZ0R4JHIEBqiWWHz78Ixm\nlo0j8HPdqtyqftne2jAHmHO584MCbJm+/g4gmcwOqyz46smhBdNvCXo0n3Z9Be1T\nVc52xFIyUU4+N4ZqzmrJ9T7fZm8aBDzqpCeq6Yta9gOoHNz9/A2o99bLa87EGRkf\nNzWBlpKUVpHbBIWh3NWKiQ==\n-----END PRIVATE KEY-----\n", "selfSigned": false, "ca": false, "valid": true, @@ -1066,18 +1567,16 @@ "revoked": false, "autoRenew": true, "letsEncrypt": false, - "subject": "SURNAME=Otoroshi Default Wildcard Certificate, OU=Otoroshi Certificates, O=Otoroshi, CN=*.oto.tools", - "from": 1669036674000, - "to": 1700572674000, + "subject": "OU=Otoroshi Certificates, O=Otoroshi, CN=Otoroshi Default Jwt Signing Keypair", + "from": 1715604031000, + "to": 1747140031000, "client": false, - "keypair": false, - "sans": [ - "*.oto.tools" - ], - "certType": "certificate", + "keypair": true, + "sans": [], + "certType": "keypair", "password": null, "metadata": { - "csr": "{\"hosts\":[\"*.oto.tools\"],\"key\":{\"algo\":\"rsa\",\"size\":2048},\"name\":{},\"subject\":\"CN=*.oto.tools, SN=Otoroshi Default Wildcard Certificate, OU=Otoroshi Certificates, O=Otoroshi\",\"client\":false,\"ca\":false,\"duration\":31536000000,\"signatureAlg\":\"SHA256WithRSAEncryption\",\"digestAlg\":\"SHA-256\"}" + "csr": "{\"hosts\":[],\"key\":{\"algo\":\"rsa\",\"size\":2048},\"name\":{},\"subject\":\"CN=Otoroshi Default Jwt Signing Keypair, OU=Otoroshi Certificates, O=Otoroshi\",\"client\":false,\"ca\":false,\"duration\":31536000000,\"signatureAlg\":\"SHA256WithRSAEncryption\",\"digestAlg\":\"SHA-256\"}" }, "tags": [] } @@ -1099,14 +1598,640 @@ { "id": "default", "tenant": "default", - "name": "Default Team", - "description": "The default Team of the default organization", + "name": "Default team", + "description": "Default team created for any otoroshi instance", "metadata": {}, "tags": [] } ], - "routes": [], - "services": [], + "routes": [ + { + "_loc": { + "tenant": "default", + "teams": [ + "default" + ] + }, + "id": "route_8ce030cbd-6c07-43d4-9c61-4a330ae0975d", + "name": "child route", + "description": "A new route", + "tags": [], + "metadata": {}, + "enabled": true, + "debug_flow": false, + "export_reporting": false, + "capture": false, + "groups": [ + "group_dev_574c57dd-ab79-48a1-a810-22ba214b25f5" + ], + "bound_listeners": [], + "frontend": { + "domains": [ + "child.oto.tools" + ], + "strip_path": true, + "exact": false, + "headers": {}, + "query": {}, + "methods": [] + }, + "backend": { + "targets": [ + { + "id": "target_1", + "hostname": "mirror.otoroshi.io", + "port": 80, + "tls": false, + "weight": 1, + "predicate": { + "type": "AlwaysMatch" + }, + "protocol": "HTTP/1.1", + "ip_address": null, + "tls_config": { + "certs": [], + "trusted_certs": [], + "enabled": false, + "loose": false, + "trust_all": false + } + } + ], + "root": "/", + "rewrite": false, + "load_balancing": { + "type": "RoundRobin" + }, + "client": { + "retries": 1, + "max_errors": 20, + "retry_initial_delay": 50, + "backoff_factor": 2, + "call_timeout": 30000, + "call_and_stream_timeout": 120000, + "connection_timeout": 10000, + "idle_timeout": 60000, + "global_timeout": 30000, + "sample_interval": 2000, + "proxy": {}, + "custom_timeouts": [], + "cache_connection_settings": { + "enabled": false, + "queue_size": 2048 + } + }, + "health_check": { + "enabled": false, + "url": "", + "timeout": 5000, + "healthyStatuses": [], + "unhealthyStatuses": [] + } + }, + "backend_ref": null, + "plugins": [ + { + "enabled": true, + "debug": false, + "plugin": "cp:otoroshi.next.plugins.OverrideHost", + "include": [], + "exclude": [], + "config": {}, + "bound_listeners": [], + "plugin_index": { + "transform_request": 0 + } + } + ] + }, + { + "_loc": { + "tenant": "default", + "teams": [ + "default" + ] + }, + "id": "route_d74ea8b27-b8be-4177-82d9-c50722416c50", + "name": "parent route", + "description": "A new route", + "tags": [], + "metadata": {}, + "enabled": true, + "debug_flow": false, + "export_reporting": false, + "capture": false, + "groups": [ + "group_dev_574c57dd-ab79-48a1-a810-22ba214b25f5" + ], + "bound_listeners": [], + "frontend": { + "domains": [ + "parent.oto.tools" + ], + "strip_path": true, + "exact": false, + "headers": {}, + "query": {}, + "methods": [] + }, + "backend": { + "targets": [ + { + "id": "target_1", + "hostname": "mirror.otoroshi.io", + "port": 80, + "tls": false, + "weight": 1, + "predicate": { + "type": "AlwaysMatch" + }, + "protocol": "HTTP/1.1", + "ip_address": null, + "tls_config": { + "certs": [], + "trusted_certs": [], + "enabled": false, + "loose": false, + "trust_all": false + } + } + ], + "root": "/", + "rewrite": false, + "load_balancing": { + "type": "RoundRobin" + }, + "client": { + "retries": 1, + "max_errors": 20, + "retry_initial_delay": 50, + "backoff_factor": 2, + "call_timeout": 30000, + "call_and_stream_timeout": 120000, + "connection_timeout": 10000, + "idle_timeout": 60000, + "global_timeout": 30000, + "sample_interval": 2000, + "proxy": {}, + "custom_timeouts": [], + "cache_connection_settings": { + "enabled": false, + "queue_size": 2048 + } + }, + "health_check": { + "enabled": false, + "url": "", + "timeout": 5000, + "healthyStatuses": [], + "unhealthyStatuses": [] + } + }, + "backend_ref": null, + "plugins": [ + { + "enabled": true, + "debug": false, + "plugin": "cp:otoroshi.next.plugins.OverrideHost", + "include": [], + "exclude": [], + "config": {}, + "bound_listeners": [], + "plugin_index": { + "transform_request": 0 + } + } + ] + }, + { + "_loc": { + "tenant": "default", + "teams": [ + "default" + ] + }, + "id": "route_d74ea8b27-b8be-4177-82d9-c50722416c51", + "name": "other route", + "description": "A new route", + "tags": [], + "metadata": {}, + "enabled": true, + "debug_flow": false, + "export_reporting": false, + "capture": false, + "groups": [ + "group_dev_574c57dd-ab79-48a1-a810-22ba214b25f5" + ], + "bound_listeners": [], + "frontend": { + "domains": [ + "other.oto.tools" + ], + "strip_path": true, + "exact": false, + "headers": {}, + "query": {}, + "methods": [] + }, + "backend": { + "targets": [ + { + "id": "target_1", + "hostname": "mirror.otoroshi.io", + "port": 80, + "tls": false, + "weight": 1, + "predicate": { + "type": "AlwaysMatch" + }, + "protocol": "HTTP/1.1", + "ip_address": null, + "tls_config": { + "certs": [], + "trusted_certs": [], + "enabled": false, + "loose": false, + "trust_all": false + } + } + ], + "root": "/", + "rewrite": false, + "load_balancing": { + "type": "RoundRobin" + }, + "client": { + "retries": 1, + "max_errors": 20, + "retry_initial_delay": 50, + "backoff_factor": 2, + "call_timeout": 30000, + "call_and_stream_timeout": 120000, + "connection_timeout": 10000, + "idle_timeout": 60000, + "global_timeout": 30000, + "sample_interval": 2000, + "proxy": {}, + "custom_timeouts": [], + "cache_connection_settings": { + "enabled": false, + "queue_size": 2048 + } + }, + "health_check": { + "enabled": false, + "url": "", + "timeout": 5000, + "healthyStatuses": [], + "unhealthyStatuses": [] + } + }, + "backend_ref": null, + "plugins": [ + { + "enabled": true, + "debug": false, + "plugin": "cp:otoroshi.next.plugins.OverrideHost", + "include": [], + "exclude": [], + "config": {}, + "bound_listeners": [], + "plugin_index": { + "transform_request": 0 + } + } + ] + }, + { + "_loc": { + "tenant": "default", + "teams": [ + "default" + ] + }, + "id": "admin-api-service", + "name": "otoroshi-admin-api", + "description": "", + "tags": [ + "env:prod" + ], + "metadata": { + "otoroshi-core-legacy": "true", + "otoroshi-core-env": "prod" + }, + "enabled": true, + "debug_flow": false, + "export_reporting": false, + "capture": false, + "groups": [ + "admin-api-group" + ], + "bound_listeners": [], + "frontend": { + "domains": [ + "otoroshi-api.oto.tools/" + ], + "strip_path": true, + "exact": false, + "headers": {}, + "query": {}, + "methods": [] + }, + "backend": { + "targets": [ + { + "id": "127.0.0.1:8080", + "hostname": "127.0.0.1", + "port": 8080, + "tls": false, + "weight": 1, + "predicate": { + "type": "AlwaysMatch" + }, + "protocol": "HTTP/1.1", + "ip_address": null, + "tls_config": { + "certs": [], + "trusted_certs": [], + "enabled": false, + "loose": false, + "trust_all": false + } + } + ], + "root": "/", + "rewrite": false, + "load_balancing": { + "type": "RoundRobin" + }, + "client": { + "retries": 1, + "max_errors": 20, + "retry_initial_delay": 50, + "backoff_factor": 2, + "call_timeout": 30000, + "call_and_stream_timeout": 120000, + "connection_timeout": 10000, + "idle_timeout": 60000, + "global_timeout": 30000, + "sample_interval": 2000, + "proxy": {}, + "custom_timeouts": [], + "cache_connection_settings": { + "enabled": false, + "queue_size": 2048 + } + }, + "health_check": { + "enabled": false, + "url": "", + "timeout": 5000, + "healthyStatuses": [], + "unhealthyStatuses": [] + } + }, + "backend_ref": null, + "plugins": [ + { + "enabled": true, + "debug": false, + "plugin": "cp:otoroshi.next.plugins.OverrideHost", + "include": [], + "exclude": [], + "config": {}, + "bound_listeners": [] + }, + { + "enabled": true, + "debug": false, + "plugin": "cp:otoroshi.next.plugins.OtoroshiHeadersIn", + "include": [], + "exclude": [], + "config": {}, + "bound_listeners": [] + }, + { + "enabled": true, + "debug": false, + "plugin": "cp:otoroshi.next.plugins.AdditionalHeadersIn", + "include": [], + "exclude": [], + "config": { + "headers": { + "Host": "otoroshi-admin-internal-api.oto.tools" + } + }, + "bound_listeners": [] + }, + { + "enabled": true, + "debug": false, + "plugin": "cp:otoroshi.next.plugins.SendOtoroshiHeadersBack", + "include": [], + "exclude": [], + "config": {}, + "bound_listeners": [] + }, + { + "enabled": true, + "debug": false, + "plugin": "cp:otoroshi.next.plugins.OtoroshiChallenge", + "include": [], + "exclude": [], + "config": { + "version": "V1", + "ttl": 30, + "request_header_name": "Otoroshi-State", + "response_header_name": "Otoroshi-State-Resp", + "algo_to_backend": { + "type": "HSAlgoSettings", + "size": 512, + "secret": "${config.app.claim.sharedKey}", + "base64": false + }, + "algo_from_backend": { + "type": "HSAlgoSettings", + "size": 512, + "secret": "${config.app.claim.sharedKey}", + "base64": false + }, + "state_resp_leeway": 10 + }, + "bound_listeners": [] + }, + { + "enabled": true, + "debug": false, + "plugin": "cp:otoroshi.next.plugins.NgLegacyApikeyCall", + "include": [], + "exclude": [], + "config": { + "public_patterns": [ + "/health", + "/metrics" + ], + "private_patterns": [], + "extractors": { + "basic": { + "enabled": true, + "header_name": null, + "query_name": null + }, + "custom_headers": { + "enabled": true, + "client_id_header_name": null, + "client_secret_header_name": null + }, + "client_id": { + "enabled": true, + "header_name": null, + "query_name": null + }, + "jwt": { + "enabled": true, + "secret_signed": true, + "keypair_signed": true, + "include_request_attrs": false, + "max_jwt_lifespan_sec": null, + "header_name": null, + "query_name": null, + "cookie_name": null + } + }, + "routing": { + "enabled": false + }, + "validate": true, + "mandatory": true, + "pass_with_user": false, + "wipe_backend_request": true, + "update_quotas": true + }, + "bound_listeners": [] + }, + { + "enabled": true, + "debug": false, + "plugin": "cp:otoroshi.next.plugins.OtoroshiInfos", + "include": [], + "exclude": [], + "config": { + "version": "Legacy", + "ttl": 30, + "header_name": "Otoroshi-Claim", + "add_fields": null, + "algo": { + "type": "HSAlgoSettings", + "size": 512, + "secret": "${config.app.claim.sharedKey}", + "base64": false + } + }, + "bound_listeners": [] + } + ] + } + ], + "routeCompositions": [], "backends": [], - "targets": [] + "wasmPlugins": [], + "extensions": { + "otoroshi_extensions_greenscore": { + "green-scores": [ + { + "_loc": { + "tenant": "default", + "teams": [ + "default" + ] + }, + "id": "green-score_dev_21b8957a-7aa5-4a25-a157-41e82d23a8d4", + "name": "green score group", + "description": "screen score for the routes of this group", + "metadata": { + "created_at": "2024-05-21T10:22:45.927+02:00" + }, + "tags": [], + "routes": [ + { + "routeId": "KAyMY0iHPF7aljWqNkJx3mehRiwJtwKt2ZUybPZDZMIfOJDDJZiXvnrzlQlXyaAc", + "rulesConfig": { + "states": [ + { + "date": 1716249600000, + "states": [] + } + ] + } + }, + { + "routeId": "ZSTWnemHcCDi4yA6h7qJz0wCP9glqeWnCMG9KQrDi17hcapQ5mpN15cWr68y4vs8", + "rulesConfig": { + "states": [ + { + "date": 1716249600000, + "states": [] + } + ] + } + } + ], + "thresholds": { + "overhead": { + "excellent": 5, + "sufficient": 10, + "poor": 15 + }, + "duration": { + "excellent": 5, + "sufficient": 10, + "poor": 15 + }, + "backendDuration": { + "excellent": 5, + "sufficient": 10, + "poor": 15 + }, + "calls": { + "excellent": 5, + "sufficient": 10, + "poor": 15 + }, + "dataIn": { + "excellent": 100, + "sufficient": 500, + "poor": 1000 + }, + "dataOut": { + "excellent": 100, + "sufficient": 500, + "poor": 1000 + }, + "headersOut": { + "excellent": 10, + "sufficient": 30, + "poor": 50 + }, + "headersIn": { + "excellent": 10, + "sufficient": 30, + "poor": 50 + } + }, + "efficiency": { + "paths": [] + } + } + ] + }, + "otoroshi_extensions_foo": { + "foos": [] + }, + "otoroshi_extensions_httplisteners": { + "http-listeners": [] + }, + "otoroshi_extensions_corazawaf": { + "coraza-configs": [] + } + } } \ No newline at end of file diff --git a/daikoku/test/daikoku/suites.scala b/daikoku/test/daikoku/suites.scala index 9a111a2fb..aa0c33428 100644 --- a/daikoku/test/daikoku/suites.scala +++ b/daikoku/test/daikoku/suites.scala @@ -747,6 +747,20 @@ object utils { lazy val wireMockUrl = s"http://$stubHost:$stubPort" val stubPort = 11112 val stubHost = "localhost" + lazy val containerizedOtoroshiUrl = "http://ototoshi.oto.tools:8080" + + val otoroshiAdminApiKey = OtoroshiApiKey( + clientName = "Otoroshi Backoffice ApiKey", + clientId = "admin-api-apikey-id", + clientSecret= "admin-api-apikey-secret") + val parentApiKey = OtoroshiApiKey( + clientName = "daikoku_test_parent_key", + clientId = "5w24yl2ly3dlnn92", + clientSecret= "8iwm9fhbns0rmybnyul5evq9l1o4dxza0rh7rt4flay69jolw3okbz1owfl6w2db") + + val parentRouteId = "route_d74ea8b27-b8be-4177-82d9-c50722416c50" + val childRouteId = "route_8ce030cbd-6c07-43d4-9c61-4a330ae0975d" + val otherRouteId = "route_d74ea8b27-b8be-4177-82d9-c50722416c51" val teamOwnerId = TeamId("team-owner") val teamConsumerId = TeamId("team-consumer") @@ -759,6 +773,7 @@ object utils { val userTeamUserId = UserId("team-user") val wiremockedOtoroshi = OtoroshiSettingsId("wiremock") + val containerizedOtoroshi = OtoroshiSettingsId("test-container") val teamOwner = Team( id = teamOwnerId, From 775edf0fc1682e306e844403e941236c3d513ad7 Mon Sep 17 00:00:00 2001 From: Quentin AUBERT Date: Mon, 5 Aug 2024 10:46:32 +0200 Subject: [PATCH 002/123] FIX #726 --- daikoku/app/env/env.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/daikoku/app/env/env.scala b/daikoku/app/env/env.scala index c79be4c83..46c286175 100644 --- a/daikoku/app/env/env.scala +++ b/daikoku/app/env/env.scala @@ -526,6 +526,13 @@ class DaikokuEnv( defaultLanguage = None ) val initialDataFu = for { + _ <- Future.sequence(evolutions.list.map(e => dataStore.evolutionRepo.save( + Evolution( + id = DatastoreId(IdGenerator.token(32)), + version = e.version, + applied = true + ) + ))) _ <- dataStore.tenantRepo.save(tenant) _ <- dataStore.teamRepo.forTenant(tenant.id).save(team) _ <- From f953d78372dba83896fc4bfb33b5e0f79bb1b9a5 Mon Sep 17 00:00:00 2001 From: Quentin AUBERT Date: Mon, 5 Aug 2024 10:48:41 +0200 Subject: [PATCH 003/123] Save admin plan on fresh install --- daikoku/app/env/env.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/daikoku/app/env/env.scala b/daikoku/app/env/env.scala index 46c286175..96c82d0bc 100644 --- a/daikoku/app/env/env.scala +++ b/daikoku/app/env/env.scala @@ -543,6 +543,9 @@ class DaikokuEnv( dataStore.apiRepo .forTenant(tenant.id) .save(adminApiDefaultTenant) + _ <- dataStore.usagePlanRepo + .forTenant(tenant.id) + .save(adminApiDefaultPlan) _ <- dataStore.userRepo.save(user) } yield () From 6dbf8c3c177f58dee7520ab209ba34379665e24f Mon Sep 17 00:00:00 2001 From: Quentin AUBERT Date: Mon, 5 Aug 2024 11:32:39 +0200 Subject: [PATCH 004/123] update verifier job --- daikoku/app/jobs/OtoroshiVerifierJob.scala | 8 ++++++-- daikoku/test/daikoku/ApiControllerSpec.scala | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/daikoku/app/jobs/OtoroshiVerifierJob.scala b/daikoku/app/jobs/OtoroshiVerifierJob.scala index 7af0c2eed..98ddcee57 100644 --- a/daikoku/app/jobs/OtoroshiVerifierJob.scala +++ b/daikoku/app/jobs/OtoroshiVerifierJob.scala @@ -23,7 +23,7 @@ import play.api.libs.json._ import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicReference import scala.concurrent.duration._ -import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.{Await, ExecutionContext, Future} import scala.util.{Failure, Success} object LongExtensions { @@ -720,6 +720,7 @@ class OtoroshiVerifierJob( .forAllTenant() .findNotDeleted(query) ) + log = logger.info(s"allSubscriptions are $allSubscriptions ") //Get admin API adminApi <- EitherT.fromOptionF[Future, AppError, Api]( env.dataStore.apiRepo @@ -752,7 +753,10 @@ class OtoroshiVerifierJob( } yield subscriptions - val _allParentSubscriptions = r.leftMap(_ => Seq.empty[ApiSubscription]).merge + val _allParentSubscriptions = r.leftMap(e => { + logger.error(e.getErrorMessage()) + Seq.empty[ApiSubscription] + }).merge Source .futureSource(_allParentSubscriptions.map(Source(_))) diff --git a/daikoku/test/daikoku/ApiControllerSpec.scala b/daikoku/test/daikoku/ApiControllerSpec.scala index 6fb861267..3c7101ed1 100644 --- a/daikoku/test/daikoku/ApiControllerSpec.scala +++ b/daikoku/test/daikoku/ApiControllerSpec.scala @@ -5392,8 +5392,8 @@ class ApiControllerSpec() ))), users = Seq(userAdmin), teams = Seq(teamOwner, teamConsumer, defaultAdminTeam), - usagePlans = Seq(parentPlan, childPlan), - apis = Seq(parentApi, childApi), + usagePlans = Seq(parentPlan, childPlan, adminApiPlan), + apis = Seq(parentApi, childApi, adminApi), subscriptions = Seq(parentSub, childSub)) From 9c1431f9709b9095cb03e53e172a9f52855379f0 Mon Sep 17 00:00:00 2001 From: Quentin AUBERT Date: Mon, 5 Aug 2024 14:08:19 +0200 Subject: [PATCH 005/123] FIX #722 --- daikoku/app/jobs/OtoroshiVerifierJob.scala | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/daikoku/app/jobs/OtoroshiVerifierJob.scala b/daikoku/app/jobs/OtoroshiVerifierJob.scala index 98ddcee57..563c2a851 100644 --- a/daikoku/app/jobs/OtoroshiVerifierJob.scala +++ b/daikoku/app/jobs/OtoroshiVerifierJob.scala @@ -239,7 +239,6 @@ class OtoroshiVerifierJob( private def computeAPIKey( infos: SyncInformation ): Future[ComputedInformation] = { - synclogger.info(s"begin apk computing for ${infos.parent.id}/${infos.parent.apiKey.clientName} with ${infos.childs.size} child.s") (infos.childs :+ infos.parent) .map(subscription => { for { @@ -455,7 +454,6 @@ class OtoroshiVerifierJob( } }) .map(computedApk => { - synclogger.info(s"apk computing ended for ${computedApk.clientName}") ComputedInformation( parent = infos.parent, childs = infos.childs, @@ -471,7 +469,6 @@ class OtoroshiVerifierJob( private def updateOtoroshiApk(infos: ComputedInformation): Future[Unit] = { if (infos.apk != infos.computedApk) { - synclogger.info(s"update otoroshi APIkey ${infos.computedApk.clientName}") client .updateApiKey(infos.computedApk)(infos.otoroshiSettings) .map { @@ -711,8 +708,6 @@ class OtoroshiVerifierJob( ): Future[Done] = { import cats.implicits._ - synclogger.info(s"begin APIkeys synchronisation -- query: ${Json.stringify(query)}") - val r = for { //Get all "base" subscriptions from provided query allSubscriptions <- EitherT.liftF[Future, AppError, Seq[ApiSubscription]]( @@ -720,7 +715,6 @@ class OtoroshiVerifierJob( .forAllTenant() .findNotDeleted(query) ) - log = logger.info(s"allSubscriptions are $allSubscriptions ") //Get admin API adminApi <- EitherT.fromOptionF[Future, AppError, Api]( env.dataStore.apiRepo @@ -754,7 +748,7 @@ class OtoroshiVerifierJob( } yield subscriptions val _allParentSubscriptions = r.leftMap(e => { - logger.error(e.getErrorMessage()) + synclogger.error(e.getErrorMessage()) Seq.empty[ApiSubscription] }).merge @@ -762,7 +756,6 @@ class OtoroshiVerifierJob( .futureSource(_allParentSubscriptions.map(Source(_))) .mapAsync(1)(subscription => { - synclogger.info(s"sync parent sub ${subscription.apiKey.clientName}") val either = for { childs <- EitherT.liftF( @@ -883,7 +876,6 @@ class OtoroshiVerifierJob( () ) } yield { - synclogger.info(s"get sync information ended - ${apk.clientName}") SyncInformation( parent = subscription, childs = childs, From 773055b142b3651dac450d0c27864b720446fcd4 Mon Sep 17 00:00:00 2001 From: daikoku-github-actions Date: Mon, 5 Aug 2024 13:28:32 +0000 Subject: [PATCH 006/123] Format sources before release --- .../app/controllers/AdminApiController.scala | 14 +- daikoku/app/controllers/ApiController.scala | 20 ++- daikoku/app/controllers/HomeController.scala | 34 +++- daikoku/app/controllers/LoginController.scala | 70 ++++++-- .../OtoroshiSettingsController.scala | 38 +++-- daikoku/app/controllers/TeamController.scala | 7 +- daikoku/app/controllers/UsersController.scala | 13 +- daikoku/app/domain/SchemaDefinition.scala | 12 +- daikoku/app/domain/json.scala | 82 ++++----- daikoku/app/domain/tenantEntities.scala | 41 ++++- daikoku/app/env/env.scala | 30 ++-- daikoku/app/jobs/OtoroshiVerifierJob.scala | 105 ++++++------ daikoku/app/jobs/QueueJob.scala | 64 +++++--- daikoku/app/login/api.scala | 3 +- daikoku/app/login/oauth.scala | 7 +- daikoku/app/utils/ApiService.scala | 114 ++++++++----- daikoku/app/utils/DeletionService.scala | 13 +- daikoku/app/utils/errors.scala | 8 +- daikoku/app/utils/otoroshi.scala | 14 +- daikoku/app/utils/request.scala | 3 +- .../src/components/utils/tenantUtils.ts | 20 ++- daikoku/javascript/src/services/index.ts | 10 +- daikoku/javascript/src/types/api.ts | 4 +- daikoku/javascript/src/types/context.ts | 2 +- daikoku/javascript/src/types/tenant.ts | 1 - .../test/daikoku/AdminApiControllerSpec.scala | 6 +- daikoku/test/daikoku/ApiControllerSpec.scala | 155 ++++++++++-------- daikoku/test/daikoku/suites.scala | 7 +- 28 files changed, 581 insertions(+), 316 deletions(-) diff --git a/daikoku/app/controllers/AdminApiController.scala b/daikoku/app/controllers/AdminApiController.scala index c197934ad..2abd49911 100644 --- a/daikoku/app/controllers/AdminApiController.scala +++ b/daikoku/app/controllers/AdminApiController.scala @@ -526,11 +526,15 @@ class ApiAdminApiController( .findOne( Json.obj( "_id" -> Json.obj("$ne" -> entity.id.asJson), - "name" -> entity.name, - ) ++ entity.parent.map(p => Json.obj("_id" -> p.asJson)).getOrElse(Json.obj()) + "name" -> entity.name + ) ++ entity.parent + .map(p => Json.obj("_id" -> p.asJson)) + .getOrElse(Json.obj()) ) .map { - case Some(api) if entity.parent.contains(api.id) || api.parent.contains(entity.id) => + case Some(api) + if entity.parent.contains(api.id) || api.parent + .contains(entity.id) => Right(()) case Some(_) => Left(AppError.ParsingPayloadError("Api name already exists")) @@ -545,7 +549,9 @@ class ApiAdminApiController( Json.obj( "_id" -> Json.obj("$ne" -> entity.id.asJson), "name" -> entity.name - ) ++ entity.parent.map(p => Json.obj("_id" -> p.asJson)).getOrElse(Json.obj()) + ) ++ entity.parent + .map(p => Json.obj("_id" -> p.asJson)) + .getOrElse(Json.obj()) ) .map { case None => diff --git a/daikoku/app/controllers/ApiController.scala b/daikoku/app/controllers/ApiController.scala index 01d7587f1..1f3a9b12c 100644 --- a/daikoku/app/controllers/ApiController.scala +++ b/daikoku/app/controllers/ApiController.scala @@ -10,11 +10,19 @@ import cats.data.EitherT import cats.implicits.{catsSyntaxOptionId, toTraverseOps} import controllers.AppError import controllers.AppError._ -import fr.maif.otoroshi.daikoku.actions.{DaikokuAction, DaikokuActionContext, DaikokuActionMaybeWithGuest, DaikokuActionMaybeWithoutUser} +import fr.maif.otoroshi.daikoku.actions.{ + DaikokuAction, + DaikokuActionContext, + DaikokuActionMaybeWithGuest, + DaikokuActionMaybeWithoutUser +} import fr.maif.otoroshi.daikoku.audit.AuditTrailEvent import fr.maif.otoroshi.daikoku.audit.config.ElasticAnalyticsConfig import fr.maif.otoroshi.daikoku.ctrls.authorizations.async._ -import fr.maif.otoroshi.daikoku.domain.NotificationAction.{ApiAccess, ApiSubscriptionDemand} +import fr.maif.otoroshi.daikoku.domain.NotificationAction.{ + ApiAccess, + ApiSubscriptionDemand +} import fr.maif.otoroshi.daikoku.domain.UsagePlanVisibility.Private import fr.maif.otoroshi.daikoku.domain._ import fr.maif.otoroshi.daikoku.domain.json._ @@ -85,7 +93,9 @@ class ApiController( def fetchSwagger(api: Api): EitherT[Future, AppError, Result] = { api.swagger match { case Some(SwaggerAccess(_, Some(content), _, _, _)) => - val contentType = if(content.startsWith("{")) "application/json" else "application/yaml" + val contentType = + if (content.startsWith("{")) "application/json" + else "application/yaml" EitherT.pure[Future, AppError](Ok(content).as(contentType)) case Some(SwaggerAccess(Some(url), None, headers, _, _)) => val finalUrl = @@ -97,7 +107,9 @@ class ApiController( .withHttpHeaders(headers.toSeq: _*) .get() .map { resp => - val contentType = if(resp.body.startsWith("{")) "application/json" else "application/yaml" + val contentType = + if (resp.body.startsWith("{")) "application/json" + else "application/yaml" Right( Ok(resp.body).as( resp diff --git a/daikoku/app/controllers/HomeController.scala b/daikoku/app/controllers/HomeController.scala index 66d6bfe69..ae41609ab 100644 --- a/daikoku/app/controllers/HomeController.scala +++ b/daikoku/app/controllers/HomeController.scala @@ -4,12 +4,24 @@ import com.github.blemale.scaffeine.{Cache, Scaffeine} import com.nimbusds.jose.util.StandardCharset import controllers.Assets import daikoku.BuildInfo -import fr.maif.otoroshi.daikoku.actions.{DaikokuAction, DaikokuActionMaybeWithGuest, DaikokuActionMaybeWithoutUser, DaikokuActionMaybeWithoutUserContext} +import fr.maif.otoroshi.daikoku.actions.{ + DaikokuAction, + DaikokuActionMaybeWithGuest, + DaikokuActionMaybeWithoutUser, + DaikokuActionMaybeWithoutUserContext +} import fr.maif.otoroshi.daikoku.audit.AuditTrailEvent -import fr.maif.otoroshi.daikoku.ctrls.authorizations.async.{DaikokuAdminOrSelf, TenantAdminOnly} +import fr.maif.otoroshi.daikoku.ctrls.authorizations.async.{ + DaikokuAdminOrSelf, + TenantAdminOnly +} import fr.maif.otoroshi.daikoku.ctrls.authorizations.sync.TeamMemberOnly import fr.maif.otoroshi.daikoku.domain._ -import fr.maif.otoroshi.daikoku.domain.json.{CmsFileFormat, CmsPageFormat, CmsRequestRenderingFormat} +import fr.maif.otoroshi.daikoku.domain.json.{ + CmsFileFormat, + CmsPageFormat, + CmsRequestRenderingFormat +} import fr.maif.otoroshi.daikoku.env.Env import fr.maif.otoroshi.daikoku.logger.AppLogger import fr.maif.otoroshi.daikoku.utils.{Errors, IdGenerator, diff_match_patch} @@ -113,12 +125,20 @@ class HomeController( def getConnectedUser() = { DaikokuActionMaybeWithoutUser { ctx => - Ok( Json.obj( - "connectedUser" -> ctx.user.map(_.toUiPayload()).getOrElse(JsNull).as[JsValue], - "impersonator" -> ctx.session.map(_.impersonatorJson()).getOrElse(JsNull).as[JsValue], - "session" -> ctx.session.map(_.asSimpleJson).getOrElse(JsNull).as[JsValue], + "connectedUser" -> ctx.user + .map(_.toUiPayload()) + .getOrElse(JsNull) + .as[JsValue], + "impersonator" -> ctx.session + .map(_.impersonatorJson()) + .getOrElse(JsNull) + .as[JsValue], + "session" -> ctx.session + .map(_.asSimpleJson) + .getOrElse(JsNull) + .as[JsValue], "tenant" -> ctx.tenant.toUiPayload(env), "isTenantAdmin" -> ctx.isTenantAdmin, "apiCreationPermitted" -> ctx.apiCreationPermitted, diff --git a/daikoku/app/controllers/LoginController.scala b/daikoku/app/controllers/LoginController.scala index f25b5a4cb..f4314f644 100644 --- a/daikoku/app/controllers/LoginController.scala +++ b/daikoku/app/controllers/LoginController.scala @@ -2,7 +2,12 @@ package fr.maif.otoroshi.daikoku.ctrls import com.eatthepath.otp.TimeBasedOneTimePasswordGenerator import controllers.Assets -import fr.maif.otoroshi.daikoku.actions.{DaikokuAction, DaikokuActionMaybeWithoutUser, DaikokuTenantAction, DaikokuTenantActionContext} +import fr.maif.otoroshi.daikoku.actions.{ + DaikokuAction, + DaikokuActionMaybeWithoutUser, + DaikokuTenantAction, + DaikokuTenantActionContext +} import fr.maif.otoroshi.daikoku.audit.{AuditTrailEvent, AuthorizationLevel} import fr.maif.otoroshi.daikoku.domain.TeamPermission.Administrator import fr.maif.otoroshi.daikoku.domain._ @@ -97,12 +102,13 @@ class LoginController( DaikokuActionMaybeWithoutUser { _ => Ok( Json.obj( - "action" -> fr.maif.otoroshi.daikoku.ctrls.routes.LoginController.login(provider).url + "action" -> fr.maif.otoroshi.daikoku.ctrls.routes.LoginController + .login(provider) + .url ) ) } - def loginPage(provider: String) = DaikokuTenantAction.async { ctx => AuthProvider(provider) match { @@ -146,7 +152,12 @@ class LoginController( s"redirect" -> redirect.getOrElse("/") ) ) - case _ if env.config.isDev => FastFuture.successful(Redirect(env.getDaikokuUrl(ctx.tenant, s"/auth/${p.name}/login"))) + case _ if env.config.isDev => + FastFuture.successful( + Redirect( + env.getDaikokuUrl(ctx.tenant, s"/auth/${p.name}/login") + ) + ) case _ => assets.at("index.html").apply(ctx.request) } } @@ -547,7 +558,10 @@ class LoginController( ctx.tenant, Map( "tenant" -> ctx.tenant.name, - "link" -> env.getDaikokuUrl(ctx.tenant, s"/account/validate?id=$randomId") + "link" -> env.getDaikokuUrl( + ctx.tenant, + s"/account/validate?id=$randomId" + ) ) ) } yield { @@ -644,8 +658,12 @@ class LoginController( } yield () userCreation.map { _ => Status(302)( - Json.obj("Location" -> "/?message=user.validated.success") - ).withHeaders("Location" -> "/?message=user.validated.success") + Json.obj( + "Location" -> "/?message=user.validated.success" + ) + ).withHeaders( + "Location" -> "/?message=user.validated.success" + ) } } case _ => @@ -701,8 +719,9 @@ class LoginController( ctx.tenant.defaultLanguage.getOrElse("en") implicit val language: String = user.defaultLanguage.getOrElse(tenantLanguage) - val link = env.getDaikokuUrl(ctx.tenant, s"/account/reset?id=$randomId") - ctx.tenant.mailer + val link = + env.getDaikokuUrl(ctx.tenant, s"/account/reset?id=$randomId") + ctx.tenant.mailer .send( s"Reset your ${ctx.tenant.name} account password", Seq(email), @@ -735,7 +754,10 @@ class LoginController( ctx.request.getQueryString("id") match { case None => FastFuture.successful( - Redirect(env.getDaikokuUrl(ctx.tenant, "/reset?error=bad.creation.id"))) + Redirect( + env.getDaikokuUrl(ctx.tenant, "/reset?error=bad.creation.id") + ) + ) case Some(id) => { env.dataStore.passwordResetRepo .findOneNotDeleted(Json.obj("randomId" -> id)) @@ -745,7 +767,12 @@ class LoginController( env.dataStore.passwordResetRepo .deleteByIdLogically(pwdReset.id.value) .map { _ => - Redirect(env.getDaikokuUrl(ctx.tenant, "/reset?error=not.valid.anymore")) + Redirect( + env.getDaikokuUrl( + ctx.tenant, + "/reset?error=not.valid.anymore" + ) + ) } case Some(pwdReset) if pwdReset.validUntil.isAfter(DateTime.now()) => @@ -759,7 +786,13 @@ class LoginController( .flatMap { case None => FastFuture.successful( - Redirect(env.getDaikokuUrl(ctx.tenant, "/reset?error=user.not.found"))) + Redirect( + env.getDaikokuUrl( + ctx.tenant, + "/reset?error=user.not.found" + ) + ) + ) case Some(user) => env.dataStore.userRepo .save(user.copy(password = Some(pwdReset.password))) @@ -767,13 +800,22 @@ class LoginController( env.dataStore.passwordResetRepo .deleteByIdLogically(pwdReset.id.value) .map { _ => - Redirect(env.getDaikokuUrl(ctx.tenant, "/?message=password.reset.successfull")) + Redirect( + env.getDaikokuUrl( + ctx.tenant, + "/?message=password.reset.successfull" + ) + ) } } } case _ => FastFuture.successful( - Redirect(env.getDaikokuUrl(ctx.tenant, "/reset?error=bad.creation.id"))) + Redirect( + env + .getDaikokuUrl(ctx.tenant, "/reset?error=bad.creation.id") + ) + ) } } } diff --git a/daikoku/app/controllers/OtoroshiSettingsController.scala b/daikoku/app/controllers/OtoroshiSettingsController.scala index ddcf0daaa..e2f3da746 100644 --- a/daikoku/app/controllers/OtoroshiSettingsController.scala +++ b/daikoku/app/controllers/OtoroshiSettingsController.scala @@ -584,7 +584,8 @@ class OtoroshiSettingsController( if (previousSettings != actualSettings) { for { _ <- otoroshiClient.deleteApiKey(key.clientId)(previousSettings) - newKey <-EitherT(otoroshiClient.createApiKey(key)(actualSettings)) + newKey <- + EitherT(otoroshiClient.createApiKey(key)(actualSettings)) } yield newKey } else { EitherT(otoroshiClient.updateApiKey(key)(previousSettings)) @@ -661,16 +662,25 @@ class OtoroshiSettingsController( val otoroshiSettingsOpt = (ctx.request.body \ "otoroshiSettings").asOpt[String] - val clientIdOpt = (ctx.request.body \ "clientId").asOpt[String] (for { - otoroshiSettingsId <- EitherT.fromOption[Future](otoroshiSettingsOpt, AppError.EntityNotFound("Otoroshi settings")) - clientId <- EitherT.fromOption[Future](clientIdOpt, AppError.EntityNotFound("clientId settings")) - otoroshiSettings <- EitherT.fromOption[Future](ctx.tenant.otoroshiSettings - .find(s => s.id.value == otoroshiSettingsId), AppError.EntityNotFound("Otoroshi settings")) - _ <- otoroshiClient - .deleteApiKey(clientId)(otoroshiSettings) + otoroshiSettingsId <- EitherT.fromOption[Future]( + otoroshiSettingsOpt, + AppError.EntityNotFound("Otoroshi settings") + ) + clientId <- EitherT.fromOption[Future]( + clientIdOpt, + AppError.EntityNotFound("clientId settings") + ) + otoroshiSettings <- EitherT.fromOption[Future]( + ctx.tenant.otoroshiSettings + .find(s => s.id.value == otoroshiSettingsId), + AppError.EntityNotFound("Otoroshi settings") + ) + _ <- + otoroshiClient + .deleteApiKey(clientId)(otoroshiSettings) } yield Ok(Json.obj("done" -> true))) .leftMap(_.render()) .merge @@ -713,7 +723,8 @@ class OtoroshiSettingsController( .find(_._2 == s"fake-${api.id.value}") val headerOpt = headers.find(_._2 == s"fake-${api.id.value}") val finalUrl = api.testing match { - case Some(Testing(_, auth, _, username, _, _)) if queryOpt.isDefined && auth.name == TestingAuth.ApiKey.name => + case Some(Testing(_, auth, _, username, _, _)) + if queryOpt.isDefined && auth.name == TestingAuth.ApiKey.name => url .replace( s"&${queryOpt.get._1}=fake-${api.id.value}", @@ -727,9 +738,12 @@ class OtoroshiSettingsController( } val finalHeaders: Map[String, String] = api.testing match { - case Some(Testing(_, auth, _, username, _, _)) if auth.name == TestingAuth.ApiKey.name && headerOpt.isDefined => - headers - headerOpt.get._1 + (headerOpt.get._1 -> username.getOrElse("")) - case Some(Testing(_, auth, _, username, password, _)) if auth.name == TestingAuth.Basic.name => + case Some(Testing(_, auth, _, username, _, _)) + if auth.name == TestingAuth.ApiKey.name && headerOpt.isDefined => + headers - headerOpt.get._1 + (headerOpt.get._1 -> username + .getOrElse("")) + case Some(Testing(_, auth, _, username, password, _)) + if auth.name == TestingAuth.Basic.name => headers - "Authorization" + ("Authorization" -> s"Basic ${Base64 .encodeBase64String(s"${username.getOrElse("")}:$password".getBytes(Charsets.UTF_8))}") case _ => headers diff --git a/daikoku/app/controllers/TeamController.scala b/daikoku/app/controllers/TeamController.scala index 285c4636a..080d652a3 100644 --- a/daikoku/app/controllers/TeamController.scala +++ b/daikoku/app/controllers/TeamController.scala @@ -848,8 +848,11 @@ class TeamController( implicit val language: String = tenant.defaultLanguage.getOrElse("en") - val link = env.getDaikokuUrl(ctx.tenant, s"/join?token=${invitedUser.invitation.get.token}") - tenant.mailer + val link = env.getDaikokuUrl( + ctx.tenant, + s"/join?token=${invitedUser.invitation.get.token}" + ) + tenant.mailer .send( s"Join ${team.name}", Seq(email), diff --git a/daikoku/app/controllers/UsersController.scala b/daikoku/app/controllers/UsersController.scala index 677fc2c07..d0b60a991 100644 --- a/daikoku/app/controllers/UsersController.scala +++ b/daikoku/app/controllers/UsersController.scala @@ -4,7 +4,11 @@ import cats.data.EitherT import org.apache.pekko.http.scaladsl.util.FastFuture import com.eatthepath.otp.TimeBasedOneTimePasswordGenerator import controllers.AppError -import fr.maif.otoroshi.daikoku.actions.{DaikokuAction, DaikokuActionMaybeWithGuest, DaikokuActionMaybeWithoutUser} +import fr.maif.otoroshi.daikoku.actions.{ + DaikokuAction, + DaikokuActionMaybeWithGuest, + DaikokuActionMaybeWithoutUser +} import fr.maif.otoroshi.daikoku.audit.AuditTrailEvent import fr.maif.otoroshi.daikoku.ctrls.authorizations.async._ import fr.maif.otoroshi.daikoku.domain.TeamPermission.Administrator @@ -17,7 +21,12 @@ import org.apache.commons.codec.binary.Base32 import org.joda.time.{DateTime, Hours} import org.mindrot.jbcrypt.BCrypt import play.api.libs.json.{JsArray, JsError, JsSuccess, Json} -import play.api.mvc.{AbstractController, Action, AnyContent, ControllerComponents} +import play.api.mvc.{ + AbstractController, + Action, + AnyContent, + ControllerComponents +} import java.time.Instant import java.util.Base64 diff --git a/daikoku/app/domain/SchemaDefinition.scala b/daikoku/app/domain/SchemaDefinition.scala index 49630ab84..c23495066 100644 --- a/daikoku/app/domain/SchemaDefinition.scala +++ b/daikoku/app/domain/SchemaDefinition.scala @@ -1712,7 +1712,11 @@ object SchemaDefinition { ), ReplaceField( "specificationType", - Field("specificationType", StringType, resolve = _.value.specificationType.name) + Field( + "specificationType", + StringType, + resolve = _.value.specificationType.name + ) ) ) lazy val ApiDocumentationPageType = deriveObjectType[ @@ -2326,7 +2330,11 @@ object SchemaDefinition { ), Field("isDefault", BooleanType, resolve = _.value.isDefault), Field("lastUpdate", DateTimeUnitype, resolve = _.value.lastUpdate), - Field("testing", OptionType(TestingType), resolve = _.value.testing), + Field( + "testing", + OptionType(TestingType), + resolve = _.value.testing + ), Field( "documentation", ApiDocumentationType, diff --git a/daikoku/app/domain/json.scala b/daikoku/app/domain/json.scala index 55c0a9726..98afebe58 100644 --- a/daikoku/app/domain/json.scala +++ b/daikoku/app/domain/json.scala @@ -191,7 +191,7 @@ object json { override def reads(json: JsValue): JsResult[SpecificationType] = json.as[String] match { case "openapi" => JsSuccess(SpecificationType.OpenApi) - case "asyncapi" => JsSuccess(SpecificationType.AsyncApi) + case "asyncapi" => JsSuccess(SpecificationType.AsyncApi) case str => JsError(s"Bad specification type value: $str") } @@ -201,26 +201,28 @@ object json { val TestingFormat = new Format[Testing] { override def reads(json: JsValue): JsResult[Testing] = { json match { - case JsObject(_) => Try { - JsSuccess( - Testing( - enabled = (json \ "enabled").asOpt[Boolean].getOrElse(false), - auth = (json \ "auth").asOpt[String].filter(_.trim.nonEmpty) match { - case Some("ApiKey") => TestingAuth.ApiKey - case Some("Basic") => TestingAuth.Basic - case _ => TestingAuth.Basic - }, - name = (json \ "name").asOpt[String].filter(_.trim.nonEmpty), - username = - (json \ "username").asOpt[String].filter(_.trim.nonEmpty), - password = - (json \ "password").asOpt[String].filter(_.trim.nonEmpty), - config = (json \ "config").asOpt(TestingConfigFormat) + case JsObject(_) => + Try { + JsSuccess( + Testing( + enabled = (json \ "enabled").asOpt[Boolean].getOrElse(false), + auth = + (json \ "auth").asOpt[String].filter(_.trim.nonEmpty) match { + case Some("ApiKey") => TestingAuth.ApiKey + case Some("Basic") => TestingAuth.Basic + case _ => TestingAuth.Basic + }, + name = (json \ "name").asOpt[String].filter(_.trim.nonEmpty), + username = + (json \ "username").asOpt[String].filter(_.trim.nonEmpty), + password = + (json \ "password").asOpt[String].filter(_.trim.nonEmpty), + config = (json \ "config").asOpt(TestingConfigFormat) + ) ) - ) - } recover { - case e => JsError(e.getMessage) - } get + } recover { + case e => JsError(e.getMessage) + } get case _ => JsError() } @@ -1810,25 +1812,26 @@ object json { val SwaggerAccessFormat = new Format[SwaggerAccess] { override def reads(json: JsValue): JsResult[SwaggerAccess] = { json match { - case JsObject(_) => Try { - JsSuccess( - SwaggerAccess( - url = (json \ "url").asOpt[String], - content = (json \ "content").asOpt[String], - headers = (json \ "headers") - .asOpt[Map[String, String]] - .getOrElse(Map.empty[String, String]), - additionalConf = (json \ "additionalConf").asOpt[JsObject], - specificationType = (json \ "specificationType") - .asOpt(SpecificationTypeFormat) - .getOrElse(SpecificationType.OpenApi) + case JsObject(_) => + Try { + JsSuccess( + SwaggerAccess( + url = (json \ "url").asOpt[String], + content = (json \ "content").asOpt[String], + headers = (json \ "headers") + .asOpt[Map[String, String]] + .getOrElse(Map.empty[String, String]), + additionalConf = (json \ "additionalConf").asOpt[JsObject], + specificationType = (json \ "specificationType") + .asOpt(SpecificationTypeFormat) + .getOrElse(SpecificationType.OpenApi) + ) ) - ) - } recover { - case e => - AppLogger.error(e.getMessage, e) - JsError(e.getMessage) - } get + } recover { + case e => + AppLogger.error(e.getMessage, e) + JsError(e.getMessage) + } get case _ => JsError() } @@ -2564,8 +2567,7 @@ object json { .asOpt(SeqVersionFormat) .map(_.toSet) .getOrElse(Set.empty), - testing = - (json \ "testing").asOpt(TestingFormat), + testing = (json \ "testing").asOpt(TestingFormat), documentation = (json \ "documentation") .as(ApiDocumentationFormat), swagger = (json \ "swagger").asOpt(SwaggerAccessFormat), diff --git a/daikoku/app/domain/tenantEntities.scala b/daikoku/app/domain/tenantEntities.scala index 6014482b2..27c8376f1 100644 --- a/daikoku/app/domain/tenantEntities.scala +++ b/daikoku/app/domain/tenantEntities.scala @@ -5,10 +5,16 @@ import com.github.jknack.handlebars.{Context, Handlebars, Options} import controllers.AppError.toJson import controllers.{AppError, Assets} import domain.JsonNodeValueResolver -import fr.maif.otoroshi.daikoku.actions.{DaikokuActionContext, DaikokuActionMaybeWithoutUserContext} +import fr.maif.otoroshi.daikoku.actions.{ + DaikokuActionContext, + DaikokuActionMaybeWithoutUserContext +} import fr.maif.otoroshi.daikoku.audit.config.{ElasticAnalyticsConfig, Webhook} import fr.maif.otoroshi.daikoku.audit.{AuditTrailEvent, KafkaConfig} -import fr.maif.otoroshi.daikoku.ctrls.authorizations.async.{_TeamMemberOnly, _UberPublicUserAccess} +import fr.maif.otoroshi.daikoku.ctrls.authorizations.async.{ + _TeamMemberOnly, + _UberPublicUserAccess +} import fr.maif.otoroshi.daikoku.env.Env import fr.maif.otoroshi.daikoku.login.AuthProvider import fr.maif.otoroshi.daikoku.utils.StringImplicits.BetterString @@ -453,14 +459,33 @@ case class Tenant( "environments" -> JsArray(environments.map(JsString.apply).toSeq), "loginProvider" -> authProvider.name, "cmsRedirections" -> JsArray(cmsRedirections.map(JsString.apply).toSeq), - "colorTheme" -> style.map(_.colorTheme).map(JsString.apply).getOrElse(JsNull).as[JsValue], - "css" -> style.map(_.css).map(JsString.apply).getOrElse(JsNull).as[JsValue], - "cssUrl" -> style.flatMap(_.cssUrl).map(JsString.apply).getOrElse(JsNull).as[JsValue], - "jsUrl" -> style.flatMap(_.jsUrl).map(JsString.apply).getOrElse(JsNull).as[JsValue], + "colorTheme" -> style + .map(_.colorTheme) + .map(JsString.apply) + .getOrElse(JsNull) + .as[JsValue], + "css" -> style + .map(_.css) + .map(JsString.apply) + .getOrElse(JsNull) + .as[JsValue], + "cssUrl" -> style + .flatMap(_.cssUrl) + .map(JsString.apply) + .getOrElse(JsNull) + .as[JsValue], + "jsUrl" -> style + .flatMap(_.jsUrl) + .map(JsString.apply) + .getOrElse(JsNull) + .as[JsValue], "js" -> style.map(_.js).map(JsString.apply).getOrElse(JsNull).as[JsValue], "faviconUrl" -> favicon(), - "fontFamilyUrl" -> style.flatMap(_.fontFamilyUrl).map(JsString.apply).getOrElse(JsNull).as[JsValue], - + "fontFamilyUrl" -> style + .flatMap(_.fontFamilyUrl) + .map(JsString.apply) + .getOrElse(JsNull) + .as[JsValue] ) } def favicon(): String = { diff --git a/daikoku/app/env/env.scala b/daikoku/app/env/env.scala index 96c82d0bc..c7d3635cd 100644 --- a/daikoku/app/env/env.scala +++ b/daikoku/app/env/env.scala @@ -10,7 +10,12 @@ import com.auth0.jwt.{JWT, JWTVerifier} import fr.maif.otoroshi.daikoku.audit.AuditActorSupervizer import fr.maif.otoroshi.daikoku.domain.TeamPermission.Administrator import fr.maif.otoroshi.daikoku.domain.UsagePlan.FreeWithoutQuotas -import fr.maif.otoroshi.daikoku.domain.{DatastoreId, ReportsInfo, TeamApiKeyVisibility, Tenant} +import fr.maif.otoroshi.daikoku.domain.{ + DatastoreId, + ReportsInfo, + TeamApiKeyVisibility, + Tenant +} import fr.maif.otoroshi.daikoku.logger.AppLogger import fr.maif.otoroshi.daikoku.login.LoginFilter import fr.maif.otoroshi.daikoku.utils._ @@ -526,13 +531,17 @@ class DaikokuEnv( defaultLanguage = None ) val initialDataFu = for { - _ <- Future.sequence(evolutions.list.map(e => dataStore.evolutionRepo.save( - Evolution( - id = DatastoreId(IdGenerator.token(32)), - version = e.version, - applied = true + _ <- Future.sequence( + evolutions.list.map(e => + dataStore.evolutionRepo.save( + Evolution( + id = DatastoreId(IdGenerator.token(32)), + version = e.version, + applied = true + ) + ) ) - ))) + ) _ <- dataStore.tenantRepo.save(tenant) _ <- dataStore.teamRepo.forTenant(tenant.id).save(team) _ <- @@ -543,9 +552,10 @@ class DaikokuEnv( dataStore.apiRepo .forTenant(tenant.id) .save(adminApiDefaultTenant) - _ <- dataStore.usagePlanRepo - .forTenant(tenant.id) - .save(adminApiDefaultPlan) + _ <- + dataStore.usagePlanRepo + .forTenant(tenant.id) + .save(adminApiDefaultPlan) _ <- dataStore.userRepo.save(user) } yield () diff --git a/daikoku/app/jobs/OtoroshiVerifierJob.scala b/daikoku/app/jobs/OtoroshiVerifierJob.scala index 563c2a851..21c982ece 100644 --- a/daikoku/app/jobs/OtoroshiVerifierJob.scala +++ b/daikoku/app/jobs/OtoroshiVerifierJob.scala @@ -7,7 +7,10 @@ import cats.data.EitherT import cats.syntax.option._ import controllers.AppError import fr.maif.otoroshi.daikoku.audit.{ApiKeyRotationEvent, JobEvent} -import fr.maif.otoroshi.daikoku.domain.NotificationAction.{OtoroshiSyncApiError, OtoroshiSyncSubscriptionError} +import fr.maif.otoroshi.daikoku.domain.NotificationAction.{ + OtoroshiSyncApiError, + OtoroshiSyncSubscriptionError +} import fr.maif.otoroshi.daikoku.domain._ import fr.maif.otoroshi.daikoku.domain.json.ApiSubscriptionyRotationFormat import fr.maif.otoroshi.daikoku.env.Env @@ -93,9 +96,9 @@ class OtoroshiVerifierJob( ) def getListFromMeta( - key: String, - metadata: Map[String, String] - ): Set[String] = { + key: String, + metadata: Map[String, String] + ): Set[String] = { metadata .get(key) .map(_.split('|').toSeq.map(_.trim).toSet) @@ -103,35 +106,35 @@ class OtoroshiVerifierJob( } def mergeMetaValue( - key: String, - meta1: Map[String, String], - meta2: Map[String, String] - ): String = { + key: String, + meta1: Map[String, String], + meta2: Map[String, String] + ): String = { val list1 = getListFromMeta(key, meta1) val list2 = getListFromMeta(key, meta2) (list1 ++ list2).mkString(" | ") } case class SyncInformation( - parent: ApiSubscription, - childs: Seq[ApiSubscription], - team: Team, - parentApi: Api, - apk: ActualOtoroshiApiKey, - otoroshiSettings: OtoroshiSettings, - tenant: Tenant, - tenantAdminTeam: Team - ) + parent: ApiSubscription, + childs: Seq[ApiSubscription], + team: Team, + parentApi: Api, + apk: ActualOtoroshiApiKey, + otoroshiSettings: OtoroshiSettings, + tenant: Tenant, + tenantAdminTeam: Team + ) case class ComputedInformation( - parent: ApiSubscription, - childs: Seq[ApiSubscription], - apk: ActualOtoroshiApiKey, - computedApk: ActualOtoroshiApiKey, - otoroshiSettings: OtoroshiSettings, - tenant: Tenant, - tenantAdminTeam: Team - ) + parent: ApiSubscription, + childs: Seq[ApiSubscription], + apk: ActualOtoroshiApiKey, + computedApk: ActualOtoroshiApiKey, + otoroshiSettings: OtoroshiSettings, + tenant: Tenant, + tenantAdminTeam: Team + ) def start(): Unit = { if ( @@ -237,8 +240,8 @@ class OtoroshiVerifierJob( } private def computeAPIKey( - infos: SyncInformation - ): Future[ComputedInformation] = { + infos: SyncInformation + ): Future[ComputedInformation] = { (infos.childs :+ infos.parent) .map(subscription => { for { @@ -403,12 +406,16 @@ class OtoroshiVerifierJob( } }) - .foldLeft(infos.apk.copy( - authorizedEntities = AuthorizedEntities(), - tags = Set.empty, - metadata = Map.empty, - restrictions = ApiKeyRestrictions(), - ).future)((_apikey1, either) => { + .foldLeft( + infos.apk + .copy( + authorizedEntities = AuthorizedEntities(), + tags = Set.empty, + metadata = Map.empty, + restrictions = ApiKeyRestrictions() + ) + .future + )((_apikey1, either) => { either.value.flatMap { case Left(_) => _apikey1 case Right(apikey2) => @@ -462,8 +469,8 @@ class OtoroshiVerifierJob( otoroshiSettings = infos.otoroshiSettings, tenant = infos.tenant, tenantAdminTeam = infos.tenantAdminTeam - )} - ) + ) + }) } @@ -564,9 +571,11 @@ class OtoroshiVerifierJob( synclogger.error(e.getErrorMessage()) } } else { - FastFuture.successful(synclogger.info( - s"No need to update api key: ${infos.apk.clientName} on ${infos.otoroshiSettings.host}" - )) + FastFuture.successful( + synclogger.info( + s"No need to update api key: ${infos.apk.clientName} on ${infos.otoroshiSettings.host}" + ) + ) } } @@ -704,8 +713,8 @@ class OtoroshiVerifierJob( */ private def synchronizeApikeys( - query: JsObject = Json.obj("parent" -> JsNull) - ): Future[Done] = { + query: JsObject = Json.obj("parent" -> JsNull) + ): Future[Done] = { import cats.implicits._ val r = for { @@ -747,10 +756,12 @@ class OtoroshiVerifierJob( } yield subscriptions - val _allParentSubscriptions = r.leftMap(e => { - synclogger.error(e.getErrorMessage()) - Seq.empty[ApiSubscription] - }).merge + val _allParentSubscriptions = r + .leftMap(e => { + synclogger.error(e.getErrorMessage()) + Seq.empty[ApiSubscription] + }) + .merge Source .futureSource(_allParentSubscriptions.map(Source(_))) @@ -857,10 +868,8 @@ class OtoroshiVerifierJob( sendErrorNotification( NotificationAction.OtoroshiSyncSubscriptionError( subscription, - s"Unable to fetch apikey from otoroshi: ${ - Json - .stringify(AppError.toJson(e)) - }" + s"Unable to fetch apikey from otoroshi: ${Json + .stringify(AppError.toJson(e))}" ), parentApi.team, parentApi.tenant, diff --git a/daikoku/app/jobs/QueueJob.scala b/daikoku/app/jobs/QueueJob.scala index f97d26603..1fb50909c 100644 --- a/daikoku/app/jobs/QueueJob.scala +++ b/daikoku/app/jobs/QueueJob.scala @@ -536,35 +536,45 @@ class QueueJob( def deleteFirstOperation(): Future[Unit] = { val value: EitherT[Future, Unit, Unit] = for { - alreadyRunning <- EitherT.liftF(env.dataStore.operationRepo.forAllTenant() - .exists(Json.obj("Status" -> OperationStatus.InProgress.name))) + alreadyRunning <- EitherT.liftF( + env.dataStore.operationRepo + .forAllTenant() + .exists(Json.obj("Status" -> OperationStatus.InProgress.name)) + ) _ <- EitherT.cond[Future][Unit, Unit](!alreadyRunning, (), ()) - firstOperation <- EitherT.fromOptionF[Future, Unit, Operation](env.dataStore.operationRepo - .forAllTenant() - .findOne( - Json.obj( - "$and" -> Json.arr( - Json.obj("status" -> Json.obj("$ne" -> OperationStatus.Error.name)), - Json.obj("status" -> OperationStatus.Idle.name) + firstOperation <- EitherT.fromOptionF[Future, Unit, Operation]( + env.dataStore.operationRepo + .forAllTenant() + .findOne( + Json.obj( + "$and" -> Json.arr( + Json.obj( + "status" -> Json.obj("$ne" -> OperationStatus.Error.name) + ), + Json.obj("status" -> OperationStatus.Idle.name) + ) ) - ) - ), ()) - _ <- EitherT.liftF((firstOperation.itemType, firstOperation.action) match { - case (ItemType.Subscription, OperationAction.Delete) => - deleteSubscription(firstOperation) - case (ItemType.Api, OperationAction.Delete) => deleteApi(firstOperation) - case (ItemType.Team, OperationAction.Delete) => - deleteTeam(firstOperation) - case (ItemType.User, OperationAction.Delete) => - deleteUser(firstOperation) - case (ItemType.ThirdPartySubscription, OperationAction.Delete) => - deleteThirdPartySubscription(firstOperation) - case (ItemType.ThirdPartyProduct, OperationAction.Delete) => - deleteThirdPartyProduct(firstOperation) - case (ItemType.ApiKeyConsumption, OperationAction.Sync) => - syncConsumption(firstOperation) - case (_, _) => FastFuture.successful(()) - }) + ), + () + ) + _ <- + EitherT.liftF((firstOperation.itemType, firstOperation.action) match { + case (ItemType.Subscription, OperationAction.Delete) => + deleteSubscription(firstOperation) + case (ItemType.Api, OperationAction.Delete) => + deleteApi(firstOperation) + case (ItemType.Team, OperationAction.Delete) => + deleteTeam(firstOperation) + case (ItemType.User, OperationAction.Delete) => + deleteUser(firstOperation) + case (ItemType.ThirdPartySubscription, OperationAction.Delete) => + deleteThirdPartySubscription(firstOperation) + case (ItemType.ThirdPartyProduct, OperationAction.Delete) => + deleteThirdPartyProduct(firstOperation) + case (ItemType.ApiKeyConsumption, OperationAction.Sync) => + syncConsumption(firstOperation) + case (_, _) => FastFuture.successful(()) + }) _ <- EitherT.liftF(deleteFirstOperation()) } yield () value.merge diff --git a/daikoku/app/login/api.scala b/daikoku/app/login/api.scala index f87991fee..b5a307946 100644 --- a/daikoku/app/login/api.scala +++ b/daikoku/app/login/api.scala @@ -186,8 +186,7 @@ object TenantHelper { Results.NotFound, request, None, - env, - + env ) case Some(tenant) if !tenant.enabled => Errors.craftResponseResult( diff --git a/daikoku/app/login/oauth.scala b/daikoku/app/login/oauth.scala index 857de3b72..5ab4404a4 100644 --- a/daikoku/app/login/oauth.scala +++ b/daikoku/app/login/oauth.scala @@ -241,9 +241,12 @@ object OAuth2Support { .getOrElse("no.name@foo.bar") val picture = (userFromOauth \ authConfig.pictureField).asOpt[String] - val maybeDaikokuAdmin = (userFromOauth \ "daikokuAdmin").asOpt[Boolean] + val maybeDaikokuAdmin = + (userFromOauth \ "daikokuAdmin").asOpt[Boolean] - val isDaikokuAdmin = maybeDaikokuAdmin.getOrElse(authConfig.daikokuAdmins.contains(email)) + val isDaikokuAdmin = maybeDaikokuAdmin.getOrElse( + authConfig.daikokuAdmins.contains(email) + ) _env.dataStore.userRepo .findOne(Json.obj("_deleted" -> false, "email" -> email)) diff --git a/daikoku/app/utils/ApiService.scala b/daikoku/app/utils/ApiService.scala index 3c3e83519..8e981fad2 100644 --- a/daikoku/app/utils/ApiService.scala +++ b/daikoku/app/utils/ApiService.scala @@ -425,53 +425,85 @@ class ApiService( } def updateSubscription( - tenant: Tenant, - subscription: ApiSubscription, - plan: UsagePlan - ): Future[Either[AppError, JsObject]] = { + tenant: Tenant, + subscription: ApiSubscription, + plan: UsagePlan + ): Future[Either[AppError, JsObject]] = { import cats.implicits._ - val maybeTarget = plan.otoroshiTarget.map(_.otoroshiSettings).flatMap { id => - tenant.otoroshiSettings.find(_.id == id) + val maybeTarget = plan.otoroshiTarget.map(_.otoroshiSettings).flatMap { + id => + tenant.otoroshiSettings.find(_.id == id) } val r = for { - otoSettings <- EitherT.fromOption[Future][AppError, OtoroshiSettings](maybeTarget, AppError.OtoroshiSettingsNotFound) - authorizedEntities <- EitherT.fromOption[Future][AppError, AuthorizedEntities](plan.otoroshiTarget.flatMap(_.authorizedEntities), AppError.ApiNotLinked) - _ <- EitherT.cond[Future][AppError, Unit](!authorizedEntities.isEmpty, (), AppError.ApiNotLinked) - apiKey <- EitherT[Future, AppError, ActualOtoroshiApiKey](otoroshiClient.getApikey(subscription.apiKey.clientId)(otoSettings)) - - aggregatedSubs <- EitherT.liftF[Future, AppError, Seq[ApiSubscription]](env.dataStore.apiSubscriptionRepo.forTenant(tenant) - .findNotDeleted(Json.obj("parent" -> subscription.id.asJson))) - aggregatedPlan <- EitherT.liftF[Future, AppError, Seq[UsagePlan]](env.dataStore.usagePlanRepo.forTenant(tenant) - .findNotDeleted(Json.obj("_id" -> Json.obj("$in" -> JsArray(aggregatedSubs.map(_.plan.asJson)))))) - aggregatedAuthorizedEntities <- EitherT.pure[Future, AppError](aggregatedPlan - .map(_.otoroshiTarget.flatMap(_.authorizedEntities).getOrElse(AuthorizedEntities()))) - - _authorizedEntities = aggregatedAuthorizedEntities.fold(authorizedEntities)((acc, curr) => { - AuthorizedEntities( - services = acc.services ++ curr.services, - groups = acc.groups ++ curr.groups, - routes = acc.routes ++ curr.routes + otoSettings <- EitherT.fromOption[Future][AppError, OtoroshiSettings]( + maybeTarget, + AppError.OtoroshiSettingsNotFound + ) + authorizedEntities <- + EitherT.fromOption[Future][AppError, AuthorizedEntities]( + plan.otoroshiTarget.flatMap(_.authorizedEntities), + AppError.ApiNotLinked ) - }) + _ <- EitherT.cond[Future][AppError, Unit]( + !authorizedEntities.isEmpty, + (), + AppError.ApiNotLinked + ) + apiKey <- EitherT[Future, AppError, ActualOtoroshiApiKey]( + otoroshiClient.getApikey(subscription.apiKey.clientId)(otoSettings) + ) - _ <- EitherT[Future, AppError, ActualOtoroshiApiKey](otoroshiClient.updateApiKey( - apiKey.copy( - authorizedEntities = _authorizedEntities, - throttlingQuota = subscription.customMaxPerSecond - .getOrElse(apiKey.throttlingQuota), - dailyQuota = - subscription.customMaxPerDay.getOrElse(apiKey.dailyQuota), - monthlyQuota = subscription.customMaxPerMonth - .getOrElse(apiKey.monthlyQuota), - metadata = apiKey.metadata ++ subscription.customMetadata - .flatMap(_.asOpt[Map[String, String]]) - .getOrElse(Map.empty[String, String]), - readOnly = - subscription.customReadOnly.getOrElse(apiKey.readOnly) - ) - )(otoSettings) + aggregatedSubs <- EitherT.liftF[Future, AppError, Seq[ApiSubscription]]( + env.dataStore.apiSubscriptionRepo + .forTenant(tenant) + .findNotDeleted(Json.obj("parent" -> subscription.id.asJson)) + ) + aggregatedPlan <- EitherT.liftF[Future, AppError, Seq[UsagePlan]]( + env.dataStore.usagePlanRepo + .forTenant(tenant) + .findNotDeleted( + Json.obj( + "_id" -> Json + .obj("$in" -> JsArray(aggregatedSubs.map(_.plan.asJson))) + ) + ) + ) + aggregatedAuthorizedEntities <- EitherT.pure[Future, AppError]( + aggregatedPlan + .map( + _.otoroshiTarget + .flatMap(_.authorizedEntities) + .getOrElse(AuthorizedEntities()) + ) + ) + + _authorizedEntities = + aggregatedAuthorizedEntities.fold(authorizedEntities)((acc, curr) => { + AuthorizedEntities( + services = acc.services ++ curr.services, + groups = acc.groups ++ curr.groups, + routes = acc.routes ++ curr.routes + ) + }) + + _ <- EitherT[Future, AppError, ActualOtoroshiApiKey]( + otoroshiClient.updateApiKey( + apiKey.copy( + authorizedEntities = _authorizedEntities, + throttlingQuota = subscription.customMaxPerSecond + .getOrElse(apiKey.throttlingQuota), + dailyQuota = + subscription.customMaxPerDay.getOrElse(apiKey.dailyQuota), + monthlyQuota = subscription.customMaxPerMonth + .getOrElse(apiKey.monthlyQuota), + metadata = apiKey.metadata ++ subscription.customMetadata + .flatMap(_.asOpt[Map[String, String]]) + .getOrElse(Map.empty[String, String]), + readOnly = subscription.customReadOnly.getOrElse(apiKey.readOnly) + ) + )(otoSettings) ) _ <- EitherT.liftF[Future, AppError, Boolean]( env.dataStore.apiSubscriptionRepo @@ -483,8 +515,6 @@ class ApiService( r.value } - - def deleteApiKey( tenant: Tenant, subscription: ApiSubscription, diff --git a/daikoku/app/utils/DeletionService.scala b/daikoku/app/utils/DeletionService.scala index 278095269..8bd178184 100644 --- a/daikoku/app/utils/DeletionService.scala +++ b/daikoku/app/utils/DeletionService.scala @@ -89,9 +89,16 @@ class DeletionService( tenant: Tenant ): EitherT[Future, AppError, Unit] = { for { - target <- EitherT.fromOption[Future](plan.otoroshiTarget, AppError.EntityNotFound("Otoroshi settings")) - settings <- EitherT.fromOption[Future](tenant.otoroshiSettings.find(s => s.id == target.otoroshiSettings), AppError.EntityNotFound("Otoroshi settings")) - _ <- otoroshiClient.deleteApiKey(apiSubscription.apiKey.clientId)(settings) + target <- EitherT.fromOption[Future]( + plan.otoroshiTarget, + AppError.EntityNotFound("Otoroshi settings") + ) + settings <- EitherT.fromOption[Future]( + tenant.otoroshiSettings.find(s => s.id == target.otoroshiSettings), + AppError.EntityNotFound("Otoroshi settings") + ) + _ <- + otoroshiClient.deleteApiKey(apiSubscription.apiKey.clientId)(settings) } yield () } diff --git a/daikoku/app/utils/errors.scala b/daikoku/app/utils/errors.scala index 22a76deb1..20d155f2b 100644 --- a/daikoku/app/utils/errors.scala +++ b/daikoku/app/utils/errors.scala @@ -26,13 +26,15 @@ object Errors { env: Env ): Future[Result] = { - val accept = req.headers.get("Accept").getOrElse("text/html").split(",").toSeq + val accept = + req.headers.get("Accept").getOrElse("text/html").split(",").toSeq if (accept.contains("text/html")) { val msg = Base64.getEncoder.encodeToString(message.getBytes) FastFuture.successful( - Redirect(s"${req.theProtocol}://${req.domain}:${env.config.exposedPort}/error#$msg") - .withHeaders( + Redirect( + s"${req.theProtocol}://${req.domain}:${env.config.exposedPort}/error#$msg" + ).withHeaders( "x-error" -> "true", "x-error-msg" -> message // TODO: handled by otoroshi filter ? diff --git a/daikoku/app/utils/otoroshi.scala b/daikoku/app/utils/otoroshi.scala index 583268497..b0125a646 100644 --- a/daikoku/app/utils/otoroshi.scala +++ b/daikoku/app/utils/otoroshi.scala @@ -290,10 +290,20 @@ class OtoroshiClient(env: Env) { def deleteApiKey( clientId: String - )(implicit otoroshiSettings: OtoroshiSettings): EitherT[Future, AppError, Unit] = { + )(implicit + otoroshiSettings: OtoroshiSettings + ): EitherT[Future, AppError, Unit] = { for { resp <- EitherT.liftF(client(s"/api/apikeys/$clientId").delete()) - _ <- EitherT.cond[Future][AppError, Unit](resp.status == 200, (), AppError.OtoroshiError(Json.obj("error" -> s"Error while deleting otoroshi apikey: ${resp.status} - ${resp.body}"))) + _ <- EitherT.cond[Future][AppError, Unit]( + resp.status == 200, + (), + AppError.OtoroshiError( + Json.obj( + "error" -> s"Error while deleting otoroshi apikey: ${resp.status} - ${resp.body}" + ) + ) + ) } yield () } diff --git a/daikoku/app/utils/request.scala b/daikoku/app/utils/request.scala index 421728bd9..9785062c8 100644 --- a/daikoku/app/utils/request.scala +++ b/daikoku/app/utils/request.scala @@ -55,7 +55,8 @@ object RequestImplicits { .get("Otoroshi-Proxied-Host") .orElse(requestHeader.headers.get("X-Forwarded-Host")) .getOrElse(requestHeader.host) - .split(':').head + .split(':') + .head } def getLanguage(tenant: Tenant): String = { diff --git a/daikoku/javascript/src/components/utils/tenantUtils.ts b/daikoku/javascript/src/components/utils/tenantUtils.ts index 104171bc6..8c909b051 100644 --- a/daikoku/javascript/src/components/utils/tenantUtils.ts +++ b/daikoku/javascript/src/components/utils/tenantUtils.ts @@ -24,20 +24,24 @@ export const injectFavicon = (src) => { var link = document.createElement('link'); link.id = 'favicon'; link.rel = 'shortcut icon'; - link.href = src + link.href = src; document.head.appendChild(link); -} +}; export const injectFontFamily = (ffUrl) => { - injectCSS("\ + injectCSS( + "\ @font-face {\ font-family: Custom;\ - src: url('" + ffUrl + "') format('yourFontFormat');\ + src: url('" + + ffUrl + + "') format('yourFontFormat');\ }\ -"); -} +" + ); +}; export const parseAsHtml = (element: string): DocumentFragment => { const parse = Range.prototype.createContextualFragment.bind(document.createRange()); - return parse(element) -} \ No newline at end of file + return parse(element); +}; diff --git a/daikoku/javascript/src/services/index.ts b/daikoku/javascript/src/services/index.ts index 2a35abeb8..fbb767a95 100644 --- a/daikoku/javascript/src/services/index.ts +++ b/daikoku/javascript/src/services/index.ts @@ -59,7 +59,8 @@ export const me = (): PromiseWithError => customFetch('/api/me'); export const myOwnTeam = () => customFetch('/api/me/teams/own'); export const oneOfMyTeam = (id: any) => customFetch(`/api/me/teams/${id}`); export const getUserContext = (): PromiseWithError => customFetch('/api/me/context'); -export const getAuthContext = (provider: string): PromiseWithError => customFetch(`/api/auth/${provider}/context`); +export const getAuthContext = (provider: string): PromiseWithError => + customFetch(`/api/auth/${provider}/context`); export const getVisibleApiWithId = (id: string): PromiseWithError => customFetch(`/api/me/visible-apis/${id}`); @@ -1018,7 +1019,12 @@ function updateQueryStringParameter(uri, key, value) { } } -export const login = (username: string, password: string, action: string, redirect?: string | null) => { +export const login = ( + username: string, + password: string, + action: string, + redirect?: string | null +) => { const body = new URLSearchParams(); body.append('username', username); body.append('password', password); diff --git a/daikoku/javascript/src/types/api.ts b/daikoku/javascript/src/types/api.ts index 9423d8e1e..edf8614ee 100644 --- a/daikoku/javascript/src/types/api.ts +++ b/daikoku/javascript/src/types/api.ts @@ -147,14 +147,14 @@ export interface IImportingDocumentation { export enum SpecificationType { openapi = 'openapi', - asyncapi = 'asyncapi' + asyncapi = 'asyncapi', } export interface ISwagger { url?: string; content?: string; headers: { [key: string]: string }; additionalConf?: object; - specificationType: SpecificationType + specificationType: SpecificationType; } export type IValidationStepType = 'teamAdmin' | 'email' | 'payment' | 'httpRequest'; diff --git a/daikoku/javascript/src/types/context.ts b/daikoku/javascript/src/types/context.ts index b2dca27d9..40d6a0758 100644 --- a/daikoku/javascript/src/types/context.ts +++ b/daikoku/javascript/src/types/context.ts @@ -14,7 +14,7 @@ export interface IStateModal { } export interface IAuthContext { - action: string + action: string; } export interface IStateContext { diff --git a/daikoku/javascript/src/types/tenant.ts b/daikoku/javascript/src/types/tenant.ts index 4a8311048..caae5260a 100644 --- a/daikoku/javascript/src/types/tenant.ts +++ b/daikoku/javascript/src/types/tenant.ts @@ -189,7 +189,6 @@ export interface ITenant { jsUrl?: string; faviconUrl?: string; fontFamilyUrl?: string; - } export interface ITenantFull extends ITenant { diff --git a/daikoku/test/daikoku/AdminApiControllerSpec.scala b/daikoku/test/daikoku/AdminApiControllerSpec.scala index ddbc5dbeb..3d53d1ed6 100644 --- a/daikoku/test/daikoku/AdminApiControllerSpec.scala +++ b/daikoku/test/daikoku/AdminApiControllerSpec.scala @@ -1235,7 +1235,11 @@ class AdminApiControllerSpec respCreateKo.status mustBe 400 val some = defaultApi.api - .copy(id = ApiId(IdGenerator.token), parent = defaultApi.api.id.some, currentVersion = Version("vTest")) + .copy( + id = ApiId(IdGenerator.token), + parent = defaultApi.api.id.some, + currentVersion = Version("vTest") + ) .asJson .some diff --git a/daikoku/test/daikoku/ApiControllerSpec.scala b/daikoku/test/daikoku/ApiControllerSpec.scala index 3c7101ed1..aba173475 100644 --- a/daikoku/test/daikoku/ApiControllerSpec.scala +++ b/daikoku/test/daikoku/ApiControllerSpec.scala @@ -9,13 +9,19 @@ import com.github.tomakehurst.wiremock.client.WireMock._ import com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig import controllers.AppError import controllers.AppError.SubscriptionAggregationDisabled -import fr.maif.otoroshi.daikoku.domain.NotificationAction.{ApiAccess, ApiSubscriptionDemand} +import fr.maif.otoroshi.daikoku.domain.NotificationAction.{ + ApiAccess, + ApiSubscriptionDemand +} import fr.maif.otoroshi.daikoku.domain.NotificationType.AcceptOrReject import fr.maif.otoroshi.daikoku.domain.TeamPermission.Administrator import fr.maif.otoroshi.daikoku.domain.UsagePlan._ import fr.maif.otoroshi.daikoku.domain.UsagePlanVisibility.{Private, Public} import fr.maif.otoroshi.daikoku.domain._ -import fr.maif.otoroshi.daikoku.domain.json.{ApiFormat, SeqApiSubscriptionFormat} +import fr.maif.otoroshi.daikoku.domain.json.{ + ApiFormat, + SeqApiSubscriptionFormat +} import fr.maif.otoroshi.daikoku.logger.AppLogger import fr.maif.otoroshi.daikoku.tests.utils.DaikokuSpecHelper import fr.maif.otoroshi.daikoku.utils.IdGenerator @@ -37,21 +43,26 @@ class ApiControllerSpec() extends PlaySpec with DaikokuSpecHelper with IntegrationPatience - with BeforeAndAfterEach - with BeforeAndAfter - with ForAllTestContainer { + with BeforeAndAfterEach + with BeforeAndAfter + with ForAllTestContainer { val pwd = System.getProperty("user.dir"); - lazy val wireMockServer = new WireMockServer(wireMockConfig() - .port(stubPort)) + lazy val wireMockServer = new WireMockServer( + wireMockConfig() + .port(stubPort) + ) override val container = GenericContainer( "maif/otoroshi", exposedPorts = Seq(8080), fileSystemBind = Seq( - FileSystemBind(s"$pwd/test/daikoku/otoroshi.json", - "/home/user/otoroshi.json", - BindMode.READ_ONLY)), + FileSystemBind( + s"$pwd/test/daikoku/otoroshi.json", + "/home/user/otoroshi.json", + BindMode.READ_ONLY + ) + ), env = Map("APP_IMPORT_FROM" -> "/home/user/otoroshi.json") ) @@ -2818,7 +2829,8 @@ class ApiControllerSpec() val sessionUser = loginWithBlocking(user, tenant) val subAdminResp = httpJsonCallBlocking( - path = s"/api/me/subscriptions/${defaultApi.api.id.value}/${defaultApi.api.currentVersion.value}" + path = + s"/api/me/subscriptions/${defaultApi.api.id.value}/${defaultApi.api.currentVersion.value}" )(tenant, sessionAdmin) logger.warn(Json.prettyPrint(subAdminResp.json)) subAdminResp.status mustBe 200 @@ -5190,7 +5202,6 @@ class ApiControllerSpec() aggregationApiKeysSecurity = Some(true) ) - val parentApi = defaultApi.api.copy( id = ApiId("parent-id"), name = "parent API", @@ -5203,7 +5214,7 @@ class ApiControllerSpec() name = "child API", team = teamOwnerId, possibleUsagePlans = Seq(UsagePlanId("child.dev")), - defaultUsagePlan = UsagePlanId("child.dev").some, + defaultUsagePlan = UsagePlanId("child.dev").some ) val parentSub = ApiSubscription( @@ -5234,25 +5245,28 @@ class ApiControllerSpec() parent = Some(parentSub.id) ) - setupEnvBlocking( - tenants = Seq(tenant.copy( - aggregationApiKeysSecurity = Some(true), - otoroshiSettings = Set( - OtoroshiSettings( - id = containerizedOtoroshi, - url = s"http://otoroshi.oto.tools:${container.mappedPort(8080)}", - host = "otoroshi-api.oto.tools", - clientSecret = otoroshiAdminApiKey.clientSecret, - clientId = otoroshiAdminApiKey.clientId - ), - ))), + tenants = Seq( + tenant.copy( + aggregationApiKeysSecurity = Some(true), + otoroshiSettings = Set( + OtoroshiSettings( + id = containerizedOtoroshi, + url = + s"http://otoroshi.oto.tools:${container.mappedPort(8080)}", + host = "otoroshi-api.oto.tools", + clientSecret = otoroshiAdminApiKey.clientSecret, + clientId = otoroshiAdminApiKey.clientId + ) + ) + ) + ), users = Seq(userAdmin), teams = Seq(teamOwner, teamConsumer), usagePlans = Seq(parentPlan, childPlan), apis = Seq(parentApi, childApi), - subscriptions = Seq(parentSub, childSub)) - + subscriptions = Seq(parentSub, childSub) + ) // AppLogger.info(container.logs) val session = loginWithBlocking(userAdmin, tenant) @@ -5260,9 +5274,12 @@ class ApiControllerSpec() path = s"/api/teams/${teamOwnerId.value}/subscriptions/${parentSub.id.value}", method = "PUT", - body = parentSub.copy( - customMetadata = Json.obj("foo"-> "bar").some - ).asJson.some + body = parentSub + .copy( + customMetadata = Json.obj("foo" -> "bar").some + ) + .asJson + .some )(tenant, session) resp.status mustBe 200 @@ -5282,8 +5299,8 @@ class ApiControllerSpec() val authorizations = (respVerif.json \ "authorizations").as[JsArray] val strings = authorizations.value.map(value => (value \ "id").as[String]) strings.size mustBe 2 - strings.contains(childRouteId) mustBe true - strings.contains(parentRouteId) mustBe true + strings.contains(childRouteId) mustBe true + strings.contains(parentRouteId) mustBe true } "update plan in aggregated APIkey do not erase authorizedEntities" in { val parentPlan = FreeWithoutQuotas( @@ -5333,7 +5350,6 @@ class ApiControllerSpec() aggregationApiKeysSecurity = Some(true) ) - val parentApi = defaultApi.api.copy( id = ApiId("parent-id"), name = "parent API", @@ -5346,7 +5362,7 @@ class ApiControllerSpec() name = "child API", team = teamOwnerId, possibleUsagePlans = Seq(UsagePlanId("child.dev")), - defaultUsagePlan = UsagePlanId("child.dev").some, + defaultUsagePlan = UsagePlanId("child.dev").some ) val parentSub = ApiSubscription( @@ -5377,35 +5393,38 @@ class ApiControllerSpec() parent = Some(parentSub.id) ) - setupEnvBlocking( - tenants = Seq(tenant.copy( - aggregationApiKeysSecurity = Some(true), - otoroshiSettings = Set( - OtoroshiSettings( - id = containerizedOtoroshi, - url = s"http://otoroshi.oto.tools:${container.mappedPort(8080)}", - host = "otoroshi-api.oto.tools", - clientSecret = otoroshiAdminApiKey.clientSecret, - clientId = otoroshiAdminApiKey.clientId - ), - ))), + tenants = Seq( + tenant.copy( + aggregationApiKeysSecurity = Some(true), + otoroshiSettings = Set( + OtoroshiSettings( + id = containerizedOtoroshi, + url = + s"http://otoroshi.oto.tools:${container.mappedPort(8080)}", + host = "otoroshi-api.oto.tools", + clientSecret = otoroshiAdminApiKey.clientSecret, + clientId = otoroshiAdminApiKey.clientId + ) + ) + ) + ), users = Seq(userAdmin), teams = Seq(teamOwner, teamConsumer, defaultAdminTeam), usagePlans = Seq(parentPlan, childPlan, adminApiPlan), apis = Seq(parentApi, childApi, adminApi), - subscriptions = Seq(parentSub, childSub)) - + subscriptions = Seq(parentSub, childSub) + ) // AppLogger.info(container.logs) val session = loginWithBlocking(userAdmin, tenant) - val resp = httpJsonCallBlocking( path = s"/api/teams/${teamOwnerId.value}/apis/${parentApi.id.value}/${parentApi.currentVersion.value}/plan/${parentPlan.id.value}", method = "PUT", - body = parentPlan.copy( + body = parentPlan + .copy( otoroshiTarget = OtoroshiTarget( containerizedOtoroshi, Some( @@ -5414,7 +5433,9 @@ class ApiControllerSpec() ) ) ).some - ).asJson.some + ) + .asJson + .some )(tenant, session) resp.status mustBe 200 @@ -5434,24 +5455,29 @@ class ApiControllerSpec() val authorizations = (respVerif.json \ "authorizations").as[JsArray] val strings = authorizations.value.map(value => (value \ "id").as[String]) strings.size mustBe 2 - strings.contains(otherRouteId) mustBe true - strings.contains(childRouteId) mustBe true - + strings.contains(otherRouteId) mustBe true + strings.contains(childRouteId) mustBe true httpJsonCallBlocking( path = s"/api/teams/${teamOwnerId.value}/apis/${childApi.id.value}/${childApi.currentVersion.value}/plan/${childPlan.id.value}", method = "PUT", - body = childPlan.copy( - otoroshiTarget = OtoroshiTarget( - containerizedOtoroshi, - Some( - AuthorizedEntities( - routes = Set(OtoroshiRouteId(parentRouteId), OtoroshiRouteId(childRouteId)) + body = childPlan + .copy( + otoroshiTarget = OtoroshiTarget( + containerizedOtoroshi, + Some( + AuthorizedEntities( + routes = Set( + OtoroshiRouteId(parentRouteId), + OtoroshiRouteId(childRouteId) + ) + ) ) - ) - ).some - ).asJson.some + ).some + ) + .asJson + .some )(tenant, session) val respVerif2 = httpJsonCallBlocking( @@ -5467,7 +5493,8 @@ class ApiControllerSpec() AppLogger.info(Json.prettyPrint(respVerif2.json)) val authorizations2 = (respVerif2.json \ "authorizations").as[JsArray] - val strings2 = authorizations2.value.map(value => (value \ "id").as[String]) + val strings2 = + authorizations2.value.map(value => (value \ "id").as[String]) strings2.size mustBe 3 } } diff --git a/daikoku/test/daikoku/suites.scala b/daikoku/test/daikoku/suites.scala index aa0c33428..0f38eedc8 100644 --- a/daikoku/test/daikoku/suites.scala +++ b/daikoku/test/daikoku/suites.scala @@ -752,11 +752,14 @@ object utils { val otoroshiAdminApiKey = OtoroshiApiKey( clientName = "Otoroshi Backoffice ApiKey", clientId = "admin-api-apikey-id", - clientSecret= "admin-api-apikey-secret") + clientSecret = "admin-api-apikey-secret" + ) val parentApiKey = OtoroshiApiKey( clientName = "daikoku_test_parent_key", clientId = "5w24yl2ly3dlnn92", - clientSecret= "8iwm9fhbns0rmybnyul5evq9l1o4dxza0rh7rt4flay69jolw3okbz1owfl6w2db") + clientSecret = + "8iwm9fhbns0rmybnyul5evq9l1o4dxza0rh7rt4flay69jolw3okbz1owfl6w2db" + ) val parentRouteId = "route_d74ea8b27-b8be-4177-82d9-c50722416c50" val childRouteId = "route_8ce030cbd-6c07-43d4-9c61-4a330ae0975d" From 534814f01212e7c30bb89ede286ca8bf8b3c801a Mon Sep 17 00:00:00 2001 From: daikoku-github-actions Date: Mon, 5 Aug 2024 13:28:33 +0000 Subject: [PATCH 007/123] Update version number before release --- manual/docs/02-usages/07-adminusage/4-importexport.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/docs/02-usages/07-adminusage/4-importexport.md b/manual/docs/02-usages/07-adminusage/4-importexport.md index dcd575a41..bd96178c6 100644 --- a/manual/docs/02-usages/07-adminusage/4-importexport.md +++ b/manual/docs/02-usages/07-adminusage/4-importexport.md @@ -19,7 +19,7 @@ If you want to restore an export, Go to `settings (avatar icon) / Organizations Since v1.1.1 Daikoku supports Postgresql databases. If you want to migrate you MongoDB to Postgresql, it's dead simple like the following instructions. :::danger -Since **v17.3.2**, Daikoku does not support MongoDB anymore. To run database migration, you need to be in **16.3.6 max**. +Since **v17.4.0**, Daikoku does not support MongoDB anymore. To run database migration, you need to be in **16.3.6 max**. ::: 1. Add your Postgresql access in Daikoku configuration From 2133d6bea3519f9f02708d4405b5be8a93513a5a Mon Sep 17 00:00:00 2001 From: daikoku-github-actions Date: Mon, 5 Aug 2024 13:28:50 +0000 Subject: [PATCH 008/123] Setting version to 17.4.0 --- daikoku/version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daikoku/version.sbt b/daikoku/version.sbt index 75b849a28..7e0b54c76 100644 --- a/daikoku/version.sbt +++ b/daikoku/version.sbt @@ -1 +1 @@ -ThisBuild / version := "17.4.0-dev" +ThisBuild / version := "17.4.0" From 394ed6811d5ae8ccf8f5b542e4517af0d8e541b9 Mon Sep 17 00:00:00 2001 From: daikoku-github-actions Date: Mon, 5 Aug 2024 13:28:51 +0000 Subject: [PATCH 009/123] Setting version to 17.5.0-dev --- daikoku/version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daikoku/version.sbt b/daikoku/version.sbt index 7e0b54c76..eb0f5b7cb 100644 --- a/daikoku/version.sbt +++ b/daikoku/version.sbt @@ -1 +1 @@ -ThisBuild / version := "17.4.0" +ThisBuild / version := "17.5.0-dev" From 69e2b67c2c69b16dd2bd488018bbbd34a05083d5 Mon Sep 17 00:00:00 2001 From: daikoku-github-actions Date: Mon, 5 Aug 2024 13:34:49 +0000 Subject: [PATCH 010/123] Update CHANGELOG --- CHANGELOG | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index b2791e401..8b6b553c2 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -609,3 +609,19 @@ We'd like to thank all the contributors who worked on this release! - Create testing key with otorshi is broken [#698](https://github.com/MAIF/daikoku/issues/698) +## [v17.4.0] - 2024-08-05 + +## :star: New Features + +- use color theme for dark mode [#645](https://github.com/MAIF/daikoku/issues/645) +- Add tutorial to setup authentication [#644](https://github.com/MAIF/daikoku/issues/644) +- Support AsyncAPI v2 [#511](https://github.com/MAIF/daikoku/issues/511) + +## :beetle: Bug Fixes + +- Update aggregated subscription delete authorized entities in otoroshi [#722](https://github.com/MAIF/daikoku/issues/722) +- Error Api name already exists when creating a new version by admin-api [#719](https://github.com/MAIF/daikoku/issues/719) +- unknown Apikey authorized entities [#709](https://github.com/MAIF/daikoku/issues/709) +- Lack of translation [#700](https://github.com/MAIF/daikoku/issues/700) +- API testing UI is broken [#699](https://github.com/MAIF/daikoku/issues/699) + From de9eff192634370f50c0faf1b2eba8f219702969 Mon Sep 17 00:00:00 2001 From: Quentin AUBERT Date: Tue, 6 Aug 2024 10:30:56 +0200 Subject: [PATCH 011/123] FIX typo in manual --- manual/docs/03-guides/14-authentication.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/docs/03-guides/14-authentication.mdx b/manual/docs/03-guides/14-authentication.mdx index 4031f38fa..b9a301fe4 100644 --- a/manual/docs/03-guides/14-authentication.mdx +++ b/manual/docs/03-guides/14-authentication.mdx @@ -226,7 +226,7 @@ Authentication in daikoku can be tricky. It's a tenant configuration, we are her - Click on `OAuth2` at the top of the form - Enable `Read profile from JWT token` - Fill `Token scope` with `openid profile name email picture` - - Fill `Client Id` with the clietn ID provided by Auth0 + - Fill `Client Id` with the client ID provided by Auth0 - Fill `Client Secret` with the client secret provided by Auth0 - Fill `Authorize URL` with `https://.eu.auth0.com/authorize` - Fill `Token URL` with `https://.eu.auth0.com/oauth/token` From 054629e9d1b7eb346231a31e9695086905700c44 Mon Sep 17 00:00:00 2001 From: HelaKaraa Date: Wed, 7 Aug 2024 10:47:09 +0200 Subject: [PATCH 012/123] fix#694: delete tags --- .../frontend/api/issues/TeamApiIssueTags.tsx | 128 +++++++++++------- 1 file changed, 77 insertions(+), 51 deletions(-) diff --git a/daikoku/javascript/src/components/frontend/api/issues/TeamApiIssueTags.tsx b/daikoku/javascript/src/components/frontend/api/issues/TeamApiIssueTags.tsx index b057bf813..538790d7a 100644 --- a/daikoku/javascript/src/components/frontend/api/issues/TeamApiIssueTags.tsx +++ b/daikoku/javascript/src/components/frontend/api/issues/TeamApiIssueTags.tsx @@ -7,11 +7,9 @@ import RefreshCcw from 'react-feather/dist/icons/refresh-ccw'; import { ModalContext } from '../../../../contexts'; import { I18nContext } from '../../../../contexts'; import { randomColor } from '../../../utils'; +import { nanoid } from 'nanoid'; -export function TeamApiIssueTags({ - value, - onChange -}: any) { +export function TeamApiIssueTags({ value, onChange }: any) { const [api, setApi] = useState(value); const [updated, setUpdated] = useState(false); @@ -22,7 +20,7 @@ export function TeamApiIssueTags({ const updatedApi = { ...api, issuesTags: [...api.issuesTags.filter((iss: any) => iss.id !== id)], - } + }; onChange(updatedApi); setApi(updatedApi); } @@ -31,48 +29,70 @@ export function TeamApiIssueTags({
- + value: { color: randomColor() }, + actionLabel: translate('Create'), + }) + } + > + {translate('issues.new_tag')} +
@@ -81,7 +101,10 @@ export function TeamApiIssueTags({ {api.issuesTags .sort((a: any, b: any) => a.name.localeCompare(b.name)) .map((issueTag: any, i: any) => ( -
+
{ if (c.r) { @@ -175,7 +198,7 @@ function ColorTag({ width: '36px', height: '14px', borderRadius: '2px', - background: `${color}`, + background: `${sketchColorToReadableColor(initialColor)}`, }, swatch: { padding: '5px', @@ -217,7 +240,10 @@ function ColorTag({
{displayColorPicker ? (
-
setDisplayColorPicker(false)} /> +
setDisplayColorPicker(false)} + /> {/* @ts-ignore */} (presetColors).sort()} From 21dacf1ef63610a98f537fac13ecda3c5beefbbe Mon Sep 17 00:00:00 2001 From: HelaKaraa Date: Wed, 7 Aug 2024 11:02:47 +0200 Subject: [PATCH 013/123] fix#622: add placeholder for Bucket url --- .../tenants/forms/bucketForm.tsx | 122 +++++++++--------- 1 file changed, 63 insertions(+), 59 deletions(-) diff --git a/daikoku/javascript/src/components/adminbackoffice/tenants/forms/bucketForm.tsx b/daikoku/javascript/src/components/adminbackoffice/tenants/forms/bucketForm.tsx index 10e99bab5..431d05c17 100644 --- a/daikoku/javascript/src/components/adminbackoffice/tenants/forms/bucketForm.tsx +++ b/daikoku/javascript/src/components/adminbackoffice/tenants/forms/bucketForm.tsx @@ -2,68 +2,72 @@ import { Form, Schema, type } from '@maif/react-forms'; import { useContext } from 'react'; import { UseMutationResult } from '@tanstack/react-query'; - import { I18nContext } from '../../../../contexts'; import { IBucketSettings, ITenantFull } from '../../../../types'; -export const BucketForm = (props: { tenant?: ITenantFull, updateTenant: UseMutationResult }) => { - const { translate } = useContext(I18nContext) - - const schema: Schema = { - bucket: { - type: type.string, - label: translate('Bucket name'), - placeholder: 'daikoku-tenant-1', - help: translate('The name of the S3 bucket'), +export const BucketForm = (props: { + tenant?: ITenantFull; + updateTenant: UseMutationResult; +}) => { + const { translate } = useContext(I18nContext); - }, - endpoint: { - type: type.string, - label: translate('Bucket url'), - help: translate('The url of the bucket'), + const schema: Schema = { + bucket: { + type: type.string, + label: translate('Bucket name'), + placeholder: 'daikoku-tenant-1', + help: translate('The name of the S3 bucket'), + }, + endpoint: { + type: type.string, + label: translate('Bucket url'), + placeholder: 'https://.example-s3.io', + help: translate('The url of the bucket'), + }, + region: { + type: type.string, + label: translate('S3 region'), + placeholder: 'us-west-2', + help: translate('The region of the bucket'), + }, + access: { + type: type.string, + label: translate('Bucket access key'), + help: translate('The access key to access bucket'), + }, + secret: { + type: type.string, + label: translate('Bucket secret'), + help: translate('The secret to access the bucket'), + }, + chunkSize: { + type: type.number, + label: translate('Chunk size'), + defaultValue: 1024 * 1024 * 8, + help: translate('The size of each chunk sent'), + }, + v4auth: { + type: type.bool, + label: translate('Use V4 auth.'), + defaultValue: true, + }, + }; + return ( + + schema={schema} + onSubmit={(r) => + props.updateTenant.mutateAsync({ + ...props.tenant, + bucketSettings: r, + } as ITenantFull) + } + value={props.tenant?.bucketSettings} + options={{ + actions: { + submit: { label: translate('Save') }, }, - region: { - type: type.string, - label: translate('S3 region'), - placeholder: 'us-west-2', - help: translate('The region of the bucket'), - }, - access: { - type: type.string, - label: translate('Bucket access key'), - help: translate('The access key to access bucket'), - }, - secret: { - type: type.string, - label: translate('Bucket secret'), - help: translate('The secret to access the bucket'), - }, - chunkSize: { - type: type.number, - label: translate('Chunk size'), - defaultValue: 1024 * 1024 * 8, - help: translate('The size of each chunk sent'), - }, - v4auth: { - type: type.bool, - label: translate('Use V4 auth.'), - defaultValue: true - }, - } - - - return ( - - schema={schema} - onSubmit={(r) => props.updateTenant.mutateAsync({ ...props.tenant, bucketSettings: r } as ITenantFull)} - value={props.tenant?.bucketSettings} - options={{ - actions: { - submit: { label: translate('Save') } - } - }} - /> - ) - -} \ No newline at end of file + }} + /> + ); +}; From a98595852bcd1b906d8a0a8a3175a67c1c961b5f Mon Sep 17 00:00:00 2001 From: HelaKaraa Date: Wed, 7 Aug 2024 11:42:56 +0200 Subject: [PATCH 014/123] fix#715: invalidate query subscription --- .../backoffice/apis/TeamApiSubscriptions.tsx | 597 ++++++++++-------- 1 file changed, 346 insertions(+), 251 deletions(-) diff --git a/daikoku/javascript/src/components/backoffice/apis/TeamApiSubscriptions.tsx b/daikoku/javascript/src/components/backoffice/apis/TeamApiSubscriptions.tsx index 576881cc8..36cc2e644 100644 --- a/daikoku/javascript/src/components/backoffice/apis/TeamApiSubscriptions.tsx +++ b/daikoku/javascript/src/components/backoffice/apis/TeamApiSubscriptions.tsx @@ -1,15 +1,23 @@ -import {getApolloContext} from "@apollo/client"; -import {format, type} from "@maif/react-forms"; -import {createColumnHelper} from '@tanstack/react-table'; -import {useContext, useEffect, useRef, useState} from 'react'; -import {toast} from 'sonner'; +import { getApolloContext } from '@apollo/client'; +import { format, type } from '@maif/react-forms'; +import { createColumnHelper } from '@tanstack/react-table'; +import { useContext, useEffect, useRef, useState } from 'react'; +import { toast } from 'sonner'; -import {ModalContext} from '../../../contexts'; -import {CustomSubscriptionData} from '../../../contexts/modals/SubscriptionMetadataModal'; -import {I18nContext} from '../../../contexts'; +import { ModalContext } from '../../../contexts'; +import { CustomSubscriptionData } from '../../../contexts/modals/SubscriptionMetadataModal'; +import { I18nContext } from '../../../contexts'; import * as Services from '../../../services'; -import {IApi, isError, IState, ISubscriptionCustomization, ITeamSimple, IUsagePlan, ResponseError} from "../../../types"; -import {SwitchButton, Table, TableRef} from '../../inputs'; +import { + IApi, + isError, + IState, + ISubscriptionCustomization, + ITeamSimple, + IUsagePlan, + ResponseError, +} from '../../../types'; +import { SwitchButton, Table, TableRef } from '../../inputs'; import { api as API, BeautifulTitle, @@ -20,132 +28,161 @@ import { Option, Spinner, } from '../../utils'; -import {useQuery, useQueryClient, useMutation} from "@tanstack/react-query"; +import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query'; type TeamApiSubscriptionsProps = { - api: IApi, - currentTeam: ITeamSimple -} + api: IApi; + currentTeam: ITeamSimple; +}; type SubscriptionsFilter = { - metadata: Array<{ key: string, value: string }>, - tags: Array, - clientIds: Array -} + metadata: Array<{ key: string; value: string }>; + tags: Array; + clientIds: Array; +}; type LimitedPlan = { - _id: string - customName?: string - type: string - -} + _id: string; + customName?: string; + type: string; +}; interface IApiSubscriptionGql extends ISubscriptionCustomization { - _id: string + _id: string; apiKey: { - clientName: string - clientId: string - clientSecret: string - } - plan: LimitedPlan + clientName: string; + clientId: string; + clientSecret: string; + }; + plan: LimitedPlan; team: { - _id: string - name: string - type: string - } - createdAt: string + _id: string; + name: string; + type: string; + }; + createdAt: string; api: { - _id: string - } - customName: string - enabled: boolean - customMetadata?: JSON - adminCustomName?: string - customMaxPerSecond?: number - customMaxPerDay?: number - customMaxPerMonth?: number - customReadOnly?: boolean - tags: Array - metadata?: JSON + _id: string; + }; + customName: string; + enabled: boolean; + customMetadata?: JSON; + adminCustomName?: string; + customMaxPerSecond?: number; + customMaxPerDay?: number; + customMaxPerMonth?: number; + customReadOnly?: boolean; + tags: Array; + metadata?: JSON; parent: { - _id: string - adminCustomName: string + _id: string; + adminCustomName: string; api: { - _id: string - name: string - } + _id: string; + name: string; + }; plan: { - _id: string - customName: string - type: string - } - } + _id: string; + customName: string; + type: string; + }; + }; } interface IApiSubscriptionGqlWithUsage extends IApiSubscriptionGql { - lastUsage?: number + lastUsage?: number; } -export const TeamApiSubscriptions = ({ api, currentTeam }: TeamApiSubscriptionsProps) => { - +export const TeamApiSubscriptions = ({ + api, + currentTeam, +}: TeamApiSubscriptionsProps) => { const { client } = useContext(getApolloContext()); const queryClient = useQueryClient(); - const [filters, setFilters] = useState() - const tableRef = useRef() + const [filters, setFilters] = useState(); + const tableRef = useRef(); const { translate, language, Translation } = useContext(I18nContext); - const { confirm, openFormModal, openSubMetadataModal, } = useContext(ModalContext); + const { confirm, openFormModal, openSubMetadataModal } = + useContext(ModalContext); - const plansQuery = useQuery({ queryKey: ['plans'], queryFn: () => Services.getAllPlanOfApi(api.team, api._id, api.currentVersion) }) + const plansQuery = useQuery({ + queryKey: ['plans'], + queryFn: () => + Services.getAllPlanOfApi(api.team, api._id, api.currentVersion), + }); const subscriptionsQuery = useQuery({ queryKey: ['subscriptions'], - queryFn: () => client!.query<{ apiApiSubscriptions: Array; }>({ - query: Services.graphql.getApiSubscriptions, - fetchPolicy: "no-cache", - variables: { - apiId: api._id, - teamId: currentTeam._id, - version: api.currentVersion - } - }).then(({ data: { apiApiSubscriptions } }) => { - if (!filters || (!filters.tags.length && !Object.keys(filters.metadata).length && !filters.clientIds.length)) { - return apiApiSubscriptions - } else { - const filterByMetadata = (subscription: IApiSubscriptionGql) => { - const meta = { ...(subscription.metadata || {}), ...(subscription.customMetadata || {}) }; - - return !Object.keys(meta) || (!filters.metadata.length || filters.metadata.every(item => { - const value = meta[item.key] - return value && value.includes(item.value) - })) - } + queryFn: () => + client! + .query<{ apiApiSubscriptions: Array }>({ + query: Services.graphql.getApiSubscriptions, + fetchPolicy: 'no-cache', + variables: { + apiId: api._id, + teamId: currentTeam._id, + version: api.currentVersion, + }, + }) + .then(({ data: { apiApiSubscriptions } }) => { + if ( + !filters || + (!filters.tags.length && + !Object.keys(filters.metadata).length && + !filters.clientIds.length) + ) { + return apiApiSubscriptions; + } else { + const filterByMetadata = (subscription: IApiSubscriptionGql) => { + const meta = { + ...(subscription.metadata || {}), + ...(subscription.customMetadata || {}), + }; - const filterByTags = (subscription: IApiSubscriptionGql) => { - return filters.tags.every(tag => subscription.tags.includes(tag)) - } + return ( + !Object.keys(meta) || + !filters.metadata.length || + filters.metadata.every((item) => { + const value = meta[item.key]; + return value && value.includes(item.value); + }) + ); + }; - const filterByClientIds = (subscription: IApiSubscriptionGql) => { - return filters.clientIds.includes(subscription.apiKey.clientId) - } + const filterByTags = (subscription: IApiSubscriptionGql) => { + return filters.tags.every((tag) => + subscription.tags.includes(tag) + ); + }; + const filterByClientIds = (subscription: IApiSubscriptionGql) => { + return filters.clientIds.includes(subscription.apiKey.clientId); + }; - return apiApiSubscriptions - .filter(filterByMetadata) - .filter(filterByTags) - .filter(filterByClientIds) - } - }) - .then((apiApiSubscriptions) => Services.getSubscriptionsLastUsages(api.team, subscriptionsQuery.data?.map(s => s._id) || []) - .then(lastUsages => { - if (isError(lastUsages)) { - return subscriptionsQuery.data as IApiSubscriptionGqlWithUsage[] - } else { - return (apiApiSubscriptions ?? []) - .map(s => ({ - ...s, - lastUsage: lastUsages.find(u => u.subscription === s._id)?.date - } as IApiSubscriptionGqlWithUsage)); + return apiApiSubscriptions + .filter(filterByMetadata) + .filter(filterByTags) + .filter(filterByClientIds); } - })) - }) + }) + .then((apiApiSubscriptions) => + Services.getSubscriptionsLastUsages( + api.team, + subscriptionsQuery.data?.map((s) => s._id) || [] + ).then((lastUsages) => { + if (isError(lastUsages)) { + return subscriptionsQuery.data as IApiSubscriptionGqlWithUsage[]; + } else { + return (apiApiSubscriptions ?? []).map( + (s) => + ({ + ...s, + lastUsage: lastUsages.find((u) => u.subscription === s._id) + ?.date, + }) as IApiSubscriptionGqlWithUsage + ); + } + }) + ), + }); useEffect(() => { document.title = `${currentTeam.name} - ${translate('Subscriptions')}`; @@ -153,74 +190,88 @@ export const TeamApiSubscriptions = ({ api, currentTeam }: TeamApiSubscriptionsP useEffect(() => { if (api && subscriptionsQuery.data) { - tableRef.current?.update() + tableRef.current?.update(); } - }, [api, subscriptionsQuery.data]) + }, [api, subscriptionsQuery.data]); useEffect(() => { - tableRef.current?.update() - }, [filters]) + tableRef.current?.update(); + }, [filters]); - const columnHelper = createColumnHelper() + const columnHelper = createColumnHelper(); const columns = (usagePlans) => [ - columnHelper.accessor(row => row.adminCustomName || row.apiKey.clientName, { - id: 'adminCustomName', - header: translate('Name'), - meta: { style: { textAlign: 'left' } }, - filterFn: (row, _, value) => { - const sub = row.original - const displayed: string = sub.team._id === currentTeam._id ? sub.customName || sub.apiKey.clientName : sub.apiKey.clientName + columnHelper.accessor( + (row) => row.adminCustomName || row.apiKey.clientName, + { + id: 'adminCustomName', + header: translate('Name'), + meta: { style: { textAlign: 'left' } }, + filterFn: (row, _, value) => { + const sub = row.original; + const displayed: string = + sub.team._id === currentTeam._id + ? sub.customName || sub.apiKey.clientName + : sub.apiKey.clientName; - return displayed.toLocaleLowerCase().includes(value.toLocaleLowerCase()) - }, - sortingFn: 'basic', - cell: (info) => { - const sub = info.row.original; - if (sub.parent) { - const title = `
+ return displayed + .toLocaleLowerCase() + .includes(value.toLocaleLowerCase()); + }, + sortingFn: 'basic', + cell: (info) => { + const sub = info.row.original; + if (sub.parent) { + const title = `
${translate('aggregated.apikey.badge.title')}
  • ${translate('Api')}: ${sub.parent.api.name}
  • ${translate('Plan')}: ${sub.parent.plan.customName}
  • ${translate('aggregated.apikey.badge.apikey.name')}: ${sub.parent.adminCustomName}
-
` - return ( -
- {info.getValue()} - - -
A
-
-
- ); - } - return
{info.getValue()}
; +
`; + return ( +
+ {info.getValue()} + +
A
+
+
+ ); + } + return
{info.getValue()}
; + }, } - }), + ), columnHelper.accessor('plan', { header: translate('Plan'), meta: { style: { textAlign: 'left' } }, - cell: (info) => Option(usagePlans.find((pp) => pp._id === info.getValue()._id)) - .map((p: IUsagePlan) => p.customName || formatPlanType(p, translate)) - .getOrNull(), + cell: (info) => + Option(usagePlans.find((pp) => pp._id === info.getValue()._id)) + .map((p: IUsagePlan) => p.customName || formatPlanType(p, translate)) + .getOrNull(), filterFn: (row, columnId, value) => { - const displayed: string = Option(usagePlans.find((pp) => pp._id === row.original.plan._id)) + const displayed: string = Option( + usagePlans.find((pp) => pp._id === row.original.plan._id) + ) .map((p: IUsagePlan) => p.customName || formatPlanType(p, translate)) - .getOrElse("") + .getOrElse(''); - return displayed.toLocaleLowerCase().includes(value.toLocaleLowerCase()) - } + return displayed + .toLocaleLowerCase() + .includes(value.toLocaleLowerCase()); + }, }), columnHelper.accessor('team', { header: translate('Team'), meta: { style: { textAlign: 'left' } }, cell: (info) => info.getValue().name, filterFn: (row, columnId, value) => { - const displayed: string = row.original.team.name + const displayed: string = row.original.team.name; - return displayed.toLocaleLowerCase().includes(value.toLocaleLowerCase()) - } + return displayed + .toLocaleLowerCase() + .includes(value.toLocaleLowerCase()); + }, }), columnHelper.accessor('enabled', { header: translate('Enabled'), @@ -231,9 +282,19 @@ export const TeamApiSubscriptions = ({ api, currentTeam }: TeamApiSubscriptionsP const sub = info.row.original; return ( Services.archiveSubscriptionByOwner(currentTeam._id, sub._id, !sub.enabled) - .then(() => tableRef.current?.update())} - checked={sub.enabled} />); + onSwitch={() => + Services.archiveSubscriptionByOwner( + currentTeam._id, + sub._id, + !sub.enabled + ).then(() => { + tableRef.current?.update(); + queryClient.invalidateQueries({ queryKey: ['subscriptions'] }); + }) + } + checked={sub.enabled} + /> + ); }, }), columnHelper.accessor('createdAt', { @@ -241,11 +302,11 @@ export const TeamApiSubscriptions = ({ api, currentTeam }: TeamApiSubscriptionsP header: translate('Created at'), meta: { style: { textAlign: 'left' } }, cell: (info) => { - const date = info.getValue() + const date = info.getValue(); if (!!date) { - return formatDate(date, language) + return formatDate(date, language); } - return translate('N/A') + return translate('N/A'); }, }), columnHelper.accessor('lastUsage', { @@ -253,11 +314,11 @@ export const TeamApiSubscriptions = ({ api, currentTeam }: TeamApiSubscriptionsP header: translate('apisubscription.lastUsage.label'), meta: { style: { textAlign: 'left' } }, cell: (info) => { - const date = info.getValue() + const date = info.getValue(); if (!!date) { - return formatDate(date, language) + return formatDate(date, language); } - return translate('N/A') + return translate('N/A'); }, }), columnHelper.display({ @@ -265,50 +326,70 @@ export const TeamApiSubscriptions = ({ api, currentTeam }: TeamApiSubscriptionsP meta: { style: { textAlign: 'center', width: '120px' } }, cell: (info) => { const sub = info.row.original; - return (
- - - - - - - - - -
); + return ( +
+ + + + + + + + + +
+ ); }, }), - ] + ]; - const updateMeta = (sub: IApiSubscriptionGql) => openSubMetadataModal({ - save: (updates: CustomSubscriptionData) => { - Services.updateSubscription(currentTeam, { ...sub, ...updates }) - .then(() => { - queryClient.invalidateQueries({ queryKey: ['subscriptions'] }) - }); - }, - api: sub.api._id, - plan: sub.plan._id, - team: sub.team, - subscription: sub, - creationMode: false, - value: (plansQuery.data as Array) - .find(p => sub.plan._id === p._id)! - }); + const updateMeta = (sub: IApiSubscriptionGql) => + openSubMetadataModal({ + save: (updates: CustomSubscriptionData) => { + Services.updateSubscription(currentTeam, { ...sub, ...updates }).then( + () => { + queryClient.invalidateQueries({ queryKey: ['subscriptions'] }); + } + ); + }, + api: sub.api._id, + plan: sub.plan._id, + team: sub.team, + subscription: sub, + creationMode: false, + value: (plansQuery.data as Array).find( + (p) => sub.plan._id === p._id + )!, + }); const regenerateApiKeySecret = useMutation({ mutationFn: (sub: IApiSubscriptionGql) => - Services.regenerateApiKeySecret(currentTeam._id, sub._id), + Services.regenerateApiKeySecret(currentTeam._id, sub._id), onSuccess: () => { - toast.success(translate("secret.refresh.success")); + toast.success(translate('secret.refresh.success')); tableRef.current?.update(); - queryClient.invalidateQueries({queryKey: ["subscriptions"]}); + queryClient.invalidateQueries({ queryKey: ['subscriptions'] }); }, onError: (e: ResponseError) => { toast.error(translate(e.error)); @@ -316,31 +397,32 @@ export const TeamApiSubscriptions = ({ api, currentTeam }: TeamApiSubscriptionsP }); const regenerateSecret = (sub: IApiSubscriptionGql) => { - - const plan = sub.plan + const plan = sub.plan; confirm({ message: translate({ key: 'secret.refresh.confirm', - replacements: [sub.team.name, plan.customName ? plan.customName : plan.type] + replacements: [ + sub.team.name, + plan.customName ? plan.customName : plan.type, + ], }), okLabel: translate('Yes'), cancelLabel: translate('No'), - }) - .then((ok) => { - if (ok) { - regenerateApiKeySecret.mutate(sub) - } - }); + }).then((ok) => { + if (ok) { + regenerateApiKeySecret.mutate(sub); + } + }); }; const deleteApiSubscription = useMutation({ mutationFn: (sub: IApiSubscriptionGql) => - Services.deleteApiSubscription(sub.team._id, sub._id), + Services.deleteApiSubscription(sub.team._id, sub._id), onSuccess: () => { - toast.success(translate("api.delete.subscription.deleted")); + toast.success(translate('api.delete.subscription.deleted')); tableRef.current?.update(); - queryClient.invalidateQueries({queryKey: ["subscriptions"]}); + queryClient.invalidateQueries({ queryKey: ['subscriptions'] }); }, onError: (e: ResponseError) => { toast.error(translate(e.error)); @@ -348,16 +430,16 @@ export const TeamApiSubscriptions = ({ api, currentTeam }: TeamApiSubscriptionsP }); const deleteSubscription = (sub: IApiSubscriptionGql) => { confirm({ - title: translate("api.delete.subscription.form.title"), + title: translate('api.delete.subscription.form.title'), message: translate({ - key: "api.delete.subscription.message", + key: 'api.delete.subscription.message', replacements: [ sub.team.name, sub.plan.customName ? sub.plan.customName : sub.plan.type, ], }), - okLabel: translate("Yes"), - cancelLabel: translate("No"), + okLabel: translate('Yes'), + cancelLabel: translate('No'), }).then((ok) => { if (ok) { deleteApiSubscription.mutate(sub); @@ -366,58 +448,71 @@ export const TeamApiSubscriptions = ({ api, currentTeam }: TeamApiSubscriptionsP }; if (plansQuery.isLoading) { - return () + return ; } else if (plansQuery.data && !isError(plansQuery.data)) { const usagePlans = plansQuery.data; - const options = usagePlans.flatMap(plan => { + const options = usagePlans.flatMap((plan) => { return [ - ...(plan.otoroshiTarget?.apikeyCustomization.customMetadata.map(({ key }) => key) || []), - ...Object.keys(plan.otoroshiTarget?.apikeyCustomization.metadata || {}) - ] + ...(plan.otoroshiTarget?.apikeyCustomization.customMetadata.map( + ({ key }) => key + ) || []), + ...Object.keys(plan.otoroshiTarget?.apikeyCustomization.metadata || {}), + ]; }); return (
-
- + array: true, + label: translate('Filter Client Ids'), + }, + }, + title: translate('Filter data'), + value: filters, + }) + } + > + {' '} + {translate('Filter')}{' '} + {!!filters && ( -
setFilters(undefined)}> +
setFilters(undefined)} + > clear filter
@@ -429,9 +524,9 @@ export const TeamApiSubscriptions = ({ api, currentTeam }: TeamApiSubscriptionsP columns={columns(usagePlans)} fetchItems={() => { if (subscriptionsQuery.isLoading || subscriptionsQuery.error) { - return [] + return []; } else { - return subscriptionsQuery.data ?? [] + return subscriptionsQuery.data ?? []; } }} ref={tableRef} @@ -441,6 +536,6 @@ export const TeamApiSubscriptions = ({ api, currentTeam }: TeamApiSubscriptionsP ); } else { - return
error while fetching usage plan
+ return
error while fetching usage plan
; } }; From bcfef5856935f9e9e0bd3ef94b99df900e8c8ab5 Mon Sep 17 00:00:00 2001 From: HelaKaraa Date: Wed, 7 Aug 2024 16:21:38 +0200 Subject: [PATCH 015/123] fix#696: add actions to show api and apikeys --- .../backoffice/apikeys/TeamApiKeys.tsx | 95 ++++++++++++------- 1 file changed, 61 insertions(+), 34 deletions(-) diff --git a/daikoku/javascript/src/components/backoffice/apikeys/TeamApiKeys.tsx b/daikoku/javascript/src/components/backoffice/apikeys/TeamApiKeys.tsx index 7251b6592..e2f142a78 100644 --- a/daikoku/javascript/src/components/backoffice/apikeys/TeamApiKeys.tsx +++ b/daikoku/javascript/src/components/backoffice/apikeys/TeamApiKeys.tsx @@ -2,16 +2,27 @@ import { createColumnHelper } from '@tanstack/react-table'; import { useContext, useEffect, useRef, useState } from 'react'; import { Link } from 'react-router-dom'; -import { I18nContext, ModalContext, useTeamBackOffice } from '../../../contexts'; +import { + I18nContext, + ModalContext, + useTeamBackOffice, +} from '../../../contexts'; import { GlobalContext } from '../../../contexts/globalContext'; import * as Services from '../../../services'; import { IApi, ITeamSimple, isError } from '../../../types'; import { Table, TableRef } from '../../inputs'; -import { Can, Spinner, apikey, isUserIsTeamAdmin, manage, teamPermissions } from '../../utils'; +import { + Can, + Spinner, + apikey, + isUserIsTeamAdmin, + manage, + teamPermissions, +} from '../../utils'; import { toast } from 'sonner'; export const TeamApiKeys = () => { - const { isLoading, currentTeam, error } = useTeamBackOffice() + const { isLoading, currentTeam, error } = useTeamBackOffice(); const { connectedUser } = useContext(GlobalContext); @@ -24,8 +35,10 @@ export const TeamApiKeys = () => { useEffect(() => { setShowApiKey( connectedUser.isDaikokuAdmin || - (currentTeam && !isError(currentTeam) && currentTeam.apiKeyVisibility !== teamPermissions.administrator) || - isUserIsTeamAdmin(connectedUser, currentTeam) + (currentTeam && + !isError(currentTeam) && + currentTeam.apiKeyVisibility !== teamPermissions.administrator) || + isUserIsTeamAdmin(connectedUser, currentTeam) ); }, [connectedUser.isDaikokuAdmin, currentTeam]); @@ -36,13 +49,13 @@ export const TeamApiKeys = () => { const columnHelper = createColumnHelper(); const columns = (currentTeam: ITeamSimple) => [ - columnHelper.accessor("name", { + columnHelper.accessor('name', { header: translate('Api Name'), - meta: { style: { textAlign: 'left' } } + meta: { style: { textAlign: 'left' } }, }), - columnHelper.accessor("currentVersion", { + columnHelper.accessor('currentVersion', { header: translate('Version'), - meta: { style: { textAlign: 'left' } } + meta: { style: { textAlign: 'left' } }, }), columnHelper.display({ header: translate('Actions'), @@ -53,15 +66,24 @@ export const TeamApiKeys = () => { const api = info.row.original; return ( showApiKey && ( -
- - - Api keys - -
+ <> +
+ + + + + + +
+ ) ); }, @@ -69,24 +91,26 @@ export const TeamApiKeys = () => { ]; const cleanSubs = (team: ITeamSimple) => { - confirm({ message: translate('clean.archived.sub.confirm') }) - .then((ok) => { - if (ok) { - Services.cleanArchivedSubscriptions(team._id) - .then(() => tableRef.current?.update()); - } - }); - } - + confirm({ message: translate('clean.archived.sub.confirm') }).then((ok) => { + if (ok) { + Services.cleanArchivedSubscriptions(team._id).then(() => + tableRef.current?.update() + ); + } + }); + }; + if (isLoading) { - return + return ; } else if (currentTeam && !isError(currentTeam)) { return (

- Subscribed Apis + + Subscribed Apis +

{ fetchItems={() => Services.subscribedApis(currentTeam._id)} ref={tableRef} /> -
@@ -111,9 +140,7 @@ export const TeamApiKeys = () => {
); } else { - toast.error(error?.message || currentTeam?.error) + toast.error(error?.message || currentTeam?.error); return <>; } - - }; From 514689f1b55cbbbd437c9dce5a0c547a47e9237e Mon Sep 17 00:00:00 2001 From: Quentin AUBERT Date: Thu, 8 Aug 2024 11:37:43 +0200 Subject: [PATCH 016/123] WIP #711 --- daikoku/app/controllers/ApiController.scala | 8 +- daikoku/app/controllers/AppError.scala | 7 +- .../controllers/NotificationController.scala | 4 +- daikoku/app/controllers/PaymentClient.scala | 2 +- daikoku/app/controllers/TeamController.scala | 4 +- daikoku/app/controllers/UsersController.scala | 2 +- daikoku/app/domain/tenantEntities.scala | 2 +- daikoku/app/utils/ApiService.scala | 431 +++++++++-- daikoku/app/utils/DeletionService.scala | 4 +- daikoku/app/utils/otoroshi.scala | 57 +- .../backoffice/apikeys/TeamApiKeysForApi.tsx | 4 +- .../src/locales/en/translation.json | 2 + .../src/locales/fr/translation.json | 2 + .../javascript/tests/connectedUser.spec.ts | 4 +- daikoku/test/daikoku/ApiControllerSpec.scala | 675 +++++++++++++++++- daikoku/test/daikoku/otoroshi.json | 57 ++ daikoku/test/daikoku/suites.scala | 7 + 17 files changed, 1152 insertions(+), 120 deletions(-) diff --git a/daikoku/app/controllers/ApiController.scala b/daikoku/app/controllers/ApiController.scala index 1f3a9b12c..1b3142642 100644 --- a/daikoku/app/controllers/ApiController.scala +++ b/daikoku/app/controllers/ApiController.scala @@ -2060,16 +2060,16 @@ class ApiController( subscriptionId, (_: Api, plan: UsagePlan, subscription: ApiSubscription) => { ctx.setCtxValue("subscription", subscription) - subscription.parent match { - case Some(_) => FastFuture.successful(Left(ForbiddenAction)) - case None => +// subscription.parent match { +// case Some(_) => FastFuture.successful(Left(ForbiddenAction)) +// case None => toggleSubscription( plan, subscription, ctx.tenant, enabled.getOrElse(false) ) - } +// } } ) } diff --git a/daikoku/app/controllers/AppError.scala b/daikoku/app/controllers/AppError.scala index 10ace1b84..eadbc04e7 100644 --- a/daikoku/app/controllers/AppError.scala +++ b/daikoku/app/controllers/AppError.scala @@ -2,6 +2,7 @@ package controllers import org.apache.pekko.http.scaladsl.util.FastFuture import controllers.AppError.toJson +import fr.maif.otoroshi.daikoku.domain.UserId import play.api.libs.json.{JsObject, Json} import play.api.mvc import play.api.mvc.Results._ @@ -25,7 +26,7 @@ object AppError { case object ApiGroupNotFound extends AppError case object TenantNotFound extends AppError case object TeamNotFound extends AppError - case object UserNotFound extends AppError + case class UserNotFound(user: Option[UserId] = None) extends AppError case class EntityNotFound(entityName: String) extends AppError case object ForbiddenAction extends AppError case object OtoroshiSettingsNotFound extends AppError @@ -77,7 +78,7 @@ object AppError { case ApiGroupNotFound => NotFound(toJson(error)) case TeamNotFound => NotFound(toJson(error)) case TenantNotFound => NotFound(toJson(error)) - case UserNotFound => NotFound(toJson(error)) + case UserNotFound(_) => NotFound(toJson(error)) case EntityNotFound(_) => NotFound(toJson(error)) case SubscriptionDemandNotFound => NotFound(toJson(error)) case SubscriptionDemandClosed => Forbidden(toJson(error)) @@ -137,7 +138,7 @@ object AppError { case ApiGroupNotFound => "API group not found" case TeamNotFound => "Team not found" case TenantNotFound => "Tenant not found" - case UserNotFound => "User not found" + case UserNotFound(user) => s"User not found ${user.map(id => s"(ID: $id)").getOrElse("")}" case EntityNotFound(name) => s"$name not found" case NotificationNotFound => "Notification not found" case SubscriptionDemandNotFound => "SubscriptionDemand not found" diff --git a/daikoku/app/controllers/NotificationController.scala b/daikoku/app/controllers/NotificationController.scala index f4239e371..28d1ff001 100644 --- a/daikoku/app/controllers/NotificationController.scala +++ b/daikoku/app/controllers/NotificationController.scala @@ -686,7 +686,7 @@ class NotificationController( ) sender <- EitherT.fromOptionF[Future, AppError, User]( env.dataStore.userRepo.findByIdNotDeleted(notification.sender.id.get), - AppError.UserNotFound + AppError.UserNotFound() ) } yield { implicit val lang: String = sender.defaultLanguage @@ -813,7 +813,7 @@ class NotificationController( val r: EitherT[Future, AppError, Unit] = for { invitedUser <- EitherT.fromOptionF( env.dataStore.userRepo.findByIdNotDeleted(invitedUserId), - UserNotFound + UserNotFound() ) team <- EitherT.fromOptionF( env.dataStore.teamRepo.forTenant(tenant).findByIdNotDeleted(team), diff --git a/daikoku/app/controllers/PaymentClient.scala b/daikoku/app/controllers/PaymentClient.scala index c23bb3358..c2dce3fc5 100644 --- a/daikoku/app/controllers/PaymentClient.scala +++ b/daikoku/app/controllers/PaymentClient.scala @@ -208,7 +208,7 @@ class PaymentClient( }) user <- EitherT.fromOptionF( env.dataStore.userRepo.findByIdNotDeleted(subscriptionDemand.from), - AppError.UserNotFound + AppError.UserNotFound() ) settings <- EitherT.fromOption[Future]( plan.paymentSettings, diff --git a/daikoku/app/controllers/TeamController.scala b/daikoku/app/controllers/TeamController.scala index 080d652a3..0d6f8e739 100644 --- a/daikoku/app/controllers/TeamController.scala +++ b/daikoku/app/controllers/TeamController.scala @@ -955,11 +955,11 @@ class TeamController( // TODO: verify if the behavior is correct case team if team.includeUser(UserId(id)) => env.dataStore.userRepo.findByIdNotDeleted(id).map { - case None => Left(AppError.UserNotFound) + case None => Left(AppError.UserNotFound(None)) case Some(user) => Right(Ok(user.asSimpleJson)) } case _ => - FastFuture.successful(Left(AppError.UserNotFound)) + FastFuture.successful(Left(AppError.UserNotFound())) } } diff --git a/daikoku/app/controllers/UsersController.scala b/daikoku/app/controllers/UsersController.scala index d0b60a991..88e68806b 100644 --- a/daikoku/app/controllers/UsersController.scala +++ b/daikoku/app/controllers/UsersController.scala @@ -536,7 +536,7 @@ class UsersController( user <- EitherT.fromOptionF[Future, AppError, User]( env.dataStore.userRepo .findOne(Json.obj("invitation.token" -> token)), - AppError.UserNotFound + AppError.UserNotFound() ) _ = AppLogger.info(user.id.value) _ <- EitherT.pure[Future, AppError]( diff --git a/daikoku/app/domain/tenantEntities.scala b/daikoku/app/domain/tenantEntities.scala index 27c8376f1..bda1581cb 100644 --- a/daikoku/app/domain/tenantEntities.scala +++ b/daikoku/app/domain/tenantEntities.scala @@ -728,7 +728,7 @@ case class CmsPage( ), req ) - case None => AppError.render(AppError.UserNotFound) + case None => AppError.render(AppError.UserNotFound()) } } ) diff --git a/daikoku/app/utils/ApiService.scala b/daikoku/app/utils/ApiService.scala index 8e981fad2..a00480d2c 100644 --- a/daikoku/app/utils/ApiService.scala +++ b/daikoku/app/utils/ApiService.scala @@ -25,6 +25,7 @@ import play.api.libs.json._ import play.api.mvc.Result import play.api.mvc.Results.Ok +import scala.:+ import scala.concurrent.{ExecutionContext, Future} import scala.util.Try @@ -424,6 +425,257 @@ class ApiService( } } + case class SyncInformation( + parent: ApiSubscription, + childs: Seq[ApiSubscription], + team: Team, + parentApi: Api, + apk: ActualOtoroshiApiKey, + otoroshiSettings: OtoroshiSettings, + tenant: Tenant, + tenantAdminTeam: Team + ) + + case class ComputedInformation( + parent: ApiSubscription, + childs: Seq[ApiSubscription], + apk: ActualOtoroshiApiKey, + computedApk: ActualOtoroshiApiKey, + otoroshiSettings: OtoroshiSettings, + tenant: Tenant, + tenantAdminTeam: Team + ) + + def getListFromMeta( + key: String, + metadata: Map[String, String] + ): Set[String] = { + metadata + .get(key) + .map(_.split('|').toSeq.map(_.trim).toSet) + .getOrElse(Set.empty) + } + + def mergeMetaValue( + key: String, + meta1: Map[String, String], + meta2: Map[String, String] + ): String = { + val list1 = getListFromMeta(key, meta1) + val list2 = getListFromMeta(key, meta2) + (list1 ++ list2).mkString(" | ") + } + + private def computeAPIKey( + infos: SyncInformation + ): Future[Either[AppError, ComputedInformation]] = { + val seq = (infos.childs :+ infos.parent) + .map(subscription => { + for { + api <- EitherT.fromOptionF[Future, AppError, Api]( + env.dataStore.apiRepo + .forAllTenant() + .findOneNotDeleted( + Json.obj( + "_id" -> subscription.api.value, + "state" -> ApiState.publishedJsonFilter + ) + ), + AppError.EntityNotFound(s"Api ${subscription.api.value} (for subscription ${subscription.id.value})") + ) + plan <- EitherT.fromOptionF[Future, AppError, UsagePlan]( + env.dataStore.usagePlanRepo + .forTenant(infos.tenant) + .findById(subscription.plan), + AppError.EntityNotFound(s"usage plan ${subscription.plan.value} (for subscription ${subscription.id.value})") + ) + user <- + EitherT + .fromOptionF[Future, AppError, User]( + env.dataStore.userRepo.findById(subscription.by), + AppError.UserNotFound(subscription.by.some) + ) + } yield { + val ctx: Map[String, String] = Map( + "user.id" -> user.id.value, + "user.name" -> user.name, + "user.email" -> user.email, + "api.id" -> infos.parent.api.value, + "api.name" -> infos.parentApi.name, + "team.id" -> infos.parent.team.value, + "team.name" -> infos.team.name, + "tenant.id" -> infos.tenant.id.value, + "tenant.name" -> infos.tenant.name, + "client.id" -> infos.apk.clientId, + "client.name" -> infos.apk.clientName + ) ++ infos.team.metadata + .map(t => ("team.metadata." + t._1, t._2)) ++ + user.metadata.map(t => ("user.metadata." + t._1, t._2)) + + // ******************** + // process new tags + // ******************** + val planTags = plan.otoroshiTarget + .flatMap(_.apikeyCustomization.tags.asOpt[Set[String]]) + .getOrElse(Set.empty[String]) + + val tagsFromDk = + getListFromMeta("daikoku__tags", infos.apk.metadata) + val newTagsFromDk = + planTags.map(OtoroshiTarget.processValue(_, ctx)) + + //todo: unnecessary ?? + //val newTags: Set[String] = apk.tags.diff(tagsFromDk) ++ newTagsFromDk + + // ******************** + // process new metadata + // ******************** + val planMeta = plan.otoroshiTarget + .map(_.apikeyCustomization.metadata.as[Map[String, String]]) + .getOrElse(Map.empty[String, String]) + + val metaFromDk = infos.apk.metadata + .get("daikoku__metadata") + .map( + _.split('|').toSeq + .map(_.trim) + .map(key => key -> infos.apk.metadata.get(key).orNull) + ) + .getOrElse(planMeta.map { + case (a, b) => + a -> OtoroshiTarget.processValue(b, ctx) + }) + .toMap + + val customMetaFromSub = subscription.customMetadata + .flatMap(_.asOpt[Map[String, String]]) + .getOrElse(Map.empty[String, String]) + + val newMetaFromDk = (planMeta ++ customMetaFromSub).map { + case (a, b) => a -> OtoroshiTarget.processValue(b, ctx) + } + val newMeta = infos.apk.metadata + .removedAll(metaFromDk.keys) ++ newMetaFromDk ++ Map( + "daikoku__metadata" -> newMetaFromDk.keys + .mkString(" | "), + "daikoku__tags" -> newTagsFromDk.mkString(" | ") + ) + + // ******************** + // process new metadata + // ******************** + + infos.apk.copy( + tags = newTagsFromDk, + enabled = subscription.enabled, + metadata = newMeta, + constrainedServicesOnly = plan.otoroshiTarget + .exists(_.apikeyCustomization.constrainedServicesOnly), + allowClientIdOnly = + plan.otoroshiTarget.exists(_.apikeyCustomization.clientIdOnly), + restrictions = plan.otoroshiTarget + .map(_.apikeyCustomization.restrictions) + .getOrElse(ApiKeyRestrictions()), + throttlingQuota = subscription.customMaxPerSecond + .orElse(plan.maxRequestPerSecond) + .getOrElse(infos.apk.throttlingQuota), + dailyQuota = subscription.customMaxPerDay + .orElse(plan.maxRequestPerDay) + .getOrElse(infos.apk.dailyQuota), + monthlyQuota = subscription.customMaxPerMonth + .orElse(plan.maxRequestPerMonth) + .getOrElse(infos.apk.monthlyQuota), + authorizedEntities = plan.otoroshiTarget + .flatMap(_.authorizedEntities) + .getOrElse(AuthorizedEntities()), + readOnly = subscription.customReadOnly + .orElse( + plan.otoroshiTarget + .map(_.apikeyCustomization.readOnly) + ) + .getOrElse(infos.apk.readOnly), + rotation = infos.apk.rotation + .map(r => + r.copy(enabled = + r.enabled || subscription.rotation + .exists(_.enabled) || plan.autoRotation + .exists(e => e) + ) + ) + .orElse( + subscription.rotation.map(r => + ApiKeyRotation( + enabled = + r.enabled || plan.autoRotation.exists(e => e), + rotationEvery = r.rotationEvery, + gracePeriod = r.gracePeriod + ) + ) + ) + .orElse( + plan.autoRotation + .map(enabled => ApiKeyRotation(enabled = enabled)) + ) + ) + + } + }) + + seq.reduce((info1, info2) => { + for { + apikey1 <- info1 + apikey2 <- info2 + } yield apikey1.copy( + tags = apikey1.tags ++ apikey2.tags, + metadata = apikey1.metadata ++ + apikey2.metadata ++ + Map( + "daikoku__metadata" -> mergeMetaValue( + "daikoku__metadata", + apikey1.metadata, + apikey2.metadata + ), + "daikoku__tags" -> mergeMetaValue( + "daikoku__tags", + apikey1.metadata, + apikey2.metadata + ) + ), + restrictions = ApiKeyRestrictions( + enabled = + apikey1.restrictions.enabled && apikey2.restrictions.enabled, + allowLast = + apikey1.restrictions.allowLast || apikey2.restrictions.allowLast, + allowed = + apikey1.restrictions.allowed ++ apikey2.restrictions.allowed, + forbidden = + apikey1.restrictions.forbidden ++ apikey2.restrictions.forbidden, + notFound = + apikey1.restrictions.notFound ++ apikey2.restrictions.notFound + ), + authorizedEntities = AuthorizedEntities( + groups = + apikey1.authorizedEntities.groups | apikey2.authorizedEntities.groups, + services = + apikey1.authorizedEntities.services | apikey2.authorizedEntities.services, + routes = + apikey1.authorizedEntities.routes | apikey2.authorizedEntities.routes + ) + ) + }) + .map(computedApk => { + ComputedInformation( + parent = infos.parent, + childs = infos.childs, + apk = infos.apk, + computedApk = computedApk, + otoroshiSettings = infos.otoroshiSettings, + tenant = infos.tenant, + tenantAdminTeam = infos.tenantAdminTeam + ) + }).value + } + def updateSubscription( tenant: Tenant, subscription: ApiSubscription, @@ -558,6 +810,90 @@ class ApiService( } } + def computeOtoroshiApiKey( + subscription: ApiSubscription): Future[Either[AppError, ActualOtoroshiApiKey]] = { + val r = for { + tenant <- EitherT.fromOptionF[Future, AppError, Tenant]( + env.dataStore.tenantRepo.findByIdNotDeleted(subscription.tenant), + AppError.TenantNotFound + ) + //get tenant team admin + tenantAdminTeam <- EitherT.fromOptionF[Future, AppError, Team]( + env.dataStore.teamRepo + .forTenant(tenant) + .findOne(Json.obj("type" -> "Admin")), + AppError.EntityNotFound(s"Tenant admin team for tenant ${tenant.id.value}") + ) + + //GET parent API + parentApi <- EitherT.fromOptionF[Future, AppError, Api]( + env.dataStore.apiRepo + .forAllTenant() + .findOneNotDeleted( + Json.obj( + "_id" -> subscription.api.value, + "state" -> ApiState.publishedJsonFilter + ) + ), + AppError.ApiNotFound + ) + + //Get parent plan + plan <- EitherT.fromOptionF[Future, AppError, UsagePlan]( + env.dataStore.usagePlanRepo + .forTenant(tenant) + .findById(subscription.plan), + AppError.PlanNotFound + ) + + //get ototoshi target from parent plan + otoroshiTarget <- EitherT.fromOption[Future]( + plan.otoroshiTarget, + AppError.EntityNotFound(s"Otoroshi target for plan ${plan.id.value}") + ) + + //get otoroshi settings from parent plan + otoroshiSettings <- EitherT.fromOption[Future]( + tenant.otoroshiSettings + .find(_.id == otoroshiTarget.otoroshiSettings), + AppError.EntityNotFound(s"otoroshi settings (${otoroshiTarget.otoroshiSettings.value}") + ) + + // get previous apikey from otoroshi + apk <- EitherT(otoroshiClient.getApikey(subscription.apiKey.clientId)(otoroshiSettings)) + + // get subscription team + team <- EitherT.fromOptionF[Future, AppError, Team]( + env.dataStore.teamRepo + .forTenant(tenant) + .findById(subscription.team), + AppError.TeamNotFound + ) + + childs <- EitherT.liftF( + env.dataStore.apiSubscriptionRepo + .forAllTenant() + .findNotDeleted(Json.obj( + "parent" -> subscription.id.asJson, + "enabled" -> true + )) + ) + + computedInformation <- EitherT(computeAPIKey(SyncInformation( + parent = subscription, + childs = childs, + apk = apk, + otoroshiSettings = otoroshiSettings, + tenant = tenant, + team = team, + parentApi = parentApi, + tenantAdminTeam = tenantAdminTeam + ))) + } yield computedInformation.computedApk + + r.value + } + def archiveApiKey( tenant: Tenant, subscription: ApiSubscription, @@ -568,73 +904,42 @@ class ApiService( val updatedSubscription = subscription.copy(enabled = enabled) - plan.otoroshiTarget.map(_.otoroshiSettings).flatMap { id => - tenant.otoroshiSettings.find(_.id == id) - } match { + plan.otoroshiTarget.map(_.otoroshiSettings) + .flatMap { id => + tenant.otoroshiSettings.find(_.id == id) + } match { case None => FastFuture.successful(Left(OtoroshiSettingsNotFound)) case Some(otoSettings) => implicit val otoroshiSettings: OtoroshiSettings = otoSettings val r: EitherT[Future, AppError, JsObject] = for { - apiKey <- EitherT( - otoroshiClient.getApikey(subscription.apiKey.clientId) - ) - _ <- subscription.parent match { - case Some(_) => - plan.otoroshiTarget match { - case Some(target) - if target.authorizedEntities.isDefined && enabled => - EitherT.liftF( - otoroshiClient.updateApiKey( - apiKey.copy(authorizedEntities = - apiKey.authorizedEntities.copy( - services = - apiKey.authorizedEntities.services ++ target.authorizedEntities.get.services, - groups = - apiKey.authorizedEntities.groups ++ target.authorizedEntities.get.groups, - routes = - apiKey.authorizedEntities.routes ++ target.authorizedEntities.get.routes - ) - ) - ) - ) - case Some(target) if target.authorizedEntities.isDefined => - EitherT.liftF( - otoroshiClient.updateApiKey( - apiKey.copy(authorizedEntities = - apiKey.authorizedEntities.copy( - services = - apiKey.authorizedEntities.services.filter(s => - !target.authorizedEntities.get.services - .contains(OtoroshiServiceId(s.value)) - ), - groups = apiKey.authorizedEntities.groups.filter(s => - !target.authorizedEntities.get.groups - .contains(OtoroshiServiceGroupId(s.value)) - ), - routes = apiKey.authorizedEntities.routes.filter(s => - !target.authorizedEntities.get.routes - .contains(OtoroshiRouteId(s.value)) - ) - ) - ) - ) - ) - case _ => - EitherT.leftT[Future, JsObject](OtoroshiSettingsNotFound) - } - case None => - EitherT.liftF( - otoroshiClient.updateApiKey(apiKey.copy(enabled = enabled)) - ) - } - _ <- - paymentClient.toggleStateThirdPartySubscription(updatedSubscription) _ <- EitherT.liftF( env.dataStore.apiSubscriptionRepo .forTenant(tenant.id) .save(updatedSubscription) ) + _ <- if (!enabled) EitherT.liftF( + env.dataStore.apiSubscriptionRepo + .forTenant(tenant.id) + .updateManyByQuery( + Json.obj( + "parent" -> subscription.id.asJson + ), + Json.obj( + "$set" -> Json.obj( + "enabled" -> enabled, + ) + ) + ) + ) else EitherT.pure[Future, AppError](0) + parentSubscription <- subscription.parent match { + case Some(parentId) => EitherT.fromOptionF(env.dataStore.apiSubscriptionRepo.forTenant(tenant).findById(parentId), + AppError.EntityNotFound(s"Parent subscription (ID: ${parentId.value})")) + case None => EitherT.pure[Future, AppError](updatedSubscription) + } + apk <- EitherT(computeOtoroshiApiKey(parentSubscription)) + _ <- EitherT(otoroshiClient.updateApiKey(apk)) + _ <- paymentClient.toggleStateThirdPartySubscription(updatedSubscription) } yield updatedSubscription.asSafeJson.as[JsObject] r.value @@ -1292,13 +1597,13 @@ class ApiService( val motivationPattern = "\\[\\[(.+?)\\]\\]".r for { - api <- EitherT.fromOptionF( + api <- EitherT.fromOptionF[Future, AppError, Api]( env.dataStore.apiRepo.forTenant(tenant).findByIdNotDeleted(demand.api), AppError.ApiNotFound ) user <- EitherT.fromOptionF( env.dataStore.userRepo.findByIdNotDeleted(demand.from), - AppError.UserNotFound + AppError.UserNotFound() ) motivationAsString = motivationPattern @@ -1448,7 +1753,7 @@ class ApiService( ) user <- EitherT.fromOptionF[Future, AppError, User]( env.dataStore.userRepo.findById(demand.from), - AppError.UserNotFound + AppError.UserNotFound() ) parentSubscription <- EitherT.liftF[Future, AppError, Option[ApiSubscription]]( @@ -1587,7 +1892,7 @@ class ApiService( ) user <- EitherT.fromOptionF( env.dataStore.userRepo.findByIdNotDeleted(demand.from), - AppError.UserNotFound + AppError.UserNotFound() ) team <- EitherT.fromOptionF( env.dataStore.teamRepo @@ -1783,7 +2088,7 @@ class ApiService( for { from <- EitherT.fromOptionF( env.dataStore.userRepo.findByIdNotDeleted(demand.from), - AppError.UserNotFound + AppError.UserNotFound() ) api <- EitherT.fromOptionF( env.dataStore.apiRepo @@ -2203,7 +2508,7 @@ class ApiService( ) from <- EitherT.fromOptionF( env.dataStore.userRepo.findByIdNotDeleted(demand.from), - AppError.UserNotFound + AppError.UserNotFound() ) team <- EitherT.fromOptionF( env.dataStore.teamRepo diff --git a/daikoku/app/utils/DeletionService.scala b/daikoku/app/utils/DeletionService.scala index 8bd178184..31bff44cc 100644 --- a/daikoku/app/utils/DeletionService.scala +++ b/daikoku/app/utils/DeletionService.scala @@ -299,7 +299,7 @@ class DeletionService( for { user <- EitherT.fromOptionF( env.dataStore.userRepo.findByIdNotDeleted(userId), - AppError.UserNotFound + AppError.UserNotFound() ) team <- EitherT.fromOptionF( env.dataStore.teamRepo @@ -348,7 +348,7 @@ class DeletionService( for { user <- EitherT.fromOptionF( env.dataStore.userRepo.findByIdNotDeleted(userId), - AppError.UserNotFound + AppError.UserNotFound() ) teams <- EitherT.liftF( env.dataStore.teamRepo diff --git a/daikoku/app/utils/otoroshi.scala b/daikoku/app/utils/otoroshi.scala index b0125a646..1026b176c 100644 --- a/daikoku/app/utils/otoroshi.scala +++ b/daikoku/app/utils/otoroshi.scala @@ -6,19 +6,10 @@ import cats.data.EitherT import cats.implicits.catsSyntaxOptionId import controllers.AppError import controllers.AppError.OtoroshiError -import fr.maif.otoroshi.daikoku.audit.{ - ElasticReadsAnalytics, - ElasticWritesAnalytics -} +import fr.maif.otoroshi.daikoku.audit.{ElasticReadsAnalytics, ElasticWritesAnalytics} import fr.maif.otoroshi.daikoku.audit.config.ElasticAnalyticsConfig import fr.maif.otoroshi.daikoku.domain.json.ActualOtoroshiApiKeyFormat -import fr.maif.otoroshi.daikoku.domain.{ - ActualOtoroshiApiKey, - ApiSubscription, - OtoroshiSettings, - Tenant, - json -} +import fr.maif.otoroshi.daikoku.domain.{ActualOtoroshiApiKey, ApiKeyQuotas, ApiSubscription, OtoroshiSettings, Tenant, json} import fr.maif.otoroshi.daikoku.env.Env import fr.maif.otoroshi.daikoku.logger.AppLogger import play.api.libs.json._ @@ -327,32 +318,46 @@ class OtoroshiClient(env: Env) { def getApiKeyConsumption(clientId: String, from: String, to: String)(implicit otoroshiSettings: OtoroshiSettings ): Future[JsObject] = { - client(s"/api/stats?apikey=$clientId&from=$from&to=$to").get().flatMap { + client(s"/api/stats?apikey=$clientId&from=$from&to=$to").get().map { resp => if (resp.status == 200) { - Future.successful(resp.json.as[JsObject]) + resp.json.as[JsObject] } else { - Future.failed( - new RuntimeException( - s"Error while getting otoroshi apikey stats: ${resp.status} - ${resp.body}" - ) + AppLogger.error(s"Error while getting otoroshi apikey stats: ${resp.status} - ${resp.body}") + Json.obj( + "hits" -> Json.obj("count" -> 0), + "dataIn" -> Json.obj("data" -> Json.obj( + "dataIn" -> 0 + )), + "dataOut" -> Json.obj("data" -> Json.obj( + "dataOut" -> 0 + )), + "avgDuration"-> Json.obj("duration" -> 0), + "avgOverhead"-> Json.obj("overhead" -> 0) ) } } } def getApiKeyQuotas( - clientId: String - )(implicit otoroshiSettings: OtoroshiSettings): Future[JsObject] = { - client(s"/api/apikeys/$clientId/quotas").get().flatMap { resp => + clientId: String + )(implicit otoroshiSettings: OtoroshiSettings): Future[JsObject] = { + client(s"/api/apikeys/$clientId/quotas").get().map { resp => if (resp.status == 200) { - Future.successful(resp.json.as[JsObject]) + resp.json.as[JsObject] } else { - Future.failed( - new RuntimeException( - s"Error while getting otoroshi apikey stats: ${resp.status} - ${resp.body}" - ) - ) + AppLogger.error(s"Error while getting otoroshi apikey stats: ${resp.status} - ${resp.body}") + ApiKeyQuotas( + authorizedCallsPerSec = 0, + currentCallsPerSec = 0, + remainingCallsPerSec = 0, + authorizedCallsPerDay = 0, + currentCallsPerDay = 0, + remainingCallsPerDay = 0, + authorizedCallsPerMonth = 0, + currentCallsPerMonth = 0, + remainingCallsPerMonth = 0 + ).asJson.as[JsObject] } } } diff --git a/daikoku/javascript/src/components/backoffice/apikeys/TeamApiKeysForApi.tsx b/daikoku/javascript/src/components/backoffice/apikeys/TeamApiKeysForApi.tsx index db2e65c93..91f591b05 100644 --- a/daikoku/javascript/src/components/backoffice/apikeys/TeamApiKeysForApi.tsx +++ b/daikoku/javascript/src/components/backoffice/apikeys/TeamApiKeysForApi.tsx @@ -617,14 +617,14 @@ const ApiKeyCard = ({ )} {!subscription.parent && ( - + DaikokuDocumentationOpenAPICLI

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

diff --git a/docs/assets/js/9755f291.e6f5e14c.js b/docs/assets/js/9755f291.e6f5e14c.js new file mode 100644 index 000000000..ad707f85c --- /dev/null +++ b/docs/assets/js/9755f291.e6f5e14c.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdaikoku_documentation=self.webpackChunkdaikoku_documentation||[]).push([[9235],{5277:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>d,contentTitle:()=>l,default:()=>p,frontMatter:()=>s,metadata:()=>c,toc:()=>h});var t=i(5893),o=i(1151),a=i(4866),r=i(5162);const s={},l="Authentication",c={id:"guides/authentication",title:"Authentication",description:"Authentication in daikoku can be tricky. It's a tenant configuration, we are here to setup some examples.",source:"@site/docs/03-guides/14-authentication.mdx",sourceDirName:"03-guides",slug:"/guides/authentication",permalink:"/daikoku/docs/guides/authentication",draft:!1,unlisted:!1,tags:[],version:"current",sidebarPosition:14,frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Deploy to production",permalink:"/daikoku/docs/guides/deploy"},next:{title:"CLI",permalink:"/daikoku/docs/cli/"}},d={},h=[{value:"Before starting",id:"before-starting",level:2},{value:"Running an openldap server",id:"running-an-openldap-server",level:2},{value:"Create an Authentication configuration",id:"create-an-authentication-configuration",level:2},{value:"Testing your configuration",id:"testing-your-configuration",level:2},{value:"Before starting",id:"before-starting-1",level:2},{value:"Running an otoroshi server",id:"running-an-otoroshi-server",level:2},{value:"Create an authentication module",id:"create-an-authentication-module",level:2},{value:"Expose your daikoku by Otoroshi",id:"expose-your-daikoku-by-otoroshi",level:2},{value:"Create an Authentication configuration",id:"create-an-authentication-configuration-1",level:2},{value:"Testing your configuration",id:"testing-your-configuration-1",level:2},{value:"Before starting",id:"before-starting-2",level:2},{value:"Configure an Auth0 client",id:"configure-an-auth0-client",level:2},{value:"Create authentication configuration",id:"create-authentication-configuration",level:2},{value:"Testing your configuration",id:"testing-your-configuration-2",level:2}];function u(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",p:"p",pre:"pre",ul:"ul",...(0,o.a)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.h1,{id:"authentication",children:"Authentication"}),"\n",(0,t.jsx)(n.p,{children:"Authentication in daikoku can be tricky. It's a tenant configuration, we are here to setup some examples."}),"\n",(0,t.jsxs)(a.Z,{children:[(0,t.jsx)(r.Z,{value:"local",label:"Local",default:!0,children:(0,t.jsx)(n.p,{children:"this is the default authentication mode. Every user can create an account in your Daikoku instance, the user profil is saved in database.\nThere is no needed configuration for this mode."})}),(0,t.jsxs)(r.Z,{value:"ldap",label:"LDAP",children:[(0,t.jsx)(n.h2,{id:"before-starting",children:"Before starting"}),(0,t.jsx)(n.p,{children:"If you already have an up and running Daikoku instance, you can skip the following instructions and log in to your instance."}),(0,t.jsxs)(n.p,{children:["Let\u2019s start by ",(0,t.jsx)(n.a,{href:"/daikoku/docs/getstarted/getdaikoku/",children:"downloading the latest Daikoku"})," and ",(0,t.jsx)(n.a,{href:"/daikoku/docs/getstarted/firstrun/run",children:"run it"})]}),(0,t.jsx)(n.p,{children:"Once Daikoku is started you can log in to your brand new instance."}),(0,t.jsx)(n.h2,{id:"running-an-openldap-server",children:"Running an openldap server"}),(0,t.jsxs)(n.p,{children:["Run ghcr.io/rroemhild/docker-test-openldap",":master"," docker Image"]}),(0,t.jsxs)(n.admonition,{type:"note",children:[(0,t.jsx)(n.mdxAdmonitionTitle,{}),(0,t.jsx)(n.p,{children:(0,t.jsxs)(n.em,{children:["you can find all documention on the ",(0,t.jsx)(n.a,{href:"https://github.com/rroemhild/docker-test-openldap",children:"github repo"})]})})]}),(0,t.jsx)(n.p,{children:"first, pull and run your ldap. This openldap is already initialized with data based on futurama tv show."}),(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"docker pull ghcr.io/rroemhild/docker-test-openldap:master\ndocker run --rm -p 10389:10389 -p 10636:10636 ghcr.io/rroemhild/docker-test-openldap:master\n\n"})}),(0,t.jsx)(n.p,{children:"Let\u2019s make the first search in our LDAP container :"}),(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:'# List all Users\nldapsearch -H ldap://localhost:10389 -x -b "ou=people,dc=planetexpress,dc=com" -D "cn=admin,dc=planetexpress,dc=com" -w GoodNewsEveryone "(objectClass=inetOrgPerson)"\n'})}),(0,t.jsx)(n.p,{children:"the response is very long due to image but at the end you shoulf have the following output:"}),(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-Bash",children:" ...\n ...\n # admin_staff, people, planetexpress.com\n dn: cn=admin_staff,ou=people,dc=planetexpress,dc=com\n objectClass: Group\n objectClass: top\n groupType: 2147483650\n cn: admin_staff\n member: cn=Hubert J. Farnsworth,ou=people,dc=planetexpress,dc=com\n member: cn=Hermes Conrad,ou=people,dc=planetexpress,dc=com\n\n # ship_crew, people, planetexpress.com\n dn: cn=ship_crew,ou=people,dc=planetexpress,dc=com\n objectClass: Group\n objectClass: top\n groupType: 2147483650\n cn: ship_crew\n member: cn=Philip J. Fry,ou=people,dc=planetexpress,dc=com\n member: cn=Turanga Leela,ou=people,dc=planetexpress,dc=com\n member:: Y249QmVuZGVyIEJlbmRpbmcgUm9kcsOtZ3VleixvdT1wZW9wbGUsZGM9cGxhbmV0ZXhwc\n mVzcyxkYz1jb20=\n\n # search result\n search: 2\n result: 0 Success\n\n # numResponses: 11\n # numEntries: 10\n"})}),(0,t.jsx)(n.h2,{id:"create-an-authentication-configuration",children:"Create an Authentication configuration"}),(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Go ahead and navigate to your Daikoku home page"}),"\n",(0,t.jsx)(n.li,{children:"Click on the your avatar on bottom left of the screen"}),"\n",(0,t.jsxs)(n.li,{children:["Then ",(0,t.jsx)(n.code,{children:" settings"})]}),"\n",(0,t.jsxs)(n.li,{children:["Then ",(0,t.jsx)(n.code,{children:"Authentication"})," on left panel"]}),"\n",(0,t.jsxs)(n.li,{children:["You must be on ",(0,t.jsx)(n.code,{children:"/settings/settings/authentication"})]}),"\n",(0,t.jsxs)(n.li,{children:["This page show the settings for the current authentication mode (by default ",(0,t.jsx)(n.code,{children:"Local"}),")"]}),"\n",(0,t.jsxs)(n.li,{children:["Click on ",(0,t.jsx)(n.code,{children:"LDAP"})," at the top of the form"]}),"\n",(0,t.jsxs)(n.li,{children:["Add a ",(0,t.jsx)(n.code,{children:"LDAP Server URL"})," with value ",(0,t.jsx)(n.code,{children:"ldap://localhost:10389"})," and ",(0,t.jsx)(n.code,{children:"dc=planetexpress,dc=com"})," as ",(0,t.jsx)(n.code,{children:"Search base"})]}),"\n",(0,t.jsxs)(n.li,{children:["Set ",(0,t.jsx)(n.code,{children:"ou=people"})," as ",(0,t.jsx)(n.code,{children:"Users search base"})]}),"\n",(0,t.jsxs)(n.li,{children:["Set ",(0,t.jsx)(n.code,{children:"cn=ship_crew"})," as ",(0,t.jsx)(n.code,{children:"Simple user filter"})]}),"\n",(0,t.jsxs)(n.li,{children:["Set ",(0,t.jsx)(n.code,{children:"cn=admin_staff"})," as ",(0,t.jsx)(n.code,{children:"Daikoku admin filter"})]}),"\n",(0,t.jsxs)(n.li,{children:["Set ",(0,t.jsx)(n.code,{children:"(mail=${username})"})," as ",(0,t.jsx)(n.code,{children:"Search filter"})]}),"\n",(0,t.jsxs)(n.li,{children:["Set ",(0,t.jsx)(n.code,{children:"cn=admin,dc=planetexpress,dc=com"})," as ",(0,t.jsx)(n.code,{children:"Admin username (bind DN)"})]}),"\n",(0,t.jsxs)(n.li,{children:["Set ",(0,t.jsx)(n.code,{children:"GoodNewsEveryone"})," as ",(0,t.jsx)(n.code,{children:"Admin password"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"Name fields name"})," can be ",(0,t.jsx)(n.code,{children:"givenName"})," and ",(0,t.jsx)(n.code,{children:"sn"})]}),"\n",(0,t.jsxs)(n.li,{children:["Set ",(0,t.jsx)(n.code,{children:"mail"})," as ",(0,t.jsx)(n.code,{children:"Email field name"})]}),"\n"]}),(0,t.jsxs)(n.p,{children:["With this configuration, all ldap users with cn ",(0,t.jsx)(n.code,{children:"admin_staff"})," will be Daikoku admin otherwise, with cn ",(0,t.jsx)(n.code,{children:"ship_crew"})," he will be a simple user."]}),(0,t.jsx)(n.h2,{id:"testing-your-configuration",children:"Testing your configuration"}),(0,t.jsxs)(n.p,{children:["Disconnect from your instance\nThen click on the Login button (or navigate to ",(0,t.jsx)(n.code,{children:"/login"}),")\nSet ",(0,t.jsx)(n.a,{href:"mailto:fry@planetexpress.com",children:"fry@planetexpress.com"}),"/fry as credentials for a simple user and ",(0,t.jsx)(n.a,{href:"mailto:professor@planetexpress.com",children:"professor@planetexpress.com"}),"/professor for a Daikoku admin."]}),(0,t.jsxs)(n.p,{children:["A fallback solution is always available in the event of a bad authentication configuration.\nBy going to ",(0,t.jsx)(n.code,{children:".login"}),", the previous local administrators will be able to login."]}),(0,t.jsxs)(n.admonition,{type:"warning",children:[(0,t.jsx)(n.p,{children:"In one case, your search filter can be annoying.\nif instead of mail, you've choose to log user by uid, when team admins will wants to invite a collaborator, they will be required to know the user uid."}),(0,t.jsxs)(n.p,{children:["In this case you can replace ",(0,t.jsx)(n.code,{children:"Search filter"})," by ",(0,t.jsx)(n.code,{children:"(|(uid=${username})(mail=${username}))"})," both ",(0,t.jsx)(n.code,{children:"uid"})," and ",(0,t.jsx)(n.code,{children:"mail"})," will be auhtorized to log in (and search collaborator)"]})]})]}),(0,t.jsxs)(r.Z,{value:"otoroshi",label:"Otoroshi",default:!0,children:[(0,t.jsx)(n.h2,{id:"before-starting-1",children:"Before starting"}),(0,t.jsx)(n.p,{children:"If you already have an up and running Daikoku instance, you can skip the following instructions and log in to your instance."}),(0,t.jsxs)(n.p,{children:["Let\u2019s start by ",(0,t.jsx)(n.a,{href:"/daikoku/docs/getstarted/getdaikoku/",children:"downloading the latest Daikoku"})," and ",(0,t.jsx)(n.a,{href:"/daikoku/docs/getstarted/firstrun/run",children:"run it"})]}),(0,t.jsx)(n.p,{children:"Once Daikoku is started you can log in to your brand new instance."}),(0,t.jsx)(n.h2,{id:"running-an-otoroshi-server",children:"Running an otoroshi server"}),(0,t.jsxs)(n.p,{children:["Otoroshi have his own manual to this part let's ",(0,t.jsx)(n.a,{href:"https://maif.github.io/otoroshi/manual/install/get-otoroshi.html",children:"get"})," as ",(0,t.jsx)(n.a,{href:"https://maif.github.io/otoroshi/manual/install/run-otoroshi.html",children:"run"})," your Otoroshi."]}),(0,t.jsx)(n.h2,{id:"create-an-authentication-module",children:"Create an authentication module"}),(0,t.jsxs)(n.p,{children:["Like the previous section, you can follow instructions in ",(0,t.jsx)(n.a,{href:"https://maif.github.io/otoroshi/manual/how-to-s/secure-app-with-auth0.html",children:"here"})," to create a new Auth plugin in Otoroshi.\nYou can find many possibility to secure Daikoku with Otoroshi in the documentation."]}),(0,t.jsx)(n.h2,{id:"expose-your-daikoku-by-otoroshi",children:"Expose your daikoku by Otoroshi"}),(0,t.jsx)(n.admonition,{type:"warning",children:(0,t.jsx)(n.p,{children:"before exposing your daikoku instance with otoroshi, you need to setup exposing mode to Otoroshi."})}),(0,t.jsx)(n.p,{children:"You ultimately have to add some plugins along the route to make this work :"}),(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Authentication, select the created Auth module in configuration to use it"}),"\n",(0,t.jsx)(n.li,{children:"Otoroshi info. token, you can configure the name of the header in which the authentication token will be passed and the secert to signe this token."}),"\n"]}),(0,t.jsx)(n.h2,{id:"create-an-authentication-configuration-1",children:"Create an Authentication configuration"}),(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Go ahead and navigate to your Daikoku home page"}),"\n",(0,t.jsx)(n.li,{children:"Click on the your avatar on bottom left of the screen"}),"\n",(0,t.jsxs)(n.li,{children:["Then ",(0,t.jsx)(n.code,{children:" settings"})]}),"\n",(0,t.jsxs)(n.li,{children:["Then ",(0,t.jsx)(n.code,{children:"Authentication"})," on left panel"]}),"\n",(0,t.jsxs)(n.li,{children:["You must be on ",(0,t.jsx)(n.code,{children:"/settings/settings/authentication"})]}),"\n",(0,t.jsxs)(n.li,{children:["This page show the settings for the current authentication mode (by default ",(0,t.jsx)(n.code,{children:"Local"}),")"]}),"\n",(0,t.jsxs)(n.li,{children:["Click on ",(0,t.jsx)(n.code,{children:"Otoroshi"})," at the top of the form"]}),"\n",(0,t.jsx)(n.li,{children:"Fill the header name previously setup"}),"\n",(0,t.jsx)(n.li,{children:"Fill the secret previously setup"}),"\n"]}),(0,t.jsx)(n.h2,{id:"testing-your-configuration-1",children:"Testing your configuration"}),(0,t.jsxs)(n.p,{children:["Disconnect from your instance\nThen click on the Login button (or navigate to ",(0,t.jsx)(n.code,{children:"/login"}),")\nYou can now login with a user setup with the auth module."]}),(0,t.jsxs)(n.admonition,{type:"warning",children:[(0,t.jsxs)(n.p,{children:["To be daikoku admin, a user need to have a ",(0,t.jsx)(n.code,{children:"daikokuAdmin"})," property in his definition setup to true.\nYou can cerate a Daikoku admin by adding a metadata in the user definition in the Auth. plugin :"]}),(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-json",children:"{daikokuAdmin: true}\n"})})]})]}),(0,t.jsxs)(r.Z,{value:"oauth2",label:"OAuth2",default:!0,children:[(0,t.jsx)(n.admonition,{type:"info",children:(0,t.jsxs)(n.p,{children:["For this tutorial, we'll use ",(0,t.jsx)(n.a,{href:"https://manage.auth0.com",children:"Auth0"})]})}),(0,t.jsx)(n.h2,{id:"before-starting-2",children:"Before starting"}),(0,t.jsx)(n.p,{children:"If you already have an up and running Daikoku instance, you can skip the following instructions and log in to your instance."}),(0,t.jsxs)(n.p,{children:["Let\u2019s start by ",(0,t.jsx)(n.a,{href:"/daikoku/docs/getstarted/getdaikoku/",children:"downloading the latest Daikoku"})," and ",(0,t.jsx)(n.a,{href:"/daikoku/docs/getstarted/firstrun/run",children:"run it"})]}),(0,t.jsx)(n.p,{children:"Once Daikoku is started you can log in to your brand new instance."}),(0,t.jsx)(n.h2,{id:"configure-an-auth0-client",children:"Configure an Auth0 client"}),(0,t.jsx)(n.p,{children:"The first step of this tutorial is to setup an Auth0 application with the information of the instance of our Otoroshi."}),(0,t.jsxs)(n.p,{children:["Navigate to ",(0,t.jsx)(n.a,{href:"https://manage.auth0.com",children:"https://manage.auth0.com"})," (create an account if it\u2019s not already done)."]}),(0,t.jsx)(n.p,{children:"Let\u2019s create an application when clicking on the Applications button on the sidebar. Then click on the Create application button on the top right."}),(0,t.jsxs)(n.p,{children:["Choose ",(0,t.jsx)(n.code,{children:"Regular Web Applications"})," as Application type\nThen set for example ",(0,t.jsx)(n.code,{children:"daikoku-client"})," as Name, and confirm the creation\nJump to the Settings tab\nScroll to the Application URLs section and add the following urls"]}),(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"http://localhost:9000/auth/oauth2/callback",children:"http://localhost:9000/auth/oauth2/callback"})," as Allowed Callback URLs"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"http://localhost:9000",children:"http://localhost:9000"})," as Allowed Logout URLs"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"http://localhost:9000",children:"http://localhost:9000"})," as Allowed Web Origins\nSave changes at the bottom of the page."]}),"\n"]}),(0,t.jsx)(n.p,{children:"Once done, we have a full setup, with a client ID and secret at the top of the page, which authorizes our Daikoku and redirects the user to the callback url when they log into Auth0."}),(0,t.jsxs)(n.admonition,{type:"warning",children:[(0,t.jsxs)(n.p,{children:["To be daikoku admin, a user need to have a ",(0,t.jsx)(n.code,{children:"daikokuAdmin"})," property in his definition setup to true. Daikoku get this information in the metadata of user."]}),(0,t.jsx)(n.p,{children:"In the Auth0 case, here are the steps to follow :"}),(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["add app metadata to a user ",(0,t.jsx)(n.code,{children:"daikokuAdmin"})," with ",(0,t.jsx)(n.code,{children:"true"})," as value"]}),"\n",(0,t.jsx)(n.li,{children:"create a new custom action in auth0 :"}),"\n"]}),(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-js",children:"exports.onExecutePostLogin = async (event, api) => {\n const { daikokuAdmin } = event.user.app_metadata;\n\n if (event.authorization) {\n api.idToken.setCustomClaim(`daikokuAdmin`, daikokuAdmin);\n }\n};\n"})}),(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"deploy it"}),"\n"]})]}),(0,t.jsx)(n.h2,{id:"create-authentication-configuration",children:"Create authentication configuration"}),(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Go ahead and navigate to your Daikoku home page"}),"\n",(0,t.jsx)(n.li,{children:"Click on the your avatar on bottom left of the screen"}),"\n",(0,t.jsxs)(n.li,{children:["Then ",(0,t.jsx)(n.code,{children:" settings"})]}),"\n",(0,t.jsxs)(n.li,{children:["Then ",(0,t.jsx)(n.code,{children:"Authentication"})," on left panel"]}),"\n",(0,t.jsxs)(n.li,{children:["You must be on ",(0,t.jsx)(n.code,{children:"/settings/settings/authentication"})]}),"\n",(0,t.jsxs)(n.li,{children:["This page show the settings for the current authentication mode (by default ",(0,t.jsx)(n.code,{children:"Local"}),")"]}),"\n",(0,t.jsxs)(n.li,{children:["Click on ",(0,t.jsx)(n.code,{children:"OAuth2"})," at the top of the form"]}),"\n",(0,t.jsxs)(n.li,{children:["Enable ",(0,t.jsx)(n.code,{children:"Read profile from JWT token"})]}),"\n",(0,t.jsxs)(n.li,{children:["Fill ",(0,t.jsx)(n.code,{children:"Token scope"})," with ",(0,t.jsx)(n.code,{children:"openid profile name email picture"})]}),"\n",(0,t.jsxs)(n.li,{children:["Fill ",(0,t.jsx)(n.code,{children:"Client Id"})," with the client ID provided by Auth0"]}),"\n",(0,t.jsxs)(n.li,{children:["Fill ",(0,t.jsx)(n.code,{children:"Client Secret"})," with the client secret provided by Auth0"]}),"\n",(0,t.jsxs)(n.li,{children:["Fill ",(0,t.jsx)(n.code,{children:"Authorize URL"})," with ",(0,t.jsx)(n.code,{children:"https://.eu.auth0.com/authorize"})]}),"\n",(0,t.jsxs)(n.li,{children:["Fill ",(0,t.jsx)(n.code,{children:"Token URL"})," with ",(0,t.jsx)(n.code,{children:"https://.eu.auth0.com/oauth/token"})]}),"\n",(0,t.jsxs)(n.li,{children:["Fill ",(0,t.jsx)(n.code,{children:"Userinfo URL"})," with ",(0,t.jsx)(n.code,{children:"https://.eu.auth0.com/userinfo"})]}),"\n",(0,t.jsxs)(n.li,{children:["Fill ",(0,t.jsx)(n.code,{children:"Login URL"})," with ",(0,t.jsx)(n.code,{children:"https://.eu.auth0.com/authorize"})]}),"\n",(0,t.jsxs)(n.li,{children:["Fill ",(0,t.jsx)(n.code,{children:"Logout URL"})," with ",(0,t.jsx)(n.code,{children:"https://.eu.auth0.com/oidc/logout?redirectTo=${redirect}&client_id=${clientId}"})]}),"\n",(0,t.jsxs)(n.li,{children:["Fill ",(0,t.jsx)(n.code,{children:"Callback URL"})," with ",(0,t.jsx)(n.code,{children:"http://localhost:9000/auth/oauth2/callback"})]}),"\n",(0,t.jsxs)(n.li,{children:["Fill ",(0,t.jsx)(n.code,{children:"Access token field name"})," with ",(0,t.jsx)(n.code,{children:"access_token"})]}),"\n",(0,t.jsxs)(n.li,{children:["Fill ",(0,t.jsx)(n.code,{children:"Name field name"})," with ",(0,t.jsx)(n.code,{children:"name"})]}),"\n",(0,t.jsxs)(n.li,{children:["Fill ",(0,t.jsx)(n.code,{children:"Email field name"})," with ",(0,t.jsx)(n.code,{children:"email"})]}),"\n",(0,t.jsxs)(n.li,{children:["Fill ",(0,t.jsx)(n.code,{children:"Picture field name"})," with ",(0,t.jsx)(n.code,{children:"picture"})]}),"\n"]}),(0,t.jsx)(n.p,{children:"Two optional fields can be filled :"}),(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"Email of Daikoku Admins"})," is a white list of Daikoku admins if you don't want use user metadata to setup admins"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"jwt verifier"})," can be use to verify the JWT token received by Daikoku. According to the selected algorithm, the validation form will change."]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"mac + SHA"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"SHA Size: Word size for the SHA-2 hash function used"}),"\n",(0,t.jsx)(n.li,{children:"Hmac secret: used to verify the token"}),"\n",(0,t.jsx)(n.li,{children:"Base64 encoded secret: if enabled, the extracted token will be base64 decoded before it is verifier"}),"\n"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"RSASSA-PKCS1 + SHA"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"SHA Size: Word size for the SHA-2 hash function used"}),"\n",(0,t.jsx)(n.li,{children:"Public key: the RSA public key"}),"\n",(0,t.jsx)(n.li,{children:"Private key: the RSA private key that can be empty if not used for JWT token signing"}),"\n"]}),"\n"]}),"\n",(0,t.jsxs)(n.li,{children:["\n",(0,t.jsx)(n.p,{children:"JWK Set (only for verification)"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"URL: the JWK set URL where the public keys are exposed"}),"\n",(0,t.jsx)(n.li,{children:"HTTP call timeout: timeout for fetching the keyset"}),"\n",(0,t.jsx)(n.li,{children:"TTL: cache TTL for the keyset"}),"\n",(0,t.jsx)(n.li,{children:"HTTP Headers: the HTTP headers passed"}),"\n",(0,t.jsx)(n.li,{children:"Key type: type of the key searched in the jwks"}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"testing-your-configuration-2",children:"Testing your configuration"}),"\n",(0,t.jsxs)(n.p,{children:["Disconnect from your instance\nThen click on the Login button (or navigate to ",(0,t.jsx)(n.code,{children:"/login"}),")\nYou can now login with a user setup with the auth module."]}),"\n"]}),"\n"]})]})]})]})}function p(e={}){const{wrapper:n}={...(0,o.a)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(u,{...e})}):u(e)}},5162:(e,n,i)=>{i.d(n,{Z:()=>r});i(7294);var t=i(6905);const o={tabItem:"tabItem_Ymn6"};var a=i(5893);function r(e){let{children:n,hidden:i,className:r}=e;return(0,a.jsx)("div",{role:"tabpanel",className:(0,t.Z)(o.tabItem,r),hidden:i,children:n})}},4866:(e,n,i)=>{i.d(n,{Z:()=>v});var t=i(7294),o=i(6905),a=i(2466),r=i(6550),s=i(469),l=i(1980),c=i(7392),d=i(12);function h(e){return t.Children.toArray(e).filter((e=>"\n"!==e)).map((e=>{if(!e||(0,t.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}function u(e){const{values:n,children:i}=e;return(0,t.useMemo)((()=>{const e=n??function(e){return h(e).map((e=>{let{props:{value:n,label:i,attributes:t,default:o}}=e;return{value:n,label:i,attributes:t,default:o}}))}(i);return function(e){const n=(0,c.l)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,i])}function p(e){let{value:n,tabValues:i}=e;return i.some((e=>e.value===n))}function x(e){let{queryString:n=!1,groupId:i}=e;const o=(0,r.k6)(),a=function(e){let{queryString:n=!1,groupId:i}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!i)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return i??null}({queryString:n,groupId:i});return[(0,l._X)(a),(0,t.useCallback)((e=>{if(!a)return;const n=new URLSearchParams(o.location.search);n.set(a,e),o.replace({...o.location,search:n.toString()})}),[a,o])]}function m(e){const{defaultValue:n,queryString:i=!1,groupId:o}=e,a=u(e),[r,l]=(0,t.useState)((()=>function(e){let{defaultValue:n,tabValues:i}=e;if(0===i.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!p({value:n,tabValues:i}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${i.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=i.find((e=>e.default))??i[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:a}))),[c,h]=x({queryString:i,groupId:o}),[m,j]=function(e){let{groupId:n}=e;const i=function(e){return e?`docusaurus.tab.${e}`:null}(n),[o,a]=(0,d.Nk)(i);return[o,(0,t.useCallback)((e=>{i&&a.set(e)}),[i,a])]}({groupId:o}),f=(()=>{const e=c??m;return p({value:e,tabValues:a})?e:null})();(0,s.Z)((()=>{f&&l(f)}),[f]);return{selectedValue:r,selectValue:(0,t.useCallback)((e=>{if(!p({value:e,tabValues:a}))throw new Error(`Can't select invalid tab value=${e}`);l(e),h(e),j(e)}),[h,j,a]),tabValues:a}}var j=i(2389);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};var g=i(5893);function k(e){let{className:n,block:i,selectedValue:t,selectValue:r,tabValues:s}=e;const l=[],{blockElementScrollPositionUntilNextRender:c}=(0,a.o5)(),d=e=>{const n=e.currentTarget,i=l.indexOf(n),o=s[i].value;o!==t&&(c(n),r(o))},h=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const i=l.indexOf(e.currentTarget)+1;n=l[i]??l[0];break}case"ArrowLeft":{const i=l.indexOf(e.currentTarget)-1;n=l[i]??l[l.length-1];break}}n?.focus()};return(0,g.jsx)("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.Z)("tabs",{"tabs--block":i},n),children:s.map((e=>{let{value:n,label:i,attributes:a}=e;return(0,g.jsx)("li",{role:"tab",tabIndex:t===n?0:-1,"aria-selected":t===n,ref:e=>l.push(e),onKeyDown:h,onClick:d,...a,className:(0,o.Z)("tabs__item",f.tabItem,a?.className,{"tabs__item--active":t===n}),children:i??n},n)}))})}function b(e){let{lazy:n,children:i,selectedValue:o}=e;const a=(Array.isArray(i)?i:[i]).filter(Boolean);if(n){const e=a.find((e=>e.props.value===o));return e?(0,t.cloneElement)(e,{className:"margin-top--md"}):null}return(0,g.jsx)("div",{className:"margin-top--md",children:a.map(((e,n)=>(0,t.cloneElement)(e,{key:n,hidden:e.props.value!==o})))})}function y(e){const n=m(e);return(0,g.jsxs)("div",{className:(0,o.Z)("tabs-container",f.tabList),children:[(0,g.jsx)(k,{...e,...n}),(0,g.jsx)(b,{...e,...n})]})}function v(e){const n=(0,j.Z)();return(0,g.jsx)(y,{...e,children:h(e.children)},String(n))}},1151:(e,n,i)=>{i.d(n,{Z:()=>s,a:()=>r});var t=i(7294);const o={},a=t.createContext(o);function r(e){const n=t.useContext(a);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function s(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:r(e.components),t.createElement(a.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/b277a811.9f584504.js b/docs/assets/js/b277a811.9f584504.js new file mode 100644 index 000000000..a2330c255 --- /dev/null +++ b/docs/assets/js/b277a811.9f584504.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdaikoku_documentation=self.webpackChunkdaikoku_documentation||[]).push([[2774],{5852:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>d,contentTitle:()=>a,default:()=>u,frontMatter:()=>s,metadata:()=>r,toc:()=>l});var o=n(5893),i=n(1151);const s={},a="Import and export",r={id:"usages/adminusage/importexport",title:"Import and export",description:"With Daikoku, you can easily save the current state of the instance and restore it later. Go to Settings (avatar icon) / Organizations settings and then import/export.",source:"@site/docs/02-usages/07-adminusage/4-importexport.md",sourceDirName:"02-usages/07-adminusage",slug:"/usages/adminusage/importexport",permalink:"/daikoku/docs/usages/adminusage/importexport",draft:!1,unlisted:!1,tags:[],version:"current",sidebarPosition:4,frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Managing sessions",permalink:"/daikoku/docs/usages/adminusage/sessions"},next:{title:"Using Daikoku as Tenant admin",permalink:"/daikoku/docs/usages/tenantusage/"}},d={},l=[{value:"Full export",id:"full-export",level:2},{value:"Full import",id:"full-import",level:2},{value:"Database migration",id:"database-migration",level:2}];function c(e){const t={admonition:"admonition",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",li:"li",ol:"ol",p:"p",strong:"strong",...(0,i.a)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(t.h1,{id:"import-and-export",children:"Import and export"}),"\n",(0,o.jsxs)(t.p,{children:["With Daikoku, you can easily save the current state of the instance and restore it later. Go to ",(0,o.jsx)(t.code,{children:"Settings (avatar icon) / Organizations settings"})," and then ",(0,o.jsx)(t.code,{children:"import/export"}),"."]}),"\n",(0,o.jsx)(t.h2,{id:"full-export",children:"Full export"}),"\n",(0,o.jsxs)(t.p,{children:["Click on the ",(0,o.jsx)(t.code,{children:"download state"})," button."]}),"\n",(0,o.jsx)(t.p,{children:"Your browser will start downloading a ndjson file containing the internal state of your Daikoku instance."}),"\n",(0,o.jsxs)(t.blockquote,{children:["\n",(0,o.jsxs)(t.p,{children:["Audit trail store could be massive, you can exclude this collection from export by toggle the button below the ",(0,o.jsx)(t.code,{children:"download state"})," button."]}),"\n"]}),"\n",(0,o.jsx)(t.h2,{id:"full-import",children:"Full import"}),"\n",(0,o.jsxs)(t.p,{children:["If you want to restore an export, Go to ",(0,o.jsx)(t.code,{children:"settings (avatar icon) / Organizations settings"})," and then ",(0,o.jsx)(t.code,{children:"import/export"}),". Click on the ",(0,o.jsx)(t.code,{children:"import state"})," button and choose your ndjson export file."]}),"\n",(0,o.jsx)(t.h2,{id:"database-migration",children:"Database migration"}),"\n",(0,o.jsx)(t.p,{children:"Since v1.1.1 Daikoku supports Postgresql databases. If you want to migrate you MongoDB to Postgresql, it's dead simple like the following instructions."}),"\n",(0,o.jsx)(t.admonition,{type:"danger",children:(0,o.jsxs)(t.p,{children:["Since ",(0,o.jsx)(t.strong,{children:"v17.4.0"}),", Daikoku does not support MongoDB anymore. To run database migration, you need to be in ",(0,o.jsx)(t.strong,{children:"16.3.6 max"}),"."]})}),"\n",(0,o.jsxs)(t.ol,{children:["\n",(0,o.jsx)(t.li,{children:"Add your Postgresql access in Daikoku configuration"}),"\n",(0,o.jsx)(t.li,{children:"Keep mongo as daikoku.storage configuration"}),"\n",(0,o.jsx)(t.li,{children:"Run the migration"}),"\n",(0,o.jsx)(t.li,{children:"Update your daikoku.storage to postgres"}),"\n",(0,o.jsx)(t.li,{children:"Restart your Daikoku"}),"\n",(0,o.jsx)(t.li,{children:"Remember to disable the maintenance mode"}),"\n"]})]})}function u(e={}){const{wrapper:t}={...(0,i.a)(),...e.components};return t?(0,o.jsx)(t,{...e,children:(0,o.jsx)(c,{...e})}):c(e)}},1151:(e,t,n)=>{n.d(t,{Z:()=>r,a:()=>a});var o=n(7294);const i={},s=o.createContext(i);function a(e){const t=o.useContext(s);return o.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function r(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:a(e.components),o.createElement(s.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/docs/assets/js/main.f1622a89.js b/docs/assets/js/main.f1622a89.js new file mode 100644 index 000000000..5eee0e935 --- /dev/null +++ b/docs/assets/js/main.f1622a89.js @@ -0,0 +1,2 @@ +/*! For license information please see main.f1622a89.js.LICENSE.txt */ +(self.webpackChunkdaikoku_documentation=self.webpackChunkdaikoku_documentation||[]).push([[179],{723:(e,t,n)=>{"use strict";n.d(t,{Z:()=>d});n(7294);var r=n(8356),a=n.n(r),o=n(6887);const i={"00b536ae":[()=>n.e(200).then(n.t.bind(n,1273,19)),"~docs/default/tag-daikoku-docs-tags-licence-876.json",1273],"03885ea5":[()=>n.e(1769).then(n.bind(n,8678)),"@site/docs/02-usages/10-consumerusage/index.md",8678],"07c0c607":[()=>n.e(3998).then(n.bind(n,7195)),"@site/docs/02-usages/07-adminusage/2-users.md",7195],"092b0f67":[()=>Promise.all([n.e(532),n.e(2475)]).then(n.bind(n,7466)),"@site/docs/01-getstarted/05-firstrun/run.mdx",7466],"0e3215c3":[()=>n.e(8534).then(n.bind(n,7781)),"@site/docs/03-guides/02-archi.md",7781],17896441:[()=>Promise.all([n.e(532),n.e(7918)]).then(n.bind(n,5317)),"@theme/DocItem",5317],"18f231a6":[()=>n.e(9864).then(n.bind(n,2440)),"@site/docs/02-usages/08-tenantusage/5.5-display.md",2440],"1a4e3797":[()=>Promise.all([n.e(532),n.e(7920)]).then(n.bind(n,1473)),"@theme/SearchPage",1473],"20fde71e":[()=>n.e(4652).then(n.t.bind(n,2776,19)),"/home/runner/work/daikoku/daikoku/manual/.docusaurus/@easyops-cn/docusaurus-search-local/default/plugin-route-context-module-100.json",2776],"2820f203":[()=>Promise.all([n.e(532),n.e(7515)]).then(n.bind(n,8800)),"@site/docs/02-usages/index.mdx",8800],"28af0bc8":[()=>Promise.all([n.e(532),n.e(5805)]).then(n.bind(n,6760)),"@site/docs/01-getstarted/04-getdaikoku/index.mdx",6760],"2d238738":[()=>n.e(93).then(n.bind(n,8929)),"@site/docs/01-getstarted/05-firstrun/datastore.md",8929],"331c362e":[()=>n.e(6570).then(n.bind(n,5415)),"@site/docs/01-getstarted/00-about.md",5415],"3720c009":[()=>Promise.all([n.e(532),n.e(3751)]).then(n.bind(n,9861)),"@theme/DocTagsListPage",9861],37484594:[()=>n.e(7162).then(n.bind(n,300)),"@site/docs/01-getstarted/03-quickstart.md",300],"37a8690e":[()=>n.e(339).then(n.bind(n,2150)),"@site/docs/01-getstarted/06-setup/index.md",2150],"3a2220e2":[()=>n.e(2752).then(n.t.bind(n,5745,19)),"/home/runner/work/daikoku/daikoku/manual/.docusaurus/docusaurus-plugin-content-pages/default/plugin-route-context-module-100.json",5745],"3bccb1e5":[()=>n.e(8862).then(n.bind(n,7846)),"@site/docs/01-getstarted/06-setup/admin.md",7846],"3f62fecf":[()=>n.e(5014).then(n.bind(n,6839)),"@site/docs/02-usages/10-consumerusage/2-apikeys.md",6839],"3f725ac7":[()=>n.e(9369).then(n.t.bind(n,5659,19)),"~docs/default/tag-daikoku-docs-tags-installation-eab.json",5659],"47f5f50e":[()=>n.e(7487).then(n.bind(n,2543)),"@site/docs/02-usages/10-consumerusage/5-fastmode.md",2543],"484e5d86":[()=>n.e(1388).then(n.bind(n,2565)),"@site/docs/01-getstarted/05-firstrun/env.md",2565],"4fd64565":[()=>n.e(6396).then(n.bind(n,174)),"@site/docs/02-usages/08-tenantusage/6-cms.md",174],55940994:[()=>Promise.all([n.e(532),n.e(4310)]).then(n.bind(n,9274)),"@site/docs/01-getstarted/index.mdx",9274],"55960ee5":[()=>n.e(4121).then(n.t.bind(n,8070,19)),"~docs/default/tags-list-current-prop-15a.json",8070],"5e95c892":[()=>n.e(9661).then(n.bind(n,1892)),"@theme/DocsRoot",1892],"5e9f5e1a":[()=>Promise.resolve().then(n.bind(n,6809)),"@generated/docusaurus.config",6809],"637d173b":[()=>n.e(5299).then(n.bind(n,7971)),"@site/docs/01-getstarted/06-setup/reporting.md",7971],"64737f0d":[()=>n.e(2237).then(n.bind(n,3214)),"@site/docs/02-usages/09-producerusage/1-apis.md",3214],"67a8f2c5":[()=>n.e(3857).then(n.bind(n,7002)),"@site/docs/01-getstarted/05-firstrun/index.md",7002],"6a9470e5":[()=>n.e(3733).then(n.bind(n,5532)),"@site/docs/02-usages/08-tenantusage/1.5-initialize.md",5532],"6b339c0f":[()=>n.e(2075).then(n.bind(n,9143)),"@site/docs/02-usages/07-adminusage/index.md",9143],"6ba831b6":[()=>n.e(7988).then(n.t.bind(n,1837,19)),"/home/runner/work/daikoku/daikoku/manual/.docusaurus/docusaurus-plugin-redoc/plugin-redoc-0/redocApiSpecV1.2-plugin-redoc-0.json",1837],"73b1ab36":[()=>n.e(6471).then(n.bind(n,4509)),"@site/docs/02-usages/10-consumerusage/1-subscribe.md",4509],"7482af60":[()=>n.e(3194).then(n.bind(n,2865)),"@site/docs/02-usages/09-producerusage/4-income.md",2865],"7cdc31f5":[()=>n.e(5961).then(n.bind(n,6401)),"@site/docs/03-guides/12-apis.md",6401],"7f6c714f":[()=>n.e(4225).then(n.t.bind(n,9162,19)),"/home/runner/work/daikoku/daikoku/manual/.docusaurus/docusaurus-plugin-redoc/plugin-redoc-0/plugin-route-context-module-100.json",9162],"8129fd32":[()=>n.e(2293).then(n.bind(n,9610)),"@site/docs/01-getstarted/05-firstrun/configfile.md",9610],"8ca3e574":[()=>n.e(9193).then(n.bind(n,4255)),"@site/docs/01-getstarted/05-firstrun/initialstate.md",4255],"91086ed6":[()=>n.e(9902).then(n.bind(n,1462)),"@site/docs/02-usages/07-adminusage/1-tenants.md",1462],"9117216e":[()=>n.e(6125).then(n.bind(n,6685)),"@site/docs/02-usages/07-adminusage/3-sessions.md",6685],"935f2afb":[()=>n.e(53).then(n.t.bind(n,1109,19)),"~docs/default/version-current-metadata-prop-751.json",1109],"9755f291":[()=>Promise.all([n.e(532),n.e(9235)]).then(n.bind(n,5277)),"@site/docs/03-guides/14-authentication.mdx",5277],a7bd4aaa:[()=>n.e(8518).then(n.bind(n,8564)),"@theme/DocVersionRoot",8564],a8fdd87e:[()=>n.e(209).then(n.t.bind(n,3769,19)),"/home/runner/work/daikoku/daikoku/manual/.docusaurus/docusaurus-plugin-content-docs/default/plugin-route-context-module-100.json",3769],a94703ab:[()=>Promise.all([n.e(532),n.e(4368)]).then(n.bind(n,2674)),"@theme/DocRoot",2674],aa74e104:[()=>n.e(7875).then(n.bind(n,7977)),"@site/docs/02-usages/09-producerusage/index.md",7977],b277a811:[()=>n.e(2774).then(n.bind(n,5852)),"@site/docs/02-usages/07-adminusage/4-importexport.md",5852],bb669cdd:[()=>n.e(1241).then(n.bind(n,5534)),"@site/docs/02-usages/10-consumerusage/4-billing.md",5534],c4f5d8e4:[()=>Promise.all([n.e(532),n.e(4195)]).then(n.bind(n,4804)),"@site/src/pages/index.js",4804],c5a98549:[()=>Promise.all([n.e(532),n.e(4976)]).then(n.bind(n,3670)),"@site/docs/03-guides/index.mdx",3670],c5f7dd62:[()=>n.e(5514).then(n.bind(n,5549)),"@site/docs/02-usages/08-tenantusage/1-otoroshi.md",5549],c658d08c:[()=>n.e(2478).then(n.bind(n,778)),"@site/docs/02-usages/08-tenantusage/4-messages.md",778],c9d9ec16:[()=>n.e(9129).then(n.bind(n,1253)),"@site/docs/02-usages/09-producerusage/3-assets.md",1253],d5060a81:[()=>n.e(7581).then(n.bind(n,3725)),"@site/docs/03-guides/13-deploy.md",3725],d7242cac:[()=>n.e(821).then(n.bind(n,5194)),"@site/docs/04-cli/index.mdx",5194],d8973fa8:[()=>n.e(915).then(n.bind(n,3827)),"@site/docs/02-usages/09-producerusage/2-subscriptions.md",3827],d9990e8a:[()=>n.e(191).then(n.bind(n,3267)),"@site/docs/02-usages/09-producerusage/2-members.md",3267],df203c0f:[()=>Promise.all([n.e(532),n.e(9924)]).then(n.bind(n,491)),"@theme/DocTagDocListPage",491],e06b81f4:[()=>n.e(616).then(n.bind(n,6828)),"@site/docs/02-usages/08-tenantusage/index.md",6828],e83df34e:[()=>n.e(1958).then(n.bind(n,4729)),"@site/docs/03-guides/11-integrations.md",4729],e953d3c5:[()=>n.e(2104).then(n.bind(n,2859)),"@site/docs/02-usages/08-tenantusage/2-teams.md",2859],ee06beb8:[()=>n.e(6852).then(n.bind(n,7783)),"@site/docs/02-usages/10-consumerusage/3-aggregation-of-apikeys.md",7783],ef4bb451:[()=>n.e(9389).then(n.bind(n,2978)),"@site/docs/02-usages/08-tenantusage/5-translations.md",2978],f0ad3fbb:[()=>Promise.all([n.e(532),n.e(5679),n.e(8612)]).then(n.bind(n,5679)),"@theme/ApiDoc",5679],fa8a5ad9:[()=>n.e(5207).then(n.bind(n,3066)),"@site/docs/02-usages/08-tenantusage/3-assets.md",3066],fc0be43e:[()=>n.e(6677).then(n.t.bind(n,1294,19)),"/home/runner/work/daikoku/daikoku/manual/.docusaurus/docusaurus-plugin-redoc/plugin-redoc-0/redocApiLayoutV1-plugin-redoc-0.json",1294]};var s=n(5893);function l(e){let{error:t,retry:n,pastDelay:r}=e;return t?(0,s.jsxs)("div",{style:{textAlign:"center",color:"#fff",backgroundColor:"#fa383e",borderColor:"#fa383e",borderStyle:"solid",borderRadius:"0.25rem",borderWidth:"1px",boxSizing:"border-box",display:"block",padding:"1rem",flex:"0 0 50%",marginLeft:"25%",marginRight:"25%",marginTop:"5rem",maxWidth:"50%",width:"100%"},children:[(0,s.jsx)("p",{children:String(t)}),(0,s.jsx)("div",{children:(0,s.jsx)("button",{type:"button",onClick:n,children:"Retry"})})]}):r?(0,s.jsx)("div",{style:{display:"flex",justifyContent:"center",alignItems:"center",height:"100vh"},children:(0,s.jsx)("svg",{id:"loader",style:{width:128,height:110,position:"absolute",top:"calc(100vh - 64%)"},viewBox:"0 0 45 45",xmlns:"http://www.w3.org/2000/svg",stroke:"#61dafb",children:(0,s.jsxs)("g",{fill:"none",fillRule:"evenodd",transform:"translate(1 1)",strokeWidth:"2",children:[(0,s.jsxs)("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0",children:[(0,s.jsx)("animate",{attributeName:"r",begin:"1.5s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),(0,s.jsx)("animate",{attributeName:"stroke-opacity",begin:"1.5s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),(0,s.jsx)("animate",{attributeName:"stroke-width",begin:"1.5s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})]}),(0,s.jsxs)("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0",children:[(0,s.jsx)("animate",{attributeName:"r",begin:"3s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),(0,s.jsx)("animate",{attributeName:"stroke-opacity",begin:"3s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),(0,s.jsx)("animate",{attributeName:"stroke-width",begin:"3s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})]}),(0,s.jsx)("circle",{cx:"22",cy:"22",r:"8",children:(0,s.jsx)("animate",{attributeName:"r",begin:"0s",dur:"1.5s",values:"6;1;2;3;4;5;6",calcMode:"linear",repeatCount:"indefinite"})})]})})}):null}var c=n(9670),u=n(226);function p(e,t){if("*"===e)return a()({loading:l,loader:()=>n.e(1772).then(n.bind(n,1772)),modules:["@theme/NotFound"],webpack:()=>[1772],render(e,t){const n=e.default;return(0,s.jsx)(u.z,{value:{plugin:{name:"native",id:"default"}},children:(0,s.jsx)(n,{...t})})}});const r=o[`${e}-${t}`],p={},d=[],f=[],m=(0,c.Z)(r);return Object.entries(m).forEach((e=>{let[t,n]=e;const r=i[n];r&&(p[t]=r[0],d.push(r[1]),f.push(r[2]))})),a().Map({loading:l,loader:p,modules:d,webpack:()=>f,render(t,n){const a=JSON.parse(JSON.stringify(r));Object.entries(t).forEach((t=>{let[n,r]=t;const o=r.default;if(!o)throw new Error(`The page component at ${e} doesn't have a default export. This makes it impossible to render anything. Consider default-exporting a React component.`);"object"!=typeof o&&"function"!=typeof o||Object.keys(r).filter((e=>"default"!==e)).forEach((e=>{o[e]=r[e]}));let i=a;const s=n.split(".");s.slice(0,-1).forEach((e=>{i=i[e]})),i[s[s.length-1]]=o}));const o=a.__comp;delete a.__comp;const i=a.__context;return delete a.__context,(0,s.jsx)(u.z,{value:i,children:(0,s.jsx)(o,{...a,...n})})}})}const d=[{path:"/daikoku/",component:p("/daikoku/","221"),exact:!0},{path:"/daikoku/openapi",component:p("/daikoku/openapi","b9b"),exact:!0},{path:"/daikoku/search",component:p("/daikoku/search","e64"),exact:!0},{path:"/daikoku/docs",component:p("/daikoku/docs","d57"),routes:[{path:"/daikoku/docs",component:p("/daikoku/docs","b01"),routes:[{path:"/daikoku/docs/tags",component:p("/daikoku/docs/tags","477"),exact:!0},{path:"/daikoku/docs/tags/installation",component:p("/daikoku/docs/tags/installation","80f"),exact:!0},{path:"/daikoku/docs/tags/licence",component:p("/daikoku/docs/tags/licence","120"),exact:!0},{path:"/daikoku/docs",component:p("/daikoku/docs","a1c"),routes:[{path:"/daikoku/docs/cli/",component:p("/daikoku/docs/cli/","928"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/getstarted/",component:p("/daikoku/docs/getstarted/","96d"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/getstarted/about",component:p("/daikoku/docs/getstarted/about","ba6"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/getstarted/firstrun/",component:p("/daikoku/docs/getstarted/firstrun/","e8b"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/getstarted/firstrun/configfile",component:p("/daikoku/docs/getstarted/firstrun/configfile","661"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/getstarted/firstrun/datastore",component:p("/daikoku/docs/getstarted/firstrun/datastore","dfa"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/getstarted/firstrun/env",component:p("/daikoku/docs/getstarted/firstrun/env","c87"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/getstarted/firstrun/initialstate",component:p("/daikoku/docs/getstarted/firstrun/initialstate","cf8"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/getstarted/firstrun/run",component:p("/daikoku/docs/getstarted/firstrun/run","622"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/getstarted/getdaikoku/",component:p("/daikoku/docs/getstarted/getdaikoku/","c1c"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/getstarted/quickstart",component:p("/daikoku/docs/getstarted/quickstart","2e4"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/getstarted/setup/",component:p("/daikoku/docs/getstarted/setup/","9ac"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/getstarted/setup/admin",component:p("/daikoku/docs/getstarted/setup/admin","c31"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/getstarted/setup/reporting",component:p("/daikoku/docs/getstarted/setup/reporting","c46"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/guides/",component:p("/daikoku/docs/guides/","8ee"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/guides/apis",component:p("/daikoku/docs/guides/apis","9bc"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/guides/archi",component:p("/daikoku/docs/guides/archi","137"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/guides/authentication",component:p("/daikoku/docs/guides/authentication","baa"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/guides/deploy",component:p("/daikoku/docs/guides/deploy","6d0"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/guides/integrations",component:p("/daikoku/docs/guides/integrations","9e1"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/",component:p("/daikoku/docs/usages/","a78"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/adminusage/",component:p("/daikoku/docs/usages/adminusage/","b6c"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/adminusage/importexport",component:p("/daikoku/docs/usages/adminusage/importexport","4c3"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/adminusage/sessions",component:p("/daikoku/docs/usages/adminusage/sessions","5a0"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/adminusage/tenants",component:p("/daikoku/docs/usages/adminusage/tenants","c7b"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/adminusage/users",component:p("/daikoku/docs/usages/adminusage/users","c7f"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/consumerusage/",component:p("/daikoku/docs/usages/consumerusage/","b59"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/consumerusage/aggregation-of-apikeys",component:p("/daikoku/docs/usages/consumerusage/aggregation-of-apikeys","8dc"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/consumerusage/apikeys",component:p("/daikoku/docs/usages/consumerusage/apikeys","2bc"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/consumerusage/billing",component:p("/daikoku/docs/usages/consumerusage/billing","9fd"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/consumerusage/fastmode",component:p("/daikoku/docs/usages/consumerusage/fastmode","f62"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/consumerusage/subscribe",component:p("/daikoku/docs/usages/consumerusage/subscribe","f7a"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/producerusage/",component:p("/daikoku/docs/usages/producerusage/","bbf"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/producerusage/apis",component:p("/daikoku/docs/usages/producerusage/apis","f6b"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/producerusage/assets",component:p("/daikoku/docs/usages/producerusage/assets","8a6"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/producerusage/income",component:p("/daikoku/docs/usages/producerusage/income","527"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/producerusage/members",component:p("/daikoku/docs/usages/producerusage/members","c2e"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/producerusage/subscriptions",component:p("/daikoku/docs/usages/producerusage/subscriptions","a40"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/tenantusage/",component:p("/daikoku/docs/usages/tenantusage/","443"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/tenantusage/1.5-initialize",component:p("/daikoku/docs/usages/tenantusage/1.5-initialize","337"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/tenantusage/5.5-display",component:p("/daikoku/docs/usages/tenantusage/5.5-display","ba3"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/tenantusage/assets",component:p("/daikoku/docs/usages/tenantusage/assets","772"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/tenantusage/cms",component:p("/daikoku/docs/usages/tenantusage/cms","6ab"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/tenantusage/messages",component:p("/daikoku/docs/usages/tenantusage/messages","36c"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/tenantusage/otoroshi",component:p("/daikoku/docs/usages/tenantusage/otoroshi","17e"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/tenantusage/teams",component:p("/daikoku/docs/usages/tenantusage/teams","22f"),exact:!0,sidebar:"tutorialSidebar"},{path:"/daikoku/docs/usages/tenantusage/translations",component:p("/daikoku/docs/usages/tenantusage/translations","5a6"),exact:!0,sidebar:"tutorialSidebar"}]}]}]},{path:"*",component:p("*")}]},8934:(e,t,n)=>{"use strict";n.d(t,{_:()=>o,t:()=>i});var r=n(7294),a=n(5893);const o=r.createContext(!1);function i(e){let{children:t}=e;const[n,i]=(0,r.useState)(!1);return(0,r.useEffect)((()=>{i(!0)}),[]),(0,a.jsx)(o.Provider,{value:n,children:t})}},7221:(e,t,n)=>{"use strict";var r=n(7294),a=n(745),o=n(3727),i=n(405),s=n(412);const l=[n(2497),n(3310),n(8320),n(2295),n(1304),n(4750)];var c=n(723),u=n(6550),p=n(8790),d=n(5893);function f(e){let{children:t}=e;return(0,d.jsx)(d.Fragment,{children:t})}var m=n(5742),h=n(2263),g=n(4996),y=n(6668),b=n(1944),v=n(4711),k=n(9727),w=n(3320),S=n(8780),x=n(197);function E(){const{i18n:{currentLocale:e,defaultLocale:t,localeConfigs:n}}=(0,h.Z)(),r=(0,v.l)(),a=n[e].htmlLang,o=e=>e.replace("-","_");return(0,d.jsxs)(m.Z,{children:[Object.entries(n).map((e=>{let[t,{htmlLang:n}]=e;return(0,d.jsx)("link",{rel:"alternate",href:r.createUrl({locale:t,fullyQualified:!0}),hrefLang:n},t)})),(0,d.jsx)("link",{rel:"alternate",href:r.createUrl({locale:t,fullyQualified:!0}),hrefLang:"x-default"}),(0,d.jsx)("meta",{property:"og:locale",content:o(a)}),Object.values(n).filter((e=>a!==e.htmlLang)).map((e=>(0,d.jsx)("meta",{property:"og:locale:alternate",content:o(e.htmlLang)},`meta-og-${e.htmlLang}`)))]})}function _(e){let{permalink:t}=e;const{siteConfig:{url:n}}=(0,h.Z)(),r=function(){const{siteConfig:{url:e,baseUrl:t,trailingSlash:n}}=(0,h.Z)(),{pathname:r}=(0,u.TH)();return e+(0,S.applyTrailingSlash)((0,g.Z)(r),{trailingSlash:n,baseUrl:t})}(),a=t?`${n}${t}`:r;return(0,d.jsxs)(m.Z,{children:[(0,d.jsx)("meta",{property:"og:url",content:a}),(0,d.jsx)("link",{rel:"canonical",href:a})]})}function C(){const{i18n:{currentLocale:e}}=(0,h.Z)(),{metadata:t,image:n}=(0,y.L)();return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsxs)(m.Z,{children:[(0,d.jsx)("meta",{name:"twitter:card",content:"summary_large_image"}),(0,d.jsx)("body",{className:k.h})]}),n&&(0,d.jsx)(b.d,{image:n}),(0,d.jsx)(_,{}),(0,d.jsx)(E,{}),(0,d.jsx)(x.Z,{tag:w.HX,locale:e}),(0,d.jsx)(m.Z,{children:t.map(((e,t)=>(0,d.jsx)("meta",{...e},t)))})]})}const j=new Map;function A(e){if(j.has(e.pathname))return{...e,pathname:j.get(e.pathname)};if((0,p.f)(c.Z,e.pathname).some((e=>{let{route:t}=e;return!0===t.exact})))return j.set(e.pathname,e.pathname),e;const t=e.pathname.trim().replace(/(?:\/index)?\.html$/,"")||"/";return j.set(e.pathname,t),{...e,pathname:t}}var T=n(8934),P=n(8940),I=n(469);function N(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r{const r=t.default?.[e]??t[e];return r?.(...n)}));return()=>a.forEach((e=>e?.()))}const O=function(e){let{children:t,location:n,previousLocation:r}=e;return(0,I.Z)((()=>{r!==n&&(!function(e){let{location:t,previousLocation:n}=e;if(!n)return;const r=t.pathname===n.pathname,a=t.hash===n.hash,o=t.search===n.search;if(r&&a&&!o)return;const{hash:i}=t;if(i){const e=decodeURIComponent(i.substring(1)),t=document.getElementById(e);t?.scrollIntoView()}else window.scrollTo(0,0)}({location:n,previousLocation:r}),N("onRouteDidUpdate",{previousLocation:r,location:n}))}),[r,n]),t};function $(e){const t=Array.from(new Set([e,decodeURI(e)])).map((e=>(0,p.f)(c.Z,e))).flat();return Promise.all(t.map((e=>e.route.component.preload?.())))}class L extends r.Component{previousLocation;routeUpdateCleanupCb;constructor(e){super(e),this.previousLocation=null,this.routeUpdateCleanupCb=s.default.canUseDOM?N("onRouteUpdate",{previousLocation:null,location:this.props.location}):()=>{},this.state={nextRouteHasLoaded:!0}}shouldComponentUpdate(e,t){if(e.location===this.props.location)return t.nextRouteHasLoaded;const n=e.location;return this.previousLocation=this.props.location,this.setState({nextRouteHasLoaded:!1}),this.routeUpdateCleanupCb=N("onRouteUpdate",{previousLocation:this.previousLocation,location:n}),$(n.pathname).then((()=>{this.routeUpdateCleanupCb(),this.setState({nextRouteHasLoaded:!0})})).catch((e=>{console.warn(e),window.location.reload()})),!1}render(){const{children:e,location:t}=this.props;return(0,d.jsx)(O,{previousLocation:this.previousLocation,location:t,children:(0,d.jsx)(u.AW,{location:t,render:()=>e})})}}const R=L,D="__docusaurus-base-url-issue-banner-container",F="__docusaurus-base-url-issue-banner",M="__docusaurus-base-url-issue-banner-suggestion-container";function z(e){return`\ndocument.addEventListener('DOMContentLoaded', function maybeInsertBanner() {\n var shouldInsert = typeof window['docusaurus'] === 'undefined';\n shouldInsert && insertBanner();\n});\n\nfunction insertBanner() {\n var bannerContainer = document.createElement('div');\n bannerContainer.id = '${D}';\n var bannerHtml = ${JSON.stringify(function(e){return`\n
\n

Your Docusaurus site did not load properly.

\n

A very common reason is a wrong site baseUrl configuration.

\n

Current configured baseUrl = ${e} ${"/"===e?" (default value)":""}

\n

We suggest trying baseUrl =

\n
\n`}(e)).replace(/{if("undefined"==typeof document)return void n();const r=document.createElement("link");r.setAttribute("rel","prefetch"),r.setAttribute("href",e),r.onload=()=>t(),r.onerror=()=>n();const a=document.getElementsByTagName("head")[0]??document.getElementsByName("script")[0]?.parentNode;a?.appendChild(r)}))}:function(e){return new Promise(((t,n)=>{const r=new XMLHttpRequest;r.open("GET",e,!0),r.withCredentials=!0,r.onload=()=>{200===r.status?t():n()},r.send(null)}))};var G=n(9670);const Y=new Set,J=new Set,X=()=>navigator.connection?.effectiveType.includes("2g")||navigator.connection?.saveData,ee={prefetch(e){if(!(e=>!X()&&!J.has(e)&&!Y.has(e))(e))return!1;Y.add(e);const t=(0,p.f)(c.Z,e).flatMap((e=>{return t=e.route.path,Object.entries(Z).filter((e=>{let[n]=e;return n.replace(/-[^-]+$/,"")===t})).flatMap((e=>{let[,t]=e;return Object.values((0,G.Z)(t))}));var t}));return Promise.all(t.map((e=>{const t=n.gca(e);return t&&!t.includes("undefined")?K(t).catch((()=>{})):Promise.resolve()})))},preload:e=>!!(e=>!X()&&!J.has(e))(e)&&(J.add(e),$(e))},te=Object.freeze(ee),ne=Boolean(!0);if(s.default.canUseDOM){window.docusaurus=te;const e=document.getElementById("__docusaurus"),t=(0,d.jsx)(i.B6,{children:(0,d.jsx)(o.VK,{children:(0,d.jsx)(V,{})})}),n=(e,t)=>{console.error("Docusaurus React Root onRecoverableError:",e,t)},s=()=>{if(ne)r.startTransition((()=>{a.hydrateRoot(e,t,{onRecoverableError:n})}));else{const o=a.createRoot(e,{onRecoverableError:n});r.startTransition((()=>{o.render(t)}))}};$(window.location.pathname).then(s)}},8940:(e,t,n)=>{"use strict";n.d(t,{_:()=>p,M:()=>d});var r=n(7294),a=n(6809);const o=JSON.parse('{"docusaurus-plugin-content-docs":{"default":{"path":"/daikoku/docs","versions":[{"name":"current","label":"Next","isLast":true,"path":"/daikoku/docs","mainDocId":"getstarted/index","docs":[{"id":"cli/index","path":"/daikoku/docs/cli/","sidebar":"tutorialSidebar"},{"id":"getstarted/about","path":"/daikoku/docs/getstarted/about","sidebar":"tutorialSidebar"},{"id":"getstarted/firstrun/configfile","path":"/daikoku/docs/getstarted/firstrun/configfile","sidebar":"tutorialSidebar"},{"id":"getstarted/firstrun/datastore","path":"/daikoku/docs/getstarted/firstrun/datastore","sidebar":"tutorialSidebar"},{"id":"getstarted/firstrun/env","path":"/daikoku/docs/getstarted/firstrun/env","sidebar":"tutorialSidebar"},{"id":"getstarted/firstrun/index","path":"/daikoku/docs/getstarted/firstrun/","sidebar":"tutorialSidebar"},{"id":"getstarted/firstrun/initialstate","path":"/daikoku/docs/getstarted/firstrun/initialstate","sidebar":"tutorialSidebar"},{"id":"getstarted/firstrun/run","path":"/daikoku/docs/getstarted/firstrun/run","sidebar":"tutorialSidebar"},{"id":"getstarted/getdaikoku/index","path":"/daikoku/docs/getstarted/getdaikoku/","sidebar":"tutorialSidebar"},{"id":"getstarted/index","path":"/daikoku/docs/getstarted/","sidebar":"tutorialSidebar"},{"id":"getstarted/quickstart","path":"/daikoku/docs/getstarted/quickstart","sidebar":"tutorialSidebar"},{"id":"getstarted/setup/admin","path":"/daikoku/docs/getstarted/setup/admin","sidebar":"tutorialSidebar"},{"id":"getstarted/setup/index","path":"/daikoku/docs/getstarted/setup/","sidebar":"tutorialSidebar"},{"id":"getstarted/setup/reporting","path":"/daikoku/docs/getstarted/setup/reporting","sidebar":"tutorialSidebar"},{"id":"guides/apis","path":"/daikoku/docs/guides/apis","sidebar":"tutorialSidebar"},{"id":"guides/archi","path":"/daikoku/docs/guides/archi","sidebar":"tutorialSidebar"},{"id":"guides/authentication","path":"/daikoku/docs/guides/authentication","sidebar":"tutorialSidebar"},{"id":"guides/deploy","path":"/daikoku/docs/guides/deploy","sidebar":"tutorialSidebar"},{"id":"guides/index","path":"/daikoku/docs/guides/","sidebar":"tutorialSidebar"},{"id":"guides/integrations","path":"/daikoku/docs/guides/integrations","sidebar":"tutorialSidebar"},{"id":"usages/adminusage/importexport","path":"/daikoku/docs/usages/adminusage/importexport","sidebar":"tutorialSidebar"},{"id":"usages/adminusage/index","path":"/daikoku/docs/usages/adminusage/","sidebar":"tutorialSidebar"},{"id":"usages/adminusage/sessions","path":"/daikoku/docs/usages/adminusage/sessions","sidebar":"tutorialSidebar"},{"id":"usages/adminusage/tenants","path":"/daikoku/docs/usages/adminusage/tenants","sidebar":"tutorialSidebar"},{"id":"usages/adminusage/users","path":"/daikoku/docs/usages/adminusage/users","sidebar":"tutorialSidebar"},{"id":"usages/consumerusage/aggregation-of-apikeys","path":"/daikoku/docs/usages/consumerusage/aggregation-of-apikeys","sidebar":"tutorialSidebar"},{"id":"usages/consumerusage/apikeys","path":"/daikoku/docs/usages/consumerusage/apikeys","sidebar":"tutorialSidebar"},{"id":"usages/consumerusage/billing","path":"/daikoku/docs/usages/consumerusage/billing","sidebar":"tutorialSidebar"},{"id":"usages/consumerusage/fastmode","path":"/daikoku/docs/usages/consumerusage/fastmode","sidebar":"tutorialSidebar"},{"id":"usages/consumerusage/index","path":"/daikoku/docs/usages/consumerusage/","sidebar":"tutorialSidebar"},{"id":"usages/consumerusage/subscribe","path":"/daikoku/docs/usages/consumerusage/subscribe","sidebar":"tutorialSidebar"},{"id":"usages/index","path":"/daikoku/docs/usages/","sidebar":"tutorialSidebar"},{"id":"usages/producerusage/apis","path":"/daikoku/docs/usages/producerusage/apis","sidebar":"tutorialSidebar"},{"id":"usages/producerusage/assets","path":"/daikoku/docs/usages/producerusage/assets","sidebar":"tutorialSidebar"},{"id":"usages/producerusage/income","path":"/daikoku/docs/usages/producerusage/income","sidebar":"tutorialSidebar"},{"id":"usages/producerusage/index","path":"/daikoku/docs/usages/producerusage/","sidebar":"tutorialSidebar"},{"id":"usages/producerusage/members","path":"/daikoku/docs/usages/producerusage/members","sidebar":"tutorialSidebar"},{"id":"usages/producerusage/subscriptions","path":"/daikoku/docs/usages/producerusage/subscriptions","sidebar":"tutorialSidebar"},{"id":"usages/tenantusage/1.5-initialize","path":"/daikoku/docs/usages/tenantusage/1.5-initialize","sidebar":"tutorialSidebar"},{"id":"usages/tenantusage/5.5-display","path":"/daikoku/docs/usages/tenantusage/5.5-display","sidebar":"tutorialSidebar"},{"id":"usages/tenantusage/assets","path":"/daikoku/docs/usages/tenantusage/assets","sidebar":"tutorialSidebar"},{"id":"usages/tenantusage/cms","path":"/daikoku/docs/usages/tenantusage/cms","sidebar":"tutorialSidebar"},{"id":"usages/tenantusage/index","path":"/daikoku/docs/usages/tenantusage/","sidebar":"tutorialSidebar"},{"id":"usages/tenantusage/messages","path":"/daikoku/docs/usages/tenantusage/messages","sidebar":"tutorialSidebar"},{"id":"usages/tenantusage/otoroshi","path":"/daikoku/docs/usages/tenantusage/otoroshi","sidebar":"tutorialSidebar"},{"id":"usages/tenantusage/teams","path":"/daikoku/docs/usages/tenantusage/teams","sidebar":"tutorialSidebar"},{"id":"usages/tenantusage/translations","path":"/daikoku/docs/usages/tenantusage/translations","sidebar":"tutorialSidebar"}],"draftIds":[],"sidebars":{"tutorialSidebar":{"link":{"path":"/daikoku/docs/getstarted/","label":"Get started"}}}}],"breadcrumbs":true}},"docusaurus-plugin-redoc":{"plugin-redoc-0":{"url":"redocusaurus/plugin-redoc-0.yaml","themeId":"theme-redoc","isSpecFile":true,"spec":{"openapi":"3.0.2","externalDocs":{"description":"Find out more about Daikoku","url":"https://maif.github.io/daikoku/"},"info":{"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"contact":{"name":"MAIF open source team","email":"oss@maif.fr"},"description":"Admin API of Daikoku","title":"Daikoku Admin API","version":"16.3.0"},"tags":[],"components":{"schemas":{"Tenant":{"description":"Tenant","properties":{"_id":{"type":"string"},"enabled":{"type":"boolean"},"_deleted":{"type":"boolean","nullable":true},"name":{"type":"string"},"domain":{"type":"string","nullable":true},"contact":{"type":"string"},"style":{"$ref":"#/components/schemas/DaikokuStyle","nullable":true},"defaultLanguage":{"type":"string","nullable":true},"otoroshiSettings":{"type":"array","nullable":true,"items":{"$ref":"#/components/schemas/OtoroshiSettings"}},"mailerSettings":{"nullable":true,"oneOf":[{"$ref":"#/components/schemas/MailerSettings.ConsoleMailerSettings"},{"$ref":"#/components/schemas/MailerSettings.MailgunSettings"},{"$ref":"#/components/schemas/MailerSettings.MailjetSettings"},{"$ref":"#/components/schemas/MailerSettings.SimpleSMTPSettings"},{"$ref":"#/components/schemas/MailerSettings.SendgridSettings"}]},"bucketSettings":{"nullable":true,"$ref":"#/components/schemas/S3Configuration"},"authProvider":{"nullable":true,"type":"string","enum":["local","otoroshi","ldap","oauth2"]},"authProviderSettings":{"type":"object"},"auditTrailConfig":{"nullable":false,"$ref":"#/components/schemas/AuditTrailConfig"},"isPrivate":{"type":"boolean","nullable":true},"adminApi":{"type":"string"},"adminSubscriptions":{"type":"array","items":{"type":"string"}},"creationSecurity":{"type":"boolean","nullable":true},"subscriptionSecurity":{"type":"boolean","nullable":true},"apiReferenceHideForGuest":{"type":"boolean","nullable":true},"defaultMessage":{"type":"string","nullable":true},"tenantMode":{"type":"string","enum":["default","maintenance","construction","translation"],"nullable":true},"aggregationApiKeysSecurity":{"type":"boolean","nullable":true},"robotTxt":{"type":"string","nullable":true},"thirdPartyPaymentSettings":{"type":"array","nullable":true,"items":{"oneOf":[{"$ref":"#/components/schemas/ThirdPartyPaymentSettings.StripeSettings"}]}},"display":{"nullable":true,"type":"string","enum":["default","environment"]},"environments":{"type":"array","nullable":true,"items":{"type":"string"}}},"required":["_id","enabled","name","contact","adminApi"]},"object":{"type":"object"},"done":{"description":"task is done","properties":{"done":{"type":"boolean"}}},"error":{"description":"error response","properties":{"error":{"type":"string"}}},"GraphQLQuery":{"type":"object","properties":{"query":{"type":"string"}}},"GraphQLResponse":{"type":"object","properties":{"data":{"type":"object"}}},"GraphQLSchema":{"type":"object"},"patch":{"description":"A set of changes described in JSON Patch format: http://jsonpatch.com/ (RFC 6902)","type":"array","items":{"type":"object","required":["op","path"],"properties":{"op":{"type":"string","enum":["add","replace","remove","copy","test"]},"path":{"type":"string"},"value":{}}}},"DaikokuStyle":{"description":"DaikokuStyle","properties":{"js":{"type":"string","nullable":true},"css":{"type":"string","nullable":true},"colorTheme":{"type":"string","nullable":true},"jsUrl":{"type":"string","nullable":true},"cssUrl":{"type":"string","nullable":true},"faviconUrl":{"type":"string","nullable":true},"fontFamilyUrl":{"type":"string","nullable":true},"title":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"unloggedHome":{"type":"string","nullable":true},"homePageVisible":{"type":"boolean","nullable":true},"homeCmsPage":{"type":"string","nullable":true},"notFoundCmsPage":{"type":"string","nullable":true},"authenticatedCmsPage":{"type":"string","nullable":true},"cacheTTL":{"type":"integer","format":"int32","nullable":true},"cmsHistoryLength":{"type":"integer","format":"int32","nullable":true},"logo":{"type":"string","nullable":true},"footer":{"type":"string","nullable":true}}},"ThirdPartyPaymentSettings.StripeSettings":{"description":"Stripe settings","properties":{"_id":{"type":"string"},"name":{"type":"string"},"publicKey":{"type":"string"},"secretKey":{"type":"string"}},"required":["_id","name","publicKey","secretKey"]},"OtoroshiSettings":{"description":"OtoroshiSettings","properties":{"id":{"type":"string"},"url":{"type":"string"},"host":{"type":"string"},"clientId":{"type":"string","nullable":true},"clientSecret":{"type":"string","nullable":true}},"required":["id","url","host"]},"MailerSettings.ConsoleMailerSettings":{"description":"MailerSettings","properties":{"type":{"type":"string","enum":["console"]},"template":{"type":"string","nullable":true}},"required":["type"]},"MailerSettings.MailgunSettings":{"description":"MailgunSettings","properties":{"type":{"type":"string","enum":["mailgun"]},"template":{"type":"string","nullable":true},"domain":{"type":"string"},"eu":{"type":"boolean","nullable":true},"key":{"type":"string"},"fromTitle":{"type":"string"},"fromEmail":{"type":"string"}},"required":["type","domain","key","fromTitle","fromEmail"]},"MailerSettings.MailjetSettings":{"description":"MailgunSettings","properties":{"type":{"type":"string","enum":["mailjet"]},"template":{"type":"string","nullable":true},"apiKeyPublic":{"type":"string"},"apiKeyPrivate":{"type":"string"},"fromTitle":{"type":"string"},"fromEmail":{"type":"string"}},"required":["type","apiKeyPublic","apiKeyPrivate","fromTitle","fromEmail"]},"MailerSettings.SendgridSettings":{"description":"SendgridSettings","properties":{"type":{"type":"string","enum":["sendgrid"]},"template":{"type":"string","nullable":true},"apikey":{"type":"string"},"fromTitle":{"type":"string"},"fromEmail":{"type":"string"}},"required":["type","apikey","fromEmail","fromTitle"]},"MailerSettings.SimpleSMTPSettings":{"description":"SimpleSMTPSettings","properties":{"type":{"type":"string","enum":["smtpClient"]},"template":{"type":"string","nullable":true},"host":{"type":"string"},"port":{"type":"string","additionalProperties":{"oneOf":[{"type":"integer"}]}},"fromTitle":{"type":"string"},"fromEmail":{"type":"string"}},"required":["type","host","port","fromEmail","fromTitle"]},"S3Configuration":{"description":"S3Configuration","properties":{"bucket":{"type":"string"},"endpoint":{"type":"string"},"region":{"type":"string"},"access":{"type":"string"},"secret":{"type":"string"},"chunkSize":{"type":"integer","format":"int32","nullable":true},"v4auth":{"type":"boolean","nullable":true}},"required":["bucket","endpoint","region","access","secret"]},"AuditTrailConfig":{"description":"AuditTrailConfig","properties":{"elasticConfigs":{"$ref":"#/components/schemas/ElasticAnalyticsConfig","nullable":true},"auditWebhooks":{"type":"array","nullable":true,"items":{"$ref":"#/components/schemas/Webhook"}},"alertsEmails":{"type":"array","nullable":true,"items":{"type":"string"}},"kafkaConfig":{"$ref":"#/components/schemas/KafkaConfig","nullable":true}}},"Webhook":{"description":"Webhook","properties":{"url":{"type":"string"},"headers":{"type":"object","nullable":true}},"required":["url"]},"ElasticAnalyticsConfig":{"description":"ElasticAnalyticsConfig","properties":{"clusterUri":{"type":"string"},"index":{"type":"string","nullable":true},"type":{"type":"string","nullable":true},"user":{"type":"string","nullable":true},"password":{"type":"string","nullable":true},"headers":{"type":"object","nullable":true}},"required":["clusterUri"]},"KafkaConfig":{"description":"KafkaConfig","properties":{"servers":{"type":"array","nullable":true,"items":{"type":"string"}},"keyPass":{"type":"string","nullable":true},"keystore":{"type":"string","nullable":true},"truststore":{"type":"string","nullable":true},"auditTopic":{"type":"string","nullable":true},"hostValidation":{"type":"boolean","nullable":true}}},"User":{"description":"User","properties":{"_id":{"type":"string"},"_deleted":{"type":"boolean"},"tenants":{"type":"array","nullable":true,"items":{"type":"string"}},"origins":{"type":"array","nullable":true,"items":{"type":"string","enum":["local","otoroshi","ldap","oauth2"]}},"name":{"type":"string"},"email":{"type":"string"},"picture":{"type":"string","nullable":true},"pictureFromProvider":{"type":"boolean","nullable":true},"personalToken":{"type":"string","nullable":true},"isDaikokuAdmin":{"type":"boolean","nullable":true},"password":{"type":"string","nullable":true,"description":"optionnal value but required in case of tenant has a Local AuthProvider"},"hardwareKeyRegistrations":{"type":"array","nullable":true,"items":{"type":"object"}},"lastTenant":{"type":"string","nullable":true},"metadata":{"type":"object","nullable":true},"defaultLanguage":{"type":"string","nullable":true},"starredApis":{"type":"array","nullable":true,"items":{"type":"string"}},"twoFactorAuthentication":{"$ref":"#/components/schemas/TwoFactorAuthentication","nullable":true},"invitation":{"$ref":"#/components/schemas/UserInvitation","nullable":true}},"required":["_id","name","email"]},"UserInvitation":{"description":"UserInvitation","properties":{"token":{"type":"string"},"createdAt":{"type":"integer","format":"timestamp","nullable":true},"team":{"type":"string"},"notificationId":{"type":"string","nullable":true},"registered":{"type":"boolean","nullable":true}},"required":["token"]},"TwoFactorAuthentication":{"description":"TwoFactorAuthentication","properties":{"enabled":{"type":"boolean"},"secret":{"type":"string"},"token":{"type":"string"},"backupCodes":{"type":"string"}},"required":["enabled","secret","token","backupCodes"]},"Team":{"description":"Team","properties":{"_id":{"type":"string"},"_tenant":{"type":"string"},"_deleted":{"type":"boolean","nullable":true},"type":{"type":"string","enum":["Personal","Organization","Admin"]},"name":{"type":"string"},"description":{"type":"string","nullable":true},"contact":{"type":"string"},"avatar":{"type":"string","nullable":true},"users":{"type":"array","items":{"$ref":"#/components/schemas/UserWithPermission"}},"authorizedEntities":{"type":"array","nullable":true,"items":{"properties":{"otoroshiSettingsId":{"type":"string"},"authorizedEntities":{"$ref":"#/components/schemas/AuthorizedEntities"}}}},"apiKeyVisibility":{"type":"string","enum":["administrator","apiEditor","user"]},"metadata":{"type":"object","nullable":true},"apisCreationPermission":{"type":"boolean","nullable":true},"verified":{"type":"boolean"}},"required":["_id","_tenant","type","contact","verified"]},"UserWithPermission":{"description":"UserWithPermission","properties":{"userId":{"type":"string"},"teamPermission":{"type":"string","enum":["Administrator","ApiEditor","User"]}},"required":["userId","teamPermission"]},"Api":{"description":"Api","properties":{"_id":{"type":"string"},"_tenant":{"type":"string"},"_deleted":{"type":"boolean","nullable":true},"team":{"type":"string"},"name":{"type":"string"},"lastUpdate":{"type":"integer","format":"timestamp"},"description":{"type":"string","nullable":true},"smallDescription":{"type":"string","nullable":true},"header":{"type":"string","nullable":true},"image":{"type":"string","nullable":true},"currentVersion":{"type":"string"},"supportedVersions":{"type":"array","items":{"type":"string"}},"isDefault":{"type":"boolean","nullable":true},"testing":{"$ref":"#/components/schemas/Testing","nullable":true},"documentation":{"$ref":"#/components/schemas/ApiDocumentation"},"swagger":{"$ref":"#/components/schemas/SwaggerAccess","nullable":true},"tags":{"type":"array","nullable":true,"items":{"type":"string"}},"categories":{"type":"array","nullable":true,"items":{"type":"string"}},"visibility":{"type":"string","enum":["Public","Private","PublicWithAuthorizations","AdminOnly"]},"possibleUsagePlans":{"type":"array","items":{"type":"string"}},"defaultUsagePlan":{"type":"string","nullable":true},"authorizedTeams":{"type":"array","nullable":true,"items":{"type":"string"}},"posts":{"type":"array","nullable":true,"items":{"type":"string"}},"issues":{"type":"array","nullable":true,"items":{"type":"string"}},"issuesTags":{"type":"array","nullable":true,"items":{"$ref":"#/components/schemas/ApiIssueTag"}},"stars":{"type":"integer","format":"int32"},"parent":{"type":"string"},"apis":{"type":"array","items":{"type":"string"}},"state":{"type":"string","enum":["created","published","blocked","deprecated"]}},"required":["_id","_tenant","team","name","lastUpdate","currentVersion","documentation","visibility","possibleUsagePlans","state"]},"ApiIssueTag":{"description":"ApiIssueTag","properties":{"id":{"type":"string","nullable":true},"name":{"type":"string"},"color":{"type":"string"}},"required":["name","color"]},"Testing":{"description":"Testing","properties":{"enabled":{"type":"boolean","nullable":true},"auth":{"type":"string","enum":["ApiKey","Basic"],"nullable":true},"name":{"type":"string","nullable":true},"username":{"type":"string","nullable":true},"password":{"type":"string","nullable":true},"config":{"$ref":"#/components/schemas/TestingConfig","nullable":true}}},"ApiDocumentation":{"description":"ApiDocumentation","properties":{"_id":{"type":"string"},"_tenant":{"type":"string"},"pages":{"type":"array","nullable":true,"items":{"$ref":"#/components/schemas/ApiDocumentationDetailPage"}},"lastModificationAt":{"type":"integer","format":"timestamp"}},"required":["_id","_tenant","lastModificationAt"]},"ApiState":{"description":"ApiState","properties":{},"required":["headers","content","value","url"]},"SwaggerAccess":{"description":"SwaggerAccess","properties":{"url":{"type":"string","nullable":true},"content":{"type":"string","nullable":true},"headers":{"type":"object","nullable":true}}},"Version":{"description":"Version","properties":{"value":{"type":"string"}},"required":["headers","content","value","url"]},"ApiVisibility":{"description":"ApiVisibility","properties":{},"required":["headers","content","url"]},"TestingConfig":{"description":"TestingConfig","properties":{"otoroshiSettings":{"type":"string"},"authorizedEntities":{"$ref":"#/components/schemas/AuthorizedEntities"},"clientName":{"type":"string"},"tag":{"type":"string"},"customMetadata":{"type":"object","nullable":true},"customMaxPerSecond":{"type":"integer","format":"int64","nullable":true},"customMaxPerDay":{"type":"integer","format":"int64","nullable":true},"customMaxPerMonth":{"type":"integer","format":"int64","nullable":true},"customReadOnly":{"type":"boolean","nullable":true}},"required":["otoroshiSettings","authorizedEntities","clientName","tag"]},"ApiDocumentationDetailPage":{"description":"ApiDocumentationDetailPage","properties":{"id":{"type":"string"},"title":{"type":"string"},"children":{"type":"array","items":{"$ref":"#/components/schemas/ApiDocumentationDetailPage"}}},"required":["id","title","children"]},"AuthorizedEntities":{"description":"AuthorizedEntities","properties":{"services":{"type":"array","items":{"type":"string"}},"groups":{"type":"array","items":{"type":"string"}},"routes":{"type":"array","items":{"type":"string"}}},"required":["routes","groups","services"]},"ApiSubscription":{"description":"ApiSubscription","properties":{"_id":{"type":"string"},"_tenant":{"type":"string"},"_deleted":{"type":"boolean","nullable":true},"apiKey":{"$ref":"#/components/schemas/OtoroshiApiKey"},"plan":{"type":"string"},"team":{"type":"string"},"api":{"type":"string"},"createdAt":{"type":"integer","format":"timestamp"},"by":{"type":"string"},"customName":{"type":"string","nullable":true},"adminCustomName":{"type":"string","nullable":true},"enabled":{"type":"boolean","nullable":true},"rotation":{"$ref":"#/components/schemas/ApiSubscriptionRotation"},"integrationToken":{"type":"string"},"customMetadata":{"type":"object"},"metadata":{"type":"object","nullable":true},"tags":{"type":"array","items":{"type":"string"}},"customMaxPerSecond":{"type":"integer","format":"int64","nullable":true},"customMaxPerDay":{"type":"integer","format":"int64","nullable":true},"customMaxPerMonth":{"type":"integer","format":"int64","nullable":true},"customReadOnly":{"type":"boolean","nullable":true},"parent":{"type":"string","nullable":true},"thirdPartySubscriptionInformations":{"oneOf":[{"$ref":"#/components/schemas/ThirdPartySubscriptionInformations.Stripe"}]}},"required":["_id","_tenant","apiKey","plan","team","api","createdAt","by","integrationToken"]},"ThirdPartySubscriptionInformations.Stripe":{"description":"ThirdPartySubscriptionInformations","properties":{"subscriptionId":{"type":"string"},"primaryElementId":{"type":"string","nullable":true},"meteredElementId":{"type":"string","nullable":true}}},"OtoroshiApiKey":{"description":"OtoroshiApiKey","properties":{"clientName":{"type":"string"},"clientId":{"type":"string"},"clientSecret":{"type":"string"}},"required":["clientName","clientId","clientSecret"]},"ApiSubscriptionRotation":{"description":"ApiSubscriptionRotation","properties":{"enabled":{"type":"boolean"},"rotationEvery":{"type":"integer","format":"int64"},"gracePeriod":{"type":"integer","format":"int64"},"pendingRotation":{"type":"boolean"}},"required":["enabled","rotationEvery","gracePeriod","pendingRotation"]},"ApiDocumentationPage":{"description":"ApiDocumentationPage","properties":{"_id":{"type":"string"},"_tenant":{"type":"string"},"_deleted":{"type":"boolean","nullable":true},"title":{"type":"string"},"lastModificationAt":{"type":"integer","format":"timestamp"},"content":{"type":"string","nullable":true},"contentType":{"type":"string","nullable":true},"remoteContentEnabled":{"type":"boolean","nullable":true},"remoteContentUrl":{"type":"string","nullable":true},"remoteContentHeaders":{"type":"object","nullable":true}},"required":["_id","_tenant","title","lastModificationAt"]},"Notification":{"description":"Notification","properties":{"_id":{"type":"string"},"_tenant":{"type":"string"},"_deleted":{"type":"boolean","nullable":true},"team":{"type":"string"},"sender":{"$ref":"#/components/schemas/NotificationSender"},"date":{"type":"integer","format":"timestamp","nullable":true},"notificationType":{"type":"string","enum":["AcceptOrReject","AcceptOnly"]},"status":{"oneOf":[{"$ref":"#/components/schemas/NotificationStatus.Accepted"},{"$ref":"#/components/schemas/NotificationStatus.Rejected"},{"$ref":"#/components/schemas/NotificationStatus.Pending"}]},"action":{"oneOf":[{"$ref":"#/components/schemas/NotificationAction.ApiAccess"},{"$ref":"#/components/schemas/NotificationAction.TeamAccess"},{"$ref":"#/components/schemas/NotificationAction.ApiSubscription"},{"$ref":"#/components/schemas/NotificationAction.ApiSubscriptionReject"},{"$ref":"#/components/schemas/NotificationAction.ApiSubscriptionAccept"},{"$ref":"#/components/schemas/NotificationAction.OtoroshiSyncSubscriptionError"},{"$ref":"#/components/schemas/NotificationAction.OtoroshiSyncApiError"},{"$ref":"#/components/schemas/NotificationAction.ApiKeyDeletionInformation"},{"$ref":"#/components/schemas/NotificationAction.ApiKeyRotationInProgress"},{"$ref":"#/components/schemas/NotificationAction.ApiKeyRotationEnded"},{"$ref":"#/components/schemas/NotificationAction.TeamInvitation"},{"$ref":"#/components/schemas/NotificationAction.ApiKeyRefresh"},{"$ref":"#/components/schemas/NotificationAction.NewPostPublished"},{"$ref":"#/components/schemas/NotificationAction.NewIssueOpen"},{"$ref":"#/components/schemas/NotificationAction.NewCommentOnIssue"},{"$ref":"#/components/schemas/NotificationAction.TransferApiOwnership"},{"$ref":"#/components/schemas/NotificationAction.CheckoutForSubscription"}]}},"required":["_id","_tenant","sender","status","action"]},"NotificationAction.ApiAccess":{"description":"NotificationAction apiAccess","properties":{"api":{"type":"string"},"team":{"type":"string"}},"required":["api","team","sender"]},"NotificationAction.TeamAccess":{"description":"NotificationAction teamAccess","properties":{"team":{"type":"string"}},"required":["team"]},"NotificationAction.ApiSubscription":{"description":"NotificationAction teamAccess","properties":{"api":{"type":"string"},"plan":{"type":"string"},"team":{"type":"string"},"demand":{"type":"string"},"step":{"type":"string"},"parentSubscriptionId":{"type":"string","nullable":true},"motivation":{"type":"string","nullable":true}},"required":["api","plan","team","demand","step"]},"NotificationAction.ApiSubscriptionReject":{"description":"NotificationAction ApiSubscriptionReject","properties":{"api":{"type":"string"},"plan":{"type":"string"},"team":{"type":"string"},"message":{"type":"string","nullable":true}},"required":["api","plan","team"]},"NotificationAction.ApiSubscriptionAccept":{"description":"NotificationAction ApiSubscriptionAccept","properties":{"api":{"type":"string"},"plan":{"type":"string"},"team":{"type":"string"}},"required":["api","plan","team"]},"NotificationAction.OtoroshiSyncSubscriptionError":{"description":"NotificationAction OtoroshiSyncSubscriptionError","properties":{"subscription":{"type":"string"},"message":{"type":"string"}},"required":["subscription","message"]},"NotificationAction.OtoroshiSyncApiError":{"description":"NotificationAction OtoroshiSyncApiError","properties":{"api":{"type":"string"},"message":{"type":"string"}},"required":["api","message"]},"NotificationAction.ApiKeyDeletionInformation":{"description":"NotificationAction ApiKeyDeletionInformation","properties":{"api":{"type":"string"},"clientId":{"type":"string"}},"required":["api","clientId"]},"NotificationAction.ApiKeyRotationInProgress":{"description":"NotificationAction ApiKeyRotationInProgress","properties":{"clientId":{"type":"string"},"api":{"type":"string"},"plan":{"type":"string"}},"required":["clientId","api","plan"]},"NotificationAction.ApiKeyRotationEnded":{"description":"NotificationAction ApiKeyRotationEnded","properties":{"clientId":{"type":"string"},"api":{"type":"string"},"plan":{"type":"string"}},"required":["clientId","api","plan"]},"NotificationAction.TeamInvitation":{"description":"NotificationAction teamAccess","properties":{"team":{"type":"string"},"user":{"type":"string"}},"required":["team","user"]},"NotificationAction.ApiKeyRefresh":{"description":"NotificationAction ApiKeyRefresh","properties":{"subscription":{"type":"string"},"api":{"type":"string"},"plan":{"type":"string"}},"required":["subscription","api","plan"]},"NotificationAction.NewPostPublished":{"description":"NotificationAction NewPostPublished","properties":{"apiName":{"type":"string","nullable":true},"teamId":{"type":"string"}},"required":["teamId"]},"NotificationAction.NewIssueOpen":{"description":"NotificationAction NewIssueOpen","properties":{"apiName":{"type":"string"},"teamId":{"type":"string"},"linkTo":{"type":"string"}},"required":["apiName","teamId","linkTo"]},"NotificationAction.NewCommentOnIssue":{"description":"NotificationAction NewCommentOnIssue","properties":{"apiName":{"type":"string"},"teamId":{"type":"string"},"linkTo":{"type":"string"}},"required":["apiName","teamId","linkTo"]},"NotificationAction.TransferApiOwnership":{"description":"NotificationAction TransferApiOwnership","properties":{"api":{"type":"string"},"team":{"type":"string"}},"required":["api","team"]},"NotificationAction.CheckoutForSubscription":{"description":"NotificationAction teamAccess","properties":{"demand":{"type":"string"},"api":{"type":"string"},"plan":{"type":"string"},"step":{"type":"string"}},"required":["demand","api","plan","step"]},"NotificationStatus.Pending":{"description":"NotificationStatus pending","properties":{"status":{"type":"string","enum":["Pending"]}},"required":["status"]},"NotificationStatus.Accepted":{"description":"NotificationStatus pending","properties":{"status":{"type":"string","enum":["Accepted"]},"date":{"type":"integer","format":"timestamp"}},"required":["status","date"]},"NotificationStatus.Rejected":{"description":"NotificationStatus pending","properties":{"status":{"type":"string","enum":["Rejected"]},"date":{"type":"integer","format":"timestamp"}},"required":["status","date"]},"NotificationSender":{"description":"NotificationSender","properties":{"name":{"type":"string"},"email":{"type":"string"},"id":{"type":"string","description":"user id","nullable":true}},"required":["email","name"]},"UserSession":{"description":"UserSession","properties":{"_id":{"type":"string"},"sessionId":{"type":"string"},"userId":{"type":"string"},"userName":{"type":"string"},"userEmail":{"type":"string"},"impersonatorId":{"type":"string","nullable":true},"impersonatorName":{"type":"string","nullable":true},"impersonatorEmail":{"type":"string","nullable":true},"impersonatorSessionId":{"type":"string","nullable":true},"created":{"type":"integer","format":"timestamp","nullable":true},"ttl":{"type":"integer","format":"int64","nullable":true},"expires":{"type":"integer","format":"timestamp","nullable":true}},"required":["_id","sessionId","userId","userName","userEmail"]},"ApiKeyConsumption":{"description":"ApiKeyConsumption","properties":{"_id":{"type":"string"},"_deleted":{"type":"boolean","nullable":true},"_tenant":{"type":"string"},"team":{"type":"string"},"api":{"type":"string"},"plan":{"type":"string"},"clientId":{"type":"string"},"hits":{"type":"integer","format":"int32"},"globalInformations":{"$ref":"#/components/schemas/ApiKeyGlobalConsumptionInformations"},"quotas":{"$ref":"#/components/schemas/ApiKeyQuotas"},"billing":{"$ref":"#/components/schemas/ApiKeyBilling"},"from":{"type":"integer","format":"timestamp"},"to":{"type":"integer","format":"timestamp"},"state":{"type":"string","enum":["completed","inProgress"]}},"required":["_id","_tenant","team","api","plan","clientId","hits","globalInformations","quotas","billing","to","from","state"]},"ApiKeyBilling":{"description":"ApiKeyBilling","properties":{"hits":{"type":"integer","format":"int64"},"total":{"type":"number","format":"float64"}},"required":["hits","total"]},"ApiKeyQuotas":{"description":"ApiKeyQuotas","properties":{"authorizedCallsPerSec":{"type":"integer","format":"int64"},"currentCallsPerSec":{"type":"integer","format":"int64"},"remainingCallsPerSec":{"type":"integer","format":"int64"},"authorizedCallsPerDay":{"type":"integer","format":"int64"},"currentCallsPerDay":{"type":"integer","format":"int64"},"remainingCallsPerDay":{"type":"integer","format":"int64"},"authorizedCallsPerMonth":{"type":"integer","format":"int64"},"currentCallsPerMonth":{"type":"integer","format":"int64"},"remainingCallsPerMonth":{"type":"integer","format":"int64"}},"required":["authorizedCallsPerSec","currentCallsPerSec","remainingCallsPerSec","authorizedCallsPerDay","currentCallsPerDay","remainingCallsPerDay","authorizedCallsPerMonth","currentCallsPerMonth","remainingCallsPerMonth"]},"ApiKeyGlobalConsumptionInformations":{"description":"ApiKeyGlobalConsumptionInformations","properties":{"hits":{"type":"integer","format":"int64"},"dataIn":{"type":"integer","format":"int64"},"dataOut":{"type":"integer","format":"int64"},"avgDuration":{"type":"number","format":"float64","nullable":true},"avgOverhead":{"type":"number","format":"float64","nullable":true}},"required":["hits","dataIn","dataOut"]},"Message":{"description":"Message","properties":{"_id":{"type":"string"},"_tenant":{"type":"string"},"messageType":{"oneOf":[{"$ref":"#/components/schemas/MessageType.Tenant"}]},"chat":{"type":"string","description":"user id"},"participants":{"type":"array","items":{"type":"string","description":"user ids of the tenant admins"}},"readBy":{"type":"array","items":{"type":"string","description":"user ids"}},"date":{"type":"integer","format":"timestamp"},"sender":{"type":"string","description":"user id"},"message":{"type":"string"},"closed":{"type":"integer","format":"timestamp","nullable":true},"send":{"type":"boolean","nullable":true}},"required":["_id","_tenant","chat","readBy","participants","message","messageType","date","sender"]},"MessageType.Tenant":{"description":"MessageType for Tenant administrators","properties":{"type":{"type":"string","enum":["tenant"]},"value":{"type":"string","description":"the id of the tenant"}}},"ApiIssue":{"description":"ApiIssue","properties":{"_id":{"type":"string"},"seqId":{"type":"integer","format":"int32","nullable":true},"_tenant":{"type":"string"},"_deleted":{"type":"boolean","nullable":true},"title":{"type":"string"},"tags":{"type":"array","nullable":true,"items":{"type":"string"}},"open":{"type":"boolean","nullable":true},"createdAt":{"type":"integer","format":"timestamp","nullable":true},"closedAt":{"type":"integer","format":"timestamp","nullable":true},"by":{"type":"string"},"comments":{"type":"array","items":{"$ref":"#/components/schemas/ApiIssueComment"}},"lastModificationAt":{"type":"integer","format":"timestamp","nullable":true},"apiVersion":{"type":"string","nullable":true}},"required":["_id","_tenant","title","by"]},"ApiIssueComment":{"description":"ApiIssueComment","properties":{"by":{"type":"string"},"createdAt":{"type":"integer","format":"timestamp","nullable":true},"lastModificationAt":{"type":"integer","format":"timestamp","nullable":true},"content":{"type":"string"}},"required":["content","by"]},"ApiPost":{"description":"ApiPost","properties":{"_id":{"type":"string"},"_tenant":{"type":"string"},"_deleted":{"type":"boolean","nullable":true},"title":{"type":"string"},"lastModificationAt":{"type":"integer","format":"timestamp"},"content":{"type":"string","nullable":true}},"required":["_id","_tenant","title","lastModificationAt"]},"Translation":{"description":"Translation","properties":{"_id":{"type":"string"},"_tenant":{"type":"string"},"language":{"type":"string"},"key":{"type":"string"},"value":{"type":"string"},"lastModificationAt":{"type":"integer","format":"timestamp","nullable":true}},"required":["_id","_tenant","key","value","language"]},"UsagePlan.Admin":{"description":"UsagePlan","properties":{"type":{"type":"string","enum":["Admin"]},"_id":{"type":"string"},"_tenant":{"type":"string"},"_deleted":{"type":"boolean","nullable":true},"otoroshiTarget":{"$ref":"#/components/schemas/OtoroshiTarget","nullable":true},"aggregationApiKeysSecurity":{"type":"boolean","nullable":false}},"required":["_id","_tenant"]},"UsagePlan.FreeWithoutQuotas":{"description":"UsagePlan","properties":{"type":{"type":"string","enum":["FreeWithoutQuotas"]},"_id":{"type":"string"},"_tenant":{"type":"string"},"_deleted":{"type":"boolean","nullable":true},"currency":{"$ref":"#/components/schemas/Currency"},"customName":{"type":"string","nullable":true},"customDescription":{"type":"string","nullable":true},"otoroshiTarget":{"$ref":"#/components/schemas/OtoroshiTarget","nullable":true},"billingDuration":{"$ref":"#/components/schemas/BillingDuration"},"allowMultipleKeys":{"type":"boolean","nullable":true},"visibility":{"type":"string","enum":["Public","Private"]},"authorizedTeams":{"type":"array","items":{"type":"string"}},"autoRotation":{"type":"boolean","nullable":true},"subscriptionProcess":{"type":"array","items":{"oneOf":[{"$ref":"#/components/schemas/SubscriptionDemandStep.Email"},{"$ref":"#/components/schemas/SubscriptionDemandStep.TeamAdmin"},{"$ref":"#/components/schemas/SubscriptionDemandStep.Payment"},{"$ref":"#/components/schemas/SubscriptionDemandStep.HttpRequest"}]}},"integrationProcess":{"type":"string","enum":["Automatic","ApiKey"]},"aggregationApiKeysSecurity":{"type":"boolean","nullable":true},"swagger":{"nullable":true,"$ref":"#/components/schemas/SwaggerAccess"},"testing":{"nullable":true,"$ref":"#/components/schemas/Testing"},"documentation":{"nullable":true,"$ref":"#/components/schemas/ApiDocumentation"}},"required":["_id","_tenant","currency","billingDuration","subscriptionProcess","integrationProcess"]},"UsagePlan.FreeWithQuotas":{"description":"UsagePlan","properties":{"type":{"type":"string","enum":["FreeWithoutQuotas"]},"_id":{"type":"string"},"_tenant":{"type":"string"},"_deleted":{"type":"boolean","nullable":true},"maxPerSecond":{"type":"integer","format":"int64"},"maxPerDay":{"type":"integer","format":"int64"},"maxPerMonth":{"type":"integer","format":"int64"},"currency":{"$ref":"#/components/schemas/Currency"},"customName":{"type":"string","nullable":true},"customDescription":{"type":"string","nullable":true},"otoroshiTarget":{"$ref":"#/components/schemas/OtoroshiTarget","nullable":true},"billingDuration":{"$ref":"#/components/schemas/BillingDuration"},"allowMultipleKeys":{"type":"boolean","nullable":true},"visibility":{"type":"string","enum":["Public","Private"]},"authorizedTeams":{"type":"array","items":{"type":"string"}},"autoRotation":{"type":"boolean","nullable":true},"subscriptionProcess":{"type":"array","items":{"oneOf":[{"$ref":"#/components/schemas/SubscriptionDemandStep.Email"},{"$ref":"#/components/schemas/SubscriptionDemandStep.TeamAdmin"},{"$ref":"#/components/schemas/SubscriptionDemandStep.Payment"},{"$ref":"#/components/schemas/SubscriptionDemandStep.HttpRequest"}]}},"integrationProcess":{"type":"string","enum":["Automatic","ApiKey"]},"aggregationApiKeysSecurity":{"type":"boolean","nullable":false},"swagger":{"nullable":true,"$ref":"#/components/schemas/SwaggerAccess"},"testing":{"nullable":true,"$ref":"#/components/schemas/Testing"},"documentation":{"nullable":true,"$ref":"#/components/schemas/ApiDocumentation"}},"required":["_id","_tenant","maxPerSecond","maxPerDay","maxPerMonth","currency","billingDuration","subscriptionProcess","integrationProcess"]},"UsagePlan.QuotasWithLimits":{"description":"UsagePlan","properties":{"type":{"type":"string","enum":["FreeWithoutQuotas"]},"_id":{"type":"string"},"_tenant":{"type":"string"},"_deleted":{"type":"boolean","nullable":true},"maxPerSecond":{"type":"integer","format":"int64"},"maxPerDay":{"type":"integer","format":"int64"},"maxPerMonth":{"type":"integer","format":"int64"},"costPerMonth":{"type":"integer","format":"float64"},"trialPeriod":{"$ref":"#/components/schemas/BillingDuration"},"currency":{"$ref":"#/components/schemas/Currency"},"customName":{"type":"string","nullable":true},"customDescription":{"type":"string","nullable":true},"otoroshiTarget":{"$ref":"#/components/schemas/OtoroshiTarget","nullable":true},"billingDuration":{"$ref":"#/components/schemas/BillingDuration"},"allowMultipleKeys":{"type":"boolean","nullable":true},"visibility":{"type":"string","enum":["Public","Private"]},"authorizedTeams":{"type":"array","items":{"type":"string"}},"autoRotation":{"type":"boolean","nullable":true},"subscriptionProcess":{"type":"array","items":{"oneOf":[{"$ref":"#/components/schemas/SubscriptionDemandStep.Email"},{"$ref":"#/components/schemas/SubscriptionDemandStep.TeamAdmin"},{"$ref":"#/components/schemas/SubscriptionDemandStep.Payment"},{"$ref":"#/components/schemas/SubscriptionDemandStep.HttpRequest"}]}},"integrationProcess":{"type":"string","enum":["Automatic","ApiKey"]},"aggregationApiKeysSecurity":{"type":"boolean","nullable":false},"swagger":{"nullable":true,"$ref":"#/components/schemas/SwaggerAccess"},"testing":{"nullable":true,"$ref":"#/components/schemas/Testing"},"documentation":{"nullable":true,"$ref":"#/components/schemas/ApiDocumentation"}},"required":["_id","_tenant","maxPerSecond","maxPerDay","maxPerMonth","costPerMonth","trialPeriod","currency","billingDuration","subscriptionProcess","integrationProcess"]},"UsagePlan.QuotasWithoutLimits":{"description":"UsagePlan","properties":{"type":{"type":"string","enum":["FreeWithoutQuotas"]},"_id":{"type":"string"},"_tenant":{"type":"string"},"_deleted":{"type":"boolean","nullable":true},"maxPerSecond":{"type":"integer","format":"int64"},"maxPerDay":{"type":"integer","format":"int64"},"maxPerMonth":{"type":"integer","format":"int64"},"costPerMonth":{"type":"integer","format":"float64"},"costPerAdditionalRequest":{"type":"integer","format":"float64"},"trialPeriod":{"$ref":"#/components/schemas/BillingDuration"},"currency":{"$ref":"#/components/schemas/Currency"},"customName":{"type":"string","nullable":true},"customDescription":{"type":"string","nullable":true},"otoroshiTarget":{"$ref":"#/components/schemas/OtoroshiTarget","nullable":true},"billingDuration":{"$ref":"#/components/schemas/BillingDuration"},"allowMultipleKeys":{"type":"boolean","nullable":true},"visibility":{"type":"string","enum":["Public","Private"]},"authorizedTeams":{"type":"array","items":{"type":"string"}},"autoRotation":{"type":"boolean","nullable":true},"subscriptionProcess":{"type":"array","items":{"oneOf":[{"$ref":"#/components/schemas/SubscriptionDemandStep.Email"},{"$ref":"#/components/schemas/SubscriptionDemandStep.TeamAdmin"},{"$ref":"#/components/schemas/SubscriptionDemandStep.Payment"},{"$ref":"#/components/schemas/SubscriptionDemandStep.HttpRequest"}]}},"integrationProcess":{"type":"string","enum":["Automatic","ApiKey"]},"aggregationApiKeysSecurity":{"type":"boolean","nullable":false},"swagger":{"nullable":true,"$ref":"#/components/schemas/SwaggerAccess"},"testing":{"nullable":true,"$ref":"#/components/schemas/Testing"},"documentation":{"nullable":true,"$ref":"#/components/schemas/ApiDocumentation"},"paymentSettings":{"nullable":true,"oneOf":[{"$ref":"#/components/schemas/PaymentSettings.Stripe"}]}},"required":["_id","_tenant","maxPerSecond","maxPerDay","maxPerMonth","costPerMonth","trialPeriod","costPerAdditionalRequest","currency","billingDuration","subscriptionProcess","integrationProcess"]},"UsagePlan.PayPerUse":{"description":"UsagePlan","properties":{"type":{"type":"string","enum":["FreeWithoutQuotas"]},"_id":{"type":"string"},"_tenant":{"type":"string"},"_deleted":{"type":"boolean","nullable":true},"costPerMonth":{"type":"integer","format":"float64"},"costPerRequest":{"type":"integer","format":"float64"},"trialPeriod":{"$ref":"#/components/schemas/BillingDuration"},"billingDuration":{"$ref":"#/components/schemas/BillingDuration"},"currency":{"$ref":"#/components/schemas/Currency"},"customName":{"type":"string","nullable":true},"customDescription":{"type":"string","nullable":true},"otoroshiTarget":{"$ref":"#/components/schemas/OtoroshiTarget","nullable":true},"allowMultipleKeys":{"type":"boolean","nullable":true},"visibility":{"type":"string","enum":["Public","Private"]},"authorizedTeams":{"type":"array","items":{"type":"string"}},"autoRotation":{"type":"boolean","nullable":true},"subscriptionProcess":{"type":"array","items":{"oneOf":[{"$ref":"#/components/schemas/SubscriptionDemandStep.Email"},{"$ref":"#/components/schemas/SubscriptionDemandStep.TeamAdmin"},{"$ref":"#/components/schemas/SubscriptionDemandStep.Payment"},{"$ref":"#/components/schemas/SubscriptionDemandStep.HttpRequest"}]}},"integrationProcess":{"type":"string","enum":["Automatic","ApiKey"]},"aggregationApiKeysSecurity":{"type":"boolean","nullable":false},"swagger":{"nullable":true,"$ref":"#/components/schemas/SwaggerAccess"},"testing":{"nullable":true,"$ref":"#/components/schemas/Testing"},"documentation":{"nullable":true,"$ref":"#/components/schemas/ApiDocumentation"},"paymentSettings":{"nullable":true,"oneOf":[{"$ref":"#/components/schemas/PaymentSettings.Stripe"}]}},"required":["_id","_tenant","costPerMonth","trialPeriod","costPerRequest","currency","billingDuration","subscriptionProcess","integrationProcess"]},"Currency":{"description":"Currency","properties":{"code":{"type":"string"}},"required":["code"]},"BillingDuration":{"description":"BillingDuration","properties":{"value":{"type":"integer","format":"int64"},"unit":{"type":"string","enum":["hour","hours","day","days","month","months","year","years"]}}},"OtoroshiTarget":{"description":"Otoroshi target","properties":{"otoroshiSettings":{"type":"string"},"authorizedEntities":{"$ref":"#/components/schemas/AuthorizedEntities"},"apikeyCustomization":{"$ref":"#/components/schemas/ApikeyCustomization"}},"required":["otoroshiSettings","authorizedEntities","apikeyCustomization"]},"PaymentSettings.Stripe":{"description":"PaymentSettings.Stripe","properties":{"thirdPartyPaymentSettingsId":{"type":"string"},"productId":{"type":"string"},"priceIds":{"$ref":"#/components/schemas/StripePriceIds"}},"required":["thirdPartyPaymentSettings","productId","priceIds"]},"StripePriceIds":{"description":"StripePriceIds","properties":{"basePriceId":{"type":"string"},"additionalPriceId":{"type":"string","nullable":true}},"required":["basePriceId"]},"ApikeyCustomization":{"description":"Apikey customization","properties":{"clientIdOnly":{"type":"boolean","nullable":true},"readOnly":{"type":"boolean","nullable":true},"constrainedServicesOnly":{"type":"boolean","nullable":true},"metadata":{"type":"object","nullable":true},"customMetadata":{"type":"object","nullable":true},"tags":{"type":"array","items":{"type":"string"},"nullable":true},"restrictions":{"$ref":"#/components/schemas/ApiKeyRestrictions"}},"required":["restrictions"]},"ApiKeyRestrictions":{"description":"ApiKey Restrictions","properties":{"enabled":{"type":"boolean","nullable":true},"allowlast":{"type":"boolean","nullable":true},"allowed":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyRestrictionPath"}},"forbidden":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyRestrictionPath"}},"notFound":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyRestrictionPath"}}}},"ApiKeyRestrictionPath":{"description":"ApiKeyRestriction Path","properties":{"method":{"type":"string"},"path":{"type":"string"}},"required":["method","path"]},"SubscriptionDemand":{"description":"SubscriptionDemand","properties":{"_id":{"type":"string"},"_tenant":{"type":"string"},"_deleted":{"type":"boolean"},"api":{"type":"string"},"plan":{"type":"string"},"steps":{"type":"array","items":{"oneOf":[{"$ref":"#/components/schemas/SubscriptionDemandStep.Email"},{"$ref":"#/components/schemas/SubscriptionDemandStep.TeamAdmin"},{"$ref":"#/components/schemas/SubscriptionDemandStep.Payment"},{"$ref":"#/components/schemas/SubscriptionDemandStep.HttpRequest"}]}},"state":{"$ref":"#/components/schemas/SubscriptionDemandState"},"team":{"type":"string"},"from":{"type":"string"},"date":{"type":"integer","format":"timestamp"},"motivation":{"type":"string","nullable":true},"parentSubscriptionId":{"type":"string","nullable":true},"customReadOnly":{"type":"boolean","nullable":true},"customMetadata":{"type":"object"},"customMaxPerSecond":{"type":"integer","format":"int64","nullable":true},"customMaxPerDay":{"type":"integer","format":"int64","nullable":true},"customMaxPerMonth":{"type":"integer","format":"int64","nullable":true},"adminCustomName":{"type":"string","nullable":true}},"required":["_id","_tenant","api","plan","team","steps","state","from","date"]},"SubscriptionDemandState":{"description":"SubscriptionDemandStep","properties":{"id":{"type":"string"},"state":{"type":"string","enum":["waiting","inProgress","canceled","accepted","refused","blocked"]},"step":{"$ref":"#/components/schemas/ValidationStep"},"metadata":{"type":"object"}},"required":["id","step","state","metadata"]},"SubscriptionDemandStep.Email":{"description":"SubscriptionDemandState by Email","properties":{"type":{"type":"string","enum":["email"]},"id":{"type":"string"},"emails":{"type":"array","items":{"type":"string","format":"email"}},"title":{"type":"string"},"message":{"type":"string","nullable":true}},"required":["type","id","emails","title"]},"SubscriptionDemandStep.TeamAdmin":{"description":"SubscriptionDemandState by team admin","properties":{"type":{"type":"string","enum":["teamAdmin"]},"id":{"type":"string"},"schema":{"type":"object","nullable":true},"formatter":{"type":"string","nullable":true},"title":{"type":"string"}},"required":["type","id","title"]},"SubscriptionDemandStep.Payment":{"description":"SubscriptionDemandState by payment","properties":{"type":{"type":"string","enum":["payment"]},"id":{"type":"string"},"thirdPartyPaymentSettingsId":{"type":"string"},"title":{"type":"string"}},"required":["type","id","thirdPartyPaymentSettingsId","title"]},"SubscriptionDemandStep.HttpRequest":{"description":"SubscriptionDemandState by Http request","properties":{"type":{"type":"string","enum":["httpRequest"]},"id":{"type":"string"},"url":{"type":"string"},"headers":{"type":"object","nullable":true}},"required":["type","id","url","title"]},"ValidationStep":{"description":"ValidationStep","properties":{}}}},"paths":{"/admin-api/tenants/{id}":{"delete":{"summary":"delete a tenant","operationId":"tenants.delete","responses":{"200":{"description":"entity deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/done"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","description":"The ID of the tenant to delete","required":true}],"tags":["tenant"]},"patch":{"summary":"update a tenant with JSON patch or by merging JSON object","operationId":"tenants.patch","requestBody":{"description":"the patch to update the tenant or a JSON object","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/patch"},{"$ref":"#/components/schemas/Tenant"}]}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","description":"The tenant ID to update","required":true}],"tags":["tenant"]},"put":{"summary":"update a tenant","operationId":"tenants.update","requestBody":{"description":"the ID of the tenant to update","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tenant"}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","description":"The ID of the tenant to update","required":true}],"tags":["tenant"]},"get":{"summary":"read a tenant","operationId":"tenants.findById","responses":{"200":{"description":"found entity","content":{"application/json":{"schema":{"type":"object","items":{"$ref":"#/components/schemas/Tenant"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","description":"The ID of the tenant to retrieve","required":true}],"tags":["tenant"]}},"/admin-api/tenants":{"get":{"summary":"read all tenant","operationId":"tenants.findAll","responses":{"200":{"description":"success","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Tenant"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["tenant"]},"post":{"summary":"create a tenant","requestBody":{"description":"the tenant to create","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tenant"}}}},"operationId":"tenants.create","responses":{"201":{"description":"entity created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tenant"}}}},"400":{"description":"bad entity format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["tenant"]}},"/admin-api/users/{id}":{"delete":{"summary":"delete a user","operationId":"users.delete","responses":{"200":{"description":"entity deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/done"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["user"]},"patch":{"summary":"update a user with JSON patch or by merging JSON object","operationId":"users.patch","requestBody":{"description":"the patch to update the user or a JSON object","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/patch"},{"$ref":"#/components/schemas/User"}]}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["user"]},"put":{"summary":"update a user","operationId":"users.update","requestBody":{"description":"the user to update","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["user"]},"get":{"summary":"read a user","operationId":"users.findById","responses":{"200":{"description":"found entity","content":{"application/json":{"schema":{"type":"object","items":{"$ref":"#/components/schemas/User"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["user"]}},"/admin-api/users":{"get":{"summary":"read all user","operationId":"users.findAll","responses":{"200":{"description":"success","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/User"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["user"]},"post":{"summary":"creates a user","requestBody":{"description":"the user to create","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"operationId":"users.create","responses":{"201":{"description":"entity created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"400":{"description":"bad entity format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["user"]}},"/admin-api/teams/{id}":{"delete":{"summary":"delete a team","operationId":"teams.delete","responses":{"200":{"description":"entity deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/done"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["team"]},"patch":{"summary":"update a team with JSON patch or by merging JSON object","operationId":"teams.patch","requestBody":{"description":"the patch to update the team or a JSON object","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/patch"},{"$ref":"#/components/schemas/Team"}]}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["team"]},"put":{"summary":"update a team","operationId":"teams.update","requestBody":{"description":"the team to update","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Team"}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["team"]},"get":{"summary":"read a team","operationId":"teams.findById","responses":{"200":{"description":"found entity","content":{"application/json":{"schema":{"type":"object","items":{"$ref":"#/components/schemas/Team"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["team"]}},"/admin-api/teams":{"get":{"summary":"read all team","operationId":"teams.findAll","responses":{"200":{"description":"success","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Team"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["team"]},"post":{"summary":"creates a team","requestBody":{"description":"the team to create","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Team"}}}},"operationId":"teams.create","responses":{"201":{"description":"entity created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Team"}}}},"400":{"description":"bad entity format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["team"]}},"/admin-api/apis/{id}":{"delete":{"summary":"delete a api","operationId":"apis.delete","responses":{"200":{"description":"entity deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/done"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["api"]},"patch":{"summary":"update a api with JSON patch or by merging JSON object","operationId":"apis.patch","requestBody":{"description":"the patch to update the api or a JSON object","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/patch"},{"$ref":"#/components/schemas/Api"}]}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["api"]},"put":{"summary":"update a api","operationId":"apis.update","requestBody":{"description":"the api to update","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Api"}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["api"]},"get":{"summary":"read a api","operationId":"apis.findById","responses":{"200":{"description":"found entity","content":{"application/json":{"schema":{"type":"object","items":{"$ref":"#/components/schemas/Api"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["api"]}},"/admin-api/apis":{"get":{"summary":"read all api","operationId":"apis.findAll","responses":{"200":{"description":"success","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Api"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["api"]},"post":{"summary":"creates a api","requestBody":{"description":"the api to create","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Api"}}}},"operationId":"apis.create","responses":{"201":{"description":"entity created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Api"}}}},"400":{"description":"bad entity format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["api"]}},"/admin-api/subscriptions/{id}":{"delete":{"summary":"delete a api-subscription","operationId":"api-subscriptions.delete","responses":{"200":{"description":"entity deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/done"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["api-subscription"]},"patch":{"summary":"update a api-subscription with JSON patch or by merging JSON object","operationId":"api-subscriptions.patch","requestBody":{"description":"the patch to update the api-subscription or a JSON object","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/patch"},{"$ref":"#/components/schemas/ApiSubscription"}]}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["api-subscription"]},"put":{"summary":"update a api-subscription","operationId":"api-subscriptions.update","requestBody":{"description":"the api-subscription to update","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiSubscription"}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["api-subscription"]},"get":{"summary":"read a api-subscription","operationId":"api-subscriptions.findById","responses":{"200":{"description":"found entity","content":{"application/json":{"schema":{"type":"object","items":{"$ref":"#/components/schemas/ApiSubscription"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["api-subscription"]}},"/admin-api/subscriptions":{"get":{"summary":"read all api-subscription","operationId":"api-subscriptions.findAll","responses":{"200":{"description":"success","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiSubscription"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["api-subscription"]},"post":{"summary":"creates a api-subscription","requestBody":{"description":"the api-subscription to create","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiSubscription"}}}},"operationId":"api-subscriptions.create","responses":{"201":{"description":"entity created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiSubscription"}}}},"400":{"description":"bad entity format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["api-subscription"]}},"/admin-api/pages/{id}":{"delete":{"summary":"delete a api-documentation-page","operationId":"api-documentation-pages.delete","responses":{"200":{"description":"entity deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/done"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["api-documentation-page"]},"patch":{"summary":"update a api-documentation-page with JSON patch or by merging JSON object","operationId":"api-documentation-pages.patch","requestBody":{"description":"the patch to update the api-documentation-page or a JSON object","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/patch"},{"$ref":"#/components/schemas/ApiDocumentationPage"}]}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["api-documentation-page"]},"put":{"summary":"update a api-documentation-page","operationId":"api-documentation-pages.update","requestBody":{"description":"the api-documentation-page to update","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiDocumentationPage"}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["api-documentation-page"]},"get":{"summary":"read a api-documentation-page","operationId":"api-documentation-pages.findById","responses":{"200":{"description":"found entity","content":{"application/json":{"schema":{"type":"object","items":{"$ref":"#/components/schemas/ApiDocumentationPage"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["api-documentation-page"]}},"/admin-api/pages":{"get":{"summary":"read all api-documentation-page","operationId":"api-documentation-pages.findAll","responses":{"200":{"description":"success","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiDocumentationPage"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["api-documentation-page"]},"post":{"summary":"creates a api-documentation-page","requestBody":{"description":"the api-documentation-page to create","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiDocumentationPage"}}}},"operationId":"api-documentation-pages.create","responses":{"201":{"description":"entity created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiDocumentationPage"}}}},"400":{"description":"bad entity format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["api-documentation-page"]}},"/admin-api/notifications/{id}":{"delete":{"summary":"delete a notification","operationId":"notifications.delete","responses":{"200":{"description":"entity deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/done"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["notification"]},"patch":{"summary":"update a notification with JSON patch or by merging JSON object","operationId":"notifications.patch","requestBody":{"description":"the patch to update the notification or a JSON object","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/patch"},{"$ref":"#/components/schemas/Notification"}]}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["notification"]},"put":{"summary":"update a notification","operationId":"notifications.update","requestBody":{"description":"the notification to update","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Notification"}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["notification"]},"get":{"summary":"read a notification","operationId":"notifications.findById","responses":{"200":{"description":"found entity","content":{"application/json":{"schema":{"type":"object","items":{"$ref":"#/components/schemas/Notification"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["notification"]}},"/admin-api/notifications":{"get":{"summary":"read all notification","operationId":"notifications.findAll","responses":{"200":{"description":"success","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Notification"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["notification"]},"post":{"summary":"creates a notification","requestBody":{"description":"the notification to create","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Notification"}}}},"operationId":"notifications.create","responses":{"201":{"description":"entity created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Notification"}}}},"400":{"description":"bad entity format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["notification"]}},"/admin-api/sessions/{id}":{"delete":{"summary":"delete a user-session","operationId":"user-sessions.delete","responses":{"200":{"description":"entity deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/done"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["user-session"]},"patch":{"summary":"update a user-session with JSON patch or by merging JSON object","operationId":"user-sessions.patch","requestBody":{"description":"the patch to update the user-session or a JSON object","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/patch"},{"$ref":"#/components/schemas/UserSession"}]}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["user-session"]},"put":{"summary":"update a user-session","operationId":"user-sessions.update","requestBody":{"description":"the user-session to update","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSession"}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["user-session"]},"get":{"summary":"read a user-session","operationId":"user-sessions.findById","responses":{"200":{"description":"found entity","content":{"application/json":{"schema":{"type":"object","items":{"$ref":"#/components/schemas/UserSession"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["user-session"]}},"/admin-api/sessions":{"get":{"summary":"read all user-session","operationId":"user-sessions.findAll","responses":{"200":{"description":"success","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserSession"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["user-session"]},"post":{"summary":"creates a user-session","requestBody":{"description":"the user-session to create","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSession"}}}},"operationId":"user-sessions.create","responses":{"201":{"description":"entity created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSession"}}}},"400":{"description":"bad entity format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["user-session"]}},"/admin-api/consumptions/{id}":{"delete":{"summary":"delete a api-key-consumption","operationId":"api-key-consumptions.delete","responses":{"200":{"description":"entity deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/done"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["api-key-consumption"]},"patch":{"summary":"update a api-key-consumption with JSON patch or by merging JSON object","operationId":"api-key-consumptions.patch","requestBody":{"description":"the patch to update the api-key-consumption or a JSON object","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/patch"},{"$ref":"#/components/schemas/ApiKeyConsumption"}]}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["api-key-consumption"]},"put":{"summary":"update a api-key-consumption","operationId":"api-key-consumptions.update","requestBody":{"description":"the api-key-consumption to update","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyConsumption"}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["api-key-consumption"]},"get":{"summary":"read a api-key-consumption","operationId":"api-key-consumptions.findById","responses":{"200":{"description":"found entity","content":{"application/json":{"schema":{"type":"object","items":{"$ref":"#/components/schemas/ApiKeyConsumption"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["api-key-consumption"]}},"/admin-api/consumptions":{"get":{"summary":"read all api-key-consumption","operationId":"api-key-consumptions.findAll","responses":{"200":{"description":"success","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyConsumption"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["api-key-consumption"]},"post":{"summary":"creates a api-key-consumption","requestBody":{"description":"the api-key-consumption to create","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyConsumption"}}}},"operationId":"api-key-consumptions.create","responses":{"201":{"description":"entity created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyConsumption"}}}},"400":{"description":"bad entity format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["api-key-consumption"]}},"/admin-api/audit-events/{id}":{"delete":{"summary":"delete a audit-event","operationId":"audit-events.delete","responses":{"200":{"description":"entity deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/done"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["audit-event"]},"patch":{"summary":"update a audit-event with JSON patch or by merging JSON object","operationId":"audit-events.patch","requestBody":{"description":"the patch to update the audit-event or a JSON object","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/patch"},{"$ref":"#/components/schemas/object"}]}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["audit-event"]},"put":{"summary":"update a audit-event","operationId":"audit-events.update","requestBody":{"description":"the audit-event to update","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/object"}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["audit-event"]},"get":{"summary":"read a audit-event","operationId":"audit-events.findById","responses":{"200":{"description":"found entity","content":{"application/json":{"schema":{"type":"object","items":{"$ref":"#/components/schemas/object"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["audit-event"]}},"/admin-api/audit-events":{"get":{"summary":"read all audit-event","operationId":"audit-events.findAll","responses":{"200":{"description":"success","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/object"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["audit-event"]},"post":{"summary":"creates a audit-event","requestBody":{"description":"the audit-event to create","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/object"}}}},"operationId":"audit-events.create","responses":{"201":{"description":"entity created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/object"}}}},"400":{"description":"bad entity format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["audit-event"]}},"/admin-api/messages/{id}":{"delete":{"summary":"delete a message","operationId":"messages.delete","responses":{"200":{"description":"entity deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/done"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["message"]},"patch":{"summary":"update a message with JSON patch or by merging JSON object","operationId":"messages.patch","requestBody":{"description":"the patch to update the message or a JSON object","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/patch"},{"$ref":"#/components/schemas/Message"}]}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["message"]},"put":{"summary":"update a message","operationId":"messages.update","requestBody":{"description":"the message to update","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["message"]},"get":{"summary":"read a message","operationId":"messages.findById","responses":{"200":{"description":"found entity","content":{"application/json":{"schema":{"type":"object","items":{"$ref":"#/components/schemas/Message"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["message"]}},"/admin-api/messages":{"get":{"summary":"read all message","operationId":"messages.findAll","responses":{"200":{"description":"success","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Message"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["message"]},"post":{"summary":"creates a message","requestBody":{"description":"the message to create","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"operationId":"messages.create","responses":{"201":{"description":"entity created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"400":{"description":"bad entity format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["message"]}},"/admin-api/issues/{id}":{"delete":{"summary":"delete a issue","operationId":"issues.delete","responses":{"200":{"description":"entity deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/done"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["issue"]},"patch":{"summary":"update a issue with JSON patch or by merging JSON object","operationId":"issues.patch","requestBody":{"description":"the patch to update the issue or a JSON object","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/patch"},{"$ref":"#/components/schemas/ApiIssue"}]}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["issue"]},"put":{"summary":"update a issue","operationId":"issues.update","requestBody":{"description":"the issue to update","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiIssue"}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["issue"]},"get":{"summary":"read a issue","operationId":"issues.findById","responses":{"200":{"description":"found entity","content":{"application/json":{"schema":{"type":"object","items":{"$ref":"#/components/schemas/ApiIssue"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["issue"]}},"/admin-api/issues":{"get":{"summary":"read all issue","operationId":"issues.findAll","responses":{"200":{"description":"success","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiIssue"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["issue"]},"post":{"summary":"creates a issue","requestBody":{"description":"the issue to create","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiIssue"}}}},"operationId":"issues.create","responses":{"201":{"description":"entity created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiIssue"}}}},"400":{"description":"bad entity format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["issue"]}},"/admin-api/posts/{id}":{"delete":{"summary":"delete a post","operationId":"posts.delete","responses":{"200":{"description":"entity deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/done"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["post"]},"patch":{"summary":"update a post with JSON patch or by merging JSON object","operationId":"posts.patch","requestBody":{"description":"the patch to update the post or a JSON object","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/patch"},{"$ref":"#/components/schemas/ApiPost"}]}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["post"]},"put":{"summary":"update a post","operationId":"posts.update","requestBody":{"description":"the post to update","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiPost"}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["post"]},"get":{"summary":"read a post","operationId":"posts.findById","responses":{"200":{"description":"found entity","content":{"application/json":{"schema":{"type":"object","items":{"$ref":"#/components/schemas/ApiPost"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["post"]}},"/admin-api/posts":{"get":{"summary":"read all post","operationId":"posts.findAll","responses":{"200":{"description":"success","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiPost"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["post"]},"post":{"summary":"creates a post","requestBody":{"description":"the post to create","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiPost"}}}},"operationId":"posts.create","responses":{"201":{"description":"entity created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiPost"}}}},"400":{"description":"bad entity format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["post"]}},"/admin-api/translations/{id}":{"delete":{"summary":"delete a translation","operationId":"translations.delete","responses":{"200":{"description":"entity deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/done"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["translation"]},"patch":{"summary":"update a translation with JSON patch or by merging JSON object","operationId":"translations.patch","requestBody":{"description":"the patch to update the translation or a JSON object","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/patch"},{"$ref":"#/components/schemas/Translation"}]}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["translation"]},"put":{"summary":"update a translation","operationId":"translations.update","requestBody":{"description":"the translation to update","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Translation"}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["translation"]},"get":{"summary":"read a translation","operationId":"translations.findById","responses":{"200":{"description":"found entity","content":{"application/json":{"schema":{"type":"object","items":{"$ref":"#/components/schemas/Translation"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["translation"]}},"/admin-api/translations":{"get":{"summary":"read all translation","operationId":"translations.findAll","responses":{"200":{"description":"success","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Translation"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["translation"]},"post":{"summary":"creates a translation","requestBody":{"description":"the translation to create","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Translation"}}}},"operationId":"translations.create","responses":{"201":{"description":"entity created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Translation"}}}},"400":{"description":"bad entity format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["translation"]}},"/admin-api/usage-plans/{id}":{"delete":{"summary":"delete a usage-plan","operationId":"usage-plans.delete","responses":{"200":{"description":"entity deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/done"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["usage-plan"]},"patch":{"summary":"update a usage-plan with JSON patch or by merging JSON object","operationId":"usage-plans.patch","requestBody":{"description":"the patch to update the usage-plan or a JSON object","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/patch"},{"$ref":"#/components/schemas/UsagePlan.Admin"},{"$ref":"#/components/schemas/UsagePlan.FreeWithoutQuotas"},{"$ref":"#/components/schemas/UsagePlan.FreeWithQuotas"},{"$ref":"#/components/schemas/UsagePlan.QuotasWithLimits"},{"$ref":"#/components/schemas/UsagePlan.QuotasWithoutLimits"},{"$ref":"#/components/schemas/UsagePlan.PayPerUse"}]}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["usage-plan"]},"put":{"summary":"update a usage-plan","operationId":"usage-plans.update","requestBody":{"description":"the usage-plan to update","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/UsagePlan.Admin"},{"$ref":"#/components/schemas/UsagePlan.FreeWithoutQuotas"},{"$ref":"#/components/schemas/UsagePlan.FreeWithQuotas"},{"$ref":"#/components/schemas/UsagePlan.QuotasWithLimits"},{"$ref":"#/components/schemas/UsagePlan.QuotasWithoutLimits"},{"$ref":"#/components/schemas/UsagePlan.PayPerUse"}]}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["usage-plan"]},"get":{"summary":"read a usage-plan","operationId":"usage-plans.findById","responses":{"200":{"description":"found entity","content":{"application/json":{"schema":{"type":"object","items":{"oneOf":[{"$ref":"#/components/schemas/UsagePlan.Admin"},{"$ref":"#/components/schemas/UsagePlan.FreeWithoutQuotas"},{"$ref":"#/components/schemas/UsagePlan.FreeWithQuotas"},{"$ref":"#/components/schemas/UsagePlan.QuotasWithLimits"},{"$ref":"#/components/schemas/UsagePlan.QuotasWithoutLimits"},{"$ref":"#/components/schemas/UsagePlan.PayPerUse"}]}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["usage-plan"]}},"/admin-api/usage-plans":{"get":{"summary":"read all usage-plan","operationId":"usage-plans.findAll","responses":{"200":{"description":"success","content":{"application/json":{"schema":{"type":"array","items":{"oneOf":[{"$ref":"#/components/schemas/UsagePlan.Admin"},{"$ref":"#/components/schemas/UsagePlan.FreeWithoutQuotas"},{"$ref":"#/components/schemas/UsagePlan.FreeWithQuotas"},{"$ref":"#/components/schemas/UsagePlan.QuotasWithLimits"},{"$ref":"#/components/schemas/UsagePlan.QuotasWithoutLimits"},{"$ref":"#/components/schemas/UsagePlan.PayPerUse"}]}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["usage-plan"]},"post":{"summary":"creates a usage-plan","requestBody":{"description":"the usage-plan to create","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/UsagePlan.Admin"},{"$ref":"#/components/schemas/UsagePlan.FreeWithoutQuotas"},{"$ref":"#/components/schemas/UsagePlan.FreeWithQuotas"},{"$ref":"#/components/schemas/UsagePlan.QuotasWithLimits"},{"$ref":"#/components/schemas/UsagePlan.QuotasWithoutLimits"},{"$ref":"#/components/schemas/UsagePlan.PayPerUse"}]}}}},"operationId":"usage-plans.create","responses":{"201":{"description":"entity created","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/UsagePlan.Admin"},{"$ref":"#/components/schemas/UsagePlan.FreeWithoutQuotas"},{"$ref":"#/components/schemas/UsagePlan.FreeWithQuotas"},{"$ref":"#/components/schemas/UsagePlan.QuotasWithLimits"},{"$ref":"#/components/schemas/UsagePlan.QuotasWithoutLimits"},{"$ref":"#/components/schemas/UsagePlan.PayPerUse"}]}}}},"400":{"description":"bad entity format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["usage-plan"]}},"/admin-api/subscription-demands/{id}":{"delete":{"summary":"delete a subscription-demand","operationId":"subscription-demands.delete","responses":{"200":{"description":"entity deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/done"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["subscription-demand"]},"patch":{"summary":"update a subscription-demand with JSON patch or by merging JSON object","operationId":"subscription-demands.patch","requestBody":{"description":"the patch to update the subscription-demand or a JSON object","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/patch"},{"$ref":"#/components/schemas/SubscriptionDemand"}]}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["subscription-demand"]},"put":{"summary":"update a subscription-demand","operationId":"subscription-demands.update","requestBody":{"description":"the subscription-demand to update","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionDemand"}}}},"responses":{"204":{"description":"No Content"},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["subscription-demand"]},"get":{"summary":"read a subscription-demand","operationId":"subscription-demands.findById","responses":{"200":{"description":"found entity","content":{"application/json":{"schema":{"type":"object","items":{"$ref":"#/components/schemas/SubscriptionDemand"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"tags":["subscription-demand"]}},"/admin-api/subscription-demands":{"get":{"summary":"read all subscription-demand","operationId":"subscription-demands.findAll","responses":{"200":{"description":"success","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SubscriptionDemand"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["subscription-demand"]},"post":{"summary":"creates a subscription-demand","requestBody":{"description":"the subscription-demand to create","required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionDemand"}}}},"operationId":"subscription-demands.create","responses":{"201":{"description":"entity created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionDemand"}}}},"400":{"description":"bad entity format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"tags":["subscription-demand"]}},"/integration-api/apis":{"get":{"summary":"get all public apis for integration","operationId":"integration-api.findallapis","responses":{"200":{"description":"List of public APIs","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Api"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"header","name":"X-Personal-Token","required":true}],"tags":["integration-apis"]}},"/integration-api/{teamId}":{"get":{"summary":"get all teams for integration","operationId":"integration-api.findallateams","responses":{"200":{"description":"List of teams","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Team"}}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"header","name":"X-Personal-Token","required":true},{"schema":{"type":"string"},"in":"path","name":"teamId","required":true}],"tags":["integration-apis"]}},"/integration-api/{teamId}/{apiId}/complete":{"get":{"summary":"get complete API for integration","operationId":"integration-api.findcompleteapi","responses":{"200":{"description":"Complete API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Team"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"header","name":"X-Personal-Token","required":true},{"schema":{"type":"string"},"in":"path","name":"teamId","required":true},{"schema":{"type":"string"},"in":"path","name":"apiId","required":true}],"tags":["integration-apis"]}},"/integration-api/{teamId}/{apiId}/description":{"get":{"summary":"get API description","operationId":"integration-api.findapidescription","responses":{"200":{"description":"API description","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Api"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"header","name":"X-Personal-Token","required":true},{"schema":{"type":"string"},"in":"path","name":"teamId","required":true},{"schema":{"type":"string"},"in":"path","name":"apiId","required":true}],"tags":["integration-apis"]}},"/integration-api/{teamId}/{apiId}/plans":{"get":{"summary":"get API plans","operationId":"integration-api.findapiplans","responses":{"200":{"description":"API plans","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Api"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"header","name":"X-Personal-Token","required":true},{"schema":{"type":"string"},"in":"path","name":"teamId","required":true},{"schema":{"type":"string"},"in":"path","name":"apiId","required":true}],"tags":["integration-apis"]}},"/integration-api/{teamId}/{apiId}/documentation":{"get":{"summary":"get API documentation","operationId":"integration-api.findapidocumentation","responses":{"200":{"description":"API documentation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Api"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"header","name":"X-Personal-Token","required":true},{"schema":{"type":"string"},"in":"path","name":"teamId","required":true},{"schema":{"type":"string"},"in":"path","name":"apiId","required":true}],"tags":["integration-apis"]}},"/integration-api/{teamId}/{apiId}/apidoc":{"get":{"summary":"get API description","operationId":"integration-api.findapidoc","responses":{"200":{"description":"API doc (swagger)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Api"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"header","name":"X-Personal-Token","required":true},{"schema":{"type":"string"},"in":"path","name":"teamId","required":true},{"schema":{"type":"string"},"in":"path","name":"apiId","required":true}],"tags":["integration-apis"]}},"/integration-api/{teamId}/{apiId}":{"get":{"summary":"get API","operationId":"integration-api.findapi","responses":{"200":{"description":"API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Api"}}}},"401":{"description":"unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}},"404":{"description":"entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error"}}}}},"parameters":[{"schema":{"type":"string"},"in":"header","name":"X-Personal-Token","required":true},{"schema":{"type":"string"},"in":"path","name":"teamId","required":true},{"schema":{"type":"string"},"in":"path","name":"apiId","required":true}],"tags":["integration-apis"]}},"/admin-api/search":{"post":{"summary":"search with GraphQL","operationId":"GraphQL.search","requestBody":{"description":"GraphQL query","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/GraphQLQuery"}]}}}},"responses":{"200":{"description":"successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GraphQLResponse"}}}}},"tags":["GraphQL"]}},"/admin-api/schema":{"post":{"summary":"search with GraphQL","operationId":"GraphQL.schema","requestBody":{"description":"get GraphQL schema","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/GraphQLQuery"}]}}}},"responses":{"200":{"description":"successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GraphQLSchema"}}}}},"tags":["GraphQL"]}},"/admin-api/credentials/{token}/plans":{"get":{"summary":"get apikey from integration token","tags":["Integration ApiKey"],"parameters":[{"in":"path","name":"token","schema":{"type":"string"},"required":true,"description":"integration token"}],"responses":{"200":{"description":"success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OtoroshiApiKey"}}}}}}}}}}},"docusaurus-theme-redoc":{"theme-redoc":{"lightTheme":{"typography":{"fontFamily":"var(--ifm-font-family-base)","fontSize":"var(--ifm-font-size-base)","lineHeight":"var(--ifm-line-height-base)","fontWeightLight":"var(--ifm-font-weight-light)","fontWeightRegular":"var(--ifm-font-weight-base)","fontWeightBold":"var(--ifm-font-weight-bold)","headings":{"fontFamily":"var(--ifm-heading-font-family)","fontWeight":"var(--ifm-heading-font-weight)","lineHeight":"var(--ifm-heading-line-height)"},"code":{"fontFamily":"var(--ifm-font-family-monospace)","lineHeight":"var(--ifm-pre-line-height)"}},"sidebar":{"width":"300px","backgroundColor":"#ffffff"},"rightPanel":{"backgroundColor":"#303846"},"colors":{"primary":{"main":"#25c2a0"}},"theme":{"prism":{"additionalLanguages":["scala"]}}},"darkTheme":{"typography":{"fontFamily":"var(--ifm-font-family-base)","fontSize":"var(--ifm-font-size-base)","lineHeight":"var(--ifm-line-height-base)","fontWeightLight":"var(--ifm-font-weight-light)","fontWeightRegular":"var(--ifm-font-weight-base)","fontWeightBold":"var(--ifm-font-weight-bold)","headings":{"fontFamily":"var(--ifm-heading-font-family)","fontWeight":"var(--ifm-heading-font-weight)","lineHeight":"var(--ifm-heading-line-height)"},"code":{"fontFamily":"var(--ifm-font-family-monospace)","lineHeight":"var(--ifm-pre-line-height)"}},"sidebar":{"width":"300px","backgroundColor":"rgb(24, 25, 26)","textColor":"#f5f6f7","arrow":{"color":"#f5f6f7"}},"colors":{"text":{"primary":"#f5f6f7","secondary":"rgba(255, 255, 255, 1)"},"gray":{"50":"#FAFAFA","100":"#F5F5F5"},"border":{"dark":"#ffffff","light":"rgba(0,0,0, 0.1)"},"primary":{"main":"#25c2a0"}},"schema":{"nestedBackground":"rgb(24, 25, 26)","typeNameColor":"rgba(255, 255, 255, 1)","typeTitleColor":"rgba(255, 255, 255, 1)"},"theme":{"prism":{"additionalLanguages":["scala"]}}},"options":{"scrollYOffset":"nav.navbar","expandSingleSchemaField":true,"menuToggle":true,"suppressWarnings":true}}}}'),i=JSON.parse('{"defaultLocale":"en","locales":["en"],"path":"i18n","currentLocale":"en","localeConfigs":{"en":{"label":"English","direction":"ltr","htmlLang":"en","calendar":"gregory","path":"en"}}}');var s=n(7529);const l=JSON.parse('{"docusaurusVersion":"3.2.1","siteVersion":"17.0.0","pluginVersions":{"docusaurus-plugin-content-docs":{"type":"package","name":"@docusaurus/plugin-content-docs","version":"3.2.1"},"docusaurus-plugin-content-blog":{"type":"package","name":"@docusaurus/plugin-content-blog","version":"3.2.1"},"docusaurus-plugin-content-pages":{"type":"package","name":"@docusaurus/plugin-content-pages","version":"3.2.1"},"docusaurus-plugin-sitemap":{"type":"package","name":"@docusaurus/plugin-sitemap","version":"3.2.1"},"docusaurus-plugin-redoc":{"type":"package","name":"docusaurus-plugin-redoc","version":"2.0.0"},"docusaurus-theme-classic":{"type":"package","name":"@docusaurus/theme-classic","version":"3.2.1"},"docusaurus-theme-redoc":{"type":"package","name":"docusaurus-theme-redoc","version":"2.0.0"},"@easyops-cn/docusaurus-search-local":{"type":"package","name":"@easyops-cn/docusaurus-search-local","version":"0.37.4"}}}');var c=n(5893);const u={siteConfig:a.default,siteMetadata:l,globalData:o,i18n:i,codeTranslations:s},p=r.createContext(u);function d(e){let{children:t}=e;return(0,c.jsx)(p.Provider,{value:u,children:t})}},4763:(e,t,n)=>{"use strict";n.d(t,{Z:()=>h});var r=n(7294),a=n(412),o=n(5742),i=n(8780),s=n(8346),l=n(226),c=n(5893);function u(e){let{error:t,tryAgain:n}=e;return(0,c.jsxs)("div",{style:{display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"flex-start",minHeight:"100vh",width:"100%",maxWidth:"80ch",fontSize:"20px",margin:"0 auto",padding:"1rem"},children:[(0,c.jsx)("h1",{style:{fontSize:"3rem"},children:"This page crashed"}),(0,c.jsx)("button",{type:"button",onClick:n,style:{margin:"1rem 0",fontSize:"2rem",cursor:"pointer",borderRadius:20,padding:"1rem"},children:"Try again"}),(0,c.jsx)(p,{error:t})]})}function p(e){let{error:t}=e;const n=(0,i.getErrorCausalChain)(t).map((e=>e.message)).join("\n\nCause:\n");return(0,c.jsx)("p",{style:{whiteSpace:"pre-wrap"},children:n})}function d(e){let{children:t}=e;return(0,c.jsx)(l.z,{value:{plugin:{name:"docusaurus-core-error-boundary",id:"default"}},children:t})}function f(e){let{error:t,tryAgain:n}=e;return(0,c.jsx)(d,{children:(0,c.jsxs)(h,{fallback:()=>(0,c.jsx)(u,{error:t,tryAgain:n}),children:[(0,c.jsx)(o.Z,{children:(0,c.jsx)("title",{children:"Page Error"})}),(0,c.jsx)(s.Z,{children:(0,c.jsx)(u,{error:t,tryAgain:n})})]})})}const m=e=>(0,c.jsx)(f,{...e});class h extends r.Component{constructor(e){super(e),this.state={error:null}}componentDidCatch(e){a.default.canUseDOM&&this.setState({error:e})}render(){const{children:e}=this.props,{error:t}=this.state;if(t){const e={error:t,tryAgain:()=>this.setState({error:null})};return(this.props.fallback??m)(e)}return e??null}}},412:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>a});const r="undefined"!=typeof window&&"document"in window&&"createElement"in window.document,a={canUseDOM:r,canUseEventListeners:r&&("addEventListener"in window||"attachEvent"in window),canUseIntersectionObserver:r&&"IntersectionObserver"in window,canUseViewport:r&&"screen"in window}},5742:(e,t,n)=>{"use strict";n.d(t,{Z:()=>o});n(7294);var r=n(405),a=n(5893);function o(e){return(0,a.jsx)(r.ql,{...e})}},3692:(e,t,n)=>{"use strict";n.d(t,{Z:()=>f});var r=n(7294),a=n(3727),o=n(8780),i=n(2263),s=n(3919),l=n(412),c=n(8138),u=n(4996),p=n(5893);function d(e,t){let{isNavLink:n,to:d,href:f,activeClassName:m,isActive:h,"data-noBrokenLinkCheck":g,autoAddBaseUrl:y=!0,...b}=e;const{siteConfig:{trailingSlash:v,baseUrl:k}}=(0,i.Z)(),{withBaseUrl:w}=(0,u.C)(),S=(0,c.Z)(),x=(0,r.useRef)(null);(0,r.useImperativeHandle)(t,(()=>x.current));const E=d||f;const _=(0,s.Z)(E),C=E?.replace("pathname://","");let j=void 0!==C?(A=C,y&&(e=>e.startsWith("/"))(A)?w(A):A):void 0;var A;j&&_&&(j=(0,o.applyTrailingSlash)(j,{trailingSlash:v,baseUrl:k}));const T=(0,r.useRef)(!1),P=n?a.OL:a.rU,I=l.default.canUseIntersectionObserver,N=(0,r.useRef)(),O=()=>{T.current||null==j||(window.docusaurus.preload(j),T.current=!0)};(0,r.useEffect)((()=>(!I&&_&&null!=j&&window.docusaurus.prefetch(j),()=>{I&&N.current&&N.current.disconnect()})),[N,j,I,_]);const $=j?.startsWith("#")??!1,L=!b.target||"_self"===b.target,R=!j||!_||!L||$;return g||!$&&R||S.collectLink(j),b.id&&S.collectAnchor(b.id),R?(0,p.jsx)("a",{ref:x,href:j,...E&&!_&&{target:"_blank",rel:"noopener noreferrer"},...b}):(0,p.jsx)(P,{...b,onMouseEnter:O,onTouchStart:O,innerRef:e=>{x.current=e,I&&e&&_&&(N.current=new window.IntersectionObserver((t=>{t.forEach((t=>{e===t.target&&(t.isIntersecting||t.intersectionRatio>0)&&(N.current.unobserve(e),N.current.disconnect(),null!=j&&window.docusaurus.prefetch(j))}))})),N.current.observe(e))},to:j,...n&&{isActive:h,activeClassName:m}})}const f=r.forwardRef(d)},5999:(e,t,n)=>{"use strict";n.d(t,{Z:()=>c,I:()=>l});var r=n(7294),a=n(5893);function o(e,t){const n=e.split(/(\{\w+\})/).map(((e,n)=>{if(n%2==1){const n=t?.[e.slice(1,-1)];if(void 0!==n)return n}return e}));return n.some((e=>(0,r.isValidElement)(e)))?n.map(((e,t)=>(0,r.isValidElement)(e)?r.cloneElement(e,{key:t}):e)).filter((e=>""!==e)):n.join("")}var i=n(7529);function s(e){let{id:t,message:n}=e;if(void 0===t&&void 0===n)throw new Error("Docusaurus translation declarations must have at least a translation id or a default translation message");return i[t??n]??n??t}function l(e,t){let{message:n,id:r}=e;return o(s({message:n,id:r}),t)}function c(e){let{children:t,id:n,values:r}=e;if(t&&"string"!=typeof t)throw console.warn("Illegal children",t),new Error("The Docusaurus component only accept simple string values");const i=s({message:t,id:n});return(0,a.jsx)(a.Fragment,{children:o(i,r)})}},9935:(e,t,n)=>{"use strict";n.d(t,{m:()=>r});const r="default"},3919:(e,t,n)=>{"use strict";function r(e){return/^(?:\w*:|\/\/)/.test(e)}function a(e){return void 0!==e&&!r(e)}n.d(t,{Z:()=>a,b:()=>r})},4996:(e,t,n)=>{"use strict";n.d(t,{C:()=>i,Z:()=>s});var r=n(7294),a=n(2263),o=n(3919);function i(){const{siteConfig:{baseUrl:e,url:t}}=(0,a.Z)(),n=(0,r.useCallback)(((n,r)=>function(e,t,n,r){let{forcePrependBaseUrl:a=!1,absolute:i=!1}=void 0===r?{}:r;if(!n||n.startsWith("#")||(0,o.b)(n))return n;if(a)return t+n.replace(/^\//,"");if(n===t.replace(/\/$/,""))return t;const s=n.startsWith(t)?n:t+n.replace(/^\//,"");return i?e+s:s}(t,e,n,r)),[t,e]);return{withBaseUrl:n}}function s(e,t){void 0===t&&(t={});const{withBaseUrl:n}=i();return n(e,t)}},8138:(e,t,n)=>{"use strict";n.d(t,{Z:()=>i});var r=n(7294);n(5893);const a=r.createContext({collectAnchor:()=>{},collectLink:()=>{}}),o=()=>(0,r.useContext)(a);function i(){return o()}},2263:(e,t,n)=>{"use strict";n.d(t,{Z:()=>o});var r=n(7294),a=n(8940);function o(){return(0,r.useContext)(a._)}},8084:(e,t,n)=>{"use strict";n.d(t,{OD:()=>o,eZ:()=>i});var r=n(2263),a=n(9935);function o(e,t){void 0===t&&(t={});const n=function(){const{globalData:e}=(0,r.Z)();return e}()[e];if(!n&&t.failfast)throw new Error(`Docusaurus plugin global data not found for "${e}" plugin.`);return n}function i(e,t,n){void 0===t&&(t=a.m),void 0===n&&(n={});const r=o(e),i=r?.[t];if(!i&&n.failfast)throw new Error(`Docusaurus plugin global data not found for "${e}" plugin with id "${t}".`);return i}},2389:(e,t,n)=>{"use strict";n.d(t,{Z:()=>o});var r=n(7294),a=n(8934);function o(){return(0,r.useContext)(a._)}},469:(e,t,n)=>{"use strict";n.d(t,{Z:()=>a});var r=n(7294);const a=n(412).default.canUseDOM?r.useLayoutEffect:r.useEffect},9670:(e,t,n)=>{"use strict";n.d(t,{Z:()=>a});const r=e=>"object"==typeof e&&!!e&&Object.keys(e).length>0;function a(e){const t={};return function e(n,a){Object.entries(n).forEach((n=>{let[o,i]=n;const s=a?`${a}.${o}`:o;r(i)?e(i,s):t[s]=i}))}(e),t}},226:(e,t,n)=>{"use strict";n.d(t,{_:()=>o,z:()=>i});var r=n(7294),a=n(5893);const o=r.createContext(null);function i(e){let{children:t,value:n}=e;const i=r.useContext(o),s=(0,r.useMemo)((()=>function(e){let{parent:t,value:n}=e;if(!t){if(!n)throw new Error("Unexpected: no Docusaurus route context found");if(!("plugin"in n))throw new Error("Unexpected: Docusaurus topmost route context has no `plugin` attribute");return n}const r={...t.data,...n?.data};return{plugin:t.plugin,data:r}}({parent:i,value:n})),[i,n]);return(0,a.jsx)(o.Provider,{value:s,children:t})}},4104:(e,t,n)=>{"use strict";n.d(t,{Iw:()=>f,gA:()=>u,_r:()=>l,Jo:()=>m,zh:()=>c,yW:()=>d,gB:()=>p});var r=n(6550),a=n(8084);const o=e=>e.versions.find((e=>e.isLast));function i(e,t){const n=function(e,t){const n=o(e);return[...e.versions.filter((e=>e!==n)),n].find((e=>!!(0,r.LX)(t,{path:e.path,exact:!1,strict:!1})))}(e,t),a=n?.docs.find((e=>!!(0,r.LX)(t,{path:e.path,exact:!0,strict:!1})));return{activeVersion:n,activeDoc:a,alternateDocVersions:a?function(t){const n={};return e.versions.forEach((e=>{e.docs.forEach((r=>{r.id===t&&(n[e.name]=r)}))})),n}(a.id):{}}}const s={},l=()=>(0,a.OD)("docusaurus-plugin-content-docs")??s,c=e=>{try{return(0,a.eZ)("docusaurus-plugin-content-docs",e,{failfast:!0})}catch(t){throw new Error("You are using a feature of the Docusaurus docs plugin, but this plugin does not seem to be enabled"+("Default"===e?"":` (pluginId=${e}`),{cause:t})}};function u(e){void 0===e&&(e={});const t=l(),{pathname:n}=(0,r.TH)();return function(e,t,n){void 0===n&&(n={});const a=Object.entries(e).sort(((e,t)=>t[1].path.localeCompare(e[1].path))).find((e=>{let[,n]=e;return!!(0,r.LX)(t,{path:n.path,exact:!1,strict:!1})})),o=a?{pluginId:a[0],pluginData:a[1]}:void 0;if(!o&&n.failfast)throw new Error(`Can't find active docs plugin for "${t}" pathname, while it was expected to be found. Maybe you tried to use a docs feature that can only be used on a docs-related page? Existing docs plugin paths are: ${Object.values(e).map((e=>e.path)).join(", ")}`);return o}(t,n,e)}function p(e){return c(e).versions}function d(e){const t=c(e);return o(t)}function f(e){const t=c(e),{pathname:n}=(0,r.TH)();return i(t,n)}function m(e){const t=c(e),{pathname:n}=(0,r.TH)();return function(e,t){const n=o(e);return{latestDocSuggestion:i(e,t).alternateDocVersions[n.name],latestVersionSuggestion:n}}(t,n)}},8320:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(4865),a=n.n(r);a().configure({showSpinner:!1});const o={onRouteUpdate(e){let{location:t,previousLocation:n}=e;if(n&&t.pathname!==n.pathname){const e=window.setTimeout((()=>{a().start()}),200);return()=>window.clearTimeout(e)}},onRouteDidUpdate(){a().done()}}},3310:(e,t,n)=>{"use strict";n.r(t);var r=n(4965),a=n(6809);!function(e){const{themeConfig:{prism:t}}=a.default,{additionalLanguages:r}=t;globalThis.Prism=e,r.forEach((e=>{"php"===e&&n(6854),n(1486)(`./prism-${e}`)})),delete globalThis.Prism}(r.p1)},2503:(e,t,n)=>{"use strict";n.d(t,{Z:()=>u});n(7294);var r=n(6905),a=n(5999),o=n(6668),i=n(3692),s=n(8138);const l={anchorWithStickyNavbar:"anchorWithStickyNavbar_LWe7",anchorWithHideOnScrollNavbar:"anchorWithHideOnScrollNavbar_WYt5"};var c=n(5893);function u(e){let{as:t,id:n,...u}=e;const p=(0,s.Z)(),{navbar:{hideOnScroll:d}}=(0,o.L)();if("h1"===t||!n)return(0,c.jsx)(t,{...u,id:void 0});p.collectAnchor(n);const f=(0,a.I)({id:"theme.common.headingLinkTitle",message:"Direct link to {heading}",description:"Title for link to heading"},{heading:"string"==typeof u.children?u.children:n});return(0,c.jsxs)(t,{...u,className:(0,r.Z)("anchor",d?l.anchorWithHideOnScrollNavbar:l.anchorWithStickyNavbar,u.className),id:n,children:[u.children,(0,c.jsx)(i.Z,{className:"hash-link",to:`#${n}`,"aria-label":f,title:f,children:"\u200b"})]})}},9471:(e,t,n)=>{"use strict";n.d(t,{Z:()=>o});n(7294);const r={iconExternalLink:"iconExternalLink_nPIU"};var a=n(5893);function o(e){let{width:t=13.5,height:n=13.5}=e;return(0,a.jsx)("svg",{width:t,height:n,"aria-hidden":"true",viewBox:"0 0 24 24",className:r.iconExternalLink,children:(0,a.jsx)("path",{fill:"currentColor",d:"M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"})})}},8346:(e,t,n)=>{"use strict";n.d(t,{Z:()=>Lt});var r=n(7294),a=n(6905),o=n(4763),i=n(1944),s=n(6550),l=n(5999),c=n(5936),u=n(5893);const p="__docusaurus_skipToContent_fallback";function d(e){e.setAttribute("tabindex","-1"),e.focus(),e.removeAttribute("tabindex")}function f(){const e=(0,r.useRef)(null),{action:t}=(0,s.k6)(),n=(0,r.useCallback)((e=>{e.preventDefault();const t=document.querySelector("main:first-of-type")??document.getElementById(p);t&&d(t)}),[]);return(0,c.S)((n=>{let{location:r}=n;e.current&&!r.hash&&"PUSH"===t&&d(e.current)})),{containerRef:e,onClick:n}}const m=(0,l.I)({id:"theme.common.skipToMainContent",description:"The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation",message:"Skip to main content"});function h(e){const t=e.children??m,{containerRef:n,onClick:r}=f();return(0,u.jsx)("div",{ref:n,role:"region","aria-label":m,children:(0,u.jsx)("a",{...e,href:`#${p}`,onClick:r,children:t})})}var g=n(5281),y=n(9727);const b={skipToContent:"skipToContent_fXgn"};function v(){return(0,u.jsx)(h,{className:b.skipToContent})}var k=n(6668),w=n(9689);function S(e){let{width:t=21,height:n=21,color:r="currentColor",strokeWidth:a=1.2,className:o,...i}=e;return(0,u.jsx)("svg",{viewBox:"0 0 15 15",width:t,height:n,...i,children:(0,u.jsx)("g",{stroke:r,strokeWidth:a,children:(0,u.jsx)("path",{d:"M.75.75l13.5 13.5M14.25.75L.75 14.25"})})})}const x={closeButton:"closeButton_CVFx"};function E(e){return(0,u.jsx)("button",{type:"button","aria-label":(0,l.I)({id:"theme.AnnouncementBar.closeButtonAriaLabel",message:"Close",description:"The ARIA label for close button of announcement bar"}),...e,className:(0,a.Z)("clean-btn close",x.closeButton,e.className),children:(0,u.jsx)(S,{width:14,height:14,strokeWidth:3.1})})}const _={content:"content_knG7"};function C(e){const{announcementBar:t}=(0,k.L)(),{content:n}=t;return(0,u.jsx)("div",{...e,className:(0,a.Z)(_.content,e.className),dangerouslySetInnerHTML:{__html:n}})}const j={announcementBar:"announcementBar_mb4j",announcementBarPlaceholder:"announcementBarPlaceholder_vyr4",announcementBarClose:"announcementBarClose_gvF7",announcementBarContent:"announcementBarContent_xLdY"};function A(){const{announcementBar:e}=(0,k.L)(),{isActive:t,close:n}=(0,w.nT)();if(!t)return null;const{backgroundColor:r,textColor:a,isCloseable:o}=e;return(0,u.jsxs)("div",{className:j.announcementBar,style:{backgroundColor:r,color:a},role:"banner",children:[o&&(0,u.jsx)("div",{className:j.announcementBarPlaceholder}),(0,u.jsx)(C,{className:j.announcementBarContent}),o&&(0,u.jsx)(E,{onClick:n,className:j.announcementBarClose})]})}var T=n(3163),P=n(2466);var I=n(902),N=n(3102);const O=r.createContext(null);function $(e){let{children:t}=e;const n=function(){const e=(0,T.e)(),t=(0,N.HY)(),[n,a]=(0,r.useState)(!1),o=null!==t.component,i=(0,I.D9)(o);return(0,r.useEffect)((()=>{o&&!i&&a(!0)}),[o,i]),(0,r.useEffect)((()=>{o?e.shown||a(!0):a(!1)}),[e.shown,o]),(0,r.useMemo)((()=>[n,a]),[n])}();return(0,u.jsx)(O.Provider,{value:n,children:t})}function L(e){if(e.component){const t=e.component;return(0,u.jsx)(t,{...e.props})}}function R(){const e=(0,r.useContext)(O);if(!e)throw new I.i6("NavbarSecondaryMenuDisplayProvider");const[t,n]=e,a=(0,r.useCallback)((()=>n(!1)),[n]),o=(0,N.HY)();return(0,r.useMemo)((()=>({shown:t,hide:a,content:L(o)})),[a,o,t])}function D(e){let{header:t,primaryMenu:n,secondaryMenu:r}=e;const{shown:o}=R();return(0,u.jsxs)("div",{className:"navbar-sidebar",children:[t,(0,u.jsxs)("div",{className:(0,a.Z)("navbar-sidebar__items",{"navbar-sidebar__items--show-secondary":o}),children:[(0,u.jsx)("div",{className:"navbar-sidebar__item menu",children:n}),(0,u.jsx)("div",{className:"navbar-sidebar__item menu",children:r})]})]})}var F=n(2949),M=n(2389);function z(e){return(0,u.jsx)("svg",{viewBox:"0 0 24 24",width:24,height:24,...e,children:(0,u.jsx)("path",{fill:"currentColor",d:"M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"})})}function B(e){return(0,u.jsx)("svg",{viewBox:"0 0 24 24",width:24,height:24,...e,children:(0,u.jsx)("path",{fill:"currentColor",d:"M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"})})}const U={toggle:"toggle_vylO",toggleButton:"toggleButton_gllP",darkToggleIcon:"darkToggleIcon_wfgR",lightToggleIcon:"lightToggleIcon_pyhR",toggleButtonDisabled:"toggleButtonDisabled_aARS"};function q(e){let{className:t,buttonClassName:n,value:r,onChange:o}=e;const i=(0,M.Z)(),s=(0,l.I)({message:"Switch between dark and light mode (currently {mode})",id:"theme.colorToggle.ariaLabel",description:"The ARIA label for the navbar color mode toggle"},{mode:"dark"===r?(0,l.I)({message:"dark mode",id:"theme.colorToggle.ariaLabel.mode.dark",description:"The name for the dark color mode"}):(0,l.I)({message:"light mode",id:"theme.colorToggle.ariaLabel.mode.light",description:"The name for the light color mode"})});return(0,u.jsx)("div",{className:(0,a.Z)(U.toggle,t),children:(0,u.jsxs)("button",{className:(0,a.Z)("clean-btn",U.toggleButton,!i&&U.toggleButtonDisabled,n),type:"button",onClick:()=>o("dark"===r?"light":"dark"),disabled:!i,title:s,"aria-label":s,"aria-live":"polite",children:[(0,u.jsx)(z,{className:(0,a.Z)(U.toggleIcon,U.lightToggleIcon)}),(0,u.jsx)(B,{className:(0,a.Z)(U.toggleIcon,U.darkToggleIcon)})]})})}const Q=r.memo(q),H={darkNavbarColorModeToggle:"darkNavbarColorModeToggle_X3D1"};function W(e){let{className:t}=e;const n=(0,k.L)().navbar.style,r=(0,k.L)().colorMode.disableSwitch,{colorMode:a,setColorMode:o}=(0,F.I)();return r?null:(0,u.jsx)(Q,{className:t,buttonClassName:"dark"===n?H.darkNavbarColorModeToggle:void 0,value:a,onChange:o})}var V=n(1327);function Z(){return(0,u.jsx)(V.Z,{className:"navbar__brand",imageClassName:"navbar__logo",titleClassName:"navbar__title text--truncate"})}function K(){const e=(0,T.e)();return(0,u.jsx)("button",{type:"button","aria-label":(0,l.I)({id:"theme.docs.sidebar.closeSidebarButtonAriaLabel",message:"Close navigation bar",description:"The ARIA label for close button of mobile sidebar"}),className:"clean-btn navbar-sidebar__close",onClick:()=>e.toggle(),children:(0,u.jsx)(S,{color:"var(--ifm-color-emphasis-600)"})})}function G(){return(0,u.jsxs)("div",{className:"navbar-sidebar__brand",children:[(0,u.jsx)(Z,{}),(0,u.jsx)(W,{className:"margin-right--md"}),(0,u.jsx)(K,{})]})}var Y=n(3692),J=n(4996),X=n(3919);function ee(e,t){return void 0!==e&&void 0!==t&&new RegExp(e,"gi").test(t)}var te=n(9471);function ne(e){let{activeBasePath:t,activeBaseRegex:n,to:r,href:a,label:o,html:i,isDropdownLink:s,prependBaseUrlToHref:l,...c}=e;const p=(0,J.Z)(r),d=(0,J.Z)(t),f=(0,J.Z)(a,{forcePrependBaseUrl:!0}),m=o&&a&&!(0,X.Z)(a),h=i?{dangerouslySetInnerHTML:{__html:i}}:{children:(0,u.jsxs)(u.Fragment,{children:[o,m&&(0,u.jsx)(te.Z,{...s&&{width:12,height:12}})]})};return a?(0,u.jsx)(Y.Z,{href:l?f:a,...c,...h}):(0,u.jsx)(Y.Z,{to:p,isNavLink:!0,...(t||n)&&{isActive:(e,t)=>n?ee(n,t.pathname):t.pathname.startsWith(d)},...c,...h})}function re(e){let{className:t,isDropdownItem:n=!1,...r}=e;const o=(0,u.jsx)(ne,{className:(0,a.Z)(n?"dropdown__link":"navbar__item navbar__link",t),isDropdownLink:n,...r});return n?(0,u.jsx)("li",{children:o}):o}function ae(e){let{className:t,isDropdownItem:n,...r}=e;return(0,u.jsx)("li",{className:"menu__list-item",children:(0,u.jsx)(ne,{className:(0,a.Z)("menu__link",t),...r})})}function oe(e){let{mobile:t=!1,position:n,...r}=e;const a=t?ae:re;return(0,u.jsx)(a,{...r,activeClassName:r.activeClassName??(t?"menu__link--active":"navbar__link--active")})}var ie=n(6043),se=n(8596),le=n(2263);const ce={dropdownNavbarItemMobile:"dropdownNavbarItemMobile_S0Fm"};function ue(e,t){return e.some((e=>function(e,t){return!!(0,se.Mg)(e.to,t)||!!ee(e.activeBaseRegex,t)||!(!e.activeBasePath||!t.startsWith(e.activeBasePath))}(e,t)))}function pe(e){let{items:t,position:n,className:o,onClick:i,...s}=e;const l=(0,r.useRef)(null),[c,p]=(0,r.useState)(!1);return(0,r.useEffect)((()=>{const e=e=>{l.current&&!l.current.contains(e.target)&&p(!1)};return document.addEventListener("mousedown",e),document.addEventListener("touchstart",e),document.addEventListener("focusin",e),()=>{document.removeEventListener("mousedown",e),document.removeEventListener("touchstart",e),document.removeEventListener("focusin",e)}}),[l]),(0,u.jsxs)("div",{ref:l,className:(0,a.Z)("navbar__item","dropdown","dropdown--hoverable",{"dropdown--right":"right"===n,"dropdown--show":c}),children:[(0,u.jsx)(ne,{"aria-haspopup":"true","aria-expanded":c,role:"button",href:s.to?void 0:"#",className:(0,a.Z)("navbar__link",o),...s,onClick:s.to?void 0:e=>e.preventDefault(),onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),p(!c))},children:s.children??s.label}),(0,u.jsx)("ul",{className:"dropdown__menu",children:t.map(((e,t)=>(0,r.createElement)(Ve,{isDropdownItem:!0,activeClassName:"dropdown__link--active",...e,key:t})))})]})}function de(e){let{items:t,className:n,position:o,onClick:i,...l}=e;const c=function(){const{siteConfig:{baseUrl:e}}=(0,le.Z)(),{pathname:t}=(0,s.TH)();return t.replace(e,"/")}(),p=ue(t,c),{collapsed:d,toggleCollapsed:f,setCollapsed:m}=(0,ie.u)({initialState:()=>!p});return(0,r.useEffect)((()=>{p&&m(!p)}),[c,p,m]),(0,u.jsxs)("li",{className:(0,a.Z)("menu__list-item",{"menu__list-item--collapsed":d}),children:[(0,u.jsx)(ne,{role:"button",className:(0,a.Z)(ce.dropdownNavbarItemMobile,"menu__link menu__link--sublist menu__link--sublist-caret",n),...l,onClick:e=>{e.preventDefault(),f()},children:l.children??l.label}),(0,u.jsx)(ie.z,{lazy:!0,as:"ul",className:"menu__list",collapsed:d,children:t.map(((e,t)=>(0,r.createElement)(Ve,{mobile:!0,isDropdownItem:!0,onClick:i,activeClassName:"menu__link--active",...e,key:t})))})]})}function fe(e){let{mobile:t=!1,...n}=e;const r=t?de:pe;return(0,u.jsx)(r,{...n})}var me=n(4711);function he(e){let{width:t=20,height:n=20,...r}=e;return(0,u.jsx)("svg",{viewBox:"0 0 24 24",width:t,height:n,"aria-hidden":!0,...r,children:(0,u.jsx)("path",{fill:"currentColor",d:"M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z"})})}const ge="iconLanguage_nlXk";var ye=n(1029),be=n(6010),ve=n(373),ke=n(4104),we=n(22),Se=n(8202),xe=n(3926),Ee=n(1073),_e=n(2539),Ce=n(726);const je='',Ae='',Te='',Pe='',Ie='',Ne='',Oe='',$e={searchBar:"searchBar_RVTs",dropdownMenu:"dropdownMenu_qbY6",searchBarLeft:"searchBarLeft_MXDe",suggestion:"suggestion_fB_2",cursor:"cursor_eG29",hitTree:"hitTree_kk6K",hitIcon:"hitIcon_a7Zy",hitPath:"hitPath_ieM4",noResultsIcon:"noResultsIcon_EBY5",hitFooter:"hitFooter_E9YW",hitWrapper:"hitWrapper_sAK8",hitTitle:"hitTitle_vyVt",hitAction:"hitAction_NqkB",hideAction:"hideAction_vcyE",noResults:"noResults_l6Q3",searchBarContainer:"searchBarContainer_NW3z",searchBarLoadingRing:"searchBarLoadingRing_YnHq",searchClearButton:"searchClearButton_qk4g",searchIndexLoading:"searchIndexLoading_EJ1f",searchHintContainer:"searchHintContainer_Pkmr",searchHint:"searchHint_iIMx",focused:"focused_OWtg",input:"input_FOTf",hint:"hint_URu1",suggestions:"suggestions_X8XU",dataset:"dataset_QiCy",empty:"empty_eITn"};function Le(e){let{document:t,type:n,page:r,metadata:a,tokens:o,isInterOfTree:i,isLastOfTree:s}=e;const l=0===n,c=1===n,u=[];i?u.push(Ne):s&&u.push(Oe);const p=u.map((e=>`${e}`)),d=`${l?je:c?Ae:Te}`,f=[`${(0,Ce.o)(t.t,(0,Ee.m)(a,"t"),o)}`];if(!i&&!s&&ye.H6){const e=r?(r.b??[]).concat(r.t).concat(t.s&&t.s!==r.t?t.s:[]):t.b;f.push(`${(0,xe.e)(e??[])}`)}else l||f.push(`${(0,_e.C)(r.t||(t.u.startsWith("/docs/api-reference/")?"API Reference":""),o)}`);const m=`${Pe}`;return[...p,d,``,...f,"",m].join("")}function Re(){return`${Ie}${(0,l.I)({id:"theme.SearchBar.noResultsText",message:"No results"})}`}var De=n(311);async function Fe(){const e=await Promise.all([n.e(8443),n.e(5525)]).then(n.t.bind(n,8443,23)),t=e.default;return t.noConflict?t.noConflict():e.noConflict&&e.noConflict(),t}const Me="_highlight";const ze=function(e){let{handleSearchBarToggle:t}=e;const n=(0,M.Z)(),{siteConfig:{baseUrl:a}}=(0,le.Z)(),o=(0,ke.gA)();let i=a;try{const{preferredVersion:e}=(0,ve.J)(o?.pluginId??ye.gQ);e&&!e.isLast&&(i=e.path+"/")}catch(R){if(ye.l9&&!(R instanceof I.i6))throw R}const c=(0,s.k6)(),p=(0,s.TH)(),d=(0,r.useRef)(null),f=(0,r.useRef)(new Map),m=(0,r.useRef)(!1),[h,g]=(0,r.useState)(!1),[y,b]=(0,r.useState)(!1),[v,k]=(0,r.useState)(""),w=(0,r.useRef)(null),S=(0,r.useRef)(""),[x,E]=(0,r.useState)("");(0,r.useEffect)((()=>{if(!Array.isArray(ye.Kc))return;let e="";if(p.pathname.startsWith(i)){const t=p.pathname.substring(i.length),n=ye.Kc.find((e=>t===e||t.startsWith(`${e}/`)));n&&(e=n)}S.current!==e&&(f.current.delete(e),S.current=e),E(e)}),[p.pathname,i]);const _=!!ye.hG&&Array.isArray(ye.Kc)&&""===x,C=(0,r.useCallback)((async()=>{if(_||f.current.get(x))return;f.current.set(x,"loading"),w.current?.autocomplete.destroy(),g(!0);const[{wrappedIndexes:e,zhDictionary:t},n]=await Promise.all([(0,we.w)(i,x),Fe()]);if(w.current=n(d.current,{hint:!1,autoselect:!0,openOnFocus:!0,cssClasses:{root:(0,be.Z)($e.searchBar,{[$e.searchBarLeft]:"left"===ye.pu}),noPrefix:!0,dropdownMenu:$e.dropdownMenu,input:$e.input,hint:$e.hint,suggestions:$e.suggestions,suggestion:$e.suggestion,cursor:$e.cursor,dataset:$e.dataset,empty:$e.empty}},[{source:(0,Se.v)(e,t,ye.qo),templates:{suggestion:Le,empty:Re,footer:e=>{let{query:t,isEmpty:n}=e;if(n&&!x)return;const r=(e=>{let{query:t,isEmpty:n}=e;const r=document.createElement("a"),o=new URLSearchParams,s=(0,l.I)({id:"theme.SearchBar.seeAll",message:"See all results"}),u=(0,l.I)({id:"theme.SearchBar.seeAllOutsideContext",message:"See results outside {context}"},{context:x}),p=(0,l.I)({id:"theme.SearchBar.searchInContext",message:"See all results in {context}"},{context:x});let d;if(o.set("q",t),d=x&&n?u:x?p:s,Array.isArray(ye.Kc)&&!n&&o.set("ctx",x),i!==a){if(!i.startsWith(a))throw new Error(`Version url '${i}' does not start with base url '${a}', this is a bug of \`@easyops-cn/docusaurus-search-local\`, please report it.`);o.set("version",i.substring(a.length))}const f=`${a}search?${o.toString()}`;return r.href=f,r.textContent=d,r.addEventListener("click",(e=>{e.ctrlKey||e.metaKey||(e.preventDefault(),w.current?.autocomplete.close(),c.push(f))})),r})({query:t,isEmpty:n}),o=document.createElement("div");return o.className=$e.hitFooter,o.appendChild(r),o}}}]).on("autocomplete:selected",(function(e,t){let{document:{u:n,h:r},tokens:a}=t;d.current?.blur();let o=n;if(ye.vc&&a.length>0){const e=new URLSearchParams;for(const t of a)e.append(Me,t);o+=`?${e.toString()}`}r&&(o+=r),c.push(o)})).on("autocomplete:closed",(()=>{d.current?.blur()})),f.current.set(x,"done"),g(!1),m.current){const e=d.current;e.value&&w.current?.autocomplete.open(),e.focus()}}),[_,x,i,a,c]);(0,r.useEffect)((()=>{if(!ye.vc)return;const e=n?new URLSearchParams(p.search).getAll(Me):[];setTimeout((()=>{const t=document.querySelector("article");if(!t)return;const n=new ye.vc(t);n.unmark(),0!==e.length&&n.mark(e),k(e.join(" ")),w.current?.autocomplete.setVal(e.join(" "))}))}),[n,p.search,p.pathname]);const[j,A]=(0,r.useState)(!1),T=(0,r.useCallback)((()=>{m.current=!0,C(),A(!0),t?.(!0)}),[t,C]),P=(0,r.useCallback)((()=>{A(!1),t?.(!1)}),[t]),N=(0,r.useCallback)((()=>{C()}),[C]),O=(0,r.useCallback)((e=>{k(e.target.value),e.target.value&&b(!0)}),[]),$=!!n&&/mac/i.test(navigator.userAgentData?.platform??navigator.platform);(0,r.useEffect)((()=>{if(!ye.AY)return;const e=e=>{!($?e.metaKey:e.ctrlKey)||"k"!==e.key&&"K"!==e.key||(e.preventDefault(),d.current?.focus(),T())};return document.addEventListener("keydown",e),()=>{document.removeEventListener("keydown",e)}}),[$,T]);const L=(0,r.useCallback)((()=>{const e=new URLSearchParams(p.search);e.delete(Me);const t=e.toString(),n=p.pathname+(""!=t?`?${t}`:"")+p.hash;n!=p.pathname+p.search+p.hash&&c.push(n),k(""),w.current?.autocomplete.setVal("")}),[p.pathname,p.search,p.hash,c]);return(0,u.jsxs)("div",{className:(0,be.Z)("navbar__search",$e.searchBarContainer,{[$e.searchIndexLoading]:h&&y,[$e.focused]:j}),hidden:_,children:[(0,u.jsx)("input",{placeholder:(0,l.I)({id:"theme.SearchBar.label",message:"Search",description:"The ARIA label and placeholder for search button"}),"aria-label":"Search",className:"navbar__search-input",onMouseEnter:N,onFocus:T,onBlur:P,onChange:O,ref:d,value:v}),(0,u.jsx)(De.Z,{className:$e.searchBarLoadingRing}),ye.AY&&ye.t_&&(""!==v?(0,u.jsx)("button",{className:$e.searchClearButton,onClick:L,children:"\u2715"}):n&&(0,u.jsxs)("div",{className:$e.searchHintContainer,children:[(0,u.jsx)("kbd",{className:$e.searchHint,children:$?"\u2318":"ctrl"}),(0,u.jsx)("kbd",{className:$e.searchHint,children:"K"})]}))]})};function Be(e){return(0,u.jsx)(u.Fragment,{children:(0,u.jsx)(ze,{...e})})}const Ue={navbarSearchContainer:"navbarSearchContainer_Bca1"};function qe(e){let{children:t,className:n}=e;return(0,u.jsx)("div",{className:(0,a.Z)(n,Ue.navbarSearchContainer),children:t})}var Qe=n(3438);const He=e=>e.docs.find((t=>t.id===e.mainDocId));const We={default:oe,localeDropdown:function(e){let{mobile:t,dropdownItemsBefore:n,dropdownItemsAfter:r,queryString:a="",...o}=e;const{i18n:{currentLocale:i,locales:c,localeConfigs:p}}=(0,le.Z)(),d=(0,me.l)(),{search:f,hash:m}=(0,s.TH)(),h=[...n,...c.map((e=>{const n=`${`pathname://${d.createUrl({locale:e,fullyQualified:!1})}`}${f}${m}${a}`;return{label:p[e].label,lang:p[e].htmlLang,to:n,target:"_self",autoAddBaseUrl:!1,className:e===i?t?"menu__link--active":"dropdown__link--active":""}})),...r],g=t?(0,l.I)({message:"Languages",id:"theme.navbar.mobileLanguageDropdown.label",description:"The label for the mobile language switcher dropdown"}):p[i].label;return(0,u.jsx)(fe,{...o,mobile:t,label:(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(he,{className:ge}),g]}),items:h})},search:function(e){let{mobile:t,className:n}=e;return t?null:(0,u.jsx)(qe,{className:n,children:(0,u.jsx)(Be,{})})},dropdown:fe,html:function(e){let{value:t,className:n,mobile:r=!1,isDropdownItem:o=!1}=e;const i=o?"li":"div";return(0,u.jsx)(i,{className:(0,a.Z)({navbar__item:!r&&!o,"menu__list-item":r},n),dangerouslySetInnerHTML:{__html:t}})},doc:function(e){let{docId:t,label:n,docsPluginId:r,...a}=e;const{activeDoc:o}=(0,ke.Iw)(r),i=(0,Qe.vY)(t,r),s=o?.path===i?.path;return null===i||i.unlisted&&!s?null:(0,u.jsx)(oe,{exact:!0,...a,isActive:()=>s||!!o?.sidebar&&o.sidebar===i.sidebar,label:n??i.id,to:i.path})},docSidebar:function(e){let{sidebarId:t,label:n,docsPluginId:r,...a}=e;const{activeDoc:o}=(0,ke.Iw)(r),i=(0,Qe.oz)(t,r).link;if(!i)throw new Error(`DocSidebarNavbarItem: Sidebar with ID "${t}" doesn't have anything to be linked to.`);return(0,u.jsx)(oe,{exact:!0,...a,isActive:()=>o?.sidebar===t,label:n??i.label,to:i.path})},docsVersion:function(e){let{label:t,to:n,docsPluginId:r,...a}=e;const o=(0,Qe.lO)(r)[0],i=t??o.label,s=n??(e=>e.docs.find((t=>t.id===e.mainDocId)))(o).path;return(0,u.jsx)(oe,{...a,label:i,to:s})},docsVersionDropdown:function(e){let{mobile:t,docsPluginId:n,dropdownActiveClassDisabled:r,dropdownItemsBefore:a,dropdownItemsAfter:o,...i}=e;const{search:c,hash:p}=(0,s.TH)(),d=(0,ke.Iw)(n),f=(0,ke.gB)(n),{savePreferredVersionName:m}=(0,ve.J)(n),h=[...a,...f.map((e=>{const t=d.alternateDocVersions[e.name]??He(e);return{label:e.label,to:`${t.path}${c}${p}`,isActive:()=>e===d.activeVersion,onClick:()=>m(e.name)}})),...o],g=(0,Qe.lO)(n)[0],y=t&&h.length>1?(0,l.I)({id:"theme.navbar.mobileVersionsDropdown.label",message:"Versions",description:"The label for the navbar versions dropdown on mobile view"}):g.label,b=t&&h.length>1?void 0:He(g).path;return h.length<=1?(0,u.jsx)(oe,{...i,mobile:t,label:y,to:b,isActive:r?()=>!1:void 0}):(0,u.jsx)(fe,{...i,mobile:t,label:y,to:b,items:h,isActive:r?()=>!1:void 0})}};function Ve(e){let{type:t,...n}=e;const r=function(e,t){return e&&"default"!==e?e:"items"in t?"dropdown":"default"}(t,n),a=We[r];if(!a)throw new Error(`No NavbarItem component found for type "${t}".`);return(0,u.jsx)(a,{...n})}function Ze(){const e=(0,T.e)(),t=(0,k.L)().navbar.items;return(0,u.jsx)("ul",{className:"menu__list",children:t.map(((t,n)=>(0,r.createElement)(Ve,{mobile:!0,...t,onClick:()=>e.toggle(),key:n})))})}function Ke(e){return(0,u.jsx)("button",{...e,type:"button",className:"clean-btn navbar-sidebar__back",children:(0,u.jsx)(l.Z,{id:"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel",description:"The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)",children:"\u2190 Back to main menu"})})}function Ge(){const e=0===(0,k.L)().navbar.items.length,t=R();return(0,u.jsxs)(u.Fragment,{children:[!e&&(0,u.jsx)(Ke,{onClick:()=>t.hide()}),t.content]})}function Ye(){const e=(0,T.e)();var t;return void 0===(t=e.shown)&&(t=!0),(0,r.useEffect)((()=>(document.body.style.overflow=t?"hidden":"visible",()=>{document.body.style.overflow="visible"})),[t]),e.shouldRender?(0,u.jsx)(D,{header:(0,u.jsx)(G,{}),primaryMenu:(0,u.jsx)(Ze,{}),secondaryMenu:(0,u.jsx)(Ge,{})}):null}const Je={navbarHideable:"navbarHideable_m1mJ",navbarHidden:"navbarHidden_jGov"};function Xe(e){return(0,u.jsx)("div",{role:"presentation",...e,className:(0,a.Z)("navbar-sidebar__backdrop",e.className)})}function et(e){let{children:t}=e;const{navbar:{hideOnScroll:n,style:o}}=(0,k.L)(),i=(0,T.e)(),{navbarRef:s,isNavbarVisible:p}=function(e){const[t,n]=(0,r.useState)(e),a=(0,r.useRef)(!1),o=(0,r.useRef)(0),i=(0,r.useCallback)((e=>{null!==e&&(o.current=e.getBoundingClientRect().height)}),[]);return(0,P.RF)(((t,r)=>{let{scrollY:i}=t;if(!e)return;if(i=s?n(!1):i+c{if(!e)return;const r=t.location.hash;if(r?document.getElementById(r.substring(1)):void 0)return a.current=!0,void n(!1);n(!0)})),{navbarRef:i,isNavbarVisible:t}}(n);return(0,u.jsxs)("nav",{ref:s,"aria-label":(0,l.I)({id:"theme.NavBar.navAriaLabel",message:"Main",description:"The ARIA label for the main navigation"}),className:(0,a.Z)("navbar","navbar--fixed-top",n&&[Je.navbarHideable,!p&&Je.navbarHidden],{"navbar--dark":"dark"===o,"navbar--primary":"primary"===o,"navbar-sidebar--show":i.shown}),children:[t,(0,u.jsx)(Xe,{onClick:i.toggle}),(0,u.jsx)(Ye,{})]})}var tt=n(8780);const nt={errorBoundaryError:"errorBoundaryError_a6uf",errorBoundaryFallback:"errorBoundaryFallback_VBag"};function rt(e){return(0,u.jsx)("button",{type:"button",...e,children:(0,u.jsx)(l.Z,{id:"theme.ErrorPageContent.tryAgain",description:"The label of the button to try again rendering when the React error boundary captures an error",children:"Try again"})})}function at(e){let{error:t}=e;const n=(0,tt.getErrorCausalChain)(t).map((e=>e.message)).join("\n\nCause:\n");return(0,u.jsx)("p",{className:nt.errorBoundaryError,children:n})}class ot extends r.Component{componentDidCatch(e,t){throw this.props.onError(e,t)}render(){return this.props.children}}const it="right";function st(e){let{width:t=30,height:n=30,className:r,...a}=e;return(0,u.jsx)("svg",{className:r,width:t,height:n,viewBox:"0 0 30 30","aria-hidden":"true",...a,children:(0,u.jsx)("path",{stroke:"currentColor",strokeLinecap:"round",strokeMiterlimit:"10",strokeWidth:"2",d:"M4 7h22M4 15h22M4 23h22"})})}function lt(){const{toggle:e,shown:t}=(0,T.e)();return(0,u.jsx)("button",{onClick:e,"aria-label":(0,l.I)({id:"theme.docs.sidebar.toggleSidebarButtonAriaLabel",message:"Toggle navigation bar",description:"The ARIA label for hamburger menu button of mobile navigation"}),"aria-expanded":t,className:"navbar__toggle clean-btn",type:"button",children:(0,u.jsx)(st,{})})}const ct={colorModeToggle:"colorModeToggle_DEke"};function ut(e){let{items:t}=e;return(0,u.jsx)(u.Fragment,{children:t.map(((e,t)=>(0,u.jsx)(ot,{onError:t=>new Error(`A theme navbar item failed to render.\nPlease double-check the following navbar item (themeConfig.navbar.items) of your Docusaurus config:\n${JSON.stringify(e,null,2)}`,{cause:t}),children:(0,u.jsx)(Ve,{...e})},t)))})}function pt(e){let{left:t,right:n}=e;return(0,u.jsxs)("div",{className:"navbar__inner",children:[(0,u.jsx)("div",{className:"navbar__items",children:t}),(0,u.jsx)("div",{className:"navbar__items navbar__items--right",children:n})]})}function dt(){const e=(0,T.e)(),t=(0,k.L)().navbar.items,[n,r]=function(e){function t(e){return"left"===(e.position??it)}return[e.filter(t),e.filter((e=>!t(e)))]}(t),a=t.find((e=>"search"===e.type));return(0,u.jsx)(pt,{left:(0,u.jsxs)(u.Fragment,{children:[!e.disabled&&(0,u.jsx)(lt,{}),(0,u.jsx)(Z,{}),(0,u.jsx)(ut,{items:n})]}),right:(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(ut,{items:r}),(0,u.jsx)(W,{className:ct.colorModeToggle}),!a&&(0,u.jsx)(qe,{children:(0,u.jsx)(Be,{})})]})})}function ft(){return(0,u.jsx)(et,{children:(0,u.jsx)(dt,{})})}function mt(e){let{item:t}=e;const{to:n,href:r,label:a,prependBaseUrlToHref:o,...i}=t,s=(0,J.Z)(n),l=(0,J.Z)(r,{forcePrependBaseUrl:!0});return(0,u.jsxs)(Y.Z,{className:"footer__link-item",...r?{href:o?l:r}:{to:s},...i,children:[a,r&&!(0,X.Z)(r)&&(0,u.jsx)(te.Z,{})]})}function ht(e){let{item:t}=e;return t.html?(0,u.jsx)("li",{className:"footer__item",dangerouslySetInnerHTML:{__html:t.html}}):(0,u.jsx)("li",{className:"footer__item",children:(0,u.jsx)(mt,{item:t})},t.href??t.to)}function gt(e){let{column:t}=e;return(0,u.jsxs)("div",{className:"col footer__col",children:[(0,u.jsx)("div",{className:"footer__title",children:t.title}),(0,u.jsx)("ul",{className:"footer__items clean-list",children:t.items.map(((e,t)=>(0,u.jsx)(ht,{item:e},t)))})]})}function yt(e){let{columns:t}=e;return(0,u.jsx)("div",{className:"row footer__links",children:t.map(((e,t)=>(0,u.jsx)(gt,{column:e},t)))})}function bt(){return(0,u.jsx)("span",{className:"footer__link-separator",children:"\xb7"})}function vt(e){let{item:t}=e;return t.html?(0,u.jsx)("span",{className:"footer__link-item",dangerouslySetInnerHTML:{__html:t.html}}):(0,u.jsx)(mt,{item:t})}function kt(e){let{links:t}=e;return(0,u.jsx)("div",{className:"footer__links text--center",children:(0,u.jsx)("div",{className:"footer__links",children:t.map(((e,n)=>(0,u.jsxs)(r.Fragment,{children:[(0,u.jsx)(vt,{item:e}),t.length!==n+1&&(0,u.jsx)(bt,{})]},n)))})})}function wt(e){let{links:t}=e;return function(e){return"title"in e[0]}(t)?(0,u.jsx)(yt,{columns:t}):(0,u.jsx)(kt,{links:t})}var St=n(9965);const xt={footerLogoLink:"footerLogoLink_BH7S"};function Et(e){let{logo:t}=e;const{withBaseUrl:n}=(0,J.C)(),r={light:n(t.src),dark:n(t.srcDark??t.src)};return(0,u.jsx)(St.Z,{className:(0,a.Z)("footer__logo",t.className),alt:t.alt,sources:r,width:t.width,height:t.height,style:t.style})}function _t(e){let{logo:t}=e;return t.href?(0,u.jsx)(Y.Z,{href:t.href,className:xt.footerLogoLink,target:t.target,children:(0,u.jsx)(Et,{logo:t})}):(0,u.jsx)(Et,{logo:t})}function Ct(e){let{copyright:t}=e;return(0,u.jsx)("div",{className:"footer__copyright",dangerouslySetInnerHTML:{__html:t}})}function jt(e){let{style:t,links:n,logo:r,copyright:o}=e;return(0,u.jsx)("footer",{className:(0,a.Z)("footer",{"footer--dark":"dark"===t}),children:(0,u.jsxs)("div",{className:"container container-fluid",children:[n,(r||o)&&(0,u.jsxs)("div",{className:"footer__bottom text--center",children:[r&&(0,u.jsx)("div",{className:"margin-bottom--sm",children:r}),o]})]})})}function At(){const{footer:e}=(0,k.L)();if(!e)return null;const{copyright:t,links:n,logo:r,style:a}=e;return(0,u.jsx)(jt,{style:a,links:n&&n.length>0&&(0,u.jsx)(wt,{links:n}),logo:r&&(0,u.jsx)(_t,{logo:r}),copyright:t&&(0,u.jsx)(Ct,{copyright:t})})}const Tt=r.memo(At),Pt=(0,I.Qc)([F.S,w.pl,P.OC,ve.L5,i.VC,function(e){let{children:t}=e;return(0,u.jsx)(N.n2,{children:(0,u.jsx)(T.M,{children:(0,u.jsx)($,{children:t})})})}]);function It(e){let{children:t}=e;return(0,u.jsx)(Pt,{children:t})}var Nt=n(2503);function Ot(e){let{error:t,tryAgain:n}=e;return(0,u.jsx)("main",{className:"container margin-vert--xl",children:(0,u.jsx)("div",{className:"row",children:(0,u.jsxs)("div",{className:"col col--6 col--offset-3",children:[(0,u.jsx)(Nt.Z,{as:"h1",className:"hero__title",children:(0,u.jsx)(l.Z,{id:"theme.ErrorPageContent.title",description:"The title of the fallback page when the page crashed",children:"This page crashed."})}),(0,u.jsx)("div",{className:"margin-vert--lg",children:(0,u.jsx)(rt,{onClick:n,className:"button button--primary shadow--lw"})}),(0,u.jsx)("hr",{}),(0,u.jsx)("div",{className:"margin-vert--md",children:(0,u.jsx)(at,{error:t})})]})})})}const $t={mainWrapper:"mainWrapper_z2l0"};function Lt(e){const{children:t,noFooter:n,wrapperClassName:r,title:s,description:l}=e;return(0,y.t)(),(0,u.jsxs)(It,{children:[(0,u.jsx)(i.d,{title:s,description:l}),(0,u.jsx)(v,{}),(0,u.jsx)(A,{}),(0,u.jsx)(ft,{}),(0,u.jsx)("div",{id:p,className:(0,a.Z)(g.k.wrapper.main,$t.mainWrapper,r),children:(0,u.jsx)(o.Z,{fallback:e=>(0,u.jsx)(Ot,{...e}),children:t})}),!n&&(0,u.jsx)(Tt,{})]})}},1327:(e,t,n)=>{"use strict";n.d(t,{Z:()=>u});n(7294);var r=n(3692),a=n(4996),o=n(2263),i=n(6668),s=n(9965),l=n(5893);function c(e){let{logo:t,alt:n,imageClassName:r}=e;const o={light:(0,a.Z)(t.src),dark:(0,a.Z)(t.srcDark||t.src)},i=(0,l.jsx)(s.Z,{className:t.className,sources:o,height:t.height,width:t.width,alt:n,style:t.style});return r?(0,l.jsx)("div",{className:r,children:i}):i}function u(e){const{siteConfig:{title:t}}=(0,o.Z)(),{navbar:{title:n,logo:s}}=(0,i.L)(),{imageClassName:u,titleClassName:p,...d}=e,f=(0,a.Z)(s?.href||"/"),m=n?"":t,h=s?.alt??m;return(0,l.jsxs)(r.Z,{to:f,...d,...s?.target&&{target:s.target},children:[s&&(0,l.jsx)(c,{logo:s,alt:h,imageClassName:u}),null!=n&&(0,l.jsx)("b",{className:p,children:n})]})}},197:(e,t,n)=>{"use strict";n.d(t,{Z:()=>o});n(7294);var r=n(5742),a=n(5893);function o(e){let{locale:t,version:n,tag:o}=e;const i=t;return(0,a.jsxs)(r.Z,{children:[t&&(0,a.jsx)("meta",{name:"docusaurus_locale",content:t}),n&&(0,a.jsx)("meta",{name:"docusaurus_version",content:n}),o&&(0,a.jsx)("meta",{name:"docusaurus_tag",content:o}),i&&(0,a.jsx)("meta",{name:"docsearch:language",content:i}),n&&(0,a.jsx)("meta",{name:"docsearch:version",content:n}),o&&(0,a.jsx)("meta",{name:"docsearch:docusaurus_tag",content:o})]})}},9965:(e,t,n)=>{"use strict";n.d(t,{Z:()=>u});var r=n(7294),a=n(788),o=n(2389),i=n(2949);const s={themedComponent:"themedComponent_mlkZ","themedComponent--light":"themedComponent--light_NVdE","themedComponent--dark":"themedComponent--dark_xIcU"};var l=n(5893);function c(e){let{className:t,children:n}=e;const c=(0,o.Z)(),{colorMode:u}=(0,i.I)();return(0,l.jsx)(l.Fragment,{children:(c?"dark"===u?["dark"]:["light"]:["light","dark"]).map((e=>{const o=n({theme:e,className:(0,a.Z)(t,s.themedComponent,s[`themedComponent--${e}`])});return(0,l.jsx)(r.Fragment,{children:o},e)}))})}function u(e){const{sources:t,className:n,alt:r,...a}=e;return(0,l.jsx)(c,{className:n,children:e=>{let{theme:n,className:o}=e;return(0,l.jsx)("img",{src:t[n],alt:r,className:o,...a})}})}},6043:(e,t,n)=>{"use strict";n.d(t,{u:()=>c,z:()=>y});var r=n(7294),a=n(412),o=n(469),i=n(1442),s=n(5893);const l="ease-in-out";function c(e){let{initialState:t}=e;const[n,a]=(0,r.useState)(t??!1),o=(0,r.useCallback)((()=>{a((e=>!e))}),[]);return{collapsed:n,setCollapsed:a,toggleCollapsed:o}}const u={display:"none",overflow:"hidden",height:"0px"},p={display:"block",overflow:"visible",height:"auto"};function d(e,t){const n=t?u:p;e.style.display=n.display,e.style.overflow=n.overflow,e.style.height=n.height}function f(e){let{collapsibleRef:t,collapsed:n,animation:a}=e;const o=(0,r.useRef)(!1);(0,r.useEffect)((()=>{const e=t.current;function r(){const t=e.scrollHeight,n=a?.duration??function(e){if((0,i.n)())return 1;const t=e/36;return Math.round(10*(4+15*t**.25+t/5))}(t);return{transition:`height ${n}ms ${a?.easing??l}`,height:`${t}px`}}function s(){const t=r();e.style.transition=t.transition,e.style.height=t.height}if(!o.current)return d(e,n),void(o.current=!0);return e.style.willChange="height",function(){const t=requestAnimationFrame((()=>{n?(s(),requestAnimationFrame((()=>{e.style.height=u.height,e.style.overflow=u.overflow}))):(e.style.display="block",requestAnimationFrame((()=>{s()})))}));return()=>cancelAnimationFrame(t)}()}),[t,n,a])}function m(e){if(!a.default.canUseDOM)return e?u:p}function h(e){let{as:t="div",collapsed:n,children:a,animation:o,onCollapseTransitionEnd:i,className:l,disableSSRStyle:c}=e;const u=(0,r.useRef)(null);return f({collapsibleRef:u,collapsed:n,animation:o}),(0,s.jsx)(t,{ref:u,style:c?void 0:m(n),onTransitionEnd:e=>{"height"===e.propertyName&&(d(u.current,n),i?.(n))},className:l,children:a})}function g(e){let{collapsed:t,...n}=e;const[a,i]=(0,r.useState)(!t),[l,c]=(0,r.useState)(t);return(0,o.Z)((()=>{t||i(!0)}),[t]),(0,o.Z)((()=>{a&&c(t)}),[a,t]),a?(0,s.jsx)(h,{...n,collapsed:l}):null}function y(e){let{lazy:t,...n}=e;const r=t?g:h;return(0,s.jsx)(r,{...n})}},9689:(e,t,n)=>{"use strict";n.d(t,{nT:()=>h,pl:()=>m});var r=n(7294),a=n(2389),o=n(12),i=n(902),s=n(6668),l=n(5893);const c=(0,o.WA)("docusaurus.announcement.dismiss"),u=(0,o.WA)("docusaurus.announcement.id"),p=()=>"true"===c.get(),d=e=>c.set(String(e)),f=r.createContext(null);function m(e){let{children:t}=e;const n=function(){const{announcementBar:e}=(0,s.L)(),t=(0,a.Z)(),[n,o]=(0,r.useState)((()=>!!t&&p()));(0,r.useEffect)((()=>{o(p())}),[]);const i=(0,r.useCallback)((()=>{d(!0),o(!0)}),[]);return(0,r.useEffect)((()=>{if(!e)return;const{id:t}=e;let n=u.get();"annoucement-bar"===n&&(n="announcement-bar");const r=t!==n;u.set(t),r&&d(!1),!r&&p()||o(!1)}),[e]),(0,r.useMemo)((()=>({isActive:!!e&&!n,close:i})),[e,n,i])}();return(0,l.jsx)(f.Provider,{value:n,children:t})}function h(){const e=(0,r.useContext)(f);if(!e)throw new i.i6("AnnouncementBarProvider");return e}},2949:(e,t,n)=>{"use strict";n.d(t,{I:()=>y,S:()=>g});var r=n(7294),a=n(412),o=n(902),i=n(12),s=n(6668),l=n(5893);const c=r.createContext(void 0),u="theme",p=(0,i.WA)(u),d={light:"light",dark:"dark"},f=e=>e===d.dark?d.dark:d.light,m=e=>a.default.canUseDOM?f(document.documentElement.getAttribute("data-theme")):f(e),h=e=>{p.set(f(e))};function g(e){let{children:t}=e;const n=function(){const{colorMode:{defaultMode:e,disableSwitch:t,respectPrefersColorScheme:n}}=(0,s.L)(),[a,o]=(0,r.useState)(m(e));(0,r.useEffect)((()=>{t&&p.del()}),[t]);const i=(0,r.useCallback)((function(t,r){void 0===r&&(r={});const{persist:a=!0}=r;t?(o(t),a&&h(t)):(o(n?window.matchMedia("(prefers-color-scheme: dark)").matches?d.dark:d.light:e),p.del())}),[n,e]);(0,r.useEffect)((()=>{document.documentElement.setAttribute("data-theme",f(a))}),[a]),(0,r.useEffect)((()=>{if(t)return;const e=e=>{if(e.key!==u)return;const t=p.get();null!==t&&i(f(t))};return window.addEventListener("storage",e),()=>window.removeEventListener("storage",e)}),[t,i]);const l=(0,r.useRef)(!1);return(0,r.useEffect)((()=>{if(t&&!n)return;const e=window.matchMedia("(prefers-color-scheme: dark)"),r=()=>{window.matchMedia("print").matches||l.current?l.current=window.matchMedia("print").matches:i(null)};return e.addListener(r),()=>e.removeListener(r)}),[i,t,n]),(0,r.useMemo)((()=>({colorMode:a,setColorMode:i,get isDarkTheme(){return a===d.dark},setLightTheme(){i(d.light)},setDarkTheme(){i(d.dark)}})),[a,i])}();return(0,l.jsx)(c.Provider,{value:n,children:t})}function y(){const e=(0,r.useContext)(c);if(null==e)throw new o.i6("ColorModeProvider","Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.");return e}},373:(e,t,n)=>{"use strict";n.d(t,{J:()=>v,L5:()=>y});var r=n(7294),a=n(4104),o=n(9935),i=n(6668),s=n(3438),l=n(902),c=n(12),u=n(5893);const p=e=>`docs-preferred-version-${e}`,d={save:(e,t,n)=>{(0,c.WA)(p(e),{persistence:t}).set(n)},read:(e,t)=>(0,c.WA)(p(e),{persistence:t}).get(),clear:(e,t)=>{(0,c.WA)(p(e),{persistence:t}).del()}},f=e=>Object.fromEntries(e.map((e=>[e,{preferredVersionName:null}])));const m=r.createContext(null);function h(){const e=(0,a._r)(),t=(0,i.L)().docs.versionPersistence,n=(0,r.useMemo)((()=>Object.keys(e)),[e]),[o,s]=(0,r.useState)((()=>f(n)));(0,r.useEffect)((()=>{s(function(e){let{pluginIds:t,versionPersistence:n,allDocsData:r}=e;function a(e){const t=d.read(e,n);return r[e].versions.some((e=>e.name===t))?{preferredVersionName:t}:(d.clear(e,n),{preferredVersionName:null})}return Object.fromEntries(t.map((e=>[e,a(e)])))}({allDocsData:e,versionPersistence:t,pluginIds:n}))}),[e,t,n]);return[o,(0,r.useMemo)((()=>({savePreferredVersion:function(e,n){d.save(e,t,n),s((t=>({...t,[e]:{preferredVersionName:n}})))}})),[t])]}function g(e){let{children:t}=e;const n=h();return(0,u.jsx)(m.Provider,{value:n,children:t})}function y(e){let{children:t}=e;return s.cE?(0,u.jsx)(g,{children:t}):(0,u.jsx)(u.Fragment,{children:t})}function b(){const e=(0,r.useContext)(m);if(!e)throw new l.i6("DocsPreferredVersionContextProvider");return e}function v(e){void 0===e&&(e=o.m);const t=(0,a.zh)(e),[n,i]=b(),{preferredVersionName:s}=n[e];return{preferredVersion:t.versions.find((e=>e.name===s))??null,savePreferredVersionName:(0,r.useCallback)((t=>{i.savePreferredVersion(e,t)}),[i,e])}}},1116:(e,t,n)=>{"use strict";n.d(t,{V:()=>c,b:()=>l});var r=n(7294),a=n(902),o=n(5893);const i=Symbol("EmptyContext"),s=r.createContext(i);function l(e){let{children:t,name:n,items:a}=e;const i=(0,r.useMemo)((()=>n&&a?{name:n,items:a}:null),[n,a]);return(0,o.jsx)(s.Provider,{value:i,children:t})}function c(){const e=(0,r.useContext)(s);if(e===i)throw new a.i6("DocsSidebarProvider");return e}},4477:(e,t,n)=>{"use strict";n.d(t,{E:()=>l,q:()=>s});var r=n(7294),a=n(902),o=n(5893);const i=r.createContext(null);function s(e){let{children:t,version:n}=e;return(0,o.jsx)(i.Provider,{value:n,children:t})}function l(){const e=(0,r.useContext)(i);if(null===e)throw new a.i6("DocsVersionProvider");return e}},3163:(e,t,n)=>{"use strict";n.d(t,{M:()=>d,e:()=>f});var r=n(7294),a=n(3102),o=n(7524),i=n(1980),s=n(6668),l=n(902),c=n(5893);const u=r.createContext(void 0);function p(){const e=function(){const e=(0,a.HY)(),{items:t}=(0,s.L)().navbar;return 0===t.length&&!e.component}(),t=(0,o.i)(),n=!e&&"mobile"===t,[l,c]=(0,r.useState)(!1);(0,i.Rb)((()=>{if(l)return c(!1),!1}));const u=(0,r.useCallback)((()=>{c((e=>!e))}),[]);return(0,r.useEffect)((()=>{"desktop"===t&&c(!1)}),[t]),(0,r.useMemo)((()=>({disabled:e,shouldRender:n,toggle:u,shown:l})),[e,n,u,l])}function d(e){let{children:t}=e;const n=p();return(0,c.jsx)(u.Provider,{value:n,children:t})}function f(){const e=r.useContext(u);if(void 0===e)throw new l.i6("NavbarMobileSidebarProvider");return e}},3102:(e,t,n)=>{"use strict";n.d(t,{HY:()=>l,Zo:()=>c,n2:()=>s});var r=n(7294),a=n(902),o=n(5893);const i=r.createContext(null);function s(e){let{children:t}=e;const n=(0,r.useState)({component:null,props:null});return(0,o.jsx)(i.Provider,{value:n,children:t})}function l(){const e=(0,r.useContext)(i);if(!e)throw new a.i6("NavbarSecondaryMenuContentProvider");return e[0]}function c(e){let{component:t,props:n}=e;const o=(0,r.useContext)(i);if(!o)throw new a.i6("NavbarSecondaryMenuContentProvider");const[,s]=o,l=(0,a.Ql)(n);return(0,r.useEffect)((()=>{s({component:t,props:l})}),[s,t,l]),(0,r.useEffect)((()=>()=>s({component:null,props:null})),[s]),null}},9727:(e,t,n)=>{"use strict";n.d(t,{h:()=>a,t:()=>o});var r=n(7294);const a="navigation-with-keyboard";function o(){(0,r.useEffect)((()=>{function e(e){"keydown"===e.type&&"Tab"===e.key&&document.body.classList.add(a),"mousedown"===e.type&&document.body.classList.remove(a)}return document.addEventListener("keydown",e),document.addEventListener("mousedown",e),()=>{document.body.classList.remove(a),document.removeEventListener("keydown",e),document.removeEventListener("mousedown",e)}}),[])}},7524:(e,t,n)=>{"use strict";n.d(t,{i:()=>s});var r=n(7294),a=n(412);const o={desktop:"desktop",mobile:"mobile",ssr:"ssr"},i=996;function s(e){let{desktopBreakpoint:t=i}=void 0===e?{}:e;const[n,s]=(0,r.useState)((()=>"ssr"));return(0,r.useEffect)((()=>{function e(){s(function(e){if(!a.default.canUseDOM)throw new Error("getWindowSize() should only be called after React hydration");return window.innerWidth>e?o.desktop:o.mobile}(t))}return e(),window.addEventListener("resize",e),()=>{window.removeEventListener("resize",e)}}),[t]),n}},5281:(e,t,n)=>{"use strict";n.d(t,{k:()=>r});const r={page:{blogListPage:"blog-list-page",blogPostPage:"blog-post-page",blogTagsListPage:"blog-tags-list-page",blogTagPostListPage:"blog-tags-post-list-page",docsDocPage:"docs-doc-page",docsTagsListPage:"docs-tags-list-page",docsTagDocListPage:"docs-tags-doc-list-page",mdxPage:"mdx-page"},wrapper:{main:"main-wrapper",blogPages:"blog-wrapper",docsPages:"docs-wrapper",mdxPages:"mdx-wrapper"},common:{editThisPage:"theme-edit-this-page",lastUpdated:"theme-last-updated",backToTopButton:"theme-back-to-top-button",codeBlock:"theme-code-block",admonition:"theme-admonition",unlistedBanner:"theme-unlisted-banner",admonitionType:e=>`theme-admonition-${e}`},layout:{},docs:{docVersionBanner:"theme-doc-version-banner",docVersionBadge:"theme-doc-version-badge",docBreadcrumbs:"theme-doc-breadcrumbs",docMarkdown:"theme-doc-markdown",docTocMobile:"theme-doc-toc-mobile",docTocDesktop:"theme-doc-toc-desktop",docFooter:"theme-doc-footer",docFooterTagsRow:"theme-doc-footer-tags-row",docFooterEditMetaRow:"theme-doc-footer-edit-meta-row",docSidebarContainer:"theme-doc-sidebar-container",docSidebarMenu:"theme-doc-sidebar-menu",docSidebarItemCategory:"theme-doc-sidebar-item-category",docSidebarItemLink:"theme-doc-sidebar-item-link",docSidebarItemCategoryLevel:e=>`theme-doc-sidebar-item-category-level-${e}`,docSidebarItemLinkLevel:e=>`theme-doc-sidebar-item-link-level-${e}`},blog:{blogFooterTagsRow:"theme-blog-footer-tags-row",blogFooterEditMetaRow:"theme-blog-footer-edit-meta-row"}}},1442:(e,t,n)=>{"use strict";function r(){return window.matchMedia("(prefers-reduced-motion: reduce)").matches}n.d(t,{n:()=>r})},3438:(e,t,n)=>{"use strict";n.d(t,{LM:()=>f,SN:()=>E,_F:()=>g,cE:()=>d,f:()=>b,lO:()=>w,oz:()=>S,s1:()=>k,vY:()=>x});var r=n(7294),a=n(6550),o=n(8790),i=n(4104),s=n(373),l=n(4477),c=n(1116),u=n(7392),p=n(8596);const d=!!i._r;function f(e){return"link"!==e.type||e.unlisted?"category"===e.type?function(e){if(e.href&&!e.linkUnlisted)return e.href;for(const t of e.items){const e=f(t);if(e)return e}}(e):void 0:e.href}const m=(e,t)=>void 0!==e&&(0,p.Mg)(e,t),h=(e,t)=>e.some((e=>g(e,t)));function g(e,t){return"link"===e.type?m(e.href,t):"category"===e.type&&(m(e.href,t)||h(e.items,t))}function y(e,t){switch(e.type){case"category":return g(e,t)||e.items.some((e=>y(e,t)));case"link":return!e.unlisted||g(e,t);default:return!0}}function b(e,t){return(0,r.useMemo)((()=>e.filter((e=>y(e,t)))),[e,t])}function v(e){let{sidebarItems:t,pathname:n,onlyCategories:r=!1}=e;const a=[];return function e(t){for(const o of t)if("category"===o.type&&((0,p.Mg)(o.href,n)||e(o.items))||"link"===o.type&&(0,p.Mg)(o.href,n)){return r&&"category"!==o.type||a.unshift(o),!0}return!1}(t),a}function k(){const e=(0,c.V)(),{pathname:t}=(0,a.TH)(),n=(0,i.gA)()?.pluginData.breadcrumbs;return!1!==n&&e?v({sidebarItems:e.items,pathname:t}):null}function w(e){const{activeVersion:t}=(0,i.Iw)(e),{preferredVersion:n}=(0,s.J)(e),a=(0,i.yW)(e);return(0,r.useMemo)((()=>(0,u.j)([t,n,a].filter(Boolean))),[t,n,a])}function S(e,t){const n=w(t);return(0,r.useMemo)((()=>{const t=n.flatMap((e=>e.sidebars?Object.entries(e.sidebars):[])),r=t.find((t=>t[0]===e));if(!r)throw new Error(`Can't find any sidebar with id "${e}" in version${n.length>1?"s":""} ${n.map((e=>e.name)).join(", ")}".\nAvailable sidebar ids are:\n- ${t.map((e=>e[0])).join("\n- ")}`);return r[1]}),[e,n])}function x(e,t){const n=w(t);return(0,r.useMemo)((()=>{const t=n.flatMap((e=>e.docs)),r=t.find((t=>t.id===e));if(!r){if(n.flatMap((e=>e.draftIds)).includes(e))return null;throw new Error(`Couldn't find any doc with id "${e}" in version${n.length>1?"s":""} "${n.map((e=>e.name)).join(", ")}".\nAvailable doc ids are:\n- ${(0,u.j)(t.map((e=>e.id))).join("\n- ")}`)}return r}),[e,n])}function E(e){let{route:t}=e;const n=(0,a.TH)(),r=(0,l.E)(),i=t.routes,s=i.find((e=>(0,a.LX)(n.pathname,e)));if(!s)return null;const c=s.sidebar,u=c?r.docsSidebars[c]:void 0;return{docElement:(0,o.H)(i),sidebarName:c,sidebarItems:u}}},1980:(e,t,n)=>{"use strict";n.d(t,{Rb:()=>i,_X:()=>s});var r=n(7294),a=n(6550),o=n(902);function i(e){!function(e){const t=(0,a.k6)(),n=(0,o.zX)(e);(0,r.useEffect)((()=>t.block(((e,t)=>n(e,t)))),[t,n])}(((t,n)=>{if("POP"===n)return e(t,n)}))}function s(e){return function(e){const t=(0,a.k6)();return(0,r.useSyncExternalStore)(t.listen,(()=>e(t)),(()=>e(t)))}((t=>null===e?null:new URLSearchParams(t.location.search).get(e)))}},7392:(e,t,n)=>{"use strict";function r(e,t){return void 0===t&&(t=(e,t)=>e===t),e.filter(((n,r)=>e.findIndex((e=>t(e,n)))!==r))}function a(e){return Array.from(new Set(e))}n.d(t,{j:()=>a,l:()=>r})},1944:(e,t,n)=>{"use strict";n.d(t,{FG:()=>f,d:()=>p,VC:()=>m});var r=n(7294),a=n(788),o=n(5742),i=n(226);function s(){const e=r.useContext(i._);if(!e)throw new Error("Unexpected: no Docusaurus route context found");return e}var l=n(4996),c=n(2263);var u=n(5893);function p(e){let{title:t,description:n,keywords:r,image:a,children:i}=e;const s=function(e){const{siteConfig:t}=(0,c.Z)(),{title:n,titleDelimiter:r}=t;return e?.trim().length?`${e.trim()} ${r} ${n}`:n}(t),{withBaseUrl:p}=(0,l.C)(),d=a?p(a,{absolute:!0}):void 0;return(0,u.jsxs)(o.Z,{children:[t&&(0,u.jsx)("title",{children:s}),t&&(0,u.jsx)("meta",{property:"og:title",content:s}),n&&(0,u.jsx)("meta",{name:"description",content:n}),n&&(0,u.jsx)("meta",{property:"og:description",content:n}),r&&(0,u.jsx)("meta",{name:"keywords",content:Array.isArray(r)?r.join(","):r}),d&&(0,u.jsx)("meta",{property:"og:image",content:d}),d&&(0,u.jsx)("meta",{name:"twitter:image",content:d}),i]})}const d=r.createContext(void 0);function f(e){let{className:t,children:n}=e;const i=r.useContext(d),s=(0,a.Z)(i,t);return(0,u.jsxs)(d.Provider,{value:s,children:[(0,u.jsx)(o.Z,{children:(0,u.jsx)("html",{className:s})}),n]})}function m(e){let{children:t}=e;const n=s(),r=`plugin-${n.plugin.name.replace(/docusaurus-(?:plugin|theme)-(?:content-)?/gi,"")}`;const o=`plugin-id-${n.plugin.id}`;return(0,u.jsx)(f,{className:(0,a.Z)(r,o),children:t})}},902:(e,t,n)=>{"use strict";n.d(t,{D9:()=>s,Qc:()=>u,Ql:()=>c,i6:()=>l,zX:()=>i});var r=n(7294),a=n(469),o=n(5893);function i(e){const t=(0,r.useRef)(e);return(0,a.Z)((()=>{t.current=e}),[e]),(0,r.useCallback)((function(){return t.current(...arguments)}),[])}function s(e){const t=(0,r.useRef)();return(0,a.Z)((()=>{t.current=e})),t.current}class l extends Error{constructor(e,t){super(),this.name="ReactContextError",this.message=`Hook ${this.stack?.split("\n")[1]?.match(/at (?:\w+\.)?(?\w+)/)?.groups.name??""} is called outside the <${e}>. ${t??""}`}}function c(e){const t=Object.entries(e);return t.sort(((e,t)=>e[0].localeCompare(t[0]))),(0,r.useMemo)((()=>e),t.flat())}function u(e){return t=>{let{children:n}=t;return(0,o.jsx)(o.Fragment,{children:e.reduceRight(((e,t)=>(0,o.jsx)(t,{children:e})),n)})}}},8596:(e,t,n)=>{"use strict";n.d(t,{Mg:()=>i,Ns:()=>s});var r=n(7294),a=n(723),o=n(2263);function i(e,t){const n=e=>(!e||e.endsWith("/")?e:`${e}/`)?.toLowerCase();return n(e)===n(t)}function s(){const{baseUrl:e}=(0,o.Z)().siteConfig;return(0,r.useMemo)((()=>function(e){let{baseUrl:t,routes:n}=e;function r(e){return e.path===t&&!0===e.exact}function a(e){return e.path===t&&!e.exact}return function e(t){if(0===t.length)return;return t.find(r)||e(t.filter(a).flatMap((e=>e.routes??[])))}(n)}({routes:a.Z,baseUrl:e})),[e])}},2466:(e,t,n)=>{"use strict";n.d(t,{Ct:()=>h,OC:()=>u,RF:()=>f,o5:()=>m});var r=n(7294),a=n(412),o=n(2389),i=n(469),s=n(902),l=n(5893);const c=r.createContext(void 0);function u(e){let{children:t}=e;const n=function(){const e=(0,r.useRef)(!0);return(0,r.useMemo)((()=>({scrollEventsEnabledRef:e,enableScrollEvents:()=>{e.current=!0},disableScrollEvents:()=>{e.current=!1}})),[])}();return(0,l.jsx)(c.Provider,{value:n,children:t})}function p(){const e=(0,r.useContext)(c);if(null==e)throw new s.i6("ScrollControllerProvider");return e}const d=()=>a.default.canUseDOM?{scrollX:window.pageXOffset,scrollY:window.pageYOffset}:null;function f(e,t){void 0===t&&(t=[]);const{scrollEventsEnabledRef:n}=p(),a=(0,r.useRef)(d()),o=(0,s.zX)(e);(0,r.useEffect)((()=>{const e=()=>{if(!n.current)return;const e=d();o(e,a.current),a.current=e},t={passive:!0};return e(),window.addEventListener("scroll",e,t),()=>window.removeEventListener("scroll",e,t)}),[o,n,...t])}function m(){const e=p(),t=function(){const e=(0,r.useRef)({elem:null,top:0}),t=(0,r.useCallback)((t=>{e.current={elem:t,top:t.getBoundingClientRect().top}}),[]),n=(0,r.useCallback)((()=>{const{current:{elem:t,top:n}}=e;if(!t)return{restored:!1};const r=t.getBoundingClientRect().top-n;return r&&window.scrollBy({left:0,top:r}),e.current={elem:null,top:0},{restored:0!==r}}),[]);return(0,r.useMemo)((()=>({save:t,restore:n})),[n,t])}(),n=(0,r.useRef)(void 0),a=(0,r.useCallback)((r=>{t.save(r),e.disableScrollEvents(),n.current=()=>{const{restored:r}=t.restore();if(n.current=void 0,r){const t=()=>{e.enableScrollEvents(),window.removeEventListener("scroll",t)};window.addEventListener("scroll",t)}else e.enableScrollEvents()}}),[e,t]);return(0,i.Z)((()=>{queueMicrotask((()=>n.current?.()))})),{blockElementScrollPositionUntilNextRender:a}}function h(){const e=(0,r.useRef)(null),t=(0,o.Z)()&&"smooth"===getComputedStyle(document.documentElement).scrollBehavior;return{startScroll:n=>{e.current=t?function(e){return window.scrollTo({top:e,behavior:"smooth"}),()=>{}}(n):function(e){let t=null;const n=document.documentElement.scrollTop>e;return function r(){const a=document.documentElement.scrollTop;(n&&a>e||!n&&at&&cancelAnimationFrame(t)}(n)},cancelScroll:()=>e.current?.()}}},3320:(e,t,n)=>{"use strict";n.d(t,{HX:()=>r,os:()=>a});n(2263);const r="default";function a(e,t){return`docs-${e}-${t}`}},12:(e,t,n)=>{"use strict";n.d(t,{Nk:()=>u,WA:()=>c});var r=n(7294);const a="localStorage";function o(e){let{key:t,oldValue:n,newValue:r,storage:a}=e;if(n===r)return;const o=document.createEvent("StorageEvent");o.initStorageEvent("storage",!1,!1,t,n,r,window.location.href,a),window.dispatchEvent(o)}function i(e){if(void 0===e&&(e=a),"undefined"==typeof window)throw new Error("Browser storage is not available on Node.js/Docusaurus SSR process.");if("none"===e)return null;try{return window[e]}catch(n){return t=n,s||(console.warn("Docusaurus browser storage is not available.\nPossible reasons: running Docusaurus in an iframe, in an incognito browser session, or using too strict browser privacy settings.",t),s=!0),null}var t}let s=!1;const l={get:()=>null,set:()=>{},del:()=>{},listen:()=>()=>{}};function c(e,t){if("undefined"==typeof window)return function(e){function t(){throw new Error(`Illegal storage API usage for storage key "${e}".\nDocusaurus storage APIs are not supposed to be called on the server-rendering process.\nPlease only call storage APIs in effects and event handlers.`)}return{get:t,set:t,del:t,listen:t}}(e);const n=i(t?.persistence);return null===n?l:{get:()=>{try{return n.getItem(e)}catch(t){return console.error(`Docusaurus storage error, can't get key=${e}`,t),null}},set:t=>{try{const r=n.getItem(e);n.setItem(e,t),o({key:e,oldValue:r,newValue:t,storage:n})}catch(r){console.error(`Docusaurus storage error, can't set ${e}=${t}`,r)}},del:()=>{try{const t=n.getItem(e);n.removeItem(e),o({key:e,oldValue:t,newValue:null,storage:n})}catch(t){console.error(`Docusaurus storage error, can't delete key=${e}`,t)}},listen:t=>{try{const r=r=>{r.storageArea===n&&r.key===e&&t(r)};return window.addEventListener("storage",r),()=>window.removeEventListener("storage",r)}catch(r){return console.error(`Docusaurus storage error, can't listen for changes of key=${e}`,r),()=>{}}}}}function u(e,t){const n=(0,r.useRef)((()=>null===e?l:c(e,t))).current(),a=(0,r.useCallback)((e=>"undefined"==typeof window?()=>{}:n.listen(e)),[n]);return[(0,r.useSyncExternalStore)(a,(()=>"undefined"==typeof window?null:n.get()),(()=>null)),n]}},4711:(e,t,n)=>{"use strict";n.d(t,{l:()=>i});var r=n(2263),a=n(6550),o=n(8780);function i(){const{siteConfig:{baseUrl:e,url:t,trailingSlash:n},i18n:{defaultLocale:i,currentLocale:s}}=(0,r.Z)(),{pathname:l}=(0,a.TH)(),c=(0,o.applyTrailingSlash)(l,{trailingSlash:n,baseUrl:e}),u=s===i?e:e.replace(`/${s}/`,"/"),p=c.replace(e,"");return{createUrl:function(e){let{locale:n,fullyQualified:r}=e;return`${r?t:""}${function(e){return e===i?`${u}`:`${u}${e}/`}(n)}${p}`}}}},5936:(e,t,n)=>{"use strict";n.d(t,{S:()=>i});var r=n(7294),a=n(6550),o=n(902);function i(e){const t=(0,a.TH)(),n=(0,o.D9)(t),i=(0,o.zX)(e);(0,r.useEffect)((()=>{n&&t!==n&&i({location:t,previousLocation:n})}),[i,t,n])}},6668:(e,t,n)=>{"use strict";n.d(t,{L:()=>a});var r=n(2263);function a(){return(0,r.Z)().siteConfig.themeConfig}},8802:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.removeTrailingSlash=t.addLeadingSlash=t.addTrailingSlash=void 0;const r=n(5913);function a(e){return e.endsWith("/")?e:`${e}/`}function o(e){return(0,r.removeSuffix)(e,"/")}t.addTrailingSlash=a,t.default=function(e,t){const{trailingSlash:n,baseUrl:r}=t;if(e.startsWith("#"))return e;if(void 0===n)return e;const[i]=e.split(/[#?]/),s="/"===i||i===r?i:(l=i,n?a(l):o(l));var l;return e.replace(i,s)},t.addLeadingSlash=function(e){return(0,r.addPrefix)(e,"/")},t.removeTrailingSlash=o},4143:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getErrorCausalChain=void 0,t.getErrorCausalChain=function e(t){return t.cause?[t,...e(t.cause)]:[t]}},8780:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.getErrorCausalChain=t.removePrefix=t.addSuffix=t.removeSuffix=t.addPrefix=t.removeTrailingSlash=t.addLeadingSlash=t.addTrailingSlash=t.applyTrailingSlash=t.blogPostContainerID=void 0,t.blogPostContainerID="__blog-post-container";var a=n(8802);Object.defineProperty(t,"applyTrailingSlash",{enumerable:!0,get:function(){return r(a).default}}),Object.defineProperty(t,"addTrailingSlash",{enumerable:!0,get:function(){return a.addTrailingSlash}}),Object.defineProperty(t,"addLeadingSlash",{enumerable:!0,get:function(){return a.addLeadingSlash}}),Object.defineProperty(t,"removeTrailingSlash",{enumerable:!0,get:function(){return a.removeTrailingSlash}});var o=n(5913);Object.defineProperty(t,"addPrefix",{enumerable:!0,get:function(){return o.addPrefix}}),Object.defineProperty(t,"removeSuffix",{enumerable:!0,get:function(){return o.removeSuffix}}),Object.defineProperty(t,"addSuffix",{enumerable:!0,get:function(){return o.addSuffix}}),Object.defineProperty(t,"removePrefix",{enumerable:!0,get:function(){return o.removePrefix}});var i=n(4143);Object.defineProperty(t,"getErrorCausalChain",{enumerable:!0,get:function(){return i.getErrorCausalChain}})},5913:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.removePrefix=t.addSuffix=t.removeSuffix=t.addPrefix=void 0,t.addPrefix=function(e,t){return e.startsWith(t)?e:`${t}${e}`},t.removeSuffix=function(e,t){return""===t?e:e.endsWith(t)?e.slice(0,-t.length):e},t.addSuffix=function(e,t){return e.endsWith(t)?e:`${e}${t}`},t.removePrefix=function(e,t){return e.startsWith(t)?e.slice(t.length):e}},311:(e,t,n)=>{"use strict";n.d(t,{Z:()=>i});n(7294);var r=n(6010);const a={loadingRing:"loadingRing_RJI3","loading-ring":"loading-ring_FB5o"};var o=n(5893);function i(e){let{className:t}=e;return(0,o.jsxs)("div",{className:(0,r.Z)(a.loadingRing,t),children:[(0,o.jsx)("div",{}),(0,o.jsx)("div",{}),(0,o.jsx)("div",{}),(0,o.jsx)("div",{})]})}},22:(e,t,n)=>{"use strict";n.d(t,{w:()=>s});var r=n(1336),a=n.n(r),o=n(1029);const i=new Map;function s(e,t){const n=`${e}${t}`;let r=i.get(n);return r||(r=async function(e,t){{const n=`${e}${o.J.replace("{dir}",t?`-${t.replace(/\//g,"-")}`:"")}`;if(new URL(n,location.origin).origin!==location.origin)throw new Error("Unexpected version url");const r=await(await fetch(n)).json(),i=r.map(((e,t)=>{let{documents:n,index:r}=e;return{type:t,documents:n,index:a().Index.load(r)}})),s=r.reduce(((e,t)=>{for(const n of t.index.invertedIndex)/\p{Unified_Ideograph}/u.test(n[0][0])&&e.add(n[0]);return e}),new Set);return{wrappedIndexes:i,zhDictionary:Array.from(s)}}return{wrappedIndexes:[],zhDictionary:[]}}(e,t),i.set(n,r)),r}},8202:(e,t,n)=>{"use strict";n.d(t,{v:()=>l});var r=n(1336),a=n.n(r);var o=n(1029);function i(e){return s(e).concat(s(e.filter((e=>{const t=e[e.length-1];return!t.trailing&&t.maybeTyping})),!0))}function s(e,t){return e.map((e=>({tokens:e.map((e=>e.value)),term:e.map((e=>({value:e.value,presence:a().Query.presence.REQUIRED,wildcard:(t?e.trailing||e.maybeTyping:e.trailing)?a().Query.wildcard.TRAILING:a().Query.wildcard.NONE})))})))}function l(e,t,n){return function(r,s){const l=function(e,t){if(1===t.length&&["ja","jp","th"].includes(t[0]))return a()[t[0]].tokenizer(e).map((e=>e.toString()));let n=/[^-\s]+/g;return t.includes("zh")&&(n=/\w+|\p{Unified_Ideograph}+/gu),e.toLowerCase().match(n)||[]}(r,o.dK);if(0===l.length)return void s([]);const c=function(e,t){const n=function(e,t){const n=[];return function e(r,a){if(0===r.length)return void n.push(a);const o=r[0];if(/\p{Unified_Ideograph}/u.test(o)){const n=function(e,t){const n=[];return function e(r,a){let o=0,i=!1;for(const s of t)if(r.substr(0,s.length)===s){const t={missed:a.missed,term:a.term.concat({value:s})};r.length>s.length?e(r.substr(s.length),t):n.push(t),i=!0}else for(let t=s.length-1;t>o;t-=1){const l=s.substr(0,t);if(r.substr(0,t)===l){o=t;const s={missed:a.missed,term:a.term.concat({value:l,trailing:!0})};r.length>t?e(r.substr(t),s):n.push(s),i=!0;break}}i||(r.length>0?e(r.substr(1),{missed:a.missed+1,term:a.term}):a.term.length>0&&n.push(a))}(e,{missed:0,term:[]}),n.sort(((e,t)=>{const n=e.missed>0?1:0,r=t.missed>0?1:0;return n!==r?n-r:e.term.length-t.term.length})).map((e=>e.term))}(o,t);for(const t of n){const n=a.concat(...t);e(r.slice(1),n)}}else{const t=a.concat({value:o});e(r.slice(1),t)}}(e,[]),n}(e,t);if(0===n.length)return[{tokens:e,term:e.map((e=>({value:e,presence:a().Query.presence.REQUIRED,wildcard:a().Query.wildcard.LEADING|a().Query.wildcard.TRAILING})))}];for(const a of n)a[a.length-1].maybeTyping=!0;const r=[];for(const i of o.dK)if("en"===i)o._k||r.unshift(a().stopWordFilter);else{const e=a()[i];e.stopWordFilter&&r.unshift(e.stopWordFilter)}let s;if(r.length>0){const e=e=>r.reduce(((e,t)=>e.filter((e=>t(e.value)))),e);s=[];const t=[];for(const r of n){const n=e(r);s.push(n),n.length0&&t.push(n)}n.push(...t)}else s=n.slice();const l=[];for(const a of s)if(a.length>2)for(let e=a.length-1;e>=0;e-=1)l.push(a.slice(0,e).concat(a.slice(e+1)));return i(n).concat(i(l))}(l,t),u=[];e:for(const{term:t,tokens:a}of c)for(const{documents:r,index:o,type:i}of e)if(u.push(...o.query((e=>{for(const n of t)e.term(n.value,{wildcard:n.wildcard,presence:n.presence})})).slice(0,n).filter((e=>!u.some((t=>t.document.i.toString()===e.ref)))).slice(0,n-u.length).map((t=>{const n=r.find((e=>e.i.toString()===t.ref));return{document:n,type:i,page:0!==i&&e[0].documents.find((e=>e.i===n.p)),metadata:t.matchData.metadata,tokens:a,score:t.score}}))),u.length>=n)break e;!function(e){e.forEach(((e,t)=>{e.index=t})),e.sort(((t,n)=>{let r=t.type>0&&t.page?e.findIndex((e=>e.document===t.page)):t.index,a=n.type>0&&n.page?e.findIndex((e=>e.document===n.page)):n.index;return-1===r&&(r=t.index),-1===a&&(a=n.index),r===a?0===t.type?-1:0===n.type?1:t.index-n.index:r-a}))}(u),function(e){e.forEach(((t,n)=>{n>0&&t.page&&e.some((e=>e.document===t.page))&&(n{"use strict";function r(e){return e.join(" \u203a ")}n.d(t,{e:()=>r})},1690:(e,t,n)=>{"use strict";function r(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}n.d(t,{X:()=>r})},1073:(e,t,n)=>{"use strict";function r(e,t){const n=[];for(const r of Object.values(e))r[t]&&n.push(...r[t].position);return n.sort(((e,t)=>e[0]-t[0]||t[1]-e[1]))}n.d(t,{m:()=>r})},2539:(e,t,n)=>{"use strict";n.d(t,{C:()=>a});var r=n(1690);function a(e,t,n){const o=[];for(const i of t){const n=e.toLowerCase().indexOf(i);if(n>=0){n>0&&o.push(a(e.substr(0,n),t)),o.push(`${(0,r.X)(e.substr(n,i.length))}`);const s=n+i.length;s${(0,r.X)(e)}`:(0,r.X)(e):o.join("")}},726:(e,t,n)=>{"use strict";n.d(t,{o:()=>l});var r=n(1690),a=n(2539);const o=/\w+|\p{Unified_Ideograph}/u;function i(e){const t=[];let n=0,r=e;for(;r.length>0;){const a=r.match(o);if(!a){t.push(r);break}a.index>0&&t.push(r.substring(0,a.index)),t.push(a[0]),n+=a.index+a[0].length,r=e.substring(n)}return t}var s=n(1029);function l(e,t,n,o){void 0===o&&(o=s.Hk);const{chunkIndex:l,chunks:c}=function(e,t,n){const o=[];let s=0,l=0,c=-1;for(;sl){const t=i(e.substring(l,u)).map((e=>({html:(0,r.X)(e),textLength:e.length})));for(const e of t)o.push(e)}-1===c&&(c=o.length),l=u+p,o.push({html:(0,a.C)(e.substring(u,l),n,!0),textLength:p})}}if(l({html:(0,r.X)(e),textLength:e.length})));for(const e of t)o.push(e)}return{chunkIndex:c,chunks:o}}(e,t,n),u=c.slice(0,l),p=c[l],d=[p.html],f=c.slice(l+1);let m=p.textLength,h=0,g=0,y=!1,b=!1;for(;m0){const e=u.pop();m+e.textLength<=o?(d.unshift(e.html),h+=e.textLength,m+=e.textLength):(y=!0,u.length=0)}else{if(!(f.length>0))break;{const e=f.shift();m+e.textLength<=o?(d.push(e.html),g+=e.textLength,m+=e.textLength):(b=!0,f.length=0)}}return(y||u.length>0)&&d.unshift("\u2026"),(b||f.length>0)&&d.push("\u2026"),d.join("")}},1029:(e,t,n)=>{"use strict";n.d(t,{vc:()=>i(),gQ:()=>g,H6:()=>d,hG:()=>v,l9:()=>y,dK:()=>s,_k:()=>l,pu:()=>h,AY:()=>f,t_:()=>m,Kc:()=>b,J:()=>c,Hk:()=>p,qo:()=>u,pQ:()=>k});var r=n(1336),a=n.n(r),o=n(813),i=n.n(o);n(892)(a()),n(1728).w(a()),n(4182)(a());const s=["en","zh"],l=!1,c="search-index{dir}.json?_=953c3732",u=8,p=50,d=!0,f=!0,m=!0,h="right",g=void 0,y=!0,b=null,v=!1,k=!1},1728:(e,t,n)=>{"use strict";function r(e){const t=new RegExp("^[^"+e+"]+","u"),n=new RegExp("[^"+e+"]+$","u");return function(e){return e.update((function(e){return e.replace(t,"").replace(n,"")}))}}function a(e,t){e.trimmerSupport.generateTrimmer=r,e.zh=function(){this.pipeline.reset(),this.pipeline.add(e.zh.trimmer,e.zh.stopWordFilter),t&&(this.tokenizer=t)},t&&(e.zh.tokenizer=t),e.zh.wordCharacters="\\u3400-\\u4DBF\\u4E00-\\u9FFC\\uFA0E\\uFA0F\\uFA11\\uFA13\\uFA14\\uFA1F\\uFA21\\uFA23\\uFA24\\uFA27-\\uFA29\\u{20000}-\\u{2A6DD}\\u{2A700}-\\u{2B734}\\u{2B740}-\\u{2B81D}\\u{2B820}-\\u{2CEA1}\\u{2CEB0}-\\u{2EBE0}\\u{30000}-\\u{3134A}",e.zh.trimmer=e.trimmerSupport.generateTrimmer(e.zh.wordCharacters),e.Pipeline.registerFunction(e.zh.trimmer,"trimmer-zh"),e.zh.stopWordFilter=e.generateStopWordFilter("\u7684 \u4e00 \u4e0d \u5728 \u4eba \u6709 \u662f \u4e3a \u4ee5 \u4e8e \u4e0a \u4ed6 \u800c \u540e \u4e4b \u6765 \u53ca \u4e86 \u56e0 \u4e0b \u53ef \u5230 \u7531 \u8fd9 \u4e0e \u4e5f \u6b64 \u4f46 \u5e76 \u4e2a \u5176 \u5df2 \u65e0 \u5c0f \u6211 \u4eec \u8d77 \u6700 \u518d \u4eca \u53bb \u597d \u53ea \u53c8 \u6216 \u5f88 \u4ea6 \u67d0 \u628a \u90a3 \u4f60 \u4e43 \u5b83 \u5427 \u88ab \u6bd4 \u522b \u8d81 \u5f53 \u4ece \u5230 \u5f97 \u6253 \u51e1 \u513f \u5c14 \u8be5 \u5404 \u7ed9 \u8ddf \u548c \u4f55 \u8fd8 \u5373 \u51e0 \u65e2 \u770b \u636e \u8ddd \u9760 \u5566 \u4e86 \u53e6 \u4e48 \u6bcf \u4eec \u561b \u62ff \u54ea \u90a3 \u60a8 \u51ed \u4e14 \u5374 \u8ba9 \u4ecd \u5565 \u5982 \u82e5 \u4f7f \u8c01 \u867d \u968f \u540c \u6240 \u5979 \u54c7 \u55e1 \u5f80 \u54ea \u4e9b \u5411 \u6cbf \u54df \u7528 \u4e8e \u54b1 \u5219 \u600e \u66fe \u81f3 \u81f4 \u7740 \u8bf8 \u81ea".split(" ")),e.Pipeline.registerFunction(e.zh.stopWordFilter,"stopWordFilter-zh")}n.d(t,{w:()=>a})},4750:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});r(n(412)).default.canUseDOM&&(window.Prism=window.Prism||{},window.Prism.manual=!0)},6010:(e,t,n)=>{"use strict";function r(e){var t,n,a="";if("string"==typeof e||"number"==typeof e)a+=e;else if("object"==typeof e)if(Array.isArray(e))for(t=0;ta});const a=function(){for(var e,t,n=0,a="";n{"use strict";n.d(t,{lX:()=>k,q_:()=>C,ob:()=>f,PP:()=>A,Ep:()=>d});var r=n(7462);function a(e){return"/"===e.charAt(0)}function o(e,t){for(var n=t,r=n+1,a=e.length;r=0;d--){var f=i[d];"."===f?o(i,d):".."===f?(o(i,d),p++):p&&(o(i,d),p--)}if(!c)for(;p--;p)i.unshift("..");!c||""===i[0]||i[0]&&a(i[0])||i.unshift("");var m=i.join("/");return n&&"/"!==m.substr(-1)&&(m+="/"),m};var s=n(8776);function l(e){return"/"===e.charAt(0)?e:"/"+e}function c(e){return"/"===e.charAt(0)?e.substr(1):e}function u(e,t){return function(e,t){return 0===e.toLowerCase().indexOf(t.toLowerCase())&&-1!=="/?#".indexOf(e.charAt(t.length))}(e,t)?e.substr(t.length):e}function p(e){return"/"===e.charAt(e.length-1)?e.slice(0,-1):e}function d(e){var t=e.pathname,n=e.search,r=e.hash,a=t||"/";return n&&"?"!==n&&(a+="?"===n.charAt(0)?n:"?"+n),r&&"#"!==r&&(a+="#"===r.charAt(0)?r:"#"+r),a}function f(e,t,n,a){var o;"string"==typeof e?(o=function(e){var t=e||"/",n="",r="",a=t.indexOf("#");-1!==a&&(r=t.substr(a),t=t.substr(0,a));var o=t.indexOf("?");return-1!==o&&(n=t.substr(o),t=t.substr(0,o)),{pathname:t,search:"?"===n?"":n,hash:"#"===r?"":r}}(e),o.state=t):(void 0===(o=(0,r.Z)({},e)).pathname&&(o.pathname=""),o.search?"?"!==o.search.charAt(0)&&(o.search="?"+o.search):o.search="",o.hash?"#"!==o.hash.charAt(0)&&(o.hash="#"+o.hash):o.hash="",void 0!==t&&void 0===o.state&&(o.state=t));try{o.pathname=decodeURI(o.pathname)}catch(s){throw s instanceof URIError?new URIError('Pathname "'+o.pathname+'" could not be decoded. This is likely caused by an invalid percent-encoding.'):s}return n&&(o.key=n),a?o.pathname?"/"!==o.pathname.charAt(0)&&(o.pathname=i(o.pathname,a.pathname)):o.pathname=a.pathname:o.pathname||(o.pathname="/"),o}function m(){var e=null;var t=[];return{setPrompt:function(t){return e=t,function(){e===t&&(e=null)}},confirmTransitionTo:function(t,n,r,a){if(null!=e){var o="function"==typeof e?e(t,n):e;"string"==typeof o?"function"==typeof r?r(o,a):a(!0):a(!1!==o)}else a(!0)},appendListener:function(e){var n=!0;function r(){n&&e.apply(void 0,arguments)}return t.push(r),function(){n=!1,t=t.filter((function(e){return e!==r}))}},notifyListeners:function(){for(var e=arguments.length,n=new Array(e),r=0;rt?n.splice(t,n.length-t,a):n.push(a),p({action:r,location:a,index:t,entries:n})}}))},replace:function(e,t){var r="REPLACE",a=f(e,t,h(),k.location);u.confirmTransitionTo(a,r,n,(function(e){e&&(k.entries[k.index]=a,p({action:r,location:a}))}))},go:v,goBack:function(){v(-1)},goForward:function(){v(1)},canGo:function(e){var t=k.index+e;return t>=0&&t{"use strict";var r=n(9864),a={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},o={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},i={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},s={};function l(e){return r.isMemo(e)?i:s[e.$$typeof]||a}s[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},s[r.Memo]=i;var c=Object.defineProperty,u=Object.getOwnPropertyNames,p=Object.getOwnPropertySymbols,d=Object.getOwnPropertyDescriptor,f=Object.getPrototypeOf,m=Object.prototype;e.exports=function e(t,n,r){if("string"!=typeof n){if(m){var a=f(n);a&&a!==m&&e(t,a,r)}var i=u(n);p&&(i=i.concat(p(n)));for(var s=l(t),h=l(n),g=0;g{"use strict";e.exports=function(e,t,n,r,a,o,i,s){if(!e){var l;if(void 0===t)l=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var c=[n,r,a,o,i,s],u=0;(l=new Error(t.replace(/%s/g,(function(){return c[u++]})))).name="Invariant Violation"}throw l.framesToPop=1,l}}},5826:e=>{e.exports=Array.isArray||function(e){return"[object Array]"==Object.prototype.toString.call(e)}},4182:function(e,t,n){var r,a;r=function(){return function(e){e.multiLanguage=function(){for(var t=Array.prototype.slice.call(arguments),n=t.join("-"),r="",a=[],o=[],i=0;i=n&&t[(a-=n)>>3]&1<<(7&a))return this.cursor++,!0}return!1},in_grouping_b:function(t,n,r){if(this.cursor>this.limit_backward){var a=e.charCodeAt(this.cursor-1);if(a<=r&&a>=n&&t[(a-=n)>>3]&1<<(7&a))return this.cursor--,!0}return!1},out_grouping:function(t,n,r){if(this.cursorr||a>3]&1<<(7&a)))return this.cursor++,!0}return!1},out_grouping_b:function(t,n,r){if(this.cursor>this.limit_backward){var a=e.charCodeAt(this.cursor-1);if(a>r||a>3]&1<<(7&a)))return this.cursor--,!0}return!1},eq_s:function(t,n){if(this.limit-this.cursor>1),p=0,d=s0||a==r||c)break;c=!0}}for(;;){if(s>=(f=t[r]).s_size){if(this.cursor=o+f.s_size,!f.method)return f.result;var h=f.method();if(this.cursor=o+f.s_size,h)return f.result}if((r=f.substring_i)<0)return 0}},find_among_b:function(t,n){for(var r=0,a=n,o=this.cursor,i=this.limit_backward,s=0,l=0,c=!1;;){for(var u=r+(a-r>>1),p=0,d=s=0;f--){if(o-d==i){p=-1;break}if(p=e.charCodeAt(o-1-d)-m.s[f])break;d++}if(p<0?(a=u,l=d):(r=u,s=d),a-r<=1){if(r>0||a==r||c)break;c=!0}}for(;;){var m;if(s>=(m=t[r]).s_size){if(this.cursor=o-m.s_size,!m.method)return m.result;var h=m.method();if(this.cursor=o-m.s_size,h)return m.result}if((r=m.substring_i)<0)return 0}},replace_s:function(t,n,r){var a=r.length-(n-t),o=e.substring(0,t),i=e.substring(n);return e=o+r+i,this.limit+=a,this.cursor>=n?this.cursor+=a:this.cursor>t&&(this.cursor=t),a},slice_check:function(){if(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>e.length)throw"faulty slice operation"},slice_from:function(e){this.slice_check(),this.replace_s(this.bra,this.ket,e)},slice_del:function(){this.slice_from("")},insert:function(e,t,n){var r=this.replace_s(e,t,n);e<=this.bra&&(this.bra+=r),e<=this.ket&&(this.ket+=r)},slice_to:function(){return this.slice_check(),e.substring(this.bra,this.ket)},eq_v_b:function(e){return this.eq_s_b(e.length,e)}}}},e.trimmerSupport={generateTrimmer:function(e){var t=new RegExp("^[^"+e+"]+"),n=new RegExp("[^"+e+"]+$");return function(e){return"function"==typeof e.update?e.update((function(e){return e.replace(t,"").replace(n,"")})):e.replace(t,"").replace(n,"")}}}}})?r.call(t,n,t,e):r)||(e.exports=a)},1336:(e,t,n)=>{var r,a;!function(){var o,i,s,l,c,u,p,d,f,m,h,g,y,b,v,k,w,S,x,E,_,C,j,A,T,P,I,N,O,$,L=function(e){var t=new L.Builder;return t.pipeline.add(L.trimmer,L.stopWordFilter,L.stemmer),t.searchPipeline.add(L.stemmer),e.call(t,t),t.build()};L.version="2.3.9",L.utils={},L.utils.warn=(o=this,function(e){o.console&&console.warn&&console.warn(e)}),L.utils.asString=function(e){return null==e?"":e.toString()},L.utils.clone=function(e){if(null==e)return e;for(var t=Object.create(null),n=Object.keys(e),r=0;r0){var l=L.utils.clone(t)||{};l.position=[i,s],l.index=a.length,a.push(new L.Token(n.slice(i,o),l))}i=o+1}}return a},L.tokenizer.separator=/[\s\-]+/,L.Pipeline=function(){this._stack=[]},L.Pipeline.registeredFunctions=Object.create(null),L.Pipeline.registerFunction=function(e,t){t in this.registeredFunctions&&L.utils.warn("Overwriting existing registered function: "+t),e.label=t,L.Pipeline.registeredFunctions[e.label]=e},L.Pipeline.warnIfFunctionNotRegistered=function(e){e.label&&e.label in this.registeredFunctions||L.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},L.Pipeline.load=function(e){var t=new L.Pipeline;return e.forEach((function(e){var n=L.Pipeline.registeredFunctions[e];if(!n)throw new Error("Cannot load unregistered function: "+e);t.add(n)})),t},L.Pipeline.prototype.add=function(){Array.prototype.slice.call(arguments).forEach((function(e){L.Pipeline.warnIfFunctionNotRegistered(e),this._stack.push(e)}),this)},L.Pipeline.prototype.after=function(e,t){L.Pipeline.warnIfFunctionNotRegistered(t);var n=this._stack.indexOf(e);if(-1==n)throw new Error("Cannot find existingFn");n+=1,this._stack.splice(n,0,t)},L.Pipeline.prototype.before=function(e,t){L.Pipeline.warnIfFunctionNotRegistered(t);var n=this._stack.indexOf(e);if(-1==n)throw new Error("Cannot find existingFn");this._stack.splice(n,0,t)},L.Pipeline.prototype.remove=function(e){var t=this._stack.indexOf(e);-1!=t&&this._stack.splice(t,1)},L.Pipeline.prototype.run=function(e){for(var t=this._stack.length,n=0;n1&&(oe&&(n=a),o!=e);)r=n-t,a=t+Math.floor(r/2),o=this.elements[2*a];return o==e||o>e?2*a:os?c+=2:i==s&&(t+=n[l+1]*r[c+1],l+=2,c+=2);return t},L.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},L.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),t=1,n=0;t0){var o,i=a.str.charAt(0);i in a.node.edges?o=a.node.edges[i]:(o=new L.TokenSet,a.node.edges[i]=o),1==a.str.length&&(o.final=!0),r.push({node:o,editsRemaining:a.editsRemaining,str:a.str.slice(1)})}if(0!=a.editsRemaining){if("*"in a.node.edges)var s=a.node.edges["*"];else{s=new L.TokenSet;a.node.edges["*"]=s}if(0==a.str.length&&(s.final=!0),r.push({node:s,editsRemaining:a.editsRemaining-1,str:a.str}),a.str.length>1&&r.push({node:a.node,editsRemaining:a.editsRemaining-1,str:a.str.slice(1)}),1==a.str.length&&(a.node.final=!0),a.str.length>=1){if("*"in a.node.edges)var l=a.node.edges["*"];else{l=new L.TokenSet;a.node.edges["*"]=l}1==a.str.length&&(l.final=!0),r.push({node:l,editsRemaining:a.editsRemaining-1,str:a.str.slice(1)})}if(a.str.length>1){var c,u=a.str.charAt(0),p=a.str.charAt(1);p in a.node.edges?c=a.node.edges[p]:(c=new L.TokenSet,a.node.edges[p]=c),1==a.str.length&&(c.final=!0),r.push({node:c,editsRemaining:a.editsRemaining-1,str:u+a.str.slice(2)})}}}return n},L.TokenSet.fromString=function(e){for(var t=new L.TokenSet,n=t,r=0,a=e.length;r=e;t--){var n=this.uncheckedNodes[t],r=n.child.toString();r in this.minimizedNodes?n.parent.edges[n.char]=this.minimizedNodes[r]:(n.child._str=r,this.minimizedNodes[r]=n.child),this.uncheckedNodes.pop()}},L.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},L.Index.prototype.search=function(e){return this.query((function(t){new L.QueryParser(e,t).parse()}))},L.Index.prototype.query=function(e){for(var t=new L.Query(this.fields),n=Object.create(null),r=Object.create(null),a=Object.create(null),o=Object.create(null),i=Object.create(null),s=0;s1?1:e},L.Builder.prototype.k1=function(e){this._k1=e},L.Builder.prototype.add=function(e,t){var n=e[this._ref],r=Object.keys(this._fields);this._documents[n]=t||{},this.documentCount+=1;for(var a=0;a=this.length)return L.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},L.QueryLexer.prototype.width=function(){return this.pos-this.start},L.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},L.QueryLexer.prototype.backup=function(){this.pos-=1},L.QueryLexer.prototype.acceptDigitRun=function(){var e,t;do{t=(e=this.next()).charCodeAt(0)}while(t>47&&t<58);e!=L.QueryLexer.EOS&&this.backup()},L.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(L.QueryLexer.TERM)),e.ignore(),e.more())return L.QueryLexer.lexText},L.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(L.QueryLexer.EDIT_DISTANCE),L.QueryLexer.lexText},L.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(L.QueryLexer.BOOST),L.QueryLexer.lexText},L.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(L.QueryLexer.TERM)},L.QueryLexer.termSeparator=L.tokenizer.separator,L.QueryLexer.lexText=function(e){for(;;){var t=e.next();if(t==L.QueryLexer.EOS)return L.QueryLexer.lexEOS;if(92!=t.charCodeAt(0)){if(":"==t)return L.QueryLexer.lexField;if("~"==t)return e.backup(),e.width()>0&&e.emit(L.QueryLexer.TERM),L.QueryLexer.lexEditDistance;if("^"==t)return e.backup(),e.width()>0&&e.emit(L.QueryLexer.TERM),L.QueryLexer.lexBoost;if("+"==t&&1===e.width())return e.emit(L.QueryLexer.PRESENCE),L.QueryLexer.lexText;if("-"==t&&1===e.width())return e.emit(L.QueryLexer.PRESENCE),L.QueryLexer.lexText;if(t.match(L.QueryLexer.termSeparator))return L.QueryLexer.lexTerm}else e.escapeCharacter()}},L.QueryParser=function(e,t){this.lexer=new L.QueryLexer(e),this.query=t,this.currentClause={},this.lexemeIdx=0},L.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=L.QueryParser.parseClause;e;)e=e(this);return this.query},L.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},L.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},L.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},L.QueryParser.parseClause=function(e){var t=e.peekLexeme();if(null!=t)switch(t.type){case L.QueryLexer.PRESENCE:return L.QueryParser.parsePresence;case L.QueryLexer.FIELD:return L.QueryParser.parseField;case L.QueryLexer.TERM:return L.QueryParser.parseTerm;default:var n="expected either a field or a term, found "+t.type;throw t.str.length>=1&&(n+=" with value '"+t.str+"'"),new L.QueryParseError(n,t.start,t.end)}},L.QueryParser.parsePresence=function(e){var t=e.consumeLexeme();if(null!=t){switch(t.str){case"-":e.currentClause.presence=L.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=L.Query.presence.REQUIRED;break;default:var n="unrecognised presence operator'"+t.str+"'";throw new L.QueryParseError(n,t.start,t.end)}var r=e.peekLexeme();if(null==r){n="expecting term or field, found nothing";throw new L.QueryParseError(n,t.start,t.end)}switch(r.type){case L.QueryLexer.FIELD:return L.QueryParser.parseField;case L.QueryLexer.TERM:return L.QueryParser.parseTerm;default:n="expecting term or field, found '"+r.type+"'";throw new L.QueryParseError(n,r.start,r.end)}}},L.QueryParser.parseField=function(e){var t=e.consumeLexeme();if(null!=t){if(-1==e.query.allFields.indexOf(t.str)){var n=e.query.allFields.map((function(e){return"'"+e+"'"})).join(", "),r="unrecognised field '"+t.str+"', possible fields: "+n;throw new L.QueryParseError(r,t.start,t.end)}e.currentClause.fields=[t.str];var a=e.peekLexeme();if(null==a){r="expecting term, found nothing";throw new L.QueryParseError(r,t.start,t.end)}if(a.type===L.QueryLexer.TERM)return L.QueryParser.parseTerm;r="expecting term, found '"+a.type+"'";throw new L.QueryParseError(r,a.start,a.end)}},L.QueryParser.parseTerm=function(e){var t=e.consumeLexeme();if(null!=t){e.currentClause.term=t.str.toLowerCase(),-1!=t.str.indexOf("*")&&(e.currentClause.usePipeline=!1);var n=e.peekLexeme();if(null!=n)switch(n.type){case L.QueryLexer.TERM:return e.nextClause(),L.QueryParser.parseTerm;case L.QueryLexer.FIELD:return e.nextClause(),L.QueryParser.parseField;case L.QueryLexer.EDIT_DISTANCE:return L.QueryParser.parseEditDistance;case L.QueryLexer.BOOST:return L.QueryParser.parseBoost;case L.QueryLexer.PRESENCE:return e.nextClause(),L.QueryParser.parsePresence;default:var r="Unexpected lexeme type '"+n.type+"'";throw new L.QueryParseError(r,n.start,n.end)}else e.nextClause()}},L.QueryParser.parseEditDistance=function(e){var t=e.consumeLexeme();if(null!=t){var n=parseInt(t.str,10);if(isNaN(n)){var r="edit distance must be numeric";throw new L.QueryParseError(r,t.start,t.end)}e.currentClause.editDistance=n;var a=e.peekLexeme();if(null!=a)switch(a.type){case L.QueryLexer.TERM:return e.nextClause(),L.QueryParser.parseTerm;case L.QueryLexer.FIELD:return e.nextClause(),L.QueryParser.parseField;case L.QueryLexer.EDIT_DISTANCE:return L.QueryParser.parseEditDistance;case L.QueryLexer.BOOST:return L.QueryParser.parseBoost;case L.QueryLexer.PRESENCE:return e.nextClause(),L.QueryParser.parsePresence;default:r="Unexpected lexeme type '"+a.type+"'";throw new L.QueryParseError(r,a.start,a.end)}else e.nextClause()}},L.QueryParser.parseBoost=function(e){var t=e.consumeLexeme();if(null!=t){var n=parseInt(t.str,10);if(isNaN(n)){var r="boost must be numeric";throw new L.QueryParseError(r,t.start,t.end)}e.currentClause.boost=n;var a=e.peekLexeme();if(null!=a)switch(a.type){case L.QueryLexer.TERM:return e.nextClause(),L.QueryParser.parseTerm;case L.QueryLexer.FIELD:return e.nextClause(),L.QueryParser.parseField;case L.QueryLexer.EDIT_DISTANCE:return L.QueryParser.parseEditDistance;case L.QueryLexer.BOOST:return L.QueryParser.parseBoost;case L.QueryLexer.PRESENCE:return e.nextClause(),L.QueryParser.parsePresence;default:r="Unexpected lexeme type '"+a.type+"'";throw new L.QueryParseError(r,a.start,a.end)}else e.nextClause()}},void 0===(a="function"==typeof(r=function(){return L})?r.call(t,n,t,e):r)||(e.exports=a)}()},813:function(e){e.exports=function(){"use strict";var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},n=function(){function e(e,t){for(var n=0;n1&&void 0!==arguments[1])||arguments[1],a=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:5e3;t(this,e),this.ctx=n,this.iframes=r,this.exclude=a,this.iframesTimeout=o}return n(e,[{key:"getContexts",value:function(){var e=[];return(void 0!==this.ctx&&this.ctx?NodeList.prototype.isPrototypeOf(this.ctx)?Array.prototype.slice.call(this.ctx):Array.isArray(this.ctx)?this.ctx:"string"==typeof this.ctx?Array.prototype.slice.call(document.querySelectorAll(this.ctx)):[this.ctx]:[]).forEach((function(t){var n=e.filter((function(e){return e.contains(t)})).length>0;-1!==e.indexOf(t)||n||e.push(t)})),e}},{key:"getIframeContents",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){},r=void 0;try{var a=e.contentWindow;if(r=a.document,!a||!r)throw new Error("iframe inaccessible")}catch(o){n()}r&&t(r)}},{key:"isIframeBlank",value:function(e){var t="about:blank",n=e.getAttribute("src").trim();return e.contentWindow.location.href===t&&n!==t&&n}},{key:"observeIframeLoad",value:function(e,t,n){var r=this,a=!1,o=null,i=function i(){if(!a){a=!0,clearTimeout(o);try{r.isIframeBlank(e)||(e.removeEventListener("load",i),r.getIframeContents(e,t,n))}catch(s){n()}}};e.addEventListener("load",i),o=setTimeout(i,this.iframesTimeout)}},{key:"onIframeReady",value:function(e,t,n){try{"complete"===e.contentWindow.document.readyState?this.isIframeBlank(e)?this.observeIframeLoad(e,t,n):this.getIframeContents(e,t,n):this.observeIframeLoad(e,t,n)}catch(r){n()}}},{key:"waitForIframes",value:function(e,t){var n=this,r=0;this.forEachIframe(e,(function(){return!0}),(function(e){r++,n.waitForIframes(e.querySelector("html"),(function(){--r||t()}))}),(function(e){e||t()}))}},{key:"forEachIframe",value:function(t,n,r){var a=this,o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},i=t.querySelectorAll("iframe"),s=i.length,l=0;i=Array.prototype.slice.call(i);var c=function(){--s<=0&&o(l)};s||c(),i.forEach((function(t){e.matches(t,a.exclude)?c():a.onIframeReady(t,(function(e){n(t)&&(l++,r(e)),c()}),c)}))}},{key:"createIterator",value:function(e,t,n){return document.createNodeIterator(e,t,n,!1)}},{key:"createInstanceOnIframe",value:function(t){return new e(t.querySelector("html"),this.iframes)}},{key:"compareNodeIframe",value:function(e,t,n){if(e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_PRECEDING){if(null===t)return!0;if(t.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_FOLLOWING)return!0}return!1}},{key:"getIteratorNode",value:function(e){var t=e.previousNode();return{prevNode:t,node:(null===t||e.nextNode())&&e.nextNode()}}},{key:"checkIframeFilter",value:function(e,t,n,r){var a=!1,o=!1;return r.forEach((function(e,t){e.val===n&&(a=t,o=e.handled)})),this.compareNodeIframe(e,t,n)?(!1!==a||o?!1===a||o||(r[a].handled=!0):r.push({val:n,handled:!0}),!0):(!1===a&&r.push({val:n,handled:!1}),!1)}},{key:"handleOpenIframes",value:function(e,t,n,r){var a=this;e.forEach((function(e){e.handled||a.getIframeContents(e.val,(function(e){a.createInstanceOnIframe(e).forEachNode(t,n,r)}))}))}},{key:"iterateThroughNodes",value:function(e,t,n,r,a){for(var o=this,i=this.createIterator(t,e,r),s=[],l=[],c=void 0,u=void 0,p=function(){var e=o.getIteratorNode(i);return u=e.prevNode,c=e.node};p();)this.iframes&&this.forEachIframe(t,(function(e){return o.checkIframeFilter(c,u,e,s)}),(function(t){o.createInstanceOnIframe(t).forEachNode(e,(function(e){return l.push(e)}),r)})),l.push(c);l.forEach((function(e){n(e)})),this.iframes&&this.handleOpenIframes(s,e,n,r),a()}},{key:"forEachNode",value:function(e,t,n){var r=this,a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},o=this.getContexts(),i=o.length;i||a(),o.forEach((function(o){var s=function(){r.iterateThroughNodes(e,o,t,n,(function(){--i<=0&&a()}))};r.iframes?r.waitForIframes(o,s):s()}))}}],[{key:"matches",value:function(e,t){var n="string"==typeof t?[t]:t,r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector;if(r){var a=!1;return n.every((function(t){return!r.call(e,t)||(a=!0,!1)})),a}return!1}}]),e}(),o=function(){function o(e){t(this,o),this.ctx=e,this.ie=!1;var n=window.navigator.userAgent;(n.indexOf("MSIE")>-1||n.indexOf("Trident")>-1)&&(this.ie=!0)}return n(o,[{key:"log",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"debug",r=this.opt.log;this.opt.debug&&"object"===(void 0===r?"undefined":e(r))&&"function"==typeof r[n]&&r[n]("mark.js: "+t)}},{key:"escapeStr",value:function(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}},{key:"createRegExp",value:function(e){return"disabled"!==this.opt.wildcards&&(e=this.setupWildcardsRegExp(e)),e=this.escapeStr(e),Object.keys(this.opt.synonyms).length&&(e=this.createSynonymsRegExp(e)),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),this.opt.diacritics&&(e=this.createDiacriticsRegExp(e)),e=this.createMergedBlanksRegExp(e),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.createJoinersRegExp(e)),"disabled"!==this.opt.wildcards&&(e=this.createWildcardsRegExp(e)),e=this.createAccuracyRegExp(e)}},{key:"createSynonymsRegExp",value:function(e){var t=this.opt.synonyms,n=this.opt.caseSensitive?"":"i",r=this.opt.ignoreJoiners||this.opt.ignorePunctuation.length?"\0":"";for(var a in t)if(t.hasOwnProperty(a)){var o=t[a],i="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(a):this.escapeStr(a),s="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(o):this.escapeStr(o);""!==i&&""!==s&&(e=e.replace(new RegExp("("+this.escapeStr(i)+"|"+this.escapeStr(s)+")","gm"+n),r+"("+this.processSynomyms(i)+"|"+this.processSynomyms(s)+")"+r))}return e}},{key:"processSynomyms",value:function(e){return(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),e}},{key:"setupWildcardsRegExp",value:function(e){return(e=e.replace(/(?:\\)*\?/g,(function(e){return"\\"===e.charAt(0)?"?":"\x01"}))).replace(/(?:\\)*\*/g,(function(e){return"\\"===e.charAt(0)?"*":"\x02"}))}},{key:"createWildcardsRegExp",value:function(e){var t="withSpaces"===this.opt.wildcards;return e.replace(/\u0001/g,t?"[\\S\\s]?":"\\S?").replace(/\u0002/g,t?"[\\S\\s]*?":"\\S*")}},{key:"setupIgnoreJoinersRegExp",value:function(e){return e.replace(/[^(|)\\]/g,(function(e,t,n){var r=n.charAt(t+1);return/[(|)\\]/.test(r)||""===r?e:e+"\0"}))}},{key:"createJoinersRegExp",value:function(e){var t=[],n=this.opt.ignorePunctuation;return Array.isArray(n)&&n.length&&t.push(this.escapeStr(n.join(""))),this.opt.ignoreJoiners&&t.push("\\u00ad\\u200b\\u200c\\u200d"),t.length?e.split(/\u0000+/).join("["+t.join("")+"]*"):e}},{key:"createDiacriticsRegExp",value:function(e){var t=this.opt.caseSensitive?"":"i",n=this.opt.caseSensitive?["a\xe0\xe1\u1ea3\xe3\u1ea1\u0103\u1eb1\u1eaf\u1eb3\u1eb5\u1eb7\xe2\u1ea7\u1ea5\u1ea9\u1eab\u1ead\xe4\xe5\u0101\u0105","A\xc0\xc1\u1ea2\xc3\u1ea0\u0102\u1eb0\u1eae\u1eb2\u1eb4\u1eb6\xc2\u1ea6\u1ea4\u1ea8\u1eaa\u1eac\xc4\xc5\u0100\u0104","c\xe7\u0107\u010d","C\xc7\u0106\u010c","d\u0111\u010f","D\u0110\u010e","e\xe8\xe9\u1ebb\u1ebd\u1eb9\xea\u1ec1\u1ebf\u1ec3\u1ec5\u1ec7\xeb\u011b\u0113\u0119","E\xc8\xc9\u1eba\u1ebc\u1eb8\xca\u1ec0\u1ebe\u1ec2\u1ec4\u1ec6\xcb\u011a\u0112\u0118","i\xec\xed\u1ec9\u0129\u1ecb\xee\xef\u012b","I\xcc\xcd\u1ec8\u0128\u1eca\xce\xcf\u012a","l\u0142","L\u0141","n\xf1\u0148\u0144","N\xd1\u0147\u0143","o\xf2\xf3\u1ecf\xf5\u1ecd\xf4\u1ed3\u1ed1\u1ed5\u1ed7\u1ed9\u01a1\u1edf\u1ee1\u1edb\u1edd\u1ee3\xf6\xf8\u014d","O\xd2\xd3\u1ece\xd5\u1ecc\xd4\u1ed2\u1ed0\u1ed4\u1ed6\u1ed8\u01a0\u1ede\u1ee0\u1eda\u1edc\u1ee2\xd6\xd8\u014c","r\u0159","R\u0158","s\u0161\u015b\u0219\u015f","S\u0160\u015a\u0218\u015e","t\u0165\u021b\u0163","T\u0164\u021a\u0162","u\xf9\xfa\u1ee7\u0169\u1ee5\u01b0\u1eeb\u1ee9\u1eed\u1eef\u1ef1\xfb\xfc\u016f\u016b","U\xd9\xda\u1ee6\u0168\u1ee4\u01af\u1eea\u1ee8\u1eec\u1eee\u1ef0\xdb\xdc\u016e\u016a","y\xfd\u1ef3\u1ef7\u1ef9\u1ef5\xff","Y\xdd\u1ef2\u1ef6\u1ef8\u1ef4\u0178","z\u017e\u017c\u017a","Z\u017d\u017b\u0179"]:["a\xe0\xe1\u1ea3\xe3\u1ea1\u0103\u1eb1\u1eaf\u1eb3\u1eb5\u1eb7\xe2\u1ea7\u1ea5\u1ea9\u1eab\u1ead\xe4\xe5\u0101\u0105A\xc0\xc1\u1ea2\xc3\u1ea0\u0102\u1eb0\u1eae\u1eb2\u1eb4\u1eb6\xc2\u1ea6\u1ea4\u1ea8\u1eaa\u1eac\xc4\xc5\u0100\u0104","c\xe7\u0107\u010dC\xc7\u0106\u010c","d\u0111\u010fD\u0110\u010e","e\xe8\xe9\u1ebb\u1ebd\u1eb9\xea\u1ec1\u1ebf\u1ec3\u1ec5\u1ec7\xeb\u011b\u0113\u0119E\xc8\xc9\u1eba\u1ebc\u1eb8\xca\u1ec0\u1ebe\u1ec2\u1ec4\u1ec6\xcb\u011a\u0112\u0118","i\xec\xed\u1ec9\u0129\u1ecb\xee\xef\u012bI\xcc\xcd\u1ec8\u0128\u1eca\xce\xcf\u012a","l\u0142L\u0141","n\xf1\u0148\u0144N\xd1\u0147\u0143","o\xf2\xf3\u1ecf\xf5\u1ecd\xf4\u1ed3\u1ed1\u1ed5\u1ed7\u1ed9\u01a1\u1edf\u1ee1\u1edb\u1edd\u1ee3\xf6\xf8\u014dO\xd2\xd3\u1ece\xd5\u1ecc\xd4\u1ed2\u1ed0\u1ed4\u1ed6\u1ed8\u01a0\u1ede\u1ee0\u1eda\u1edc\u1ee2\xd6\xd8\u014c","r\u0159R\u0158","s\u0161\u015b\u0219\u015fS\u0160\u015a\u0218\u015e","t\u0165\u021b\u0163T\u0164\u021a\u0162","u\xf9\xfa\u1ee7\u0169\u1ee5\u01b0\u1eeb\u1ee9\u1eed\u1eef\u1ef1\xfb\xfc\u016f\u016bU\xd9\xda\u1ee6\u0168\u1ee4\u01af\u1eea\u1ee8\u1eec\u1eee\u1ef0\xdb\xdc\u016e\u016a","y\xfd\u1ef3\u1ef7\u1ef9\u1ef5\xffY\xdd\u1ef2\u1ef6\u1ef8\u1ef4\u0178","z\u017e\u017c\u017aZ\u017d\u017b\u0179"],r=[];return e.split("").forEach((function(a){n.every((function(n){if(-1!==n.indexOf(a)){if(r.indexOf(n)>-1)return!1;e=e.replace(new RegExp("["+n+"]","gm"+t),"["+n+"]"),r.push(n)}return!0}))})),e}},{key:"createMergedBlanksRegExp",value:function(e){return e.replace(/[\s]+/gim,"[\\s]+")}},{key:"createAccuracyRegExp",value:function(e){var t=this,n="!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~\xa1\xbf",r=this.opt.accuracy,a="string"==typeof r?r:r.value,o="string"==typeof r?[]:r.limiters,i="";switch(o.forEach((function(e){i+="|"+t.escapeStr(e)})),a){case"partially":default:return"()("+e+")";case"complementary":return"()([^"+(i="\\s"+(i||this.escapeStr(n)))+"]*"+e+"[^"+i+"]*)";case"exactly":return"(^|\\s"+i+")("+e+")(?=$|\\s"+i+")"}}},{key:"getSeparatedKeywords",value:function(e){var t=this,n=[];return e.forEach((function(e){t.opt.separateWordSearch?e.split(" ").forEach((function(e){e.trim()&&-1===n.indexOf(e)&&n.push(e)})):e.trim()&&-1===n.indexOf(e)&&n.push(e)})),{keywords:n.sort((function(e,t){return t.length-e.length})),length:n.length}}},{key:"isNumeric",value:function(e){return Number(parseFloat(e))==e}},{key:"checkRanges",value:function(e){var t=this;if(!Array.isArray(e)||"[object Object]"!==Object.prototype.toString.call(e[0]))return this.log("markRanges() will only accept an array of objects"),this.opt.noMatch(e),[];var n=[],r=0;return e.sort((function(e,t){return e.start-t.start})).forEach((function(e){var a=t.callNoMatchOnInvalidRanges(e,r),o=a.start,i=a.end;a.valid&&(e.start=o,e.length=i-o,n.push(e),r=i)})),n}},{key:"callNoMatchOnInvalidRanges",value:function(e,t){var n=void 0,r=void 0,a=!1;return e&&void 0!==e.start?(r=(n=parseInt(e.start,10))+parseInt(e.length,10),this.isNumeric(e.start)&&this.isNumeric(e.length)&&r-t>0&&r-n>0?a=!0:(this.log("Ignoring invalid or overlapping range: "+JSON.stringify(e)),this.opt.noMatch(e))):(this.log("Ignoring invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:n,end:r,valid:a}}},{key:"checkWhitespaceRanges",value:function(e,t,n){var r=void 0,a=!0,o=n.length,i=t-o,s=parseInt(e.start,10)-i;return(r=(s=s>o?o:s)+parseInt(e.length,10))>o&&(r=o,this.log("End range automatically set to the max value of "+o)),s<0||r-s<0||s>o||r>o?(a=!1,this.log("Invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)):""===n.substring(s,r).replace(/\s+/g,"")&&(a=!1,this.log("Skipping whitespace only range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:s,end:r,valid:a}}},{key:"getTextNodes",value:function(e){var t=this,n="",r=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,(function(e){r.push({start:n.length,end:(n+=e.textContent).length,node:e})}),(function(e){return t.matchesExclude(e.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}),(function(){e({value:n,nodes:r})}))}},{key:"matchesExclude",value:function(e){return a.matches(e,this.opt.exclude.concat(["script","style","title","head","html"]))}},{key:"wrapRangeInTextNode",value:function(e,t,n){var r=this.opt.element?this.opt.element:"mark",a=e.splitText(t),o=a.splitText(n-t),i=document.createElement(r);return i.setAttribute("data-markjs","true"),this.opt.className&&i.setAttribute("class",this.opt.className),i.textContent=a.textContent,a.parentNode.replaceChild(i,a),o}},{key:"wrapRangeInMappedTextNode",value:function(e,t,n,r,a){var o=this;e.nodes.every((function(i,s){var l=e.nodes[s+1];if(void 0===l||l.start>t){if(!r(i.node))return!1;var c=t-i.start,u=(n>i.end?i.end:n)-i.start,p=e.value.substr(0,i.start),d=e.value.substr(u+i.start);if(i.node=o.wrapRangeInTextNode(i.node,c,u),e.value=p+d,e.nodes.forEach((function(t,n){n>=s&&(e.nodes[n].start>0&&n!==s&&(e.nodes[n].start-=u),e.nodes[n].end-=u)})),n-=u,a(i.node.previousSibling,i.start),!(n>i.end))return!1;t=i.end}return!0}))}},{key:"wrapMatches",value:function(e,t,n,r,a){var o=this,i=0===t?0:t+1;this.getTextNodes((function(t){t.nodes.forEach((function(t){t=t.node;for(var a=void 0;null!==(a=e.exec(t.textContent))&&""!==a[i];)if(n(a[i],t)){var s=a.index;if(0!==i)for(var l=1;l{"use strict";n.r(t)},2497:(e,t,n)=>{"use strict";n.r(t)},2295:(e,t,n)=>{"use strict";n.r(t)},4865:function(e,t,n){var r,a;r=function(){var e,t,n={version:"0.2.0"},r=n.settings={minimum:.08,easing:"ease",positionUsing:"",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,showSpinner:!0,barSelector:'[role="bar"]',spinnerSelector:'[role="spinner"]',parent:"body",template:'
'};function a(e,t,n){return en?n:e}function o(e){return 100*(-1+e)}function i(e,t,n){var a;return(a="translate3d"===r.positionUsing?{transform:"translate3d("+o(e)+"%,0,0)"}:"translate"===r.positionUsing?{transform:"translate("+o(e)+"%,0)"}:{"margin-left":o(e)+"%"}).transition="all "+t+"ms "+n,a}n.configure=function(e){var t,n;for(t in e)void 0!==(n=e[t])&&e.hasOwnProperty(t)&&(r[t]=n);return this},n.status=null,n.set=function(e){var t=n.isStarted();e=a(e,r.minimum,1),n.status=1===e?null:e;var o=n.render(!t),c=o.querySelector(r.barSelector),u=r.speed,p=r.easing;return o.offsetWidth,s((function(t){""===r.positionUsing&&(r.positionUsing=n.getPositioningCSS()),l(c,i(e,u,p)),1===e?(l(o,{transition:"none",opacity:1}),o.offsetWidth,setTimeout((function(){l(o,{transition:"all "+u+"ms linear",opacity:0}),setTimeout((function(){n.remove(),t()}),u)}),u)):setTimeout(t,u)})),this},n.isStarted=function(){return"number"==typeof n.status},n.start=function(){n.status||n.set(0);var e=function(){setTimeout((function(){n.status&&(n.trickle(),e())}),r.trickleSpeed)};return r.trickle&&e(),this},n.done=function(e){return e||n.status?n.inc(.3+.5*Math.random()).set(1):this},n.inc=function(e){var t=n.status;return t?("number"!=typeof e&&(e=(1-t)*a(Math.random()*t,.1,.95)),t=a(t+e,0,.994),n.set(t)):n.start()},n.trickle=function(){return n.inc(Math.random()*r.trickleRate)},e=0,t=0,n.promise=function(r){return r&&"resolved"!==r.state()?(0===t&&n.start(),e++,t++,r.always((function(){0==--t?(e=0,n.done()):n.set((e-t)/e)})),this):this},n.render=function(e){if(n.isRendered())return document.getElementById("nprogress");u(document.documentElement,"nprogress-busy");var t=document.createElement("div");t.id="nprogress",t.innerHTML=r.template;var a,i=t.querySelector(r.barSelector),s=e?"-100":o(n.status||0),c=document.querySelector(r.parent);return l(i,{transition:"all 0 linear",transform:"translate3d("+s+"%,0,0)"}),r.showSpinner||(a=t.querySelector(r.spinnerSelector))&&f(a),c!=document.body&&u(c,"nprogress-custom-parent"),c.appendChild(t),t},n.remove=function(){p(document.documentElement,"nprogress-busy"),p(document.querySelector(r.parent),"nprogress-custom-parent");var e=document.getElementById("nprogress");e&&f(e)},n.isRendered=function(){return!!document.getElementById("nprogress")},n.getPositioningCSS=function(){var e=document.body.style,t="WebkitTransform"in e?"Webkit":"MozTransform"in e?"Moz":"msTransform"in e?"ms":"OTransform"in e?"O":"";return t+"Perspective"in e?"translate3d":t+"Transform"in e?"translate":"margin"};var s=function(){var e=[];function t(){var n=e.shift();n&&n(t)}return function(n){e.push(n),1==e.length&&t()}}(),l=function(){var e=["Webkit","O","Moz","ms"],t={};function n(e){return e.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,(function(e,t){return t.toUpperCase()}))}function r(t){var n=document.body.style;if(t in n)return t;for(var r,a=e.length,o=t.charAt(0).toUpperCase()+t.slice(1);a--;)if((r=e[a]+o)in n)return r;return t}function a(e){return e=n(e),t[e]||(t[e]=r(e))}function o(e,t,n){t=a(t),e.style[t]=n}return function(e,t){var n,r,a=arguments;if(2==a.length)for(n in t)void 0!==(r=t[n])&&t.hasOwnProperty(n)&&o(e,n,r);else o(e,a[1],a[2])}}();function c(e,t){return("string"==typeof e?e:d(e)).indexOf(" "+t+" ")>=0}function u(e,t){var n=d(e),r=n+t;c(n,t)||(e.className=r.substring(1))}function p(e,t){var n,r=d(e);c(e,t)&&(n=r.replace(" "+t+" "," "),e.className=n.substring(1,n.length-1))}function d(e){return(" "+(e.className||"")+" ").replace(/\s+/gi," ")}function f(e){e&&e.parentNode&&e.parentNode.removeChild(e)}return n},void 0===(a="function"==typeof r?r.call(t,n,t,e):r)||(e.exports=a)},4779:(e,t,n)=>{var r=n(5826);e.exports=f,e.exports.parse=o,e.exports.compile=function(e,t){return s(o(e,t),t)},e.exports.tokensToFunction=s,e.exports.tokensToRegExp=d;var a=new RegExp(["(\\\\.)","([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))"].join("|"),"g");function o(e,t){for(var n,r=[],o=0,i=0,s="",u=t&&t.delimiter||"/";null!=(n=a.exec(e));){var p=n[0],d=n[1],f=n.index;if(s+=e.slice(i,f),i=f+p.length,d)s+=d[1];else{var m=e[i],h=n[2],g=n[3],y=n[4],b=n[5],v=n[6],k=n[7];s&&(r.push(s),s="");var w=null!=h&&null!=m&&m!==h,S="+"===v||"*"===v,x="?"===v||"*"===v,E=n[2]||u,_=y||b;r.push({name:g||o++,prefix:h||"",delimiter:E,optional:x,repeat:S,partial:w,asterisk:!!k,pattern:_?c(_):k?".*":"[^"+l(E)+"]+?"})}}return i{!function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",n={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},r={bash:n,environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--|\+\+|\*\*=?|<<=?|>>=?|&&|\|\||[=!+\-*/%<>^&|]=?|[?~:]/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{1,2})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b[\w-]+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?:\.\w+)*(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},parameter:{pattern:/(^|\s)-{1,2}(?:\w+:[+-]?)?\w+(?:\.\w+)*(?=[=\s]|$)/,alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:r},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:n}},{pattern:/(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,lookbehind:!0,greedy:!0,inside:r},{pattern:/(^|[^$\\])'[^']*'/,lookbehind:!0,greedy:!0},{pattern:/\$'(?:[^'\\]|\\[\s\S])*'/,greedy:!0,inside:{entity:r.entity}}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:r.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|apt-cache|apt-get|aptitude|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cargo|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|docker|docker-compose|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|java|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|node|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|podman|podman-compose|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|sysctl|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vcpkg|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:case|do|done|elif|else|esac|fi|for|function|if|in|select|then|until|while)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|alias|bind|break|builtin|caller|cd|command|continue|declare|echo|enable|eval|exec|exit|export|getopts|hash|help|let|local|logout|mapfile|printf|pwd|read|readarray|readonly|return|set|shift|shopt|source|test|times|trap|type|typeset|ulimit|umask|unalias|unset)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:false|true)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|=[=~]?|!=?|<<[<-]?|[&\d]?>>|\d[<>]&?|[<>][&=]?|&[>&]?|\|[&|]?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},n.inside=e.languages.bash;for(var a=["comment","function-name","for-or-select","assign-left","parameter","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],o=r.variable[1].inside,i=0;i{Prism.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},Prism.languages.webmanifest=Prism.languages.json},6854:()=>{!function(e){function t(e,t){return"___"+e.toUpperCase()+t+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(n,r,a,o){if(n.language===r){var i=n.tokenStack=[];n.code=n.code.replace(a,(function(e){if("function"==typeof o&&!o(e))return e;for(var a,s=i.length;-1!==n.code.indexOf(a=t(r,s));)++s;return i[s]=e,a})),n.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(n,r){if(n.language===r&&n.tokenStack){n.grammar=e.languages[r];var a=0,o=Object.keys(n.tokenStack);!function i(s){for(var l=0;l=o.length);l++){var c=s[l];if("string"==typeof c||c.content&&"string"==typeof c.content){var u=o[a],p=n.tokenStack[u],d="string"==typeof c?c:c.content,f=t(r,u),m=d.indexOf(f);if(m>-1){++a;var h=d.substring(0,m),g=new e.Token(r,e.tokenize(p,n.grammar),"language-"+r,p),y=d.substring(m+f.length),b=[];h&&b.push.apply(b,i([h])),b.push(g),y&&b.push.apply(b,i([y])),"string"==typeof c?s.splice.apply(s,[l,1].concat(b)):c.content=b}}else c.content&&i(c.content)}return s}(n.tokens)}}}})}(Prism)},1486:(e,t,n)=>{var r={"./prism-bash":7874,"./prism-json":4277};function a(e){var t=o(e);return n(t)}function o(e){if(!n.o(r,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r[e]}a.keys=function(){return Object.keys(r)},a.resolve=o,e.exports=a,a.id=1486},2703:(e,t,n)=>{"use strict";var r=n(414);function a(){}function o(){}o.resetWarningCache=a,e.exports=function(){function e(e,t,n,a,o,i){if(i!==r){var s=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw s.name="Invariant Violation",s}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:o,resetWarningCache:a};return n.PropTypes=n,n}},5697:(e,t,n)=>{e.exports=n(2703)()},414:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},4448:(e,t,n)=>{"use strict";var r=n(7294),a=n(3840);function o(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n