Commit 0fc9f7e4 authored by Fabien Forestier's avatar Fabien Forestier
Browse files

Merge branch 'development' into 'master'

Version 3.2.1

See merge request refonte-data/legacy-auth-middleware!2
parents bd9346c0 97d39b33
Pipeline #3151 passed with stage
in 7 seconds
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo_text.svg" width="320" alt="Nest Logo" /></a>
</p>
[travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master
[travis-url]: https://travis-ci.org/nestjs/nest
[linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux
[linux-url]: https://travis-ci.org/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="blank">Node.js</a> framework for building efficient and scalable server-side applications, heavily inspired by <a href="https://angular.io" target="blank">Angular</a>.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/dm/@nestjs/core.svg" alt="NPM Downloads" /></a>
<a href="https://travis-ci.org/nestjs/nest"><img src="https://api.travis-ci.org/nestjs/nest.svg?branch=master" alt="Travis" /></a>
<a href="https://travis-ci.org/nestjs/nest"><img src="https://img.shields.io/travis/nestjs/nest/master.svg?label=linux" alt="Linux" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#5" alt="Coverage" /></a>
<a href="https://gitter.im/nestjs/nestjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge"><img src="https://badges.gitter.im/nestjs/nestjs.svg" alt="Gitter" /></a>
<a href="https://opencollective.com/nest#backer"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec"><img src="https://img.shields.io/badge/Donate-PayPal-dc3d53.svg"/></a>
<a href="https://twitter.com/nestframework"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Installation
```bash
$ npm install
```
## Running the app
## Environment variables
In order to run the code, some environment variables are needed. They are specified in the `template.env` file at the root of the project.
For a local deployment:
1. `cp template.env .env`
2. Edit .env according to the chosen configuration
The values will be read from the file by default, but you can override any of those by exporting manually the variable before launching the service.
## Running the app without docker
You will need to provide a healthy connection to a database in order for the service to start.
```bash
# development
......@@ -43,15 +26,24 @@ $ npm run start
# watch mode
$ npm run start:dev
# incremental rebuild (webpack)
$ npm run webpack
$ npm run start:hmr
# production mode
$ npm run start:prod
```
## Test
## Running the app with docker
```bash
# build
$ docker-compose build
# deploy
$ docker-compose up [-d]
# build and deploy
$ docker-compose up --build [-d]
```
<!-- ## Test
```bash
# unit tests
......@@ -62,18 +54,6 @@ $ npm run test:e2e
# test coverage
$ npm run test:cov
```
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
``` -->
## License
Nest is [MIT licensed](LICENSE).
......@@ -13,8 +13,13 @@ services:
- SERVICE_EMAIL_URL=${SERVICE_EMAIL_URL}
- USER_SUPPORT_MAILBOX=${USER_SUPPORT_MAILBOX}
- FRONT_END_URL=${FRONT_END_URL}
- VALIDATE_ACCOUNT_URI=login
- PASSWORD_RESET_URI=reinitialiser-mon-mot-de-passe
- API_KEY=${API_KEY}
- ACCESS_TOKEN_COOKIE_KEY=${ACCESS_TOKEN_COOKIE_KEY}
- IMAGE_HOST=https://minio.alpha.grandlyon.com/email-template-assets
- REDIS_HOST=redis
- REDIS_PORT=6379
restart: unless-stopped
depends_on:
- redis
......
favicon.ico

4.19 KB | W: | H:

favicon.ico

31.3 KB | W: | H:

favicon.ico
favicon.ico
favicon.ico
favicon.ico
  • 2-up
  • Swipe
  • Onion skin
{
"name": "legacy-auth-middleware",
"version": "3.2.0",
"description": "description",
"version": "3.2.1",
"description": "",
"author": "",
"license": "MIT",
"scripts": {
......
import { Logger } from '@nestjs/common';
export class AppLogger extends Logger {
log(message: string, context?: string) {
// add your tailored logic here
super.log(`[log] ${message}`, context);
}
warn(message: string, context?: string) {
// add your tailored logic here
super.warn(`[warn] ${message}`, context);
}
error(message: string, trace?: string, context?: string) {
// add your tailored logic here
super.error(`[error] ${message}`, trace, context);
}
}
\ No newline at end of file
......@@ -3,6 +3,7 @@ import { LegacyModule } from './legacy/legacy.module';
import { ConfigModule } from './configuration/config.module';
import { VerifyXsrfTokenAndDecodeJWTPayloadMiddleware } from './middlewares/decode-jwt-payload.middleware';
import { HealthModule } from './health/health.module';
import { AppLogger } from './app-logger';
@Module({
imports: [
......@@ -10,6 +11,9 @@ import { HealthModule } from './health/health.module';
HealthModule,
LegacyModule,
],
providers: [
AppLogger,
]
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
......
......@@ -8,16 +8,6 @@ export class ConfigService {
private _config = Config;
constructor() {
// Only for development purpose, set environment variable based on the env file provided
if (process.env.NODE_ENV === 'LOCAL') {
const envConfig = dotenv.parse(fs.readFileSync(`./src/configuration/${process.env.NODE_ENV}.env`));
for (const k in envConfig) {
if (envConfig.hasOwnProperty(k)) {
process.env[k] = envConfig[k];
}
}
}
this._config.legacyAuthServiceUrl = process.env.LEGACY_AUTH_SERVICE_URL;
this._config.adminPassword = process.env.ADMIN_PASSWORD;
this._config.adminUsername = process.env.ADMIN_USERNAME;
......@@ -26,6 +16,11 @@ export class ConfigService {
this._config.frontEnd.url = process.env.FRONT_END_URL;
this._config.apiKey = process.env.API_KEY;
this._config.accessTokenCookieKey = process.env.ACCESS_TOKEN_COOKIE_KEY;
this._config.imageHost = process.env.IMAGE_HOST;
this._config.redis.host = process.env.REDIS_HOST;
this._config.redis.port = process.env.REDIS_PORT;
this._config.frontEnd.validateAccountUri = process.env.VALIDATE_ACCOUNT_URI;
this._config.frontEnd.passwordResetUri = process.env.PASSWORD_RESET_URI;
this.initilizePublicPrivateKeys();
}
......@@ -35,7 +30,7 @@ export class ConfigService {
}
initilizePublicPrivateKeys() {
const key = new NodeRSA({b: 2048});
const key = new NodeRSA({ b: 2048 });
this._config.publicKey = key.exportKey('public');
this._config.privateKey = key.exportKey('private');
......
......@@ -9,15 +9,15 @@ export const Config = {
accountCreationTokenTTL: 86400, // Time to live of the token used to validate the cration of an account in second (24h)
frontEnd: {
url: '',
validateAccountUri: 'login',
passwordResetUri: 'reinitialiser-mon-mot-de-passe',
validateAccountUri: '',
passwordResetUri: '',
},
resetPasswordSessionTtl: 86400, // 24 hours
redis: {
port: 6379,
host: 'redis',
port: null,
host: '',
},
imageHost: 'https://highway-to-data.alpha.grandlyon.com/email-template-assets',
imageHost: '',
apiKey: '',
accessTokenCookieKey: '',
};
\ No newline at end of file
LEGACY_AUTH_SERVICE_URL=
ADMIN_PASSWORD=
ADMIN_USERNAME=
SERVICE_EMAIL_URL=
USER_SUPPORT_MAILBOX=
FRONT_END_URL=
API_KEY=
ACCESS_TOKEN_COOKIE_KEY=
......@@ -3,18 +3,17 @@ import {
TerminusOptionsFactory,
TerminusModuleOptions,
DNSHealthIndicator,
MicroserviceHealthIndicator,
} from '@nestjs/terminus';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '../configuration/config.service';
import { Transport } from '@nestjs/microservices';
import { RedisHealthIndicator } from './redis.healthIndicator';
@Injectable()
export class HealthCheckService implements TerminusOptionsFactory {
constructor(
private _configService: ConfigService,
private readonly _dnsHealthIndicator: DNSHealthIndicator,
private readonly _microserviceHealthIndicator: MicroserviceHealthIndicator,
private readonly _redisHealthIndicator: RedisHealthIndicator,
) { }
createTerminusOptions(): TerminusModuleOptions {
......@@ -25,10 +24,7 @@ export class HealthCheckService implements TerminusOptionsFactory {
healthIndicators: [
async () => this._dnsHealthIndicator.pingCheck('LegacyAuthService', `${config.legacyAuthServiceUrl}/get_services`),
async () => this._dnsHealthIndicator.pingCheck('EmailService', `${config.serviceEmailUrl}/health`),
async () => this._microserviceHealthIndicator.pingCheck('Redis', {
transport: Transport.REDIS,
options: { url: `redis://${config.redis.host}:${config.redis.port}` },
}),
async () => this._redisHealthIndicator.checkRedisConnection(config.redis),
],
};
return {
......
import { Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus';
import { HealthCheckService } from './health-check.service';
import { LegacyModule } from '../legacy/legacy.module';
import { ConfigModule } from '../configuration/config.module';
import { RedisHealthIndicator } from './redis.healthIndicator';
@Module({
imports: [
ConfigModule,
TerminusModule.forRootAsync({
imports: [HealthModule],
useClass: HealthCheckService,
}),
],
providers: [RedisHealthIndicator],
exports: [RedisHealthIndicator],
})
export class HealthModule { }
\ No newline at end of file
import { HealthIndicator } from '@nestjs/terminus/dist/health-indicators/abstract/health-indicator';
import { HealthIndicatorResult } from '@nestjs/terminus';
import * as redis from 'redis';
import * as bluebird from 'bluebird';
import { HealthCheckError } from '@godaddy/terminus';
bluebird.promisifyAll(redis);
export class RedisHealthIndicator extends HealthIndicator {
async checkRedisConnection(redisConf: { host: string, port: number }): Promise<HealthIndicatorResult> {
const client = redis.createClient({
host: redisConf.host, port: redisConf.port,
});
await new Promise((resolve, reject) => {
client.on('ready', resolve);
client.on('error', reject);
setTimeout(reject, 2000);
}).catch((err) => {
client.quit();
throw new HealthCheckError('Redis connection failed', {
Redis: {
err,
status: 'down',
message: 'Failed to connect to redis',
},
});
});
client.quit();
return this.getStatus('Redis', true);
}
}
\ No newline at end of file
import { UnauthorizedException, InternalServerErrorException, BadRequestException, Logger } from '@nestjs/common';
import { UnauthorizedException, BadRequestException, Logger } from '@nestjs/common';
import * as NodeRSA from 'node-rsa';
export const decrypt = (encrypted: string, privateKey) => {
......@@ -8,7 +8,8 @@ export const decrypt = (encrypted: string, privateKey) => {
const decrypted = key.decrypt(encrypted, 'utf8');
return decrypted;
} catch (error) {
Logger.error(error);
const logger = new Logger();
logger.error('Couldn\'t decrypt password', `${error}`, 'Decrypt password');
throw new UnauthorizedException('Couldn\'t decrypt password');
}
} else {
......
import { Controller, Post, Body, Logger, HttpException, InternalServerErrorException,
HttpCode, Get, Req, Delete, Put, Query } from '@nestjs/common';
import {
Controller, Post, Body, HttpException, InternalServerErrorException,
HttpCode, Get, Req, Delete, Put, Query
} from '@nestjs/common';
import { LegacyService } from './legacy.service';
import { ApiOperation, ApiResponse, ApiUseTags, ApiImplicitHeader, ApiImplicitBody } from '@nestjs/swagger';
import { LoginForm, UserCreationForm, UserInfoWithEcryptedPassword, JWTToken, Service, Resource, RestrictedAccessDataset,
AccessRequest, UpdatePasswordForm, UserUpdateForm, UserInfo, AccessDeletionResponse,
AccessRequestResponse, AccessRenewalResponse, UserAccountValidationRequest, PasswordResetForm, PasswordForgottenForm} from './legacy.model';
import {
LoginForm, UserCreationForm, UserInfoWithEcryptedPassword, JWTToken, Service, Resource, RestrictedAccessDataset,
AccessRequest, UpdatePasswordForm, UserUpdateForm, UserInfo, AccessDeletionResponse,
AccessRequestResponse, AccessRenewalResponse, UserAccountValidationRequest, PasswordResetForm, PasswordForgottenForm
} from './legacy.model';
import { handleError } from './errorHandlingHelper';
@ApiUseTags('legacy')
......@@ -21,17 +25,15 @@ export class LegacyController {
name: 'Cookie',
description: 'The JWT token is sent by the browser as a cookie (refer to the config of the Authentication project to know which key is used)',
})
@ApiImplicitHeader({ name: 'x-xsrf-token', description: 'Xsrf Token'})
@ApiImplicitHeader({ name: 'x-xsrf-token', description: 'Xsrf Token' })
@ApiResponse({ status: 200, description: 'User is existing, returning its info', type: UserInfo })
@ApiResponse({ status: 400, description: 'Bad Request' })
@ApiResponse({ status: 500, description: 'Internal error' })
async getUserInfo(@Req() req) {
Logger.log('[-] get user info endpoint');
const token: JWTToken = req.headers.token;
try {
return await this.legacyService.getUserInfo(token.username, token.authzKey);
} catch (error) {
Logger.error(`[x] Error in controller: ${error}`);
if (error instanceof HttpException) {
throw error;
} else {
......@@ -47,12 +49,9 @@ export class LegacyController {
@ApiResponse({ status: 500, description: 'Internal error' })
@HttpCode(201)
async createUser(@Body() userCreationForm: UserCreationForm) {
Logger.log('[-] Add user endpoint');
try {
return await this.legacyService.createUser(userCreationForm);
} catch (error) {
Logger.error(`[x] Error in controller: ${error}`);
if (error instanceof HttpException) {
throw error;
} else {
......@@ -68,12 +67,9 @@ export class LegacyController {
@ApiResponse({ status: 500, description: 'Internal error' })
@HttpCode(201)
async validateAccount(@Body() body: UserAccountValidationRequest) {
Logger.log('[-] Validate user account endpoint');
try {
return await this.legacyService.validateUserAccount(body.token);
} catch (error) {
Logger.error(`[x] Error in controller: ${error}`);
if (error instanceof HttpException) {
throw error;
} else {
......@@ -88,12 +84,10 @@ export class LegacyController {
@ApiResponse({ status: 400, description: 'Bad Request (user not found)' })
@ApiResponse({ status: 500, description: 'Internal error' })
async login(@Body() loginForm: LoginForm): Promise<UserInfoWithEcryptedPassword> {
Logger.log('[-] user login endpoint');
try {
const userInfo = await this.legacyService.getUser(loginForm);
return userInfo;
} catch (error) {
Logger.error(` [x] Error in controller: ${error}`);
if (error instanceof HttpException) {
throw error;
} else {
......@@ -108,19 +102,16 @@ export class LegacyController {
name: 'Cookie',
description: 'The JWT token is sent by the browser as a cookie (refer to the config of the Authentication project to know which key is used)',
})
@ApiImplicitHeader({ name: 'x-xsrf-token', description: 'Xsrf Token'})
@ApiImplicitHeader({ name: 'x-xsrf-token', description: 'Xsrf Token' })
@ApiResponse({ status: 204, description: 'Account deleted' })
@ApiResponse({ status: 400, description: 'Bad Request (Invalid user credentials.)' })
@ApiResponse({ status: 500, description: 'Internal error' })
@HttpCode(204)
async deleteUserAccount(@Req() req) {
Logger.log('[-] DELETE user endpoint');
const token: JWTToken = req.headers.token;
try {
return await this.legacyService.deleteUserAccount(token);
} catch (error) {
Logger.error(`[x] Error in controller:`);
Logger.error(error);
if (error instanceof HttpException) {
throw error;
} else {
......@@ -135,18 +126,16 @@ export class LegacyController {
name: 'Cookie',
description: 'The JWT token is sent by the browser as a cookie (refer to the config of the Authentication project to know which key is used)',
})
@ApiImplicitHeader({ name: 'x-xsrf-token', description: 'Xsrf Token'})
@ApiImplicitHeader({ name: 'x-xsrf-token', description: 'Xsrf Token' })
@ApiResponse({ status: 200, description: 'Success' })
@ApiResponse({ status: 400, description: 'Bad Request (user not found, password incorrect)' })
@ApiResponse({ status: 500, description: 'Internal error' })
async updatePässword(@Req() req, @Body() updatePasswordForm: UpdatePasswordForm): Promise<void> {
Logger.log('[-] update user password endpoint');
const token: JWTToken = req.headers.token;
try {
const res = await this.legacyService.updateUserPassword(token.username, updatePasswordForm);
return res;
} catch (error) {
Logger.error(` [x] Error in controller: ${error}`);
if (error instanceof HttpException) {
throw error;
} else {
......@@ -161,12 +150,11 @@ export class LegacyController {
name: 'Cookie',
description: 'The JWT token is sent by the browser as a cookie (refer to the config of the Authentication project to know which key is used)',
})
@ApiImplicitHeader({ name: 'x-xsrf-token', description: 'Xsrf Token'})
@ApiImplicitHeader({ name: 'x-xsrf-token', description: 'Xsrf Token' })
@ApiResponse({ status: 200, description: 'Success', type: UserInfo })
@ApiResponse({ status: 400, description: 'Bad Request (user not found, password incorrect)' })
@ApiResponse({ status: 500, description: 'Internal error' })
async updateUserInfo(@Req() req, @Body() userUpdateForm: UserUpdateForm): Promise<UserInfoWithEcryptedPassword> {
Logger.log('[-] update user password endpoint');
const token: JWTToken = req.headers.token;
try {
await this.legacyService.updateUserInfo(token, userUpdateForm);
......@@ -176,7 +164,6 @@ export class LegacyController {
});
return userInfo;
} catch (error) {
Logger.error(` [x] Error in controller: ${error}`);
if (error instanceof HttpException) {
throw error;
} else {
......@@ -187,9 +174,9 @@ export class LegacyController {
@Put('user/resetPassword')
@ApiOperation({ title: 'Replace user password with the one provided (user is identified with the email associated to the token provided)' })
@ApiResponse({ status: 200, description: 'Password updated'})
@ApiResponse({ status: 400, description: 'Bad request'})
@ApiResponse({ status: 500, description: 'Internal error'})
@ApiResponse({ status: 200, description: 'Password updated' })
@ApiResponse({ status: 400, description: 'Bad request' })
@ApiResponse({ status: 500, description: 'Internal error' })
@HttpCode(200)
async resetPassword(@Body() body: PasswordResetForm): Promise<void> {
try {
......@@ -200,10 +187,10 @@ export class LegacyController {
}
@Get('/isPasswordResetTokenValid')
@ApiOperation({ title: 'Verify the validity of the password reset token'})
@ApiResponse({ status: 200, description: 'Returns the validity status of the token' , type: Boolean })
@ApiResponse({ status: 400, description: 'Bad request'})
@ApiResponse({ status: 500, description: 'Internal error'})
@ApiOperation({ title: 'Verify the validity of the password reset token' })
@ApiResponse({ status: 200, description: 'Returns the validity status of the token', type: Boolean })
@ApiResponse({ status: 400, description: 'Bad request' })
@ApiResponse({ status: 500, description: 'Internal error' })
@HttpCode(200)
async isPasswordResetTokenValid(@Query('token') token: string): Promise<boolean> {
try {
......@@ -215,9 +202,9 @@ export class LegacyController {
@Post('/passwordForgotten')
@ApiOperation({ title: 'Launch the password forgotten procedure' })
@ApiResponse({ status: 200, description: 'Procedure launched'})
@ApiResponse({ status: 400, description: 'Bad request'})
@ApiResponse({ status: 500, description: 'Internal error'})
@ApiResponse({ status: 200, description: 'Procedure launched' })
@ApiResponse({ status: 400, description: 'Bad request' })
@ApiResponse({ status: 500, description: 'Internal error' })
@HttpCode(200)
async passwordForgotten(@Body() body: PasswordForgottenForm): Promise<void> {
try {
......@@ -234,19 +221,16 @@ export class LegacyController {
name: 'Cookie',
description: 'The JWT token is sent by the browser as a cookie (refer to the config of the Authentication project to know which key is used)',
})
@ApiImplicitHeader({ name: 'x-xsrf-token', description: 'Xsrf Token'})
@ApiImplicitHeader({ name: 'x-xsrf-token', description: 'Xsrf Token' })
@ApiResponse({ status: 200, description: 'Success, returns accessible resources', type: [Resource] })
@ApiResponse({ status: 401, description: 'Authorization is not set, not a valid token, or user credentials are not correct'})
@ApiResponse({ status: 401, description: 'Authorization is not set, not a valid token, or user credentials are not correct' })
@ApiResponse({ status: 500, description: 'Internal error' })
async getUserResources(@Req() req): Promise<Resource[]> {
const token: JWTToken = req.headers.token;
Logger.log('[-] user/resources endpoint');
try {
const userServices = await this.legacyService.getUserResources(token.username, token.authzKey);
return userServices;
} catch (error) {
Logger.error(` [x] Error in controller:`);
Logger.error(error);
if (error instanceof HttpException) {
throw error;
} else {
......@@ -261,20 +245,17 @@ export class LegacyController {
name: 'Cookie',
description: 'The JWT token is sent by the browser as a cookie (refer to the config of the Authentication project to know which key is used)',
})
@ApiImplicitHeader({ name: 'x-xsrf-token', description: 'Xsrf Token'})
@ApiImplicitHeader({ name: 'x-xsrf-token', description: 'Xsrf Token' })
@ApiImplicitBody({ name: 'body', type: AccessRequest, isArray: true })
@ApiResponse({ status: 201, description: 'Request created', type: AccessRequestResponse })
@ApiResponse({ status: 400, description: 'Bad Request (Mode does not exist)' })
@ApiResponse({ status: 500, description: 'Internal error' })
@HttpCode(201)
async addUserResource(@Req() req, @Body() body) {
Logger.log('[-] user/resources/add endpoint');
const token: JWTToken = req.headers.token;
try {
return await this.legacyService.addUserResource(token, body);
} catch (error) {
Logger.error(`[x] Error in controller:`);
Logger.error(error);
if (error instanceof HttpException) {
throw error;
} else {
......@@ -289,20 +270,17 @@ export class LegacyController {
name: 'Cookie',
description: 'The JWT token is sent by the browser as a cookie (refer to the config of the Authentication project to know which key is used)',
})
@ApiImplicitHeader({ name: 'x-xsrf-token', description: 'Xsrf Token'})
@ApiImplicitHeader({ name: 'x-xsrf-token', description: 'Xsrf Token' })
@ApiImplicitBody({ name: 'body', type: AccessRequest, isArray: true })
@ApiResponse({ status: 201, description: 'Request created', type: AccessRenewalResponse })
@ApiResponse({ status: 400, description: 'Bad Request (Mode does not exist)' })
@ApiResponse({ status: 500, description: 'Internal error' })
@HttpCode(201)
async renewUserResource(@Req() req, @Body() body: AccessRequest[]) {
Logger.log('[-] user/resources/renew endpoint');
const token: JWTToken = req.headers.token;
try {
return await this.legacyService.renewUserResource(token, body);
} catch (error) {
Logger.error(`[x] Error in controller:`);
Logger.error(error);
if (error instanceof HttpException) {