🚀Bumped version to v0.10.0

This commit is contained in:
Nicolai Ort 2021-04-01 18:30:30 +02:00
parent 33c13de32c
commit dc3071f7d2
2 changed files with 228 additions and 228 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@odit/lfk-backend", "name": "@odit/lfk-backend",
"version": "0.9.2", "version": "0.10.0",
"main": "src/app.ts", "main": "src/app.ts",
"repository": "https://git.odit.services/lfk/backend", "repository": "https://git.odit.services/lfk/backend",
"author": { "author": {

View File

@ -1,228 +1,228 @@
import { Request } from "express"; import { Request } from "express";
import * as jwt from "jsonwebtoken"; import * as jwt from "jsonwebtoken";
import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam, Req, UseBefore } from 'routing-controllers'; import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam, Req, UseBefore } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm'; import { getConnectionManager, Repository } from 'typeorm';
import { config } from '../config'; import { config } from '../config';
import { InvalidCredentialsError, JwtNotProvidedError } from '../errors/AuthError'; import { InvalidCredentialsError, JwtNotProvidedError } from '../errors/AuthError';
import { MailSendingError } from '../errors/MailErrors'; import { MailSendingError } from '../errors/MailErrors';
import { RunnerEmailNeededError, RunnerHasDistanceDonationsError, RunnerNotFoundError, RunnerSelfserviceTimeoutError } from '../errors/RunnerErrors'; import { RunnerEmailNeededError, RunnerHasDistanceDonationsError, RunnerNotFoundError, RunnerSelfserviceTimeoutError } from '../errors/RunnerErrors';
import { RunnerOrganizationNotFoundError } from '../errors/RunnerOrganizationErrors'; import { RunnerOrganizationNotFoundError } from '../errors/RunnerOrganizationErrors';
import { ScanStationNotFoundError } from '../errors/ScanStationErrors'; import { ScanStationNotFoundError } from '../errors/ScanStationErrors';
import { JwtCreator } from '../jwtcreator'; import { JwtCreator } from '../jwtcreator';
import { Mailer } from '../mailer'; import { Mailer } from '../mailer';
import ScanAuth from '../middlewares/ScanAuth'; import ScanAuth from '../middlewares/ScanAuth';
import { CreateSelfServiceCitizenRunner } from '../models/actions/create/CreateSelfServiceCitizenRunner'; import { CreateSelfServiceCitizenRunner } from '../models/actions/create/CreateSelfServiceCitizenRunner';
import { CreateSelfServiceRunner } from '../models/actions/create/CreateSelfServiceRunner'; import { CreateSelfServiceRunner } from '../models/actions/create/CreateSelfServiceRunner';
import { Runner } from '../models/entities/Runner'; import { Runner } from '../models/entities/Runner';
import { RunnerGroup } from '../models/entities/RunnerGroup'; import { RunnerGroup } from '../models/entities/RunnerGroup';
import { RunnerOrganization } from '../models/entities/RunnerOrganization'; import { RunnerOrganization } from '../models/entities/RunnerOrganization';
import { ScanStation } from '../models/entities/ScanStation'; import { ScanStation } from '../models/entities/ScanStation';
import { ResponseEmpty } from '../models/responses/ResponseEmpty'; import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseScanStation } from '../models/responses/ResponseScanStation'; import { ResponseScanStation } from '../models/responses/ResponseScanStation';
import { ResponseSelfServiceOrganisation } from '../models/responses/ResponseSelfServiceOrganisation'; import { ResponseSelfServiceOrganisation } from '../models/responses/ResponseSelfServiceOrganisation';
import { ResponseSelfServiceRunner } from '../models/responses/ResponseSelfServiceRunner'; import { ResponseSelfServiceRunner } from '../models/responses/ResponseSelfServiceRunner';
import { ResponseSelfServiceScan } from '../models/responses/ResponseSelfServiceScan'; import { ResponseSelfServiceScan } from '../models/responses/ResponseSelfServiceScan';
import { DonationController } from './DonationController'; import { DonationController } from './DonationController';
import { RunnerCardController } from './RunnerCardController'; import { RunnerCardController } from './RunnerCardController';
import { ScanController } from './ScanController'; import { ScanController } from './ScanController';
@JsonController() @JsonController()
export class RunnerSelfServiceController { export class RunnerSelfServiceController {
private runnerRepository: Repository<Runner>; private runnerRepository: Repository<Runner>;
private orgRepository: Repository<RunnerOrganization>; private orgRepository: Repository<RunnerOrganization>;
private stationRepository: Repository<ScanStation>; private stationRepository: Repository<ScanStation>;
/** /**
* Gets the repository of this controller's model/entity. * Gets the repository of this controller's model/entity.
*/ */
constructor() { constructor() {
this.runnerRepository = getConnectionManager().get().getRepository(Runner); this.runnerRepository = getConnectionManager().get().getRepository(Runner);
this.orgRepository = getConnectionManager().get().getRepository(RunnerOrganization); this.orgRepository = getConnectionManager().get().getRepository(RunnerOrganization);
this.stationRepository = getConnectionManager().get().getRepository(ScanStation); this.stationRepository = getConnectionManager().get().getRepository(ScanStation);
} }
@Get('/runners/me/:jwt') @Get('/runners/me/:jwt')
@ResponseSchema(ResponseSelfServiceRunner) @ResponseSchema(ResponseSelfServiceRunner)
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) @ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@OnUndefined(RunnerNotFoundError) @OnUndefined(RunnerNotFoundError)
@OpenAPI({ description: 'Lists all information about yourself. <br> Please provide your runner jwt(that code we gave you during registration) for auth. <br> If you lost your jwt/personalized link please use the forgot endpoint.' }) @OpenAPI({ description: 'Lists all information about yourself. <br> Please provide your runner jwt(that code we gave you during registration) for auth. <br> If you lost your jwt/personalized link please use the forgot endpoint.' })
async get(@Param('jwt') token: string) { async get(@Param('jwt') token: string) {
return (new ResponseSelfServiceRunner(await this.getRunner(token))); return (new ResponseSelfServiceRunner(await this.getRunner(token)));
} }
@Delete('/runners/me/:jwt') @Delete('/runners/me/:jwt')
@ResponseSchema(ResponseSelfServiceRunner) @ResponseSchema(ResponseSelfServiceRunner)
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) @ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@OnUndefined(RunnerNotFoundError) @OnUndefined(RunnerNotFoundError)
@OpenAPI({ description: 'Deletes all information about yourself. <br> Please provide your runner jwt(that code we gave you during registration) for auth. <br> If you lost your jwt/personalized link please use the forgot endpoint.' }) @OpenAPI({ description: 'Deletes all information about yourself. <br> Please provide your runner jwt(that code we gave you during registration) for auth. <br> If you lost your jwt/personalized link please use the forgot endpoint.' })
async remove(@Param('jwt') token: string, @QueryParam("force") force: boolean) { async remove(@Param('jwt') token: string, @QueryParam("force") force: boolean) {
const responseRunner = await this.getRunner(token); const responseRunner = await this.getRunner(token);
let runner = await this.runnerRepository.findOne({ id: responseRunner.id }); let runner = await this.runnerRepository.findOne({ id: responseRunner.id });
if (!runner) { return null; } if (!runner) { return null; }
if (!runner) { if (!runner) {
throw new RunnerNotFoundError(); throw new RunnerNotFoundError();
} }
const runnerDonations = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["distanceDonations"] })).distanceDonations; const runnerDonations = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["distanceDonations"] })).distanceDonations;
if (runnerDonations.length > 0 && !force) { if (runnerDonations.length > 0 && !force) {
throw new RunnerHasDistanceDonationsError(); throw new RunnerHasDistanceDonationsError();
} }
const donationController = new DonationController(); const donationController = new DonationController();
for (let donation of runnerDonations) { for (let donation of runnerDonations) {
await donationController.remove(donation.id, force); await donationController.remove(donation.id, force);
} }
const runnerCards = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["cards"] })).cards; const runnerCards = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["cards"] })).cards;
const cardController = new RunnerCardController; const cardController = new RunnerCardController;
for (let card of runnerCards) { for (let card of runnerCards) {
await cardController.remove(card.id, force); await cardController.remove(card.id, force);
} }
const runnerScans = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["scans"] })).scans; const runnerScans = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["scans"] })).scans;
const scanController = new ScanController; const scanController = new ScanController;
for (let scan of runnerScans) { for (let scan of runnerScans) {
await scanController.remove(scan.id, force); await scanController.remove(scan.id, force);
} }
await this.runnerRepository.delete(runner); await this.runnerRepository.delete(runner);
return new ResponseSelfServiceRunner(responseRunner); return new ResponseSelfServiceRunner(responseRunner);
} }
@Get('/runners/me/:jwt/scans') @Get('/runners/me/:jwt/scans')
@ResponseSchema(ResponseSelfServiceScan, { isArray: true }) @ResponseSchema(ResponseSelfServiceScan, { isArray: true })
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) @ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@OnUndefined(RunnerNotFoundError) @OnUndefined(RunnerNotFoundError)
@OpenAPI({ description: 'Lists all your (runner) scans. <br> Please provide your runner jwt(that code we gave you during registration) for auth. <br> If you lost your jwt/personalized link please contact support.' }) @OpenAPI({ description: 'Lists all your (runner) scans. <br> Please provide your runner jwt(that code we gave you during registration) for auth. <br> If you lost your jwt/personalized link please contact support.' })
async getScans(@Param('jwt') token: string) { async getScans(@Param('jwt') token: string) {
const scans = (await this.getRunner(token)).scans; const scans = (await this.getRunner(token)).scans;
let responseScans = new Array<ResponseSelfServiceScan>() let responseScans = new Array<ResponseSelfServiceScan>()
for (let scan of scans) { for (let scan of scans) {
responseScans.push(new ResponseSelfServiceScan(scan)); responseScans.push(new ResponseSelfServiceScan(scan));
} }
return responseScans; return responseScans;
} }
@Get('/stations/me') @Get('/stations/me')
@UseBefore(ScanAuth) @UseBefore(ScanAuth)
@ResponseSchema(ResponseScanStation) @ResponseSchema(ResponseScanStation)
@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 }) @ResponseSchema(ScanStationNotFoundError, { statusCode: 404 })
@OnUndefined(ScanStationNotFoundError) @OnUndefined(ScanStationNotFoundError)
@OpenAPI({ description: 'Lists basic information about the station whose token got provided. <br> This includes it\'s associated track.', security: [{ "StationApiToken": [] }] }) @OpenAPI({ description: 'Lists basic information about the station whose token got provided. <br> This includes it\'s associated track.', security: [{ "StationApiToken": [] }] })
async getStationMe(@Req() req: Request) { async getStationMe(@Req() req: Request) {
let scan = await this.stationRepository.findOne({ id: parseInt(req.headers["station_id"].toString()) }, { relations: ['track'] }) let scan = await this.stationRepository.findOne({ id: parseInt(req.headers["station_id"].toString()) }, { relations: ['track'] })
if (!scan) { throw new ScanStationNotFoundError(); } if (!scan) { throw new ScanStationNotFoundError(); }
return scan.toResponse(); return scan.toResponse();
} }
@Post('/runners/forgot') @Post('/runners/forgot')
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) @ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@OnUndefined(ResponseEmpty) @OnUndefined(ResponseEmpty)
@OpenAPI({ description: 'Use this endpoint to reuqest a new selfservice token/link to be sent to your mail address (rate limited to one mail every 24hrs).', parameters: [{ in: "query", name: "locale", schema: { type: "string", enum: ["de", "en"] } }] }) @OpenAPI({ description: 'Use this endpoint to reuqest a new selfservice token/link to be sent to your mail address (rate limited to one mail every 24hrs).', parameters: [{ in: "query", name: "locale", schema: { type: "string", enum: ["de", "en"] } }] })
async requestNewToken(@QueryParam('mail') mail: string, @QueryParam("locale") locale: string = "en") { async requestNewToken(@QueryParam('mail') mail: string, @QueryParam("locale") locale: string = "en") {
if (!mail) { if (!mail) {
throw new RunnerNotFoundError(); throw new RunnerNotFoundError();
} }
const runner = await this.runnerRepository.findOne({ email: mail }); const runner = await this.runnerRepository.findOne({ email: mail });
if (!runner) { throw new RunnerNotFoundError(); } if (!runner) { throw new RunnerNotFoundError(); }
if (runner.resetRequestedTimestamp > (Math.floor(Date.now() / 1000) - 60 * 60 * 24)) { throw new RunnerSelfserviceTimeoutError(); } if (runner.resetRequestedTimestamp > (Math.floor(Date.now() / 1000) - 60 * 60 * 24)) { throw new RunnerSelfserviceTimeoutError(); }
const token = JwtCreator.createSelfService(runner); const token = JwtCreator.createSelfService(runner);
try { try {
await Mailer.sendSelfserviceForgottenMail(runner.email, token, locale) await Mailer.sendSelfserviceForgottenMail(runner.email, token, locale)
} catch (error) { } catch (error) {
throw new MailSendingError(); throw new MailSendingError();
} }
runner.resetRequestedTimestamp = Math.floor(Date.now() / 1000); runner.resetRequestedTimestamp = Math.floor(Date.now() / 1000);
await this.runnerRepository.save(runner); await this.runnerRepository.save(runner);
return { token }; return { token };
} }
@Post('/runners/register') @Post('/runners/register')
@ResponseSchema(ResponseSelfServiceRunner) @ResponseSchema(ResponseSelfServiceRunner)
@ResponseSchema(RunnerEmailNeededError, { statusCode: 406 }) @ResponseSchema(RunnerEmailNeededError, { statusCode: 406 })
@OpenAPI({ description: 'Create a new selfservice runner in the citizen org. <br> This endpoint shoud be used to allow "everyday citizen" to register themselves. <br> You have to provide a mail address, b/c the future we\'ll implement email verification.', parameters: [{ in: "query", name: "locale", schema: { type: "string", enum: ["de", "en"] } }] }) @OpenAPI({ description: 'Create a new selfservice runner in the citizen org. <br> This endpoint shoud be used to allow "everyday citizen" to register themselves. <br> You have to provide a mail address, b/c the future we\'ll implement email verification.', parameters: [{ in: "query", name: "locale", schema: { type: "string", enum: ["de", "en"] } }] })
async registerRunner(@Body({ validate: true }) createRunner: CreateSelfServiceCitizenRunner, @QueryParam("locale") locale: string = "en") { async registerRunner(@Body({ validate: true }) createRunner: CreateSelfServiceCitizenRunner, @QueryParam("locale") locale: string = "en") {
let runner = await createRunner.toEntity(); let runner = await createRunner.toEntity();
runner = await this.runnerRepository.save(runner); runner = await this.runnerRepository.save(runner);
let response = new ResponseSelfServiceRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] })); let response = new ResponseSelfServiceRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] }));
response.token = JwtCreator.createSelfService(runner); response.token = JwtCreator.createSelfService(runner);
try { try {
await Mailer.sendSelfserviceWelcomeMail(runner.email, response.token, locale) await Mailer.sendSelfserviceWelcomeMail(runner.email, response.token, locale)
} catch (error) { } catch (error) {
throw new MailSendingError(); throw new MailSendingError();
} }
return response; return response;
} }
@Post('/runners/register/:token') @Post('/runners/register/:token')
@ResponseSchema(ResponseSelfServiceRunner) @ResponseSchema(ResponseSelfServiceRunner)
@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 }) @ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Create a new selfservice runner in a provided org. <br> The orgs get provided and authorized via api tokens that can be optained via the /organizations endpoint.', parameters: [{ in: "query", name: "locale", schema: { type: "string", enum: ["de", "en"] } }] }) @OpenAPI({ description: 'Create a new selfservice runner in a provided org. <br> The orgs get provided and authorized via api tokens that can be optained via the /organizations endpoint.', parameters: [{ in: "query", name: "locale", schema: { type: "string", enum: ["de", "en"] } }] })
async registerOrganizationRunner(@Param('token') token: string, @Body({ validate: true }) createRunner: CreateSelfServiceRunner, @QueryParam("locale") locale: string = "en") { async registerOrganizationRunner(@Param('token') token: string, @Body({ validate: true }) createRunner: CreateSelfServiceRunner, @QueryParam("locale") locale: string = "en") {
const org = await this.getOrgansisation(token); const org = await this.getOrgansisation(token);
let runner = await createRunner.toEntity(org); let runner = await createRunner.toEntity(org);
runner = await this.runnerRepository.save(runner); runner = await this.runnerRepository.save(runner);
let response = new ResponseSelfServiceRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] })); let response = new ResponseSelfServiceRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] }));
response.token = JwtCreator.createSelfService(runner); response.token = JwtCreator.createSelfService(runner);
try { try {
await Mailer.sendSelfserviceWelcomeMail(runner.email, response.token, locale) await Mailer.sendSelfserviceWelcomeMail(runner.email, response.token, locale)
} catch (error) { } catch (error) {
throw new MailSendingError(); throw new MailSendingError();
} }
return response; return response;
} }
@Get('/organizations/selfservice/:token') @Get('/organizations/selfservice/:token')
@ResponseSchema(ResponseSelfServiceOrganisation, { isArray: false }) @ResponseSchema(ResponseSelfServiceOrganisation, { isArray: false })
@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 }) @ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Get the basic info and teams for a org.' }) @OpenAPI({ description: 'Get the basic info and teams for a org.' })
async getSelfserviceOrg(@Param('token') token: string) { async getSelfserviceOrg(@Param('token') token: string) {
const orgid = (await this.getOrgansisation(token)).id; const orgid = (await this.getOrgansisation(token)).id;
const org = await this.orgRepository.findOne({ id: orgid }, { relations: ['teams'] }) const org = await this.orgRepository.findOne({ id: orgid }, { relations: ['teams'] })
return new ResponseSelfServiceOrganisation(<RunnerOrganization>org); return new ResponseSelfServiceOrganisation(<RunnerOrganization>org);
} }
/** /**
* Get's a runner by a provided jwt token. * Get's a runner by a provided jwt token.
* @param token The runner jwt provided by the runner to identitfy themselves. * @param token The runner jwt provided by the runner to identitfy themselves.
*/ */
private async getRunner(token: string): Promise<Runner> { private async getRunner(token: string): Promise<Runner> {
if (token == "") { throw new JwtNotProvidedError(); } if (token == "") { throw new JwtNotProvidedError(); }
let jwtPayload = undefined let jwtPayload = undefined
try { try {
jwtPayload = <any>jwt.verify(token, config.jwt_secret); jwtPayload = <any>jwt.verify(token, config.jwt_secret);
} catch (error) { } catch (error) {
throw new InvalidCredentialsError(); throw new InvalidCredentialsError();
} }
const runner = await this.runnerRepository.findOne({ id: jwtPayload["id"] }, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] }); const runner = await this.runnerRepository.findOne({ id: jwtPayload["id"] }, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] });
if (!runner) { throw new RunnerNotFoundError() } if (!runner) { throw new RunnerNotFoundError() }
return runner; return runner;
} }
/** /**
* Get's a runner org by a provided registration api key. * Get's a runner org by a provided registration api key.
* @param token The organization's registration api token. * @param token The organization's registration api token.
*/ */
private async getOrgansisation(token: string): Promise<RunnerGroup> { private async getOrgansisation(token: string): Promise<RunnerGroup> {
token = Buffer.from(token, 'base64').toString('utf8'); token = Buffer.from(token, 'base64').toString('utf8');
const organization = await this.orgRepository.findOne({ key: token }); const organization = await this.orgRepository.findOne({ key: token });
if (!organization) { throw new RunnerOrganizationNotFoundError; } if (!organization) { throw new RunnerOrganizationNotFoundError; }
return organization; return organization;
} }
} }