From 69651d9f6cd826b6d4720f164897a2a72a57c851 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Thu, 2 Feb 2023 11:03:12 +0100 Subject: [PATCH 1/7] Rename selfservice forgot to login ref #197 --- src/controllers/RunnerSelfServiceController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/RunnerSelfServiceController.ts b/src/controllers/RunnerSelfServiceController.ts index 9563975..3d2750f 100644 --- a/src/controllers/RunnerSelfServiceController.ts +++ b/src/controllers/RunnerSelfServiceController.ts @@ -116,7 +116,7 @@ export class RunnerSelfServiceController { return scan.toResponse(); } - @Post('/runners/forgot') + @Post('/runners/login') @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) @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).' }) -- 2.47.2 From 68cd746a9f3360b3630a9ba570213d2aa62497b4 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Thu, 2 Feb 2023 11:08:36 +0100 Subject: [PATCH 2/7] Added selfservice runner create check to prevent duplicate email ref #197 --- .../RunnerSelfServiceController.ts | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/controllers/RunnerSelfServiceController.ts b/src/controllers/RunnerSelfServiceController.ts index 3d2750f..41655ca 100644 --- a/src/controllers/RunnerSelfServiceController.ts +++ b/src/controllers/RunnerSelfServiceController.ts @@ -1,6 +1,6 @@ import { Request } from "express"; import * as jwt from "jsonwebtoken"; -import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam, Req, UseBefore } from 'routing-controllers'; +import { BadRequestError, Body, Delete, 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'; @@ -148,8 +148,11 @@ export class RunnerSelfServiceController { @OpenAPI({ description: 'Create a new selfservice runner in the citizen org.
This endpoint shoud be used to allow "everyday citizen" to register themselves.
You have to provide a mail address, b/c the future we\'ll implement email verification.' }) async registerRunner(@Body({ validate: true }) createRunner: CreateSelfServiceCitizenRunner, @QueryParam("locale") locale: string = "en") { let runner = await createRunner.toEntity(); - + if (await this.getRunnerExistsByMail(runner.email)) { + throw new BadRequestError("E-Mail already registered") + } 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); @@ -170,6 +173,9 @@ export class RunnerSelfServiceController { const org = await this.getOrgansisation(token); let runner = await createRunner.toEntity(org); + if (await this.getRunnerExistsByMail(runner.email)) { + throw new BadRequestError("E-Mail already registered") + } 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'] })); @@ -225,4 +231,14 @@ export class RunnerSelfServiceController { return organization; } + + /** + * Checks if a runner already exists + * @param email The runner's email address + * @returns Boolean (true if exists, false if not) + */ + private async getRunnerExistsByMail(email: string): Promise { + const runner = await this.runnerRepository.findOne({ email }); + return runner != undefined + } } \ No newline at end of file -- 2.47.2 From e1846739638905aab6ba7e059fd2cbf8ff467bf3 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Thu, 2 Feb 2023 11:10:04 +0100 Subject: [PATCH 3/7] Added faker for testing ref #197 --- package.json | 217 ++++++++++++++++++++++++++------------------------- 1 file changed, 109 insertions(+), 108 deletions(-) diff --git a/package.json b/package.json index 4dd13c9..9823b61 100644 --- a/package.json +++ b/package.json @@ -1,108 +1,109 @@ -{ - "name": "@odit/lfk-backend", - "version": "0.12.0", - "main": "src/app.ts", - "repository": "https://git.odit.services/lfk/backend", - "author": { - "name": "ODIT.Services", - "email": "info@odit.services", - "url": "https://odit.services" - }, - "contributors": [ - { - "name": "Philipp Dormann", - "email": "philipp@philippdormann.de", - "url": "https://philippdormann.de" - }, - { - "name": "Nicolai Ort", - "email": "info@nicolai-ort.com", - "url": "https://nicolai-ort.com" - } - ], - "license": "CC-BY-NC-SA-4.0", - "dependencies": { - "@odit/class-validator-jsonschema": "2.1.1", - "argon2": "0.27.1", - "axios": "0.21.1", - "body-parser": "1.19.0", - "check-password-strength": "2.0.2", - "class-transformer": "0.3.1", - "class-validator": "0.13.1", - "consola": "2.15.0", - "cookie": "0.4.1", - "cookie-parser": "1.4.5", - "cors": "2.8.5", - "csvtojson": "2.0.10", - "dotenv": "8.2.0", - "express": "4.17.1", - "jsonwebtoken": "8.5.1", - "libphonenumber-js": "1.9.9", - "mysql": "2.18.1", - "pg": "8.5.1", - "reflect-metadata": "0.1.13", - "routing-controllers": "0.9.0-alpha.6", - "routing-controllers-openapi": "2.2.0", - "sqlite3": "5.0.0", - "typeorm": "0.2.30", - "typeorm-routing-controllers-extensions": "0.2.0", - "typeorm-seeding": "1.6.1", - "uuid": "8.3.2", - "validator": "13.5.2" - }, - "devDependencies": { - "@odit/license-exporter": "0.0.9", - "@types/cors": "2.8.9", - "@types/csvtojson": "1.1.5", - "@types/express": "4.17.11", - "@types/jest": "26.0.20", - "@types/jsonwebtoken": "8.5.0", - "@types/node": "14.14.22", - "@types/uuid": "8.3.0", - "cp-cli": "2.0.0", - "jest": "26.6.3", - "nodemon": "2.0.7", - "release-it": "14.2.2", - "rimraf": "3.0.2", - "start-server-and-test": "1.11.7", - "ts-jest": "26.5.0", - "ts-node": "9.1.1", - "typedoc": "0.20.19", - "typescript": "4.1.3" - }, - "scripts": { - "dev": "nodemon src/app.ts", - "build": "rimraf ./dist && tsc && cp-cli ./src/static ./dist/static", - "docs": "typedoc --out docs src", - "test": "jest", - "test:watch": "jest --watchAll", - "test:ci:generate_env": "ts-node scripts/create_testenv.ts", - "test:ci:run": "start-server-and-test dev http://localhost:4010/api/docs/openapi.json test", - "test:ci": "npm run test:ci:generate_env && npm run test:ci:run", - "seed": "ts-node ./node_modules/typeorm/cli.js schema:sync && ts-node ./node_modules/typeorm-seeding/dist/cli.js seed", - "openapi:export": "ts-node scripts/openapi_export.ts", - "licenses:export": "license-exporter --markdown", - "release": "release-it --only-version" - }, - "release-it": { - "git": { - "commit": true, - "requireCleanWorkingDir": false, - "commitMessage": "🚀Bumped version to v${version}", - "requireBranch": "dev", - "push": true, - "tag": true, - "tagName": "v${version}", - "tagAnnotation": "v${version}" - }, - "npm": { - "publish": false - } - }, - "nodemonConfig": { - "ignore": [ - "src/tests/*", - "docs/*" - ] - } -} +{ + "name": "@odit/lfk-backend", + "version": "0.12.0", + "main": "src/app.ts", + "repository": "https://git.odit.services/lfk/backend", + "author": { + "name": "ODIT.Services", + "email": "info@odit.services", + "url": "https://odit.services" + }, + "contributors": [ + { + "name": "Philipp Dormann", + "email": "philipp@philippdormann.de", + "url": "https://philippdormann.de" + }, + { + "name": "Nicolai Ort", + "email": "info@nicolai-ort.com", + "url": "https://nicolai-ort.com" + } + ], + "license": "CC-BY-NC-SA-4.0", + "dependencies": { + "@odit/class-validator-jsonschema": "2.1.1", + "argon2": "0.27.1", + "axios": "0.21.1", + "body-parser": "1.19.0", + "check-password-strength": "2.0.2", + "class-transformer": "0.3.1", + "class-validator": "0.13.1", + "consola": "2.15.0", + "cookie": "0.4.1", + "cookie-parser": "1.4.5", + "cors": "2.8.5", + "csvtojson": "2.0.10", + "dotenv": "8.2.0", + "express": "4.17.1", + "jsonwebtoken": "8.5.1", + "libphonenumber-js": "1.9.9", + "mysql": "2.18.1", + "pg": "8.5.1", + "reflect-metadata": "0.1.13", + "routing-controllers": "0.9.0-alpha.6", + "routing-controllers-openapi": "2.2.0", + "sqlite3": "5.0.0", + "typeorm": "0.2.30", + "typeorm-routing-controllers-extensions": "0.2.0", + "typeorm-seeding": "1.6.1", + "uuid": "8.3.2", + "validator": "13.5.2" + }, + "devDependencies": { + "@faker-js/faker": "^7.6.0", + "@odit/license-exporter": "0.0.9", + "@types/cors": "2.8.9", + "@types/csvtojson": "1.1.5", + "@types/express": "4.17.11", + "@types/jest": "26.0.20", + "@types/jsonwebtoken": "8.5.0", + "@types/node": "14.14.22", + "@types/uuid": "8.3.0", + "cp-cli": "2.0.0", + "jest": "26.6.3", + "nodemon": "2.0.7", + "release-it": "14.2.2", + "rimraf": "3.0.2", + "start-server-and-test": "1.11.7", + "ts-jest": "26.5.0", + "ts-node": "9.1.1", + "typedoc": "0.20.19", + "typescript": "4.1.3" + }, + "scripts": { + "dev": "nodemon src/app.ts", + "build": "rimraf ./dist && tsc && cp-cli ./src/static ./dist/static", + "docs": "typedoc --out docs src", + "test": "jest", + "test:watch": "jest --watchAll", + "test:ci:generate_env": "ts-node scripts/create_testenv.ts", + "test:ci:run": "start-server-and-test dev http://localhost:4010/api/docs/openapi.json test", + "test:ci": "npm run test:ci:generate_env && npm run test:ci:run", + "seed": "ts-node ./node_modules/typeorm/cli.js schema:sync && ts-node ./node_modules/typeorm-seeding/dist/cli.js seed", + "openapi:export": "ts-node scripts/openapi_export.ts", + "licenses:export": "license-exporter --markdown", + "release": "release-it --only-version" + }, + "release-it": { + "git": { + "commit": true, + "requireCleanWorkingDir": false, + "commitMessage": "🚀Bumped version to v${version}", + "requireBranch": "dev", + "push": true, + "tag": true, + "tagName": "v${version}", + "tagAnnotation": "v${version}" + }, + "npm": { + "publish": false + } + }, + "nodemonConfig": { + "ignore": [ + "src/tests/*", + "docs/*" + ] + } +} -- 2.47.2 From 9bc80aac8aab9b4dedc26c9bc3ce705d7fe9c0bf Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Thu, 2 Feb 2023 11:14:48 +0100 Subject: [PATCH 4/7] Updated selfservice tests to prevent email duplication ref #197 --- .../selfservice/selfservice_delete.spec.ts | 6 ++-- src/tests/selfservice/selfservice_get.spec.ts | 3 +- .../selfservice/selfservice_register.spec.ts | 36 ++++++++++++++----- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/tests/selfservice/selfservice_delete.spec.ts b/src/tests/selfservice/selfservice_delete.spec.ts index 9add148..423d93e 100644 --- a/src/tests/selfservice/selfservice_delete.spec.ts +++ b/src/tests/selfservice/selfservice_delete.spec.ts @@ -1,5 +1,7 @@ +import { faker } from '@faker-js/faker'; import axios from 'axios'; import { config } from '../../config'; + const base = "http://localhost:" + config.internal_port let access_token; @@ -21,7 +23,7 @@ describe('delete selfservice runner invalid', () => { const res = await axios.post(base + '/api/runners/register', { "firstname": "string", "lastname": "string", - "email": "user@example.com" + "email": faker.internet.exampleEmail(), }, axios_config); added_runner = res.data; expect(res.status).toEqual(200); @@ -50,7 +52,7 @@ describe('delete selfservice runner valid', () => { const res = await axios.post(base + '/api/runners/register', { "firstname": "string", "lastname": "string", - "email": "user@example.com" + "email": faker.internet.exampleEmail(), }, axios_config); added_runner = res.data; expect(res.status).toEqual(200); diff --git a/src/tests/selfservice/selfservice_get.spec.ts b/src/tests/selfservice/selfservice_get.spec.ts index 7867127..201cc08 100644 --- a/src/tests/selfservice/selfservice_get.spec.ts +++ b/src/tests/selfservice/selfservice_get.spec.ts @@ -1,3 +1,4 @@ +import { faker } from '@faker-js/faker'; import axios from 'axios'; import { config } from '../../config'; const base = "http://localhost:" + config.internal_port @@ -30,7 +31,7 @@ describe('register + get should return 200', () => { "firstname": "string", "middlename": "string", "lastname": "string", - "email": "user@example.com" + "email": faker.internet.exampleEmail(), }, axios_config); expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json"); diff --git a/src/tests/selfservice/selfservice_register.spec.ts b/src/tests/selfservice/selfservice_register.spec.ts index 36dc7fa..18fac19 100644 --- a/src/tests/selfservice/selfservice_register.spec.ts +++ b/src/tests/selfservice/selfservice_register.spec.ts @@ -1,3 +1,4 @@ +import { faker } from '@faker-js/faker'; import axios from 'axios'; import { config } from '../../config'; const base = "http://localhost:" + config.internal_port @@ -39,7 +40,7 @@ describe('register invalid citizen', () => { const res = await axios.post(base + '/api/runners/register', { "middlename": "string", "lastname": "string", - "email": "user@example.com" + "email": faker.internet.exampleEmail(), }, axios_config); expect(res.status).toEqual(400); expect(res.headers['content-type']).toContain("application/json"); @@ -48,7 +49,7 @@ describe('register invalid citizen', () => { const res = await axios.post(base + '/api/runners/register', { "firstname": "string", "middlename": "string", - "email": "user@example.com" + "email": faker.internet.exampleEmail(), }, axios_config); expect(res.status).toEqual(400); expect(res.headers['content-type']).toContain("application/json"); @@ -59,7 +60,26 @@ describe('register invalid citizen', () => { "middlename": "string", "lastname": "string", "phone": "peter", - "email": "user@example.com" + "email": faker.internet.exampleEmail(), + }, axios_config); + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json"); + }); + it('registering as citizen with duplicate mail should return 400', async () => { + const mail = faker.internet.exampleEmail(); + await axios.post(base + '/api/runners/register', { + "firstname": "string", + "middlename": "string", + "lastname": "string", + "phone": "peter", + "email": mail, + }, axios_config); + const res = await axios.post(base + '/api/runners/register', { + "firstname": "string", + "middlename": "string", + "lastname": "string", + "phone": "peter", + "email": mail, }, axios_config); expect(res.status).toEqual(400); expect(res.headers['content-type']).toContain("application/json"); @@ -71,7 +91,7 @@ describe('register citizen valid', () => { const res = await axios.post(base + '/api/runners/register', { "firstname": "string", "lastname": "string", - "email": "user@example.com" + "email": faker.internet.exampleEmail(), }, axios_config); expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json"); @@ -81,7 +101,7 @@ describe('register citizen valid', () => { "firstname": "string", "middlename": "string", "lastname": "string", - "email": "user@example.com", + "email": faker.internet.exampleEmail(), "phone": "+4909132123456", "address": { address1: "Teststreet 1", @@ -187,7 +207,7 @@ describe('register valid company', () => { "firstname": "string", "middlename": "string", "lastname": "string", - "email": "user@example.com", + "email": faker.internet.exampleEmail(), "phone": "+4909132123456", "address": { address1: "Teststreet 1", @@ -214,7 +234,7 @@ describe('register valid company', () => { "firstname": "string", "middlename": "string", "lastname": "string", - "email": "user@example.com", + "email": "faker.internet.exampleEmail(), "phone": "+4909132123456", "address": { address1: "Teststreet 1", @@ -232,7 +252,7 @@ describe('register valid company', () => { "firstname": "string", "middlename": "string", "lastname": "string", - "email": "user@example.com", + "email": faker.internet.exampleEmail(), "phone": "+4909132123456", "address": { address1: "Teststreet 1", -- 2.47.2 From 19a290c3a931ead0d9ae9ebb0985bfbaac54df59 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Thu, 2 Feb 2023 11:17:18 +0100 Subject: [PATCH 5/7] Fixed typo --- src/tests/selfservice/selfservice_register.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/selfservice/selfservice_register.spec.ts b/src/tests/selfservice/selfservice_register.spec.ts index 18fac19..724857a 100644 --- a/src/tests/selfservice/selfservice_register.spec.ts +++ b/src/tests/selfservice/selfservice_register.spec.ts @@ -234,7 +234,7 @@ describe('register valid company', () => { "firstname": "string", "middlename": "string", "lastname": "string", - "email": "faker.internet.exampleEmail(), + "email": faker.internet.exampleEmail(), "phone": "+4909132123456", "address": { address1: "Teststreet 1", -- 2.47.2 From 39aa7598b7cd0ecb0f077f50ebdd31c6e205f06d Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Thu, 2 Feb 2023 11:18:45 +0100 Subject: [PATCH 6/7] Updated tests for new login in selfservice ref #197 --- .../selfservice/selfservice_forgotten.spec.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/tests/selfservice/selfservice_forgotten.spec.ts b/src/tests/selfservice/selfservice_forgotten.spec.ts index c67ef7e..9eb865e 100644 --- a/src/tests/selfservice/selfservice_forgotten.spec.ts +++ b/src/tests/selfservice/selfservice_forgotten.spec.ts @@ -15,20 +15,20 @@ beforeAll(async () => { }; }); -describe('POST /api/runners/me/forgot invalid syntax/mail should fail', () => { +describe('POST /api/runners/me/login invalid syntax/mail should fail', () => { it('get without mail return 404', async () => { - const res = await axios.post(base + '/api/runners/forgot', null, axios_config); + const res = await axios.post(base + '/api/runners/login', 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); + const res = await axios.post(base + '/api/runners/login?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', () => { +describe('POST /api/runners/me/login 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', { @@ -42,19 +42,19 @@ describe('POST /api/runners/me/forgot 2 times within timeout should fail', () => 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); + const res = await axios.post(base + '/api/runners/login?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); + const res = await axios.post(base + '/api/runners/login?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', () => { +describe('POST /api/runners/me/login valid should return 200', () => { let added_runner; let new_token; it('registering as citizen should return 200', async () => { @@ -69,7 +69,7 @@ describe('POST /api/runners/me/forgot valid should return 200', () => { 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); + const res = await axios.post(base + '/api/runners/login?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; -- 2.47.2 From 4433ddb1e15a35481728670e22049200644bf337 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Thu, 2 Feb 2023 11:24:04 +0100 Subject: [PATCH 7/7] Updated logo url --- src/models/actions/create/CreateUser.ts | 2 +- src/models/actions/update/UpdateUser.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/models/actions/create/CreateUser.ts b/src/models/actions/create/CreateUser.ts index a29fb62..d85a8ba 100644 --- a/src/models/actions/create/CreateUser.ts +++ b/src/models/actions/create/CreateUser.ts @@ -114,7 +114,7 @@ export class CreateUser { newUser.groups = await this.getGroups(); newUser.enabled = this.enabled; - if (!this.profilePic) { newUser.profilePic = `https://dev.lauf-fuer-kaya.de/lfk-logo.png`; } + if (!this.profilePic) { newUser.profilePic = `https://lauf-fuer-kaya.de/lfk-logo.png`; } else { newUser.profilePic = this.profilePic; } return newUser; diff --git a/src/models/actions/update/UpdateUser.ts b/src/models/actions/update/UpdateUser.ts index e5685eb..88f36c6 100644 --- a/src/models/actions/update/UpdateUser.ts +++ b/src/models/actions/update/UpdateUser.ts @@ -124,7 +124,7 @@ export class UpdateUser { user.phone = this.phone; user.groups = await this.getGroups(); - if (!this.profilePic) { user.profilePic = `https://dev.lauf-fuer-kaya.de/lfk-logo.png`; } + if (!this.profilePic) { user.profilePic = `https://lauf-fuer-kaya.de/lfk-logo.png`; } else { user.profilePic = this.profilePic; } return user; -- 2.47.2