fix email

This commit is contained in:
Simon Larsen 2022-11-10 16:56:20 +00:00
parent 77c22d9e04
commit 396e8f8b40
No known key found for this signature in database
GPG Key ID: AB45983AA9C81CDE
13 changed files with 137 additions and 115 deletions

View File

@ -35,6 +35,10 @@ function App(): ReactElement {
path="/accounts/forgot-password"
element={<ForgotPasswordPage />}
/>
<Route
path="/accounts/reset-password/:token"
element={<ForgotPasswordPage />}
/>
<Route path="/accounts/register" element={<RegisterPage />} />
<Route path="/accounts/login/sso" element={<SsoLoginPage />} />
<Route

View File

@ -0,0 +1,11 @@
import Email from '../Email';
import Dictionary from '../Dictionary';
import EmailTemplateType from './EmailTemplateType';
export default interface EmailMessage {
toEmail: Email;
subject: string;
templateType?: EmailTemplateType;
vars: Dictionary<string>;
body?: string;
}

View File

@ -2,7 +2,7 @@ import Email from '../Email';
import Port from '../Port';
import Hostname from '../API/Hostname';
export default interface MailServer {
export default interface EmailServer {
host: Hostname;
port: Port;
username: string;

View File

@ -1,26 +0,0 @@
import Dictionary from '../Dictionary';
import BadOperationException from '../Exception/BadOperationException';
import EmailTemplateType from './EmailTemplateType';
class EmailSubjects {
private subjectMap: Dictionary<string> = {};
public constructor() {
this.subjectMap[EmailTemplateType.SIGNUP_WELCOME_EMAIL] =
'Welcome to OneUptime.';
this.subjectMap[EmailTemplateType.SIGNUP_VERIFICATION_EMAIL] =
'Welcome to OneUptime. Please verify your email.';
}
public getSubjectByType(emailTemplateType: EmailTemplateType): string {
if (this.subjectMap[emailTemplateType]) {
return this.subjectMap[emailTemplateType] as string;
}
throw new BadOperationException(
`Subject for ${emailTemplateType} not found.`
);
}
}
export default new EmailSubjects();

View File

@ -1,15 +1,6 @@
enum EmailTemplateType {
SIGNUP_WELCOME_EMAIL = 'SignupWelcomeEmail',
SIGNUP_VERIFICATION_EMAIL = 'SignupVerificationEmail',
SUBSCRIBER_INCIDENT_CREATED = 'Subscriber Incident Created',
SUBSCRIBER_INCIDENT_ACKNOWLEDGED = 'Subscriber Incident Acknowledged',
SUBSCRIBER_INCIDENT_RESOLVED = 'Subscriber Incident Resolved',
INVESTIGATION_NOTE_CREATED = 'Investigation Note Created',
SUBSCRIBER_SCHEDULED_MAINTENANCE_CREATED = 'Subscriber Scheduled Maintenance Created',
SUBSCRIBER_SCHEDULED_MAINTENANCE_NOTE_CREATED = 'Subscriber Scheduled Maintenance Note Created',
SUBSCRIBER_SCHEDULED_MAINTENANCE_RESOLVED = 'Subscriber Scheduled Maintenance Resolved',
SUBSCRIBER_SCHEDULED_MAINTENANCE_CANCELLED = 'Subscriber Scheduled Maintenance Cancelled',
SUBSCRIBER_ANNOUNCEMENT_NOTIFICATION_CREATED = 'Subscriber Announcement Notification Created',
ForgotPassword = "ForgotPassword.hbs",
WelomeEmail="WelcomeEmail.hbs"
}
export default EmailTemplateType;

View File

@ -1,11 +0,0 @@
import Email from '../Email';
import Dictionary from '../Dictionary';
import EmailTemplateType from '../Email/EmailTemplateType';
export default interface Mail {
toEmail: Email;
subject: string;
templateType: EmailTemplateType;
vars: Dictionary<string>;
body: string;
}

View File

@ -2,6 +2,7 @@ import Protocol from 'Common/Types/API/Protocol';
import ObjectID from 'Common/Types/ObjectID';
import Port from 'Common/Types/Port';
import Hostname from 'Common/Types/API/Hostname';
import Route from 'Common/Types/API/Route';
export const DisableSignup: boolean = process.env['DISABLE_SIGNUP'] === 'true';
@ -33,6 +34,10 @@ export const ClusterKey: ObjectID = new ObjectID(
process.env['ONEUPTIME_SECRET'] || ''
);
export const Domain: Hostname = Hostname.fromString(
process.env['DOMAIN'] || ''
);
export const RealtimeHostname: Hostname = Hostname.fromString(
process.env['REALTIME_HOSTNAME'] || ''
);
@ -79,3 +84,25 @@ export const HttpProtocol: Protocol = (
export const RedisHostname: string = process.env['REDIS_HOST'] || '';
export const RedisPassword: string = process.env['REDIS_PASSWORD'] || '';
export const RedisPort: Port = new Port(process.env['REDIS_PORT'] || '');
export const DashboardApiRoute: Route = new Route(process.env['DASHBOARD_API_ROUTE'] || '');
export const IdentityRoute: Route = new Route(process.env['IDENTITY_ROUTE'] || '');
export const FileRoute: Route = new Route(process.env['FILE_ROUTE'] || '');
export const StausPageRoute: Route = new Route(process.env['STATUS_PAGE_ROUTE'] || '');
export const DashboardRoute: Route = new Route(process.env['DASHBOARD_ROUTE'] || '');
export const IntegrationRoute: Route = new Route(process.env['INTEGRATION_ROUTE'] || '');
export const HelmRoute: Route = new Route(process.env['HELMCHART_ROUTE'] || '');
export const AccountsRoute: Route = new Route(process.env['ACCOUNTS_ROUTE'] || '');
export const ApiDocsRoute: Route = new Route(process.env['APIDOCS_ROUTE'] || '');
export const AdminDashboardRoute: Route = new Route(
process.env['ADMINDASHBOARD_ROUTE'] || ''
);

View File

@ -5,13 +5,13 @@ import URL from 'Common/Types/API/URL';
import { JSONObject } from 'Common/Types/JSON';
import API from 'Common/Utils/API';
import { ClusterKey, HttpProtocol, MailHostname } from '../Config';
import Mail from 'Common/Types/Mail/Mail';
import MailServer from 'Common/Types/Mail/MailServer';
import Email from 'Common/Types/Email/EmailMessage';
import EmailServer from 'Common/Types/Email/EmailServer';
export default class MailService {
public static async sendMail(
mail: Mail,
mailServer?: MailServer
mail: Email,
mailServer?: EmailServer
): Promise<HTTPResponse<EmptyResponseData>> {
const body: JSONObject = {
...mail,

View File

@ -1,11 +1,12 @@
import {
AccountsHostname,
DashboardHostname,
DisableSignup,
HomeHostname,
HttpProtocol,
IsSaaSService,
EncryptionSecret,
Domain,
AccountsRoute,
DashboardRoute,
} from 'CommonServer/Config';
import Express, {
ExpressRequest,
@ -23,13 +24,13 @@ import EmailVerificationToken from 'Model/Models/EmailVerificationToken';
import BadDataException from 'Common/Types/Exception/BadDataException';
import MailService from 'CommonServer/Services/MailService';
import EmailTemplateType from 'Common/Types/Email/EmailTemplateType';
import EmailSubjects from 'Common/Types/Email/EmailSubjects';
import URL from 'Common/Types/API/URL';
import Response from 'CommonServer/Utils/Response';
import JSONWebToken from 'CommonServer/Utils/JsonWebToken';
import OneUptimeDate from 'Common/Types/Date';
import PositiveNumber from 'Common/Types/PositiveNumber';
import BaseModel from 'Common/Models/BaseModel';
import Route from 'Common/Types/API/Route';
const router: ExpressRouter = Express.getRouter();
@ -85,7 +86,7 @@ router.post(
emailVerificationToken &&
user &&
alreadySavedUser?.id?.toString() ===
emailVerificationToken?.userId?.toString()
emailVerificationToken?.userId?.toString()
) {
user.isEmailVerified = true;
}
@ -122,37 +123,31 @@ router.post(
if (alreadySavedUser) {
// Send Welcome Mail
await MailService.sendMail(
user.email!,
EmailSubjects.getSubjectByType(
EmailTemplateType.SIGNUP_WELCOME_EMAIL
),
EmailTemplateType.SIGNUP_WELCOME_EMAIL,
{
await MailService.sendMail({
toEmail: user.email!,
subject: "Welcome to OneUptime.",
templateType: EmailTemplateType.WelomeEmail,
vars: {
name: user.name!.toString(),
dashboardUrl: new URL(
HttpProtocol,
DashboardHostname
Domain, DashboardRoute
).toString(),
homeUrl: new URL(HttpProtocol, HomeHostname).toString(),
}
);
});
} else {
// Send EmailVerification Link because this is a new user.
await MailService.sendMail(
user.email!,
EmailSubjects.getSubjectByType(
EmailTemplateType.SIGNUP_WELCOME_EMAIL
),
EmailTemplateType.SIGNUP_WELCOME_EMAIL,
{
MailService.sendMail({
toEmail: user.email!,
subject: 'Welcome to OneUptime. Please verify your email.',
templateType: EmailTemplateType.WelomeEmail,
vars: {
name: user.name!.toString(),
emailVerificationUrl: new URL(
HttpProtocol,
AccountsHostname
).toString(),
emailVerificationUrl: new URL(HttpProtocol, Domain, new Route(AccountsRoute.toString()).addRoute("/reset-password/")).toString(),
homeUrl: new URL(HttpProtocol, HomeHostname).toString(),
}
}
);
}
@ -187,10 +182,8 @@ router.post(
const user: User = User.fromJSON(data as JSONObject, User) as User;
await user.password?.hashValue(EncryptionSecret);
const alreadySavedUser: User | null = await UserService.findOneBy({
query: { email: user.email!, password: user.password! },
query: { email: user.email! },
select: {
_id: true,
password: true,
@ -204,19 +197,38 @@ router.post(
});
if (alreadySavedUser) {
const token: string = JSONWebToken.sign(
alreadySavedUser,
OneUptimeDate.getSecondsInDays(new PositiveNumber(30))
);
return Response.sendJsonObjectResponse(req, res, {
token: token,
user: BaseModel.toJSON(alreadySavedUser, User),
const token = ObjectID.generate().toString();
await UserService.updateOneBy({
query: {
_id: user._id!
},
data: {
resetPasswordToken: token,
resetPasswordExpires: OneUptimeDate.getOneDayAfter()
},
props: {
isRoot: true
}
});
MailService.sendMail({
toEmail: user.email!,
subject: "Password Reset Request for OneUptime",
templateType: EmailTemplateType.ForgotPassword,
vars: {
homeURL: new URL(HttpProtocol, Domain).toString(),
tokenVerifyUrl: new URL(HttpProtocol, Domain, new Route(AccountsRoute.toString()).addRoute("/reset-password/" + token)).toString(),
}
});
return Response.sendEmptyResponse(req, res);
}
throw new BadDataException(
'Invalid login: Email or password does not match.'
`No user is registered with ${user.email?.toString()}`
);
} catch (err) {
return next(err);
}

View File

@ -7,12 +7,12 @@ const router: ExpressRouter = Express.getRouter();
import Response from 'CommonServer/Utils/Response';
import ClusterKeyAuthorization from 'CommonServer/Middleware/ClusterKeyAuthorization';
import MailService from '../Services/MailService';
import Mail from 'Common/Types/Mail/Mail';
import EmailMessage from 'Common/Types/Email/EmailMessage';
import EmailTemplateType from 'Common/Types/Email/EmailTemplateType';
import { JSONObject } from 'Common/Types/JSON';
import Email from 'Common/Types/Email';
import Dictionary from 'Common/Types/Dictionary';
import MailServer from 'Common/Types/Mail/MailServer';
import EmailServer from 'Common/Types/Email/EmailServer';
router.post(
'/send',
@ -20,7 +20,7 @@ router.post(
async (req: ExpressRequest, res: ExpressResponse) => {
const body: JSONObject = req.body;
const mail: Mail = {
const mail: EmailMessage = {
templateType: body['template-name'] as EmailTemplateType,
toEmail: new Email(body['to-email'] as string),
subject: body['subject'] as string,
@ -28,10 +28,10 @@ router.post(
body: body['body'] as string || '',
};
let mailServer: MailServer | undefined = undefined;
let mailServer: EmailServer | undefined = undefined;
if (hasMailServerSettingsInBody(body)) {
mailServer = MailService.getMailServer(req.body);
mailServer = MailService.getEmailServer(req.body);
}
await MailService.send(

View File

@ -2,11 +2,11 @@ import nodemailer, { Transporter } from 'nodemailer';
import hbs from 'nodemailer-express-handlebars';
import Handlebars from 'handlebars';
import fsp from 'fs/promises';
import Mail from 'Common/Types/Mail/Mail';
import EmailMessage from 'Common/Types/Email/EmailMessage';
import Path from 'path';
import Email from 'Common/Types/Email';
import BadDataException from 'Common/Types/Exception/BadDataException';
import MailServer from 'Common/Types/Mail/MailServer';
import EmailServer from 'Common/Types/Email/EmailServer';
import LocalCache from 'CommonServer/Infrastructure/LocalCache';
import OneUptimeDate from 'Common/Types/Date';
import EmailTemplateType from 'Common/Types/Email/EmailTemplateType';
@ -72,7 +72,7 @@ export default class MailService {
return true;
}
public static getMailServer(obj : JSONObject): MailServer {
public static getEmailServer(obj : JSONObject): EmailServer {
if (!this.isSMTPConfigValid(obj)) {
throw new BadDataException("SMTP Config is not valid");
}
@ -89,8 +89,8 @@ export default class MailService {
};
}
private static getGlobalSmtpSettings(): MailServer {
return this.getMailServer(process.env);
private static getGlobalSmtpSettings(): EmailServer {
return this.getEmailServer(process.env);
}
@ -111,7 +111,7 @@ export default class MailService {
Path.resolve(
process.cwd(),
'Templates',
`${emailTemplateType}.hbs`
`${emailTemplateType}`
),
{ encoding: 'utf8', flag: 'r' }
);
@ -136,7 +136,7 @@ export default class MailService {
return subjectHandlebars(vars).toString();
}
private static createMailer(mailServer: MailServer): Transporter {
private static createMailer(EmailServer: EmailServer): Transporter {
const helpers: Dictionary<string> = {
year: OneUptimeDate.getCurrentYear().toString(),
};
@ -154,12 +154,12 @@ export default class MailService {
};
const privateMailer: Transporter = nodemailer.createTransport({
host: mailServer.host.toString(),
port: mailServer.port.toNumber(),
secure: mailServer.secure,
host: EmailServer.host.toString(),
port: EmailServer.port.toNumber(),
secure: EmailServer.secure,
auth: {
user: mailServer.username,
pass: mailServer.password,
user: EmailServer.username,
pass: EmailServer.password,
},
});
@ -169,24 +169,33 @@ export default class MailService {
}
private static async transportMail(
mail: Mail,
mailServer: MailServer
mail: EmailMessage,
EmailServer: EmailServer
): Promise<void> {
const mailer: Transporter = this.createMailer(mailServer);
const mailer: Transporter = this.createMailer(EmailServer);
await mailer.sendMail(mail);
}
public static async send(
mail: Mail,
mailServer?: MailServer
mail: EmailMessage,
EmailServer?: EmailServer
): Promise<void> {
if (!mailServer) {
mailServer = this.getGlobalSmtpSettings();
if (!EmailServer) {
EmailServer = this.getGlobalSmtpSettings();
}
mail.body = mail.templateType ? await this.compileEmailBody(mail.templateType, mail.vars) : this.compileText(mail.body, mail.vars);
// default vars.
if (!mail.vars) {
mail.vars = {};
}
if (!mail.vars['year']) {
mail.vars['year'] = OneUptimeDate.getCurrentYear().toString();
}
mail.body = mail.templateType ? await this.compileEmailBody(mail.templateType, mail.vars) : this.compileText(mail.body || '', mail.vars);
mail.subject = this.compileText(mail.subject, mail.vars);
await this.transportMail(mail, mailServer);
await this.transportMail(mail, EmailServer);
}
}

View File

@ -429,7 +429,7 @@
style="border: 0; margin: 0; padding: 0; color: #000000 !important; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif; font-size: 16px; line-height: 24px;">
If you need any help using OneUptime, please send us an email at <a
The password reset email expires in 24 hours. If you need any help using OneUptime, please send us an email at <a
style="color: #000000; text-decoration: none; font-weight: bold"
href="mailto:support@oneuptime.com" target="_blank">support@oneuptime.com</a> and let us
know.

View File

@ -5,6 +5,11 @@ APP_TAG=latest
# This will be generated automatically during install.
ONEUPTIME_SECRET={{ .Env.ONEUPTIME_SECRET }}
# Which domain is this server hosted on?
DOMAIN=oneuptime.com
# Is this server hosted with a TLS cert. If yes, this should be "https"
HTTP_PROTOCOL=http
# This supports test | production | development | ci.
# Development is used for local development. Test is used for insider / beta / staging builds. Production is used for production ready app. ci is for testing in the CI/CD.
ENVIRONMENT=production