diff --git a/package.json b/package.json index c8a2473..b37638d 100644 --- a/package.json +++ b/package.json @@ -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/*" ] } -} \ No newline at end of file +} diff --git a/src/authchecker.ts b/src/authchecker.ts index 00c84c1..36cb40b 100644 --- a/src/authchecker.ts +++ b/src/authchecker.ts @@ -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 = 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 = 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 \ No newline at end of file diff --git a/src/controllers/ImportController.ts b/src/controllers/ImportController.ts index 232df6f..bccdfb3 100644 --- a/src/controllers/ImportController.ts +++ b/src/controllers/ImportController.ts @@ -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; diff --git a/src/controllers/PermissionController.ts b/src/controllers/PermissionController.ts new file mode 100644 index 0000000..6be00fd --- /dev/null +++ b/src/controllers/PermissionController.ts @@ -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; + + /** + * 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(); + 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; + } +} \ No newline at end of file diff --git a/src/controllers/RunnerController.ts b/src/controllers/RunnerController.ts index a8bf93f..8d70c30 100644 --- a/src/controllers/RunnerController.ts +++ b/src/controllers/RunnerController.ts @@ -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; @@ -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) diff --git a/src/controllers/RunnerOrganisationController.ts b/src/controllers/RunnerOrganisationController.ts index 8c48496..5504dbb 100644 --- a/src/controllers/RunnerOrganisationController.ts +++ b/src/controllers/RunnerOrganisationController.ts @@ -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; @@ -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 }) diff --git a/src/controllers/RunnerTeamController.ts b/src/controllers/RunnerTeamController.ts index c9c4d5c..e02098a 100644 --- a/src/controllers/RunnerTeamController.ts +++ b/src/controllers/RunnerTeamController.ts @@ -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; @@ -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 }) diff --git a/src/controllers/TrackController.ts b/src/controllers/TrackController.ts index e17e098..4691c86 100644 --- a/src/controllers/TrackController.ts +++ b/src/controllers/TrackController.ts @@ -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; @@ -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(); 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) diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index a94ac71..eb3a458 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -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; @@ -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(); + 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); } } diff --git a/src/controllers/UserGroupController.ts b/src/controllers/UserGroupController.ts index 8a90d94..3297f50 100644 --- a/src/controllers/UserGroupController.ts +++ b/src/controllers/UserGroupController.ts @@ -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; @@ -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); } } diff --git a/src/errors/PermissionErrors.ts b/src/errors/PermissionErrors.ts new file mode 100644 index 0000000..c2492b4 --- /dev/null +++ b/src/errors/PermissionErrors.ts @@ -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." +} \ No newline at end of file diff --git a/src/errors/PrincipalErrors.ts b/src/errors/PrincipalErrors.ts new file mode 100644 index 0000000..d519dc7 --- /dev/null +++ b/src/errors/PrincipalErrors.ts @@ -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." +} diff --git a/src/jwtcreator.ts b/src/jwtcreator.ts new file mode 100644 index 0000000..e837f13 --- /dev/null +++ b/src/jwtcreator.ts @@ -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(); + 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)); + } +} \ No newline at end of file diff --git a/src/models/actions/CreateAuth.ts b/src/models/actions/CreateAuth.ts index b31bee6..adec57c 100644 --- a/src/models/actions/CreateAuth.ts +++ b/src/models/actions/CreateAuth.ts @@ -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; } } \ No newline at end of file diff --git a/src/models/actions/CreatePermission.ts b/src/models/actions/CreatePermission.ts new file mode 100644 index 0000000..7a49e03 --- /dev/null +++ b/src/models/actions/CreatePermission.ts @@ -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 { + let newPermission: Permission = new Permission(); + + newPermission.principal = await this.getPrincipal(); + newPermission.target = this.target; + newPermission.action = this.action; + + return newPermission; + } + + public async getPrincipal(): Promise { + let principal = await getConnectionManager().get().getRepository(Principal).findOne({ id: this.principal }) + if (!principal) { throw new PrincipalNotFoundError(); } + return principal; + } +} diff --git a/src/models/actions/CreateUser.ts b/src/models/actions/CreateUser.ts index 48858f4..1f80ee3 100644 --- a/src/models/actions/CreateUser.ts +++ b/src/models/actions/CreateUser.ts @@ -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(); + 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; + } } \ No newline at end of file diff --git a/src/models/actions/HandleLogout.ts b/src/models/actions/HandleLogout.ts index 37c30c4..165c78e 100644 --- a/src/models/actions/HandleLogout.ts +++ b/src/models/actions/HandleLogout.ts @@ -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++; diff --git a/src/models/actions/RefreshAuth.ts b/src/models/actions/RefreshAuth.ts index afd22fe..963c83c 100644 --- a/src/models/actions/RefreshAuth.ts +++ b/src/models/actions/RefreshAuth.ts @@ -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; } } \ No newline at end of file diff --git a/src/models/actions/UpdatePermission.ts b/src/models/actions/UpdatePermission.ts new file mode 100644 index 0000000..26c2a83 --- /dev/null +++ b/src/models/actions/UpdatePermission.ts @@ -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 { + 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 { + 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(); + } +} \ No newline at end of file diff --git a/src/models/entities/Permission.ts b/src/models/entities/Permission.ts index b9e2777..b95bb84 100644 --- a/src/models/entities/Permission.ts +++ b/src/models/entities/Permission.ts @@ -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; + } } \ No newline at end of file diff --git a/src/models/entities/Principal.ts b/src/models/entities/Principal.ts new file mode 100644 index 0000000..618e3db --- /dev/null +++ b/src/models/entities/Principal.ts @@ -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; +} \ No newline at end of file diff --git a/src/models/entities/User.ts b/src/models/entities/User.ts index ebc62b1..21970c4 100644 --- a/src/models/entities/User.ts +++ b/src/models/entities/User.ts @@ -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); } } diff --git a/src/models/entities/UserGroup.ts b/src/models/entities/UserGroup.ts index e86b2a6..c14f8fb 100644 --- a/src/models/entities/UserGroup.ts +++ b/src/models/entities/UserGroup.ts @@ -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); + } } \ No newline at end of file diff --git a/src/models/enums/PermissionAction.ts b/src/models/enums/PermissionAction.ts new file mode 100644 index 0000000..0e984bf --- /dev/null +++ b/src/models/enums/PermissionAction.ts @@ -0,0 +1,7 @@ +export enum PermissionAction { + GET = 'GET', + CREATE = 'CREATE', + UPDATE = 'UPDATE', + DELETE = 'DELETE', + IMPORT = 'IMPORT' +} \ No newline at end of file diff --git a/src/models/enums/PermissionTargets.ts b/src/models/enums/PermissionTargets.ts new file mode 100644 index 0000000..7b18eb0 --- /dev/null +++ b/src/models/enums/PermissionTargets.ts @@ -0,0 +1,9 @@ +export enum PermissionTarget { + RUNNER = 'RUNNER', + ORGANISATION = 'ORGANISATION', + TEAM = 'TEAM', + TRACK = 'TRACK', + USER = 'USER', + GROUP = 'USERGROUP', + PERMISSION = 'PERMISSION' +} \ No newline at end of file diff --git a/src/models/responses/ResponseParticipant.ts b/src/models/responses/ResponseParticipant.ts index 22ec2a3..0647efe 100644 --- a/src/models/responses/ResponseParticipant.ts +++ b/src/models/responses/ResponseParticipant.ts @@ -15,7 +15,7 @@ export abstract class ResponseParticipant { * Autogenerated unique id (primary key). */ @IsInt() - id: number;; + id: number; /** * The participant's first name. diff --git a/src/models/responses/ResponsePermission.ts b/src/models/responses/ResponsePermission.ts new file mode 100644 index 0000000..23a3686 --- /dev/null +++ b/src/models/responses/ResponsePermission.ts @@ -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; + } +} diff --git a/src/models/responses/ResponsePrincipal.ts b/src/models/responses/ResponsePrincipal.ts new file mode 100644 index 0000000..920bac2 --- /dev/null +++ b/src/models/responses/ResponsePrincipal.ts @@ -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; + } +} diff --git a/src/models/responses/ResponseUser.ts b/src/models/responses/ResponseUser.ts new file mode 100644 index 0000000..9ec729a --- /dev/null +++ b/src/models/responses/ResponseUser.ts @@ -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; + } +} diff --git a/src/models/responses/ResponseUserGroup.ts b/src/models/responses/ResponseUserGroup.ts new file mode 100644 index 0000000..1317c8f --- /dev/null +++ b/src/models/responses/ResponseUserGroup.ts @@ -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; + } +} diff --git a/src/seeds/SeedUsers.ts b/src/seeds/SeedUsers.ts index 9db0fd7..dae3b17 100644 --- a/src/seeds/SeedUsers.ts +++ b/src/seeds/SeedUsers.ts @@ -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 { + 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 = target; + permission.action = action; + permission.principal = principal; + await repo.save(await permission.toPermission()); + } + } } } \ No newline at end of file diff --git a/src/tests/firsttest.spec.ts b/src/tests/firsttest.spec.ts index 27c84d3..4cd1477 100644 --- a/src/tests/firsttest.spec.ts +++ b/src/tests/firsttest.spec.ts @@ -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") }); diff --git a/src/tests/runnerOrgs/org_add.spec.ts b/src/tests/runnerOrgs/org_add.spec.ts index fb28b62..6ee0606 100644 --- a/src/tests/runnerOrgs/org_add.spec.ts +++ b/src/tests/runnerOrgs/org_add.spec.ts @@ -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 diff --git a/src/tests/runnerOrgs/org_delete.spec.ts b/src/tests/runnerOrgs/org_delete.spec.ts index f8d5824..08891f8 100644 --- a/src/tests/runnerOrgs/org_delete.spec.ts +++ b/src/tests/runnerOrgs/org_delete.spec.ts @@ -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") }); diff --git a/src/tests/runnerOrgs/org_get.spec.ts b/src/tests/runnerOrgs/org_get.spec.ts index 3e899f7..5e17f19 100644 --- a/src/tests/runnerOrgs/org_get.spec.ts +++ b/src/tests/runnerOrgs/org_get.spec.ts @@ -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") }); diff --git a/src/tests/runnerOrgs/org_update.spec.ts b/src/tests/runnerOrgs/org_update.spec.ts index 06a35f6..e6a1055 100644 --- a/src/tests/runnerOrgs/org_update.spec.ts +++ b/src/tests/runnerOrgs/org_update.spec.ts @@ -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") }); diff --git a/src/tests/runnerTeams/team_add.spec.ts b/src/tests/runnerTeams/team_add.spec.ts index 5ce61d3..588276a 100644 --- a/src/tests/runnerTeams/team_add.spec.ts +++ b/src/tests/runnerTeams/team_add.spec.ts @@ -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") }); diff --git a/src/tests/runnerTeams/team_delete.spec.ts b/src/tests/runnerTeams/team_delete.spec.ts index 9210789..2f4270d 100644 --- a/src/tests/runnerTeams/team_delete.spec.ts +++ b/src/tests/runnerTeams/team_delete.spec.ts @@ -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); }); }); diff --git a/src/tests/runnerTeams/team_get.spec.ts b/src/tests/runnerTeams/team_get.spec.ts index f30b230..be67268 100644 --- a/src/tests/runnerTeams/team_get.spec.ts +++ b/src/tests/runnerTeams/team_get.spec.ts @@ -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") }); diff --git a/src/tests/runnerTeams/team_update.spec.ts b/src/tests/runnerTeams/team_update.spec.ts index 10b6c0c..5d1355d 100644 --- a/src/tests/runnerTeams/team_update.spec.ts +++ b/src/tests/runnerTeams/team_update.spec.ts @@ -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") diff --git a/src/tests/runners/runner_add.spec.ts b/src/tests/runners/runner_add.spec.ts index f92ef87..44e2354 100644 --- a/src/tests/runners/runner_add.spec.ts +++ b/src/tests/runners/runner_add.spec.ts @@ -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") }); diff --git a/src/tests/runners/runner_delete.spec.ts b/src/tests/runners/runner_delete.spec.ts index b1239e4..36e72e7 100644 --- a/src/tests/runners/runner_delete.spec.ts +++ b/src/tests/runners/runner_delete.spec.ts @@ -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") }); diff --git a/src/tests/runners/runner_get.spec.ts b/src/tests/runners/runner_get.spec.ts index 13d259c..963512a 100644 --- a/src/tests/runners/runner_get.spec.ts +++ b/src/tests/runners/runner_get.spec.ts @@ -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 diff --git a/src/tests/runners/runner_update.spec.ts b/src/tests/runners/runner_update.spec.ts index 97ccf2a..d9a1266 100644 --- a/src/tests/runners/runner_update.spec.ts +++ b/src/tests/runners/runner_update.spec.ts @@ -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") }); diff --git a/src/tests/tracks.spec.ts b/src/tests/tracks.spec.ts index b29294c..4646878 100644 --- a/src/tests/tracks.spec.ts +++ b/src/tests/tracks.spec.ts @@ -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]