@technomoron/mail-magic 1.0.16 → 1.0.23
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 +48 -0
- package/dist/api/assets.js +19 -8
- package/dist/api/auth.js +7 -2
- package/dist/api/forms.js +480 -82
- package/dist/api/mailer.js +2 -2
- package/dist/index.js +57 -25
- package/dist/models/db.js +29 -2
- package/dist/models/form.js +23 -2
- package/dist/models/init.js +13 -3
- package/dist/models/recipient.js +65 -0
- package/dist/models/user.js +40 -3
- package/dist/server.js +19 -7
- package/dist/store/envloader.js +55 -0
- package/dist/store/store.js +10 -1
- package/dist/util.js +16 -6
- package/package.json +4 -3
package/CHANGES
CHANGED
|
@@ -1,3 +1,51 @@
|
|
|
1
|
+
Version 1.0.23 (2026-02-07)
|
|
2
|
+
|
|
3
|
+
- Added a form recipient allowlist table plus `POST /api/v1/form/recipient` to map a
|
|
4
|
+
public recipient `idname` to an email address (with optional display name),
|
|
5
|
+
scoped per domain and optionally per `form_key`.
|
|
6
|
+
- `POST /api/v1/form/message` now supports `recipient_idname` so public forms can
|
|
7
|
+
select a configured recipient without exposing email addresses client-side.
|
|
8
|
+
- Added integration tests covering recipient allowlist fallbacks, overrides, locale scoping,
|
|
9
|
+
validation, and ownership checks.
|
|
10
|
+
|
|
11
|
+
Version 1.0.22 (2026-02-07)
|
|
12
|
+
|
|
13
|
+
- Added opt-in anti-abuse controls for public form submissions, including:
|
|
14
|
+
`UPLOAD_MAX`, in-memory IP rate limiting, optional CAPTCHA verification
|
|
15
|
+
(Turnstile/hCaptcha/reCAPTCHA) with per-form `captcha_required`, optional
|
|
16
|
+
attachment count limits, and optional upload cleanup.
|
|
17
|
+
|
|
18
|
+
Version 1.0.21 (2026-02-07)
|
|
19
|
+
|
|
20
|
+
- Added `AUTOESCAPE_HTML` (default: true) to control Nunjucks HTML autoescaping
|
|
21
|
+
when rendering transactional and form templates.
|
|
22
|
+
|
|
23
|
+
Version 1.0.20 (2026-02-07)
|
|
24
|
+
|
|
25
|
+
- Centralized env parsing in `mailStore` (no direct `process.env` reads in core
|
|
26
|
+
server code).
|
|
27
|
+
|
|
28
|
+
Version 1.0.19 (2026-02-07)
|
|
29
|
+
|
|
30
|
+
- Store API tokens as HMAC-SHA256 (with required `API_TOKEN_PEPPER`) instead of
|
|
31
|
+
plaintext, and migrate legacy plaintext tokens on startup.
|
|
32
|
+
|
|
33
|
+
Version 1.0.18 (2026-02-07)
|
|
34
|
+
|
|
35
|
+
- Added a stable `form_key` (generated via nanoid) to uniquely identify forms and
|
|
36
|
+
return it from `POST /api/v1/form/template`.
|
|
37
|
+
- Updated `POST /api/v1/form/message` form lookup to use `form_key` (preferred),
|
|
38
|
+
otherwise require `domain` + `formid` (+ optional `locale`) to avoid ambiguous
|
|
39
|
+
cross-domain/locale matches.
|
|
40
|
+
|
|
41
|
+
Version 1.0.17 (2026-02-07)
|
|
42
|
+
|
|
43
|
+
- Reduced sensitive logging by disabling env dumps unless `DEBUG` is enabled,
|
|
44
|
+
removing API tokens from debug output and API errors, and redacting DB
|
|
45
|
+
credentials in debug logs.
|
|
46
|
+
- Fixed TypeScript build issues by aligning Express typings and updating
|
|
47
|
+
Express-related utility types.
|
|
48
|
+
|
|
1
49
|
Version 1.0.15 (2026-02-01)
|
|
2
50
|
|
|
3
51
|
- Made the optional admin UI/API opt-in via `ADMIN_ENABLED`, delegating admin
|
package/dist/api/assets.js
CHANGED
|
@@ -144,17 +144,27 @@ export class AssetAPI extends ApiModule {
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
export function createAssetHandler(server) {
|
|
147
|
-
return async (req, res) => {
|
|
147
|
+
return async (req, res, next) => {
|
|
148
|
+
if (req.method && req.method !== 'GET' && req.method !== 'HEAD') {
|
|
149
|
+
if (next) {
|
|
150
|
+
next();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
res.status(405).end();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
148
156
|
const domain = decodeComponent(req?.params?.domain);
|
|
149
157
|
if (!domain || !DOMAIN_PATTERN.test(domain)) {
|
|
150
158
|
res.status(404).end();
|
|
151
159
|
return;
|
|
152
160
|
}
|
|
153
|
-
const
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
161
|
+
const rawPathParam = req?.params?.path ?? req?.params?.[0];
|
|
162
|
+
const rawSegments = Array.isArray(rawPathParam)
|
|
163
|
+
? rawPathParam
|
|
164
|
+
: typeof rawPathParam === 'string'
|
|
165
|
+
? rawPathParam.split('/').filter(Boolean)
|
|
166
|
+
: [];
|
|
167
|
+
const segments = rawSegments.map((segment) => decodeComponent(typeof segment === 'string' ? segment : ''));
|
|
158
168
|
if (!segments.length || segments.some((segment) => !SEGMENT_PATTERN.test(segment))) {
|
|
159
169
|
res.status(404).end();
|
|
160
170
|
return;
|
|
@@ -191,9 +201,10 @@ export function createAssetHandler(server) {
|
|
|
191
201
|
return;
|
|
192
202
|
}
|
|
193
203
|
res.type(path.extname(realCandidate));
|
|
194
|
-
res.set('Cache-Control', 'public, max-age=300');
|
|
195
204
|
try {
|
|
196
|
-
|
|
205
|
+
// Express' `sendFile()` sets Cache-Control based on `maxAge` (in ms). Setting the header
|
|
206
|
+
// before calling `sendFile()` can be overwritten by Express defaults.
|
|
207
|
+
await sendFileAsync(res, realCandidate, { maxAge: 300_000 });
|
|
197
208
|
}
|
|
198
209
|
catch (err) {
|
|
199
210
|
server.storage.print_debug(`Failed to serve asset ${domain}/${segments.join('/')}: ${err instanceof Error ? err.message : String(err)}`);
|
package/dist/api/auth.js
CHANGED
|
@@ -20,9 +20,14 @@ export async function assert_domain_and_user(apireq) {
|
|
|
20
20
|
if (!domain) {
|
|
21
21
|
throw new ApiError({ code: 401, message: 'Missing domain' });
|
|
22
22
|
}
|
|
23
|
-
const
|
|
23
|
+
const rawUid = apireq.getRealUid();
|
|
24
|
+
const uid = rawUid === null ? null : Number(rawUid);
|
|
25
|
+
if (!uid || Number.isNaN(uid)) {
|
|
26
|
+
throw new ApiError({ code: 401, message: 'Invalid/Unknown API Key/Token' });
|
|
27
|
+
}
|
|
28
|
+
const user = await api_user.findByPk(uid);
|
|
24
29
|
if (!user) {
|
|
25
|
-
throw new ApiError({ code: 401, message:
|
|
30
|
+
throw new ApiError({ code: 401, message: 'Invalid/Unknown API Key/Token' });
|
|
26
31
|
}
|
|
27
32
|
const dbdomain = await api_domain.findOne({ where: { name: domain } });
|
|
28
33
|
if (!dbdomain) {
|