From 9a080c1f10cf71730d5e539ef51e4d24b41de2e6 Mon Sep 17 00:00:00 2001
From: Thomas Citharel <tcit@tcit.fr>
Date: Sat, 27 Jun 2020 19:12:45 +0200
Subject: [PATCH 1/4] Introduce support for 3rd-party auth (OAuth2 & LDAP)

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
---
 config/config.exs                             |  24 ++
 js/src/components/User/AuthProvider.vue       |  26 ++
 js/src/components/User/AuthProviders.vue      |  26 ++
 js/src/graphql/config.ts                      |   7 +
 js/src/graphql/user.ts                        |  11 +-
 js/src/i18n/en_US.json                        |   7 +-
 js/src/i18n/fr_FR.json                        |   7 +-
 js/src/router/index.ts                        |   5 +
 js/src/types/config.model.ts                  |   9 +
 js/src/types/current-user.model.ts            |  24 +-
 js/src/types/login-error-code.model.ts        |   2 +
 js/src/utils/auth.ts                          |  11 +
 js/src/views/Settings/AccountSettings.vue     |  59 ++++-
 js/src/views/Settings/Notifications.vue       |   7 +-
 js/src/views/Settings/Preferences.vue         |   6 +-
 js/src/views/User/Login.vue                   |  29 +++
 js/src/views/User/ProviderValidation.vue      |  64 +++++
 js/src/views/User/Register.vue                |   9 +-
 js/src/views/User/Validate.vue                |   3 +-
 lib/graphql/resolvers/config.ex               |   6 +-
 lib/graphql/resolvers/person.ex               |   6 +-
 lib/graphql/resolvers/user.ex                 |  68 +++--
 lib/graphql/schema/config.ex                  |  11 +
 lib/graphql/schema/user.ex                    |   2 +
 lib/mobilizon/actors/actors.ex                |  12 +-
 lib/mobilizon/config.ex                       |  18 ++
 lib/mobilizon/users/user.ex                   |  17 +-
 lib/mobilizon/users/users.ex                  |  75 ++----
 lib/service/auth/authenticator.ex             |  93 +++++++
 lib/service/auth/ldap_authenticator.ex        | 180 +++++++++++++
 lib/service/auth/mobilizon_authenticator.ex   |  39 +++
 lib/web/controllers/auth_controller.ex        |  82 ++++++
 lib/web/router.ex                             |   4 +
 lib/web/views/auth_view.ex                    |  29 +++
 mix.exs                                       |  29 ++-
 mix.lock                                      |  19 +-
 priv/gettext/en/LC_MESSAGES/default.po        |   4 +-
 ...er_to_user_and_make_password_mandatory.exs |  17 ++
 test/graphql/resolvers/participant_test.exs   |   2 +-
 test/graphql/resolvers/report_test.exs        |   2 +-
 test/graphql/resolvers/user_test.exs          | 186 ++++++--------
 test/mobilizon/users/users_test.exs           |   8 -
 test/service/auth/authentificator_test.exs    |  34 +++
 .../auth/ldap_authentificator_test.exs        | 238 ++++++++++++++++++
 .../auth/mobilizon_authentificator_test.exs   |  29 +++
 test/support/factory.ex                       |   3 +-
 test/support/helpers.ex                       |  17 +-
 test/web/controllers/auth_controller_test.exs |  54 ++++
 48 files changed, 1380 insertions(+), 240 deletions(-)
 create mode 100644 js/src/components/User/AuthProvider.vue
 create mode 100644 js/src/components/User/AuthProviders.vue
 create mode 100644 js/src/views/User/ProviderValidation.vue
 create mode 100644 lib/service/auth/authenticator.ex
 create mode 100644 lib/service/auth/ldap_authenticator.ex
 create mode 100644 lib/service/auth/mobilizon_authenticator.ex
 create mode 100644 lib/web/controllers/auth_controller.ex
 create mode 100644 lib/web/views/auth_view.ex
 create mode 100644 priv/repo/migrations/20200630123819_add_provider_to_user_and_make_password_mandatory.exs
 create mode 100644 test/service/auth/authentificator_test.exs
 create mode 100644 test/service/auth/ldap_authentificator_test.exs
 create mode 100644 test/service/auth/mobilizon_authentificator_test.exs
 create mode 100644 test/web/controllers/auth_controller_test.exs

diff --git a/config/config.exs b/config/config.exs
index 012953b09..e4e4aaad2 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -118,6 +118,30 @@ config :guardian, Guardian.DB,
 
 config :elixir, :time_zone_database, Tzdata.TimeZoneDatabase
 
+config :mobilizon,
+       Mobilizon.Service.Auth.Authenticator,
+       Mobilizon.Service.Auth.MobilizonAuthenticator
+
+config :ueberauth,
+       Ueberauth,
+       providers: []
+
+config :mobilizon, :auth, oauth_consumer_strategies: []
+
+config :mobilizon, :ldap,
+  enabled: System.get_env("LDAP_ENABLED") == "true",
+  host: System.get_env("LDAP_HOST") || "localhost",
+  port: String.to_integer(System.get_env("LDAP_PORT") || "389"),
+  ssl: System.get_env("LDAP_SSL") == "true",
+  sslopts: [],
+  tls: System.get_env("LDAP_TLS") == "true",
+  tlsopts: [],
+  base: System.get_env("LDAP_BASE") || "dc=example,dc=com",
+  uid: System.get_env("LDAP_UID") || "cn",
+  require_bind_for_search: !(System.get_env("LDAP_REQUIRE_BIND_FOR_SEARCH") == "false"),
+  bind_uid: System.get_env("LDAP_BIND_UID"),
+  bind_password: System.get_env("LDAP_BIND_PASSWORD")
+
 config :geolix,
   databases: [
     %{
diff --git a/js/src/components/User/AuthProvider.vue b/js/src/components/User/AuthProvider.vue
new file mode 100644
index 000000000..79e48ef6b
--- /dev/null
+++ b/js/src/components/User/AuthProvider.vue
@@ -0,0 +1,26 @@
+<template>
+  <a
+    class="button is-light"
+    v-if="Object.keys(SELECTED_PROVIDERS).includes(oauthProvider.id)"
+    :href="`/auth/${oauthProvider.id}`"
+  >
+    <b-icon :icon="oauthProvider.id" />
+    <span>{{ SELECTED_PROVIDERS[oauthProvider.id] }}</span></a
+  >
+  <a class="button is-light" :href="`/auth/${oauthProvider.id}`" v-else>
+    <b-icon icon="lock" />
+    <span>{{ oauthProvider.label }}</span>
+  </a>
+</template>
+<script lang="ts">
+import { Component, Vue, Prop } from "vue-property-decorator";
+import { IOAuthProvider } from "../../types/config.model";
+import { SELECTED_PROVIDERS } from "../../utils/auth";
+
+@Component
+export default class AuthProvider extends Vue {
+  @Prop({ required: true, type: Object }) oauthProvider!: IOAuthProvider;
+
+  SELECTED_PROVIDERS = SELECTED_PROVIDERS;
+}
+</script>
diff --git a/js/src/components/User/AuthProviders.vue b/js/src/components/User/AuthProviders.vue
new file mode 100644
index 000000000..cd04d09d8
--- /dev/null
+++ b/js/src/components/User/AuthProviders.vue
@@ -0,0 +1,26 @@
+<template>
+  <div>
+    <b>{{ $t("Sign in with") }}</b>
+    <div class="buttons">
+      <auth-provider
+        v-for="provider in oauthProviders"
+        :oauthProvider="provider"
+        :key="provider.id"
+      />
+    </div>
+  </div>
+</template>
+<script lang="ts">
+import { Component, Vue, Prop } from "vue-property-decorator";
+import { IOAuthProvider } from "../../types/config.model";
+import AuthProvider from "./AuthProvider.vue";
+
+@Component({
+  components: {
+    AuthProvider,
+  },
+})
+export default class AuthProviders extends Vue {
+  @Prop({ required: true, type: Array }) oauthProviders!: IOAuthProvider[];
+}
+</script>
diff --git a/js/src/graphql/config.ts b/js/src/graphql/config.ts
index 90ab6ef8e..44471990c 100644
--- a/js/src/graphql/config.ts
+++ b/js/src/graphql/config.ts
@@ -62,6 +62,13 @@ export const CONFIG = gql`
       features {
         groups
       }
+      auth {
+        ldap
+        oauthProviders {
+          id
+          label
+        }
+      }
     }
   }
 `;
diff --git a/js/src/graphql/user.ts b/js/src/graphql/user.ts
index 91d7e14af..f5fdd350d 100644
--- a/js/src/graphql/user.ts
+++ b/js/src/graphql/user.ts
@@ -35,6 +35,15 @@ export const LOGGED_USER = gql`
     loggedUser {
       id
       email
+      defaultActor {
+        id
+        preferredUsername
+        name
+        avatar {
+          url
+        }
+      }
+      provider
     }
   }
 `;
@@ -64,7 +73,7 @@ export const VALIDATE_EMAIL = gql`
 `;
 
 export const DELETE_ACCOUNT = gql`
-  mutation DeleteAccount($password: String, $userId: ID!) {
+  mutation DeleteAccount($password: String, $userId: ID) {
     deleteAccount(password: $password, userId: $userId) {
       id
     }
diff --git a/js/src/i18n/en_US.json b/js/src/i18n/en_US.json
index 0f8cad665..95070573f 100644
--- a/js/src/i18n/en_US.json
+++ b/js/src/i18n/en_US.json
@@ -703,5 +703,10 @@
   "New discussion": "New discussion",
   "Create a discussion": "Create a discussion",
   "Create the discussion": "Create the discussion",
-  "View all discussions": "View all discussions"
+  "View all discussions": "View all discussions",
+  "Sign in with": "Sign in with",
+  "Your email address was automatically set based on your {provider} account.": "Your email address was automatically set based on your {provider} account.",
+  "You can't change your password because you are registered through {provider}.": "You can't change your password because you are registered through {provider}.",
+  "Error while login with {provider}. Retry or login another way.": "Error while login with {provider}. Retry or login another way.",
+  "Error while login with {provider}. This login provider doesn't exist.": "Error while login with {provider}. This login provider doesn't exist."
 }
diff --git a/js/src/i18n/fr_FR.json b/js/src/i18n/fr_FR.json
index 12204305f..b0ad62388 100644
--- a/js/src/i18n/fr_FR.json
+++ b/js/src/i18n/fr_FR.json
@@ -703,5 +703,10 @@
   "{number} participations": "Aucune participation|Une participation|{number} participations",
   "{profile} (by default)": "{profile} (par défault)",
   "{title} ({count} todos)": "{title} ({count} todos)",
-  "© The OpenStreetMap Contributors": "© Les Contributeur⋅ices OpenStreetMap"
+  "© The OpenStreetMap Contributors": "© Les Contributeur⋅ices OpenStreetMap",
+  "Sign in with": "Se connecter avec",
+  "Your email address was automatically set based on your {provider} account.": "Votre adresse email a été définie automatiquement en se basant sur votre compte {provider}.",
+  "You can't change your password because you are registered through {provider}.": "Vous ne pouvez pas changer votre mot de passe car vous vous êtes enregistré via {provider}.",
+  "Error while login with {provider}. Retry or login another way.": "Erreur lors de la connexion avec {provider}. Réessayez ou bien connectez vous autrement.",
+  "Error while login with {provider}. This login provider doesn't exist.": "Erreur lors de la connexion avec {provider}. Cette méthode de connexion n'existe pas."
 }
diff --git a/js/src/router/index.ts b/js/src/router/index.ts
index e7cce702c..52e64663f 100644
--- a/js/src/router/index.ts
+++ b/js/src/router/index.ts
@@ -112,6 +112,11 @@ const router = new Router({
       component: () => import(/* webpackChunkName: "cookies" */ "@/views/Interact.vue"),
       meta: { requiredAuth: false },
     },
+    {
+      path: "/auth/:provider/callback",
+      name: "auth-callback",
+      component: () => import("@/views/User/ProviderValidation.vue"),
+    },
     {
       path: "/404",
       name: RouteName.PAGE_NOT_FOUND,
diff --git a/js/src/types/config.model.ts b/js/src/types/config.model.ts
index 61f0aaad9..3e53bf3bf 100644
--- a/js/src/types/config.model.ts
+++ b/js/src/types/config.model.ts
@@ -74,4 +74,13 @@ export interface IConfig {
   };
   federating: boolean;
   version: string;
+  auth: {
+    ldap: boolean;
+    oauthProviders: IOAuthProvider[];
+  };
+}
+
+export interface IOAuthProvider {
+  id: string;
+  label: string;
 }
diff --git a/js/src/types/current-user.model.ts b/js/src/types/current-user.model.ts
index 5eb7a2034..574ecd078 100644
--- a/js/src/types/current-user.model.ts
+++ b/js/src/types/current-user.model.ts
@@ -9,15 +9,11 @@ export enum ICurrentUserRole {
 }
 
 export interface ICurrentUser {
-  id: number;
+  id: string;
   email: string;
   isLoggedIn: boolean;
   role: ICurrentUserRole;
-  participations: Paginate<IParticipant>;
-  defaultActor: IPerson;
-  drafts: IEvent[];
-  settings: IUserSettings;
-  locale: string;
+  defaultActor?: IPerson;
 }
 
 export interface IUser extends ICurrentUser {
@@ -25,6 +21,22 @@ export interface IUser extends ICurrentUser {
   confirmationSendAt: Date;
   actors: IPerson[];
   disabled: boolean;
+  participations: Paginate<IParticipant>;
+  drafts: IEvent[];
+  settings: IUserSettings;
+  locale: string;
+  provider?: string;
+}
+
+export enum IAuthProvider {
+  LDAP = "ldap",
+  GOOGLE = "google",
+  DISCORD = "discord",
+  GITHUB = "github",
+  KEYCLOAK = "keycloak",
+  FACEBOOK = "facebook",
+  GITLAB = "gitlab",
+  TWITTER = "twitter",
 }
 
 export enum INotificationPendingParticipationEnum {
diff --git a/js/src/types/login-error-code.model.ts b/js/src/types/login-error-code.model.ts
index c12c4c5c9..e2752030b 100644
--- a/js/src/types/login-error-code.model.ts
+++ b/js/src/types/login-error-code.model.ts
@@ -6,4 +6,6 @@ export enum LoginError {
   USER_NOT_CONFIRMED = "User account not confirmed",
   USER_DOES_NOT_EXIST = "No user with this email was found",
   USER_EMAIL_PASSWORD_INVALID = "Impossible to authenticate, either your email or password are invalid.",
+  LOGIN_PROVIDER_ERROR = "Error with Login Provider",
+  LOGIN_PROVIDER_NOT_FOUND = "Login Provider not found",
 }
diff --git a/js/src/utils/auth.ts b/js/src/utils/auth.ts
index 9d8a91c4e..f18f6104a 100644
--- a/js/src/utils/auth.ts
+++ b/js/src/utils/auth.ts
@@ -94,3 +94,14 @@ export async function logout(apollo: ApolloClient<NormalizedCacheObject>) {
 
   deleteUserData();
 }
+
+export const SELECTED_PROVIDERS: { [key: string]: string } = {
+  twitter: "Twitter",
+  discord: "Discord",
+  facebook: "Facebook",
+  github: "Github",
+  gitlab: "Gitlab",
+  google: "Google",
+  keycloak: "Keycloak",
+  ldap: "LDAP",
+};
diff --git a/js/src/views/Settings/AccountSettings.vue b/js/src/views/Settings/AccountSettings.vue
index a1f320cc7..80168b738 100644
--- a/js/src/views/Settings/AccountSettings.vue
+++ b/js/src/views/Settings/AccountSettings.vue
@@ -1,5 +1,5 @@
 <template>
-  <div>
+  <div v-if="loggedUser">
     <nav class="breadcrumb" aria-label="breadcrumbs">
       <ul>
         <li>
@@ -24,6 +24,13 @@
       >
         <b slot="email">{{ loggedUser.email }}</b>
       </i18n>
+      <b-message v-if="!canChangeEmail" type="is-warning" :closable="false">
+        {{
+          $t("Your email address was automatically set based on your {provider} account.", {
+            provider: providerName(loggedUser.provider),
+          })
+        }}
+      </b-message>
       <b-notification
         type="is-danger"
         has-icon
@@ -33,7 +40,7 @@
         v-for="error in changeEmailErrors"
         >{{ error }}</b-notification
       >
-      <form @submit.prevent="resetEmailAction" ref="emailForm" class="form">
+      <form @submit.prevent="resetEmailAction" ref="emailForm" class="form" v-if="canChangeEmail">
         <b-field :label="$t('New email')">
           <b-input aria-required="true" required type="email" v-model="newEmail" />
         </b-field>
@@ -58,6 +65,13 @@
       <div class="setting-title">
         <h2>{{ $t("Password") }}</h2>
       </div>
+      <b-message v-if="!canChangePassword" type="is-warning" :closable="false">
+        {{
+          $t("You can't change your password because you are registered through {provider}.", {
+            provider: providerName(loggedUser.provider),
+          })
+        }}
+      </b-message>
       <b-notification
         type="is-danger"
         has-icon
@@ -67,7 +81,12 @@
         v-for="error in changePasswordErrors"
         >{{ error }}</b-notification
       >
-      <form @submit.prevent="resetPasswordAction" ref="passwordForm" class="form">
+      <form
+        @submit.prevent="resetPasswordAction"
+        ref="passwordForm"
+        class="form"
+        v-if="canChangePassword"
+      >
         <b-field :label="$t('Old password')">
           <b-input
             aria-required="true"
@@ -124,11 +143,11 @@
                     <br />
                     <b>{{ $t("There will be no way to recover your data.") }}</b>
                   </p>
-                  <p class="content">
+                  <p class="content" v-if="hasUserGotAPassword">
                     {{ $t("Please enter your password to confirm this action.") }}
                   </p>
                   <form @submit.prevent="deleteAccount">
-                    <b-field>
+                    <b-field v-if="hasUserGotAPassword">
                       <b-input
                         type="password"
                         v-model="passwordForAccountDeletion"
@@ -160,8 +179,8 @@
 import { Component, Vue, Ref } from "vue-property-decorator";
 import { CHANGE_EMAIL, CHANGE_PASSWORD, DELETE_ACCOUNT, LOGGED_USER } from "../../graphql/user";
 import RouteName from "../../router/name";
-import { ICurrentUser } from "../../types/current-user.model";
-import { logout } from "../../utils/auth";
+import { IUser, IAuthProvider } from "../../types/current-user.model";
+import { logout, SELECTED_PROVIDERS } from "../../utils/auth";
 
 @Component({
   apollo: {
@@ -171,7 +190,7 @@ import { logout } from "../../utils/auth";
 export default class AccountSettings extends Vue {
   @Ref("passwordForm") readonly passwordForm!: HTMLElement;
 
-  loggedUser!: ICurrentUser;
+  loggedUser!: IUser;
 
   passwordForEmailChange = "";
 
@@ -243,7 +262,7 @@ export default class AccountSettings extends Vue {
       await this.$apollo.mutate({
         mutation: DELETE_ACCOUNT,
         variables: {
-          password: this.passwordForAccountDeletion,
+          password: this.hasUserGotAPassword ? this.passwordForAccountDeletion : null,
         },
       });
       await logout(this.$apollo.provider.defaultClient);
@@ -260,6 +279,28 @@ export default class AccountSettings extends Vue {
     }
   }
 
+  get canChangePassword() {
+    return !this.loggedUser.provider;
+  }
+
+  get canChangeEmail() {
+    return !this.loggedUser.provider;
+  }
+
+  providerName(id: string) {
+    if (SELECTED_PROVIDERS[id]) {
+      return SELECTED_PROVIDERS[id];
+    }
+    return id;
+  }
+
+  get hasUserGotAPassword(): boolean {
+    return (
+      this.loggedUser &&
+      (this.loggedUser.provider == null || this.loggedUser.provider == IAuthProvider.LDAP)
+    );
+  }
+
   private handleErrors(type: string, err: any) {
     console.error(err);
 
diff --git a/js/src/views/Settings/Notifications.vue b/js/src/views/Settings/Notifications.vue
index ca7e5141f..9974f0303 100644
--- a/js/src/views/Settings/Notifications.vue
+++ b/js/src/views/Settings/Notifications.vue
@@ -95,10 +95,7 @@
 <script lang="ts">
 import { Component, Vue, Watch } from "vue-property-decorator";
 import { USER_SETTINGS, SET_USER_SETTINGS } from "../../graphql/user";
-import {
-  ICurrentUser,
-  INotificationPendingParticipationEnum,
-} from "../../types/current-user.model";
+import { IUser, INotificationPendingParticipationEnum } from "../../types/current-user.model";
 import RouteName from "../../router/name";
 
 @Component({
@@ -107,7 +104,7 @@ import RouteName from "../../router/name";
   },
 })
 export default class Notifications extends Vue {
-  loggedUser!: ICurrentUser;
+  loggedUser!: IUser;
 
   notificationOnDay = true;
 
diff --git a/js/src/views/Settings/Preferences.vue b/js/src/views/Settings/Preferences.vue
index 9048ec65a..12238d850 100644
--- a/js/src/views/Settings/Preferences.vue
+++ b/js/src/views/Settings/Preferences.vue
@@ -52,7 +52,7 @@ import { Component, Vue, Watch } from "vue-property-decorator";
 import { TIMEZONES } from "../../graphql/config";
 import { USER_SETTINGS, SET_USER_SETTINGS, UPDATE_USER_LOCALE } from "../../graphql/user";
 import { IConfig } from "../../types/config.model";
-import { ICurrentUser } from "../../types/current-user.model";
+import { IUser } from "../../types/current-user.model";
 import langs from "../../i18n/langs.json";
 import RouteName from "../../router/name";
 
@@ -65,7 +65,7 @@ import RouteName from "../../router/name";
 export default class Preferences extends Vue {
   config!: IConfig;
 
-  loggedUser!: ICurrentUser;
+  loggedUser!: IUser;
 
   selectedTimezone: string | null = null;
 
@@ -74,7 +74,7 @@ export default class Preferences extends Vue {
   RouteName = RouteName;
 
   @Watch("loggedUser")
-  setSavedTimezone(loggedUser: ICurrentUser) {
+  setSavedTimezone(loggedUser: IUser) {
     if (loggedUser && loggedUser.settings.timezone) {
       this.selectedTimezone = loggedUser.settings.timezone;
     } else {
diff --git a/js/src/views/User/Login.vue b/js/src/views/User/Login.vue
index 73293f269..9862045c6 100644
--- a/js/src/views/User/Login.vue
+++ b/js/src/views/User/Login.vue
@@ -10,6 +10,26 @@
           :aria-close-label="$t('Close')"
           >{{ $t("You need to login.") }}</b-message
         >
+        <b-message
+          v-else-if="errorCode === LoginError.LOGIN_PROVIDER_ERROR"
+          type="is-danger"
+          :aria-close-label="$t('Close')"
+          >{{
+            $t("Error while login with {provider}. Retry or login another way.", {
+              provider: $route.query.provider,
+            })
+          }}</b-message
+        >
+        <b-message
+          v-else-if="errorCode === LoginError.LOGIN_PROVIDER_NOT_FOUND"
+          type="is-danger"
+          :aria-close-label="$t('Close')"
+          >{{
+            $t("Error while login with {provider}. This login provider doesn't exist.", {
+              provider: $route.query.provider,
+            })
+          }}</b-message
+        >
         <b-message title="Error" type="is-danger" v-for="error in errors" :key="error">
           <span v-if="error === LoginError.USER_NOT_CONFIRMED">
             <span>
@@ -60,6 +80,11 @@
           <p class="control has-text-centered">
             <button class="button is-primary is-large">{{ $t("Login") }}</button>
           </p>
+
+          <div class="control" v-if="config && config.auth.oauthProviders.length > 0">
+            <auth-providers :oauthProviders="config.auth.oauthProviders" />
+          </div>
+
           <p class="control">
             <router-link
               class="button is-text"
@@ -103,6 +128,7 @@ import { LoginErrorCode, LoginError } from "../../types/login-error-code.model";
 import { ICurrentUser } from "../../types/current-user.model";
 import { CONFIG } from "../../graphql/config";
 import { IConfig } from "../../types/config.model";
+import AuthProviders from "../../components/User/AuthProviders.vue";
 
 @Component({
   apollo: {
@@ -113,6 +139,9 @@ import { IConfig } from "../../types/config.model";
       query: CURRENT_USER_CLIENT,
     },
   },
+  components: {
+    AuthProviders,
+  },
   metaInfo() {
     return {
       // if no subcomponents specify a metaInfo.title, this title will be used
diff --git a/js/src/views/User/ProviderValidation.vue b/js/src/views/User/ProviderValidation.vue
new file mode 100644
index 000000000..097e35f5b
--- /dev/null
+++ b/js/src/views/User/ProviderValidation.vue
@@ -0,0 +1,64 @@
+<template> </template>
+<script lang="ts">
+import { Component, Prop, Vue } from "vue-property-decorator";
+import { VALIDATE_USER, UPDATE_CURRENT_USER_CLIENT, LOGGED_USER } from "../../graphql/user";
+import RouteName from "../../router/name";
+import { saveUserData, changeIdentity } from "../../utils/auth";
+import { ILogin } from "../../types/login.model";
+import { ICurrentUserRole, ICurrentUser, IUser } from "../../types/current-user.model";
+import { IDENTITIES } from "../../graphql/actor";
+
+@Component
+export default class ProviderValidate extends Vue {
+  async mounted() {
+    const accessToken = this.getValueFromMeta("auth-access-token");
+    const refreshToken = this.getValueFromMeta("auth-refresh-token");
+    const userId = this.getValueFromMeta("auth-user-id");
+    const userEmail = this.getValueFromMeta("auth-user-email");
+    const userRole = this.getValueFromMeta("auth-user-role") as ICurrentUserRole;
+    const userActorId = this.getValueFromMeta("auth-user-actor-id");
+
+    if (!(userId && userEmail && userRole && accessToken && refreshToken)) {
+      return this.$router.push("/");
+    }
+    const login = {
+      user: { id: userId, email: userEmail, role: userRole, isLoggedIn: true },
+      accessToken,
+      refreshToken,
+    };
+    saveUserData(login);
+    await this.$apollo.mutate({
+      mutation: UPDATE_CURRENT_USER_CLIENT,
+      variables: {
+        id: userId,
+        email: userEmail,
+        isLoggedIn: true,
+        role: ICurrentUserRole.USER,
+      },
+    });
+    const { data } = await this.$apollo.query<{ loggedUser: IUser }>({
+      query: LOGGED_USER,
+    });
+    const { loggedUser } = data;
+
+    if (loggedUser.defaultActor) {
+      await changeIdentity(this.$apollo.provider.defaultClient, loggedUser.defaultActor);
+      await this.$router.push({ name: RouteName.HOME });
+    } else {
+      // If the user didn't register any profile yet, let's create one for them
+      await this.$router.push({
+        name: RouteName.REGISTER_PROFILE,
+        params: { email: loggedUser.email, userAlreadyActivated: "true" },
+      });
+    }
+  }
+
+  getValueFromMeta(name: string) {
+    const element = document.querySelector(`meta[name="${name}"]`);
+    if (element && element.getAttribute("content")) {
+      return element.getAttribute("content");
+    }
+    return null;
+  }
+}
+</script>
diff --git a/js/src/views/User/Register.vue b/js/src/views/User/Register.vue
index abe9ce3a0..3df320bdc 100644
--- a/js/src/views/User/Register.vue
+++ b/js/src/views/User/Register.vue
@@ -96,6 +96,7 @@
                 {{ $t("Register") }}
               </b-button>
             </p>
+
             <p class="control">
               <router-link
                 class="button is-text"
@@ -113,6 +114,11 @@
                 >{{ $t("Login") }}</router-link
               >
             </p>
+
+            <hr />
+            <div class="control" v-if="config && config.auth.oauthProviders.length > 0">
+              <auth-providers :oauthProviders="config.auth.oauthProviders" />
+            </div>
           </form>
 
           <div v-if="errors.length > 0">
@@ -131,9 +137,10 @@ import RouteName from "../../router/name";
 import { IConfig } from "../../types/config.model";
 import { CONFIG } from "../../graphql/config";
 import Subtitle from "../../components/Utils/Subtitle.vue";
+import AuthProviders from "../../components/User/AuthProviders.vue";
 
 @Component({
-  components: { Subtitle },
+  components: { Subtitle, AuthProviders },
   metaInfo() {
     return {
       // if no subcomponents specify a metaInfo.title, this title will be used
diff --git a/js/src/views/User/Validate.vue b/js/src/views/User/Validate.vue
index c7a7ef183..f6df0fe0f 100644
--- a/js/src/views/User/Validate.vue
+++ b/js/src/views/User/Validate.vue
@@ -18,7 +18,7 @@
 import { Component, Prop, Vue } from "vue-property-decorator";
 import { VALIDATE_USER, UPDATE_CURRENT_USER_CLIENT } from "../../graphql/user";
 import RouteName from "../../router/name";
-import { saveUserData, changeIdentity } from "../../utils/auth";
+import { saveUserData, saveTokenData, changeIdentity } from "../../utils/auth";
 import { ILogin } from "../../types/login.model";
 import { ICurrentUserRole } from "../../types/current-user.model";
 
@@ -45,6 +45,7 @@ export default class Validate extends Vue {
 
       if (data) {
         saveUserData(data.validateUser);
+        saveTokenData(data.validateUser);
 
         const { user } = data.validateUser;
 
diff --git a/lib/graphql/resolvers/config.ex b/lib/graphql/resolvers/config.ex
index 0e6aadf4f..21f536f7e 100644
--- a/lib/graphql/resolvers/config.ex
+++ b/lib/graphql/resolvers/config.ex
@@ -124,7 +124,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
       },
       rules: Config.instance_rules(),
       version: Config.instance_version(),
-      federating: Config.instance_federating()
+      federating: Config.instance_federating(),
+      auth: %{
+        ldap: Config.ldap_enabled?(),
+        oauth_providers: Config.oauth_consumer_strategies()
+      }
     }
   end
 end
diff --git a/lib/graphql/resolvers/person.ex b/lib/graphql/resolvers/person.ex
index e6446f185..472d86669 100644
--- a/lib/graphql/resolvers/person.ex
+++ b/lib/graphql/resolvers/person.ex
@@ -202,10 +202,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
   """
   def register_person(_parent, args, _resolution) do
     with {:ok, %User{} = user} <- Users.get_user_by_email(args.email),
-         {:no_actor, nil} <- {:no_actor, Users.get_actor_for_user(user)},
+         user_actor <- Users.get_actor_for_user(user),
+         no_actor <- is_nil(user_actor),
+         {:no_actor, true} <- {:no_actor, no_actor},
          args <- Map.put(args, :user_id, user.id),
          args <- save_attached_pictures(args),
-         {:ok, %Actor{} = new_person} <- Actors.new_person(args) do
+         {:ok, %Actor{} = new_person} <- Actors.new_person(args, true) do
       {:ok, new_person}
     else
       {:error, :user_not_found} ->
diff --git a/lib/graphql/resolvers/user.ex b/lib/graphql/resolvers/user.ex
index 7c2cd5ec4..b3043b97c 100644
--- a/lib/graphql/resolvers/user.ex
+++ b/lib/graphql/resolvers/user.ex
@@ -9,6 +9,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
   alias Mobilizon.Actors.Actor
   alias Mobilizon.Crypto
   alias Mobilizon.Federation.ActivityPub
+  alias Mobilizon.Service.Auth.Authenticator
   alias Mobilizon.Storage.{Page, Repo}
   alias Mobilizon.Users.{Setting, User}
 
@@ -59,18 +60,16 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
   Login an user. Returns a token and the user
   """
   def login_user(_parent, %{email: email, password: password}, _resolution) do
-    with {:ok, %User{confirmed_at: %DateTime{}} = user} <- Users.get_user_by_email(email),
-         {:ok, %{access_token: access_token, refresh_token: refresh_token}} <-
-           Users.authenticate(%{user: user, password: password}) do
-      {:ok, %{access_token: access_token, refresh_token: refresh_token, user: user}}
-    else
-      {:ok, %User{confirmed_at: nil} = _user} ->
-        {:error, "User account not confirmed"}
+    case Authenticator.authenticate(email, password) do
+      {:ok,
+       %{access_token: _access_token, refresh_token: _refresh_token, user: _user} =
+           user_and_tokens} ->
+        {:ok, user_and_tokens}
 
       {:error, :user_not_found} ->
         {:error, "No user with this email was found"}
 
-      {:error, :unauthorized} ->
+      {:error, _error} ->
         {:error, "Impossible to authenticate, either your email or password are invalid."}
     end
   end
@@ -82,7 +81,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
     with {:ok, user, _claims} <- Auth.Guardian.resource_from_token(refresh_token),
          {:ok, _old, {exchanged_token, _claims}} <-
            Auth.Guardian.exchange(refresh_token, ["access", "refresh"], "access"),
-         {:ok, refresh_token} <- Users.generate_refresh_token(user) do
+         {:ok, refresh_token} <- Authenticator.generate_refresh_token(user) do
       {:ok, %{access_token: exchanged_token, refresh_token: refresh_token}}
     else
       {:error, message} ->
@@ -151,7 +150,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
            {:check_confirmation_token, Email.User.check_confirmation_token(token)},
          {:get_actor, actor} <- {:get_actor, Users.get_actor_for_user(user)},
          {:ok, %{access_token: access_token, refresh_token: refresh_token}} <-
-           Users.generate_tokens(user) do
+           Authenticator.generate_tokens(user) do
       {:ok,
        %{
          access_token: access_token,
@@ -192,10 +191,15 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
   def send_reset_password(_parent, args, _resolution) do
     with email <- Map.get(args, :email),
          {:ok, %User{locale: locale} = user} <- Users.get_user_by_email(email, true),
+         {:can_reset_password, true} <-
+           {:can_reset_password, Authenticator.can_reset_password?(user)},
          {:ok, %Bamboo.Email{} = _email_html} <-
            Email.User.send_password_reset_email(user, Map.get(args, :locale, locale)) do
       {:ok, email}
     else
+      {:can_reset_password, false} ->
+        {:error, "This user can't reset their password"}
+
       {:error, :user_not_found} ->
         # TODO : implement rate limits for this endpoint
         {:error, "No user with this email was found"}
@@ -209,10 +213,10 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
   Reset the password from an user
   """
   def reset_password(_parent, %{password: password, token: token}, _resolution) do
-    with {:ok, %User{} = user} <-
+    with {:ok, %User{email: email} = user} <-
            Email.User.check_reset_password_token(password, token),
          {:ok, %{access_token: access_token, refresh_token: refresh_token}} <-
-           Users.authenticate(%{user: user, password: password}) do
+           Authenticator.authenticate(email, password) do
       {:ok, %{access_token: access_token, refresh_token: refresh_token, user: user}}
     end
   end
@@ -295,10 +299,12 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
   def change_password(
         _parent,
         %{old_password: old_password, new_password: new_password},
-        %{context: %{current_user: %User{password_hash: old_password_hash} = user}}
+        %{context: %{current_user: %User{} = user}}
       ) do
-    with {:current_password, true} <-
-           {:current_password, Argon2.verify_pass(old_password, old_password_hash)},
+    with {:can_change_password, true} <-
+           {:can_change_password, Authenticator.can_change_password?(user)},
+         {:current_password, {:ok, %User{}}} <-
+           {:current_password, Authenticator.login(user.email, old_password)},
          {:same_password, false} <- {:same_password, old_password == new_password},
          {:ok, %User{} = user} <-
            user
@@ -306,7 +312,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
            |> Repo.update() do
       {:ok, user}
     else
-      {:current_password, false} ->
+      {:current_password, _} ->
         {:error, "The current password is invalid"}
 
       {:same_password, true} ->
@@ -323,10 +329,12 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
   end
 
   def change_email(_parent, %{email: new_email, password: password}, %{
-        context: %{current_user: %User{email: old_email, password_hash: password_hash} = user}
+        context: %{current_user: %User{email: old_email} = user}
       }) do
-    with {:current_password, true} <-
-           {:current_password, Argon2.verify_pass(password, password_hash)},
+    with {:can_change_password, true} <-
+           {:can_change_password, Authenticator.can_change_email?(user)},
+         {:current_password, {:ok, %User{}}} <-
+           {:current_password, Authenticator.login(user.email, password)},
          {:same_email, false} <- {:same_email, new_email == old_email},
          {:email_valid, true} <- {:email_valid, Email.Checker.valid?(new_email)},
          {:ok, %User{} = user} <-
@@ -347,7 +355,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
 
       {:ok, user}
     else
-      {:current_password, false} ->
+      {:current_password, _} ->
         {:error, "The password provided is invalid"}
 
       {:same_email, true} ->
@@ -377,14 +385,24 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
     end
   end
 
-  def delete_account(_parent, %{password: password}, %{
-        context: %{current_user: %User{password_hash: password_hash} = user}
+  def delete_account(_parent, args, %{
+        context: %{current_user: %User{email: email} = user}
       }) do
-    case {:current_password, Argon2.verify_pass(password, password_hash)} do
-      {:current_password, true} ->
+    with {:user_has_password, true} <- {:user_has_password, Authenticator.has_password?(user)},
+         {:confirmation_password, password} when not is_nil(password) <-
+           {:confirmation_password, Map.get(args, :password)},
+         {:current_password, {:ok, _}} <-
+           {:current_password, Authenticator.authenticate(email, password)} do
+      do_delete_account(user)
+    else
+      # If the user hasn't got any password (3rd-party auth)
+      {:user_has_password, false} ->
         do_delete_account(user)
 
-      {:current_password, false} ->
+      {:confirmation_password, nil} ->
+        {:error, "The password provided is invalid"}
+
+      {:current_password, _} ->
         {:error, "The password provided is invalid"}
     end
   end
diff --git a/lib/graphql/schema/config.ex b/lib/graphql/schema/config.ex
index b9cbf36da..e8070cbe6 100644
--- a/lib/graphql/schema/config.ex
+++ b/lib/graphql/schema/config.ex
@@ -39,6 +39,7 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
     end
 
     field(:rules, :string, description: "The instance's rules")
+    field(:auth, :auth, description: "The instance auth methods")
   end
 
   object :terms do
@@ -132,6 +133,16 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
     field(:groups, :boolean)
   end
 
+  object :auth do
+    field(:ldap, :boolean, description: "Whether or not LDAP auth is enabled")
+    field(:oauth_providers, list_of(:oauth_provider), description: "List of oauth providers")
+  end
+
+  object :oauth_provider do
+    field(:id, :string, description: "The provider ID")
+    field(:label, :string, description: "The label for the auth provider")
+  end
+
   object :config_queries do
     @desc "Get the instance config"
     field :config, :config do
diff --git a/lib/graphql/schema/user.ex b/lib/graphql/schema/user.ex
index a41317b7d..511f94f45 100644
--- a/lib/graphql/schema/user.ex
+++ b/lib/graphql/schema/user.ex
@@ -52,6 +52,8 @@ defmodule Mobilizon.GraphQL.Schema.UserType do
 
     field(:locale, :string, description: "The user's locale")
 
+    field(:provider, :string, description: "The user's login provider")
+
     field(:disabled, :boolean, description: "Whether the user is disabled")
 
     field(:participations, :paginated_participant_list,
diff --git a/lib/mobilizon/actors/actors.ex b/lib/mobilizon/actors/actors.ex
index 64dc74603..757b0eedc 100644
--- a/lib/mobilizon/actors/actors.ex
+++ b/lib/mobilizon/actors/actors.ex
@@ -13,6 +13,7 @@ defmodule Mobilizon.Actors do
   alias Mobilizon.Media.File
   alias Mobilizon.Service.Workers
   alias Mobilizon.Storage.{Page, Repo}
+  alias Mobilizon.Users
 
   alias Mobilizon.Federation.ActivityPub
 
@@ -189,14 +190,19 @@ defmodule Mobilizon.Actors do
   Creates a new person actor.
   """
   @spec new_person(map) :: {:ok, Actor.t()} | {:error, Ecto.Changeset.t()}
-  def new_person(args) do
+  def new_person(args, default_actor \\ false) do
     args = Map.put(args, :keys, Crypto.generate_rsa_2048_private_key())
 
-    with {:ok, %Actor{} = person} <-
+    with {:ok, %Actor{id: person_id} = person} <-
            %Actor{}
            |> Actor.registration_changeset(args)
            |> Repo.insert() do
-      Events.create_feed_token(%{user_id: args["user_id"], actor_id: person.id})
+      Events.create_feed_token(%{user_id: args.user_id, actor_id: person.id})
+
+      if default_actor do
+        user = Users.get_user!(args.user_id)
+        Users.update_user(user, %{default_actor_id: person_id})
+      end
 
       {:ok, person}
     end
diff --git a/lib/mobilizon/config.ex b/lib/mobilizon/config.ex
index 2b8ab92ce..77e1ffadb 100644
--- a/lib/mobilizon/config.ex
+++ b/lib/mobilizon/config.ex
@@ -186,6 +186,24 @@ defmodule Mobilizon.Config do
   def anonymous_reporting?,
     do: Application.get_env(:mobilizon, :anonymous)[:reports][:allowed]
 
+  @spec oauth_consumer_strategies() :: list({atom(), String.t()})
+  def oauth_consumer_strategies do
+    [:auth, :oauth_consumer_strategies]
+    |> get([])
+    |> Enum.map(fn strategy ->
+      case strategy do
+        {id, label} when is_atom(id) -> %{id: id, label: label}
+        id when is_atom(id) -> %{id: id, label: nil}
+      end
+    end)
+  end
+
+  @spec oauth_consumer_enabled? :: boolean()
+  def oauth_consumer_enabled?, do: oauth_consumer_strategies() != []
+
+  @spec ldap_enabled? :: boolean()
+  def ldap_enabled?, do: get([:ldap, :enabled], false)
+
   def instance_resource_providers do
     types = get_in(Application.get_env(:mobilizon, Mobilizon.Service.ResourceProviders), [:types])
 
diff --git a/lib/mobilizon/users/user.ex b/lib/mobilizon/users/user.ex
index 31a493a86..51b737626 100644
--- a/lib/mobilizon/users/user.ex
+++ b/lib/mobilizon/users/user.ex
@@ -40,14 +40,18 @@ defmodule Mobilizon.Users.User do
     :confirmation_token,
     :reset_password_sent_at,
     :reset_password_token,
+    :default_actor_id,
     :locale,
     :unconfirmed_email,
-    :disabled
+    :disabled,
+    :provider
   ]
   @attrs @required_attrs ++ @optional_attrs
 
   @registration_required_attrs @required_attrs ++ [:password]
 
+  @auth_provider_required_attrs @required_attrs ++ [:provider]
+
   @password_change_required_attrs [:password]
   @password_reset_required_attrs @password_change_required_attrs ++
                                    [:reset_password_token, :reset_password_sent_at]
@@ -67,6 +71,7 @@ defmodule Mobilizon.Users.User do
     field(:unconfirmed_email, :string)
     field(:locale, :string, default: "en")
     field(:disabled, :boolean, default: false)
+    field(:provider, :string)
 
     belongs_to(:default_actor, Actor)
     has_many(:actors, Actor)
@@ -116,6 +121,16 @@ defmodule Mobilizon.Users.User do
     )
   end
 
+  @doc false
+  @spec auth_provider_changeset(t, map) :: Ecto.Changeset.t()
+  def auth_provider_changeset(%__MODULE__{} = user, attrs) do
+    user
+    |> changeset(attrs)
+    |> cast_assoc(:default_actor)
+    |> put_change(:confirmed_at, DateTime.utc_now() |> DateTime.truncate(:second))
+    |> validate_required(@auth_provider_required_attrs)
+  end
+
   @doc false
   @spec send_password_reset_changeset(t, map) :: Ecto.Changeset.t()
   def send_password_reset_changeset(%__MODULE__{} = user, attrs) do
diff --git a/lib/mobilizon/users/users.ex b/lib/mobilizon/users/users.ex
index 0ee54fd09..21bc20c9b 100644
--- a/lib/mobilizon/users/users.ex
+++ b/lib/mobilizon/users/users.ex
@@ -15,13 +15,6 @@ defmodule Mobilizon.Users do
   alias Mobilizon.Storage.{Page, Repo}
   alias Mobilizon.Users.{Setting, User}
 
-  alias Mobilizon.Web.Auth
-
-  @type tokens :: %{
-          required(:access_token) => String.t(),
-          required(:refresh_token) => String.t()
-        }
-
   defenum(UserRole, :user_role, [:administrator, :moderator, :user])
 
   defenum(NotificationPendingNotificationDelay, none: 0, direct: 1, one_hour: 5, one_day: 10)
@@ -41,6 +34,18 @@ defmodule Mobilizon.Users do
     end
   end
 
+  @spec create_external(String.t(), String.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
+  def create_external(email, provider) do
+    with {:ok, %User{} = user} <-
+           %User{}
+           |> User.auth_provider_changeset(%{email: email, provider: provider})
+           |> Repo.insert() do
+      Events.create_feed_token(%{user_id: user.id})
+
+      {:ok, user}
+    end
+  end
+
   @doc """
   Gets a single user.
   Raises `Ecto.NoResultsError` if the user does not exist.
@@ -75,6 +80,16 @@ defmodule Mobilizon.Users do
     end
   end
 
+  @doc """
+  Gets an user by its email.
+  """
+  @spec get_user_by_email!(String.t(), boolean | nil) :: User.t()
+  def get_user_by_email!(email, activated \\ nil) do
+    email
+    |> user_by_email_query(activated)
+    |> Repo.one!()
+  end
+
   @doc """
   Get an user by its activation token.
   """
@@ -267,52 +282,6 @@ defmodule Mobilizon.Users do
   @spec count_users :: integer
   def count_users, do: Repo.one(from(u in User, select: count(u.id)))
 
-  @doc """
-  Authenticate an user.
-  """
-  @spec authenticate(User.t()) :: {:ok, tokens} | {:error, :unauthorized}
-  def authenticate(%{user: %User{password_hash: password_hash} = user, password: password}) do
-    # Does password match the one stored in the database?
-    if Argon2.verify_pass(password, password_hash) do
-      {:ok, _tokens} = generate_tokens(user)
-    else
-      {:error, :unauthorized}
-    end
-  end
-
-  @doc """
-  Generates access token and refresh token for an user.
-  """
-  @spec generate_tokens(User.t()) :: {:ok, tokens}
-  def generate_tokens(user) do
-    with {:ok, access_token} <- generate_access_token(user),
-         {:ok, refresh_token} <- generate_refresh_token(user) do
-      {:ok, %{access_token: access_token, refresh_token: refresh_token}}
-    end
-  end
-
-  @doc """
-  Generates access token for an user.
-  """
-  @spec generate_access_token(User.t()) :: {:ok, String.t()}
-  def generate_access_token(user) do
-    with {:ok, access_token, _claims} <-
-           Auth.Guardian.encode_and_sign(user, %{}, token_type: "access") do
-      {:ok, access_token}
-    end
-  end
-
-  @doc """
-  Generates refresh token for an user.
-  """
-  @spec generate_refresh_token(User.t()) :: {:ok, String.t()}
-  def generate_refresh_token(user) do
-    with {:ok, refresh_token, _claims} <-
-           Auth.Guardian.encode_and_sign(user, %{}, token_type: "refresh") do
-      {:ok, refresh_token}
-    end
-  end
-
   @doc """
   Gets a settings for an user.
 
diff --git a/lib/service/auth/authenticator.ex b/lib/service/auth/authenticator.ex
new file mode 100644
index 000000000..15b8cec25
--- /dev/null
+++ b/lib/service/auth/authenticator.ex
@@ -0,0 +1,93 @@
+defmodule Mobilizon.Service.Auth.Authenticator do
+  @moduledoc """
+  Module to handle authentification (currently through database or LDAP)
+  """
+  alias Mobilizon.Users
+  alias Mobilizon.Users.User
+  alias Mobilizon.Web.Auth.Guardian
+
+  @type tokens :: %{
+          required(:access_token) => String.t(),
+          required(:refresh_token) => String.t()
+        }
+
+  @type tokens_with_user :: %{
+          required(:access_token) => String.t(),
+          required(:refresh_token) => String.t(),
+          required(:user) => User.t()
+        }
+
+  def implementation do
+    Mobilizon.Config.get(
+      Mobilizon.Service.Auth.Authenticator,
+      Mobilizon.Service.Auth.MobilizonAuthenticator
+    )
+  end
+
+  @callback login(String.t(), String.t()) :: {:ok, User.t()} | {:error, any()}
+  @spec login(String.t(), String.t()) :: {:ok, User.t()} | {:error, any()}
+  def login(email, password), do: implementation().login(email, password)
+
+  @callback can_change_email?(User.t()) :: boolean
+  def can_change_email?(%User{} = user), do: implementation().can_change_email?(user)
+
+  @callback can_change_password?(User.t()) :: boolean
+  def can_change_password?(%User{} = user), do: implementation().can_change_password?(user)
+
+  @spec has_password?(User.t()) :: boolean()
+  def has_password?(%User{provider: provider}), do: is_nil(provider) or provider == "ldap"
+
+  @spec can_reset_password?(User.t()) :: boolean()
+  def can_reset_password?(%User{} = user), do: has_password?(user) && can_change_password?(user)
+
+  @spec authenticate(String.t(), String.t()) :: {:ok, tokens_with_user()}
+  def authenticate(email, password) do
+    with {:ok, %User{} = user} <- login(email, password),
+         {:ok, %{access_token: access_token, refresh_token: refresh_token}} <-
+           generate_tokens(user) do
+      {:ok, %{access_token: access_token, refresh_token: refresh_token, user: user}}
+    end
+  end
+
+  @doc """
+  Generates access token and refresh token for an user.
+  """
+  @spec generate_tokens(User.t()) :: {:ok, tokens}
+  def generate_tokens(user) do
+    with {:ok, access_token} <- generate_access_token(user),
+         {:ok, refresh_token} <- generate_refresh_token(user) do
+      {:ok, %{access_token: access_token, refresh_token: refresh_token}}
+    end
+  end
+
+  @doc """
+  Generates access token for an user.
+  """
+  @spec generate_access_token(User.t()) :: {:ok, String.t()}
+  def generate_access_token(user) do
+    with {:ok, access_token, _claims} <-
+           Guardian.encode_and_sign(user, %{}, token_type: "access") do
+      {:ok, access_token}
+    end
+  end
+
+  @doc """
+  Generates refresh token for an user.
+  """
+  @spec generate_refresh_token(User.t()) :: {:ok, String.t()}
+  def generate_refresh_token(user) do
+    with {:ok, refresh_token, _claims} <-
+           Guardian.encode_and_sign(user, %{}, token_type: "refresh") do
+      {:ok, refresh_token}
+    end
+  end
+
+  @spec fetch_user(String.t()) :: User.t() | {:error, :user_not_found}
+  def fetch_user(nil), do: {:error, :user_not_found}
+
+  def fetch_user(email) when not is_nil(email) do
+    with {:ok, %User{} = user} <- Users.get_user_by_email(email, true) do
+      user
+    end
+  end
+end
diff --git a/lib/service/auth/ldap_authenticator.ex b/lib/service/auth/ldap_authenticator.ex
new file mode 100644
index 000000000..6db154376
--- /dev/null
+++ b/lib/service/auth/ldap_authenticator.ex
@@ -0,0 +1,180 @@
+# Portions of this file are derived from Pleroma:
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mobilizon.Service.Auth.LDAPAuthenticator do
+  @moduledoc """
+  Authenticate Mobilizon users through LDAP accounts
+  """
+  alias Mobilizon.Service.Auth.{Authenticator, MobilizonAuthenticator}
+  alias Mobilizon.Users
+  alias Mobilizon.Users.User
+
+  require Logger
+
+  import Authenticator,
+    only: [fetch_user: 1]
+
+  @behaviour Authenticator
+  @base MobilizonAuthenticator
+
+  @connection_timeout 10_000
+  @search_timeout 10_000
+
+  def login(email, password) do
+    with {:ldap, true} <- {:ldap, Mobilizon.Config.get([:ldap, :enabled])},
+         %User{} = user <- ldap_user(email, password) do
+      {:ok, user}
+    else
+      {:error, {:ldap_connection_error, _}} ->
+        # When LDAP is unavailable, try default authenticator
+        @base.login(email, password)
+
+      {:ldap, _} ->
+        @base.login(email, password)
+
+      error ->
+        error
+    end
+  end
+
+  def can_change_email?(%User{provider: provider}), do: provider != "ldap"
+
+  def can_change_password?(%User{provider: provider}), do: provider != "ldap"
+
+  defp ldap_user(email, password) do
+    ldap = Mobilizon.Config.get(:ldap, [])
+    host = Keyword.get(ldap, :host, "localhost")
+    port = Keyword.get(ldap, :port, 389)
+    ssl = Keyword.get(ldap, :ssl, false)
+    sslopts = Keyword.get(ldap, :sslopts, [])
+
+    options =
+      [{:port, port}, {:ssl, ssl}, {:timeout, @connection_timeout}] ++
+        if sslopts != [], do: [{:sslopts, sslopts}], else: []
+
+    case :eldap.open([to_charlist(host)], options) do
+      {:ok, connection} ->
+        try do
+          ensure_eventual_tls(connection, ldap)
+
+          base = Keyword.get(ldap, :base)
+          uid_field = Keyword.get(ldap, :uid, "cn")
+
+          # We first need to find the LDAP UID/CN for this specif email
+          with uid when is_binary(uid) <- search_user(connection, ldap, base, uid_field, email),
+               # Then we can verify the user's password
+               :ok <- bind_user(connection, base, uid_field, uid, password) do
+            case fetch_user(email) do
+              %User{} = user ->
+                user
+
+              _ ->
+                register_user(email)
+            end
+          else
+            {:error, error} ->
+              {:error, error}
+
+            error ->
+              {:error, error}
+          end
+        after
+          :eldap.close(connection)
+        end
+
+      {:error, error} ->
+        Logger.error("Could not open LDAP connection: #{inspect(error)}")
+        {:error, {:ldap_connection_error, error}}
+    end
+  end
+
+  @spec bind_user(any(), String.t(), String.t(), String.t(), String.t()) ::
+          User.t() | any()
+  defp bind_user(connection, base, uid, field, password) do
+    bind = "#{uid}=#{field},#{base}"
+    Logger.debug("Binding to LDAP with \"#{bind}\"")
+    :eldap.simple_bind(connection, bind, password)
+  end
+
+  @spec search_user(any(), Keyword.t(), String.t(), String.t(), String.t()) ::
+          String.t() | {:error, :ldap_registration_missing_attributes} | any()
+  defp search_user(connection, ldap, base, uid, email) do
+    # We may need to bind before performing the search
+    res =
+      if Keyword.get(ldap, :require_bind_for_search, true) do
+        admin_field = Keyword.get(ldap, :bind_uid)
+        admin_password = Keyword.get(ldap, :bind_password)
+        bind_user(connection, base, uid, admin_field, admin_password)
+      else
+        :ok
+      end
+
+    if res == :ok do
+      do_search_user(connection, base, uid, email)
+    else
+      res
+    end
+  end
+
+  # Search an user by uid to find their CN
+  @spec do_search_user(any(), String.t(), String.t(), String.t()) ::
+          String.t() | {:error, :ldap_registration_missing_attributes} | any()
+  defp do_search_user(connection, base, uid, email) do
+    with {:ok, {:eldap_search_result, [{:eldap_entry, _, attributes}], _}} <-
+           :eldap.search(connection, [
+             {:base, to_charlist(base)},
+             {:filter, :eldap.equalityMatch(to_charlist("mail"), to_charlist(email))},
+             {:scope, :eldap.wholeSubtree()},
+             {:attributes, [to_charlist(uid)]},
+             {:timeout, @search_timeout}
+           ]),
+         {:uid, {_, [uid]}} <- {:uid, List.keyfind(attributes, to_charlist(uid), 0)} do
+      :erlang.list_to_binary(uid)
+    else
+      {:ok, {:eldap_search_result, [], []}} ->
+        Logger.info("Unable to find user with email #{email}")
+        {:error, :ldap_search_email_not_found}
+
+      {:cn, err} ->
+        Logger.error("Could not find LDAP attribute CN: #{inspect(err)}")
+        {:error, :ldap_searcy_missing_attributes}
+
+      error ->
+        error
+    end
+  end
+
+  @spec register_user(String.t()) :: User.t() | any()
+  defp register_user(email) do
+    case Users.create_external(email, "ldap") do
+      {:ok, %User{} = user} ->
+        user
+
+      error ->
+        error
+    end
+  end
+
+  @spec ensure_eventual_tls(any(), Keyword.t()) :: :ok
+  defp ensure_eventual_tls(connection, ldap) do
+    if Keyword.get(ldap, :tls, false) do
+      :application.ensure_all_started(:ssl)
+
+      case :eldap.start_tls(
+             connection,
+             Keyword.get(ldap, :tlsopts, []),
+             @connection_timeout
+           ) do
+        :ok ->
+          :ok
+
+        error ->
+          Logger.error("Could not start TLS: #{inspect(error)}")
+      end
+    end
+
+    :ok
+  end
+end
diff --git a/lib/service/auth/mobilizon_authenticator.ex b/lib/service/auth/mobilizon_authenticator.ex
new file mode 100644
index 000000000..bec126901
--- /dev/null
+++ b/lib/service/auth/mobilizon_authenticator.ex
@@ -0,0 +1,39 @@
+defmodule Mobilizon.Service.Auth.MobilizonAuthenticator do
+  @moduledoc """
+  Authenticate Mobilizon users through database accounts
+  """
+  alias Mobilizon.Users.User
+
+  alias Mobilizon.Service.Auth.Authenticator
+
+  import Authenticator,
+    only: [fetch_user: 1]
+
+  @behaviour Authenticator
+
+  def login(email, password) do
+    require Logger
+
+    with {:user, %User{password_hash: password_hash, provider: nil} = user}
+         when not is_nil(password_hash) <-
+           {:user, fetch_user(email)},
+         {:acceptable_password, true} <-
+           {:acceptable_password, not (is_nil(password) || password == "")},
+         {:checkpw, true} <- {:checkpw, Argon2.verify_pass(password, password_hash)} do
+      {:ok, user}
+    else
+      {:user, {:error, :user_not_found}} ->
+        {:error, :user_not_found}
+
+      {:acceptable_password, false} ->
+        {:error, :bad_password}
+
+      {:checkpw, false} ->
+        {:error, :bad_password}
+    end
+  end
+
+  def can_change_email?(%User{provider: provider}), do: is_nil(provider)
+
+  def can_change_password?(%User{provider: provider}), do: is_nil(provider)
+end
diff --git a/lib/web/controllers/auth_controller.ex b/lib/web/controllers/auth_controller.ex
new file mode 100644
index 000000000..9e6c9d62c
--- /dev/null
+++ b/lib/web/controllers/auth_controller.ex
@@ -0,0 +1,82 @@
+defmodule Mobilizon.Web.AuthController do
+  use Mobilizon.Web, :controller
+
+  alias Mobilizon.Service.Auth.Authenticator
+  alias Mobilizon.Users
+  alias Mobilizon.Users.User
+  require Logger
+  plug(:put_layout, false)
+
+  plug(Ueberauth)
+
+  def request(conn, %{"provider" => provider} = _params) do
+    redirect(conn, to: "/login?code=Login Provider not found&provider=#{provider}")
+  end
+
+  def callback(
+        %{assigns: %{ueberauth_failure: fails}} = conn,
+        %{"provider" => provider} = _params
+      ) do
+    Logger.warn("Unable to login user with #{provider} #{inspect(fails)}")
+
+    redirect(conn, to: "/login?code=Error with Login Provider&provider=#{provider}")
+  end
+
+  def callback(
+        %{assigns: %{ueberauth_auth: %Ueberauth.Auth{strategy: strategy} = auth}} = conn,
+        _params
+      ) do
+    email = email_from_ueberauth(auth)
+    [_, _, _, strategy] = strategy |> to_string() |> String.split(".")
+    strategy = String.downcase(strategy)
+
+    user =
+      with {:valid_email, false} <- {:valid_email, is_nil(email) or email == ""},
+           {:error, :user_not_found} <- Users.get_user_by_email(email),
+           {:ok, %User{} = user} <- Users.create_external(email, strategy) do
+        user
+      else
+        {:ok, %User{} = user} ->
+          user
+
+        {:error, error} ->
+          {:error, error}
+
+        error ->
+          {:error, error}
+      end
+
+    with %User{} = user <- user,
+         {:ok, %{access_token: access_token, refresh_token: refresh_token}} <-
+           Authenticator.generate_tokens(user) do
+      Logger.info("Logged-in user \"#{email}\" through #{strategy}")
+
+      render(conn, "callback.html", %{
+        access_token: access_token,
+        refresh_token: refresh_token,
+        user: user
+      })
+    else
+      err ->
+        Logger.warn("Unable to login user \"#{email}\" #{inspect(err)}")
+        redirect(conn, to: "/login?code=Error with Login Provider&provider=#{strategy}")
+    end
+  end
+
+  # Github only give public emails as part of the user profile,
+  # so we explicitely request all user emails and filter on the primary one
+  defp email_from_ueberauth(%Ueberauth.Auth{
+         strategy: Ueberauth.Strategy.Github,
+         extra: %Ueberauth.Auth.Extra{raw_info: %{user: %{"emails" => emails}}}
+       })
+       when length(emails) > 0,
+       do: emails |> Enum.find(& &1["primary"]) |> (& &1["email"]).()
+
+  defp email_from_ueberauth(%Ueberauth.Auth{
+         extra: %Ueberauth.Auth.Extra{raw_info: %{user: %{"email" => email}}}
+       })
+       when not is_nil(email) and email != "",
+       do: email
+
+  defp email_from_ueberauth(_), do: nil
+end
diff --git a/lib/web/router.ex b/lib/web/router.ex
index d7a565186..725afa2d8 100644
--- a/lib/web/router.ex
+++ b/lib/web/router.ex
@@ -150,6 +150,10 @@ defmodule Mobilizon.Web.Router do
     get("/groups/me", PageController, :index, as: "my_groups")
 
     get("/interact", PageController, :interact)
+
+    get("/auth/:provider", AuthController, :request)
+    get("/auth/:provider/callback", AuthController, :callback)
+    post("/auth/:provider/callback", AuthController, :callback)
   end
 
   scope "/proxy/", Mobilizon.Web do
diff --git a/lib/web/views/auth_view.ex b/lib/web/views/auth_view.ex
new file mode 100644
index 000000000..1f94a3407
--- /dev/null
+++ b/lib/web/views/auth_view.ex
@@ -0,0 +1,29 @@
+defmodule Mobilizon.Web.AuthView do
+  @moduledoc """
+  View for the auth routes
+  """
+
+  use Mobilizon.Web, :view
+  alias Mobilizon.Service.Metadata.Instance
+  alias Phoenix.HTML.Tag
+  import Mobilizon.Web.Views.Utils
+
+  def render("callback.html", %{
+        conn: conn,
+        access_token: access_token,
+        refresh_token: refresh_token,
+        user: %{id: user_id, email: user_email, role: user_role, default_actor_id: user_actor_id}
+      }) do
+    info_tags = [
+      Tag.tag(:meta, name: "auth-access-token", content: access_token),
+      Tag.tag(:meta, name: "auth-refresh-token", content: refresh_token),
+      Tag.tag(:meta, name: "auth-user-id", content: user_id),
+      Tag.tag(:meta, name: "auth-user-email", content: user_email),
+      Tag.tag(:meta, name: "auth-user-role", content: user_role),
+      Tag.tag(:meta, name: "auth-user-actor-id", content: user_actor_id)
+    ]
+
+    tags = Instance.build_tags() ++ info_tags
+    inject_tags(tags, get_locale(conn))
+  end
+end
diff --git a/mix.exs b/mix.exs
index 745298465..9b4cf27e2 100644
--- a/mix.exs
+++ b/mix.exs
@@ -46,6 +46,23 @@ defmodule Mobilizon.Mixfile do
   defp elixirc_paths(:dev), do: ["lib", "test/support/factory.ex"]
   defp elixirc_paths(_), do: ["lib"]
 
+  # Specifies OAuth dependencies.
+  defp oauth_deps do
+    oauth_strategy_packages =
+      System.get_env("OAUTH_CONSUMER_STRATEGIES")
+      |> to_string()
+      |> String.split()
+      |> Enum.map(fn strategy_entry ->
+        with [_strategy, dependency] <- String.split(strategy_entry, ":") do
+          dependency
+        else
+          [strategy] -> "ueberauth_#{strategy}"
+        end
+      end)
+
+    for s <- oauth_strategy_packages, do: {String.to_atom(s), ">= 0.0.0"}
+  end
+
   # Specifies your project dependencies.
   #
   # Type `mix help deps` for examples and options.
@@ -104,6 +121,16 @@ defmodule Mobilizon.Mixfile do
       {:floki, "~> 0.26.0"},
       {:ip_reserved, "~> 0.1.0"},
       {:fast_sanitize, "~> 0.1"},
+      {:ueberauth, "~> 0.6"},
+      {:ueberauth_twitter, "~> 0.3"},
+      {:ueberauth_github, "~> 0.7"},
+      {:ueberauth_facebook, "~> 0.8"},
+      {:ueberauth_discord, "~> 0.5"},
+      {:ueberauth_google, "~> 0.9"},
+      {:ueberauth_keycloak_strategy,
+       git: "https://github.com/tcitworld/ueberauth_keycloak.git", branch: "upgrade-deps"},
+      {:ueberauth_gitlab_strategy,
+       git: "https://github.com/tcitworld/ueberauth_gitlab.git", branch: "upgrade-deps"},
       # Dev and test dependencies
       {:phoenix_live_reload, "~> 1.2", only: [:dev, :e2e]},
       {:ex_machina, "~> 2.3", only: [:dev, :test]},
@@ -116,7 +143,7 @@ defmodule Mobilizon.Mixfile do
       {:credo, "~> 1.4.0", only: [:dev, :test], runtime: false},
       {:mock, "~> 0.3.4", only: :test},
       {:elixir_feed_parser, "~> 2.1.0", only: :test}
-    ]
+    ] ++ oauth_deps()
   end
 
   # Aliases are shortcuts or tasks specific to the current project.
diff --git a/mix.lock b/mix.lock
index 13a34361a..4949dd0c5 100644
--- a/mix.lock
+++ b/mix.lock
@@ -31,12 +31,13 @@
   "elixir_feed_parser": {:hex, :elixir_feed_parser, "2.1.0", "bb96fb6422158dc7ad59de62ef211cc69d264acbbe63941a64a5dce97bbbc2e6", [:mix], [{:timex, "~> 3.4", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "2d3c62fe7b396ee3b73d7160bc8fadbd78bfe9597c98c7d79b3f1038d9cba28f"},
   "elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"},
   "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
+  "esaml": {:git, "git://github.com/wrren/esaml.git", "2cace5778e4323216bcff2085ca9739e42a68a42", [branch: "ueberauth_saml"]},
   "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"},
   "ex_cldr": {:hex, :ex_cldr, "2.16.1", "905b03c38b5fb51668a347f2e6b586bcb2c0816cd98f7d913104872c43cbc61f", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:cldr_utils, "~> 2.9", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.13", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "006e500769982e57e6f3e32cbc4664345f78b014bb5ff48ddc394d67c86c1a8d"},
   "ex_cldr_calendars": {:hex, :ex_cldr_calendars, "1.9.0", "ace1c57ba3850753c9ac6ddb89dc0c9a9e5e1c57ecad587e21c8925ad30a3838", [:mix], [{:calendar_interval, "~> 0.2", [hex: :calendar_interval, repo: "hexpm", optional: true]}, {:earmark, "~> 1.0", [hex: :earmark, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.13", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_cldr_units, "~> 3.0", [hex: :ex_cldr_units, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "a4b07773e2a326474f44a6bc51fffbec634859a1bad5cc6e6eb55eba45115541"},
   "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.5.0", "e369ae3c1cd5cd20aa20988b153fd2902b4ab08aec63ca8757d7104bdb79f867", [:mix], [{:ex_cldr, "~> 2.14", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ba16b1df60bcec52c986481bbdfa7cfaec899b610f869d2b3c5a9a8149f67668"},
   "ex_cldr_dates_times": {:hex, :ex_cldr_dates_times, "2.5.1", "9439d1c40cfd03c3d8f3f60f5d3e3f2c6eaf0fd714541d687531cce78cfb9909", [:mix], [{:calendar_interval, "~> 0.2", [hex: :calendar_interval, repo: "hexpm", optional: true]}, {:ex_cldr_calendars, "~> 1.8", [hex: :ex_cldr_calendars, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.15", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "62a2f8d41ec6e789137bbf3ac7c944885a8ef6b7ce475905d056d1805b482427"},
-  "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.15.0", "207843c6ddae802a2b5fd43eb95c4b65eae8a0a876ce23ae4413eb098b222977", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.15", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, "~> 2.5", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "3c6c220e03590f08e2f3cb4f3e0c2e1a78fe56a12229331edb952cbdc67935e1"},
+  "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.15.1", "dced7ffee69c4830593258b69b294adb4c65cf539e1d8ae0a4de31cfc8aa56a0", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.15", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, "~> 2.5", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "c6a4b69ef80b8ffbb6c8fb69a2b365ba542580e0f76a15d8c6ee9142bd1b97ea"},
   "ex_crypto": {:hex, :ex_crypto, "0.10.0", "af600a89b784b36613a989da6e998c1b200ff1214c3cfbaf8deca4aa2f0a1739", [:mix], [{:poison, ">= 2.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "ccc7472cfe8a0f4565f97dce7e9280119bf15a5ea51c6535e5b65f00660cde1c"},
   "ex_doc": {:hex, :ex_doc, "0.22.1", "9bb6d51508778193a4ea90fa16eac47f8b67934f33f8271d5e1edec2dc0eee4c", [:mix], [{:earmark, "~> 1.4.0", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "d957de1b75cb9f78d3ee17820733dc4460114d8b1e11f7ee4fd6546e69b1db60"},
   "ex_ical": {:hex, :ex_ical, "0.2.0", "4b928b554614704016cc0c9ee226eb854da9327a1cc460457621ceacb1ac29a6", [:mix], [{:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "db76473b2ae0259e6633c6c479a5a4d8603f09497f55c88f9ef4d53d2b75befb"},
@@ -91,7 +92,10 @@
   "mock": {:hex, :mock, "0.3.5", "feb81f52b8dcf0a0d65001d2fec459f6b6a8c22562d94a965862f6cc066b5431", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "6fae404799408300f863550392635d8f7e3da6b71abdd5c393faf41b131c8728"},
   "mogrify": {:hex, :mogrify, "0.7.4", "9b2496dde44b1ce12676f85d7dc531900939e6367bc537c7243a1b089435b32d", [:mix], [], "hexpm", "50d79e337fba6bc95bfbef918058c90f50b17eed9537771e61d4619488f099c3"},
   "nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"},
+  "oauth2": {:hex, :oauth2, "2.0.0", "338382079fe16c514420fa218b0903f8ad2d4bfc0ad0c9f988867dfa246731b0", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "881b8364ac7385f9fddc7949379cbe3f7081da37233a1aa7aab844670a91e7e7"},
+  "oauther": {:hex, :oauther, "1.1.1", "7d8b16167bb587ecbcddd3f8792beb9ec3e7b65c1f8ebd86b8dd25318d535752", [:mix], [], "hexpm", "9374f4302045321874cccdc57eb975893643bd69c3b22bf1312dab5f06e5788e"},
   "oban": {:hex, :oban, "1.2.0", "7cca94d341be43d220571e28f69131c4afc21095b25257397f50973d3fc59b07", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba5f8b3f7d76967b3e23cf8014f6a13e4ccb33431e4808f036709a7f822362ee"},
+  "paddle": {:hex, :paddle, "0.1.4", "3697996d79e3d771d6f7560a23e4bad1ed7b7f7fd3e784f97bc39565963b2b13", [:mix], [], "hexpm", "fc719a9e7c86f319b9f4bf413d6f0f326b0c4930d5bc6630d074598ed38e2143"},
   "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
   "phoenix": {:hex, :phoenix, "1.5.3", "bfe0404e48ea03dfe17f141eff34e1e058a23f15f109885bbdcf62be303b49ff", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8e16febeb9640d8b33895a691a56481464b82836d338bb3a23125cd7b6157c25"},
   "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"},
@@ -110,10 +114,21 @@
   "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},
   "slugger": {:hex, :slugger, "0.3.0", "efc667ab99eee19a48913ccf3d038b1fb9f165fa4fbf093be898b8099e61b6ed", [:mix], [], "hexpm", "20d0ded0e712605d1eae6c5b4889581c3460d92623a930ddda91e0e609b5afba"},
   "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
-  "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
+  "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
   "timex": {:hex, :timex, "3.6.2", "845cdeb6119e2fef10751c0b247b6c59d86d78554c83f78db612e3290f819bc2", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "26030b46199d02a590be61c2394b37ea25a3664c02fafbeca0b24c972025d47a"},
   "tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a6e1ee7003c4d04ecbd21dd3ec690d4c6662db5d3bbdd7262d53cdf5e7c746c1"},
+  "ueberauth": {:hex, :ueberauth, "0.6.3", "d42ace28b870e8072cf30e32e385579c57b9cc96ec74fa1f30f30da9c14f3cc0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "afc293d8a1140d6591b53e3eaf415ca92842cb1d32fad3c450c6f045f7f91b60"},
+  "ueberauth_discord": {:hex, :ueberauth_discord, "0.5.0", "52421277b93fda769b51636e542b5085f3861efdc7fa48ac4bedb6dae0b645e1", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.6.3", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "9a3808baf44297e26bd5042ba9ea5398aa60023e054eb9a5ac8a4eacd0467a78"},
+  "ueberauth_facebook": {:hex, :ueberauth_facebook, "0.8.1", "c254be4ab367c276773c2e41d3c0fe343ae118e244afc8d5a4e3e5c438951fdc", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.6.0", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "c2cf210ef45bd20611234ef17517f9d1dff6b31d3fb6ad96789143eb0943f540"},
+  "ueberauth_github": {:hex, :ueberauth_github, "0.8.0", "2216c8cdacee0de6245b422fb397921b64a29416526985304e345dab6a799d17", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.6.0", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "b65ccc001a7b0719ba069452f3333d68891f4613ae787a340cce31e2a43307a3"},
+  "ueberauth_gitlab_strategy": {:git, "https://github.com/tcitworld/ueberauth_gitlab.git", "9fc5d30b5d87ff7cdef293a1c128f25777dcbe59", [branch: "upgrade-deps"]},
+  "ueberauth_google": {:hex, :ueberauth_google, "0.9.0", "e098e1d6df647696b858b0289eae7e4dc8c662abee9e309d64bc115192c51bf5", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.6.0", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "5453ba074df7ee14fb5b121bb04a64cda5266cd23b28af8a2fdf02dd40959ab4"},
+  "ueberauth_keycloak": {:git, "https://github.com/tcitworld/ueberauth_keycloak.git", "02447d8a75bd36ba26c17c7b1b8bab3538bb2e7a", [branch: "upgrade-deps"]},
+  "ueberauth_keycloak_strategy": {:git, "https://github.com/tcitworld/ueberauth_keycloak.git", "d892f0f9daf9e0023319b69ac2f7c2c6edff2b14", [branch: "upgrade-deps"]},
+  "ueberauth_saml": {:git, "https://github.com/wrren/ueberauth_saml.git", "dfcb4ae3f509afec0f442ce455c41feacac24511", []},
+  "ueberauth_twitter": {:hex, :ueberauth_twitter, "0.4.0", "4b98620341bc91bac90459093bba093c650823b6e2df35b70255c493c17e9227", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:oauther, "~> 1.1", [hex: :oauther, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.6", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "fb29c9047ca263038c0c61f5a0ec8597e8564aba3f2b4cb02704b60205fd4468"},
   "unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"},
   "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
+  "uuid": {:git, "git://github.com/botsunit/erlang-uuid", "1effbbbd200f9f5d9d5154e81b83fe8e4c3fe714", [branch: "master"]},
   "xml_builder": {:hex, :xml_builder, "2.0.0", "371ed27bb63bf0598dbaf3f0c466e5dc7d16cb4ecb68f06a67f953654062e21b", [:mix], [], "hexpm", "baeb5c8d42204bac2b856ffd50e8cda42d63b622984538d18d92733e4e790fbd"},
 }
diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po
index 6f944ba9d..dbbd40d98 100644
--- a/priv/gettext/en/LC_MESSAGES/default.po
+++ b/priv/gettext/en/LC_MESSAGES/default.po
@@ -1177,12 +1177,12 @@ msgstr "If you didn't request this, please ignore this email."
 #, elixir-format, fuzzy
 #: lib/web/templates/email/email.text.eex:10
 msgid "In the meantime, please consider this the software as not (yet) finished. Read more on the Framasoft blog:"
-msgstr "In the meantime, please consider that the software is not (yet) finished. More information %{a_start}on our blog%{a_end}."
+msgstr "In the meantime, please consider that the software is not (yet) finished. More information on our blog."
 
 #, elixir-format, fuzzy
 #: lib/web/templates/email/email.text.eex:9
 msgid "Mobilizon is still under development, we will add new features along the updates, until the release of version 1 of the software in the fall of 2020."
-msgstr "Mobilizon is under development, we will add new features to this site during regular updates, until the release of %{b_start}version 1 of the software in the first half of 2020%{b_end}."
+msgstr "Mobilizon is under development, we will add new features to this site during regular updates, until the release of version 1 of the software in the first half of 2020."
 
 #, elixir-format, fuzzy
 #: lib/web/templates/email/email.text.eex:7
diff --git a/priv/repo/migrations/20200630123819_add_provider_to_user_and_make_password_mandatory.exs b/priv/repo/migrations/20200630123819_add_provider_to_user_and_make_password_mandatory.exs
new file mode 100644
index 000000000..3eb536019
--- /dev/null
+++ b/priv/repo/migrations/20200630123819_add_provider_to_user_and_make_password_mandatory.exs
@@ -0,0 +1,17 @@
+defmodule Mobilizon.Storage.Repo.Migrations.AddProviderToUserAndMakePasswordMandatory do
+  use Ecto.Migration
+
+  def up do
+    alter table(:users) do
+      add(:provider, :string, null: true)
+      modify(:password_hash, :string, null: true)
+    end
+  end
+
+  def down do
+    alter table(:users) do
+      remove(:provider)
+      modify(:password_hash, :string, null: false)
+    end
+  end
+end
diff --git a/test/graphql/resolvers/participant_test.exs b/test/graphql/resolvers/participant_test.exs
index 5d12cab22..3290c9f1d 100644
--- a/test/graphql/resolvers/participant_test.exs
+++ b/test/graphql/resolvers/participant_test.exs
@@ -991,7 +991,7 @@ defmodule Mobilizon.GraphQL.Resolvers.ParticipantTest do
           }
     """
 
-    clear_config([:anonymous, :participation])
+    setup do: clear_config([:anonymous, :participation])
 
     setup %{conn: conn, actor: actor, user: user} do
       Mobilizon.Config.clear_config_cache()
diff --git a/test/graphql/resolvers/report_test.exs b/test/graphql/resolvers/report_test.exs
index 7f7e209ec..7063f3197 100644
--- a/test/graphql/resolvers/report_test.exs
+++ b/test/graphql/resolvers/report_test.exs
@@ -33,7 +33,7 @@ defmodule Mobilizon.GraphQL.Resolvers.ReportTest do
       }
     """
 
-    clear_config([:anonymous, :reports])
+    setup do: clear_config([:anonymous, :reports])
 
     setup %{conn: conn} do
       Mobilizon.Config.clear_config_cache()
diff --git a/test/graphql/resolvers/user_test.exs b/test/graphql/resolvers/user_test.exs
index 9e8fc06cc..477e9fed3 100644
--- a/test/graphql/resolvers/user_test.exs
+++ b/test/graphql/resolvers/user_test.exs
@@ -9,6 +9,7 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
   alias Mobilizon.Actors.Actor
   alias Mobilizon.Conversations.Comment
   alias Mobilizon.Events.{Event, Participant}
+  alias Mobilizon.Service.Auth.Authenticator
   alias Mobilizon.Users.User
 
   alias Mobilizon.GraphQL.AbsintheHelpers
@@ -45,8 +46,14 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
         }
   """
 
+  @send_reset_password_mutation """
+    mutation SendResetPassword($email: String!) {
+      sendResetPassword(email: $email)
+    }
+  """
+
   @delete_user_account_mutation """
-    mutation DeleteAccount($password: String!) {
+    mutation DeleteAccount($password: String) {
       deleteAccount (password: $password) {
         id
       }
@@ -712,45 +719,50 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
   end
 
   describe "Resolver: Send reset password" do
-    test "test send_reset_password/3 with valid email", context do
-      user = insert(:user)
-
-      mutation = """
-          mutation {
-            sendResetPassword(
-                  email: "#{user.email}"
-              )
-            }
-      """
+    test "test send_reset_password/3 with valid email", %{conn: conn} do
+      %User{email: email} = insert(:user)
 
       res =
-        context.conn
-        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
+        conn
+        |> AbsintheHelpers.graphql_query(
+          query: @send_reset_password_mutation,
+          variables: %{email: email}
+        )
 
-      assert json_response(res, 200)["data"]["sendResetPassword"] == user.email
+      assert res["data"]["sendResetPassword"] == email
     end
 
-    test "test send_reset_password/3 with invalid email", context do
-      mutation = """
-          mutation {
-            sendResetPassword(
-                  email: "oh no"
-              )
-            }
-      """
+    test "test send_reset_password/3 with invalid email", %{conn: conn} do
+      res =
+        conn
+        |> AbsintheHelpers.graphql_query(
+          query: @send_reset_password_mutation,
+          variables: %{email: "not an email"}
+        )
+
+      assert hd(res["errors"])["message"] ==
+               "No user with this email was found"
+    end
+
+    test "test send_reset_password/3 for an LDAP user", %{conn: conn} do
+      {:ok, %User{email: email}} = Users.create_external("some@users.com", "ldap")
 
       res =
-        context.conn
-        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
+        conn
+        |> AbsintheHelpers.graphql_query(
+          query: @send_reset_password_mutation,
+          variables: %{email: email}
+        )
 
-      assert hd(json_response(res, 200)["errors"])["message"] ==
-               "No user with this email was found"
+      assert hd(res["errors"])["message"] ==
+               "This user can't reset their password"
     end
   end
 
   describe "Resolver: Reset user's password" do
     test "test reset_password/3 with valid email", context do
       {:ok, %User{} = user} = Users.register(%{email: "toto@tata.tld", password: "p4ssw0rd"})
+      Users.update_user(user, %{confirmed_at: DateTime.utc_now()})
       %Actor{} = insert(:actor, user: user)
       {:ok, _email_sent} = Email.User.send_password_reset_email(user)
       %User{reset_password_token: reset_password_token} = Users.get_user!(user.id)
@@ -772,6 +784,7 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
         context.conn
         |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
 
+      assert is_nil(json_response(res, 200)["errors"])
       assert json_response(res, 200)["data"]["resetPassword"]["user"]["id"] == to_string(user.id)
     end
 
@@ -829,7 +842,7 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
   end
 
   describe "Resolver: Login a user" do
-    test "test login_user/3 with valid credentials", context do
+    test "test login_user/3 with valid credentials", %{conn: conn} do
       {:ok, %User{} = user} = Users.register(%{email: "toto@tata.tld", password: "p4ssw0rd"})
 
       {:ok, %User{} = _user} =
@@ -839,30 +852,18 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
           "confirmation_token" => nil
         })
 
-      mutation = """
-          mutation {
-            login(
-                  email: "#{user.email}",
-                  password: "#{user.password}",
-              ) {
-                accessToken,
-                refreshToken,
-                user {
-                  id
-                }
-              }
-            }
-      """
-
       res =
-        context.conn
-        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
+        conn
+        |> AbsintheHelpers.graphql_query(
+          query: @login_mutation,
+          variables: %{email: user.email, password: user.password}
+        )
 
-      assert login = json_response(res, 200)["data"]["login"]
+      assert login = res["data"]["login"]
       assert Map.has_key?(login, "accessToken") && not is_nil(login["accessToken"])
     end
 
-    test "test login_user/3 with invalid password", context do
+    test "test login_user/3 with invalid password", %{conn: conn} do
       {:ok, %User{} = user} = Users.register(%{email: "toto@tata.tld", password: "p4ssw0rd"})
 
       {:ok, %User{} = _user} =
@@ -872,79 +873,40 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
           "confirmation_token" => nil
         })
 
-      mutation = """
-          mutation {
-            login(
-                  email: "#{user.email}",
-                  password: "bad password",
-              ) {
-                accessToken,
-                user {
-                  default_actor {
-                    preferred_username,
-                  }
-                }
-              }
-            }
-      """
-
       res =
-        context.conn
-        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
+        conn
+        |> AbsintheHelpers.graphql_query(
+          query: @login_mutation,
+          variables: %{email: user.email, password: "bad password"}
+        )
 
-      assert hd(json_response(res, 200)["errors"])["message"] ==
+      assert hd(res["errors"])["message"] ==
                "Impossible to authenticate, either your email or password are invalid."
     end
 
-    test "test login_user/3 with invalid email", context do
-      mutation = """
-          mutation {
-            login(
-                  email: "bad email",
-                  password: "bad password",
-              ) {
-                accessToken,
-                user {
-                  default_actor {
-                    preferred_username,
-                  }
-                }
-              }
-            }
-      """
-
+    test "test login_user/3 with invalid email", %{conn: conn} do
       res =
-        context.conn
-        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
+        conn
+        |> AbsintheHelpers.graphql_query(
+          query: @login_mutation,
+          variables: %{email: "bad email", password: "bad password"}
+        )
 
-      assert hd(json_response(res, 200)["errors"])["message"] ==
+      assert hd(res["errors"])["message"] ==
                "No user with this email was found"
     end
 
-    test "test login_user/3 with unconfirmed user", context do
+    test "test login_user/3 with unconfirmed user", %{conn: conn} do
       {:ok, %User{} = user} = Users.register(%{email: "toto@tata.tld", password: "p4ssw0rd"})
 
-      mutation = """
-          mutation {
-            login(
-                  email: "#{user.email}",
-                  password: "#{user.password}",
-              ) {
-                accessToken,
-                user {
-                  default_actor {
-                    preferred_username,
-                  }
-                }
-              }
-            }
-      """
-
       res =
-        context.conn
-        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
+        conn
+        |> AbsintheHelpers.graphql_query(
+          query: @login_mutation,
+          variables: %{email: user.email, password: user.password}
+        )
 
-      assert hd(json_response(res, 200)["errors"])["message"] == "User account not confirmed"
+      assert hd(res["errors"])["message"] == "No user with this email was found"
     end
   end
 
@@ -970,7 +932,7 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
 
     test "test refresh_token/3 with an appropriate token", context do
       user = insert(:user)
-      {:ok, refresh_token} = Users.generate_refresh_token(user)
+      {:ok, refresh_token} = Authenticator.generate_refresh_token(user)
 
       mutation = """
           mutation {
@@ -1441,6 +1403,18 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
       assert is_nil(Events.get_participant(participant_id))
     end
 
+    test "delete_account/3 with 3rd-party auth login", %{conn: conn} do
+      {:ok, %User{} = user} = Users.create_external(@email, "keycloak")
+
+      res =
+        conn
+        |> auth_conn(user)
+        |> AbsintheHelpers.graphql_query(query: @delete_user_account_mutation)
+
+      assert is_nil(res["errors"])
+      assert res["data"]["deleteAccount"]["id"] == to_string(user.id)
+    end
+
     test "delete_account/3 with invalid password", %{conn: conn} do
       {:ok, %User{} = user} = Users.register(%{email: @email, password: @password})
 
diff --git a/test/mobilizon/users/users_test.exs b/test/mobilizon/users/users_test.exs
index 01b1520e2..3f06ae066 100644
--- a/test/mobilizon/users/users_test.exs
+++ b/test/mobilizon/users/users_test.exs
@@ -72,14 +72,6 @@ defmodule Mobilizon.UsersTest do
 
     @email "email@domain.tld"
     @password "password"
-    test "authenticate/1 checks the user's password" do
-      {:ok, %User{} = user} = Users.register(%{email: @email, password: @password})
-
-      assert {:ok, _} = Users.authenticate(%{user: user, password: @password})
-
-      assert {:error, :unauthorized} ==
-               Users.authenticate(%{user: user, password: "bad password"})
-    end
 
     test "get_user_by_email/1 finds an user by its email" do
       {:ok, %User{email: email} = user} = Users.register(%{email: @email, password: @password})
diff --git a/test/service/auth/authentificator_test.exs b/test/service/auth/authentificator_test.exs
new file mode 100644
index 000000000..e0d768c99
--- /dev/null
+++ b/test/service/auth/authentificator_test.exs
@@ -0,0 +1,34 @@
+defmodule Mobilizon.Service.Auth.AuthenticatorTest do
+  use Mobilizon.DataCase
+
+  alias Mobilizon.Service.Auth.Authenticator
+  alias Mobilizon.Users
+  alias Mobilizon.Users.User
+  import Mobilizon.Factory
+
+  @email "email@domain.tld"
+  @password "password"
+
+  describe "test authentification" do
+    test "authenticate/1 checks the user's password" do
+      {:ok, %User{} = user} = Users.register(%{email: @email, password: @password})
+      Users.update_user(user, %{confirmed_at: DateTime.utc_now()})
+
+      assert {:ok, _} = Authenticator.authenticate(@email, @password)
+
+      assert {:error, :bad_password} ==
+               Authenticator.authenticate(@email, "completely wrong password")
+    end
+  end
+
+  describe "fetch_user/1" do
+    test "returns user by email" do
+      user = insert(:user)
+      assert Authenticator.fetch_user(user.email).id == user.id
+    end
+
+    test "returns nil" do
+      assert Authenticator.fetch_user("email") == {:error, :user_not_found}
+    end
+  end
+end
diff --git a/test/service/auth/ldap_authentificator_test.exs b/test/service/auth/ldap_authentificator_test.exs
new file mode 100644
index 000000000..6168f8594
--- /dev/null
+++ b/test/service/auth/ldap_authentificator_test.exs
@@ -0,0 +1,238 @@
+defmodule Mobilizon.Service.Auth.LDAPAuthenticatorTest do
+  use Mobilizon.Web.ConnCase
+  use Mobilizon.Tests.Helpers
+
+  alias Mobilizon.GraphQL.AbsintheHelpers
+  alias Mobilizon.Service.Auth.{Authenticator, LDAPAuthenticator}
+  alias Mobilizon.Users.User
+  alias Mobilizon.Web.Auth.Guardian
+
+  import Mobilizon.Factory
+  import ExUnit.CaptureLog
+  import Mock
+
+  @skip if !Code.ensure_loaded?(:eldap), do: :skip
+  @admin_password "admin_password"
+
+  setup_all do
+    clear_config([:ldap, :enabled], true)
+    clear_config([:ldap, :bind_uid], "admin")
+    clear_config([:ldap, :bind_password], @admin_password)
+  end
+
+  setup_all do:
+              clear_config(
+                Authenticator,
+                LDAPAuthenticator
+              )
+
+  @login_mutation """
+  mutation Login($email: String!, $password: String!) {
+    login(email: $email, password: $password) {
+        accessToken,
+        refreshToken,
+        user {
+          id
+        }
+      }
+    }
+  """
+
+  describe "login" do
+    @tag @skip
+    test "authorizes the existing user using LDAP credentials", %{conn: conn} do
+      user_password = "testpassword"
+      admin_password = "admin_password"
+      user = insert(:user, password_hash: Argon2.hash_pwd_salt(user_password))
+
+      host = [:ldap, :host] |> Mobilizon.Config.get() |> to_charlist
+      port = Mobilizon.Config.get([:ldap, :port])
+
+      with_mocks [
+        {:eldap, [],
+         [
+           open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end,
+           simple_bind: fn _connection, _dn, password ->
+             case password do
+               ^admin_password -> :ok
+               ^user_password -> :ok
+             end
+           end,
+           equalityMatch: fn _type, _value -> :ok end,
+           wholeSubtree: fn -> :ok end,
+           search: fn _connection, _options ->
+             {:ok,
+              {:eldap_search_result, [{:eldap_entry, '', [{'cn', [to_charlist("MyUser")]}]}], []}}
+           end,
+           close: fn _connection ->
+             send(self(), :close_connection)
+             :ok
+           end
+         ]}
+      ] do
+        res =
+          conn
+          |> AbsintheHelpers.graphql_query(
+            query: @login_mutation,
+            variables: %{email: user.email, password: user_password}
+          )
+
+        assert is_nil(res["error"])
+        assert token = res["data"]["login"]["accessToken"]
+
+        {:ok, %User{} = user_from_token, _claims} = Guardian.resource_from_token(token)
+
+        assert user_from_token.id == user.id
+        assert_received :close_connection
+      end
+    end
+
+    @tag @skip
+    test "creates a new user after successful LDAP authorization", %{conn: conn} do
+      user_password = "testpassword"
+      admin_password = "admin_password"
+      user = build(:user)
+
+      host = [:ldap, :host] |> Mobilizon.Config.get() |> to_charlist
+      port = Mobilizon.Config.get([:ldap, :port])
+
+      with_mocks [
+        {:eldap, [],
+         [
+           open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end,
+           simple_bind: fn _connection, _dn, password ->
+             case password do
+               ^admin_password -> :ok
+               ^user_password -> :ok
+             end
+           end,
+           equalityMatch: fn _type, _value -> :ok end,
+           wholeSubtree: fn -> :ok end,
+           search: fn _connection, _options ->
+             {:ok,
+              {:eldap_search_result, [{:eldap_entry, '', [{'cn', [to_charlist("MyUser")]}]}], []}}
+           end,
+           close: fn _connection ->
+             send(self(), :close_connection)
+             :ok
+           end
+         ]}
+      ] do
+        res =
+          conn
+          |> AbsintheHelpers.graphql_query(
+            query: @login_mutation,
+            variables: %{email: user.email, password: user_password}
+          )
+
+        assert is_nil(res["error"])
+        assert token = res["data"]["login"]["accessToken"]
+
+        {:ok, %User{} = user_from_token, _claims} = Guardian.resource_from_token(token)
+
+        assert user_from_token.email == user.email
+        assert_received :close_connection
+      end
+    end
+
+    @tag @skip
+    test "falls back to the default authorization when LDAP is unavailable", %{conn: conn} do
+      user_password = "testpassword"
+      admin_password = "admin_password"
+      user = insert(:user, password_hash: Argon2.hash_pwd_salt(user_password))
+
+      host = [:ldap, :host] |> Mobilizon.Config.get() |> to_charlist
+      port = Mobilizon.Config.get([:ldap, :port])
+
+      with_mocks [
+        {:eldap, [],
+         [
+           open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:error, 'connect failed'} end,
+           simple_bind: fn _connection, _dn, password ->
+             case password do
+               ^admin_password -> :ok
+               ^user_password -> :ok
+             end
+           end,
+           equalityMatch: fn _type, _value -> :ok end,
+           wholeSubtree: fn -> :ok end,
+           search: fn _connection, _options ->
+             {:ok,
+              {:eldap_search_result, [{:eldap_entry, '', [{'cn', [to_charlist("MyUser")]}]}], []}}
+           end,
+           close: fn _connection ->
+             send(self(), :close_connection)
+             :ok
+           end
+         ]}
+      ] do
+        log =
+          capture_log(fn ->
+            res =
+              conn
+              |> AbsintheHelpers.graphql_query(
+                query: @login_mutation,
+                variables: %{email: user.email, password: user_password}
+              )
+
+            assert is_nil(res["error"])
+            assert token = res["data"]["login"]["accessToken"]
+
+            {:ok, %User{} = user_from_token, _claims} = Guardian.resource_from_token(token)
+
+            assert user_from_token.email == user.email
+          end)
+
+        assert log =~ "Could not open LDAP connection: 'connect failed'"
+        refute_received :close_connection
+      end
+    end
+
+    @tag @skip
+    test "disallow authorization for wrong LDAP credentials", %{conn: conn} do
+      user_password = "testpassword"
+      user = insert(:user, password_hash: Argon2.hash_pwd_salt(user_password))
+
+      host = [:ldap, :host] |> Mobilizon.Config.get() |> to_charlist
+      port = Mobilizon.Config.get([:ldap, :port])
+
+      with_mocks [
+        {:eldap, [],
+         [
+           open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end,
+           simple_bind: fn _connection, _dn, _password -> {:error, :invalidCredentials} end,
+           close: fn _connection ->
+             send(self(), :close_connection)
+             :ok
+           end
+         ]}
+      ] do
+        res =
+          conn
+          |> AbsintheHelpers.graphql_query(
+            query: @login_mutation,
+            variables: %{email: user.email, password: user_password}
+          )
+
+        refute is_nil(res["errors"])
+
+        assert assert hd(res["errors"])["message"] ==
+                        "Impossible to authenticate, either your email or password are invalid."
+
+        assert_received :close_connection
+      end
+    end
+  end
+
+  describe "can change" do
+    test "password" do
+      assert LDAPAuthenticator.can_change_password?(%User{provider: "ldap"}) == false
+      assert LDAPAuthenticator.can_change_password?(%User{provider: nil}) == true
+    end
+
+    test "email" do
+      assert LDAPAuthenticator.can_change_password?(%User{provider: "ldap"}) == false
+      assert LDAPAuthenticator.can_change_password?(%User{provider: nil}) == true
+    end
+  end
+end
diff --git a/test/service/auth/mobilizon_authentificator_test.exs b/test/service/auth/mobilizon_authentificator_test.exs
new file mode 100644
index 000000000..4019f17b3
--- /dev/null
+++ b/test/service/auth/mobilizon_authentificator_test.exs
@@ -0,0 +1,29 @@
+defmodule Mobilizon.Service.Auth.MobilizonAuthenticatorTest do
+  use Mobilizon.DataCase
+
+  alias Mobilizon.Service.Auth.MobilizonAuthenticator
+  alias Mobilizon.Users.User
+  import Mobilizon.Factory
+
+  setup do
+    password = "testpassword"
+    email = "someone@somewhere.tld"
+    user = insert(:user, email: email, password_hash: Argon2.hash_pwd_salt(password))
+    {:ok, [user: user, email: email, password: password]}
+  end
+
+  test "login", %{email: email, password: password, user: user} do
+    assert {:ok, %User{} = returned_user} = MobilizonAuthenticator.login(email, password)
+    assert returned_user.id == user.id
+  end
+
+  test "login with invalid password", %{email: email} do
+    assert {:error, :bad_password} == MobilizonAuthenticator.login(email, "invalid")
+    assert {:error, :bad_password} == MobilizonAuthenticator.login(email, nil)
+  end
+
+  test "login with no credentials" do
+    assert {:error, :user_not_found} == MobilizonAuthenticator.login("some@email.com", nil)
+    assert {:error, :user_not_found} == MobilizonAuthenticator.login(nil, nil)
+  end
+end
diff --git a/test/support/factory.ex b/test/support/factory.ex
index 184b78813..220e7755d 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -18,7 +18,8 @@ defmodule Mobilizon.Factory do
       role: :user,
       confirmed_at: DateTime.utc_now() |> DateTime.truncate(:second),
       confirmation_sent_at: nil,
-      confirmation_token: nil
+      confirmation_token: nil,
+      provider: nil
     }
   end
 
diff --git a/test/support/helpers.ex b/test/support/helpers.ex
index a58f382d8..ffa59e4a4 100644
--- a/test/support/helpers.ex
+++ b/test/support/helpers.ex
@@ -7,6 +7,7 @@ defmodule Mobilizon.Tests.Helpers do
   @moduledoc """
   Helpers for use in tests.
   """
+  alias Mobilizon.Config
 
   defmacro clear_config(config_path) do
     quote do
@@ -17,11 +18,17 @@ defmodule Mobilizon.Tests.Helpers do
 
   defmacro clear_config(config_path, do: yield) do
     quote do
-      setup do
-        initial_setting = Mobilizon.Config.get(unquote(config_path))
-        unquote(yield)
-        on_exit(fn -> Mobilizon.Config.put(unquote(config_path), initial_setting) end)
-        :ok
+      initial_setting = Config.get(unquote(config_path))
+      unquote(yield)
+      on_exit(fn -> Config.put(unquote(config_path), initial_setting) end)
+      :ok
+    end
+  end
+
+  defmacro clear_config(config_path, temp_setting) do
+    quote do
+      clear_config(unquote(config_path)) do
+        Config.put(unquote(config_path), unquote(temp_setting))
       end
     end
   end
diff --git a/test/web/controllers/auth_controller_test.exs b/test/web/controllers/auth_controller_test.exs
new file mode 100644
index 000000000..ca2bad443
--- /dev/null
+++ b/test/web/controllers/auth_controller_test.exs
@@ -0,0 +1,54 @@
+defmodule Mobilizon.Web.AuthControllerTest do
+  use Mobilizon.Web.ConnCase
+  alias Mobilizon.Service.Auth.Authenticator
+  alias Mobilizon.Users.User
+
+  @email "someone@somewhere.tld"
+
+  test "login and registration",
+       %{conn: conn} do
+    conn =
+      conn
+      |> assign(:ueberauth_auth, %Ueberauth.Auth{
+        strategy: Ueberauth.Strategy.Twitter,
+        extra: %Ueberauth.Auth.Extra{raw_info: %{user: %{"email" => @email}}}
+      })
+      |> get("/auth/twitter/callback")
+
+    assert html_response(conn, 200) =~ "auth-access-token"
+
+    assert %User{confirmed_at: confirmed_at, email: @email} = Authenticator.fetch_user(@email)
+
+    refute is_nil(confirmed_at)
+  end
+
+  test "on bad provider error", %{
+    conn: conn
+  } do
+    conn =
+      conn
+      |> assign(:ueberauth_failure, %{errors: [%{message: "Some error"}]})
+      |> get("/auth/nothing")
+
+    assert "/login?code=Login Provider not found&provider=nothing" =
+             redirection = redirected_to(conn, 302)
+
+    conn = get(recycle(conn), redirection)
+    assert html_response(conn, 200)
+  end
+
+  test "on authentication error", %{
+    conn: conn
+  } do
+    conn =
+      conn
+      |> assign(:ueberauth_failure, %{errors: [%{message: "Some error"}]})
+      |> get("/auth/twitter/callback")
+
+    assert "/login?code=Error with Login Provider&provider=twitter" =
+             redirection = redirected_to(conn, 302)
+
+    conn = get(recycle(conn), redirection)
+    assert html_response(conn, 200)
+  end
+end

From daa6b59af836dd6145a018132e23a8642777707f Mon Sep 17 00:00:00 2001
From: Thomas Citharel <tcit@tcit.fr>
Date: Fri, 3 Jul 2020 18:44:45 +0200
Subject: [PATCH 2/4] Upgrade deps

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
---
 js/package.json |    2 +-
 js/yarn.lock    | 1326 ++++++++++++++++++++++++-----------------------
 mix.exs         |    2 +-
 mix.lock        |    5 +-
 4 files changed, 695 insertions(+), 640 deletions(-)

diff --git a/js/package.json b/js/package.json
index e4679b4fa..43d57fa04 100644
--- a/js/package.json
+++ b/js/package.json
@@ -90,7 +90,7 @@
     "prettier-eslint": "^10.1.1",
     "sass-loader": "^8.0.2",
     "typescript": "~3.9.3",
-    "vue-cli-plugin-styleguidist": "^4.25.0",
+    "vue-cli-plugin-styleguidist": "~4.26.0",
     "vue-cli-plugin-svg": "~0.1.3",
     "vue-i18n-extract": "^1.0.2",
     "vue-template-compiler": "^2.6.11",
diff --git a/js/yarn.lock b/js/yarn.lock
index 6b8fb3084..fb896df7a 100644
--- a/js/yarn.lock
+++ b/js/yarn.lock
@@ -41,35 +41,35 @@
   dependencies:
     "@babel/highlight" "^7.0.0"
 
-"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.3.tgz#324bcfd8d35cd3d47dae18cde63d752086435e9a"
-  integrity sha512-fDx9eNW0qz0WkUeqL6tXEXzVlPh6Y5aCDEZesl0xBGA8ndRukX91Uk44ZqnkECp01NAZUdCAl+aiQNGi0k88Eg==
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
+  integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==
   dependencies:
-    "@babel/highlight" "^7.10.3"
+    "@babel/highlight" "^7.10.4"
 
-"@babel/compat-data@^7.10.1", "@babel/compat-data@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.10.3.tgz#9af3e033f36e8e2d6e47570db91e64a846f5d382"
-  integrity sha512-BDIfJ9uNZuI0LajPfoYV28lX8kyCPMHY6uY4WH1lJdcicmAfxCK5ASzaeV0D/wsUaRH/cLk+amuxtC37sZ8TUg==
+"@babel/compat-data@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.10.4.tgz#706a6484ee6f910b719b696a9194f8da7d7ac241"
+  integrity sha512-t+rjExOrSVvjQQXNp5zAIYDp00KjdvGl/TpDX5REPr0S9IAIPQMTilcfG6q8c0QFmj9lSTVySV2VTsyggvtNIw==
   dependencies:
     browserslist "^4.12.0"
     invariant "^2.2.4"
     semver "^5.5.0"
 
 "@babel/core@^7.7.5", "@babel/core@^7.9.6":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.3.tgz#73b0e8ddeec1e3fdd7a2de587a60e17c440ec77e"
-  integrity sha512-5YqWxYE3pyhIi84L84YcwjeEgS+fa7ZjK6IBVGTjDVfm64njkR2lfDhVR5OudLk8x2GK59YoSyVv+L/03k1q9w==
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.4.tgz#780e8b83e496152f8dd7df63892b2e052bf1d51d"
+  integrity sha512-3A0tS0HWpy4XujGc7QtOIHTeNwUgWaZc/WuS5YQrfhU67jnVmsD6OGPc1AKHH0LJHQICGncy3+YUjIhVlfDdcA==
   dependencies:
-    "@babel/code-frame" "^7.10.3"
-    "@babel/generator" "^7.10.3"
-    "@babel/helper-module-transforms" "^7.10.1"
-    "@babel/helpers" "^7.10.1"
-    "@babel/parser" "^7.10.3"
-    "@babel/template" "^7.10.3"
-    "@babel/traverse" "^7.10.3"
-    "@babel/types" "^7.10.3"
+    "@babel/code-frame" "^7.10.4"
+    "@babel/generator" "^7.10.4"
+    "@babel/helper-module-transforms" "^7.10.4"
+    "@babel/helpers" "^7.10.4"
+    "@babel/parser" "^7.10.4"
+    "@babel/template" "^7.10.4"
+    "@babel/traverse" "^7.10.4"
+    "@babel/types" "^7.10.4"
     convert-source-map "^1.7.0"
     debug "^4.1.0"
     gensync "^1.0.0-beta.1"
@@ -90,321 +90,321 @@
     source-map "^0.5.0"
     trim-right "^1.0.1"
 
-"@babel/generator@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.3.tgz#32b9a0d963a71d7a54f5f6c15659c3dbc2a523a5"
-  integrity sha512-drt8MUHbEqRzNR0xnF8nMehbY11b1SDkRw03PSNH/3Rb2Z35oxkddVSi3rcaak0YJQ86PCuE7Qx1jSFhbLNBMA==
+"@babel/generator@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.4.tgz#e49eeed9fe114b62fa5b181856a43a5e32f5f243"
+  integrity sha512-toLIHUIAgcQygFZRAQcsLQV3CBuX6yOIru1kJk/qqqvcRmZrYe6WavZTSG+bB8MxhnL9YPf+pKQfuiP161q7ng==
   dependencies:
-    "@babel/types" "^7.10.3"
+    "@babel/types" "^7.10.4"
     jsesc "^2.5.1"
     lodash "^4.17.13"
     source-map "^0.5.0"
 
-"@babel/helper-annotate-as-pure@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.1.tgz#f6d08acc6f70bbd59b436262553fb2e259a1a268"
-  integrity sha512-ewp3rvJEwLaHgyWGe4wQssC2vjks3E80WiUe2BpMb0KhreTjMROCbxXcEovTrbeGVdQct5VjQfrv9EgC+xMzCw==
+"@babel/helper-annotate-as-pure@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3"
+  integrity sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==
   dependencies:
-    "@babel/types" "^7.10.1"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.1":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.3.tgz#4e9012d6701bef0030348d7f9c808209bd3e8687"
-  integrity sha512-lo4XXRnBlU6eRM92FkiZxpo1xFLmv3VsPFk61zJKMm7XYJfwqXHsYJTY6agoc4a3L8QPw1HqWehO18coZgbT6A==
+"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3"
+  integrity sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==
   dependencies:
-    "@babel/helper-explode-assignable-expression" "^7.10.3"
-    "@babel/types" "^7.10.3"
+    "@babel/helper-explode-assignable-expression" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-compilation-targets@^7.10.2", "@babel/helper-compilation-targets@^7.9.6":
-  version "7.10.2"
-  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.2.tgz#a17d9723b6e2c750299d2a14d4637c76936d8285"
-  integrity sha512-hYgOhF4To2UTB4LTaZepN/4Pl9LD4gfbJx8A34mqoluT8TLbof1mhUlYuNWTEebONa8+UlCC4X0TEXu7AOUyGA==
+"@babel/helper-compilation-targets@^7.10.4", "@babel/helper-compilation-targets@^7.9.6":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz#804ae8e3f04376607cc791b9d47d540276332bd2"
+  integrity sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==
   dependencies:
-    "@babel/compat-data" "^7.10.1"
+    "@babel/compat-data" "^7.10.4"
     browserslist "^4.12.0"
     invariant "^2.2.4"
     levenary "^1.1.1"
     semver "^5.5.0"
 
-"@babel/helper-create-class-features-plugin@^7.10.1", "@babel/helper-create-class-features-plugin@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.3.tgz#2783daa6866822e3d5ed119163b50f0fc3ae4b35"
-  integrity sha512-iRT9VwqtdFmv7UheJWthGc/h2s7MqoweBF9RUj77NFZsg9VfISvBTum3k6coAhJ8RWv2tj3yUjA03HxPd0vfpQ==
+"@babel/helper-create-class-features-plugin@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.4.tgz#2d4015d0136bd314103a70d84a7183e4b344a355"
+  integrity sha512-9raUiOsXPxzzLjCXeosApJItoMnX3uyT4QdM2UldffuGApNrF8e938MwNpDCK9CPoyxrEoCgT+hObJc3mZa6lQ==
   dependencies:
-    "@babel/helper-function-name" "^7.10.3"
-    "@babel/helper-member-expression-to-functions" "^7.10.3"
-    "@babel/helper-optimise-call-expression" "^7.10.3"
-    "@babel/helper-plugin-utils" "^7.10.3"
-    "@babel/helper-replace-supers" "^7.10.1"
-    "@babel/helper-split-export-declaration" "^7.10.1"
+    "@babel/helper-function-name" "^7.10.4"
+    "@babel/helper-member-expression-to-functions" "^7.10.4"
+    "@babel/helper-optimise-call-expression" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-replace-supers" "^7.10.4"
+    "@babel/helper-split-export-declaration" "^7.10.4"
 
-"@babel/helper-create-regexp-features-plugin@^7.10.1", "@babel/helper-create-regexp-features-plugin@^7.8.3":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.1.tgz#1b8feeab1594cbcfbf3ab5a3bbcabac0468efdbd"
-  integrity sha512-Rx4rHS0pVuJn5pJOqaqcZR4XSgeF9G/pO/79t+4r7380tXFJdzImFnxMU19f83wjSrmKHq6myrM10pFHTGzkUA==
+"@babel/helper-create-regexp-features-plugin@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz#fdd60d88524659a0b6959c0579925e425714f3b8"
+  integrity sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.10.1"
-    "@babel/helper-regex" "^7.10.1"
+    "@babel/helper-annotate-as-pure" "^7.10.4"
+    "@babel/helper-regex" "^7.10.4"
     regexpu-core "^4.7.0"
 
-"@babel/helper-define-map@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.3.tgz#d27120a5e57c84727b30944549b2dfeca62401a8"
-  integrity sha512-bxRzDi4Sin/k0drWCczppOhov1sBSdBvXJObM1NLHQzjhXhwRtn7aRWGvLJWCYbuu2qUk3EKs6Ci9C9ps8XokQ==
+"@babel/helper-define-map@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.4.tgz#f037ad794264f729eda1889f4ee210b870999092"
+  integrity sha512-nIij0oKErfCnLUCWaCaHW0Bmtl2RO9cN7+u2QT8yqTywgALKlyUVOvHDElh+b5DwVC6YB1FOYFOTWcN/+41EDA==
   dependencies:
-    "@babel/helper-function-name" "^7.10.3"
-    "@babel/types" "^7.10.3"
+    "@babel/helper-function-name" "^7.10.4"
+    "@babel/types" "^7.10.4"
     lodash "^4.17.13"
 
-"@babel/helper-explode-assignable-expression@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.3.tgz#9dc14f0cfa2833ea830a9c8a1c742b6e7461b05e"
-  integrity sha512-0nKcR64XrOC3lsl+uhD15cwxPvaB6QKUDlD84OT9C3myRbhJqTMYir69/RWItUvHpharv0eJ/wk7fl34ONSwZw==
+"@babel/helper-explode-assignable-expression@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.4.tgz#40a1cd917bff1288f699a94a75b37a1a2dbd8c7c"
+  integrity sha512-4K71RyRQNPRrR85sr5QY4X3VwG4wtVoXZB9+L3r1Gp38DhELyHCtovqydRi7c1Ovb17eRGiQ/FD5s8JdU0Uy5A==
   dependencies:
-    "@babel/traverse" "^7.10.3"
-    "@babel/types" "^7.10.3"
+    "@babel/traverse" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-function-name@^7.10.1", "@babel/helper-function-name@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.3.tgz#79316cd75a9fa25ba9787ff54544307ed444f197"
-  integrity sha512-FvSj2aiOd8zbeqijjgqdMDSyxsGHaMt5Tr0XjQsGKHD3/1FP3wksjnLAWzxw7lvXiej8W1Jt47SKTZ6upQNiRw==
+"@babel/helper-function-name@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a"
+  integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==
   dependencies:
-    "@babel/helper-get-function-arity" "^7.10.3"
-    "@babel/template" "^7.10.3"
-    "@babel/types" "^7.10.3"
+    "@babel/helper-get-function-arity" "^7.10.4"
+    "@babel/template" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-get-function-arity@^7.10.1", "@babel/helper-get-function-arity@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.3.tgz#3a28f7b28ccc7719eacd9223b659fdf162e4c45e"
-  integrity sha512-iUD/gFsR+M6uiy69JA6fzM5seno8oE85IYZdbVVEuQaZlEzMO2MXblh+KSPJgsZAUx0EEbWXU0yJaW7C9CdAVg==
+"@babel/helper-get-function-arity@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2"
+  integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==
   dependencies:
-    "@babel/types" "^7.10.3"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-hoist-variables@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.3.tgz#d554f52baf1657ffbd7e5137311abc993bb3f068"
-  integrity sha512-9JyafKoBt5h20Yv1+BXQMdcXXavozI1vt401KBiRc2qzUepbVnd7ogVNymY1xkQN9fekGwfxtotH2Yf5xsGzgg==
+"@babel/helper-hoist-variables@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz#d49b001d1d5a68ca5e6604dda01a6297f7c9381e"
+  integrity sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==
   dependencies:
-    "@babel/types" "^7.10.3"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-member-expression-to-functions@^7.10.1", "@babel/helper-member-expression-to-functions@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.3.tgz#bc3663ac81ac57c39148fef4c69bf48a77ba8dd6"
-  integrity sha512-q7+37c4EPLSjNb2NmWOjNwj0+BOyYlssuQ58kHEWk1Z78K5i8vTUsteq78HMieRPQSl/NtpQyJfdjt3qZ5V2vw==
+"@babel/helper-member-expression-to-functions@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.4.tgz#7cd04b57dfcf82fce9aeae7d4e4452fa31b8c7c4"
+  integrity sha512-m5j85pK/KZhuSdM/8cHUABQTAslV47OjfIB9Cc7P+PvlAoBzdb79BGNfw8RhT5Mq3p+xGd0ZfAKixbrUZx0C7A==
   dependencies:
-    "@babel/types" "^7.10.3"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.10.1", "@babel/helper-module-imports@^7.10.3", "@babel/helper-module-imports@^7.8.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.3.tgz#766fa1d57608e53e5676f23ae498ec7a95e1b11a"
-  integrity sha512-Jtqw5M9pahLSUWA+76nhK9OG8nwYXzhQzVIGFoNaHnXF/r4l7kz4Fl0UAW7B6mqC5myoJiBP5/YQlXQTMfHI9w==
+"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.8.3":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620"
+  integrity sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==
   dependencies:
-    "@babel/types" "^7.10.3"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-module-transforms@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.10.1.tgz#24e2f08ee6832c60b157bb0936c86bef7210c622"
-  integrity sha512-RLHRCAzyJe7Q7sF4oy2cB+kRnU4wDZY/H2xJFGof+M+SJEGhZsb+GFj5j1AD8NiSaVBJ+Pf0/WObiXu/zxWpFg==
+"@babel/helper-module-transforms@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.10.4.tgz#ca1f01fdb84e48c24d7506bb818c961f1da8805d"
+  integrity sha512-Er2FQX0oa3nV7eM1o0tNCTx7izmQtwAQsIiaLRWtavAAEcskb0XJ5OjJbVrYXWOTr8om921Scabn4/tzlx7j1Q==
   dependencies:
-    "@babel/helper-module-imports" "^7.10.1"
-    "@babel/helper-replace-supers" "^7.10.1"
-    "@babel/helper-simple-access" "^7.10.1"
-    "@babel/helper-split-export-declaration" "^7.10.1"
-    "@babel/template" "^7.10.1"
-    "@babel/types" "^7.10.1"
+    "@babel/helper-module-imports" "^7.10.4"
+    "@babel/helper-replace-supers" "^7.10.4"
+    "@babel/helper-simple-access" "^7.10.4"
+    "@babel/helper-split-export-declaration" "^7.10.4"
+    "@babel/template" "^7.10.4"
+    "@babel/types" "^7.10.4"
     lodash "^4.17.13"
 
-"@babel/helper-optimise-call-expression@^7.10.1", "@babel/helper-optimise-call-expression@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.3.tgz#f53c4b6783093195b0f69330439908841660c530"
-  integrity sha512-kT2R3VBH/cnSz+yChKpaKRJQJWxdGoc6SjioRId2wkeV3bK0wLLioFpJROrX0U4xr/NmxSSAWT/9Ih5snwIIzg==
+"@babel/helper-optimise-call-expression@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673"
+  integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==
   dependencies:
-    "@babel/types" "^7.10.3"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.1", "@babel/helper-plugin-utils@^7.10.3", "@babel/helper-plugin-utils@^7.8.0":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.3.tgz#aac45cccf8bc1873b99a85f34bceef3beb5d3244"
-  integrity sha512-j/+j8NAWUTxOtx4LKHybpSClxHoq6I91DQ/mKgAXn5oNUPIUiGppjPIX3TDtJWPrdfP9Kfl7e4fgVMiQR9VE/g==
+"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.8.0":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375"
+  integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==
 
-"@babel/helper-regex@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.1.tgz#021cf1a7ba99822f993222a001cc3fec83255b96"
-  integrity sha512-7isHr19RsIJWWLLFn21ubFt223PjQyg1HY7CZEMRr820HttHPpVvrsIN3bUOo44DEfFV4kBXO7Abbn9KTUZV7g==
+"@babel/helper-regex@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.4.tgz#59b373daaf3458e5747dece71bbaf45f9676af6d"
+  integrity sha512-inWpnHGgtg5NOF0eyHlC0/74/VkdRITY9dtTpB2PrxKKn+AkVMRiZz/Adrx+Ssg+MLDesi2zohBW6MVq6b4pOQ==
   dependencies:
     lodash "^4.17.13"
 
-"@babel/helper-remap-async-to-generator@^7.10.1", "@babel/helper-remap-async-to-generator@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.3.tgz#18564f8a6748be466970195b876e8bba3bccf442"
-  integrity sha512-sLB7666ARbJUGDO60ZormmhQOyqMX/shKBXZ7fy937s+3ID8gSrneMvKSSb+8xIM5V7Vn6uNVtOY1vIm26XLtA==
+"@babel/helper-remap-async-to-generator@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.4.tgz#fce8bea4e9690bbe923056ded21e54b4e8b68ed5"
+  integrity sha512-86Lsr6NNw3qTNl+TBcF1oRZMaVzJtbWTyTko+CQL/tvNvcGYEFKbLXDPxtW0HKk3McNOk4KzY55itGWCAGK5tg==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.10.1"
-    "@babel/helper-wrap-function" "^7.10.1"
-    "@babel/template" "^7.10.3"
-    "@babel/traverse" "^7.10.3"
-    "@babel/types" "^7.10.3"
+    "@babel/helper-annotate-as-pure" "^7.10.4"
+    "@babel/helper-wrap-function" "^7.10.4"
+    "@babel/template" "^7.10.4"
+    "@babel/traverse" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-replace-supers@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz#ec6859d20c5d8087f6a2dc4e014db7228975f13d"
-  integrity sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A==
+"@babel/helper-replace-supers@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz#d585cd9388ea06e6031e4cd44b6713cbead9e6cf"
+  integrity sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==
   dependencies:
-    "@babel/helper-member-expression-to-functions" "^7.10.1"
-    "@babel/helper-optimise-call-expression" "^7.10.1"
-    "@babel/traverse" "^7.10.1"
-    "@babel/types" "^7.10.1"
+    "@babel/helper-member-expression-to-functions" "^7.10.4"
+    "@babel/helper-optimise-call-expression" "^7.10.4"
+    "@babel/traverse" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-simple-access@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.1.tgz#08fb7e22ace9eb8326f7e3920a1c2052f13d851e"
-  integrity sha512-VSWpWzRzn9VtgMJBIWTZ+GP107kZdQ4YplJlCmIrjoLVSi/0upixezHCDG8kpPVTBJpKfxTH01wDhh+jS2zKbw==
+"@babel/helper-simple-access@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461"
+  integrity sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==
   dependencies:
-    "@babel/template" "^7.10.1"
-    "@babel/types" "^7.10.1"
+    "@babel/template" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-split-export-declaration@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz#c6f4be1cbc15e3a868e4c64a17d5d31d754da35f"
-  integrity sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==
+"@babel/helper-split-export-declaration@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz#2c70576eaa3b5609b24cb99db2888cc3fc4251d1"
+  integrity sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==
   dependencies:
-    "@babel/types" "^7.10.1"
+    "@babel/types" "^7.10.4"
 
-"@babel/helper-validator-identifier@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.3.tgz#60d9847f98c4cea1b279e005fdb7c28be5412d15"
-  integrity sha512-bU8JvtlYpJSBPuj1VUmKpFGaDZuLxASky3LhaKj3bmpSTY6VWooSM8msk+Z0CZoErFye2tlABF6yDkT3FOPAXw==
+"@babel/helper-validator-identifier@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2"
+  integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==
 
-"@babel/helper-wrap-function@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.10.1.tgz#956d1310d6696257a7afd47e4c42dfda5dfcedc9"
-  integrity sha512-C0MzRGteVDn+H32/ZgbAv5r56f2o1fZSA/rj/TYo8JEJNHg+9BdSmKBUND0shxWRztWhjlT2cvHYuynpPsVJwQ==
+"@babel/helper-wrap-function@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz#8a6f701eab0ff39f765b5a1cfef409990e624b87"
+  integrity sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==
   dependencies:
-    "@babel/helper-function-name" "^7.10.1"
-    "@babel/template" "^7.10.1"
-    "@babel/traverse" "^7.10.1"
-    "@babel/types" "^7.10.1"
+    "@babel/helper-function-name" "^7.10.4"
+    "@babel/template" "^7.10.4"
+    "@babel/traverse" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/helpers@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.1.tgz#a6827b7cb975c9d9cef5fd61d919f60d8844a973"
-  integrity sha512-muQNHF+IdU6wGgkaJyhhEmI54MOZBKsFfsXFhboz1ybwJ1Kl7IHlbm2a++4jwrmY5UYsgitt5lfqo1wMFcHmyw==
+"@babel/helpers@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.4.tgz#2abeb0d721aff7c0a97376b9e1f6f65d7a475044"
+  integrity sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==
   dependencies:
-    "@babel/template" "^7.10.1"
-    "@babel/traverse" "^7.10.1"
-    "@babel/types" "^7.10.1"
+    "@babel/template" "^7.10.4"
+    "@babel/traverse" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/highlight@^7.0.0", "@babel/highlight@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.3.tgz#c633bb34adf07c5c13156692f5922c81ec53f28d"
-  integrity sha512-Ih9B/u7AtgEnySE2L2F0Xm0GaM729XqqLfHkalTsbjXGyqmf/6M0Cu0WpvqueUlW+xk88BHw9Nkpj49naU+vWw==
+"@babel/highlight@^7.0.0", "@babel/highlight@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143"
+  integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==
   dependencies:
-    "@babel/helper-validator-identifier" "^7.10.3"
+    "@babel/helper-validator-identifier" "^7.10.4"
     chalk "^2.0.0"
     js-tokens "^4.0.0"
 
-"@babel/parser@^7.10.3", "@babel/parser@^7.6.0":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.3.tgz#7e71d892b0d6e7d04a1af4c3c79d72c1f10f5315"
-  integrity sha512-oJtNJCMFdIMwXGmx+KxuaD7i3b8uS7TTFYW/FNG2BT8m+fmGHoiPYoH0Pe3gya07WuFmM5FCDIr1x0irkD/hyA==
+"@babel/parser@^7.10.4", "@babel/parser@^7.6.0":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.4.tgz#9eedf27e1998d87739fb5028a5120557c06a1a64"
+  integrity sha512-8jHII4hf+YVDsskTF6WuMB3X4Eh+PsUkC2ljq22so5rHvH+T8BzyL94VOdyFLNR8tBSVXOTbNHOKpR4TfRxVtA==
 
-"@babel/plugin-proposal-async-generator-functions@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.3.tgz#5a02453d46e5362e2073c7278beab2e53ad7d939"
-  integrity sha512-WUUWM7YTOudF4jZBAJIW9D7aViYC/Fn0Pln4RIHlQALyno3sXSjqmTA4Zy1TKC2D49RCR8Y/Pn4OIUtEypK3CA==
+"@babel/plugin-proposal-async-generator-functions@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.4.tgz#4b65abb3d9bacc6c657aaa413e56696f9f170fc6"
+  integrity sha512-MJbxGSmejEFVOANAezdO39SObkURO5o/8b6fSH6D1pi9RZQt+ldppKPXfqgUWpSQ9asM6xaSaSJIaeWMDRP0Zg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.3"
-    "@babel/helper-remap-async-to-generator" "^7.10.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-remap-async-to-generator" "^7.10.4"
     "@babel/plugin-syntax-async-generators" "^7.8.0"
 
-"@babel/plugin-proposal-class-properties@^7.10.1", "@babel/plugin-proposal-class-properties@^7.8.3":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.1.tgz#046bc7f6550bb08d9bd1d4f060f5f5a4f1087e01"
-  integrity sha512-sqdGWgoXlnOdgMXU+9MbhzwFRgxVLeiGBqTrnuS7LC2IBU31wSsESbTUreT2O418obpfPdGUR2GbEufZF1bpqw==
+"@babel/plugin-proposal-class-properties@^7.10.4", "@babel/plugin-proposal-class-properties@^7.8.3":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz#a33bf632da390a59c7a8c570045d1115cd778807"
+  integrity sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==
   dependencies:
-    "@babel/helper-create-class-features-plugin" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-create-class-features-plugin" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
 "@babel/plugin-proposal-decorators@^7.8.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.10.3.tgz#2fc6b5696028adccfcd14bc826c184c578b857f8"
-  integrity sha512-Rzwn5tcYFTdWWK3IrhMZkMDjzFQLIGYqHvv9XuzNnEB91Y6gHr/JjazYV1Yec9g0yMLhy1p/21eiW1P7f5UN4A==
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.10.4.tgz#fe20ef10cc73f386f70910fca48798041cd357c7"
+  integrity sha512-JHTWjQngOPv+ZQQqOGv2x6sCCr4IYWy7S1/VH6BE9ZfkoLrdQ2GpEP3tfb5M++G9PwvqjhY8VC/C3tXm+/eHvA==
   dependencies:
-    "@babel/helper-create-class-features-plugin" "^7.10.3"
-    "@babel/helper-plugin-utils" "^7.10.3"
-    "@babel/plugin-syntax-decorators" "^7.10.1"
+    "@babel/helper-create-class-features-plugin" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/plugin-syntax-decorators" "^7.10.4"
 
-"@babel/plugin-proposal-dynamic-import@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.1.tgz#e36979dc1dc3b73f6d6816fc4951da2363488ef0"
-  integrity sha512-Cpc2yUVHTEGPlmiQzXj026kqwjEQAD9I4ZC16uzdbgWgitg/UHKHLffKNCQZ5+y8jpIZPJcKcwsr2HwPh+w3XA==
+"@babel/plugin-proposal-dynamic-import@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz#ba57a26cb98b37741e9d5bca1b8b0ddf8291f17e"
+  integrity sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
     "@babel/plugin-syntax-dynamic-import" "^7.8.0"
 
-"@babel/plugin-proposal-json-strings@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.1.tgz#b1e691ee24c651b5a5e32213222b2379734aff09"
-  integrity sha512-m8r5BmV+ZLpWPtMY2mOKN7wre6HIO4gfIiV+eOmsnZABNenrt/kzYBwrh+KOfgumSWpnlGs5F70J8afYMSJMBg==
+"@babel/plugin-proposal-json-strings@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz#593e59c63528160233bd321b1aebe0820c2341db"
+  integrity sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
     "@babel/plugin-syntax-json-strings" "^7.8.0"
 
-"@babel/plugin-proposal-nullish-coalescing-operator@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.1.tgz#02dca21673842ff2fe763ac253777f235e9bbf78"
-  integrity sha512-56cI/uHYgL2C8HVuHOuvVowihhX0sxb3nnfVRzUeVHTWmRHTZrKuAh/OBIMggGU/S1g/1D2CRCXqP+3u7vX7iA==
+"@babel/plugin-proposal-nullish-coalescing-operator@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz#02a7e961fc32e6d5b2db0649e01bf80ddee7e04a"
+  integrity sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
     "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0"
 
-"@babel/plugin-proposal-numeric-separator@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.1.tgz#a9a38bc34f78bdfd981e791c27c6fdcec478c123"
-  integrity sha512-jjfym4N9HtCiNfyyLAVD8WqPYeHUrw4ihxuAynWj6zzp2gf9Ey2f7ImhFm6ikB3CLf5Z/zmcJDri6B4+9j9RsA==
+"@babel/plugin-proposal-numeric-separator@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz#ce1590ff0a65ad12970a609d78855e9a4c1aef06"
+  integrity sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/plugin-syntax-numeric-separator" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/plugin-syntax-numeric-separator" "^7.10.4"
 
-"@babel/plugin-proposal-object-rest-spread@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.3.tgz#b8d0d22f70afa34ad84b7a200ff772f9b9fce474"
-  integrity sha512-ZZh5leCIlH9lni5bU/wB/UcjtcVLgR8gc+FAgW2OOY+m9h1II3ItTO1/cewNUcsIDZSYcSaz/rYVls+Fb0ExVQ==
+"@babel/plugin-proposal-object-rest-spread@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.4.tgz#50129ac216b9a6a55b3853fdd923e74bf553a4c0"
+  integrity sha512-6vh4SqRuLLarjgeOf4EaROJAHjvu9Gl+/346PbDH9yWbJyfnJ/ah3jmYKYtswEyCoWZiidvVHjHshd4WgjB9BA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
     "@babel/plugin-syntax-object-rest-spread" "^7.8.0"
-    "@babel/plugin-transform-parameters" "^7.10.1"
+    "@babel/plugin-transform-parameters" "^7.10.4"
 
-"@babel/plugin-proposal-optional-catch-binding@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.1.tgz#c9f86d99305f9fa531b568ff5ab8c964b8b223d2"
-  integrity sha512-VqExgeE62YBqI3ogkGoOJp1R6u12DFZjqwJhqtKc2o5m1YTUuUWnos7bZQFBhwkxIFpWYJ7uB75U7VAPPiKETA==
+"@babel/plugin-proposal-optional-catch-binding@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz#31c938309d24a78a49d68fdabffaa863758554dd"
+  integrity sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
     "@babel/plugin-syntax-optional-catch-binding" "^7.8.0"
 
-"@babel/plugin-proposal-optional-chaining@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.3.tgz#9a726f94622b653c0a3a7a59cdce94730f526f7c"
-  integrity sha512-yyG3n9dJ1vZ6v5sfmIlMMZ8azQoqx/5/nZTSWX1td6L1H1bsjzA8TInDChpafCZiJkeOFzp/PtrfigAQXxI1Ng==
+"@babel/plugin-proposal-optional-chaining@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.4.tgz#750f1255e930a1f82d8cdde45031f81a0d0adff7"
+  integrity sha512-ZIhQIEeavTgouyMSdZRap4VPPHqJJ3NEs2cuHs5p0erH+iz6khB0qfgU8g7UuJkG88+fBMy23ZiU+nuHvekJeQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
     "@babel/plugin-syntax-optional-chaining" "^7.8.0"
 
-"@babel/plugin-proposal-private-methods@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.1.tgz#ed85e8058ab0fe309c3f448e5e1b73ca89cdb598"
-  integrity sha512-RZecFFJjDiQ2z6maFprLgrdnm0OzoC23Mx89xf1CcEsxmHuzuXOdniEuI+S3v7vjQG4F5sa6YtUp+19sZuSxHg==
+"@babel/plugin-proposal-private-methods@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz#b160d972b8fdba5c7d111a145fc8c421fc2a6909"
+  integrity sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw==
   dependencies:
-    "@babel/helper-create-class-features-plugin" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-create-class-features-plugin" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-proposal-unicode-property-regex@^7.10.1", "@babel/plugin-proposal-unicode-property-regex@^7.4.4":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.1.tgz#dc04feb25e2dd70c12b05d680190e138fa2c0c6f"
-  integrity sha512-JjfngYRvwmPwmnbRZyNiPFI8zxCZb8euzbCG/LxyKdeTb59tVciKo9GK9bi6JYKInk1H11Dq9j/zRqIH4KigfQ==
+"@babel/plugin-proposal-unicode-property-regex@^7.10.4", "@babel/plugin-proposal-unicode-property-regex@^7.4.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz#4483cda53041ce3413b7fe2f00022665ddfaa75d"
+  integrity sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-create-regexp-features-plugin" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
 "@babel/plugin-syntax-async-generators@^7.8.0":
   version "7.8.4"
@@ -413,19 +413,19 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-syntax-class-properties@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.1.tgz#d5bc0645913df5b17ad7eda0fa2308330bde34c5"
-  integrity sha512-Gf2Yx/iRs1JREDtVZ56OrjjgFHCaldpTnuy9BHla10qyVT3YkIIGEtoDWhyop0ksu1GvNjHIoYRBqm3zoR1jyQ==
+"@babel/plugin-syntax-class-properties@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz#6644e6a0baa55a61f9e3231f6c9eeb6ee46c124c"
+  integrity sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-syntax-decorators@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.10.1.tgz#16b869c4beafc9a442565147bda7ce0967bd4f13"
-  integrity sha512-a9OAbQhKOwSle1Vr0NJu/ISg1sPfdEkfRKWpgPuzhnWWzForou2gIeUIIwjAMHRekhhpJ7eulZlYs0H14Cbi+g==
+"@babel/plugin-syntax-decorators@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.10.4.tgz#6853085b2c429f9d322d02f5a635018cdeb2360c"
+  integrity sha512-2NaoC6fAk2VMdhY1eerkfHV+lVYC1u8b+jmRJISqANCJlTxYy19HGdIkkQtix2UtkcPuPu+IlDgrVseZnU03bw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
 "@babel/plugin-syntax-dynamic-import@^7.8.0", "@babel/plugin-syntax-dynamic-import@^7.8.3":
   version "7.8.3"
@@ -442,11 +442,11 @@
     "@babel/helper-plugin-utils" "^7.8.0"
 
 "@babel/plugin-syntax-jsx@^7.2.0", "@babel/plugin-syntax-jsx@^7.8.3":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.1.tgz#0ae371134a42b91d5418feb3c8c8d43e1565d2da"
-  integrity sha512-+OxyOArpVFXQeXKLO9o+r2I4dIoVoy6+Uu0vKELrlweDM3QJADZj+Z+5ERansZqIZBcLj42vHnDI8Rz9BnRIuQ==
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.4.tgz#39abaae3cbf710c4373d8429484e6ba21340166c"
+  integrity sha512-KCg9mio9jwiARCB7WAcQ7Y1q+qicILjoK8LP/VkPkEKaf5dkaZZK1EcTe91a3JJlZ3qy6L5s9X52boEYi8DM9g==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
 "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0":
   version "7.8.3"
@@ -455,12 +455,12 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-syntax-numeric-separator@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.1.tgz#25761ee7410bc8cf97327ba741ee94e4a61b7d99"
-  integrity sha512-uTd0OsHrpe3tH5gRPTxG8Voh99/WCU78vIm5NMRYPAqC8lR4vajt6KkCAknCHrx24vkPdd/05yfdGSB4EIY2mg==
+"@babel/plugin-syntax-numeric-separator@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97"
+  integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
 "@babel/plugin-syntax-object-rest-spread@^7.8.0":
   version "7.8.3"
@@ -483,338 +483,338 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-syntax-top-level-await@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.1.tgz#8b8733f8c57397b3eaa47ddba8841586dcaef362"
-  integrity sha512-hgA5RYkmZm8FTFT3yu2N9Bx7yVVOKYT6yEdXXo6j2JTm0wNxgqaGeQVaSHRjhfnQbX91DtjFB6McRFSlcJH3xQ==
+"@babel/plugin-syntax-top-level-await@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz#4bbeb8917b54fcf768364e0a81f560e33a3ef57d"
+  integrity sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-arrow-functions@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.1.tgz#cb5ee3a36f0863c06ead0b409b4cc43a889b295b"
-  integrity sha512-6AZHgFJKP3DJX0eCNJj01RpytUa3SOGawIxweHkNX2L6PYikOZmoh5B0d7hIHaIgveMjX990IAa/xK7jRTN8OA==
+"@babel/plugin-transform-arrow-functions@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz#e22960d77e697c74f41c501d44d73dbf8a6a64cd"
+  integrity sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-async-to-generator@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.1.tgz#e5153eb1a3e028f79194ed8a7a4bf55f862b2062"
-  integrity sha512-XCgYjJ8TY2slj6SReBUyamJn3k2JLUIiiR5b6t1mNCMSvv7yx+jJpaewakikp0uWFQSF7ChPPoe3dHmXLpISkg==
+"@babel/plugin-transform-async-to-generator@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz#41a5017e49eb6f3cda9392a51eef29405b245a37"
+  integrity sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==
   dependencies:
-    "@babel/helper-module-imports" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/helper-remap-async-to-generator" "^7.10.1"
+    "@babel/helper-module-imports" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-remap-async-to-generator" "^7.10.4"
 
-"@babel/plugin-transform-block-scoped-functions@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.1.tgz#146856e756d54b20fff14b819456b3e01820b85d"
-  integrity sha512-B7K15Xp8lv0sOJrdVAoukKlxP9N59HS48V1J3U/JGj+Ad+MHq+am6xJVs85AgXrQn4LV8vaYFOB+pr/yIuzW8Q==
+"@babel/plugin-transform-block-scoped-functions@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz#1afa595744f75e43a91af73b0d998ecfe4ebc2e8"
+  integrity sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-block-scoping@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.1.tgz#47092d89ca345811451cd0dc5d91605982705d5e"
-  integrity sha512-8bpWG6TtF5akdhIm/uWTyjHqENpy13Fx8chg7pFH875aNLwX8JxIxqm08gmAT+Whe6AOmaTeLPe7dpLbXt+xUw==
+"@babel/plugin-transform-block-scoping@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.4.tgz#a670d1364bb5019a621b9ea2001482876d734787"
+  integrity sha512-J3b5CluMg3hPUii2onJDRiaVbPtKFPLEaV5dOPY5OeAbDi1iU/UbbFFTgwb7WnanaDy7bjU35kc26W3eM5Qa0A==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
     lodash "^4.17.13"
 
-"@babel/plugin-transform-classes@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.3.tgz#8d9a656bc3d01f3ff69e1fccb354b0f9d72ac544"
-  integrity sha512-irEX0ChJLaZVC7FvvRoSIxJlmk0IczFLcwaRXUArBKYHCHbOhe57aG8q3uw/fJsoSXvZhjRX960hyeAGlVBXZw==
+"@babel/plugin-transform-classes@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz#405136af2b3e218bc4a1926228bc917ab1a0adc7"
+  integrity sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.10.1"
-    "@babel/helper-define-map" "^7.10.3"
-    "@babel/helper-function-name" "^7.10.3"
-    "@babel/helper-optimise-call-expression" "^7.10.3"
-    "@babel/helper-plugin-utils" "^7.10.3"
-    "@babel/helper-replace-supers" "^7.10.1"
-    "@babel/helper-split-export-declaration" "^7.10.1"
+    "@babel/helper-annotate-as-pure" "^7.10.4"
+    "@babel/helper-define-map" "^7.10.4"
+    "@babel/helper-function-name" "^7.10.4"
+    "@babel/helper-optimise-call-expression" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-replace-supers" "^7.10.4"
+    "@babel/helper-split-export-declaration" "^7.10.4"
     globals "^11.1.0"
 
-"@babel/plugin-transform-computed-properties@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.3.tgz#d3aa6eef67cb967150f76faff20f0abbf553757b"
-  integrity sha512-GWzhaBOsdbjVFav96drOz7FzrcEW6AP5nax0gLIpstiFaI3LOb2tAg06TimaWU6YKOfUACK3FVrxPJ4GSc5TgA==
+"@babel/plugin-transform-computed-properties@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz#9ded83a816e82ded28d52d4b4ecbdd810cdfc0eb"
+  integrity sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.3"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-destructuring@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.1.tgz#abd58e51337815ca3a22a336b85f62b998e71907"
-  integrity sha512-V/nUc4yGWG71OhaTH705pU8ZSdM6c1KmmLP8ys59oOYbT7RpMYAR3MsVOt6OHL0WzG7BlTU076va9fjJyYzJMA==
+"@babel/plugin-transform-destructuring@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz#70ddd2b3d1bea83d01509e9bb25ddb3a74fc85e5"
+  integrity sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-dotall-regex@^7.10.1", "@babel/plugin-transform-dotall-regex@^7.4.4":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.1.tgz#920b9fec2d78bb57ebb64a644d5c2ba67cc104ee"
-  integrity sha512-19VIMsD1dp02RvduFUmfzj8uknaO3uiHHF0s3E1OHnVsNj8oge8EQ5RzHRbJjGSetRnkEuBYO7TG1M5kKjGLOA==
+"@babel/plugin-transform-dotall-regex@^7.10.4", "@babel/plugin-transform-dotall-regex@^7.4.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz#469c2062105c1eb6a040eaf4fac4b488078395ee"
+  integrity sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-create-regexp-features-plugin" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-duplicate-keys@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.1.tgz#c900a793beb096bc9d4d0a9d0cde19518ffc83b9"
-  integrity sha512-wIEpkX4QvX8Mo9W6XF3EdGttrIPZWozHfEaDTU0WJD/TDnXMvdDh30mzUl/9qWhnf7naicYartcEfUghTCSNpA==
+"@babel/plugin-transform-duplicate-keys@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz#697e50c9fee14380fe843d1f306b295617431e47"
+  integrity sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-exponentiation-operator@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.1.tgz#279c3116756a60dd6e6f5e488ba7957db9c59eb3"
-  integrity sha512-lr/przdAbpEA2BUzRvjXdEDLrArGRRPwbaF9rvayuHRvdQ7lUTTkZnhZrJ4LE2jvgMRFF4f0YuPQ20vhiPYxtA==
+"@babel/plugin-transform-exponentiation-operator@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz#5ae338c57f8cf4001bdb35607ae66b92d665af2e"
+  integrity sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==
   dependencies:
-    "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-for-of@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.1.tgz#ff01119784eb0ee32258e8646157ba2501fcfda5"
-  integrity sha512-US8KCuxfQcn0LwSCMWMma8M2R5mAjJGsmoCBVwlMygvmDUMkTCykc84IqN1M7t+agSfOmLYTInLCHJM+RUoz+w==
+"@babel/plugin-transform-for-of@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz#c08892e8819d3a5db29031b115af511dbbfebae9"
+  integrity sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-function-name@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.1.tgz#4ed46fd6e1d8fde2a2ec7b03c66d853d2c92427d"
-  integrity sha512-//bsKsKFBJfGd65qSNNh1exBy5Y9gD9ZN+DvrJ8f7HXr4avE5POW6zB7Rj6VnqHV33+0vXWUwJT0wSHubiAQkw==
+"@babel/plugin-transform-function-name@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz#6a467880e0fc9638514ba369111811ddbe2644b7"
+  integrity sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==
   dependencies:
-    "@babel/helper-function-name" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-function-name" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-literals@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.1.tgz#5794f8da82846b22e4e6631ea1658bce708eb46a"
-  integrity sha512-qi0+5qgevz1NHLZroObRm5A+8JJtibb7vdcPQF1KQE12+Y/xxl8coJ+TpPW9iRq+Mhw/NKLjm+5SHtAHCC7lAw==
+"@babel/plugin-transform-literals@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz#9f42ba0841100a135f22712d0e391c462f571f3c"
+  integrity sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-member-expression-literals@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.1.tgz#90347cba31bca6f394b3f7bd95d2bbfd9fce2f39"
-  integrity sha512-UmaWhDokOFT2GcgU6MkHC11i0NQcL63iqeufXWfRy6pUOGYeCGEKhvfFO6Vz70UfYJYHwveg62GS83Rvpxn+NA==
+"@babel/plugin-transform-member-expression-literals@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz#b1ec44fcf195afcb8db2c62cd8e551c881baf8b7"
+  integrity sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-modules-amd@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.1.tgz#65950e8e05797ebd2fe532b96e19fc5482a1d52a"
-  integrity sha512-31+hnWSFRI4/ACFr1qkboBbrTxoBIzj7qA69qlq8HY8p7+YCzkCT6/TvQ1a4B0z27VeWtAeJd6pr5G04dc1iHw==
+"@babel/plugin-transform-modules-amd@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.4.tgz#cb407c68b862e4c1d13a2fc738c7ec5ed75fc520"
+  integrity sha512-3Fw+H3WLUrTlzi3zMiZWp3AR4xadAEMv6XRCYnd5jAlLM61Rn+CRJaZMaNvIpcJpQ3vs1kyifYvEVPFfoSkKOA==
   dependencies:
-    "@babel/helper-module-transforms" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-module-transforms" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
     babel-plugin-dynamic-import-node "^2.3.3"
 
-"@babel/plugin-transform-modules-commonjs@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.1.tgz#d5ff4b4413ed97ffded99961056e1fb980fb9301"
-  integrity sha512-AQG4fc3KOah0vdITwt7Gi6hD9BtQP/8bhem7OjbaMoRNCH5Djx42O2vYMfau7QnAzQCa+RJnhJBmFFMGpQEzrg==
+"@babel/plugin-transform-modules-commonjs@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz#66667c3eeda1ebf7896d41f1f16b17105a2fbca0"
+  integrity sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==
   dependencies:
-    "@babel/helper-module-transforms" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/helper-simple-access" "^7.10.1"
+    "@babel/helper-module-transforms" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-simple-access" "^7.10.4"
     babel-plugin-dynamic-import-node "^2.3.3"
 
-"@babel/plugin-transform-modules-systemjs@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.3.tgz#004ae727b122b7b146b150d50cba5ffbff4ac56b"
-  integrity sha512-GWXWQMmE1GH4ALc7YXW56BTh/AlzvDWhUNn9ArFF0+Cz5G8esYlVbXfdyHa1xaD1j+GnBoCeoQNlwtZTVdiG/A==
+"@babel/plugin-transform-modules-systemjs@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.4.tgz#8f576afd943ac2f789b35ded0a6312f929c633f9"
+  integrity sha512-Tb28LlfxrTiOTGtZFsvkjpyjCl9IoaRI52AEU/VIwOwvDQWtbNJsAqTXzh+5R7i74e/OZHH2c2w2fsOqAfnQYQ==
   dependencies:
-    "@babel/helper-hoist-variables" "^7.10.3"
-    "@babel/helper-module-transforms" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.3"
+    "@babel/helper-hoist-variables" "^7.10.4"
+    "@babel/helper-module-transforms" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
     babel-plugin-dynamic-import-node "^2.3.3"
 
-"@babel/plugin-transform-modules-umd@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.1.tgz#ea080911ffc6eb21840a5197a39ede4ee67b1595"
-  integrity sha512-EIuiRNMd6GB6ulcYlETnYYfgv4AxqrswghmBRQbWLHZxN4s7mupxzglnHqk9ZiUpDI4eRWewedJJNj67PWOXKA==
+"@babel/plugin-transform-modules-umd@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz#9a8481fe81b824654b3a0b65da3df89f3d21839e"
+  integrity sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==
   dependencies:
-    "@babel/helper-module-transforms" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-module-transforms" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-named-capturing-groups-regex@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.3.tgz#a4f8444d1c5a46f35834a410285f2c901c007ca6"
-  integrity sha512-I3EH+RMFyVi8Iy/LekQm948Z4Lz4yKT7rK+vuCAeRm0kTa6Z5W7xuhRxDNJv0FPya/her6AUgrDITb70YHtTvA==
+"@babel/plugin-transform-named-capturing-groups-regex@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz#78b4d978810b6f3bcf03f9e318f2fc0ed41aecb6"
+  integrity sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.8.3"
+    "@babel/helper-create-regexp-features-plugin" "^7.10.4"
 
-"@babel/plugin-transform-new-target@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.1.tgz#6ee41a5e648da7632e22b6fb54012e87f612f324"
-  integrity sha512-MBlzPc1nJvbmO9rPr1fQwXOM2iGut+JC92ku6PbiJMMK7SnQc1rytgpopveE3Evn47gzvGYeCdgfCDbZo0ecUw==
+"@babel/plugin-transform-new-target@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz#9097d753cb7b024cb7381a3b2e52e9513a9c6888"
+  integrity sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-object-super@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.1.tgz#2e3016b0adbf262983bf0d5121d676a5ed9c4fde"
-  integrity sha512-WnnStUDN5GL+wGQrJylrnnVlFhFmeArINIR9gjhSeYyvroGhBrSAXYg/RHsnfzmsa+onJrTJrEClPzgNmmQ4Gw==
+"@babel/plugin-transform-object-super@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz#d7146c4d139433e7a6526f888c667e314a093894"
+  integrity sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/helper-replace-supers" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-replace-supers" "^7.10.4"
 
-"@babel/plugin-transform-parameters@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.1.tgz#b25938a3c5fae0354144a720b07b32766f683ddd"
-  integrity sha512-tJ1T0n6g4dXMsL45YsSzzSDZCxiHXAQp/qHrucOq5gEHncTA3xDxnd5+sZcoQp+N1ZbieAaB8r/VUCG0gqseOg==
+"@babel/plugin-transform-parameters@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.4.tgz#7b4d137c87ea7adc2a0f3ebf53266871daa6fced"
+  integrity sha512-RurVtZ/D5nYfEg0iVERXYKEgDFeesHrHfx8RT05Sq57ucj2eOYAP6eu5fynL4Adju4I/mP/I6SO0DqNWAXjfLQ==
   dependencies:
-    "@babel/helper-get-function-arity" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-get-function-arity" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-property-literals@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.1.tgz#cffc7315219230ed81dc53e4625bf86815b6050d"
-  integrity sha512-Kr6+mgag8auNrgEpbfIWzdXYOvqDHZOF0+Bx2xh4H2EDNwcbRb9lY6nkZg8oSjsX+DH9Ebxm9hOqtKW+gRDeNA==
+"@babel/plugin-transform-property-literals@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz#f6fe54b6590352298785b83edd815d214c42e3c0"
+  integrity sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-regenerator@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.3.tgz#6ec680f140a5ceefd291c221cb7131f6d7e8cb6d"
-  integrity sha512-H5kNeW0u8mbk0qa1jVIVTeJJL6/TJ81ltD4oyPx0P499DhMJrTmmIFCmJ3QloGpQG8K9symccB7S7SJpCKLwtw==
+"@babel/plugin-transform-regenerator@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz#2015e59d839074e76838de2159db421966fd8b63"
+  integrity sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==
   dependencies:
     regenerator-transform "^0.14.2"
 
-"@babel/plugin-transform-reserved-words@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.1.tgz#0fc1027312b4d1c3276a57890c8ae3bcc0b64a86"
-  integrity sha512-qN1OMoE2nuqSPmpTqEM7OvJ1FkMEV+BjVeZZm9V9mq/x1JLKQ4pcv8riZJMNN3u2AUGl0ouOMjRr2siecvHqUQ==
+"@babel/plugin-transform-reserved-words@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz#8f2682bcdcef9ed327e1b0861585d7013f8a54dd"
+  integrity sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
 "@babel/plugin-transform-runtime@^7.9.6":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.3.tgz#3b287b06acc534a7cb6e6c71d6b1d88b1922dd6c"
-  integrity sha512-b5OzMD1Hi8BBzgQdRHyVVaYrk9zG0wset1it2o3BgonkPadXfOv0aXRqd7864DeOIu3FGKP/h6lr15FE5mahVw==
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.4.tgz#594fb53453ea1b6f0779cceb48ce0718a447feb7"
+  integrity sha512-8ULlGv8p+Vuxu+kz2Y1dk6MYS2b/Dki+NO6/0ZlfSj5tMalfDL7jI/o/2a+rrWLqSXvnadEqc2WguB4gdQIxZw==
   dependencies:
-    "@babel/helper-module-imports" "^7.10.3"
-    "@babel/helper-plugin-utils" "^7.10.3"
+    "@babel/helper-module-imports" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
     resolve "^1.8.1"
     semver "^5.5.1"
 
-"@babel/plugin-transform-shorthand-properties@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.1.tgz#e8b54f238a1ccbae482c4dce946180ae7b3143f3"
-  integrity sha512-AR0E/lZMfLstScFwztApGeyTHJ5u3JUKMjneqRItWeEqDdHWZwAOKycvQNCasCK/3r5YXsuNG25funcJDu7Y2g==
+"@babel/plugin-transform-shorthand-properties@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz#9fd25ec5cdd555bb7f473e5e6ee1c971eede4dd6"
+  integrity sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-spread@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.1.tgz#0c6d618a0c4461a274418460a28c9ccf5239a7c8"
-  integrity sha512-8wTPym6edIrClW8FI2IoaePB91ETOtg36dOkj3bYcNe7aDMN2FXEoUa+WrmPc4xa1u2PQK46fUX2aCb+zo9rfw==
+"@babel/plugin-transform-spread@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.4.tgz#4e2c85ea0d6abaee1b24dcfbbae426fe8d674cff"
+  integrity sha512-1e/51G/Ni+7uH5gktbWv+eCED9pP8ZpRhZB3jOaI3mmzfvJTWHkuyYTv0Z5PYtyM+Tr2Ccr9kUdQxn60fI5WuQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-sticky-regex@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.1.tgz#90fc89b7526228bed9842cff3588270a7a393b00"
-  integrity sha512-j17ojftKjrL7ufX8ajKvwRilwqTok4q+BjkknmQw9VNHnItTyMP5anPFzxFJdCQs7clLcWpCV3ma+6qZWLnGMA==
+"@babel/plugin-transform-sticky-regex@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz#8f3889ee8657581130a29d9cc91d7c73b7c4a28d"
+  integrity sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/helper-regex" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/helper-regex" "^7.10.4"
 
-"@babel/plugin-transform-template-literals@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.3.tgz#69d39b3d44b31e7b4864173322565894ce939b25"
-  integrity sha512-yaBn9OpxQra/bk0/CaA4wr41O0/Whkg6nqjqApcinxM7pro51ojhX6fv1pimAnVjVfDy14K0ULoRL70CA9jWWA==
+"@babel/plugin-transform-template-literals@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.4.tgz#e6375407b30fcb7fcfdbba3bb98ef3e9d36df7bc"
+  integrity sha512-4NErciJkAYe+xI5cqfS8pV/0ntlY5N5Ske/4ImxAVX7mk9Rxt2bwDTGv1Msc2BRJvWQcmYEC+yoMLdX22aE4VQ==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.3"
+    "@babel/helper-annotate-as-pure" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-typeof-symbol@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.1.tgz#60c0239b69965d166b80a84de7315c1bc7e0bb0e"
-  integrity sha512-qX8KZcmbvA23zDi+lk9s6hC1FM7jgLHYIjuLgULgc8QtYnmB3tAVIYkNoKRQ75qWBeyzcoMoK8ZQmogGtC/w0g==
+"@babel/plugin-transform-typeof-symbol@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz#9509f1a7eec31c4edbffe137c16cc33ff0bc5bfc"
+  integrity sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-unicode-escapes@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.1.tgz#add0f8483dab60570d9e03cecef6c023aa8c9940"
-  integrity sha512-zZ0Poh/yy1d4jeDWpx/mNwbKJVwUYJX73q+gyh4bwtG0/iUlzdEu0sLMda8yuDFS6LBQlT/ST1SJAR6zYwXWgw==
+"@babel/plugin-transform-unicode-escapes@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz#feae523391c7651ddac115dae0a9d06857892007"
+  integrity sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-unicode-regex@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.1.tgz#6b58f2aea7b68df37ac5025d9c88752443a6b43f"
-  integrity sha512-Y/2a2W299k0VIUdbqYm9X2qS6fE0CUBhhiPpimK6byy7OJ/kORLlIX+J6UrjgNu5awvs62k+6RSslxhcvVw2Tw==
+"@babel/plugin-transform-unicode-regex@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz#e56d71f9282fac6db09c82742055576d5e6d80a8"
+  integrity sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-create-regexp-features-plugin" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
 "@babel/preset-env@^7.9.6":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.3.tgz#3e58c9861bbd93b6a679987c7e4bd365c56c80c9"
-  integrity sha512-jHaSUgiewTmly88bJtMHbOd1bJf2ocYxb5BWKSDQIP5tmgFuS/n0gl+nhSrYDhT33m0vPxp+rP8oYYgPgMNQlg==
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.4.tgz#fbf57f9a803afd97f4f32e4f798bb62e4b2bef5f"
+  integrity sha512-tcmuQ6vupfMZPrLrc38d0sF2OjLT3/bZ0dry5HchNCQbrokoQi4reXqclvkkAT5b+gWc23meVWpve5P/7+w/zw==
   dependencies:
-    "@babel/compat-data" "^7.10.3"
-    "@babel/helper-compilation-targets" "^7.10.2"
-    "@babel/helper-module-imports" "^7.10.3"
-    "@babel/helper-plugin-utils" "^7.10.3"
-    "@babel/plugin-proposal-async-generator-functions" "^7.10.3"
-    "@babel/plugin-proposal-class-properties" "^7.10.1"
-    "@babel/plugin-proposal-dynamic-import" "^7.10.1"
-    "@babel/plugin-proposal-json-strings" "^7.10.1"
-    "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.1"
-    "@babel/plugin-proposal-numeric-separator" "^7.10.1"
-    "@babel/plugin-proposal-object-rest-spread" "^7.10.3"
-    "@babel/plugin-proposal-optional-catch-binding" "^7.10.1"
-    "@babel/plugin-proposal-optional-chaining" "^7.10.3"
-    "@babel/plugin-proposal-private-methods" "^7.10.1"
-    "@babel/plugin-proposal-unicode-property-regex" "^7.10.1"
+    "@babel/compat-data" "^7.10.4"
+    "@babel/helper-compilation-targets" "^7.10.4"
+    "@babel/helper-module-imports" "^7.10.4"
+    "@babel/helper-plugin-utils" "^7.10.4"
+    "@babel/plugin-proposal-async-generator-functions" "^7.10.4"
+    "@babel/plugin-proposal-class-properties" "^7.10.4"
+    "@babel/plugin-proposal-dynamic-import" "^7.10.4"
+    "@babel/plugin-proposal-json-strings" "^7.10.4"
+    "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.4"
+    "@babel/plugin-proposal-numeric-separator" "^7.10.4"
+    "@babel/plugin-proposal-object-rest-spread" "^7.10.4"
+    "@babel/plugin-proposal-optional-catch-binding" "^7.10.4"
+    "@babel/plugin-proposal-optional-chaining" "^7.10.4"
+    "@babel/plugin-proposal-private-methods" "^7.10.4"
+    "@babel/plugin-proposal-unicode-property-regex" "^7.10.4"
     "@babel/plugin-syntax-async-generators" "^7.8.0"
-    "@babel/plugin-syntax-class-properties" "^7.10.1"
+    "@babel/plugin-syntax-class-properties" "^7.10.4"
     "@babel/plugin-syntax-dynamic-import" "^7.8.0"
     "@babel/plugin-syntax-json-strings" "^7.8.0"
     "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0"
-    "@babel/plugin-syntax-numeric-separator" "^7.10.1"
+    "@babel/plugin-syntax-numeric-separator" "^7.10.4"
     "@babel/plugin-syntax-object-rest-spread" "^7.8.0"
     "@babel/plugin-syntax-optional-catch-binding" "^7.8.0"
     "@babel/plugin-syntax-optional-chaining" "^7.8.0"
-    "@babel/plugin-syntax-top-level-await" "^7.10.1"
-    "@babel/plugin-transform-arrow-functions" "^7.10.1"
-    "@babel/plugin-transform-async-to-generator" "^7.10.1"
-    "@babel/plugin-transform-block-scoped-functions" "^7.10.1"
-    "@babel/plugin-transform-block-scoping" "^7.10.1"
-    "@babel/plugin-transform-classes" "^7.10.3"
-    "@babel/plugin-transform-computed-properties" "^7.10.3"
-    "@babel/plugin-transform-destructuring" "^7.10.1"
-    "@babel/plugin-transform-dotall-regex" "^7.10.1"
-    "@babel/plugin-transform-duplicate-keys" "^7.10.1"
-    "@babel/plugin-transform-exponentiation-operator" "^7.10.1"
-    "@babel/plugin-transform-for-of" "^7.10.1"
-    "@babel/plugin-transform-function-name" "^7.10.1"
-    "@babel/plugin-transform-literals" "^7.10.1"
-    "@babel/plugin-transform-member-expression-literals" "^7.10.1"
-    "@babel/plugin-transform-modules-amd" "^7.10.1"
-    "@babel/plugin-transform-modules-commonjs" "^7.10.1"
-    "@babel/plugin-transform-modules-systemjs" "^7.10.3"
-    "@babel/plugin-transform-modules-umd" "^7.10.1"
-    "@babel/plugin-transform-named-capturing-groups-regex" "^7.10.3"
-    "@babel/plugin-transform-new-target" "^7.10.1"
-    "@babel/plugin-transform-object-super" "^7.10.1"
-    "@babel/plugin-transform-parameters" "^7.10.1"
-    "@babel/plugin-transform-property-literals" "^7.10.1"
-    "@babel/plugin-transform-regenerator" "^7.10.3"
-    "@babel/plugin-transform-reserved-words" "^7.10.1"
-    "@babel/plugin-transform-shorthand-properties" "^7.10.1"
-    "@babel/plugin-transform-spread" "^7.10.1"
-    "@babel/plugin-transform-sticky-regex" "^7.10.1"
-    "@babel/plugin-transform-template-literals" "^7.10.3"
-    "@babel/plugin-transform-typeof-symbol" "^7.10.1"
-    "@babel/plugin-transform-unicode-escapes" "^7.10.1"
-    "@babel/plugin-transform-unicode-regex" "^7.10.1"
+    "@babel/plugin-syntax-top-level-await" "^7.10.4"
+    "@babel/plugin-transform-arrow-functions" "^7.10.4"
+    "@babel/plugin-transform-async-to-generator" "^7.10.4"
+    "@babel/plugin-transform-block-scoped-functions" "^7.10.4"
+    "@babel/plugin-transform-block-scoping" "^7.10.4"
+    "@babel/plugin-transform-classes" "^7.10.4"
+    "@babel/plugin-transform-computed-properties" "^7.10.4"
+    "@babel/plugin-transform-destructuring" "^7.10.4"
+    "@babel/plugin-transform-dotall-regex" "^7.10.4"
+    "@babel/plugin-transform-duplicate-keys" "^7.10.4"
+    "@babel/plugin-transform-exponentiation-operator" "^7.10.4"
+    "@babel/plugin-transform-for-of" "^7.10.4"
+    "@babel/plugin-transform-function-name" "^7.10.4"
+    "@babel/plugin-transform-literals" "^7.10.4"
+    "@babel/plugin-transform-member-expression-literals" "^7.10.4"
+    "@babel/plugin-transform-modules-amd" "^7.10.4"
+    "@babel/plugin-transform-modules-commonjs" "^7.10.4"
+    "@babel/plugin-transform-modules-systemjs" "^7.10.4"
+    "@babel/plugin-transform-modules-umd" "^7.10.4"
+    "@babel/plugin-transform-named-capturing-groups-regex" "^7.10.4"
+    "@babel/plugin-transform-new-target" "^7.10.4"
+    "@babel/plugin-transform-object-super" "^7.10.4"
+    "@babel/plugin-transform-parameters" "^7.10.4"
+    "@babel/plugin-transform-property-literals" "^7.10.4"
+    "@babel/plugin-transform-regenerator" "^7.10.4"
+    "@babel/plugin-transform-reserved-words" "^7.10.4"
+    "@babel/plugin-transform-shorthand-properties" "^7.10.4"
+    "@babel/plugin-transform-spread" "^7.10.4"
+    "@babel/plugin-transform-sticky-regex" "^7.10.4"
+    "@babel/plugin-transform-template-literals" "^7.10.4"
+    "@babel/plugin-transform-typeof-symbol" "^7.10.4"
+    "@babel/plugin-transform-unicode-escapes" "^7.10.4"
+    "@babel/plugin-transform-unicode-regex" "^7.10.4"
     "@babel/preset-modules" "^0.1.3"
-    "@babel/types" "^7.10.3"
+    "@babel/types" "^7.10.4"
     browserslist "^4.12.0"
     core-js-compat "^3.6.2"
     invariant "^2.2.2"
@@ -833,13 +833,21 @@
     esutils "^2.0.2"
 
 "@babel/runtime-corejs2@^7.0.0":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.10.3.tgz#81bc99a96bfcb6db3f0dcf73fdc577cc554d341b"
-  integrity sha512-enKvnR/kKFbZFgXYo165wtSA5OeiTlgsnU4jV3vpKRhfWUJjLS6dfVcjIPeRcgJbgEgdgu0I+UyBWqu6c0GumQ==
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.10.4.tgz#5d48ee239624d511c88208da86c27a161ee01cf7"
+  integrity sha512-9sArmpZDQsnR1yyAcU51DxQrntWxt0LUKjPp3pIyo7kVLfaqKt8muppcT87QmFkXV5H50qXAF8JWOjk0jaXRYA==
   dependencies:
     core-js "^2.6.5"
     regenerator-runtime "^0.13.4"
 
+"@babel/runtime-corejs3@^7.8.3":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.10.4.tgz#f29fc1990307c4c57b10dbd6ce667b27159d9e0d"
+  integrity sha512-BFlgP2SoLO9HJX9WBwN67gHWMBhDX/eDz64Jajd6mR/UAUzqrNMm99d4qHnVaKscAElZoFiPv+JpR/Siud5lXw==
+  dependencies:
+    core-js-pure "^3.0.0"
+    regenerator-runtime "^0.13.4"
+
 "@babel/runtime@7.2.0":
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.2.0.tgz#b03e42eeddf5898e00646e4c840fa07ba8dcad7f"
@@ -848,32 +856,32 @@
     regenerator-runtime "^0.12.0"
 
 "@babel/runtime@^7.0.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.6":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.3.tgz#670d002655a7c366540c67f6fd3342cd09500364"
-  integrity sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw==
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.4.tgz#a6724f1a6b8d2f6ea5236dbfe58c7d7ea9c5eb99"
+  integrity sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw==
   dependencies:
     regenerator-runtime "^0.13.4"
 
-"@babel/template@^7.10.1", "@babel/template@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.3.tgz#4d13bc8e30bf95b0ce9d175d30306f42a2c9a7b8"
-  integrity sha512-5BjI4gdtD+9fHZUsaxPHPNpwa+xRkDO7c7JbhYn2afvrkDu5SfAAbi9AIMXw2xEhO/BR35TqiW97IqNvCo/GqA==
+"@babel/template@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278"
+  integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==
   dependencies:
-    "@babel/code-frame" "^7.10.3"
-    "@babel/parser" "^7.10.3"
-    "@babel/types" "^7.10.3"
+    "@babel/code-frame" "^7.10.4"
+    "@babel/parser" "^7.10.4"
+    "@babel/types" "^7.10.4"
 
-"@babel/traverse@^7.10.1", "@babel/traverse@^7.10.3":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.3.tgz#0b01731794aa7b77b214bcd96661f18281155d7e"
-  integrity sha512-qO6623eBFhuPm0TmmrUFMT1FulCmsSeJuVGhiLodk2raUDFhhTECLd9E9jC4LBIWziqt4wgF6KuXE4d+Jz9yug==
+"@babel/traverse@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.4.tgz#e642e5395a3b09cc95c8e74a27432b484b697818"
+  integrity sha512-aSy7p5THgSYm4YyxNGz6jZpXf+Ok40QF3aA2LyIONkDHpAcJzDUqlCKXv6peqYUs2gmic849C/t2HKw2a2K20Q==
   dependencies:
-    "@babel/code-frame" "^7.10.3"
-    "@babel/generator" "^7.10.3"
-    "@babel/helper-function-name" "^7.10.3"
-    "@babel/helper-split-export-declaration" "^7.10.1"
-    "@babel/parser" "^7.10.3"
-    "@babel/types" "^7.10.3"
+    "@babel/code-frame" "^7.10.4"
+    "@babel/generator" "^7.10.4"
+    "@babel/helper-function-name" "^7.10.4"
+    "@babel/helper-split-export-declaration" "^7.10.4"
+    "@babel/parser" "^7.10.4"
+    "@babel/types" "^7.10.4"
     debug "^4.1.0"
     globals "^11.1.0"
     lodash "^4.17.13"
@@ -887,12 +895,12 @@
     lodash "^4.2.0"
     to-fast-properties "^2.0.0"
 
-"@babel/types@^7.10.1", "@babel/types@^7.10.3", "@babel/types@^7.4.4", "@babel/types@^7.6.0":
-  version "7.10.3"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.3.tgz#6535e3b79fea86a6b09e012ea8528f935099de8e"
-  integrity sha512-nZxaJhBXBQ8HVoIcGsf9qWep3Oh3jCENK54V4mRF7qaJabVsAYdbTtmSD8WmAp1R6ytPiu5apMwSXyxB1WlaBA==
+"@babel/types@^7.10.4", "@babel/types@^7.4.4", "@babel/types@^7.6.0":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.4.tgz#369517188352e18219981efd156bfdb199fff1ee"
+  integrity sha512-UTCFOxC3FsFHb7lkRMVvgLzaRVamXuAs2Tz4wajva4WxtVY82eZeaUBtC2Zt95FU9TiznuC0Zk35tsim8jeVpg==
   dependencies:
-    "@babel/helper-validator-identifier" "^7.10.3"
+    "@babel/helper-validator-identifier" "^7.10.4"
     lodash "^4.17.13"
     to-fast-properties "^2.0.0"
 
@@ -1039,9 +1047,9 @@
     fastq "^1.6.0"
 
 "@popperjs/core@^2.3.2":
-  version "2.4.2"
-  resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.4.2.tgz#7c6dc4ecef16149fd7a736710baa1b811017fdca"
-  integrity sha512-JlGTGRYHC2QK+DDbePyXdBdooxFq2+noLfWpRqJtkxcb/oYWzOF0kcbfvvbWrwevCC1l6hLUg1wHYT+ona5BWQ==
+  version "2.4.4"
+  resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.4.4.tgz#11d5db19bd178936ec89cd84519c4de439574398"
+  integrity sha512-1oO6+dN5kdIA3sKPZhRGJTfGVP4SWV6KqlMOwry4J3HfyD68sl/3KmG7DeYUzvN+RbhXDnv/D8vNNB8168tAMg==
 
 "@sindresorhus/is@^0.14.0":
   version "0.14.0"
@@ -1137,9 +1145,9 @@
     "@types/leaflet" "*"
 
 "@types/leaflet@*", "@types/leaflet@^1.5.2":
-  version "1.5.12"
-  resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.5.12.tgz#9953b7e47a53e0a70cdd7a903a43dc3c343cf623"
-  integrity sha512-61HRMIng+bWvnnAIqUWLBlrd/TQZc4gU+gN1JL4K47EDtwIrcMEhWgi7PdcpbG1YmpH4F0EfOimkvV82gJIl9w==
+  version "1.5.13"
+  resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.5.13.tgz#e0e41612b236a6a37877a69a6eb996d2a9ba6905"
+  integrity sha512-aCNOIeoukY8DIQUs/8bNiiKQKc75HSFwo1YqcFaLe+SkG4DL+0ygKCDfhfZ54UX8k28pn5uA3QJEAw3wa2hqHw==
   dependencies:
     "@types/geojson" "*"
 
@@ -1194,9 +1202,9 @@
     "@types/orderedmap" "*"
 
 "@types/prosemirror-state@*", "@types/prosemirror-state@^1.2.4":
-  version "1.2.4"
-  resolved "https://registry.yarnpkg.com/@types/prosemirror-state/-/prosemirror-state-1.2.4.tgz#d135ecd4cdbd9ffa97cff24ffadc1e611dea7211"
-  integrity sha512-Gch4THfZ9QNsRQ7myibU8cG99F3b8/3Gto083ZuutNG72E0VmS8yfQzA5ahbndr5GUIbmKyOD5LqKTBvx/M0qw==
+  version "1.2.5"
+  resolved "https://registry.yarnpkg.com/@types/prosemirror-state/-/prosemirror-state-1.2.5.tgz#a91304e9aab6e71f868e23b3a1ae514a75033f8f"
+  integrity sha512-a5DxAifiF6vmdSJ5jsDMkpykUgUJUy+T5Q5hCjFOKJ4cfd3m3q1lsFKr7Bc4r91Qb7rfqyiKCMDnASS8LIHrKw==
   dependencies:
     "@types/prosemirror-model" "*"
     "@types/prosemirror-transform" "*"
@@ -1210,9 +1218,9 @@
     "@types/prosemirror-model" "*"
 
 "@types/prosemirror-view@*", "@types/prosemirror-view@^1.11.4":
-  version "1.11.4"
-  resolved "https://registry.yarnpkg.com/@types/prosemirror-view/-/prosemirror-view-1.11.4.tgz#580f5044fc600c79e199dfb56bf988028422c4e4"
-  integrity sha512-Hh8v2tpCEMaIesQuw7Y7Pz6imoC1T/bR5OlNGVtp944PZvctXiBvFRkQIb0YvZpt7vVkFzeq2kmR+7mnUfvWiw==
+  version "1.15.0"
+  resolved "https://registry.yarnpkg.com/@types/prosemirror-view/-/prosemirror-view-1.15.0.tgz#47d80431559107854f559ccaa29f919f5224a440"
+  integrity sha512-OBIAiVInYS0cr4txLZEVYs1t1aFKaAZvogFDgkZfLwa+uda+LNPSs6m4tNLU/KXoFu9iK9CPOpiYTlDQPEnU6g==
   dependencies:
     "@types/prosemirror-model" "*"
     "@types/prosemirror-state" "*"
@@ -1272,9 +1280,9 @@
     source-map "^0.7.3"
 
 "@types/webpack@^4.4.31":
-  version "4.41.17"
-  resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.17.tgz#0a69005e644d657c85b7d6ec1c826a71bebd1c93"
-  integrity sha512-6FfeCidTSHozwKI67gIVQQ5Mp0g4X96c2IXxX75hYEQJwST/i6NyZexP//zzMOBb+wG9jJ7oO8fk9yObP2HWAw==
+  version "4.41.18"
+  resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.18.tgz#2945202617866ecdffa582087f1b6de04a7eed55"
+  integrity sha512-mQm2R8vV2BZE/qIDVYqmBVLfX73a8muwjs74SpjEyJWJxeXBbsI9L65Pcia9XfYLYWzD1c1V8m+L0p30y2N7MA==
   dependencies:
     "@types/anymatch" "*"
     "@types/node" "*"
@@ -1308,13 +1316,14 @@
     eslint-scope "^5.0.0"
     eslint-utils "^2.0.0"
 
-"@typescript-eslint/experimental-utils@3.4.0":
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.4.0.tgz#8a44dfc6fb7f1d071937b390fe27608ebda122b8"
-  integrity sha512-rHPOjL43lOH1Opte4+dhC0a/+ks+8gOBwxXnyrZ/K4OTAChpSjP76fbI8Cglj7V5GouwVAGaK+xVwzqTyE/TPw==
+"@typescript-eslint/experimental-utils@3.5.0":
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.5.0.tgz#d09f9ffb890d1b15a7ffa9975fae92eee05597c4"
+  integrity sha512-zGNOrVi5Wz0jcjUnFZ6QUD0MCox5hBuVwemGCew2qJzUX5xPoyR+0EzS5qD5qQXL/vnQ8Eu+nv03tpeFRwLrDg==
   dependencies:
     "@types/json-schema" "^7.0.3"
-    "@typescript-eslint/typescript-estree" "3.4.0"
+    "@typescript-eslint/types" "3.5.0"
+    "@typescript-eslint/typescript-estree" "3.5.0"
     eslint-scope "^5.0.0"
     eslint-utils "^2.0.0"
 
@@ -1329,15 +1338,21 @@
     eslint-visitor-keys "^1.1.0"
 
 "@typescript-eslint/parser@^3.0.0":
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.4.0.tgz#fe52b68c5cb3bba3f5d875bd17adb70420d49d8d"
-  integrity sha512-ZUGI/de44L5x87uX5zM14UYcbn79HSXUR+kzcqU42gH0AgpdB/TjuJy3m4ezI7Q/jk3wTQd755mxSDLhQP79KA==
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.5.0.tgz#9ff8c11877c48df24e10e19d7bf542ee0359500d"
+  integrity sha512-sU07VbYB70WZHtgOjH/qfAp1+OwaWgrvD1Km1VXqRpcVxt971PMTU7gJtlrCje0M+Sdz7xKAbtiyIu+Y6QdnVA==
   dependencies:
     "@types/eslint-visitor-keys" "^1.0.0"
-    "@typescript-eslint/experimental-utils" "3.4.0"
-    "@typescript-eslint/typescript-estree" "3.4.0"
+    "@typescript-eslint/experimental-utils" "3.5.0"
+    "@typescript-eslint/types" "3.5.0"
+    "@typescript-eslint/typescript-estree" "3.5.0"
     eslint-visitor-keys "^1.1.0"
 
+"@typescript-eslint/types@3.5.0":
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.5.0.tgz#4e3d2a2272268d8ec3e3e4a37152a64956682639"
+  integrity sha512-Dreqb5idi66VVs1QkbAwVeDmdJG+sDtofJtKwKCZXIaBsINuCN7Jv5eDIHrS0hFMMiOvPH9UuOs4splW0iZe4Q==
+
 "@typescript-eslint/typescript-estree@2.34.0":
   version "2.34.0"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5"
@@ -1351,19 +1366,27 @@
     semver "^7.3.2"
     tsutils "^3.17.1"
 
-"@typescript-eslint/typescript-estree@3.4.0":
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.4.0.tgz#6a787eb70b48969e4cd1ea67b057083f96dfee29"
-  integrity sha512-zKwLiybtt4uJb4mkG5q2t6+W7BuYx2IISiDNV+IY68VfoGwErDx/RfVI7SWL4gnZ2t1A1ytQQwZ+YOJbHHJ2rw==
+"@typescript-eslint/typescript-estree@3.5.0":
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.5.0.tgz#dfc895db21a381b84f24c2a719f5bf9c600dcfdc"
+  integrity sha512-Na71ezI6QP5WVR4EHxwcBJgYiD+Sre9BZO5iJK2QhrmRPo/42+b0no/HZIrdD1sjghzlYv7t+7Jis05M1uMxQg==
   dependencies:
+    "@typescript-eslint/types" "3.5.0"
+    "@typescript-eslint/visitor-keys" "3.5.0"
     debug "^4.1.1"
-    eslint-visitor-keys "^1.1.0"
     glob "^7.1.6"
     is-glob "^4.0.1"
     lodash "^4.17.15"
     semver "^7.3.2"
     tsutils "^3.17.1"
 
+"@typescript-eslint/visitor-keys@3.5.0":
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.5.0.tgz#73c1ea2582f814735e4afdc1cf6f5e3af78db60a"
+  integrity sha512-7cTp9rcX2sz9Z+zua9MCOX4cqp5rYyFD5o8LlbSpXrMTXoRdngTtotRZEkm8+FNMHPWYFhitFK+qt/brK8BVJQ==
+  dependencies:
+    eslint-visitor-keys "^1.1.0"
+
 "@vue/babel-helper-vue-jsx-merge-props@^1.0.0":
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.0.0.tgz#048fe579958da408fb7a8b2a3ec050b50a661040"
@@ -2695,9 +2718,9 @@ binary-extensions@^1.0.0:
   integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==
 
 binary-extensions@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c"
-  integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9"
+  integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==
 
 bindings@^1.5.0:
   version "1.5.0"
@@ -2919,12 +2942,12 @@ browserslist@4.7.0:
     node-releases "^1.1.29"
 
 browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.8.5:
-  version "4.12.1"
-  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.1.tgz#6d08bef149b70d153930780ba762644e0f329122"
-  integrity sha512-WMjXwFtPskSW1pQUDJRxvRKRkeCr7usN0O/Za76N+F4oadaTdQHotSGcX9jT/Hs7mSKPkyMFNvqawB/1HzYDKQ==
+  version "4.12.2"
+  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.2.tgz#76653d7e4c57caa8a1a28513e2f4e197dc11a711"
+  integrity sha512-MfZaeYqR8StRZdstAK9hCKDd2StvePCYp5rHzQCPicUjfFliDgmuaBNPHYUTpAywBN8+Wc/d7NYVFkO0aqaBUw==
   dependencies:
     caniuse-lite "^1.0.30001088"
-    electron-to-chromium "^1.3.481"
+    electron-to-chromium "^1.3.483"
     escalade "^3.0.1"
     node-releases "^1.1.58"
 
@@ -3186,9 +3209,9 @@ caniuse-api@^3.0.0:
     lodash.uniq "^4.5.0"
 
 caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000929, caniuse-lite@^1.0.30000989, caniuse-lite@^1.0.30001087, caniuse-lite@^1.0.30001088:
-  version "1.0.30001088"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001088.tgz#23a6b9e192106107458528858f2c0e0dba0d9073"
-  integrity sha512-6eYUrlShRYveyqKG58HcyOfPgh3zb2xqs7NvT2VVtP3hEUeeWvc3lqhpeMTxYWBBeeaT9A4bKsrtjATm66BTHg==
+  version "1.0.30001093"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001093.tgz#833e80f64b1a0455cbceed2a4a3baf19e4abd312"
+  integrity sha512-0+ODNoOjtWD5eS9aaIpf4K0gQqZfILNY4WSNuYzeT1sXni+lMrrVjc0odEobJt6wrODofDZUX8XYi/5y7+xl8g==
 
 capture-stack-trace@^1.0.0:
   version "1.0.1"
@@ -3267,6 +3290,14 @@ chalk@^3.0.0:
     ansi-styles "^4.1.0"
     supports-color "^7.1.0"
 
+chalk@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
+  integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
+  dependencies:
+    ansi-styles "^4.1.0"
+    supports-color "^7.1.0"
+
 change-case@^3.0.1:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/change-case/-/change-case-3.1.0.tgz#0e611b7edc9952df2e8513b27b42de72647dd17e"
@@ -3516,6 +3547,11 @@ cli-width@^2.0.0:
   resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
   integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==
 
+cli-width@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
+  integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
+
 clipboard-copy@^3.0.0, clipboard-copy@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/clipboard-copy/-/clipboard-copy-3.1.0.tgz#4c59030a43d4988990564a664baeafba99f78ca4"
@@ -4004,6 +4040,11 @@ core-js-compat@^3.6.2, core-js-compat@^3.6.5:
     browserslist "^4.8.5"
     semver "7.0.0"
 
+core-js-pure@^3.0.0:
+  version "3.6.5"
+  resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813"
+  integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==
+
 core-js@2.6.0:
   version "2.6.0"
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.0.tgz#1e30793e9ee5782b307e37ffa22da0eacddd84d4"
@@ -4357,9 +4398,9 @@ cssstyle@^2.0.0:
     cssom "~0.3.6"
 
 csstype@^2.6.5:
-  version "2.6.10"
-  resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.10.tgz#e63af50e66d7c266edb6b32909cfd0aabe03928b"
-  integrity sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==
+  version "2.6.11"
+  resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.11.tgz#452f4d024149ecf260a852b025e36562a253ffc5"
+  integrity sha512-l8YyEC9NBkSm783PFTvh0FmJy7s5pFKrDp49ZL7zBGX3fWkO+N4EEyan1qqp8cwPLDcD0OSdyY6hAMoxp34JFw==
 
 cucumber-html-reporter@^3.0.4:
   version "3.0.4"
@@ -4483,6 +4524,13 @@ decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0:
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
   integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
 
+decamelize@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-3.2.0.tgz#84b8e8f4f8c579f938e35e2cc7024907e0090851"
+  integrity sha512-4TgkVUsmmu7oCSyGBm5FvfMoACuoh9EOidm7V5/J2X2djAwwt57qb3F2KMP2ITqODTCSwb+YRV+0Zqrv18k/hw==
+  dependencies:
+    xregexp "^4.2.4"
+
 decode-uri-component@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
@@ -4937,10 +4985,10 @@ ejs@^2.6.1:
   resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba"
   integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==
 
-electron-to-chromium@^1.3.103, electron-to-chromium@^1.3.247, electron-to-chromium@^1.3.481:
-  version "1.3.483"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.483.tgz#9269e7cfc1c8e72709824da171cbe47ca5e3ca9e"
-  integrity sha512-+05RF8S9rk8S0G8eBCqBRBaRq7+UN3lDs2DAvnG8SBSgQO3hjy0+qt4CmRk5eiuGbTcaicgXfPmBi31a+BD3lg==
+electron-to-chromium@^1.3.103, electron-to-chromium@^1.3.247, electron-to-chromium@^1.3.483:
+  version "1.3.487"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.487.tgz#8075e6ea33ee2e79a2dfb2a2467033f014017258"
+  integrity sha512-m4QS3IDShxauFfYFpnEzRCcUI55oKB9acEnHCuY/hSCZMz9Pz2KJj+UBnGHxRxS/mS1aphqOQ5wI6gc3yDZ7ew==
 
 elegant-spinner@^1.0.1:
   version "1.0.1"
@@ -5190,9 +5238,9 @@ eslint-plugin-cypress@^2.10.3:
     globals "^11.12.0"
 
 eslint-plugin-import@^2.20.2, eslint-plugin-import@^2.21.2:
-  version "2.21.2"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.21.2.tgz#8fef77475cc5510801bedc95f84b932f7f334a7c"
-  integrity sha512-FEmxeGI6yaz+SnEB6YgNHlQK1Bs2DKLM+YF+vuTk5H8J9CLbJLtlPvRFgZZ2+sXiKAlN5dpdlrWOjK8ZoZJpQA==
+  version "2.22.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e"
+  integrity sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg==
   dependencies:
     array-includes "^3.1.1"
     array.prototype.flat "^1.2.3"
@@ -6635,9 +6683,9 @@ graphql@^14.0.0, graphql@^14.0.2:
     iterall "^1.2.2"
 
 graphql@^15.0.0:
-  version "15.1.0"
-  resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.1.0.tgz#b93e28de805294ec08e1630d901db550cb8960a1"
-  integrity sha512-0TVyfOlCGhv/DBczQkJmwXOK6fjWkjzY3Pt7wY8i0gcYXq8aogG3weCsg48m72lywKSeOqedEHvVPOvZvSD51Q==
+  version "15.2.0"
+  resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.2.0.tgz#d9c655a523a3196d4b23657ec6ec5963b3bd4970"
+  integrity sha512-tsceRyHfgzZo+ee0YK3o8f0CR0cXAXxRlxoORWFo/CoM1bVy3UXGWeyzBcf+Y6oqPvO27BDmOEVATcunOO/MrQ==
 
 growl@1.10.5:
   version "1.10.5"
@@ -7245,20 +7293,20 @@ inquirer@6.5.0:
     through "^2.3.6"
 
 inquirer@^7.0.0, inquirer@^7.1.0:
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.2.0.tgz#63ce99d823090de7eb420e4bb05e6f3449aa389a"
-  integrity sha512-E0c4rPwr9ByePfNlTIB8z51kK1s2n6jrHuJeEHENl/sbq2G/S1auvibgEwNR4uSyiU+PiYHqSwsgGiXjG8p5ZQ==
+  version "7.3.0"
+  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.0.tgz#aa3e7cb0c18a410c3c16cdd2bc9dcbe83c4d333e"
+  integrity sha512-K+LZp6L/6eE5swqIcVXrxl21aGDU4S50gKH0/d96OMQnSBCyGyZl/oZhbkVmdp5sBoINHd4xZvFSARh2dk6DWA==
   dependencies:
     ansi-escapes "^4.2.1"
-    chalk "^3.0.0"
+    chalk "^4.1.0"
     cli-cursor "^3.1.0"
-    cli-width "^2.0.0"
+    cli-width "^3.0.0"
     external-editor "^3.0.3"
     figures "^3.0.0"
     lodash "^4.17.15"
     mute-stream "0.0.8"
     run-async "^2.4.0"
-    rxjs "^6.5.3"
+    rxjs "^6.6.0"
     string-width "^4.1.0"
     strip-ansi "^6.0.0"
     through "^2.3.6"
@@ -7841,9 +7889,9 @@ javascript-stringify@^2.0.0, javascript-stringify@^2.0.1:
   integrity sha512-yV+gqbd5vaOYjqlbk16EG89xB5udgjqQF3C5FAORDg4f/IS1Yc5ERCv5e/57yBcfJYw05V5JyIXabhwb75Xxow==
 
 javascript-time-ago@^2.0.4:
-  version "2.0.7"
-  resolved "https://registry.yarnpkg.com/javascript-time-ago/-/javascript-time-ago-2.0.7.tgz#ed3e4cfae7059d1c3ecc0e7fc253427f0e120636"
-  integrity sha512-NjM8FNqY91lciAE4bIm6KBW1efDt+0T6+08erwaQRu6SzLov8fJ6623LcyxnBN/Xtf4q9O4s5AMxPtStaNz0rA==
+  version "2.0.8"
+  resolved "https://registry.yarnpkg.com/javascript-time-ago/-/javascript-time-ago-2.0.8.tgz#708e5d507b9e1a4ea0c25987b0d9a1a86e8ee229"
+  integrity sha512-/cQbnAwmF2OgpMCg8r185ZGqkkoHE8paNn9T3S98A6DXFQn4irzMVgMSWnKB4jvcvzTauF3HRi9wZXUdwHyj6Q==
   dependencies:
     relative-time-format "^0.1.3"
 
@@ -11698,12 +11746,11 @@ regenerator-runtime@^0.13.4:
   integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==
 
 regenerator-transform@^0.14.2:
-  version "0.14.4"
-  resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.4.tgz#5266857896518d1616a78a0479337a30ea974cc7"
-  integrity sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==
+  version "0.14.5"
+  resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4"
+  integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==
   dependencies:
     "@babel/runtime" "^7.8.4"
-    private "^0.1.8"
 
 regex-not@^1.0.0, regex-not@^1.0.2:
   version "1.0.2"
@@ -12165,10 +12212,10 @@ rxjs@^5.0.0-beta.11:
   dependencies:
     symbol-observable "1.0.1"
 
-rxjs@^6.1.0, rxjs@^6.4.0, rxjs@^6.5.3:
-  version "6.5.5"
-  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec"
-  integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==
+rxjs@^6.1.0, rxjs@^6.4.0, rxjs@^6.6.0:
+  version "6.6.0"
+  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.0.tgz#af2901eedf02e3a83ffa7f886240ff9018bbec84"
+  integrity sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==
   dependencies:
     tslib "^1.9.0"
 
@@ -13358,9 +13405,9 @@ tiny-warning@^1.0.2:
   integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
 
 tippy.js@^6.2.3:
-  version "6.2.3"
-  resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.2.3.tgz#0a5db67dc6bd9129233b26052b7ae2b2047fd73e"
-  integrity sha512-MzqHMrr2C0IC8ZUnG5kLQPxonWJ7V+Usqiy2W5b+dCvAfousio0mA85h+Ea5wRq94AQGd8mbFGeciRgkP+F+7w==
+  version "6.2.4"
+  resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.2.4.tgz#b76000080cc035745cab4f20ac7a30e1ccb1b6bb"
+  integrity sha512-S3qLJhx7cpeGDpHw411jU62W1RvOGPkt3r68y8nwPi7wm/aexrSYAADbVb1ZNYCjspEwLWQvtAx76COvPcLvCw==
   dependencies:
     "@popperjs/core" "^2.3.2"
 
@@ -13756,9 +13803,9 @@ typedarray@^0.0.6:
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
 typescript@^3.9.3, typescript@~3.9.3:
-  version "3.9.5"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36"
-  integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==
+  version "3.9.6"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a"
+  integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==
 
 uglify-js@3.4.x:
   version "3.4.10"
@@ -14231,12 +14278,12 @@ vue-class-component@^7.2.3:
   resolved "https://registry.yarnpkg.com/vue-class-component/-/vue-class-component-7.2.3.tgz#a5b1abd53513a72ad51098752e2dedd499807cca"
   integrity sha512-oEqYpXKaFN+TaXU+mRLEx8dX0ah85aAJEe61mpdoUrq0Bhe/6sWhyZX1JjMQLhVsHAkncyhedhmCdDVSasUtDw==
 
-vue-cli-plugin-styleguidist@^4.25.0:
-  version "4.25.0"
-  resolved "https://registry.yarnpkg.com/vue-cli-plugin-styleguidist/-/vue-cli-plugin-styleguidist-4.25.0.tgz#8aab348db3983217c7be63cd92f8ad462cd4da89"
-  integrity sha512-7TPPOscE2j+0otWy5EFAS6us48mBEfGboycJE8bStSbnImNtBEBmIwAx2Jy8sd8Mnf1biSl0fzMzPizd1MVylQ==
+vue-cli-plugin-styleguidist@~4.26.0:
+  version "4.26.0"
+  resolved "https://registry.yarnpkg.com/vue-cli-plugin-styleguidist/-/vue-cli-plugin-styleguidist-4.26.0.tgz#fd267f763e85939756b42db0e1a902343113db0b"
+  integrity sha512-cyS6cGwEG+wghZO8T06O6A2sUMMPpoVIbqQLMWqZTc+zU+A56+Ljo6ecPN0L9G0yaOv9MKXuIPZrAahLNGyxBQ==
   dependencies:
-    vue-styleguidist "^4.25.0"
+    vue-styleguidist "^4.26.0"
     webpack-merge "^4.2.1"
 
 vue-cli-plugin-svg@~0.1.3:
@@ -14250,10 +14297,10 @@ vue-cli-plugin-svg@~0.1.3:
     url-loader "^2.0.0"
     vue-svg-loader "^0.12.0"
 
-vue-docgen-api@^4.25.0:
-  version "4.25.0"
-  resolved "https://registry.yarnpkg.com/vue-docgen-api/-/vue-docgen-api-4.25.0.tgz#844d68c0badf0e30284283df94a2d544958b553a"
-  integrity sha512-HlYQ7MK9AhkXfnTlJcNWMDvCnSo1q6M0lbO0U39OdEUoQpzYqzBXSMvPZjuvYe6Tmdhuhl2PD7L37J5RspkVnw==
+vue-docgen-api@^4.26.0:
+  version "4.26.0"
+  resolved "https://registry.yarnpkg.com/vue-docgen-api/-/vue-docgen-api-4.26.0.tgz#bf1a7fd201ddbcd62e4432a0e8b6369651fcf1fe"
+  integrity sha512-uJbmLup5NHukMUecMJiKgjLPdPIFDjlCFwOGX107S5gdlb+c/rd4ihOvWRAuVEBiUXWFxHJ12DZxCX1/UJGPcQ==
   dependencies:
     "@babel/parser" "^7.6.0"
     "@babel/types" "^7.6.0"
@@ -14366,10 +14413,10 @@ vue-style-loader@^4.1.0, vue-style-loader@^4.1.2:
     hash-sum "^1.0.2"
     loader-utils "^1.0.2"
 
-vue-styleguidist@^4.25.0:
-  version "4.25.0"
-  resolved "https://registry.yarnpkg.com/vue-styleguidist/-/vue-styleguidist-4.25.0.tgz#280cd6addefd0aa7d0f4047af56c0b17c5fcf2f2"
-  integrity sha512-tmKA+iHlbDKFJ2EIK6zQ9eeKXdoNPCuWKGKxdO3oiIdS8t47SslIJQ6TCQHQEx0CkGv1B4EHPybAouiJGSsTSw==
+vue-styleguidist@^4.26.0:
+  version "4.26.0"
+  resolved "https://registry.yarnpkg.com/vue-styleguidist/-/vue-styleguidist-4.26.0.tgz#2e0be5a5bbbb276e0cfad1daffe0594ff7c25b23"
+  integrity sha512-/H/d47DMcCvSew88q3nXD+P5jlYvlBFqu162S9EqY4h3G1E1qaBXHxcKGAutJaEJNlWOWeNU8RrUmAau0Jb6Sw==
   dependencies:
     "@vxna/mini-html-webpack-template" "^1.0.0"
     ast-types "^0.13.2"
@@ -14419,7 +14466,7 @@ vue-styleguidist@^4.25.0:
     style-loader "^1.0.0"
     terser-webpack-plugin "^2.2.2"
     to-ast "^1.0.0"
-    vue-docgen-api "^4.25.0"
+    vue-docgen-api "^4.26.0"
     vue-inbrowser-compiler "^4.23.3"
     vue-inbrowser-compiler-utils "^4.23.3"
     webpack-dev-server "^3.11.0"
@@ -14546,9 +14593,9 @@ webpack-bundle-analyzer@^3.8.0:
     ws "^6.0.0"
 
 webpack-chain@^6.4.0:
-  version "6.4.0"
-  resolved "https://registry.yarnpkg.com/webpack-chain/-/webpack-chain-6.4.0.tgz#22f0b27b6a9bc9ee3cba4f9e6513cf66394034e2"
-  integrity sha512-f97PYqxU+9/u0IUqp/ekAHRhBD1IQwhBv3wlJo2nvyELpr2vNnUqO3XQEk+qneg0uWGP54iciotszpjfnEExFA==
+  version "6.5.0"
+  resolved "https://registry.yarnpkg.com/webpack-chain/-/webpack-chain-6.5.0.tgz#0b4af2094a5058a9ccd34b8f7ab194de4c83365f"
+  integrity sha512-K4EHiEg4WlP4w1rKXKpYWvX9cfGBERHCGP06ETSNV62XUIfOUg1DDRQpxyBsFYxZLKc4YUAI3iiCIvWoliheGA==
   dependencies:
     deepmerge "^1.5.2"
     javascript-stringify "^2.0.1"
@@ -14706,9 +14753,9 @@ whatwg-fetch@2.0.4:
   integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==
 
 whatwg-fetch@>=0.10.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
-  integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.1.0.tgz#49d630cdfa308dba7f2819d49d09364f540dbcc6"
+  integrity sha512-pgmbsVWKpH9GxLXZmtdowDIqtb/rvPyjjQv3z9wLcmgWKFHilKnZD3ldgrOlwJoPGOUluQsRPWd52yVkPfmI1A==
 
 whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0:
   version "2.3.0"
@@ -15003,6 +15050,13 @@ xmlchars@^2.1.1:
   resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
   integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
 
+xregexp@^4.2.4:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.3.0.tgz#7e92e73d9174a99a59743f67a4ce879a04b5ae50"
+  integrity sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==
+  dependencies:
+    "@babel/runtime-corejs3" "^7.8.3"
+
 xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
@@ -15054,7 +15108,7 @@ yargs-parser@^11.1.1:
     camelcase "^5.0.0"
     decamelize "^1.2.0"
 
-yargs-parser@^18.1.1:
+yargs-parser@^18.1.2:
   version "18.1.3"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
   integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
@@ -15155,12 +15209,12 @@ yargs@^10.0.3:
     yargs-parser "^8.1.0"
 
 yargs@^15.0.0:
-  version "15.3.1"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b"
-  integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==
+  version "15.4.0"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.0.tgz#53949fb768309bac1843de9b17b80051e9805ec2"
+  integrity sha512-D3fRFnZwLWp8jVAAhPZBsmeIHY8tTsb8ItV9KaAaopmC6wde2u6Yw29JBIZHXw14kgkRnYmDgmQU4FVMDlIsWw==
   dependencies:
     cliui "^6.0.0"
-    decamelize "^1.2.0"
+    decamelize "^3.2.0"
     find-up "^4.1.0"
     get-caller-file "^2.0.1"
     require-directory "^2.1.1"
@@ -15169,7 +15223,7 @@ yargs@^15.0.0:
     string-width "^4.2.0"
     which-module "^2.0.0"
     y18n "^4.0.0"
-    yargs-parser "^18.1.1"
+    yargs-parser "^18.1.2"
 
 yargs@^8.0.2:
   version "8.0.2"
diff --git a/mix.exs b/mix.exs
index 9b4cf27e2..1fb048c12 100644
--- a/mix.exs
+++ b/mix.exs
@@ -98,7 +98,7 @@ defmodule Mobilizon.Mixfile do
       {:bamboo_smtp, "~> 2.0"},
       {:geolix, "~> 1.0"},
       {:geolix_adapter_mmdb2, "~> 0.5.0"},
-      {:absinthe, "~> 1.5.1"},
+      {:absinthe, "~> 1.5.2"},
       {:absinthe_phoenix, "~> 2.0.0"},
       {:absinthe_plug, "~> 1.5.0"},
       {:dataloader, "~> 1.0.6"},
diff --git a/mix.lock b/mix.lock
index 4949dd0c5..9445c84ab 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,5 +1,5 @@
 %{
-  "absinthe": {:hex, :absinthe, "1.5.1", "2f462f5849b2a4f72889d5a131ca6760b47ca8c5de2ba21c1dca3889634f2277", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b264eeb69605c6012f563e240edcca3d8abc5a725cab6f58ad82510a0283618b"},
+  "absinthe": {:hex, :absinthe, "1.5.2", "2f9449b0c135ea61c09c11968d3d4fe6abd5bed38cf9be1c6d6b7c5ec858cfa0", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "669c84879629b7fffdc6cda9361ab9c81c9c7691e65418ba089b912a227963ac"},
   "absinthe_ecto": {:hex, :absinthe_ecto, "0.1.3", "420b68129e79fe4571a4838904ba03e282330d335da47729ad52ffd7b8c5fcb1", [:mix], [{:absinthe, "~> 1.3.0 or ~> 1.4.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:ecto, ">= 0.0.0", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "355b9db34abfab96ae1e025434b66e11002babcf4fe6b7144d26ff7548985f52"},
   "absinthe_phoenix": {:hex, :absinthe_phoenix, "2.0.0", "01c6a90af0ca12ee08d0fb93e23f9890d75bb6d3027f49ee4383bc03058ef5c3", [:mix], [{:absinthe, "~> 1.5.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:absinthe_plug, "~> 1.5.0", [hex: :absinthe_plug, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.5", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "7ffbfe9fb82a14cafb78885cc2cef4f9d454bbbe2c95eec12b5463f5a20d1020"},
   "absinthe_plug": {:hex, :absinthe_plug, "1.5.0", "018ef544cf577339018d1f482404b4bed762e1b530c78be9de4bbb88a6f3a805", [:mix], [{:absinthe, "~> 1.5.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.2 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "4c160f4ce9a1233a4219a42de946e4e05d0e8733537cd5d8d20e7d4ef8d4b7c7"},
@@ -23,7 +23,8 @@
   "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"},
   "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
   "dialyxir": {:hex, :dialyxir, "1.0.0", "6a1fa629f7881a9f5aaf3a78f094b2a51a0357c843871b8bc98824e7342d00a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "aeb06588145fac14ca08d8061a142d52753dbc2cf7f0d00fc1013f53f8654654"},
-  "earmark": {:hex, :earmark, "1.4.5", "62ffd3bd7722fb7a7b1ecd2419ea0b458c356e7168c1f5d65caf09b4fbdd13c8", [:mix], [], "hexpm", "b7d0e6263d83dc27141a523467799a685965bf8b13b6743413f19a7079843f4f"},
+  "earmark": {:hex, :earmark, "1.4.9", "837e4c1c5302b3135e9955f2bbf52c6c52e950c383983942b68b03909356c0d9", [:mix], [{:earmark_parser, ">= 1.4.9", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "0d72df7d13a3dc8422882bed5263fdec5a773f56f7baeb02379361cb9e5b0d8e"},
+  "earmark_parser": {:hex, :earmark_parser, "1.4.9", "819bda2049e6ee1365424e4ced1ba65806eacf0d2867415f19f3f80047f8037b", [:mix], [], "hexpm", "8bf54fddabf2d7e137a0c22660e71b49d5a0a82d1fb05b5af62f2761cd6485c4"},
   "ecto": {:hex, :ecto, "3.4.5", "2bcd262f57b2c888b0bd7f7a28c8a48aa11dc1a2c6a858e45dd8f8426d504265", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8c6d1d4d524559e9b7a062f0498e2c206122552d63eacff0a6567ffe7a8e8691"},
   "ecto_autoslug_field": {:hex, :ecto_autoslug_field, "2.0.1", "2177c1c253f6dd3efd4b56d1cb76104d0a6ef044c6b9a7a0ad6d32665c4111e5", [:mix], [{:ecto, ">= 2.1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:slugger, ">= 0.2.0", [hex: :slugger, repo: "hexpm", optional: false]}], "hexpm", "a3cc73211f2e75b89a03332183812ebe1ac08be2e25a1df5aa3d1422f92c45c3"},
   "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},

From c4f8c30c418e8696e7f28580ca1250f5871f7dbe Mon Sep 17 00:00:00 2001
From: Thomas Citharel <tcit@tcit.fr>
Date: Sun, 5 Jul 2020 13:44:55 +0200
Subject: [PATCH 3/4] Add basic documentation for LDAP & OAuth support

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
---
 docs/administration/configure/auth.md | 113 ++++++++++++++++++++++++++
 1 file changed, 113 insertions(+)
 create mode 100644 docs/administration/configure/auth.md

diff --git a/docs/administration/configure/auth.md b/docs/administration/configure/auth.md
new file mode 100644
index 000000000..91ff5b91e
--- /dev/null
+++ b/docs/administration/configure/auth.md
@@ -0,0 +1,113 @@
+# Authentification
+
+## LDAP
+
+Use LDAP for user authentication.  When a user logs in to the Mobilizon instance, the email and password will be verified by trying to authenticate
+(bind) to an LDAP server. If a user exists in the LDAP directory but there is no account with the same email yet on the Mobilizon instance then a new
+Mobilizon account will be created (without needing email confirmation) with the same email as the LDAP email name.
+
+!!! tip
+    As Mobilizon uses email for login and LDAP bind is often done with account UID/CN, we need to start by searching for LDAP account matching with this email. LDAP search without bind is often disallowed, so you'll probably need an admin LDAP user.
+
+
+Change authentification method:
+```elixir
+config :mobilizon,
+       Mobilizon.Service.Auth.Authenticator,
+       Mobilizon.Service.Auth.LDAPAuthenticator
+```
+
+LDAP configuration under `:mobilizon, :ldap`:
+
+* `enabled`: enables LDAP authentication
+* `host`: LDAP server hostname
+* `port`: LDAP port, e.g. 389 or 636
+* `ssl`: true to use SSL, usually implies the port 636
+* `sslopts`: additional SSL options
+* `tls`: true to start TLS, usually implies the port 389
+* `tlsopts`: additional TLS options
+* `base`: LDAP base, e.g. "dc=example,dc=com"
+* `uid`: LDAP attribute name to authenticate the user, e.g. when "cn", the filter will be "cn=username,base"
+* `require_bind_for_search` whether admin bind is required to perform search
+* `bind_uid` the admin uid/cn for binding before searching
+* `bind_password` the admin password for binding before searching
+
+Example:
+
+```elixir
+config :mobilizon, :ldap,
+  enabled: true,
+  host: "localhost",
+  port: 636,
+  ssl: true,
+  sslopts: [],
+  tls: true,
+  tlsopts: [],
+  base: "ou=users,dc=example,dc=local",
+  uid: "cn",
+  require_bind_for_search: true,
+  bind_uid: "admin_account",
+  bind_password: "some_admin_password"
+```
+
+## OAuth
+
+Mobilizon currently supports the following providers:
+
+* [Discord](https://github.com/schwarz/ueberauth_discord)
+* [Facebook](https://github.com/ueberauth/ueberauth_facebook)
+* [Github](https://github.com/ueberauth/ueberauth_github)
+* [Gitlab](https://github.com/mtchavez/ueberauth_gitlab) (including self-hosted)
+* [Google](https://github.com/ueberauth/ueberauth_google)
+* [Keycloak](https://github.com/Rukenshia/ueberauth_keycloak) (through OpenID Connect)
+* [Twitter](https://github.com/Rukenshia/ueberauth_keycloak)
+
+Support for [other providers](https://github.com/ueberauth/ueberauth/wiki/List-of-Strategies) can easily be added if requested.
+
+!!! tip
+    We advise to look at each provider's README file for eventual specific instructions.
+
+You'll have to start by registering an app at the provider. Be sure to activate features like "Sign-in with" and "emails" scope, as Mobilizon needs users emails to register them.
+
+Add the configured providers to configuration (you may find the appropriate scopes on the provider's API documentation):
+```elixir
+config :ueberauth,
+       Ueberauth,
+       providers: [
+         gitlab: {Ueberauth.Strategy.Gitlab, [default_scope: "read_user"]},
+         keycloak: {Ueberauth.Strategy.Keycloak, [default_scope: "email"]}
+         # ...
+       ]
+```
+
+In order for the « Sign-in with » buttons to be added on Register and Login pages, list your providers:
+```elixir
+config :mobilizon, :auth,
+  oauth_consumer_strategies: [
+    :gitlab,
+    {:keycloak, "My corporate account"}
+    # ...
+  ]
+```
+
+!!! note
+    If you use the `{:provider_id, "Some label"}` form, the label will be used inside the buttons on Register and Login pages.
+
+Finally add the configuration for each specific provider. The Client ID and Client Secret are at least required:
+```elixir
+config :ueberauth, Ueberauth.Strategy.Facebook.OAuth,
+  client_id: "some_numeric_id",
+  client_secret: "some_secret"
+
+keycloak_url = "https://some-keycloak-instance.org"
+
+# Realm may be something else than master
+config :ueberauth, Ueberauth.Strategy.Keycloak.OAuth,
+  client_id: "some_id",
+  client_secret: "some_hexadecimal_secret",
+  site: keycloak_url,
+  authorize_url: "#{keycloak_url}/auth/realms/master/protocol/openid-connect/auth",
+  token_url: "#{keycloak_url}/auth/realms/master/protocol/openid-connect/token",
+  userinfo_url: "#{keycloak_url}/auth/realms/master/protocol/openid-connect/userinfo",
+  token_method: :post
+```
\ No newline at end of file

From baf55a97c20e9f77b625db944207b9ae1a8222e1 Mon Sep 17 00:00:00 2001
From: Thomas Citharel <tcit@tcit.fr>
Date: Sun, 5 Jul 2020 13:46:10 +0200
Subject: [PATCH 4/4] Add changelog entries for LDAP & OAuth

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
---
 CHANGELOG.md | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 224ac4ad4..1d5a0246a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## Unreleased
 
+### Added
+
+- Possibility to login using LDAP
+- Possibility to login using OAuth providers
+
 ### Changed
 
 - Completely replaced HTMLSanitizeEx with FastSanitize [!490](https://framagit.org/framasoft/mobilizon/-/merge_requests/490)