Merge pull request 'Alpha Release 0.4.3' (#139) from dev into main
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing

Reviewed-on: #139
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
This commit is contained in:
Nicolai Ort 2021-02-07 12:43:30 +00:00
commit dd9cb6d3ef
6 changed files with 129 additions and 117 deletions

View File

@ -2,12 +2,24 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC. All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [v0.4.3](https://git.odit.services/lfk/backend/compare/v0.4.2...v0.4.3)
- 🚀Bumped version to v0.4.3 [`656d564`](https://git.odit.services/lfk/backend/commit/656d564baa8c8bf1f63523b0301ad9ff23e08aa4)
- 🧾New changelog file version [CI SKIP] [skip ci] [`f3f5cb4`](https://git.odit.services/lfk/backend/commit/f3f5cb462e4ecf932ad55eb519815222b4e5dd17)
- Merge pull request 'Bugfix for @lfk/frontend/#43' (#138) from bugfix/118-encode_jwt_in_mail into dev [`9959172`](https://git.odit.services/lfk/backend/commit/9959172f2ae11cbb7a2b8e97bad432956fc70a80)
- Bugfix for @lfk/frontend/#43 [`8f0a396`](https://git.odit.services/lfk/backend/commit/8f0a396dd07937fb7ccfb345d1acbac86eb5d9bb)
- 🧾New changelog file version [CI SKIP] [skip ci] [`a18d4d3`](https://git.odit.services/lfk/backend/commit/a18d4d3cee58f8eb9dc428b051a2394bd3ece5c2)
#### [v0.4.2](https://git.odit.services/lfk/backend/compare/v0.4.1...v0.4.2) #### [v0.4.2](https://git.odit.services/lfk/backend/compare/v0.4.1...v0.4.2)
- 📖New license file version [CI SKIP] [skip ci] [`74791df`](https://git.odit.services/lfk/backend/commit/74791df68b40355e1d1a1f7f5ae4f64422571dc9) > 2 February 2021
- Merge pull request 'Alpha Release 0.4.2' (#137) from dev into main [`390b36d`](https://git.odit.services/lfk/backend/commit/390b36dfd46cf8829126581bd62dd3d4ce8558fa)
- 🧾New changelog file version [CI SKIP] [skip ci] [`3b718f3`](https://git.odit.services/lfk/backend/commit/3b718f3ce58f12007b6068e5db00a00dbe1c5398)
- 🧾New changelog file version [CI SKIP] [skip ci] [`f7a0ec7`](https://git.odit.services/lfk/backend/commit/f7a0ec7174521b1863a4bc58c7ff2b86cafdee66) - 🧾New changelog file version [CI SKIP] [skip ci] [`f7a0ec7`](https://git.odit.services/lfk/backend/commit/f7a0ec7174521b1863a4bc58c7ff2b86cafdee66)
- 🚀Bumped version to v0.4.2 [`321b20b`](https://git.odit.services/lfk/backend/commit/321b20b073b6debd605a92544779d0dfc0449f10) - 🚀Bumped version to v0.4.2 [`321b20b`](https://git.odit.services/lfk/backend/commit/321b20b073b6debd605a92544779d0dfc0449f10)
- Merge pull request 'Imprint&Privacy Links feature/135-imprint_and_privacy' (#136) from feature/135-imprint_and_privacy into dev [`110a847`](https://git.odit.services/lfk/backend/commit/110a84783e023407cbcf81506deb7cc204db9659) - Merge pull request 'Imprint&Privacy Links feature/135-imprint_and_privacy' (#136) from feature/135-imprint_and_privacy into dev [`110a847`](https://git.odit.services/lfk/backend/commit/110a84783e023407cbcf81506deb7cc204db9659)
- 📖New license file version [CI SKIP] [skip ci] [`74791df`](https://git.odit.services/lfk/backend/commit/74791df68b40355e1d1a1f7f5ae4f64422571dc9)
- 🧾New changelog file version [CI SKIP] [skip ci] [`8425043`](https://git.odit.services/lfk/backend/commit/84250430996920ada15af23b68756daba8f99257) - 🧾New changelog file version [CI SKIP] [skip ci] [`8425043`](https://git.odit.services/lfk/backend/commit/84250430996920ada15af23b68756daba8f99257)
- Added new url env vars to config [`bcad691`](https://git.odit.services/lfk/backend/commit/bcad691045d00c9630bedb0936c123610b655946) - Added new url env vars to config [`bcad691`](https://git.odit.services/lfk/backend/commit/bcad691045d00c9630bedb0936c123610b655946)
- fixed license-exporter call [`74b982a`](https://git.odit.services/lfk/backend/commit/74b982afba3ec82a038c4748420920732fe32a51) - fixed license-exporter call [`74b982a`](https://git.odit.services/lfk/backend/commit/74b982afba3ec82a038c4748420920732fe32a51)

View File

@ -1,6 +1,6 @@
{ {
"name": "@odit/lfk-backend", "name": "@odit/lfk-backend",
"version": "0.4.2", "version": "0.4.3",
"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

@ -11,7 +11,7 @@ export const config = {
postalcode_validation_countrycode: getPostalCodeLocale(), postalcode_validation_countrycode: getPostalCodeLocale(),
version: process.env.VERSION || require('../package.json').version, version: process.env.VERSION || require('../package.json').version,
seedTestData: getDataSeeding(), seedTestData: getDataSeeding(),
app_url: process.env.APP_URL || "http://localhost:4010", app_url: process.env.APP_URL || "http://localhost:8080",
mail_server: process.env.MAIL_SERVER, mail_server: process.env.MAIL_SERVER,
mail_port: Number(process.env.MAIL_PORT) || 25, mail_port: Number(process.env.MAIL_PORT) || 25,
mail_user: process.env.MAIL_USER, mail_user: process.env.MAIL_USER,

View File

@ -1,110 +1,110 @@
import { Body, CookieParam, JsonController, Param, Post, Req, Res } from 'routing-controllers'; import { Body, CookieParam, JsonController, Param, Post, Req, Res } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { IllegalJWTError, InvalidCredentialsError, JwtNotProvidedError, PasswordNeededError, RefreshTokenCountInvalidError, UsernameOrEmailNeededError } from '../errors/AuthError'; import { IllegalJWTError, InvalidCredentialsError, JwtNotProvidedError, PasswordNeededError, RefreshTokenCountInvalidError, UsernameOrEmailNeededError } from '../errors/AuthError';
import { UserNotFoundError } from '../errors/UserErrors'; import { UserNotFoundError } from '../errors/UserErrors';
import { Mailer } from '../mailer'; import { Mailer } from '../mailer';
import { CreateAuth } from '../models/actions/create/CreateAuth'; import { CreateAuth } from '../models/actions/create/CreateAuth';
import { CreateResetToken } from '../models/actions/create/CreateResetToken'; import { CreateResetToken } from '../models/actions/create/CreateResetToken';
import { HandleLogout } from '../models/actions/HandleLogout'; import { HandleLogout } from '../models/actions/HandleLogout';
import { RefreshAuth } from '../models/actions/RefreshAuth'; import { RefreshAuth } from '../models/actions/RefreshAuth';
import { ResetPassword } from '../models/actions/ResetPassword'; import { ResetPassword } from '../models/actions/ResetPassword';
import { ResponseAuth } from '../models/responses/ResponseAuth'; import { ResponseAuth } from '../models/responses/ResponseAuth';
import { ResponseEmpty } from '../models/responses/ResponseEmpty'; import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { Logout } from '../models/responses/ResponseLogout'; import { Logout } from '../models/responses/ResponseLogout';
@JsonController('/auth') @JsonController('/auth')
export class AuthController { export class AuthController {
private mailer: Mailer; private mailer: Mailer;
constructor() { constructor() {
this.mailer = new Mailer(); this.mailer = new Mailer();
} }
@Post("/login") @Post("/login")
@ResponseSchema(ResponseAuth) @ResponseSchema(ResponseAuth)
@ResponseSchema(InvalidCredentialsError) @ResponseSchema(InvalidCredentialsError)
@ResponseSchema(UserNotFoundError) @ResponseSchema(UserNotFoundError)
@ResponseSchema(UsernameOrEmailNeededError) @ResponseSchema(UsernameOrEmailNeededError)
@ResponseSchema(PasswordNeededError) @ResponseSchema(PasswordNeededError)
@ResponseSchema(InvalidCredentialsError) @ResponseSchema(InvalidCredentialsError)
@OpenAPI({ description: 'Login with your username/email and password. <br> You will receive: \n * access token (use it as a bearer token) \n * refresh token (will also be sent as a cookie)' }) @OpenAPI({ description: 'Login with your username/email and password. <br> You will receive: \n * access token (use it as a bearer token) \n * refresh token (will also be sent as a cookie)' })
async login(@Body({ validate: true }) createAuth: CreateAuth, @Res() response: any) { async login(@Body({ validate: true }) createAuth: CreateAuth, @Res() response: any) {
let auth; let auth;
try { try {
auth = await createAuth.toAuth(); auth = await createAuth.toAuth();
response.cookie('lfk_backend__refresh_token', auth.refresh_token, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true }); response.cookie('lfk_backend__refresh_token', auth.refresh_token, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
response.cookie('lfk_backend__refresh_token_expires_at', auth.refresh_token_expires_at, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true }); response.cookie('lfk_backend__refresh_token_expires_at', auth.refresh_token_expires_at, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
return response.send(auth) return response.send(auth)
} catch (error) { } catch (error) {
throw error; throw error;
} }
} }
@Post("/logout") @Post("/logout")
@ResponseSchema(Logout) @ResponseSchema(Logout)
@ResponseSchema(InvalidCredentialsError) @ResponseSchema(InvalidCredentialsError)
@ResponseSchema(UserNotFoundError) @ResponseSchema(UserNotFoundError)
@ResponseSchema(UsernameOrEmailNeededError) @ResponseSchema(UsernameOrEmailNeededError)
@ResponseSchema(PasswordNeededError) @ResponseSchema(PasswordNeededError)
@ResponseSchema(InvalidCredentialsError) @ResponseSchema(InvalidCredentialsError)
@OpenAPI({ description: 'Logout using your refresh token. <br> This instantly invalidates all your access and refresh tokens.', security: [{ "RefreshTokenCookie": [] }] }) @OpenAPI({ description: 'Logout using your refresh token. <br> This instantly invalidates all your access and refresh tokens.', security: [{ "RefreshTokenCookie": [] }] })
async logout(@Body({ validate: true }) handleLogout: HandleLogout, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any) { async logout(@Body({ validate: true }) handleLogout: HandleLogout, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any) {
if (refresh_token && refresh_token.length != 0 && handleLogout.token == undefined) { if (refresh_token && refresh_token.length != 0 && handleLogout.token == undefined) {
handleLogout.token = refresh_token; handleLogout.token = refresh_token;
} }
let logout; let logout;
try { try {
logout = await handleLogout.logout() logout = await handleLogout.logout()
await response.cookie('lfk_backend__refresh_token', "expired", { expires: new Date(Date.now()), httpOnly: true }); await response.cookie('lfk_backend__refresh_token', "expired", { expires: new Date(Date.now()), httpOnly: true });
response.cookie('lfk_backend__refresh_token_expires_at', "expired", { expires: new Date(Date.now()), httpOnly: true }); response.cookie('lfk_backend__refresh_token_expires_at', "expired", { expires: new Date(Date.now()), httpOnly: true });
} catch (error) { } catch (error) {
throw error; throw error;
} }
return response.send(logout) return response.send(logout)
} }
@Post("/refresh") @Post("/refresh")
@ResponseSchema(ResponseAuth) @ResponseSchema(ResponseAuth)
@ResponseSchema(JwtNotProvidedError) @ResponseSchema(JwtNotProvidedError)
@ResponseSchema(IllegalJWTError) @ResponseSchema(IllegalJWTError)
@ResponseSchema(UserNotFoundError) @ResponseSchema(UserNotFoundError)
@ResponseSchema(RefreshTokenCountInvalidError) @ResponseSchema(RefreshTokenCountInvalidError)
@OpenAPI({ description: 'Refresh your access and refresh tokens using a valid refresh token. <br> You will receive: \n * access token (use it as a bearer token) \n * refresh token (will also be sent as a cookie)', security: [{ "RefreshTokenCookie": [] }] }) @OpenAPI({ description: 'Refresh your access and refresh tokens using a valid refresh token. <br> You will receive: \n * access token (use it as a bearer token) \n * refresh token (will also be sent as a cookie)', security: [{ "RefreshTokenCookie": [] }] })
async refresh(@Body({ validate: true }) refreshAuth: RefreshAuth, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any, @Req() req: any) { async refresh(@Body({ validate: true }) refreshAuth: RefreshAuth, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any, @Req() req: any) {
if (refresh_token && refresh_token.length != 0 && refreshAuth.token == undefined) { if (refresh_token && refresh_token.length != 0 && refreshAuth.token == undefined) {
refreshAuth.token = refresh_token; refreshAuth.token = refresh_token;
} }
let auth; let auth;
try { try {
auth = await refreshAuth.toAuth(); auth = await refreshAuth.toAuth();
response.cookie('lfk_backend__refresh_token', auth.refresh_token, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true }); response.cookie('lfk_backend__refresh_token', auth.refresh_token, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
response.cookie('lfk_backend__refresh_token_expires_at', auth.refresh_token_expires_at, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true }); response.cookie('lfk_backend__refresh_token_expires_at', auth.refresh_token_expires_at, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
} catch (error) { } catch (error) {
throw error; throw error;
} }
return response.send(auth) return response.send(auth)
} }
@Post("/reset") @Post("/reset")
@ResponseSchema(ResponseEmpty, { statusCode: 200 }) @ResponseSchema(ResponseEmpty, { statusCode: 200 })
@ResponseSchema(UserNotFoundError, { statusCode: 404 }) @ResponseSchema(UserNotFoundError, { statusCode: 404 })
@ResponseSchema(UsernameOrEmailNeededError, { statusCode: 406 }) @ResponseSchema(UsernameOrEmailNeededError, { statusCode: 406 })
@OpenAPI({ description: "Request a password reset token. <br> This will provide you with a reset token that you can use by posting to /api/auth/reset/{token}." }) @OpenAPI({ description: "Request a password reset token. <br> 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) { async getResetToken(@Body({ validate: true }) passwordReset: CreateResetToken) {
const reset_token: String = await passwordReset.toResetToken(); const reset_token: string = await passwordReset.toResetToken();
await this.mailer.sendResetMail(passwordReset.email, reset_token); await this.mailer.sendResetMail(passwordReset.email, reset_token);
return new ResponseEmpty(); return new ResponseEmpty();
} }
@Post("/reset/:token") @Post("/reset/:token")
@ResponseSchema(ResponseAuth) @ResponseSchema(ResponseAuth)
@ResponseSchema(UserNotFoundError) @ResponseSchema(UserNotFoundError)
@ResponseSchema(UsernameOrEmailNeededError) @ResponseSchema(UsernameOrEmailNeededError)
@OpenAPI({ description: "Reset a user's utilising a valid password reset token. <br> This will set the user's password to the one you provided in the body. <br> To get a reset token post to /api/auth/reset with your username." }) @OpenAPI({ description: "Reset a user's utilising a valid password reset token. <br> This will set the user's password to the one you provided in the body. <br> To get a reset token post to /api/auth/reset with your username." })
async resetPassword(@Param("token") token: string, @Body({ validate: true }) passwordReset: ResetPassword) { async resetPassword(@Param("token") token: string, @Body({ validate: true }) passwordReset: ResetPassword) {
passwordReset.resetToken = token; passwordReset.resetToken = token;
return await passwordReset.resetPassword(); return await passwordReset.resetPassword();
} }
} }

View File

@ -38,8 +38,8 @@ export class Mailer {
* @param to_address The address the mail will be sent to. Should always get pulled from a user object. * @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. * @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) { public async sendResetMail(to_address: string, token: string) {
const reset_link = `${config.app_url}/reset/${token}` const reset_link = `${config.app_url}/reset/${(Buffer.from(token)).toString("base64")}`
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_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 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`);

View File

@ -23,7 +23,7 @@ export class CreateResetToken {
/** /**
* Create a password reset token based on this. * Create a password reset token based on this.
*/ */
public async toResetToken(): Promise<any> { public async toResetToken(): Promise<string> {
if (!this.email) { if (!this.email) {
throw new UserEmailNeededError(); throw new UserEmailNeededError();
} }
@ -37,7 +37,7 @@ export class CreateResetToken {
await getConnectionManager().get().getRepository(User).save(found_user); await getConnectionManager().get().getRepository(User).save(found_user);
//Create the reset token //Create the reset token
let reset_token = JwtCreator.createReset(found_user); let reset_token: string = JwtCreator.createReset(found_user);
return reset_token; return reset_token;
} }