Commit 1808246f authored by FORESTIER Fabien's avatar FORESTIER Fabien
Browse files

Add config module and group guard on some endpoints

parent f5800e71
Pipeline #2382 passed with stages
in 4 minutes and 37 seconds
......@@ -4,8 +4,8 @@ stages:
- db_migration
variables:
HOST_MIGRATION: vm-refonte-data-2
GROUP_HEADER: x-consumer-groups
ADMIN_GROUP_NAME: admin
build_development:
stage: build
......
......@@ -13,6 +13,8 @@ services:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: postgres
POSTGRES_HOST: database-organizations
GROUP_HEADER: ${GROUP_HEADER}
ADMIN_GROUP_NAME: ${ADMIN_GROUP_NAME}
restart: unless-stopped
depends_on:
- database-organizations
......
......@@ -3,13 +3,23 @@ import { OrganizationsModule } from './organizations/organizations.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { LinksModule } from './links/links.module';
import { HealthModule } from './health/health.module';
import { ConfigModule } from './configuration/config.module';
import { APP_GUARD } from '@nestjs/core';
import { GroupsGuard } from './guards/groups.guards';
@Module({
imports: [
ConfigModule,
TypeOrmModule.forRoot(),
OrganizationsModule,
LinksModule,
HealthModule,
],
providers: [
{
provide: APP_GUARD,
useClass: GroupsGuard,
},
],
})
export class AppModule { }
import { Module, Global } from '@nestjs/common';
import { ConfigService } from './config.service';
@Global()
@Module({
providers: [
{
provide: ConfigService,
useValue: new ConfigService(),
},
],
exports: [ConfigService],
})
export class ConfigModule {}
\ No newline at end of file
import * as dotenv from 'dotenv';
import * as fs from 'fs';
import { config } from './config';
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];
}
}
}
// Initializing conf with values from var env
this._config.groupNames.admin = process.env.ADMIN_GROUP_NAME;
this._config.groupHeader = process.env.GROUP_HEADER;
}
get config() {
return this._config;
}
}
\ No newline at end of file
export const config = {
groupNames: {
admin: '',
},
groupHeader: '',
};
\ No newline at end of file
GROUP_HEADER=
ADMIN_GROUP_NAME=
\ No newline at end of file
import { ReflectMetadata } from '@nestjs/common';
export const Groups = (...roles: string[]) => ReflectMetadata('groups', roles);
\ No newline at end of file
import { Injectable, CanActivate, ExecutionContext, Logger } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ConfigService } from '../configuration/config.service';
@Injectable()
export class GroupsGuard implements CanActivate {
constructor(private readonly reflector: Reflector, private configService: ConfigService) { }
canActivate(context: ExecutionContext): boolean {
Logger.log('[+] GroupMiddleware');
// We get the groups specified on the endpoint with the @Groups annotation
// We need then to get the real group names associated to those keys from the config file
let groups = this.reflector.get<string[]>('groups', context.getHandler());
// if no groups specified then access is always ok
if (!groups) {
return true;
}
groups = groups.map((e) => {
return this.configService.config.groupNames[e];
});
// if the spcified group is not found
if (!groups) {
return true;
}
// Get the group from the header
const request = context.switchToHttp().getRequest();
const groupHeader = request.headers[this.configService.config.groupHeader];
if (!groupHeader) {
Logger.log(' [-] no group header');
return false;
}
const consumerGroups = groupHeader.split(',').map(e => e.trim());
Logger.log(` [-] consumer groups: ${consumerGroups}`);
const hasGroup = () => consumerGroups.some((group) => groups.includes(group));
return consumerGroups && hasGroup();
}
}
\ No newline at end of file
......@@ -3,6 +3,7 @@ import { LinksService } from './links.service';
import { LinkEntity } from './link.entity';
import { ApiUseTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { Link } from './link.dto';
import { Groups } from '../decorators/groups.decorators';
@ApiUseTags('links')
@Controller('links')
......@@ -30,7 +31,9 @@ export class LinksController {
@ApiOperation({ title: 'Create one link' })
@ApiResponse({ status: 201, description: 'The record has been successfully created.' })
@ApiResponse({ status: 403, description: 'User does not have sufficient rights.' })
@Post()
@Groups('admin')
async create(@Body() linkDTo: Link) {
return this.linksService.create(linkDTo).then((link) => {
return link;
......@@ -41,13 +44,17 @@ export class LinksController {
@ApiOperation({ title: 'Update one link' })
@ApiResponse({ status: 201, description: 'The record has been successfully updated.' })
@ApiResponse({ status: 403, description: 'User does not have sufficient rights.' })
@Put(':id')
@Groups('admin')
async update(@Param('id') id: number, @Body() link: Link) {
return this.linksService.update(id, link);
}
@ApiOperation({ title: 'Delete one link' })
@ApiResponse({ status: 403, description: 'User does not have sufficient rights.' })
@Delete(':id')
@Groups('admin')
delete(@Param('id') id: number) {
return this.linksService.delete(id);
}
......
......@@ -4,6 +4,7 @@ import { ApiUseTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { OrganizationDTO } from './organization.dto';
import { Organization } from './organization.entity';
import { LinkEntity } from '../links/link.entity';
import { Groups } from '../decorators/groups.decorators';
@ApiUseTags('organizations')
@Controller('organizations')
......@@ -39,7 +40,9 @@ export class OrganizationsController {
@ApiOperation({ title: 'Create one organization' })
@ApiResponse({ status: 201, description: 'The record has been successfully created.', type: Organization })
@ApiResponse({ status: 400, description: 'The values of one or many fields are not correct.' })
@ApiResponse({ status: 403, description: 'User does not have sufficient rights.' })
@Post()
@Groups('admin')
create(@Body() organizationDTo: OrganizationDTO) {
return this.organizationsService.create(organizationDTo);
}
......@@ -47,16 +50,20 @@ export class OrganizationsController {
@ApiOperation({ title: 'Update one organization' })
@ApiResponse({ status: 200, description: 'The record has been successfully updated.', type: Organization })
@ApiResponse({ status: 400, description: 'No item exists with the given id.' })
@ApiResponse({ status: 403, description: 'User does not have sufficient rights.' })
@Put(':id')
@Groups('admin')
update(@Param('id') id: number, @Body() organizationDTo: OrganizationDTO) {
return this.organizationsService.update(id, organizationDTo);
}
@ApiOperation({ title: 'Delete one organization' })
@ApiResponse({ status: 400, description: 'No item exists with the given id.' })
@ApiResponse({ status: 403, description: 'User does not have sufficient rights.' })
@ApiResponse({ status: 204, description: 'The organization has been deleted successfully.' })
@HttpCode(204)
@Delete(':id')
@Groups('admin')
delete(@Param('id') id: number) {
return this.organizationsService.delete(id);
}
......
......@@ -8,6 +8,11 @@
"allowSyntheticDefaultImports": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [
"es6",
"es2017",
"dom"
],
"target": "es6",
"sourceMap": true,
"outDir": "./dist",
......@@ -20,4 +25,4 @@
"node_modules",
"**/*.spec.ts"
]
}
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment