From cc5a30980a8d07966981f0474f2de83e15c7a5e5 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Thu, 17 Dec 2020 20:46:54 +0100 Subject: [PATCH 01/37] Implemented new Permission system on the DB side. ref #22 --- src/models/entities/Permission.ts | 40 ++++++++++++--------------- src/models/entities/Principal.ts | 24 ++++++++++++++++ src/models/entities/User.ts | 21 +++----------- src/models/entities/UserGroup.ts | 21 +++----------- src/models/enums/PermissionAction.ts | 6 ++++ src/models/enums/PermissionTargets.ts | 8 ++++++ 6 files changed, 64 insertions(+), 56 deletions(-) create mode 100644 src/models/entities/Principal.ts create mode 100644 src/models/enums/PermissionAction.ts create mode 100644 src/models/enums/PermissionTargets.ts diff --git a/src/models/entities/Permission.ts b/src/models/entities/Permission.ts index b9e2777..86f7d6a 100644 --- a/src/models/entities/Permission.ts +++ b/src/models/entities/Permission.ts @@ -1,12 +1,11 @@ import { 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. */ @@ -20,30 +19,27 @@ 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: 'simple-enum', + enum: PermissionTarget + }) @IsNotEmpty() - @IsString() - target: string; + target: PermissionTarget; /** * The action type */ - @Column() - @IsNotEmpty() - @IsString() - action: string; + @Column({ + type: 'simple-enum', + enum: PermissionAction + }) + action: PermissionAction; } \ 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..f1242b5 --- /dev/null +++ b/src/models/entities/Principal.ts @@ -0,0 +1,24 @@ +import { IsInt, IsOptional } from 'class-validator'; +import { Entity, OneToMany, PrimaryGeneratedColumn, TableInheritance } from 'typeorm'; +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 +*/ + @IsOptional() + @OneToMany(() => Permission, permission => permission.principal, { nullable: true }) + permissions?: Permission[]; +} \ No newline at end of file diff --git a/src/models/entities/User.ts b/src/models/entities/User.ts index ebc62b1..2b5b9b3 100644 --- a/src/models/entities/User.ts +++ b/src/models/entities/User.ts @@ -1,22 +1,16 @@ 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 { 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 +72,6 @@ export class User { @IsNotEmpty() password: string; - /** - * permissions - */ - @IsOptional() - @ManyToOne(() => Permission, permission => permission.users, { nullable: true }) - permissions?: Permission[]; - /** * groups */ diff --git a/src/models/entities/UserGroup.ts b/src/models/entities/UserGroup.ts index e86b2a6..21dfe25 100644 --- a/src/models/entities/UserGroup.ts +++ b/src/models/entities/UserGroup.ts @@ -1,29 +1,16 @@ 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 { 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 diff --git a/src/models/enums/PermissionAction.ts b/src/models/enums/PermissionAction.ts new file mode 100644 index 0000000..8ea9e65 --- /dev/null +++ b/src/models/enums/PermissionAction.ts @@ -0,0 +1,6 @@ +export enum PermissionAction { + READ = 'READ', + ADD = 'ADD', + UPDATE = 'UPDATE', + DELETE = 'DELETE' +} \ 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..9a6f040 --- /dev/null +++ b/src/models/enums/PermissionTargets.ts @@ -0,0 +1,8 @@ +export enum PermissionTarget { + RUNNER = 'RUNNER', + ORGANISATION = 'RUNNERORGANISATION', + TEAM = 'RUNNERTEAM', + TRACK = 'TRACK', + USER = 'USER', + GROUP = 'USERGROUP' +} \ No newline at end of file -- 2.47.2 From efecffb72d85ecedc8fb5656312d4e514d393145 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Thu, 17 Dec 2020 21:12:45 +0100 Subject: [PATCH 02/37] Added responseusers ref #6 --- src/controllers/UserController.ts | 40 +++++++----- src/models/responses/ResponseUser.ts | 94 ++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 14 deletions(-) create mode 100644 src/models/responses/ResponseUser.ts diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index a94ac71..ae84f47 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 { 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,6 +7,7 @@ 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'; @JsonController('/users') @@ -23,8 +24,13 @@ export class UserController { @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') @@ -32,8 +38,10 @@ export class UserController { @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() @@ -48,7 +56,8 @@ 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') @@ -57,7 +66,7 @@ export class UserController { @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,7 +77,7 @@ 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') @@ -76,13 +85,16 @@ export class UserController { @ResponseSchema(ResponseEmpty, { statusCode: 204 }) @OnUndefined(204) @OpenAPI({ description: 'Delete a specified runner (if it exists).' }) - async remove(@Param("id") id: number) { - let user = await this.userRepository.findOne({ id: id }); - if (!user) { - return null; + async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { + let runner = await this.userRepository.findOne({ id: id }); + if (!runner) { return null; } + const responseUser = await this.userRepository.findOne(runner, { relations: ['permissions', 'groups'] }); + + if (!runner) { + throw new UserNotFoundError(); } - await this.userRepository.delete(user); - return user; + await this.userRepository.delete(runner); + return new ResponseUser(responseUser); } } diff --git a/src/models/responses/ResponseUser.ts b/src/models/responses/ResponseUser.ts new file mode 100644 index 0000000..f4066cf --- /dev/null +++ b/src/models/responses/ResponseUser.ts @@ -0,0 +1,94 @@ +import { + IsArray, + IsBoolean, + IsInt, + IsOptional, + IsString +} from "class-validator"; +import { Permission } from '../entities/Permission'; +import { User } from '../entities/User'; +import { UserGroup } from '../entities/UserGroup'; + +/** + * Defines a user response. +*/ +export class ResponseUser { + /** + * Autogenerated unique id (primary key). + */ + @IsInt() + id: number;; + + /** + * 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) { + this.id = user.id; + 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; + } +} -- 2.47.2 From 476afc6a997b68221708c0c2afbd735a5603efe7 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 14:29:17 +0100 Subject: [PATCH 03/37] Updated nameing to fit the usual scheme ref #6 --- src/models/actions/CreateUser.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/models/actions/CreateUser.ts b/src/models/actions/CreateUser.ts index 48858f4..d51db4e 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,11 +81,11 @@ export class CreateUser { throw new UsernameOrEmailNeededError(); } - if (this.groupId) { - if (!Array.isArray(this.groupId)) { - this.groupId = [this.groupId] + if (this.group) { + if (!Array.isArray(this.group)) { + this.group = [this.group] } - const groupIDs: number[] = this.groupId + const groupIDs: number[] = this.group let errors = 0 const validateusergroups = async () => { let foundgroups = [] -- 2.47.2 From eb9473e230e8737b624b5084b31c73aa99bc4e66 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 14:29:31 +0100 Subject: [PATCH 04/37] Cleaned up relation types ref #6 --- src/models/entities/Permission.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/models/entities/Permission.ts b/src/models/entities/Permission.ts index 86f7d6a..d284489 100644 --- a/src/models/entities/Permission.ts +++ b/src/models/entities/Permission.ts @@ -22,24 +22,18 @@ export abstract class Permission { * The permissions principal */ @ManyToOne(() => Principal, principal => principal.permissions) - principal: Principal[] + principal: Principal; /** * The target */ - @Column({ - type: 'simple-enum', - enum: PermissionTarget - }) + @Column({ type: 'varchar' }) @IsNotEmpty() target: PermissionTarget; /** * The action type */ - @Column({ - type: 'simple-enum', - enum: PermissionAction - }) + @Column({ type: 'varchar' }) action: PermissionAction; } \ No newline at end of file -- 2.47.2 From 5dc9edfe40fe0898946861430bda15769e67fdb8 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 14:40:19 +0100 Subject: [PATCH 05/37] Pulled out some linguini-esc code ref #6 --- src/models/actions/CreateUser.ts | 50 +++++++++++++++++--------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/models/actions/CreateUser.ts b/src/models/actions/CreateUser.ts index d51db4e..78fee8b 100644 --- a/src/models/actions/CreateUser.ts +++ b/src/models/actions/CreateUser.ts @@ -81,30 +81,6 @@ export class CreateUser { throw new UsernameOrEmailNeededError(); } - if (this.group) { - if (!Array.isArray(this.group)) { - this.group = [this.group] - } - const groupIDs: number[] = this.group - 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,34 @@ 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] + } + const groupIDs: number[] = this.group + let errors = 0 + const validateusergroups = async () => { + for (const g of groupIDs) { + const found = await getConnectionManager().get().getRepository(UserGroup).find({ id: g }); + if (found.length === 0) { + errors++ + } else { + groups.push(found[0]) + } + } + return groups; + } + await validateusergroups() + if (errors !== 0) { + throw new UserGroupNotFoundError(); + } + } } \ No newline at end of file -- 2.47.2 From 388fc6ba6a62088d773d5e1afd299ee78b47d573 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 14:52:31 +0100 Subject: [PATCH 06/37] Fixed typo --- src/models/responses/ResponseParticipant.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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. -- 2.47.2 From d89fcb84a28b535a017dd22494281710d465072f Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 15:12:06 +0100 Subject: [PATCH 07/37] Implemented permission getting ref #6 --- src/controllers/PermissionController.ts | 109 +++++++++++++++++++++ src/models/entities/Permission.ts | 2 +- src/models/responses/ResponsePermission.ts | 46 +++++++++ src/models/responses/ResponsePrincipal.ts | 20 ++++ src/models/responses/ResponseUser.ts | 13 +-- 5 files changed, 180 insertions(+), 10 deletions(-) create mode 100644 src/controllers/PermissionController.ts create mode 100644 src/models/responses/ResponsePermission.ts create mode 100644 src/models/responses/ResponsePrincipal.ts diff --git a/src/controllers/PermissionController.ts b/src/controllers/PermissionController.ts new file mode 100644 index 0000000..3998013 --- /dev/null +++ b/src/controllers/PermissionController.ts @@ -0,0 +1,109 @@ +import { Get, JsonController } from 'routing-controllers'; +import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; +import { getConnectionManager, Repository } from 'typeorm'; +import { Permission } from '../models/entities/Permission'; +import { ResponsePermission } from '../models/responses/ResponsePermission'; +import { ResponseRunnerTeam } from '../models/responses/ResponseRunnerTeam'; + + +@JsonController('/permissions') +//@Authorized('RUNNERS:read') +export class PermissionController { + private permissionController: Repository; + + /** + * Gets the repository of this controller's model/entity. + */ + constructor() { + this.permissionController = getConnectionManager().get().getRepository(Permission); + } + + @Get() + @ResponseSchema(ResponseRunnerTeam, { isArray: true }) + @OpenAPI({ description: 'Lists all runnerTeams.' }) + async getAll() { + let responsePermissions: ResponsePermission[] = new Array(); + const permissions = await this.permissionController.find({ relations: ['principal'] }); + permissions.forEach(permission => { + responsePermissions.push(new ResponsePermission(permission)); + }); + return responsePermissions; + } + + /* + @Get('/:id') + @ResponseSchema(ResponseRunnerTeam) + @ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 }) + @OnUndefined(RunnerTeamNotFoundError) + @OpenAPI({ description: 'Returns a runnerTeam of a specified id (if it exists)' }) + async getOne(@Param('id') id: number) { + let runnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact'] }); + if (!runnerTeam) { throw new RunnerTeamNotFoundError(); } + return new ResponseRunnerTeam(runnerTeam); + } + + @Post() + @ResponseSchema(ResponseRunnerTeam) + @OpenAPI({ description: 'Create a new runnerTeam object (id will be generated automagicly).' }) + async post(@Body({ validate: true }) createRunnerTeam: CreateRunnerTeam) { + let runnerTeam; + try { + runnerTeam = await createRunnerTeam.toRunnerTeam(); + } catch (error) { + throw error; + } + + runnerTeam = await this.runnerTeamRepository.save(runnerTeam); + runnerTeam = await this.runnerTeamRepository.findOne(runnerTeam, { relations: ['parentGroup', 'contact'] }); + + return new ResponseRunnerTeam(runnerTeam); + } + + @Put('/:id') + @ResponseSchema(ResponseRunnerTeam) + @ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 }) + @ResponseSchema(RunnerTeamIdsNotMatchingError, { statusCode: 406 }) + @OpenAPI({ description: "Update a runnerTeam object (id can't be changed)." }) + async put(@Param('id') id: number, @Body({ validate: true }) runnerTeam: UpdateRunnerTeam) { + let oldRunnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact'] }); + + if (!oldRunnerTeam) { + throw new RunnerTeamNotFoundError(); + } + + if (oldRunnerTeam.id != runnerTeam.id) { + throw new RunnerTeamIdsNotMatchingError(); + } + + await this.runnerTeamRepository.update(oldRunnerTeam, await runnerTeam.toRunnerTeam()); + + return new ResponseRunnerTeam(await this.runnerTeamRepository.findOne({ id: runnerTeam.id }, { relations: ['parentGroup', 'contact'] })); + } + + @Delete('/:id') + @ResponseSchema(ResponseRunnerTeam) + @ResponseSchema(ResponseEmpty, { statusCode: 204 }) + @ResponseSchema(RunnerTeamHasRunnersError, { statusCode: 406 }) + @OnUndefined(204) + @OpenAPI({ description: 'Delete a specified runnerTeam (if it exists).' }) + async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { + let team = await this.runnerTeamRepository.findOne({ id: id }); + if (!team) { return null; } + let runnerTeam = await this.runnerTeamRepository.findOne(team, { relations: ['parentGroup', 'contact', 'runners'] }); + + if (!force) { + if (runnerTeam.runners.length != 0) { + throw new RunnerTeamHasRunnersError(); + } + } + const runnerController = new RunnerController() + for (let runner of runnerTeam.runners) { + await runnerController.remove(runner.id, true); + } + + const responseTeam = new ResponseRunnerTeam(runnerTeam); + await this.runnerTeamRepository.delete(team); + return responseTeam; + } + */ +} diff --git a/src/models/entities/Permission.ts b/src/models/entities/Permission.ts index d284489..f22f74a 100644 --- a/src/models/entities/Permission.ts +++ b/src/models/entities/Permission.ts @@ -10,7 +10,7 @@ import { Principal } from './Principal'; * Defines the Permission interface. */ @Entity() -export abstract class Permission { +export class Permission { /** * Autogenerated unique id (primary key). */ diff --git a/src/models/responses/ResponsePermission.ts b/src/models/responses/ResponsePermission.ts new file mode 100644 index 0000000..1ce3161 --- /dev/null +++ b/src/models/responses/ResponsePermission.ts @@ -0,0 +1,46 @@ +import { + IsInt, + IsNotEmpty, + IsObject +} from "class-validator"; +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 ResponsePermission { + /** + * Autogenerated unique id (primary key). + */ + @IsInt() + id: number;; + + /** + * The permissions's principal. + */ + @IsObject() + @IsNotEmpty() + principal: Principal; + + /** + * The permissions's target. + */ + @IsNotEmpty() + target: PermissionTarget; + + /** + * The permissions's action. + */ + @IsNotEmpty() + action: PermissionAction; + + public constructor(permission: Permission) { + this.id = permission.id; + this.principal = permission.principal; + 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 index f4066cf..9ec729a 100644 --- a/src/models/responses/ResponseUser.ts +++ b/src/models/responses/ResponseUser.ts @@ -1,24 +1,19 @@ import { IsArray, IsBoolean, - IsInt, + 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 { - /** - * Autogenerated unique id (primary key). - */ - @IsInt() - id: number;; - +export class ResponseUser extends ResponsePrincipal { /** * The user's first name. */ @@ -80,7 +75,7 @@ export class ResponseUser { permissions: Permission[]; public constructor(user: User) { - this.id = user.id; + super(user); this.firstname = user.firstname; this.middlename = user.middlename; this.lastname = user.lastname; -- 2.47.2 From ebb0c5faca6fee18482a6552f10be18be5398b40 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 15:16:15 +0100 Subject: [PATCH 08/37] Added specific permission getting ref #6 --- src/controllers/PermissionController.ts | 27 +++++++++++++------------ src/errors/PermissionErrors.ts | 25 +++++++++++++++++++++++ src/errors/PrincipalsErrors.ts | 13 ++++++++++++ 3 files changed, 52 insertions(+), 13 deletions(-) create mode 100644 src/errors/PermissionErrors.ts create mode 100644 src/errors/PrincipalsErrors.ts diff --git a/src/controllers/PermissionController.ts b/src/controllers/PermissionController.ts index 3998013..d6778df 100644 --- a/src/controllers/PermissionController.ts +++ b/src/controllers/PermissionController.ts @@ -1,9 +1,9 @@ -import { Get, JsonController } from 'routing-controllers'; +import { Get, JsonController, OnUndefined, Param } from 'routing-controllers'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { getConnectionManager, Repository } from 'typeorm'; +import { PermissionNotFoundError } from '../errors/PermissionErrors'; import { Permission } from '../models/entities/Permission'; import { ResponsePermission } from '../models/responses/ResponsePermission'; -import { ResponseRunnerTeam } from '../models/responses/ResponseRunnerTeam'; @JsonController('/permissions') @@ -19,8 +19,8 @@ export class PermissionController { } @Get() - @ResponseSchema(ResponseRunnerTeam, { isArray: true }) - @OpenAPI({ description: 'Lists all runnerTeams.' }) + @ResponseSchema(ResponsePermission, { isArray: true }) + @OpenAPI({ description: 'Lists all permissions.' }) async getAll() { let responsePermissions: ResponsePermission[] = new Array(); const permissions = await this.permissionController.find({ relations: ['principal'] }); @@ -30,18 +30,19 @@ export class PermissionController { return responsePermissions; } - /* + @Get('/:id') - @ResponseSchema(ResponseRunnerTeam) - @ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 }) - @OnUndefined(RunnerTeamNotFoundError) - @OpenAPI({ description: 'Returns a runnerTeam of a specified id (if it exists)' }) + @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 runnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact'] }); - if (!runnerTeam) { throw new RunnerTeamNotFoundError(); } - return new ResponseRunnerTeam(runnerTeam); + let permission = await this.permissionController.findOne({ id: id }, { relations: ['principal'] }); + if (!permission) { throw new PermissionNotFoundError(); } + return new ResponsePermission(permission); } + /* @Post() @ResponseSchema(ResponseRunnerTeam) @OpenAPI({ description: 'Create a new runnerTeam object (id will be generated automagicly).' }) @@ -58,7 +59,7 @@ export class PermissionController { return new ResponseRunnerTeam(runnerTeam); } - + /* @Put('/:id') @ResponseSchema(ResponseRunnerTeam) @ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 }) diff --git a/src/errors/PermissionErrors.ts b/src/errors/PermissionErrors.ts new file mode 100644 index 0000000..e53b143 --- /dev/null +++ b/src/errors/PermissionErrors.ts @@ -0,0 +1,25 @@ +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" +} \ No newline at end of file diff --git a/src/errors/PrincipalsErrors.ts b/src/errors/PrincipalsErrors.ts new file mode 100644 index 0000000..73e9819 --- /dev/null +++ b/src/errors/PrincipalsErrors.ts @@ -0,0 +1,13 @@ +import { IsString } from 'class-validator'; +import { 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!" +} \ No newline at end of file -- 2.47.2 From dc485c02eac36096011fa731bb85cce10c1bc4b3 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 15:19:44 +0100 Subject: [PATCH 09/37] Added Permission creation ref #11 --- src/controllers/PermissionController.ts | 27 +++++++------ src/models/actions/CreatePermission.ts | 54 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 13 deletions(-) create mode 100644 src/models/actions/CreatePermission.ts diff --git a/src/controllers/PermissionController.ts b/src/controllers/PermissionController.ts index d6778df..a90ed5b 100644 --- a/src/controllers/PermissionController.ts +++ b/src/controllers/PermissionController.ts @@ -1,7 +1,8 @@ -import { Get, JsonController, OnUndefined, Param } from 'routing-controllers'; +import { Body, Get, JsonController, OnUndefined, Param, Post } from 'routing-controllers'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { getConnectionManager, Repository } from 'typeorm'; import { PermissionNotFoundError } from '../errors/PermissionErrors'; +import { CreatePermission } from '../models/actions/CreatePermission'; import { Permission } from '../models/entities/Permission'; import { ResponsePermission } from '../models/responses/ResponsePermission'; @@ -9,13 +10,13 @@ import { ResponsePermission } from '../models/responses/ResponsePermission'; @JsonController('/permissions') //@Authorized('RUNNERS:read') export class PermissionController { - private permissionController: Repository; + private permissionRepository: Repository; /** * Gets the repository of this controller's model/entity. */ constructor() { - this.permissionController = getConnectionManager().get().getRepository(Permission); + this.permissionRepository = getConnectionManager().get().getRepository(Permission); } @Get() @@ -23,7 +24,7 @@ export class PermissionController { @OpenAPI({ description: 'Lists all permissions.' }) async getAll() { let responsePermissions: ResponsePermission[] = new Array(); - const permissions = await this.permissionController.find({ relations: ['principal'] }); + const permissions = await this.permissionRepository.find({ relations: ['principal'] }); permissions.forEach(permission => { responsePermissions.push(new ResponsePermission(permission)); }); @@ -37,27 +38,27 @@ export class PermissionController { @OnUndefined(PermissionNotFoundError) @OpenAPI({ description: 'Returns a permissions of a specified id (if it exists)' }) async getOne(@Param('id') id: number) { - let permission = await this.permissionController.findOne({ id: id }, { relations: ['principal'] }); + let permission = await this.permissionRepository.findOne({ id: id }, { relations: ['principal'] }); if (!permission) { throw new PermissionNotFoundError(); } return new ResponsePermission(permission); } - /* + @Post() - @ResponseSchema(ResponseRunnerTeam) + @ResponseSchema(ResponsePermission) @OpenAPI({ description: 'Create a new runnerTeam object (id will be generated automagicly).' }) - async post(@Body({ validate: true }) createRunnerTeam: CreateRunnerTeam) { - let runnerTeam; + async post(@Body({ validate: true }) createPermission: CreatePermission) { + let permission; try { - runnerTeam = await createRunnerTeam.toRunnerTeam(); + permission = await createPermission.toPermission(); } catch (error) { throw error; } - runnerTeam = await this.runnerTeamRepository.save(runnerTeam); - runnerTeam = await this.runnerTeamRepository.findOne(runnerTeam, { relations: ['parentGroup', 'contact'] }); + permission = await this.permissionRepository.save(permission); + permission = await this.permissionRepository.findOne(permission, { relations: ['principal'] }); - return new ResponseRunnerTeam(runnerTeam); + return new ResponsePermission(permission); } /* @Put('/:id') diff --git a/src/models/actions/CreatePermission.ts b/src/models/actions/CreatePermission.ts new file mode 100644 index 0000000..829bff1 --- /dev/null +++ b/src/models/actions/CreatePermission.ts @@ -0,0 +1,54 @@ +import { + IsInt, + IsNotEmpty +} from "class-validator"; +import { getConnectionManager } from 'typeorm'; +import { PrincipalNotFoundError } from '../../errors/PrincipalsErrors'; +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() + 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; + } + + public async getPrincipal(): Promise { + let principal = await getConnectionManager().get().getRepository(Principal).findOne({ id: this.principal }) + if (!principal) { throw new PrincipalNotFoundError(); } + return principal; + } +} -- 2.47.2 From 145a08b1b4f0c8c7a389b04d7ef033e23641a5b7 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 15:26:54 +0100 Subject: [PATCH 10/37] Now with cleaner participants in the responses ref #6 --- src/models/entities/Principal.ts | 3 ++ src/models/entities/User.ts | 6 +++ src/models/entities/UserGroup.ts | 6 +++ src/models/responses/ResponsePermission.ts | 6 +-- src/models/responses/ResponseUserGroup.ts | 45 ++++++++++++++++++++++ 5 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 src/models/responses/ResponseUserGroup.ts diff --git a/src/models/entities/Principal.ts b/src/models/entities/Principal.ts index f1242b5..8e74368 100644 --- a/src/models/entities/Principal.ts +++ b/src/models/entities/Principal.ts @@ -1,5 +1,6 @@ import { IsInt, IsOptional } from 'class-validator'; import { Entity, OneToMany, PrimaryGeneratedColumn, TableInheritance } from 'typeorm'; +import { ResponsePrincipal } from '../responses/ResponsePrincipal'; import { Permission } from './Permission'; /** @@ -21,4 +22,6 @@ export abstract class Principal { @IsOptional() @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 2b5b9b3..3c06e85 100644 --- a/src/models/entities/User.ts +++ b/src/models/entities/User.ts @@ -1,6 +1,8 @@ import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUUID } from "class-validator"; import { ChildEntity, Column, JoinTable, ManyToMany, OneToMany } from "typeorm"; import { config } from '../../config'; +import { ResponsePrincipal } from '../responses/ResponsePrincipal'; +import { ResponseUser } from '../responses/ResponseUser'; import { Permission } from './Permission'; import { Principal } from './Principal'; import { UserAction } from './UserAction'; @@ -126,4 +128,8 @@ export class User extends Principal { }) 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 21dfe25..c14f8fb 100644 --- a/src/models/entities/UserGroup.ts +++ b/src/models/entities/UserGroup.ts @@ -4,6 +4,8 @@ import { IsString } from "class-validator"; import { ChildEntity, Column } from "typeorm"; +import { ResponsePrincipal } from '../responses/ResponsePrincipal'; +import { ResponseUserGroup } from '../responses/ResponseUserGroup'; import { Principal } from './Principal'; /** @@ -27,4 +29,8 @@ export class UserGroup extends Principal { @IsOptional() @IsString() description?: string; + + public toResponse(): ResponsePrincipal { + return new ResponseUserGroup(this); + } } \ No newline at end of file diff --git a/src/models/responses/ResponsePermission.ts b/src/models/responses/ResponsePermission.ts index 1ce3161..824e83b 100644 --- a/src/models/responses/ResponsePermission.ts +++ b/src/models/responses/ResponsePermission.ts @@ -4,9 +4,9 @@ import { IsObject } from "class-validator"; import { Permission } from '../entities/Permission'; -import { Principal } from '../entities/Principal'; import { PermissionAction } from '../enums/PermissionAction'; import { PermissionTarget } from '../enums/PermissionTargets'; +import { ResponsePrincipal } from './ResponsePrincipal'; /** * Defines a track of given length. @@ -23,7 +23,7 @@ export class ResponsePermission { */ @IsObject() @IsNotEmpty() - principal: Principal; + principal: ResponsePrincipal; /** * The permissions's target. @@ -39,7 +39,7 @@ export class ResponsePermission { public constructor(permission: Permission) { this.id = permission.id; - this.principal = permission.principal; + this.principal = permission.principal.toResponse(); this.target = permission.target; this.action = permission.action; } 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; + } +} -- 2.47.2 From d4293c164db3478c5adc799e33b1d721e99b1972 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 15:37:45 +0100 Subject: [PATCH 11/37] Implemented permission deletion ref #6 --- src/controllers/PermissionController.ts | 59 +++++++++++-------------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/src/controllers/PermissionController.ts b/src/controllers/PermissionController.ts index a90ed5b..e056f01 100644 --- a/src/controllers/PermissionController.ts +++ b/src/controllers/PermissionController.ts @@ -1,9 +1,11 @@ -import { Body, Get, JsonController, OnUndefined, Param, Post } from 'routing-controllers'; +import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam } from 'routing-controllers'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { getConnectionManager, Repository } from 'typeorm'; import { PermissionNotFoundError } from '../errors/PermissionErrors'; +import { PrincipalNotFoundError } from '../errors/PrincipalsErrors'; import { CreatePermission } from '../models/actions/CreatePermission'; import { Permission } from '../models/entities/Permission'; +import { ResponseEmpty } from '../models/responses/ResponseEmpty'; import { ResponsePermission } from '../models/responses/ResponsePermission'; @@ -46,6 +48,7 @@ export class PermissionController { @Post() @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; @@ -60,52 +63,40 @@ export class PermissionController { return new ResponsePermission(permission); } + /* @Put('/:id') - @ResponseSchema(ResponseRunnerTeam) - @ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 }) - @ResponseSchema(RunnerTeamIdsNotMatchingError, { statusCode: 406 }) + @ResponseSchema(ResponsePrincipal) + @ResponseSchema(PermissionNotFoundError, { statusCode: 404 }) + @ResponseSchema(PermissionIdsNotMatchingError, { statusCode: 406 }) @OpenAPI({ description: "Update a runnerTeam object (id can't be changed)." }) - async put(@Param('id') id: number, @Body({ validate: true }) runnerTeam: UpdateRunnerTeam) { - let oldRunnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact'] }); + async put(@Param('id') id: number, @EntityFromBody() permission: Permission) { + let oldPermission = await this.permissionRepository.findOne({ id: id }, { relations: ['principal'] }); - if (!oldRunnerTeam) { - throw new RunnerTeamNotFoundError(); + if (!oldPermission) { + throw new PermissionNotFoundError(); } - if (oldRunnerTeam.id != runnerTeam.id) { - throw new RunnerTeamIdsNotMatchingError(); + if (oldPermission.id != permission.id) { + throw new PermissionIdsNotMatchingError(); } - await this.runnerTeamRepository.update(oldRunnerTeam, await runnerTeam.toRunnerTeam()); + await this.permissionRepository.update(oldPermission, permission); - return new ResponseRunnerTeam(await this.runnerTeamRepository.findOne({ id: runnerTeam.id }, { relations: ['parentGroup', 'contact'] })); - } + return new ResponsePermission(await this.permissionRepository.findOne({ id: permission.id }, { relations: ['principal'] })); + }*/ @Delete('/:id') - @ResponseSchema(ResponseRunnerTeam) + @ResponseSchema(ResponsePermission) @ResponseSchema(ResponseEmpty, { statusCode: 204 }) - @ResponseSchema(RunnerTeamHasRunnersError, { statusCode: 406 }) @OnUndefined(204) - @OpenAPI({ description: 'Delete a specified runnerTeam (if it exists).' }) + @OpenAPI({ description: 'Delete a specified permission (if it exists).' }) async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { - let team = await this.runnerTeamRepository.findOne({ id: id }); - if (!team) { return null; } - let runnerTeam = await this.runnerTeamRepository.findOne(team, { relations: ['parentGroup', 'contact', 'runners'] }); + let permission = await this.permissionRepository.findOne({ id: id }, { relations: ['principal'] }); + if (!permission) { return null; } - if (!force) { - if (runnerTeam.runners.length != 0) { - throw new RunnerTeamHasRunnersError(); - } - } - const runnerController = new RunnerController() - for (let runner of runnerTeam.runners) { - await runnerController.remove(runner.id, true); - } - - const responseTeam = new ResponseRunnerTeam(runnerTeam); - await this.runnerTeamRepository.delete(team); - return responseTeam; + const responsePermission = new ResponsePermission(permission); + await this.permissionRepository.delete(permission); + return responsePermission; } - */ -} +} \ No newline at end of file -- 2.47.2 From ff3a5b4545d0359cd4af94229bb71c023888a860 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 15:49:42 +0100 Subject: [PATCH 12/37] User deletion now also delete's the users permissons ref #6 --- src/controllers/UserController.ts | 14 ++++++++------ src/models/entities/Principal.ts | 5 ++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index ae84f47..e460ad1 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -8,6 +8,7 @@ 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') @@ -86,15 +87,16 @@ export class UserController { @OnUndefined(204) @OpenAPI({ description: 'Delete a specified runner (if it exists).' }) async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { - let runner = await this.userRepository.findOne({ id: id }); - if (!runner) { return null; } - const responseUser = await this.userRepository.findOne(runner, { relations: ['permissions', 'groups'] }); + let user = await this.userRepository.findOne({ id: id }); + if (!user) { return null; } + const responseUser = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] });; - if (!runner) { - throw new UserNotFoundError(); + const permissionControler = new PermissionController(); + for (let permission of responseUser.permissions) { + await permissionControler.remove(permission.id, true); } - await this.userRepository.delete(runner); + await this.userRepository.delete(user); return new ResponseUser(responseUser); } } diff --git a/src/models/entities/Principal.ts b/src/models/entities/Principal.ts index 8e74368..618e3db 100644 --- a/src/models/entities/Principal.ts +++ b/src/models/entities/Principal.ts @@ -1,4 +1,4 @@ -import { IsInt, IsOptional } from 'class-validator'; +import { IsInt } from 'class-validator'; import { Entity, OneToMany, PrimaryGeneratedColumn, TableInheritance } from 'typeorm'; import { ResponsePrincipal } from '../responses/ResponsePrincipal'; import { Permission } from './Permission'; @@ -19,9 +19,8 @@ export abstract class Principal { /** * permissions */ - @IsOptional() @OneToMany(() => Permission, permission => permission.principal, { nullable: true }) - permissions?: Permission[]; + permissions: Permission[]; public abstract toResponse(): ResponsePrincipal; } \ No newline at end of file -- 2.47.2 From 882065470adf15ee38a6a0047d42c9a9d864f12d Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 16:05:25 +0100 Subject: [PATCH 13/37] Implemented permission updateing ref #6 --- src/controllers/PermissionController.ts | 20 +++++--- src/errors/PermissionErrors.ts | 11 +++++ src/errors/PrincipalErrors.ts | 24 +++++++++ src/errors/PrincipalsErrors.ts | 13 ----- src/models/actions/CreatePermission.ts | 2 +- src/models/actions/UpdatePermission.ts | 65 +++++++++++++++++++++++++ 6 files changed, 113 insertions(+), 22 deletions(-) create mode 100644 src/errors/PrincipalErrors.ts delete mode 100644 src/errors/PrincipalsErrors.ts create mode 100644 src/models/actions/UpdatePermission.ts diff --git a/src/controllers/PermissionController.ts b/src/controllers/PermissionController.ts index e056f01..5304fee 100644 --- a/src/controllers/PermissionController.ts +++ b/src/controllers/PermissionController.ts @@ -1,12 +1,14 @@ -import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam } from 'routing-controllers'; +import { 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 { PermissionNotFoundError } from '../errors/PermissionErrors'; -import { PrincipalNotFoundError } from '../errors/PrincipalsErrors'; +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') @@ -64,13 +66,15 @@ export class PermissionController { return new ResponsePermission(permission); } - /* + @Put('/:id') @ResponseSchema(ResponsePrincipal) @ResponseSchema(PermissionNotFoundError, { statusCode: 404 }) + @ResponseSchema(PrincipalNotFoundError, { statusCode: 404 }) @ResponseSchema(PermissionIdsNotMatchingError, { statusCode: 406 }) - @OpenAPI({ description: "Update a runnerTeam object (id can't be changed)." }) - async put(@Param('id') id: number, @EntityFromBody() permission: Permission) { + @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) { @@ -81,10 +85,10 @@ export class PermissionController { throw new PermissionIdsNotMatchingError(); } - await this.permissionRepository.update(oldPermission, permission); + await this.permissionRepository.update(oldPermission, await permission.toPermission()); return new ResponsePermission(await this.permissionRepository.findOne({ id: permission.id }, { relations: ['principal'] })); - }*/ + } @Delete('/:id') @ResponseSchema(ResponsePermission) diff --git a/src/errors/PermissionErrors.ts b/src/errors/PermissionErrors.ts index e53b143..c2492b4 100644 --- a/src/errors/PermissionErrors.ts +++ b/src/errors/PermissionErrors.ts @@ -22,4 +22,15 @@ export class PermissionIdsNotMatchingError extends NotAcceptableError { @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/errors/PrincipalsErrors.ts b/src/errors/PrincipalsErrors.ts deleted file mode 100644 index 73e9819..0000000 --- a/src/errors/PrincipalsErrors.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { IsString } from 'class-validator'; -import { 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!" -} \ No newline at end of file diff --git a/src/models/actions/CreatePermission.ts b/src/models/actions/CreatePermission.ts index 829bff1..d7fe91a 100644 --- a/src/models/actions/CreatePermission.ts +++ b/src/models/actions/CreatePermission.ts @@ -3,7 +3,7 @@ import { IsNotEmpty } from "class-validator"; import { getConnectionManager } from 'typeorm'; -import { PrincipalNotFoundError } from '../../errors/PrincipalsErrors'; +import { PrincipalNotFoundError } from '../../errors/PrincipalErrors'; import { Permission } from '../entities/Permission'; import { Principal } from '../entities/Principal'; import { PermissionAction } from '../enums/PermissionAction'; 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 -- 2.47.2 From 6a7e8ccc37a05a2745ff85d785e6ceb9a180c71b Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 16:10:33 +0100 Subject: [PATCH 14/37] Now with duplication avoidance ref #6 --- src/controllers/PermissionController.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/controllers/PermissionController.ts b/src/controllers/PermissionController.ts index 5304fee..3763145 100644 --- a/src/controllers/PermissionController.ts +++ b/src/controllers/PermissionController.ts @@ -59,6 +59,8 @@ export class PermissionController { } 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'] }); @@ -84,6 +86,11 @@ export class PermissionController { 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()); -- 2.47.2 From 9dc336f0bbe3708201c1745f55f8e00f4a82557f Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 16:21:59 +0100 Subject: [PATCH 15/37] Added permission deletion on group deletion ref #6 --- src/controllers/UserGroupController.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/controllers/UserGroupController.ts b/src/controllers/UserGroupController.ts index 8a90d94..95c6ca3 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 { 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,6 +6,8 @@ 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') @@ -71,17 +73,21 @@ export class UserGroupController { } @Delete('/:id') - @ResponseSchema(UserGroup) + @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); } } -- 2.47.2 From b9e91502cdbfa391805435cc2ea07a563894f3c7 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 17:11:44 +0100 Subject: [PATCH 16/37] Cleaned up the auth checker a little bit --- src/authchecker.ts | 38 ++++++++++++-------------------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/src/authchecker.ts b/src/authchecker.ts index 00c84c1..2075076 100644 --- a/src/authchecker.ts +++ b/src/authchecker.ts @@ -4,43 +4,29 @@ import { getConnectionManager } from 'typeorm'; import { config } from './config'; import { IllegalJWTError, NoPermissionError, UserNonexistantOrRefreshtokenInvalidError } from './errors/AuthError'; import { User } from './models/entities/User'; -// ----------- -const authchecker = async (action: Action, permissions: string | string[]) => { - let required_permissions = undefined + +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"]; + + const provided_token = action.request.headers["authorization"].replace("Bearer ", ""); let jwtPayload = undefined try { jwtPayload = jwt.verify(provided_token, config.jwt_secret); } catch (error) { throw new IllegalJWTError() } - const count = await getConnectionManager().get().getRepository(User).count({ id: jwtPayload["userdetails"]["id"], refreshTokenCount: jwtPayload["userdetails"]["refreshTokenCount"] }) - if (count !== 1) { - throw new UserNonexistantOrRefreshtokenInvalidError() - } - 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() - } - // + const user = await getConnectionManager().get().getRepository(User).findOne({ id: jwtPayload["userdetails"]["id"], refreshTokenCount: jwtPayload["userdetails"]["refreshTokenCount"] }, { relations: ['permissions'] }) + if (!user) { throw new UserNonexistantOrRefreshtokenInvalidError() } + if (!jwtPayload.permissions) { throw new NoPermissionError(); } + + action.response.local = {} + action.response.local.jwtPayload = jwtPayload.permissions + //TODO: Check Permissions try { jwt.verify(provided_token, config.jwt_secret); return true -- 2.47.2 From 6237e62a03a6d35beed78c4283b82c26d0753ce9 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 17:15:44 +0100 Subject: [PATCH 17/37] Reimplmented the old permission checking system ref #6 --- src/authchecker.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/authchecker.ts b/src/authchecker.ts index 2075076..344e0c1 100644 --- a/src/authchecker.ts +++ b/src/authchecker.ts @@ -26,7 +26,16 @@ const authchecker = async (action: Action, permissions: string[] | string) => { action.response.local = {} action.response.local.jwtPayload = jwtPayload.permissions - //TODO: Check 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() + } + }); try { jwt.verify(provided_token, config.jwt_secret); return true -- 2.47.2 From 445e96dcdf273cb67724ebce86b2eb9bd4155085 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 17:17:02 +0100 Subject: [PATCH 18/37] Added toString for permissions ref #6 --- src/models/entities/Permission.ts | 7 +++++++ src/models/entities/User.ts | 3 +++ 2 files changed, 10 insertions(+) diff --git a/src/models/entities/Permission.ts b/src/models/entities/Permission.ts index f22f74a..5d93258 100644 --- a/src/models/entities/Permission.ts +++ b/src/models/entities/Permission.ts @@ -36,4 +36,11 @@ export class Permission { */ @Column({ type: 'varchar' }) 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/User.ts b/src/models/entities/User.ts index 3c06e85..27e14ba 100644 --- a/src/models/entities/User.ts +++ b/src/models/entities/User.ts @@ -129,6 +129,9 @@ export class User extends Principal { return final_permissions } + /** + * Turn this into a response. + */ public toResponse(): ResponsePrincipal { return new ResponseUser(this); } -- 2.47.2 From b21dd6f0c0dff656c7b1d2acc4142a5cf44c66dc Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 17:19:02 +0100 Subject: [PATCH 19/37] Added tracks/get as test-route for auth ref #6 --- src/controllers/TrackController.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/controllers/TrackController.ts b/src/controllers/TrackController.ts index e17e098..ab56159 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'; @@ -21,8 +21,9 @@ export class TrackController { } @Get() + @Authorized("TRACK:READ") @ResponseSchema(ResponseTrack, { isArray: true }) - @OpenAPI({ description: "Lists all tracks." }) + @OpenAPI({ description: "Lists all tracks.", security: [{ "AuthToken": [] }] }) async getAll() { let responseTracks: ResponseTrack[] = new Array(); const tracks = await this.trackRepository.find(); -- 2.47.2 From 65a8449ea390503060a9882dc6a102411ad93880 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 17:57:48 +0100 Subject: [PATCH 20/37] Now with 1000% cleaner jwt generation ref #6 --- src/jwtcreator.ts | 85 +++++++++++++++++++++++++++++++ src/models/actions/CreateAuth.ts | 45 +++++++--------- src/models/actions/RefreshAuth.ts | 21 +++----- 3 files changed, 108 insertions(+), 43 deletions(-) create mode 100644 src/jwtcreator.ts diff --git a/src/jwtcreator.ts b/src/jwtcreator.ts new file mode 100644 index 0000000..d32d231 --- /dev/null +++ b/src/jwtcreator.ts @@ -0,0 +1,85 @@ +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'; + +export class JwtCreator { + 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, + userid: user.id, + exp: expiry_timestamp + }, config.jwt_secret) + } + + 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) + } +} + +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; + + 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); + } + + public getPermissions(user: User): string[] { + let returnPermissions: string[] = new Array(); + for (let permission of user.permissions) { + returnPermissions.push(permission.toString()); + } + return returnPermissions; + } +} \ No newline at end of file diff --git a/src/models/actions/CreateAuth.ts b/src/models/actions/CreateAuth.ts index b31bee6..a29f28a 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'], 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/RefreshAuth.ts b/src/models/actions/RefreshAuth.ts index afd22fe..757dea3 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'; @@ -29,24 +30,14 @@ export class RefreshAuth { 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 -- 2.47.2 From 6403e386ab3a2ec5a994ad8f50fecdc73a8791c9 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 19:07:31 +0100 Subject: [PATCH 21/37] Now with smooth access token refreshing ref #6 --- package.json | 3 +- src/authchecker.ts | 61 +++++++++++++++++++++---------- src/jwtcreator.ts | 6 +-- src/models/actions/RefreshAuth.ts | 2 +- 4 files changed, 47 insertions(+), 25 deletions(-) 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 344e0c1..5243598 100644 --- a/src/authchecker.ts +++ b/src/authchecker.ts @@ -1,10 +1,13 @@ +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; if (typeof permissions === "string") { @@ -13,34 +16,52 @@ const authchecker = async (action: Action, permissions: string[] | string) => { required_permissions = permissions } - const provided_token = action.request.headers["authorization"].replace("Bearer ", ""); + let provided_token = "" + action.request.headers["authorization"]; + try { + provided_token = provided_token.replace("Bearer ", ""); + } + catch { } let jwtPayload = undefined try { jwtPayload = jwt.verify(provided_token, config.jwt_secret); + jwtPayload = jwtPayload["userdetails"]; } catch (error) { - throw new IllegalJWTError() + jwtPayload = await refresh(action); } - const user = await getConnectionManager().get().getRepository(User).findOne({ id: jwtPayload["userdetails"]["id"], refreshTokenCount: jwtPayload["userdetails"]["refreshTokenCount"] }, { relations: ['permissions'] }) + 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(); } + if (!jwtPayload["permissions"]) { throw new NoPermissionError(); } 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() - } - }); - try { - jwt.verify(provided_token, config.jwt_secret); - return true - } catch (error) { - return false + action.response.local.jwtPayload = jwtPayload; + for (let required_permission of required_permissions) { + if (!(jwtPayload["permissions"].includes(required_permission))) { return false; } } + return true; +} + +const refresh = async (action: Action) => { + let refresh_token = undefined; + try { + 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'] }) + 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/jwtcreator.ts b/src/jwtcreator.ts index d32d231..cb3269d 100644 --- a/src/jwtcreator.ts +++ b/src/jwtcreator.ts @@ -7,8 +7,8 @@ export class JwtCreator { 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, - userid: user.id, + refreshTokenCount: user.refreshTokenCount, + id: user.id, exp: expiry_timestamp }, config.jwt_secret) } @@ -22,7 +22,7 @@ export class JwtCreator { } } -class JwtUser { +export class JwtUser { @IsInt() id: number; diff --git a/src/models/actions/RefreshAuth.ts b/src/models/actions/RefreshAuth.ts index 757dea3..1435436 100644 --- a/src/models/actions/RefreshAuth.ts +++ b/src/models/actions/RefreshAuth.ts @@ -23,7 +23,7 @@ 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'] }); if (!found_user) { throw new UserNotFoundError() } -- 2.47.2 From 1d54fb085b43aaa9584a9998a2381fd49bec0040 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 19:19:47 +0100 Subject: [PATCH 22/37] Shoothed out variable nameing scheme ref #6 --- src/authchecker.ts | 2 +- src/models/actions/HandleLogout.ts | 4 ++-- src/models/actions/RefreshAuth.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/authchecker.ts b/src/authchecker.ts index 5243598..b17a803 100644 --- a/src/authchecker.ts +++ b/src/authchecker.ts @@ -43,7 +43,7 @@ const authchecker = async (action: Action, permissions: string[] | string) => { const refresh = async (action: Action) => { let refresh_token = undefined; try { - cookie.parse(action.request.headers["cookie"])["lfk_backend__refresh_token"]; + refresh_token = cookie.parse(action.request.headers["cookie"])["lfk_backend__refresh_token"]; } catch { throw new IllegalJWTError(); 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 1435436..ee263a8 100644 --- a/src/models/actions/RefreshAuth.ts +++ b/src/models/actions/RefreshAuth.ts @@ -27,7 +27,7 @@ export class RefreshAuth { if (!found_user) { throw new UserNotFoundError() } - if (found_user.refreshTokenCount !== decoded["refreshtokencount"]) { + if (found_user.refreshTokenCount !== decoded["refreshTokenCount"]) { throw new RefreshTokenCountInvalidError() } //Create the auth token -- 2.47.2 From 428e2c38cef3ce34714a28ce78d18681d28b8bfc Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 19:33:10 +0100 Subject: [PATCH 23/37] Added coments to the jwt creator --- src/jwtcreator.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/jwtcreator.ts b/src/jwtcreator.ts index cb3269d..5fd73ae 100644 --- a/src/jwtcreator.ts +++ b/src/jwtcreator.ts @@ -3,7 +3,15 @@ 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({ @@ -13,6 +21,11 @@ export class JwtCreator { }, 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({ @@ -22,6 +35,9 @@ export class JwtCreator { } } +/** + * Special variant of the user class that + */ export class JwtUser { @IsInt() id: number; @@ -62,6 +78,10 @@ export class JwtUser { @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; @@ -75,6 +95,10 @@ export class JwtUser { 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) { -- 2.47.2 From 595a9213c199f5bd0274c8dde382845f308e1567 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 19:42:08 +0100 Subject: [PATCH 24/37] Added comments and formatting to the auth checker ref #6 --- src/authchecker.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/authchecker.ts b/src/authchecker.ts index b17a803..44cfd36 100644 --- a/src/authchecker.ts +++ b/src/authchecker.ts @@ -7,7 +7,11 @@ import { IllegalJWTError, NoPermissionError, UserNonexistantOrRefreshtokenInvali import { JwtCreator, JwtUser } from './JwtCreator'; import { User } from './models/entities/User'; - +/** + * 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") { @@ -16,18 +20,15 @@ const authchecker = async (action: Action, permissions: string[] | string) => { required_permissions = permissions } - let provided_token = "" + action.request.headers["authorization"]; - try { - provided_token = provided_token.replace("Bearer ", ""); - } - catch { } 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) { jwtPayload = await refresh(action); } + 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(); } @@ -40,6 +41,10 @@ const authchecker = async (action: Action, permissions: string[] | string) => { 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 { -- 2.47.2 From 2240a45a91c7597cae154cad0672784965c1cbe6 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 19:49:39 +0100 Subject: [PATCH 25/37] Added class validation for the enum ref #6 --- src/models/actions/CreatePermission.ts | 3 +++ src/models/entities/Permission.ts | 3 +++ src/models/enums/PermissionAction.ts | 4 ++-- src/models/enums/PermissionTargets.ts | 3 ++- src/models/responses/ResponsePermission.ts | 3 +++ 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/models/actions/CreatePermission.ts b/src/models/actions/CreatePermission.ts index d7fe91a..7a49e03 100644 --- a/src/models/actions/CreatePermission.ts +++ b/src/models/actions/CreatePermission.ts @@ -1,4 +1,5 @@ import { + IsEnum, IsInt, IsNotEmpty } from "class-validator"; @@ -25,12 +26,14 @@ export class CreatePermission { * The permissions's target. */ @IsNotEmpty() + @IsEnum(PermissionTarget) target: PermissionTarget; /** * The permissions's action. */ @IsNotEmpty() + @IsEnum(PermissionAction) action: PermissionAction; /** diff --git a/src/models/entities/Permission.ts b/src/models/entities/Permission.ts index 5d93258..b95bb84 100644 --- a/src/models/entities/Permission.ts +++ b/src/models/entities/Permission.ts @@ -1,4 +1,5 @@ import { + IsEnum, IsInt, IsNotEmpty } from "class-validator"; @@ -29,12 +30,14 @@ export class Permission { */ @Column({ type: 'varchar' }) @IsNotEmpty() + @IsEnum(PermissionTarget) target: PermissionTarget; /** * The action type */ @Column({ type: 'varchar' }) + @IsEnum(PermissionAction) action: PermissionAction; /** diff --git a/src/models/enums/PermissionAction.ts b/src/models/enums/PermissionAction.ts index 8ea9e65..4535dfd 100644 --- a/src/models/enums/PermissionAction.ts +++ b/src/models/enums/PermissionAction.ts @@ -1,6 +1,6 @@ export enum PermissionAction { - READ = 'READ', - ADD = 'ADD', + GET = 'GET', + CREATE = 'CREATE', UPDATE = 'UPDATE', DELETE = 'DELETE' } \ No newline at end of file diff --git a/src/models/enums/PermissionTargets.ts b/src/models/enums/PermissionTargets.ts index 9a6f040..c12c988 100644 --- a/src/models/enums/PermissionTargets.ts +++ b/src/models/enums/PermissionTargets.ts @@ -4,5 +4,6 @@ export enum PermissionTarget { TEAM = 'RUNNERTEAM', TRACK = 'TRACK', USER = 'USER', - GROUP = 'USERGROUP' + GROUP = 'USERGROUP', + PERMISSION = 'PERMISSION' } \ No newline at end of file diff --git a/src/models/responses/ResponsePermission.ts b/src/models/responses/ResponsePermission.ts index 824e83b..23a3686 100644 --- a/src/models/responses/ResponsePermission.ts +++ b/src/models/responses/ResponsePermission.ts @@ -1,4 +1,5 @@ import { + IsEnum, IsInt, IsNotEmpty, IsObject @@ -29,12 +30,14 @@ export class ResponsePermission { * The permissions's target. */ @IsNotEmpty() + @IsEnum(PermissionTarget) target: PermissionTarget; /** * The permissions's action. */ @IsNotEmpty() + @IsEnum(PermissionAction) action: PermissionAction; public constructor(permission: Permission) { -- 2.47.2 From e25fc795fe006b27b64199868fb928b94851f4cf Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 20:06:27 +0100 Subject: [PATCH 26/37] Added additional targets and actions for permissions ref #6 --- src/models/enums/PermissionAction.ts | 3 ++- src/models/enums/PermissionTargets.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/models/enums/PermissionAction.ts b/src/models/enums/PermissionAction.ts index 4535dfd..0e984bf 100644 --- a/src/models/enums/PermissionAction.ts +++ b/src/models/enums/PermissionAction.ts @@ -2,5 +2,6 @@ export enum PermissionAction { GET = 'GET', CREATE = 'CREATE', UPDATE = 'UPDATE', - DELETE = 'DELETE' + DELETE = 'DELETE', + IMPORT = 'IMPORT' } \ No newline at end of file diff --git a/src/models/enums/PermissionTargets.ts b/src/models/enums/PermissionTargets.ts index c12c988..7b18eb0 100644 --- a/src/models/enums/PermissionTargets.ts +++ b/src/models/enums/PermissionTargets.ts @@ -1,7 +1,7 @@ export enum PermissionTarget { RUNNER = 'RUNNER', - ORGANISATION = 'RUNNERORGANISATION', - TEAM = 'RUNNERTEAM', + ORGANISATION = 'ORGANISATION', + TEAM = 'TEAM', TRACK = 'TRACK', USER = 'USER', GROUP = 'USERGROUP', -- 2.47.2 From cdfd0e0d64ae469b0ecfde353834b89364708b28 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 20:07:05 +0100 Subject: [PATCH 27/37] Added the openapi security header to all routes that need some kind of auth ref #6 --- src/controllers/ImportController.ts | 5 +++-- src/controllers/PermissionController.ts | 2 +- src/controllers/RunnerController.ts | 2 +- src/controllers/RunnerOrganisationController.ts | 2 +- src/controllers/RunnerTeamController.ts | 2 +- src/controllers/TrackController.ts | 3 +-- src/controllers/UserController.ts | 1 + src/controllers/UserGroupController.ts | 1 + 8 files changed, 10 insertions(+), 8 deletions(-) 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 index 3763145..f5264f6 100644 --- a/src/controllers/PermissionController.ts +++ b/src/controllers/PermissionController.ts @@ -12,7 +12,7 @@ import { ResponsePrincipal } from '../models/responses/ResponsePrincipal'; @JsonController('/permissions') -//@Authorized('RUNNERS:read') +@OpenAPI({ security: [{ "AuthToken": [] }] }) export class PermissionController { private permissionRepository: Repository; diff --git a/src/controllers/RunnerController.ts b/src/controllers/RunnerController.ts index a8bf93f..05f8c93 100644 --- a/src/controllers/RunnerController.ts +++ b/src/controllers/RunnerController.ts @@ -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; diff --git a/src/controllers/RunnerOrganisationController.ts b/src/controllers/RunnerOrganisationController.ts index 8c48496..d435b1d 100644 --- a/src/controllers/RunnerOrganisationController.ts +++ b/src/controllers/RunnerOrganisationController.ts @@ -12,7 +12,7 @@ import { RunnerTeamController } from './RunnerTeamController'; @JsonController('/organisations') -//@Authorized('RUNNERS:read') +@OpenAPI({ security: [{ "AuthToken": [] }] }) export class RunnerOrganisationController { private runnerOrganisationRepository: Repository; diff --git a/src/controllers/RunnerTeamController.ts b/src/controllers/RunnerTeamController.ts index c9c4d5c..4b90c53 100644 --- a/src/controllers/RunnerTeamController.ts +++ b/src/controllers/RunnerTeamController.ts @@ -11,7 +11,7 @@ import { RunnerController } from './RunnerController'; @JsonController('/teams') -//@Authorized('RUNNERS:read') +@OpenAPI({ security: [{ "AuthToken": [] }] }) export class RunnerTeamController { private runnerTeamRepository: Repository; diff --git a/src/controllers/TrackController.ts b/src/controllers/TrackController.ts index ab56159..8a3736b 100644 --- a/src/controllers/TrackController.ts +++ b/src/controllers/TrackController.ts @@ -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; @@ -23,7 +23,6 @@ export class TrackController { @Get() @Authorized("TRACK:READ") @ResponseSchema(ResponseTrack, { isArray: true }) - @OpenAPI({ description: "Lists all tracks.", security: [{ "AuthToken": [] }] }) async getAll() { let responseTracks: ResponseTrack[] = new Array(); const tracks = await this.trackRepository.find(); diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index e460ad1..4cc1544 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -12,6 +12,7 @@ import { PermissionController } from './PermissionController'; @JsonController('/users') +@OpenAPI({ security: [{ "AuthToken": [] }] }) export class UserController { private userRepository: Repository; diff --git a/src/controllers/UserGroupController.ts b/src/controllers/UserGroupController.ts index 95c6ca3..f5212ea 100644 --- a/src/controllers/UserGroupController.ts +++ b/src/controllers/UserGroupController.ts @@ -11,6 +11,7 @@ import { PermissionController } from './PermissionController'; @JsonController('/usergroups') +@OpenAPI({ security: [{ "AuthToken": [] }] }) export class UserGroupController { private userGroupsRepository: Repository; -- 2.47.2 From 744faba7eec3702b8cbd15fe51e7a248cc7cc19a Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 20:33:13 +0100 Subject: [PATCH 28/37] Added auth to all endpoints --- src/controllers/PermissionController.ts | 7 ++++++- src/controllers/RunnerController.ts | 7 ++++++- src/controllers/RunnerOrganisationController.ts | 7 ++++++- src/controllers/RunnerTeamController.ts | 7 ++++++- src/controllers/TrackController.ts | 6 +++++- src/controllers/UserController.ts | 7 ++++++- src/controllers/UserGroupController.ts | 7 ++++++- 7 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/controllers/PermissionController.ts b/src/controllers/PermissionController.ts index f5264f6..6be00fd 100644 --- a/src/controllers/PermissionController.ts +++ b/src/controllers/PermissionController.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 { PermissionIdsNotMatchingError, PermissionNeedsPrincipalError, PermissionNotFoundError } from '../errors/PermissionErrors'; @@ -24,6 +24,7 @@ export class PermissionController { } @Get() + @Authorized("PERMISSION:GET") @ResponseSchema(ResponsePermission, { isArray: true }) @OpenAPI({ description: 'Lists all permissions.' }) async getAll() { @@ -37,6 +38,7 @@ export class PermissionController { @Get('/:id') + @Authorized("PERMISSION:GET") @ResponseSchema(ResponsePermission) @ResponseSchema(PermissionNotFoundError, { statusCode: 404 }) @OnUndefined(PermissionNotFoundError) @@ -49,6 +51,7 @@ export class PermissionController { @Post() + @Authorized("PERMISSION:CREATE") @ResponseSchema(ResponsePermission) @ResponseSchema(PrincipalNotFoundError, { statusCode: 404 }) @OpenAPI({ description: 'Create a new runnerTeam object (id will be generated automagicly).' }) @@ -70,6 +73,7 @@ export class PermissionController { @Put('/:id') + @Authorized("PERMISSION:UPDATE") @ResponseSchema(ResponsePrincipal) @ResponseSchema(PermissionNotFoundError, { statusCode: 404 }) @ResponseSchema(PrincipalNotFoundError, { statusCode: 404 }) @@ -98,6 +102,7 @@ export class PermissionController { } @Delete('/:id') + @Authorized("PERMISSION:DELETE") @ResponseSchema(ResponsePermission) @ResponseSchema(ResponseEmpty, { statusCode: 204 }) @OnUndefined(204) diff --git a/src/controllers/RunnerController.ts b/src/controllers/RunnerController.ts index 05f8c93..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'; @@ -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 d435b1d..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'; @@ -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 4b90c53..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'; @@ -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 8a3736b..4691c86 100644 --- a/src/controllers/TrackController.ts +++ b/src/controllers/TrackController.ts @@ -21,7 +21,7 @@ export class TrackController { } @Get() - @Authorized("TRACK:READ") + @Authorized("TRACK:GET") @ResponseSchema(ResponseTrack, { isArray: true }) async getAll() { let responseTracks: ResponseTrack[] = new Array(); @@ -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 4cc1544..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, 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'; @@ -24,6 +24,7 @@ export class UserController { } @Get() + @Authorized("USER:GET") @ResponseSchema(User, { isArray: true }) @OpenAPI({ description: 'Lists all users.' }) async getAll() { @@ -36,6 +37,7 @@ export class UserController { } @Get('/:id') + @Authorized("USER:GET") @ResponseSchema(User) @ResponseSchema(UserNotFoundError, { statusCode: 404 }) @OnUndefined(UserNotFoundError) @@ -47,6 +49,7 @@ export class UserController { } @Post() + @Authorized("USER:CREATE") @ResponseSchema(User) @ResponseSchema(UserGroupNotFoundError) @OpenAPI({ description: 'Create a new user object (id will be generated automagicly).' }) @@ -63,6 +66,7 @@ export class UserController { } @Put('/:id') + @Authorized("USER:UPDATE") @ResponseSchema(User) @ResponseSchema(UserNotFoundError, { statusCode: 404 }) @ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 }) @@ -83,6 +87,7 @@ export class UserController { } @Delete('/:id') + @Authorized("USER:DELETE") @ResponseSchema(User) @ResponseSchema(ResponseEmpty, { statusCode: 204 }) @OnUndefined(204) diff --git a/src/controllers/UserGroupController.ts b/src/controllers/UserGroupController.ts index f5212ea..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, 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'; @@ -23,6 +23,7 @@ export class UserGroupController { } @Get() + @Authorized("USERGROUP:GET") @ResponseSchema(UserGroup, { isArray: true }) @OpenAPI({ description: 'Lists all usergroups.' }) getAll() { @@ -30,6 +31,7 @@ export class UserGroupController { } @Get('/:id') + @Authorized("USERGROUP:GET") @ResponseSchema(UserGroup) @ResponseSchema(UserGroupNotFoundError, { statusCode: 404 }) @OnUndefined(UserGroupNotFoundError) @@ -39,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).' }) @@ -54,6 +57,7 @@ export class UserGroupController { } @Put('/:id') + @Authorized("USERGROUP:UPDATE") @ResponseSchema(UserGroup) @ResponseSchema(UserGroupNotFoundError, { statusCode: 404 }) @ResponseSchema(UserGroupIdsNotMatchingError, { statusCode: 406 }) @@ -74,6 +78,7 @@ export class UserGroupController { } @Delete('/:id') + @Authorized("USERGROUP:DELETE") @ResponseSchema(ResponseUserGroup) @ResponseSchema(ResponseEmpty, { statusCode: 204 }) @OnUndefined(204) -- 2.47.2 From f25ae9ba4f390c9ec71891ed326d47060dfb7133 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 20:33:27 +0100 Subject: [PATCH 29/37] Added a admin group with all permissions to seeding --- src/seeds/SeedUsers.ts | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/src/seeds/SeedUsers.ts b/src/seeds/SeedUsers.ts index 9db0fd7..e366916 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); + let demouser = 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 -- 2.47.2 From 1a9c860188dda598582d01ba5c39b36cc33f4d55 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 20:53:35 +0100 Subject: [PATCH 30/37] Formatting #6 --- src/authchecker.ts | 2 +- src/models/actions/CreateUser.ts | 2 +- src/models/actions/RefreshAuth.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/authchecker.ts b/src/authchecker.ts index 44cfd36..f6ffdad 100644 --- a/src/authchecker.ts +++ b/src/authchecker.ts @@ -61,7 +61,7 @@ const refresh = async (action: Action) => { throw new IllegalJWTError(); } - const user = await getConnectionManager().get().getRepository(User).findOne({ id: jwtPayload["id"], refreshTokenCount: jwtPayload["refreshTokenCount"] }, { relations: ['permissions'] }) + 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); diff --git a/src/models/actions/CreateUser.ts b/src/models/actions/CreateUser.ts index 78fee8b..7b7fb29 100644 --- a/src/models/actions/CreateUser.ts +++ b/src/models/actions/CreateUser.ts @@ -109,7 +109,7 @@ export class CreateUser { if (found.length === 0) { errors++ } else { - groups.push(found[0]) + groups.push(found[0]); } } return groups; diff --git a/src/models/actions/RefreshAuth.ts b/src/models/actions/RefreshAuth.ts index ee263a8..e937f79 100644 --- a/src/models/actions/RefreshAuth.ts +++ b/src/models/actions/RefreshAuth.ts @@ -23,7 +23,7 @@ export class RefreshAuth { } catch (error) { throw new IllegalJWTError() } - const found_user = await getConnectionManager().get().getRepository(User).findOne({ id: decoded["id"] }, { 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() } -- 2.47.2 From d670b814a4d516f1874dc11dc3bccdd9c59c536c Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 21:42:43 +0100 Subject: [PATCH 31/37] Fixed the user->Group relation ref #6 --- src/models/actions/CreateAuth.ts | 2 +- src/models/actions/CreateUser.ts | 21 +++++---------------- src/models/entities/User.ts | 19 ------------------- 3 files changed, 6 insertions(+), 36 deletions(-) diff --git a/src/models/actions/CreateAuth.ts b/src/models/actions/CreateAuth.ts index a29f28a..2584aa2 100644 --- a/src/models/actions/CreateAuth.ts +++ b/src/models/actions/CreateAuth.ts @@ -27,7 +27,7 @@ export class CreateAuth { if (!this.password) { throw new PasswordNeededError(); } - const found_user = await getConnectionManager().get().getRepository(User).findOne({ relations: ['groups', 'permissions'], where: [{ username: this.username }, { email: this.email }] }); + 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(); } diff --git a/src/models/actions/CreateUser.ts b/src/models/actions/CreateUser.ts index 7b7fb29..1f80ee3 100644 --- a/src/models/actions/CreateUser.ts +++ b/src/models/actions/CreateUser.ts @@ -101,22 +101,11 @@ export class CreateUser { if (!Array.isArray(this.group)) { this.group = [this.group] } - const groupIDs: number[] = this.group - let errors = 0 - const validateusergroups = async () => { - for (const g of groupIDs) { - const found = await getConnectionManager().get().getRepository(UserGroup).find({ id: g }); - if (found.length === 0) { - errors++ - } else { - groups.push(found[0]); - } - } - return groups; - } - await validateusergroups() - if (errors !== 0) { - throw new UserGroupNotFoundError(); + 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/entities/User.ts b/src/models/entities/User.ts index 27e14ba..21970c4 100644 --- a/src/models/entities/User.ts +++ b/src/models/entities/User.ts @@ -3,7 +3,6 @@ import { ChildEntity, Column, JoinTable, ManyToMany, OneToMany } from "typeorm"; import { config } from '../../config'; import { ResponsePrincipal } from '../responses/ResponsePrincipal'; import { ResponseUser } from '../responses/ResponseUser'; -import { Permission } from './Permission'; import { Principal } from './Principal'; import { UserAction } from './UserAction'; import { UserGroup } from './UserGroup'; @@ -111,24 +110,6 @@ export class User extends Principal { @OneToMany(() => UserAction, action => action.user, { nullable: true }) actions: UserAction[] - /** - * calculate all permissions - */ - 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 - } - /** * Turn this into a response. */ -- 2.47.2 From d742ccd581fc803663d90916365d60b0c7caccd5 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 21:44:30 +0100 Subject: [PATCH 32/37] Jwt's now feature group permissions and permission deduplication ref #6 --- src/jwtcreator.ts | 7 ++++++- src/seeds/SeedUsers.ts | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/jwtcreator.ts b/src/jwtcreator.ts index 5fd73ae..e837f13 100644 --- a/src/jwtcreator.ts +++ b/src/jwtcreator.ts @@ -104,6 +104,11 @@ export class JwtUser { for (let permission of user.permissions) { returnPermissions.push(permission.toString()); } - return returnPermissions; + 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/seeds/SeedUsers.ts b/src/seeds/SeedUsers.ts index e366916..dae3b17 100644 --- a/src/seeds/SeedUsers.ts +++ b/src/seeds/SeedUsers.ts @@ -12,7 +12,7 @@ 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); - let demouser = await this.createUser(connection, adminGroup.id); + await this.createUser(connection, adminGroup.id); await this.createPermissions(connection, adminGroup.id); } -- 2.47.2 From b19f18ada16d7e7c150e3e4281603b0706435124 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 22:11:41 +0100 Subject: [PATCH 33/37] Added auth to all tests ref #6 --- src/tests/firsttest.spec.ts | 16 ++++++++-- src/tests/runnerOrgs/org_add.spec.ts | 26 +++++++++++----- src/tests/runnerOrgs/org_delete.spec.ts | 34 ++++++++++++++------- src/tests/runnerOrgs/org_get.spec.ts | 16 ++++++++-- src/tests/runnerOrgs/org_update.spec.ts | 20 +++++++++--- src/tests/runnerTeams/team_add.spec.ts | 26 +++++++++++----- src/tests/runnerTeams/team_delete.spec.ts | 22 +++++++++++--- src/tests/runnerTeams/team_get.spec.ts | 16 ++++++++-- src/tests/runnerTeams/team_update.spec.ts | 31 +++++++++++++------ src/tests/runners/runner_add.spec.ts | 32 ++++++++++++++------ src/tests/runners/runner_delete.spec.ts | 22 +++++++++++--- src/tests/runners/runner_get.spec.ts | 23 ++++++++++---- src/tests/runners/runner_update.spec.ts | 37 +++++++++++++++-------- src/tests/tracks.spec.ts | 32 ++++++++++++++------ 14 files changed, 259 insertions(+), 94 deletions(-) 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] -- 2.47.2 From c7fd0593fb5cce2b2ae39789073436d674a56825 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 22:42:48 +0100 Subject: [PATCH 34/37] Bugfix for bs file names ref #6 --- src/authchecker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/authchecker.ts b/src/authchecker.ts index f6ffdad..36cb40b 100644 --- a/src/authchecker.ts +++ b/src/authchecker.ts @@ -4,7 +4,7 @@ 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 { JwtCreator, JwtUser } from './jwtcreator'; import { User } from './models/entities/User'; /** -- 2.47.2 From 23758e7a91ddccef2b37b6b0c20b257de7681764 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 22:42:48 +0100 Subject: [PATCH 35/37] Bugfix for bs file names ref #6 --- src/authchecker.ts | 2 +- src/models/actions/CreateAuth.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/authchecker.ts b/src/authchecker.ts index f6ffdad..36cb40b 100644 --- a/src/authchecker.ts +++ b/src/authchecker.ts @@ -4,7 +4,7 @@ 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 { JwtCreator, JwtUser } from './jwtcreator'; import { User } from './models/entities/User'; /** diff --git a/src/models/actions/CreateAuth.ts b/src/models/actions/CreateAuth.ts index 2584aa2..adec57c 100644 --- a/src/models/actions/CreateAuth.ts +++ b/src/models/actions/CreateAuth.ts @@ -3,7 +3,7 @@ import { IsEmail, IsOptional, IsString } from 'class-validator'; import { getConnectionManager } from 'typeorm'; import { InvalidCredentialsError, PasswordNeededError, UserNotFoundError } from '../../errors/AuthError'; import { UsernameOrEmailNeededError } from '../../errors/UserErrors'; -import { JwtCreator } from '../../JwtCreator'; +import { JwtCreator } from '../../jwtcreator'; import { User } from '../entities/User'; import { Auth } from '../responses/ResponseAuth'; -- 2.47.2 From c3e3c6bed170f2c3002895fc06a4e0244f976b71 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 22:46:59 +0100 Subject: [PATCH 36/37] Manual overwrite ref #6 --- src/models/actions/CreateAuth.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/models/actions/CreateAuth.ts b/src/models/actions/CreateAuth.ts index adec57c..74fed72 100644 --- a/src/models/actions/CreateAuth.ts +++ b/src/models/actions/CreateAuth.ts @@ -7,6 +7,7 @@ import { JwtCreator } from '../../jwtcreator'; import { User } from '../entities/User'; import { Auth } from '../responses/ResponseAuth'; + export class CreateAuth { @IsOptional() @IsString() -- 2.47.2 From 631310f1589511803bd2044fc0f021e7f53226a7 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Fri, 18 Dec 2020 22:49:01 +0100 Subject: [PATCH 37/37] Fixed import for linux ref #6 --- src/models/actions/CreateAuth.ts | 1 - src/models/actions/RefreshAuth.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/models/actions/CreateAuth.ts b/src/models/actions/CreateAuth.ts index 74fed72..adec57c 100644 --- a/src/models/actions/CreateAuth.ts +++ b/src/models/actions/CreateAuth.ts @@ -7,7 +7,6 @@ import { JwtCreator } from '../../jwtcreator'; import { User } from '../entities/User'; import { Auth } from '../responses/ResponseAuth'; - export class CreateAuth { @IsOptional() @IsString() diff --git a/src/models/actions/RefreshAuth.ts b/src/models/actions/RefreshAuth.ts index e937f79..963c83c 100644 --- a/src/models/actions/RefreshAuth.ts +++ b/src/models/actions/RefreshAuth.ts @@ -3,7 +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 { JwtCreator } from "../../jwtcreator"; import { User } from '../entities/User'; import { Auth } from '../responses/ResponseAuth'; -- 2.47.2