From d475851bb32e14fd2e1bbed7d5dba8dbe8acc107 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 12 Sep 2020 17:51:08 +0200 Subject: [PATCH 1/7] Add RPC Client implementation - Add Message class to serialize and deserialize rpc messages - Add Method enum for all rpc methods represented as 32 bit unsigned integers - Add responses file to map rpc responses to types - Add implementation of userserver api with PromiseSocket Signed-off-by: trivernis --- .gitignore | 4 +- package-lock.json | 60 ++++++++++++++++++++ package.json | 5 ++ src/datasources/userserver/message.ts | 43 ++++++++++++++ src/datasources/userserver/method.ts | 11 ++++ src/datasources/userserver/responses.ts | 5 ++ src/datasources/userserver/userserviceAPI.ts | 55 +++++++++++++++++- src/index.ts | 6 +- 8 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 src/datasources/userserver/message.ts create mode 100644 src/datasources/userserver/method.ts create mode 100644 src/datasources/userserver/responses.ts diff --git a/.gitignore b/.gitignore index 76add87..02551b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules -dist \ No newline at end of file +dist +.env +.idea diff --git a/package-lock.json b/package-lock.json index d5e0b5a..86b5a65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -306,6 +306,14 @@ "@types/express": "*" } }, + "@types/crc": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@types/crc/-/crc-3.4.0.tgz", + "integrity": "sha1-I2a+tDmc1zSzPkLHrICVduYX1Io=", + "requires": { + "@types/node": "*" + } + }, "@types/express": { "version": "4.17.8", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.8.tgz", @@ -2071,6 +2079,14 @@ "vary": "^1" } }, + "crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "requires": { + "buffer": "^5.1.0" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -4958,6 +4974,11 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, + "messagepack": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/messagepack/-/messagepack-1.1.12.tgz", + "integrity": "sha512-pNB6K4q4VMLRXdvlGZkTtQhmKFntvLisnOQnL0VhKpZooL8B8Wsv5TXuidIJil0bCH6V172p3+Onfyow0usPYQ==" + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -5893,6 +5914,45 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "promise-duplex": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/promise-duplex/-/promise-duplex-6.0.0.tgz", + "integrity": "sha512-ZL7rquzjTFzInDBeWYcsT+qddolNvzigahk6MI6qLSbQvlyRRCJkU3JztgaVunzvkH28smRa2Qu/cY9RXtSkgA==", + "requires": { + "core-js": "^3.6.5", + "promise-readable": "^6.0.0", + "promise-writable": "^6.0.0" + } + }, + "promise-readable": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/promise-readable/-/promise-readable-6.0.0.tgz", + "integrity": "sha512-5NxtmUswijvX5cAM0zPSy6yiCXH/eKBpiiBq6JfAUrmngMquMbzcBhF2qA+ocs4rYYKdvAfv3cOvZxADLtL1CA==", + "requires": { + "core-js": "^3.6.5" + } + }, + "promise-socket": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/promise-socket/-/promise-socket-7.0.0.tgz", + "integrity": "sha512-Oic9BrxmcHOPEnzKp2Js+ehFyvsbd0WxsE5khweCTHuRvdzbXjHUZmSDT6F9TW8SIkAJ0lCzoHjMYnb0WQJPiw==", + "requires": { + "promise-duplex": "^6.0.0", + "tslib": "^2.0.1" + }, + "dependencies": { + "tslib": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==" + } + } + }, + "promise-writable": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/promise-writable/-/promise-writable-6.0.0.tgz", + "integrity": "sha512-b81zre/itgJFS7dwWzIdKNVVqvLiUxYRS/wolUB0H1YY/tAaS146XGKa4Q/5wCbsnXLyn0MCeV6f8HHe4iUHLg==" + }, "proxy-addr": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", diff --git a/package.json b/package.json index cc9fd74..caf276b 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,14 @@ "typescript": "^4.0.2" }, "dependencies": { + "@types/crc": "^3.4.0", "apollo-server": "^2.17.0", + "crc": "^3.8.0", + "dotenv": "^8.2.0", "graphql": "^15.3.0", + "messagepack": "^1.1.12", "pg": "^8.3.3", + "promise-socket": "^7.0.0", "reflect-metadata": "^0.1.13", "typeorm": "^0.2.26" } diff --git a/src/datasources/userserver/message.ts b/src/datasources/userserver/message.ts new file mode 100644 index 0000000..70df300 --- /dev/null +++ b/src/datasources/userserver/message.ts @@ -0,0 +1,43 @@ +import { encode, decode } from 'messagepack' +import { Method } from './method' +import { crc32 } from 'crc' + +export class RPCMessage { + private readonly method: Method + readonly data: T + + constructor (method: Method, data: any) { + this.method = method + this.data = data + } + + static fromBuffer (raw: Buffer): RPCMessage { + const length = raw.readUInt32BE() + if (raw.length !== length) { + throw new Error('Invalid Buffer length') + } + const crcNum = raw.readUInt32BE(length - 4) + + if (crc32(raw.slice(0, length - 4)) !== crcNum) { + throw new Error('Validation check failed') + } + const method = raw.readUInt32BE(4) + const msgData = decode(raw.slice(8, length)) + + return new RPCMessage(method, msgData) + } + + public toBuffer (): Buffer { + const msgData = encode(this.data) + const length = msgData.length + 12 + const buffer = Buffer.alloc(length - 4) + buffer.writeUInt32BE(length) + buffer.writeUInt32BE(this.method, 4) + buffer.fill(msgData, 8) + const crcNum = crc32(buffer) + const resultBuffer = Buffer.alloc(length, buffer) + resultBuffer.writeUInt32BE(crcNum, length - 4) + + return resultBuffer + } +} diff --git a/src/datasources/userserver/method.ts b/src/datasources/userserver/method.ts new file mode 100644 index 0000000..9247029 --- /dev/null +++ b/src/datasources/userserver/method.ts @@ -0,0 +1,11 @@ +/* eslint no-unused-vars: 0 */ +export enum Method { + Null = 0x00, + Error = 0x0F_0F_0F_0F, + Info = 0x49_4e_46_4f, + ValidateToken = 0x56_41_4c_49, + GetRoles = 0x52_4f_4c_45, + GetRolePermissions = 0x50_45_52_4d, + CreateRole = 0x43_52_4f_4c, + CreatePermissionn = 0x43_50_45_52 +} diff --git a/src/datasources/userserver/responses.ts b/src/datasources/userserver/responses.ts new file mode 100644 index 0000000..b532789 --- /dev/null +++ b/src/datasources/userserver/responses.ts @@ -0,0 +1,5 @@ +// Response in the form of [valid, ttl] +export type ValidateTokenResponse = [boolean, number] + +// Info in the form of [name, binary, description, request_structure][] +export type GetInfoResponse = [string, string, string][] diff --git a/src/datasources/userserver/userserviceAPI.ts b/src/datasources/userserver/userserviceAPI.ts index 2dede6f..9166441 100644 --- a/src/datasources/userserver/userserviceAPI.ts +++ b/src/datasources/userserver/userserviceAPI.ts @@ -1,13 +1,64 @@ import { DataSource } from 'apollo-datasource' +import { Socket } from 'net' +import { PromiseSocket } from 'promise-socket' +import { RPCMessage } from './message' +import { Method } from './method' +import { GetInfoResponse, ValidateTokenResponse } from './responses' /** * fetches datafrom user server, especially validates user tokens */ export class UserServerAPI extends DataSource { + port: number + host: string + + constructor (address: string) { + super() + const parts = address.split(':') + this.host = parts[0] + if (parts.length === 2) { + this.port = Number(parts[1]) + } else { + this.port = 5000 + } + } + + async getInfo (): Promise { + const response = await this.send(new RPCMessage(Method.Info, null)) + + return response.data + } + /** * validates user token */ - async validateToken (token:string) { - return true + async validateToken (token:string): Promise { + const response = await this.send(new RPCMessage(Method.ValidateToken, { token })) + if (response) { + return response.data[0] + } else { + return false + } + } + + /** + * Connects to the socket and returns the client + */ + async getSocket (): Promise> { + const socket = new Socket() + const promiseSocket = new PromiseSocket(socket) + await promiseSocket.connect(this.port, this.host) + + return promiseSocket + } + + async send (message: RPCMessage): Promise> { + const socket = await this.getSocket() + await socket.writeAll(message.toBuffer()) + const response = await socket.readAll() as Buffer + + if (response?.length > 0) { + return RPCMessage.fromBuffer(response) + } } } diff --git a/src/index.ts b/src/index.ts index 61fc39d..e427d51 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,9 @@ import { CargoBikeAPI } from './datasources/db/cargobikeAPI' import typeDefs from './schema/type-defs' import 'reflect-metadata' import { createConnection } from 'typeorm' +import { UserServerAPI } from './datasources/userserver/userserviceAPI' + +require('dotenv').config() createConnection().then(async () => { console.log('connected to db') @@ -13,7 +16,8 @@ const server = new ApolloServer({ resolvers: [bikeresolver], typeDefs, dataSources: () => ({ - cargoBikeAPI: new CargoBikeAPI() + cargoBikeAPI: new CargoBikeAPI(), + userAPI: new UserServerAPI(process.env.RPC_HOST) }) }) From b61dfa88107cda6afd6e08ea1e0488adce33fb65 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 12 Sep 2020 18:45:00 +0200 Subject: [PATCH 2/7] Switch to apollo-server-express to enable middleware for authentication Signed-off-by: trivernis --- package-lock.json | 13 +---------- package.json | 7 +++--- src/datasources/db/cargobikeAPI.ts | 2 +- src/index.ts | 37 ++++++++++++++++++++++++++---- 4 files changed, 39 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 86b5a65..734d53b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -310,6 +310,7 @@ "version": "3.4.0", "resolved": "https://registry.npmjs.org/@types/crc/-/crc-3.4.0.tgz", "integrity": "sha1-I2a+tDmc1zSzPkLHrICVduYX1Io=", + "dev": true, "requires": { "@types/node": "*" } @@ -863,18 +864,6 @@ "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 caf276b..5414519 100644 --- a/package.json +++ b/package.json @@ -27,13 +27,14 @@ "gulp-eslint": "^6.0.0", "gulp-nodemon": "^2.5.0", "gulp-typescript": "^6.0.0-alpha.1", - "typescript": "^4.0.2" + "typescript": "^4.0.2", + "@types/crc": "^3.4.0" }, "dependencies": { - "@types/crc": "^3.4.0", - "apollo-server": "^2.17.0", + "apollo-server-express": "^2.17.0", "crc": "^3.8.0", "dotenv": "^8.2.0", + "express": "^4.17.1", "graphql": "^15.3.0", "messagepack": "^1.1.12", "pg": "^8.3.3", diff --git a/src/datasources/db/cargobikeAPI.ts b/src/datasources/db/cargobikeAPI.ts index 1a3709d..026d7d4 100644 --- a/src/datasources/db/cargobikeAPI.ts +++ b/src/datasources/db/cargobikeAPI.ts @@ -26,7 +26,7 @@ export class CargoBikeAPI extends DataSource { bike.id = id bike.description = token bike.name = name - this.connection.manager.save(bike) + await this.connection.manager.save(bike) return { success: true, message: token, diff --git a/src/index.ts b/src/index.ts index e427d51..965f99b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,26 +1,55 @@ -import { ApolloServer } from 'apollo-server' +import { ApolloServer } from 'apollo-server-express' import bikeresolver from './resolvers/cargobikeResolver' import { CargoBikeAPI } from './datasources/db/cargobikeAPI' import typeDefs from './schema/type-defs' import 'reflect-metadata' import { createConnection } from 'typeorm' import { UserServerAPI } from './datasources/userserver/userserviceAPI' +import express from 'express' require('dotenv').config() +async function authenticate (req: any, res: any, next: any) { + if (process.env.NODE_ENV === 'develop') { + next() + } else { + const token = req.headers.authorization?.replace('Bearer ', '') + if (token) { + if (await userAPI.validateToken(token)) { + next() + } else { + res.status(401) + res.send('Unauthorized') + } + } else { + res.status(401) + res.send('Unauthorized') + } + } +} + createConnection().then(async () => { console.log('connected to db') }).catch(error => console.log(error)) +const userAPI = new UserServerAPI(process.env.RPC_HOST) + const server = new ApolloServer({ resolvers: [bikeresolver], typeDefs, dataSources: () => ({ cargoBikeAPI: new CargoBikeAPI(), - userAPI: new UserServerAPI(process.env.RPC_HOST) + userAPI }) }) +const app = express() + +app.post('/graphql', authenticate) +app.get(/\/graphql?&.*query=/, authenticate) +server.applyMiddleware({ app }) + console.log(__dirname) -server.listen() - .then(({ url }) => console.log(`Server ready at ${url} `)) +app.listen(4000, () => { + console.log('Server listening on port 4000') +}) From 96916b0fd13aba4ee3d0dc14a39f3b7f1cde6a87 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 12 Sep 2020 19:48:15 +0200 Subject: [PATCH 3/7] Add permission management Signed-off-by: trivernis --- package-lock.json | 12 ++++++ package.json | 1 + src/datasources/userserver/method.ts | 2 +- src/datasources/userserver/permission.ts | 17 +++++++++ src/datasources/userserver/responses.ts | 6 +++ src/datasources/userserver/userserviceAPI.ts | 39 +++++++++++++++++++- src/index.ts | 9 ++++- src/resolvers/cargobikeResolver.ts | 21 +++++++++-- 8 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 src/datasources/userserver/permission.ts 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') + } + } } } From fcbd65d53bf0f12b40e90773fcbb6105d7c109f0 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 13 Sep 2020 20:33:51 +0200 Subject: [PATCH 4/7] Add createRole method to userServiceAPI Signed-off-by: trivernis --- src/datasources/userserver/responses.ts | 2 ++ src/datasources/userserver/userserviceAPI.ts | 28 +++++++++++++++++++- src/index.ts | 6 +++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/datasources/userserver/responses.ts b/src/datasources/userserver/responses.ts index c1bbd21..9da667c 100644 --- a/src/datasources/userserver/responses.ts +++ b/src/datasources/userserver/responses.ts @@ -9,3 +9,5 @@ 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][]} + +export type CreateRoleResponse = [number, string, string] diff --git a/src/datasources/userserver/userserviceAPI.ts b/src/datasources/userserver/userserviceAPI.ts index 0dcc978..6c16ae4 100644 --- a/src/datasources/userserver/userserviceAPI.ts +++ b/src/datasources/userserver/userserviceAPI.ts @@ -3,7 +3,13 @@ import { Socket } from 'net' import { PromiseSocket } from 'promise-socket' import { RPCMessage } from './message' import { Method } from './method' -import { GetInfoResponse, GetRolesPermissionsResponse, GetRolesResponse, ValidateTokenResponse } from './responses' +import { + CreateRoleResponse, + GetInfoResponse, + GetRolesPermissionsResponse, + GetRolesResponse, + ValidateTokenResponse +} from './responses' import { Permission, requiredPermissions } from './permission' /** @@ -40,6 +46,22 @@ export class UserServerAPI extends DataSource { await this.send(new RPCMessage(Method.CreatePermissions, { permissions: requiredPermissions })) } + /** + * Creates a new role with the given parameters + * @param name - the name of the role + * @param description - a description of the role + * @param permissionIDs - an array of IDs the role is created with + */ + async createRole (name: string, description: string, permissionIDs: number[]): Promise { + const response = await this.send(new RPCMessage(Method.CreateRole, { + name, + description, + permissions: permissionIDs + })) + + return response.data + } + /** * validates user token */ @@ -89,6 +111,10 @@ export class UserServerAPI extends DataSource { return promiseSocket } + /** + * Sends a message and reads and parses the response of it + * @param message + */ async send (message: RPCMessage): Promise> { const socket = await this.getSocket() await socket.writeAll(message.toBuffer()) diff --git a/src/index.ts b/src/index.ts index 61a51b2..5ffdc5c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,12 @@ import express from 'express' require('dotenv').config() +/** + * Function that is called to authenticate a user by using the user rpc server + * @param req + * @param res + * @param next + */ async function authenticate (req: any, res: any, next: any) { if (process.env.NODE_ENV === 'develop') { next() From 910e120baf2b42769098d7f285a5ad0ca70527c0 Mon Sep 17 00:00:00 2001 From: trivernis Date: Mon, 14 Sep 2020 12:42:28 +0200 Subject: [PATCH 5/7] Bypass role handling in development mode Signed-off-by: trivernis --- src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/index.ts b/src/index.ts index 5ffdc5c..8eb5a8f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ import 'reflect-metadata' import { createConnection } from 'typeorm' import { UserServerAPI } from './datasources/userserver/userserviceAPI' import express from 'express' +import { requiredPermissions } from './datasources/userserver/permission' require('dotenv').config() @@ -17,6 +18,7 @@ require('dotenv').config() */ async function authenticate (req: any, res: any, next: any) { if (process.env.NODE_ENV === 'develop') { + req.permissions = requiredPermissions.map((e) => e.name) next() } else { const token = req.headers.authorization?.replace('Bearer ', '') From 49e29259bd1f5cab36adb3edd7c1602c3d199616 Mon Sep 17 00:00:00 2001 From: trivernis Date: Mon, 14 Sep 2020 12:56:12 +0200 Subject: [PATCH 6/7] Change semi rule to enforce semicolons Signed-off-by: trivernis --- .eslintrc.json | 7 +- src/datasources/db/cargobikeAPI.ts | 24 +++---- src/datasources/userserver/message.ts | 44 ++++++------ src/datasources/userserver/permission.ts | 2 +- src/datasources/userserver/userserviceAPI.ts | 72 ++++++++++---------- src/index.ts | 64 ++++++++--------- src/model/CargoBike.ts | 2 +- src/resolvers/cargobikeResolver.ts | 14 ++-- src/schema/type-defs.ts | 4 +- 9 files changed, 117 insertions(+), 116 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 0517186..206fce3 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -24,8 +24,9 @@ "max-classes-per-file": 0, "no-var-requires": 0, "array-type": 0, - "semi": 1, - "semi-style": ["error", "last"] + "semi": ["error", "always"], + "semi-style": ["error", "last"], + "no-extra-semi": "off" } - + } diff --git a/src/datasources/db/cargobikeAPI.ts b/src/datasources/db/cargobikeAPI.ts index 026d7d4..f23571c 100644 --- a/src/datasources/db/cargobikeAPI.ts +++ b/src/datasources/db/cargobikeAPI.ts @@ -1,14 +1,14 @@ -import { DataSource } from 'apollo-datasource' -import { getConnection, Connection } from 'typeorm' -import { CargoBike } from '../../model/CargoBike' +import { DataSource } from 'apollo-datasource'; +import { getConnection, Connection } from 'typeorm'; +import { CargoBike } from '../../model/CargoBike'; /** * extended datasource to feed resolvers with data about cargoBikes */ export class CargoBikeAPI extends DataSource { connection : Connection constructor () { - super() - this.connection = getConnection() + super(); + this.connection = getConnection(); } /** @@ -18,15 +18,15 @@ export class CargoBikeAPI extends DataSource { return { id, name: token - } + }; } async updateBike ({ id, token, name }:{id:any, token: string, name: string }) { - const bike = new CargoBike() - bike.id = id - bike.description = token - bike.name = name - await this.connection.manager.save(bike) + const bike = new CargoBike(); + bike.id = id; + bike.description = token; + bike.name = name; + await this.connection.manager.save(bike); return { success: true, message: token, @@ -34,6 +34,6 @@ export class CargoBikeAPI extends DataSource { id, name } - } + }; } } diff --git a/src/datasources/userserver/message.ts b/src/datasources/userserver/message.ts index 70df300..547d3f9 100644 --- a/src/datasources/userserver/message.ts +++ b/src/datasources/userserver/message.ts @@ -1,43 +1,43 @@ -import { encode, decode } from 'messagepack' -import { Method } from './method' -import { crc32 } from 'crc' +import { encode, decode } from 'messagepack'; +import { Method } from './method'; +import { crc32 } from 'crc'; export class RPCMessage { private readonly method: Method readonly data: T constructor (method: Method, data: any) { - this.method = method - this.data = data + this.method = method; + this.data = data; } static fromBuffer (raw: Buffer): RPCMessage { - const length = raw.readUInt32BE() + const length = raw.readUInt32BE(); if (raw.length !== length) { - throw new Error('Invalid Buffer length') + throw new Error('Invalid Buffer length'); } - const crcNum = raw.readUInt32BE(length - 4) + const crcNum = raw.readUInt32BE(length - 4); if (crc32(raw.slice(0, length - 4)) !== crcNum) { - throw new Error('Validation check failed') + throw new Error('Validation check failed'); } - const method = raw.readUInt32BE(4) - const msgData = decode(raw.slice(8, length)) + const method = raw.readUInt32BE(4); + const msgData = decode(raw.slice(8, length)); - return new RPCMessage(method, msgData) + return new RPCMessage(method, msgData); } public toBuffer (): Buffer { - const msgData = encode(this.data) - const length = msgData.length + 12 - const buffer = Buffer.alloc(length - 4) - buffer.writeUInt32BE(length) - buffer.writeUInt32BE(this.method, 4) - buffer.fill(msgData, 8) - const crcNum = crc32(buffer) - const resultBuffer = Buffer.alloc(length, buffer) - resultBuffer.writeUInt32BE(crcNum, length - 4) + const msgData = encode(this.data); + const length = msgData.length + 12; + const buffer = Buffer.alloc(length - 4); + buffer.writeUInt32BE(length); + buffer.writeUInt32BE(this.method, 4); + buffer.fill(msgData, 8); + const crcNum = crc32(buffer); + const resultBuffer = Buffer.alloc(length, buffer); + resultBuffer.writeUInt32BE(crcNum, length - 4); - return resultBuffer + return resultBuffer; } } diff --git a/src/datasources/userserver/permission.ts b/src/datasources/userserver/permission.ts index 23a3a86..a7a639a 100644 --- a/src/datasources/userserver/permission.ts +++ b/src/datasources/userserver/permission.ts @@ -14,4 +14,4 @@ export const requiredPermissions = [ name: Permission.WriteBike, description: 'Allows the modification of bike information' } -] +]; diff --git a/src/datasources/userserver/userserviceAPI.ts b/src/datasources/userserver/userserviceAPI.ts index 6c16ae4..a87c58c 100644 --- a/src/datasources/userserver/userserviceAPI.ts +++ b/src/datasources/userserver/userserviceAPI.ts @@ -1,16 +1,16 @@ -import { DataSource } from 'apollo-datasource' -import { Socket } from 'net' -import { PromiseSocket } from 'promise-socket' -import { RPCMessage } from './message' -import { Method } from './method' +import { DataSource } from 'apollo-datasource'; +import { Socket } from 'net'; +import { PromiseSocket } from 'promise-socket'; +import { RPCMessage } from './message'; +import { Method } from './method'; import { CreateRoleResponse, GetInfoResponse, GetRolesPermissionsResponse, GetRolesResponse, ValidateTokenResponse -} from './responses' -import { Permission, requiredPermissions } from './permission' +} from './responses'; +import { Permission, requiredPermissions } from './permission'; /** * fetches datafrom user server, especially validates user tokens @@ -20,13 +20,13 @@ export class UserServerAPI extends DataSource { host: string constructor (address: string) { - super() - const parts = address.split(':') - this.host = parts[0] + super(); + const parts = address.split(':'); + this.host = parts[0]; if (parts.length === 2) { - this.port = Number(parts[1]) + this.port = Number(parts[1]); } else { - this.port = 5000 + this.port = 5000; } } @@ -34,16 +34,16 @@ 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)) + const response = await this.send(new RPCMessage(Method.Info, null)); - return response.data + return response.data; } /** * Creates required API permissions */ async createDefinedPermissions () { - await this.send(new RPCMessage(Method.CreatePermissions, { permissions: requiredPermissions })) + await this.send(new RPCMessage(Method.CreatePermissions, { permissions: requiredPermissions })); } /** @@ -57,20 +57,20 @@ export class UserServerAPI extends DataSource { name, description, permissions: permissionIDs - })) + })); - return response.data + return response.data; } /** * validates user token */ async validateToken (token:string): Promise { - const response = await this.send(new RPCMessage(Method.ValidateToken, { token })) + const response = await this.send(new RPCMessage(Method.ValidateToken, { token })); if (response) { - return response.data[0] + return response.data[0]; } else { - return false + return false; } } @@ -79,8 +79,8 @@ export class UserServerAPI extends DataSource { * @param token */ async getUserRoles (token: String): Promise { - const response = await this.send(new RPCMessage(Method.GetRoles, { token })) - return response.data + const response = await this.send(new RPCMessage(Method.GetRoles, { token })); + return response.data; } /** @@ -88,27 +88,27 @@ export class UserServerAPI extends DataSource { * @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[] = [] + 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])) + permissions.push(...permissionsResponse.data[id].map(entry => entry[1])); } - return permissions + return permissions; } /** * Connects to the socket and returns the client */ async getSocket (): Promise> { - const socket = new Socket() - const promiseSocket = new PromiseSocket(socket) - await promiseSocket.connect(this.port, this.host) + const socket = new Socket(); + const promiseSocket = new PromiseSocket(socket); + await promiseSocket.connect(this.port, this.host); - return promiseSocket + return promiseSocket; } /** @@ -116,12 +116,12 @@ export class UserServerAPI extends DataSource { * @param message */ async send (message: RPCMessage): Promise> { - const socket = await this.getSocket() - await socket.writeAll(message.toBuffer()) - const response = await socket.readAll() as Buffer + const socket = await this.getSocket(); + await socket.writeAll(message.toBuffer()); + const response = await socket.readAll() as Buffer; if (response?.length > 0) { - return RPCMessage.fromBuffer(response) + return RPCMessage.fromBuffer(response); } } } diff --git a/src/index.ts b/src/index.ts index 8eb5a8f..1d0e8fb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,14 +1,14 @@ -import { ApolloServer } from 'apollo-server-express' -import bikeresolver from './resolvers/cargobikeResolver' -import { CargoBikeAPI } from './datasources/db/cargobikeAPI' -import typeDefs from './schema/type-defs' -import 'reflect-metadata' -import { createConnection } from 'typeorm' -import { UserServerAPI } from './datasources/userserver/userserviceAPI' -import express from 'express' -import { requiredPermissions } from './datasources/userserver/permission' +import { ApolloServer } from 'apollo-server-express'; +import bikeresolver from './resolvers/cargobikeResolver'; +import { CargoBikeAPI } from './datasources/db/cargobikeAPI'; +import typeDefs from './schema/type-defs'; +import 'reflect-metadata'; +import { createConnection } from 'typeorm'; +import { UserServerAPI } from './datasources/userserver/userserviceAPI'; +import express from 'express'; +import { requiredPermissions } from './datasources/userserver/permission'; -require('dotenv').config() +require('dotenv').config(); /** * Function that is called to authenticate a user by using the user rpc server @@ -18,30 +18,30 @@ require('dotenv').config() */ async function authenticate (req: any, res: any, next: any) { if (process.env.NODE_ENV === 'develop') { - req.permissions = requiredPermissions.map((e) => e.name) - next() + req.permissions = requiredPermissions.map((e) => e.name); + next(); } else { - const token = req.headers.authorization?.replace('Bearer ', '') + const token = req.headers.authorization?.replace('Bearer ', ''); if (token) { if (await userAPI.validateToken(token)) { - req.permissions = await userAPI.getUserPermissions(token) - next() + req.permissions = await userAPI.getUserPermissions(token); + next(); } else { - res.status(401) - res.send('Unauthorized') + res.status(401); + res.send('Unauthorized'); } } else { - res.status(401) - res.send('Unauthorized') + res.status(401); + res.send('Unauthorized'); } } } createConnection().then(async () => { - console.log('connected to db') -}).catch(error => console.log(error)) + console.log('connected to db'); +}).catch(error => console.log(error)); -const userAPI = new UserServerAPI(process.env.RPC_HOST) +const userAPI = new UserServerAPI(process.env.RPC_HOST); const server = new ApolloServer({ resolvers: [bikeresolver], @@ -51,18 +51,18 @@ const server = new ApolloServer({ userAPI }), context: (req: any) => { - return req + return req; } -}) +}); -const app = express() +const app = express(); -app.post('/graphql', authenticate) -app.get(/\/graphql?&.*query=/, authenticate) -server.applyMiddleware({ app }) +app.post('/graphql', authenticate); +app.get(/\/graphql?&.*query=/, authenticate); +server.applyMiddleware({ app }); -console.log(__dirname) +console.log(__dirname); app.listen(4000, async () => { - console.log('Server listening on port 4000') - await userAPI.createDefinedPermissions() -}) + console.log('Server listening on port 4000'); + await userAPI.createDefinedPermissions(); +}); diff --git a/src/model/CargoBike.ts b/src/model/CargoBike.ts index ddb2b9c..46de65c 100644 --- a/src/model/CargoBike.ts +++ b/src/model/CargoBike.ts @@ -1,4 +1,4 @@ -import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm' +import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class CargoBike { diff --git a/src/resolvers/cargobikeResolver.ts b/src/resolvers/cargobikeResolver.ts index 07e6066..89d05df 100644 --- a/src/resolvers/cargobikeResolver.ts +++ b/src/resolvers/cargobikeResolver.ts @@ -1,23 +1,23 @@ -import { Permission } from '../datasources/userserver/permission' -import { GraphQLError } from 'graphql' +import { Permission } from '../datasources/userserver/permission'; +import { GraphQLError } from 'graphql'; export default { Query: { 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 }) + return dataSources.cargoBikeAPI.findCargoBikeById({ id, token }); } else { - throw new GraphQLError('Insufficient Permissions') + throw new GraphQLError('Insufficient Permissions'); } } }, Mutation: { 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 }) + return dataSources.cargoBikeAPI.updateBike({ id, token, name }); } else { - throw new GraphQLError('Insufficient Permissions') + throw new GraphQLError('Insufficient Permissions'); } } } -} +}; diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index 517cbdf..a86a338 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -1,4 +1,4 @@ -import { gql } from 'apollo-server' +import { gql } from 'apollo-server'; export default gql` @@ -355,4 +355,4 @@ type Mutation { cargoBike(token:String!,cargoBike: CargoBikeInput): UpdateBikeResponse! } -` +`; From c8ad2badc2e5af3193bc0863c755c53ae6250cde Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Mon, 14 Sep 2020 13:04:44 +0200 Subject: [PATCH 7/7] src/datasource/userservice/userserviceAPI.ts: fix eslint error --- src/datasources/userserver/userserviceAPI.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datasources/userserver/userserviceAPI.ts b/src/datasources/userserver/userserviceAPI.ts index a87c58c..09174cd 100644 --- a/src/datasources/userserver/userserviceAPI.ts +++ b/src/datasources/userserver/userserviceAPI.ts @@ -10,7 +10,7 @@ import { GetRolesResponse, ValidateTokenResponse } from './responses'; -import { Permission, requiredPermissions } from './permission'; +import { requiredPermissions } from './permission'; /** * fetches datafrom user server, especially validates user tokens