Merge branch 'dev' into feature/13-runner_controllers
This commit is contained in:
commit
7bb7da4eed
@ -53,9 +53,11 @@ docker-compose up --build
|
||||
|
||||
## File Structure
|
||||
|
||||
- src/models/\* - database models (typeorm entities)
|
||||
- src/models/entities\* - database models (typeorm entities)
|
||||
- src/models/actions\* - actions models
|
||||
- src/models/responses\* - response models
|
||||
- src/controllers/\* - routing-controllers
|
||||
- src/loaders/\* - loaders for the different init steps of the api server
|
||||
- src/routes/\* - express routes for everything we don't do via routing-controllers (shouldn't be much)
|
||||
- src/middlewares/\* - express middlewares (mainly auth r/n)
|
||||
- src/errors/* - our custom (http) errors
|
||||
- src/routes/\* - express routes for everything we don't do via routing-controllers (depreciated)
|
17
src/app.ts
17
src/app.ts
@ -1,18 +1,15 @@
|
||||
import "reflect-metadata";
|
||||
import * as dotenvSafe from "dotenv-safe";
|
||||
import { createExpressServer } from "routing-controllers";
|
||||
import consola from "consola";
|
||||
import loaders from "./loaders/index";
|
||||
import "reflect-metadata";
|
||||
import { createExpressServer } from "routing-controllers";
|
||||
import authchecker from "./authchecker";
|
||||
import { config } from './config';
|
||||
import loaders from "./loaders/index";
|
||||
import { ErrorHandler } from './middlewares/ErrorHandler';
|
||||
|
||||
dotenvSafe.config();
|
||||
const PORT = process.env.APP_PORT || 4010;
|
||||
|
||||
const app = createExpressServer({
|
||||
authorizationChecker: authchecker,
|
||||
middlewares: [ErrorHandler],
|
||||
development: process.env.NODE_ENV === "production",
|
||||
development: config.development,
|
||||
cors: true,
|
||||
routePrefix: "/api",
|
||||
controllers: [__dirname + "/controllers/*.ts"],
|
||||
@ -20,9 +17,9 @@ const app = createExpressServer({
|
||||
|
||||
async function main() {
|
||||
await loaders(app);
|
||||
app.listen(PORT, () => {
|
||||
app.listen(config.internal_port, () => {
|
||||
consola.success(
|
||||
`⚡️[server]: Server is running at http://localhost:${PORT}`
|
||||
`⚡️[server]: Server is running at http://localhost:${config.internal_port}`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -1,13 +1,9 @@
|
||||
import * as jwt from "jsonwebtoken";
|
||||
import { Action, HttpError } from "routing-controllers";
|
||||
// -----------
|
||||
const sampletoken = jwt.sign({
|
||||
"permissions": {
|
||||
"TRACKS": ["read", "update", "delete", "add"]
|
||||
// "TRACKS": []
|
||||
}
|
||||
}, process.env.JWT_SECRET || "secretjwtsecret")
|
||||
console.log(`sampletoken: ${sampletoken}`);
|
||||
import { Action } from "routing-controllers";
|
||||
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
|
||||
@ -20,9 +16,14 @@ const authchecker = async (action: Action, permissions: string | string[]) => {
|
||||
const provided_token = action.request.query["auth"];
|
||||
let jwtPayload = undefined
|
||||
try {
|
||||
jwtPayload = <any>jwt.verify(provided_token, process.env.JWT_SECRET || "secretjwtsecret");
|
||||
jwtPayload = <any>jwt.verify(provided_token, config.jwt_secret);
|
||||
} catch (error) {
|
||||
throw new HttpError(401, "jwt_illegal")
|
||||
console.log(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 = {}
|
||||
@ -34,15 +35,15 @@ const authchecker = async (action: Action, permissions: string | string[]) => {
|
||||
if (actual_accesslevel_for_permission.includes(permission_access_level)) {
|
||||
return true;
|
||||
} else {
|
||||
throw new HttpError(403, "no")
|
||||
throw new NoPermissionError()
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new HttpError(403, "no")
|
||||
throw new NoPermissionError()
|
||||
}
|
||||
//
|
||||
try {
|
||||
jwt.verify(provided_token, process.env.JWT_SECRET || "secretjwtsecret");
|
||||
jwt.verify(provided_token, config.jwt_secret);
|
||||
return true
|
||||
} catch (error) {
|
||||
return false
|
||||
|
7
src/config.ts
Normal file
7
src/config.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import * as dotenvSafe from "dotenv-safe";
|
||||
dotenvSafe.config();
|
||||
export const config = {
|
||||
internal_port: process.env.APP_PORT || 4010,
|
||||
development: process.env.NODE_ENV === "production",
|
||||
jwt_secret: process.env.JWT_SECRET || "secretjwtsecret"
|
||||
}
|
68
src/controllers/AuthController.ts
Normal file
68
src/controllers/AuthController.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { Body, JsonController, Post } from 'routing-controllers';
|
||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
||||
import { IllegalJWTError, InvalidCredentialsError, JwtNotProvidedError, PasswordNeededError, RefreshTokenCountInvalidError, UsernameOrEmailNeededError } from '../errors/AuthError';
|
||||
import { UserNotFoundError } from '../errors/UserErrors';
|
||||
import { CreateAuth } from '../models/actions/CreateAuth';
|
||||
import { HandleLogout } from '../models/actions/HandleLogout';
|
||||
import { RefreshAuth } from '../models/actions/RefreshAuth';
|
||||
import { Auth } from '../models/responses/ResponseAuth';
|
||||
import { Logout } from '../models/responses/ResponseLogout';
|
||||
|
||||
@JsonController('/auth')
|
||||
export class AuthController {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
@Post("/login")
|
||||
@ResponseSchema(Auth)
|
||||
@ResponseSchema(InvalidCredentialsError)
|
||||
@ResponseSchema(UserNotFoundError)
|
||||
@ResponseSchema(UsernameOrEmailNeededError)
|
||||
@ResponseSchema(PasswordNeededError)
|
||||
@ResponseSchema(InvalidCredentialsError)
|
||||
@OpenAPI({ description: 'Create a new access token object' })
|
||||
async login(@Body({ validate: true }) createAuth: CreateAuth) {
|
||||
let auth;
|
||||
try {
|
||||
auth = await createAuth.toAuth();
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
return auth
|
||||
}
|
||||
|
||||
@Post("/logout")
|
||||
@ResponseSchema(Logout)
|
||||
@ResponseSchema(InvalidCredentialsError)
|
||||
@ResponseSchema(UserNotFoundError)
|
||||
@ResponseSchema(UsernameOrEmailNeededError)
|
||||
@ResponseSchema(PasswordNeededError)
|
||||
@ResponseSchema(InvalidCredentialsError)
|
||||
@OpenAPI({ description: 'Create a new access token object' })
|
||||
async logout(@Body({ validate: true }) handleLogout: HandleLogout) {
|
||||
let logout;
|
||||
try {
|
||||
logout = await handleLogout.logout()
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
return logout
|
||||
}
|
||||
|
||||
@Post("/refresh")
|
||||
@ResponseSchema(Auth)
|
||||
@ResponseSchema(JwtNotProvidedError)
|
||||
@ResponseSchema(IllegalJWTError)
|
||||
@ResponseSchema(UserNotFoundError)
|
||||
@ResponseSchema(RefreshTokenCountInvalidError)
|
||||
@OpenAPI({ description: 'refresh a access token' })
|
||||
async refresh(@Body({ validate: true }) refreshAuth: RefreshAuth) {
|
||||
let auth;
|
||||
try {
|
||||
auth = await refreshAuth.toAuth();
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
return auth
|
||||
}
|
||||
}
|
@ -2,8 +2,9 @@ import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, Query
|
||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
||||
import { getConnectionManager, Repository } from 'typeorm';
|
||||
import { EntityFromBody, EntityFromParam } from 'typeorm-routing-controllers-extensions';
|
||||
import { RunnerGroupNeededError, RunnerGroupNotFoundError, RunnerIdsNotMatchingError, RunnerNotFoundError, RunnerOnlyOneGroupAllowedError } from '../errors/RunnerErrors';
|
||||
import { CreateRunner } from '../models/creation/CreateRunner';
|
||||
import { RunnerGroupNeededError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors';
|
||||
import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors';
|
||||
import { CreateRunner } from '../models/actions/CreateRunner';
|
||||
import { Runner } from '../models/entities/Runner';
|
||||
import { ResponseRunner } from '../models/responses/ResponseRunner';
|
||||
|
||||
@ -43,7 +44,6 @@ export class RunnerController {
|
||||
|
||||
@Post()
|
||||
@ResponseSchema(ResponseRunner)
|
||||
@ResponseSchema(RunnerOnlyOneGroupAllowedError)
|
||||
@ResponseSchema(RunnerGroupNeededError)
|
||||
@ResponseSchema(RunnerGroupNotFoundError)
|
||||
@OpenAPI({ description: 'Create a new runner object (id will be generated automagicly).' })
|
||||
|
@ -3,7 +3,7 @@ import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
||||
import { getConnectionManager, Repository } from 'typeorm';
|
||||
import { EntityFromBody, EntityFromParam } from 'typeorm-routing-controllers-extensions';
|
||||
import { RunnerOrganisationHasRunnersError, RunnerOrganisationHasTeamsError, RunnerOrganisationIdsNotMatchingError, RunnerOrganisationNotFoundError } from '../errors/RunnerOrganisationErrors';
|
||||
import { CreateRunnerOrganisation } from '../models/creation/CreateRunnerOrganisation';
|
||||
import { CreateRunnerOrganisation } from '../models/actions/CreateRunnerOrganisation';
|
||||
import { RunnerOrganisation } from '../models/entities/RunnerOrganisation';
|
||||
import { ResponseRunnerOrganisation } from '../models/responses/ResponseRunnerOrganisation';
|
||||
import { RunnerController } from './RunnerController';
|
||||
|
@ -3,7 +3,7 @@ import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
||||
import { getConnectionManager, Repository } from 'typeorm';
|
||||
import { EntityFromBody, EntityFromParam } from 'typeorm-routing-controllers-extensions';
|
||||
import { RunnerTeamHasRunnersError, RunnerTeamIdsNotMatchingError, RunnerTeamNotFoundError } from '../errors/RunnerTeamErrors';
|
||||
import { CreateRunnerTeam } from '../models/creation/CreateRunnerTeam';
|
||||
import { CreateRunnerTeam } from '../models/actions/CreateRunnerTeam';
|
||||
import { RunnerTeam } from '../models/entities/RunnerTeam';
|
||||
import { ResponseRunnerTeam } from '../models/responses/ResponseRunnerTeam';
|
||||
import { RunnerController } from './RunnerController';
|
||||
|
@ -3,7 +3,7 @@ import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
||||
import { getConnectionManager, Repository } from 'typeorm';
|
||||
import { EntityFromBody, EntityFromParam } from 'typeorm-routing-controllers-extensions';
|
||||
import { TrackIdsNotMatchingError, TrackNotFoundError } from "../errors/TrackErrors";
|
||||
import { CreateTrack } from '../models/creation/CreateTrack';
|
||||
import { CreateTrack } from '../models/actions/CreateTrack';
|
||||
import { Track } from '../models/entities/Track';
|
||||
import { ResponseTrack } from '../models/responses/ResponseTrack';
|
||||
|
||||
|
@ -2,8 +2,9 @@ import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from
|
||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
||||
import { getConnectionManager, Repository } from 'typeorm';
|
||||
import { EntityFromBody, EntityFromParam } from 'typeorm-routing-controllers-extensions';
|
||||
import { UserGroupNotFoundError, UserIdsNotMatchingError, UserNotFoundError } from '../errors/UserErrors';
|
||||
import { CreateUser } from '../models/creation/CreateUser';
|
||||
import { UserIdsNotMatchingError, UserNotFoundError } from '../errors/UserErrors';
|
||||
import { UserGroupNotFoundError } from '../errors/UserGroupErrors';
|
||||
import { CreateUser } from '../models/actions/CreateUser';
|
||||
import { User } from '../models/entities/User';
|
||||
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
||||
import { getConnectionManager, Repository } from 'typeorm';
|
||||
import { EntityFromBody, EntityFromParam } from 'typeorm-routing-controllers-extensions';
|
||||
import { UserGroupIdsNotMatchingError, UserGroupNotFoundError } from '../errors/UserGroupErrors';
|
||||
import { CreateUserGroup } from '../models/creation/CreateUserGroup';
|
||||
import { CreateUserGroup } from '../models/actions/CreateUserGroup';
|
||||
import { UserGroup } from '../models/entities/UserGroup';
|
||||
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
import { IsString } from 'class-validator';
|
||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
||||
|
||||
/**
|
||||
* Error to throw, when to provided address doesn't belong to the accepted types.
|
||||
*/
|
||||
export class AddressWrongTypeError extends NotAcceptableError {
|
||||
@IsString()
|
||||
name = "AddressWrongTypeError"
|
||||
@ -9,6 +12,9 @@ export class AddressWrongTypeError extends NotAcceptableError {
|
||||
message = "The address must be an existing adress's id. \n You provided a object of another type."
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw, when a non-existant address get's loaded.
|
||||
*/
|
||||
export class AddressNotFoundError extends NotFoundError {
|
||||
@IsString()
|
||||
name = "AddressNotFoundError"
|
||||
|
123
src/errors/AuthError.ts
Normal file
123
src/errors/AuthError.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import { IsString } from 'class-validator';
|
||||
import { ForbiddenError, NotAcceptableError, NotFoundError, UnauthorizedError } from 'routing-controllers';
|
||||
|
||||
/**
|
||||
* Error to throw when a jwt is expired.
|
||||
*/
|
||||
export class ExpiredJWTError extends UnauthorizedError {
|
||||
@IsString()
|
||||
name = "ExpiredJWTError"
|
||||
|
||||
@IsString()
|
||||
message = "your provided jwt is expired"
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw when a jwt could not be parsed.
|
||||
*/
|
||||
export class IllegalJWTError extends UnauthorizedError {
|
||||
@IsString()
|
||||
name = "IllegalJWTError"
|
||||
|
||||
@IsString()
|
||||
message = "your provided jwt could not be parsed"
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw when user is nonexistant or refreshtoken is invalid.
|
||||
*/
|
||||
export class UserNonexistantOrRefreshtokenInvalidError extends UnauthorizedError {
|
||||
@IsString()
|
||||
name = "UserNonexistantOrRefreshtokenInvalidError"
|
||||
|
||||
@IsString()
|
||||
message = "user is nonexistant or refreshtoken is invalid"
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw when provided credentials are invalid.
|
||||
*/
|
||||
export class InvalidCredentialsError extends UnauthorizedError {
|
||||
@IsString()
|
||||
name = "InvalidCredentialsError"
|
||||
|
||||
@IsString()
|
||||
message = "your provided credentials are invalid"
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw when a jwt does not have permission for this route/action.
|
||||
*/
|
||||
export class NoPermissionError extends ForbiddenError {
|
||||
@IsString()
|
||||
name = "NoPermissionError"
|
||||
|
||||
@IsString()
|
||||
message = "your provided jwt does not have permission for this route/ action"
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw when no username and no email is set.
|
||||
*/
|
||||
export class UsernameOrEmailNeededError extends NotAcceptableError {
|
||||
@IsString()
|
||||
name = "UsernameOrEmailNeededError"
|
||||
|
||||
@IsString()
|
||||
message = "Auth needs to have email or username set! \n You provided neither."
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw when no password is provided.
|
||||
*/
|
||||
export class PasswordNeededError extends NotAcceptableError {
|
||||
@IsString()
|
||||
name = "PasswordNeededError"
|
||||
|
||||
@IsString()
|
||||
message = "no password is provided - you need to provide it"
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw when no user could be found mating the provided credential.
|
||||
*/
|
||||
export class UserNotFoundError extends NotFoundError {
|
||||
@IsString()
|
||||
name = "UserNotFoundError"
|
||||
|
||||
@IsString()
|
||||
message = "no user could be found for provided credential"
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw when no jwt token was provided (but one had to be).
|
||||
*/
|
||||
export class JwtNotProvidedError extends NotAcceptableError {
|
||||
@IsString()
|
||||
name = "JwtNotProvidedError"
|
||||
|
||||
@IsString()
|
||||
message = "no jwt token was provided"
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw when user was not found or refresh token count was invalid.
|
||||
*/
|
||||
export class UserNotFoundOrRefreshTokenCountInvalidError extends NotAcceptableError {
|
||||
@IsString()
|
||||
name = "UserNotFoundOrRefreshTokenCountInvalidError"
|
||||
|
||||
@IsString()
|
||||
message = "user was not found or refresh token count was invalid"
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw when refresh token count was invalid
|
||||
*/
|
||||
export class RefreshTokenCountInvalidError extends NotAcceptableError {
|
||||
@IsString()
|
||||
name = "RefreshTokenCountInvalidError"
|
||||
|
||||
@IsString()
|
||||
message = "refresh token count was invalid"
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
import { IsString } from 'class-validator';
|
||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
||||
|
||||
/**
|
||||
* Error to throw, when a provided groupContact doesn't belong to the accepted types.
|
||||
*/
|
||||
export class GroupContactWrongTypeError extends NotAcceptableError {
|
||||
@IsString()
|
||||
name = "GroupContactWrongTypeError"
|
||||
@ -9,6 +12,9 @@ export class GroupContactWrongTypeError extends NotAcceptableError {
|
||||
message = "The groupContact must be an existing groupContact's id. \n You provided a object of another type."
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw, when a non-existant groupContact get's loaded.
|
||||
*/
|
||||
export class GroupContactNotFoundError extends NotFoundError {
|
||||
@IsString()
|
||||
name = "GroupContactNotFoundError"
|
||||
|
@ -26,27 +26,13 @@ export class RunnerIdsNotMatchingError extends NotAcceptableError {
|
||||
message = "The id's don't match!! \n And if you wanted to change a runner's id: This isn't allowed"
|
||||
}
|
||||
|
||||
export class RunnerOnlyOneGroupAllowedError extends NotAcceptableError {
|
||||
@IsString()
|
||||
name = "RunnerOnlyOneGroupAllowedError"
|
||||
|
||||
@IsString()
|
||||
message = "Runner's can only be part of one group (team or organisiation)! \n You provided an id for both."
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw when a runner is missing his group association.
|
||||
*/
|
||||
export class RunnerGroupNeededError extends NotAcceptableError {
|
||||
@IsString()
|
||||
name = "RunnerGroupNeededError"
|
||||
|
||||
@IsString()
|
||||
message = "Runner's need to be part of one group (team or organisiation)! \n You provided neither."
|
||||
}
|
||||
|
||||
|
||||
export class RunnerGroupNotFoundError extends NotFoundError {
|
||||
@IsString()
|
||||
name = "RunnerGroupNotFoundError"
|
||||
|
||||
@IsString()
|
||||
message = "The group you provided couldn't be located in the system. \n Please check your request."
|
||||
}
|
14
src/errors/RunnerGroupErrors.ts
Normal file
14
src/errors/RunnerGroupErrors.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { IsString } from 'class-validator';
|
||||
import { NotFoundError } from 'routing-controllers';
|
||||
|
||||
/**
|
||||
* Error to throw when a runner group couldn't be found.
|
||||
* Implemented this ways to work with the json-schema conversion for openapi.
|
||||
*/
|
||||
export class RunnerGroupNotFoundError extends NotFoundError {
|
||||
@IsString()
|
||||
name = "RunnerGroupNotFoundError"
|
||||
|
||||
@IsString()
|
||||
message = "RunnerGroup not found!"
|
||||
}
|
@ -50,6 +50,9 @@ export class RunnerOrganisationHasTeamsError extends NotAcceptableError {
|
||||
message = "This organisation still has teams associated with it. \n If you want to delete this organisation with all it's runners and teams ass `?force` to your query."
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw, when a provided runnerOrganisation doesn't belong to the accepted types.
|
||||
*/
|
||||
export class RunnerOrganisationWrongTypeError extends NotAcceptableError {
|
||||
@IsString()
|
||||
name = "RunnerOrganisationWrongTypeError"
|
||||
|
@ -1,16 +1,6 @@
|
||||
import { IsString } from 'class-validator';
|
||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
||||
|
||||
/**
|
||||
* Error to throw when a usergroup couldn't be found.
|
||||
*/
|
||||
export class UserGroupNotFoundError extends NotFoundError {
|
||||
@IsString()
|
||||
name = "UserGroupNotFoundError"
|
||||
|
||||
@IsString()
|
||||
message = "User Group not found!"
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw when no username or email is set
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { createConnection } from "typeorm";
|
||||
|
||||
/**
|
||||
* Loader for the database that creates the database connection and initializes the database tabels.
|
||||
*/
|
||||
export default async () => {
|
||||
const connection = await createConnection();
|
||||
connection.synchronize();
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { Application } from "express";
|
||||
import bodyParser from 'body-parser';
|
||||
import cors from 'cors';
|
||||
|
||||
/**
|
||||
* Loader for express related configurations.
|
||||
* Currently only enables the proxy trust.
|
||||
*/
|
||||
export default async (app: Application) => {
|
||||
app.enable('trust proxy');
|
||||
return app;
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { Application } from "express";
|
||||
import databaseLoader from "./database";
|
||||
import expressLoader from "./express";
|
||||
import openapiLoader from "./openapi";
|
||||
import databaseLoader from "./database";
|
||||
import { Application } from "express";
|
||||
|
||||
/**
|
||||
* Index Loader that executes the other loaders in the right order.
|
||||
*/
|
||||
export default async (app: Application) => {
|
||||
await databaseLoader();
|
||||
await openapiLoader(app);
|
||||
|
@ -1,14 +1,19 @@
|
||||
import { validationMetadatasToSchemas } from "class-validator-jsonschema";
|
||||
import { Application } from "express";
|
||||
import * as swaggerUiExpress from "swagger-ui-express";
|
||||
import { getMetadataArgsStorage } from "routing-controllers";
|
||||
import { routingControllersToSpec } from "routing-controllers-openapi";
|
||||
import { validationMetadatasToSchemas } from "class-validator-jsonschema";
|
||||
import * as swaggerUiExpress from "swagger-ui-express";
|
||||
|
||||
/**
|
||||
* Loader for everything openapi related - from creating the schema to serving it via a static route.
|
||||
*/
|
||||
export default async (app: Application) => {
|
||||
const storage = getMetadataArgsStorage();
|
||||
const schemas = validationMetadatasToSchemas({
|
||||
refPointerPrefix: "#/components/schemas/",
|
||||
});
|
||||
|
||||
//Spec creation based on the previously created schemas
|
||||
const spec = routingControllersToSpec(
|
||||
storage,
|
||||
{
|
||||
@ -17,6 +22,13 @@ export default async (app: Application) => {
|
||||
{
|
||||
components: {
|
||||
schemas,
|
||||
"securitySchemes": {
|
||||
"AuthToken": {
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
"bearerFormat": "JWT"
|
||||
}
|
||||
}
|
||||
},
|
||||
info: {
|
||||
description: "The the backend API for the LfK! runner system.",
|
||||
@ -25,6 +37,8 @@ export default async (app: Application) => {
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
//Options for swaggerUiExpress
|
||||
const options = {
|
||||
explorer: true,
|
||||
};
|
||||
|
@ -1,20 +1,14 @@
|
||||
import {
|
||||
Middleware,
|
||||
ExpressErrorMiddlewareInterface
|
||||
} from "routing-controllers";
|
||||
import { ExpressErrorMiddlewareInterface, Middleware } from "routing-controllers";
|
||||
|
||||
/**
|
||||
* Our Error handling middlware that returns our custom httperrors to the user
|
||||
*/
|
||||
@Middleware({ type: "after" })
|
||||
export class ErrorHandler implements ExpressErrorMiddlewareInterface {
|
||||
public error(
|
||||
error: any,
|
||||
request: any,
|
||||
response: any,
|
||||
next: (err: any) => any
|
||||
) {
|
||||
public error(error: any, request: any, response: any, next: (err: any) => any) {
|
||||
if (response.headersSent) {
|
||||
return;
|
||||
}
|
||||
|
||||
response.json(error);
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +0,0 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
// import bodyParser from 'body-parser';
|
||||
// import cors from 'cors';
|
||||
import * as jwt from "jsonwebtoken";
|
||||
|
||||
export default (req: Request, res: Response, next: NextFunction) => {
|
||||
const token = <string>req.headers["auth"];
|
||||
try {
|
||||
const jwtPayload = <any>jwt.verify(token, "secretjwtsecret");
|
||||
// const jwtPayload = <any>jwt.verify(token, process.env.JWT_SECRET);
|
||||
res.locals.jwtPayload = jwtPayload;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return res.status(401).send();
|
||||
}
|
||||
next();
|
||||
};
|
58
src/models/actions/CreateAuth.ts
Normal file
58
src/models/actions/CreateAuth.ts
Normal file
@ -0,0 +1,58 @@
|
||||
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 { User } from '../entities/User';
|
||||
import { Auth } from '../responses/ResponseAuth';
|
||||
|
||||
export class CreateAuth {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
username?: string;
|
||||
@IsString()
|
||||
password: string;
|
||||
@IsOptional()
|
||||
@IsEmail()
|
||||
@IsString()
|
||||
email?: string;
|
||||
|
||||
public async toAuth(): Promise<Auth> {
|
||||
let newAuth: Auth = new Auth();
|
||||
|
||||
if (this.email === undefined && this.username === undefined) {
|
||||
throw new UsernameOrEmailNeededError();
|
||||
}
|
||||
if (!this.password) {
|
||||
throw new PasswordNeededError()
|
||||
}
|
||||
const found_users = await getConnectionManager().get().getRepository(User).find({ 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
|
||||
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()
|
||||
}
|
||||
}
|
||||
return newAuth;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { IsInt } from 'class-validator';
|
||||
import { getConnectionManager } from 'typeorm';
|
||||
import { RunnerGroupNotFoundError } from '../../errors/RunnerErrors';
|
||||
import { RunnerGroupNotFoundError } from '../../errors/RunnerGroupErrors';
|
||||
import { RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
|
||||
import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
|
||||
import { Runner } from '../entities/Runner';
|
@ -2,7 +2,8 @@ import * as argon2 from "argon2";
|
||||
import { IsEmail, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
|
||||
import { getConnectionManager } from 'typeorm';
|
||||
import * as uuid from 'uuid';
|
||||
import { UserGroupNotFoundError, UsernameOrEmailNeededError } from '../../errors/UserErrors';
|
||||
import { UsernameOrEmailNeededError } from '../../errors/UserErrors';
|
||||
import { UserGroupNotFoundError } from '../../errors/UserGroupErrors';
|
||||
import { User } from '../entities/User';
|
||||
import { UserGroup } from '../entities/UserGroup';
|
||||
|
36
src/models/actions/HandleLogout.ts
Normal file
36
src/models/actions/HandleLogout.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { IsString } from 'class-validator';
|
||||
import * as jsonwebtoken from 'jsonwebtoken';
|
||||
import { getConnectionManager } from 'typeorm';
|
||||
import { config } from '../../config';
|
||||
import { IllegalJWTError, JwtNotProvidedError, RefreshTokenCountInvalidError, UserNotFoundError } from '../../errors/AuthError';
|
||||
import { User } from '../entities/User';
|
||||
import { Logout } from '../responses/ResponseLogout';
|
||||
|
||||
export class HandleLogout {
|
||||
@IsString()
|
||||
token: string;
|
||||
|
||||
public async logout(): Promise<Logout> {
|
||||
let logout: Logout = new Logout();
|
||||
if (!this.token || this.token === undefined) {
|
||||
throw new JwtNotProvidedError()
|
||||
}
|
||||
let decoded;
|
||||
try {
|
||||
decoded = jsonwebtoken.verify(this.token, config.jwt_secret)
|
||||
} catch (error) {
|
||||
throw new IllegalJWTError()
|
||||
}
|
||||
logout.timestamp = Math.floor(Date.now() / 1000)
|
||||
let found_user: User = await getConnectionManager().get().getRepository(User).findOne({ id: decoded["userid"] });
|
||||
if (!found_user) {
|
||||
throw new UserNotFoundError()
|
||||
}
|
||||
if (found_user.refreshTokenCount !== decoded["refreshtokencount"]) {
|
||||
throw new RefreshTokenCountInvalidError()
|
||||
}
|
||||
found_user.refreshTokenCount++;
|
||||
await getConnectionManager().get().getRepository(User).update({ id: found_user.id }, found_user)
|
||||
return logout;
|
||||
}
|
||||
}
|
50
src/models/actions/RefreshAuth.ts
Normal file
50
src/models/actions/RefreshAuth.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { IsString } from 'class-validator';
|
||||
import * as jsonwebtoken from 'jsonwebtoken';
|
||||
import { getConnectionManager } from 'typeorm';
|
||||
import { config } from '../../config';
|
||||
import { IllegalJWTError, JwtNotProvidedError, RefreshTokenCountInvalidError, UserNotFoundError } from '../../errors/AuthError';
|
||||
import { User } from '../entities/User';
|
||||
import { Auth } from '../responses/ResponseAuth';
|
||||
|
||||
export class RefreshAuth {
|
||||
@IsString()
|
||||
token: string;
|
||||
|
||||
public async toAuth(): Promise<Auth> {
|
||||
let newAuth: Auth = new Auth();
|
||||
if (!this.token || this.token === undefined) {
|
||||
throw new JwtNotProvidedError()
|
||||
}
|
||||
let decoded
|
||||
try {
|
||||
decoded = jsonwebtoken.verify(this.token, config.jwt_secret)
|
||||
} catch (error) {
|
||||
throw new IllegalJWTError()
|
||||
}
|
||||
const found_user = await getConnectionManager().get().getRepository(User).findOne({ id: decoded["userid"] });
|
||||
if (!found_user) {
|
||||
throw new UserNotFoundError()
|
||||
}
|
||||
if (found_user.refreshTokenCount !== decoded["refreshtokencount"]) {
|
||||
throw new RefreshTokenCountInvalidError()
|
||||
}
|
||||
delete found_user.password;
|
||||
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_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
|
||||
|
||||
return newAuth;
|
||||
}
|
||||
}
|
@ -19,14 +19,14 @@ export class User {
|
||||
/**
|
||||
* uuid
|
||||
*/
|
||||
@Column()
|
||||
@Column({ unique: true })
|
||||
@IsUUID(4)
|
||||
uuid: string;
|
||||
|
||||
/**
|
||||
* user email
|
||||
*/
|
||||
@Column({ nullable: true })
|
||||
@Column({ nullable: true, unique: true })
|
||||
@IsEmail()
|
||||
email?: string;
|
||||
|
||||
@ -41,7 +41,7 @@ export class User {
|
||||
/**
|
||||
* username
|
||||
*/
|
||||
@Column({ nullable: true })
|
||||
@Column({ nullable: true, unique: true })
|
||||
@IsString()
|
||||
username?: string;
|
||||
|
||||
@ -109,7 +109,7 @@ export class User {
|
||||
/**
|
||||
* profilepic
|
||||
*/
|
||||
@Column({ nullable: true })
|
||||
@Column({ nullable: true, unique: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
profilePic?: string;
|
||||
|
27
src/models/responses/ResponseAuth.ts
Normal file
27
src/models/responses/ResponseAuth.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { IsInt, IsString } from 'class-validator';
|
||||
|
||||
/**
|
||||
* Defines a auth object
|
||||
*/
|
||||
export class Auth {
|
||||
/**
|
||||
* access_token - JWT shortterm access token
|
||||
*/
|
||||
@IsString()
|
||||
access_token: string;
|
||||
/**
|
||||
* refresh_token - longterm refresh token (used for requesting new access tokens)
|
||||
*/
|
||||
@IsString()
|
||||
refresh_token: string;
|
||||
/**
|
||||
* access_token_expires_at - unix timestamp of access token expiry
|
||||
*/
|
||||
@IsInt()
|
||||
access_token_expires_at: number;
|
||||
/**
|
||||
* refresh_token_expires_at - unix timestamp of access token expiry
|
||||
*/
|
||||
@IsInt()
|
||||
refresh_token_expires_at: number;
|
||||
}
|
12
src/models/responses/ResponseLogout.ts
Normal file
12
src/models/responses/ResponseLogout.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { IsString } from 'class-validator';
|
||||
|
||||
/**
|
||||
* Defines a Logout object
|
||||
*/
|
||||
export class Logout {
|
||||
/**
|
||||
* timestamp of logout
|
||||
*/
|
||||
@IsString()
|
||||
timestamp: number;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import { Router } from "express";
|
||||
import jwtauth from "../../middlewares/jwtauth";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use("*", jwtauth, async (req, res, next) => {
|
||||
return res.send("ok");
|
||||
});
|
||||
|
||||
export default router;
|
Loading…
x
Reference in New Issue
Block a user