From 0313761ab60e764fe7e8a1e738bbb019726e9db2 Mon Sep 17 00:00:00 2001
From: hawkspar <quentin.chevalier@polytechnique.edu>
Date: Sat, 9 Feb 2019 11:50:37 +0100
Subject: [PATCH] clean peek User

---
 .env_dist                   |   2 +-
 ldap_config.json            |   1 -
 ldap_credentials_dist.json  |   2 +-
 package-lock.json           | 417 ++++++++++++++---------------
 src/ldap/export/group.js    | 453 ++++++++++++++++++++++++++++++++
 src/ldap/export/user.js     | 332 +++++++++++++++++++++++
 src/ldap/export/user.ts     |   2 +-
 src/ldap/internal/basics.js | 330 +++++++++++++++++++++++
 src/ldap/internal/config.js |  86 ++++++
 src/ldap/internal/config.ts |   2 +-
 src/ldap/internal/tools.js  | 509 ++++++++++++++++++++++++++++++++++++
 src/ldap/test.js            |   5 +
 12 files changed, 1921 insertions(+), 220 deletions(-)
 create mode 100644 src/ldap/export/group.js
 create mode 100644 src/ldap/export/user.js
 create mode 100644 src/ldap/internal/basics.js
 create mode 100644 src/ldap/internal/config.js
 create mode 100644 src/ldap/internal/tools.js
 create mode 100644 src/ldap/test.js

diff --git a/.env_dist b/.env_dist
index 246780d..f79e0a2 100644
--- a/.env_dist
+++ b/.env_dist
@@ -1,6 +1,6 @@
 # Modele du fichier '.env', definissant variables "d'environnement" utilisees dans divers fichiers de src/ et de db/.
 # (Ces variables sont chargees par le package dotenv dans `process.env`, dans les fichiers .js et .ts ou dotenv.config() est appele)
-# Copier ce fichier en le renommant '.env', (le placer a la racine du repo,) et modifier les champs comme il faut.
+#Copier et renommer .env
 
 TARGET_ENV=development
 PORT=3000
diff --git a/ldap_config.json b/ldap_config.json
index 982ac05..62d56af 100644
--- a/ldap_config.json
+++ b/ldap_config.json
@@ -1,6 +1,5 @@
 {
 	"comment_1": "Tout ce fichier sert à protéger les vrais champs du LDAP dans les scripts dans src/ldap. Les champs ci-dessous contiennent le nécessaire à une première connexion par exemple.",
-	"comment_tmp": "Passer en LDAPS ASAP.",
 	"comment_1bis": "Ce fichier est importé par src/ldap/internal/config.ts ; comme indiqué dans config.ts, la présence d'une variable d'environnement correspondante peut override les paramètres donnés ici.",
 	"server_prod_old": "ldap://frankiz.eleves.polytechnique.fr:389",
 	"server_dev_old": "ldap://localhost:389",
diff --git a/ldap_credentials_dist.json b/ldap_credentials_dist.json
index 7702f74..46253a9 100644
--- a/ldap_credentials_dist.json
+++ b/ldap_credentials_dist.json
@@ -1,5 +1,5 @@
 {
-	"README": "Modele du fichier 'ldap_credentials.json', requis par src/ldap/. Copier ce fichier en le renommant 'ldap_credentials.json', (le placer a la racine du repo,) et modifier les champs comme il faut.",
+	"README": "Modele du fichier 'ldap_credentials.json', requis par src/ldap/. Copier ce fichier en le renommant 'ldap_credentials.json' (le placer a la racine du repo). A copier et renommer en local.",
 
 	"dn": "uid=sigma,ou=services,dc=frankiz,dc=net",
 	"passwd": "&vH}x%7;FK3j53eX"
diff --git a/package-lock.json b/package-lock.json
index daa192c..a812679 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,7 +15,7 @@
     "@apollographql/graphql-playground-html": {
       "version": "1.6.6",
       "resolved": "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.6.tgz",
-      "integrity": "sha512-lqK94b+caNtmKFs5oUVXlSpN3sm5IXZ+KfhMxOtr0LR2SqErzkoJilitjDvJ1WbjHlxLI7WtCjRmOLdOGJqtMQ=="
+      "integrity": "sha1-AiIJ4oorVH3N4VshnwxQ9HqlvrM="
     },
     "@babel/cli": {
       "version": "7.2.3",
@@ -221,7 +221,7 @@
     "@babel/helper-annotate-as-pure": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz",
-      "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==",
+      "integrity": "sha1-Mj053QtQ4Qx8Bsp9djjmhk2MXDI=",
       "dev": true,
       "requires": {
         "@babel/types": "^7.0.0"
@@ -243,7 +243,7 @@
     "@babel/helper-builder-binary-assignment-operator-visitor": {
       "version": "7.1.0",
       "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz",
-      "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==",
+      "integrity": "sha1-a2lijf5Ah3mODE7Zjj1Kay+9L18=",
       "dev": true,
       "requires": {
         "@babel/helper-explode-assignable-expression": "^7.1.0",
@@ -266,7 +266,7 @@
     "@babel/helper-call-delegate": {
       "version": "7.1.0",
       "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.1.0.tgz",
-      "integrity": "sha512-YEtYZrw3GUK6emQHKthltKNZwszBcHK58Ygcis+gVUrF4/FmTVr5CCqQNSfmvg2y+YDEANyYoaLz/SHsnusCwQ==",
+      "integrity": "sha1-apV/EF83dV6GRTQ9MDiiLhRJzEo=",
       "dev": true,
       "requires": {
         "@babel/helper-hoist-variables": "^7.0.0",
@@ -484,7 +484,7 @@
     "@babel/helper-define-map": {
       "version": "7.1.0",
       "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz",
-      "integrity": "sha512-yPPcW8dc3gZLN+U1mhYV91QU3n5uTbx7DUdf8NnPbjS0RMwBuHi9Xt2MUgppmNz7CJxTBWsGczTiEp1CSOTPRg==",
+      "integrity": "sha1-O3TK7DKbPIDBFikIh8DdmuRowgw=",
       "dev": true,
       "requires": {
         "@babel/helper-function-name": "^7.1.0",
@@ -565,7 +565,7 @@
     "@babel/helper-explode-assignable-expression": {
       "version": "7.1.0",
       "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz",
-      "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==",
+      "integrity": "sha1-U3+hP28WdN90WwwA7I/k6ZaByPY=",
       "dev": true,
       "requires": {
         "@babel/traverse": "^7.1.0",
@@ -719,7 +719,7 @@
     "@babel/helper-hoist-variables": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0.tgz",
-      "integrity": "sha512-Ggv5sldXUeSKsuzLkddtyhyHe2YantsxWKNi7A+7LeD12ExRDWTRk29JCXpaHPAbMaIPZSil7n+lq78WY2VY7w==",
+      "integrity": "sha1-Rq3ExedYZFrnpF3rkrqwkYwju4g=",
       "dev": true,
       "requires": {
         "@babel/types": "^7.0.0"
@@ -741,7 +741,7 @@
     "@babel/helper-member-expression-to-functions": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz",
-      "integrity": "sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==",
+      "integrity": "sha1-jNFLCg33/wDwCefXpDaUX0fHoW8=",
       "dev": true,
       "requires": {
         "@babel/types": "^7.0.0"
@@ -763,7 +763,7 @@
     "@babel/helper-module-imports": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz",
-      "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==",
+      "integrity": "sha1-lggbcRHkhtpNLNlxrRpP4hbMLj0=",
       "dev": true,
       "requires": {
         "@babel/types": "^7.0.0"
@@ -858,7 +858,7 @@
     "@babel/helper-optimise-call-expression": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz",
-      "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==",
+      "integrity": "sha1-opIMVwKwc8Fd5REGIAqoytIEl9U=",
       "dev": true,
       "requires": {
         "@babel/types": "^7.0.0"
@@ -880,13 +880,13 @@
     "@babel/helper-plugin-utils": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz",
-      "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==",
+      "integrity": "sha1-u7P77phmHFaQNCN8wDlnupm08lA=",
       "dev": true
     },
     "@babel/helper-regex": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.0.0.tgz",
-      "integrity": "sha512-TR0/N0NDCcUIUEbqV6dCO+LptmmSQFQ7q70lfcEB4URsjD0E1HzicrwUH+ap6BAQ2jhCX9Q4UqZy4wilujWlkg==",
+      "integrity": "sha1-LBcYkjtX+bvmRwX/5WQKxk2b2yc=",
       "dev": true,
       "requires": {
         "lodash": "^4.17.10"
@@ -895,7 +895,7 @@
     "@babel/helper-remap-async-to-generator": {
       "version": "7.1.0",
       "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz",
-      "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==",
+      "integrity": "sha1-Nh2AghtvONp1vT8HheziCojF/n8=",
       "dev": true,
       "requires": {
         "@babel/helper-annotate-as-pure": "^7.0.0",
@@ -1168,7 +1168,7 @@
     "@babel/helper-simple-access": {
       "version": "7.1.0",
       "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz",
-      "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==",
+      "integrity": "sha1-Ze65VMjCRb6qToWdphiPOdceWFw=",
       "dev": true,
       "requires": {
         "@babel/template": "^7.1.0",
@@ -1237,7 +1237,7 @@
     "@babel/helper-wrap-function": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz",
-      "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==",
+      "integrity": "sha1-xOABJEV2nigVtVKW6tQ6lYVJ9vo=",
       "dev": true,
       "requires": {
         "@babel/helper-function-name": "^7.1.0",
@@ -1525,7 +1525,7 @@
     "@babel/plugin-proposal-async-generator-functions": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz",
-      "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==",
+      "integrity": "sha1-somzBmadzkrSCwJSiJoVdoydQX4=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0",
@@ -1546,7 +1546,7 @@
     "@babel/plugin-proposal-json-strings": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz",
-      "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==",
+      "integrity": "sha1-Vo7MRGxhSK5rJn8CVREwiR4p8xc=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0",
@@ -1566,7 +1566,7 @@
     "@babel/plugin-proposal-optional-catch-binding": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz",
-      "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==",
+      "integrity": "sha1-E12B7baKCB5V5W7EhUHs6AZcOPU=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0",
@@ -1576,7 +1576,7 @@
     "@babel/plugin-proposal-unicode-property-regex": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.2.0.tgz",
-      "integrity": "sha512-LvRVYb7kikuOtIoUeWTkOxQEV1kYvL5B6U3iWEGCzPNRus1MzJweFqORTj+0jkxozkTSYNJozPOddxmqdqsRpw==",
+      "integrity": "sha1-q+coH+Rsld3BQ6ZeU1hkd5IDlSA=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0",
@@ -1587,7 +1587,7 @@
     "@babel/plugin-syntax-async-generators": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz",
-      "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==",
+      "integrity": "sha1-aeHw2zTG9aDPfiszI78VmnbIy38=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0"
@@ -1596,7 +1596,7 @@
     "@babel/plugin-syntax-json-strings": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz",
-      "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==",
+      "integrity": "sha1-cr0T9v/h0lk4Ep0qGGsR/WKVFHA=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0"
@@ -1605,7 +1605,7 @@
     "@babel/plugin-syntax-object-rest-spread": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz",
-      "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==",
+      "integrity": "sha1-O3o+czUQxX6CC5FCpleayLDfrS4=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0"
@@ -1614,7 +1614,7 @@
     "@babel/plugin-syntax-optional-catch-binding": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz",
-      "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==",
+      "integrity": "sha1-qUAT1u2okI3+akd+f57ahWVuz1w=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0"
@@ -1632,7 +1632,7 @@
     "@babel/plugin-transform-arrow-functions": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz",
-      "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==",
+      "integrity": "sha1-mur75Nb/xlY7+Pg3IJFijwB3lVA=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0"
@@ -1641,7 +1641,7 @@
     "@babel/plugin-transform-async-to-generator": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.2.0.tgz",
-      "integrity": "sha512-CEHzg4g5UraReozI9D4fblBYABs7IM6UerAVG7EJVrTLC5keh00aEuLUT+O40+mJCEzaXkYfTCUKIyeDfMOFFQ==",
+      "integrity": "sha1-aLikOGY+iFGeZbd2+JOPNEWxov8=",
       "dev": true,
       "requires": {
         "@babel/helper-module-imports": "^7.0.0",
@@ -1652,7 +1652,7 @@
     "@babel/plugin-transform-block-scoped-functions": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz",
-      "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==",
+      "integrity": "sha1-XTzBHo1d3XUqpkyRSNDbbLef0ZA=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0"
@@ -1661,7 +1661,7 @@
     "@babel/plugin-transform-block-scoping": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.2.0.tgz",
-      "integrity": "sha512-vDTgf19ZEV6mx35yiPJe4fS02mPQUUcBNwWQSZFXSzTSbsJFQvHt7DqyS3LK8oOWALFOsJ+8bbqBgkirZteD5Q==",
+      "integrity": "sha1-8XxJ2R7tvN9d1QWX0W9fL3cBMtQ=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0",
@@ -1766,7 +1766,7 @@
     "@babel/plugin-transform-computed-properties": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz",
-      "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==",
+      "integrity": "sha1-g6ffamWIZbHI9kHVEMbzryICFto=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0"
@@ -1775,7 +1775,7 @@
     "@babel/plugin-transform-destructuring": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.2.0.tgz",
-      "integrity": "sha512-coVO2Ayv7g0qdDbrNiadE4bU7lvCd9H539m2gMknyVjjMdwF/iCOM7R+E8PkntoqLkltO0rk+3axhpp/0v68VQ==",
+      "integrity": "sha1-51JptLeInsOjMs0NDIz/j+0NxvM=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0"
@@ -1784,7 +1784,7 @@
     "@babel/plugin-transform-dotall-regex": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.2.0.tgz",
-      "integrity": "sha512-sKxnyHfizweTgKZf7XsXu/CNupKhzijptfTM+bozonIuyVrLWVUvYjE2bhuSBML8VQeMxq4Mm63Q9qvcvUcciQ==",
+      "integrity": "sha1-8Kq7k9EgqKxh6SXqC6RAgS2+Dkk=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0",
@@ -1795,7 +1795,7 @@
     "@babel/plugin-transform-duplicate-keys": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz",
-      "integrity": "sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw==",
+      "integrity": "sha1-2VLEkw8xKk2//xjwspFOYMNVMLM=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0"
@@ -1804,7 +1804,7 @@
     "@babel/plugin-transform-exponentiation-operator": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz",
-      "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==",
+      "integrity": "sha1-pjhoKJ5bQAf3BU1GSRr1FDV2YAg=",
       "dev": true,
       "requires": {
         "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0",
@@ -1814,7 +1814,7 @@
     "@babel/plugin-transform-for-of": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.2.0.tgz",
-      "integrity": "sha512-Kz7Mt0SsV2tQk6jG5bBv5phVbkd0gd27SgYD4hH1aLMJRchM0dzHaXvrWhVZ+WxAlDoAKZ7Uy3jVTW2mKXQ1WQ==",
+      "integrity": "sha1-q3RovvqA92S7A9PLXu+MyZjhytk=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0"
@@ -1823,7 +1823,7 @@
     "@babel/plugin-transform-function-name": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.2.0.tgz",
-      "integrity": "sha512-kWgksow9lHdvBC2Z4mxTsvc7YdY7w/V6B2vy9cTIPtLEE9NhwoWivaxdNM/S37elu5bqlLP/qOY906LukO9lkQ==",
+      "integrity": "sha1-95MDYoKf+ZoxdMOfCvzAJO9Zcxo=",
       "dev": true,
       "requires": {
         "@babel/helper-function-name": "^7.1.0",
@@ -1903,7 +1903,7 @@
     "@babel/plugin-transform-literals": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz",
-      "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==",
+      "integrity": "sha1-aQNT6B+SZ9rU/Yz9d+r6hqulPqE=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0"
@@ -1912,7 +1912,7 @@
     "@babel/plugin-transform-modules-amd": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz",
-      "integrity": "sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw==",
+      "integrity": "sha1-gqm85FuVRB9heiQBHcidEtp/TuY=",
       "dev": true,
       "requires": {
         "@babel/helper-module-transforms": "^7.1.0",
@@ -1922,7 +1922,7 @@
     "@babel/plugin-transform-modules-commonjs": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.2.0.tgz",
-      "integrity": "sha512-V6y0uaUQrQPXUrmj+hgnks8va2L0zcZymeU7TtWEgdRLNkceafKXEduv7QzgQAE4lT+suwooG9dC7LFhdRAbVQ==",
+      "integrity": "sha1-xPGTP1mR1RRenPrR39hI6hcn9AQ=",
       "dev": true,
       "requires": {
         "@babel/helper-module-transforms": "^7.1.0",
@@ -1933,7 +1933,7 @@
     "@babel/plugin-transform-modules-systemjs": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.2.0.tgz",
-      "integrity": "sha512-aYJwpAhoK9a+1+O625WIjvMY11wkB/ok0WClVwmeo3mCjcNRjt+/8gHWrB5i+00mUju0gWsBkQnPpdvQ7PImmQ==",
+      "integrity": "sha1-kSv+nl/5gpJMgdCTfJLSSZS7kGg=",
       "dev": true,
       "requires": {
         "@babel/helper-hoist-variables": "^7.0.0",
@@ -1943,7 +1943,7 @@
     "@babel/plugin-transform-modules-umd": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz",
-      "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==",
+      "integrity": "sha1-dnjOdRafCHe46yI1U4wHQmjdAa4=",
       "dev": true,
       "requires": {
         "@babel/helper-module-transforms": "^7.1.0",
@@ -1962,7 +1962,7 @@
     "@babel/plugin-transform-new-target": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz",
-      "integrity": "sha512-yin069FYjah+LbqfGeTfzIBODex/e++Yfa0rH0fpfam9uTbuEeEOx5GLGr210ggOV77mVRNoeqSYqeuaqSzVSw==",
+      "integrity": "sha1-ro+9iVF/p4ktIOZWTmQeh3DDqko=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0"
@@ -1971,7 +1971,7 @@
     "@babel/plugin-transform-object-super": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz",
-      "integrity": "sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg==",
+      "integrity": "sha1-s11MEPVrq11lAEfa0PHY6IFLZZg=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0",
@@ -1981,7 +1981,7 @@
     "@babel/plugin-transform-parameters": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.2.0.tgz",
-      "integrity": "sha512-kB9+hhUidIgUoBQ0MsxMewhzr8i60nMa2KgeJKQWYrqQpqcBYtnpR+JgkadZVZoaEZ/eKu9mclFaVwhRpLNSzA==",
+      "integrity": "sha1-DVrRXcgF4uqGbfTdZoK/520UCMI=",
       "dev": true,
       "requires": {
         "@babel/helper-call-delegate": "^7.1.0",
@@ -2014,7 +2014,7 @@
     "@babel/plugin-transform-regenerator": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0.tgz",
-      "integrity": "sha512-sj2qzsEx8KDVv1QuJc/dEfilkg3RRPvPYx/VnKLtItVQRWt1Wqf5eVCOLZm29CiGFfYYsA3VPjfizTCV0S0Dlw==",
+      "integrity": "sha1-W0Foa07UC++HTX7WqEvdhJwT4ME=",
       "dev": true,
       "requires": {
         "regenerator-transform": "^0.13.3"
@@ -2023,7 +2023,7 @@
     "@babel/plugin-transform-shorthand-properties": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz",
-      "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==",
+      "integrity": "sha1-YzOu4vjW7n4oYVRXKYk0o7RhmPA=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0"
@@ -2041,7 +2041,7 @@
     "@babel/plugin-transform-sticky-regex": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz",
-      "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==",
+      "integrity": "sha1-oeRUtZlVYKnB4NU338FQYf0mh+E=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0",
@@ -2051,7 +2051,7 @@
     "@babel/plugin-transform-template-literals": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.2.0.tgz",
-      "integrity": "sha512-FkPix00J9A/XWXv4VoKJBMeSkyY9x/TqIh76wzcdfl57RJJcf8CehQ08uwfhCDNtRQYtHQKBTwKZDEyjE13Lwg==",
+      "integrity": "sha1-2H7QG46qx6kkc/YIyXwIneK6Hls=",
       "dev": true,
       "requires": {
         "@babel/helper-annotate-as-pure": "^7.0.0",
@@ -2061,7 +2061,7 @@
     "@babel/plugin-transform-typeof-symbol": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz",
-      "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==",
+      "integrity": "sha1-EX0rzsL79ktLWdH5gZiUaC0p8rI=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0"
@@ -2080,7 +2080,7 @@
     "@babel/plugin-transform-unicode-regex": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.2.0.tgz",
-      "integrity": "sha512-m48Y0lMhrbXEJnVUaYly29jRXbQ3ksxPrS1Tg8t+MHqzXhtBYAvI51euOBaoAlZLPHsieY9XPVMf80a5x0cPcA==",
+      "integrity": "sha1-TrjbFvly+Ku1BiwWG4sRVUat4Is=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0",
@@ -2163,7 +2163,7 @@
     "@babel/preset-typescript": {
       "version": "7.1.0",
       "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.1.0.tgz",
-      "integrity": "sha512-LYveByuF9AOM8WrsNne5+N79k1YxjNB6gmpCQsnuSBAcV8QUeB+ZUxQzL7Rz7HksPbahymKkq2qBR+o36ggFZA==",
+      "integrity": "sha1-Sa1uIIT/C/tfH3+ztedsQ01ELH8=",
       "dev": true,
       "requires": {
         "@babel/helper-plugin-utils": "^7.0.0",
@@ -2309,12 +2309,12 @@
     "@protobufjs/base64": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
-      "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
+      "integrity": "sha1-TIVzDlm5ofHzSQR9vyQpYDS7JzU="
     },
     "@protobufjs/codegen": {
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
-      "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
+      "integrity": "sha1-fvN/DQEPsCitGtWXIuUG2SYoFcs="
     },
     "@protobufjs/eventemitter": {
       "version": "1.1.0",
@@ -2358,7 +2358,7 @@
     "@types/accepts": {
       "version": "1.3.5",
       "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz",
-      "integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==",
+      "integrity": "sha1-w0vsEVz8dG4E/loFnfTOfns5FXU=",
       "requires": {
         "@types/node": "*"
       }
@@ -2394,7 +2394,7 @@
     "@types/connect": {
       "version": "3.4.32",
       "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz",
-      "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==",
+      "integrity": "sha1-qg6WFrlDXMrQK8UrW0VP/Cxwuig=",
       "requires": {
         "@types/node": "*"
       }
@@ -2420,7 +2420,7 @@
     "@types/cors": {
       "version": "2.8.4",
       "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.4.tgz",
-      "integrity": "sha512-ipZjBVsm2tF/n8qFGOuGBkUij9X9ZswVi9G3bx/6dz7POpVa6gVHcj1wsX/LVEn9MMF41fxK/PnZPPoTD1UFPw==",
+      "integrity": "sha1-UJkadZopwLiUknUQCMavenyCZ7A=",
       "requires": {
         "@types/express": "*"
       }
@@ -2428,7 +2428,7 @@
     "@types/events": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz",
-      "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA=="
+      "integrity": "sha1-gaZzHOTfQ2GeXIyUU4Oz5iqJ6oY="
     },
     "@types/express": {
       "version": "4.11.1",
@@ -2486,12 +2486,12 @@
     "@types/long": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz",
-      "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q=="
+      "integrity": "sha1-cZVR0jUtMBrIuB23Mqy2vcKNve8="
     },
     "@types/mime": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.0.tgz",
-      "integrity": "sha512-A2TAGbTFdBw9azHbpVd+/FkdW2T6msN1uct1O9bH3vTerEHKZhTXJUQXy+hNq1B0RagfU8U+KBdqiZpxjhOUQA=="
+      "integrity": "sha1-WnMG42fFObn2VDSZ3o3VGfrDeos="
     },
     "@types/node": {
       "version": "10.12.18",
@@ -2541,7 +2541,7 @@
     "@types/ws": {
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.1.tgz",
-      "integrity": "sha512-EzH8k1gyZ4xih/MaZTXwT2xOkPiIMSrhQ9b8wrlX88L0T02eYsddatQlwVFlEPyEqV0ChpdpNnE51QPH6NVT4Q==",
+      "integrity": "sha1-yno/N1aqEvYqCmIUXtFMbbJdWig=",
       "requires": {
         "@types/events": "*",
         "@types/node": "*"
@@ -2573,7 +2573,7 @@
     "@webassemblyjs/helper-buffer": {
       "version": "1.7.11",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.11.tgz",
-      "integrity": "sha512-MynuervdylPPh3ix+mKZloTcL06P8tenNH3sx6s0qE8SLR6DdwnfgA7Hc9NSYeob2jrW5Vql6GVlsQzKQCa13w==",
+      "integrity": "sha1-MSLUjcxslFbtmC3r4WyPNxAd85s=",
       "dev": true
     },
     "@webassemblyjs/helper-code-frame": {
@@ -2606,7 +2606,7 @@
     "@webassemblyjs/helper-wasm-section": {
       "version": "1.7.11",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.11.tgz",
-      "integrity": "sha512-8ZRY5iZbZdtNFE5UFunB8mmBEAbSI3guwbrsCl4fWdfRiAcvqQpeqd5KHhSWLL5wuxo53zcaGZDBU64qgn4I4Q==",
+      "integrity": "sha1-nJrEHs+fvP/8lvbSZ14t4zgR5oo=",
       "dev": true,
       "requires": {
         "@webassemblyjs/ast": "1.7.11",
@@ -2618,7 +2618,7 @@
     "@webassemblyjs/ieee754": {
       "version": "1.7.11",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.7.11.tgz",
-      "integrity": "sha512-Mmqx/cS68K1tSrvRLtaV/Lp3NZWzXtOHUW2IvDvl2sihAwJh4ACE0eL6A8FvMyDG9abes3saB6dMimLOs+HMoQ==",
+      "integrity": "sha1-yVg562N1ejGICq7HtlEtQZGsZAs=",
       "dev": true,
       "requires": {
         "@xtuc/ieee754": "^1.2.0"
@@ -2627,7 +2627,7 @@
     "@webassemblyjs/leb128": {
       "version": "1.7.11",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.7.11.tgz",
-      "integrity": "sha512-vuGmgZjjp3zjcerQg+JA+tGOncOnJLWVkt8Aze5eWQLwTQGNgVLcyOTqgSCxWTR4J42ijHbBxnuRaL1Rv7XMdw==",
+      "integrity": "sha1-1yZ6HunEWU/T9+NymIGOxlaH22M=",
       "dev": true,
       "requires": {
         "@xtuc/long": "4.2.1"
@@ -2636,13 +2636,13 @@
     "@webassemblyjs/utf8": {
       "version": "1.7.11",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.7.11.tgz",
-      "integrity": "sha512-C6GFkc7aErQIAH+BMrIdVSmW+6HSe20wg57HEC1uqJP8E/xpMjXqQUxkQw07MhNDSDcGpxI9G5JSNOQCqJk4sA==",
+      "integrity": "sha1-Btchjqn9yUpnk6qSIIFg2z0m7oI=",
       "dev": true
     },
     "@webassemblyjs/wasm-edit": {
       "version": "1.7.11",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.11.tgz",
-      "integrity": "sha512-FUd97guNGsCZQgeTPKdgxJhBXkUbMTY6hFPf2Y4OedXd48H97J+sOY2Ltaq6WGVpIH8o/TGOVNiVz/SbpEMJGg==",
+      "integrity": "sha1-jHTKR01PlR0B266b1wgU7iKoIAU=",
       "dev": true,
       "requires": {
         "@webassemblyjs/ast": "1.7.11",
@@ -2658,7 +2658,7 @@
     "@webassemblyjs/wasm-gen": {
       "version": "1.7.11",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.11.tgz",
-      "integrity": "sha512-U/KDYp7fgAZX5KPfq4NOupK/BmhDc5Kjy2GIqstMhvvdJRcER/kUsMThpWeRP8BMn4LXaKhSTggIJPOeYHwISA==",
+      "integrity": "sha1-m7upQvIjdWhqb7dZr816ycRdoag=",
       "dev": true,
       "requires": {
         "@webassemblyjs/ast": "1.7.11",
@@ -2671,7 +2671,7 @@
     "@webassemblyjs/wasm-opt": {
       "version": "1.7.11",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.11.tgz",
-      "integrity": "sha512-XynkOwQyiRidh0GLua7SkeHvAPXQV/RxsUeERILmAInZegApOUAIJfRuPYe2F7RcjOC9tW3Cb9juPvAC/sCqvg==",
+      "integrity": "sha1-szHo5874+OLwB9QsOjagWAp9bKc=",
       "dev": true,
       "requires": {
         "@webassemblyjs/ast": "1.7.11",
@@ -2683,7 +2683,7 @@
     "@webassemblyjs/wasm-parser": {
       "version": "1.7.11",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.11.tgz",
-      "integrity": "sha512-6lmXRTrrZjYD8Ng8xRyvyXQJYUQKYSXhJqXOBLw24rdiXsHAOlvw5PhesjdcaMadU/pyPQOJ5dHreMjBxwnQKg==",
+      "integrity": "sha1-bj0g+mo1GfawhO+Tka1YIR77Cho=",
       "dev": true,
       "requires": {
         "@webassemblyjs/ast": "1.7.11",
@@ -2722,7 +2722,7 @@
     "@xtuc/ieee754": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
-      "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+      "integrity": "sha1-7vAUoxRa5Hehy8AM0eVSM23Ot5A=",
       "dev": true
     },
     "@xtuc/long": {
@@ -2734,7 +2734,7 @@
     "abbrev": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
-      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+      "integrity": "sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg=",
       "dev": true
     },
     "accepts": {
@@ -2792,7 +2792,7 @@
     "ajv-errors": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz",
-      "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==",
+      "integrity": "sha1-81mGrOuRr63sQQL72FAUlQzvpk0=",
       "dev": true
     },
     "ajv-keywords": {
@@ -2861,7 +2861,7 @@
     "anymatch": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
-      "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
+      "integrity": "sha1-vLJLTzeTTZqnrBe0ra+J58du8us=",
       "dev": true,
       "requires": {
         "micromatch": "^3.1.4",
@@ -3181,7 +3181,7 @@
     "apollo-server-env": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.2.0.tgz",
-      "integrity": "sha512-wjJiI5nQWPBpNmpiLP389Ezpstp71szS6DHAeTgYLb/ulCw3CTuuA+0/E1bsThVWiQaDeHZE0sE3yI8q2zrYiA==",
+      "integrity": "sha1-Xuxdv0ZYH2Y/1mkrLgXH6K5tYDQ=",
       "requires": {
         "node-fetch": "^2.1.2",
         "util.promisify": "^1.0.0"
@@ -3190,7 +3190,7 @@
     "apollo-server-errors": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.2.0.tgz",
-      "integrity": "sha512-gV9EZG2tovFtT1cLuCTavnJu2DaKxnXPRNGSTo+SDI6IAk6cdzyW0Gje5N2+3LybI0Wq5KAbW6VLei31S4MWmg=="
+      "integrity": "sha1-W0UqHW/3ZEDrDxJ1EdxYAxqPPLU="
     },
     "apollo-server-express": {
       "version": "2.3.2",
@@ -3286,7 +3286,7 @@
     "aproba": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
-      "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
+      "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo="
     },
     "argparse": {
       "version": "1.0.10",
@@ -3382,7 +3382,7 @@
     "asn1.js": {
       "version": "4.10.1",
       "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
-      "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
+      "integrity": "sha1-ucK/WAXx5kqt7tbfOiv6+1pz9aA=",
       "dev": true,
       "requires": {
         "bn.js": "^4.0.0",
@@ -3424,7 +3424,7 @@
     "assertion-error": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
-      "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+      "integrity": "sha1-5gtrDo8wG9l+U3UhW9pAbIURjAs=",
       "dev": true
     },
     "assign-symbols": {
@@ -3452,12 +3452,12 @@
     "async-limiter": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
-      "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
+      "integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg="
     },
     "async-retry": {
       "version": "1.2.3",
       "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.2.3.tgz",
-      "integrity": "sha512-tfDb02Th6CE6pJUF2gjW5ZVjsgwlucVXOEQMvEX9JgSJMs9gAX+Nz3xRuJBKuUYjTSYORqvDBORdAQ3LU59g7Q==",
+      "integrity": "sha1-plIfM4NY0yKxoAEreQMMb0EdHOA=",
       "requires": {
         "retry": "0.12.0"
       }
@@ -3637,7 +3637,7 @@
     "base64-js": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz",
-      "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==",
+      "integrity": "sha1-yrHmEY8FEJXli1KBrqjBzSK/wOM=",
       "dev": true
     },
     "basic-auth": {
@@ -3668,7 +3668,7 @@
     "binary-extensions": {
       "version": "1.12.0",
       "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz",
-      "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==",
+      "integrity": "sha1-wteA9T1Fu6gxeokC1M7q86Y4WxQ=",
       "dev": true
     },
     "bluebird": {
@@ -3679,7 +3679,7 @@
     "bn.js": {
       "version": "4.11.8",
       "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
-      "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
+      "integrity": "sha1-LN4J617jQfSEdGuwMJsyU7GxRC8=",
       "dev": true
     },
     "body-parser": {
@@ -3752,7 +3752,7 @@
     "boxen": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz",
-      "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==",
+      "integrity": "sha1-VcbDmouljZxhrSLNh3Uy3rZlogs=",
       "dev": true,
       "requires": {
         "ansi-align": "^2.0.0",
@@ -3814,13 +3814,13 @@
     "browser-stdout": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
-      "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+      "integrity": "sha1-uqVZ7hTO1zRSIputcyZGfGH6vWA=",
       "dev": true
     },
     "browserify-aes": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
-      "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
+      "integrity": "sha1-Mmc0ZC9APavDADIJhTu3CtQo70g=",
       "dev": true,
       "requires": {
         "buffer-xor": "^1.0.3",
@@ -3834,7 +3834,7 @@
     "browserify-cipher": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
-      "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
+      "integrity": "sha1-jWR0wbhwv9q807z8wZNKEOlPFfA=",
       "dev": true,
       "requires": {
         "browserify-aes": "^1.0.4",
@@ -3845,7 +3845,7 @@
     "browserify-des": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
-      "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
+      "integrity": "sha1-OvTx9Zg5QDVy8cZiBDdfen9wPpw=",
       "dev": true,
       "requires": {
         "cipher-base": "^1.0.1",
@@ -3890,7 +3890,7 @@
     "browserify-zlib": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
-      "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+      "integrity": "sha1-KGlFnZqjviRf6P4sofRuLn9U1z8=",
       "dev": true,
       "requires": {
         "pako": "~1.0.5"
@@ -3932,7 +3932,7 @@
     "buffer-writer": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
-      "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw=="
+      "integrity": "sha1-zn64Gjj3gp2wnIc/L7t5LAyY7AQ="
     },
     "buffer-xor": {
       "version": "1.0.3",
@@ -4054,7 +4054,7 @@
     "capture-stack-trace": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz",
-      "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==",
+      "integrity": "sha1-psC74fOPOqC5Ijjstv9Cw0TUE10=",
       "dev": true
     },
     "catharsis": {
@@ -4078,7 +4078,7 @@
     "chai": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
-      "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==",
+      "integrity": "sha1-dgqnLPION5XoSxKHfODoNzeqKeU=",
       "dev": true,
       "requires": {
         "assertion-error": "^1.1.0",
@@ -4153,7 +4153,7 @@
     "chokidar": {
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz",
-      "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==",
+      "integrity": "sha1-NW/04rDo5D4yLRijckYLvPOszSY=",
       "dev": true,
       "requires": {
         "anymatch": "^2.0.0",
@@ -4179,7 +4179,7 @@
     "chrome-trace-event": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz",
-      "integrity": "sha512-xDbVgyfDTT2piup/h8dK/y4QZfJRSa73bw1WZ8b4XM1o7fsFubUVGYcE+1ANtOzJJELGpYoG2961z0Z6OAld9A==",
+      "integrity": "sha1-Rakb0sIMlBHwljtarrmhuV4JzEg=",
       "dev": true,
       "requires": {
         "tslib": "^1.9.0"
@@ -4188,13 +4188,13 @@
     "ci-info": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
-      "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==",
+      "integrity": "sha1-LKINu5zrMtRSSmgzAzE/AwSx5Jc=",
       "dev": true
     },
     "cipher-base": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
-      "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+      "integrity": "sha1-h2Dk7MJy9MNjUy+SbYdKriwTl94=",
       "dev": true,
       "requires": {
         "inherits": "^2.0.1",
@@ -4466,7 +4466,7 @@
     "configstore": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz",
-      "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==",
+      "integrity": "sha1-xvJd767vJt8S3TNBSwAf6BpUP48=",
       "dev": true,
       "requires": {
         "dot-prop": "^4.1.0",
@@ -4577,7 +4577,7 @@
     "copy-concurrently": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
-      "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==",
+      "integrity": "sha1-kilzmMrjSTf8r9bsgTnBgFHwteA=",
       "requires": {
         "aproba": "^1.1.1",
         "fs-write-stream-atomic": "^1.0.8",
@@ -4595,7 +4595,7 @@
     "copy-webpack-plugin": {
       "version": "4.6.0",
       "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.6.0.tgz",
-      "integrity": "sha512-Y+SQCF+0NoWQryez2zXn5J5knmr9z/9qSQt7fbL78u83rxmigOy8X5+BFn8CFSuX+nKT8gpYwJX68ekqtQt6ZA==",
+      "integrity": "sha1-5/QN2KaEd9QF3Rt6hUquMksVi64=",
       "requires": {
         "cacache": "^10.0.4",
         "find-cache-dir": "^1.0.0",
@@ -4620,7 +4620,7 @@
     "cors": {
       "version": "2.8.5",
       "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
-      "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+      "integrity": "sha1-6sEdpRWS3Ya58G9uesKTs9+HXSk=",
       "requires": {
         "object-assign": "^4",
         "vary": "^1"
@@ -4634,7 +4634,7 @@
     "create-ecdh": {
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
-      "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
+      "integrity": "sha1-yREbbzMEXEaX8UR4f5JUzcd8Rf8=",
       "dev": true,
       "requires": {
         "bn.js": "^4.1.0",
@@ -4653,7 +4653,7 @@
     "create-hash": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
-      "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
+      "integrity": "sha1-iJB4rxGmN1a8+1m9IhmWvjqe8ZY=",
       "dev": true,
       "requires": {
         "cipher-base": "^1.0.1",
@@ -4666,7 +4666,7 @@
     "create-hmac": {
       "version": "1.1.7",
       "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
-      "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
+      "integrity": "sha1-aRcMeLOrlXFHsriwRXLkfq0iQ/8=",
       "dev": true,
       "requires": {
         "cipher-base": "^1.0.3",
@@ -4709,7 +4709,7 @@
     "crypto-browserify": {
       "version": "3.12.0",
       "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
-      "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
+      "integrity": "sha1-OWz58xN/A+S45TLFj2mCVOAPgOw=",
       "dev": true,
       "requires": {
         "browserify-cipher": "^1.0.0",
@@ -4790,7 +4790,7 @@
     "deep-eql": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
-      "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
+      "integrity": "sha1-38lARACtHI/gI+faHfHBR8S0RN8=",
       "dev": true,
       "requires": {
         "type-detect": "^4.0.0"
@@ -4816,7 +4816,7 @@
     "define-properties": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
-      "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+      "integrity": "sha1-z4jabL7ib+bbcJT2HYcMvYTO6fE=",
       "requires": {
         "object-keys": "^1.0.12"
       }
@@ -4904,13 +4904,13 @@
     "diff": {
       "version": "3.5.0",
       "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
-      "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
+      "integrity": "sha1-gAwN0eCov7yVg1wgKtIg/jF+WhI=",
       "dev": true
     },
     "diffie-hellman": {
       "version": "5.0.3",
       "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
-      "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
+      "integrity": "sha1-QOjumPVaIUlgcUaSHGPhrl89KHU=",
       "dev": true,
       "requires": {
         "bn.js": "^4.1.0",
@@ -4921,7 +4921,7 @@
     "dir-glob": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz",
-      "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==",
+      "integrity": "sha1-CyBdK2rvmCOMooZZioIE0p0KADQ=",
       "requires": {
         "arrify": "^1.0.1",
         "path-type": "^3.0.0"
@@ -4969,7 +4969,7 @@
     "domain-browser": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
-      "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
+      "integrity": "sha1-PTH1AZGmdJ3RN1p/Ui6CPULlTto=",
       "dev": true
     },
     "dot-case": {
@@ -4984,7 +4984,7 @@
     "dot-prop": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
-      "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==",
+      "integrity": "sha1-HxngwuGqDjJ5fEl5nyg3rGr2nFc=",
       "dev": true,
       "requires": {
         "is-obj": "^1.0.0"
@@ -4993,7 +4993,7 @@
     "dotenv": {
       "version": "6.2.0",
       "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz",
-      "integrity": "sha1-lBwEEFNdlCyL7PKNPzV9vZ1HYGQ="
+      "integrity": "sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w=="
     },
     "dtrace-provider": {
       "version": "0.8.6",
@@ -5043,7 +5043,7 @@
     "elliptic": {
       "version": "6.4.1",
       "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz",
-      "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==",
+      "integrity": "sha1-wtC3d2kRuGcixjLDwGxg8vgZk5o=",
       "dev": true,
       "requires": {
         "bn.js": "^4.4.0",
@@ -5076,7 +5076,7 @@
     "end-of-stream": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
-      "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
+      "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=",
       "requires": {
         "once": "^1.4.0"
       }
@@ -5141,7 +5141,7 @@
     "es-to-primitive": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
-      "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
+      "integrity": "sha1-7fckeAM0VujdqO8J4ArZZQcH83c=",
       "requires": {
         "is-callable": "^1.1.4",
         "is-date-object": "^1.0.1",
@@ -5549,7 +5549,7 @@
     "esrecurse": {
       "version": "4.2.1",
       "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
-      "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
+      "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=",
       "dev": true,
       "requires": {
         "estraverse": "^4.1.0"
@@ -5574,7 +5574,7 @@
     "eventemitter3": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz",
-      "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA=="
+      "integrity": "sha1-CQtNbNvWRe0Qv3UNS1QHlC17oWM="
     },
     "events": {
       "version": "3.0.0",
@@ -5585,7 +5585,7 @@
     "evp_bytestokey": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
-      "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+      "integrity": "sha1-f8vbGY3HGVlDLv4ThCaE4FJaywI=",
       "dev": true,
       "requires": {
         "md5.js": "^1.3.4",
@@ -5695,7 +5695,7 @@
         "debug": {
           "version": "2.6.9",
           "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=",
           "requires": {
             "ms": "2.0.0"
           }
@@ -5899,7 +5899,7 @@
     "figgy-pudding": {
       "version": "3.5.1",
       "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz",
-      "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==",
+      "integrity": "sha1-hiRwESkBxyeg5JWoB0S9W6odZ5A=",
       "dev": true
     },
     "figures": {
@@ -5980,7 +5980,7 @@
     "finalhandler": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
-      "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
+      "integrity": "sha1-7r9O2EAHnIP0JJA4ydcDAIMBsQU=",
       "requires": {
         "debug": "2.6.9",
         "encodeurl": "~1.0.2",
@@ -6082,7 +6082,7 @@
     "flush-write-stream": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz",
-      "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==",
+      "integrity": "sha1-xdWG7zivYJdlC0m8QbVfq7GfNb0=",
       "requires": {
         "inherits": "^2.0.1",
         "readable-stream": "^2.0.4"
@@ -6194,8 +6194,7 @@
         "ansi-regex": {
           "version": "2.1.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "aproba": {
           "version": "1.2.0",
@@ -6238,8 +6237,7 @@
         "code-point-at": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "concat-map": {
           "version": "0.0.1",
@@ -6250,8 +6248,7 @@
         "console-control-strings": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -6368,8 +6365,7 @@
         "inherits": {
           "version": "2.0.3",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "ini": {
           "version": "1.3.5",
@@ -6381,7 +6377,6 @@
           "version": "1.0.0",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -6411,7 +6406,6 @@
           "version": "2.3.5",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.2",
             "yallist": "^3.0.0"
@@ -6430,7 +6424,6 @@
           "version": "0.5.1",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -6524,7 +6517,6 @@
           "version": "1.4.0",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -6610,8 +6602,7 @@
         "safe-buffer": {
           "version": "5.1.2",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "safer-buffer": {
           "version": "2.1.2",
@@ -6647,7 +6638,6 @@
           "version": "1.0.2",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
@@ -6667,7 +6657,6 @@
           "version": "3.0.1",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
@@ -6711,14 +6700,12 @@
         "wrappy": {
           "version": "1.0.2",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "yallist": {
           "version": "3.0.3",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         }
       }
     },
@@ -6736,7 +6723,7 @@
     "fuse.js": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.3.0.tgz",
-      "integrity": "sha512-ESBRkGLWMuVkapqYCcNO1uqMg5qbCKkgb+VS6wsy17Rix0/cMS9kSOZoYkjH8Ko//pgJ/EEGu0GTjk2mjX2LGQ=="
+      "integrity": "sha1-Hk/hcqYGhyMPtUpcskfrluLn6IU="
     },
     "get-caller-file": {
       "version": "1.0.3",
@@ -6918,7 +6905,7 @@
     "graphql-request": {
       "version": "1.8.2",
       "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-1.8.2.tgz",
-      "integrity": "sha512-dDX2M+VMsxXFCmUX0Vo0TopIZIX4ggzOtiCsThgtrKR4niiaagsGTDIHj3fsOMFETpa064vzovI+4YV4QnMbcg==",
+      "integrity": "sha1-OY0QrhXFhWdnQb3j/AHVypSPj74=",
       "dev": true,
       "requires": {
         "cross-fetch": "2.2.2"
@@ -6927,7 +6914,7 @@
     "graphql-subscriptions": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-1.0.0.tgz",
-      "integrity": "sha512-+ytmryoHF1LVf58NKEaNPRUzYyXplm120ntxfPcgOBC7TnK7Tv/4VRHeh4FAR9iL+O1bqhZs4nkibxQ+OA5cDQ==",
+      "integrity": "sha1-R1JnaUs71GWvZHfbq0Jjo/YnArg=",
       "requires": {
         "iterall": "^1.2.1"
       }
@@ -7001,7 +6988,7 @@
     "growl": {
       "version": "1.10.5",
       "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
-      "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
+      "integrity": "sha1-8nNdwig2dPpnR4sQGBBZNVw2nl4=",
       "dev": true
     },
     "handlebars": {
@@ -7113,7 +7100,7 @@
     "hash.js": {
       "version": "1.1.7",
       "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
-      "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+      "integrity": "sha1-C6vKU46NTuSg+JiNaIZlN6ADz0I=",
       "dev": true,
       "requires": {
         "inherits": "^2.0.3",
@@ -7196,7 +7183,7 @@
     "ieee754": {
       "version": "1.1.12",
       "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz",
-      "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==",
+      "integrity": "sha1-UL8k5bnIu5ivSWTJQc2wkY2ntgs=",
       "dev": true
     },
     "iferr": {
@@ -7467,12 +7454,12 @@
     "is-callable": {
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
-      "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA=="
+      "integrity": "sha1-HhrfIZ4e62hNaR+dagX/DTCiTXU="
     },
     "is-ci": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz",
-      "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==",
+      "integrity": "sha1-43ecjuF/zPQoSI9uKBGH8uYyhBw=",
       "dev": true,
       "requires": {
         "ci-info": "^1.5.0"
@@ -7696,7 +7683,7 @@
     "is-symbol": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
-      "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
+      "integrity": "sha1-oFX2rlcZLK7jKeeoYBGLSXqVDzg=",
       "requires": {
         "has-symbols": "^1.0.0"
       }
@@ -7846,7 +7833,7 @@
     "jsdoc-babel": {
       "version": "0.5.0",
       "resolved": "https://registry.npmjs.org/jsdoc-babel/-/jsdoc-babel-0.5.0.tgz",
-      "integrity": "sha512-PYfTbc3LNTeR8TpZs2M94NLDWqARq0r9gx3SvuziJfmJS7/AeMKvtj0xjzOX0R/4MOVA7/FqQQK7d6U0iEoztQ==",
+      "integrity": "sha1-che4ggRp/mANzP3uiVZIxqDdSi4=",
       "dev": true,
       "requires": {
         "jsdoc-regex": "^1.0.1",
@@ -7897,7 +7884,7 @@
     "json-parse-better-errors": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
-      "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+      "integrity": "sha1-u4Z8+zRQ5pEHwTHRxRS6s9yLyqk=",
       "dev": true
     },
     "json-schema-traverse": {
@@ -7919,7 +7906,7 @@
     "jsonwebtoken": {
       "version": "8.4.0",
       "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.4.0.tgz",
-      "integrity": "sha512-coyXjRTCy0pw5WYBpMvWOMN+Kjaik2MwTUIq9cna/W7NpO9E+iYbumZONAz3hcr+tXFJECoQVrtmIoC3Oz0gvg==",
+      "integrity": "sha1-h1f3tMt0QNhtXi877O+nBTbI5Go=",
       "requires": {
         "jws": "^3.1.5",
         "lodash.includes": "^4.3.0",
@@ -8096,7 +8083,7 @@
     "jwa": {
       "version": "1.1.6",
       "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz",
-      "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==",
+      "integrity": "sha1-hyQOdsmAjb3hh4PPImTvSSnuUOY=",
       "requires": {
         "buffer-equal-constant-time": "1.0.1",
         "ecdsa-sig-formatter": "1.0.10",
@@ -8106,7 +8093,7 @@
     "jws": {
       "version": "3.1.5",
       "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz",
-      "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==",
+      "integrity": "sha1-gNEtBbKT0ehB58uLTmnlYa3Pg08=",
       "requires": {
         "jwa": "^1.1.5",
         "safe-buffer": "^5.0.1"
@@ -8502,7 +8489,7 @@
     "long": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
-      "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
+      "integrity": "sha1-mntxz7fTYaGU6lVSQckvdGjVvyg="
     },
     "longest": {
       "version": "1.0.1",
@@ -8535,7 +8522,7 @@
     "lowercase-keys": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
-      "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==",
+      "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=",
       "dev": true
     },
     "lru-cache": {
@@ -8550,7 +8537,7 @@
     "make-dir": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
-      "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
+      "integrity": "sha1-ecEDO4BRW9bSTsmTPoYMp17ifww=",
       "requires": {
         "pify": "^3.0.0"
       }
@@ -8594,7 +8581,7 @@
     "md5.js": {
       "version": "1.3.5",
       "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
-      "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
+      "integrity": "sha1-tdB7jjIW4+J81yjXL3DR5qNCAF8=",
       "dev": true,
       "requires": {
         "hash-base": "^3.0.0",
@@ -8605,7 +8592,7 @@
         "safe-buffer": {
           "version": "5.1.2",
           "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=",
           "dev": true
         }
       }
@@ -8672,7 +8659,7 @@
     "miller-rabin": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
-      "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
+      "integrity": "sha1-8IA1HIZbDcViqEYpZtqlNUPHik0=",
       "dev": true,
       "requires": {
         "bn.js": "^4.0.0",
@@ -8706,7 +8693,7 @@
     "minimalistic-assert": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
-      "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+      "integrity": "sha1-LhlN4ERibUoQ5/f7wAznPoPk1cc=",
       "dev": true
     },
     "minimalistic-crypto-utils": {
@@ -8731,7 +8718,7 @@
     "mississippi": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz",
-      "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==",
+      "integrity": "sha1-NEKlCPr8KFAEhv7qmUCWduTuWm8=",
       "requires": {
         "concat-stream": "^1.5.0",
         "duplexify": "^3.4.2",
@@ -8781,7 +8768,7 @@
     "mocha": {
       "version": "5.2.0",
       "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz",
-      "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==",
+      "integrity": "sha1-bYrlCPWRZ/lA8rWzxKYSrlDJCuY=",
       "dev": true,
       "requires": {
         "browser-stdout": "1.3.1",
@@ -9127,7 +9114,7 @@
     "object-keys": {
       "version": "1.0.12",
       "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz",
-      "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag=="
+      "integrity": "sha1-CcU4VTd1dTEMymL1W7M0q/97PtI="
     },
     "object-path": {
       "version": "0.11.4",
@@ -9278,7 +9265,7 @@
     "output-file-sync": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-2.0.1.tgz",
-      "integrity": "sha512-mDho4qm7WgIXIGf4eYU1RHN2UU5tPfVYVSRwDJw0uTmj35DQUt/eNp19N7v6T3SrR0ESTEf2up2CGO73qI35zQ==",
+      "integrity": "sha1-9TEYKC9fVTwnmVQXkrcjpMcUMMA=",
       "dev": true,
       "requires": {
         "graceful-fs": "^4.1.11",
@@ -9559,7 +9546,7 @@
     "path-type": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
-      "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
+      "integrity": "sha1-zvMdyOCho7sNEFwM2Xzzv0f0428=",
       "requires": {
         "pify": "^3.0.0"
       }
@@ -9578,7 +9565,7 @@
     "pbkdf2": {
       "version": "3.0.17",
       "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
-      "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
+      "integrity": "sha1-l2wgZTBhexTrsyEUI597CTNuk6Y=",
       "dev": true,
       "requires": {
         "create-hash": "^1.1.2",
@@ -9662,7 +9649,7 @@
     },
     "pinkie-promise": {
       "version": "2.0.1",
-      "resolved": "http://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
       "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
       "dev": true,
       "requires": {
@@ -9711,7 +9698,7 @@
     "postgres-interval": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.1.2.tgz",
-      "integrity": "sha512-fC3xNHeTskCxL1dC8KOtxXt7YeFmlbTYtn7ul8MkVERuTmf7pI4DrkAxcw3kh1fQ9uz4wQmd03a1mRiXUZChfQ==",
+      "integrity": "sha1-v3H/kCY18hyyQaAT/EIdgdHbFak=",
       "requires": {
         "xtend": "^4.0.0"
       }
@@ -9747,7 +9734,7 @@
     "process-nextick-args": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
-      "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
+      "integrity": "sha1-o31zL0JxtKsa0HDTVQjoKQeI/6o="
     },
     "progress": {
       "version": "2.0.3",
@@ -9780,7 +9767,7 @@
     "protobufjs": {
       "version": "6.8.8",
       "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz",
-      "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==",
+      "integrity": "sha1-yLTxKC/XqQ5vWxCe0RyEr4KQjnw=",
       "requires": {
         "@protobufjs/aspromise": "^1.1.2",
         "@protobufjs/base64": "^1.1.2",
@@ -9826,7 +9813,7 @@
     "public-encrypt": {
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
-      "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
+      "integrity": "sha1-T8ydd6B+SLp1J+fL4N4z0HATMeA=",
       "dev": true,
       "requires": {
         "bn.js": "^4.1.0",
@@ -9962,7 +9949,7 @@
     "pump": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
-      "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
+      "integrity": "sha1-Ejma3W5M91Jtlzy8i1zi4pCLOQk=",
       "requires": {
         "end-of-stream": "^1.1.0",
         "once": "^1.3.1"
@@ -9971,7 +9958,7 @@
     "pumpify": {
       "version": "1.5.1",
       "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
-      "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
+      "integrity": "sha1-NlE74karJ1cLGjdKXOJ4v9dDcM4=",
       "requires": {
         "duplexify": "^3.6.0",
         "inherits": "^2.0.3",
@@ -10009,7 +9996,7 @@
     "randombytes": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz",
-      "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==",
+      "integrity": "sha1-0wLFIpSFiISKjTAMkytEwkIx2oA=",
       "dev": true,
       "requires": {
         "safe-buffer": "^5.1.0"
@@ -10018,7 +10005,7 @@
     "randomfill": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
-      "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
+      "integrity": "sha1-ySGW/IarQr6YPxvzF3giSTHWFFg=",
       "dev": true,
       "requires": {
         "randombytes": "^2.0.5",
@@ -10033,7 +10020,7 @@
     "rc": {
       "version": "1.2.8",
       "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
-      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+      "integrity": "sha1-zZJL9SAKB1uDwYjNa54hG3/A0+0=",
       "dev": true,
       "requires": {
         "deep-extend": "^0.6.0",
@@ -10044,7 +10031,7 @@
       "dependencies": {
         "minimist": {
           "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
           "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
           "dev": true
         }
@@ -10178,7 +10165,7 @@
     "readdirp": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
-      "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
+      "integrity": "sha1-DodiKjMlqjPokihcr4tOhGUppSU=",
       "dev": true,
       "requires": {
         "graceful-fs": "^4.1.11",
@@ -10291,13 +10278,13 @@
     "regenerate": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
-      "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==",
+      "integrity": "sha1-SoVuxLVuQHfFV1icroXnpMiGmhE=",
       "dev": true
     },
     "regenerate-unicode-properties": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz",
-      "integrity": "sha512-s5NGghCE4itSlUS+0WUj88G6cfMVMmH8boTPNvABf8od+2dhT9WDlWu8n01raQAJZMOK8Ch6jSexaRO7swd6aw==",
+      "integrity": "sha1-EHQFr8xKGQ7F7UUOyqAO0Mr6ekw=",
       "dev": true,
       "requires": {
         "regenerate": "^1.4.0"
@@ -10311,7 +10298,7 @@
     "regenerator-transform": {
       "version": "0.13.3",
       "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.13.3.tgz",
-      "integrity": "sha512-5ipTrZFSq5vU2YoGoww4uaRVAK4wyYC4TSICibbfEPOruUu8FFP7ErV0BjmbIOEpn3O/k9na9UEdYR/3m7N6uA==",
+      "integrity": "sha1-JkvZ/zioziSwbgY2SWsshWtXvLs=",
       "dev": true,
       "requires": {
         "private": "^0.1.6"
@@ -10385,7 +10372,7 @@
     "regexpu-core": {
       "version": "4.4.0",
       "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.4.0.tgz",
-      "integrity": "sha512-eDDWElbwwI3K0Lo6CqbQbA6FwgtCz4kYTarrri1okfkRLZAqstU+B3voZBCjg8Fl6iq0gXrJG6MvRgLthfvgOA==",
+      "integrity": "sha1-jUPg0SZog5aXIDRecMJ17grsDTI=",
       "dev": true,
       "requires": {
         "regenerate": "^1.4.0",
@@ -10399,7 +10386,7 @@
     "registry-auth-token": {
       "version": "3.3.2",
       "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz",
-      "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==",
+      "integrity": "sha1-hR/UkDjuy1hpERFa+EUmDuyYPyA=",
       "dev": true,
       "requires": {
         "rc": "^1.1.6",
@@ -10418,13 +10405,13 @@
     "regjsgen": {
       "version": "0.5.0",
       "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz",
-      "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==",
+      "integrity": "sha1-p2NNwI+JIJwgSa3aNSVxH7lyZd0=",
       "dev": true
     },
     "regjsparser": {
       "version": "0.6.0",
       "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz",
-      "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==",
+      "integrity": "sha1-8eaui32iuulsmTmbhozWyTOiupw=",
       "dev": true,
       "requires": {
         "jsesc": "~0.5.0"
@@ -10572,7 +10559,7 @@
     "ripemd160": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
-      "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+      "integrity": "sha1-ocGm9iR1FXe6XQeRTLyShQWFiQw=",
       "dev": true,
       "requires": {
         "hash-base": "^3.0.0",
@@ -10670,7 +10657,7 @@
     "send": {
       "version": "0.16.2",
       "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
-      "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
+      "integrity": "sha1-bsyh4PjBVtFBWXVZhI32RzCmu8E=",
       "requires": {
         "debug": "2.6.9",
         "depd": "~1.1.2",
@@ -10710,7 +10697,7 @@
     "serialize-javascript": {
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.5.0.tgz",
-      "integrity": "sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ=="
+      "integrity": "sha1-GqM2FiyIqJDdrVOEuuvJOmVRYf4="
     },
     "serve-favicon": {
       "version": "2.5.0",
@@ -10734,7 +10721,7 @@
     "serve-static": {
       "version": "1.13.2",
       "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
-      "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
+      "integrity": "sha1-CV6Ecv1bRiN9tQzkhqQ/S4bGzsE=",
       "requires": {
         "encodeurl": "~1.0.2",
         "escape-html": "~1.0.3",
@@ -10782,7 +10769,7 @@
     "sha.js": {
       "version": "2.4.11",
       "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
-      "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+      "integrity": "sha1-N6XPC4HsvGlD3hCbopYNGyZYSuc=",
       "dev": true,
       "requires": {
         "inherits": "^2.0.1",
@@ -11044,7 +11031,7 @@
     "split": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
-      "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
+      "integrity": "sha1-YFvZvjA6pZ+zX5Ip++oN3snqB9k=",
       "requires": {
         "through": "2"
       }
@@ -11066,7 +11053,7 @@
     "ssri": {
       "version": "5.3.0",
       "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz",
-      "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==",
+      "integrity": "sha1-ujhyycbTOgcEp9cf8EXl7EiZnQY=",
       "requires": {
         "safe-buffer": "^5.1.1"
       }
@@ -11137,7 +11124,7 @@
     "stream-http": {
       "version": "2.8.3",
       "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz",
-      "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==",
+      "integrity": "sha1-stJCRpKIpaJ+xP6JM6z2I95lFPw=",
       "dev": true,
       "requires": {
         "builtin-status-codes": "^3.0.0",
@@ -11165,7 +11152,7 @@
         "string_decoder": {
           "version": "1.1.1",
           "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
           "dev": true,
           "requires": {
             "safe-buffer": "~5.1.0"
@@ -11250,7 +11237,7 @@
     "subscriptions-transport-ws": {
       "version": "0.9.15",
       "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.15.tgz",
-      "integrity": "sha512-f9eBfWdHsePQV67QIX+VRhf++dn1adyC/PZHP6XI5AfKnZ4n0FW+v5omxwdHVpd4xq2ZijaHEcmlQrhBY79ZWQ==",
+      "integrity": "sha1-aKi3ugA32MSJ+y9aEC0UlNspfQ0=",
       "requires": {
         "backo2": "^1.0.2",
         "eventemitter3": "^3.1.0",
@@ -11262,7 +11249,7 @@
         "ws": {
           "version": "5.2.2",
           "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
-          "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
+          "integrity": "sha1-3/7xSGa46NyRM1glFNG++vlumA8=",
           "requires": {
             "async-limiter": "~1.0.0"
           }
@@ -11632,7 +11619,7 @@
     "through2": {
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
-      "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+      "integrity": "sha1-AcHjnrMdB8t9A6lqcIIyYLIxMs0=",
       "requires": {
         "readable-stream": "~2.3.6",
         "xtend": "~4.0.1"
@@ -11655,7 +11642,7 @@
         "string_decoder": {
           "version": "1.1.1",
           "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
           "requires": {
             "safe-buffer": "~5.1.0"
           }
@@ -11679,7 +11666,7 @@
     "timers-browserify": {
       "version": "2.0.10",
       "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz",
-      "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==",
+      "integrity": "sha1-HSjj0qrfHVpZlsTp+VYBzQU0gK4=",
       "dev": true,
       "requires": {
         "setimmediate": "^1.0.4"
@@ -11772,7 +11759,7 @@
     "touch": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
-      "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
+      "integrity": "sha1-/jZfX3XsntTlaCXgu3bSSrdK+Ds=",
       "dev": true,
       "requires": {
         "nopt": "~1.0.10"
@@ -11805,7 +11792,7 @@
     "tslib": {
       "version": "1.9.3",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
-      "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
+      "integrity": "sha1-1+TdeSRdhUKMTX5IIqeZF5VMooY=",
       "dev": true
     },
     "tslint": {
@@ -11855,7 +11842,7 @@
     "type-detect": {
       "version": "4.0.8",
       "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
-      "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+      "integrity": "sha1-dkb7XxiHHPu3dJ5pvTmmOI63RQw=",
       "dev": true
     },
     "type-is": {
@@ -11930,7 +11917,7 @@
         "debug": {
           "version": "2.6.9",
           "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=",
           "dev": true,
           "requires": {
             "ms": "2.0.0"
@@ -11964,13 +11951,13 @@
     "unicode-canonical-property-names-ecmascript": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
-      "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==",
+      "integrity": "sha1-JhmADEyCWADv3YNDr33Zkzy+KBg=",
       "dev": true
     },
     "unicode-match-property-ecmascript": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz",
-      "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==",
+      "integrity": "sha1-jtKjJWmWG86SJ9Cc0/+7j+1fAgw=",
       "dev": true,
       "requires": {
         "unicode-canonical-property-names-ecmascript": "^1.0.4",
@@ -11980,13 +11967,13 @@
     "unicode-match-property-value-ecmascript": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz",
-      "integrity": "sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ==",
+      "integrity": "sha1-nx3HaSbWzPRSMQVk/YNKzgWWY9Q=",
       "dev": true
     },
     "unicode-property-aliases-ecmascript": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz",
-      "integrity": "sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg==",
+      "integrity": "sha1-WlM/MbQxfqdvF9gH+g0RZUYRHdA=",
       "dev": true
     },
     "union-value": {
@@ -12096,13 +12083,13 @@
     "upath": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz",
-      "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==",
+      "integrity": "sha1-NSVll+RqWB20eT0M5H+prr/J+r0=",
       "dev": true
     },
     "update-notifier": {
       "version": "2.5.0",
       "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz",
-      "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==",
+      "integrity": "sha1-0HRFk+E/Fh5AassdlAi3LK0Ir/Y=",
       "dev": true,
       "requires": {
         "boxen": "^1.2.1",
@@ -12236,7 +12223,7 @@
     "util.promisify": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz",
-      "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==",
+      "integrity": "sha1-RA9xZaRZyaFtwUXrjnLzVocJcDA=",
       "requires": {
         "define-properties": "^1.1.2",
         "object.getownpropertydescriptors": "^2.0.3"
@@ -12345,7 +12332,7 @@
     "watchpack": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",
-      "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==",
+      "integrity": "sha1-S8EsLr6KonenHx0/FNaFx7RGzQA=",
       "dev": true,
       "requires": {
         "chokidar": "^2.0.2",
@@ -12629,7 +12616,7 @@
     "webpack-sources": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz",
-      "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==",
+      "integrity": "sha1-KijcufH0X+lg2PFJMlK17mUw+oU=",
       "dev": true,
       "requires": {
         "source-list-map": "^2.0.0",
@@ -12666,7 +12653,7 @@
     "widest-line": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz",
-      "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==",
+      "integrity": "sha1-dDh2RzDsfvQ4HOTfgvuYpTFCo/w=",
       "dev": true,
       "requires": {
         "string-width": "^2.1.1"
@@ -12712,7 +12699,7 @@
     "worker-farm": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz",
-      "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==",
+      "integrity": "sha1-rsxAWXb6talVJhgIRvDboojzpKA=",
       "dev": true,
       "requires": {
         "errno": "~0.1.7"
diff --git a/src/ldap/export/group.js b/src/ldap/export/group.js
new file mode 100644
index 0000000..fc2be13
--- /dev/null
+++ b/src/ldap/export/group.js
@@ -0,0 +1,453 @@
+"use strict";
+/**
+ * @file Ce fichier contient la classe de l'API du LDAP qui gère les opérations sur les groupes. Elle est destinée à être exportée pour être utilisée par les resolvers.
+ * @author hawkspar
+ */
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+var __generator = (this && this.__generator) || function (thisArg, body) {
+    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
+    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
+    function verb(n) { return function (v) { return step([n, v]); }; }
+    function step(op) {
+        if (f) throw new TypeError("Generator is already executing.");
+        while (_) try {
+            if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
+            if (y = 0, t) op = [0, t.value];
+            switch (op[0]) {
+                case 0: case 1: t = op; break;
+                case 4: _.label++; return { value: op[1], done: false };
+                case 5: _.label++; y = op[1]; op = [0]; continue;
+                case 7: op = _.ops.pop(); _.trys.pop(); continue;
+                default:
+                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
+                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
+                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
+                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
+                    if (t[2]) _.ops.pop();
+                    _.trys.pop(); continue;
+            }
+            op = body.call(thisArg, _);
+        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
+        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
+    }
+};
+exports.__esModule = true;
+var config_1 = require("../internal/config");
+var basics_1 = require("../internal/basics");
+var tools_1 = require("../internal/tools");
+//------------------------------------------------------------------------------------------------------------------------
+// Classes à exporter TBT
+//------------------------------------------------------------------------------------------------------------------------
+var Group = /** @class */ (function () {
+    /**
+     * @memberof LDAP
+     * @class Group
+     * @classdesc Cette classe est une des deux classes exportables permettant de faire des opérations sur les groupes.
+     * @summary Constructeur vide.
+     */
+    function Group() {
+    }
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui renvoit toutes les infos relatives à un groupe particulier.
+     * @desc Cette fonction utilise {@link Tools.peek} avec l'interface {@link groupData}. Elle rajoute les individus
+     * @arg {string} gid - Identifiant du groupe
+     * @return {Promise(groupData)} Informations recueillies ; renvoie une liste de dictionnaire avec le profil complet du groupe au format {@link groupData}.
+     * @static
+     * @async
+     */
+    Group.peek = function (gid) {
+        return __awaiter(this, void 0, void 0, function () {
+            return __generator(this, function (_a) {
+                try {
+                    return [2 /*return*/, tools_1.Tools.peek("group", gid, config_1.groupData)];
+                }
+                catch (err) {
+                    throw "Erreur lors d'une recherche d'informations sur un groupe.";
+                }
+                return [2 /*return*/];
+            });
+        });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui retrouve le groupe qui ressemblent à l'input et qui correspond au type fourni. Etape 0 vers un vrai TOL (Trombino On Line) pour les groupes.
+     * @desc Cette fonction utilise {@link Tools.search}.
+     * @arg {groupData} input - String entré par l'utilisateur qui ressemble au nom du groupe.
+     * @return {Promise(string[])} Liste des gid dont le nom ressemble à l'input.
+     * @static
+     * @async
+    */
+    Group.search = function (data) {
+        return __awaiter(this, void 0, void 0, function () {
+            return __generator(this, function (_a) {
+                try {
+                    return [2 /*return*/, tools_1.Tools.search("group", data)];
+                }
+                catch (err) {
+                    throw "Erreur lors de la recherche approximative d'un groupe.";
+                }
+                return [2 /*return*/];
+            });
+        });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui permet de rajouter un administrateur à un groupe.
+     * @desc Cette fonction fait essentiellement appel à {@link Tools.add}. Le nouvel administrateur ne devient pas membre ou porte-parole du groupe pour autant !
+     * @arg {string} uid - Identifiant du membre futur admin
+     * @arg {string} gid - Identifiant du groupe
+     * @return {boolean} `true` si la modification s'est bien déroulée, false sinon
+     * @async
+     * @static
+     */
+    Group.addAdmin = function (uid, gid) {
+        return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) {
+            switch (_a.label) {
+                case 0: return [4 /*yield*/, tools_1.Tools.add(uid, gid, "admins")];
+                case 1: return [2 /*return*/, _a.sent()];
+            }
+        }); });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui permet de supprimer un administrateur.
+     * @desc Cette fonction fait essentiellement appel à {@link Tools.remove}.
+     * Elle ne remonte pas les échelons, car cela permettrait à un admin d'un petit groupe de supprimer un admin d'un grand.
+     * @arg {string} uid - Identifiant de l'admin à dégrader, supposé membre
+     * @arg {string} gid - Identifiant du groupe
+     * @return {boolean} `true` si la modification s'est bien déroulée, false sinon
+     * @async
+     * @static
+     */
+    Group.remAdmin = function (uid, gid) {
+        return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) {
+            return [2 /*return*/, tools_1.Tools.remove(uid, gid, "admins")];
+        }); });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui permet de rajouter un porte-parole à un groupe.
+     * @desc Cette fonction fait essentiellement appel à {@link Tools.add}. Elle ne rajoute pas l'utilisateur au groupe.
+     * @arg {string} uid - Identifiant du membre futur porte-parole
+     * @arg {string} gid - Identifiant du groupe
+     * @return {boolean} `true` si la modification s'est bien déroulée, false sinon
+     * @async
+     * @static
+     */
+    Group.addSpeaker = function (uid, gid) {
+        return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) {
+            switch (_a.label) {
+                case 0: return [4 /*yield*/, tools_1.Tools.add(uid, gid, "speakers")];
+                case 1: return [2 /*return*/, _a.sent()];
+            }
+        }); });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui permet de rétrograder un membre du stade de porte-parole d'un groupe au stade d'utilisateur.
+     * @desc Cette fonction fait essentiellement appel à {@link Tools.remove}. Elle dégrade aussi d'un éventuel statut d'administrateur.
+     * @arg {string} uid - Identifiant de l'admin à dégrader (pas supposé valide)
+     * @arg {string} gid - Identifiant du groupe
+     * @return {boolean} `true` si la modification s'est bien déroulée, false sinon
+     * @async
+     * @static
+     */
+    Group.remSpeaker = function (uid, gid) {
+        return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) {
+            switch (_a.label) {
+                case 0: return [4 /*yield*/, tools_1.Tools.remove(uid, gid, "speakers")];
+                case 1: return [2 /*return*/, _a.sent()];
+            }
+        }); });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui permet d'ajouter un utilisateur à un groupe.
+     * @desc Cette fonction fait essentiellement appel à {@link Tools.add}.
+     * @arg {string} uid - Identifiant de l'utilisateur à ajouter
+     * @arg {string} gid - Identifiant du groupe
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     * @async
+     * @static
+     */
+    Group.addMember = function (uid, gid) {
+        return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) {
+            return [2 /*return*/, tools_1.Tools.add(uid, gid, "members")];
+        }); });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui permet de supprimer un membre existant d'un groupe.
+     * @desc Cette fonction fait essentiellement appel à {@link Tools.add}.
+     * Cette fonction supprime tous les droits de l'utilisateur sur le groupe, mais aussi sur les groupes sources si son statut de membre était hérité.
+     * @arg {string} uid - Identifiant de l'ex-membre (pas supposé valide)
+     * @arg {string} gid - Identifiant du groupe
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     * @async
+     * @static
+     */
+    Group.remMember = function (uid, gid) {
+        return __awaiter(this, void 0, void 0, function () {
+            var stack, res, visited, cur_id, _a, _b, _c, _d, _e;
+            return __generator(this, function (_f) {
+                switch (_f.label) {
+                    case 0: return [4 /*yield*/, tools_1.Tools.remove(uid, gid, "members")];
+                    case 1: return [2 /*return*/, _f.sent()];
+                    case 2:
+                        if (!(stack.length > 0)) return [3 /*break*/, 11];
+                        cur_id = stack.pop();
+                        if (!(visited[cur_id] == undefined)) return [3 /*break*/, 10];
+                        visited[cur_id] = true;
+                        _c = res;
+                        if (!_c) return [3 /*break*/, 4];
+                        return [4 /*yield*/, tools_1.Tools.remove(uid, cur_id, "admins")];
+                    case 3:
+                        _c = (_f.sent());
+                        _f.label = 4;
+                    case 4:
+                        _b = _c;
+                        if (!_b) return [3 /*break*/, 6];
+                        return [4 /*yield*/, tools_1.Tools.remove(uid, cur_id, "speakers")];
+                    case 5:
+                        _b = (_f.sent());
+                        _f.label = 6;
+                    case 6:
+                        _a = _b;
+                        if (!_a) return [3 /*break*/, 8];
+                        return [4 /*yield*/, tools_1.Tools.remove(uid, cur_id, "members")];
+                    case 7:
+                        _a = (_f.sent());
+                        _f.label = 8;
+                    case 8:
+                        res = _a;
+                        _e = (_d = stack).concat;
+                        return [4 /*yield*/, basics_1.Basics.searchSingle("group", config_1.ldapConfig.group.childs, cur_id)];
+                    case 9:
+                        _e.apply(_d, [_f.sent()]);
+                        _f.label = 10;
+                    case 10: return [3 /*break*/, 2];
+                    case 11: return [2 /*return*/, res];
+                }
+            });
+        });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui permet d'ajouter un sympathisant à un groupe.
+     * @desc Cette fonction fait essentiellement appel à {@link Tools.add}.
+     * @arg {string} uid - Identifiant de l'utilisateur à ajouter
+     * @arg {string} gid - Identifiant du groupe
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     * @async
+     * @static
+     */
+    Group.addFollower = function (uid, gid) {
+        return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) {
+            return [2 /*return*/, tools_1.Tools.add(uid, gid, "followers")];
+        }); });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui permet de supprimer un sympathisant d'un groupe.
+     * @desc Cette fonction fait essentiellement appel à {@link Tools.add}.
+     * @arg {string} uid - Identifiant de l'ex-sympathisant (pas supposé valide)
+     * @arg {string} gid - Identifiant du groupe
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     * @async
+     * @static
+     */
+    Group.remFollower = function (uid, gid) {
+        return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) {
+            return [2 /*return*/, tools_1.Tools.remove(uid, gid, "followers")];
+        }); });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui créé un nouveau groupe dans le LDAP.
+     * @desc Cette fonction fait une utilisation massive d'eval pour anonymiser son code ; c'est mal et cela suppose que beaucoup de soins ont été pris lors de
+     * l'escape de ses paramètres. Appelle {@link LDAP.add} et {@link LDAP.change}, mais aussi {@link Tools.add}
+     * pour gérer les groupes du nouvel utilisateur. Cettte application permet de rajouter des utilisateurs à toutes les catégories du groupe.
+     * @arg {groupData} data - Dictionnaire des informations utilisateurs (voir détail des champs dans ldapConfig.json)
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     * @async
+     * @static
+     */
+    Group.create = function (data) {
+        return __awaiter(this, void 0, void 0, function () {
+            var vals, gid, key_att, vals2, _loop_1, cat;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        vals = {};
+                        // gid de base généré à partir du nom standardisé, pas à partir de l'entrée 'gid' !
+                        try {
+                            tools_1.Tools.generateReadableId(data['name']).then(function (id) {
+                                vals[config_1.ldapConfig.key_id] = id;
+                                vals[config_1.ldapConfig.group['name']] = id;
+                            });
+                        }
+                        catch (err) {
+                            throw "Erreur lors de la génération d'un hruid pour créer un nouveau groupe.";
+                        }
+                        gid = vals[config_1.ldapConfig.key_id];
+                        // Ecriture de toutes les valeurs directement inscrites dans le LDAP
+                        for (key_att in data) {
+                            vals[config_1.ldapConfig.group[key_att]] = data[key_att];
+                        }
+                        ;
+                        return [4 /*yield*/, basics_1.Basics.add("group", vals)];
+                    case 1:
+                        // Appel à la fonction de base
+                        if (!(_a.sent())) {
+                            throw "Erreur lors de la création d'une nouvelle feuille dans l'arbre des groupes.";
+                        }
+                        vals2 = {};
+                        // ?!
+                        vals2[config_1.ldapConfig.group['password']] = "{CRYPT}" + data['password'];
+                        // Génération id aléatoire et test contre le LDAP
+                        try {
+                            tools_1.Tools.generateId(config_1.ldapConfig.group["idNumber"], "group").then(function (id) { vals2[config_1.ldapConfig.group['idNumber']] = id; });
+                        }
+                        catch (err) {
+                            throw "Erreur lors de la génération d'un id numérique pour créer un nouveau groupe.";
+                        }
+                        // Code root
+                        vals2[config_1.ldapConfig.group['cleanFullName']] = data['name'].replace(':', ';').toLowerCase().normalize('UFD');
+                        return [4 /*yield*/, basics_1.Basics.change("group", gid, "add", vals2)];
+                    case 2:
+                        // Inscription des valeurs calculées par effet de bord
+                        if (!(_a.sent())) {
+                            throw "Erreur lors de l'ajout des valeurs intelligentes du nouveau groupe.";
+                        }
+                        ["posixGroup", "brGroup"].forEach(function (cst) {
+                            var vals3 = {};
+                            vals3[config_1.ldapConfig.group['classes']] = cst;
+                            basics_1.Basics.change("group", gid, "add", vals3).then(function (res) {
+                                if (!res) {
+                                    throw "Erreur lors de l'ajout des valeurs constantes du nouveau groupe.";
+                                }
+                            });
+                        });
+                        _loop_1 = function (cat) {
+                            data[cat].forEach(function (uid) {
+                                tools_1.Tools.add(uid, gid, cat).then(function (res) {
+                                    if (!res) {
+                                        throw "Erreur de l'ajout d'un membre au nouveau groupe.";
+                                    }
+                                });
+                            });
+                        };
+                        // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble
+                        for (cat in config_1.categories) {
+                            _loop_1(cat);
+                        }
+                        return [2 /*return*/, true];
+                }
+            });
+        });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui supprime un groupe du LDAP.
+     * @desc Cette fonction commence par gérer les groupes du membre puis le supprime entièrement. A modifier une fois que le LDAP incluerait les groupes administres par une utilisateur.
+     * Appelle {@link LDAP.clear} bien sûr, mais aussi {@link Tools.remove} pour gérer les groupes de l'utilisateur sortant.
+     * @arg {string} gid - gid de la victime
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     * @async
+     * @static
+     */
+    Group["delete"] = function (gid) {
+        return __awaiter(this, void 0, void 0, function () {
+            var profil, _loop_2, _i, categories_1, cat, err_1;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 3, , 4]);
+                        return [4 /*yield*/, Group.peek(gid)];
+                    case 1:
+                        profil = _a.sent();
+                        _loop_2 = function (cat) {
+                            profil[config_1.ldapConfig.group[cat]].forEach(function quickPartRemUser(uid) {
+                                return __awaiter(this, void 0, void 0, function () {
+                                    var lg;
+                                    return __generator(this, function (_a) {
+                                        switch (_a.label) {
+                                            case 0: return [4 /*yield*/, tools_1.Tools.get(uid, "user", cat)];
+                                            case 1:
+                                                lg = _a.sent();
+                                                if (!lg.includes(gid)) return [3 /*break*/, 3];
+                                                return [4 /*yield*/, basics_1.Basics.change("user", uid, "del", config_1.ldapConfig.user[cat])];
+                                            case 2:
+                                                // Supprime tous les groupes
+                                                if (!(_a.sent())) {
+                                                    throw "Erreur lors de la suppression de tous les groupes du membre.";
+                                                }
+                                                // Les rajoute un par un, sauf pour le groupe supprimé
+                                                lg.forEach(function (id) {
+                                                    if (id != gid) {
+                                                        tools_1.Tools.add(uid, id, cat).then(function (res) {
+                                                            if (!res) {
+                                                                throw "Erreur lors du ré-ajout des autres groupes";
+                                                            }
+                                                        });
+                                                    }
+                                                });
+                                                _a.label = 3;
+                                            case 3: return [2 /*return*/];
+                                        }
+                                    });
+                                });
+                            });
+                        };
+                        for (_i = 0, categories_1 = config_1.categories; _i < categories_1.length; _i++) {
+                            cat = categories_1[_i];
+                            _loop_2(cat);
+                        }
+                        return [4 /*yield*/, basics_1.Basics.clear("group", gid)];
+                    case 2:
+                        // Elimination
+                        if (!(_a.sent())) {
+                            throw "Erreur lors de la suppression de la feuille dans l'arbre des groupes.";
+                        }
+                        return [2 /*return*/, true];
+                    case 3:
+                        err_1 = _a.sent();
+                        throw "Erreur lors de l'obtention du profil d'un groupe pour le supprimer.";
+                    case 4: return [2 /*return*/];
+                }
+            });
+        });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui édite un groupe existant dans le LDAP. Sans influence sur ses membres ou admins.
+     * @desc Appelle {@link Tools.edit}.
+     * @arg {groupData} data - Dictionnaire des informations du groupe.
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     * @async
+     * @static
+     */
+    Group.edit = function (data) {
+        return __awaiter(this, void 0, void 0, function () {
+            return __generator(this, function (_a) {
+                try {
+                    return [2 /*return*/, tools_1.Tools.edit("group", data)];
+                }
+                catch (err) {
+                    throw "Erreur lors de la modification d'un groupe.";
+                }
+                return [2 /*return*/];
+            });
+        });
+    };
+    return Group;
+}());
+exports.Group = Group;
diff --git a/src/ldap/export/user.js b/src/ldap/export/user.js
new file mode 100644
index 0000000..0828e17
--- /dev/null
+++ b/src/ldap/export/user.js
@@ -0,0 +1,332 @@
+"use strict";
+/**
+ * @file Ce fichier contient la classe de l'API du LDAP qui gère les opérations sur les groupes. Elle est destinée à être exportée pour être utilisée par les resolvers.
+ * @author hawkspar
+ */
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+var __generator = (this && this.__generator) || function (thisArg, body) {
+    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
+    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
+    function verb(n) { return function (v) { return step([n, v]); }; }
+    function step(op) {
+        if (f) throw new TypeError("Generator is already executing.");
+        while (_) try {
+            if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
+            if (y = 0, t) op = [0, t.value];
+            switch (op[0]) {
+                case 0: case 1: t = op; break;
+                case 4: _.label++; return { value: op[1], done: false };
+                case 5: _.label++; y = op[1]; op = [0]; continue;
+                case 7: op = _.ops.pop(); _.trys.pop(); continue;
+                default:
+                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
+                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
+                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
+                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
+                    if (t[2]) _.ops.pop();
+                    _.trys.pop(); continue;
+            }
+            op = body.call(thisArg, _);
+        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
+        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
+    }
+};
+exports.__esModule = true;
+var config_1 = require("../internal/config");
+exports.userData = config_1.userData;
+var basics_1 = require("../internal/basics");
+var tools_1 = require("../internal/tools");
+var User = /** @class */ (function () {
+    /**
+     * @memberof LDAP
+     * @class User
+     * @classdesc Cette classe est une des deux classes exportables permettant de faire des opérations sur les utilisateurs.
+     * @summary Constructeur vide.
+     */
+    function User() {
+    }
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui renvoit les infos de base relatives à un utilisateur particulier.
+     * @desc Cette fonction utilise {@link Tools.peek} avec l'interface {@link userData}.
+     * @arg {string} uid - Identifiant de l'utilisateur, supposé valide.
+     * @return {Promise(userData)} Informations recueillies au format {@link userData}.
+     * @static
+     * @async
+     */
+    User.peek = function (uid) {
+        return __awaiter(this, void 0, void 0, function () {
+            var data, _a, _b, _i, cat, _c, _d, err_1;
+            return __generator(this, function (_e) {
+                switch (_e.label) {
+                    case 0:
+                        _e.trys.push([0, 6, , 7]);
+                        return [4 /*yield*/, tools_1.Tools.peek("user", uid, config_1.userData)];
+                    case 1:
+                        data = _e.sent();
+                        _a = [];
+                        for (_b in config_1.categories)
+                            _a.push(_b);
+                        _i = 0;
+                        _e.label = 2;
+                    case 2:
+                        if (!(_i < _a.length)) return [3 /*break*/, 5];
+                        cat = _a[_i];
+                        _c = data;
+                        _d = cat;
+                        return [4 /*yield*/, basics_1.Basics.searchSingle("group", config_1.ldapConfig.group[cat], "*", config_1.ldapConfig.group[cat] + "=" + uid)];
+                    case 3:
+                        _c[_d] = _e.sent();
+                        _e.label = 4;
+                    case 4:
+                        _i++;
+                        return [3 /*break*/, 2];
+                    case 5: return [2 /*return*/, data];
+                    case 6:
+                        err_1 = _e.sent();
+                        throw "Error while peeking a user.";
+                    case 7: return [2 /*return*/];
+                }
+            });
+        });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui retrouve les uid des paxs validant les critères de recherche. Utiliser {@link User.peek} au cas par cas après pour obtenir les vraies infos.
+     * @desc Cette fonction utilise {@link Tools.search}.
+     * @arg {userData} data - Dictionnaire contenant les données nécessaires à la recherche. Les valeurs sont celles entrées par l'utilisateur et sont par hypothèse
+     * comme des sous-parties compactes des valeurs renvoyées. Tous les champs ci-dessous peuvent être indifféremment des listes (par exemple pour chercher un membre
+     * de plusieurs groupes) ou des éléments isolés. Si un champ n'est pas pertinent, le mettre à '' ou undefined.
+     * @return {Promise(string[])} gids des profils qui "match" les critères proposés.
+     * @static
+     * @async
+     */
+    User.search = function (data) {
+        return __awaiter(this, void 0, void 0, function () {
+            return __generator(this, function (_a) {
+                try {
+                    return [2 /*return*/, tools_1.Tools.search("user", data)];
+                }
+                catch (err) {
+                    throw "Erreur lors de la recherche approximative d'un utilisateur.";
+                }
+                return [2 /*return*/];
+            });
+        });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui créé un nouvel utilisateur dans le LDAP.
+     * @desc Appelle {@link LDAP.add} bien sûr, mais aussi {@link Tools.add} pour gérer les groupes du nouvel utilisateur.
+     * @arg {userData} data - Dictionnaire des informations utilisateurs. Des erreurs peuvent apparaître si tous les champs ne sont pas remplis.
+     * Cette application permet de rejoindre des groupes en masse pour toute catégorie.
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     * @async
+     * @static
+     */
+    User.create = function (data) {
+        return __awaiter(this, void 0, void 0, function () {
+            var vals, uid, key_att, _loop_1, key_att, vals3, _a, _b, err_2, _i, categories_1, cat, _c, _d, gid;
+            return __generator(this, function (_e) {
+                switch (_e.label) {
+                    case 0:
+                        vals = {};
+                        // uid de base généré à partir de nom et prénom, plus potentiellement promo et un offset
+                        // MEF mélange de Promise et de fonction standard
+                        try {
+                            tools_1.Tools.generateUid(data['givenName'], data['lastName'], data['birthdate']).then(function (id) { vals[config_1.ldapConfig.key_id] = id; });
+                        }
+                        catch (err) {
+                            throw "Erreur lors de la génération d'un hruid pour un nouvel utilisateur.";
+                        }
+                        uid = vals[config_1.ldapConfig.key_id];
+                        // Génère une erreur si un champ n'est pas rempli
+                        for (key_att in data) {
+                            // Ecriture de toutes les valeurs uniques
+                            if (!Array.isArray(data[key_att])) {
+                                vals[config_1.ldapConfig.user[key_att]] = data[key_att];
+                            }
+                        }
+                        return [4 /*yield*/, basics_1.Basics.add("user", vals)];
+                    case 1:
+                        // Appel à la fonction de base
+                        if (!(_e.sent())) {
+                            throw "Erreur de l'ajout de la feuille à l'arbre utilisateur.";
+                        }
+                        _loop_1 = function (key_att) {
+                            // Modifications multiples pour avoir plusieurs champs de même type ; boucle sur les attributs multiples
+                            if (Array.isArray(data[key_att])) {
+                                // On rajoute chaque valeur en entrée
+                                data[key_att].forEach(function (val) {
+                                    var vals2 = {};
+                                    vals2[config_1.ldapConfig.user[key_att]] = val;
+                                    basics_1.Basics.change("user", uid, "add", vals2).then(function (res) {
+                                        if (!res) {
+                                            throw "Erreur lors de l'ajout d'une valeur pour un champ à valeurs multiples à la feuille du nouvel utilisateur.";
+                                        }
+                                    });
+                                });
+                            }
+                        };
+                        for (key_att in data) {
+                            _loop_1(key_att);
+                        }
+                        vals3 = {};
+                        // ldapConfiguration du mot de passe utilisateur
+                        // Le préfixe {CRYPT} signifie que le mdp est hashé dans OpenLDAP voir : https://www.openldap.org/doc/admin24/security.html 
+                        vals3[config_1.ldapConfig.user['password']] = "{CRYPT}" + data['password'];
+                        // Ecriture d'un surnom s'il y a lieu
+                        if ((data['nickname'] != undefined) && (data['nickname'] != '')) {
+                            vals3[config_1.ldapConfig.user['nickname']] = data['nickname'];
+                        }
+                        _e.label = 2;
+                    case 2:
+                        _e.trys.push([2, 4, , 5]);
+                        // Génération id aléatoire unique
+                        _a = vals3;
+                        _b = config_1.ldapConfig.user['id'];
+                        return [4 /*yield*/, tools_1.Tools.generateId(config_1.ldapConfig.user['id'], "user")];
+                    case 3:
+                        // Génération id aléatoire unique
+                        _a[_b] = _e.sent();
+                        return [3 /*break*/, 5];
+                    case 4:
+                        err_2 = _e.sent();
+                        throw "Erreur lors de la génération d'un id numérique pour un nouvel utilisateur.";
+                    case 5:
+                        // Code root
+                        vals3[config_1.ldapConfig.user['cleanFullName']] = data['fullName'].replace(':', ';').toLowerCase().normalize('UFD');
+                        return [4 /*yield*/, basics_1.Basics.change("user", uid, "add", vals3)];
+                    case 6:
+                        // Inscription des valeurs calculées
+                        if (!(_e.sent())) {
+                            throw "Erreur lors de l'ajout des valeurs calculées à la feuille du nouvel utilisateur.";
+                        }
+                        ["posixAccount", "shadowAccount", "brUser"].forEach(function (cst) {
+                            var val3 = {};
+                            vals3[config_1.ldapConfig.user['class']] = cst;
+                            basics_1.Basics.change("user", uid, "add", vals3).then(function (res) {
+                                if (!res) {
+                                    throw "Erreur lors de l'ajout d'une valeur constante à la feuille du nouvel utilisateur.";
+                                }
+                            });
+                        });
+                        // Ajout dans les groupes à la catégorie voulue
+                        for (_i = 0, categories_1 = config_1.categories; _i < categories_1.length; _i++) {
+                            cat = categories_1[_i];
+                            for (_c = 0, _d = data[cat]; _c < _d.length; _c++) {
+                                gid = _d[_c];
+                                tools_1.Tools.add(uid, gid, cat);
+                            }
+                        }
+                        return [2 /*return*/, true];
+                }
+            });
+        });
+    };
+    //------------------------------------------------------------------------------------------------------------------------
+    // Fonctions de suppression TBT
+    //------------------------------------------------------------------------------------------------------------------------
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui supprime un utilisateur du LDAP.
+     * @desc Cette fonction commence par gérer les groupes du membre puis le supprime entièrement.
+     * Appelle {@link LDAP.clear} bien sûr, mais aussi {@link Tools.remove} pour gérer les groupes de l'utilisateur sortant.
+     * @arg {string} uid - uid de la victime
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     * @async
+     * @static
+     */
+    User["delete"] = function (uid) {
+        return __awaiter(this, void 0, void 0, function () {
+            var profil, _loop_2, _i, categories_2, cat, err_3;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 2, , 3]);
+                        return [4 /*yield*/, User.peek(uid)];
+                    case 1:
+                        profil = _a.sent();
+                        _loop_2 = function (cat) {
+                            profil[config_1.ldapConfig.user[cat]].forEach(function (gid) {
+                                return __awaiter(this, void 0, void 0, function () {
+                                    var lm;
+                                    return __generator(this, function (_a) {
+                                        switch (_a.label) {
+                                            case 0: return [4 /*yield*/, tools_1.Tools.get(gid, "group", cat)];
+                                            case 1:
+                                                lm = _a.sent();
+                                                if (!lm.includes(uid)) return [3 /*break*/, 3];
+                                                return [4 /*yield*/, basics_1.Basics.change("group", gid, "del", config_1.ldapConfig.group[cat])];
+                                            case 2:
+                                                // Supprime tous les membres
+                                                if (!(_a.sent())) {
+                                                    throw "Erreur lors de la suppression de tous les membres du groupe.";
+                                                }
+                                                // Les rajoute un par un, sauf pour le supprimé
+                                                lm.forEach(function (id) {
+                                                    if (id != uid) {
+                                                        tools_1.Tools.add(id, gid, cat).then(function (res) {
+                                                            if (!res) {
+                                                                throw "Erreur lors du ré-ajout d'un autre membre";
+                                                            }
+                                                        });
+                                                    }
+                                                });
+                                                _a.label = 3;
+                                            case 3: return [2 /*return*/];
+                                        }
+                                    });
+                                });
+                            });
+                        };
+                        for (_i = 0, categories_2 = config_1.categories; _i < categories_2.length; _i++) {
+                            cat = categories_2[_i];
+                            _loop_2(cat);
+                        }
+                        return [3 /*break*/, 3];
+                    case 2:
+                        err_3 = _a.sent();
+                        throw "Erreur lors de l'obtention des informations de l'utilisateur à supprimer.";
+                    case 3:
+                        // Elimination
+                        if (!basics_1.Basics.clear("user", uid)) {
+                            throw "Erreur lors de la suppression de l'utilisateur.";
+                        }
+                        return [2 /*return*/, true];
+                }
+            });
+        });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui édite un utilisateur existant dans le LDAP.
+     * @desc Appelle simplement {@link Tools.edit}. Sans effet sur les groupes de l'utilisateur concerné.
+     * @arg {userData} data - Dictionnaire des informations utilisateurs
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     * @async
+     * @static
+     */
+    User.edit = function (data) {
+        return __awaiter(this, void 0, void 0, function () {
+            return __generator(this, function (_a) {
+                try {
+                    return [2 /*return*/, tools_1.Tools.edit("user", data)];
+                }
+                catch (err) {
+                    throw "Erreur lors de la modification d'un utilisateur.";
+                }
+                return [2 /*return*/];
+            });
+        });
+    };
+    return User;
+}());
+exports.User = User;
diff --git a/src/ldap/export/user.ts b/src/ldap/export/user.ts
index f3aa03e..0924928 100644
--- a/src/ldap/export/user.ts
+++ b/src/ldap/export/user.ts
@@ -34,7 +34,7 @@ export class User {
     static async peek(uid: string) : Promise<userData> {
         try { 
             let data : userData = await Tools.peek<userData>("user", uid, userData);
-            for (let cat in categories) { data[cat] = await Basics.searchSingle("group", ldapConfig.group[cat], "*", ldapConfig.group[cat]+"="+uid); }
+            for (let cat in categories) { data[cat] = await Basics.searchSingle("group", ldapConfig.group.key_id, "*", ldapConfig.group[cat]+"="+uid); }
             return data;
         }
         catch(err) {
diff --git a/src/ldap/internal/basics.js b/src/ldap/internal/basics.js
new file mode 100644
index 0000000..a0a80e3
--- /dev/null
+++ b/src/ldap/internal/basics.js
@@ -0,0 +1,330 @@
+"use strict";
+/**
+ * @file Ce fichier regroupe les fonctions fondamentales aux interactions avec le LDAP.
+ * C'est ici que tout le filtrage est opéré, au plus bas niveau.
+ * Toutes les fonctions écrites ici sont asynchrones et renvoient des Promises ce qui nécessite de les appeler avec la synthaxe
+ * un peu particulière `f(args).then(res => ...)` pour exploiter leur résultat.
+ * @author hawkspar
+ */
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+var __generator = (this && this.__generator) || function (thisArg, body) {
+    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
+    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
+    function verb(n) { return function (v) { return step([n, v]); }; }
+    function step(op) {
+        if (f) throw new TypeError("Generator is already executing.");
+        while (_) try {
+            if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
+            if (y = 0, t) op = [0, t.value];
+            switch (op[0]) {
+                case 0: case 1: t = op; break;
+                case 4: _.label++; return { value: op[1], done: false };
+                case 5: _.label++; y = op[1]; op = [0]; continue;
+                case 7: op = _.ops.pop(); _.trys.pop(); continue;
+                default:
+                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
+                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
+                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
+                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
+                    if (t[2]) _.ops.pop();
+                    _.trys.pop(); continue;
+            }
+            op = body.call(thisArg, _);
+        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
+        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
+    }
+};
+exports.__esModule = true;
+var ldapjs_1 = require("ldapjs");
+// Toutes les entrées utilisateur sont escapées par sécurité
+var ldap_escape_1 = require("ldap-escape");
+// Fichier de ldapConfig du ldap
+var config_1 = require("./config");
+// Connection au serveur LDAP avec des temps de timeout arbitraires
+var client = ldapjs_1["default"].createClient({ url: config_1.ldapConfig.server });
+//------------------------------------------------------------------------------------------------------------------------
+// Fonctions de base agissant sur le LDAP
+//------------------------------------------------------------------------------------------------------------------------
+var Basics = /** @class */ (function () {
+    /**
+     * @class Basics
+     * @classdesc Cette classe est la brique de base du fichier tout entier puisqu'elle contient les functions qui agissent directement sur le LDAP.
+     * @memberof LDAP
+     * @summary Constructeur vide.
+     */
+    function Basics() {
+    }
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui sert à s'identifier sur le LDAP.
+     * @desc Assez important en terme de sécurité, de gestion de conflit, et de droit d'accès. Méthode ldapjs
+     * (voir [`Client API`](http://ldapjs.org/client.html) méthode bind).
+     * @arg {string} dn - Nom de domaine ; identifiant de l'utilisateur cherchant à se connecter
+     * @arg {string} password - Mot de passe de l'utilisateur cherchant à se connecter
+     * @returns {Promise(boolean)} `true` si l'opération s'est bien déroulée, `false` sinon.
+     * @static
+     * @async
+     */
+    Basics.bind = function (dn, password) {
+        return __awaiter(this, void 0, void 0, function () {
+            return __generator(this, function (_a) {
+                // Escape DN as everywhere in this file, but password is taken as is
+                client.bind(dn, password, function (res) {
+                    // Gestion erreur
+                    try {
+                        res;
+                    }
+                    catch (err) {
+                        throw "Erreur lors de la connection au LDAP.";
+                    }
+                });
+                // End with a boolean
+                return [2 /*return*/, true];
+            });
+        });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui sert à s'identifier sur le LDAP avec plein pouvoirs.
+     * @desc Appelle {@link bind} avec un utilisateur tout puissant.
+     * @returns {Promise(boolean)} `true` si l'opération s'est bien déroulée, `false` sinon.
+     * @static
+     * @async
+     */
+    Basics.adminBind = function () {
+        return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) {
+            return [2 /*return*/, Basics.bind(config_1.credentialsLdapConfig.dn, config_1.credentialsLdapConfig.password)];
+        }); });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui sert à se déconnecter du LDAP.
+     * @desc Assez important en terme de sécurité, de gestion de conflit, et de droit d'accès.
+     * Fait appel à {@link Basics.bind} avec deux champs vides.
+     * @returns {Promise(boolean)} `true` si l'opération s'est bien déroulée, `false` sinon.
+     * @static
+     * @async
+     */
+    Basics.unbind = function () {
+        return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) {
+            return [2 /*return*/, Basics.bind("", "")];
+        }); });
+    };
+    /**
+     * @callback entryHandler
+     * @arg entry {*} - Convoluted ldap.js search result object
+     */
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui interroge le LDAP selon un protocole spécifié en argument et modifie une liste pour y insérer les valeurs trouvées.
+     * @desc Cette fonction utilise ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode search). Cette fonction fait une demande au LDAP
+     * qu'elle filtre selon un schéma prédéfini dans `filter` et à chaque résultat (event SearchEntry) le met dans une liste, et renvoit la liste à l'issue (event end).
+     * @arg {'gr'|'us'} domain - Emplacement de la requête (groupe ou utilisateur)
+     * @arg {string[]} attributes - Attributs à renvoyer
+     * @arg {string} id [null] - Identifiant facultatif pour une recherche triviale en o(1)
+     * @arg {string} filter ["(objectClass=*)"] - Filtre logique de la recherche (format [`RFC2254`](https://tools.ietf.org/search/rfc2254)) déjà passé au ldapEscape
+     * @arg {entryHandler} handler - Wrapper pour gérer les requêtes simples ou multiples
+     * @return {void} Utilise handler pour gérer ses résultats au fur et à mesure
+     * @static
+     * @async
+     */
+    Basics.search = function (domain, attributes, id, filter, handler) {
+        Basics.adminBind();
+        var dn = "";
+        if (id != null) {
+            dn += config_1.ldapConfig.key_id + '=' + ldap_escape_1["default"].dn("${txt}", { txt: id }) + ',';
+        }
+        dn += config_1.ldapConfig.dn[domain];
+        // Interrogation LDAP selon filter
+        var promise = new Promise(function (resolve, reject) {
+            client.search(dn, {
+                "scope": "sub",
+                "filter": filter,
+                "attributes": attributes
+            }, function (err, res) {
+                // Gestion erreur ; pb car pas simple true / autre en sortie
+                if (err) {
+                    throw "Erreur lors de la recherche sur le LDAP.";
+                }
+                else {
+                    // Dès que la recherche renvoit une entrée, on stocke les attributs qui nous intéresse
+                    res.on('searchEntry', function (entry) { handler(entry); });
+                    // Si la recherche renvoie une erreur, on renvoit
+                    res.on('error', function (resErr) { throw resErr; });
+                    // Quand la recherche est finie on se déconnecte
+                    res.on('end', function (_) { Basics.unbind(); resolve(); });
+                }
+            });
+        });
+        return promise;
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui interroge le LDAP selon un protocole spécifié en argument et renvoit une liste de valeurs trouvées.
+     * @desc Cette fonction utilise {@link LDAP.search} directement.
+     * @arg {'gr'|'us'} domain - Emplacement de la requête (groupe ou utilisateur)
+     * @arg {string} attribute - Attribut unique à renvoyer
+     * @arg {string} id [null] - Identifiant facultatif pour une recherche triviale en o(1)
+     * @arg {string} filter ["(objectClass=*)"] - Filtre logique de la recherche (format [`RFC2254`](https://tools.ietf.org/search/rfc2254)) déjà passé au ldapEscape
+     * @return {Promise(string[])} Résultats de la recherche ; soit une liste de valeurs d'attributs,
+     * soit une liste de dictionnaires si on veut plus d'un attribut (les clés du dictionnaire sont celles du LDAP)
+     * @static
+     * @async
+     */
+    Basics.searchSingle = function (domain, attribute, id, filter) {
+        if (id === void 0) { id = null; }
+        if (filter === void 0) { filter = "(objectClass=*)"; }
+        return __awaiter(this, void 0, void 0, function () {
+            var vals;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        vals = [];
+                        return [4 /*yield*/, Basics.search(domain, [attribute], id, filter, function (entry) {
+                                // Cas un seul attribut où le résultat est une liste directement
+                                vals.push(entry.object[attribute]);
+                            })];
+                    case 1:
+                        _a.sent();
+                        return [2 /*return*/, vals];
+                }
+            });
+        });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui interroge le LDAP selon un protocole spécifié en argument et renvoit les valeurs trouvées.
+     * @desc Cette fonction utilise ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode search). Cette fonction fait une demande au LDAP
+     * qu'elle filtre selon un schéma prédéfini dans `filter` et à chaque résultat (event SearchEntry) le met dans une liste, et renvoit la liste à l'issue (event end).
+     * @arg {'gr'|'us'} domain - Emplacement de la requête (groupe ou utilisateur)
+     * @arg {string[]} attributes - Liste des attributs qui figureront dans le résultat final ; peut aussi être un seul élément
+     * @arg {string} id [null] - Identifiant facultatif pour une recherche triviale en o(1)
+     * @arg {string} filter ["(objectClass=*)"] - Filtre logique de la recherche (format [`RFC2254`](https://tools.ietf.org/search/rfc2254)) déjà passé au ldapEscape
+     * @return {Promise(Array<dic>)} Résultats de la recherche ; soit une liste de valeurs d'attributs,
+     * soit une liste de dictionnaires si on veut plus d'un attribut (les clés du dictionnaire sont celles du LDAP)
+     * @static
+     * @async
+     */
+    Basics.searchMultiple = function (domain, attributes, id, filter) {
+        if (id === void 0) { id = null; }
+        if (filter === void 0) { filter = "(objectClass=*)"; }
+        return __awaiter(this, void 0, void 0, function () {
+            var vals;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        vals = [];
+                        return [4 /*yield*/, Basics.search(domain, attributes, id, filter, function (entry) {
+                                // Cas plusieurs attributs donc résultat dictionnaire
+                                vals.push({});
+                                attributes.forEach(function (attribute) {
+                                    vals.slice(-1)[0][attribute] = entry.object[attribute];
+                                });
+                            })];
+                    case 1:
+                        _a.sent();
+                        return [2 /*return*/, vals];
+                }
+            });
+        });
+    };
+    //TBT
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui permet de modifier un élément sur le LDAP. Gestion intelligente de l'appartenance à un binet.
+     * @desc Cette fonction traite la demande avec ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode modify).
+     * @arg {'gr'|'us'} domain - Emplacement de la requête (groupe ou utilisateur)
+     * @arg {string} id - Identifiant unique de la feuille à modifier
+     * @arg {"add"|"del"|"replace"} op - Operation à réaliser sur le LDAP. Trois opération sont possibles ; "add", qui rajoute des attributs et qui peut créer des doublons,
+     * "del" qui en supprime, et "replace" qui remplace du contenu par un autre.
+     * @arg {dic} mod - Dictionnaire contenant les attributs à modifier et les nouvelles valeurs des attributs.
+     * @arg {string} mod[key] - Nouvelle valeur de l'attribut key. Une nouvelle valeur vide ("") est équivalent à la suppression de cet attribut.
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, `false` sinon.
+     * @static
+     * @async
+     */
+    Basics.change = function (domain, id, op, mod) {
+        return __awaiter(this, void 0, void 0, function () {
+            var dn;
+            return __generator(this, function (_a) {
+                Basics.adminBind();
+                dn = config_1.ldapConfig.key_id + '=' + id + ',';
+                dn += config_1.ldapConfig.dn[domain];
+                // Modification LDAP selon dn fourni en argument (pourrait prendre une liste de Changes)
+                client.modify(ldap_escape_1["default"].dn("${txt}", { txt: dn }), new ldapjs_1["default"].Change({
+                    operation: op,
+                    modification: mod
+                }), function (err) {
+                    throw "Erreur lors d'une opération de modification sur le LDAP.";
+                });
+                Basics.unbind();
+                return [2 /*return*/, true];
+            });
+        });
+    };
+    //TBT
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui permet de rajouter un élément sur le LDAP.
+     * @desc  Cette fonction traite la demande avec ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode add).
+     * @arg {'gr'|'us'} domain - Emplacement de la requête (groupe ou utilisateur)
+     * @arg {Object.<string, string>} vals - Dictionnaire contenant les valeurs à créer (contient un champ en ldapConfig.key_id)
+     * @arg {Object} vals[key] - Nouvelle valeur pour le champ key
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon.
+     * @static
+     * @async
+     */
+    Basics.add = function (domain, vals) {
+        return __awaiter(this, void 0, void 0, function () {
+            var dn;
+            return __generator(this, function (_a) {
+                Basics.adminBind();
+                dn = config_1.ldapConfig.key_id + "=" + vals[config_1.ldapConfig.key_id];
+                dn += config_1.ldapConfig.dn[domain];
+                // Ajout LDAP selon la ldapConfiguration en argument
+                client.add(ldap_escape_1["default"].dn("${txt}", { txt: dn }), vals, function (err) {
+                    throw "Erreur lors d'une opération d'ajout sur le LDAP.";
+                });
+                Basics.unbind();
+                return [2 /*return*/, true];
+            });
+        });
+    };
+    //TBT
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui permet de supprimer une feuille du LDAP.
+     * @desc Cette fonction traite la demande avec ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode del).
+     * Elle est différente de modify avec "del" car elle affecte directement une feuille et pas un attribut.
+     * @arg {'gr'|'us'} domain - Emplacement de la requête (groupe ou utilisateur)
+     * @arg {string} id - Identifiant unique de la cible
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     * @static
+     * @async
+     */
+    Basics.clear = function (domain, id) {
+        return __awaiter(this, void 0, void 0, function () {
+            var dn;
+            return __generator(this, function (_a) {
+                Basics.adminBind();
+                dn = config_1.ldapConfig.key_id + '=' + id + ',';
+                dn += config_1.ldapConfig.dn[domain];
+                // Suppression LDAP
+                client.del(ldap_escape_1["default"].dn("${txt}", { txt: dn }), function (err) {
+                    throw "Erreur lors d'une opération de suppression sur le LDAP.";
+                });
+                Basics.unbind();
+                return [2 /*return*/, true];
+            });
+        });
+    };
+    return Basics;
+}());
+exports.Basics = Basics;
diff --git a/src/ldap/internal/config.js b/src/ldap/internal/config.js
new file mode 100644
index 0000000..41d8b53
--- /dev/null
+++ b/src/ldap/internal/config.js
@@ -0,0 +1,86 @@
+"use strict";
+/**
+ * Namespace qui regroupe toutes les fonctions en rapport avec le LDAP - API et fonctions internes
+ * @namespace LDAP
+ * @file Importe la configuration du LDAP au sein de l'application, et remplace certaines valeurs en fonction des variables d'environnement.
+ * @memberof LDAP
+ * @author manifold, hawkspar
+ */
+exports.__esModule = true;
+var fs_1 = require("fs");
+var path_1 = require("path");
+var colors_1 = require("colors");
+var dotenv_1 = require("dotenv");
+dotenv_1["default"].config();
+// Point central ; tous les champs de la BDD sont 'cachés' dans config.json et pas visibles directement
+var path_config = path_1["default"].resolve(__dirname, '..', '..', '..', 'ldap_config.json');
+console.log(colors_1["default"].cyan("Loading LDAP config file from " + path_config));
+exports.ldapConfig = JSON.parse(fs_1["default"].readFileSync(path_config).toString());
+// Override config server from environment
+if (process.env.LDAP_URI != null) {
+    exports.ldapConfig.server = process.env.LDAP_URI;
+}
+else {
+    if (process.env.TARGET_ENV == "production") {
+        exports.ldapConfig.server = exports.ldapConfig.server_prod;
+    }
+    else {
+        exports.ldapConfig.server = exports.ldapConfig.server_dev;
+    }
+}
+// Gestion des super-identifiants
+var path_credentials = path_1["default"].resolve(__dirname, '..', '..', '..', 'ldap_credentials.json');
+console.log(colors_1["default"].cyan("Loading LDAP credentials from " + path_credentials));
+exports.credentialsLdapConfig = JSON.parse(fs_1["default"].readFileSync(path_credentials).toString());
+// Data formats and useful constants
+exports.categories = ["admins", "speakers", "members", "followers"];
+/**
+ * @class userData
+ * @desc Interface avec toutes les données extractables pour un utilisateur.
+ * @var {string?} uid - Identifiant utilisateur
+ * @var {string?} password - Mot de passe généré en amont (utilisé seulement à l'initialialisation, pas stocké bien sûr)
+ * @var {string?} givenName - Prénom
+ * @var {string?} lastName - Nom
+ * @var {string?} nickname - Surnom
+ * @var {string?} gender - Sexe
+ * @var {string?} photo - Bytestring de la photo de l'utilisateur
+ * @var {string?} phone - Numéro(s) de téléphone
+ * @var {string?} address - Adresse(s)
+ * @var {string?} mail - Adresse(s) courriel
+ * @var {string?} birthdate - Date d'anniversaire
+ * @var {string?} nationality - Nationalité d'origine
+ * @var {string[]?} admins - Liste des gid (group id, inclus section sportive, binet, PA...) dont l'utilisateur est admin ; pas forcément sous-liste de members
+ * @var {string[]?} speakers - Liste des gid dont l'utilisateur est porte-parole ; pas forcément sous-liste de members
+ * @var {string[]?} members - Liste des gid dont l'utilisateur est membre
+ * @var {string[]?} followers - Liste des gid dont l'utilisateur est sympathisant
+ * @memberof LDAP
+ */
+var userData = /** @class */ (function () {
+    function userData() {
+    }
+    return userData;
+}());
+exports.userData = userData;
+/**
+ * @class groupData
+ * @var {string} gid - Identifiant du groupe
+ * @var {string} password - Mot de passe du groupe
+ * @var {string} name - Nom du groupe (souvent son nom mais pas nécessairement)
+ * @var {string} logo - Logo du groupe (en bytestring)
+ * @var {string} description - Description du groupe (script Markdown)
+ * @var {string} site - Site web du groupe (URL)
+ * @var {string} category - Statut du groupe ; binet, section sportive... (actuellement juste 'binet' ou 'free')
+ * @var {string[]} childs - Liste des groupes enfants de première génération de celui-ci
+ * @var {string[]} parents - Liste des groupes directement parents de celui-ci ; symétrique du précédent
+ * @var {string[]} admins - Liste des admins du groupe
+ * @var {string[]} speakers - Liste des porte-parole du groupe
+ * @var {string[]} members - Liste des membres du groupe
+ * @var {string[]} followers - Liste des sympathisants du groupe
+ * @memberof LDAP
+ */
+var groupData = /** @class */ (function () {
+    function groupData() {
+    }
+    return groupData;
+}());
+exports.groupData = groupData;
diff --git a/src/ldap/internal/config.ts b/src/ldap/internal/config.ts
index 9f6d0d1..79c3b7d 100644
--- a/src/ldap/internal/config.ts
+++ b/src/ldap/internal/config.ts
@@ -10,7 +10,7 @@ import fs from 'fs';
 import path from 'path';
 import colors from 'colors';
 import dotenv from 'dotenv';
-dotenv.config();
+dotenv.config({ path: path.resolve(__dirname, '..', '..', '..', '.env') });
 
 // Point central ; tous les champs de la BDD sont 'cachés' dans config.json et pas visibles directement
 let path_config = path.resolve(__dirname, '..', '..', '..', 'ldap_config.json');
diff --git a/src/ldap/internal/tools.js b/src/ldap/internal/tools.js
new file mode 100644
index 0000000..a1748de
--- /dev/null
+++ b/src/ldap/internal/tools.js
@@ -0,0 +1,509 @@
+"use strict";
+/**
+ * @file Ce fichier regroupe les fonctions génériques de recherche et de test utiles, mais trop puissantes pour être exportées directement.
+ * @author hawkspar
+ */
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+var __generator = (this && this.__generator) || function (thisArg, body) {
+    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
+    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
+    function verb(n) { return function (v) { return step([n, v]); }; }
+    function step(op) {
+        if (f) throw new TypeError("Generator is already executing.");
+        while (_) try {
+            if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
+            if (y = 0, t) op = [0, t.value];
+            switch (op[0]) {
+                case 0: case 1: t = op; break;
+                case 4: _.label++; return { value: op[1], done: false };
+                case 5: _.label++; y = op[1]; op = [0]; continue;
+                case 7: op = _.ops.pop(); _.trys.pop(); continue;
+                default:
+                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
+                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
+                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
+                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
+                    if (t[2]) _.ops.pop();
+                    _.trys.pop(); continue;
+            }
+            op = body.call(thisArg, _);
+        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
+        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
+    }
+};
+exports.__esModule = true;
+// Toutes les entrées utilisateur sont escapées par sécurité
+var ldap_escape_1 = require("ldap-escape");
+// Imports internes
+var config_1 = require("./config");
+var basics_1 = require("./basics");
+//------------------------------------------------------------------------------------------------------------------------
+// Fonctions intermédiaires TBT
+//------------------------------------------------------------------------------------------------------------------------
+var Tools = /** @class */ (function () {
+    /**
+     * @memberof LDAP
+     * @class Tools
+     * @classdesc Cette classe contient des fonctions intermédiaires qui ne sont pas destinées à être utilisées dans les resolvers.
+     * @summary Constructeur vide.
+     */
+    function Tools() {
+    }
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui renvoit toutes les infos relatives à un groupe ou un utilisateur particulier.
+     * @desc Cette fonction utilise {@link LDAP.search} avec des attributs prédéfinis. Elle est naïve et n'opère pas de récursion.
+     * @param T - Format renvoyé (en pratique {@link userData} ou {@link groupData})
+     * @arg {string} domain - Domaine de la recherche (utilisateur ou groupe)
+     * @arg {string} id - Identifiant de la feuille cherchée (uid ou gid)
+     * @return {Promise(T)} Informations recueillies ; renvoie une liste de dictionnaire avec le profil complet du groupe tel que défini par le paramètre T.
+     * @static
+     * @async
+     */
+    Tools.peek = function (domain, id, type) {
+        return __awaiter(this, void 0, void 0, function () {
+            var dirtyKeys, cleanData, attr, dirtyData, uncleanKey, _i, _a, cleanKey;
+            return __generator(this, function (_b) {
+                switch (_b.label) {
+                    case 0:
+                        dirtyKeys = config_1.ldapConfig[domain];
+                        cleanData = new type();
+                        attr = Object.keys(dirtyKeys).map(function (key) { return dirtyKeys[key]; });
+                        return [4 /*yield*/, basics_1.Basics.searchMultiple(domain, attr, id)];
+                    case 1:
+                        dirtyData = (_b.sent())[0];
+                        console.log(dirtyData);
+                        console.log(cleanData);
+                        // Rename output
+                        for (uncleanKey in dirtyData) {
+                            for (_i = 0, _a = Object.keys(cleanData); _i < _a.length; _i++) {
+                                cleanKey = _a[_i];
+                                console.log(cleanKey);
+                                if (uncleanKey == dirtyKeys[cleanKey]) {
+                                    cleanData[cleanKey] = dirtyData[uncleanKey];
+                                }
+                            }
+                        }
+                        return [2 /*return*/, cleanData];
+                }
+            });
+        });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui retrouve les id des paxs ou groupes validant les critères de recherche. Etape vers vrai TOL (Trombino On Line).
+     * Utiliser {@link peekUser} au cas par cas après pour obtenir les vraies infos.
+     * @desc Cette fonction utilise {@link LDAP.search} mais avec un filtre généré à la volée. Accepte des champs exacts ou incomplets pour la plupart des champs
+     * mais pas approximatifs et ne gère pas l'auto-complete. MEF Timeout pour des recherches trop vagues. Va crasher si un champ n'est pas dans ldapConfig.
+     * @param T - Format renvoyé (en pratique {@link userData} ou {@link groupData})
+     * @arg {"us"|"gr"} domain - Domaine de la recherche (utilisateur ou groupe)
+     * @arg {userData | groupData} data - Dictionnaire contenant les données nécessaires à la recherche. Les valeurs sont celles entrées par l'utilisateur et sont par hypothèse
+     * comme des sous-parties compactes des valeurs renvoyées. Tous les champs ci-dessous peuvent être indifféremment des listes (par exemple pour chercher un membre
+     * de plusieurs groupes) ou des éléments isolés. Si un champ n'est pas pertinent, le mettre à '' ou undefined.
+     * @return {Promise(string[])} ids des profils qui "match" les critères proposés.
+     * @static
+     * @async
+     */
+    Tools.search = function (domain, data) {
+        return __awaiter(this, void 0, void 0, function () {
+            var filter, key;
+            return __generator(this, function (_a) {
+                filter = "";
+                // Iteration pour chaque champ, alourdissement du filtre selon des trucs prédéfinis dans ldapConfig encore
+                for (key in data) {
+                    if ((data[key] != undefined) && (data[key] != '')) {
+                        if (!Array.isArray(data[key])) {
+                            data[key] = [data[key]];
+                        } // Génération systématique d'une liste de valeurs à rechercher
+                        // Iteration pour chaque valeur fournie par l'utilisateur
+                        data[key].forEach(function (val) {
+                            // Traduction en language LDAP
+                            var attribute = "";
+                            attribute = config_1.ldapConfig[domain][key];
+                            // Escape user input
+                            val = ldap_escape_1["default"].filter("${fil}", { fil: val });
+                            // Creation incrémentale du filtre
+                            filter = "(&" + filter + "(|(" + attribute + "=" + val + ")" + // On cherche la valeur exacte
+                                "(" + attribute + "=*" + val + ")" + // La valeur finale avec des trucs avant ; wildcard * (MEF la wildcart ne marche pas pour tous les attributs)
+                                "(" + attribute + "=*" + val + "*)" + // La valeur du milieu avec des trucs avant et après
+                                "(" + attribute + "=" + val + "*)))"; // La valeur du début avec des trucs après
+                        });
+                    }
+                }
+                // Appel avec filtre de l'espace 
+                return [2 /*return*/, basics_1.Basics.searchSingle(domain, config_1.ldapConfig.key_id, null, filter)];
+            });
+        });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui édite un groupe ou utilisateur existant dans le LDAP. N'agit pas sur l'apprtenance à un groupe.
+     * @desc Appelle {@link LDAP.change}.
+     * @arg {"us"|"gr"} domain - Domaine de l'opération' (utilisateur ou groupe).
+     * @arg {userData | groupData} data - Dictionnaire avec les nouvelles valeurs de la feuille.
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon.
+     * @async
+     * @static
+     */
+    Tools.edit = function (domain, data) {
+        return __awaiter(this, void 0, void 0, function () {
+            var id, id, dirtyKeys, dirtyData;
+            return __generator(this, function (_a) {
+                if (domain == "user") {
+                    id = data['uid'];
+                }
+                else {
+                    id = data['gid'];
+                }
+                dirtyKeys = config_1.ldapConfig[domain];
+                dirtyData = {};
+                Object.keys(data).forEach(function (key) {
+                    // Some values edit can't change
+                    if (!['readPerm', 'writePerm', 'groups', 'groupsIsAdmin', 'followers', 'members', 'speakers', 'admins'].includes(key)) {
+                        dirtyData[dirtyKeys.key] = data[key];
+                    }
+                });
+                return [2 /*return*/, basics_1.Basics.change(domain, id, "replace", dirtyData)];
+            });
+        });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui permet d'ajouter un utilisateur à une catégorie  d'un groupe.
+     * @desc Cette fonction fait essentiellement appel à d'autres fonctions de {@link Tools} passées en argument et {@link LDAP.change}.
+     * Cette fonction ne créé pas de doublon et opère conjointement dans les deux arbres "group" et "user"
+     * @arg {string} uid - Identifiant du futur membre
+     * @arg {string} gid - Identifiant du groupe
+     * @arg {"admins"|"speakers"|"members"|"followers"} category - Categorie de l'utilisateur concerné (admin, speaker, member ou follower)
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     * @async
+     * @static
+     */
+    Tools.add = function (uid, gid, category) {
+        return __awaiter(this, void 0, void 0, function () {
+            var lu, catName, err_1, lg, catName, err_2;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 4, , 5]);
+                        return [4 /*yield*/, Tools.get(gid, "group", category)];
+                    case 1:
+                        lu = _a.sent();
+                        catName = config_1.ldapConfig.group[category];
+                        if (!!lu.includes(uid)) return [3 /*break*/, 3];
+                        return [4 /*yield*/, basics_1.Basics.change("group", gid, "add", catName)];
+                    case 2:
+                        // Ajoute l'utilisateur dans la catégorie concernée
+                        if (!(_a.sent())) {
+                            throw "Erreur lors de la modification dans l'arbre des groupes pour ajouter un membre dans la catégorie voulue.";
+                        }
+                        _a.label = 3;
+                    case 3: return [3 /*break*/, 5];
+                    case 4:
+                        err_1 = _a.sent();
+                        throw "Erreur pour obtenir une liste de membres d'une catégorie d'un groupe pour ajouter un membre de cette categorie du groupe.";
+                    case 5:
+                        _a.trys.push([5, 9, , 10]);
+                        return [4 /*yield*/, Tools.get(uid, "user", category)];
+                    case 6:
+                        lg = _a.sent();
+                        catName = config_1.ldapConfig.user[category];
+                        if (!!lg.includes(gid)) return [3 /*break*/, 8];
+                        return [4 /*yield*/, basics_1.Basics.change("user", uid, "add", catName)];
+                    case 7:
+                        // Ajoute l'utilisateur dans la categorie voulue
+                        if (!(_a.sent())) {
+                            throw "Erreur lors de l'ajout d'un utilisateur dans une catégorie d'un groupe.";
+                        }
+                        _a.label = 8;
+                    case 8: return [2 /*return*/, true];
+                    case 9:
+                        err_2 = _a.sent();
+                        throw "Erreur pour obtenir une liste de groupes d'une categorie d'un membre pour ajouter un groupe de cette category pour le membre.";
+                    case 10: return [2 /*return*/];
+                }
+            });
+        });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui permet de supprimer un membre d'une catégorie existant d'un groupe.
+     * @desc Cette fonction fait essentiellement appel à d'autres fonctions de {@link Tools} passées en argument et {@link LDAP.change}.
+     * @arg {string} uid - Identifiant de l'ex-membre
+     * @arg {string} gid - Identifiant du groupe
+     * @arg {"admins"|"speakers"|"members"|"followers"} category - Categorie de l'utilisateur concerné (admin, speaker, member ou follower)
+     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
+     * @async
+     * @static
+     */
+    Tools.remove = function (uid, gid, category) {
+        return __awaiter(this, void 0, void 0, function () {
+            var lu, catName, err_3, lg, catName, err_4;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 4, , 5]);
+                        return [4 /*yield*/, Tools.get(gid, "group", category)];
+                    case 1:
+                        lu = _a.sent();
+                        catName = config_1.ldapConfig.group[category];
+                        if (!lu.includes(uid)) return [3 /*break*/, 3];
+                        return [4 /*yield*/, basics_1.Basics.change("group", gid, "del", catName)];
+                    case 2:
+                        // Supprime tous les utilisateurs
+                        if (!(_a.sent())) {
+                            throw "Erreur lors de la suppression de tous les membres d'une catégorie du groupe.";
+                        }
+                        // Les rajoute un par un, sauf pour le supprimé
+                        lu.forEach(function (id) {
+                            if (id != uid) {
+                                Tools.add(id, gid, category).then(function (res) {
+                                    if (!res) {
+                                        throw "Erreur lors du ré-ajout d'un autre membre d'une catégorie.";
+                                    }
+                                });
+                            }
+                        });
+                        _a.label = 3;
+                    case 3: return [3 /*break*/, 5];
+                    case 4:
+                        err_3 = _a.sent();
+                        throw "Erreur pour obtenir une liste de membres d'une catégorie d'un groupe pour supprimer un membre de cette categorie du groupe.";
+                    case 5:
+                        _a.trys.push([5, 9, , 10]);
+                        return [4 /*yield*/, Tools.get(uid, "user", category)];
+                    case 6:
+                        lg = _a.sent();
+                        catName = config_1.ldapConfig.user[category];
+                        if (!lg.includes(gid)) return [3 /*break*/, 8];
+                        return [4 /*yield*/, basics_1.Basics.change("user", uid, "del", catName)];
+                    case 7:
+                        // Supprime tous les groupes de la catégorie pour l'utilisateur
+                        if (!(_a.sent())) {
+                            throw "Erreur lors de la suppression de tous les groupes d'un membre.";
+                        }
+                        // Les rajoute un par un, sauf pour le supprimé
+                        lg.forEach(function (id) {
+                            if (id != uid) {
+                                Tools.add(id, gid, category).then(function (res) {
+                                    if (!res) {
+                                        throw "Erreur lors du ré-ajout d'un autre groupe.";
+                                    }
+                                });
+                            }
+                        });
+                        _a.label = 8;
+                    case 8: return [2 /*return*/, true];
+                    case 9:
+                        err_4 = _a.sent();
+                        throw "Erreur pour obtenir une liste de groupes d'une categorie d'un membre pour supprimer un groupe de cette category pour le membre.";
+                    case 10: return [2 /*return*/];
+                }
+            });
+        });
+    };
+    /**
+     * @callback changeValueCallback
+     * @param {string} id - Id à modifier
+     * @param {number} n - Nombre d'itérations
+     * @return {string} Nouveau id
+     */
+    /**
+     * @memberof LDAP
+     * @summary Cette fonction teste une valeur d'un attribut (typiquement un identifiant) et le fait évoluer jusqu'à ce qu'il soit unique.
+     * @desc Librement adapté de Stack Overflow. Appelle {@link LDAP.search} pour vérifier
+     * qu'il n'y a pas d'autres occurences de cette valeur pour cette attribut
+     * dans le dn fourni.
+     * @param {string} value - Valeur de l'attribut (le plus souvent un identifiant) à tester à cette itération
+     * @param {string} attribute - Attribut à tester
+     * @param {"gr"|"us"} domain - Domaine dans lequel l'attribut doit être unique
+     * @param {changeValueCallback} changeValue - Fonction qui prend uniquement en argument l'id courant et
+     * le nombre d'itérations et qui renvoit la prochaine valeur de l'attribut
+     * @param {number} n [0] - Nombre d'itérations (à initialiser à 0)
+     * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié
+     * @static
+     * @async
+     */
+    Tools.ensureUnique = function (value, attribute, domain, changeValue, n) {
+        if (n === void 0) { n = 0; }
+        return __awaiter(this, void 0, void 0, function () {
+            return __generator(this, function (_a) {
+                // Recherche d'autres occurences de l'id
+                try {
+                    return [2 /*return*/, basics_1.Basics.searchSingle(domain, config_1.ldapConfig.key_id, null, "(" + attribute + "=" + value + ")").then(function (matches) {
+                            if (!matches) {
+                                throw "";
+                            }
+                            else if (matches.length = 0) {
+                                return value;
+                            }
+                            else {
+                                return Tools.ensureUnique(changeValue(value, n + 1), attribute, domain, changeValue, n + 1);
+                            }
+                        })];
+                }
+                catch (err) {
+                    throw "Erreur lors de la recherche d'une valeur pour assurer son unicité.";
+                }
+                return [2 /*return*/];
+            });
+        });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Cette fonction génère un uid standard, puis le fait évoluer jusqu'à ce qu'il soit unique.
+     * @desc Limité à un appel à {@link Tools.ensureUnique} avec les bons paramètres, et quelques opérations sur l'uid pour qu'il soit valide (escape, normalisation).
+     * @param {string} givenName - Prénom
+     * @param {string} lastName - Nom
+     * @param {string} promotion - Année de promotion
+     * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié
+     * @static
+     * @async
+     */
+    Tools.generateUid = function (givenName, lastName, promotion) {
+        return __awaiter(this, void 0, void 0, function () {
+            return __generator(this, function (_a) {
+                try {
+                    // normalize et lowerCase standardisent le format
+                    return [2 /*return*/, Tools.ensureUnique((givenName + '.' + lastName).toLowerCase().normalize('UFD'), config_1.ldapConfig.key_id, "user", function (id, n) {
+                            if (n = 1) {
+                                id += '.' + promotion;
+                            } // Si prénom.nom existe déjà, on rajoute la promo
+                            else if (n = 2) {
+                                id += '.' + (n - 1).toString();
+                            } // Puis si prénom.nom.promo existe déjà on passe à nom.prenom.promo .1
+                            else if (n > 2) {
+                                id += n;
+                            } // Ensuite on continue .123, .1234, etc...
+                            return id;
+                        })];
+                }
+                catch (err) {
+                    throw "Erreur lors de l'assurance de l'unicité d'un human readable unique identifier (hruid).";
+                }
+                return [2 /*return*/];
+            });
+        });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Cette fonction génère un id lisible, puis le fait évoluer jusqu'à ce qu'il soit unique.
+     * @desc Limité à un appel à {@link Tools.ensureUnique} avec les bons paramètres, et quelques opérations sur l'uid pour qu'il soit valide (escape, normalisation).
+     * @param {string} name - Nom
+     * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié
+     * @static
+     * @async
+     */
+    Tools.generateReadableId = function (name) {
+        return __awaiter(this, void 0, void 0, function () {
+            return __generator(this, function (_a) {
+                try {
+                    // normalize et lowerCase standardisent le format
+                    return [2 /*return*/, Tools.ensureUnique(name.toLowerCase().normalize('UFD'), config_1.ldapConfig.key_id, "group", function (id, n) {
+                            if (n = 1) {
+                                id += '.' + n.toString();
+                            } // Si nom existe déjà, on essaie nom.1
+                            else if (n > 1) {
+                                id += n.toString();
+                            } // Ensuite on continue .12, .123, etc...
+                            return id;
+                        })];
+                }
+                catch (err) {
+                    throw "Erreur lors de l'assurance de l'unicité d'un human readable unique identifier (hruid).";
+                }
+                return [2 /*return*/];
+            });
+        });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Cette fonction teste une valeur dummy (0) pour un identifiant numérique puis le fait évoluer aléatoirement (entre 1 et 100 000) jusqu'à ce qu'il soit unique.
+     * @param {string} attribut - Intitulé exact de l'id concerné
+     * @param {"gr"|"us"} domain - Domaine dans lequel l'attribut doit être unique
+     * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié
+     * @static
+     * @async
+     */
+    Tools.generateId = function (attribut, domain) {
+        return __awaiter(this, void 0, void 0, function () {
+            return __generator(this, function (_a) {
+                try {
+                    return [2 /*return*/, Tools.ensureUnique("0", attribut, domain, function (id, n) { return Math.floor((Math.random() * 100000) + 1).toString(); })];
+                }
+                catch (err) {
+                    throw "Erreur lors de l'assurance de l'unicité d'un unique identifier numérique.";
+                }
+                return [2 /*return*/];
+            });
+        });
+    };
+    /**
+     * @memberof LDAP
+     * @summary Fonction qui retrouve les utilisateurs ou groupes respectivement correspondant à un groupe ou un utilisateur de la même catégorie.
+     * @desc Cette fonction utilise {@link LDAP.search} et va directement à la feuille de l'utilisateur ou du groupe interrogé.
+     * Pour autant, elle est moins naïve qu'elle en a l'air. Elle ne gère ni la descente des admins ni la remontée des membres et renvoit une réponse naïve.
+     * @param {string} id - Identifiant du groupe ou de l'individu à interroger (supposé valide)
+     * @param {"user"|"group"} domain - Arbre à interroger
+     * @param {"admins"|"speakers"|"members"|"followers"} category - Catégorie considérée
+     * @return {Promise(string[])} Liste des id de groupes ou d'utilisateurs de la bonne catégorie associé à l'id
+     * @static
+     * @async
+     */
+    Tools.get = function (id, domain, category) {
+        return __awaiter(this, void 0, void 0, function () {
+            var stack, res, visited, cur_id, _a, _b, _c, _d, _e, _f, err_5;
+            return __generator(this, function (_g) {
+                switch (_g.label) {
+                    case 0:
+                        _g.trys.push([0, 11, , 12]);
+                        return [4 /*yield*/, basics_1.Basics.searchSingle(domain, config_1.ldapConfig[domain][category], id)];
+                    case 1: return [2 /*return*/, _g.sent()];
+                    case 2: return [2 /*return*/, _g.sent()];
+                    case 3:
+                        stack = [];
+                        res = [];
+                        visited = {};
+                        stack.push(id);
+                        _g.label = 4;
+                    case 4:
+                        if (!(stack.length > 0)) return [3 /*break*/, 10];
+                        cur_id = stack.pop();
+                        if (!(visited[cur_id] == undefined)) return [3 /*break*/, 9];
+                        visited[cur_id] = true;
+                        _b = (_a = res).concat;
+                        return [4 /*yield*/, basics_1.Basics.searchSingle("group", config_1.ldapConfig.group[category], cur_id)];
+                    case 5:
+                        _b.apply(_a, [_g.sent()]);
+                        if (!(category == "members")) return [3 /*break*/, 7];
+                        _d = (_c = stack).concat;
+                        return [4 /*yield*/, basics_1.Basics.searchSingle("group", config_1.ldapConfig.group.childs, cur_id)];
+                    case 6:
+                        _d.apply(_c, [_g.sent()]);
+                        return [3 /*break*/, 9];
+                    case 7:
+                        _f = (_e = stack).concat;
+                        return [4 /*yield*/, basics_1.Basics.searchSingle("group", config_1.ldapConfig.group.parents, cur_id)];
+                    case 8:
+                        _f.apply(_e, [_g.sent()]);
+                        _g.label = 9;
+                    case 9: return [3 /*break*/, 4];
+                    case 10: return [3 /*break*/, 12];
+                    case 11:
+                        err_5 = _g.sent();
+                        throw "Erreur lors d'une recherche générique d'un membre d'une certaine catégorie d'un groupe.";
+                    case 12: return [2 /*return*/];
+                }
+            });
+        });
+    };
+    return Tools;
+}());
+exports.Tools = Tools;
diff --git a/src/ldap/test.js b/src/ldap/test.js
new file mode 100644
index 0000000..5b8f1d1
--- /dev/null
+++ b/src/ldap/test.js
@@ -0,0 +1,5 @@
+var Group = require("./export/group");
+
+console.log(Group.peek("faerix"));
+
+var User = require("./export/group");
\ No newline at end of file
-- 
GitLab