work-license-server 1.0.17 → 1.0.19

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/assets/admin.js CHANGED
@@ -35,7 +35,7 @@
35
35
  }
36
36
  catch { return readCookieSession(); }
37
37
  }
38
- function setStoredToken(tok, email = '') {
38
+ function setStoredToken(tok, email = '', userObj = null) {
39
39
  adminToken = tok || '';
40
40
  try {
41
41
  if (tok) {
@@ -49,12 +49,16 @@
49
49
  sessionStorage.removeItem(TOKEN_KEY);
50
50
  localStorage.removeItem(TOKEN_KEY);
51
51
  localStorage.removeItem('odant_work_session');
52
+ localStorage.removeItem('odant_work_user_cache');
52
53
  sessionStorage.removeItem('odant_work_session');
53
54
  document.cookie = 'odant_work_session=; path=/; max-age=0; SameSite=Lax';
54
55
  }
55
56
  if (email) {
56
57
  localStorage.setItem('odant_work_email', email);
57
58
  }
59
+ if (userObj && typeof userObj === 'object') {
60
+ localStorage.setItem('odant_work_user_cache', JSON.stringify(userObj));
61
+ }
58
62
  }
59
63
  catch { /* приватный режим */ }
60
64
  }
@@ -121,7 +125,7 @@
121
125
  ind.style.width = active.offsetWidth + 'px';
122
126
  }
123
127
 
124
- function showAdminApp(login = 'BOSS') {
128
+ function showAdminApp(login = 'Администратор') {
125
129
  $('#adminLoginScreen').style.display = 'none';
126
130
  $('#adminAppShell').style.display = '';
127
131
  if ($('#admUserLabel')) $('#admUserLabel').textContent = login;
@@ -187,9 +191,9 @@
187
191
  throw new Error(user.error || 'Неверный код подтверждения');
188
192
  }
189
193
  if (!user.isAdmin) {
190
- throw new Error(`Аккаунт ${user.email} не обладает правами администратора (BOSS) сервера лицензирования.`);
194
+ throw new Error(`Аккаунт ${user.email} не обладает правами администратора сервера лицензирования.`);
191
195
  }
192
- setStoredToken(user.sessionKey, user.email);
196
+ setStoredToken(user.sessionKey, user.email, user);
193
197
  resetAdmStep();
194
198
  showAdminApp(user.email);
195
199
  await refreshRegistry();
@@ -305,7 +309,7 @@
305
309
  if ($('#navAdmServers')) $('#navAdmServers').textContent = `Серверы и лицензии (${licenses.length})`;
306
310
  if ($('#navAdmFranchises')) $('#navAdmFranchises').textContent = `Франшизы (${activeFranchises.length})`;
307
311
  if ($('#navAdmOrders')) $('#navAdmOrders').textContent = `Заказы и счета (${orders.length})`;
308
- if ($('#navAdmUsers')) $('#navAdmUsers').textContent = `Клиенты и роли (${users.length})`;
312
+ if ($('#navAdmUsers')) $('#navAdmUsers').textContent = `Клиенты и администраторы (${users.length})`;
309
313
  if ($('#navAdmTickets')) $('#navAdmTickets').textContent = `Тикеты поддержки (${openTickets.length})`;
310
314
 
311
315
  renderServersTable(licenses);
@@ -347,7 +351,7 @@
347
351
  const baseHtml = l.baseActivated
348
352
  ? `<span class="tag ok">✓ Активна (${l.baseSeats || 10} вечн. + ИИ)</span>
349
353
  <div style="margin-top:4px"><button class="btn btn-ghost btn-sm" style="padding:3px 8px;font-size:11.5px;color:var(--danger)" data-adm-base="${esc(lid)}" data-activate="0">Отозвать базовую</button></div>`
350
- : `<span class="tag">Не активирована (1 сессия BOSS)</span>
354
+ : `<span class="tag">Не активирована (1 сессия)</span>
351
355
  <div style="margin-top:4px"><button class="btn btn-ok btn-sm" style="padding:3px 8px;font-size:11.5px" data-adm-base="${esc(lid)}" data-activate="1">Активировать базовую</button></div>`;
352
356
 
353
357
  const hwidHtml = hwidBound
@@ -539,7 +543,7 @@
539
543
  }).join('') || '<tr><td colspan="6" class="muted">Заказов по заданному фильтру не найдено</td></tr>';
540
544
  }
541
545
 
542
- /* 4. Таблица клиентов и ролей BOSS */
546
+ /* 4. Таблица клиентов и администраторов */
543
547
  function renderUsersTable(users = []) {
544
548
  const tbody = $('#admUsersBody');
545
549
  if (!tbody) return;
@@ -558,8 +562,8 @@
558
562
  <td><b>${esc(u.email)}</b><div class="small muted"><code>${esc(u.userId)}</code></div></td>
559
563
  <td>
560
564
  ${u.isAdmin
561
- ? `<span class="tag ok">BOSS · Администратор</span> <button class="btn btn-ghost btn-sm" style="margin-left:6px" data-toggle-admin="${esc(u.userId)}" data-make-admin="0">Снять права</button>`
562
- : `<span class="tag">Клиент</span> <button class="btn btn-ghost btn-sm" style="margin-left:6px" data-toggle-admin="${esc(u.userId)}" data-make-admin="1">Назначить BOSS</button>`}
565
+ ? `<span class="tag ok">Администратор</span> <button class="btn btn-ghost btn-sm" style="margin-left:6px" data-toggle-admin="${esc(u.userId)}" data-make-admin="0">Снять права</button>`
566
+ : `<span class="tag">Клиент</span> <button class="btn btn-ghost btn-sm" style="margin-left:6px" data-toggle-admin="${esc(u.userId)}" data-make-admin="1">Сделать администратором</button>`}
563
567
  </td>
564
568
  <td><b>${esc(u.companyName || '—')}</b><div class="small muted">ИНН ${esc(u.inn || '—')}</div></td>
565
569
  <td>Серверов: <b>${srvCount}</b> · Франшиз: <b>${frCount}</b></td>
package/assets/landing.js CHANGED
@@ -14,6 +14,7 @@
14
14
  let REGIONS = (D.regions || []).slice();
15
15
  let currentUser = null;
16
16
  let currentStage = 'product';
17
+ let requestedInitialStage = '';
17
18
  let selectedCode = null;
18
19
  let pendingAuthAction = null;
19
20
 
@@ -137,7 +138,7 @@
137
138
  }
138
139
  catch { return ''; }
139
140
  }
140
- function persistSessionInBrowser(sessionKey = '', email = '', isAdmin = false) {
141
+ function persistSessionInBrowser(sessionKey = '', email = '', isAdmin = false, userObj = null) {
141
142
  try {
142
143
  if (sessionKey) {
143
144
  localStorage.setItem('odant_work_session', sessionKey);
@@ -151,6 +152,9 @@
151
152
  if (email) {
152
153
  localStorage.setItem('odant_work_email', email);
153
154
  }
155
+ if (userObj && typeof userObj === 'object') {
156
+ localStorage.setItem('odant_work_user_cache', JSON.stringify(userObj));
157
+ }
154
158
  }
155
159
  catch { /* приватный режим */ }
156
160
  }
@@ -158,12 +162,57 @@
158
162
  try {
159
163
  localStorage.removeItem('odant_work_session');
160
164
  localStorage.removeItem('odant_work_admin_token');
165
+ localStorage.removeItem('odant_work_user_cache');
161
166
  sessionStorage.removeItem('odant_work_session');
162
167
  sessionStorage.removeItem('odant_work_admin_token');
163
168
  document.cookie = 'odant_work_session=; path=/; max-age=0; SameSite=Lax';
164
169
  }
165
170
  catch { /* ignore */ }
166
171
  }
172
+ function hydrateCachedUser() {
173
+ try {
174
+ const key = localStorage.getItem('odant_work_session')
175
+ || sessionStorage.getItem('odant_work_session')
176
+ || localStorage.getItem('odant_work_admin_token')
177
+ || readCookieSession()
178
+ || '';
179
+ if (!key) return;
180
+ const raw = localStorage.getItem('odant_work_user_cache');
181
+ if (raw) {
182
+ const cached = JSON.parse(raw);
183
+ if (cached?.email) {
184
+ currentUser = { ...cached, sessionKey: key, ofertaAccepted: cached.ofertaAccepted !== false };
185
+ onAuthenticated();
186
+ return;
187
+ }
188
+ }
189
+ // Если есть подписанный токен wks_1.<payload>.<sig>, декодируем payload синхронно до ответа /api/auth/me
190
+ if (key.startsWith('wks_1.')) {
191
+ const parts = key.split('.');
192
+ if (parts[1]) {
193
+ const b64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
194
+ const json = decodeURIComponent(escape(atob(b64)));
195
+ const payload = JSON.parse(json);
196
+ if (payload?.email) {
197
+ currentUser = {
198
+ userId: payload.userId || '',
199
+ email: payload.email,
200
+ name: payload.name || payload.email,
201
+ companyName: payload.companyName || '',
202
+ inn: payload.inn || '',
203
+ isAdmin: Boolean(payload.isAdmin),
204
+ ofertaAccepted: true,
205
+ sessionKey: key,
206
+ servers: [],
207
+ franchises: [],
208
+ };
209
+ onAuthenticated();
210
+ }
211
+ }
212
+ }
213
+ }
214
+ catch { /* ignore */ }
215
+ }
167
216
 
168
217
  function openAuth(hintMsg = '', intentTitle = '') {
169
218
  if ($('#authErrorMsg')) $('#authErrorMsg').textContent = hintMsg || '';
@@ -187,7 +236,7 @@
187
236
  if ($('#authLoggedEmail')) $('#authLoggedEmail').textContent = currentUser.email || '';
188
237
  if ($('#authLoggedRoleBadge')) {
189
238
  $('#authLoggedRoleBadge').innerHTML = currentUser.isAdmin
190
- ? '<span class="tag ok">BOSS · Главный администратор</span>'
239
+ ? '<span class="tag ok">Администратор портала</span>'
191
240
  : '<span class="tag">Клиент ODANT-WORK</span>';
192
241
  }
193
242
  if ($('#btnGoAdminModal')) $('#btnGoAdminModal').style.display = currentUser.isAdmin ? '' : 'none';
@@ -286,7 +335,7 @@
286
335
  try {
287
336
  const data = await api('/api/auth/verify-code', { email, code });
288
337
  currentUser = data;
289
- persistSessionInBrowser(data.sessionKey, data.email, data.isAdmin);
338
+ persistSessionInBrowser(data.sessionKey, data.email, data.isAdmin, data);
290
339
  resetAuthStep();
291
340
  onAuthenticated();
292
341
  closeAuth();
@@ -335,9 +384,12 @@
335
384
  const data = await api('/api/auth/me?sessionKey=' + encodeURIComponent(key));
336
385
  if (data.authenticated && data.user) {
337
386
  currentUser = { ...data.user, sessionKey: data.user.sessionKey || key };
338
- persistSessionInBrowser(currentUser.sessionKey, currentUser.email, currentUser.isAdmin);
387
+ persistSessionInBrowser(currentUser.sessionKey, currentUser.email, currentUser.isAdmin, currentUser);
339
388
  onAuthenticated();
340
- if (location.hash === '#addons' && isUserReadyForOrders()) switchStage('addons', { scroll: false });
389
+ if ((requestedInitialStage === 'addons' || location.hash === '#addons') && isUserReadyForOrders()) {
390
+ requestedInitialStage = '';
391
+ switchStage('addons', { scroll: false });
392
+ }
341
393
  }
342
394
  }
343
395
  catch { /* сессия отозвана */ }
@@ -368,8 +420,8 @@
368
420
  if ($('#authOfertaCheck')) $('#authOfertaCheck').checked = Boolean(currentUser.ofertaAccepted);
369
421
  if ($('#userBadgeBox')) {
370
422
  const orgInfo = currentUser.companyName ? `${esc(currentUser.companyName)}${currentUser.inn ? ' (ИНН ' + esc(currentUser.inn) + ')' : ''} · ` : '';
371
- const bossBadge = currentUser.isAdmin ? `<span class="tag ok" style="margin-right:6px">BOSS · Администратор</span> <a href="/admin" style="font-weight:700;margin-right:8px">Открыть Админ-панель →</a> · ` : '';
372
- $('#userBadgeBox').innerHTML = `${bossBadge}${orgInfo}Аккаунт: <b>${esc(currentUser.email)}</b> · <a href="#" data-action="logout" style="color:var(--muted)">Выйти</a>`;
423
+ const adminBadge = currentUser.isAdmin ? `<span class="tag ok" style="margin-right:6px">Администратор</span> <a href="/admin" style="font-weight:700;margin-right:8px">Открыть Админ-панель →</a> · ` : '';
424
+ $('#userBadgeBox').innerHTML = `${adminBadge}${orgInfo}Аккаунт: <b>${esc(currentUser.email)}</b> · <a href="#" data-action="logout" style="color:var(--muted)">Выйти</a>`;
373
425
  }
374
426
  syncClientServerPicker();
375
427
  renderCabinetServers();
@@ -454,7 +506,7 @@
454
506
 
455
507
  const baseBadge = s.baseActivated
456
508
  ? `<span class="tag ok">✓ Базовая: ${s.baseSeats || 10} вечн. мест + ИИ</span>`
457
- : `<span class="tag">Базовая не активна (1 сессия BOSS)</span>`;
509
+ : `<span class="tag">Базовая не активирована (1 сессия)</span>`;
458
510
 
459
511
  const files = s.licenseFiles || [];
460
512
  const activeAddons = s.activeAddons || [];
@@ -1834,14 +1886,18 @@
1834
1886
 
1835
1887
  function start() {
1836
1888
  applyTheme(document.documentElement.getAttribute('data-theme') || 'dark');
1889
+ requestedInitialStage = D.initialStage || location.hash.slice(1);
1890
+ hydrateCachedUser();
1837
1891
  bind();
1838
1892
  renderOrbit();
1839
1893
  recalcBaseOrder();
1840
1894
  recalcPartner();
1841
1895
  renderRegionsTable();
1842
1896
  initBackdrop();
1843
- const initial = D.initialStage || location.hash.slice(1);
1844
- switchStage(initial && initial !== 'addons' && initial !== 'admin' ? initial : 'product', { scroll: false });
1897
+ const targetStage = (requestedInitialStage === 'addons' && isUserReadyForOrders())
1898
+ ? 'addons'
1899
+ : (requestedInitialStage && requestedInitialStage !== 'addons' && requestedInitialStage !== 'admin' ? requestedInitialStage : 'product');
1900
+ switchStage(targetStage, { scroll: false });
1845
1901
  if (document.fonts?.ready) document.fonts.ready.then(moveIndicator);
1846
1902
  initMap().then(() => selectRegion(selectedCode || 'RU-SVE'));
1847
1903
  restoreSession();
package/bin/server.mjs CHANGED
@@ -14,4 +14,4 @@ const server = await app.listen(port);
14
14
  const addr = server.address();
15
15
  const actualPort = typeof addr === 'object' && addr ? addr.port : port;
16
16
  console.log(`[work.odant.org] Витрина ODANT-WORK: http://localhost:${actualPort}/`);
17
- console.log(`[work.odant.org] Панель администратора (BOSS): http://localhost:${actualPort}/admin (первый вошедший по Email становится BOSS)`);
17
+ console.log(`[work.odant.org] Панель администратора: http://localhost:${actualPort}/admin`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "work-license-server",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "description": "Лицензионный сервер, биллинг ЮKassa, боты поддержки (MAX, Telegram, VK) и лендинг ПАК WORK (work.odant.org)",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/admin.js CHANGED
@@ -58,14 +58,14 @@ export function renderAdminPage({ publicKey = '', assetVersion = '' } = {}) {
58
58
  <div class="modal-head" style="margin-bottom:18px">
59
59
  <img src="/assets/odant-logo.png" alt="ODANT">
60
60
  <div>
61
- <span class="kicker">Защищённый контур BOSS</span>
61
+ <span class="kicker">Защищённый контур</span>
62
62
  <h1 class="h3" style="margin:4px 0 2px;font-size:21px">Админ-панель ODANT-WORK</h1>
63
63
  <div class="small muted">Вход по вашему Email администратора с 6-значным кодом из письма</div>
64
64
  </div>
65
65
  </div>
66
66
  <div id="admStepEmail">
67
67
  <div class="field">
68
- <label for="admLoginEmail">Email администратора (BOSS)</label>
68
+ <label for="admLoginEmail">Email администратора</label>
69
69
  <input type="email" id="admLoginEmail" value="" placeholder="Введите ваш email…" autocomplete="email" required>
70
70
  </div>
71
71
  <button type="button" class="btn btn-primary btn-lg" style="width:100%" id="btnAdmSendCode" data-action="adm-send-code">
@@ -115,7 +115,7 @@ export function renderAdminPage({ publicKey = '', assetVersion = '' } = {}) {
115
115
 
116
116
  <div class="top-actions">
117
117
  <button class="icon-btn" id="btnTheme" title="Светлая / тёмная тема" aria-label="Переключить тему">${icon('sun')}</button>
118
- <button class="btn btn-primary btn-sm" data-action="open-adm-profile" title="Аккаунт администратора">${icon('user')}<span id="admUserLabel">BOSS</span></button>
118
+ <button class="btn btn-primary btn-sm" data-action="open-adm-profile" title="Аккаунт администратора">${icon('user')}<span id="admUserLabel">Администратор</span></button>
119
119
  </div>
120
120
  </header>
121
121
 
@@ -127,7 +127,7 @@ export function renderAdminPage({ publicKey = '', assetVersion = '' } = {}) {
127
127
  <button type="button" class="sec-nav-item" data-adm-tab="franchises" id="navAdmFranchises">Франшизы (0)</button>
128
128
  <div class="sec-nav-sep"></div>
129
129
  <button type="button" class="sec-nav-item" data-adm-tab="orders" id="navAdmOrders">Заказы и счета (0)</button>
130
- <button type="button" class="sec-nav-item" data-adm-tab="users" id="navAdmUsers">Клиенты и роли (0)</button>
130
+ <button type="button" class="sec-nav-item" data-adm-tab="users" id="navAdmUsers">Клиенты и администраторы (0)</button>
131
131
  <button type="button" class="sec-nav-item" data-adm-tab="tickets" id="navAdmTickets">Тикеты поддержки (0)</button>
132
132
  </aside>
133
133
 
@@ -223,10 +223,10 @@ export function renderAdminPage({ publicKey = '', assetVersion = '' } = {}) {
223
223
  </div>
224
224
  </div>
225
225
 
226
- <!-- 4. КЛИЕНТЫ И РОЛИ BOSS -->
226
+ <!-- 4. КЛИЕНТЫ И АДМИНИСТРАТОРЫ -->
227
227
  <div data-adm-sec="users" style="display:none">
228
228
  <div class="sec-bar" style="margin-bottom:12px">
229
- <h2 class="sec-title">Зарегистрированные клиенты и права администраторов (BOSS)</h2>
229
+ <h2 class="sec-title">Зарегистрированные клиенты и администраторы</h2>
230
230
  <div class="sec-controls">
231
231
  <input type="search" id="inpFilterUsers" placeholder="Поиск по Email, организации, ИНН…">
232
232
  </div>
@@ -416,7 +416,7 @@ export function renderAdminPage({ publicKey = '', assetVersion = '' } = {}) {
416
416
  <div style="padding:14px 16px;border-radius:12px;border:1px solid var(--line-strong);background:var(--field);margin-bottom:14px">
417
417
  <div class="small muted">Вы вошли как</div>
418
418
  <div style="font-size:16px;font-weight:700;margin-top:2px" id="admProfileEmail">—</div>
419
- <div style="margin-top:6px"><span class="tag ok">BOSS · Главный администратор</span></div>
419
+ <div style="margin-top:6px"><span class="tag ok">Администратор портала</span></div>
420
420
  </div>
421
421
  <div style="display:flex;flex-direction:column;gap:8px">
422
422
  <a class="btn btn-primary" href="/#addons" style="width:100%;text-decoration:none">Перейти в Личный кабинет</a>
package/src/server.js CHANGED
@@ -940,7 +940,7 @@ export function createLicenseServerApp(options = {}) {
940
940
  const body = await readJsonBody(req);
941
941
  const tok = extractBearerToken(req, body, url);
942
942
  if (!store.verifyAdminToken(tok))
943
- return sendJson(res, 401, { error: 'Требуется авторизация администратора (BOSS)' });
943
+ return sendJson(res, 401, { error: 'Требуется авторизация администратора' });
944
944
  const updated = store.setUserAdminRole(body.userId || body.email, body.isAdmin !== false);
945
945
  return sendJson(res, 200, { ok: true, user: updated });
946
946
  }
package/src/store.js CHANGED
@@ -149,14 +149,14 @@ export class LicenseStore {
149
149
  login: userFromTok.email,
150
150
  email: userFromTok.email,
151
151
  userId: userFromTok.userId,
152
- role: 'BOSS',
152
+ role: 'ADMIN',
153
153
  };
154
154
  }
155
155
  if (clean === this.adminPassword && this.adminPassword.length >= 6) {
156
156
  return {
157
157
  authenticated: true,
158
158
  login: this.adminLogin,
159
- role: 'BOSS',
159
+ role: 'ADMIN',
160
160
  };
161
161
  }
162
162
  const sess = this.adminSessions.get(clean);
@@ -169,12 +169,12 @@ export class LicenseStore {
169
169
  return {
170
170
  authenticated: true,
171
171
  login: sess.login || this.adminLogin,
172
- role: 'BOSS',
172
+ role: 'ADMIN',
173
173
  };
174
174
  }
175
175
 
176
176
  /**
177
- * Гарантировать, что первый зарегистрированный на сервере пользователь является главным Администратором (BOSS).
177
+ * Гарантировать, что первый зарегистрированный на сервере пользователь является администратором портала.
178
178
  */
179
179
  _ensureBootstrapAdmin() {
180
180
  const allUsers = Object.values(this.state.users || {});
@@ -182,19 +182,19 @@ export class LicenseStore {
182
182
  return null;
183
183
  const existingAdmin = allUsers.find(u => u.isAdmin);
184
184
  if (existingAdmin) {
185
- existingAdmin.role = 'BOSS';
185
+ existingAdmin.role = 'ADMIN';
186
186
  return existingAdmin;
187
187
  }
188
188
  const firstUser = [...allUsers].sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0))[0];
189
189
  if (firstUser) {
190
190
  firstUser.isAdmin = true;
191
- firstUser.role = 'BOSS';
191
+ firstUser.role = 'ADMIN';
192
192
  }
193
193
  return firstUser || null;
194
194
  }
195
195
 
196
196
  /**
197
- * Назначить или снять права администратора (BOSS) у пользователя по Email или userId.
197
+ * Назначить или снять права администратора у пользователя по Email или userId.
198
198
  */
199
199
  setUserAdminRole(emailOrUserId = '', isAdmin = true) {
200
200
  const key = String(emailOrUserId || '').trim();
@@ -207,11 +207,11 @@ export class LicenseStore {
207
207
  if (!nextAdmin && user.isAdmin) {
208
208
  const otherAdmins = Object.values(this.state.users || {}).filter(u => u.isAdmin && u.userId !== user.userId);
209
209
  if (!otherAdmins.length) {
210
- throw new Error('Нельзя снять права BOSS у единственного администратора сервера');
210
+ throw new Error('Нельзя снять права у единственного администратора сервера');
211
211
  }
212
212
  }
213
213
  user.isAdmin = nextAdmin;
214
- user.role = nextAdmin ? 'BOSS' : 'USER';
214
+ user.role = nextAdmin ? 'ADMIN' : 'CLIENT';
215
215
  this._save();
216
216
  return this._formatUserResponse(user);
217
217
  }
@@ -1229,7 +1229,7 @@ export class LicenseStore {
1229
1229
  if (user) {
1230
1230
  if (verifiedPayload.isAdmin) {
1231
1231
  user.isAdmin = true;
1232
- user.role = 'BOSS';
1232
+ user.role = 'ADMIN';
1233
1233
  }
1234
1234
  user.sessionKey = key;
1235
1235
  try { this._save(); } catch { /* ignore */ }
@@ -1456,7 +1456,7 @@ export class LicenseStore {
1456
1456
  userId: user.userId,
1457
1457
  email: user.email,
1458
1458
  isAdmin: Boolean(user.isAdmin),
1459
- role: user.isAdmin ? 'BOSS' : 'USER',
1459
+ role: user.isAdmin ? 'ADMIN' : 'CLIENT',
1460
1460
  authProvider: user.authProvider || 'email',
1461
1461
  name: user.name,
1462
1462
  position: user.position || '',
@@ -1590,7 +1590,7 @@ export class LicenseStore {
1590
1590
  const isFirstAdmin = !Object.values(this.state.users || {}).some(u => u.isAdmin);
1591
1591
  if (isFirstAdmin) {
1592
1592
  user.isAdmin = true;
1593
- user.role = 'BOSS';
1593
+ user.role = 'ADMIN';
1594
1594
  }
1595
1595
 
1596
1596
  // Выпускаем криптографически подписанный бессрочный токен сессии (wks_1...), устойчивый к перезапускам сервера