@technomoron/mail-magic 1.0.40 → 1.0.42
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/CHANGES +45 -2
- package/README.md +5 -0
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +1 -1
- package/dist/esm/api/assets.d.ts +11 -0
- package/dist/esm/api/assets.js +48 -18
- package/dist/esm/api/auth.d.ts +2 -0
- package/dist/esm/api/auth.js +18 -9
- package/dist/esm/api/forms.d.ts +9 -0
- package/dist/esm/api/forms.js +42 -7
- package/dist/esm/api/mailer.d.ts +11 -0
- package/dist/esm/api/mailer.js +37 -8
- package/dist/esm/bin/mail-magic.d.ts +2 -0
- package/dist/esm/index.d.ts +12 -0
- package/dist/esm/index.js +5 -4
- package/dist/esm/models/db.d.ts +5 -0
- package/dist/esm/models/domain.d.ts +24 -0
- package/dist/esm/models/form.d.ts +50 -0
- package/dist/esm/models/form.js +16 -13
- package/dist/esm/models/init.d.ts +12 -0
- package/dist/esm/models/recipient.d.ts +24 -0
- package/dist/esm/models/txmail.d.ts +42 -0
- package/dist/esm/models/user.d.ts +33 -0
- package/dist/esm/server.d.ts +8 -0
- package/dist/esm/store/envloader.d.ts +188 -0
- package/dist/esm/store/envloader.js +9 -4
- package/dist/esm/store/store.d.ts +38 -0
- package/dist/esm/store/store.js +20 -16
- package/dist/esm/swagger.d.ts +10 -0
- package/dist/esm/types.d.ts +36 -0
- package/dist/esm/util/captcha.d.ts +7 -0
- package/dist/esm/util/captcha.js +4 -1
- package/dist/esm/util/email.d.ts +3 -0
- package/dist/esm/util/form-replyto.d.ts +6 -0
- package/dist/esm/util/form-submission.d.ts +24 -0
- package/dist/esm/util/forms.d.ts +140 -0
- package/dist/esm/util/forms.js +42 -39
- package/dist/esm/util/paths.d.ts +15 -0
- package/dist/esm/util/paths.js +17 -0
- package/dist/esm/util/ratelimit.d.ts +7 -0
- package/dist/esm/util/ratelimit.js +10 -41
- package/dist/esm/util/route.d.ts +1 -0
- package/dist/esm/util/shared-template-flatten.d.ts +17 -0
- package/dist/esm/util/uploads.d.ts +11 -0
- package/dist/esm/util/uploads.js +16 -11
- package/dist/esm/util/utils.d.ts +25 -0
- package/dist/esm/util/utils.js +0 -18
- package/dist/esm/util.d.ts +7 -0
- package/docs/swagger/openapi.json +16 -12
- package/examples/.env-dist +21 -0
- package/examples/README.md +74 -0
- package/examples/data/example.test/form-template/base.njk +4 -0
- package/examples/data/example.test/form-template/en/base.njk +1 -0
- package/examples/data/example.test/form-template/en/change-password.njk +5 -0
- package/examples/data/example.test/form-template/en/confirm-account.njk +5 -0
- package/examples/data/example.test/form-template/en/contact.njk +5 -0
- package/examples/data/example.test/form-template/en/partials/fields.njk +5 -0
- package/examples/data/example.test/form-template/en/welcome-signup.njk +5 -0
- package/examples/data/example.test/form-template/nb/base.njk +1 -0
- package/examples/data/example.test/form-template/nb/change-password.njk +5 -0
- package/examples/data/example.test/form-template/nb/confirm-account.njk +5 -0
- package/examples/data/example.test/form-template/nb/contact.njk +5 -0
- package/examples/data/example.test/form-template/nb/partials/fields.njk +5 -0
- package/examples/data/example.test/form-template/nb/welcome-signup.njk +5 -0
- package/examples/data/example.test/form-template/partials/header.njk +1 -0
- package/examples/data/example.test/tx-template/base.njk +16 -0
- package/examples/data/example.test/tx-template/en/base.njk +1 -0
- package/examples/data/example.test/tx-template/en/change-password.njk +7 -0
- package/examples/data/example.test/tx-template/en/confirm.njk +6 -0
- package/examples/data/example.test/tx-template/en/invoice.njk +8 -0
- package/examples/data/example.test/tx-template/en/partials/header.njk +1 -0
- package/examples/data/example.test/tx-template/en/partials/line-items.njk +14 -0
- package/examples/data/example.test/tx-template/en/receipt.njk +7 -0
- package/examples/data/example.test/tx-template/en/welcome.njk +5 -0
- package/examples/data/example.test/tx-template/nb/base.njk +1 -0
- package/examples/data/example.test/tx-template/nb/change-password.njk +6 -0
- package/examples/data/example.test/tx-template/nb/confirm.njk +6 -0
- package/examples/data/example.test/tx-template/nb/invoice.njk +7 -0
- package/examples/data/example.test/tx-template/nb/partials/header.njk +1 -0
- package/examples/data/example.test/tx-template/nb/receipt.njk +6 -0
- package/examples/data/example.test/tx-template/nb/welcome.njk +5 -0
- package/examples/data/example.test/tx-template/partials/header.njk +7 -0
- package/examples/data/init-data.json +213 -0
- package/examples/scripts/mm-api.ts +206 -0
- package/examples/scripts/public-form.ts +100 -0
- package/examples/scripts/send-messages.ts +114 -0
- package/package.json +7 -5
package/dist/esm/models/form.js
CHANGED
|
@@ -2,7 +2,7 @@ import path from 'path';
|
|
|
2
2
|
import { nanoid } from 'nanoid';
|
|
3
3
|
import { Model, DataTypes, UniqueConstraintError } from 'sequelize';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { assertSafeRelativePath } from '../util/paths.js';
|
|
5
|
+
import { assertSafeRelativePath, buildFormSlugAndFilename } from '../util/paths.js';
|
|
6
6
|
import { user_and_domain, normalizeSlug } from '../util.js';
|
|
7
7
|
const stored_file_schema = z
|
|
8
8
|
.object({
|
|
@@ -173,7 +173,9 @@ export async function init_api_form(api_db) {
|
|
|
173
173
|
}
|
|
174
174
|
try {
|
|
175
175
|
const parsed = JSON.parse(raw);
|
|
176
|
-
return Array.isArray(parsed)
|
|
176
|
+
return Array.isArray(parsed)
|
|
177
|
+
? parsed.filter((item) => typeof item === 'string')
|
|
178
|
+
: [];
|
|
177
179
|
}
|
|
178
180
|
catch {
|
|
179
181
|
return [];
|
|
@@ -231,23 +233,24 @@ export async function init_api_form(api_db) {
|
|
|
231
233
|
export async function upsert_form(record) {
|
|
232
234
|
const { user, domain } = await user_and_domain(record.domain_id);
|
|
233
235
|
const dname = normalizeSlug(domain.name);
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
+
const { slug, filename: generatedFilename } = buildFormSlugAndFilename({
|
|
237
|
+
domainName: domain.name,
|
|
238
|
+
domainLocale: domain.locale,
|
|
239
|
+
userLocale: user.locale,
|
|
240
|
+
idname: record.idname,
|
|
241
|
+
locale: record.locale
|
|
242
|
+
});
|
|
236
243
|
if (!record.slug) {
|
|
237
|
-
record.slug =
|
|
244
|
+
record.slug = slug;
|
|
238
245
|
}
|
|
239
246
|
if (!record.filename) {
|
|
240
|
-
|
|
241
|
-
if (locale)
|
|
242
|
-
parts.push(locale);
|
|
243
|
-
parts.push(name);
|
|
244
|
-
record.filename = path.join(...parts);
|
|
247
|
+
record.filename = generatedFilename;
|
|
245
248
|
}
|
|
246
249
|
else {
|
|
247
250
|
record.filename = path.join(dname, 'form-template', record.filename);
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
+
if (!record.filename.endsWith('.njk')) {
|
|
252
|
+
record.filename += '.njk';
|
|
253
|
+
}
|
|
251
254
|
}
|
|
252
255
|
record.filename = assertSafeRelativePath(record.filename, 'Form filename');
|
|
253
256
|
let instance = null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { mailStore } from '../store/store.js';
|
|
2
|
+
import { StoredFile } from '../types.js';
|
|
3
|
+
import { api_form_type } from './form.js';
|
|
4
|
+
import { api_txmail_type } from './txmail.js';
|
|
5
|
+
interface LoadedTemplate {
|
|
6
|
+
html: string;
|
|
7
|
+
assets: StoredFile[];
|
|
8
|
+
}
|
|
9
|
+
export declare function loadFormTemplate(store: mailStore, form: api_form_type): Promise<LoadedTemplate>;
|
|
10
|
+
export declare function loadTxTemplate(store: mailStore, template: api_txmail_type): Promise<LoadedTemplate>;
|
|
11
|
+
export declare function importData(store: mailStore): Promise<void>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Sequelize, Model } from 'sequelize';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare const api_recipient_schema: z.ZodObject<{
|
|
4
|
+
recipient_id: z.ZodNumber;
|
|
5
|
+
domain_id: z.ZodNumber;
|
|
6
|
+
form_key: z.ZodDefault<z.ZodString>;
|
|
7
|
+
idname: z.ZodString;
|
|
8
|
+
email: z.ZodString;
|
|
9
|
+
name: z.ZodDefault<z.ZodString>;
|
|
10
|
+
}, z.core.$strip>;
|
|
11
|
+
export type api_recipient_input = z.input<typeof api_recipient_schema>;
|
|
12
|
+
export type api_recipient_type = z.output<typeof api_recipient_schema>;
|
|
13
|
+
export type api_recipient_creation_type = Omit<api_recipient_input, 'recipient_id'> & {
|
|
14
|
+
recipient_id?: number;
|
|
15
|
+
};
|
|
16
|
+
export declare class api_recipient extends Model<api_recipient_type, api_recipient_creation_type> {
|
|
17
|
+
recipient_id: number;
|
|
18
|
+
domain_id: number;
|
|
19
|
+
form_key: string;
|
|
20
|
+
idname: string;
|
|
21
|
+
email: string;
|
|
22
|
+
name: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function init_api_recipient(api_db: Sequelize): Promise<typeof api_recipient>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Sequelize, Model } from 'sequelize';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { StoredFile } from '../types.js';
|
|
4
|
+
export declare const api_txmail_schema: z.ZodObject<{
|
|
5
|
+
template_id: z.ZodNumber;
|
|
6
|
+
user_id: z.ZodNumber;
|
|
7
|
+
domain_id: z.ZodNumber;
|
|
8
|
+
name: z.ZodString;
|
|
9
|
+
locale: z.ZodDefault<z.ZodString>;
|
|
10
|
+
template: z.ZodDefault<z.ZodString>;
|
|
11
|
+
filename: z.ZodDefault<z.ZodString>;
|
|
12
|
+
sender: z.ZodString;
|
|
13
|
+
subject: z.ZodString;
|
|
14
|
+
slug: z.ZodDefault<z.ZodString>;
|
|
15
|
+
part: z.ZodDefault<z.ZodBoolean>;
|
|
16
|
+
files: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
17
|
+
filename: z.ZodString;
|
|
18
|
+
path: z.ZodString;
|
|
19
|
+
cid: z.ZodOptional<z.ZodString>;
|
|
20
|
+
}, z.core.$strip>>>;
|
|
21
|
+
}, z.core.$strip>;
|
|
22
|
+
export type api_txmail_input = z.input<typeof api_txmail_schema>;
|
|
23
|
+
export type api_txmail_type = z.output<typeof api_txmail_schema>;
|
|
24
|
+
export type api_txmail_creation_type = Omit<api_txmail_input, 'template_id'> & {
|
|
25
|
+
template_id?: number;
|
|
26
|
+
};
|
|
27
|
+
export declare class api_txmail extends Model<api_txmail_type, api_txmail_creation_type> {
|
|
28
|
+
template_id: number;
|
|
29
|
+
user_id: number;
|
|
30
|
+
domain_id: number;
|
|
31
|
+
name: string;
|
|
32
|
+
locale: string;
|
|
33
|
+
template: string;
|
|
34
|
+
filename: string;
|
|
35
|
+
sender: string;
|
|
36
|
+
subject: string;
|
|
37
|
+
slug: string;
|
|
38
|
+
part: boolean;
|
|
39
|
+
files: StoredFile[];
|
|
40
|
+
}
|
|
41
|
+
export declare function upsert_txmail(record: api_txmail_type): Promise<api_txmail>;
|
|
42
|
+
export declare function init_api_txmail(api_db: Sequelize): Promise<typeof api_txmail>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Sequelize, Model } from 'sequelize';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare const api_user_schema: z.ZodObject<{
|
|
4
|
+
user_id: z.ZodNumber;
|
|
5
|
+
idname: z.ZodString;
|
|
6
|
+
token: z.ZodOptional<z.ZodString>;
|
|
7
|
+
token_hmac: z.ZodOptional<z.ZodString>;
|
|
8
|
+
name: z.ZodString;
|
|
9
|
+
email: z.ZodString;
|
|
10
|
+
domain: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
11
|
+
locale: z.ZodDefault<z.ZodString>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
export type api_user_input = z.input<typeof api_user_schema>;
|
|
14
|
+
export type api_user_type = z.output<typeof api_user_schema>;
|
|
15
|
+
export type api_user_creation_type = Omit<api_user_input, 'user_id'> & {
|
|
16
|
+
user_id?: number;
|
|
17
|
+
};
|
|
18
|
+
export declare class api_user extends Model<api_user_type, api_user_creation_type> {
|
|
19
|
+
user_id: number;
|
|
20
|
+
idname: string;
|
|
21
|
+
token: string | undefined;
|
|
22
|
+
token_hmac: string | undefined;
|
|
23
|
+
name: string;
|
|
24
|
+
email: string;
|
|
25
|
+
domain: number | null | undefined;
|
|
26
|
+
locale: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function apiTokenToHmac(token: string, pepper: string): string;
|
|
29
|
+
export declare function migrateLegacyApiTokens(pepper: string): Promise<{
|
|
30
|
+
migrated: number;
|
|
31
|
+
cleared: number;
|
|
32
|
+
}>;
|
|
33
|
+
export declare function init_api_user(api_db: Sequelize): Promise<typeof api_user>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ApiServerConf, ApiServer } from '@technomoron/api-server-base';
|
|
2
|
+
import { mailStore } from './store/store.js';
|
|
3
|
+
export declare class mailApiServer extends ApiServer {
|
|
4
|
+
private store;
|
|
5
|
+
storage: mailStore;
|
|
6
|
+
constructor(config: Partial<ApiServerConf>, store: mailStore);
|
|
7
|
+
getApiKey<ApiKey>(token: string): Promise<ApiKey | null>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
export declare const envOptions: {
|
|
2
|
+
NODE_ENV: {
|
|
3
|
+
description: string;
|
|
4
|
+
options: string[];
|
|
5
|
+
default: string;
|
|
6
|
+
};
|
|
7
|
+
API_PORT: {
|
|
8
|
+
description: string;
|
|
9
|
+
default: string;
|
|
10
|
+
type: "number";
|
|
11
|
+
};
|
|
12
|
+
API_HOST: {
|
|
13
|
+
description: string;
|
|
14
|
+
default: string;
|
|
15
|
+
};
|
|
16
|
+
DB_AUTO_RELOAD: {
|
|
17
|
+
description: string;
|
|
18
|
+
type: "boolean";
|
|
19
|
+
default: false;
|
|
20
|
+
};
|
|
21
|
+
DB_FORCE_SYNC: {
|
|
22
|
+
description: string;
|
|
23
|
+
type: "boolean";
|
|
24
|
+
default: false;
|
|
25
|
+
};
|
|
26
|
+
DB_SYNC_ALTER: {
|
|
27
|
+
description: string;
|
|
28
|
+
type: "boolean";
|
|
29
|
+
default: false;
|
|
30
|
+
};
|
|
31
|
+
API_URL: {
|
|
32
|
+
description: string;
|
|
33
|
+
default: string;
|
|
34
|
+
};
|
|
35
|
+
API_BASE_PATH: {
|
|
36
|
+
description: string;
|
|
37
|
+
default: string;
|
|
38
|
+
};
|
|
39
|
+
ASSET_PUBLIC_BASE: {
|
|
40
|
+
description: string;
|
|
41
|
+
default: string;
|
|
42
|
+
};
|
|
43
|
+
SWAGGER_ENABLED: {
|
|
44
|
+
description: string;
|
|
45
|
+
type: "boolean";
|
|
46
|
+
default: false;
|
|
47
|
+
};
|
|
48
|
+
SWAGGER_PATH: {
|
|
49
|
+
description: string;
|
|
50
|
+
default: string;
|
|
51
|
+
};
|
|
52
|
+
ADMIN_ENABLED: {
|
|
53
|
+
description: string;
|
|
54
|
+
default: false;
|
|
55
|
+
type: "boolean";
|
|
56
|
+
};
|
|
57
|
+
ADMIN_APP_PATH: {
|
|
58
|
+
description: string;
|
|
59
|
+
default: string;
|
|
60
|
+
};
|
|
61
|
+
ASSET_ROUTE: {
|
|
62
|
+
description: string;
|
|
63
|
+
default: string;
|
|
64
|
+
};
|
|
65
|
+
CONFIG_PATH: {
|
|
66
|
+
description: string;
|
|
67
|
+
default: string;
|
|
68
|
+
};
|
|
69
|
+
GEN_ENV_TEMPLATE: {
|
|
70
|
+
description: string;
|
|
71
|
+
default: false;
|
|
72
|
+
type: "boolean";
|
|
73
|
+
};
|
|
74
|
+
DB_USER: {
|
|
75
|
+
description: string;
|
|
76
|
+
};
|
|
77
|
+
DB_PASS: {
|
|
78
|
+
description: string;
|
|
79
|
+
};
|
|
80
|
+
DB_NAME: {
|
|
81
|
+
description: string;
|
|
82
|
+
default: string;
|
|
83
|
+
};
|
|
84
|
+
DB_HOST: {
|
|
85
|
+
description: string;
|
|
86
|
+
default: string;
|
|
87
|
+
};
|
|
88
|
+
DB_TYPE: {
|
|
89
|
+
description: string;
|
|
90
|
+
options: string[];
|
|
91
|
+
default: string;
|
|
92
|
+
};
|
|
93
|
+
DB_LOG: {
|
|
94
|
+
description: string;
|
|
95
|
+
default: string;
|
|
96
|
+
type: "boolean";
|
|
97
|
+
};
|
|
98
|
+
DEBUG: {
|
|
99
|
+
description: string;
|
|
100
|
+
default: false;
|
|
101
|
+
type: "boolean";
|
|
102
|
+
};
|
|
103
|
+
AUTOESCAPE_HTML: {
|
|
104
|
+
description: string;
|
|
105
|
+
default: true;
|
|
106
|
+
type: "boolean";
|
|
107
|
+
};
|
|
108
|
+
SMTP_HOST: {
|
|
109
|
+
description: string;
|
|
110
|
+
default: string;
|
|
111
|
+
};
|
|
112
|
+
SMTP_PORT: {
|
|
113
|
+
description: string;
|
|
114
|
+
default: number;
|
|
115
|
+
type: "number";
|
|
116
|
+
};
|
|
117
|
+
SMTP_SECURE: {
|
|
118
|
+
description: string;
|
|
119
|
+
default: false;
|
|
120
|
+
type: "boolean";
|
|
121
|
+
};
|
|
122
|
+
SMTP_TLS_REJECT: {
|
|
123
|
+
description: string;
|
|
124
|
+
default: true;
|
|
125
|
+
type: "boolean";
|
|
126
|
+
};
|
|
127
|
+
SMTP_REQUIRE_TLS: {
|
|
128
|
+
description: string;
|
|
129
|
+
default: true;
|
|
130
|
+
type: "boolean";
|
|
131
|
+
};
|
|
132
|
+
SMTP_USER: {
|
|
133
|
+
description: string;
|
|
134
|
+
default: string;
|
|
135
|
+
};
|
|
136
|
+
SMTP_PASSWORD: {
|
|
137
|
+
description: string;
|
|
138
|
+
default: string;
|
|
139
|
+
};
|
|
140
|
+
UPLOAD_PATH: {
|
|
141
|
+
description: string;
|
|
142
|
+
default: string;
|
|
143
|
+
};
|
|
144
|
+
UPLOAD_MAX: {
|
|
145
|
+
description: string;
|
|
146
|
+
default: number;
|
|
147
|
+
type: "number";
|
|
148
|
+
};
|
|
149
|
+
FORM_RATE_LIMIT_WINDOW_SEC: {
|
|
150
|
+
description: string;
|
|
151
|
+
default: number;
|
|
152
|
+
type: "number";
|
|
153
|
+
};
|
|
154
|
+
FORM_RATE_LIMIT_MAX: {
|
|
155
|
+
description: string;
|
|
156
|
+
default: number;
|
|
157
|
+
type: "number";
|
|
158
|
+
};
|
|
159
|
+
FORM_MAX_ATTACHMENTS: {
|
|
160
|
+
description: string;
|
|
161
|
+
default: number;
|
|
162
|
+
type: "number";
|
|
163
|
+
};
|
|
164
|
+
FORM_KEEP_UPLOADS: {
|
|
165
|
+
description: string;
|
|
166
|
+
default: true;
|
|
167
|
+
type: "boolean";
|
|
168
|
+
};
|
|
169
|
+
FORM_CAPTCHA_PROVIDER: {
|
|
170
|
+
description: string;
|
|
171
|
+
options: string[];
|
|
172
|
+
default: string;
|
|
173
|
+
};
|
|
174
|
+
FORM_CAPTCHA_SECRET: {
|
|
175
|
+
description: string;
|
|
176
|
+
default: string;
|
|
177
|
+
};
|
|
178
|
+
FORM_CAPTCHA_REQUIRED: {
|
|
179
|
+
description: string;
|
|
180
|
+
default: false;
|
|
181
|
+
type: "boolean";
|
|
182
|
+
};
|
|
183
|
+
API_TOKEN_PEPPER: {
|
|
184
|
+
description: string;
|
|
185
|
+
required: true;
|
|
186
|
+
transform: (raw: string) => string;
|
|
187
|
+
};
|
|
188
|
+
};
|
|
@@ -27,7 +27,7 @@ export const envOptions = defineEnvOptions({
|
|
|
27
27
|
DB_SYNC_ALTER: {
|
|
28
28
|
description: 'Alter existing tables on startup to match models (requires write access to the DB)',
|
|
29
29
|
type: 'boolean',
|
|
30
|
-
default:
|
|
30
|
+
default: false
|
|
31
31
|
},
|
|
32
32
|
API_URL: {
|
|
33
33
|
description: 'Sets the public URL for the API (i.e. https://ml.example.com:3790)',
|
|
@@ -88,7 +88,7 @@ export const envOptions = defineEnvOptions({
|
|
|
88
88
|
},
|
|
89
89
|
DB_TYPE: {
|
|
90
90
|
description: 'Database type for the API database',
|
|
91
|
-
options: ['sqlite'],
|
|
91
|
+
options: ['sqlite', 'mysql', 'postgres'],
|
|
92
92
|
default: 'sqlite'
|
|
93
93
|
},
|
|
94
94
|
DB_LOG: {
|
|
@@ -121,8 +121,13 @@ export const envOptions = defineEnvOptions({
|
|
|
121
121
|
type: 'boolean'
|
|
122
122
|
},
|
|
123
123
|
SMTP_TLS_REJECT: {
|
|
124
|
-
description: '
|
|
125
|
-
default:
|
|
124
|
+
description: 'Validate the SMTP server TLS certificate. Set false to allow self-signed certificates.',
|
|
125
|
+
default: true,
|
|
126
|
+
type: 'boolean'
|
|
127
|
+
},
|
|
128
|
+
SMTP_REQUIRE_TLS: {
|
|
129
|
+
description: 'Require STARTTLS upgrade on SMTP connection. Set false for servers that do not support STARTTLS (e.g. MailHog).',
|
|
130
|
+
default: true,
|
|
126
131
|
type: 'boolean'
|
|
127
132
|
},
|
|
128
133
|
SMTP_USER: {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { envConfig } from '@technomoron/env-loader';
|
|
2
|
+
import { Transporter } from 'nodemailer';
|
|
3
|
+
import { Sequelize } from 'sequelize';
|
|
4
|
+
import { envOptions } from './envloader.js';
|
|
5
|
+
import type SMTPTransport from 'nodemailer/lib/smtp-transport';
|
|
6
|
+
type UploadedFile = {
|
|
7
|
+
fieldname?: string;
|
|
8
|
+
originalname?: string;
|
|
9
|
+
filepath?: string;
|
|
10
|
+
buffer?: Buffer;
|
|
11
|
+
};
|
|
12
|
+
export type MailStoreVars = envConfig<typeof envOptions>;
|
|
13
|
+
type AutoReloadHandle = {
|
|
14
|
+
close: () => void;
|
|
15
|
+
};
|
|
16
|
+
type AutoReloadContext = {
|
|
17
|
+
vars: Pick<MailStoreVars, 'DB_AUTO_RELOAD'>;
|
|
18
|
+
config_filename: (name: string) => string;
|
|
19
|
+
print_debug: (msg: string) => void;
|
|
20
|
+
};
|
|
21
|
+
export declare function enableInitDataAutoReload(ctx: AutoReloadContext, reload: () => void): AutoReloadHandle | null;
|
|
22
|
+
export declare class mailStore {
|
|
23
|
+
private env;
|
|
24
|
+
vars: MailStoreVars;
|
|
25
|
+
transport?: Transporter<SMTPTransport.SentMessageInfo>;
|
|
26
|
+
api_db: Sequelize | null;
|
|
27
|
+
configpath: string;
|
|
28
|
+
uploadTemplate?: string;
|
|
29
|
+
uploadStagingPath?: string;
|
|
30
|
+
autoReloadHandle: AutoReloadHandle | null;
|
|
31
|
+
print_debug(msg: string): void;
|
|
32
|
+
config_filename(name: string): string;
|
|
33
|
+
resolveUploadPath(domainName?: string): string;
|
|
34
|
+
getUploadStagingPath(): string;
|
|
35
|
+
relocateUploads(domainName: string | null, files: UploadedFile[]): Promise<void>;
|
|
36
|
+
init(overrides?: Partial<MailStoreVars>): Promise<this>;
|
|
37
|
+
}
|
|
38
|
+
export {};
|
package/dist/esm/store/store.js
CHANGED
|
@@ -13,7 +13,7 @@ function create_mail_transport(vars) {
|
|
|
13
13
|
tls: {
|
|
14
14
|
rejectUnauthorized: vars.SMTP_TLS_REJECT
|
|
15
15
|
},
|
|
16
|
-
requireTLS:
|
|
16
|
+
requireTLS: vars.SMTP_REQUIRE_TLS,
|
|
17
17
|
logger: vars.DEBUG,
|
|
18
18
|
debug: vars.DEBUG
|
|
19
19
|
};
|
|
@@ -102,24 +102,28 @@ export class mailStore {
|
|
|
102
102
|
}
|
|
103
103
|
await fs.promises.mkdir(targetDir, { recursive: true });
|
|
104
104
|
await Promise.all(files.map(async (file) => {
|
|
105
|
-
|
|
105
|
+
const name = (file.originalname ?? file.filepath) ? path.basename(file.filepath ?? file.originalname ?? '') : '';
|
|
106
|
+
if (!name) {
|
|
106
107
|
return;
|
|
107
108
|
}
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
await fs.promises.rename(file.path, destination);
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
await fs.promises.copyFile(file.path, destination);
|
|
118
|
-
await fs.promises.unlink(file.path);
|
|
109
|
+
const destination = path.join(targetDir, name);
|
|
110
|
+
if (file.buffer) {
|
|
111
|
+
await fs.promises.writeFile(destination, file.buffer);
|
|
112
|
+
file.filepath = destination;
|
|
113
|
+
file.buffer = undefined;
|
|
119
114
|
}
|
|
120
|
-
file.
|
|
121
|
-
|
|
122
|
-
|
|
115
|
+
else if (file.filepath) {
|
|
116
|
+
if (destination === file.filepath) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
await fs.promises.rename(file.filepath, destination);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
await fs.promises.copyFile(file.filepath, destination);
|
|
124
|
+
await fs.promises.unlink(file.filepath);
|
|
125
|
+
}
|
|
126
|
+
file.filepath = destination;
|
|
123
127
|
}
|
|
124
128
|
}));
|
|
125
129
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { mailApiServer } from './server.js';
|
|
2
|
+
type SwaggerInstallOptions = {
|
|
3
|
+
apiBasePath: string;
|
|
4
|
+
assetRoute: string;
|
|
5
|
+
apiUrl: string;
|
|
6
|
+
swaggerEnabled?: boolean;
|
|
7
|
+
swaggerPath?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function installMailMagicSwagger(server: mailApiServer, opts: SwaggerInstallOptions): void;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ApiRequest } from '@technomoron/api-server-base';
|
|
2
|
+
import { api_domain } from './models/domain.js';
|
|
3
|
+
import { api_user } from './models/user.js';
|
|
4
|
+
export interface mailApiKey {
|
|
5
|
+
uid: number;
|
|
6
|
+
}
|
|
7
|
+
export interface mailApiRequest extends ApiRequest {
|
|
8
|
+
user?: api_user;
|
|
9
|
+
domain?: api_domain;
|
|
10
|
+
locale?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface formType {
|
|
13
|
+
rcpt: string;
|
|
14
|
+
sender: string;
|
|
15
|
+
subject: string;
|
|
16
|
+
template: string;
|
|
17
|
+
}
|
|
18
|
+
export interface StoredFile {
|
|
19
|
+
filename: string;
|
|
20
|
+
path: string;
|
|
21
|
+
cid?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface RequestMeta {
|
|
24
|
+
client_ip: string;
|
|
25
|
+
received_at: string;
|
|
26
|
+
ip_chain: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface UploadedFile {
|
|
29
|
+
fieldname: string;
|
|
30
|
+
originalname: string;
|
|
31
|
+
encoding?: string;
|
|
32
|
+
mimetype?: string;
|
|
33
|
+
size?: number;
|
|
34
|
+
buffer?: Buffer;
|
|
35
|
+
filepath?: string;
|
|
36
|
+
}
|
package/dist/esm/util/captcha.js
CHANGED
|
@@ -4,7 +4,10 @@ export async function verifyCaptcha(params) {
|
|
|
4
4
|
hcaptcha: 'https://hcaptcha.com/siteverify',
|
|
5
5
|
recaptcha: 'https://www.google.com/recaptcha/api/siteverify'
|
|
6
6
|
};
|
|
7
|
-
const endpoint = endpoints[params.provider]
|
|
7
|
+
const endpoint = endpoints[params.provider];
|
|
8
|
+
if (!endpoint) {
|
|
9
|
+
throw new Error(`Unknown CAPTCHA provider: "${params.provider}"`);
|
|
10
|
+
}
|
|
8
11
|
const body = new URLSearchParams();
|
|
9
12
|
body.set('secret', params.secret);
|
|
10
13
|
body.set('response', params.token);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const form_submission_schema: z.ZodObject<{
|
|
3
|
+
_mm_form_key: z.ZodString;
|
|
4
|
+
_mm_locale: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
5
|
+
_mm_recipients: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
6
|
+
email: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
7
|
+
name: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
8
|
+
first_name: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
9
|
+
last_name: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
10
|
+
'cf-turnstile-response': z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
11
|
+
'h-captcha-response': z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
12
|
+
'g-recaptcha-response': z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
13
|
+
captcha: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
14
|
+
}, z.core.$loose>;
|
|
15
|
+
export type ParsedFormSubmission = {
|
|
16
|
+
mm: {
|
|
17
|
+
form_key: string;
|
|
18
|
+
locale: string;
|
|
19
|
+
captcha_token: string;
|
|
20
|
+
recipients_raw: unknown;
|
|
21
|
+
};
|
|
22
|
+
fields: Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
export declare function parseFormSubmissionInput(raw: unknown): ParsedFormSubmission;
|