diff --git a/api/src/auth/auth.service.ts b/api/src/auth/auth.service.ts index 77264edc94394f8702161707b06f89d006086fc4..e992fa760c238d0d475115e87cf52e5e07a0d5f3 100644 --- a/api/src/auth/auth.service.ts +++ b/api/src/auth/auth.service.ts @@ -17,10 +17,10 @@ export class AuthService { return await this.engineService.login?.(username, password); } - login(user: User): Pick<AuthenticationOutput, 'accessToken'> { + async login(user: User): Promise<Pick<AuthenticationOutput, 'accessToken'>> { const payload = { username: user.username, sub: user }; - return { + return Promise.resolve({ accessToken: this.jwtService.sign(payload), - }; + }); } } diff --git a/api/src/engine/connectors/datashield/main.connector.ts b/api/src/engine/connectors/datashield/main.connector.ts index 81f7bf6bb6ecba86cba15b2350d460e57dadf933..2e4f3be2ebce9f21319426a07cda073005fcd4ec 100644 --- a/api/src/engine/connectors/datashield/main.connector.ts +++ b/api/src/engine/connectors/datashield/main.connector.ts @@ -1,9 +1,11 @@ import { HttpService } from '@nestjs/axios'; -import { Inject, Logger } from '@nestjs/common'; +import { Inject, Logger, NotImplementedException } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { Request } from 'express'; -import { firstValueFrom, Observable } from 'rxjs'; +import { catchError, firstValueFrom, Observable } from 'rxjs'; +import { User } from 'src/auth/models/user.model'; import { MIME_TYPES } from 'src/common/interfaces/utilities.interface'; +import { errorAxiosHandler } from 'src/common/utilities'; import { ENGINE_MODULE_OPTIONS } from 'src/engine/engine.constants'; import { IConfiguration, @@ -43,22 +45,50 @@ export default class DataShieldService implements IEngineService { return {}; } - logout(): void { - throw new Error('Method not implemented.'); + async login(username: string, password: string): Promise<User> { + const loginPath = this.options.baseurl + 'login'; + + const user: User = { + id: username, + username, + extraFields: { + sid: '', + }, + }; + + const loginData = await firstValueFrom( + this.httpService + .get(loginPath, { + auth: { username, password }, + }) + .pipe(catchError((e) => errorAxiosHandler(e))), + ); + + const cookies = (loginData.headers['set-cookie'] as string[]) ?? []; + if (loginData.headers && loginData.headers['set-cookie']) { + cookies.forEach((cookie) => { + const [key, value] = cookie.split(/={1}/); + if (key === 'sid') { + user.extraFields.sid = value; + } + }); + } + + return user; } getAlgorithms(): Algorithm[] | Promise<Algorithm[]> { - throw new Error('Method not implemented.'); + throw new NotImplementedException(); } - async getHistogram(variable: string): Promise<RawResult> { + async getHistogram(variable: string, cookie?: string): Promise<RawResult> { const path = this.options.baseurl + `histogram?var=${variable}&type=combine`; const response = await firstValueFrom( this.httpService.get(path, { headers: { - cookie: this.req['req'].headers['cookie'], + cookie, }, }), ); @@ -87,13 +117,16 @@ export default class DataShieldService implements IEngineService { }; } - async getDescriptiveStats(variable: string): Promise<TableResult> { + async getDescriptiveStats( + variable: string, + cookie?: string, + ): Promise<TableResult> { const path = this.options.baseurl + `quantiles?var=${variable}&type=split`; const response = await firstValueFrom( this.httpService.get(path, { headers: { - cookie: this.req['req'].headers['cookie'], + cookie, }, }), ); @@ -111,6 +144,10 @@ export default class DataShieldService implements IEngineService { data: ExperimentCreateInput, isTransient: boolean, ): Promise<Experiment> { + const user = this.req.user as User; + const cookie = [`sid=${user.extraFields['sid']}`, `user=${user.id}`].join( + ';', + ); const expResult: Experiment = { id: `${data.algorithm.id}-${Date.now()}`, variables: data.variables, @@ -126,7 +163,7 @@ export default class DataShieldService implements IEngineService { case 'MULTIPLE_HISTOGRAMS': { expResult.results = await Promise.all<RawResult>( data.variables.map( - async (variable) => await this.getHistogram(variable), + async (variable) => await this.getHistogram(variable, cookie), ), ); break; @@ -134,7 +171,8 @@ export default class DataShieldService implements IEngineService { case 'DESCRIPTIVE_STATS': { expResult.results = await Promise.all<TableResult>( [...data.variables, ...data.coVariables].map( - async (variable) => await this.getDescriptiveStats(variable), + async (variable) => + await this.getDescriptiveStats(variable, cookie), ), ); break; @@ -157,40 +195,23 @@ export default class DataShieldService implements IEngineService { } getExperiment(id: string): Experiment | Promise<Experiment> { - throw new Error('Method not implemented.'); + throw new NotImplementedException(); } removeExperiment(id: string): PartialExperiment | Promise<PartialExperiment> { - throw new Error('Method not implemented.'); + throw new NotImplementedException(); } editExperient( id: string, expriment: ExperimentEditInput, ): Experiment | Promise<Experiment> { - throw new Error('Method not implemented.'); + throw new NotImplementedException(); } async getDomains(): Promise<Domain[]> { - const loginPath = this.options.baseurl + 'login'; - - const loginData = await firstValueFrom( - this.httpService.get(loginPath, { - auth: { username: 'guest', password: 'guest123' }, - }), - ); - - const cookies = (loginData.headers['set-cookie'] as string[]) ?? []; - if (loginData.headers && loginData.headers['set-cookie']) { - cookies.forEach((cookie) => { - const [key, value] = cookie.split(/={1}/); - this.req.res.cookie(key, value, { - httpOnly: true, - //sameSite: 'none', - }); - }); - } - + const user = this.req.user as User; + const cookies = [`sid=${user.extraFields['sid']}`, `user=${user.id}`]; const path = this.options.baseurl + 'getvars'; const response = await firstValueFrom( @@ -216,27 +237,27 @@ export default class DataShieldService implements IEngineService { } editActiveUser(): Observable<string> { - throw new Error('Method not implemented.'); + throw new NotImplementedException(); } getExperimentREST(): Observable<string> { - throw new Error('Method not implemented.'); + throw new NotImplementedException(); } deleteExperiment(): Observable<string> { - throw new Error('Method not implemented.'); + throw new NotImplementedException(); } editExperimentREST(): Observable<string> { - throw new Error('Method not implemented.'); + throw new NotImplementedException(); } startExperimentTransient(): Observable<string> { - throw new Error('Method not implemented.'); + throw new NotImplementedException(); } startExperiment(): Observable<string> { - throw new Error('Method not implemented.'); + throw new NotImplementedException(); } getExperiments(): string { diff --git a/api/src/engine/engine.module.ts b/api/src/engine/engine.module.ts index 59634992441b528ee3eab239cd62342fcc0ff7bf..3f5cf47b16136f4485649ad6ef81b96ed2bc32e5 100644 --- a/api/src/engine/engine.module.ts +++ b/api/src/engine/engine.module.ts @@ -2,6 +2,7 @@ import { HttpModule, HttpService } from '@nestjs/axios'; import { DynamicModule, Global, Logger, Module } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { Request } from 'express'; +import { IncomingMessage } from 'http'; import { ENGINE_MODULE_OPTIONS, ENGINE_SERVICE } from './engine.constants'; import { EngineController } from './engine.controller'; import { IEngineOptions, IEngineService } from './engine.interfaces'; @@ -50,7 +51,10 @@ export class EngineModule { ): Promise<IEngineService> { try { const service = await import(`./connectors/${opt.type}/main.connector`); - const engine = new service.default(opt, httpService, req); + const gqlRequest = req && req['req']; // graphql headers exception + const request = + gqlRequest && gqlRequest instanceof IncomingMessage ? gqlRequest : req; + const engine = new service.default(opt, httpService, request); return engine; } catch (e) { diff --git a/api/src/engine/engine.resolver.ts b/api/src/engine/engine.resolver.ts index 36d51f1790edd8c4dbb2f1ce3acbec2289f4c278..ac8845c7e04e77ddb05f56cebf1b1e02ad316fdd 100644 --- a/api/src/engine/engine.resolver.ts +++ b/api/src/engine/engine.resolver.ts @@ -1,5 +1,6 @@ -import { Inject } from '@nestjs/common'; +import { Inject, UseGuards } from '@nestjs/common'; import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; +import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard'; import { Md5 } from 'ts-md5'; import { ENGINE_MODULE_OPTIONS, ENGINE_SERVICE } from './engine.constants'; import { IEngineOptions, IEngineService } from './engine.interfaces'; @@ -14,6 +15,7 @@ import { ExperimentCreateInput } from './models/experiment/input/experiment-crea import { ExperimentEditInput } from './models/experiment/input/experiment-edit.input'; import { ListExperiments } from './models/experiment/list-experiments.model'; +@UseGuards(JwtAuthGuard) @Resolver() export class EngineResolver { constructor(