diff --git a/example.env b/example.env
index 614678be8a..1f6b1f5ee9 100644
--- a/example.env
+++ b/example.env
@@ -40,6 +40,7 @@ EXTENSIONS_PATH="./extensions"
####################################################################################################
# Email
+EMAIL_FROM="no-reply@directus.io"
EMAIL_TRANSPORT="sendmail"
## Email (Sendmail Transport)
diff --git a/src/mail/index.ts b/src/mail/index.ts
index 067381aa16..bdf194b9f5 100644
--- a/src/mail/index.ts
+++ b/src/mail/index.ts
@@ -7,7 +7,10 @@ import { promisify } from 'util';
const readFile = promisify(fs.readFile);
-const liquidEngine = new Liquid();
+const liquidEngine = new Liquid({
+ root: path.resolve(__dirname, 'templates'),
+ extname: '.liquid',
+});
logger.trace('[Email] Initializing email transport...');
@@ -52,6 +55,7 @@ if (emailTransport === 'sendmail') {
export type EmailOptions = {
to: string; // email address of the recipient
+ from: string;
subject: string;
text: string;
html: string;
@@ -61,6 +65,8 @@ export default async function sendMail(options: EmailOptions) {
const templateString = await readFile(path.join(__dirname, 'templates/base.liquid'), 'utf8');
const html = await liquidEngine.parseAndRender(templateString, { html: options.html });
+ options.from = options.from || process.env.EMAIL_FROM;
+
try {
await transporter.sendMail({ ...options, html: html });
} catch (error) {
@@ -68,3 +74,13 @@ export default async function sendMail(options: EmailOptions) {
logger.warn(error);
}
}
+
+export async function sendInviteMail(email: string, url: string) {
+ /**
+ * @TODO pull this from directus_settings
+ */
+ const projectName = 'directus';
+
+ const html = await liquidEngine.renderFile('user-invitation', { email, url, projectName });
+ await transporter.sendMail({ from: process.env.EMAIL_FROM, to: email, html: html });
+}
diff --git a/src/mail/templates/user-invitation.liquid b/src/mail/templates/user-invitation.liquid
new file mode 100644
index 0000000000..eaba3a24a0
--- /dev/null
+++ b/src/mail/templates/user-invitation.liquid
@@ -0,0 +1,15 @@
+{% layout "base" %}
+{% block content %}
+
+
You have been invited to {{ projectName }}. Please click the link below to join:
+
+{{ url }}
+
+{% comment %}
+@TODO
+Make this white-labeled
+{% endcomment %}
+
+Love,
Directus
+
+{% endblock %}
diff --git a/src/routes/users.ts b/src/routes/users.ts
index bb9de1fadc..5b147c136c 100644
--- a/src/routes/users.ts
+++ b/src/routes/users.ts
@@ -3,6 +3,7 @@ import asyncHandler from 'express-async-handler';
import sanitizeQuery from '../middleware/sanitize-query';
import validateQuery from '../middleware/validate-query';
import * as UsersService from '../services/users';
+import Joi from '@hapi/joi';
const router = express.Router();
@@ -14,6 +15,20 @@ router.post(
})
);
+const inviteSchema = Joi.object({
+ email: Joi.string().email().required(),
+ role: Joi.string().uuid({ version: 'uuidv4' }).required(),
+});
+
+router.post(
+ '/invite',
+ asyncHandler(async (req, res) => {
+ await inviteSchema.validateAsync(req.body);
+ await UsersService.inviteUser(req.body.email, req.body.role);
+ res.end();
+ })
+);
+
router.get(
'/',
sanitizeQuery,
diff --git a/src/services/users.ts b/src/services/users.ts
index ad2a4df4ba..1060eb9119 100644
--- a/src/services/users.ts
+++ b/src/services/users.ts
@@ -1,22 +1,34 @@
import { Query } from '../types/query';
import * as ItemsService from './items';
+import jwt from 'jsonwebtoken';
+import { sendInviteMail } from '../mail';
-export const createUser = async (data: Record, query: Query) => {
+export const createUser = async (data: Record, query?: Query) => {
return await ItemsService.createItem('directus_users', data, query);
};
-export const readUsers = async (query: Query) => {
+export const readUsers = async (query?: Query) => {
return await ItemsService.readItems('directus_users', query);
};
-export const readUser = async (pk: string | number, query: Query) => {
+export const readUser = async (pk: string | number, query?: Query) => {
return await ItemsService.readItem('directus_users', pk, query);
};
-export const updateUser = async (pk: string | number, data: Record, query: Query) => {
+export const updateUser = async (pk: string | number, data: Record, query?: Query) => {
return await ItemsService.updateItem('directus_users', pk, data, query);
};
export const deleteUser = async (pk: string | number) => {
await ItemsService.deleteItem('directus_users', pk);
};
+
+export const inviteUser = async (email: string, role: string) => {
+ await createUser({ email, role, status: 'invited' });
+
+ const payload = { email };
+ const token = jwt.sign(payload, process.env.SECRET, { expiresIn: '7d' });
+ const acceptURL = process.env.PUBLIC_URL + '/admin/accept-invite?token=' + token;
+
+ await sendInviteMail(email, acceptURL);
+};