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.
|
|
4
|
-
"createdAt":
|
|
5
|
-
"updatedAt":
|
|
3
|
+
"version": "1.0.15",
|
|
4
|
+
"createdAt": 1794498034451,
|
|
5
|
+
"updatedAt": 1794498034451,
|
|
6
6
|
"files": {
|
|
7
7
|
"run.mjs": "f8918c612e8641e9beee06221be6680aa6fd21f25468451f2ac857fd51772266",
|
|
8
|
-
"package.json": "
|
|
9
|
-
"package-lock.json": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"description": "ODANT-WORK — платформа цифровой организации (встроенная система учёта, документооборот, управление процессами и приватный мультимодальный ИИ)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "sources/work.js",
|
package/sources/data/progress.md
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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('Почтовый сервер не
|
|
107
|
+
throw new Error('Почтовый сервер не настроен. Администратор (BOSS) может настроить почтовый ящик в корне WORK → представление «Почта» (⚙️ Почтовые ящики) или в файле #system/mail.json');
|
|
81
108
|
}
|
|
82
|
-
const fromUser =
|
|
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
|
|
126
|
+
await activeMailer.transport.sendMail(mailOptions);
|
|
100
127
|
return "На Вашу почту отправлено письмо с одноразовым кодом, введите его для продолжения регистрации.";
|
|
101
128
|
},
|
|
102
129
|
|
package/sources/host/mail.js
CHANGED
|
@@ -1,14 +1,93 @@
|
|
|
1
|
-
import * as fs from 'node:fs';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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) {
|