diff --git a/api/.env.defaults b/api/.env.defaults index 8fb46c2daed05d83125ffa7a0d2cb1de23c364cc..ec63f0099d8e332beeb98eebeb534786a303b01a 100644 --- a/api/.env.defaults +++ b/api/.env.defaults @@ -2,6 +2,9 @@ ENGINE_TYPE=exareme ENGINE_BASE_URL=http://127.0.0.1:8080/services/ +# GLOBAL +TOS_SKIP=false + # SERVER GATEWAY_PORT=8081 diff --git a/api/assets/engines/datashield/logo.png b/api/assets/engines/datashield/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..19885d0405d59e2447045061a3d85fdf88dc7af1 Binary files /dev/null and b/api/assets/engines/datashield/logo.png differ diff --git a/api/package.json b/api/package.json index b0a16f8d2dda2ef1c2c44705d4f2e56c3b25761b..142d29fc53b88f607a56e402253edafcad98d9c7 100644 --- a/api/package.json +++ b/api/package.json @@ -100,8 +100,8 @@ "!**/*.decorator.ts", "!**/*.model.ts", "!**/*.input.ts", - "!src/jest.config.js", - "!src/main.js" + "!**/jest.config.ts", + "!**/main.ts" ], "coverageDirectory": "../coverage", "testEnvironment": "node" diff --git a/api/src/auth/auth-constants.ts b/api/src/auth/auth-constants.ts index c904d4554a98dc825e4d2e7d6183fbf7590cebcc..5e4748c1e52bca63b765b555568f6da6d6bb9d85 100644 --- a/api/src/auth/auth-constants.ts +++ b/api/src/auth/auth-constants.ts @@ -2,6 +2,7 @@ export const authConstants = { JWTSecret: 'AUTH_JWT_SECRET', skipAuth: 'AUTH_SKIP', expiresIn: 'AUTH_JWT_TOKEN_EXPIRES_IN', + enableSSO: 'AUTH_ENABLE_SSO', cookie: { name: 'jwt-gateway', sameSite: 'AUTH_COOKIE_SAME_SITE', diff --git a/api/src/auth/auth.module.ts b/api/src/auth/auth.module.ts index 3a5381e281ff0715c1f08a862b4090f3f133c3f0..805f0dcba28a75eee88f5deaff33cc6ff5c31ad5 100644 --- a/api/src/auth/auth.module.ts +++ b/api/src/auth/auth.module.ts @@ -17,9 +17,9 @@ import { LocalStrategy } from './strategies/local.strategy'; JwtModule.registerAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ - secret: configService.get<string>(authConstants.JWTSecret), + secret: configService.get(authConstants.JWTSecret), signOptions: { - expiresIn: configService.get<string>(authConstants.expiresIn), + expiresIn: configService.get(authConstants.expiresIn, '2d'), }, }), inject: [ConfigService], diff --git a/api/src/auth/auth.resolver.ts b/api/src/auth/auth.resolver.ts index 5853fe0ad0e76d9a8e5cdf647e93ff2f270c9d82..228943e65bc8582c5a209e5d815523f1716b98ad 100644 --- a/api/src/auth/auth.resolver.ts +++ b/api/src/auth/auth.resolver.ts @@ -7,18 +7,18 @@ import { import { ConfigService } from '@nestjs/config'; import { Args, Mutation, Resolver } from '@nestjs/graphql'; import { Response } from 'express'; -import { parseToBoolean } from '../common/interfaces/utilities.interface'; +import { GQLResponse } from '../common/decorators/gql-response.decoractor'; +import { parseToBoolean } from '../common/utilities'; import { ENGINE_SERVICE } from '../engine/engine.constants'; import { IEngineService } from '../engine/engine.interfaces'; +import { User } from '../users/models/user.model'; import { authConstants } from './auth-constants'; import { AuthService } from './auth.service'; import { CurrentUser } from './decorators/user.decorator'; import { JwtAuthGuard } from './guards/jwt-auth.guard'; import { LocalAuthGuard } from './guards/local-auth.guard'; import { AuthenticationInput } from './inputs/authentication.input'; -import { User } from '../users/models/user.model'; import { AuthenticationOutput } from './outputs/authentication.output'; -import { GQLResponse } from '../common/decorators/gql-response.decoractor'; //Custom defined type because Pick<CookieOptions, 'sameSite'> does not work type SameSiteType = boolean | 'lax' | 'strict' | 'none' | undefined; @@ -69,7 +69,7 @@ export class AuthResolver { @Mutation(() => Boolean) @UseGuards(JwtAuthGuard) logout(@GQLResponse() res: Response, @CurrentUser() user: User): boolean { - this.logger.verbose(`${user.username} logged out`); + if (user) this.logger.verbose(`${user.username} logged out`); res.clearCookie(authConstants.cookie.name); this.engineService.logout?.(); diff --git a/api/src/auth/auth.service.ts b/api/src/auth/auth.service.ts index 2c09c5b35d6443d78a40ca38d4e72a3706538dec..18eaadfa1d35fbae3b6ca306b057e17ae02a225d 100644 --- a/api/src/auth/auth.service.ts +++ b/api/src/auth/auth.service.ts @@ -17,6 +17,11 @@ export class AuthService { return await this.engineService.login?.(username, password); } + /** + * It takes a user and returns an access token + * @param {User} user - The user object that is being authenticated. + * @returns An object with an accessToken property. + */ async login(user: User): Promise<Pick<AuthenticationOutput, 'accessToken'>> { const payload = { username: user.username, sub: user }; return Promise.resolve({ diff --git a/api/src/auth/decorators/public.decorator.ts b/api/src/auth/decorators/public.decorator.ts new file mode 100644 index 0000000000000000000000000000000000000000..466abc78a6705e247228227b7698ae8940344954 --- /dev/null +++ b/api/src/auth/decorators/public.decorator.ts @@ -0,0 +1,2 @@ +import { SetMetadata } from '@nestjs/common'; +export const Public = () => SetMetadata('isPublic', true); diff --git a/api/src/auth/guards/jwt-auth.guard.ts b/api/src/auth/guards/jwt-auth.guard.ts index 4afaca585a6117aca640b98ef5c641c7cbde9837..2ee1a63de580474c54270024e570cc9570788b1d 100644 --- a/api/src/auth/guards/jwt-auth.guard.ts +++ b/api/src/auth/guards/jwt-auth.guard.ts @@ -1,14 +1,18 @@ import { ExecutionContext, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; +import { Reflector } from '@nestjs/core'; import { GqlExecutionContext } from '@nestjs/graphql'; import { AuthGuard } from '@nestjs/passport'; import { Observable } from 'rxjs'; -import { parseToBoolean } from '../../common/interfaces/utilities.interface'; +import { parseToBoolean } from '../../common/utilities'; import { authConstants } from '../auth-constants'; @Injectable() export class JwtAuthGuard extends AuthGuard(['jwt-cookies', 'jwt-bearer']) { - constructor(private readonly configService: ConfigService) { + constructor( + private readonly configService: ConfigService, + private readonly reflector: Reflector, + ) { super(); } @@ -22,11 +26,16 @@ export class JwtAuthGuard extends AuthGuard(['jwt-cookies', 'jwt-bearer']) { canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { + const isPublic = this.reflector.get<boolean>( + 'isPublic', + context.getHandler(), + ); + const skipAuth = parseToBoolean( this.configService.get(authConstants.skipAuth, 'false'), ); - if (skipAuth) { + if (skipAuth || isPublic) { return true; } diff --git a/api/src/common/interfaces/utilities.interface.ts b/api/src/common/interfaces/utilities.interface.ts index 762534ac111d76f505e55c787d771ff6eb164d13..db02ef99e3f4c67f65f320f1d0bfe1a2a6b832f5 100644 --- a/api/src/common/interfaces/utilities.interface.ts +++ b/api/src/common/interfaces/utilities.interface.ts @@ -16,16 +16,3 @@ export enum MIME_TYPES { HTML = 'text/html', TEXT = 'text/plain', } - -/** - * Utility method to convert string value to boolean - * @param value string value to be converted - * @returns true if string value equals to 'true', false otherwise - */ -export const parseToBoolean = (value: string): boolean => { - try { - return value.toLowerCase() == 'true'; - } catch { - return false; - } -}; diff --git a/api/src/common/utilities.spec.ts b/api/src/common/utilities.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..88f5fb323c1fe3c5de33008cabf3060fa141b8fe --- /dev/null +++ b/api/src/common/utilities.spec.ts @@ -0,0 +1,95 @@ +import { + HttpException, + InternalServerErrorException, + NotFoundException, + RequestTimeoutException, + UnauthorizedException, +} from '@nestjs/common'; +import axios from 'axios'; +import { response } from 'express'; +import { errorAxiosHandler, parseToBoolean } from './utilities'; + +describe('Utility parseToBoolean testing', () => { + it('Parse true string to boolean', () => { + expect(parseToBoolean('true')).toBe(true); + }); + + it('Parse false string to boolean', () => { + expect(parseToBoolean('false')).toBe(false); + }); + + it('Parse wrong string to boolean, should fallback to false', () => { + expect(parseToBoolean('truee')).toBe(false); + }); + + it('Parse wrong string to boolean, should fallback to false', () => { + expect(parseToBoolean('falseee')).toBe(false); + }); + + it('Parse wrong string to boolean, should fallback to default value', () => { + expect(parseToBoolean('trueee', true)).toBe(true); + }); + + it('Parse empty string to boolean, should fallback to false', () => { + expect(parseToBoolean('')).toBe(false); + }); + + it('Parse true uppercased string to boolean', () => { + expect(parseToBoolean('TRUE')).toBe(true); + }); + + it('Parse false uppercased string to boolean', () => { + expect(parseToBoolean('FALSE')).toBe(false); + }); +}); + +jest.mock('axios'); + +describe('Utility error handling testing', () => { + const error = { + response: { + status: 401, + data: 'Dummmy Data', + }, + }; + + beforeAll(() => { + // eslint-disable-next-line + // @ts-ignore + axios.isAxiosError.mockReturnValue(true).mockReturnValueOnce(false); + }); + + afterAll(() => { + jest.resetModules(); + }); + + it('Throw internal error', () => { + expect(() => errorAxiosHandler(error)).toThrow( + InternalServerErrorException, + ); + }); + + [ + { code: 401, type: UnauthorizedException }, + { code: 404, type: NotFoundException }, + { code: 408, type: RequestTimeoutException }, + { code: 500, type: InternalServerErrorException }, + ].forEach((errorIt) => { + it(`Throw ${errorIt.code} error`, () => { + error.response.status = errorIt.code; + expect(() => errorAxiosHandler(error)).toThrow(errorIt.type); + }); + }); + + it('Throw HttpException error', () => { + error.response.status = 505; + expect(() => errorAxiosHandler(error)).toThrow(HttpException); + }); + + it('Axios error with no response, should throw Internal server error with msg unknown error', () => { + error.response = undefined; + expect(() => errorAxiosHandler(error)).toThrow( + InternalServerErrorException, + ); + }); +}); diff --git a/api/src/common/utilities.ts b/api/src/common/utilities.ts index c109df5e5b8c5506844a4e43a7b1d7612440a135..92d045784438e0181d5e80c85e85c20e82234fbf 100644 --- a/api/src/common/utilities.ts +++ b/api/src/common/utilities.ts @@ -10,12 +10,32 @@ import axios from 'axios'; export const errorAxiosHandler = (e: any) => { if (!axios.isAxiosError(e)) throw new InternalServerErrorException(e); - if (e.response.status === 401) throw new UnauthorizedException(); - if (e.response.status === 404) throw new NotFoundException(); - if (e.response.status === 408) throw new RequestTimeoutException(); - if (e.response.status === 500) throw new InternalServerErrorException(); - - if (e.response) throw new HttpException(e.response.data, e.response.status); + if (e.response) { + if (e.response.status === 401) throw new UnauthorizedException(); + if (e.response.status === 404) throw new NotFoundException(); + if (e.response.status === 408) throw new RequestTimeoutException(); + if (e.response.status === 500) throw new InternalServerErrorException(); + if (e.response.status && e.response.status) + throw new HttpException(e.response.data, e.response.status); + } throw new InternalServerErrorException('Unknown error'); }; + +/** + * Parse a string to a boolean + * @param {string} value - The value to parse. + * @param [defaultValue=false] - The default value to return if the value is not a valid boolean. + * @returns A boolean value. + */ +export const parseToBoolean = ( + value: string, + defaultValue = false, +): boolean => { + try { + if (value.toLowerCase() == 'true') return true; + return value.toLowerCase() == 'false' ? false : defaultValue; + } catch { + return defaultValue; + } +}; diff --git a/api/src/engine/connectors/datashield/main.connector.ts b/api/src/engine/connectors/datashield/main.connector.ts index 3c34f0f23ad0f27347ae6b9de3c538c0d5e2d180..454654e8e774617de4ef68b5e99f9e15a8b1918c 100644 --- a/api/src/engine/connectors/datashield/main.connector.ts +++ b/api/src/engine/connectors/datashield/main.connector.ts @@ -4,7 +4,6 @@ import { InternalServerErrorException, Logger, NotImplementedException, - UnauthorizedException, } from '@nestjs/common'; import { Request } from 'express'; import { catchError, firstValueFrom } from 'rxjs'; @@ -102,7 +101,9 @@ export default class DataShieldService implements IEngineService { DataShieldService.logger.verbose(path); return { rawdata: { - data: 'Engine result are inconsitent', + data: + 'Engine error when processing the request. Reason: ' + + response.data, type: MIME_TYPES.ERROR, }, }; @@ -234,15 +235,13 @@ export default class DataShieldService implements IEngineService { return [transformToDomains.evaluate(response.data)]; } - async getActiveUser(): Promise<User> { - const dummyUser = { - username: 'anonymous', - id: 'anonymousId', - fullname: 'anonymous', - email: 'anonymous@anonymous.com', - agreeNDA: true, + async getActiveUser(req: Request): Promise<User> { + const user = req.user as User; + return { + username: user.id, + id: user.id, + fullname: user.id, }; - return dummyUser; } getAlgorithmsREST(): string { diff --git a/api/src/engine/connectors/exareme/main.connector.ts b/api/src/engine/connectors/exareme/main.connector.ts index daa996d8e7c1c2398ff2e9af0ff0a4c31c253a1c..8d25f9ffd2b3eba957ee2bcbe73a7f704841b38a 100644 --- a/api/src/engine/connectors/exareme/main.connector.ts +++ b/api/src/engine/connectors/exareme/main.connector.ts @@ -54,7 +54,7 @@ export default class ExaremeService implements IEngineService { getConfiguration(): IConfiguration { return { contactLink: 'https://ebrains.eu/support/', - galaxy: true, + hasGalaxy: true, }; } diff --git a/api/src/engine/engine.constants.ts b/api/src/engine/engine.constants.ts index e8fe57799577d257d79d8eea188da5f1bed75417..3c9ae327491d37456964f9ce2cba8a7f04ecccf5 100644 --- a/api/src/engine/engine.constants.ts +++ b/api/src/engine/engine.constants.ts @@ -1,2 +1,3 @@ export const ENGINE_MODULE_OPTIONS = 'EngineModuleOption'; export const ENGINE_SERVICE = 'EngineService'; +export const ENGINE_SKIP_TOS = 'TOS_SKIP'; diff --git a/api/src/engine/engine.controller.ts b/api/src/engine/engine.controller.ts index 8a704c0032c0ece83f85df2e3158e2fa2fac5c64..f00a8e74803cc955fae75214d99bf561713e63cb 100644 --- a/api/src/engine/engine.controller.ts +++ b/api/src/engine/engine.controller.ts @@ -1,20 +1,10 @@ -import { - Controller, - Get, - Inject, - Post, - Req, - UseGuards, - UseInterceptors, -} from '@nestjs/common'; +import { Controller, Get, Inject, Req, UseInterceptors } from '@nestjs/common'; import { Request } from 'express'; import { Observable } from 'rxjs'; -import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; import { ENGINE_SERVICE } from './engine.constants'; import { IEngineService } from './engine.interfaces'; import { ErrorsInterceptor } from './interceptors/errors.interceptor'; -@UseGuards(JwtAuthGuard) @UseInterceptors(ErrorsInterceptor) @Controller() export class EngineController { @@ -27,21 +17,6 @@ export class EngineController { return this.engineService.getAlgorithmsREST(request); } - @Get('activeUser') - async getActiveUser(@Req() request: Request) { - return await this.engineService.getActiveUser(request); - } - - @Post('activeUser/agreeNDA') - async agreeNDA(@Req() request: Request) { - return await this.engineService.updateUser(request); - } - - @Get('logout') - logout(@Req() request: Request): void { - this.engineService.logout(request); - } - @Get('galaxy') galaxy(@Req() request: Request): Observable<string> | string { return this.engineService.getPassthrough?.('galaxy', request); diff --git a/api/src/engine/engine.interfaces.ts b/api/src/engine/engine.interfaces.ts index 7d745e4559f96b582c873a7045491e89f8c0e83e..b8b21e9ab52e82ed911ee044e080ec401146f861 100644 --- a/api/src/engine/engine.interfaces.ts +++ b/api/src/engine/engine.interfaces.ts @@ -18,7 +18,7 @@ export interface IEngineOptions { baseurl: string; } -export type IConfiguration = Pick<Configuration, 'contactLink' | 'galaxy'>; +export type IConfiguration = Pick<Configuration, 'contactLink' | 'hasGalaxy'>; export interface IEngineService { //GraphQL diff --git a/api/src/engine/engine.module.ts b/api/src/engine/engine.module.ts index 35c413825e7fe45ec4d98504c154199e83c68b06..efdf729c1ea27eb85028aee5d0f5c4b597ed6486 100644 --- a/api/src/engine/engine.module.ts +++ b/api/src/engine/engine.module.ts @@ -1,8 +1,5 @@ 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'; diff --git a/api/src/engine/engine.resolver.ts b/api/src/engine/engine.resolver.ts index a6a845dbec74d576d0468a4d1434f6373c886e0a..2ea02a19025029f551ea684fa756ca7b842e82d6 100644 --- a/api/src/engine/engine.resolver.ts +++ b/api/src/engine/engine.resolver.ts @@ -1,10 +1,14 @@ -import { Inject, UseGuards } from '@nestjs/common'; +import { Inject, UseGuards, UseInterceptors } from '@nestjs/common'; import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; import { Request } from 'express'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; import { GQLRequest } from '../common/decorators/gql-request.decoractor'; import { Md5 } from 'ts-md5'; -import { ENGINE_MODULE_OPTIONS, ENGINE_SERVICE } from './engine.constants'; +import { + ENGINE_MODULE_OPTIONS, + ENGINE_SERVICE, + ENGINE_SKIP_TOS, +} from './engine.constants'; import { IEngineOptions, IEngineService } from './engine.interfaces'; import { Configuration } from './models/configuration.model'; import { Domain } from './models/domain.model'; @@ -16,7 +20,13 @@ import { import { ExperimentCreateInput } from './models/experiment/input/experiment-create.input'; import { ExperimentEditInput } from './models/experiment/input/experiment-edit.input'; import { ListExperiments } from './models/experiment/list-experiments.model'; +import { ConfigService } from '@nestjs/config'; +import { parseToBoolean } from '../common/utilities'; +import { authConstants } from '../auth/auth-constants'; +import { Public } from 'src/auth/decorators/public.decorator'; +import { ErrorsInterceptor } from './interceptors/errors.interceptor'; +@UseInterceptors(ErrorsInterceptor) @UseGuards(JwtAuthGuard) @Resolver() export class EngineResolver { @@ -24,14 +34,24 @@ export class EngineResolver { @Inject(ENGINE_SERVICE) private readonly engineService: IEngineService, @Inject(ENGINE_MODULE_OPTIONS) private readonly engineOptions: IEngineOptions, + private readonly configSerivce: ConfigService, ) {} @Query(() => Configuration) + @Public() configuration(): Configuration { const config = this.engineService.getConfiguration?.(); const data = { ...(config ?? {}), + skipAuth: parseToBoolean( + this.configSerivce.get(authConstants.skipAuth), + true, + ), + skipTos: parseToBoolean(this.configSerivce.get(ENGINE_SKIP_TOS)), + enableSSO: parseToBoolean( + this.configSerivce.get(authConstants.enableSSO), + ), connectorId: this.engineOptions.type, }; diff --git a/api/src/engine/interceptors/errors.interceptor.ts b/api/src/engine/interceptors/errors.interceptor.ts index 47e61fc3aab815fa820b1180b655a65529723dc0..17bd2202b5ee03a82ba2c15bb2563da16f57a4ba 100644 --- a/api/src/engine/interceptors/errors.interceptor.ts +++ b/api/src/engine/interceptors/errors.interceptor.ts @@ -30,7 +30,9 @@ export class ErrorsInterceptor implements NestInterceptor { this.logger.log(e.message); this.logger.verbose( - `[Error ${e.response.status}] ${e.response.data.message}`, + `[Error ${e.response.status}] ${ + e.response.data.message ?? e.response.data + }`, ); throw new HttpException(e.response.data, e.response.status); // catch errors, maybe make it optional (module parameter) }), diff --git a/api/src/engine/models/configuration.model.ts b/api/src/engine/models/configuration.model.ts index 69edf367554e1ecf8aae7831a665a0a998dfa2b1..d2d390dc67e6bbd120932a382dc324a0d1e1f677 100644 --- a/api/src/engine/models/configuration.model.ts +++ b/api/src/engine/models/configuration.model.ts @@ -5,11 +5,20 @@ export class Configuration { connectorId: string; @Field({ nullable: true, defaultValue: false }) - galaxy?: boolean; + hasGalaxy?: boolean; @Field({ nullable: true }) contactLink?: string; @Field() version: string; + + @Field({ nullable: true }) + skipAuth?: boolean; + + @Field({ nullable: true, defaultValue: false }) + skipTos?: boolean; + + @Field({ nullable: true, defaultValue: true }) + enableSSO?: boolean; } diff --git a/api/src/engine/models/result/table-result.model.ts b/api/src/engine/models/result/table-result.model.ts index 664d64c2e823aa2c37b9d98fd345af6cced24d66..a733683d607f6f8bfbaa9d6cec5884620a76cceb 100644 --- a/api/src/engine/models/result/table-result.model.ts +++ b/api/src/engine/models/result/table-result.model.ts @@ -22,6 +22,6 @@ export class TableResult extends Result { @Field(() => [Header]) headers: Header[]; - @Field(() => ThemeType, { defaultValue: ThemeType.DEFAULT }) + @Field(() => ThemeType, { defaultValue: ThemeType.DEFAULT, nullable: true }) theme?: ThemeType; } diff --git a/api/src/main/app.module.ts b/api/src/main/app.module.ts index 9ec49e6ee1808cc605e8e8eae247ddaa53210d27..1555caaa8bcc0a04abeb20da660200561c83e6d2 100644 --- a/api/src/main/app.module.ts +++ b/api/src/main/app.module.ts @@ -3,6 +3,7 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { GraphQLModule } from '@nestjs/graphql'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { GraphQLError } from 'graphql'; import { join } from 'path'; import { AuthModule } from 'src/auth/auth.module'; import { EngineModule } from 'src/engine/engine.module'; @@ -25,6 +26,19 @@ import { AppService } from './app.service'; credentials: true, origin: [/http:\/\/localhost($|:\d*)/, /http:\/\/127.0.0.1($|:\d*)/], }, + formatError: (error: GraphQLError) => { + const extensions = { + code: error.extensions.code, + status: + error.extensions?.response?.statusCode ?? + error.extensions.exception.status, + message: + error.extensions?.response?.message ?? + error.extensions?.exception?.message, + }; + + return { ...error, extensions: { ...error.extensions, ...extensions } }; + }, }), EngineModule.forRoot({ type: process.env.ENGINE_TYPE, diff --git a/api/src/schema.gql b/api/src/schema.gql index 5abb5414234e66fada8b5b8e582ab74f6144e228..4d248dc43d05871c60002fd581ba9a9a41f0ff07 100644 --- a/api/src/schema.gql +++ b/api/src/schema.gql @@ -16,9 +16,12 @@ type AuthenticationOutput { type Configuration { connectorId: String! - galaxy: Boolean + hasGalaxy: Boolean contactLink: String version: String! + skipAuth: Boolean + skipTos: Boolean + enableSSO: Boolean } type Dataset { @@ -94,7 +97,7 @@ type TableResult { name: String! data: [[String!]!]! headers: [Header!]! - theme: ThemeType! + theme: ThemeType } enum ThemeType { diff --git a/api/src/users/users.resolver.spec.ts b/api/src/users/users.resolver.spec.ts index 6ea7e0f2e761999597e95784afbd8162e4be2e8a..e449f71fb0900a50fc53711d934d8be13fdcc4d1 100644 --- a/api/src/users/users.resolver.spec.ts +++ b/api/src/users/users.resolver.spec.ts @@ -102,6 +102,10 @@ describe('UsersResolver', () => { }); }); + it('Undefined user should not throw exception', async () => { + expect(await resolver.getUser(req, undefined)).toBeTruthy(); + }); + it('Update user from engine ', async () => { expect(await resolver.updateUser(req, updateData, user)).toStrictEqual({ ...user, @@ -116,4 +120,8 @@ describe('UsersResolver', () => { ...internUser, }); }); + + it('Undefined user should not throw exception', async () => { + expect(await resolver.updateUser(req, updateData, user)).toBeTruthy(); + }); }); diff --git a/api/src/users/users.resolver.ts b/api/src/users/users.resolver.ts index 46c77cc1fb0260c9f0f7b6776a0661592ed2b912..13ec63ae73dc528d0b9c1dd1a0425f7854045f4e 100644 --- a/api/src/users/users.resolver.ts +++ b/api/src/users/users.resolver.ts @@ -43,7 +43,9 @@ export class UsersResolver { // Checking if the user exists in the internal database. If it does, it will assign the user to the `user` object. try { - const internalUser = await this.usersService.findOne(reqUser.id); + const internalUser = reqUser + ? await this.usersService.findOne(reqUser.id) + : undefined; if (internalUser && (!user.id || internalUser.id === user.id)) { Object.assign(user, internalUser); @@ -71,10 +73,10 @@ export class UsersResolver { async updateUser( @GQLRequest() request: Request, @Args('updateUserInput') updateUserInput: UpdateUserInput, - @CurrentUser() user: User, + @CurrentUser() user?: User, ) { if (this.engineService.updateUser) - return this.engineService.updateUser(request, user.id, updateUserInput); + return this.engineService.updateUser(request, user?.id, updateUserInput); await this.usersService.update(user.id, updateUserInput);