diff --git a/.drone.yml b/.drone.yml index db1b1e7..1fd9285 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,5 +1,27 @@ +--- +kind: secret +name: docker_username +get: + path: odit-registry-builder + name: username + +--- +kind: secret +name: docker_password +get: + path: odit-registry-builder + name: password + +--- +kind: secret +name: git_ssh +get: + path: odit-git-bot + name: sshkey + --- kind: pipeline +type: kubernetes name: tests:node_latest clone: disable: true @@ -20,7 +42,7 @@ trigger: --- kind: pipeline -type: docker +type: kubernetes name: build:dev clone: disable: true @@ -36,13 +58,14 @@ steps: depends_on: [clone] settings: username: - from_secret: DOCKER_REGISTRY_USER + from_secret: docker_username password: - from_secret: DOCKER_REGISTRY_PASSWORD + from_secret: docker_password repo: registry.odit.services/lfk/backend tags: - dev registry: registry.odit.services + mtu: 1000 - name: run changelog export depends_on: ["clone"] image: node:latest @@ -58,7 +81,7 @@ steps: author_email: bot@odit.services remote: git@git.odit.services:lfk/backend.git ssh_key: - from_secret: GITLAB_SSHKEY + from_secret: git_ssh - name: run full license export depends_on: ["clone"] image: node:14.15.1-alpine3.12 @@ -76,7 +99,7 @@ steps: remote: git@git.odit.services:lfk/backend.git skip_verify: true ssh_key: - from_secret: GITLAB_SSHKEY + from_secret: git_ssh trigger: @@ -87,7 +110,7 @@ trigger: --- kind: pipeline -type: docker +type: kubernetes name: build:latest clone: disable: true @@ -105,13 +128,14 @@ steps: image: plugins/docker settings: username: - from_secret: DOCKER_REGISTRY_USER + from_secret: docker_username password: - from_secret: DOCKER_REGISTRY_PASSWORD + from_secret: docker_password repo: registry.odit.services/lfk/backend tags: - latest registry: registry.odit.services + mtu: 1000 - name: push merge to repo depends_on: ["clone"] image: appleboy/drone-git-push @@ -120,7 +144,7 @@ steps: commit: false remote: git@git.odit.services:lfk/backend.git ssh_key: - from_secret: GITLAB_SSHKEY + from_secret: git_ssh trigger: branch: @@ -130,7 +154,7 @@ trigger: --- kind: pipeline -type: docker +type: kubernetes name: build:tags steps: @@ -139,13 +163,14 @@ steps: depends_on: [clone] settings: username: - from_secret: DOCKER_REGISTRY_USER + from_secret: docker_username password: - from_secret: DOCKER_REGISTRY_PASSWORD + from_secret: docker_password repo: registry.odit.services/lfk/backend tags: - '${DRONE_TAG}' registry: registry.odit.services + mtu: 1000 - name: trigger node lib build image: idcooldi/drone-webhook settings: diff --git a/CHANGELOG.md b/CHANGELOG.md index e65ea03..9db607f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,60 @@ All notable changes to this project will be documented in this file. Dates are displayed in UTC. +#### [v0.6.1](https://git.odit.services/lfk/backend/compare/v0.5.0...v0.6.1) + +- 📖New license file version [CI SKIP] [skip ci] [`5f5c8a0`](https://git.odit.services/lfk/backend/commit/5f5c8a061eb94361e4cd02e9a6469194a9092513) +- As requested by @philpp [`2cb7ec7`](https://git.odit.services/lfk/backend/commit/2cb7ec7317d8a48364261506facb2c11c7cf895f) +- Updated ci with new kubernetes secrets 🚀🚀🚀 [`5541ae6`](https://git.odit.services/lfk/backend/commit/5541ae6ebd7f36f4482ae752f358102a18b95de0) +- Added selfservice forgott positive tests [`bf1ec97`](https://git.odit.services/lfk/backend/commit/bf1ec976e3732b6ac052a55a51ee2ee18a8b1d3d) +- Added all "negative" tests [`d0a7e34`](https://git.odit.services/lfk/backend/commit/d0a7e34de8095fca282adefff01fa5f72e7cdba3) +- Added mailer functions [`8376513`](https://git.odit.services/lfk/backend/commit/83765136ccacd82ba6a8f9fb43eed78191ee0aa5) +- Added tests for the new endpoint [`757332e`](https://git.odit.services/lfk/backend/commit/757332ed2b3325d8730ef1b284ac6ba40356df93) +- 🧾New changelog file version [CI SKIP] [skip ci] [`e4ed20d`](https://git.odit.services/lfk/backend/commit/e4ed20da3e0a9e32a2e4664d50f316f9131564f0) +- Added first selfservice forgotten test [`a95a9b4`](https://git.odit.services/lfk/backend/commit/a95a9b4ec4a3012a91f6f622cfb9f5bff3376344) +- Created basic endpoint for user forgotten mails [`d709971`](https://git.odit.services/lfk/backend/commit/d7099717c2eee8aaf1b580345717cc5acc06dbd2) +- Implemented the "real" errors [`e26b7d4`](https://git.odit.services/lfk/backend/commit/e26b7d4923777a3013368e29c122709de7e1d9da) +- Runner controller now uses the Mailer functions [`a343747`](https://git.odit.services/lfk/backend/commit/a3437475caf6b435ae4bdf6d48aeb7da7d43b25f) +- Added scanstation me endpoint [`c5178e0`](https://git.odit.services/lfk/backend/commit/c5178e01814cedaa4402773b10f24d186714c1d2) +- Added last reset requested timestamp to runners [`66d6023`](https://git.odit.services/lfk/backend/commit/66d6023335c7a9d1a145c4189b610940ef5a525a) +- Scanauth return objects [`46b7ace`](https://git.odit.services/lfk/backend/commit/46b7aceb0b86b03688faf0ec6661e4c9fbc6115c) +- Revert "Switched normal images to chached registry" [`ca6fa63`](https://git.odit.services/lfk/backend/commit/ca6fa633a156a265d8f643a5f23090b6ab32260d) +- Switched normal images to chached registry [`cba4455`](https://git.odit.services/lfk/backend/commit/cba4455d53f9a39b6f9993c36b5abd281201dfa1) +- 🧾New changelog file version [CI SKIP] [skip ci] [`a7958ee`](https://git.odit.services/lfk/backend/commit/a7958eecd65116ab937f640cbebcae1962cb86c8) +- 🧾New changelog file version [CI SKIP] [skip ci] [`a1a94ec`](https://git.odit.services/lfk/backend/commit/a1a94ec9dafecd9b4c453cc8cfe32c2e90acccf5) +- 🧾New changelog file version [CI SKIP] [skip ci] [`076aa87`](https://git.odit.services/lfk/backend/commit/076aa87dba1d6fc544e76c16f99c64d37fc82ea0) +- 🧾New changelog file version [CI SKIP] [skip ci] [`486e450`](https://git.odit.services/lfk/backend/commit/486e450a58d3671dc867ae1a99d052d9fe814c1a) +- Updated request timeout [`ffcd45e`](https://git.odit.services/lfk/backend/commit/ffcd45e5724fccdec9b1dbc48f1320525dcd7288) +- Added testing env check [`3f37212`](https://git.odit.services/lfk/backend/commit/3f372123fd2e1fae467e9cb20985de1eeb9f6a57) +- 🚀Bumped version to v0.6.1 [`ce3ca9f`](https://git.odit.services/lfk/backend/commit/ce3ca9f1c86a6fe72e4dd77e3a0d60bf1e1bf542) +- 🚀Bumped version to v0.6.0 [`623b5a1`](https://git.odit.services/lfk/backend/commit/623b5a1873afa73a984251543995b7da1cfdb5c9) +- Merge pull request 'Scanstation "me" endpoint feature/157-scanstation_me' (#158) from feature/157-scanstation_me into dev [`13e8399`](https://git.odit.services/lfk/backend/commit/13e839902c063057e902fdb52b403be081d1667e) +- 🧾New changelog file version [CI SKIP] [skip ci] [`d5930f7`](https://git.odit.services/lfk/backend/commit/d5930f7c46f4fc8ed56b6eeec9f784d435fd3b2b) +- Changed ci pipeline type to kubernetes [`6c43872`](https://git.odit.services/lfk/backend/commit/6c43872198c3dba44b3af3a7cfc7b628d5b304a3) +- Mailer now ignores mailing erros when env is set to test [`6bb3ae8`](https://git.odit.services/lfk/backend/commit/6bb3ae8ba992bd6c4d5809d75a264c710999cdcf) +- 🧾New changelog file version [CI SKIP] [skip ci] [`bf71e35`](https://git.odit.services/lfk/backend/commit/bf71e35ecd333d888d63213d69b04fc681a9d0bd) +- Changed endpoint url to avoid conflicts [`e5dab34`](https://git.odit.services/lfk/backend/commit/e5dab3469c3cef6298fc8deb1192a38f7d18406b) +- Adjusted tests for the new testing env [`9292027`](https://git.odit.services/lfk/backend/commit/92920273bec409563d1e38ea27f4d30f893598e8) +- Applied Docker MTU fix 🛠 [`f7af777`](https://git.odit.services/lfk/backend/commit/f7af77710421d7aae5efb048e0622cd067fc20eb) +- Updated description [`94001a4`](https://git.odit.services/lfk/backend/commit/94001a48f1b314e91ea5ec982e5585124f9541b6) +- Now adding station id to headers of request for scan auth [`8ba7ee1`](https://git.odit.services/lfk/backend/commit/8ba7ee1d481e44e686489e237980b21aaaf6071c) +- Merge pull request 'selfservice forgotten mails feature/154-selfservice_forgotten' (#155) from feature/154-selfservice_forgotten into dev [`cb6e78f`](https://git.odit.services/lfk/backend/commit/cb6e78fc176ec9efe94311b64286020b3c5bf633) +- Added console logging when a testing env get's discovered [`c01233b`](https://git.odit.services/lfk/backend/commit/c01233b4d663aefece26dbb86f8b6bcd5c916325) +- Added not found error logic [`e7f0cb4`](https://git.odit.services/lfk/backend/commit/e7f0cb45c9ac3aa06e2a57786aa1cc51c9d66598) +- Updated to new responsetype [`08957d4`](https://git.odit.services/lfk/backend/commit/08957d4dc2951cfeec56a54680c2ae4ef1525ab2) +- Added readme description for testing env [`cedc175`](https://git.odit.services/lfk/backend/commit/cedc1750c21ad256c3337f293f06e894e2c2ef9f) +- Renamed test [`1d762f5`](https://git.odit.services/lfk/backend/commit/1d762f56628eff47f4e1a910c7152bd0158283bd) + #### [v0.5.0](https://git.odit.services/lfk/backend/compare/v0.4.6...v0.5.0) +> 4 March 2021 + +- Merge pull request 'Alpha Release 0.5.0' (#153) from dev into main [`64da0ea`](https://git.odit.services/lfk/backend/commit/64da0eadb313f3bd3ae20a66bcaf4401528008d9) - Removed mail templates [`c2fdfee`](https://git.odit.services/lfk/backend/commit/c2fdfeed4f5fc454b02bc4b198965889c173bbaa) - Removed mail config [`0342757`](https://git.odit.services/lfk/backend/commit/0342757d929b12635c88e74f17495df656865b1a) - Added selfservice scan response class [`6074ac5`](https://git.odit.services/lfk/backend/commit/6074ac5b3a8e43fd98394c1fb70c6e1dea8fcd5e) - Removed old mailer code [`0fcc729`](https://git.odit.services/lfk/backend/commit/0fcc729b56430f0fdb56242857aa1d883d5a4866) +- 🧾New changelog file version [CI SKIP] [skip ci] [`5272829`](https://git.odit.services/lfk/backend/commit/52728290b477d3f90ee7c14e0d438c4c74415322) - Added the new mailer code [`1551a44`](https://git.odit.services/lfk/backend/commit/1551a444babc025cde6e894c66d2be2c84ab26da) - Removed (now useless) mail controller [`485c247`](https://git.odit.services/lfk/backend/commit/485c247cd3305c4c4422d5582b1d61cc7af84989) - Trackscans now have a laptime that get's calculated on creation [`aa83373`](https://git.odit.services/lfk/backend/commit/aa833736d32993b1656abeeb02a4f8b021ec6252) @@ -23,11 +71,11 @@ All notable changes to this project will be documented in this file. Dates are d - Updated auth reset test for new mailer [`ae7d617`](https://git.odit.services/lfk/backend/commit/ae7d6176902699f82ea127194908ee360233e7b4) - Added scans returns 200 test [`82c65b6`](https://git.odit.services/lfk/backend/commit/82c65b632cdf44165b083494702b836c74e46a41) - 🚀Bumped version to v0.4.7 [`f1d85cf`](https://git.odit.services/lfk/backend/commit/f1d85cfb855c2aae581ade69751b3969ce38f020) +- Now generateing bs mailer config in test env [`bf6b701`](https://git.odit.services/lfk/backend/commit/bf6b70106eb735d9ad6f6ad89f09194680af5ae1) - Added new mailer settings to config [`ddea02d`](https://git.odit.services/lfk/backend/commit/ddea02db574cc348685558f3fa3ecc84adbd6b65) -- 🧾New changelog file version [CI SKIP] [skip ci] [`be397c8`](https://git.odit.services/lfk/backend/commit/be397c8899d5b4406c17e8f9951555c54f852901) - 🚀Bumped version to v0.5.0 [`3f2a2d2`](https://git.odit.services/lfk/backend/commit/3f2a2d292979c7f8162d92465b60b220f2634e7a) - Merge pull request 'Features for the new selfservice feature/151-selfservice_scans_mails' (#152) from feature/151-selfservice_scans_mails into dev [`15356c1`](https://git.odit.services/lfk/backend/commit/15356c1030988d03e3739f3ffe770669789759f2) -- Now generateing bs mailer config in test env [`bf6b701`](https://git.odit.services/lfk/backend/commit/bf6b70106eb735d9ad6f6ad89f09194680af5ae1) +- 🧾New changelog file version [CI SKIP] [skip ci] [`be397c8`](https://git.odit.services/lfk/backend/commit/be397c8899d5b4406c17e8f9951555c54f852901) - Promoted axios to dependency [`a9e06c9`](https://git.odit.services/lfk/backend/commit/a9e06c905537b6da24706389e304e825a33a28ad) - Removed nodemailer from backend [`5833f42`](https://git.odit.services/lfk/backend/commit/5833f4218f9a4c97b69021814df92470a1816917) - Added another resonse type [`030b225`](https://git.odit.services/lfk/backend/commit/030b2255d42aab21d8974fc3a7235285934d53b7) diff --git a/README.md b/README.md index b2ba341..06a44b1 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ yarn docs | DB_USER | String | N/A | The user for accessing the db | DB_PASSWORD | String | N/A | The user's password for accessing the db | DB_NAME | String | N/A | The db's name -| NODE_ENV | String | dev | The apps env - influences debug info. +| NODE_ENV | String | dev | The apps env - influences debug info. Also when the env is set to "test", mailing errors get ignored. | POSTALCODE_COUNTRYCODE | String/CountryCode | N/A | The countrycode used to validate address's postal codes | PHONE_COUNTRYCODE | String/CountryCode | null (international) | The countrycode used to validate phone numers | SEED_TEST_DATA | Boolean | False | If you want the app to seed some example data set this to true diff --git a/licenses.md b/licenses.md index 3c77ba7..e809379 100644 --- a/licenses.md +++ b/licenses.md @@ -57,6 +57,33 @@ SOFTWARE. +# axios +**Author**: Matt Zabriskie +**Repo**: [object Object] +**License**: MIT +**Description**: Promise based HTTP client for the browser and node.js +## License Text +Copyright (c) 2014-present Matt Zabriskie + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + # body-parser **Author**: undefined **Repo**: expressjs/body-parser @@ -390,30 +417,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## License Text -# nodemailer -**Author**: Andris Reinman -**Repo**: [object Object] -**License**: MIT -**Description**: Easy as cake e-mail sending from your Node.js applications -## License Text -Copyright (c) 2011-2019 Andris Reinman - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - # pg **Author**: Brian Carlson **Repo**: [object Object] @@ -865,35 +868,6 @@ OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. SOFTWARE -# @types/nodemailer -**Author**: undefined -**Repo**: [object Object] -**License**: MIT -**Description**: TypeScript definitions for Nodemailer -## License Text - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE - - # @types/uuid **Author**: undefined **Repo**: [object Object] @@ -923,33 +897,6 @@ OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. SOFTWARE -# axios -**Author**: Matt Zabriskie -**Repo**: [object Object] -**License**: MIT -**Description**: Promise based HTTP client for the browser and node.js -## License Text -Copyright (c) 2014-present Matt Zabriskie - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - # cp-cli **Author**: undefined **Repo**: [object Object] diff --git a/package.json b/package.json index 5f6a7d9..cdd3518 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@odit/lfk-backend", - "version": "0.5.0", + "version": "0.6.1", "main": "src/app.ts", "repository": "https://git.odit.services/lfk/backend", "author": { diff --git a/scripts/create_testenv.ts b/scripts/create_testenv.ts index da08dea..ed18169 100644 --- a/scripts/create_testenv.ts +++ b/scripts/create_testenv.ts @@ -10,7 +10,7 @@ DB_PORT=bla DB_USER=bla DB_PASSWORD=bla DB_NAME=./test.sqlite -NODE_ENV=dev +NODE_ENV=test POSTALCODE_COUNTRYCODE=DE SEED_TEST_DATA=true MAILER_URL=https://dev.lauf-fuer-kaya.de/mailer diff --git a/src/app.ts b/src/app.ts index 6e814b3..9bb6803 100644 --- a/src/app.ts +++ b/src/app.ts @@ -20,6 +20,9 @@ const app = createExpressServer({ async function main() { await loaders(app); + if (config.testing) { + consola.info("🛠[config]: Discovered testing env. Mailing errors will get ignored!") + } app.listen(config.internal_port, () => { consola.success( `⚡️[server]: Server is running at http://localhost:${config.internal_port}` diff --git a/src/config.ts b/src/config.ts index 08af1ac..28b687e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -6,6 +6,7 @@ configDotenv(); export const config = { internal_port: parseInt(process.env.APP_PORT) || 4010, development: process.env.NODE_ENV === "production", + testing: process.env.NODE_ENV === "test", jwt_secret: process.env.JWT_SECRET || "secretjwtsecret", phone_validation_countrycode: getPhoneCodeLocale(), postalcode_validation_countrycode: getPostalCodeLocale(), diff --git a/src/controllers/RunnerSelfServiceController.ts b/src/controllers/RunnerSelfServiceController.ts index 8b1f30c..878cf1b 100644 --- a/src/controllers/RunnerSelfServiceController.ts +++ b/src/controllers/RunnerSelfServiceController.ts @@ -1,26 +1,34 @@ +import { Request } from "express"; import * as jwt from "jsonwebtoken"; -import { Body, Get, JsonController, OnUndefined, Param, Post } from 'routing-controllers'; +import { Body, Get, JsonController, OnUndefined, Param, Post, QueryParam, Req, UseBefore } from 'routing-controllers'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { getConnectionManager, Repository } from 'typeorm'; import { config } from '../config'; import { InvalidCredentialsError, JwtNotProvidedError } from '../errors/AuthError'; -import { RunnerEmailNeededError, RunnerNotFoundError } from '../errors/RunnerErrors'; +import { MailSendingError } from '../errors/MailErrors'; +import { RunnerEmailNeededError, RunnerNotFoundError, RunnerSelfserviceTimeoutError } from '../errors/RunnerErrors'; import { RunnerOrganizationNotFoundError } from '../errors/RunnerOrganizationErrors'; +import { ScanStationNotFoundError } from '../errors/ScanStationErrors'; import { JwtCreator } from '../jwtcreator'; +import { Mailer } from '../mailer'; +import ScanAuth from '../middlewares/ScanAuth'; import { CreateSelfServiceCitizenRunner } from '../models/actions/create/CreateSelfServiceCitizenRunner'; import { CreateSelfServiceRunner } from '../models/actions/create/CreateSelfServiceRunner'; import { Runner } from '../models/entities/Runner'; import { RunnerGroup } from '../models/entities/RunnerGroup'; import { RunnerOrganization } from '../models/entities/RunnerOrganization'; +import { ScanStation } from '../models/entities/ScanStation'; +import { ResponseEmpty } from '../models/responses/ResponseEmpty'; +import { ResponseScanStation } from '../models/responses/ResponseScanStation'; import { ResponseSelfServiceOrganisation } from '../models/responses/ResponseSelfServiceOrganisation'; import { ResponseSelfServiceRunner } from '../models/responses/ResponseSelfServiceRunner'; import { ResponseSelfServiceScan } from '../models/responses/ResponseSelfServiceScan'; - @JsonController() export class RunnerSelfServiceController { private runnerRepository: Repository; private orgRepository: Repository; + private stationRepository: Repository; /** * Gets the repository of this controller's model/entity. @@ -28,6 +36,7 @@ export class RunnerSelfServiceController { constructor() { this.runnerRepository = getConnectionManager().get().getRepository(Runner); this.orgRepository = getConnectionManager().get().getRepository(RunnerOrganization); + this.stationRepository = getConnectionManager().get().getRepository(ScanStation); } @Get('/runners/me/:jwt') @@ -53,6 +62,44 @@ export class RunnerSelfServiceController { return responseScans; } + @Get('/stations/me') + @UseBefore(ScanAuth) + @ResponseSchema(ResponseScanStation) + @ResponseSchema(ScanStationNotFoundError, { statusCode: 404 }) + @OnUndefined(ScanStationNotFoundError) + @OpenAPI({ description: 'Lists basic information about the station whose token got provided.
This includes it\'s associated track.', security: [{ "ScanApiToken": [] }] }) + async getStationMe(@Req() req: Request) { + let scan = await this.stationRepository.findOne({ id: parseInt(req.headers["station_id"].toString()) }, { relations: ['track'] }) + if (!scan) { throw new ScanStationNotFoundError(); } + return scan.toResponse(); + } + + @Post('/runners/forgot') + @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) + @OnUndefined(ResponseEmpty) + @OpenAPI({ description: 'TODO' }) + async requestNewToken(@QueryParam('mail') mail: string) { + if (!mail) { + throw new RunnerNotFoundError(); + } + const runner = await this.runnerRepository.findOne({ email: mail }); + if (!runner) { throw new RunnerNotFoundError(); } + + if (runner.resetRequestedTimestamp > (Math.floor(Date.now() / 1000) - 60 * 60 * 24)) { throw new RunnerSelfserviceTimeoutError(); } + const token = JwtCreator.createSelfService(runner); + + try { + await Mailer.sendSelfserviceForgottenMail(runner.email, token, "en") + } catch (error) { + throw new MailSendingError(); + } + + runner.resetRequestedTimestamp = Math.floor(Date.now() / 1000); + await this.runnerRepository.save(runner); + + return { token }; + } + @Post('/runners/register') @ResponseSchema(ResponseSelfServiceRunner) @ResponseSchema(RunnerEmailNeededError, { statusCode: 406 }) @@ -63,6 +110,13 @@ export class RunnerSelfServiceController { 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'] })); response.token = JwtCreator.createSelfService(runner); + + try { + await Mailer.sendSelfserviceWelcomeMail(runner.email, response.token, "en") + } catch (error) { + throw new MailSendingError(); + } + return response; } @@ -78,6 +132,13 @@ export class RunnerSelfServiceController { 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); + + try { + await Mailer.sendSelfserviceWelcomeMail(runner.email, response.token, "en") + } catch (error) { + throw new MailSendingError(); + } + return response; } diff --git a/src/errors/RunnerErrors.ts b/src/errors/RunnerErrors.ts index 7be9048..f5eea84 100644 --- a/src/errors/RunnerErrors.ts +++ b/src/errors/RunnerErrors.ts @@ -46,6 +46,17 @@ export class RunnerEmailNeededError extends NotAcceptableError { message = "Citizenrunners have to provide an email address for verification and contacting." } +/** + * Error to throw when a runner already requested a new selfservice link in the last 24hrs. + */ +export class RunnerSelfserviceTimeoutError extends NotAcceptableError { + @IsString() + name = "RunnerSelfserviceTimeoutError" + + @IsString() + message = "You can only reqest a new token every 24hrs." +} + /** * Error to throw when a runner still has distance donations associated. */ diff --git a/src/mailer.ts b/src/mailer.ts index 33d7f24..fae23d1 100644 --- a/src/mailer.ts +++ b/src/mailer.ts @@ -9,6 +9,7 @@ import { MailSendingError } from './errors/MailErrors'; export class Mailer { public static base: string = config.mailer_url; public static key: string = config.mailer_key; + public static testing: boolean = config.testing; /** * Function for sending a password reset mail. @@ -22,6 +23,41 @@ export class Mailer { resetKey: token }); } catch (error) { + if (Mailer.testing) { return true; } + throw new MailSendingError(); + } + } + + /** + * Function for sending a runner selfservice welcome mail. + * @param to_address The address the mail will be sent to. Should always get pulled from a runner object. + * @param token The requested selfservice token - will be combined with the app_url to generate a selfservice profile link. + */ + public static async sendSelfserviceWelcomeMail(to_address: string, token: string, locale: string = "en") { + try { + await axios.post(`${Mailer.base}/registration?locale=${locale}&key=${Mailer.key}`, { + address: to_address, + selfserviceToken: token + }); + } catch (error) { + if (Mailer.testing) { return true; } + throw new MailSendingError(); + } + } + + /** + * Function for sending a runner selfservice link forgotten mail. + * @param to_address The address the mail will be sent to. Should always get pulled from a runner object. + * @param token The requested selfservice token - will be combined with the app_url to generate a selfservice profile link. + */ + public static async sendSelfserviceForgottenMail(to_address: string, token: string, locale: string = "en") { + try { + await axios.post(`${Mailer.base}/registration_forgot?locale=${locale}&key=${Mailer.key}`, { + address: to_address, + selfserviceToken: token + }); + } catch (error) { + if (Mailer.testing) { return true; } throw new MailSendingError(); } } diff --git a/src/middlewares/ScanAuth.ts b/src/middlewares/ScanAuth.ts index 2f39bbf..eefe69c 100644 --- a/src/middlewares/ScanAuth.ts +++ b/src/middlewares/ScanAuth.ts @@ -15,14 +15,14 @@ import authchecker from './authchecker'; const ScanAuth = async (req: Request, res: Response, next: () => void) => { let provided_token: string = req.headers["authorization"]; if (provided_token == "" || provided_token === undefined || provided_token === null) { - res.status(401).send("No api token provided."); + res.status(401).send({ http_code: 401, short: "no_token", message: "No api token provided." }); return; } try { provided_token = provided_token.replace("Bearer ", ""); } catch (error) { - res.status(401).send("No valid jwt or api token provided."); + res.status(401).send({ http_code: 401, short: "no_token", message: "No valid jwt or api token provided." }); return; } @@ -32,7 +32,7 @@ const ScanAuth = async (req: Request, res: Response, next: () => void) => { } finally { if (prefix == "" || prefix == undefined || prefix == null) { - res.status(401).send("Api token non-existent or invalid syntax."); + res.status(401).send({ http_code: 401, short: "invalid_token", message: "Api token non-existent or invalid syntax." }); return; } } @@ -46,7 +46,7 @@ const ScanAuth = async (req: Request, res: Response, next: () => void) => { } finally { if (user_authorized == false) { - res.status(401).send("Api token non-existent or invalid syntax."); + res.status(401).send({ http_code: 401, short: "invalid_token", message: "Api token non-existent or invalid syntax." }); return; } else { @@ -56,13 +56,13 @@ const ScanAuth = async (req: Request, res: Response, next: () => void) => { } else { if (station.enabled == false) { - res.status(401).send("Station disabled."); + res.status(401).send({ http_code: 401, short: "station_disabled", message: "Station is disabled." }); } if (!(await argon2.verify(station.key, provided_token))) { - res.status(401).send("Api token invalid."); + res.status(401).send({ http_code: 401, short: "invalid_token", message: "Api token non-existent or invalid syntax." }); return; } - + req.headers["station_id"] = station.id.toString(); next(); } } diff --git a/src/models/entities/Runner.ts b/src/models/entities/Runner.ts index ad16eae..87ef38d 100644 --- a/src/models/entities/Runner.ts +++ b/src/models/entities/Runner.ts @@ -1,5 +1,5 @@ -import { IsInt, IsNotEmpty } from "class-validator"; -import { ChildEntity, ManyToOne, OneToMany } from "typeorm"; +import { IsInt, IsNotEmpty, IsOptional, IsString } from "class-validator"; +import { ChildEntity, Column, ManyToOne, OneToMany } from "typeorm"; import { ResponseRunner } from '../responses/ResponseRunner'; import { DistanceDonation } from "./DistanceDonation"; import { Participant } from "./Participant"; @@ -43,6 +43,15 @@ export class Runner extends Participant { @OneToMany(() => Scan, scan => scan.runner, { nullable: true }) scans: Scan[]; + /** + * The last time the runner requested a selfservice link. + * Used to prevent spamming of the selfservice link forgotten route. + */ + @Column({ nullable: true, unique: false }) + @IsString() + @IsOptional() + resetRequestedTimestamp?: number; + /** * Returns all valid scans associated with this runner. * This is implemented here to avoid duplicate code in other files. diff --git a/src/tests/auth/auth_reset.spec.ts b/src/tests/auth/auth_reset.spec.ts index 64aa790..59da15a 100644 --- a/src/tests/auth/auth_reset.spec.ts +++ b/src/tests/auth/auth_reset.spec.ts @@ -38,7 +38,7 @@ describe('POST /api/auth/reset valid', () => { it('valid reset token request should return 200 (500 w/o correct auth)', async () => { const res1 = await axios.post(base + '/api/auth/reset', { email: "demo_reset1@dev.lauf-fuer-kaya.de" }, axios_config); reset_token = res1.data.resetToken; - expect(res1.status).toEqual(500); + expect(res1.status).toEqual(200); }); }); // --------------- diff --git a/src/tests/scanstations/scanstations_get.spec.ts b/src/tests/scanstations/scanstations_get.spec.ts index 7280c31..3cb24ad 100644 --- a/src/tests/scanstations/scanstations_get.spec.ts +++ b/src/tests/scanstations/scanstations_get.spec.ts @@ -56,4 +56,34 @@ describe('adding + getting stations', () => { expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json"); }); +}); +// --------------- +describe('adding + getting via me endpoint', () => { + let added_track; + let added_station; + it('creating a track should return 200', async () => { + const res1 = await axios.post(base + '/api/tracks', { + "name": "test123", + "distance": 123 + }, axios_config); + added_track = res1.data + expect(res1.status).toEqual(200); + expect(res1.headers['content-type']).toContain("application/json") + }); + it('correct description and track input for station creation return 200', async () => { + const res = await axios.post(base + '/api/stations', { + "track": added_track.id, + "description": "I am but a simple test." + }, axios_config); + added_station = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('correct description and track input for station creation return 200', async () => { + const res = await axios.get(base + '/api/stations/me', { headers: { "authorization": "Bearer " + added_station.key } }); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + added_station.key = "Only visible on creation."; + expect(res.data).toEqual(added_station); + }); }); \ No newline at end of file diff --git a/src/tests/selfservice/selfservice_forgotten.spec.ts b/src/tests/selfservice/selfservice_forgotten.spec.ts new file mode 100644 index 0000000..8f11d48 --- /dev/null +++ b/src/tests/selfservice/selfservice_forgotten.spec.ts @@ -0,0 +1,81 @@ +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('POST /api/runners/me/forgot invalid syntax/mail should fail', () => { + it('get without mail return 404', async () => { + const res = await axios.post(base + '/api/runners/forgot', null, axios_config); + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json"); + }); + it('get without bs mail return 404', async () => { + const res = await axios.post(base + '/api/runners/forgot?mail=asdasdasdasdasd@tester.test.dev.lauf-fuer-kaya.de', null, axios_config); + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json"); + }); +}); +// --------------- +describe('POST /api/runners/me/forgot 2 times within timeout should fail', () => { + let added_runner; + it('registering as citizen should return 200', async () => { + const res = await axios.post(base + '/api/runners/register', { + "firstname": "string", + "middlename": "string", + "lastname": "string", + "email": "citizen420@dev.lauf-fuer-kaya.de" + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + added_runner = res.data; + }); + it('post with valid mail should return 200', async () => { + const res = await axios.post(base + '/api/runners/forgot?mail=' + added_runner.email, null, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + }); + it('2nd post with valid mail should return 406', async () => { + const res = await axios.post(base + '/api/runners/forgot?mail=' + added_runner.email, null, axios_config); + expect(res.status).toEqual(406); + expect(res.headers['content-type']).toContain("application/json"); + }); +}); + +// --------------- +describe('POST /api/runners/me/forgot valid should return 200', () => { + let added_runner; + let new_token; + it('registering as citizen should return 200', async () => { + const res = await axios.post(base + '/api/runners/register', { + "firstname": "string", + "middlename": "string", + "lastname": "string", + "email": "citizen69@dev.lauf-fuer-kaya.de" + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + added_runner = res.data; + }); + it('post with valid mail should return 200', async () => { + const res = await axios.post(base + '/api/runners/forgot?mail=' + added_runner.email, null, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + new_token = res.data.token; + }); + it('get infos with valid jwt should return 200', async () => { + const res = await axios.get(base + '/api/runners/me/' + new_token, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + }); +}); diff --git a/src/tests/selfservice/selfservice_org.ts b/src/tests/selfservice/selfservice_org.spec.ts similarity index 97% rename from src/tests/selfservice/selfservice_org.ts rename to src/tests/selfservice/selfservice_org.spec.ts index f269285..f718fac 100644 --- a/src/tests/selfservice/selfservice_org.ts +++ b/src/tests/selfservice/selfservice_org.spec.ts @@ -49,6 +49,6 @@ describe('get valid org w/teams', () => { expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json"); expect(res.data.name).toEqual(added_org.name); - expect(res.data.teams[0]).toEqual({ name: added_team.name, id: added_team.id }); + expect(res.data.teams[0]).toEqual({ name: added_team.name, id: added_team.id, responseType: "SELFSERVICETEAM" }); }); }); \ No newline at end of file