@technomoron/mail-magic 1.0.35 → 1.0.38

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.
Files changed (34) hide show
  1. package/CHANGES +43 -0
  2. package/dist/cjs/index.js +9 -0
  3. package/dist/cjs/package.json +3 -0
  4. package/dist/{api → esm/api}/assets.js +2 -1
  5. package/dist/{api → esm/api}/auth.js +1 -1
  6. package/dist/{api → esm/api}/forms.js +3 -2
  7. package/dist/{api → esm/api}/mailer.js +3 -22
  8. package/dist/{index.js → esm/index.js} +3 -15
  9. package/dist/{models → esm/models}/db.js +10 -2
  10. package/dist/{models → esm/models}/form.js +20 -2
  11. package/dist/{models → esm/models}/txmail.js +10 -1
  12. package/dist/esm/server.js +22 -0
  13. package/dist/{store → esm/store}/envloader.js +1 -1
  14. package/dist/{store → esm/store}/store.js +32 -33
  15. package/dist/{swagger.js → esm/swagger.js} +1 -14
  16. package/dist/esm/util/route.js +14 -0
  17. package/package.json +14 -5
  18. package/dist/server.js +0 -34
  19. /package/dist/{bin → esm/bin}/mail-magic.js +0 -0
  20. /package/dist/{models → esm/models}/domain.js +0 -0
  21. /package/dist/{models → esm/models}/init.js +0 -0
  22. /package/dist/{models → esm/models}/recipient.js +0 -0
  23. /package/dist/{models → esm/models}/user.js +0 -0
  24. /package/dist/{types.js → esm/types.js} +0 -0
  25. /package/dist/{util → esm/util}/captcha.js +0 -0
  26. /package/dist/{util → esm/util}/email.js +0 -0
  27. /package/dist/{util → esm/util}/form-replyto.js +0 -0
  28. /package/dist/{util → esm/util}/form-submission.js +0 -0
  29. /package/dist/{util → esm/util}/forms.js +0 -0
  30. /package/dist/{util → esm/util}/paths.js +0 -0
  31. /package/dist/{util → esm/util}/ratelimit.js +0 -0
  32. /package/dist/{util → esm/util}/uploads.js +0 -0
  33. /package/dist/{util → esm/util}/utils.js +0 -0
  34. /package/dist/{util.js → esm/util.js} +0 -0
package/CHANGES CHANGED
@@ -1,3 +1,32 @@
1
+ Version 1.0.38 (2026-02-17)
2
+
3
+ - Prefer `fs.watch` for init-data auto-reload with automatic fallback to
4
+ `fs.watchFile` when native file watching is unavailable.
5
+ - Add regression tests for auto-reload watcher behavior (`watch`, fallback,
6
+ disabled mode).
7
+ - Add schema-drift regression tests that assert Zod schema keys match Sequelize
8
+ model attributes for core API models.
9
+
10
+ Version 1.0.37 (2026-02-17)
11
+
12
+ - Remove stale commented-out debug code from server mailer/store paths.
13
+ - Remove unused legacy API-key store scaffolding from `mailStore` (`api_key`,
14
+ `ImailStore`, `keys`, `load_api_keys`, `get_api_key`).
15
+ - Consolidate mailer address validation by reusing shared `util/email` helpers.
16
+
17
+ Version 1.0.36 (2026-02-17)
18
+
19
+ - Remove legacy plaintext-token fallback from request authentication; API key auth
20
+ now requires `token_hmac`.
21
+ - Update token migration regression coverage to verify auth rejection before
22
+ migration and success after migration.
23
+ - Extract shared `normalizeRoute` helper and reuse it across bootstrap and swagger
24
+ modules.
25
+ - Standardize `getBodyValue` usage to `util/utils` imports in API modules for
26
+ clearer utility ownership.
27
+ - Remove unreachable null check after `createTransport()`.
28
+ - Add utility test coverage for route normalization.
29
+
1
30
  Version 1.0.35 (2026-02-17)
2
31
 
3
32
  - Enforce deterministic locale resolution for transactional template sends:
@@ -8,6 +37,20 @@ Version 1.0.35 (2026-02-17)
8
37
  locales are missing.
9
38
  - Add regression tests covering deterministic locale fallback and missing-locale
10
39
  behavior for both transactional sends and template asset upload resolution.
40
+ - Harden JSON-backed getters for template/form asset columns so malformed DB JSON
41
+ safely falls back to empty arrays instead of throwing runtime errors.
42
+ - Standardize form send SMTP failure handling to return ApiError responses
43
+ (consistent `message` contract instead of ad-hoc `error` payloads).
44
+ - Guard SQLite-only PRAGMA usage by database dialect during DB sync.
45
+ - Fix startup error text to use the current project name (`mail-magic`).
46
+ - Fix `DB_TYPE` environment description text ("API database" wording).
47
+ - Add regression tests for malformed JSON getter handling, standardized form-send
48
+ failure responses, sqlite PRAGMA guard helper behavior, startup error message,
49
+ and env option description text.
50
+ - Disallow legacy plaintext-token fallback during API authentication; requests now
51
+ authenticate strictly via `token_hmac`.
52
+ - Update token migration regression coverage to verify plaintext auth rejection
53
+ prior to migration and successful auth after migration.
11
54
 
12
55
  Version 1.0.34 (2026-02-10)
13
56
 
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ const load = () => import('../esm/index.js');
4
+
5
+ module.exports = {
6
+ STARTUP_ERROR_MESSAGE: 'Failed to start mail-magic:',
7
+ createMailMagicServer: async (...args) => (await load()).createMailMagicServer(...args),
8
+ startMailMagicServer: async (...args) => (await load()).startMailMagicServer(...args)
9
+ };
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -5,7 +5,8 @@ import { api_form } from '../models/form.js';
5
5
  import { api_txmail } from '../models/txmail.js';
6
6
  import { SEGMENT_PATTERN, normalizeSubdir } from '../util/paths.js';
7
7
  import { moveUploadedFiles } from '../util/uploads.js';
8
- import { decodeComponent, getBodyValue, sendFileAsync } from '../util.js';
8
+ import { getBodyValue } from '../util/utils.js';
9
+ import { decodeComponent, sendFileAsync } from '../util.js';
9
10
  import { assert_domain_and_user } from './auth.js';
10
11
  const DOMAIN_PATTERN = /^[a-z0-9][a-z0-9._-]*$/i;
11
12
  export class AssetAPI extends ApiModule {
@@ -1,7 +1,7 @@
1
1
  import { ApiError } from '@technomoron/api-server-base';
2
2
  import { api_domain } from '../models/domain.js';
3
3
  import { api_user } from '../models/user.js';
4
- import { getBodyValue } from '../util.js';
4
+ import { getBodyValue } from '../util/utils.js';
5
5
  export async function assert_domain_and_user(apireq) {
6
6
  const body = apireq.req.body ?? {};
7
7
  const domain = getBodyValue(body, 'domain');
@@ -8,7 +8,8 @@ import { api_recipient } from '../models/recipient.js';
8
8
  import { buildFormTemplateRecord, buildFormTemplatePaths, buildRecipientTo, buildReplyToValue, buildSubmissionContext, enforceAttachmentPolicy, enforceCaptchaPolicy, filterSubmissionFields, getPrimaryRecipientInfo, normalizeRecipientEmail, normalizeRecipientIdname, normalizeRecipientName, parseIdnameList, parseFormTemplatePayload, parseRecipientPayload, parsePublicSubmissionOrThrow, resolveFormKeyForTemplate, resolveFormKeyForRecipient, resolveRecipients, validateFormTemplatePayload } from '../util/forms.js';
9
9
  import { FixedWindowRateLimiter, enforceFormRateLimit } from '../util/ratelimit.js';
10
10
  import { buildAttachments, cleanupUploadedFiles } from '../util/uploads.js';
11
- import { buildRequestMeta, getBodyValue } from '../util.js';
11
+ import { getBodyValue } from '../util/utils.js';
12
+ import { buildRequestMeta } from '../util.js';
12
13
  import { assert_domain_and_user } from './auth.js';
13
14
  export class FormAPI extends ApiModule {
14
15
  rateLimiter = new FixedWindowRateLimiter();
@@ -187,7 +188,7 @@ export class FormAPI extends ApiModule {
187
188
  catch (error) {
188
189
  const errorMessage = error instanceof Error ? error.message : String(error);
189
190
  this.server.storage.print_debug('Error sending email: ' + errorMessage);
190
- return [500, { error: `Error sending email: ${errorMessage}` }];
191
+ throw new ApiError({ code: 500, message: `Error sending email: ${errorMessage}` });
191
192
  }
192
193
  return [200, {}];
193
194
  }
@@ -1,21 +1,11 @@
1
1
  import { ApiModule, ApiError } from '@technomoron/api-server-base';
2
- import emailAddresses from 'email-addresses';
3
2
  import { convert } from 'html-to-text';
4
3
  import nunjucks from 'nunjucks';
5
4
  import { api_txmail } from '../models/txmail.js';
5
+ import { validateEmail } from '../util/email.js';
6
6
  import { buildRequestMeta } from '../util.js';
7
7
  import { assert_domain_and_user } from './auth.js';
8
8
  export class MailerAPI extends ApiModule {
9
- //
10
- // Validate and return the parsed email address
11
- //
12
- validateEmail(email) {
13
- const parsed = emailAddresses.parseOneAddress(email);
14
- if (parsed) {
15
- return parsed.address;
16
- }
17
- return undefined;
18
- }
19
9
  //
20
10
  // Validate a set of email addresses. Return arrays of invalid
21
11
  // and valid email addresses.
@@ -27,7 +17,7 @@ export class MailerAPI extends ApiModule {
27
17
  .map((email) => email.trim())
28
18
  .filter((email) => email !== '');
29
19
  emails.forEach((email) => {
30
- const addr = this.validateEmail(email);
20
+ const addr = validateEmail(email);
31
21
  if (addr) {
32
22
  valid.push(addr);
33
23
  }
@@ -56,13 +46,6 @@ export class MailerAPI extends ApiModule {
56
46
  sender,
57
47
  template
58
48
  };
59
- /*
60
- console.log(JSON.stringify({
61
- user: apireq.user,
62
- domain: apireq.domain,
63
- domain_id: apireq.domain.domain_id,
64
- data
65
- }, undefined, 2)); */
66
49
  try {
67
50
  const [templateRecord, created] = await api_txmail.upsert(data, {
68
51
  returning: true
@@ -94,7 +77,6 @@ export class MailerAPI extends ApiModule {
94
77
  }
95
78
  }
96
79
  const thevars = parsedVars;
97
- // const dbdomain = await api_domain.findOne({ where: { domain } });
98
80
  const { valid, invalid } = this.validateEmails(rcpt);
99
81
  if (invalid.length > 0) {
100
82
  throw new ApiError({ code: 400, message: 'Invalid email address(es): ' + invalid.join(',') });
@@ -145,7 +127,7 @@ export class MailerAPI extends ApiModule {
145
127
  const replyToValue = (replyTo || reply_to);
146
128
  let normalizedReplyTo;
147
129
  if (replyToValue) {
148
- normalizedReplyTo = this.validateEmail(replyToValue);
130
+ normalizedReplyTo = validateEmail(replyToValue);
149
131
  if (!normalizedReplyTo) {
150
132
  throw new ApiError({ code: 400, message: 'Invalid reply-to email address' });
151
133
  }
@@ -191,7 +173,6 @@ export class MailerAPI extends ApiModule {
191
173
  return [200, { Status: 'OK', Message: 'Emails sent successfully' }];
192
174
  }
193
175
  catch (error) {
194
- // console.log(JSON.stringify(e, null, 2));
195
176
  throw new ApiError({
196
177
  code: 500,
197
178
  message: error instanceof Error ? error.message : String(error)
@@ -5,20 +5,8 @@ import { MailerAPI } from './api/mailer.js';
5
5
  import { mailApiServer } from './server.js';
6
6
  import { mailStore } from './store/store.js';
7
7
  import { installMailMagicSwagger } from './swagger.js';
8
- function normalizeRoute(value, fallback = '') {
9
- if (!value) {
10
- return fallback;
11
- }
12
- const trimmed = value.trim();
13
- if (!trimmed) {
14
- return fallback;
15
- }
16
- const withLeading = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
17
- if (withLeading === '/') {
18
- return withLeading;
19
- }
20
- return withLeading.replace(/\/+$/, '');
21
- }
8
+ import { normalizeRoute } from './util/route.js';
9
+ export const STARTUP_ERROR_MESSAGE = 'Failed to start mail-magic:';
22
10
  function mergeStaticDirs(base, override) {
23
11
  const merged = { ...base, ...(override ?? {}) };
24
12
  if (Object.keys(merged).length === 0) {
@@ -107,7 +95,7 @@ async function bootMailMagic() {
107
95
  console.log(`mail-magic server listening on ${vars.API_HOST}:${vars.API_PORT}`);
108
96
  }
109
97
  catch (err) {
110
- console.error('Failed to start FormMailer:', err);
98
+ console.error(STARTUP_ERROR_MESSAGE, err);
111
99
  process.exit(1);
112
100
  }
113
101
  }
@@ -5,6 +5,9 @@ import { importData } from './init.js';
5
5
  import { init_api_recipient, api_recipient } from './recipient.js';
6
6
  import { init_api_txmail, api_txmail } from './txmail.js';
7
7
  import { init_api_user, api_user, migrateLegacyApiTokens } from './user.js';
8
+ export function usesSqlitePragmas(db) {
9
+ return db.getDialect() === 'sqlite';
10
+ }
8
11
  export async function init_api_db(db, store) {
9
12
  await init_api_user(db);
10
13
  await init_api_domain(db);
@@ -71,11 +74,16 @@ export async function init_api_db(db, store) {
71
74
  foreignKey: 'domain_id',
72
75
  as: 'domain'
73
76
  });
74
- await db.query('PRAGMA foreign_keys = OFF');
77
+ const useSqlitePragmas = usesSqlitePragmas(db);
78
+ if (useSqlitePragmas) {
79
+ await db.query('PRAGMA foreign_keys = OFF');
80
+ }
75
81
  const alter = Boolean(store.vars.DB_SYNC_ALTER);
76
82
  store.print_debug(`DB sync: alter=${alter} force=${store.vars.DB_FORCE_SYNC}`);
77
83
  await db.sync({ alter, force: store.vars.DB_FORCE_SYNC });
78
- await db.query('PRAGMA foreign_keys = ON');
84
+ if (useSqlitePragmas) {
85
+ await db.query('PRAGMA foreign_keys = ON');
86
+ }
79
87
  await importData(store);
80
88
  try {
81
89
  const { migrated, cleared } = await migrateLegacyApiTokens(store.vars.API_TOKEN_PEPPER);
@@ -170,7 +170,16 @@ export async function init_api_form(api_db) {
170
170
  get() {
171
171
  // This column is stored as JSON text but exposed as `string[]` via getter/setter.
172
172
  const raw = this.getDataValue('allowed_fields');
173
- return raw ? JSON.parse(raw) : [];
173
+ if (!raw) {
174
+ return [];
175
+ }
176
+ try {
177
+ const parsed = JSON.parse(raw);
178
+ return Array.isArray(parsed) ? parsed : [];
179
+ }
180
+ catch {
181
+ return [];
182
+ }
174
183
  },
175
184
  set(value) {
176
185
  this.setDataValue('allowed_fields', JSON.stringify(value ?? []));
@@ -188,7 +197,16 @@ export async function init_api_form(api_db) {
188
197
  get() {
189
198
  // This column is stored as JSON text but exposed as `StoredFile[]` via getter/setter.
190
199
  const raw = this.getDataValue('files');
191
- return raw ? JSON.parse(raw) : [];
200
+ if (!raw) {
201
+ return [];
202
+ }
203
+ try {
204
+ const parsed = JSON.parse(raw);
205
+ return Array.isArray(parsed) ? parsed : [];
206
+ }
207
+ catch {
208
+ return [];
209
+ }
192
210
  },
193
211
  set(value) {
194
212
  this.setDataValue('files', JSON.stringify(value ?? []));
@@ -131,7 +131,16 @@ export async function init_api_txmail(api_db) {
131
131
  defaultValue: '[]',
132
132
  get() {
133
133
  const raw = this.getDataValue('files');
134
- return raw ? JSON.parse(raw) : [];
134
+ if (!raw) {
135
+ return [];
136
+ }
137
+ try {
138
+ const parsed = JSON.parse(raw);
139
+ return Array.isArray(parsed) ? parsed : [];
140
+ }
141
+ catch {
142
+ return [];
143
+ }
135
144
  },
136
145
  set(value) {
137
146
  this.setDataValue('files', JSON.stringify(value ?? []));
@@ -0,0 +1,22 @@
1
+ import { ApiServer } from '@technomoron/api-server-base';
2
+ import { apiTokenToHmac, api_user } from './models/user.js';
3
+ export class mailApiServer extends ApiServer {
4
+ store;
5
+ storage;
6
+ constructor(config, store) {
7
+ super(config);
8
+ this.store = store;
9
+ this.storage = store;
10
+ }
11
+ async getApiKey(token) {
12
+ this.storage.print_debug('Looking up api key');
13
+ const pepper = this.storage.vars.API_TOKEN_PEPPER;
14
+ const token_hmac = apiTokenToHmac(token, pepper);
15
+ const user = await api_user.findOne({ where: { token_hmac } });
16
+ if (user) {
17
+ return { uid: user.user_id };
18
+ }
19
+ this.storage.print_debug('Unable to find user for api key');
20
+ return null;
21
+ }
22
+ }
@@ -82,7 +82,7 @@ export const envOptions = defineEnvOptions({
82
82
  default: 'localhost'
83
83
  },
84
84
  DB_TYPE: {
85
- description: 'Database type of WP database',
85
+ description: 'Database type for the API database',
86
86
  options: ['sqlite'],
87
87
  default: 'sqlite'
88
88
  },
@@ -22,25 +22,49 @@ function create_mail_transport(vars) {
22
22
  if (user && pass) {
23
23
  args.auth = { user, pass };
24
24
  }
25
- // console.log(JSON.stringify(args, undefined, 2));
26
25
  const mailer = createTransport({
27
26
  ...args
28
27
  });
29
- if (!mailer) {
30
- throw new Error('Unable to create mailer');
31
- }
32
28
  return mailer;
33
29
  }
30
+ export function enableInitDataAutoReload(ctx, reload) {
31
+ if (!ctx.vars.DB_AUTO_RELOAD) {
32
+ return null;
33
+ }
34
+ const initDataPath = ctx.config_filename('init-data.json');
35
+ ctx.print_debug('Enabling auto reload of init-data.json');
36
+ const onChange = () => {
37
+ ctx.print_debug('Config file changed, reloading...');
38
+ try {
39
+ reload();
40
+ }
41
+ catch (err) {
42
+ ctx.print_debug(`Failed to reload config: ${err}`);
43
+ }
44
+ };
45
+ try {
46
+ const watcher = fs.watch(initDataPath, { persistent: false }, onChange);
47
+ return {
48
+ close: () => watcher.close()
49
+ };
50
+ }
51
+ catch (err) {
52
+ ctx.print_debug(`fs.watch unavailable; falling back to fs.watchFile: ${err}`);
53
+ fs.watchFile(initDataPath, { interval: 2000 }, onChange);
54
+ return {
55
+ close: () => fs.unwatchFile(initDataPath, onChange)
56
+ };
57
+ }
58
+ }
34
59
  export class mailStore {
35
60
  env;
36
61
  vars;
37
62
  transport;
38
63
  api_db = null;
39
- keys = {};
40
64
  configpath = '';
41
- deflocale;
42
65
  uploadTemplate;
43
66
  uploadStagingPath;
67
+ autoReloadHandle = null;
44
68
  print_debug(msg) {
45
69
  if (this.vars.DEBUG) {
46
70
  console.log(msg);
@@ -102,17 +126,6 @@ export class mailStore {
102
126
  }
103
127
  }));
104
128
  }
105
- async load_api_keys(cfgpath) {
106
- const keyfile = path.resolve(cfgpath, 'api-keys.json');
107
- if (fs.existsSync(keyfile)) {
108
- const raw = fs.readFileSync(keyfile, 'utf-8');
109
- const jsonData = JSON.parse(raw);
110
- this.print_debug(`API Key Database loaded from ${keyfile}`);
111
- return jsonData;
112
- }
113
- this.print_debug(`No api-keys.json file found: tried ${keyfile}`);
114
- return {};
115
- }
116
129
  async init(overrides = {}) {
117
130
  // Load env config only via EnvLoader + envOptions (avoid ad-hoc `process.env` parsing here).
118
131
  // If DEBUG is enabled, re-load with EnvLoader debug output enabled.
@@ -168,24 +181,10 @@ export class mailStore {
168
181
  this.print_debug(`Unable to create upload staging path: ${err}`);
169
182
  }
170
183
  }
171
- // this.keys = await this.load_api_keys(this.configpath);
172
184
  this.transport = await create_mail_transport(this.vars);
173
185
  this.api_db = await connect_api_db(this);
174
- if (this.vars.DB_AUTO_RELOAD) {
175
- this.print_debug('Enabling auto reload of init-data.json');
176
- fs.watchFile(this.config_filename('init-data.json'), { interval: 2000 }, () => {
177
- this.print_debug('Config file changed, reloading...');
178
- try {
179
- importData(this);
180
- }
181
- catch (err) {
182
- this.print_debug(`Failed to reload config: ${err}`);
183
- }
184
- });
185
- }
186
+ this.autoReloadHandle?.close();
187
+ this.autoReloadHandle = enableInitDataAutoReload(this, () => importData(this));
186
188
  return this;
187
189
  }
188
- get_api_key(key) {
189
- return this.keys[key] || null;
190
- }
191
190
  }
@@ -1,20 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
- function normalizeRoute(value, fallback = '') {
5
- if (!value) {
6
- return fallback;
7
- }
8
- const trimmed = value.trim();
9
- if (!trimmed) {
10
- return fallback;
11
- }
12
- const withLeading = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
13
- if (withLeading === '/') {
14
- return withLeading;
15
- }
16
- return withLeading.replace(/\/+$/, '');
17
- }
4
+ import { normalizeRoute } from './util/route.js';
18
5
  function replacePrefix(input, from, to) {
19
6
  if (input === from) {
20
7
  return to;
@@ -0,0 +1,14 @@
1
+ export function normalizeRoute(value, fallback = '') {
2
+ if (!value) {
3
+ return fallback;
4
+ }
5
+ const trimmed = value.trim();
6
+ if (!trimmed) {
7
+ return fallback;
8
+ }
9
+ const withLeading = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
10
+ if (withLeading === '/') {
11
+ return withLeading;
12
+ }
13
+ return withLeading.replace(/\/+$/, '');
14
+ }
package/package.json CHANGED
@@ -1,10 +1,17 @@
1
1
  {
2
2
  "name": "@technomoron/mail-magic",
3
- "version": "1.0.35",
4
- "main": "dist/index.js",
3
+ "version": "1.0.38",
4
+ "main": "dist/cjs/index.js",
5
+ "module": "dist/esm/index.js",
5
6
  "type": "module",
6
7
  "bin": {
7
- "mail-magic": "dist/bin/mail-magic.js"
8
+ "mail-magic": "dist/esm/bin/mail-magic.js"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/esm/index.js",
13
+ "require": "./dist/cjs/index.js"
14
+ }
8
15
  },
9
16
  "files": [
10
17
  "dist",
@@ -18,10 +25,12 @@
18
25
  "directory": "packages/mail-magic"
19
26
  },
20
27
  "scripts": {
21
- "start": "node dist/index.js",
28
+ "start": "node dist/esm/index.js",
22
29
  "dev": "NODE_ENV=development nodemon --watch 'src/**/*.ts' --watch 'config/**/*.*' --watch '.env' --exec 'tsx' src/index.ts",
23
30
  "run": "NODE_ENV=production npm run start",
24
- "build": "tsc",
31
+ "build:esm": "tsc --project tsconfig/tsconfig.esm.json",
32
+ "build:cjs": "node scripts/add-shebang.cjs --cjs-only",
33
+ "build": "npm run build:esm && npm run build:cjs",
25
34
  "postbuild": "node scripts/add-shebang.cjs",
26
35
  "prepack": "npm run build",
27
36
  "test": "vitest run",
package/dist/server.js DELETED
@@ -1,34 +0,0 @@
1
- import { ApiServer } from '@technomoron/api-server-base';
2
- import { apiTokenToHmac, api_user } from './models/user.js';
3
- export class mailApiServer extends ApiServer {
4
- store;
5
- storage;
6
- constructor(config, store) {
7
- super(config);
8
- this.store = store;
9
- this.storage = store;
10
- }
11
- async getApiKey(token) {
12
- this.storage.print_debug('Looking up api key');
13
- const pepper = this.storage.vars.API_TOKEN_PEPPER;
14
- const token_hmac = apiTokenToHmac(token, pepper);
15
- const user = await api_user.findOne({ where: { token_hmac } });
16
- if (user) {
17
- return { uid: user.user_id };
18
- }
19
- // Backwards-compatible fallback for legacy databases that still store plaintext tokens.
20
- const legacy = await api_user.findOne({ where: { token } });
21
- if (!legacy) {
22
- this.storage.print_debug('Unable to find user for api key');
23
- return null;
24
- }
25
- try {
26
- await legacy.update({ token_hmac, token: '' });
27
- }
28
- catch (err) {
29
- // Don't leak token data; just surface the update failure for debugging.
30
- this.storage.print_debug(`Unable to migrate legacy api token: ${err instanceof Error ? err.message : String(err)}`);
31
- }
32
- return { uid: legacy.user_id };
33
- }
34
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes