From b8dc8652c86a2690da018af9b0a161368dd17c0d Mon Sep 17 00:00:00 2001
From: Pouria Maleki <dev.maleki@gmail.com>
Date: Sun, 21 Feb 2021 15:28:56 +0200
Subject: [PATCH] Basic access management

---
 backend/.env.sample                           |    1 +
 backend/package-lock.json                     | 1179 +++++++++++++++--
 backend/package.json                          |    2 +
 .../20210202171154_access/migration.sql       |    2 +
 backend/prisma/schema.prisma                  |   11 +-
 backend/src/graphql/resolverHelper.ts         |    5 +-
 backend/src/graphql/resolvers-types.ts        |   28 +
 backend/src/server/apolloContext.ts           |   10 +-
 backend/src/server/express.ts                 |    6 +-
 backend/src/server/expressAddUserToRequest.ts |   17 +
 backend/src/types.ts                          |    4 +-
 backend/src/user/createTokenFromUser.test.ts  |    6 +-
 backend/src/user/createTokenFromUser.ts       |    2 -
 backend/src/user/forgotPassword.test.ts       |    2 +-
 backend/src/user/getUser.ts                   |    2 +-
 .../src/user/permissions/allPermissions.ts    |    6 +
 .../permissions/checkMissingPermissions.ts    |   14 +
 .../src/user/permissions/getAllPermissions.ts |    5 +
 backend/src/user/resetPassword.test.ts        |    2 +-
 backend/src/user/updateUser.test.ts           |   95 ++
 backend/src/user/updateUser.ts                |   54 +
 backend/src/user/user.gql                     |   15 +
 backend/src/user/user.resolver.ts             |    4 +
 .../__mocks__/getTokenValidator.ts            |    2 +-
 backend/src/user/utils/constants.ts           |    1 +
 .../src/user/{ => utils}/createFakeToken.ts   |    4 +-
 .../src/user/{ => utils}/getTokenValidator.ts |    4 +-
 backend/src/user/utils/getUserById.ts         |    9 +
 backend/src/user/validateSocialLogin.test.ts  |    2 +-
 backend/src/user/validateSocialLogin.ts       |    2 +-
 backend/src/utils/isDevEnv.ts                 |    1 +
 frontend/package-lock.json                    |  190 +++
 frontend/src/graphql/hooks.ts                 |   28 +-
 33 files changed, 1534 insertions(+), 181 deletions(-)
 create mode 100644 backend/prisma/migrations/20210202171154_access/migration.sql
 create mode 100644 backend/src/server/expressAddUserToRequest.ts
 create mode 100644 backend/src/user/permissions/allPermissions.ts
 create mode 100644 backend/src/user/permissions/checkMissingPermissions.ts
 create mode 100644 backend/src/user/permissions/getAllPermissions.ts
 create mode 100644 backend/src/user/updateUser.test.ts
 create mode 100644 backend/src/user/updateUser.ts
 rename backend/src/user/{ => utils}/__mocks__/getTokenValidator.ts (76%)
 create mode 100644 backend/src/user/utils/constants.ts
 rename backend/src/user/{ => utils}/createFakeToken.ts (79%)
 rename backend/src/user/{ => utils}/getTokenValidator.ts (92%)
 create mode 100644 backend/src/user/utils/getUserById.ts
 create mode 100644 backend/src/utils/isDevEnv.ts

diff --git a/backend/.env.sample b/backend/.env.sample
index 9256656c..b199b893 100644
--- a/backend/.env.sample
+++ b/backend/.env.sample
@@ -2,6 +2,7 @@ PORT=4000
 JWT_SECRET="JWT_DARK_SECRET"
 ALLOW_ORIGIN=http://localhost:3000
 CLIENT_ADDRESS=http://localhost:3000
+NODE_ENV=development
 
 DATABASE_URL="postgresql://admin:example@localhost:5432/moro?schema=public"
 
diff --git a/backend/package-lock.json b/backend/package-lock.json
index de1eef72..dc310ce5 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -2630,6 +2630,12 @@
         "@types/koa": "*"
       }
     },
+    "@types/lodash": {
+      "version": "4.14.168",
+      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz",
+      "integrity": "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==",
+      "dev": true
+    },
     "@types/long": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz",
@@ -4443,6 +4449,32 @@
         "array-find-index": "^1.0.1"
       }
     },
+    "dashdash": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+      "dev": true,
+      "requires": {
+        "cssom": "~0.3.6"
+      },
+      "dependencies": {
+        "cssom": {
+          "version": "0.3.8",
+          "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
+          "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
+          "dev": true
+        }
+      }
+    },
+    "currently-unhandled": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
+      "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
+      "dev": true,
+      "requires": {
+        "array-find-index": "^1.0.1"
+      }
+    },
     "dashdash": {
       "version": "1.14.1",
       "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -7117,14 +7149,24 @@
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
       "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
     },
-    "js-yaml": {
-      "version": "3.14.1",
-      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
-      "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+    "istanbul-lib-instrument": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz",
+      "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==",
       "dev": true,
       "requires": {
-        "argparse": "^1.0.7",
-        "esprima": "^4.0.0"
+        "@babel/core": "^7.7.5",
+        "@istanbuljs/schema": "^0.1.2",
+        "istanbul-lib-coverage": "^3.0.0",
+        "semver": "^6.3.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        }
       }
     },
     "jsbn": {
@@ -7193,162 +7235,883 @@
     },
     "json-buffer": {
       "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
-      "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=",
-      "dev": true
-    },
-    "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==",
-      "dev": true
-    },
-    "json-parse-even-better-errors": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
-      "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
-      "dev": true
-    },
-    "json-schema": {
-      "version": "0.2.3",
-      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
-      "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
-      "dev": true
-    },
-    "json-schema-traverse": {
-      "version": "0.4.1",
-      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
-      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
-      "dev": true
-    },
-    "json-stable-stringify": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
-      "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
-      "dev": true,
-      "requires": {
-        "jsonify": "~0.0.0"
-      }
-    },
-    "json-stringify-safe": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
-      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
-      "dev": true
-    },
-    "json-to-pretty-yaml": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/json-to-pretty-yaml/-/json-to-pretty-yaml-1.2.2.tgz",
-      "integrity": "sha1-9M0L0KXo/h3yWq9boRiwmf2ZLVs=",
+      "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
+      "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==",
       "dev": true,
       "requires": {
-        "remedial": "^1.0.7",
-        "remove-trailing-spaces": "^1.0.6"
-      }
-    },
-    "json5": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
-      "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
-      "requires": {
-        "minimist": "^1.2.5"
-      }
-    },
-    "jsonify": {
-      "version": "0.0.0",
-      "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
-      "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
-      "dev": true
-    },
-    "jsonwebtoken": {
-      "version": "8.5.1",
-      "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
-      "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
-      "requires": {
-        "jws": "^3.2.2",
-        "lodash.includes": "^4.3.0",
-        "lodash.isboolean": "^3.0.3",
-        "lodash.isinteger": "^4.0.4",
-        "lodash.isnumber": "^3.0.3",
-        "lodash.isplainobject": "^4.0.6",
-        "lodash.isstring": "^4.0.1",
-        "lodash.once": "^4.0.0",
-        "ms": "^2.1.1",
-        "semver": "^5.6.0"
+        "istanbul-lib-coverage": "^3.0.0",
+        "make-dir": "^3.0.0",
+        "supports-color": "^7.1.0"
       },
       "dependencies": {
-        "semver": {
-          "version": "5.7.1",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
-          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+        "has-flag": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+          "dev": true
+        },
+        "supports-color": {
+          "version": "7.2.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+          "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+          "dev": true,
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
         }
       }
     },
-    "jsprim": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
-      "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+    "istanbul-lib-source-maps": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz",
+      "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==",
       "dev": true,
       "requires": {
-        "assert-plus": "1.0.0",
-        "extsprintf": "1.3.0",
-        "json-schema": "0.2.3",
-        "verror": "1.10.0"
+        "debug": "^4.1.1",
+        "istanbul-lib-coverage": "^3.0.0",
+        "source-map": "^0.6.1"
       }
     },
-    "jwa": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
-      "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+    "istanbul-reports": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz",
+      "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==",
+      "dev": true,
       "requires": {
-        "buffer-equal-constant-time": "1.0.1",
-        "ecdsa-sig-formatter": "1.0.11",
-        "safe-buffer": "^5.0.1"
+        "html-escaper": "^2.0.0",
+        "istanbul-lib-report": "^3.0.0"
       }
     },
-    "jws": {
-      "version": "3.2.2",
-      "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
-      "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
-      "requires": {
-        "jwa": "^1.4.1",
-        "safe-buffer": "^5.0.1"
-      }
+    "iterall": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz",
+      "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg=="
     },
-    "keyv": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz",
-      "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==",
+    "jest": {
+      "version": "26.6.3",
+      "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz",
+      "integrity": "sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==",
       "dev": true,
       "requires": {
-        "json-buffer": "3.0.0"
+        "@jest/core": "^26.6.3",
+        "import-local": "^3.0.2",
+        "jest-cli": "^26.6.3"
+      },
+      "dependencies": {
+        "jest-cli": {
+          "version": "26.6.3",
+          "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz",
+          "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==",
+          "dev": true,
+          "requires": {
+            "@jest/core": "^26.6.3",
+            "@jest/test-result": "^26.6.2",
+            "@jest/types": "^26.6.2",
+            "chalk": "^4.0.0",
+            "exit": "^0.1.2",
+            "graceful-fs": "^4.2.4",
+            "import-local": "^3.0.2",
+            "is-ci": "^2.0.0",
+            "jest-config": "^26.6.3",
+            "jest-util": "^26.6.2",
+            "jest-validate": "^26.6.2",
+            "prompts": "^2.0.1",
+            "yargs": "^15.4.1"
+          }
+        }
       }
     },
-    "kind-of": {
-      "version": "6.0.3",
-      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
-      "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
-      "dev": true
-    },
-    "kleur": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
-      "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
-      "dev": true
-    },
-    "latest-version": {
-      "version": "5.1.0",
-      "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz",
-      "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==",
+    "jest-changed-files": {
+      "version": "26.6.2",
+      "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz",
+      "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==",
       "dev": true,
       "requires": {
-        "package-json": "^6.3.0"
-      }
-    },
-    "leven": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
-      "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+        "@jest/types": "^26.6.2",
+        "execa": "^4.0.0",
+        "throat": "^5.0.0"
+      },
+      "dependencies": {
+        "cross-spawn": {
+          "version": "7.0.3",
+          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+          "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+          "dev": true,
+          "requires": {
+            "path-key": "^3.1.0",
+            "shebang-command": "^2.0.0",
+            "which": "^2.0.1"
+          }
+        },
+        "execa": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz",
+          "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==",
+          "dev": true,
+          "requires": {
+            "cross-spawn": "^7.0.0",
+            "get-stream": "^5.0.0",
+            "human-signals": "^1.1.1",
+            "is-stream": "^2.0.0",
+            "merge-stream": "^2.0.0",
+            "npm-run-path": "^4.0.0",
+            "onetime": "^5.1.0",
+            "signal-exit": "^3.0.2",
+            "strip-final-newline": "^2.0.0"
+          }
+        },
+        "get-stream": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+          "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+          "dev": true,
+          "requires": {
+            "pump": "^3.0.0"
+          }
+        },
+        "is-stream": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
+          "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==",
+          "dev": true
+        },
+        "npm-run-path": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+          "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+          "dev": true,
+          "requires": {
+            "path-key": "^3.0.0"
+          }
+        },
+        "path-key": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+          "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+          "dev": true
+        },
+        "shebang-command": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+          "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+          "dev": true,
+          "requires": {
+            "shebang-regex": "^3.0.0"
+          }
+        },
+        "shebang-regex": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+          "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+          "dev": true
+        },
+        "which": {
+          "version": "2.0.2",
+          "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+          "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+          "dev": true,
+          "requires": {
+            "isexe": "^2.0.0"
+          }
+        }
+      }
+    },
+    "jest-config": {
+      "version": "26.6.3",
+      "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz",
+      "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==",
+      "dev": true,
+      "requires": {
+        "@babel/core": "^7.1.0",
+        "@jest/test-sequencer": "^26.6.3",
+        "@jest/types": "^26.6.2",
+        "babel-jest": "^26.6.3",
+        "chalk": "^4.0.0",
+        "deepmerge": "^4.2.2",
+        "glob": "^7.1.1",
+        "graceful-fs": "^4.2.4",
+        "jest-environment-jsdom": "^26.6.2",
+        "jest-environment-node": "^26.6.2",
+        "jest-get-type": "^26.3.0",
+        "jest-jasmine2": "^26.6.3",
+        "jest-regex-util": "^26.0.0",
+        "jest-resolve": "^26.6.2",
+        "jest-util": "^26.6.2",
+        "jest-validate": "^26.6.2",
+        "micromatch": "^4.0.2",
+        "pretty-format": "^26.6.2"
+      }
+    },
+    "jest-diff": {
+      "version": "26.6.2",
+      "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz",
+      "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==",
+      "dev": true,
+      "requires": {
+        "chalk": "^4.0.0",
+        "diff-sequences": "^26.6.2",
+        "jest-get-type": "^26.3.0",
+        "pretty-format": "^26.6.2"
+      }
+    },
+    "jest-docblock": {
+      "version": "26.0.0",
+      "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz",
+      "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==",
+      "dev": true,
+      "requires": {
+        "detect-newline": "^3.0.0"
+      }
+    },
+    "jest-each": {
+      "version": "26.6.2",
+      "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz",
+      "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==",
+      "dev": true,
+      "requires": {
+        "@jest/types": "^26.6.2",
+        "chalk": "^4.0.0",
+        "jest-get-type": "^26.3.0",
+        "jest-util": "^26.6.2",
+        "pretty-format": "^26.6.2"
+      }
+    },
+    "jest-environment-jsdom": {
+      "version": "26.6.2",
+      "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz",
+      "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==",
+      "dev": true,
+      "requires": {
+        "@jest/environment": "^26.6.2",
+        "@jest/fake-timers": "^26.6.2",
+        "@jest/types": "^26.6.2",
+        "@types/node": "*",
+        "jest-mock": "^26.6.2",
+        "jest-util": "^26.6.2",
+        "jsdom": "^16.4.0"
+      }
+    },
+    "jest-environment-node": {
+      "version": "26.6.2",
+      "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz",
+      "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==",
+      "dev": true,
+      "requires": {
+        "@jest/environment": "^26.6.2",
+        "@jest/fake-timers": "^26.6.2",
+        "@jest/types": "^26.6.2",
+        "@types/node": "*",
+        "jest-mock": "^26.6.2",
+        "jest-util": "^26.6.2"
+      }
+    },
+    "jest-get-type": {
+      "version": "26.3.0",
+      "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz",
+      "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==",
+      "dev": true
+    },
+    "jest-haste-map": {
+      "version": "26.6.2",
+      "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz",
+      "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==",
+      "dev": true,
+      "requires": {
+        "@jest/types": "^26.6.2",
+        "@types/graceful-fs": "^4.1.2",
+        "@types/node": "*",
+        "anymatch": "^3.0.3",
+        "fb-watchman": "^2.0.0",
+        "fsevents": "^2.1.2",
+        "graceful-fs": "^4.2.4",
+        "jest-regex-util": "^26.0.0",
+        "jest-serializer": "^26.6.2",
+        "jest-util": "^26.6.2",
+        "jest-worker": "^26.6.2",
+        "micromatch": "^4.0.2",
+        "sane": "^4.0.3",
+        "walker": "^1.0.7"
+      }
+    },
+    "jest-jasmine2": {
+      "version": "26.6.3",
+      "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz",
+      "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==",
+      "dev": true,
+      "requires": {
+        "@babel/traverse": "^7.1.0",
+        "@jest/environment": "^26.6.2",
+        "@jest/source-map": "^26.6.2",
+        "@jest/test-result": "^26.6.2",
+        "@jest/types": "^26.6.2",
+        "@types/node": "*",
+        "chalk": "^4.0.0",
+        "co": "^4.6.0",
+        "expect": "^26.6.2",
+        "is-generator-fn": "^2.0.0",
+        "jest-each": "^26.6.2",
+        "jest-matcher-utils": "^26.6.2",
+        "jest-message-util": "^26.6.2",
+        "jest-runtime": "^26.6.3",
+        "jest-snapshot": "^26.6.2",
+        "jest-util": "^26.6.2",
+        "pretty-format": "^26.6.2",
+        "throat": "^5.0.0"
+      }
+    },
+    "jest-leak-detector": {
+      "version": "26.6.2",
+      "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz",
+      "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==",
+      "dev": true,
+      "requires": {
+        "jest-get-type": "^26.3.0",
+        "pretty-format": "^26.6.2"
+      }
+    },
+    "jest-matcher-utils": {
+      "version": "26.6.2",
+      "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz",
+      "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==",
+      "dev": true,
+      "requires": {
+        "chalk": "^4.0.0",
+        "jest-diff": "^26.6.2",
+        "jest-get-type": "^26.3.0",
+        "pretty-format": "^26.6.2"
+      }
+    },
+    "jest-message-util": {
+      "version": "26.6.2",
+      "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz",
+      "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==",
+      "dev": true,
+      "requires": {
+        "@babel/code-frame": "^7.0.0",
+        "@jest/types": "^26.6.2",
+        "@types/stack-utils": "^2.0.0",
+        "chalk": "^4.0.0",
+        "graceful-fs": "^4.2.4",
+        "micromatch": "^4.0.2",
+        "pretty-format": "^26.6.2",
+        "slash": "^3.0.0",
+        "stack-utils": "^2.0.2"
+      }
+    },
+    "jest-mock": {
+      "version": "26.6.2",
+      "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz",
+      "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==",
+      "dev": true,
+      "requires": {
+        "@jest/types": "^26.6.2",
+        "@types/node": "*"
+      }
+    },
+    "jest-pnp-resolver": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz",
+      "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==",
+      "dev": true
+    },
+    "jest-regex-util": {
+      "version": "26.0.0",
+      "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz",
+      "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==",
+      "dev": true
+    },
+    "jest-resolve": {
+      "version": "26.6.2",
+      "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz",
+      "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==",
+      "dev": true,
+      "requires": {
+        "@jest/types": "^26.6.2",
+        "chalk": "^4.0.0",
+        "graceful-fs": "^4.2.4",
+        "jest-pnp-resolver": "^1.2.2",
+        "jest-util": "^26.6.2",
+        "read-pkg-up": "^7.0.1",
+        "resolve": "^1.18.1",
+        "slash": "^3.0.0"
+      },
+      "dependencies": {
+        "read-pkg": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
+          "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
+          "dev": true,
+          "requires": {
+            "@types/normalize-package-data": "^2.4.0",
+            "normalize-package-data": "^2.5.0",
+            "parse-json": "^5.0.0",
+            "type-fest": "^0.6.0"
+          },
+          "dependencies": {
+            "type-fest": {
+              "version": "0.6.0",
+              "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
+              "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
+              "dev": true
+            }
+          }
+        },
+        "read-pkg-up": {
+          "version": "7.0.1",
+          "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz",
+          "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==",
+          "dev": true,
+          "requires": {
+            "find-up": "^4.1.0",
+            "read-pkg": "^5.2.0",
+            "type-fest": "^0.8.1"
+          }
+        }
+      }
+    },
+    "jest-resolve-dependencies": {
+      "version": "26.6.3",
+      "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz",
+      "integrity": "sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==",
+      "dev": true,
+      "requires": {
+        "@jest/types": "^26.6.2",
+        "jest-regex-util": "^26.0.0",
+        "jest-snapshot": "^26.6.2"
+      }
+    },
+    "jest-runner": {
+      "version": "26.6.3",
+      "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz",
+      "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==",
+      "dev": true,
+      "requires": {
+        "@jest/console": "^26.6.2",
+        "@jest/environment": "^26.6.2",
+        "@jest/test-result": "^26.6.2",
+        "@jest/types": "^26.6.2",
+        "@types/node": "*",
+        "chalk": "^4.0.0",
+        "emittery": "^0.7.1",
+        "exit": "^0.1.2",
+        "graceful-fs": "^4.2.4",
+        "jest-config": "^26.6.3",
+        "jest-docblock": "^26.0.0",
+        "jest-haste-map": "^26.6.2",
+        "jest-leak-detector": "^26.6.2",
+        "jest-message-util": "^26.6.2",
+        "jest-resolve": "^26.6.2",
+        "jest-runtime": "^26.6.3",
+        "jest-util": "^26.6.2",
+        "jest-worker": "^26.6.2",
+        "source-map-support": "^0.5.6",
+        "throat": "^5.0.0"
+      }
+    },
+    "jest-runtime": {
+      "version": "26.6.3",
+      "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz",
+      "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==",
+      "dev": true,
+      "requires": {
+        "@jest/console": "^26.6.2",
+        "@jest/environment": "^26.6.2",
+        "@jest/fake-timers": "^26.6.2",
+        "@jest/globals": "^26.6.2",
+        "@jest/source-map": "^26.6.2",
+        "@jest/test-result": "^26.6.2",
+        "@jest/transform": "^26.6.2",
+        "@jest/types": "^26.6.2",
+        "@types/yargs": "^15.0.0",
+        "chalk": "^4.0.0",
+        "cjs-module-lexer": "^0.6.0",
+        "collect-v8-coverage": "^1.0.0",
+        "exit": "^0.1.2",
+        "glob": "^7.1.3",
+        "graceful-fs": "^4.2.4",
+        "jest-config": "^26.6.3",
+        "jest-haste-map": "^26.6.2",
+        "jest-message-util": "^26.6.2",
+        "jest-mock": "^26.6.2",
+        "jest-regex-util": "^26.0.0",
+        "jest-resolve": "^26.6.2",
+        "jest-snapshot": "^26.6.2",
+        "jest-util": "^26.6.2",
+        "jest-validate": "^26.6.2",
+        "slash": "^3.0.0",
+        "strip-bom": "^4.0.0",
+        "yargs": "^15.4.1"
+      },
+      "dependencies": {
+        "strip-bom": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
+          "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
+          "dev": true
+        }
+      }
+    },
+    "jest-serializer": {
+      "version": "26.6.2",
+      "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz",
+      "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*",
+        "graceful-fs": "^4.2.4"
+      }
+    },
+    "jest-snapshot": {
+      "version": "26.6.2",
+      "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz",
+      "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.0.0",
+        "@jest/types": "^26.6.2",
+        "@types/babel__traverse": "^7.0.4",
+        "@types/prettier": "^2.0.0",
+        "chalk": "^4.0.0",
+        "expect": "^26.6.2",
+        "graceful-fs": "^4.2.4",
+        "jest-diff": "^26.6.2",
+        "jest-get-type": "^26.3.0",
+        "jest-haste-map": "^26.6.2",
+        "jest-matcher-utils": "^26.6.2",
+        "jest-message-util": "^26.6.2",
+        "jest-resolve": "^26.6.2",
+        "natural-compare": "^1.4.0",
+        "pretty-format": "^26.6.2",
+        "semver": "^7.3.2"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "7.3.4",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
+          "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
+          "dev": true,
+          "requires": {
+            "lru-cache": "^6.0.0"
+          }
+        }
+      }
+    },
+    "jest-util": {
+      "version": "26.6.2",
+      "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz",
+      "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==",
+      "dev": true,
+      "requires": {
+        "@jest/types": "^26.6.2",
+        "@types/node": "*",
+        "chalk": "^4.0.0",
+        "graceful-fs": "^4.2.4",
+        "is-ci": "^2.0.0",
+        "micromatch": "^4.0.2"
+      }
+    },
+    "jest-validate": {
+      "version": "26.6.2",
+      "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz",
+      "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==",
+      "dev": true,
+      "requires": {
+        "@jest/types": "^26.6.2",
+        "camelcase": "^6.0.0",
+        "chalk": "^4.0.0",
+        "jest-get-type": "^26.3.0",
+        "leven": "^3.1.0",
+        "pretty-format": "^26.6.2"
+      },
+      "dependencies": {
+        "camelcase": {
+          "version": "6.2.0",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz",
+          "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==",
+          "dev": true
+        }
+      }
+    },
+    "jest-watcher": {
+      "version": "26.6.2",
+      "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz",
+      "integrity": "sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==",
+      "dev": true,
+      "requires": {
+        "@jest/test-result": "^26.6.2",
+        "@jest/types": "^26.6.2",
+        "@types/node": "*",
+        "ansi-escapes": "^4.2.1",
+        "chalk": "^4.0.0",
+        "jest-util": "^26.6.2",
+        "string-length": "^4.0.1"
+      }
+    },
+    "jest-worker": {
+      "version": "26.6.2",
+      "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
+      "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*",
+        "merge-stream": "^2.0.0",
+        "supports-color": "^7.0.0"
+      },
+      "dependencies": {
+        "has-flag": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+          "dev": true
+        },
+        "supports-color": {
+          "version": "7.2.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+          "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+          "dev": true,
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
+        }
+      }
+    },
+    "js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+    },
+    "js-yaml": {
+      "version": "3.14.1",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+      "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+      "dev": true,
+      "requires": {
+        "argparse": "^1.0.7",
+        "esprima": "^4.0.0"
+      }
+    },
+    "jsbn": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+      "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+      "dev": true
+    },
+    "jsdom": {
+      "version": "16.4.0",
+      "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.4.0.tgz",
+      "integrity": "sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==",
+      "dev": true,
+      "requires": {
+        "abab": "^2.0.3",
+        "acorn": "^7.1.1",
+        "acorn-globals": "^6.0.0",
+        "cssom": "^0.4.4",
+        "cssstyle": "^2.2.0",
+        "data-urls": "^2.0.0",
+        "decimal.js": "^10.2.0",
+        "domexception": "^2.0.1",
+        "escodegen": "^1.14.1",
+        "html-encoding-sniffer": "^2.0.1",
+        "is-potential-custom-element-name": "^1.0.0",
+        "nwsapi": "^2.2.0",
+        "parse5": "5.1.1",
+        "request": "^2.88.2",
+        "request-promise-native": "^1.0.8",
+        "saxes": "^5.0.0",
+        "symbol-tree": "^3.2.4",
+        "tough-cookie": "^3.0.1",
+        "w3c-hr-time": "^1.0.2",
+        "w3c-xmlserializer": "^2.0.0",
+        "webidl-conversions": "^6.1.0",
+        "whatwg-encoding": "^1.0.5",
+        "whatwg-mimetype": "^2.3.0",
+        "whatwg-url": "^8.0.0",
+        "ws": "^7.2.3",
+        "xml-name-validator": "^3.0.0"
+      },
+      "dependencies": {
+        "tough-cookie": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz",
+          "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==",
+          "dev": true,
+          "requires": {
+            "ip-regex": "^2.1.0",
+            "psl": "^1.1.28",
+            "punycode": "^2.1.1"
+          }
+        },
+        "ws": {
+          "version": "7.4.2",
+          "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz",
+          "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==",
+          "dev": true
+        }
+      }
+    },
+    "jsesc": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+      "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
+    },
+    "json-buffer": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
+      "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=",
+      "dev": true
+    },
+    "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==",
+      "dev": true
+    },
+    "json-parse-even-better-errors": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+      "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+      "dev": true
+    },
+    "json-schema": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+      "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
+      "dev": true
+    },
+    "json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
+    "json-stable-stringify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
+      "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
+      "dev": true,
+      "requires": {
+        "jsonify": "~0.0.0"
+      }
+    },
+    "json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
+      "dev": true
+    },
+    "json-to-pretty-yaml": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/json-to-pretty-yaml/-/json-to-pretty-yaml-1.2.2.tgz",
+      "integrity": "sha1-9M0L0KXo/h3yWq9boRiwmf2ZLVs=",
+      "dev": true,
+      "requires": {
+        "remedial": "^1.0.7",
+        "remove-trailing-spaces": "^1.0.6"
+      }
+    },
+    "json5": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
+      "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
+      "requires": {
+        "minimist": "^1.2.5"
+      }
+    },
+    "jsonify": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
+      "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
+      "dev": true
+    },
+    "jsonwebtoken": {
+      "version": "8.5.1",
+      "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
+      "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
+      "requires": {
+        "jws": "^3.2.2",
+        "lodash.includes": "^4.3.0",
+        "lodash.isboolean": "^3.0.3",
+        "lodash.isinteger": "^4.0.4",
+        "lodash.isnumber": "^3.0.3",
+        "lodash.isplainobject": "^4.0.6",
+        "lodash.isstring": "^4.0.1",
+        "lodash.once": "^4.0.0",
+        "ms": "^2.1.1",
+        "semver": "^5.6.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+        }
+      }
+    },
+    "jsprim": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+      "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+      "dev": true,
+      "requires": {
+        "assert-plus": "1.0.0",
+        "extsprintf": "1.3.0",
+        "json-schema": "0.2.3",
+        "verror": "1.10.0"
+      }
+    },
+    "jwa": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+      "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+      "requires": {
+        "buffer-equal-constant-time": "1.0.1",
+        "ecdsa-sig-formatter": "1.0.11",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "jws": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+      "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+      "requires": {
+        "jwa": "^1.4.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "keyv": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz",
+      "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==",
+      "dev": true,
+      "requires": {
+        "json-buffer": "3.0.0"
+      }
+    },
+    "kind-of": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+      "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+      "dev": true
+    },
+    "kleur": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
+      "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
+      "dev": true
+    },
+    "latest-version": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz",
+      "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==",
+      "dev": true,
+      "requires": {
+        "package-json": "^6.3.0"
+      }
+    },
+    "leven": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+      "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
       "dev": true
     },
     "levn": {
@@ -9787,6 +10550,140 @@
         }
       }
     },
+    "snapdragon": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
+      "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
+      "dev": true,
+      "requires": {
+        "base": "^0.11.1",
+        "debug": "^2.2.0",
+        "define-property": "^0.2.5",
+        "extend-shallow": "^2.0.1",
+        "map-cache": "^0.2.2",
+        "source-map": "^0.5.6",
+        "source-map-resolve": "^0.5.0",
+        "use": "^3.1.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        },
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true
+        }
+      }
+    },
+    "snapdragon-node": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
+      "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
+      "dev": true,
+      "requires": {
+        "define-property": "^1.0.0",
+        "isobject": "^3.0.0",
+        "snapdragon-util": "^3.0.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^1.0.0"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        },
+        "isobject": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+          "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+          "dev": true
+        }
+      }
+    },
+    "snapdragon-util": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
+      "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.2.0"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
     "source-map": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
diff --git a/backend/package.json b/backend/package.json
index 41705ef7..2abdd12c 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -31,6 +31,7 @@
     "graphql-tools": "^7.0.2",
     "helmet": "^4.2.0",
     "jsonwebtoken": "^8.5.1",
+    "lodash": "^4.17.20",
     "node-fetch": "^2.6.1",
     "uuid": "^8.3.2"
   },
@@ -45,6 +46,7 @@
     "@types/express": "^4.17.9",
     "@types/express-jwt": "^6.0.0",
     "@types/jest": "^26.0.20",
+    "@types/lodash": "^4.14.168",
     "@types/node": "^14.14.11",
     "@types/uuid": "^8.3.0",
     "concurrently": "^5.3.0",
diff --git a/backend/prisma/migrations/20210202171154_access/migration.sql b/backend/prisma/migrations/20210202171154_access/migration.sql
new file mode 100644
index 00000000..eb2202b0
--- /dev/null
+++ b/backend/prisma/migrations/20210202171154_access/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "User" ADD COLUMN     "permissions" TEXT[];
diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma
index 4e62ac9d..e6217cf2 100644
--- a/backend/prisma/schema.prisma
+++ b/backend/prisma/schema.prisma
@@ -17,11 +17,12 @@ model Project {
 }
 
 model User {
-  id       Int      @id @default(autoincrement())
-  email    String   @unique
-  name     String?
-  password String?
-  projects Project?
+  id          Int      @id @default(autoincrement())
+  email       String   @unique
+  name        String?
+  password    String?
+  projects    Project?
+  permissions String[]
 }
 
 enum TOKEN_TYPES {
diff --git a/backend/src/graphql/resolverHelper.ts b/backend/src/graphql/resolverHelper.ts
index fbf77520..d46070af 100644
--- a/backend/src/graphql/resolverHelper.ts
+++ b/backend/src/graphql/resolverHelper.ts
@@ -1,4 +1,5 @@
 import { prisma } from '../server/prisma';
+import { UserWithoutPassword } from '../types';
 import { StitchingResolver } from './resolvers-types';
 
 /*
@@ -24,6 +25,6 @@ export function resolverHelper<TArgs, TResult>(
     | StitchingResolver<TResult, any, any, TArgs>
     | undefined,
 ) {
-  return (args: TArgs): TResult =>
-    (resolver as ResolverFn<TArgs, TResult>)({}, args, { prisma }, {});
+  return (args: TArgs, user?: UserWithoutPassword): TResult =>
+    (resolver as ResolverFn<TArgs, TResult>)({}, args, { prisma, user }, {});
 }
diff --git a/backend/src/graphql/resolvers-types.ts b/backend/src/graphql/resolvers-types.ts
index 2239f0b3..27631fc2 100644
--- a/backend/src/graphql/resolvers-types.ts
+++ b/backend/src/graphql/resolvers-types.ts
@@ -26,6 +26,7 @@ export type Project = {
 
 export type Query = {
   __typename?: 'Query';
+  getAllPermissions: Array<Scalars['String']>;
   projects?: Maybe<Array<Maybe<Project>>>;
   user?: Maybe<User>;
   users?: Maybe<Array<Maybe<User>>>;
@@ -37,6 +38,13 @@ export type UserInput = {
   password?: Maybe<Scalars['String']>;
 };
 
+export type AdminUserInput = {
+  id: Scalars['Int'];
+  email?: Maybe<Scalars['String']>;
+  name?: Maybe<Scalars['String']>;
+  permissions?: Maybe<Array<Scalars['String']>>;
+};
+
 export type CredentialsInput = {
   email: Scalars['String'];
   password?: Maybe<Scalars['String']>;
@@ -65,6 +73,7 @@ export type User = {
   id?: Maybe<Scalars['Int']>;
   email?: Maybe<Scalars['String']>;
   name?: Maybe<Scalars['String']>;
+  permissions?: Maybe<Array<Maybe<Scalars['String']>>>;
 };
 
 export type AuthResult = {
@@ -77,6 +86,7 @@ export type AuthResult = {
 export type Mutation = {
   __typename?: 'Mutation';
   createUser?: Maybe<User>;
+  updateUser?: Maybe<User>;
   register?: Maybe<AuthResult>;
   login?: Maybe<AuthResult>;
   forgotPassword?: Maybe<AuthResult>;
@@ -88,6 +98,10 @@ export type MutationCreateUserArgs = {
   user: UserInput;
 };
 
+export type MutationUpdateUserArgs = {
+  user: AdminUserInput;
+};
+
 export type MutationRegisterArgs = {
   user: UserInput;
 };
@@ -214,6 +228,7 @@ export type ResolversTypes = ResolversObject<{
   String: ResolverTypeWrapper<Scalars['String']>;
   Query: ResolverTypeWrapper<{}>;
   UserInput: UserInput;
+  AdminUserInput: AdminUserInput;
   CredentialsInput: CredentialsInput;
   EmailInput: EmailInput;
   NewPasswordInput: NewPasswordInput;
@@ -232,6 +247,7 @@ export type ResolversParentTypes = ResolversObject<{
   String: Scalars['String'];
   Query: {};
   UserInput: UserInput;
+  AdminUserInput: AdminUserInput;
   CredentialsInput: CredentialsInput;
   EmailInput: EmailInput;
   NewPasswordInput: NewPasswordInput;
@@ -256,6 +272,7 @@ export type QueryResolvers<
   ContextType = ApolloContext,
   ParentType extends ResolversParentTypes['Query'] = ResolversParentTypes['Query']
 > = ResolversObject<{
+  getAllPermissions?: Resolver<Array<ResolversTypes['String']>, ParentType, ContextType>;
   projects?: Resolver<
     Maybe<Array<Maybe<ResolversTypes['Project']>>>,
     ParentType,
@@ -272,6 +289,11 @@ export type UserResolvers<
   id?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
   email?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
   name?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
+  permissions?: Resolver<
+    Maybe<Array<Maybe<ResolversTypes['String']>>>,
+    ParentType,
+    ContextType
+  >;
   __isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
 }>;
 
@@ -295,6 +317,12 @@ export type MutationResolvers<
     ContextType,
     RequireFields<MutationCreateUserArgs, 'user'>
   >;
+  updateUser?: Resolver<
+    Maybe<ResolversTypes['User']>,
+    ParentType,
+    ContextType,
+    RequireFields<MutationUpdateUserArgs, 'user'>
+  >;
   register?: Resolver<
     Maybe<ResolversTypes['AuthResult']>,
     ParentType,
diff --git a/backend/src/server/apolloContext.ts b/backend/src/server/apolloContext.ts
index 5bcb0cbd..0b1f5ff4 100644
--- a/backend/src/server/apolloContext.ts
+++ b/backend/src/server/apolloContext.ts
@@ -1,5 +1,6 @@
 import { ApolloExpressContext } from '../types';
 import { prisma } from './prisma';
+import { checkMissingPermissions } from '../user/permissions/checkMissingPermissions';
 
 // This file will make apollo context available for resolvers
 
@@ -14,12 +15,5 @@ export const apolloContext = ({ req }: ApolloExpressContext): ApolloContext => (
   prisma,
   user: req.user,
   isAuthenticated: () => !!req.user,
-  checkMissingPermissions: (neededPermissions) => {
-    console.log(neededPermissions);
-    // get user permissions
-    // subtract perm from user permissions
-    // return missing permissions list
-
-    return [];
-  },
+  checkMissingPermissions: checkMissingPermissions(req.user),
 });
diff --git a/backend/src/server/express.ts b/backend/src/server/express.ts
index c520b9d2..cff7810e 100644
--- a/backend/src/server/express.ts
+++ b/backend/src/server/express.ts
@@ -4,6 +4,7 @@ import expressJwt from 'express-jwt';
 import { getApolloServer } from '../graphql/graphqlServer';
 import { apolloContext } from './apolloContext';
 import { JWT_ALGORITHM, JWT_SECRET } from '../utils/constants';
+import { expressAddUserToRequest } from './expressAddUserToRequest';
 
 export const startExpress = (): void => {
   const app = express();
@@ -22,7 +23,7 @@ export const startExpress = (): void => {
     });
   }
 
-  // Extract user from JWT (Authorization header Bearer) as user in all requests
+  // Extract user id from JWT (Authorization header Bearer) as user in all requests
   app.use(
     expressJwt({
       secret: JWT_SECRET,
@@ -31,6 +32,9 @@ export const startExpress = (): void => {
     }),
   );
 
+  // get user by user id from the db and put it in the express request (req.user)
+  app.use(expressAddUserToRequest);
+
   // some level of http security
   app.use(
     helmet({
diff --git a/backend/src/server/expressAddUserToRequest.ts b/backend/src/server/expressAddUserToRequest.ts
new file mode 100644
index 00000000..ff397e13
--- /dev/null
+++ b/backend/src/server/expressAddUserToRequest.ts
@@ -0,0 +1,17 @@
+import { RequestHandler } from 'express';
+import { ExpressRequest } from '../types';
+import { getUserById } from '../user/utils/getUserById';
+
+export const expressAddUserToRequest: RequestHandler = async (
+  req,
+  res,
+  next,
+): Promise<void> => {
+  const request = req as ExpressRequest;
+  if (request.user?.id) {
+    const user = await getUserById(request.user.id);
+    if (user) request.user = user;
+  }
+
+  next();
+};
diff --git a/backend/src/types.ts b/backend/src/types.ts
index b0d6a9eb..8961ab94 100644
--- a/backend/src/types.ts
+++ b/backend/src/types.ts
@@ -2,8 +2,10 @@ import { Request } from 'express';
 import { ExpressContext } from 'apollo-server-express/dist/ApolloServer';
 import { User } from '@prisma/client';
 
+export type UserWithoutPassword = Omit<User, 'password'>;
+
 export interface ExpressRequest extends Request {
-  user: User;
+  user?: UserWithoutPassword;
 }
 
 export interface ApolloExpressContext extends ExpressContext {
diff --git a/backend/src/user/createTokenFromUser.test.ts b/backend/src/user/createTokenFromUser.test.ts
index 34a6596a..5b0ae44f 100644
--- a/backend/src/user/createTokenFromUser.test.ts
+++ b/backend/src/user/createTokenFromUser.test.ts
@@ -6,13 +6,11 @@ describe('createTokenFromUser', () => {
   it('creates token correctly', () => {
     const token = createTokenFromUser({
       id: 1,
-      email: 'test@test.test',
-      name: 'Test',
     });
 
     const tokenDecryptedKeys = Object.keys(jwt.verify(token, JWT_SECRET)).sort();
 
-    expect(tokenDecryptedKeys).toEqual(['email', 'iat', 'id', 'name']);
+    expect(tokenDecryptedKeys).toEqual(['iat', 'id']);
   });
 
   it('does not include extra fields', () => {
@@ -25,6 +23,6 @@ describe('createTokenFromUser', () => {
 
     const tokenDecryptedKeys = Object.keys(jwt.verify(token, JWT_SECRET)).sort();
 
-    expect(tokenDecryptedKeys).toEqual(['email', 'iat', 'id', 'name']);
+    expect(tokenDecryptedKeys).toEqual(['iat', 'id']);
   });
 });
diff --git a/backend/src/user/createTokenFromUser.ts b/backend/src/user/createTokenFromUser.ts
index 6f341234..966590a4 100644
--- a/backend/src/user/createTokenFromUser.ts
+++ b/backend/src/user/createTokenFromUser.ts
@@ -5,8 +5,6 @@ import { JWT_SECRET, JWT_ALGORITHM } from '../utils/constants';
 export const createTokenFromUser = <T extends PublicUser>(user: T): string => {
   const body: PublicUser = {
     id: user.id,
-    email: user.email,
-    name: user.name,
   };
   return jwt.sign(body, JWT_SECRET, { algorithm: JWT_ALGORITHM });
 };
diff --git a/backend/src/user/forgotPassword.test.ts b/backend/src/user/forgotPassword.test.ts
index 37ad5c9c..4e0f1dd4 100644
--- a/backend/src/user/forgotPassword.test.ts
+++ b/backend/src/user/forgotPassword.test.ts
@@ -1,7 +1,7 @@
 import { resolverHelper } from '../graphql/resolverHelper';
 import { prisma } from '../server/prisma';
 import { TOKEN_EXPIRE_MINUTES } from '../utils/constants';
-import { createFakeToken } from './createFakeToken';
+import { createFakeToken } from './utils/createFakeToken';
 import { hashUserPassword } from './hashUserPassword';
 import { forgotPassword as forgotPasswordResolver } from './forgotPassword';
 const forgotPassword = resolverHelper(forgotPasswordResolver);
diff --git a/backend/src/user/getUser.ts b/backend/src/user/getUser.ts
index 4410d324..86121f5d 100644
--- a/backend/src/user/getUser.ts
+++ b/backend/src/user/getUser.ts
@@ -1,5 +1,5 @@
 import { QueryResolvers } from '../graphql/resolvers-types';
 
 export const getUser: QueryResolvers['user'] = async (parent, args, ctx) => {
-  return ctx.user;
+  return ctx.user ? ctx.user : {};
 };
diff --git a/backend/src/user/permissions/allPermissions.ts b/backend/src/user/permissions/allPermissions.ts
new file mode 100644
index 00000000..1a779e47
--- /dev/null
+++ b/backend/src/user/permissions/allPermissions.ts
@@ -0,0 +1,6 @@
+export const allPermissions = [
+  'users.create',
+  'users.read',
+  'users.update',
+  'users.delete',
+];
diff --git a/backend/src/user/permissions/checkMissingPermissions.ts b/backend/src/user/permissions/checkMissingPermissions.ts
new file mode 100644
index 00000000..15d407f7
--- /dev/null
+++ b/backend/src/user/permissions/checkMissingPermissions.ts
@@ -0,0 +1,14 @@
+import { difference } from 'lodash';
+import { UserWithoutPassword } from '../../types';
+import { isDevEnv } from '../../utils/isDevEnv';
+
+export const checkMissingPermissions = (user: UserWithoutPassword | undefined) => (
+  neededPermissions: string[],
+): string[] => {
+  if (!user) return neededPermissions;
+
+  // exceptionally skip permission check if it's a development environment
+  if (isDevEnv()) return [];
+
+  return difference(neededPermissions, user.permissions);
+};
diff --git a/backend/src/user/permissions/getAllPermissions.ts b/backend/src/user/permissions/getAllPermissions.ts
new file mode 100644
index 00000000..10de6510
--- /dev/null
+++ b/backend/src/user/permissions/getAllPermissions.ts
@@ -0,0 +1,5 @@
+import { QueryResolvers } from '../../graphql/resolvers-types';
+import { allPermissions } from './allPermissions';
+
+export const getAllPermissions: QueryResolvers['getAllPermissions'] = () =>
+  allPermissions;
diff --git a/backend/src/user/resetPassword.test.ts b/backend/src/user/resetPassword.test.ts
index 2bd01e37..5414481c 100644
--- a/backend/src/user/resetPassword.test.ts
+++ b/backend/src/user/resetPassword.test.ts
@@ -4,7 +4,7 @@ import { prisma } from '../server/prisma';
 import { TOKEN_EXPIRE_MINUTES } from '../utils/constants';
 import { resetPassword as resetPasswordResolver } from './resetPassword';
 import { hashUserPassword } from './hashUserPassword';
-import { createFakeToken } from './createFakeToken';
+import { createFakeToken } from './utils/createFakeToken';
 const resetPassword = resolverHelper(resetPasswordResolver);
 
 describe('resetPassword', () => {
diff --git a/backend/src/user/updateUser.test.ts b/backend/src/user/updateUser.test.ts
new file mode 100644
index 00000000..c6d3cb53
--- /dev/null
+++ b/backend/src/user/updateUser.test.ts
@@ -0,0 +1,95 @@
+import { resolverHelper } from '../graphql/resolverHelper';
+import { prisma } from '../server/prisma';
+import { UserWithoutPassword } from '../types';
+import { hashUserPassword } from './hashUserPassword';
+import { allPermissions } from './permissions/allPermissions';
+import { updateUser as updateUserResolver } from './updateUser';
+import { userFields } from './utils/constants';
+const updateUser = resolverHelper(updateUserResolver);
+
+describe('updateUser', () => {
+  const user = {
+    email: 'test@test.test',
+    name: 'Test',
+    password: 'testtest',
+    permissions: [],
+  };
+  const adminUser = { ...user, id: -1, permissions: allPermissions };
+
+  const makeUser = async (permissions: string[] = []): Promise<UserWithoutPassword> => {
+    const normalUser = await hashUserPassword(user);
+    return prisma.user.create({
+      data: { ...normalUser, permissions },
+      select: userFields,
+    });
+  };
+
+  afterEach(async () => {
+    await prisma.user.deleteMany();
+  });
+
+  it('updates user correctly', async () => {
+    const normalUser = await makeUser();
+    const updateValues = {
+      id: normalUser.id,
+      name: 'Changed',
+      email: 'changed@test.test',
+      permissions: [allPermissions[0]],
+    };
+    const result = await updateUser({ user: updateValues }, adminUser);
+    expect(result).toEqual(updateValues);
+  });
+
+  it('updates user correctly when only some fields are requested for updating', async () => {
+    const normalUser = await makeUser();
+    const updateValues = {
+      id: normalUser.id,
+      name: 'Changed',
+    };
+    const result = await updateUser({ user: updateValues }, adminUser);
+    expect(result).toEqual({ ...normalUser, ...updateValues });
+  });
+
+  it('updates user permissions only if admin has those access herself', async () => {
+    const normalUser = await makeUser([allPermissions[0]]);
+    const updateValues = {
+      id: normalUser.id,
+      permissions: [allPermissions[1], allPermissions[2]],
+    };
+    const result = await updateUser(
+      { user: updateValues },
+      { ...adminUser, permissions: [allPermissions[1]] },
+    );
+    expect(result).toEqual({
+      ...normalUser,
+      permissions: [allPermissions[0], allPermissions[1]],
+    });
+  });
+
+  it('does not updates user if it does not exist', async () => {
+    const result = await updateUser({ user: { ...user, id: -1 } });
+
+    expect(result).toEqual({});
+  });
+
+  it('does not updates user if it is same as admin', async () => {
+    const normalUser = await makeUser();
+    const updateValues = {
+      id: normalUser.id,
+      name: 'Changed',
+    };
+    const result = await updateUser({ user: updateValues }, normalUser);
+
+    expect(result).toEqual({});
+  });
+
+  it('does not updates user password', async () => {
+    const normalUser = await makeUser();
+    const updateValues = {
+      id: normalUser.id,
+      password: 'Changed',
+    };
+    const result = await updateUser({ user: updateValues }, adminUser);
+    expect(result).toEqual(normalUser);
+  });
+});
diff --git a/backend/src/user/updateUser.ts b/backend/src/user/updateUser.ts
new file mode 100644
index 00000000..dc926def
--- /dev/null
+++ b/backend/src/user/updateUser.ts
@@ -0,0 +1,54 @@
+import { Prisma } from '@prisma/client';
+import { difference, intersection } from 'lodash';
+import { MutationResolvers } from '../graphql/resolvers-types';
+import { isDevEnv } from '../utils/isDevEnv';
+import { allPermissions } from './permissions/allPermissions';
+import { userFields } from './utils/constants';
+
+export const updateUser: MutationResolvers['updateUser'] = async (
+  parent,
+  { user },
+  { prisma, user: currentUser },
+) => {
+  const { id, email, name, permissions } = user;
+
+  // if user tries to change own access
+  // exceptionally skip this check if it's a development environment
+  if (currentUser?.id === user.id && !isDevEnv()) return {};
+
+  const userToUpdate = await prisma.user.findUnique({
+    where: { id },
+    select: { permissions: true },
+  });
+  // if the user doesn't exist
+  if (!userToUpdate) return {};
+
+  let data: Prisma.UserUpdateInput = {};
+  if (email) data = { ...data, email };
+  if (name) data = { ...data, name };
+
+  // skip restrictions on the permission changes on the dev environments
+  if (isDevEnv() && permissions) {
+    data = { ...data, permissions };
+  } else if (permissions) {
+    // check if user has these permissions herself
+    const newPermissions = intersection(permissions, currentUser?.permissions);
+
+    // if admin is removing some permissions from user, but doesn't have them herself
+    const permissionsThatAdminHasNot = difference(
+      allPermissions,
+      currentUser?.permissions || [],
+    );
+    const shouldNotBeRemovedPermissions = intersection(
+      userToUpdate.permissions,
+      permissionsThatAdminHasNot,
+    );
+
+    data = {
+      ...data,
+      permissions: [...shouldNotBeRemovedPermissions, ...newPermissions],
+    };
+  }
+
+  return prisma.user.update({ where: { id }, data, select: userFields });
+};
diff --git a/backend/src/user/user.gql b/backend/src/user/user.gql
index 520cb94f..44611369 100644
--- a/backend/src/user/user.gql
+++ b/backend/src/user/user.gql
@@ -9,6 +9,18 @@ input UserInput {
   password: String @stringLength(min: 8, max: 300)
 }
 
+input AdminUserInput {
+  id: Int!
+  email: String
+    @stringLength(min: 0, max: 200)
+    @pattern(
+      regexp: "^(([^<>()[\\]\\\\.,;:\\s@\"]+(\\.[^<>()[\\]\\\\.,;:\\s@\"]+)*)|(\".+\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$"
+      flags: "i"
+    )
+  name: String @stringLength(min: 0, max: 200)
+  permissions: [String!]
+}
+
 input CredentialsInput {
   email: String!
     @stringLength(min: 0, max: 200)
@@ -46,6 +58,7 @@ type User {
   id: Int
   email: String
   name: String
+  permissions: [String]
 }
 
 type AuthResult {
@@ -55,12 +68,14 @@ type AuthResult {
 }
 
 type Query {
+  getAllPermissions: [String!]!
   user: User @auth
   users: [User] @hasPermissions(permissions: ["users.read"])
 }
 
 type Mutation {
   createUser(user: UserInput!): User @hasPermissions(permissions: ["users.create"])
+  updateUser(user: AdminUserInput!): User @hasPermissions(permissions: ["users.update"])
   register(user: UserInput!): AuthResult
   login(credentials: CredentialsInput!): AuthResult
   forgotPassword(credentials: EmailInput!): AuthResult
diff --git a/backend/src/user/user.resolver.ts b/backend/src/user/user.resolver.ts
index 55997fea..2a0babc1 100644
--- a/backend/src/user/user.resolver.ts
+++ b/backend/src/user/user.resolver.ts
@@ -7,11 +7,14 @@ import { login } from './login';
 import { forgotPassword } from './forgotPassword';
 import { resetPassword } from './resetPassword';
 import { validateSocialLogin } from './validateSocialLogin';
+import { updateUser } from './updateUser';
+import { getAllPermissions } from './permissions/getAllPermissions';
 
 const resolver: Resolvers = {
   Query: {
     user: getUser,
     users: getUsers,
+    getAllPermissions,
   },
   Mutation: {
     createUser,
@@ -20,6 +23,7 @@ const resolver: Resolvers = {
     forgotPassword,
     resetPassword,
     validateSocialLogin,
+    updateUser,
   },
 };
 
diff --git a/backend/src/user/__mocks__/getTokenValidator.ts b/backend/src/user/utils/__mocks__/getTokenValidator.ts
similarity index 76%
rename from backend/src/user/__mocks__/getTokenValidator.ts
rename to backend/src/user/utils/__mocks__/getTokenValidator.ts
index 979a6ffc..a0db7ba3 100644
--- a/backend/src/user/__mocks__/getTokenValidator.ts
+++ b/backend/src/user/utils/__mocks__/getTokenValidator.ts
@@ -1,4 +1,4 @@
-import { TokenValidator } from '../types';
+import { TokenValidator } from '../../types';
 
 export const getTokenValidator = (): TokenValidator => (token) =>
   Promise.resolve(token === 'OK' ? { email: 'test@test.test', name: 'Test' } : null);
diff --git a/backend/src/user/utils/constants.ts b/backend/src/user/utils/constants.ts
new file mode 100644
index 00000000..3b575f0c
--- /dev/null
+++ b/backend/src/user/utils/constants.ts
@@ -0,0 +1 @@
+export const userFields = { id: true, name: true, email: true, permissions: true };
diff --git a/backend/src/user/createFakeToken.ts b/backend/src/user/utils/createFakeToken.ts
similarity index 79%
rename from backend/src/user/createFakeToken.ts
rename to backend/src/user/utils/createFakeToken.ts
index bb4a19fc..588e0406 100644
--- a/backend/src/user/createFakeToken.ts
+++ b/backend/src/user/utils/createFakeToken.ts
@@ -1,6 +1,6 @@
 import { Token, TOKEN_TYPES } from '@prisma/client';
-import { prisma } from '../server/prisma';
-import { getRecentTime } from '../utils/getRecentTime';
+import { prisma } from '../../server/prisma';
+import { getRecentTime } from '../../utils/getRecentTime';
 
 // helper function for resetPassword
 export const createFakeToken = (
diff --git a/backend/src/user/getTokenValidator.ts b/backend/src/user/utils/getTokenValidator.ts
similarity index 92%
rename from backend/src/user/getTokenValidator.ts
rename to backend/src/user/utils/getTokenValidator.ts
index 88538581..20b16302 100644
--- a/backend/src/user/getTokenValidator.ts
+++ b/backend/src/user/utils/getTokenValidator.ts
@@ -1,11 +1,11 @@
 import fetch from 'node-fetch';
-import { AuthServices } from '../graphql/resolvers-types';
+import { AuthServices } from '../../graphql/resolvers-types';
 import {
   GoogleTokenInfoResponse,
   TokenValidator,
   UserContent,
   ValidateTokenByService,
-} from './types';
+} from '../types';
 
 const googleTokenValidator: TokenValidator = (token) =>
   fetch('https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=' + token)
diff --git a/backend/src/user/utils/getUserById.ts b/backend/src/user/utils/getUserById.ts
new file mode 100644
index 00000000..2ed71889
--- /dev/null
+++ b/backend/src/user/utils/getUserById.ts
@@ -0,0 +1,9 @@
+import { prisma } from '../../server/prisma';
+import { UserWithoutPassword } from '../../types';
+import { userFields } from './constants';
+
+export const getUserById = (id: number): Promise<UserWithoutPassword | null> =>
+  prisma.user.findUnique({
+    where: { id },
+    select: userFields,
+  });
diff --git a/backend/src/user/validateSocialLogin.test.ts b/backend/src/user/validateSocialLogin.test.ts
index 420f59af..7f27d912 100644
--- a/backend/src/user/validateSocialLogin.test.ts
+++ b/backend/src/user/validateSocialLogin.test.ts
@@ -3,7 +3,7 @@ import { AuthServices } from '../graphql/resolvers-types';
 import { prisma } from '../server/prisma';
 import { validateSocialLogin as validateSocialLoginResolver } from './validateSocialLogin';
 const validateSocialLogin = resolverHelper(validateSocialLoginResolver);
-jest.mock('./getTokenValidator');
+jest.mock('./utils/getTokenValidator');
 
 describe('validateSocialLogin', () => {
   afterEach(async () => {
diff --git a/backend/src/user/validateSocialLogin.ts b/backend/src/user/validateSocialLogin.ts
index 61d0f5dd..c4d4a127 100644
--- a/backend/src/user/validateSocialLogin.ts
+++ b/backend/src/user/validateSocialLogin.ts
@@ -1,7 +1,7 @@
 import { MutationResolvers } from '../graphql/resolvers-types';
 import { createTokenFromUser } from './createTokenFromUser';
 import { getOrMakeUser } from './getOrMakeUser';
-import { getTokenValidator } from './getTokenValidator';
+import { getTokenValidator } from './utils/getTokenValidator';
 
 export const validateSocialLogin: MutationResolvers['validateSocialLogin'] = async (
   parent,
diff --git a/backend/src/utils/isDevEnv.ts b/backend/src/utils/isDevEnv.ts
new file mode 100644
index 00000000..dcd16e9a
--- /dev/null
+++ b/backend/src/utils/isDevEnv.ts
@@ -0,0 +1 @@
+export const isDevEnv = (): boolean => process.env.NODE_ENV === 'development';
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 9cedd967..6a486c2d 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -9250,6 +9250,59 @@
         }
       }
     },
+    "cli-truncate": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz",
+      "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=",
+      "dev": true,
+      "requires": {
+        "slice-ansi": "0.0.4",
+        "string-width": "^1.0.1"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+          "dev": true,
+          "requires": {
+            "number-is-nan": "^1.0.0"
+          }
+        },
+        "slice-ansi": {
+          "version": "0.0.4",
+          "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz",
+          "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=",
+          "dev": true
+        },
+        "string-width": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+          "dev": true,
+          "requires": {
+            "code-point-at": "^1.0.0",
+            "is-fullwidth-code-point": "^1.0.0",
+            "strip-ansi": "^3.0.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^2.0.0"
+          }
+        }
+      }
+    },
     "cli-width": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz",
@@ -12867,6 +12920,143 @@
         }
       }
     },
+    "graphql-request": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-3.4.0.tgz",
+      "integrity": "sha512-acrTzidSlwAj8wBNO7Q/UQHS8T+z5qRGquCQRv9J1InwR01BBWV9ObnoE+JS5nCCEj8wSGS0yrDXVDoRiKZuOg==",
+      "dev": true,
+      "requires": {
+        "cross-fetch": "^3.0.6",
+        "extract-files": "^9.0.0",
+        "form-data": "^3.0.0"
+      },
+      "dependencies": {
+        "ignore": {
+          "version": "3.3.10",
+          "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz",
+          "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug=="
+        },
+        "slash": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
+          "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU="
+        }
+      }
+    },
+    "good-listener": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
+      "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "delegate": "^3.1.2"
+      }
+    },
+    "got": {
+      "version": "9.6.0",
+      "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
+      "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==",
+      "dev": true,
+      "requires": {
+        "@sindresorhus/is": "^0.14.0",
+        "@szmarczak/http-timer": "^1.1.2",
+        "cacheable-request": "^6.0.0",
+        "decompress-response": "^3.3.0",
+        "duplexer3": "^0.1.4",
+        "get-stream": "^4.1.0",
+        "lowercase-keys": "^1.0.1",
+        "mimic-response": "^1.0.1",
+        "p-cancelable": "^1.0.0",
+        "to-readable-stream": "^1.0.0",
+        "url-parse-lax": "^3.0.0"
+      }
+    },
+    "graceful-fs": {
+      "version": "4.2.4",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
+      "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw=="
+    },
+    "graphql": {
+      "version": "15.4.0",
+      "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.4.0.tgz",
+      "integrity": "sha512-EB3zgGchcabbsU9cFe1j+yxdzKQKAbGUWRb13DsrsMN1yyfmmIq+2+L5MqVWcDCE4V89R5AyUOi7sMOGxdsYtA=="
+    },
+    "graphql-config": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/graphql-config/-/graphql-config-3.2.0.tgz",
+      "integrity": "sha512-ygEKDeQNZKpm4137560n2oY3bGM0D5zyRsQVaJntKkufWdgPg6sb9/4J1zJW2y/yC1ortAbhNho09qmeJeLa9g==",
+      "dev": true,
+      "requires": {
+        "@endemolshinegroup/cosmiconfig-typescript-loader": "3.0.2",
+        "@graphql-tools/graphql-file-loader": "^6.0.0",
+        "@graphql-tools/json-file-loader": "^6.0.0",
+        "@graphql-tools/load": "^6.0.0",
+        "@graphql-tools/merge": "^6.0.0",
+        "@graphql-tools/url-loader": "^6.0.0",
+        "@graphql-tools/utils": "^6.0.0",
+        "cosmiconfig": "6.0.0",
+        "cosmiconfig-toml-loader": "1.0.0",
+        "minimatch": "3.0.4",
+        "string-env-interpolation": "1.0.1",
+        "tslib": "^2.0.0"
+      },
+      "dependencies": {
+        "cosmiconfig": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
+          "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
+          "dev": true,
+          "requires": {
+            "@types/parse-json": "^4.0.0",
+            "import-fresh": "^3.1.0",
+            "parse-json": "^5.0.0",
+            "path-type": "^4.0.0",
+            "yaml": "^1.7.2"
+          }
+        },
+        "import-fresh": {
+          "version": "3.3.0",
+          "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+          "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+          "dev": true,
+          "requires": {
+            "parent-module": "^1.0.0",
+            "resolve-from": "^4.0.0"
+          }
+        },
+        "parse-json": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz",
+          "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==",
+          "dev": true,
+          "requires": {
+            "@babel/code-frame": "^7.0.0",
+            "error-ex": "^1.3.1",
+            "json-parse-even-better-errors": "^2.3.0",
+            "lines-and-columns": "^1.1.6"
+          }
+        },
+        "path-type": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+          "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+          "dev": true
+        },
+        "resolve-from": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+          "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+          "dev": true
+        },
+        "tslib": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz",
+          "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==",
+          "dev": true
+        }
+      }
+    },
     "graphql-request": {
       "version": "3.4.0",
       "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-3.4.0.tgz",
diff --git a/frontend/src/graphql/hooks.ts b/frontend/src/graphql/hooks.ts
index e68aedea..ec5a179c 100644
--- a/frontend/src/graphql/hooks.ts
+++ b/frontend/src/graphql/hooks.ts
@@ -15,26 +15,34 @@ export type Scalars = {
   Float: number;
 };
 
-export type Project = {
-  __typename?: 'Project';
-  id?: Maybe<Scalars['Int']>;
-  title?: Maybe<Scalars['String']>;
-  createdAt?: Maybe<Scalars['String']>;
-};
-
 export type Query = {
   __typename?: 'Query';
+  getAccessList: Array<Scalars['String']>;
   projects?: Maybe<Array<Maybe<Project>>>;
   user?: Maybe<User>;
   users?: Maybe<Array<Maybe<User>>>;
 };
 
+export type Project = {
+  __typename?: 'Project';
+  id?: Maybe<Scalars['Int']>;
+  title?: Maybe<Scalars['String']>;
+  createdAt?: Maybe<Scalars['String']>;
+};
+
 export type UserInput = {
   email: Scalars['String'];
   name?: Maybe<Scalars['String']>;
   password?: Maybe<Scalars['String']>;
 };
 
+export type AdminUserInput = {
+  id: Scalars['Int'];
+  email?: Maybe<Scalars['String']>;
+  name?: Maybe<Scalars['String']>;
+  accessList?: Maybe<Array<Scalars['String']>>;
+};
+
 export type CredentialsInput = {
   email: Scalars['String'];
   password?: Maybe<Scalars['String']>;
@@ -63,6 +71,7 @@ export type User = {
   id?: Maybe<Scalars['Int']>;
   email?: Maybe<Scalars['String']>;
   name?: Maybe<Scalars['String']>;
+  accessList?: Maybe<Array<Maybe<Scalars['String']>>>;
 };
 
 export type AuthResult = {
@@ -75,6 +84,7 @@ export type AuthResult = {
 export type Mutation = {
   __typename?: 'Mutation';
   createUser?: Maybe<User>;
+  updateUser?: Maybe<User>;
   register?: Maybe<AuthResult>;
   login?: Maybe<AuthResult>;
   forgotPassword?: Maybe<AuthResult>;
@@ -86,6 +96,10 @@ export type MutationCreateUserArgs = {
   user: UserInput;
 };
 
+export type MutationUpdateUserArgs = {
+  user: AdminUserInput;
+};
+
 export type MutationRegisterArgs = {
   user: UserInput;
 };