odant-work 1.0.14 → 1.0.15

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.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "packageName": "odant-work",
3
- "version": "1.0.14",
4
- "createdAt": 1794496121532,
5
- "updatedAt": 1794496121532,
3
+ "version": "1.0.15",
4
+ "createdAt": 1794498034451,
5
+ "updatedAt": 1794498034451,
6
6
  "files": {
7
7
  "run.mjs": "f8918c612e8641e9beee06221be6680aa6fd21f25468451f2ac857fd51772266",
8
- "package.json": "443c5bd07f87efc2b1fbd21feb625a3487c8aaad1f860e48c54ec450ec7b4c78",
9
- "package-lock.json": "60f64bb33b571841eab4d17b256ec8c892a883c0e48cddb185d8839f98aa85f7",
8
+ "package.json": "f7934869a81b44533f6aa3444b6abf4059dcd3797630bbeb6a1a0bda3422a5ab",
9
+ "package-lock.json": "eb7dae52bf8a410d27332322b4c192415d432b353403ede559e27c5e79c945a6",
10
10
  "sources/client/class.js": "c3fb8f143fc83aaa2df1ddeddd412d2d4dfe88656578095babe2df34fb3b3922",
11
11
  "sources/client/field.js": "ffdd50fa8b6ec515cc42da5fca96658ff2b20e274601a1f926e0031372467a19",
12
12
  "sources/client/file.js": "667101d75fa30840370d5be232e518aaac7c1fd52ca05a12da560eef6c0848b0",
@@ -26,7 +26,7 @@
26
26
  "sources/data/objects.js": "23f51d5b00cd766fe27879b874738bc9bd9056bd6e27285fb7a52926c0dbc6a8",
27
27
  "sources/data/posting.js": "6595bb9ec632539ae7629504db327d25a491e61c1cdc598bb43d08ea0d45548d",
28
28
  "sources/data/print.js": "704f0f2fc80c6466a4196e4650d763150cf9b592eab14ea67ece85d51100107c",
29
- "sources/data/progress.md": "ecfef8377c096ea3880f7363ae7a1fbc5333d60021a305a18a7afd51f0fd213b",
29
+ "sources/data/progress.md": "f73016b6fb9e0cf84d9a8ec627880cb7cc7d85e50ed2ceafeac4cf181d310e02",
30
30
  "sources/data/readme.md": "593fa2ae07aa91bc26d7542275da6953a9cd419240a9a49e00cb62fb9d5d8e36",
31
31
  "sources/data/reports.js": "0330f404860fe2589a7d46a2f0fe690ca23249bd841a062a26718742d4fd11d6",
32
32
  "sources/data/schema.js": "2a598b33c9352e3b673639dda302caeb13620cf978960cbcaee906f7bb852d50",
@@ -34,14 +34,14 @@
34
34
  "sources/data/sql-inspector.js": "a3866597a5f926ef5a10220a315a4abe8ad3aeb2f39ae61a9c6c970df3fb70fd",
35
35
  "sources/data/time.js": "cb34c1137685e83265938bb007859dfc1fca3548588d1663ee01e1affc87a09a",
36
36
  "sources/data/workspace.js": "afbc007c16a26e86de2753794e0e7387218e97aba378047a5f37ea7961e3e921",
37
- "sources/host/auth-methods.js": "0686ddee70e7d20eae12a308403ca0563cc09df13fd1cc47c38043a6c001ad1b",
37
+ "sources/host/auth-methods.js": "d4cc34ea5b56bb314ea903c065706135e12ff685f69e6234658588c92d2c6509",
38
38
  "sources/host/auto-updater.js": "373f4d3a34029178a66afc0c9810bf5bf5d260235c41d743cbfbe596e6363cc6",
39
39
  "sources/host/babel-merge.js": "11d5b65cae03aeabd09f06f65bee78f6fd0131eab125cc87c0dcaaf2d10e08a5",
40
40
  "sources/host/config.js": "f41058d2ef46afc0ac5e262a0f31064d3288ab3794c4d45c0761498e775f60ae",
41
41
  "sources/host/email-utils.js": "618f474b92c85675aeeb7584c1ef7d73441b9efc1463b9b836e7facb86e5d558",
42
42
  "sources/host/http-server.js": "0d7329faafd8c83e9c327375e70c5b6e0a12fc9e73007895bd5e2ac2a6aa74de",
43
43
  "sources/host/index-db.js": "73ffe2cc8eb4ea81ab0ec9ce46d977336ae26019c100d36f0adc7120498b5d72",
44
- "sources/host/mail.js": "7041e6d1429d2176811facaff03ece33dc36baee79dcd59180724f246c360da2",
44
+ "sources/host/mail.js": "a494edb01f645bb308bfb4da6f43b1c51b388cc57aaa6d05ae12ffa10e143d78",
45
45
  "sources/host/net-guard.js": "87c4412f01c86d16347588578e7e955d6edaadf9b0a3361ba37d4e6f20ddb3f0",
46
46
  "sources/host/package-install.js": "2c3b651c744215bdfc9286f7f7c55b33b898ca8d2243406d3020a455971ef76d",
47
47
  "sources/host/prewarm.js": "14ff16e4858b580fd7b8bd6b2942913c6cf7605681144b998e058424422a2e68",
@@ -172,7 +172,7 @@
172
172
  "sources/modules/trainer/runner.js": "c0b7c3cacc7cd6d4965f9ad54e928dd3c97a36c3fd2cd06eb60cab67f247553b",
173
173
  "sources/modules/trainer/worker/local_infer_worker.py": "7b001d75c64ac3ae6de650f6d738f22bfc21b6bd0694b57248e5b40966deb443",
174
174
  "sources/modules/trainer/worker/unsloth_train.py": "7774d94ffa6f60eed20c57a8dfdc12621d5d9f74d594c605dff32ff70e321e33",
175
- "sources/modules/user-profile/user-profile.js": "da4576239917461677610f8f0d28745d79735a8fb45aedb0aff377debda6dcb4",
175
+ "sources/modules/user-profile/user-profile.js": "47b71574ec51e6d40e218e996c8397b25f36418f21e69668c75f1df7d64eb914",
176
176
  "sources/odant.png": "b0c0d28bb28125dac7e1305bca10569fb055d856261455d336931bc57a4705c1",
177
177
  "sources/page.html": "18ec691a53f9be9611e7be665e138ca5490fb15e16ce42a1b47f79b8fe2310d3",
178
178
  "sources/reactor.js": "0fe03b19227d511a806ac0d25432c18bdbc9b9d439eaec3c1f7667586238805b",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "odant-work",
3
- "version": "1.0.14",
3
+ "version": "1.0.15",
4
4
  "description": "ODANT-WORK — платформа цифровой организации (встроенная система учёта, документооборот, управление процессами и приватный мультимодальный ИИ)",
5
5
  "type": "module",
6
6
  "main": "sources/work.js",
@@ -1,6 +1,9 @@
1
1
  # Прогресс: sources/data
2
2
 
3
3
  ## Последние изменения
4
+ - [20:05, 12.11] Прямая 1-клик регистрация первого администратора (`BOSS`) без настроенного почтового сервера и горячее перечитывание SMTP-настроек из `WORK → Почта` (`$server/#secret/email.json`) и `#system/mail.json` (`sources/host/mail.js`, `sources/host/auth-methods.js`, `sources/modules/user-profile/user-profile.js`, `tests/license-and-deploy.test.js`, `odant-work@1.0.15`):
5
+ - **Регистрация первого пользователя (`BOSS`) без SMTP:** если на новом компьютере почтовый сервер ещё не настроен (`getMailer() === null`), а в `/USERS` ещё нет зарегистрированного `BOSS`, `user_register_start` возвращает `{ autoCode }`, и `user-profile.js` сразу в 1 клик генерирует криптоключ, регистрирует пользователя как `BOSS` всего `WORK` и выполняет вход без требования почтового кода.
6
+ - **Настройка почтовика из интерфейса WORK и горячий подхват (`sources/host/mail.js`):** `getMailer()` динамически (по `mtime` без перезапуска сервера) читает настройки SMTP как из `#system/mail.json`, так и из штатного диалога **«Почтовые ящики» (`⚙️` на форме `WORK → Почта`, файл `$server/#secret/email.json`)**, а при отсутствии файлов не засоряет консоль ошибкой `ENOENT`.
4
7
  - [19:30, 12.11] Шаблон организации `BASE_TEMPLATE` с 3 уровнями наполнения (`1. Пустая`, `2. С типовой конфигурацией`, `3. С демоданными`), полный типовой план счетов и операций в `/MODULES/Register`, чистое развёртывание `/ORGANIZATIONS` в npm и защита кастомизированных классов при многошаговых обновлениях (`sources/server/class/base-template.js`, `sources/server/class.js`, `$server/$folder/handlers/methods/create/$handler/class.js`, `MODULES/Register/**`, `.npmignore`, `run.mjs`, `sources/host/auto-updater.js`, `tests/license-and-deploy.test.js`, `odant-work@1.0.14`):
5
8
  - **1. Чистый `/ORGANIZATIONS` в npm-пакете и шаблон `BASE_TEMPLATE` (`sources/server/class/base-template.js`, `create/$handler/class.js`):**
6
9
  - Из npm-поставки исключены все локальные организации (`/ORGANIZATIONS/*/` кроме системной метапапки `/ORGANIZATIONS/$domain/`).
@@ -1,7 +1,7 @@
1
1
  import * as crypto from 'node:crypto';
2
2
  import * as fs from 'node:fs';
3
3
  import { DEV_MODE, HOST, CHALLENGE_TTL_MS } from './config.js';
4
- import { mailer } from './mail.js';
4
+ import { getMailer } from './mail.js';
5
5
  import * as CORE from '../server/index.js';
6
6
  import { $server } from '../server/server.js';
7
7
  import { audit } from '../server/access/audit.js';
@@ -56,6 +56,25 @@ function mcpUser(params = {}) {
56
56
  return s.uid;
57
57
  }
58
58
 
59
+ /**
60
+ * Проверить, является ли регистрируемый пользователь первым администратором (BOSS) на сервере
61
+ * (в /USERS ещё нет ни одного зарегистрированного BOSS) или уже действующим BOSS сервера.
62
+ */
63
+ async function isFirstOrBossUser(uid) {
64
+ try {
65
+ const $users = await this.$users;
66
+ const users = await $users.items;
67
+ await WORK.init;
68
+ const existingUserIds = new Set((users || []).map(u => u?.id).filter(Boolean));
69
+ const currentBosses = Array.isArray(WORK?.DATA?.['#security']?.BOSS) ? WORK.DATA['#security'].BOSS : [];
70
+ const isFirstUser = !users?.length || !currentBosses.some(id => existingUserIds.has(id));
71
+ return isFirstUser || (Boolean(uid) && currentBosses.includes(uid));
72
+ }
73
+ catch {
74
+ return false;
75
+ }
76
+ }
77
+
59
78
  export const authMethods = {
60
79
  async user_register_start(params = {}) {
61
80
  let { uid, email } = params.post || {};
@@ -73,13 +92,21 @@ export const authMethods = {
73
92
  if (DEV_MODE) {
74
93
  console.log('[DEV] Registration code for', email, ':', code);
75
94
  }
76
- if (!mailer) {
95
+ const activeMailer = getMailer();
96
+ if (!activeMailer) {
97
+ const firstOrBoss = await isFirstOrBossUser.call(this, uid);
98
+ if (firstOrBoss && !DEV_MODE) {
99
+ return {
100
+ autoCode: code,
101
+ message: 'Регистрация первого администратора (BOSS) выполняется напрямую без почтового сервера.',
102
+ };
103
+ }
77
104
  if (DEV_MODE) {
78
105
  return "На Вашу почту отправлено письмо с одноразовым кодом, введите его для продолжения регистрации.";
79
106
  }
80
- throw new Error('Почтовый сервер не настроен (#system/mail.json)');
107
+ throw new Error('Почтовый сервер не настроен. Администратор (BOSS) может настроить почтовый ящик в корне WORK → представление «Почта» (⚙️ Почтовые ящики) или в файле #system/mail.json');
81
108
  }
82
- const fromUser = mailer?.options?.auth?.user || 'noreply@localhost';
109
+ const fromUser = activeMailer.fromUser || activeMailer.transport?.options?.auth?.user || 'noreply@localhost';
83
110
  const mailOptions = {
84
111
  from: `"ODANT-WORK" <${fromUser}>`,
85
112
  to: email,
@@ -96,7 +123,7 @@ export const authMethods = {
96
123
  <p>С уважением,<br>WORK</p>
97
124
  `,
98
125
  };
99
- await mailer.sendMail(mailOptions);
126
+ await activeMailer.transport.sendMail(mailOptions);
100
127
  return "На Вашу почту отправлено письмо с одноразовым кодом, введите его для продолжения регистрации.";
101
128
  },
102
129
 
@@ -1,14 +1,93 @@
1
- import * as fs from 'node:fs';
2
- import nodemailer from 'nodemailer';
3
-
4
- export const mailer = (() => {
5
- try {
6
- let data = fs.readFileSync('./#system/mail.json', { encoding: 'utf-8' });
7
- data = JSON.parse(data);
8
- return nodemailer.createTransport(data);
9
- }
10
- catch (e) {
11
- console.error(e);
12
- return null;
13
- }
14
- })();
1
+ import * as fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import nodemailer from 'nodemailer';
4
+
5
+ const BOOT_DIR = path.resolve('.');
6
+ const REL_CANDIDATES = [
7
+ '#system/mail.json',
8
+ '$server/#secret/email.json',
9
+ '#system/email.json',
10
+ ];
11
+
12
+ let cachedKey = '';
13
+ let cachedTransport = null;
14
+ let cachedFromUser = '';
15
+
16
+ function extractSmtpConfig(raw) {
17
+ if (!raw || typeof raw !== 'object')
18
+ return null;
19
+ // 1. Прямой формат #system/mail.json ({ host, port, secure, auth: { user, pass } })
20
+ if (typeof raw.host === 'string' && raw.host.trim()) {
21
+ return {
22
+ transportOpts: raw,
23
+ fromUser: raw.auth?.user || raw.from || 'noreply@localhost',
24
+ };
25
+ }
26
+ // 2. Формат Почты WORK ($server/#secret/email.json: { mailboxes: { addr: { smtp, auth } } })
27
+ if (raw.mailboxes && typeof raw.mailboxes === 'object') {
28
+ for (const [addr, box] of Object.entries(raw.mailboxes)) {
29
+ const host = String(box?.smtp?.host || '').trim();
30
+ if (!host)
31
+ continue;
32
+ const user = String(box?.auth?.user || addr || '').trim();
33
+ const pass = String(box?.auth?.pass || '');
34
+ return {
35
+ transportOpts: {
36
+ host,
37
+ port: Number(box.smtp.port) || 465,
38
+ secure: box.smtp.secure !== false,
39
+ auth: user ? { user, pass } : undefined,
40
+ },
41
+ fromUser: user || addr || 'noreply@localhost',
42
+ };
43
+ }
44
+ }
45
+ return null;
46
+ }
47
+
48
+ /**
49
+ * Динамически получить настроенный SMTP-транспорт (перечитывает #system/mail.json
50
+ * или почтовый ящик корня WORK из $server/#secret/email.json при изменении файла без рестарта сервера).
51
+ */
52
+ export function getMailer() {
53
+ if (process.env.WORK_NO_MAIL === '1')
54
+ return null;
55
+
56
+ const cwd = path.resolve('.');
57
+ const roots = cwd === BOOT_DIR ? [cwd] : [cwd, BOOT_DIR];
58
+
59
+ for (const root of roots) {
60
+ for (const rel of REL_CANDIDATES) {
61
+ const filePath = path.join(root, rel);
62
+ try {
63
+ if (!fs.existsSync(filePath))
64
+ continue;
65
+ const stat = fs.statSync(filePath);
66
+ if (!stat.isFile())
67
+ continue;
68
+ const key = `${filePath}:${Math.trunc(stat.mtimeMs)}:${stat.size}`;
69
+ if (key === cachedKey && cachedTransport) {
70
+ return { transport: cachedTransport, fromUser: cachedFromUser, source: filePath };
71
+ }
72
+ const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
73
+ const cfg = extractSmtpConfig(parsed);
74
+ if (!cfg)
75
+ continue;
76
+ cachedTransport = nodemailer.createTransport(cfg.transportOpts);
77
+ cachedFromUser = cfg.fromUser;
78
+ cachedKey = key;
79
+ return { transport: cachedTransport, fromUser: cachedFromUser, source: filePath };
80
+ }
81
+ catch (e) {
82
+ if (e?.code !== 'ENOENT')
83
+ console.warn('[mail]', filePath, e?.message || e);
84
+ }
85
+ }
86
+ }
87
+ cachedKey = '';
88
+ cachedTransport = null;
89
+ cachedFromUser = '';
90
+ return null;
91
+ }
92
+
93
+ export const mailer = getMailer()?.transport || null;
@@ -277,7 +277,13 @@ ODA({ is: 'user-profile', imports: 'oda//otp.js',
277
277
  case 'changed':
278
278
  case 'unregistered': {
279
279
  try {
280
- this.text = await WORK.fetch("/", 'user_register_start', {}, this.params);
280
+ const res = await WORK.fetch("/", 'user_register_start', {}, this.params);
281
+ if (res && typeof res === 'object' && res.autoCode) {
282
+ this.text = res.message || 'Идет процесс регистрации ключа безопасности...';
283
+ await this._onCode({ detail: { value: res.autoCode } });
284
+ break;
285
+ }
286
+ this.text = typeof res === 'string' ? res : (res?.message || '');
281
287
  this.showCode = true;
282
288
  }
283
289
  catch (e) {