Merge pull request 'Features for the new selfservice feature/151-selfservice_scans_mails' (#152) from feature/151-selfservice_scans_mails into dev
continuous-integration/drone/push Build was killed
Details
continuous-integration/drone/push Build was killed
Details
Reviewed-on: #152
This commit is contained in:
commit
15356c1030
|
@ -49,7 +49,7 @@ 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).
|
||||
> You can use the `test:ci:generate_env` package script to generate a example env (uses bs data as test server and ignores the errors).
|
||||
|
||||
| Name | Type | Default | Description
|
||||
| - | - | - | -
|
||||
|
@ -64,11 +64,8 @@ yarn docs
|
|||
| 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
|
||||
| MAILER_URL | String(Url) | N/A | The mailer's base url (no trailing slash)
|
||||
| MAILER_KEY | String | N/A | The mailer's api key.
|
||||
| IMPRINT_URL | String(Url) | /imprint | The link to a imprint page for the system (Defaults to the frontend's imprint)
|
||||
| PRIVACY_URL | String(Url) | /privacy | The link to a privacy page for the system (Defaults to the frontend's privacy page)
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
"dependencies": {
|
||||
"@odit/class-validator-jsonschema": "2.1.1",
|
||||
"argon2": "^0.27.1",
|
||||
"axios": "^0.21.1",
|
||||
"body-parser": "^1.19.0",
|
||||
"class-transformer": "0.3.1",
|
||||
"class-validator": "^0.13.1",
|
||||
|
@ -37,7 +38,6 @@
|
|||
"jsonwebtoken": "^8.5.1",
|
||||
"libphonenumber-js": "^1.9.9",
|
||||
"mysql": "^2.18.1",
|
||||
"nodemailer": "^6.4.17",
|
||||
"pg": "^8.5.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"routing-controllers": "0.9.0-alpha.6",
|
||||
|
@ -57,9 +57,7 @@
|
|||
"@types/jest": "^26.0.20",
|
||||
"@types/jsonwebtoken": "^8.5.0",
|
||||
"@types/node": "^14.14.22",
|
||||
"@types/nodemailer": "^6.4.0",
|
||||
"@types/uuid": "^8.3.0",
|
||||
"axios": "^0.21.1",
|
||||
"cp-cli": "^2.0.0",
|
||||
"jest": "^26.6.3",
|
||||
"nodemon": "^2.0.7",
|
||||
|
|
|
@ -1,15 +1,8 @@
|
|||
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 = `
|
||||
const env = `
|
||||
APP_PORT=4010
|
||||
DB_TYPE=sqlite
|
||||
DB_HOST=bla
|
||||
|
@ -20,18 +13,12 @@ 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");
|
||||
}
|
||||
|
||||
});
|
||||
MAILER_URL=https://dev.lauf-fuer-kaya.de/mailer
|
||||
MAILER_KEY=asdasd`;
|
||||
|
||||
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");
|
||||
}
|
|
@ -12,13 +12,10 @@ export const config = {
|
|||
version: process.env.VERSION || require('../package.json').version,
|
||||
seedTestData: getDataSeeding(),
|
||||
app_url: process.env.APP_URL || "http://localhost:8080",
|
||||
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,
|
||||
privacy_url: process.env.PRIVACY_URL || "/privacy",
|
||||
imprint_url: process.env.IMPRINT_URL || "/imprint"
|
||||
imprint_url: process.env.IMPRINT_URL || "/imprint",
|
||||
mailer_url: process.env.MAILER_URL || "",
|
||||
mailer_key: process.env.MAILER_KEY || ""
|
||||
}
|
||||
let errors = 0
|
||||
if (typeof config.internal_port !== "number") {
|
||||
|
@ -27,6 +24,9 @@ if (typeof config.internal_port !== "number") {
|
|||
if (typeof config.development !== "boolean") {
|
||||
errors++
|
||||
}
|
||||
if (config.mailer_url == "" || config.mailer_key == "") {
|
||||
errors++;
|
||||
}
|
||||
function getPhoneCodeLocale(): CountryCode {
|
||||
return (process.env.PHONE_COUNTRYCODE as CountryCode);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Body, CookieParam, JsonController, Param, Post, Req, Res } from 'routing-controllers';
|
||||
import { Body, CookieParam, JsonController, Param, Post, QueryParam, Req, Res } from 'routing-controllers';
|
||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
||||
import { IllegalJWTError, InvalidCredentialsError, JwtNotProvidedError, PasswordNeededError, RefreshTokenCountInvalidError, UsernameOrEmailNeededError } from '../errors/AuthError';
|
||||
import { MailSendingError } from '../errors/MailErrors';
|
||||
import { UserNotFoundError } from '../errors/UserErrors';
|
||||
import { Mailer } from '../mailer';
|
||||
import { CreateAuth } from '../models/actions/create/CreateAuth';
|
||||
|
@ -15,12 +16,6 @@ import { Logout } from '../models/responses/ResponseLogout';
|
|||
@JsonController('/auth')
|
||||
export class AuthController {
|
||||
|
||||
private mailer: Mailer;
|
||||
|
||||
constructor() {
|
||||
this.mailer = new Mailer();
|
||||
}
|
||||
|
||||
@Post("/login")
|
||||
@ResponseSchema(ResponseAuth)
|
||||
@ResponseSchema(InvalidCredentialsError)
|
||||
|
@ -91,10 +86,11 @@ export class AuthController {
|
|||
@ResponseSchema(ResponseEmpty, { statusCode: 200 })
|
||||
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
|
||||
@ResponseSchema(UsernameOrEmailNeededError, { statusCode: 406 })
|
||||
@ResponseSchema(MailSendingError, { statusCode: 500 })
|
||||
@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, @QueryParam("locale") locale: string = "en") {
|
||||
const reset_token: string = await passwordReset.toResetToken();
|
||||
await this.mailer.sendResetMail(passwordReset.email, reset_token);
|
||||
await Mailer.sendResetMail(passwordReset.email, reset_token, locale);
|
||||
return new ResponseEmpty();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ import { RunnerGroup } from '../models/entities/RunnerGroup';
|
|||
import { RunnerOrganization } from '../models/entities/RunnerOrganization';
|
||||
import { ResponseSelfServiceOrganisation } from '../models/responses/ResponseSelfServiceOrganisation';
|
||||
import { ResponseSelfServiceRunner } from '../models/responses/ResponseSelfServiceRunner';
|
||||
import { ResponseSelfServiceScan } from '../models/responses/ResponseSelfServiceScan';
|
||||
|
||||
|
||||
@JsonController()
|
||||
|
@ -38,6 +39,20 @@ export class RunnerSelfServiceController {
|
|||
return (new ResponseSelfServiceRunner(await this.getRunner(token)));
|
||||
}
|
||||
|
||||
@Get('/runners/me/:jwt/scans')
|
||||
@ResponseSchema(ResponseSelfServiceScan, { isArray: true })
|
||||
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
|
||||
@OnUndefined(RunnerNotFoundError)
|
||||
@OpenAPI({ description: 'Lists all your (runner) scans. <br> Please provide your runner jwt(that code we gave you during registration) for auth. <br> If you lost your jwt/personalized link please contact support.' })
|
||||
async getScans(@Param('jwt') token: string) {
|
||||
const scans = (await this.getRunner(token)).scans;
|
||||
let responseScans = new Array<ResponseSelfServiceScan>()
|
||||
for (let scan of scans) {
|
||||
responseScans.push(new ResponseSelfServiceScan(scan));
|
||||
}
|
||||
return responseScans;
|
||||
}
|
||||
|
||||
@Post('/runners/register')
|
||||
@ResponseSchema(ResponseSelfServiceRunner)
|
||||
@ResponseSchema(RunnerEmailNeededError, { statusCode: 406 })
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
import { IsString } from 'class-validator'
|
||||
import { IsString } from 'class-validator';
|
||||
import { InternalServerError } from 'routing-controllers';
|
||||
|
||||
/**
|
||||
* Error to throw when a permission couldn't be found.
|
||||
*/
|
||||
export class MailServerConfigError extends Error {
|
||||
export class MailSendingError extends InternalServerError {
|
||||
@IsString()
|
||||
name = "MailServerConfigError"
|
||||
name = "MailSendingError"
|
||||
|
||||
@IsString()
|
||||
message = "The SMTP server you provided couldn't be reached!"
|
||||
message = "We had a problem sending the mail!"
|
||||
|
||||
constructor() {
|
||||
super("We had a problem sending the mail!");
|
||||
}
|
||||
}
|
|
@ -1,79 +1,28 @@
|
|||
import fs from "fs";
|
||||
import nodemailer from 'nodemailer';
|
||||
import { MailOptions } from 'nodemailer/lib/json-transport';
|
||||
import Mail from 'nodemailer/lib/mailer';
|
||||
import axios from 'axios';
|
||||
import { config } from './config';
|
||||
import { MailServerConfigError } from './errors/MailErrors';
|
||||
import { MailSendingError } from './errors/MailErrors';
|
||||
|
||||
/**
|
||||
* This class is responsible for all things mail sending.
|
||||
* This uses the mail emplates from src/static/mail_templates
|
||||
* This uses axios to communicate with the mailer api (https://git.odit.services/lfk/mailer).
|
||||
*/
|
||||
export class Mailer {
|
||||
private transport: Mail;
|
||||
public static base: string = config.mailer_url;
|
||||
public static key: string = config.mailer_key;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Function for sending a password reset mail.
|
||||
* @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/${(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_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);
|
||||
public static async sendResetMail(to_address: string, token: string, locale: string = "en") {
|
||||
try {
|
||||
await axios.post(`${Mailer.base}/reset?locale=${locale}&key=${Mailer.key}`, {
|
||||
address: to_address,
|
||||
resetKey: token
|
||||
});
|
||||
} catch (error) {
|
||||
throw new MailSendingError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ export class CreateTrackScan {
|
|||
}
|
||||
|
||||
newScan.timestamp = Math.round(new Date().getTime() / 1000);
|
||||
newScan.lapTime = await this.getLaptime(newScan)
|
||||
newScan.valid = await this.validateScan(newScan);
|
||||
|
||||
return newScan;
|
||||
|
@ -65,15 +66,15 @@ export class CreateTrackScan {
|
|||
return station;
|
||||
}
|
||||
|
||||
public async validateScan(scan: TrackScan): Promise<boolean> {
|
||||
public validateScan(scan: TrackScan): boolean {
|
||||
return (scan.lapTime > scan.track.minimumLapTime);
|
||||
}
|
||||
|
||||
public async getLaptime(scan: TrackScan): Promise<number> {
|
||||
const scans = await getConnection().getRepository(TrackScan).find({ where: { runner: scan.runner, valid: true }, relations: ["track"] });
|
||||
if (scans.length == 0) { return true; }
|
||||
if (scans.length == 0) { return 0; }
|
||||
|
||||
const newestScan = scans[scans.length - 1];
|
||||
if ((scan.timestamp - newestScan.timestamp) > scan.track.minimumLapTime) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return (scan.timestamp - newestScan.timestamp);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,8 @@ import {
|
|||
IsInt,
|
||||
IsNotEmpty,
|
||||
|
||||
IsNumber,
|
||||
|
||||
IsPositive
|
||||
} from "class-validator";
|
||||
import { ChildEntity, Column, ManyToOne } from "typeorm";
|
||||
|
@ -59,6 +61,14 @@ export class TrackScan extends Scan {
|
|||
@IsInt()
|
||||
timestamp: number;
|
||||
|
||||
/**
|
||||
* The scan's lap time.
|
||||
* This simply get's calculated from the last lap time;
|
||||
*/
|
||||
@Column()
|
||||
@IsNumber()
|
||||
lapTime: number;
|
||||
|
||||
/**
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
|
|
|
@ -21,6 +21,8 @@ export enum ResponseObjectType {
|
|||
SCANSTATION = 'SCANSTATION',
|
||||
SELFSERVICEDONATION = 'SELFSERVICEDONATION',
|
||||
SELFSERVICERUNNER = 'SELFSERVICRUNNER',
|
||||
SELFSERVICESCAN = 'SELFSERVICESCAN',
|
||||
SELFSERVICETRACKSCAN = 'SELFSERVICETRACKSCAN',
|
||||
SELFSERVICETEAM = 'SELFSERVICETEAM',
|
||||
SELFSERVICEORGANIZATION = 'SELFSERVICEORGANIZATION',
|
||||
STATS = 'STATS',
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
import { IsBoolean, IsInt, IsNotEmpty, IsPositive } from "class-validator";
|
||||
import { Scan } from '../entities/Scan';
|
||||
import { TrackScan } from '../entities/TrackScan';
|
||||
import { ResponseObjectType } from '../enums/ResponseObjectType';
|
||||
import { IResponse } from './IResponse';
|
||||
|
||||
/**
|
||||
* Defines the scan selfservice response.
|
||||
*/
|
||||
export class ResponseSelfServiceScan implements IResponse {
|
||||
/**
|
||||
* The responseType.
|
||||
* This contains the type of class/entity this response contains.
|
||||
*/
|
||||
responseType: ResponseObjectType = ResponseObjectType.SELFSERVICESCAN;
|
||||
|
||||
/**
|
||||
* The scans's id (for sorting).
|
||||
*/
|
||||
@IsInt()
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* Is the scan valid (for fraud reasons).
|
||||
* The determination of validity will work differently for every child class.
|
||||
*/
|
||||
@IsBoolean()
|
||||
valid: boolean = true;
|
||||
|
||||
/**
|
||||
* The scans's length/distance in meters.
|
||||
*/
|
||||
@IsInt()
|
||||
@IsPositive()
|
||||
distance: number;
|
||||
|
||||
/**
|
||||
* The scans's lap time (0 if non is availdable).
|
||||
*/
|
||||
@IsInt()
|
||||
@IsNotEmpty()
|
||||
lapTime: number = 0;
|
||||
|
||||
/**
|
||||
* Creates a ResponseScan object from a scan.
|
||||
* @param scan The scan the response shall be build for.
|
||||
*/
|
||||
public constructor(scan: Scan | TrackScan) {
|
||||
this.id = scan.id;
|
||||
this.distance = scan.distance;
|
||||
this.valid = scan.valid;
|
||||
if (scan instanceof TrackScan) {
|
||||
this.lapTime = scan.lapTime;
|
||||
this.responseType = ResponseObjectType.SELFSERVICETRACKSCAN;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { IsDateString, IsNotEmpty } from "class-validator";
|
||||
import { IsDateString, IsNotEmpty, IsNumber } from "class-validator";
|
||||
import { TrackScan } from '../entities/TrackScan';
|
||||
import { ResponseObjectType } from '../enums/ResponseObjectType';
|
||||
import { IResponse } from './IResponse';
|
||||
|
@ -42,6 +42,13 @@ export class ResponseTrackScan extends ResponseScan implements IResponse {
|
|||
@IsNotEmpty()
|
||||
timestamp: number;
|
||||
|
||||
/**
|
||||
* The scan's lap time.
|
||||
* This simply get's calculated from the last lap time;
|
||||
*/
|
||||
@IsNumber()
|
||||
lapTime: number;
|
||||
|
||||
/**
|
||||
* Creates a ResponseTrackScan object from a scan.
|
||||
* @param scan The trackSscan the response shall be build for.
|
||||
|
@ -53,5 +60,6 @@ export class ResponseTrackScan extends ResponseScan implements IResponse {
|
|||
if (scan.station) { scan.station.toResponse(); }
|
||||
this.timestamp = scan.timestamp;
|
||||
this.distance = scan.distance;
|
||||
this.lapTime = scan.lapTime;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
|
@ -1,369 +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! - Mail test</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! - Mail test
|
||||
</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;">Test mail</h1>
|
||||
<p style="margin: 0 0 15px;" class="has-markdown">This is a test mail triggered by an admin in the LfK! backend.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</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 mail test for this 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,8 +0,0 @@
|
|||
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.
|
|
@ -35,10 +35,10 @@ 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', { email: "demo_reset1@dev.lauf-fuer-kaya.de" });
|
||||
it('valid reset token request should return 200 (500 w/o correct auth)', async () => {
|
||||
const res1 = await axios.post(base + '/api/auth/reset', { email: "demo_reset1@dev.lauf-fuer-kaya.de" }, axios_config);
|
||||
reset_token = res1.data.resetToken;
|
||||
expect(res1.status).toEqual(200);
|
||||
expect(res1.status).toEqual(500);
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
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);
|
||||
});
|
||||
});
|
|
@ -40,4 +40,9 @@ describe('register + get should return 200', () => {
|
|||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
});
|
||||
it('get scans with valid jwt should return 200', async () => {
|
||||
const res = await axios.get(base + '/api/runners/me/' + added_runner.token + "/scans", axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue