diff --git a/package-lock.json b/package-lock.json index 12e5ef505e2d7e4795695c61bf8662ec588e7ae1..adff41ed4be44086d824aa2453e170b5aa27749f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -448,6 +448,41 @@ "uuid": "3.3.2" } }, + "@nestjs/jwt": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-6.1.1.tgz", + "integrity": "sha512-XZEYC+p69N+Accktjho0B98TkwAKCZNt91+t08eukw7Gwk6FvfJB+aBzHCmQEaWUiAOpAo4eObgac86P12XOkw==", + "requires": { + "@types/jsonwebtoken": "7.2.8", + "jsonwebtoken": "8.4.0" + }, + "dependencies": { + "@types/jsonwebtoken": { + "version": "7.2.8", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-7.2.8.tgz", + "integrity": "sha512-XENN3YzEB8D6TiUww0O8SRznzy1v+77lH7UmuN54xq/IHIsyWjWOzZuFFTtoiRuaE782uAoRwBe/wwow+vQXZw==", + "requires": { + "@types/node": "*" + } + }, + "jsonwebtoken": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.4.0.tgz", + "integrity": "sha512-coyXjRTCy0pw5WYBpMvWOMN+Kjaik2MwTUIq9cna/W7NpO9E+iYbumZONAz3hcr+tXFJECoQVrtmIoC3Oz0gvg==", + "requires": { + "jws": "^3.1.5", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1" + } + } + } + }, "@nestjs/platform-express": { "version": "6.2.4", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-6.2.4.tgz", @@ -5707,6 +5742,20 @@ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", "dev": true }, + "passport-jwt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz", + "integrity": "sha512-BwC0n2GP/1hMVjR4QpnvqA61TxenUMlmfNjYNgK0ZAs0HK4SOQkHcSv4L328blNTLtHq7DbmvyNJiH+bn6C5Mg==", + "requires": { + "jsonwebtoken": "^8.2.0", + "passport-strategy": "^1.0.0" + } + }, + "passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" + }, "path-dirname": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", diff --git a/package.json b/package.json index fc732f8bfcd616f633392fb4939543c3226c964f..e5999f15718a762aecbc838ac0ae94ffa8a81b63 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "dependencies": { "@nestjs/common": "^6.0.0", "@nestjs/core": "^6.0.0", + "@nestjs/jwt": "^6.1.1", "@nestjs/platform-express": "^6.0.0", "@nestjs/typeorm": "^6.1.1", "bcryptjs": "^2.4.3", @@ -29,6 +30,7 @@ "class-validator": "^0.9.1", "express-rate-limit": "^4.0.4", "jsonwebtoken": "^8.5.1", + "passport-jwt": "^4.0.0", "pg": "^7.11.0", "pg-hstore": "^2.3.2", "reflect-metadata": "^0.1.12", diff --git a/src/app.module.ts b/src/app.module.ts index 02a7b1b23665dbd586cb72eed6b44cfec859127d..2f7f5d74871d35449f040a5ef9e20d3fcfe692a2 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,15 +1,23 @@ import { Module } from '@nestjs/common'; -import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core'; +import { APP_FILTER, APP_INTERCEPTOR, APP_GUARD } from '@nestjs/core'; import { TypeOrmModule } from "@nestjs/typeorm"; +import { Connection } from "typeorm"; + import { AppController } from './app.controller'; import { AppService } from './app.service'; -import { Connection } from "typeorm"; import { UserModule } from './user/user.module'; -import { HttpErrorFilter } from './shared/http-error.filter'; + import { LoggingInterceptor } from './shared/logging.interceptor'; import { MapMarkerModule } from './mapmarkers/mapmarkers.module'; +import { HttpErrorFilter } from './shared/http-error.filter'; +import { RolesGuard } from './shared/roles.guard'; +/* +Appmodule is the core of the system. +forRoot imports database data from ormconfig.json file +All the modules contain their respective controllers and service +*/ @Module({ imports: [TypeOrmModule.forRoot(), UserModule, MapMarkerModule], @@ -17,12 +25,15 @@ import { MapMarkerModule } from './mapmarkers/mapmarkers.module'; providers: [ AppService, { provide: APP_FILTER, - useClass: HttpErrorFilter + useClass: HttpErrorFilter, }, { provide: APP_INTERCEPTOR, useClass: LoggingInterceptor, - } + },{ + provide: APP_GUARD, + useClass: RolesGuard, + } ], }) export class AppModule { diff --git a/src/main.ts b/src/main.ts index 3c7cc4607bac085ff529775a441ef81e4e089a6a..1e3e4e147bc386a4ce15a36bf274face5fd14c1e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,6 +3,11 @@ import * as rateLimit from 'express-rate-limit'; import { AppModule } from './app.module'; + +/* + Main.ts starts the server. +*/ + // due to a bug with newest release of express-rate-limit, call for rateLimit is broken // (rateLimit as any) works as a workaround for now // see https://github.com/nfriedly/express-rate-limit/issues/138 diff --git a/src/mapmarkers/mapmarker.service.ts b/src/mapmarkers/mapmarker.service.ts index b0abf869d6fae57db261a9b904b8fa6a7e1faf39..205e4ea63ba85ce29bd987badb357f45cccd475c 100644 --- a/src/mapmarkers/mapmarker.service.ts +++ b/src/mapmarkers/mapmarker.service.ts @@ -43,6 +43,13 @@ export class MapMarkerService { } catch (error) { return error.message; } + } + async test(): Promise<string>{ + try { + return 'hello'; + } catch (error) { + throw error; + } } } \ No newline at end of file diff --git a/src/mapmarkers/mapmarkers.controller.ts b/src/mapmarkers/mapmarkers.controller.ts index d2336b71fe5db29257c72a4a68c44c5de4336cbc..e035f9e865018f0867f63df9abbe430581460ec5 100644 --- a/src/mapmarkers/mapmarkers.controller.ts +++ b/src/mapmarkers/mapmarkers.controller.ts @@ -2,8 +2,9 @@ import { Controller, Body, Get, Put, UseGuards } from '@nestjs/common'; import { MapMarkerService } from './mapmarker.service'; import { MapMarkerDTO } from './mapmarker.dto'; -import { AuthGuard, RolesGuard } from '../shared/auth.guard'; +import { AuthGuard } from '../shared/auth.guard'; import { User } from '../user/user.decorator'; +import { Roles } from '../shared/roles.decorator'; @Controller('mapmarkers') export class MapMarkersController { @@ -31,10 +32,20 @@ export class MapMarkersController { } @Get('test') - @UseGuards(new RolesGuard()) + async test(){ try { - + return this.mapmarkerservice.test(); + } catch (error) { + return error.message; + } + } + @Get('testroles') + @Roles('admin') + async testroles(){ + try { + return Roles.toString(); + return this.mapmarkerservice.test(); } catch (error) { return error.message; } diff --git a/src/shared/auth.guard.ts b/src/shared/auth.guard.ts index 43fb6f0c3fa79992f66f46575d2d57a11c9cf238..ecce1762885e00063a17539f9c1ac74bd3f1025f 100644 --- a/src/shared/auth.guard.ts +++ b/src/shared/auth.guard.ts @@ -35,20 +35,20 @@ export class AuthGuard implements CanActivate { throw new HttpException(message, HttpStatus.FORBIDDEN); } } -} -export class RolesGuard implements CanActivate{ - // check for logged in user - async canActivate(context: ExecutionContext): Promise<boolean> { - // get request - const request = context.switchToHttp().getRequest(); - // check for authorization header - if (!request.headers.authorization) { - return false; + async checkRole(auth: string){ + // check if header contains Bearer + if (auth.split(" ")[0] !== 'Bearer') { + throw new HttpException('Invalid token', HttpStatus.FORBIDDEN); + } + // get the token + const token = auth.split(" ")[1]; + try { + // return token. + return await jwt.verify(token, process.env.SECRET); + } catch (err) { + const message = `Token error: ${err.message || err.name}` + throw new HttpException(message, HttpStatus.FORBIDDEN); } - - // check for role - - return true; } -} \ No newline at end of file +} diff --git a/src/shared/roles.decorator.ts b/src/shared/roles.decorator.ts new file mode 100644 index 0000000000000000000000000000000000000000..0d14223c3ce5ee1002cf245fa16fd1b57749bae1 --- /dev/null +++ b/src/shared/roles.decorator.ts @@ -0,0 +1,3 @@ +import { SetMetadata } from '@nestjs/common'; + +export const Roles = (...roles: string[]) => SetMetadata('roles', roles); \ No newline at end of file diff --git a/src/shared/roles.guard.ts b/src/shared/roles.guard.ts new file mode 100644 index 0000000000000000000000000000000000000000..ab7b3f1ecd230239ff6364f28cc3cb4fda8762b8 --- /dev/null +++ b/src/shared/roles.guard.ts @@ -0,0 +1,21 @@ +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { Reflector } from '@nestjs/core'; + +@Injectable() +export class RolesGuard implements CanActivate { + constructor(private readonly reflector: Reflector){} + canActivate( + context: ExecutionContext, + ): boolean | Promise<boolean> | Observable<boolean> { + const roles = this.reflector.get<string[]>('roles', context.getHandler()); + if(!roles){ + return true; + } + + const request = context.switchToHttp().getRequest(); + const user = request.user; + const hasRole = () => user.roles.some((role) => roles.includes(role)); + return user && user.roles && hasRole(); + } +} \ No newline at end of file diff --git a/src/user/user.module.ts b/src/user/user.module.ts index 4776b01af4ef21f320506cd6b96a993d586a19e2..1dcd7673f17bd313c2a74ca911277e224ebc4756 100644 --- a/src/user/user.module.ts +++ b/src/user/user.module.ts @@ -8,6 +8,6 @@ import { PersonEntity} from './user.entity'; @Module({ imports: [TypeOrmModule.forFeature([PersonEntity])], controllers: [UserController], - providers: [UserService] + providers: [UserService], }) export class UserModule {}