diff --git a/.drone.yml b/.drone.yml index 204f6c3..db1b1e7 100644 --- a/.drone.yml +++ b/.drone.yml @@ -9,7 +9,6 @@ steps: commands: - git clone $DRONE_REMOTE_URL . - git checkout $DRONE_SOURCE_BRANCH - - mv .env.ci .env - name: run tests image: node:latest commands: diff --git a/CHANGELOG.md b/CHANGELOG.md index f837d42..2b0a0d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,58 @@ All notable changes to this project will be documented in this file. Dates are displayed in UTC. +#### [v0.4.0](https://git.odit.services/lfk/backend/compare/v0.3.1...v0.4.0) + +- Added test mail templates [`8270029`](https://git.odit.services/lfk/backend/commit/827002989ee6e3f0d776b5b55b8fb6d65f10c898) +- 🧾New changelog file version [CI SKIP] [skip ci] [`09b24aa`](https://git.odit.services/lfk/backend/commit/09b24aa6094a980debf4428a1c32583cdb7b606f) +- Table fix [`1f0c842`](https://git.odit.services/lfk/backend/commit/1f0c842d9e086456f1ae0f6908e474258a04beb4) +- 🧾New changelog file version [CI SKIP] [skip ci] [`fea4857`](https://git.odit.services/lfk/backend/commit/fea485768570eb5de2bbd2783e339794a67db2de) +- 🧾New changelog file version [CI SKIP] [skip ci] [`e07f258`](https://git.odit.services/lfk/backend/commit/e07f258a315898d1183c311e7fcd8f65a415504c) +- 🚀Bumped version to v0.4.0 [`e5f4f6e`](https://git.odit.services/lfk/backend/commit/e5f4f6ee590e0885d6eef9151ce7eb76578b70ca) +- Merge pull request 'Implemented testmail endpoint feature/124-testmail' (#130) from feature/124-testmail into dev [`f9e75d0`](https://git.odit.services/lfk/backend/commit/f9e75d06b8ee8ff79f60fb384cb2c35ccf19811d) +- Merge pull request 'Email Basics feature/118-emails' (#128) from feature/118-emails into dev [`348e6cd`](https://git.odit.services/lfk/backend/commit/348e6cdec7411345953243edfb5322a17ad7614d) +- Merge pull request 'Mail+Env documentation feature/123-mail_documentation' (#129) from feature/123-mail_documentation into dev [`61bbeb0`](https://git.odit.services/lfk/backend/commit/61bbeb0d8f3fd6bfafb65bd11eb4c076a27b4a53) +- Added pw reset template provided by @philipp [`c116338`](https://git.odit.services/lfk/backend/commit/c116338cd74cf726362f8fa0ae5eea7ec9fabac4) +- Implemented automatic ci env generation [`536de2a`](https://git.odit.services/lfk/backend/commit/536de2a3199b1befed54b6fe520a2e3fcefe0d90) +- Implemented a basic mailer with reset link sending [`6379753`](https://git.odit.services/lfk/backend/commit/637975305f1adf9bf505507790638cf1e229cfb1) +- Implemented the test-mail endpoint via a new mailcontroller [`54ed313`](https://git.odit.services/lfk/backend/commit/54ed313342a72b029b9545bc5ea193e3f0c2166d) +- Added documentation for the env vars [`13ccab5`](https://git.odit.services/lfk/backend/commit/13ccab5e289d0a629cefb7fe281a85a46058ae97) +- Added comments [`9bd7636`](https://git.odit.services/lfk/backend/commit/9bd7636a23b5a9662ea2b965d2a2407727a188fb) +- Added test mail sending test [`ae74b39`](https://git.odit.services/lfk/backend/commit/ae74b3963fddb847aed4a828031b93b26cf551db) +- Password reset now enforces email [`979d36e`](https://git.odit.services/lfk/backend/commit/979d36ea9147dc575e9e989f6833388828285176) +- Implementes mail sending on pw reset request [`e26744b`](https://git.odit.services/lfk/backend/commit/e26744b7925d32d65ef4cc3911651758cfc9274f) +- Added a txt variant of the pw-reset mail [`d3647e3`](https://git.odit.services/lfk/backend/commit/d3647e339990d989dbca4d91aa8c3fe5789dd24a) +- Changed order [`583a4bc`](https://git.odit.services/lfk/backend/commit/583a4bc0dd0de8026bb2eb6a9b0c31f59344e813) +- Translated the pw reset mail to english [`5cade25`](https://git.odit.services/lfk/backend/commit/5cade25eeb137eb5890b3fd450646acfbdff2e8b) +- The auth tests now use mail to identify the user [`c43334b`](https://git.odit.services/lfk/backend/commit/c43334bf96901bfd5116301ff7cf4b2ae1dfcbd3) +- Added a test mail sending function [`b94179e`](https://git.odit.services/lfk/backend/commit/b94179e3caaf4be0654ca3372f57a490fb32e208) +- Added the first mail error [`c418603`](https://git.odit.services/lfk/backend/commit/c4186034233a296b5971fbef16e7ef6809fbac51) +- Now also sending txt mail body [`b92f633`](https://git.odit.services/lfk/backend/commit/b92f633d68604636cecc5e9fdd0d6990b9cb83fe) +- Removed tests working directly with the old pw-reset response [`d02e9de`](https://git.odit.services/lfk/backend/commit/d02e9dec5637aedefdf2ed3cd2c6d73216b6464b) +- Added the basics about mail templates to the readme [`b5018eb`](https://git.odit.services/lfk/backend/commit/b5018eb11492884db9f4ec969c767c3cce53f105) +- Cleaned up the replacements [`389e423`](https://git.odit.services/lfk/backend/commit/389e423850d68a5fe440b62413a6c662353ac9c6) +- Added mail env vars [`d7ea928`](https://git.odit.services/lfk/backend/commit/d7ea928714f94814695cbd2815c8730df58033f6) +- Added a barebones class for handleing mail stuff [`cf012c0`](https://git.odit.services/lfk/backend/commit/cf012c0b7efffb81b03497a04b0fdad0423c72f7) +- Added a Mail permisssion target [`ad4b903`](https://git.odit.services/lfk/backend/commit/ad4b903c258820f14df28d56b12e099075ca7d78) +- Added env vars [`470703c`](https://git.odit.services/lfk/backend/commit/470703c4de954da94726879becd57986b59e1f69) +- 🧾New changelog file version [CI SKIP] [skip ci] [`2071c4d`](https://git.odit.services/lfk/backend/commit/2071c4db33bbb9fd41ef650b409cac789732225f) +- Added a hint to ethereal.email [`53fcff7`](https://git.odit.services/lfk/backend/commit/53fcff77d00fc2b205ada0bcee7bdfe83d94a9f4) +- Fixed missing app_url protocol [`46af786`](https://git.odit.services/lfk/backend/commit/46af7865165bbfb97ed3e6cdfef15dfb72add611) +- Removed the duplicate env copy/create from ci tests [`08e6e59`](https://git.odit.services/lfk/backend/commit/08e6e5965544906f5033f2080166bddc37cc30c7) +- Removed bs console.log [`71c4caa`](https://git.odit.services/lfk/backend/commit/71c4caae8ba67e253d893409b3c5c3a39b08060a) +- Added nodemailer types [`78d2ac3`](https://git.odit.services/lfk/backend/commit/78d2ac3027f7109161ee36e9b3dda628a7039468) +- Added nodemailer dependecy [`908ac4f`](https://git.odit.services/lfk/backend/commit/908ac4f1ce9d78749268353c668e67b57eae6f94) +- Fixed wrong file location [`b4c117b`](https://git.odit.services/lfk/backend/commit/b4c117b7dc326d212598b6e720d0a422134e383d) +- Renamed the template [`fb77f4d`](https://git.odit.services/lfk/backend/commit/fb77f4d798550fdb6fe6b2c8a81198db0328f71e) +- Added a folder for the mail templates [`6b0155f`](https://git.odit.services/lfk/backend/commit/6b0155f01464f5ef73ab679b9e3219743e9db66b) +- Added a folder for the mail templates [`33890b5`](https://git.odit.services/lfk/backend/commit/33890b544b77272ab1c4797e91375d24568eae58) + #### [v0.3.1](https://git.odit.services/lfk/backend/compare/v0.3.0...v0.3.1) +> 27 January 2021 + +- Merge pull request 'Alpha Release 0.3.1' (#127) from dev into main [`20f960e`](https://git.odit.services/lfk/backend/commit/20f960ed6700fe58c0556895e6485d26c4a4f5e2) +- 🧾New changelog file version [CI SKIP] [skip ci] [`e6fe8fc`](https://git.odit.services/lfk/backend/commit/e6fe8fcd587751392970d3ee412559b4c1d60f21) - Merge pull request 'new advanced endpoints feature/125-team_runner' (#126) from feature/125-team_runner into dev [`870fd47`](https://git.odit.services/lfk/backend/commit/870fd47c83389345d7b24a15df6a4e61e930d140) - Added get runners by team test [`69417e9`](https://git.odit.services/lfk/backend/commit/69417e93c081422561db1e211b12f32e539010ce) - 🧾New changelog file version [CI SKIP] [skip ci] [`71898d5`](https://git.odit.services/lfk/backend/commit/71898d576c2620d2f2e2b4336e62f1d04a443201) diff --git a/README.md b/README.md index 985a69f..8d538ea 100644 --- a/README.md +++ b/README.md @@ -35,11 +35,41 @@ yarn test:watch yarn test:ci ``` +### Use your own mail templates +> You use your own mail templates by replacing the default ones we provided (either in-code or by mounting them into the /app/static/mail_templates folder). + +The mail templates always come in a .html and a .txt variant to provide compatability with legacy mail clients. +Currently the following templates exist: +* pw-reset.(html/txt) + ### Generate Docs ```bash yarn docs ``` +## ENV Vars +> You can provide them via .env file or docker env vars. +> You can use the `test:ci:generate_env` package script to generate a example env (uses [ethereal.email](https://ethereal.email) as the mailserver). + +| Name | Type | Default | Description +| - | - | - | - +| APP_PORT | Number | 4010 | The port the backend server listens on. Is optional. +| DB_TYPE | String | N/A | The type of the db u want to use. It has to be supported by typeorm. Possible: `sqlite`, `mysql`, `postgresql` +| DB_HOST | String | N/A | The db's host's ip-address/fqdn or file path for sqlite +| DB_PORT | String | N/A | The db's port +| 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. +| 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 +| MAIL_SERVER | String | N/A | The smtp server's ip-address/fqdn +| MAIL_PORT | String | N/A | The smtp server's port +| MAIL_USER | String | N/A | The username for sending mails +| MAIL_PASSWORD | String | N/A | The user's password for sending mails +| MAIL_FROM | String | N/A | The from-address for sending mails + ## Recommended Editor [Visual Studio Code](https://code.visualstudio.com/) diff --git a/package.json b/package.json index 8e0ba5c..0436bee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@odit/lfk-backend", - "version": "0.3.1", + "version": "0.4.0", "main": "src/app.ts", "repository": "https://git.odit.services/lfk/backend", "author": { @@ -37,6 +37,7 @@ "jsonwebtoken": "^8.5.1", "libphonenumber-js": "^1.9.7", "mysql": "^2.18.1", + "nodemailer": "^6.4.17", "pg": "^8.5.1", "reflect-metadata": "^0.1.13", "routing-controllers": "^0.9.0-alpha.6", @@ -56,6 +57,7 @@ "@types/jest": "^26.0.16", "@types/jsonwebtoken": "^8.5.0", "@types/node": "^14.14.20", + "@types/nodemailer": "^6.4.0", "@types/uuid": "^8.3.0", "axios": "^0.21.1", "cp-cli": "^2.0.0", @@ -75,7 +77,9 @@ "docs": "typedoc --out docs src", "test": "jest", "test:watch": "jest --watchAll", - "test:ci": "start-server-and-test dev http://localhost:4010/api/docs/openapi.json test", + "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 --md", diff --git a/scripts/create_testenv.ts b/scripts/create_testenv.ts new file mode 100644 index 0000000..1fa6c8c --- /dev/null +++ b/scripts/create_testenv.ts @@ -0,0 +1,37 @@ +import consola from "consola"; +import fs from "fs"; +import nodemailer from "nodemailer"; + + +nodemailer.createTestAccount((err, account) => { + if (err) { + console.error('Failed to create a testing account. ' + err.message); + return process.exit(1); + } + + const env = ` +APP_PORT=4010 +DB_TYPE=sqlite +DB_HOST=bla +DB_PORT=bla +DB_USER=bla +DB_PASSWORD=bla +DB_NAME=./test.sqlite +NODE_ENV=dev +POSTALCODE_COUNTRYCODE=DE +SEED_TEST_DATA=true +MAIL_SERVER=${account.smtp.host} +MAIL_PORT=${account.smtp.port} +MAIL_USER=${account.user} +MAIL_PASSWORD=${account.pass} +MAIL_FROM=${account.user}` + + try { + fs.writeFileSync("./.env", env, { encoding: "utf-8" }); + consola.success("Exported ci env to .env"); + } catch (error) { + consola.error("Couldn't export the ci env"); + } + +}); + diff --git a/src/config.ts b/src/config.ts index 3bdcd27..d2840da 100644 --- a/src/config.ts +++ b/src/config.ts @@ -10,7 +10,13 @@ export const config = { phone_validation_countrycode: getPhoneCodeLocale(), postalcode_validation_countrycode: getPostalCodeLocale(), version: process.env.VERSION || require('../package.json').version, - seedTestData: getDataSeeding() + seedTestData: getDataSeeding(), + app_url: process.env.APP_URL || "http://localhost:4010", + mail_server: process.env.MAIL_SERVER, + mail_port: Number(process.env.MAIL_PORT) || 25, + mail_user: process.env.MAIL_USER, + mail_password: process.env.MAIL_PASSWORD, + mail_from: process.env.MAIL_FROM } let errors = 0 if (typeof config.internal_port !== "number") { diff --git a/src/controllers/AuthController.ts b/src/controllers/AuthController.ts index 507ac38..3545422 100644 --- a/src/controllers/AuthController.ts +++ b/src/controllers/AuthController.ts @@ -2,17 +2,23 @@ import { Body, CookieParam, JsonController, Param, Post, Req, Res } from 'routin import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { IllegalJWTError, InvalidCredentialsError, JwtNotProvidedError, PasswordNeededError, RefreshTokenCountInvalidError, UsernameOrEmailNeededError } from '../errors/AuthError'; import { UserNotFoundError } from '../errors/UserErrors'; +import { Mailer } from '../mailer'; import { CreateAuth } from '../models/actions/create/CreateAuth'; import { CreateResetToken } from '../models/actions/create/CreateResetToken'; import { HandleLogout } from '../models/actions/HandleLogout'; import { RefreshAuth } from '../models/actions/RefreshAuth'; import { ResetPassword } from '../models/actions/ResetPassword'; import { ResponseAuth } from '../models/responses/ResponseAuth'; +import { ResponseEmpty } from '../models/responses/ResponseEmpty'; import { Logout } from '../models/responses/ResponseLogout'; @JsonController('/auth') export class AuthController { + + private mailer: Mailer; + constructor() { + this.mailer = new Mailer(); } @Post("/login") @@ -82,13 +88,14 @@ export class AuthController { } @Post("/reset") - @ResponseSchema(ResponseAuth) - @ResponseSchema(UserNotFoundError) - @ResponseSchema(UsernameOrEmailNeededError) + @ResponseSchema(ResponseEmpty, { statusCode: 200 }) + @ResponseSchema(UserNotFoundError, { statusCode: 404 }) + @ResponseSchema(UsernameOrEmailNeededError, { statusCode: 406 }) @OpenAPI({ description: "Request a password reset token.
This will provide you with a reset token that you can use by posting to /api/auth/reset/{token}." }) async getResetToken(@Body({ validate: true }) passwordReset: CreateResetToken) { - //This really shouldn't just get returned, but sent via mail or sth like that. But for dev only this is fine. - return { "resetToken": await passwordReset.toResetToken() }; + const reset_token: String = await passwordReset.toResetToken(); + await this.mailer.sendResetMail(passwordReset.email, reset_token); + return new ResponseEmpty(); } @Post("/reset/:token") diff --git a/src/controllers/MailController.ts b/src/controllers/MailController.ts new file mode 100644 index 0000000..94ff599 --- /dev/null +++ b/src/controllers/MailController.ts @@ -0,0 +1,26 @@ +import { Authorized, JsonController, Post } from 'routing-controllers'; +import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; +import { config } from '../config'; +import { Mailer } from '../mailer'; +import { ResponseEmpty } from '../models/responses/ResponseEmpty'; + + +@JsonController('/mails') +@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) +export class MailController { + + private mailer: Mailer; + + constructor() { + this.mailer = new Mailer(); + } + + @Post('/test') + @Authorized(["MAIL:CREATE"]) + @ResponseSchema(ResponseEmpty, { statusCode: 200 }) + @OpenAPI({ description: 'Sends a test email to the configured from-address.' }) + async get() { + await this.mailer.sendTestMail(config.mail_from); + return new ResponseEmpty(); + } +} \ No newline at end of file diff --git a/src/errors/MailErrors.ts b/src/errors/MailErrors.ts new file mode 100644 index 0000000..7772ca9 --- /dev/null +++ b/src/errors/MailErrors.ts @@ -0,0 +1,12 @@ +import { IsString } from 'class-validator' + +/** + * Error to throw when a permission couldn't be found. + */ +export class MailServerConfigError extends Error { + @IsString() + name = "MailServerConfigError" + + @IsString() + message = "The SMTP server you provided couldn't be reached!" +} \ No newline at end of file diff --git a/src/mailer.ts b/src/mailer.ts new file mode 100644 index 0000000..d52e540 --- /dev/null +++ b/src/mailer.ts @@ -0,0 +1,79 @@ +import fs from "fs"; +import nodemailer from 'nodemailer'; +import { MailOptions } from 'nodemailer/lib/json-transport'; +import Mail from 'nodemailer/lib/mailer'; +import { config } from './config'; +import { MailServerConfigError } from './errors/MailErrors'; + +/** + * This class is responsible for all things mail sending. + * This uses the mail emplates from src/static/mail_templates + */ +export class Mailer { + private transport: Mail; + + /** + * The class's default constructor. + * Creates the transporter and tests the connection. + */ + constructor() { + this.transport = nodemailer.createTransport({ + host: config.mail_server, + port: config.mail_port, + auth: { + user: config.mail_user, + pass: config.mail_password + } + }); + + this.transport.verify(function (error, success) { + if (error) { + throw new MailServerConfigError(); + } + }); + } + + /** + * Function for sending a test mail from the test mail template. + * @param to_address The address the mail will be sent to. Should always get pulled from a user object. + * @param token The requested password reset token - will be combined with the app_url to generate a password reset link. + */ + public async sendResetMail(to_address: string, token: String) { + const reset_link = `${config.app_url}/reset/${token}` + const body_html = fs.readFileSync(__dirname + '/static/mail_templates/pw-reset.html', { encoding: 'utf8' }).replace("{{reset_link}}", reset_link).replace("{{recipient_mail}}", to_address).replace("{{copyright_owner}}", "LfK!").replace("{{link_imprint}}", `${config.app_url}/imprint`).replace("{{link_privacy}}", `${config.app_url}/privacy`); + const body_txt = fs.readFileSync(__dirname + '/static/mail_templates/pw-reset.html', { encoding: 'utf8' }).replace("{{reset_link}}", reset_link).replace("{{recipient_mail}}", to_address).replace("{{copyright_owner}}", "LfK!").replace("{{link_imprint}}", `${config.app_url}/imprint`).replace("{{link_privacy}}", `${config.app_url}/privacy`); + + const mail: MailOptions = { + to: to_address, + subject: "LfK! Password Reset", + text: body_txt, + html: body_html + }; + await this.sendMail(mail); + } + + /** + * Function for sending a test mail from the test mail template. + * @param to_address The address the test mail will be sent to - this is the configured from-address by default. + */ + public async sendTestMail(to_address: string = config.mail_from) { + const body_html = fs.readFileSync(__dirname + '/static/mail_templates/test.html', { encoding: 'utf8' }).replace("{{recipient_mail}}", to_address).replace("{{copyright_owner}}", "LfK!").replace("{{link_imprint}}", `${config.app_url}/imprint`).replace("{{link_privacy}}", `${config.app_url}/privacy`); + const body_txt = fs.readFileSync(__dirname + '/static/mail_templates/test.txt', { encoding: 'utf8' }).replace("{{recipient_mail}}", to_address).replace("{{copyright_owner}}", "LfK!").replace("{{link_imprint}}", `${config.app_url}/imprint`).replace("{{link_privacy}}", `${config.app_url}/privacy`); + const mail: MailOptions = { + to: to_address, + subject: "LfK! Test Mail", + text: body_txt, + html: body_html + }; + await this.sendMail(mail); + } + + /** + * Wrapper function for sending a mail via this object's transporter. + * @param mail MailOptions object containing the + */ + public async sendMail(mail: MailOptions) { + mail.from = config.mail_from; + await this.transport.sendMail(mail); + } +} diff --git a/src/models/actions/create/CreateResetToken.ts b/src/models/actions/create/CreateResetToken.ts index 81f430a..8194fe4 100644 --- a/src/models/actions/create/CreateResetToken.ts +++ b/src/models/actions/create/CreateResetToken.ts @@ -1,39 +1,33 @@ -import { IsEmail, IsOptional, IsString } from 'class-validator'; +import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; import { getConnectionManager } from 'typeorm'; import { ResetAlreadyRequestedError, UserDisabledError, UserNotFoundError } from '../../../errors/AuthError'; -import { UsernameOrEmailNeededError } from '../../../errors/UserErrors'; +import { UserEmailNeededError } from '../../../errors/UserErrors'; import { JwtCreator } from '../../../jwtcreator'; import { User } from '../../entities/User'; /** - * This calss is used to create password reset tokens for users. + * This class is used to create password reset tokens for users. * These password reset token can be used to set a new password for the user for the next 15mins. */ export class CreateResetToken { - /** - * The username of the user that wants to reset their password. - */ - @IsOptional() - @IsString() - username?: string; /** * The email address of the user that wants to reset their password. */ - @IsOptional() + @IsNotEmpty() @IsEmail() @IsString() - email?: string; + email: string; /** * Create a password reset token based on this. */ public async toResetToken(): Promise { - if (this.email === undefined && this.username === undefined) { - throw new UsernameOrEmailNeededError(); + if (!this.email) { + throw new UserEmailNeededError(); } - let found_user = await getConnectionManager().get().getRepository(User).findOne({ where: [{ username: this.username }, { email: this.email }] }); + let found_user = await getConnectionManager().get().getRepository(User).findOne({ where: [{ email: this.email }] }); if (!found_user) { throw new UserNotFoundError(); } if (found_user.enabled == false) { throw new UserDisabledError(); } if (found_user.resetRequestedTimestamp > (Math.floor(Date.now() / 1000) - 15 * 60)) { throw new ResetAlreadyRequestedError(); } diff --git a/src/models/enums/PermissionTargets.ts b/src/models/enums/PermissionTargets.ts index 4bb9c1c..249ec2f 100644 --- a/src/models/enums/PermissionTargets.ts +++ b/src/models/enums/PermissionTargets.ts @@ -15,5 +15,6 @@ export enum PermissionTarget { STATION = 'STATION', CARD = 'CARD', DONATION = 'DONATION', - CONTACT = 'CONTACT' + CONTACT = 'CONTACT', + MAIL = 'MAIL' } \ No newline at end of file diff --git a/src/static/mail_templates/.gitkeep b/src/static/mail_templates/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/static/mail_templates/pw-reset.html b/src/static/mail_templates/pw-reset.html new file mode 100644 index 0000000..7f672b8 --- /dev/null +++ b/src/static/mail_templates/pw-reset.html @@ -0,0 +1,384 @@ + + + + + LfK! - Passwort zurücksetzen + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ LfK! - Password reset +
+ + + + +
+ + \ No newline at end of file diff --git a/src/static/mail_templates/pw-reset.txt b/src/static/mail_templates/pw-reset.txt new file mode 100644 index 0000000..68366b7 --- /dev/null +++ b/src/static/mail_templates/pw-reset.txt @@ -0,0 +1,12 @@ +LfK! - Password reset. + +A password reset for your account got requested +If you didn't request the reset please ignore this mail +Your password won't be changed until you click the reset link below and set a new one. + +Reset: {{reset_link}} + + +Copyright © {{copyright_owner}}. All rights reserved. +Imprint: {{link_imprint}} | Privacy: {{link_privacy}} +This mail was sent to {{recipient_mail}} because someone request a password reset for a account linked to the mail address. \ No newline at end of file diff --git a/src/static/mail_templates/test.html b/src/static/mail_templates/test.html new file mode 100644 index 0000000..35be644 --- /dev/null +++ b/src/static/mail_templates/test.html @@ -0,0 +1,369 @@ + + + + + LfK! - Mail test + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ LfK! - Mail test +
+ + + + +
+ + \ No newline at end of file diff --git a/src/static/mail_templates/test.txt b/src/static/mail_templates/test.txt new file mode 100644 index 0000000..423da10 --- /dev/null +++ b/src/static/mail_templates/test.txt @@ -0,0 +1,8 @@ +LfK! - Mail test. + +This is a test mail triggered by an admin in the LfK! backend. + + +Copyright © {{copyright_owner}}. All rights reserved. +Imprint: {{link_imprint}} | Privacy: {{link_privacy}} +This mail was sent to {{recipient_mail}} because someone requested a mail test for this mail address. \ No newline at end of file diff --git a/src/tests/auth/auth_reset.spec.ts b/src/tests/auth/auth_reset.spec.ts index 47c61e6..498b66d 100644 --- a/src/tests/auth/auth_reset.spec.ts +++ b/src/tests/auth/auth_reset.spec.ts @@ -15,7 +15,7 @@ beforeAll(async () => { "lastname": "demo_reset", "username": "demo_reset", "password": "demo_reset", - "email": "demo_reset@dev.lauf-fuer-kaya.de" + "email": "demo_reset1@dev.lauf-fuer-kaya.de" }, { headers: { "authorization": "Bearer " + res_login.data["access_token"] }, validateStatus: undefined @@ -26,7 +26,7 @@ beforeAll(async () => { "lastname": "demo_reset2", "username": "demo_reset2", "password": "demo_reset2", - "email": "demo_reset1@dev.lauf-fuer-kaya.de" + "email": "demo_reset2@dev.lauf-fuer-kaya.de" }, { headers: { "authorization": "Bearer " + res_login.data["access_token"] }, validateStatus: undefined @@ -36,24 +36,16 @@ beforeAll(async () => { describe('POST /api/auth/reset valid', () => { let reset_token; it('valid reset token request should return 200', async () => { - const res1 = await axios.post(base + '/api/auth/reset', { username: "demo_reset" }); + const res1 = await axios.post(base + '/api/auth/reset', { email: "demo_reset1@dev.lauf-fuer-kaya.de" }); reset_token = res1.data.resetToken; expect(res1.status).toEqual(200); }); - it('valid password reset should return 200', async () => { - const res2 = await axios.post(base + '/api/auth/reset/' + reset_token, { password: "demo" }, axios_config); - expect(res2.status).toEqual(200); - }); - it('valid login after reset should return 200', async () => { - const res = await axios.post(base + '/api/auth/login', { username: "demo_reset", password: "demo" }); - expect(res.status).toEqual(200); - }); }); // --------------- describe('POST /api/auth/reset invalid requests', () => { it('request another password reset before the timeout should return 406', async () => { - const res1 = await axios.post(base + '/api/auth/reset', { username: "demo_reset2" }, axios_config); - const res2 = await axios.post(base + '/api/auth/reset', { username: "demo_reset2" }, axios_config); + const res1 = await axios.post(base + '/api/auth/reset', { email: "demo_reset2@dev.lauf-fuer-kaya.de" }, axios_config); + const res2 = await axios.post(base + '/api/auth/reset', { email: "demo_reset2@dev.lauf-fuer-kaya.de" }, axios_config); expect(res2.status).toEqual(406); }); }); @@ -63,9 +55,9 @@ describe('POST /api/auth/reset invalid token', () => { const res2 = await axios.post(base + '/api/auth/reset/' + "123123", { password: "demo" }, axios_config); expect(res2.status).toEqual(401); }); - it('providing no reset token should return 404', async () => { + it('providing no reset token should return 400', async () => { const res2 = await axios.post(base + '/api/auth/reset/' + "", { password: "demo" }, axios_config); - expect(res2.status).toEqual(404); + expect(res2.status).toEqual(400); }); }); // --------------- diff --git a/src/tests/contacts/contact_update.spec.ts b/src/tests/contacts/contact_update.spec.ts index 3132cbc..52fe57c 100644 --- a/src/tests/contacts/contact_update.spec.ts +++ b/src/tests/contacts/contact_update.spec.ts @@ -111,7 +111,6 @@ describe('Update contact group after adding (should work)', () => { "lastname": "last", "groups": added_team.id }, axios_config); - console.log(res.data) expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json"); expect(res.data).toEqual({ diff --git a/src/tests/mails/mail_test.spec.ts b/src/tests/mails/mail_test.spec.ts new file mode 100644 index 0000000..d8a8d71 --- /dev/null +++ b/src/tests/mails/mail_test.spec.ts @@ -0,0 +1,22 @@ +import axios from 'axios'; +import { config } from '../../config'; + +const base = "http://localhost:" + config.internal_port + +let access_token; +let axios_config; + +beforeAll(async () => { + const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); + access_token = res.data["access_token"]; + axios_config = { + headers: { "authorization": "Bearer " + access_token }, + validateStatus: undefined + }; +}); + +describe('POST /mails/test valid', () => { + it('test mail request should return 200', async () => { + const res1 = await axios.post(base + '/api/mails/test', null, axios_config); + }); +}); \ No newline at end of file