directus 9.0.0 → 9.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +8 -4
- package/dist/auth/drivers/ldap.js +9 -13
- package/dist/auth/drivers/oauth2.d.ts +1 -1
- package/dist/auth/drivers/oauth2.js +3 -3
- package/dist/auth/drivers/openid.d.ts +1 -1
- package/dist/auth/drivers/openid.js +9 -3
- package/dist/controllers/notifications.d.ts +2 -0
- package/dist/controllers/notifications.js +147 -0
- package/dist/database/helpers/geometry.js +4 -1
- package/dist/database/migrations/20211103B-update-special-geometry.js +2 -2
- package/dist/database/migrations/20211118A-add-notifications.d.ts +3 -0
- package/dist/database/migrations/20211118A-add-notifications.js +28 -0
- package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +14 -0
- package/dist/database/system-data/collections/collections.yaml +2 -0
- package/dist/database/system-data/fields/notifications.yaml +12 -0
- package/dist/database/system-data/fields/settings.yaml +7 -7
- package/dist/database/system-data/fields/users.yaml +5 -0
- package/dist/database/system-data/fields/webhooks.yaml +4 -4
- package/dist/database/system-data/relations/relations.yaml +6 -0
- package/dist/mailer.js +12 -3
- package/dist/middleware/get-permissions.js +3 -102
- package/dist/server.js +2 -2
- package/dist/services/activity.d.ts +7 -5
- package/dist/services/activity.js +82 -3
- package/dist/services/authentication.js +15 -1
- package/dist/services/collections.js +12 -1
- package/dist/services/graphql.js +3 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/mail/index.js +2 -2
- package/dist/services/mail/templates/base.liquid +153 -85
- package/dist/services/mail/templates/password-reset.liquid +3 -2
- package/dist/services/mail/templates/user-invitation.liquid +4 -4
- package/dist/services/notifications.d.ts +12 -0
- package/dist/services/notifications.js +41 -0
- package/dist/services/users.js +1 -0
- package/dist/types/collection.d.ts +1 -0
- package/dist/utils/apply-query.js +10 -7
- package/dist/utils/apply-snapshot.js +2 -2
- package/dist/utils/get-local-type.js +12 -7
- package/dist/utils/get-permissions.d.ts +3 -0
- package/dist/utils/get-permissions.js +106 -0
- package/dist/utils/md.d.ts +4 -0
- package/dist/utils/md.js +15 -0
- package/dist/utils/sanitize-query.js +3 -3
- package/dist/utils/user-name.d.ts +2 -0
- package/dist/utils/user-name.js +16 -0
- package/package.json +27 -23
|
@@ -3,112 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const utils_1 = require("@directus/shared/utils");
|
|
7
|
-
const lodash_1 = require("lodash");
|
|
8
|
-
const database_1 = __importDefault(require("../database"));
|
|
9
|
-
const app_access_permissions_1 = require("../database/system-data/app-access-permissions");
|
|
10
6
|
const async_handler_1 = __importDefault(require("../utils/async-handler"));
|
|
11
|
-
const
|
|
12
|
-
const users_1 = require("../services/users");
|
|
13
|
-
const roles_1 = require("../services/roles");
|
|
14
|
-
const cache_1 = require("../cache");
|
|
15
|
-
const object_hash_1 = __importDefault(require("object-hash"));
|
|
16
|
-
const env_1 = __importDefault(require("../env"));
|
|
7
|
+
const get_permissions_1 = require("../utils/get-permissions");
|
|
17
8
|
const getPermissions = (0, async_handler_1.default)(async (req, res, next) => {
|
|
18
|
-
const database = (0, database_1.default)();
|
|
19
|
-
const { systemCache } = (0, cache_1.getCache)();
|
|
20
|
-
let permissions = [];
|
|
21
9
|
if (!req.accountability) {
|
|
22
|
-
throw new Error('
|
|
10
|
+
throw new Error('getPermissions middleware needs to be called after authenticate');
|
|
23
11
|
}
|
|
24
|
-
|
|
25
|
-
throw new Error('"getPermissions" needs to be used after the "schema" middleware');
|
|
26
|
-
}
|
|
27
|
-
const { user, role, app, admin } = req.accountability;
|
|
28
|
-
const cacheKey = `permissions-${(0, object_hash_1.default)({ user, role, app, admin })}`;
|
|
29
|
-
if (env_1.default.CACHE_PERMISSIONS !== false) {
|
|
30
|
-
const cachedPermissions = await systemCache.get(cacheKey);
|
|
31
|
-
if (cachedPermissions) {
|
|
32
|
-
req.accountability.permissions = cachedPermissions;
|
|
33
|
-
return next();
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
if (req.accountability.admin !== true) {
|
|
37
|
-
const permissionsForRole = await database
|
|
38
|
-
.select('*')
|
|
39
|
-
.from('directus_permissions')
|
|
40
|
-
.where({ role: req.accountability.role });
|
|
41
|
-
const requiredPermissionData = {
|
|
42
|
-
$CURRENT_USER: [],
|
|
43
|
-
$CURRENT_ROLE: [],
|
|
44
|
-
};
|
|
45
|
-
permissions = permissionsForRole.map((permissionRaw) => {
|
|
46
|
-
const permission = (0, lodash_1.cloneDeep)(permissionRaw);
|
|
47
|
-
if (permission.permissions && typeof permission.permissions === 'string') {
|
|
48
|
-
permission.permissions = JSON.parse(permission.permissions);
|
|
49
|
-
}
|
|
50
|
-
else if (permission.permissions === null) {
|
|
51
|
-
permission.permissions = {};
|
|
52
|
-
}
|
|
53
|
-
if (permission.validation && typeof permission.validation === 'string') {
|
|
54
|
-
permission.validation = JSON.parse(permission.validation);
|
|
55
|
-
}
|
|
56
|
-
else if (permission.validation === null) {
|
|
57
|
-
permission.validation = {};
|
|
58
|
-
}
|
|
59
|
-
if (permission.presets && typeof permission.presets === 'string') {
|
|
60
|
-
permission.presets = JSON.parse(permission.presets);
|
|
61
|
-
}
|
|
62
|
-
else if (permission.presets === null) {
|
|
63
|
-
permission.presets = {};
|
|
64
|
-
}
|
|
65
|
-
if (permission.fields && typeof permission.fields === 'string') {
|
|
66
|
-
permission.fields = permission.fields.split(',');
|
|
67
|
-
}
|
|
68
|
-
else if (permission.fields === null) {
|
|
69
|
-
permission.fields = [];
|
|
70
|
-
}
|
|
71
|
-
const extractPermissionData = (val) => {
|
|
72
|
-
if (typeof val === 'string' && val.startsWith('$CURRENT_USER.')) {
|
|
73
|
-
requiredPermissionData.$CURRENT_USER.push(val.replace('$CURRENT_USER.', ''));
|
|
74
|
-
}
|
|
75
|
-
if (typeof val === 'string' && val.startsWith('$CURRENT_ROLE.')) {
|
|
76
|
-
requiredPermissionData.$CURRENT_ROLE.push(val.replace('$CURRENT_ROLE.', ''));
|
|
77
|
-
}
|
|
78
|
-
return val;
|
|
79
|
-
};
|
|
80
|
-
(0, utils_1.deepMap)(permission.permissions, extractPermissionData);
|
|
81
|
-
(0, utils_1.deepMap)(permission.validation, extractPermissionData);
|
|
82
|
-
(0, utils_1.deepMap)(permission.presets, extractPermissionData);
|
|
83
|
-
return permission;
|
|
84
|
-
});
|
|
85
|
-
if (req.accountability.app === true) {
|
|
86
|
-
permissions = (0, merge_permissions_1.mergePermissions)(permissions, app_access_permissions_1.appAccessMinimalPermissions.map((perm) => ({ ...perm, role: req.accountability.role })));
|
|
87
|
-
}
|
|
88
|
-
const usersService = new users_1.UsersService({ schema: req.schema });
|
|
89
|
-
const rolesService = new roles_1.RolesService({ schema: req.schema });
|
|
90
|
-
const filterContext = {};
|
|
91
|
-
if (req.accountability.user && requiredPermissionData.$CURRENT_USER.length > 0) {
|
|
92
|
-
filterContext.$CURRENT_USER = await usersService.readOne(req.accountability.user, {
|
|
93
|
-
fields: requiredPermissionData.$CURRENT_USER,
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
if (req.accountability.role && requiredPermissionData.$CURRENT_ROLE.length > 0) {
|
|
97
|
-
filterContext.$CURRENT_ROLE = await rolesService.readOne(req.accountability.role, {
|
|
98
|
-
fields: requiredPermissionData.$CURRENT_ROLE,
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
permissions = permissions.map((permission) => {
|
|
102
|
-
permission.permissions = (0, utils_1.parseFilter)(permission.permissions, req.accountability, filterContext);
|
|
103
|
-
permission.validation = (0, utils_1.parseFilter)(permission.validation, req.accountability, filterContext);
|
|
104
|
-
permission.presets = (0, utils_1.parseFilter)(permission.presets, req.accountability, filterContext);
|
|
105
|
-
return permission;
|
|
106
|
-
});
|
|
107
|
-
if (env_1.default.CACHE_PERMISSIONS !== false) {
|
|
108
|
-
await systemCache.set(cacheKey, permissions);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
req.accountability.permissions = permissions;
|
|
12
|
+
req.accountability.permissions = await (0, get_permissions_1.getPermissions)(req.accountability, req.schema);
|
|
112
13
|
return next();
|
|
113
14
|
});
|
|
114
15
|
exports.default = getPermissions;
|
package/dist/server.js
CHANGED
|
@@ -111,7 +111,7 @@ async function createServer() {
|
|
|
111
111
|
logger_1.default.info('Database connections destroyed');
|
|
112
112
|
}
|
|
113
113
|
async function onShutdown() {
|
|
114
|
-
emitter_1.default.emitAction('server.stop', {}, {
|
|
114
|
+
emitter_1.default.emitAction('server.stop', { server }, {
|
|
115
115
|
database: (0, database_1.default)(),
|
|
116
116
|
schema: null,
|
|
117
117
|
accountability: null,
|
|
@@ -137,7 +137,7 @@ async function startServer() {
|
|
|
137
137
|
// No need to log/warn here. The update message is only an informative nice-to-have
|
|
138
138
|
});
|
|
139
139
|
logger_1.default.info(`Server started at http://localhost:${port}`);
|
|
140
|
-
emitter_1.default.emitAction('server.start', {}, {
|
|
140
|
+
emitter_1.default.emitAction('server.start', { server }, {
|
|
141
141
|
database: (0, database_1.default)(),
|
|
142
142
|
schema: null,
|
|
143
143
|
accountability: null,
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { AbstractServiceOptions } from '../types';
|
|
2
|
-
import { ItemsService } from './index';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*/
|
|
1
|
+
import { AbstractServiceOptions, PrimaryKey, Item } from '../types';
|
|
2
|
+
import { ItemsService, MutationOptions } from './index';
|
|
3
|
+
import { NotificationsService } from './notifications';
|
|
4
|
+
import { UsersService } from './users';
|
|
6
5
|
export declare class ActivityService extends ItemsService {
|
|
6
|
+
notificationsService: NotificationsService;
|
|
7
|
+
usersService: UsersService;
|
|
7
8
|
constructor(options: AbstractServiceOptions);
|
|
9
|
+
createOne(data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey>;
|
|
8
10
|
}
|
|
@@ -1,13 +1,92 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.ActivityService = void 0;
|
|
7
|
+
const types_1 = require("../types");
|
|
4
8
|
const index_1 = require("./index");
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
const notifications_1 = require("./notifications");
|
|
10
|
+
const users_1 = require("./users");
|
|
11
|
+
const authorization_1 = require("./authorization");
|
|
12
|
+
const get_permissions_1 = require("../utils/get-permissions");
|
|
13
|
+
const forbidden_1 = require("../exceptions/forbidden");
|
|
14
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
15
|
+
const user_name_1 = require("../utils/user-name");
|
|
16
|
+
const lodash_1 = require("lodash");
|
|
17
|
+
const env_1 = __importDefault(require("../env"));
|
|
8
18
|
class ActivityService extends index_1.ItemsService {
|
|
9
19
|
constructor(options) {
|
|
10
20
|
super('directus_activity', options);
|
|
21
|
+
this.notificationsService = new notifications_1.NotificationsService({ schema: this.schema });
|
|
22
|
+
this.usersService = new users_1.UsersService({ schema: this.schema });
|
|
23
|
+
}
|
|
24
|
+
async createOne(data, opts) {
|
|
25
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
26
|
+
if (data.action === types_1.Action.COMMENT && typeof data.comment === 'string') {
|
|
27
|
+
const usersRegExp = new RegExp(/@[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}/gi);
|
|
28
|
+
const mentions = (0, lodash_1.uniq)((_a = data.comment.match(usersRegExp)) !== null && _a !== void 0 ? _a : []);
|
|
29
|
+
const sender = await this.usersService.readOne(this.accountability.user, {
|
|
30
|
+
fields: ['id', 'first_name', 'last_name', 'email'],
|
|
31
|
+
});
|
|
32
|
+
for (const mention of mentions) {
|
|
33
|
+
const userID = mention.substring(1);
|
|
34
|
+
const user = await this.usersService.readOne(userID, {
|
|
35
|
+
fields: ['id', 'first_name', 'last_name', 'email', 'role.id', 'role.admin_access', 'role.app_access'],
|
|
36
|
+
});
|
|
37
|
+
const accountability = {
|
|
38
|
+
user: userID,
|
|
39
|
+
role: (_c = (_b = user.role) === null || _b === void 0 ? void 0 : _b.id) !== null && _c !== void 0 ? _c : null,
|
|
40
|
+
admin: (_e = (_d = user.role) === null || _d === void 0 ? void 0 : _d.admin_access) !== null && _e !== void 0 ? _e : null,
|
|
41
|
+
app: (_g = (_f = user.role) === null || _f === void 0 ? void 0 : _f.app_access) !== null && _g !== void 0 ? _g : null,
|
|
42
|
+
};
|
|
43
|
+
accountability.permissions = await (0, get_permissions_1.getPermissions)(accountability, this.schema);
|
|
44
|
+
const authorizationService = new authorization_1.AuthorizationService({ schema: this.schema, accountability });
|
|
45
|
+
const usersService = new users_1.UsersService({ schema: this.schema, accountability });
|
|
46
|
+
try {
|
|
47
|
+
await authorizationService.checkAccess('read', data.collection, data.item);
|
|
48
|
+
const templateData = await usersService.readByQuery({
|
|
49
|
+
fields: ['id', 'first_name', 'last_name', 'email'],
|
|
50
|
+
filter: { id: { _in: mentions.map((mention) => mention.substring(1)) } },
|
|
51
|
+
});
|
|
52
|
+
const userPreviews = templateData.reduce((acc, user) => {
|
|
53
|
+
acc[user.id] = `<em>${(0, user_name_1.userName)(user)}</em>`;
|
|
54
|
+
return acc;
|
|
55
|
+
}, {});
|
|
56
|
+
let comment = data.comment;
|
|
57
|
+
for (const mention of mentions) {
|
|
58
|
+
comment = comment.replace(mention, (_h = userPreviews[mention.substring(1)]) !== null && _h !== void 0 ? _h : '@Unknown User');
|
|
59
|
+
}
|
|
60
|
+
comment = `> ${comment}`;
|
|
61
|
+
const message = `
|
|
62
|
+
Hello ${(0, user_name_1.userName)(user)},
|
|
63
|
+
|
|
64
|
+
${(0, user_name_1.userName)(sender)} has mentioned you in a comment:
|
|
65
|
+
|
|
66
|
+
${comment}
|
|
67
|
+
|
|
68
|
+
<a href="${env_1.default.PUBLIC_URL}/admin/content/${data.collection}/${data.item}">Click here to view.</a>
|
|
69
|
+
`;
|
|
70
|
+
await this.notificationsService.createOne({
|
|
71
|
+
recipient: userID,
|
|
72
|
+
sender: sender.id,
|
|
73
|
+
subject: `You were mentioned in ${data.collection}`,
|
|
74
|
+
message,
|
|
75
|
+
collection: data.collection,
|
|
76
|
+
item: data.item,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
if (err instanceof forbidden_1.ForbiddenException) {
|
|
81
|
+
logger_1.default.warn(`User ${userID} doesn't have proper permissions to receive notification for this item.`);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return super.createOne(data, opts);
|
|
11
90
|
}
|
|
12
91
|
}
|
|
13
92
|
exports.ActivityService = ActivityService;
|
|
@@ -128,6 +128,7 @@ class AuthenticationService {
|
|
|
128
128
|
status: 'pending',
|
|
129
129
|
user: user === null || user === void 0 ? void 0 : user.id,
|
|
130
130
|
provider: providerName,
|
|
131
|
+
type: 'login',
|
|
131
132
|
}, {
|
|
132
133
|
database: this.knex,
|
|
133
134
|
schema: this.schema,
|
|
@@ -196,7 +197,20 @@ class AuthenticationService {
|
|
|
196
197
|
}
|
|
197
198
|
const provider = (0, auth_1.getAuthProvider)(user.provider);
|
|
198
199
|
const newSessionData = await provider.refresh((0, lodash_1.clone)(user), sessionData);
|
|
199
|
-
const
|
|
200
|
+
const tokenPayload = {
|
|
201
|
+
id: user.id,
|
|
202
|
+
};
|
|
203
|
+
const customClaims = await emitter_1.default.emitFilter('auth.jwt', tokenPayload, {
|
|
204
|
+
status: 'pending',
|
|
205
|
+
user: user === null || user === void 0 ? void 0 : user.id,
|
|
206
|
+
provider: user.provider,
|
|
207
|
+
type: 'refresh',
|
|
208
|
+
}, {
|
|
209
|
+
database: this.knex,
|
|
210
|
+
schema: this.schema,
|
|
211
|
+
accountability: this.accountability,
|
|
212
|
+
});
|
|
213
|
+
const accessToken = jsonwebtoken_1.default.sign(customClaims, env_1.default.SECRET, {
|
|
200
214
|
expiresIn: env_1.default.ACCESS_TOKEN_TTL,
|
|
201
215
|
issuer: 'directus',
|
|
202
216
|
});
|
|
@@ -171,11 +171,22 @@ class CollectionsService {
|
|
|
171
171
|
}));
|
|
172
172
|
meta.push(...collections_1.systemCollectionRows);
|
|
173
173
|
if (this.accountability && this.accountability.admin !== true) {
|
|
174
|
-
const
|
|
174
|
+
const collectionsGroups = meta.reduce((meta, item) => ({
|
|
175
|
+
...meta,
|
|
176
|
+
[item.collection]: item.group,
|
|
177
|
+
}), {});
|
|
178
|
+
let collectionsYouHavePermissionToRead = this.accountability
|
|
175
179
|
.permissions.filter((permission) => {
|
|
176
180
|
return permission.action === 'read';
|
|
177
181
|
})
|
|
178
182
|
.map(({ collection }) => collection);
|
|
183
|
+
for (const collection of collectionsYouHavePermissionToRead) {
|
|
184
|
+
const group = collectionsGroups[collection];
|
|
185
|
+
if (group)
|
|
186
|
+
collectionsYouHavePermissionToRead.push(group);
|
|
187
|
+
delete collectionsGroups[collection];
|
|
188
|
+
}
|
|
189
|
+
collectionsYouHavePermissionToRead = [...new Set([...collectionsYouHavePermissionToRead])];
|
|
179
190
|
tablesInDatabase = tablesInDatabase.filter((table) => {
|
|
180
191
|
return collectionsYouHavePermissionToRead.includes(table.name);
|
|
181
192
|
});
|
package/dist/services/graphql.js
CHANGED
|
@@ -28,6 +28,7 @@ const folders_1 = require("./folders");
|
|
|
28
28
|
const items_1 = require("./items");
|
|
29
29
|
const permissions_1 = require("./permissions");
|
|
30
30
|
const presets_1 = require("./presets");
|
|
31
|
+
const notifications_1 = require("./notifications");
|
|
31
32
|
const relations_1 = require("./relations");
|
|
32
33
|
const revisions_1 = require("./revisions");
|
|
33
34
|
const roles_1 = require("./roles");
|
|
@@ -1252,6 +1253,8 @@ class GraphQLService {
|
|
|
1252
1253
|
return new permissions_1.PermissionsService(opts);
|
|
1253
1254
|
case 'directus_presets':
|
|
1254
1255
|
return new presets_1.PresetsService(opts);
|
|
1256
|
+
case 'directus_notifications':
|
|
1257
|
+
return new notifications_1.NotificationsService(opts);
|
|
1255
1258
|
case 'directus_revisions':
|
|
1256
1259
|
return new revisions_1.RevisionsService(opts);
|
|
1257
1260
|
case 'directus_roles':
|
package/dist/services/index.d.ts
CHANGED
package/dist/services/index.js
CHANGED
|
@@ -24,6 +24,7 @@ __exportStar(require("./graphql"), exports);
|
|
|
24
24
|
__exportStar(require("./import"), exports);
|
|
25
25
|
__exportStar(require("./mail"), exports);
|
|
26
26
|
__exportStar(require("./meta"), exports);
|
|
27
|
+
__exportStar(require("./notifications"), exports);
|
|
27
28
|
__exportStar(require("./panels"), exports);
|
|
28
29
|
__exportStar(require("./payload"), exports);
|
|
29
30
|
__exportStar(require("./permissions"), exports);
|
|
@@ -33,10 +33,10 @@ class MailService {
|
|
|
33
33
|
async send(options) {
|
|
34
34
|
const { template, ...emailOptions } = options;
|
|
35
35
|
let { html } = options;
|
|
36
|
-
const
|
|
36
|
+
const defaultTemplateData = await this.getDefaultTemplateData();
|
|
37
|
+
const from = `${defaultTemplateData.projectName} <${options.from || env_1.default.EMAIL_FROM}>`;
|
|
37
38
|
if (template) {
|
|
38
39
|
let templateData = template.data;
|
|
39
|
-
const defaultTemplateData = await this.getDefaultTemplateData();
|
|
40
40
|
templateData = {
|
|
41
41
|
...defaultTemplateData,
|
|
42
42
|
...templateData,
|
|
@@ -1,95 +1,163 @@
|
|
|
1
|
-
<!
|
|
2
|
-
<html
|
|
3
|
-
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
4
3
|
<head>
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
4
|
+
|
|
5
|
+
<title>{{ projectName }} Email Service</title>
|
|
6
|
+
|
|
7
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
8
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
9
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
10
|
+
|
|
11
|
+
<style type="text/css" id="hs-inline-css">
|
|
12
|
+
/*<![CDATA[*/
|
|
13
|
+
|
|
14
|
+
/* CLIENT-SPECIFIC STYLES */
|
|
15
|
+
body, table, td, a {
|
|
16
|
+
-webkit-text-size-adjust: 100%;
|
|
17
|
+
-ms-text-size-adjust: 100%;
|
|
18
|
+
-webkit-font-smoothing: antialiased;
|
|
19
|
+
}
|
|
20
|
+
table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
|
|
21
|
+
img { -ms-interpolation-mode: bicubic; }
|
|
22
|
+
|
|
23
|
+
/* RESET STYLES */
|
|
24
|
+
img { border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; }
|
|
25
|
+
table { border-collapse: collapse !important; }
|
|
26
|
+
body { height: 100% !important; margin: 0 !important; padding: 0 !important; width: 100% !important; }
|
|
27
|
+
|
|
28
|
+
/* iOS BLUE LINKS */
|
|
29
|
+
a[x-apple-data-detectors] {
|
|
30
|
+
color: inherit !important;
|
|
31
|
+
text-decoration: none !important;
|
|
32
|
+
font-size: inherit !important;
|
|
33
|
+
font-family: inherit !important;
|
|
34
|
+
font-weight: inherit !important;
|
|
35
|
+
line-height: inherit !important;
|
|
36
|
+
}
|
|
37
|
+
body a {
|
|
38
|
+
color: #00C897;
|
|
39
|
+
text-decoration: none;
|
|
40
|
+
}
|
|
41
|
+
hr {
|
|
42
|
+
width:66%;
|
|
43
|
+
margin:40px auto;
|
|
44
|
+
border:1px solid #d3dae4;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* MOBILE STYLES */
|
|
48
|
+
@media screen and (max-width: 600px) {
|
|
49
|
+
.img-max {
|
|
50
|
+
width: 100% !important;
|
|
51
|
+
max-width: 100% !important;
|
|
52
|
+
height: auto !important;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.max-width {
|
|
56
|
+
max-width: 100% !important;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#content {
|
|
60
|
+
padding-left: 5% !important;
|
|
61
|
+
padding-right: 5% !important;
|
|
62
|
+
padding-top: 30px !important;
|
|
63
|
+
padding-bottom: 30px !important;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* DARK MODE */
|
|
68
|
+
@media (prefers-color-scheme: dark) {
|
|
69
|
+
#background {
|
|
70
|
+
background-color: #172940 !important;
|
|
71
|
+
}
|
|
72
|
+
#content {
|
|
73
|
+
background-color: #071930 !important;
|
|
74
|
+
color: #FFFFFF !important;
|
|
75
|
+
}
|
|
76
|
+
.link {
|
|
77
|
+
color: #00c897 !important;
|
|
78
|
+
}
|
|
79
|
+
.button {
|
|
80
|
+
background-color:#0BA582 !important;
|
|
81
|
+
}
|
|
82
|
+
hr {
|
|
83
|
+
border:1px solid #172940 !important;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* ANDROID CENTER FIX */
|
|
88
|
+
div[style*="margin: 16px 0;"] {
|
|
89
|
+
margin: 0 !important;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
blockquote {
|
|
93
|
+
background: #f0f4f9 !important;
|
|
94
|
+
border-radius: 4px !important;
|
|
95
|
+
margin: 0 !important;
|
|
96
|
+
padding: 24px !important;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
blockquote > p {
|
|
100
|
+
margin: 0 !important;
|
|
101
|
+
}
|
|
102
|
+
/*]]>*/
|
|
103
|
+
</style>
|
|
104
|
+
|
|
105
|
+
<meta name="generator" content="Directus">
|
|
106
|
+
<meta property="og:url" content="http://directus-20534155.hs-sites.com/7dea362b-3fac-3e00-956a-4952a3d4f474">
|
|
107
|
+
<meta name="x-apple-disable-message-reformatting">
|
|
108
|
+
<meta name="robots" content="noindex,follow">
|
|
109
|
+
|
|
50
110
|
</head>
|
|
111
|
+
<body style="-webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; height:100% !important; width:100% !important; margin:0 !important; padding:0; !important background-color:#f6f6f6" bgcolor="#f6f6f6">
|
|
51
112
|
|
|
52
|
-
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<img src="{{ projectLogo }}" alt="{{ projectName }}" width="130"
|
|
62
|
-
style="display: block;" />
|
|
63
|
-
</td>
|
|
64
|
-
</tr>
|
|
113
|
+
<!-- HIDDEN PREHEADER TEXT -->
|
|
114
|
+
<div class="preview-text" style="display:none;font-size:1px;color:#172940;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;"> </div>
|
|
115
|
+
|
|
116
|
+
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="-webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; mso-table-lspace:0pt; mso-table-rspace:0pt; border-collapse:collapse !important">
|
|
117
|
+
<tbody>
|
|
118
|
+
<tr>
|
|
119
|
+
<td id="background" align="center" valign="top" width="100%" bgcolor="#172940" style="-webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; mso-table-lspace:0pt; mso-table-rspace:0pt; background-size:cover; padding:50px 15px 0 15px; background-color:#172940">
|
|
120
|
+
<!--[if (gte mso 9)|(IE)]>
|
|
121
|
+
<table role="presentation" align="center" border="0" cellspacing="0" cellpadding="0" width="600">
|
|
65
122
|
<tr>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
123
|
+
<td align="center" valign="top" width="600">
|
|
124
|
+
<![endif]-->
|
|
125
|
+
<table role="presentation" align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="-webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; mso-table-lspace:0pt; mso-table-rspace:0pt; border-collapse:collapse !important; max-width:600px">
|
|
126
|
+
<tbody>
|
|
127
|
+
<tr>
|
|
128
|
+
<td align="left" valign="top" style="-webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; mso-table-lspace:0pt; mso-table-rspace:0pt; padding:0 0 30px 0">
|
|
129
|
+
<table><tbody><tr><td align="center" valign="middle" style="background-color:{{ projectColor }};width:48px;height:48px;border-radius:4px;padding:6px;">
|
|
130
|
+
<img id="logo" src="{{ projectLogo }}" alt="{{ projectName }} Logo" width="40" height="auto" border="0" style="-ms-interpolation-mode:bicubic; border:0; height:auto; line-height:100%; outline:none; text-decoration:none; display:block; width:40px;object-fit:contain;">
|
|
131
|
+
</td></tr></tbody></table>
|
|
132
|
+
</td>
|
|
133
|
+
</tr>
|
|
134
|
+
<tr>
|
|
135
|
+
<td id="content" align="left" valign="top" style="-webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; mso-table-lspace:0pt; mso-table-rspace:0pt; padding:40px 50px 50px 50px; font-family:Open Sans, Helvetica, Arial, sans-serif; border-radius:4px; box-shadow:0 4px 0 #15253A; background-color:#FFFFFE; color:#172940; font-size:15px; line-height:26px; margin:0" bgcolor="#FFFFFE">
|
|
136
|
+
<div id="hs_cos_wrapper_email_template_main_email_body" class="hs_cos_wrapper hs_cos_wrapper_widget hs_cos_wrapper_type_module" style="color: inherit; font-size: inherit; line-height: inherit;" data-hs-cos-general-type="widget" data-hs-cos-type="module">
|
|
137
|
+
|
|
71
138
|
{% block content %}{{ html }}{% endblock %}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<tr>
|
|
81
|
-
<td style="color:#b0bec5; font-family:Helvetica, Arial, sans-serif; font-size:12px;"
|
|
82
|
-
width="75%">
|
|
139
|
+
|
|
140
|
+
</div>
|
|
141
|
+
</td>
|
|
142
|
+
</tr>
|
|
143
|
+
<tr>
|
|
144
|
+
<td align="center" valign="middle" style="-webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; mso-table-lspace:0pt; mso-table-rspace:0pt; padding:25px 0; font-family:Open Sans, Helvetica, Arial, sans-serif; color:#FFFFFE">
|
|
145
|
+
<p style="margin-bottom: 1em; color: #A2B5CD;font-size: 12px; line-height: 16px;">
|
|
146
|
+
Sent by the team at {{ projectName }} — <a style="-webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; text-decoration:none; color:#A2B5CD" class="unsubscribe" data-unsubscribe="true" href="{{ url }}" data-hs-link-id="0" target="_blank">Manage Emails</a><br>
|
|
83
147
|
{% block footer %}{% endblock %}
|
|
84
|
-
</
|
|
85
|
-
</
|
|
86
|
-
</
|
|
87
|
-
</
|
|
148
|
+
</p>
|
|
149
|
+
</td>
|
|
150
|
+
</tr>
|
|
151
|
+
</tbody>
|
|
152
|
+
</table>
|
|
153
|
+
<!--[if (gte mso 9)|(IE)]>
|
|
154
|
+
</td>
|
|
88
155
|
</tr>
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
156
|
+
</table>
|
|
157
|
+
<![endif]-->
|
|
158
|
+
</td>
|
|
159
|
+
</tr>
|
|
160
|
+
</tbody>
|
|
92
161
|
</table>
|
|
93
162
|
</body>
|
|
94
|
-
|
|
95
163
|
</html>
|
|
@@ -7,17 +7,18 @@
|
|
|
7
7
|
|
|
8
8
|
<p style="text-align: center; padding: 20px 0;">
|
|
9
9
|
<a href="{{ url }}">
|
|
10
|
-
|
|
10
|
+
<b>Reset Your Password</b>
|
|
11
11
|
</a>
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
14
|
<p>
|
|
15
15
|
<b>Important: This link will expire in 24 hours.</b>
|
|
16
|
+
<br>
|
|
16
17
|
</p>
|
|
17
18
|
|
|
18
19
|
<p>
|
|
19
20
|
Thank you,<br>
|
|
20
|
-
{{ projectName }}
|
|
21
|
+
The {{ projectName }} Team
|
|
21
22
|
</p>
|
|
22
23
|
|
|
23
24
|
{% endblock %}
|