Skip to content

Commit

Permalink
fix: create application request and role guard introduction (#338)
Browse files Browse the repository at this point in the history
  • Loading branch information
kshitij-k-osmosys authored Oct 10, 2024
1 parent af90c36 commit b077b5f
Show file tree
Hide file tree
Showing 10 changed files with 108 additions and 117 deletions.
6 changes: 3 additions & 3 deletions apps/api/OsmoX-API.postman_collection.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"info": {
"_postman_id": "b97d878b-aa80-4b2f-a9f0-a0640b2bc6e2",
"_postman_id": "36ff2947-7450-4393-b3cd-39e82ee073e6",
"name": "OsmoX-API",
"description": "List of all APIs used in OsmoX",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
Expand Down Expand Up @@ -781,7 +781,7 @@
"body": {
"mode": "graphql",
"graphql": {
"query": "mutation CreateApplication {\r\n application(createApplicationInput: {\r\n name: \"<newApplicationName>\",\r\n userId: 2,\r\n }) {\r\n applicationId\r\n name\r\n userId\r\n createdOn\r\n updatedOn\r\n status\r\n }\r\n}",
"query": "mutation CreateApplication {\r\n application(createApplicationInput: {\r\n name: \"<newApplicationName>\",\r\n }) {\r\n applicationId\r\n name\r\n userId\r\n createdOn\r\n updatedOn\r\n status\r\n }\r\n}",
"variables": ""
}
},
Expand Down Expand Up @@ -883,7 +883,7 @@
"body": {
"mode": "graphql",
"graphql": {
"query": "mutation CreateProvider {\n provider(createProviderInput: {\n applicationId: 2,\n channelType: 2,\n configuration: {},\n isEnabled: 1,\n name: \"<New Provider Name>\",\n userId: 1,\n }) {\n applicationId\n channelType\n configuration\n isEnabled\n name\n userId\n createdOn\n updatedOn\n status\n }\n}",
"query": "mutation CreateProvider {\n provider(createProviderInput: {\n applicationId: 2,\n channelType: 2,\n configuration: {},\n isEnabled: 1,\n name: \"<New Provider Name>\",\n userId: 1,\n }) {\n providerId\n applicationId\n channelType\n configuration\n isEnabled\n name\n userId\n createdOn\n updatedOn\n status\n }\n}",
"variables": ""
}
},
Expand Down
32 changes: 16 additions & 16 deletions apps/api/OsmoX.postman_collection.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"info": {
"_postman_id": "e9aeb9a0-761f-4b10-86a7-dd4bcaebca94",
"_postman_id": "336711a4-7ddf-4f32-b136-4aa09b0e284b",
"name": "OsmoX",
"description": "OsmoX API helps creating new notifications, fetching existing notifications as well as perform authorization related tasks.",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
Expand Down Expand Up @@ -3276,11 +3276,11 @@
" pm.expect(jsonData).to.have.property(\"data\");\r",
" pm.expect(jsonData).to.have.property(\"errors\").to.be.an(\"array\");\r",
"});\r",
"pm.test(\"message: Access Denied. Not an ADMIN.\", function () {\r",
"pm.test(\"message: Forbidden resource\", function () {\r",
" var jsonData = pm.response.json();\r",
" pm.expect(jsonData).to.have.property(\"data\");\r",
" pm.expect(jsonData).to.have.property(\"errors\").to.be.an(\"array\");\r",
" pm.expect(jsonData.errors[0]).to.have.property(\"message\", \"Access Denied. Not an ADMIN.\");\r",
" pm.expect(jsonData.errors[0]).to.have.property(\"message\", \"Forbidden resource\");\r",
"});"
],
"type": "text/javascript",
Expand Down Expand Up @@ -3430,7 +3430,7 @@
"body": {
"mode": "graphql",
"graphql": {
"query": "mutation CreateApplication {\r\n application(createApplicationInput: {\r\n name: \"sampleTestXApp\",\r\n userId: 2,\r\n }) {\r\n applicationId\r\n name\r\n userId\r\n createdOn\r\n updatedOn\r\n status\r\n }\r\n}",
"query": "mutation CreateApplication {\r\n application(createApplicationInput: {\r\n name: \"sampleTestXApp\",\r\n }) {\r\n applicationId\r\n name\r\n userId\r\n createdOn\r\n updatedOn\r\n status\r\n }\r\n}",
"variables": ""
}
},
Expand Down Expand Up @@ -3464,11 +3464,11 @@
" pm.expect(jsonData).to.have.property(\"data\");\r",
" pm.expect(jsonData).to.have.property(\"errors\").to.be.an(\"array\");\r",
"});\r",
"pm.test(\"message: Access Denied. Not an ADMIN.\", function () {\r",
"pm.test(\"message: Forbidden resource\", function () {\r",
" var jsonData = pm.response.json();\r",
" pm.expect(jsonData).to.have.property(\"data\");\r",
" pm.expect(jsonData).to.have.property(\"errors\").to.be.an(\"array\");\r",
" pm.expect(jsonData.errors[0]).to.have.property(\"message\", \"Access Denied. Not an ADMIN.\");\r",
" pm.expect(jsonData.errors[0]).to.have.property(\"message\", \"Forbidden resource\");\r",
"});"
],
"type": "text/javascript",
Expand All @@ -3493,7 +3493,7 @@
"body": {
"mode": "graphql",
"graphql": {
"query": "mutation CreateApplication {\r\n application(createApplicationInput: {\r\n name: \"sampleTestXApp\",\r\n userId: 2,\r\n }) {\r\n applicationId\r\n name\r\n userId\r\n createdOn\r\n updatedOn\r\n status\r\n }\r\n}",
"query": "mutation CreateApplication {\r\n application(createApplicationInput: {\r\n name: \"sampleTestXApp\",\r\n }) {\r\n applicationId\r\n name\r\n userId\r\n createdOn\r\n updatedOn\r\n status\r\n }\r\n}",
"variables": ""
}
},
Expand Down Expand Up @@ -3551,7 +3551,7 @@
"body": {
"mode": "graphql",
"graphql": {
"query": "mutation CreateApplication {\r\n application(createApplicationInput: {\r\n unknownValue: \"Some unknown parameter\"\r\n name: \"sampleFoundationXApp\",\r\n userId: 2,\r\n }) {\r\n applicationId\r\n name\r\n userId\r\n createdOn\r\n updatedOn\r\n status\r\n }\r\n}",
"query": "mutation CreateApplication {\r\n application(createApplicationInput: {\r\n unknownValue: \"Some unknown parameter\"\r\n name: \"sampleFoundationXApp\",\r\n }) {\r\n applicationId\r\n name\r\n userId\r\n createdOn\r\n updatedOn\r\n status\r\n }\r\n}",
"variables": ""
}
},
Expand Down Expand Up @@ -3656,11 +3656,11 @@
" pm.expect(jsonData).to.have.property(\"data\");\r",
" pm.expect(jsonData).to.have.property(\"errors\").to.be.an(\"array\");\r",
"});\r",
"pm.test(\"message: Access Denied. Not an ADMIN.\", function () {\r",
"pm.test(\"message: Forbidden resource\", function () {\r",
" var jsonData = pm.response.json();\r",
" pm.expect(jsonData).to.have.property(\"data\");\r",
" pm.expect(jsonData).to.have.property(\"errors\").to.be.an(\"array\");\r",
" pm.expect(jsonData.errors[0]).to.have.property(\"message\", \"Access Denied. Not an ADMIN.\");\r",
" pm.expect(jsonData.errors[0]).to.have.property(\"message\", \"Forbidden resource\");\r",
"});"
],
"type": "text/javascript",
Expand Down Expand Up @@ -3810,7 +3810,7 @@
"body": {
"mode": "graphql",
"graphql": {
"query": "mutation CreateProvider {\n provider(createProviderInput: {\n applicationId: 2,\n channelType: 2,\n configuration: {},\n isEnabled: 1,\n name: \"Mailgun PineStem\",\n userId: 1,\n }) {\n applicationId\n channelType\n configuration\n isEnabled\n name\n userId\n createdOn\n updatedOn\n status\n }\n}",
"query": "mutation CreateProvider {\n provider(createProviderInput: {\n applicationId: 2,\n channelType: 2,\n configuration: {},\n isEnabled: 1,\n name: \"Mailgun PineStem\",\n userId: 1,\n }) {\n providerId\n applicationId\n channelType\n configuration\n isEnabled\n name\n userId\n createdOn\n updatedOn\n status\n }\n}",
"variables": ""
}
},
Expand Down Expand Up @@ -3873,7 +3873,7 @@
"body": {
"mode": "graphql",
"graphql": {
"query": "mutation CreateProvider {\n provider(createProviderInput: {\n # Assuming applicationId 500 is invalid\n applicationId: 500,\n channelType: 2,\n configuration: {},\n isEnabled: 1,\n name: \"Mailgun PineStem\",\n userId: 1,\n }) {\n applicationId\n channelType\n configuration\n isEnabled\n name\n userId\n createdOn\n updatedOn\n status\n }\n}",
"query": "mutation CreateProvider {\n provider(createProviderInput: {\n # Assuming applicationId 500 is invalid\n applicationId: 500,\n channelType: 2,\n configuration: {},\n isEnabled: 1,\n name: \"Mailgun PineStem\",\n userId: 1,\n }) {\n providerId\n applicationId\n channelType\n configuration\n isEnabled\n name\n userId\n createdOn\n updatedOn\n status\n }\n}",
"variables": ""
}
},
Expand Down Expand Up @@ -3907,11 +3907,11 @@
" pm.expect(jsonData).to.have.property(\"data\");\r",
" pm.expect(jsonData).to.have.property(\"errors\").to.be.an(\"array\");\r",
"});\r",
"pm.test(\"message: Access Denied. Not an ADMIN.\", function () {\r",
"pm.test(\"message: Forbidden resource\", function () {\r",
" var jsonData = pm.response.json();\r",
" pm.expect(jsonData).to.have.property(\"data\");\r",
" pm.expect(jsonData).to.have.property(\"errors\").to.be.an(\"array\");\r",
" pm.expect(jsonData.errors[0]).to.have.property(\"message\", \"Access Denied. Not an ADMIN.\");\r",
" pm.expect(jsonData.errors[0]).to.have.property(\"message\", \"Forbidden resource\");\r",
"});"
],
"type": "text/javascript",
Expand All @@ -3936,7 +3936,7 @@
"body": {
"mode": "graphql",
"graphql": {
"query": "mutation CreateProvider {\n provider(createProviderInput: {\n applicationId: 5,\n channelType: 2,\n configuration: {},\n isEnabled: 1,\n name: \"Mailgun PineStem\",\n userId: 1,\n }) {\n applicationId\n channelType\n configuration\n isEnabled\n name\n userId\n createdOn\n updatedOn\n status\n }\n}",
"query": "mutation CreateProvider {\n provider(createProviderInput: {\n applicationId: 5,\n channelType: 2,\n configuration: {},\n isEnabled: 1,\n name: \"Mailgun PineStem\",\n userId: 1,\n }) {\n providerId\n applicationId\n channelType\n configuration\n isEnabled\n name\n userId\n createdOn\n updatedOn\n status\n }\n}",
"variables": ""
}
},
Expand Down Expand Up @@ -3994,7 +3994,7 @@
"body": {
"mode": "graphql",
"graphql": {
"query": "mutation CreateProvider {\n provider(createProviderInput: {\n applicationId: 5,\n unknownValue: \"Some unknown parameter\",\n channelType: 2,\n configuration: {},\n isEnabled: 1,\n name: \"Mailgun PineStem\",\n userId: 1,\n }) {\n applicationId\n channelType\n configuration\n isEnabled\n name\n userId\n createdOn\n updatedOn\n status\n }\n}",
"query": "mutation CreateProvider {\n provider(createProviderInput: {\n applicationId: 5,\n unknownValue: \"Some unknown parameter\",\n channelType: 2,\n configuration: {},\n isEnabled: 1,\n name: \"Mailgun PineStem\",\n userId: 1,\n }) {\n providerId\n applicationId\n channelType\n configuration\n isEnabled\n name\n userId\n createdOn\n updatedOn\n status\n }\n}",
"variables": ""
}
},
Expand Down
3 changes: 1 addition & 2 deletions apps/api/docs/api-documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,6 @@ Allows the user with `Admin` role to create a new application.
mutation CreateApplication {
application(createApplicationInput: {
name: "newSampleApp",
userId: 2,
}) {
applicationId
name
Expand Down Expand Up @@ -348,7 +347,7 @@ curl --location 'http://localhost:3000/graphql' \

Allows the user to fetch all applications based on the passed query parameters. Requires passing bearer token for authorization.

Note: The API will return a successful response when the `server-api-key` passed uses `application_id` that is associated with an `Admin`.
Note: The API will return a successful response when the Bearer `authorization-token` passed is associated with an `Admin`.

The different options that can be used while fetching notifications are as follows:

Expand Down
8 changes: 8 additions & 0 deletions apps/api/src/common/decorators/roles.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { SetMetadata } from '@nestjs/common';
import { UserRoles } from '../constants/database';

// Type definition for allowed values (only values from UserRoles)
type UserRoleValues = (typeof UserRoles)[keyof typeof UserRoles];

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export const Roles = (...roles: UserRoleValues[]) => SetMetadata('roles', roles);
49 changes: 49 additions & 0 deletions apps/api/src/common/guards/role.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { GqlExecutionContext } from '@nestjs/graphql';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { UserRoles } from 'src/common/constants/database';

@Injectable()
export class RolesGuard implements CanActivate {
constructor(
private reflector: Reflector,
private jwtService: JwtService,
private configService: ConfigService,
) {}

canActivate(context: ExecutionContext): boolean {
// Fetch required roles from the metadata
const requiredRoles = this.reflector.getAllAndOverride<(keyof typeof UserRoles)[]>('roles', [
context.getHandler(),
context.getClass(),
]);

if (!requiredRoles || requiredRoles.length === 0) {
return true; // Allow access if no roles are specified
}

const ctx = GqlExecutionContext.create(context);
const req = ctx.getContext().req;
const authorizationHeader = req.headers.authorization;

if (!authorizationHeader || !authorizationHeader.startsWith('Bearer ')) {
return false; // No or invalid authorization token
}

const token = authorizationHeader.split(' ')[1];
const secret = this.configService.getOrThrow('JWT_SECRET');

try {
// Decode the JWT token to get the user information
const decodedToken = this.jwtService.verify(token, { secret });
const userRoleId = decodedToken.role;

// Check if the user's role matches any of the required roles
return requiredRoles.includes(userRoleId);
} catch (error) {
return false; // Invalid token or other error
}
}
}
19 changes: 8 additions & 11 deletions apps/api/src/modules/applications/applications.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ import { Application } from './entities/application.entity';
import { QueryOptionsDto } from 'src/common/graphql/dtos/query-options.dto';
import { ApplicationResponse } from './dto/application-response.dto';
import { GqlAuthGuard } from 'src/common/guards/api-key/gql-auth.guard';
import { Roles } from 'src/common/decorators/roles.decorator';
import { RolesGuard } from 'src/common/guards/role.guard';
import { UserRoles } from 'src/common/constants/database';

@Resolver(() => Application)
@UseGuards(GqlAuthGuard)
@Roles(UserRoles.ADMIN)
@UseGuards(GqlAuthGuard, RolesGuard)
export class ApplicationsResolver {
constructor(private readonly applicationsService: ApplicationsService) {}

Expand All @@ -17,22 +21,15 @@ export class ApplicationsResolver {
@Context() context,
@Args('createApplicationInput') createApplicationInput: CreateApplicationInput,
): Promise<Application> {
const request: Request = context.req;
const authorizationHeader = request.headers['authorization'];
return await this.applicationsService.createApplication(
createApplicationInput,
authorizationHeader,
);
const requestUserId: number = context.req.userId;
return await this.applicationsService.createApplication(createApplicationInput, requestUserId);
}

@Query(() => ApplicationResponse, { name: 'applications' })
async findAll(
@Context() context,
@Args('options', { type: () => QueryOptionsDto, nullable: true, defaultValue: {} })
options: QueryOptionsDto,
): Promise<ApplicationResponse> {
const request: Request = context.req;
const authorizationHeader = request.headers['authorization'];
return this.applicationsService.getAllApplications(options, authorizationHeader);
return this.applicationsService.getAllApplications(options);
}
}
Loading

0 comments on commit b077b5f

Please sign in to comment.