Compare commits
No commits in common. "348e6cdec7411345953243edfb5322a17ad7614d" and "e07f258a315898d1183c311e7fcd8f65a415504c" have entirely different histories.
348e6cdec7
...
e07f258a31
@ -9,6 +9,7 @@ steps:
|
|||||||
commands:
|
commands:
|
||||||
- git clone $DRONE_REMOTE_URL .
|
- git clone $DRONE_REMOTE_URL .
|
||||||
- git checkout $DRONE_SOURCE_BRANCH
|
- git checkout $DRONE_SOURCE_BRANCH
|
||||||
|
- mv .env.ci .env
|
||||||
- name: run tests
|
- name: run tests
|
||||||
image: node:latest
|
image: node:latest
|
||||||
commands:
|
commands:
|
||||||
|
@ -37,7 +37,6 @@
|
|||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"libphonenumber-js": "^1.9.7",
|
"libphonenumber-js": "^1.9.7",
|
||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
"nodemailer": "^6.4.17",
|
|
||||||
"pg": "^8.5.1",
|
"pg": "^8.5.1",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"routing-controllers": "^0.9.0-alpha.6",
|
"routing-controllers": "^0.9.0-alpha.6",
|
||||||
@ -57,7 +56,6 @@
|
|||||||
"@types/jest": "^26.0.16",
|
"@types/jest": "^26.0.16",
|
||||||
"@types/jsonwebtoken": "^8.5.0",
|
"@types/jsonwebtoken": "^8.5.0",
|
||||||
"@types/node": "^14.14.20",
|
"@types/node": "^14.14.20",
|
||||||
"@types/nodemailer": "^6.4.0",
|
|
||||||
"@types/uuid": "^8.3.0",
|
"@types/uuid": "^8.3.0",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"cp-cli": "^2.0.0",
|
"cp-cli": "^2.0.0",
|
||||||
@ -77,9 +75,7 @@
|
|||||||
"docs": "typedoc --out docs src",
|
"docs": "typedoc --out docs src",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watchAll",
|
"test:watch": "jest --watchAll",
|
||||||
"test:ci:generate_env": "ts-node scripts/create_testenv.ts",
|
"test:ci": "start-server-and-test dev http://localhost:4010/api/docs/openapi.json test",
|
||||||
"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",
|
"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",
|
"openapi:export": "ts-node scripts/openapi_export.ts",
|
||||||
"licenses:export": "license-exporter --md",
|
"licenses:export": "license-exporter --md",
|
||||||
@ -104,4 +100,4 @@
|
|||||||
"docs/*"
|
"docs/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
@ -10,13 +10,7 @@ export const config = {
|
|||||||
phone_validation_countrycode: getPhoneCodeLocale(),
|
phone_validation_countrycode: getPhoneCodeLocale(),
|
||||||
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",
|
|
||||||
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
|
let errors = 0
|
||||||
if (typeof config.internal_port !== "number") {
|
if (typeof config.internal_port !== "number") {
|
||||||
|
@ -2,23 +2,17 @@ import { Body, CookieParam, JsonController, Param, Post, Req, Res } from 'routin
|
|||||||
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 { 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 { Logout } from '../models/responses/ResponseLogout';
|
import { Logout } from '../models/responses/ResponseLogout';
|
||||||
|
|
||||||
@JsonController('/auth')
|
@JsonController('/auth')
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
|
|
||||||
private mailer: Mailer;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.mailer = new Mailer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post("/login")
|
@Post("/login")
|
||||||
@ -88,14 +82,13 @@ export class AuthController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post("/reset")
|
@Post("/reset")
|
||||||
@ResponseSchema(ResponseEmpty, { statusCode: 200 })
|
@ResponseSchema(ResponseAuth)
|
||||||
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
|
@ResponseSchema(UserNotFoundError)
|
||||||
@ResponseSchema(UsernameOrEmailNeededError, { statusCode: 406 })
|
@ResponseSchema(UsernameOrEmailNeededError)
|
||||||
@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();
|
//This really shouldn't just get returned, but sent via mail or sth like that. But for dev only this is fine.
|
||||||
await this.mailer.sendResetMail(passwordReset.email, reset_token);
|
return { "resetToken": await passwordReset.toResetToken() };
|
||||||
return new ResponseEmpty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post("/reset/:token")
|
@Post("/reset/:token")
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
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!"
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
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.
|
|
||||||
*/
|
|
||||||
export class Mailer {
|
|
||||||
private transport: Mail;
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async sendMail(mail: MailOptions) {
|
|
||||||
mail.from = config.mail_from;
|
|
||||||
await this.transport.sendMail(mail);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +1,39 @@
|
|||||||
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
|
import { IsEmail, IsOptional, IsString } from 'class-validator';
|
||||||
import { getConnectionManager } from 'typeorm';
|
import { getConnectionManager } from 'typeorm';
|
||||||
import { ResetAlreadyRequestedError, UserDisabledError, UserNotFoundError } from '../../../errors/AuthError';
|
import { ResetAlreadyRequestedError, UserDisabledError, UserNotFoundError } from '../../../errors/AuthError';
|
||||||
import { UserEmailNeededError } from '../../../errors/UserErrors';
|
import { UsernameOrEmailNeededError } from '../../../errors/UserErrors';
|
||||||
import { JwtCreator } from '../../../jwtcreator';
|
import { JwtCreator } from '../../../jwtcreator';
|
||||||
import { User } from '../../entities/User';
|
import { User } from '../../entities/User';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is used to create password reset tokens for users.
|
* This calss 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.
|
* These password reset token can be used to set a new password for the user for the next 15mins.
|
||||||
*/
|
*/
|
||||||
export class CreateResetToken {
|
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.
|
* The email address of the user that wants to reset their password.
|
||||||
*/
|
*/
|
||||||
@IsNotEmpty()
|
@IsOptional()
|
||||||
@IsEmail()
|
@IsEmail()
|
||||||
@IsString()
|
@IsString()
|
||||||
email: string;
|
email?: string;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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<any> {
|
||||||
if (!this.email) {
|
if (this.email === undefined && this.username === undefined) {
|
||||||
throw new UserEmailNeededError();
|
throw new UsernameOrEmailNeededError();
|
||||||
}
|
}
|
||||||
let found_user = await getConnectionManager().get().getRepository(User).findOne({ where: [{ email: this.email }] });
|
let found_user = await getConnectionManager().get().getRepository(User).findOne({ where: [{ username: this.username }, { email: this.email }] });
|
||||||
if (!found_user) { throw new UserNotFoundError(); }
|
if (!found_user) { throw new UserNotFoundError(); }
|
||||||
if (found_user.enabled == false) { throw new UserDisabledError(); }
|
if (found_user.enabled == false) { throw new UserDisabledError(); }
|
||||||
if (found_user.resetRequestedTimestamp > (Math.floor(Date.now() / 1000) - 15 * 60)) { throw new ResetAlreadyRequestedError(); }
|
if (found_user.resetRequestedTimestamp > (Math.floor(Date.now() / 1000) - 15 * 60)) { throw new ResetAlreadyRequestedError(); }
|
||||||
|
@ -1,384 +0,0 @@
|
|||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml">
|
|
||||||
<head>
|
|
||||||
<title>LfK! - Passwort zurücksetzen</title> <!-- The title tag shows in email notifications, like Android 4.4. -->
|
|
||||||
<meta charset="utf-8"> <!-- utf-8 works for most cases -->
|
|
||||||
<meta http-equiv="Content-Type" content="text/html charset=UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width"> <!-- Forcing initial-scale shouldn't be necessary -->
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <!-- Use the latest (edge) version of IE rendering engine -->
|
|
||||||
<meta name="x-apple-disable-message-reformatting"> <!-- Disable auto-scale in iOS 10 Mail entirely -->
|
|
||||||
<meta name="format-detection" content="telephone=no,address=no,email=no,date=no,url=no"> <!-- Tell iOS not to automatically link certain text strings. -->
|
|
||||||
|
|
||||||
<!-- CSS Reset : BEGIN -->
|
|
||||||
<style>
|
|
||||||
/* What it does: Remove spaces around the email design added by some email clients. */
|
|
||||||
/* Beware: It can remove the padding / margin and add a background color to the compose a reply window. */
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
margin: 0 auto !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
height: 100% !important;
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* What it does: Stops email clients resizing small text. */
|
|
||||||
* {
|
|
||||||
-ms-text-size-adjust: 100%;
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* What it does: Centers email on Android 4.4 */
|
|
||||||
div[style*="margin: 16px 0"] {
|
|
||||||
margin:0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* What it does: Stops Outlook from adding extra spacing to tables. */
|
|
||||||
table,
|
|
||||||
td {
|
|
||||||
mso-table-lspace: 0pt !important;
|
|
||||||
mso-table-rspace: 0pt !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* What it does: Fixes webkit padding issue. */
|
|
||||||
table {
|
|
||||||
border: 0;
|
|
||||||
border-spacing: 0;
|
|
||||||
border-collapse: collapse
|
|
||||||
}
|
|
||||||
|
|
||||||
/* What it does: Forces Samsung Android mail clients to use the entire viewport. */
|
|
||||||
#MessageViewBody,
|
|
||||||
#MessageWebViewDiv{
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* What it does: Uses a better rendering method when resizing images in IE. */
|
|
||||||
img {
|
|
||||||
-ms-interpolation-mode:bicubic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* What it does: Prevents Windows 10 Mail from underlining links despite inline CSS. Styles for underlined links should be inline. */
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* What it does: A work-around for email clients automatically linking certain text strings. */
|
|
||||||
/* iOS */
|
|
||||||
a[x-apple-data-detectors],
|
|
||||||
.unstyle-auto-detected-links a,
|
|
||||||
.aBn {
|
|
||||||
border-bottom: 0 !important;
|
|
||||||
cursor: default !important;
|
|
||||||
color: inherit !important;
|
|
||||||
text-decoration: none !important;
|
|
||||||
font-size: inherit !important;
|
|
||||||
font-family: inherit !important;
|
|
||||||
font-weight: inherit !important;
|
|
||||||
line-height: inherit !important;
|
|
||||||
}
|
|
||||||
u + #body a, /* Gmail */
|
|
||||||
#MessageViewBody a /* Samsung Mail */
|
|
||||||
{
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: inherit;
|
|
||||||
font-family: inherit;
|
|
||||||
font-weight: inherit;
|
|
||||||
line-height: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* What it does: Prevents Gmail from changing the text color in conversation threads. */
|
|
||||||
.im {
|
|
||||||
color: inherit !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* What it does: Prevents Gmail from displaying an download button on large, non-linked images. */
|
|
||||||
.a6S {
|
|
||||||
display: none !important;
|
|
||||||
opacity: 0.01 !important;
|
|
||||||
}
|
|
||||||
/* If the above doesn't work, add a .g-img class to any image in question. */
|
|
||||||
img.g-img + div {
|
|
||||||
display:none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* What it does: Removes right gutter in Gmail iOS app: https://github.com/TedGoas/Cerberus/issues/89 */
|
|
||||||
/* Create one of these media queries for each additional viewport size you'd like to fix */
|
|
||||||
|
|
||||||
/* iPhone 4, 4S, 5, 5S, 5C, and 5SE */
|
|
||||||
@media only screen and (min-device-width: 320px) and (max-device-width: 374px) {
|
|
||||||
u ~ div .email-container {
|
|
||||||
min-width: 320px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* iPhone 6, 6S, 7, 8, and X */
|
|
||||||
@media only screen and (min-device-width: 375px) and (max-device-width: 413px) {
|
|
||||||
u ~ div .email-container {
|
|
||||||
min-width: 375px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* iPhone 6+, 7+, and 8+ */
|
|
||||||
@media only screen and (min-device-width: 414px) {
|
|
||||||
u ~ div .email-container {
|
|
||||||
min-width: 414px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<!-- What it does: Helps DPI scaling in Outlook 2007-2013 -->
|
|
||||||
<!--[if gte mso 9]>
|
|
||||||
<xml>
|
|
||||||
<o:OfficeDocumentSettings>
|
|
||||||
<o:AllowPNG/>
|
|
||||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
|
||||||
</o:OfficeDocumentSettings>
|
|
||||||
</xml>
|
|
||||||
<![endif]-->
|
|
||||||
|
|
||||||
<!-- CSS Reset : END -->
|
|
||||||
|
|
||||||
<!-- Progressive Enhancements : BEGIN -->
|
|
||||||
<style>
|
|
||||||
/* What it does: Hover styles for buttons and tags */
|
|
||||||
.s-btn__primary:hover {
|
|
||||||
background: #0077CC !important;
|
|
||||||
border-color: #0077CC !important;
|
|
||||||
}
|
|
||||||
.s-btn__white:hover {
|
|
||||||
background: #EFF0F1 !important;
|
|
||||||
border-color: #EFF0F1 !important;
|
|
||||||
}
|
|
||||||
.s-btn__outlined:hover {
|
|
||||||
background: rgba(0,119,204,.05) !important;
|
|
||||||
color: #005999 !important;
|
|
||||||
}
|
|
||||||
.s-tag:hover,
|
|
||||||
.post-tag:hover {
|
|
||||||
border-color: #cee0ed !important;
|
|
||||||
background: #cee0ed !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* What it does: Styles markdown links that we can't write inline CSS for. */
|
|
||||||
.has-markdown a,
|
|
||||||
.has-markdown a:visited {
|
|
||||||
color: #0077CC !important;
|
|
||||||
text-decoration: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* What it does: Styles markdown code blocks that we can't write inline CSS for. */
|
|
||||||
code {
|
|
||||||
padding: 1px 5px;
|
|
||||||
background-color: #EFF0F1;
|
|
||||||
color: #242729;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: inherit;
|
|
||||||
font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, sans-serif;
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
margin: 0 0 15px;
|
|
||||||
line-height: 17px;
|
|
||||||
background-color: #EFF0F1;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 3px;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
pre code {
|
|
||||||
margin: 0 0 15px;
|
|
||||||
padding: 0;
|
|
||||||
line-height: 17px;
|
|
||||||
background-color: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* What it does: Styles markdown blockquotes that we can't write inline CSS for. */
|
|
||||||
blockquote {
|
|
||||||
margin: 0 0 15px;
|
|
||||||
padding: 4px 10px;
|
|
||||||
background-color: #FFF8DC;
|
|
||||||
border-left: 2px solid #ffeb8e;
|
|
||||||
}
|
|
||||||
blockquote p {
|
|
||||||
padding: 4px 0;
|
|
||||||
margin: 0;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* What it does: Rounds corners in email clients that support it */
|
|
||||||
.bar {
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
.btr {
|
|
||||||
border-top-left-radius: 5px;
|
|
||||||
border-top-right-radius: 5px;
|
|
||||||
}
|
|
||||||
.bbr {
|
|
||||||
border-bottom-left-radius: 5px;
|
|
||||||
border-bottom-right-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 680px) {
|
|
||||||
/* What it does: Forces table cells into full-width rows. */
|
|
||||||
.stack-column,
|
|
||||||
.stack-column-center {
|
|
||||||
display: block !important;
|
|
||||||
width: 100% !important;
|
|
||||||
max-width: 100% !important;
|
|
||||||
direction: ltr !important;
|
|
||||||
}
|
|
||||||
/* And center justify these ones. */
|
|
||||||
.stack-column-center {
|
|
||||||
text-align: center !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hides things in small viewports. */
|
|
||||||
.hide-on-mobile {
|
|
||||||
display: none !important;
|
|
||||||
max-height: 0 !important;
|
|
||||||
overflow: hidden !important;
|
|
||||||
visibility: hidden !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* What it does: Utility classes to reduce spacing for smaller viewports. */
|
|
||||||
.sm-p-none {padding: 0 !important;}
|
|
||||||
.sm-pt-none {padding-top: 0 !important;}
|
|
||||||
.sm-pb-none {padding-bottom: 0 !important;}
|
|
||||||
.sm-pr-none {padding-right: 0 !important;}
|
|
||||||
.sm-pl-none {padding-left: 0 !important;}
|
|
||||||
.sm-px-none {padding-left: 0 !important; padding-right: 0 !important;}
|
|
||||||
.sm-py-none {padding-top: 0 !important; padding-bottom: 0 !important;}
|
|
||||||
|
|
||||||
.sm-p {padding: 20px !important;}
|
|
||||||
.sm-pt {padding-top: 20px !important;}
|
|
||||||
.sm-pb {padding-bottom: 20px !important;}
|
|
||||||
.sm-pr {padding-right: 20px !important;}
|
|
||||||
.sm-pl {padding-left: 20px !important;}
|
|
||||||
.sm-px {padding-left: 20px !important; padding-right: 20px !important;}
|
|
||||||
.sm-py {padding-top: 20px !important; padding-bottom: 20px !important;}
|
|
||||||
.sm-mb {margin-bottom: 20px !important;}
|
|
||||||
|
|
||||||
/* What it does: Utility classes to kill border radius for smaller viewports. Used mainly on the email's main container(s). */
|
|
||||||
.bar,
|
|
||||||
.btr,
|
|
||||||
.bbr {
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<!-- Progressive Enhancements : END -->
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
The email background color is defined in three places, just below. If you change one, remember to change the others.
|
|
||||||
1. body tag: for most email clients
|
|
||||||
2. center tag: for Gmail and Inbox mobile apps and web versions of Gmail, GSuite, Inbox, Yahoo, AOL, Libero, Comcast, freenet, Mail.ru, Orange.fr
|
|
||||||
3. mso conditional: For Windows 10 Mail
|
|
||||||
-->
|
|
||||||
<body width="100%" style="margin: 0; padding: 0 !important; background: #f3f3f5; mso-line-height-rule: exactly;">
|
|
||||||
<center style="width: 100%; background: #f3f3f5;">
|
|
||||||
<!--[if mso | IE]>
|
|
||||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: #f3f3f5;">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<![endif]-->
|
|
||||||
|
|
||||||
<!-- Visually Hidden Preview Text : BEGIN -->
|
|
||||||
<div style="display: none; font-size: 1px; line-height: 1px; max-height: 0px; max-width: 0px; opacity: 0; overflow: hidden; mso-hide: all; font-family: sans-serif;">
|
|
||||||
LfK! - Password reset
|
|
||||||
</div>
|
|
||||||
<!-- Visually Hidden Preview Text : END -->
|
|
||||||
|
|
||||||
<div class="email-container" style="max-width: 680px; margin: 0 auto;">
|
|
||||||
<!--[if mso]>
|
|
||||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="680" align="center">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<![endif]-->
|
|
||||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="max-width: 680px; width:100%">
|
|
||||||
<tr>
|
|
||||||
<td style="padding: 30px; background-color: #ffffff;" class="sm-p bar">
|
|
||||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
|
||||||
<tr>
|
|
||||||
<td style="padding-bottom: 15px; font-family: arial, sans-serif; font-size: 15px; line-height: 21px; color: #3C3F44; text-align: left;">
|
|
||||||
<h1 style="font-weight: bold; font-size: 27px; line-height: 27px; color: #0C0D0E; margin: 0 0 15px 0;">LfK!</h1>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td style="padding-bottom: 15px; font-family: arial, sans-serif; font-size: 15px; line-height: 21px; color: #3C3F44; text-align: left;">
|
|
||||||
<h1 style="font-weight: bold; font-size: 21px; line-height: 21px; color: #0C0D0E; margin: 0 0 15px 0;">Password reset</h1>
|
|
||||||
<p style="margin: 0 0 15px;" class="has-markdown">A password reset for your account got requested.<br><b>If you didn't request the reset please ignore this mail.</b><br>Your password won't be changed until you click the reset link below and set a new one.</p>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<!-- Button Row : BEGIN -->
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<!-- Button : BEGIN -->
|
|
||||||
<table align="left" border="0" cellpadding="0" cellspacing="0" role="presentation">
|
|
||||||
<tr>
|
|
||||||
<td class="s-btn s-btn__primary" style="border-radius: 4px; background: #0095ff;">
|
|
||||||
<a class="s-btn s-btn__primary" href="{{reset_link}}" target="_parent" style="background: #0095FF; border: 1px solid #0077cc; box-shadow: inset 0 1px 0 0 rgba(102,191,255,.75); font-family: arial, sans-serif; font-size: 17px; line-height: 17px; color: #ffffff; text-align: center; text-decoration: none; padding: 13px 17px; display: block; border-radius: 4px; white-space: nowrap;">Reset password</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<!-- Button : END -->
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<!-- Button Row : END -->
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-----------------------------
|
|
||||||
|
|
||||||
EMAIL BODY : END
|
|
||||||
|
|
||||||
------------------------------>
|
|
||||||
|
|
||||||
<!-- Footer : BEGIN -->
|
|
||||||
<tr>
|
|
||||||
<td style="padding: 30px;" class="sm-p">
|
|
||||||
<table align="left" border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
|
|
||||||
<!-- Subscription Info : BEGIN -->
|
|
||||||
<tr>
|
|
||||||
<td style="padding-bottom: 10px; font-size: 12px; line-height: 15px; font-family: arial, sans-serif; color: #9199A1; text-align: left;">
|
|
||||||
Copyright © {{copyright_owner}}. All rights reserved.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td style="font-size: 12px; line-height: 15px; font-family: arial, sans-serif; color: #9199A1; text-align: left;">
|
|
||||||
<a href="{{link_imprint}}"
|
|
||||||
style="color: #9199A1; text-decoration: underline;">Imprint</a>
|
|
||||||
<a href="{{link_privacy}}" style="color: #9199A1; text-decoration: underline;">Privacy</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<!-- Subscription Info : BEGIN -->
|
|
||||||
<!-- HR line : BEGIN -->
|
|
||||||
<tr>
|
|
||||||
<td style="padding: 30px 0;" width="100%" class="sm-py">
|
|
||||||
<table aria-hidden="true" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%">
|
|
||||||
<tr>
|
|
||||||
<td height="1" width="100%" style="font-size: 0; line-height: 0; border-top: 1px solid #D6D8DB;"> </td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<!-- HR line : END -->
|
|
||||||
<tr>
|
|
||||||
<td style="padding-bottom: 5px; font-size: 12px; line-height: 15px; font-family: arial, sans-serif; color: #9199A1; text-align: left;">This mail was sent to <strong>{{recipient_mail}}</strong> because someone request a password reset for a account linked to the mail address.</td>
|
|
||||||
</tr>
|
|
||||||
<!-- Sender Info : END -->
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<!-- Footer : END -->
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!--[if mso | IE]>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<![endif]-->
|
|
||||||
</center>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,12 +0,0 @@
|
|||||||
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.
|
|
@ -15,7 +15,7 @@ beforeAll(async () => {
|
|||||||
"lastname": "demo_reset",
|
"lastname": "demo_reset",
|
||||||
"username": "demo_reset",
|
"username": "demo_reset",
|
||||||
"password": "demo_reset",
|
"password": "demo_reset",
|
||||||
"email": "demo_reset1@dev.lauf-fuer-kaya.de"
|
"email": "demo_reset@dev.lauf-fuer-kaya.de"
|
||||||
}, {
|
}, {
|
||||||
headers: { "authorization": "Bearer " + res_login.data["access_token"] },
|
headers: { "authorization": "Bearer " + res_login.data["access_token"] },
|
||||||
validateStatus: undefined
|
validateStatus: undefined
|
||||||
@ -26,7 +26,7 @@ beforeAll(async () => {
|
|||||||
"lastname": "demo_reset2",
|
"lastname": "demo_reset2",
|
||||||
"username": "demo_reset2",
|
"username": "demo_reset2",
|
||||||
"password": "demo_reset2",
|
"password": "demo_reset2",
|
||||||
"email": "demo_reset2@dev.lauf-fuer-kaya.de"
|
"email": "demo_reset1@dev.lauf-fuer-kaya.de"
|
||||||
}, {
|
}, {
|
||||||
headers: { "authorization": "Bearer " + res_login.data["access_token"] },
|
headers: { "authorization": "Bearer " + res_login.data["access_token"] },
|
||||||
validateStatus: undefined
|
validateStatus: undefined
|
||||||
@ -36,16 +36,24 @@ beforeAll(async () => {
|
|||||||
describe('POST /api/auth/reset valid', () => {
|
describe('POST /api/auth/reset valid', () => {
|
||||||
let reset_token;
|
let reset_token;
|
||||||
it('valid reset token request should return 200', async () => {
|
it('valid reset token request should return 200', async () => {
|
||||||
const res1 = await axios.post(base + '/api/auth/reset', { email: "demo_reset1@dev.lauf-fuer-kaya.de" });
|
const res1 = await axios.post(base + '/api/auth/reset', { username: "demo_reset" });
|
||||||
reset_token = res1.data.resetToken;
|
reset_token = res1.data.resetToken;
|
||||||
expect(res1.status).toEqual(200);
|
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', () => {
|
describe('POST /api/auth/reset invalid requests', () => {
|
||||||
it('request another password reset before the timeout should return 406', async () => {
|
it('request another password reset before the timeout should return 406', async () => {
|
||||||
const res1 = await axios.post(base + '/api/auth/reset', { email: "demo_reset2@dev.lauf-fuer-kaya.de" }, axios_config);
|
const res1 = await axios.post(base + '/api/auth/reset', { username: "demo_reset2" }, axios_config);
|
||||||
const res2 = 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', { username: "demo_reset2" }, axios_config);
|
||||||
expect(res2.status).toEqual(406);
|
expect(res2.status).toEqual(406);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -55,9 +63,9 @@ describe('POST /api/auth/reset invalid token', () => {
|
|||||||
const res2 = await axios.post(base + '/api/auth/reset/' + "123123", { password: "demo" }, axios_config);
|
const res2 = await axios.post(base + '/api/auth/reset/' + "123123", { password: "demo" }, axios_config);
|
||||||
expect(res2.status).toEqual(401);
|
expect(res2.status).toEqual(401);
|
||||||
});
|
});
|
||||||
it('providing no reset token should return 400', async () => {
|
it('providing no reset token should return 404', async () => {
|
||||||
const res2 = await axios.post(base + '/api/auth/reset/' + "", { password: "demo" }, axios_config);
|
const res2 = await axios.post(base + '/api/auth/reset/' + "", { password: "demo" }, axios_config);
|
||||||
expect(res2.status).toEqual(400);
|
expect(res2.status).toEqual(404);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// ---------------
|
// ---------------
|
||||||
|
@ -111,6 +111,7 @@ describe('Update contact group after adding (should work)', () => {
|
|||||||
"lastname": "last",
|
"lastname": "last",
|
||||||
"groups": added_team.id
|
"groups": added_team.id
|
||||||
}, axios_config);
|
}, axios_config);
|
||||||
|
console.log(res.data)
|
||||||
expect(res.status).toEqual(200);
|
expect(res.status).toEqual(200);
|
||||||
expect(res.headers['content-type']).toContain("application/json");
|
expect(res.headers['content-type']).toContain("application/json");
|
||||||
expect(res.data).toEqual({
|
expect(res.data).toEqual({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user