mirror of
https://github.com/outline/outline.git
synced 2026-05-06 10:00:20 -05:00
Add SMTP_SERVICE environment variable for well-known services (#8781)
* Add SMTP_SERVICE environment variable for well-known services * Fix PR #8777: Restore code in teams.ts and users.ts * The rest of the work * fix validation --------- Co-authored-by: codegen-sh[bot] <131295404+codegen-sh[bot]@users.noreply.github.com> Co-authored-by: Tom Moor <tom.moor@gmail.com>
This commit is contained in:
+1
-5
@@ -205,14 +205,10 @@ SENTRY_TUNNEL=
|
||||
|
||||
# To support sending outgoing transactional emails such as "document updated" or
|
||||
# "you've been invited" you'll need to provide authentication for an SMTP server
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=
|
||||
SMTP_SERVICE=
|
||||
SMTP_USERNAME=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_FROM_EMAIL=
|
||||
SMTP_REPLY_EMAIL=
|
||||
SMTP_TLS_CIPHERS=
|
||||
SMTP_SECURE=true
|
||||
|
||||
# The default interface language. See translate.getoutline.com for a list of
|
||||
# available language codes and their rough percentage translated.
|
||||
|
||||
@@ -171,6 +171,10 @@
|
||||
"description": "smtp.example.com (optional)",
|
||||
"required": false
|
||||
},
|
||||
"SMTP_SERVICE": {
|
||||
"description": "Well-known SMTP service name for nodemailer (optional, e.g. 'gmail', 'SES')",
|
||||
"required": false
|
||||
},
|
||||
"SMTP_PORT": {
|
||||
"description": "1234 (optional)",
|
||||
"required": false
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Hook, PluginManager } from "@server/utils/PluginManager";
|
||||
import config from "../plugin.json";
|
||||
import router from "./auth/email";
|
||||
|
||||
const enabled = !!env.SMTP_HOST || env.isDevelopment;
|
||||
const enabled = !!(env.SMTP_HOST || env.SMTP_SERVICE) || env.isDevelopment;
|
||||
|
||||
if (enabled) {
|
||||
PluginManager.add({
|
||||
|
||||
@@ -33,7 +33,7 @@ export class Mailer {
|
||||
transporter: Transporter | undefined;
|
||||
|
||||
constructor() {
|
||||
if (env.SMTP_HOST) {
|
||||
if (env.SMTP_HOST || env.SMTP_SERVICE) {
|
||||
this.transporter = nodemailer.createTransport(this.getOptions());
|
||||
}
|
||||
if (useTestEmailService) {
|
||||
@@ -198,6 +198,17 @@ export class Mailer {
|
||||
};
|
||||
|
||||
private getOptions(): SMTPTransport.Options {
|
||||
// nodemailer will use the service config to determine host/port
|
||||
if (env.SMTP_SERVICE) {
|
||||
return {
|
||||
service: env.SMTP_SERVICE,
|
||||
auth: {
|
||||
user: env.SMTP_USERNAME,
|
||||
pass: env.SMTP_PASSWORD,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
name: env.SMTP_NAME,
|
||||
host: env.SMTP_HOST,
|
||||
|
||||
+13
-3
@@ -15,7 +15,7 @@ import {
|
||||
} from "class-validator";
|
||||
import uniq from "lodash/uniq";
|
||||
import { languages } from "@shared/i18n";
|
||||
import { CannotUseWithout } from "@server/utils/validators";
|
||||
import { CannotUseWith, CannotUseWithout } from "@server/utils/validators";
|
||||
import Deprecated from "./models/decorators/Deprecated";
|
||||
import { getArg } from "./utils/args";
|
||||
import { Public, PublicEnvironmentRegister } from "./utils/decorators/Public";
|
||||
@@ -291,10 +291,19 @@ export class Environment {
|
||||
/**
|
||||
* The host of your SMTP server for enabling emails.
|
||||
*/
|
||||
public SMTP_HOST = environment.SMTP_HOST;
|
||||
@CannotUseWith("SMTP_SERVICE")
|
||||
public SMTP_HOST = this.toOptionalString(environment.SMTP_HOST);
|
||||
|
||||
/**
|
||||
* The service name of a well-known SMTP service for nodemailer.
|
||||
* See https://community.nodemailer.com/2-0-0-beta/setup-smtp/well-known-services/
|
||||
*/
|
||||
@CannotUseWith("SMTP_HOST")
|
||||
public SMTP_SERVICE = this.toOptionalString(environment.SMTP_SERVICE);
|
||||
|
||||
@Public
|
||||
public EMAIL_ENABLED = !!this.SMTP_HOST || this.isDevelopment;
|
||||
public EMAIL_ENABLED =
|
||||
!!(this.SMTP_HOST || this.SMTP_SERVICE) || this.isDevelopment;
|
||||
|
||||
/**
|
||||
* Optional hostname of the client, used for identifying to the server
|
||||
@@ -307,6 +316,7 @@ export class Environment {
|
||||
*/
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
@CannotUseWith("SMTP_SERVICE")
|
||||
public SMTP_PORT = this.toOptionalNumber(environment.SMTP_PORT);
|
||||
|
||||
/**
|
||||
|
||||
@@ -190,7 +190,7 @@ class Team extends ParanoidModel<
|
||||
* @return {boolean} Whether to show email login options
|
||||
*/
|
||||
get emailSigninEnabled(): boolean {
|
||||
return this.guestSignin && (!!env.SMTP_HOST || env.isDevelopment);
|
||||
return this.guestSignin && env.EMAIL_ENABLED;
|
||||
}
|
||||
|
||||
get url() {
|
||||
|
||||
@@ -19,7 +19,6 @@ import { safeEqual } from "@server/utils/crypto";
|
||||
import * as T from "./schema";
|
||||
|
||||
const router = new Router();
|
||||
const emailEnabled = !!(env.SMTP_HOST || env.isDevelopment);
|
||||
|
||||
const handleTeamUpdate = async (ctx: APIContext<T.TeamsUpdateSchemaReq>) => {
|
||||
const { transaction } = ctx.state;
|
||||
@@ -68,7 +67,7 @@ router.post(
|
||||
rateLimiter(RateLimiterStrategy.FivePerHour),
|
||||
auth(),
|
||||
async (ctx: APIContext) => {
|
||||
if (!emailEnabled) {
|
||||
if (!env.EMAIL_ENABLED) {
|
||||
throw ValidationError("Email support is not setup for this instance");
|
||||
}
|
||||
|
||||
@@ -101,7 +100,7 @@ router.post(
|
||||
|
||||
authorize(user, "delete", team);
|
||||
|
||||
if (emailEnabled) {
|
||||
if (env.EMAIL_ENABLED) {
|
||||
const deleteConfirmationCode = team.getDeleteConfirmationCode(user);
|
||||
|
||||
if (!safeEqual(code, deleteConfirmationCode)) {
|
||||
|
||||
@@ -30,7 +30,6 @@ import pagination from "../middlewares/pagination";
|
||||
import * as T from "./schema";
|
||||
|
||||
const router = new Router();
|
||||
const emailEnabled = !!(env.SMTP_HOST || env.isDevelopment);
|
||||
|
||||
router.post(
|
||||
"users.list",
|
||||
@@ -210,7 +209,7 @@ router.post(
|
||||
auth(),
|
||||
validate(T.UsersUpdateEmailSchema),
|
||||
async (ctx: APIContext<T.UsersUpdateEmailReq>) => {
|
||||
if (!emailEnabled) {
|
||||
if (!env.EMAIL_ENABLED) {
|
||||
throw ValidationError("Email support is not setup for this instance");
|
||||
}
|
||||
|
||||
@@ -252,7 +251,7 @@ router.get(
|
||||
transaction(),
|
||||
validate(T.UsersUpdateEmailConfirmSchema),
|
||||
async (ctx: APIContext<T.UsersUpdateEmailConfirmReq>) => {
|
||||
if (!emailEnabled) {
|
||||
if (!env.EMAIL_ENABLED) {
|
||||
throw ValidationError("Email support is not setup for this instance");
|
||||
}
|
||||
|
||||
@@ -626,7 +625,7 @@ router.post(
|
||||
rateLimiter(RateLimiterStrategy.FivePerHour),
|
||||
auth(),
|
||||
async (ctx: APIContext) => {
|
||||
if (!emailEnabled) {
|
||||
if (!env.EMAIL_ENABLED) {
|
||||
throw ValidationError("Email support is not setup for this instance");
|
||||
}
|
||||
|
||||
@@ -671,7 +670,7 @@ router.post(
|
||||
|
||||
// If we're attempting to delete our own account then a confirmation code
|
||||
// is required. This acts as CSRF protection.
|
||||
if ((!id || id === actor.id) && emailEnabled) {
|
||||
if ((!id || id === actor.id) && env.EMAIL_ENABLED) {
|
||||
const deleteConfirmationCode = user.deleteConfirmationCode;
|
||||
|
||||
if (!safeEqual(code, deleteConfirmationCode)) {
|
||||
|
||||
@@ -29,3 +29,31 @@ export function CannotUseWithout(
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function CannotUseWith(
|
||||
property: string,
|
||||
validationOptions?: ValidationOptions
|
||||
) {
|
||||
return function (object: Object, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: "cannotUseWith",
|
||||
target: object.constructor,
|
||||
propertyName,
|
||||
constraints: [property],
|
||||
options: validationOptions,
|
||||
validator: {
|
||||
validate<T>(value: T, args: ValidationArguments) {
|
||||
if (value === undefined) {
|
||||
return true;
|
||||
}
|
||||
const obj = args.object as unknown as T;
|
||||
const forbidden = args.constraints[0] as keyof T;
|
||||
return obj[forbidden] === undefined;
|
||||
},
|
||||
defaultMessage(args: ValidationArguments) {
|
||||
return `${propertyName} cannot be used with ${args.constraints[0]}.`;
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user