diff --git a/package-lock.json b/package-lock.json index 734d53b..a8762f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -864,6 +864,18 @@ "zen-observable-ts": "^0.8.21" } }, + "apollo-server": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/apollo-server/-/apollo-server-2.17.0.tgz", + "integrity": "sha512-vVMu+VqjmsB6yk5iNTb/AXM6EJGd2uwzrcTAbwqvGI7GqCYDRZlGBwrQCjOU/jT/EPWdNRWks/qhJYiQMeVXSg==", + "requires": { + "apollo-server-core": "^2.17.0", + "apollo-server-express": "^2.17.0", + "express": "^4.0.0", + "graphql-subscriptions": "^1.0.0", + "graphql-tools": "^4.0.0" + } + }, "apollo-server-caching": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.5.2.tgz", diff --git a/package.json b/package.json index 5414519..8d895ba 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@types/crc": "^3.4.0" }, "dependencies": { + "apollo-server": "^2.17.0", "apollo-server-express": "^2.17.0", "crc": "^3.8.0", "dotenv": "^8.2.0", diff --git a/src/datasources/userserver/method.ts b/src/datasources/userserver/method.ts index 9247029..70ec609 100644 --- a/src/datasources/userserver/method.ts +++ b/src/datasources/userserver/method.ts @@ -7,5 +7,5 @@ export enum Method { GetRoles = 0x52_4f_4c_45, GetRolePermissions = 0x50_45_52_4d, CreateRole = 0x43_52_4f_4c, - CreatePermissionn = 0x43_50_45_52 + CreatePermissions = 0x43_50_45_52 } diff --git a/src/datasources/userserver/permission.ts b/src/datasources/userserver/permission.ts new file mode 100644 index 0000000..23a3a86 --- /dev/null +++ b/src/datasources/userserver/permission.ts @@ -0,0 +1,17 @@ +/* eslint no-unused-vars: 0 */ +export enum Permission { + ReadBike = 'BIKE_READ', + WriteBike = 'BIKE_WRITE', +} + +// Permissions where the creation will be requested on startup +export const requiredPermissions = [ + { + name: Permission.ReadBike, + description: 'Allows to read of bike information' + }, + { + name: Permission.WriteBike, + description: 'Allows the modification of bike information' + } +] diff --git a/src/datasources/userserver/responses.ts b/src/datasources/userserver/responses.ts index b532789..c1bbd21 100644 --- a/src/datasources/userserver/responses.ts +++ b/src/datasources/userserver/responses.ts @@ -3,3 +3,9 @@ export type ValidateTokenResponse = [boolean, number] // Info in the form of [name, binary, description, request_structure][] export type GetInfoResponse = [string, string, string][] + +// Role array in the form of [id, name, description][] +export type GetRolesResponse = [number, string, string][] + +// Permissions map where each roleId maps to an array of permissions +export type GetRolesPermissionsResponse = {[id: string]: [number, string, string][]} diff --git a/src/datasources/userserver/userserviceAPI.ts b/src/datasources/userserver/userserviceAPI.ts index 9166441..0dcc978 100644 --- a/src/datasources/userserver/userserviceAPI.ts +++ b/src/datasources/userserver/userserviceAPI.ts @@ -3,7 +3,8 @@ import { Socket } from 'net' import { PromiseSocket } from 'promise-socket' import { RPCMessage } from './message' import { Method } from './method' -import { GetInfoResponse, ValidateTokenResponse } from './responses' +import { GetInfoResponse, GetRolesPermissionsResponse, GetRolesResponse, ValidateTokenResponse } from './responses' +import { Permission, requiredPermissions } from './permission' /** * fetches datafrom user server, especially validates user tokens @@ -23,12 +24,22 @@ export class UserServerAPI extends DataSource { } } + /** + * Returns the information about all available rpc methods + */ async getInfo (): Promise { const response = await this.send(new RPCMessage(Method.Info, null)) return response.data } + /** + * Creates required API permissions + */ + async createDefinedPermissions () { + await this.send(new RPCMessage(Method.CreatePermissions, { permissions: requiredPermissions })) + } + /** * validates user token */ @@ -41,6 +52,32 @@ export class UserServerAPI extends DataSource { } } + /** + * Returns a list of roles the user is assigned to + * @param token + */ + async getUserRoles (token: String): Promise { + const response = await this.send(new RPCMessage(Method.GetRoles, { token })) + return response.data + } + + /** + * Returns all permissions of the user + * @param token + */ + async getUserPermissions (token: String): Promise { + const roles = await this.getUserRoles(token) + const roleIds = roles.map(role => role[0]) + const permissionsResponse = await this.send(new RPCMessage(Method.GetRolePermissions, { roles: roleIds })) + const permissions: string[] = [] + + for (const id of roleIds) { + permissions.push(...permissionsResponse.data[id].map(entry => entry[1])) + } + + return permissions + } + /** * Connects to the socket and returns the client */ diff --git a/src/index.ts b/src/index.ts index 965f99b..61a51b2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,6 +16,7 @@ async function authenticate (req: any, res: any, next: any) { const token = req.headers.authorization?.replace('Bearer ', '') if (token) { if (await userAPI.validateToken(token)) { + req.permissions = await userAPI.getUserPermissions(token) next() } else { res.status(401) @@ -40,7 +41,10 @@ const server = new ApolloServer({ dataSources: () => ({ cargoBikeAPI: new CargoBikeAPI(), userAPI - }) + }), + context: (req: any) => { + return req + } }) const app = express() @@ -50,6 +54,7 @@ app.get(/\/graphql?&.*query=/, authenticate) server.applyMiddleware({ app }) console.log(__dirname) -app.listen(4000, () => { +app.listen(4000, async () => { console.log('Server listening on port 4000') + await userAPI.createDefinedPermissions() }) diff --git a/src/resolvers/cargobikeResolver.ts b/src/resolvers/cargobikeResolver.ts index a7c89db..07e6066 100644 --- a/src/resolvers/cargobikeResolver.ts +++ b/src/resolvers/cargobikeResolver.ts @@ -1,10 +1,23 @@ +import { Permission } from '../datasources/userserver/permission' +import { GraphQLError } from 'graphql' + export default { Query: { - CargobikeById: (_: any, { id, token }:{id: any, token: string}, { dataSources }:{dataSources: any}) => - dataSources.cargoBikeAPI.findCargoBikeById({ id, token }) + CargobikeById: (_: any, { id, token }:{id: any, token: string}, { dataSources, req }:{dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.ReadBike)) { + return dataSources.cargoBikeAPI.findCargoBikeById({ id, token }) + } else { + throw new GraphQLError('Insufficient Permissions') + } + } }, Mutation: { - addBike: (_:any, { id, token, name }:{id: any, token: string, name:string}, { dataSources }:{dataSources: any }) => - dataSources.cargoBikeAPI.updateBike({ id, token, name }) + addBike: (_: any, { id, token, name }:{id: any, token: string, name:string}, { dataSources, req }:{dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteBike)) { + return dataSources.cargoBikeAPI.updateBike({ id, token, name }) + } else { + throw new GraphQLError('Insufficient Permissions') + } + } } }