jsgui3-server 0.0.148 → 0.0.150
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/.github/agents/Mobile Developer.agent.md +89 -0
- package/.github/workflows/control-scan-manifest-check.yml +31 -0
- package/AGENTS.md +4 -0
- package/README.md +215 -3
- package/admin-ui/client.js +81 -51
- package/admin-ui/v1/admin_auth_service.js +197 -0
- package/admin-ui/v1/admin_user_store.js +71 -0
- package/admin-ui/v1/client.js +17 -0
- package/admin-ui/v1/controls/admin_shell.js +1399 -0
- package/admin-ui/v1/controls/group_box.js +84 -0
- package/admin-ui/v1/controls/stat_card.js +125 -0
- package/admin-ui/v1/server.js +658 -0
- package/admin-ui/v1/utils/formatters.js +68 -0
- package/dev-status.svg +139 -0
- package/docs/admin-extension-guide.md +345 -0
- package/docs/api-reference.md +301 -43
- package/docs/books/adaptive-control-improvements/01-control-candidate-matrix.md +122 -0
- package/docs/books/adaptive-control-improvements/02-tier-1-layout-playbooks.md +207 -0
- package/docs/books/adaptive-control-improvements/03-tier-2-navigation-form-overlay.md +140 -0
- package/docs/books/adaptive-control-improvements/04-cross-cutting-platform-functionality.md +141 -0
- package/docs/books/adaptive-control-improvements/05-styling-theming-density-upgrades.md +114 -0
- package/docs/books/adaptive-control-improvements/06-testing-quality-gates.md +97 -0
- package/docs/books/adaptive-control-improvements/07-delivery-roadmap-and-ownership.md +137 -0
- package/docs/books/adaptive-control-improvements/08-appendix-tier1-acceptance-and-pr-templates.md +261 -0
- package/docs/books/adaptive-control-improvements/README.md +66 -0
- package/docs/books/admin-ui-authentication/01-threat-model-and-goals.md +124 -0
- package/docs/books/admin-ui-authentication/02-session-model-and-token-model.md +75 -0
- package/docs/books/admin-ui-authentication/03-auth-middleware-patterns.md +77 -0
- package/docs/books/admin-ui-authentication/README.md +25 -0
- package/docs/books/creating-a-new-admin-ui/01-introduction-and-vision.md +130 -0
- package/docs/books/creating-a-new-admin-ui/02-architecture-and-data-flow.md +298 -0
- package/docs/books/creating-a-new-admin-ui/03-server-introspection.md +381 -0
- package/docs/books/creating-a-new-admin-ui/04-admin-module-adapter-layer.md +592 -0
- package/docs/books/creating-a-new-admin-ui/05-domain-controls-stat-cards-and-gauges.md +513 -0
- package/docs/books/creating-a-new-admin-ui/06-domain-controls-process-manager.md +544 -0
- package/docs/books/creating-a-new-admin-ui/07-domain-controls-resource-pool-inspector.md +493 -0
- package/docs/books/creating-a-new-admin-ui/08-domain-controls-route-table-and-api-explorer.md +586 -0
- package/docs/books/creating-a-new-admin-ui/09-domain-controls-log-viewer-and-activity-feed.md +490 -0
- package/docs/books/creating-a-new-admin-ui/10-domain-controls-build-status-and-bundle-inspector.md +526 -0
- package/docs/books/creating-a-new-admin-ui/11-domain-controls-configuration-panel.md +808 -0
- package/docs/books/creating-a-new-admin-ui/12-admin-shell-layout-sidebar-navigation.md +210 -0
- package/docs/books/creating-a-new-admin-ui/13-telemetry-integration.md +556 -0
- package/docs/books/creating-a-new-admin-ui/14-realtime-sse-observable-integration.md +485 -0
- package/docs/books/creating-a-new-admin-ui/15-styling-theming-aero-design-system.md +521 -0
- package/docs/books/creating-a-new-admin-ui/16-testing-and-quality-assurance.md +147 -0
- package/docs/books/creating-a-new-admin-ui/17-next-steps-process-resource-roadmap.md +356 -0
- package/docs/books/creating-a-new-admin-ui/README.md +68 -0
- package/docs/books/device-adaptive-composition/01-platform-feature-audit.md +177 -0
- package/docs/books/device-adaptive-composition/02-responsive-composition-model.md +187 -0
- package/docs/books/device-adaptive-composition/03-data-model-vs-view-model.md +231 -0
- package/docs/books/device-adaptive-composition/04-styling-theme-breakpoints.md +234 -0
- package/docs/books/device-adaptive-composition/05-showcase-app-multi-device-assessment.md +193 -0
- package/docs/books/device-adaptive-composition/06-implementation-patterns-and-apis.md +346 -0
- package/docs/books/device-adaptive-composition/07-testing-harness-and-quality-gates.md +265 -0
- package/docs/books/device-adaptive-composition/08-roadmap-and-adoption-plan.md +250 -0
- package/docs/books/device-adaptive-composition/README.md +47 -0
- package/docs/books/jsgui3-bundling-research-book/00-table-of-contents.md +35 -0
- package/docs/books/jsgui3-bundling-research-book/01-pipeline-and-runtime-semantics.md +34 -0
- package/docs/books/jsgui3-bundling-research-book/02-javascript-bundling-core.md +36 -0
- package/docs/books/jsgui3-bundling-research-book/03-style-extraction-and-css-compilation.md +35 -0
- package/docs/books/jsgui3-bundling-research-book/04-static-publishing-and-delivery.md +39 -0
- package/docs/books/jsgui3-bundling-research-book/05-current-limits-and-size-bloat-vectors.md +25 -0
- package/docs/books/jsgui3-bundling-research-book/06-unused-module-elimination-strategy.md +77 -0
- package/docs/books/jsgui3-bundling-research-book/07-jsgui3-html-control-and-mixin-pruning.md +63 -0
- package/docs/books/jsgui3-bundling-research-book/08-test-and-verification-methodology.md +43 -0
- package/docs/books/jsgui3-bundling-research-book/09-roadmap-and-rollout.md +42 -0
- package/docs/books/jsgui3-bundling-research-book/10-further-research-strategies-and-upgrades.md +211 -0
- package/docs/books/jsgui3-bundling-research-book/README.md +35 -0
- package/docs/bundling-system-deep-dive.md +9 -4
- package/docs/comparison-report-express-plex-cpanel.md +549 -0
- package/docs/comprehensive-documentation.md +49 -18
- package/docs/configuration-reference.md +152 -27
- package/docs/core/README.md +19 -0
- package/docs/core/jsgui3-server-core-book/00-table-of-contents.md +21 -0
- package/docs/core/jsgui3-server-core-book/01-startup-readiness-state-machine.md +41 -0
- package/docs/core/jsgui3-server-core-book/02-resource-abstraction-and-lifecycle.md +92 -0
- package/docs/core/jsgui3-server-core-book/03-resource-pool-and-event-topology.md +47 -0
- package/docs/core/jsgui3-server-core-book/04-sse-publisher-semantics.md +41 -0
- package/docs/core/jsgui3-server-core-book/05-serve-factory-resource-wiring.md +46 -0
- package/docs/core/jsgui3-server-core-book/06-e2e-testing-methodology.md +48 -0
- package/docs/core/jsgui3-server-core-book/07-defect-detection-and-hardening-loop.md +47 -0
- package/docs/designs/server-admin-interface-aero.svg +611 -0
- package/docs/publishers-guide.md +59 -4
- package/docs/resources-guide.md +184 -35
- package/docs/simple-server-api-design.md +72 -17
- package/docs/system-architecture.md +18 -14
- package/docs/troubleshooting.md +84 -53
- package/examples/controls/15) window, observable SSE/server.js +6 -1
- package/examples/controls/19) window, auto observable ui/server.js +9 -0
- package/examples/controls/20) window, task manager app/README.md +133 -0
- package/examples/controls/20) window, task manager app/client.js +797 -0
- package/examples/controls/20) window, task manager app/server.js +178 -0
- package/examples/controls/6) window, color_palette/client.js +165 -68
- package/examples/controls/9) window, date picker/client.js +362 -76
- package/examples/controls/9b) window, shared data.model mirrored date pickers/client.js +104 -83
- package/examples/jsgui3-html/06) theming/client.js +22 -1
- package/examples/jsgui3-html/10) binding-debugger/client.js +137 -1
- package/http/responders/static/Static_Route_HTTP_Responder.js +52 -34
- package/lab/experiments/capture-color-controls.js +196 -0
- package/lab/results/screenshots/color-controls/full_page.png +0 -0
- package/lab/results/screenshots/color-controls/section_1_color_grid_12x12.png +0 -0
- package/lab/results/screenshots/color-controls/section_2_color_grid_4x2.png +0 -0
- package/lab/results/screenshots/color-controls/section_3_color_palette.png +0 -0
- package/lab/results/screenshots/color-controls/section_4_palette_comparison.png +0 -0
- package/lab/results/screenshots/color-controls/section_5_raw_swatches.png +0 -0
- package/lab/results/screenshots/color-controls/section_6_optimized_crayola.png +0 -0
- package/lab/results/screenshots/color-controls/section_7_pastel_palette.png +0 -0
- package/lab/results/screenshots/color-controls/section_8_extended_144.png +0 -0
- package/lab/screenshot-utils.js +248 -0
- package/module.js +12 -0
- package/package.json +12 -2
- package/publishers/Publishers.js +4 -3
- package/publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner.js +5 -5
- package/publishers/http-sse-publisher.js +341 -0
- package/resources/process-resource.js +950 -0
- package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +129 -33
- package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +18 -7
- package/resources/processors/bundlers/js/esbuild/JSGUI3_HTML_Control_Optimizer.js +829 -0
- package/resources/remote-process-resource.js +355 -0
- package/resources/server-resource-pool.js +354 -41
- package/serve-factory.js +442 -259
- package/server.js +288 -13
- package/tests/README.md +71 -4
- package/tests/admin-ui-jsgui-controls.test.js +581 -0
- package/tests/admin-ui-render.test.js +24 -0
- package/tests/assigners.test.js +56 -40
- package/tests/bundling-default-control-elimination.puppeteer.test.js +260 -0
- package/tests/configuration-validation.test.js +21 -18
- package/tests/content-analysis.test.js +7 -6
- package/tests/control-optimizer-cache-behavior.test.js +52 -0
- package/tests/control-scan-manifest-regression.test.js +144 -0
- package/tests/end-to-end.test.js +15 -14
- package/tests/error-handling.test.js +222 -179
- package/tests/fixtures/bundling-default-button-client.js +37 -0
- package/tests/fixtures/bundling-default-window-client.js +34 -0
- package/tests/fixtures/control_scan_manifest_expectations.json +48 -0
- package/tests/fixtures/resource-monitor-client.js +319 -0
- package/tests/helpers/puppeteer-e2e-harness.js +317 -0
- package/tests/http-sse-publisher.test.js +136 -0
- package/tests/performance.test.js +69 -65
- package/tests/process-resource.test.js +138 -0
- package/tests/publishers.test.js +7 -7
- package/tests/remote-process-resource.test.js +160 -0
- package/tests/sass-controls.e2e.test.js +7 -1
- package/tests/serve-resources.test.js +270 -0
- package/tests/serve.test.js +120 -50
- package/tests/server-resource-pool.test.js +106 -0
- package/tests/small-controls-bundle-size.test.js +252 -0
- package/tests/test-runner.js +14 -1
- package/tests/window-examples.puppeteer.test.js +204 -1
- package/tests/window-resource-integration.puppeteer.test.js +585 -0
- package/tests/temp_invalid.js +0 -7
- package/tests/temp_invalid_utf8.js +0 -1
- package/tests/temp_malformed.js +0 -10
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
class Admin_Auth_Service {
|
|
6
|
+
constructor(spec = {}) {
|
|
7
|
+
this.user_store = spec.user_store;
|
|
8
|
+
this.session_ttl_ms = Number.isFinite(spec.session_ttl_ms) ? spec.session_ttl_ms : (8 * 60 * 60 * 1000);
|
|
9
|
+
this.cookie_name = spec.cookie_name || 'jsgui_admin_v1_sid';
|
|
10
|
+
this.sessions = new Map();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
_read_json_body(req) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const chunks = [];
|
|
16
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
17
|
+
req.on('end', () => {
|
|
18
|
+
if (chunks.length === 0) return resolve({});
|
|
19
|
+
try {
|
|
20
|
+
const text = Buffer.concat(chunks).toString('utf8');
|
|
21
|
+
resolve(text ? JSON.parse(text) : {});
|
|
22
|
+
} catch (error) {
|
|
23
|
+
reject(error);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
req.on('error', reject);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_parse_cookies(req) {
|
|
31
|
+
const cookies = {};
|
|
32
|
+
const header = req && req.headers ? req.headers.cookie : '';
|
|
33
|
+
if (!header) return cookies;
|
|
34
|
+
|
|
35
|
+
const parts = header.split(';');
|
|
36
|
+
parts.forEach((part) => {
|
|
37
|
+
const idx = part.indexOf('=');
|
|
38
|
+
if (idx === -1) return;
|
|
39
|
+
const key = part.slice(0, idx).trim();
|
|
40
|
+
const value = part.slice(idx + 1).trim();
|
|
41
|
+
cookies[key] = decodeURIComponent(value);
|
|
42
|
+
});
|
|
43
|
+
return cookies;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
_set_session_cookie(res, session_id) {
|
|
47
|
+
const secure = process.env.NODE_ENV === 'production' ? '; Secure' : '';
|
|
48
|
+
const cookie_value = this.cookie_name + '=' + encodeURIComponent(session_id) + '; Path=/; HttpOnly; SameSite=Lax; Max-Age=' + Math.floor(this.session_ttl_ms / 1000) + secure;
|
|
49
|
+
res.setHeader('Set-Cookie', cookie_value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_clear_session_cookie(res) {
|
|
53
|
+
const secure = process.env.NODE_ENV === 'production' ? '; Secure' : '';
|
|
54
|
+
res.setHeader('Set-Cookie', this.cookie_name + '=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0' + secure);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
_new_session_id() {
|
|
58
|
+
return crypto.randomBytes(24).toString('hex');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_cleanup_expired() {
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
for (const [id, session] of this.sessions.entries()) {
|
|
64
|
+
if (!session || session.expires_at <= now) {
|
|
65
|
+
this.sessions.delete(id);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
get_session(req) {
|
|
71
|
+
this._cleanup_expired();
|
|
72
|
+
const cookies = this._parse_cookies(req);
|
|
73
|
+
const sid = cookies[this.cookie_name];
|
|
74
|
+
if (!sid) return null;
|
|
75
|
+
|
|
76
|
+
const session = this.sessions.get(sid);
|
|
77
|
+
if (!session) return null;
|
|
78
|
+
if (session.expires_at <= Date.now()) {
|
|
79
|
+
this.sessions.delete(sid);
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
session_id: sid,
|
|
84
|
+
user: session.user,
|
|
85
|
+
expires_at: session.expires_at
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
is_authenticated(req) {
|
|
90
|
+
return !!this.get_session(req);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
has_role(req, role_name) {
|
|
94
|
+
const session = this.get_session(req);
|
|
95
|
+
if (!session || !session.user) return false;
|
|
96
|
+
const roles = Array.isArray(session.user.roles) ? session.user.roles : [];
|
|
97
|
+
return roles.includes(role_name);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
has_any_role(req, role_names) {
|
|
101
|
+
const session = this.get_session(req);
|
|
102
|
+
if (!session || !session.user) return false;
|
|
103
|
+
const roles = Array.isArray(session.user.roles) ? session.user.roles : [];
|
|
104
|
+
if (!Array.isArray(role_names) || role_names.length === 0) return false;
|
|
105
|
+
return role_names.some((role_name) => roles.includes(role_name));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
create_session(user, res) {
|
|
109
|
+
const session_id = this._new_session_id();
|
|
110
|
+
const expires_at = Date.now() + this.session_ttl_ms;
|
|
111
|
+
this.sessions.set(session_id, {
|
|
112
|
+
user,
|
|
113
|
+
created_at: Date.now(),
|
|
114
|
+
expires_at
|
|
115
|
+
});
|
|
116
|
+
this._set_session_cookie(res, session_id);
|
|
117
|
+
return { session_id, expires_at, user };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
destroy_session(req, res) {
|
|
121
|
+
const cookies = this._parse_cookies(req);
|
|
122
|
+
const sid = cookies[this.cookie_name];
|
|
123
|
+
if (sid) this.sessions.delete(sid);
|
|
124
|
+
this._clear_session_cookie(res);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async handle_login(req, res) {
|
|
128
|
+
if (String(req.method || 'GET').toUpperCase() !== 'POST') {
|
|
129
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
130
|
+
res.end(JSON.stringify({ ok: false, error: 'Method Not Allowed' }));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let body;
|
|
135
|
+
try {
|
|
136
|
+
body = await this._read_json_body(req);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
139
|
+
res.end(JSON.stringify({ ok: false, error: 'Invalid JSON body' }));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const username = body.username;
|
|
144
|
+
const password = body.password;
|
|
145
|
+
const user = this.user_store.verify_credentials(username, password);
|
|
146
|
+
if (!user) {
|
|
147
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
148
|
+
res.end(JSON.stringify({ ok: false, error: 'Invalid credentials' }));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const session = this.create_session(user, res);
|
|
153
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
154
|
+
res.end(JSON.stringify({
|
|
155
|
+
ok: true,
|
|
156
|
+
user: session.user,
|
|
157
|
+
expires_at: session.expires_at
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
handle_logout(req, res) {
|
|
162
|
+
if (String(req.method || 'GET').toUpperCase() !== 'POST') {
|
|
163
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
164
|
+
res.end(JSON.stringify({ ok: false, error: 'Method Not Allowed' }));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
this.destroy_session(req, res);
|
|
169
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
170
|
+
res.end(JSON.stringify({ ok: true }));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
handle_session(req, res) {
|
|
174
|
+
if (String(req.method || 'GET').toUpperCase() !== 'GET') {
|
|
175
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
176
|
+
res.end(JSON.stringify({ ok: false, error: 'Method Not Allowed' }));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const session = this.get_session(req);
|
|
181
|
+
if (!session) {
|
|
182
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
183
|
+
res.end(JSON.stringify({ ok: true, authenticated: false }));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
188
|
+
res.end(JSON.stringify({
|
|
189
|
+
ok: true,
|
|
190
|
+
authenticated: true,
|
|
191
|
+
user: session.user,
|
|
192
|
+
expires_at: session.expires_at
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = Admin_Auth_Service;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
class Admin_User_Store {
|
|
6
|
+
constructor(spec = {}) {
|
|
7
|
+
this._users = new Map();
|
|
8
|
+
this._scrypt_cost = spec.scrypt_cost || 16384;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
_hash_password(password, salt) {
|
|
12
|
+
const key = crypto.scryptSync(password, salt, 64, { N: this._scrypt_cost });
|
|
13
|
+
return key.toString('hex');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
add_user(spec = {}) {
|
|
17
|
+
const username = String(spec.username || '').trim();
|
|
18
|
+
const password = String(spec.password || '');
|
|
19
|
+
const roles = Array.isArray(spec.roles) ? spec.roles : ['admin_read'];
|
|
20
|
+
|
|
21
|
+
if (!username) throw new Error('username is required');
|
|
22
|
+
if (!password) throw new Error('password is required');
|
|
23
|
+
|
|
24
|
+
const salt = crypto.randomBytes(16).toString('hex');
|
|
25
|
+
const password_hash = this._hash_password(password, salt);
|
|
26
|
+
|
|
27
|
+
this._users.set(username, {
|
|
28
|
+
username,
|
|
29
|
+
salt,
|
|
30
|
+
password_hash,
|
|
31
|
+
roles,
|
|
32
|
+
created_at: Date.now()
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return { username, roles: roles.slice() };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
has_user(username) {
|
|
39
|
+
return this._users.has(username);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get_user(username) {
|
|
43
|
+
const user = this._users.get(username);
|
|
44
|
+
if (!user) return null;
|
|
45
|
+
return {
|
|
46
|
+
username: user.username,
|
|
47
|
+
roles: user.roles.slice(),
|
|
48
|
+
created_at: user.created_at
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
verify_credentials(username, password) {
|
|
53
|
+
const user = this._users.get(String(username || ''));
|
|
54
|
+
if (!user) return null;
|
|
55
|
+
|
|
56
|
+
const attempted_hash = this._hash_password(String(password || ''), user.salt);
|
|
57
|
+
const expected = Buffer.from(user.password_hash, 'hex');
|
|
58
|
+
const attempted = Buffer.from(attempted_hash, 'hex');
|
|
59
|
+
|
|
60
|
+
if (expected.length !== attempted.length) return null;
|
|
61
|
+
const ok = crypto.timingSafeEqual(expected, attempted);
|
|
62
|
+
if (!ok) return null;
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
username: user.username,
|
|
66
|
+
roles: user.roles.slice()
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = Admin_User_Store;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Admin UI v1 — client entry point.
|
|
5
|
+
*
|
|
6
|
+
* This file is the ESBuild entry point that the HTTP_Webpage_Publisher
|
|
7
|
+
* bundles and serves. It imports the Admin_Shell control (which
|
|
8
|
+
* transitively pulls in Group_Box and Stat_Card) and exports the
|
|
9
|
+
* jsgui module with Admin_Shell registered on controls.
|
|
10
|
+
*/
|
|
11
|
+
const jsgui = require('./controls/admin_shell');
|
|
12
|
+
|
|
13
|
+
// The require above already registers controls.Admin_Shell,
|
|
14
|
+
// controls.Stat_Card, and controls.Group_Box on the jsgui
|
|
15
|
+
// controls namespace.
|
|
16
|
+
|
|
17
|
+
module.exports = jsgui;
|