From e5114ef9ae9447fbda7f37c9ccaa2b8d043f44e8 Mon Sep 17 00:00:00 2001
From: Steve Reis <stevereis93@gmail.com>
Date: Tue, 18 Oct 2022 08:33:01 +0000
Subject: [PATCH] fix(Datashield): issue with jwt

---
 api/src/auth/auth.service.ts                  | 13 +++++++-
 .../datashield/datashield.connector.ts        | 33 ++++++++++++++++---
 api/src/engine/engine.service.ts              | 31 ++++++++++++-----
 .../engine/interfaces/connector.interface.ts  |  6 ++++
 4 files changed, 68 insertions(+), 15 deletions(-)

diff --git a/api/src/auth/auth.service.ts b/api/src/auth/auth.service.ts
index 343f0ef..303b497 100644
--- a/api/src/auth/auth.service.ts
+++ b/api/src/auth/auth.service.ts
@@ -87,6 +87,16 @@ export class AuthService {
         refreshToken,
         this.getRefreshTokenOptions(),
       );
+
+      //check if user is connected
+      const isConnected = await this.engineService.isSessionValid(
+        payload.context,
+      );
+
+      if (!isConnected) {
+        throw new UnauthorizedException('User need to reconnect');
+      }
+
       const user = await this.usersService.findOne(payload.context.id);
       const isMatchingTokens =
         user.refreshToken === (await this.getHash(refreshToken));
@@ -97,7 +107,8 @@ export class AuthService {
       }
       return this.login(payload.context);
     } catch (error) {
-      throw new UnauthorizedException('Invalid refresh token');
+      const msg = error.message ?? 'Invalid refresh token';
+      throw new UnauthorizedException(msg);
     }
   }
 
diff --git a/api/src/engine/connectors/datashield/datashield.connector.ts b/api/src/engine/connectors/datashield/datashield.connector.ts
index 0bbc8de..2bbfa3e 100644
--- a/api/src/engine/connectors/datashield/datashield.connector.ts
+++ b/api/src/engine/connectors/datashield/datashield.connector.ts
@@ -272,14 +272,10 @@ export default class DataShieldConnector implements Connector {
   ) {
     const path = new URL('/runAlgorithm', this.options.baseurl);
 
-    // Covariable and variable are inversed in Datashield API
-    const variable =
-      experiment.variables.length > 0 ? experiment.variables[0] : undefined;
-
     const expToInput = {
       algorithm: {
         id: experiment.algorithm.name,
-        variable,
+        variables: experiment.variables,
         covariables: experiment.coVariables,
       },
       datasets: experiment.datasets,
@@ -344,6 +340,33 @@ export default class DataShieldConnector implements Connector {
     return [dsDomain];
   }
 
+  async isSessionValid(user: User): Promise<boolean> {
+    const sid = user && user.extraFields && user.extraFields['sid'];
+
+    if (!sid) return false;
+
+    try {
+      const cookies = [`sid=${user.extraFields['sid']}`, `user=${user.id}`];
+      const path = this.options.baseurl + 'getvars';
+
+      await firstValueFrom(
+        this.httpService.get(path, {
+          headers: {
+            cookie: cookies.join(';'),
+          },
+        }),
+      );
+
+      return true;
+    } catch (err) {
+      DataShieldConnector.logger.verbose(
+        `User ${user.id} is not connected to Datashield`,
+      );
+      DataShieldConnector.logger.debug(err);
+      return false;
+    }
+  }
+
   async getActiveUser(req: Request): Promise<User> {
     const user = req.user as User;
 
diff --git a/api/src/engine/engine.service.ts b/api/src/engine/engine.service.ts
index fc07746..d0e4efd 100644
--- a/api/src/engine/engine.service.ts
+++ b/api/src/engine/engine.service.ts
@@ -5,6 +5,7 @@ import {
   Injectable,
   InternalServerErrorException,
   NotImplementedException,
+  UnauthorizedException,
 } from '@nestjs/common';
 import { ConfigType } from '@nestjs/config';
 import { Cache } from 'cache-manager';
@@ -79,6 +80,11 @@ export default class EngineService implements Connector {
     return this.connector.getConfiguration?.() ?? {};
   }
 
+  isSessionValid(user: User): Promise<boolean> {
+    if (!this.connector.isSessionValid) throw new NotImplementedException();
+    return this.connector.isSessionValid(user);
+  }
+
   /**
    * "If the cache is enabled, try to get the value from the cache, otherwise call the function and cache
    * the result."
@@ -162,15 +168,22 @@ export default class EngineService implements Connector {
     req?: Request,
   ): Promise<RunResult> {
     if (!this.connector.runExperiment) throw new NotImplementedException();
-    return this.connector.runExperiment(data, req).catch((err) => ({
-      results: [
-        {
-          level: AlertLevel.ERROR,
-          message: `Error while running experiment, details '${err}'`,
-        },
-      ],
-      status: ExperimentStatus.ERROR,
-    }));
+    return this.connector.runExperiment(data, req).catch((err) => {
+      if (err.status === 401 || err.response?.status === 401) {
+        throw new UnauthorizedException(
+          'Experiment cannot be run because of a bad authentication',
+        );
+      }
+      return {
+        results: [
+          {
+            level: AlertLevel.ERROR,
+            message: `Error while running experiment, details '${err}'`,
+          },
+        ],
+        status: ExperimentStatus.ERROR,
+      };
+    });
   }
 
   async listExperiments?(
diff --git a/api/src/engine/interfaces/connector.interface.ts b/api/src/engine/interfaces/connector.interface.ts
index d74d50a..22cc537 100644
--- a/api/src/engine/interfaces/connector.interface.ts
+++ b/api/src/engine/interfaces/connector.interface.ts
@@ -22,6 +22,12 @@ export default interface Connector {
    */
   getConfiguration?(): ConnectorConfiguration;
 
+  /**
+   * Tell if the session is still valid
+   * @param user User to check
+   */
+  isSessionValid?(user: User): Promise<boolean>;
+
   /**
    * Get the list of domains along with a list of variables
    * @param req - Request - this is the request object from the HTTP request.
-- 
GitLab