Merge pull request 'Auth for everything (and everything auth) #6' (#35) from feature/6-api_auth into dev
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			Reviewed-on: #35 closes #6
This commit is contained in:
		| @@ -28,6 +28,7 @@ | ||||
|     "class-validator": "^0.12.2", | ||||
|     "class-validator-jsonschema": "^2.0.3", | ||||
|     "consola": "^2.15.0", | ||||
|     "cookie": "^0.4.1", | ||||
|     "cookie-parser": "^1.4.5", | ||||
|     "cors": "^2.8.5", | ||||
|     "csvtojson": "^2.0.10", | ||||
| @@ -81,4 +82,4 @@ | ||||
|       "docs/*" | ||||
|     ] | ||||
|   } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -1,51 +1,72 @@ | ||||
| import cookie from "cookie"; | ||||
| import * as jwt from "jsonwebtoken"; | ||||
| import { Action } from "routing-controllers"; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { config } from './config'; | ||||
| import { IllegalJWTError, NoPermissionError, UserNonexistantOrRefreshtokenInvalidError } from './errors/AuthError'; | ||||
| import { JwtCreator, JwtUser } from './jwtcreator'; | ||||
| import { User } from './models/entities/User'; | ||||
| // ----------- | ||||
| const authchecker = async (action: Action, permissions: string | string[]) => { | ||||
|     let required_permissions = undefined | ||||
|  | ||||
| /** | ||||
|  * Handels authorisation verification via jwt's for all api endpoints using the @Authorized decorator. | ||||
|  * @param action Routing-Controllers action object that provides request and response objects among other stuff. | ||||
|  * @param permissions The permissions that the endpoint using @Authorized requires. | ||||
|  */ | ||||
| const authchecker = async (action: Action, permissions: string[] | string) => { | ||||
|     let required_permissions = undefined; | ||||
|     if (typeof permissions === "string") { | ||||
|         required_permissions = [permissions] | ||||
|     } else { | ||||
|         required_permissions = permissions | ||||
|     } | ||||
|     // const token = action.request.headers["authorization"]; | ||||
|     const provided_token = action.request.query["auth"]; | ||||
|  | ||||
|     let jwtPayload = undefined | ||||
|     try { | ||||
|         let provided_token = "" + action.request.headers["authorization"].replace("Bearer ", ""); | ||||
|         jwtPayload = <any>jwt.verify(provided_token, config.jwt_secret); | ||||
|         jwtPayload = jwtPayload["userdetails"]; | ||||
|     } catch (error) { | ||||
|         throw new IllegalJWTError() | ||||
|         jwtPayload = await refresh(action); | ||||
|     } | ||||
|     const count = await getConnectionManager().get().getRepository(User).count({ id: jwtPayload["userdetails"]["id"], refreshTokenCount: jwtPayload["userdetails"]["refreshTokenCount"] }) | ||||
|     if (count !== 1) { | ||||
|         throw new UserNonexistantOrRefreshtokenInvalidError() | ||||
|  | ||||
|     const user = await getConnectionManager().get().getRepository(User).findOne({ id: jwtPayload["id"], refreshTokenCount: jwtPayload["refreshTokenCount"] }, { relations: ['permissions'] }) | ||||
|     if (!user) { throw new UserNonexistantOrRefreshtokenInvalidError() } | ||||
|     if (!jwtPayload["permissions"]) { throw new NoPermissionError(); } | ||||
|  | ||||
|     action.response.local = {} | ||||
|     action.response.local.jwtPayload = jwtPayload; | ||||
|     for (let required_permission of required_permissions) { | ||||
|         if (!(jwtPayload["permissions"].includes(required_permission))) { return false; } | ||||
|     } | ||||
|     if (jwtPayload.permissions) { | ||||
|         action.response.local = {} | ||||
|         action.response.local.jwtPayload = jwtPayload.permissions | ||||
|         required_permissions.forEach(r => { | ||||
|             const permission_key = r.split(":")[0] | ||||
|             const actual_accesslevel_for_permission = jwtPayload.permissions[permission_key] | ||||
|             const permission_access_level = r.split(":")[1] | ||||
|             if (actual_accesslevel_for_permission.includes(permission_access_level)) { | ||||
|                 return true; | ||||
|             } else { | ||||
|                 throw new NoPermissionError() | ||||
|             } | ||||
|         }); | ||||
|     } else { | ||||
|         throw new NoPermissionError() | ||||
|     } | ||||
|     //  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Handels soft-refreshing of access-tokens. | ||||
|  * @param action Routing-Controllers action object that provides request and response objects among other stuff. | ||||
|  */ | ||||
| const refresh = async (action: Action) => { | ||||
|     let refresh_token = undefined; | ||||
|     try { | ||||
|         jwt.verify(provided_token, config.jwt_secret); | ||||
|         return true | ||||
|     } catch (error) { | ||||
|         return false | ||||
|         refresh_token = cookie.parse(action.request.headers["cookie"])["lfk_backend__refresh_token"]; | ||||
|     } | ||||
|     catch { | ||||
|         throw new IllegalJWTError(); | ||||
|     } | ||||
|  | ||||
|     let jwtPayload = undefined; | ||||
|     try { | ||||
|         jwtPayload = <any>jwt.verify(refresh_token, config.jwt_secret); | ||||
|     } catch (error) { | ||||
|         throw new IllegalJWTError(); | ||||
|     } | ||||
|  | ||||
|     const user = await getConnectionManager().get().getRepository(User).findOne({ id: jwtPayload["id"], refreshTokenCount: jwtPayload["refreshTokenCount"] }, { relations: ['permissions', 'groups', 'groups.permissions'] }) | ||||
|     if (!user) { throw new UserNonexistantOrRefreshtokenInvalidError() } | ||||
|  | ||||
|     let newAccess = JwtCreator.createAccess(user); | ||||
|     action.response.header("authorization", "Bearer " + newAccess); | ||||
|  | ||||
|     return await new JwtUser(user); | ||||
| } | ||||
| export default authchecker | ||||
| @@ -1,5 +1,5 @@ | ||||
| import csv from 'csvtojson'; | ||||
| import { Body, ContentType, Controller, Param, Post, QueryParam, Req, UseBefore } from 'routing-controllers'; | ||||
| import { Authorized, Body, ContentType, Controller, Param, Post, QueryParam, Req, UseBefore } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { RunnerGroupNeededError } from '../errors/RunnerErrors'; | ||||
| import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors'; | ||||
| @@ -9,7 +9,8 @@ import { ResponseRunner } from '../models/responses/ResponseRunner'; | ||||
| import { RunnerController } from './RunnerController'; | ||||
|  | ||||
| @Controller() | ||||
| //@Authorized("IMPORT:read") | ||||
| @Authorized(["RUNNER:IMPORT", "TEAM:IMPORT"]) | ||||
| @OpenAPI({ security: [{ "AuthToken": [] }] }) | ||||
| export class ImportController { | ||||
|     private runnerController: RunnerController; | ||||
|  | ||||
|   | ||||
							
								
								
									
										118
									
								
								src/controllers/PermissionController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								src/controllers/PermissionController.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { PermissionIdsNotMatchingError, PermissionNeedsPrincipalError, PermissionNotFoundError } from '../errors/PermissionErrors'; | ||||
| import { PrincipalNotFoundError } from '../errors/PrincipalErrors'; | ||||
| import { CreatePermission } from '../models/actions/CreatePermission'; | ||||
| import { UpdatePermission } from '../models/actions/UpdatePermission'; | ||||
| import { Permission } from '../models/entities/Permission'; | ||||
| import { ResponseEmpty } from '../models/responses/ResponseEmpty'; | ||||
| import { ResponsePermission } from '../models/responses/ResponsePermission'; | ||||
| import { ResponsePrincipal } from '../models/responses/ResponsePrincipal'; | ||||
|  | ||||
|  | ||||
| @JsonController('/permissions') | ||||
| @OpenAPI({ security: [{ "AuthToken": [] }] }) | ||||
| export class PermissionController { | ||||
|     private permissionRepository: Repository<Permission>; | ||||
|  | ||||
|     /** | ||||
|      * Gets the repository of this controller's model/entity. | ||||
|      */ | ||||
|     constructor() { | ||||
|         this.permissionRepository = getConnectionManager().get().getRepository(Permission); | ||||
|     } | ||||
|  | ||||
|     @Get() | ||||
|     @Authorized("PERMISSION:GET") | ||||
|     @ResponseSchema(ResponsePermission, { isArray: true }) | ||||
|     @OpenAPI({ description: 'Lists all permissions.' }) | ||||
|     async getAll() { | ||||
|         let responsePermissions: ResponsePermission[] = new Array<ResponsePermission>(); | ||||
|         const permissions = await this.permissionRepository.find({ relations: ['principal'] }); | ||||
|         permissions.forEach(permission => { | ||||
|             responsePermissions.push(new ResponsePermission(permission)); | ||||
|         }); | ||||
|         return responsePermissions; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     @Get('/:id') | ||||
|     @Authorized("PERMISSION:GET") | ||||
|     @ResponseSchema(ResponsePermission) | ||||
|     @ResponseSchema(PermissionNotFoundError, { statusCode: 404 }) | ||||
|     @OnUndefined(PermissionNotFoundError) | ||||
|     @OpenAPI({ description: 'Returns a permissions of a specified id (if it exists)' }) | ||||
|     async getOne(@Param('id') id: number) { | ||||
|         let permission = await this.permissionRepository.findOne({ id: id }, { relations: ['principal'] }); | ||||
|         if (!permission) { throw new PermissionNotFoundError(); } | ||||
|         return new ResponsePermission(permission); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     @Post() | ||||
|     @Authorized("PERMISSION:CREATE") | ||||
|     @ResponseSchema(ResponsePermission) | ||||
|     @ResponseSchema(PrincipalNotFoundError, { statusCode: 404 }) | ||||
|     @OpenAPI({ description: 'Create a new runnerTeam object (id will be generated automagicly).' }) | ||||
|     async post(@Body({ validate: true }) createPermission: CreatePermission) { | ||||
|         let permission; | ||||
|         try { | ||||
|             permission = await createPermission.toPermission(); | ||||
|         } catch (error) { | ||||
|             throw error; | ||||
|         } | ||||
|         let existingPermission = await this.permissionRepository.findOne({ target: permission.target, action: permission.action, principal: permission.principal }, { relations: ['principal'] }); | ||||
|         if (existingPermission) { return new ResponsePermission(existingPermission); } | ||||
|  | ||||
|         permission = await this.permissionRepository.save(permission); | ||||
|         permission = await this.permissionRepository.findOne(permission, { relations: ['principal'] }); | ||||
|  | ||||
|         return new ResponsePermission(permission); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     @Put('/:id') | ||||
|     @Authorized("PERMISSION:UPDATE") | ||||
|     @ResponseSchema(ResponsePrincipal) | ||||
|     @ResponseSchema(PermissionNotFoundError, { statusCode: 404 }) | ||||
|     @ResponseSchema(PrincipalNotFoundError, { statusCode: 404 }) | ||||
|     @ResponseSchema(PermissionIdsNotMatchingError, { statusCode: 406 }) | ||||
|     @ResponseSchema(PermissionNeedsPrincipalError, { statusCode: 406 }) | ||||
|     @OpenAPI({ description: "Update a permission object (id can't be changed)." }) | ||||
|     async put(@Param('id') id: number, @Body({ validate: true }) permission: UpdatePermission) { | ||||
|         let oldPermission = await this.permissionRepository.findOne({ id: id }, { relations: ['principal'] }); | ||||
|  | ||||
|         if (!oldPermission) { | ||||
|             throw new PermissionNotFoundError(); | ||||
|         } | ||||
|  | ||||
|         if (oldPermission.id != permission.id) { | ||||
|             throw new PermissionIdsNotMatchingError(); | ||||
|         } | ||||
|         let existingPermission = await this.permissionRepository.findOne({ target: permission.target, action: permission.action, principal: permission.principal }, { relations: ['principal'] }); | ||||
|         if (existingPermission) { | ||||
|             await this.remove(permission.id, true); | ||||
|             return new ResponsePermission(existingPermission); | ||||
|         } | ||||
|  | ||||
|         await this.permissionRepository.update(oldPermission, await permission.toPermission()); | ||||
|  | ||||
|         return new ResponsePermission(await this.permissionRepository.findOne({ id: permission.id }, { relations: ['principal'] })); | ||||
|     } | ||||
|  | ||||
|     @Delete('/:id') | ||||
|     @Authorized("PERMISSION:DELETE") | ||||
|     @ResponseSchema(ResponsePermission) | ||||
|     @ResponseSchema(ResponseEmpty, { statusCode: 204 }) | ||||
|     @OnUndefined(204) | ||||
|     @OpenAPI({ description: 'Delete a specified permission (if it exists).' }) | ||||
|     async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { | ||||
|         let permission = await this.permissionRepository.findOne({ id: id }, { relations: ['principal'] }); | ||||
|         if (!permission) { return null; } | ||||
|  | ||||
|         const responsePermission = new ResponsePermission(permission); | ||||
|         await this.permissionRepository.delete(permission); | ||||
|         return responsePermission; | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { RunnerGroupNeededError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors'; | ||||
| @@ -10,7 +10,7 @@ import { ResponseEmpty } from '../models/responses/ResponseEmpty'; | ||||
| import { ResponseRunner } from '../models/responses/ResponseRunner'; | ||||
|  | ||||
| @JsonController('/runners') | ||||
| //@Authorized('RUNNERS:read') | ||||
| @OpenAPI({ security: [{ "AuthToken": [] }] }) | ||||
| export class RunnerController { | ||||
| 	private runnerRepository: Repository<Runner>; | ||||
|  | ||||
| @@ -22,6 +22,7 @@ export class RunnerController { | ||||
| 	} | ||||
|  | ||||
| 	@Get() | ||||
| 	@Authorized("RUNNER:GET") | ||||
| 	@ResponseSchema(ResponseRunner, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all runners.' }) | ||||
| 	async getAll() { | ||||
| @@ -34,6 +35,7 @@ export class RunnerController { | ||||
| 	} | ||||
|  | ||||
| 	@Get('/:id') | ||||
| 	@Authorized("RUNNER:GET") | ||||
| 	@ResponseSchema(ResponseRunner) | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(RunnerNotFoundError) | ||||
| @@ -45,6 +47,7 @@ export class RunnerController { | ||||
| 	} | ||||
|  | ||||
| 	@Post() | ||||
| 	@Authorized("RUNNER:CREATE") | ||||
| 	@ResponseSchema(ResponseRunner) | ||||
| 	@ResponseSchema(RunnerGroupNeededError) | ||||
| 	@ResponseSchema(RunnerGroupNotFoundError) | ||||
| @@ -62,6 +65,7 @@ export class RunnerController { | ||||
| 	} | ||||
|  | ||||
| 	@Put('/:id') | ||||
| 	@Authorized("RUNNER:UPDATE") | ||||
| 	@ResponseSchema(ResponseRunner) | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(RunnerIdsNotMatchingError, { statusCode: 406 }) | ||||
| @@ -82,6 +86,7 @@ export class RunnerController { | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/:id') | ||||
| 	@Authorized("RUNNER:DELETE") | ||||
| 	@ResponseSchema(ResponseRunner) | ||||
| 	@ResponseSchema(ResponseEmpty, { statusCode: 204 }) | ||||
| 	@OnUndefined(204) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { EntityFromBody } from 'typeorm-routing-controllers-extensions'; | ||||
| @@ -12,7 +12,7 @@ import { RunnerTeamController } from './RunnerTeamController'; | ||||
|  | ||||
|  | ||||
| @JsonController('/organisations') | ||||
| //@Authorized('RUNNERS:read') | ||||
| @OpenAPI({ security: [{ "AuthToken": [] }] }) | ||||
| export class RunnerOrganisationController { | ||||
| 	private runnerOrganisationRepository: Repository<RunnerOrganisation>; | ||||
|  | ||||
| @@ -24,6 +24,7 @@ export class RunnerOrganisationController { | ||||
| 	} | ||||
|  | ||||
| 	@Get() | ||||
| 	@Authorized("ORGANISATION:GET") | ||||
| 	@ResponseSchema(ResponseRunnerOrganisation, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all runnerOrganisations.' }) | ||||
| 	async getAll() { | ||||
| @@ -36,6 +37,7 @@ export class RunnerOrganisationController { | ||||
| 	} | ||||
|  | ||||
| 	@Get('/:id') | ||||
| 	@Authorized("ORGANISATION:GET") | ||||
| 	@ResponseSchema(ResponseRunnerOrganisation) | ||||
| 	@ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(RunnerOrganisationNotFoundError) | ||||
| @@ -47,6 +49,7 @@ export class RunnerOrganisationController { | ||||
| 	} | ||||
|  | ||||
| 	@Post() | ||||
| 	@Authorized("ORGANISATION:CREATE") | ||||
| 	@ResponseSchema(ResponseRunnerOrganisation) | ||||
| 	@OpenAPI({ description: 'Create a new runnerOrganisation object (id will be generated automagicly).' }) | ||||
| 	async post(@Body({ validate: true }) createRunnerOrganisation: CreateRunnerOrganisation) { | ||||
| @@ -63,6 +66,7 @@ export class RunnerOrganisationController { | ||||
| 	} | ||||
|  | ||||
| 	@Put('/:id') | ||||
| 	@Authorized("ORGANISATION:UPDATE") | ||||
| 	@ResponseSchema(ResponseRunnerOrganisation) | ||||
| 	@ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(RunnerOrganisationIdsNotMatchingError, { statusCode: 406 }) | ||||
| @@ -85,6 +89,7 @@ export class RunnerOrganisationController { | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/:id') | ||||
| 	@Authorized("ORGANISATION:DELETE") | ||||
| 	@ResponseSchema(ResponseRunnerOrganisation) | ||||
| 	@ResponseSchema(ResponseEmpty, { statusCode: 204 }) | ||||
| 	@ResponseSchema(RunnerOrganisationHasTeamsError, { statusCode: 406 }) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { RunnerTeamHasRunnersError, RunnerTeamIdsNotMatchingError, RunnerTeamNotFoundError } from '../errors/RunnerTeamErrors'; | ||||
| @@ -11,7 +11,7 @@ import { RunnerController } from './RunnerController'; | ||||
|  | ||||
|  | ||||
| @JsonController('/teams') | ||||
| //@Authorized('RUNNERS:read') | ||||
| @OpenAPI({ security: [{ "AuthToken": [] }] }) | ||||
| export class RunnerTeamController { | ||||
| 	private runnerTeamRepository: Repository<RunnerTeam>; | ||||
|  | ||||
| @@ -23,6 +23,7 @@ export class RunnerTeamController { | ||||
| 	} | ||||
|  | ||||
| 	@Get() | ||||
| 	@Authorized("TEAM:GET") | ||||
| 	@ResponseSchema(ResponseRunnerTeam, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all runnerTeams.' }) | ||||
| 	async getAll() { | ||||
| @@ -35,6 +36,7 @@ export class RunnerTeamController { | ||||
| 	} | ||||
|  | ||||
| 	@Get('/:id') | ||||
| 	@Authorized("TEAM:GET") | ||||
| 	@ResponseSchema(ResponseRunnerTeam) | ||||
| 	@ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(RunnerTeamNotFoundError) | ||||
| @@ -46,6 +48,7 @@ export class RunnerTeamController { | ||||
| 	} | ||||
|  | ||||
| 	@Post() | ||||
| 	@Authorized("TEAM:CREATE") | ||||
| 	@ResponseSchema(ResponseRunnerTeam) | ||||
| 	@OpenAPI({ description: 'Create a new runnerTeam object (id will be generated automagicly).' }) | ||||
| 	async post(@Body({ validate: true }) createRunnerTeam: CreateRunnerTeam) { | ||||
| @@ -63,6 +66,7 @@ export class RunnerTeamController { | ||||
| 	} | ||||
|  | ||||
| 	@Put('/:id') | ||||
| 	@Authorized("TEAM:UPDATE") | ||||
| 	@ResponseSchema(ResponseRunnerTeam) | ||||
| 	@ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(RunnerTeamIdsNotMatchingError, { statusCode: 406 }) | ||||
| @@ -84,6 +88,7 @@ export class RunnerTeamController { | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/:id') | ||||
| 	@Authorized("TEAM:DELETE") | ||||
| 	@ResponseSchema(ResponseRunnerTeam) | ||||
| 	@ResponseSchema(ResponseEmpty, { statusCode: 204 }) | ||||
| 	@ResponseSchema(RunnerTeamHasRunnersError, { statusCode: 406 }) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers'; | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { EntityFromBody } from 'typeorm-routing-controllers-extensions'; | ||||
| @@ -9,7 +9,7 @@ import { ResponseEmpty } from '../models/responses/ResponseEmpty'; | ||||
| import { ResponseTrack } from '../models/responses/ResponseTrack'; | ||||
|  | ||||
| @JsonController('/tracks') | ||||
| //@Authorized("TRACKS:read") | ||||
| @OpenAPI({ security: [{ "AuthToken": [] }] }) | ||||
| export class TrackController { | ||||
| 	private trackRepository: Repository<Track>; | ||||
|  | ||||
| @@ -21,8 +21,8 @@ export class TrackController { | ||||
| 	} | ||||
|  | ||||
| 	@Get() | ||||
| 	@Authorized("TRACK:GET") | ||||
| 	@ResponseSchema(ResponseTrack, { isArray: true }) | ||||
| 	@OpenAPI({ description: "Lists all tracks." }) | ||||
| 	async getAll() { | ||||
| 		let responseTracks: ResponseTrack[] = new Array<ResponseTrack>(); | ||||
| 		const tracks = await this.trackRepository.find(); | ||||
| @@ -33,6 +33,7 @@ export class TrackController { | ||||
| 	} | ||||
|  | ||||
| 	@Get('/:id') | ||||
| 	@Authorized("TRACK:GET") | ||||
| 	@ResponseSchema(ResponseTrack) | ||||
| 	@ResponseSchema(TrackNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(TrackNotFoundError) | ||||
| @@ -44,6 +45,7 @@ export class TrackController { | ||||
| 	} | ||||
|  | ||||
| 	@Post() | ||||
| 	@Authorized("TRACK:CREATE") | ||||
| 	@ResponseSchema(ResponseTrack) | ||||
| 	@OpenAPI({ description: "Create a new track object (id will be generated automagicly)." }) | ||||
| 	async post( | ||||
| @@ -54,6 +56,7 @@ export class TrackController { | ||||
| 	} | ||||
|  | ||||
| 	@Put('/:id') | ||||
| 	@Authorized("TRACK:UPDATE") | ||||
| 	@ResponseSchema(ResponseTrack) | ||||
| 	@ResponseSchema(TrackNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(TrackIdsNotMatchingError, { statusCode: 406 }) | ||||
| @@ -74,6 +77,7 @@ export class TrackController { | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/:id') | ||||
| 	@Authorized("TRACK:DELETE") | ||||
| 	@ResponseSchema(ResponseTrack) | ||||
| 	@ResponseSchema(ResponseEmpty, { statusCode: 204 }) | ||||
| 	@OnUndefined(204) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers'; | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { EntityFromBody } from 'typeorm-routing-controllers-extensions'; | ||||
| @@ -7,9 +7,12 @@ import { UserGroupNotFoundError } from '../errors/UserGroupErrors'; | ||||
| import { CreateUser } from '../models/actions/CreateUser'; | ||||
| import { User } from '../models/entities/User'; | ||||
| import { ResponseEmpty } from '../models/responses/ResponseEmpty'; | ||||
| import { ResponseUser } from '../models/responses/ResponseUser'; | ||||
| import { PermissionController } from './PermissionController'; | ||||
|  | ||||
|  | ||||
| @JsonController('/users') | ||||
| @OpenAPI({ security: [{ "AuthToken": [] }] }) | ||||
| export class UserController { | ||||
| 	private userRepository: Repository<User>; | ||||
|  | ||||
| @@ -21,22 +24,32 @@ export class UserController { | ||||
| 	} | ||||
|  | ||||
| 	@Get() | ||||
| 	@Authorized("USER:GET") | ||||
| 	@ResponseSchema(User, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all users.' }) | ||||
| 	getAll() { | ||||
| 		return this.userRepository.find(); | ||||
| 	async getAll() { | ||||
| 		let responseUsers: ResponseUser[] = new Array<ResponseUser>(); | ||||
| 		const users = await this.userRepository.find({ relations: ['permissions', 'groups'] }); | ||||
| 		users.forEach(user => { | ||||
| 			responseUsers.push(new ResponseUser(user)); | ||||
| 		}); | ||||
| 		return responseUsers; | ||||
| 	} | ||||
|  | ||||
| 	@Get('/:id') | ||||
| 	@Authorized("USER:GET") | ||||
| 	@ResponseSchema(User) | ||||
| 	@ResponseSchema(UserNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(UserNotFoundError) | ||||
| 	@OpenAPI({ description: 'Returns a user of a specified id (if it exists)' }) | ||||
| 	getOne(@Param('id') id: number) { | ||||
| 		return this.userRepository.findOne({ id: id }); | ||||
| 	async getOne(@Param('id') id: number) { | ||||
| 		let user = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] }) | ||||
| 		if (!user) { throw new UserNotFoundError(); } | ||||
| 		return new ResponseUser(user); | ||||
| 	} | ||||
|  | ||||
| 	@Post() | ||||
| 	@Authorized("USER:CREATE") | ||||
| 	@ResponseSchema(User) | ||||
| 	@ResponseSchema(UserGroupNotFoundError) | ||||
| 	@OpenAPI({ description: 'Create a new user object (id will be generated automagicly).' }) | ||||
| @@ -48,16 +61,18 @@ export class UserController { | ||||
| 			throw error; | ||||
| 		} | ||||
|  | ||||
| 		return this.userRepository.save(user); | ||||
| 		user = await this.userRepository.save(user) | ||||
| 		return new ResponseUser(await this.userRepository.findOne(user, { relations: ['permissions', 'groups'] })); | ||||
| 	} | ||||
|  | ||||
| 	@Put('/:id') | ||||
| 	@Authorized("USER:UPDATE") | ||||
| 	@ResponseSchema(User) | ||||
| 	@ResponseSchema(UserNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 }) | ||||
| 	@OpenAPI({ description: "Update a user object (id can't be changed)." }) | ||||
| 	async put(@Param('id') id: number, @EntityFromBody() user: User) { | ||||
| 		let oldUser = await this.userRepository.findOne({ id: id }); | ||||
| 		let oldUser = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] }); | ||||
|  | ||||
| 		if (!oldUser) { | ||||
| 			throw new UserNotFoundError(); | ||||
| @@ -68,21 +83,26 @@ export class UserController { | ||||
| 		} | ||||
|  | ||||
| 		await this.userRepository.update(oldUser, user); | ||||
| 		return user; | ||||
| 		return new ResponseUser(await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] })); | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/:id') | ||||
| 	@Authorized("USER:DELETE") | ||||
| 	@ResponseSchema(User) | ||||
| 	@ResponseSchema(ResponseEmpty, { statusCode: 204 }) | ||||
| 	@OnUndefined(204) | ||||
| 	@OpenAPI({ description: 'Delete a specified runner (if it exists).' }) | ||||
| 	async remove(@Param("id") id: number) { | ||||
| 	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { | ||||
| 		let user = await this.userRepository.findOne({ id: id }); | ||||
| 		if (!user) { | ||||
| 			return null; | ||||
| 		if (!user) { return null; } | ||||
| 		const responseUser = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] });; | ||||
|  | ||||
| 		const permissionControler = new PermissionController(); | ||||
| 		for (let permission of responseUser.permissions) { | ||||
| 			await permissionControler.remove(permission.id, true); | ||||
| 		} | ||||
|  | ||||
| 		await this.userRepository.delete(user); | ||||
| 		return user; | ||||
| 		return new ResponseUser(responseUser); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers'; | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { EntityFromBody } from 'typeorm-routing-controllers-extensions'; | ||||
| @@ -6,9 +6,12 @@ import { UserGroupIdsNotMatchingError, UserGroupNotFoundError } from '../errors/ | ||||
| import { CreateUserGroup } from '../models/actions/CreateUserGroup'; | ||||
| import { UserGroup } from '../models/entities/UserGroup'; | ||||
| import { ResponseEmpty } from '../models/responses/ResponseEmpty'; | ||||
| import { ResponseUserGroup } from '../models/responses/ResponseUserGroup'; | ||||
| import { PermissionController } from './PermissionController'; | ||||
|  | ||||
|  | ||||
| @JsonController('/usergroups') | ||||
| @OpenAPI({ security: [{ "AuthToken": [] }] }) | ||||
| export class UserGroupController { | ||||
| 	private userGroupsRepository: Repository<UserGroup>; | ||||
|  | ||||
| @@ -20,6 +23,7 @@ export class UserGroupController { | ||||
| 	} | ||||
|  | ||||
| 	@Get() | ||||
| 	@Authorized("USERGROUP:GET") | ||||
| 	@ResponseSchema(UserGroup, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all usergroups.' }) | ||||
| 	getAll() { | ||||
| @@ -27,6 +31,7 @@ export class UserGroupController { | ||||
| 	} | ||||
|  | ||||
| 	@Get('/:id') | ||||
| 	@Authorized("USERGROUP:GET") | ||||
| 	@ResponseSchema(UserGroup) | ||||
| 	@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(UserGroupNotFoundError) | ||||
| @@ -36,6 +41,7 @@ export class UserGroupController { | ||||
| 	} | ||||
|  | ||||
| 	@Post() | ||||
| 	@Authorized("USERGROUP:CREATE") | ||||
| 	@ResponseSchema(UserGroup) | ||||
| 	@ResponseSchema(UserGroupNotFoundError) | ||||
| 	@OpenAPI({ description: 'Create a new usergroup object (id will be generated automagicly).' }) | ||||
| @@ -51,6 +57,7 @@ export class UserGroupController { | ||||
| 	} | ||||
|  | ||||
| 	@Put('/:id') | ||||
| 	@Authorized("USERGROUP:UPDATE") | ||||
| 	@ResponseSchema(UserGroup) | ||||
| 	@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(UserGroupIdsNotMatchingError, { statusCode: 406 }) | ||||
| @@ -71,17 +78,22 @@ export class UserGroupController { | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/:id') | ||||
| 	@ResponseSchema(UserGroup) | ||||
| 	@Authorized("USERGROUP:DELETE") | ||||
| 	@ResponseSchema(ResponseUserGroup) | ||||
| 	@ResponseSchema(ResponseEmpty, { statusCode: 204 }) | ||||
| 	@OnUndefined(204) | ||||
| 	@OpenAPI({ description: 'Delete a specified usergroup (if it exists).' }) | ||||
| 	async remove(@Param("id") id: number) { | ||||
| 	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { | ||||
| 		let group = await this.userGroupsRepository.findOne({ id: id }); | ||||
| 		if (!group) { | ||||
| 			return null; | ||||
| 		if (!group) { return null; } | ||||
| 		const responseGroup = await this.userGroupsRepository.findOne({ id: id }, { relations: ['permissions'] });; | ||||
|  | ||||
| 		const permissionControler = new PermissionController(); | ||||
| 		for (let permission of responseGroup.permissions) { | ||||
| 			await permissionControler.remove(permission.id, true); | ||||
| 		} | ||||
|  | ||||
| 		await this.userGroupsRepository.delete(group); | ||||
| 		return group; | ||||
| 		return new ResponseUserGroup(responseGroup); | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										36
									
								
								src/errors/PermissionErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/errors/PermissionErrors.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| import { IsString } from 'class-validator'; | ||||
| import { NotAcceptableError, NotFoundError } from 'routing-controllers'; | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a permission couldn't be found. | ||||
|  */ | ||||
| export class PermissionNotFoundError extends NotFoundError { | ||||
| 	@IsString() | ||||
| 	name = "PermissionNotFoundError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "Permission not found!" | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when two permission' ids don't match. | ||||
|  * Usually occurs when a user tries to change a permission's id. | ||||
|  */ | ||||
| export class PermissionIdsNotMatchingError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "PermissionIdsNotMatchingError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The id's don't match!! \n And if you wanted to change a permission's id: This isn't allowed" | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a permission get's provided without a principal. | ||||
|  */ | ||||
| export class PermissionNeedsPrincipalError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "PermissionNeedsPrincipalError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "You provided no principal for this permission." | ||||
| } | ||||
							
								
								
									
										24
									
								
								src/errors/PrincipalErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/errors/PrincipalErrors.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| import { IsString } from 'class-validator'; | ||||
| import { NotAcceptableError, NotFoundError } from 'routing-controllers'; | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a user couldn't be found. | ||||
|  */ | ||||
| export class PrincipalNotFoundError extends NotFoundError { | ||||
| 	@IsString() | ||||
| 	name = "PrincipalNotFoundError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "Principal not found!" | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw, when a provided runnerOrganisation doesn't belong to the accepted types. | ||||
|  */ | ||||
| export class PrincipalWrongTypeError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "PrincipalWrongTypeError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The princial must have an existing principal's id. \n You provided a object of another type." | ||||
| } | ||||
							
								
								
									
										114
									
								
								src/jwtcreator.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/jwtcreator.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | ||||
| import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; | ||||
| import * as jsonwebtoken from "jsonwebtoken"; | ||||
| import { config } from './config'; | ||||
| import { User } from './models/entities/User'; | ||||
|  | ||||
| /** | ||||
|  * This class is responsible for all things JWT creation. | ||||
|  */ | ||||
| export class JwtCreator { | ||||
|     /** | ||||
|      * Creates a new refresh token for a given user | ||||
|      * @param user User entity that the refresh token shall be created for | ||||
|      * @param expiry_timestamp Timestamp for the token expiry. Will be generated if not provided. | ||||
|      */ | ||||
|     public static createRefresh(user: User, expiry_timestamp?: number) { | ||||
|         if (!expiry_timestamp) { expiry_timestamp = Math.floor(Date.now() / 1000) + 10 * 36000; } | ||||
|         return jsonwebtoken.sign({ | ||||
|             refreshTokenCount: user.refreshTokenCount, | ||||
|             id: user.id, | ||||
|             exp: expiry_timestamp | ||||
|         }, config.jwt_secret) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a new access token for a given user | ||||
|      * @param user User entity that the access token shall be created for | ||||
|      * @param expiry_timestamp Timestamp for the token expiry. Will be generated if not provided. | ||||
|      */ | ||||
|     public static createAccess(user: User, expiry_timestamp?: number) { | ||||
|         if (!expiry_timestamp) { expiry_timestamp = Math.floor(Date.now() / 1000) + 10 * 36000; } | ||||
|         return jsonwebtoken.sign({ | ||||
|             userdetails: new JwtUser(user), | ||||
|             exp: expiry_timestamp | ||||
|         }, config.jwt_secret) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Special variant of the user class that  | ||||
|  */ | ||||
| export class JwtUser { | ||||
|     @IsInt() | ||||
|     id: number; | ||||
|  | ||||
|     @IsUUID(4) | ||||
|     uuid: string; | ||||
|  | ||||
|     @IsOptional() | ||||
|     @IsEmail() | ||||
|     email?: string; | ||||
|  | ||||
|     @IsOptional() | ||||
|     @IsString() | ||||
|     username?: string; | ||||
|  | ||||
|     @IsString() | ||||
|     @IsNotEmpty() | ||||
|     firstname: string; | ||||
|  | ||||
|     @IsString() | ||||
|     @IsOptional() | ||||
|     middlename?: string; | ||||
|  | ||||
|     @IsString() | ||||
|     @IsNotEmpty() | ||||
|     lastname: string; | ||||
|  | ||||
|     permissions: string[]; | ||||
|  | ||||
|     @IsBoolean() | ||||
|     enabled: boolean; | ||||
|  | ||||
|     @IsInt() | ||||
|     @IsNotEmpty() | ||||
|     refreshTokenCount?: number; | ||||
|  | ||||
|     @IsString() | ||||
|     @IsOptional() | ||||
|     profilePic?: string; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new instance of this class based on a provided user entity. | ||||
|      * @param user User entity that shall be encapsulated in a jwt. | ||||
|      */ | ||||
|     public constructor(user: User) { | ||||
|         this.id = user.id; | ||||
|         this.firstname = user.firstname; | ||||
|         this.middlename = user.middlename; | ||||
|         this.lastname = user.lastname; | ||||
|         this.username = user.username; | ||||
|         this.email = user.email; | ||||
|         this.refreshTokenCount = user.refreshTokenCount; | ||||
|         this.uuid = user.uuid; | ||||
|         this.profilePic = user.profilePic; | ||||
|         this.permissions = this.getPermissions(user); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Handels getting the permissions granted to this user (direct or indirect). | ||||
|      * @param user User which's permissions shall be gotten. | ||||
|      */ | ||||
|     public getPermissions(user: User): string[] { | ||||
|         let returnPermissions: string[] = new Array<string>(); | ||||
|         for (let permission of user.permissions) { | ||||
|             returnPermissions.push(permission.toString()); | ||||
|         } | ||||
|         for (let group of user.groups) { | ||||
|             for (let permission of group.permissions) { | ||||
|                 returnPermissions.push(permission.toString()); | ||||
|             } | ||||
|         } | ||||
|         return Array.from(new Set(returnPermissions)); | ||||
|     } | ||||
| } | ||||
| @@ -1,10 +1,9 @@ | ||||
| import * as argon2 from "argon2"; | ||||
| import { IsEmail, IsOptional, IsString } from 'class-validator'; | ||||
| import * as jsonwebtoken from 'jsonwebtoken'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { config } from '../../config'; | ||||
| import { InvalidCredentialsError, PasswordNeededError, UserNotFoundError } from '../../errors/AuthError'; | ||||
| import { UsernameOrEmailNeededError } from '../../errors/UserErrors'; | ||||
| import { JwtCreator } from '../../jwtcreator'; | ||||
| import { User } from '../entities/User'; | ||||
| import { Auth } from '../responses/ResponseAuth'; | ||||
|  | ||||
| @@ -26,34 +25,24 @@ export class CreateAuth { | ||||
|             throw new UsernameOrEmailNeededError(); | ||||
|         } | ||||
|         if (!this.password) { | ||||
|             throw new PasswordNeededError() | ||||
|             throw new PasswordNeededError(); | ||||
|         } | ||||
|         const found_users = await getConnectionManager().get().getRepository(User).find({ relations: ['groups', 'permissions'], where: [{ username: this.username }, { email: this.email }] }); | ||||
|         if (found_users.length === 0) { | ||||
|             throw new UserNotFoundError() | ||||
|         } else { | ||||
|             const found_user = found_users[0] | ||||
|             if (await argon2.verify(found_user.password, this.password + found_user.uuid)) { | ||||
|                 const timestamp_accesstoken_expiry = Math.floor(Date.now() / 1000) + 5 * 60 | ||||
|                 found_user.permissions = found_user.permissions || [] | ||||
|                 delete found_user.password; | ||||
|                 newAuth.access_token = jsonwebtoken.sign({ | ||||
|                     userdetails: found_user, | ||||
|                     exp: timestamp_accesstoken_expiry | ||||
|                 }, config.jwt_secret) | ||||
|                 newAuth.access_token_expires_at = timestamp_accesstoken_expiry | ||||
|                 //  | ||||
|                 const timestamp_refresh_expiry = Math.floor(Date.now() / 1000) + 10 * 36000 | ||||
|                 newAuth.refresh_token = jsonwebtoken.sign({ | ||||
|                     refreshtokencount: found_user.refreshTokenCount, | ||||
|                     userid: found_user.id, | ||||
|                     exp: timestamp_refresh_expiry | ||||
|                 }, config.jwt_secret) | ||||
|                 newAuth.refresh_token_expires_at = timestamp_refresh_expiry | ||||
|             } else { | ||||
|                 throw new InvalidCredentialsError() | ||||
|             } | ||||
|         const found_user = await getConnectionManager().get().getRepository(User).findOne({ relations: ['groups', 'permissions', 'groups.permissions'], where: [{ username: this.username }, { email: this.email }] }); | ||||
|         if (!found_user) { | ||||
|             throw new UserNotFoundError(); | ||||
|         } | ||||
|         if (!(await argon2.verify(found_user.password, this.password + found_user.uuid))) { | ||||
|             throw new InvalidCredentialsError(); | ||||
|         } | ||||
|  | ||||
|         //Create the access token | ||||
|         const timestamp_accesstoken_expiry = Math.floor(Date.now() / 1000) + 5 * 60 | ||||
|         newAuth.access_token = JwtCreator.createAccess(found_user, timestamp_accesstoken_expiry); | ||||
|         newAuth.access_token_expires_at = timestamp_accesstoken_expiry | ||||
|         //Create the refresh token | ||||
|         const timestamp_refresh_expiry = Math.floor(Date.now() / 1000) + 10 * 36000 | ||||
|         newAuth.refresh_token = JwtCreator.createRefresh(found_user, timestamp_refresh_expiry); | ||||
|         newAuth.refresh_token_expires_at = timestamp_refresh_expiry | ||||
|         return newAuth; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										57
									
								
								src/models/actions/CreatePermission.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/models/actions/CreatePermission.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| import { | ||||
|     IsEnum, | ||||
|     IsInt, | ||||
|     IsNotEmpty | ||||
| } from "class-validator"; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { PrincipalNotFoundError } from '../../errors/PrincipalErrors'; | ||||
| import { Permission } from '../entities/Permission'; | ||||
| import { Principal } from '../entities/Principal'; | ||||
| import { PermissionAction } from '../enums/PermissionAction'; | ||||
| import { PermissionTarget } from '../enums/PermissionTargets'; | ||||
|  | ||||
| /** | ||||
|  * Defines a track of given length. | ||||
| */ | ||||
| export class CreatePermission { | ||||
|  | ||||
|     /** | ||||
|      * The permissions's principal's id. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsNotEmpty() | ||||
|     principal: number; | ||||
|  | ||||
|     /** | ||||
|      * The permissions's target. | ||||
|      */ | ||||
|     @IsNotEmpty() | ||||
|     @IsEnum(PermissionTarget) | ||||
|     target: PermissionTarget; | ||||
|  | ||||
|     /** | ||||
|      * The permissions's action. | ||||
|      */ | ||||
|     @IsNotEmpty() | ||||
|     @IsEnum(PermissionAction) | ||||
|     action: PermissionAction; | ||||
|  | ||||
|     /** | ||||
|      * Converts a Permission object based on this. | ||||
|      */ | ||||
|     public async toPermission(): Promise<Permission> { | ||||
|         let newPermission: Permission = new Permission(); | ||||
|  | ||||
|         newPermission.principal = await this.getPrincipal(); | ||||
|         newPermission.target = this.target; | ||||
|         newPermission.action = this.action; | ||||
|  | ||||
|         return newPermission; | ||||
|     } | ||||
|  | ||||
|     public async getPrincipal(): Promise<Principal> { | ||||
|         let principal = await getConnectionManager().get().getRepository(Principal).findOne({ id: this.principal }) | ||||
|         if (!principal) { throw new PrincipalNotFoundError(); } | ||||
|         return principal; | ||||
|     } | ||||
| } | ||||
| @@ -67,7 +67,7 @@ export class CreateUser { | ||||
|      * Optional. | ||||
|      */ | ||||
|     @IsOptional() | ||||
|     groupId?: number[] | number | ||||
|     group?: number[] | number | ||||
|  | ||||
|     //TODO: ProfilePics | ||||
|  | ||||
| @@ -81,30 +81,6 @@ export class CreateUser { | ||||
|             throw new UsernameOrEmailNeededError(); | ||||
|         } | ||||
|  | ||||
|         if (this.groupId) { | ||||
|             if (!Array.isArray(this.groupId)) { | ||||
|                 this.groupId = [this.groupId] | ||||
|             } | ||||
|             const groupIDs: number[] = this.groupId | ||||
|             let errors = 0 | ||||
|             const validateusergroups = async () => { | ||||
|                 let foundgroups = [] | ||||
|                 for (const g of groupIDs) { | ||||
|                     const found = await getConnectionManager().get().getRepository(UserGroup).find({ id: g }); | ||||
|                     if (found.length === 0) { | ||||
|                         errors++ | ||||
|                     } else { | ||||
|                         foundgroups.push(found[0]) | ||||
|                     } | ||||
|                 } | ||||
|                 newUser.groups = foundgroups | ||||
|             } | ||||
|             await validateusergroups() | ||||
|             if (errors !== 0) { | ||||
|                 throw new UserGroupNotFoundError(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         newUser.email = this.email | ||||
|         newUser.username = this.username | ||||
|         newUser.firstname = this.firstname | ||||
| @@ -113,8 +89,23 @@ export class CreateUser { | ||||
|         newUser.uuid = uuid.v4() | ||||
|         newUser.phone = this.phone | ||||
|         newUser.password = await argon2.hash(this.password + newUser.uuid); | ||||
|         newUser.groups = await this.getGroups(); | ||||
|         //TODO: ProfilePics | ||||
|  | ||||
|         return newUser; | ||||
|     } | ||||
|  | ||||
|     public async getGroups() { | ||||
|         if (!this.group) { return null; } | ||||
|         let groups = new Array<UserGroup>(); | ||||
|         if (!Array.isArray(this.group)) { | ||||
|             this.group = [this.group] | ||||
|         } | ||||
|         for (let group of this.group) { | ||||
|             let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group }); | ||||
|             if (!found) { throw new UserGroupNotFoundError(); } | ||||
|             groups.push(found); | ||||
|         } | ||||
|         return groups; | ||||
|     } | ||||
| } | ||||
| @@ -23,11 +23,11 @@ export class HandleLogout { | ||||
|             throw new IllegalJWTError() | ||||
|         } | ||||
|         logout.timestamp = Math.floor(Date.now() / 1000) | ||||
|         let found_user: User = await getConnectionManager().get().getRepository(User).findOne({ id: decoded["userid"] }); | ||||
|         let found_user: User = await getConnectionManager().get().getRepository(User).findOne({ id: decoded["id"] }); | ||||
|         if (!found_user) { | ||||
|             throw new UserNotFoundError() | ||||
|         } | ||||
|         if (found_user.refreshTokenCount !== decoded["refreshtokencount"]) { | ||||
|         if (found_user.refreshTokenCount !== decoded["refreshTokenCount"]) { | ||||
|             throw new RefreshTokenCountInvalidError() | ||||
|         } | ||||
|         found_user.refreshTokenCount++; | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import * as jsonwebtoken from 'jsonwebtoken'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { config } from '../../config'; | ||||
| import { IllegalJWTError, JwtNotProvidedError, RefreshTokenCountInvalidError, UserNotFoundError } from '../../errors/AuthError'; | ||||
| import { JwtCreator } from "../../jwtcreator"; | ||||
| import { User } from '../entities/User'; | ||||
| import { Auth } from '../responses/ResponseAuth'; | ||||
|  | ||||
| @@ -22,31 +23,21 @@ export class RefreshAuth { | ||||
|         } catch (error) { | ||||
|             throw new IllegalJWTError() | ||||
|         } | ||||
|         const found_user = await getConnectionManager().get().getRepository(User).findOne({ id: decoded["userid"] }, { relations: ['groups', 'permissions'] }); | ||||
|         const found_user = await getConnectionManager().get().getRepository(User).findOne({ id: decoded["id"] }, { relations: ['groups', 'permissions', 'groups.permissions'] }); | ||||
|         if (!found_user) { | ||||
|             throw new UserNotFoundError() | ||||
|         } | ||||
|         if (found_user.refreshTokenCount !== decoded["refreshtokencount"]) { | ||||
|         if (found_user.refreshTokenCount !== decoded["refreshTokenCount"]) { | ||||
|             throw new RefreshTokenCountInvalidError() | ||||
|         } | ||||
|         found_user.permissions = found_user.permissions || [] | ||||
|         delete found_user.password; | ||||
|         //Create the auth token | ||||
|         const timestamp_accesstoken_expiry = Math.floor(Date.now() / 1000) + 5 * 60 | ||||
|         delete found_user.password; | ||||
|         newAuth.access_token = jsonwebtoken.sign({ | ||||
|             userdetails: found_user, | ||||
|             exp: timestamp_accesstoken_expiry | ||||
|         }, config.jwt_secret) | ||||
|         newAuth.access_token = JwtCreator.createAccess(found_user, timestamp_accesstoken_expiry); | ||||
|         newAuth.access_token_expires_at = timestamp_accesstoken_expiry | ||||
|         //  | ||||
|         //Create the refresh token | ||||
|         const timestamp_refresh_expiry = Math.floor(Date.now() / 1000) + 10 * 36000 | ||||
|         newAuth.refresh_token = jsonwebtoken.sign({ | ||||
|             refreshtokencount: found_user.refreshTokenCount, | ||||
|             userid: found_user.id, | ||||
|             exp: timestamp_refresh_expiry | ||||
|         }, config.jwt_secret) | ||||
|         newAuth.refresh_token_expires_at = timestamp_refresh_expiry | ||||
|  | ||||
|         newAuth.refresh_token = JwtCreator.createRefresh(found_user, timestamp_refresh_expiry); | ||||
|         newAuth.refresh_token_expires_at = timestamp_refresh_expiry; | ||||
|         return newAuth; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										65
									
								
								src/models/actions/UpdatePermission.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/models/actions/UpdatePermission.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| import { IsInt, IsNotEmpty, IsObject } from 'class-validator'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { PermissionNeedsPrincipalError } from '../../errors/PermissionErrors'; | ||||
| import { PrincipalNotFoundError, PrincipalWrongTypeError } from '../../errors/PrincipalErrors'; | ||||
| import { Permission } from '../entities/Permission'; | ||||
| import { Principal } from '../entities/Principal'; | ||||
| import { PermissionAction } from '../enums/PermissionAction'; | ||||
| import { PermissionTarget } from '../enums/PermissionTargets'; | ||||
|  | ||||
| export class UpdatePermission { | ||||
|  | ||||
|     /** | ||||
|      * The updated runner's id. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     id: number; | ||||
|  | ||||
|     /** | ||||
|      * The permissions's principal's id. | ||||
|      */ | ||||
|     @IsObject() | ||||
|     @IsNotEmpty() | ||||
|     principal: Principal; | ||||
|  | ||||
|     /** | ||||
|      * The permissions's target. | ||||
|      */ | ||||
|     @IsNotEmpty() | ||||
|     target: PermissionTarget; | ||||
|  | ||||
|     /** | ||||
|      * The permissions's action. | ||||
|      */ | ||||
|     @IsNotEmpty() | ||||
|     action: PermissionAction; | ||||
|  | ||||
|     /** | ||||
|      * Converts a Permission object based on this. | ||||
|      */ | ||||
|     public async toPermission(): Promise<Permission> { | ||||
|         let newPermission: Permission = new Permission(); | ||||
|  | ||||
|         newPermission.principal = await this.getPrincipal(); | ||||
|         newPermission.target = this.target; | ||||
|         newPermission.action = this.action; | ||||
|  | ||||
|         return newPermission; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Manages all the different ways a group can be provided. | ||||
|      */ | ||||
|     public async getPrincipal(): Promise<Principal> { | ||||
|         if (this.principal === undefined) { | ||||
|             throw new PermissionNeedsPrincipalError(); | ||||
|         } | ||||
|         if (!isNaN(this.principal.id)) { | ||||
|             let principal = await getConnectionManager().get().getRepository(Principal).findOne({ id: this.principal.id }); | ||||
|             if (!principal) { throw new PrincipalNotFoundError(); } | ||||
|             return principal; | ||||
|         } | ||||
|  | ||||
|         throw new PrincipalWrongTypeError(); | ||||
|     } | ||||
| } | ||||
| @@ -1,17 +1,17 @@ | ||||
| import { | ||||
|   IsEnum, | ||||
|   IsInt, | ||||
|   IsNotEmpty, | ||||
|  | ||||
|   IsString | ||||
|   IsNotEmpty | ||||
| } from "class-validator"; | ||||
| import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; | ||||
| import { User } from './User'; | ||||
| import { UserGroup } from './UserGroup'; | ||||
| import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; | ||||
| import { PermissionAction } from '../enums/PermissionAction'; | ||||
| import { PermissionTarget } from '../enums/PermissionTargets'; | ||||
| import { Principal } from './Principal'; | ||||
| /** | ||||
|  * Defines the Permission interface. | ||||
| */ | ||||
| @Entity() | ||||
| export abstract class Permission { | ||||
| export class Permission { | ||||
|   /** | ||||
|    * Autogenerated unique id (primary key). | ||||
|    */ | ||||
| @@ -20,30 +20,30 @@ export abstract class Permission { | ||||
|   id: number; | ||||
|  | ||||
|   /** | ||||
|    * users | ||||
|    * The permissions principal | ||||
|    */ | ||||
|   @OneToMany(() => User, user => user.permissions, { nullable: true }) | ||||
|   users: User[] | ||||
|  | ||||
|   /** | ||||
|    * groups | ||||
|    */ | ||||
|   @OneToMany(() => UserGroup, group => group.permissions, { nullable: true }) | ||||
|   groups: UserGroup[] | ||||
|   @ManyToOne(() => Principal, principal => principal.permissions) | ||||
|   principal: Principal; | ||||
|  | ||||
|   /** | ||||
|    * The target | ||||
|    */ | ||||
|   @Column() | ||||
|   @Column({ type: 'varchar' }) | ||||
|   @IsNotEmpty() | ||||
|   @IsString() | ||||
|   target: string; | ||||
|   @IsEnum(PermissionTarget) | ||||
|   target: PermissionTarget; | ||||
|  | ||||
|   /** | ||||
|    * The action type | ||||
|    */ | ||||
|   @Column() | ||||
|   @IsNotEmpty() | ||||
|   @IsString() | ||||
|   action: string; | ||||
|   @Column({ type: 'varchar' }) | ||||
|   @IsEnum(PermissionAction) | ||||
|   action: PermissionAction; | ||||
|  | ||||
|   /** | ||||
|    * Turn this into a string for exporting (and jwts). | ||||
|    */ | ||||
|   public toString(): string { | ||||
|     return this.target + ":" + this.action; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										26
									
								
								src/models/entities/Principal.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/models/entities/Principal.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| import { IsInt } from 'class-validator'; | ||||
| import { Entity, OneToMany, PrimaryGeneratedColumn, TableInheritance } from 'typeorm'; | ||||
| import { ResponsePrincipal } from '../responses/ResponsePrincipal'; | ||||
| import { Permission } from './Permission'; | ||||
|  | ||||
| /** | ||||
|  * Defines a admin user. | ||||
| */ | ||||
| @Entity() | ||||
| @TableInheritance({ column: { name: "type", type: "varchar" } }) | ||||
| export abstract class Principal { | ||||
|   /** | ||||
|   * autogenerated unique id (primary key). | ||||
|   */ | ||||
|   @PrimaryGeneratedColumn() | ||||
|   @IsInt() | ||||
|   id: number; | ||||
|  | ||||
|   /** | ||||
| * permissions | ||||
| */ | ||||
|   @OneToMany(() => Permission, permission => permission.principal, { nullable: true }) | ||||
|   permissions: Permission[]; | ||||
|  | ||||
|   public abstract toResponse(): ResponsePrincipal; | ||||
| } | ||||
| @@ -1,22 +1,17 @@ | ||||
| import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUUID } from "class-validator"; | ||||
| import { Column, Entity, JoinTable, ManyToMany, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; | ||||
| import { ChildEntity, Column, JoinTable, ManyToMany, OneToMany } from "typeorm"; | ||||
| import { config } from '../../config'; | ||||
| import { Permission } from './Permission'; | ||||
| import { ResponsePrincipal } from '../responses/ResponsePrincipal'; | ||||
| import { ResponseUser } from '../responses/ResponseUser'; | ||||
| import { Principal } from './Principal'; | ||||
| import { UserAction } from './UserAction'; | ||||
| import { UserGroup } from './UserGroup'; | ||||
|  | ||||
| /** | ||||
|  * Defines a admin user. | ||||
| */ | ||||
| @Entity() | ||||
| export class User { | ||||
|   /** | ||||
|   * autogenerated unique id (primary key). | ||||
|   */ | ||||
|   @PrimaryGeneratedColumn() | ||||
|   @IsInt() | ||||
|   id: number; | ||||
|  | ||||
| @ChildEntity() | ||||
| export class User extends Principal { | ||||
|   /** | ||||
|   * uuid | ||||
|   */ | ||||
| @@ -78,13 +73,6 @@ export class User { | ||||
|   @IsNotEmpty() | ||||
|   password: string; | ||||
|  | ||||
|   /** | ||||
|   * permissions | ||||
|   */ | ||||
|   @IsOptional() | ||||
|   @ManyToOne(() => Permission, permission => permission.users, { nullable: true }) | ||||
|   permissions?: Permission[]; | ||||
|  | ||||
|   /** | ||||
|   * groups | ||||
|   */ | ||||
| @@ -123,20 +111,9 @@ export class User { | ||||
|   actions: UserAction[] | ||||
|  | ||||
|   /** | ||||
|    * calculate all permissions | ||||
|    * Turn this into a response. | ||||
|    */ | ||||
|   public get calc_permissions(): Permission[] { | ||||
|     let final_permissions = [] | ||||
|     this.groups.forEach((permission) => { | ||||
|       if (!final_permissions.includes(permission)) { | ||||
|         final_permissions.push(permission) | ||||
|       } | ||||
|     }) | ||||
|     this.permissions.forEach((permission) => { | ||||
|       if (!final_permissions.includes(permission)) { | ||||
|         final_permissions.push(permission) | ||||
|       } | ||||
|     }) | ||||
|     return final_permissions | ||||
|   public toResponse(): ResponsePrincipal { | ||||
|     return new ResponseUser(this); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,29 +1,18 @@ | ||||
| import { | ||||
|   IsInt, | ||||
|   IsNotEmpty, | ||||
|   IsOptional, | ||||
|   IsString | ||||
| } from "class-validator"; | ||||
| import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; | ||||
| import { Permission } from "./Permission"; | ||||
| import { ChildEntity, Column } from "typeorm"; | ||||
| import { ResponsePrincipal } from '../responses/ResponsePrincipal'; | ||||
| import { ResponseUserGroup } from '../responses/ResponseUserGroup'; | ||||
| import { Principal } from './Principal'; | ||||
|  | ||||
| /** | ||||
|  * Defines the UserGroup interface. | ||||
| */ | ||||
| @Entity() | ||||
| export class UserGroup { | ||||
|   /** | ||||
|    * Autogenerated unique id (primary key). | ||||
|    */ | ||||
|   @PrimaryGeneratedColumn() | ||||
|   @IsInt() | ||||
|   id: number; | ||||
|  | ||||
|   /** | ||||
|      * permissions | ||||
|      */ | ||||
|   @ManyToOne(() => Permission, permission => permission.groups, { nullable: true }) | ||||
|   permissions: Permission[]; | ||||
| @ChildEntity() | ||||
| export class UserGroup extends Principal { | ||||
|  | ||||
|   /** | ||||
|    * The group's name | ||||
| @@ -40,4 +29,8 @@ export class UserGroup { | ||||
|   @IsOptional() | ||||
|   @IsString() | ||||
|   description?: string; | ||||
|  | ||||
|   public toResponse(): ResponsePrincipal { | ||||
|     return new ResponseUserGroup(this); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										7
									
								
								src/models/enums/PermissionAction.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/models/enums/PermissionAction.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| export enum PermissionAction { | ||||
|     GET = 'GET', | ||||
|     CREATE = 'CREATE', | ||||
|     UPDATE = 'UPDATE', | ||||
|     DELETE = 'DELETE', | ||||
|     IMPORT = 'IMPORT' | ||||
| } | ||||
							
								
								
									
										9
									
								
								src/models/enums/PermissionTargets.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/models/enums/PermissionTargets.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| export enum PermissionTarget { | ||||
|     RUNNER = 'RUNNER', | ||||
|     ORGANISATION = 'ORGANISATION', | ||||
|     TEAM = 'TEAM', | ||||
|     TRACK = 'TRACK', | ||||
|     USER = 'USER', | ||||
|     GROUP = 'USERGROUP', | ||||
|     PERMISSION = 'PERMISSION' | ||||
| } | ||||
| @@ -15,7 +15,7 @@ export abstract class ResponseParticipant { | ||||
|      * Autogenerated unique id (primary key). | ||||
|      */ | ||||
|     @IsInt() | ||||
|     id: number;; | ||||
|     id: number; | ||||
|  | ||||
|     /** | ||||
|      * The participant's first name. | ||||
|   | ||||
							
								
								
									
										49
									
								
								src/models/responses/ResponsePermission.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/models/responses/ResponsePermission.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| import { | ||||
|     IsEnum, | ||||
|     IsInt, | ||||
|     IsNotEmpty, | ||||
|     IsObject | ||||
| } from "class-validator"; | ||||
| import { Permission } from '../entities/Permission'; | ||||
| import { PermissionAction } from '../enums/PermissionAction'; | ||||
| import { PermissionTarget } from '../enums/PermissionTargets'; | ||||
| import { ResponsePrincipal } from './ResponsePrincipal'; | ||||
|  | ||||
| /** | ||||
|  * Defines a track of given length. | ||||
| */ | ||||
| export class ResponsePermission { | ||||
|     /** | ||||
|      * Autogenerated unique id (primary key). | ||||
|      */ | ||||
|     @IsInt() | ||||
|     id: number;; | ||||
|  | ||||
|     /** | ||||
|      * The permissions's principal. | ||||
|      */ | ||||
|     @IsObject() | ||||
|     @IsNotEmpty() | ||||
|     principal: ResponsePrincipal; | ||||
|  | ||||
|     /** | ||||
|      * The permissions's target. | ||||
|      */ | ||||
|     @IsNotEmpty() | ||||
|     @IsEnum(PermissionTarget) | ||||
|     target: PermissionTarget; | ||||
|  | ||||
|     /** | ||||
|      * The permissions's action. | ||||
|      */ | ||||
|     @IsNotEmpty() | ||||
|     @IsEnum(PermissionAction) | ||||
|     action: PermissionAction; | ||||
|  | ||||
|     public constructor(permission: Permission) { | ||||
|         this.id = permission.id; | ||||
|         this.principal = permission.principal.toResponse(); | ||||
|         this.target = permission.target; | ||||
|         this.action = permission.action; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										20
									
								
								src/models/responses/ResponsePrincipal.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/models/responses/ResponsePrincipal.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| import { | ||||
|     IsInt | ||||
| } from "class-validator"; | ||||
| import { Principal } from '../entities/Principal'; | ||||
|  | ||||
| /** | ||||
|  * Defines Principal's response class. | ||||
| */ | ||||
| export abstract class ResponsePrincipal { | ||||
|  | ||||
|     /** | ||||
|      * Autogenerated unique id (primary key). | ||||
|      */ | ||||
|     @IsInt() | ||||
|     id: number; | ||||
|  | ||||
|     public constructor(principal: Principal) { | ||||
|         this.id = principal.id; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										89
									
								
								src/models/responses/ResponseUser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/models/responses/ResponseUser.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| import { | ||||
|     IsArray, | ||||
|     IsBoolean, | ||||
|  | ||||
|     IsOptional, | ||||
|     IsString | ||||
| } from "class-validator"; | ||||
| import { Permission } from '../entities/Permission'; | ||||
| import { User } from '../entities/User'; | ||||
| import { UserGroup } from '../entities/UserGroup'; | ||||
| import { ResponsePrincipal } from './ResponsePrincipal'; | ||||
|  | ||||
| /** | ||||
|  * Defines a user response. | ||||
| */ | ||||
| export class ResponseUser extends ResponsePrincipal { | ||||
|     /** | ||||
|      * The user's first name. | ||||
|      */ | ||||
|     @IsString() | ||||
|     firstname: string; | ||||
|  | ||||
|     /** | ||||
|      * The user's middle name. | ||||
|      * Optional. | ||||
|      */ | ||||
|     @IsString() | ||||
|     middlename?: string; | ||||
|  | ||||
|     /** | ||||
|      * The user's last name. | ||||
|      */ | ||||
|     @IsString() | ||||
|     lastname: string; | ||||
|  | ||||
|     /** | ||||
|      * The user's phone number. | ||||
|      * Optional. | ||||
|      */ | ||||
|     @IsString() | ||||
|     phone?: string; | ||||
|  | ||||
|     /** | ||||
|      * The user's e-mail address. | ||||
|      * Optional. | ||||
|      */ | ||||
|     @IsString() | ||||
|     email?: string; | ||||
|  | ||||
|     /** | ||||
|     * is user enabled? | ||||
|     */ | ||||
|     @IsBoolean() | ||||
|     enabled: boolean = true; | ||||
|  | ||||
|     /** | ||||
|     * profilepic | ||||
|     */ | ||||
|     @IsString() | ||||
|     @IsOptional() | ||||
|     profilePic?: string; | ||||
|  | ||||
|     /** | ||||
|      * Groups | ||||
|      */ | ||||
|     @IsArray() | ||||
|     @IsOptional() | ||||
|     groups: UserGroup[]; | ||||
|  | ||||
|     /** | ||||
|      * permissions | ||||
|      */ | ||||
|     @IsArray() | ||||
|     @IsOptional() | ||||
|     permissions: Permission[]; | ||||
|  | ||||
|     public constructor(user: User) { | ||||
|         super(user); | ||||
|         this.firstname = user.firstname; | ||||
|         this.middlename = user.middlename; | ||||
|         this.lastname = user.lastname; | ||||
|         this.phone = user.phone; | ||||
|         this.email = user.email; | ||||
|         this.enabled = user.enabled; | ||||
|         this.profilePic = user.profilePic; | ||||
|         this.groups = user.groups; | ||||
|         this.permissions = user.permissions; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										45
									
								
								src/models/responses/ResponseUserGroup.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/models/responses/ResponseUserGroup.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| import { | ||||
|     IsArray, | ||||
|  | ||||
|  | ||||
|     IsNotEmpty, | ||||
|  | ||||
|     IsOptional, | ||||
|     IsString | ||||
| } from "class-validator"; | ||||
| import { Permission } from '../entities/Permission'; | ||||
| import { UserGroup } from '../entities/UserGroup'; | ||||
| import { ResponsePrincipal } from './ResponsePrincipal'; | ||||
|  | ||||
| /** | ||||
|  * Defines a user response. | ||||
| */ | ||||
| export class ResponseUserGroup extends ResponsePrincipal { | ||||
|     /** | ||||
|      * The group's name | ||||
|      */ | ||||
|     @IsNotEmpty() | ||||
|     @IsString() | ||||
|     name: string; | ||||
|  | ||||
|     /** | ||||
|      * The group's description | ||||
|      */ | ||||
|     @IsOptional() | ||||
|     @IsString() | ||||
|     description?: string; | ||||
|  | ||||
|     /** | ||||
|      * permissions | ||||
|      */ | ||||
|     @IsArray() | ||||
|     @IsOptional() | ||||
|     permissions: Permission[]; | ||||
|  | ||||
|     public constructor(group: UserGroup) { | ||||
|         super(group); | ||||
|         this.name = group.name; | ||||
|         this.description = group.description; | ||||
|         this.permissions = group.permissions; | ||||
|     } | ||||
| } | ||||
| @@ -1,20 +1,48 @@ | ||||
| import { Connection } from 'typeorm'; | ||||
| import { Factory, Seeder } from 'typeorm-seeding'; | ||||
| import { CreatePermission } from '../models/actions/CreatePermission'; | ||||
| import { CreateUser } from '../models/actions/CreateUser'; | ||||
| import { CreateUserGroup } from '../models/actions/CreateUserGroup'; | ||||
| import { Permission } from '../models/entities/Permission'; | ||||
| import { User } from '../models/entities/User'; | ||||
| import { UserGroup } from '../models/entities/UserGroup'; | ||||
| import { PermissionAction } from '../models/enums/PermissionAction'; | ||||
| import { PermissionTarget } from '../models/enums/PermissionTargets'; | ||||
|  | ||||
| export default class SeedUsers implements Seeder { | ||||
|     public async run(factory: Factory, connection: Connection): Promise<any> { | ||||
|         let adminGroup: UserGroup = await this.createAdminGroup(connection); | ||||
|         await this.createUser(connection, adminGroup.id); | ||||
|         await this.createPermissions(connection, adminGroup.id); | ||||
|     } | ||||
|  | ||||
|     public async createAdminGroup(connection: Connection) { | ||||
|         let adminGroup = new CreateUserGroup(); | ||||
|         adminGroup.name = "ADMINS"; | ||||
|         adminGroup.description = "Has all possible permissions"; | ||||
|         return await connection.getRepository(UserGroup).save(await adminGroup.toUserGroup()); | ||||
|     } | ||||
|  | ||||
|     public async createUser(connection: Connection, group: number) { | ||||
|         let initialUser = new CreateUser(); | ||||
|         initialUser.firstname = "demo"; | ||||
|         initialUser.lastname = "demo"; | ||||
|         initialUser.username = "demo"; | ||||
|         initialUser.password = "demo"; | ||||
|         await connection | ||||
|             .createQueryBuilder() | ||||
|             .insert() | ||||
|             .into(User) | ||||
|             .values([await initialUser.toUser()]) | ||||
|             .execute() | ||||
|         initialUser.group = group; | ||||
|         return await connection.getRepository(User).save(await initialUser.toUser()); | ||||
|     } | ||||
|  | ||||
|     public async createPermissions(connection: Connection, principal: number) { | ||||
|         let repo = await connection.getRepository(Permission); | ||||
|         for (let target in PermissionTarget) { | ||||
|             for (let action in PermissionAction) { | ||||
|                 let permission = new CreatePermission; | ||||
|                 permission.target = <PermissionTarget>target; | ||||
|                 permission.action = <PermissionAction>action; | ||||
|                 permission.principal = principal; | ||||
|                 await repo.save(await permission.toPermission()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -2,6 +2,18 @@ import axios from 'axios'; | ||||
| import { config } from '../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
|  | ||||
| let access_token; | ||||
| let axios_config; | ||||
|  | ||||
| beforeAll(async () => { | ||||
| 	const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
| 	access_token = res.data["access_token"]; | ||||
| 	axios_config = { | ||||
| 		headers: { "authorization": "Bearer " + access_token }, | ||||
| 		validateStatus: undefined | ||||
| 	}; | ||||
| }); | ||||
|  | ||||
| describe('GET /api/openapi.json', () => { | ||||
| 	it('is http 200', async () => { | ||||
| 		const res = await axios.get(base + '/api/openapi.json'); | ||||
| @@ -10,13 +22,13 @@ describe('GET /api/openapi.json', () => { | ||||
| }); | ||||
| describe('GET /', () => { | ||||
| 	it('is http 404', async () => { | ||||
| 		const res = await axios.get(base + '/', { validateStatus: undefined }); | ||||
| 		const res = await axios.get(base + '/', axios_config); | ||||
| 		expect(res.status).toEqual(404); | ||||
| 	}); | ||||
| }); | ||||
| describe('GET /api/teams', () => { | ||||
| 	it('is http 200 && is json', async () => { | ||||
| 		const res = await axios.get(base + '/api/teams', { validateStatus: undefined }); | ||||
| 		const res = await axios.get(base + '/api/teams', axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json") | ||||
| 	}); | ||||
|   | ||||
| @@ -2,9 +2,21 @@ import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
|  | ||||
| let access_token; | ||||
| let axios_config; | ||||
|  | ||||
| beforeAll(async () => { | ||||
|     const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
|     access_token = res.data["access_token"]; | ||||
|     axios_config = { | ||||
|         headers: { "authorization": "Bearer " + access_token }, | ||||
|         validateStatus: undefined | ||||
|     }; | ||||
| }); | ||||
|  | ||||
| describe('GET /api/organisations', () => { | ||||
|     it('basic get should return 200', async () => { | ||||
|         const res = await axios.get(base + '/api/organisations'); | ||||
|         const res = await axios.get(base + '/api/organisations', axios_config); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -14,14 +26,14 @@ describe('POST /api/organisations', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('creating a new org with without a name should return 400', async () => { | ||||
|         const res = await axios.post(base + '/api/organisations', { | ||||
|             "name": null | ||||
|         }, { validateStatus: undefined }); | ||||
|         }, axios_config); | ||||
|         expect(res.status).toEqual(400); | ||||
|         expect(res.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -31,12 +43,12 @@ describe('adding + getting from all orgs', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('check if org was added', async () => { | ||||
|         const res = await axios.get(base + '/api/organisations'); | ||||
|         const res = await axios.get(base + '/api/organisations', axios_config); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json") | ||||
|         let added_org = res.data[res.data.length - 1] | ||||
| @@ -55,14 +67,14 @@ describe('adding + getting explicitly', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res1 = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         let added_org = res1.data | ||||
|         added_org_id = added_org.id; | ||||
|         expect(res1.status).toEqual(200); | ||||
|         expect(res1.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('check if org was added', async () => { | ||||
|         const res2 = await axios.get(base + '/api/organisations/' + added_org_id); | ||||
|         const res2 = await axios.get(base + '/api/organisations/' + added_org_id, axios_config); | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|         let added_org2 = res2.data | ||||
|   | ||||
| @@ -2,10 +2,22 @@ import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
|  | ||||
| let access_token; | ||||
| let axios_config; | ||||
|  | ||||
| beforeAll(async () => { | ||||
|     const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
|     access_token = res.data["access_token"]; | ||||
|     axios_config = { | ||||
|         headers: { "authorization": "Bearer " + access_token }, | ||||
|         validateStatus: undefined | ||||
|     }; | ||||
| }); | ||||
|  | ||||
| // --------------- | ||||
| describe('adding + deletion (non-existant)', () => { | ||||
|     it('delete', async () => { | ||||
|         const res2 = await axios.delete(base + '/api/organisations/0', { validateStatus: undefined }); | ||||
|         const res2 = await axios.delete(base + '/api/organisations/0', axios_config); | ||||
|         expect(res2.status).toEqual(204); | ||||
|     }); | ||||
| }); | ||||
| @@ -16,14 +28,14 @@ describe('adding + deletion (successfull)', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res1 = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         added_org = res1.data | ||||
|         added_org_id = added_org.id; | ||||
|         expect(res1.status).toEqual(200); | ||||
|         expect(res1.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('delete', async () => { | ||||
|         const res2 = await axios.delete(base + '/api/organisations/' + added_org_id); | ||||
|         const res2 = await axios.delete(base + '/api/organisations/' + added_org_id, axios_config); | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|         let added_org2 = res2.data | ||||
| @@ -37,7 +49,7 @@ describe('adding + deletion (successfull)', () => { | ||||
|         }); | ||||
|     }); | ||||
|     it('check if org really was deleted', async () => { | ||||
|         const res3 = await axios.get(base + '/api/organisations/' + added_org_id, { validateStatus: undefined }); | ||||
|         const res3 = await axios.get(base + '/api/organisations/' + added_org_id, axios_config); | ||||
|         expect(res3.status).toEqual(404); | ||||
|         expect(res3.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -51,7 +63,7 @@ describe('adding + deletion with teams still existing (without force)', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res1 = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         added_org = res1.data; | ||||
|         added_org_id = added_org.id; | ||||
|         expect(res1.status).toEqual(200); | ||||
| @@ -61,14 +73,14 @@ describe('adding + deletion with teams still existing (without force)', () => { | ||||
|         const res2 = await axios.post(base + '/api/teams', { | ||||
|             "name": "test123", | ||||
|             "parentGroup": added_org_id | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         added_team = res2.data; | ||||
|         added_team_id = added_team.id; | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('delete org - this should fail with a 406', async () => { | ||||
|         const res2 = await axios.delete(base + '/api/organisations/' + added_org_id, { validateStatus: undefined }); | ||||
|         const res2 = await axios.delete(base + '/api/organisations/' + added_org_id, axios_config); | ||||
|         expect(res2.status).toEqual(406); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -82,7 +94,7 @@ describe('adding + deletion with teams still existing (with force)', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res1 = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         added_org = res1.data; | ||||
|         added_org_id = added_org.id; | ||||
|         expect(res1.status).toEqual(200); | ||||
| @@ -92,14 +104,14 @@ describe('adding + deletion with teams still existing (with force)', () => { | ||||
|         const res2 = await axios.post(base + '/api/teams', { | ||||
|             "name": "test123", | ||||
|             "parentGroup": added_org_id | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         added_team = res2.data; | ||||
|         added_team_id = added_team.id; | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('delete', async () => { | ||||
|         const res2 = await axios.delete(base + '/api/organisations/' + added_org_id + '?force=true'); | ||||
|         const res2 = await axios.delete(base + '/api/organisations/' + added_org_id + '?force=true', axios_config); | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|         let added_org2 = res2.data | ||||
| @@ -113,7 +125,7 @@ describe('adding + deletion with teams still existing (with force)', () => { | ||||
|         }); | ||||
|     }); | ||||
|     it('check if org really was deleted', async () => { | ||||
|         const res3 = await axios.get(base + '/api/organisations/' + added_org_id, { validateStatus: undefined }); | ||||
|         const res3 = await axios.get(base + '/api/organisations/' + added_org_id, axios_config); | ||||
|         expect(res3.status).toEqual(404); | ||||
|         expect(res3.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|   | ||||
| @@ -2,9 +2,21 @@ import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
|  | ||||
| let access_token; | ||||
| let axios_config; | ||||
|  | ||||
| beforeAll(async () => { | ||||
|     const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
|     access_token = res.data["access_token"]; | ||||
|     axios_config = { | ||||
|         headers: { "authorization": "Bearer " + access_token }, | ||||
|         validateStatus: undefined | ||||
|     }; | ||||
| }); | ||||
|  | ||||
| describe('GET /api/organisations', () => { | ||||
|     it('basic get should return 200', async () => { | ||||
|         const res = await axios.get(base + '/api/organisations'); | ||||
|         const res = await axios.get(base + '/api/organisations', axios_config); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -12,7 +24,7 @@ describe('GET /api/organisations', () => { | ||||
| // --------------- | ||||
| describe('GET /api/organisations/0', () => { | ||||
|     it('basic get should return 404', async () => { | ||||
|         const res = await axios.get(base + '/api/runners/0', { validateStatus: undefined }); | ||||
|         const res = await axios.get(base + '/api/runners/0', axios_config); | ||||
|         expect(res.status).toEqual(404); | ||||
|         expect(res.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|   | ||||
| @@ -2,6 +2,18 @@ import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
|  | ||||
| let access_token; | ||||
| let axios_config; | ||||
|  | ||||
| beforeAll(async () => { | ||||
|     const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
|     access_token = res.data["access_token"]; | ||||
|     axios_config = { | ||||
|         headers: { "authorization": "Bearer " + access_token }, | ||||
|         validateStatus: undefined | ||||
|     }; | ||||
| }); | ||||
|  | ||||
| // --------------- | ||||
| describe('adding + updating name', () => { | ||||
|     let added_org_id | ||||
| @@ -9,7 +21,7 @@ describe('adding + updating name', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res1 = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         added_org = res1.data | ||||
|         added_org_id = added_org.id; | ||||
|         expect(res1.status).toEqual(200); | ||||
| @@ -21,7 +33,7 @@ describe('adding + updating name', () => { | ||||
|             "name": "testlelele", | ||||
|             "contact": null, | ||||
|             "address": null, | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|         let added_org2 = res2.data | ||||
| @@ -42,7 +54,7 @@ describe('adding + try updating id (should return 406)', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res1 = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         added_org = res1.data | ||||
|         added_org_id = added_org.id; | ||||
|         expect(res1.status).toEqual(200); | ||||
| @@ -54,7 +66,7 @@ describe('adding + try updating id (should return 406)', () => { | ||||
|             "name": "testlelele", | ||||
|             "contact": null, | ||||
|             "address": null, | ||||
|         }, { validateStatus: undefined }); | ||||
|         }, axios_config); | ||||
|         expect(res2.status).toEqual(406); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|   | ||||
| @@ -2,9 +2,21 @@ import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
|  | ||||
| let access_token; | ||||
| let axios_config; | ||||
|  | ||||
| beforeAll(async () => { | ||||
|     const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
|     access_token = res.data["access_token"]; | ||||
|     axios_config = { | ||||
|         headers: { "authorization": "Bearer " + access_token }, | ||||
|         validateStatus: undefined | ||||
|     }; | ||||
| }); | ||||
|  | ||||
| describe('GET /api/teams', () => { | ||||
|     it('basic get should return 200', async () => { | ||||
|         const res = await axios.get(base + '/api/teams'); | ||||
|         const res = await axios.get(base + '/api/teams', axios_config); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -12,7 +24,7 @@ describe('GET /api/teams', () => { | ||||
| // --------------- | ||||
| describe('GET /api/teams/0', () => { | ||||
|     it('basic get should return 404', async () => { | ||||
|         const res = await axios.get(base + '/api/teams/0', { validateStatus: undefined }); | ||||
|         const res = await axios.get(base + '/api/teams/0', axios_config); | ||||
|         expect(res.status).toEqual(404); | ||||
|         expect(res.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -22,14 +34,14 @@ describe('POST /api/teams with errors', () => { | ||||
|     it('creating a new team without a name should return 400', async () => { | ||||
|         const res1 = await axios.post(base + '/api/teams', { | ||||
|             "name": null | ||||
|         }, { validateStatus: undefined }); | ||||
|         }, axios_config); | ||||
|         expect(res1.status).toEqual(400); | ||||
|         expect(res1.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('creating a new team without a org should return 400', async () => { | ||||
|         const res2 = await axios.post(base + '/api/teams', { | ||||
|             "name": "test_team" | ||||
|         }, { validateStatus: undefined }); | ||||
|         }, axios_config); | ||||
|         expect(res2.status).toEqual(400); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -37,7 +49,7 @@ describe('POST /api/teams with errors', () => { | ||||
|         const res3 = await axios.post(base + '/api/teams', { | ||||
|             "name": "test_team", | ||||
|             "parentGroup": -1 | ||||
|         }, { validateStatus: undefined }); | ||||
|         }, axios_config); | ||||
|         expect(res3.status).toEqual(404); | ||||
|         expect(res3.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -48,7 +60,7 @@ describe('POST /api/teams working', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res1 = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         let added_org = res1.data | ||||
|         added_org_id = added_org.id; | ||||
|         expect(res1.status).toEqual(200); | ||||
| @@ -58,7 +70,7 @@ describe('POST /api/teams working', () => { | ||||
|         const res2 = await axios.post(base + '/api/teams', { | ||||
|             "name": "test_team", | ||||
|             "parentGroup": added_org_id | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|   | ||||
| @@ -2,6 +2,18 @@ import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
|  | ||||
| let access_token; | ||||
| let axios_config; | ||||
|  | ||||
| beforeAll(async () => { | ||||
|     const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
|     access_token = res.data["access_token"]; | ||||
|     axios_config = { | ||||
|         headers: { "authorization": "Bearer " + access_token }, | ||||
|         validateStatus: undefined | ||||
|     }; | ||||
| }); | ||||
|  | ||||
| // --------------- | ||||
| describe('adding org', () => { | ||||
|     let added_org; | ||||
| @@ -11,7 +23,7 @@ describe('adding org', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res1 = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         added_org = res1.data; | ||||
|         added_org_id = added_org.id; | ||||
|         expect(res1.status).toEqual(200); | ||||
| @@ -21,14 +33,14 @@ describe('adding org', () => { | ||||
|         const res2 = await axios.post(base + '/api/teams', { | ||||
|             "name": "test123", | ||||
|             "parentGroup": added_org_id | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         added_team = res2.data; | ||||
|         added_team_id = added_team.id; | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('delete team', async () => { | ||||
|         const res2 = await axios.delete(base + '/api/teams/' + added_team_id); | ||||
|         const res2 = await axios.delete(base + '/api/teams/' + added_team_id, axios_config); | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|         let deleted_team = res2.data | ||||
| @@ -40,7 +52,7 @@ describe('adding org', () => { | ||||
|         }); | ||||
|     }); | ||||
|     it('check if team really was deleted', async () => { | ||||
|         const res3 = await axios.get(base + '/api/teams/' + added_team_id, { validateStatus: undefined }); | ||||
|         const res3 = await axios.get(base + '/api/teams/' + added_team_id, axios_config); | ||||
|         expect(res3.status).toEqual(404); | ||||
|         expect(res3.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -48,7 +60,7 @@ describe('adding org', () => { | ||||
| // --------------- | ||||
| describe('adding + deletion (non-existant)', () => { | ||||
|     it('delete', async () => { | ||||
|         const res2 = await axios.delete(base + '/api/teams/0', { validateStatus: undefined }); | ||||
|         const res2 = await axios.delete(base + '/api/teams/0', axios_config); | ||||
|         expect(res2.status).toEqual(204); | ||||
|     }); | ||||
| }); | ||||
|   | ||||
| @@ -2,9 +2,21 @@ import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
|  | ||||
| let access_token; | ||||
| let axios_config; | ||||
|  | ||||
| beforeAll(async () => { | ||||
|     const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
|     access_token = res.data["access_token"]; | ||||
|     axios_config = { | ||||
|         headers: { "authorization": "Bearer " + access_token }, | ||||
|         validateStatus: undefined | ||||
|     }; | ||||
| }); | ||||
|  | ||||
| describe('GET /api/teams', () => { | ||||
|     it('basic get should return 200', async () => { | ||||
|         const res = await axios.get(base + '/api/teams'); | ||||
|         const res = await axios.get(base + '/api/teams', axios_config); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -12,7 +24,7 @@ describe('GET /api/teams', () => { | ||||
| // --------------- | ||||
| describe('GET /api/teams/0', () => { | ||||
|     it('basic get should return 404', async () => { | ||||
|         const res = await axios.get(base + '/api/teams/0', { validateStatus: undefined }); | ||||
|         const res = await axios.get(base + '/api/teams/0', axios_config); | ||||
|         expect(res.status).toEqual(404); | ||||
|         expect(res.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|   | ||||
| @@ -2,6 +2,17 @@ import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
|  | ||||
| let access_token; | ||||
| let axios_config; | ||||
|  | ||||
| beforeAll(async () => { | ||||
|     const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
|     access_token = res.data["access_token"]; | ||||
|     axios_config = { | ||||
|         headers: { "authorization": "Bearer " + access_token }, | ||||
|         validateStatus: undefined | ||||
|     }; | ||||
| }); | ||||
| // --------------- | ||||
| describe('adding + updating name', () => { | ||||
|     let added_org; | ||||
| @@ -11,7 +22,7 @@ describe('adding + updating name', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res1 = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         added_org = res1.data; | ||||
|         added_org_id = added_org.id; | ||||
|         expect(res1.status).toEqual(200); | ||||
| @@ -21,7 +32,7 @@ describe('adding + updating name', () => { | ||||
|         const res2 = await axios.post(base + '/api/teams', { | ||||
|             "name": "test123", | ||||
|             "parentGroup": added_org_id | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         added_team = res2.data; | ||||
|         added_team_id = added_team.id; | ||||
|         expect(res2.status).toEqual(200); | ||||
| @@ -33,7 +44,7 @@ describe('adding + updating name', () => { | ||||
|             "name": "testlelele", | ||||
|             "contact": null, | ||||
|             "parentGroup": added_org | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         expect(res3.status).toEqual(200); | ||||
|         expect(res3.headers['content-type']).toContain("application/json") | ||||
|         let updated_team = res3.data; | ||||
| @@ -50,7 +61,7 @@ describe('adding + try updating id (should return 406)', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res1 = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         added_org = res1.data; | ||||
|         added_org_id = added_org.id; | ||||
|         expect(res1.status).toEqual(200); | ||||
| @@ -60,7 +71,7 @@ describe('adding + try updating id (should return 406)', () => { | ||||
|         const res2 = await axios.post(base + '/api/teams', { | ||||
|             "name": "test123", | ||||
|             "parentGroup": added_org_id | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         added_team = res2.data; | ||||
|         added_team_id = added_team.id; | ||||
|         expect(res2.status).toEqual(200); | ||||
| @@ -68,7 +79,7 @@ describe('adding + try updating id (should return 406)', () => { | ||||
|     }); | ||||
|     it('update team', async () => { | ||||
|         added_team.id = added_team.id + 1; | ||||
|         const res3 = await axios.put(base + '/api/teams/' + added_team_id, added_team, { validateStatus: undefined }); | ||||
|         const res3 = await axios.put(base + '/api/teams/' + added_team_id, added_team, axios_config); | ||||
|         expect(res3.status).toEqual(406); | ||||
|         expect(res3.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -82,7 +93,7 @@ describe('add+update parent org (valid)', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res1 = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         added_org = res1.data; | ||||
|         expect(res1.status).toEqual(200); | ||||
|         expect(res1.headers['content-type']).toContain("application/json") | ||||
| @@ -91,7 +102,7 @@ describe('add+update parent org (valid)', () => { | ||||
|         const res2 = await axios.post(base + '/api/teams', { | ||||
|             "name": "test123", | ||||
|             "parentGroup": added_org.id | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         added_team = res2.data; | ||||
|         added_team_id = added_team.id; | ||||
|         expect(res2.status).toEqual(200); | ||||
| @@ -100,14 +111,14 @@ describe('add+update parent org (valid)', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res3 = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         added_org2 = res3.data; | ||||
|         expect(res3.status).toEqual(200); | ||||
|         expect(res3.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('update team', async () => { | ||||
|         added_team.parentGroup = added_org2; | ||||
|         const res4 = await axios.put(base + '/api/teams/' + added_team_id, added_team, { validateStatus: undefined }); | ||||
|         const res4 = await axios.put(base + '/api/teams/' + added_team_id, added_team, axios_config); | ||||
|         let updated_team = res4.data; | ||||
|         expect(res4.status).toEqual(200); | ||||
|         expect(res4.headers['content-type']).toContain("application/json") | ||||
|   | ||||
| @@ -2,9 +2,21 @@ import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
|  | ||||
| let access_token; | ||||
| let axios_config; | ||||
|  | ||||
| beforeAll(async () => { | ||||
|     const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
|     access_token = res.data["access_token"]; | ||||
|     axios_config = { | ||||
|         headers: { "authorization": "Bearer " + access_token }, | ||||
|         validateStatus: undefined | ||||
|     }; | ||||
| }); | ||||
|  | ||||
| describe('GET /api/runners', () => { | ||||
|     it('basic get should return 200', async () => { | ||||
|         const res = await axios.get(base + '/api/runners'); | ||||
|         const res = await axios.get(base + '/api/runners', axios_config); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -12,7 +24,7 @@ describe('GET /api/runners', () => { | ||||
| // --------------- | ||||
| describe('GET /api/runners/0', () => { | ||||
|     it('basic get should return 404', async () => { | ||||
|         const res = await axios.get(base + '/api/runners/0', { validateStatus: undefined }); | ||||
|         const res = await axios.get(base + '/api/runners/0', axios_config); | ||||
|         expect(res.status).toEqual(404); | ||||
|         expect(res.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -20,7 +32,7 @@ describe('GET /api/runners/0', () => { | ||||
| // --------------- | ||||
| describe('POST /api/runners with errors', () => { | ||||
|     it('creating a new runner without any parameters should return 400', async () => { | ||||
|         const res1 = await axios.post(base + '/api/runners', null, { validateStatus: undefined }); | ||||
|         const res1 = await axios.post(base + '/api/runners', null, axios_config); | ||||
|         expect(res1.status).toEqual(400); | ||||
|         expect(res1.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -29,7 +41,7 @@ describe('POST /api/runners with errors', () => { | ||||
|             "firstname": "first", | ||||
|             "middlename": "middle", | ||||
|             "lastname": "last" | ||||
|         }, { validateStatus: undefined }); | ||||
|         }, axios_config); | ||||
|         expect(res2.status).toEqual(400); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -39,7 +51,7 @@ describe('POST /api/runners with errors', () => { | ||||
|             "middlename": "middle", | ||||
|             "lastname": "last", | ||||
|             "group": 0 | ||||
|         }, { validateStatus: undefined }); | ||||
|         }, axios_config); | ||||
|         expect(res2.status).toEqual(404); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -49,7 +61,7 @@ describe('POST /api/runners with errors', () => { | ||||
|             "middlename": "middle", | ||||
|             "lastname": "last", | ||||
|             "phone": "123" | ||||
|         }, { validateStatus: undefined }); | ||||
|         }, axios_config); | ||||
|         expect(res2.status).toEqual(400); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -59,7 +71,7 @@ describe('POST /api/runners with errors', () => { | ||||
|             "middlename": "middle", | ||||
|             "lastname": "last", | ||||
|             "email ": "123" | ||||
|         }, { validateStatus: undefined }); | ||||
|         }, axios_config); | ||||
|         expect(res2.status).toEqual(400); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -70,7 +82,7 @@ describe('POST /api/runners working', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res1 = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         let added_org = res1.data | ||||
|         added_org_id = added_org.id; | ||||
|         expect(res1.status).toEqual(200); | ||||
| @@ -81,7 +93,7 @@ describe('POST /api/runners working', () => { | ||||
|             "firstname": "first", | ||||
|             "lastname": "last", | ||||
|             "group": added_org_id | ||||
|         }, { validateStatus: undefined }); | ||||
|         }, axios_config); | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -93,7 +105,7 @@ describe('POST /api/runners working', () => { | ||||
|             "email": "test@example.com", | ||||
|             "phone": "+4909123123456789", | ||||
|             "group": added_org_id | ||||
|         }, { validateStatus: undefined }); | ||||
|         }, axios_config); | ||||
|         expect(res3.status).toEqual(200); | ||||
|         expect(res3.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|   | ||||
| @@ -2,9 +2,21 @@ import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
|  | ||||
| let access_token; | ||||
| let axios_config; | ||||
|  | ||||
| beforeAll(async () => { | ||||
|     const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
|     access_token = res.data["access_token"]; | ||||
|     axios_config = { | ||||
|         headers: { "authorization": "Bearer " + access_token }, | ||||
|         validateStatus: undefined | ||||
|     }; | ||||
| }); | ||||
|  | ||||
| describe('adding + deletion (non-existant)', () => { | ||||
|     it('delete', async () => { | ||||
|         const res2 = await axios.delete(base + '/api/runners/0', { validateStatus: undefined }); | ||||
|         const res2 = await axios.delete(base + '/api/runners/0', axios_config); | ||||
|         expect(res2.status).toEqual(204); | ||||
|     }); | ||||
| }); | ||||
| @@ -15,7 +27,7 @@ describe('add+delete', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res1 = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         let added_org = res1.data | ||||
|         added_org_id = added_org.id; | ||||
|         expect(res1.status).toEqual(200); | ||||
| @@ -26,20 +38,20 @@ describe('add+delete', () => { | ||||
|             "firstname": "first", | ||||
|             "lastname": "last", | ||||
|             "group": added_org_id | ||||
|         }, { validateStatus: undefined }); | ||||
|         }, axios_config); | ||||
|         added_runner = res2.data; | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('delete runner', async () => { | ||||
|         const res3 = await axios.delete(base + '/api/runners/' + added_runner.id); | ||||
|         const res3 = await axios.delete(base + '/api/runners/' + added_runner.id, axios_config); | ||||
|         expect(res3.status).toEqual(200); | ||||
|         expect(res3.headers['content-type']).toContain("application/json") | ||||
|         let deleted_runner = res3.data | ||||
|         expect(deleted_runner).toEqual(added_runner); | ||||
|     }); | ||||
|     it('check if team really was deleted', async () => { | ||||
|         const res4 = await axios.get(base + '/api/runners/' + added_runner.id, { validateStatus: undefined }); | ||||
|         const res4 = await axios.get(base + '/api/runners/' + added_runner.id, axios_config); | ||||
|         expect(res4.status).toEqual(404); | ||||
|         expect(res4.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|   | ||||
| @@ -1,10 +1,21 @@ | ||||
| import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
| let access_token; | ||||
| let axios_config; | ||||
|  | ||||
| beforeAll(async () => { | ||||
|     const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
|     access_token = res.data["access_token"]; | ||||
|     axios_config = { | ||||
|         headers: { "authorization": "Bearer " + access_token }, | ||||
|         validateStatus: undefined | ||||
|     }; | ||||
| }); | ||||
|  | ||||
| describe('GET /api/runners', () => { | ||||
|     it('basic get should return 200', async () => { | ||||
|         const res = await axios.get(base + '/api/runners'); | ||||
|         const res = await axios.get(base + '/api/runners', axios_config); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -12,7 +23,7 @@ describe('GET /api/runners', () => { | ||||
| // --------------- | ||||
| describe('GET /api/runners/0', () => { | ||||
|     it('basic get should return 404', async () => { | ||||
|         const res = await axios.get(base + '/api/runners/0', { validateStatus: undefined }); | ||||
|         const res = await axios.get(base + '/api/runners/0', axios_config); | ||||
|         expect(res.status).toEqual(404); | ||||
|         expect(res.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -24,7 +35,7 @@ describe('GET /api/runners after adding', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res1 = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         let added_org = res1.data | ||||
|         added_org_id = added_org.id; | ||||
|         expect(res1.status).toEqual(200); | ||||
| @@ -35,20 +46,20 @@ describe('GET /api/runners after adding', () => { | ||||
|             "firstname": "first", | ||||
|             "lastname": "last", | ||||
|             "group": added_org_id | ||||
|         }, { validateStatus: undefined }); | ||||
|         }, axios_config); | ||||
|         added_runner = res2.data; | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('explicit get should return 200', async () => { | ||||
|         const res3 = await axios.get(base + '/api/runners/' + added_runner.id, { validateStatus: undefined }); | ||||
|         const res3 = await axios.get(base + '/api/runners/' + added_runner.id, axios_config); | ||||
|         expect(res3.status).toEqual(200); | ||||
|         expect(res3.headers['content-type']).toContain("application/json") | ||||
|         let gotten_runner = res3.data | ||||
|         expect(gotten_runner).toEqual(added_runner); | ||||
|     }); | ||||
|     it('get from all runners should return 200', async () => { | ||||
|         const res4 = await axios.get(base + '/api/runners/', { validateStatus: undefined }); | ||||
|         const res4 = await axios.get(base + '/api/runners/', axios_config); | ||||
|         expect(res4.status).toEqual(200); | ||||
|         expect(res4.headers['content-type']).toContain("application/json") | ||||
|         let gotten_runners = res4.data | ||||
|   | ||||
| @@ -2,6 +2,17 @@ import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
|  | ||||
| let access_token; | ||||
| let axios_config; | ||||
|  | ||||
| beforeAll(async () => { | ||||
|     const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
|     access_token = res.data["access_token"]; | ||||
|     axios_config = { | ||||
|         headers: { "authorization": "Bearer " + access_token }, | ||||
|         validateStatus: undefined | ||||
|     }; | ||||
| }); | ||||
|  | ||||
| describe('Update runner name after adding', () => { | ||||
|     let added_org_id; | ||||
| @@ -10,7 +21,7 @@ describe('Update runner name after adding', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res1 = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         let added_org = res1.data | ||||
|         added_org_id = added_org.id; | ||||
|         expect(res1.status).toEqual(200); | ||||
| @@ -21,7 +32,7 @@ describe('Update runner name after adding', () => { | ||||
|             "firstname": "first", | ||||
|             "lastname": "last", | ||||
|             "group": added_org_id | ||||
|         }, { validateStatus: undefined }); | ||||
|         }, axios_config); | ||||
|         added_runner = res2.data; | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
| @@ -29,7 +40,7 @@ describe('Update runner name after adding', () => { | ||||
|     it('valid update should return 200', async () => { | ||||
|         let runnercopy = added_runner | ||||
|         runnercopy.firstname = "second" | ||||
|         const res3 = await axios.put(base + '/api/runners/' + added_runner.id, runnercopy, { validateStatus: undefined }); | ||||
|         const res3 = await axios.put(base + '/api/runners/' + added_runner.id, runnercopy, axios_config); | ||||
|         expect(res3.status).toEqual(200); | ||||
|         expect(res3.headers['content-type']).toContain("application/json") | ||||
|         updated_runner = res3.data | ||||
| @@ -45,7 +56,7 @@ describe('Update runner group after adding', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res1 = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         let added_org = res1.data | ||||
|         added_org_id = added_org.id; | ||||
|         expect(res1.status).toEqual(200); | ||||
| @@ -56,7 +67,7 @@ describe('Update runner group after adding', () => { | ||||
|             "firstname": "first", | ||||
|             "lastname": "last", | ||||
|             "group": added_org_id | ||||
|         }, { validateStatus: undefined }); | ||||
|         }, axios_config); | ||||
|         added_runner = res2.data; | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
| @@ -64,7 +75,7 @@ describe('Update runner group after adding', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res3 = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         added_org_2 = res3.data | ||||
|         delete added_org_2.address; | ||||
|         delete added_org_2.contact; | ||||
| @@ -74,7 +85,7 @@ describe('Update runner group after adding', () => { | ||||
|     }); | ||||
|     it('valid update should return 200', async () => { | ||||
|         added_runner.group = added_org_2; | ||||
|         const res3 = await axios.put(base + '/api/runners/' + added_runner.id, added_runner, { validateStatus: undefined }); | ||||
|         const res3 = await axios.put(base + '/api/runners/' + added_runner.id, added_runner, axios_config); | ||||
|         expect(res3.status).toEqual(200); | ||||
|         expect(res3.headers['content-type']).toContain("application/json") | ||||
|         updated_runner = res3.data | ||||
| @@ -89,7 +100,7 @@ describe('Update runner id after adding(should fail)', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res1 = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         let added_org = res1.data | ||||
|         added_org_id = added_org.id; | ||||
|         expect(res1.status).toEqual(200); | ||||
| @@ -100,7 +111,7 @@ describe('Update runner id after adding(should fail)', () => { | ||||
|             "firstname": "first", | ||||
|             "lastname": "last", | ||||
|             "group": added_org_id | ||||
|         }, { validateStatus: undefined }); | ||||
|         }, axios_config); | ||||
|         added_runner = res2.data; | ||||
|         added_runner_id = added_runner.id; | ||||
|         expect(res2.status).toEqual(200); | ||||
| @@ -108,7 +119,7 @@ describe('Update runner id after adding(should fail)', () => { | ||||
|     }); | ||||
|     it('invalid update should return 406', async () => { | ||||
|         added_runner.id++; | ||||
|         const res3 = await axios.put(base + '/api/runners/' + added_runner_id, added_runner, { validateStatus: undefined }); | ||||
|         const res3 = await axios.put(base + '/api/runners/' + added_runner_id, added_runner, axios_config); | ||||
|         expect(res3.status).toEqual(406); | ||||
|         expect(res3.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| @@ -120,7 +131,7 @@ describe('Update runner group with invalid group after adding', () => { | ||||
|     it('creating a new org with just a name should return 200', async () => { | ||||
|         const res1 = await axios.post(base + '/api/organisations', { | ||||
|             "name": "test123" | ||||
|         }); | ||||
|         }, axios_config); | ||||
|         added_org = res1.data | ||||
|         expect(res1.status).toEqual(200); | ||||
|         expect(res1.headers['content-type']).toContain("application/json") | ||||
| @@ -130,7 +141,7 @@ describe('Update runner group with invalid group after adding', () => { | ||||
|             "firstname": "first", | ||||
|             "lastname": "last", | ||||
|             "group": added_org.id | ||||
|         }, { validateStatus: undefined }); | ||||
|         }, axios_config); | ||||
|         added_runner = res2.data; | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
| @@ -138,7 +149,7 @@ describe('Update runner group with invalid group after adding', () => { | ||||
|     it('invalid update should return 404', async () => { | ||||
|         added_org.id = 0; | ||||
|         added_runner.group = added_org; | ||||
|         const res3 = await axios.put(base + '/api/runners/' + added_runner.id, added_runner, { validateStatus: undefined }); | ||||
|         const res3 = await axios.put(base + '/api/runners/' + added_runner.id, added_runner, axios_config); | ||||
|         expect(res3.status).toEqual(404); | ||||
|         expect(res3.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|   | ||||
| @@ -2,9 +2,21 @@ import axios from 'axios'; | ||||
| import { config } from '../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
|  | ||||
| let access_token; | ||||
| let axios_config; | ||||
|  | ||||
| beforeAll(async () => { | ||||
| 	const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
| 	access_token = res.data["access_token"]; | ||||
| 	axios_config = { | ||||
| 		headers: { "authorization": "Bearer " + access_token }, | ||||
| 		validateStatus: undefined | ||||
| 	}; | ||||
| }); | ||||
|  | ||||
| describe('GET /api/tracks', () => { | ||||
| 	it('basic get should return 200', async () => { | ||||
| 		const res = await axios.get(base + '/api/tracks'); | ||||
| 		const res = await axios.get(base + '/api/tracks', axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json") | ||||
| 	}); | ||||
| @@ -12,7 +24,7 @@ describe('GET /api/tracks', () => { | ||||
| 		const res = await axios.post(base + '/api/tracks', { | ||||
| 			"name": "string", | ||||
| 			"distance": 400 | ||||
| 		}); | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json") | ||||
| 	}); | ||||
| @@ -23,7 +35,7 @@ describe('POST /api/tracks', () => { | ||||
| 		const res = await axios.post(base + '/api/tracks', { | ||||
| 			"name": "string", | ||||
| 			"distance": -1 | ||||
| 		}, { validateStatus: undefined }); | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(400); | ||||
| 		expect(res.headers['content-type']).toContain("application/json") | ||||
| 	}); | ||||
| @@ -31,7 +43,7 @@ describe('POST /api/tracks', () => { | ||||
| 		const res = await axios.post(base + '/api/tracks', { | ||||
| 			"name": "string", | ||||
| 			"distance": 400 | ||||
| 		}); | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json") | ||||
| 	}); | ||||
| @@ -42,12 +54,12 @@ describe('adding + getting tracks', () => { | ||||
| 		const res = await axios.post(base + '/api/tracks', { | ||||
| 			"name": "string", | ||||
| 			"distance": 1000 | ||||
| 		}); | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json") | ||||
| 	}); | ||||
| 	it('check if track was added', async () => { | ||||
| 		const res = await axios.get(base + '/api/tracks'); | ||||
| 		const res = await axios.get(base + '/api/tracks', axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json") | ||||
| 		let added_track = res.data[res.data.length - 1] | ||||
| @@ -65,12 +77,12 @@ describe('adding + getting + updating', () => { | ||||
| 		const res = await axios.post(base + '/api/tracks', { | ||||
| 			"name": "string", | ||||
| 			"distance": 1500 | ||||
| 		}); | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json") | ||||
| 	}); | ||||
| 	it('get should return 200', async () => { | ||||
| 		const res1 = await axios.get(base + '/api/tracks'); | ||||
| 		const res1 = await axios.get(base + '/api/tracks', axios_config); | ||||
| 		expect(res1.status).toEqual(200); | ||||
| 		expect(res1.headers['content-type']).toContain("application/json") | ||||
| 		let added_track = res1.data[res1.data.length - 1] | ||||
| @@ -86,12 +98,12 @@ describe('adding + getting + updating', () => { | ||||
| 			"id": added_track_id, | ||||
| 			"name": "apitrack", | ||||
| 			"distance": 5100 | ||||
| 		}); | ||||
| 		}, axios_config); | ||||
| 		expect(res2.status).toEqual(200); | ||||
| 		expect(res2.headers['content-type']).toContain("application/json") | ||||
| 	}) | ||||
| 	it('get should return 200', async () => { | ||||
| 		const res3 = await axios.get(base + '/api/tracks'); | ||||
| 		const res3 = await axios.get(base + '/api/tracks', axios_config); | ||||
| 		expect(res3.status).toEqual(200); | ||||
| 		expect(res3.headers['content-type']).toContain("application/json") | ||||
| 		let added_track2 = res3.data[res3.data.length - 1] | ||||
|   | ||||
		Reference in New Issue
	
	Block a user