diff --git a/Authentication/src/lib/Resolvable.ts b/Authentication/src/lib/Resolvable.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6e313aae8b046e15f83dfd2198ab072573688c42
--- /dev/null
+++ b/Authentication/src/lib/Resolvable.ts
@@ -0,0 +1,26 @@
+export interface IResolvable<reply> extends Promise<reply> {
+  resolved: boolean;
+  resolve(msg: reply): void;
+  reject(msg: Error): void;
+}
+
+export default function Resolvable<reply = void>(): IResolvable<reply> {
+  let resolver: (msg: any) => void;
+  let rejector: (msg: Error) => void;
+
+  const promise: any = new Promise((res, rej) => {
+    resolver = res;
+    rejector = rej;
+  });
+
+  promise.resolved = false;
+  promise.resolve = (msg: any) => {
+    resolver(msg);
+    promise.resolved = true;
+  };
+  promise.reject = (msg: Error) => {
+    rejector(msg);
+    promise.resolved = true;
+  };
+  return promise;
+}
diff --git a/Authentication/src/providers/Canvas/refreshToken.ts b/Authentication/src/providers/Canvas/refreshToken.ts
index 72ebb4c148e4dd45d3e3d6e15191a758e9b7738e..1814488682f638f75f91e2b0a7c05742c07402a3 100644
--- a/Authentication/src/providers/Canvas/refreshToken.ts
+++ b/Authentication/src/providers/Canvas/refreshToken.ts
@@ -1,6 +1,7 @@
 import { canvas, CLIENT_SECRET, CLIENT_ID } from './canvas';
 import { IDatabaseEntry } from '../../lib/Database';
 import { REDIRECT_URL } from '../../environment';
+import Resolvable, { IResolvable } from '../../lib/Resolvable';
 
 export interface IResp {
   access_token: string;
@@ -14,7 +15,21 @@ export interface IUser {
   name: string;
 }
 
+const refreshing: Record<string, Array<IResolvable<IDatabaseEntry | null>>> = {};
 export default async function refreshToken(refresh: string): Promise<IDatabaseEntry | null> {
+  const promise = Resolvable<IDatabaseEntry | null>();
+  const firstRequest = !refreshing[refresh];
+
+  if (firstRequest) refreshing[refresh] = [promise];
+  else refreshing[refresh].push(promise);
+
+  if (firstRequest) actualRefreshToken(refresh);
+  return promise;
+}
+
+async function actualRefreshToken(refresh: string) {
+  const respond = (v: IDatabaseEntry | null) => { for (const promise of refreshing[refresh]) promise.resolve(v); delete refreshing[refresh]; };
+
   const { data: resp, status } = await canvas.post<IResp>('/login/oauth2/token', {
     grant_type: 'refresh_token',
     client_id: CLIENT_ID,
@@ -24,16 +39,16 @@ export default async function refreshToken(refresh: string): Promise<IDatabaseEn
   }, { validateStatus: () => true });
   if (status !== 200) {
     console.info(`Refreshing token failed: [${status}] /login/oauth2/token - "${JSON.stringify(resp)}"`);
-    return null;
+    respond(null);
   }
 
-  return {
+  respond({
     userName: resp.user.name,
     userId: resp.user.id,
     token: {
       access: resp.access_token,
-      expired: resp.expires_in + Date.now(),
+      expired: resp.expires_in * 1000 + Date.now(),
       refresh,
     },
-  };
+  });
 }
diff --git a/Authentication/src/router/self.ts b/Authentication/src/router/self.ts
index edfd4790c6b17161fe9e77239daa07f0fcfdb147..72b32937f059538b568593e227a8aafdadd385f1 100644
--- a/Authentication/src/router/self.ts
+++ b/Authentication/src/router/self.ts
@@ -7,7 +7,7 @@ route.put('Create Self Session', '/self', async (ctx) => {
   const { ltiId } = ctx.request.body;
   ctx.assert(!!ltiId, 400, '[ltiId] Must be specified');
   const session = new Session(ltiId);
-  ctx.assert(!!await session.getToken(true), 400, 'User\'s refresh token invalidated');
+  ctx.assert(!!await session.getToken(), 400, 'User\'s refresh token invalidated');
   ctx.body = session.id;
 });
 
diff --git a/Frontend/yarn.lock b/Frontend/yarn.lock
index d2cac204cab3c81372d38a1a05f4f79119f600bd..b28e805d1c99e8f856c01e6083f5b480a1e54eb4 100644
--- a/Frontend/yarn.lock
+++ b/Frontend/yarn.lock
@@ -3895,7 +3895,7 @@ babel-code-frame@^6.22.0:
     esutils "^2.0.2"
     js-tokens "^3.0.2"
 
-babel-eslint@10.0.3, babel-eslint@^10.0.3:
+babel-eslint@10.0.3:
   version "10.0.3"
   resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.3.tgz#81a2c669be0f205e19462fed2482d33e4687a88a"
   integrity sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA==
diff --git a/Makefile b/Makefile
index bf1712277ad9e2826f8f07e78d1045fcf00e40f7..9ca94438435fcdb32a796c2b490dc435b4f497c0 100644
--- a/Makefile
+++ b/Makefile
@@ -14,10 +14,15 @@ start:
 stop:
 	docker-compose down
 
+update: install
+
 install:
-	cd AquaSonde/; yarn --silent
-	cd Client/; yarn --silent
-	cd Database/; yarn --silent
-	cd Sponge/; yarn --silent
-	cd WaterLeveler/; yarn --silent
+	cd Authentication/; yarn --silent
+	cd Frontend/; sudo rm -rf node_modules/.cache/
+	cd Frontend/; yarn --silent
+	cd Learning/; yarn --silent
+	cd Public/; pip install -r requirements.txt
 	@echo "Finished installation"
+
+
+
diff --git a/node_modules/.yarn-integrity b/node_modules/.yarn-integrity
deleted file mode 100644
index 94105bd174d9b0fe8188179a9534271b7912a422..0000000000000000000000000000000000000000
--- a/node_modules/.yarn-integrity
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "systemParams": "linux-x64-79",
-  "modulesFolders": [],
-  "flags": [],
-  "linkedModules": [],
-  "topLevelPatterns": [],
-  "lockfileEntries": {},
-  "files": [],
-  "artifacts": {}
-}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
new file mode 100644
index 0000000000000000000000000000000000000000..fb57ccd13afbd082ad82051c2ffebef4840661ec
--- /dev/null
+++ b/yarn.lock
@@ -0,0 +1,4 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+