mm-math 0.0.7 → 0.0.9

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/index.html CHANGED
@@ -235,6 +235,7 @@ button:active {
235
235
  }
236
236
  .btn-cancel:hover { color: #6b8fb5; background: rgba(255,255,255,.04); }
237
237
  </style>
238
+ <script src="https://unpkg.com/bcryptjs@2.4.3/dist/bcrypt.min.js"></script>
238
239
  </head>
239
240
  <body>
240
241
 
@@ -520,67 +521,6 @@ document.getElementById('fUser').addEventListener('keydown', e => {
520
521
 
521
522
  // ── Login ─────────────────────────────────────────────────────────────────────
522
523
  const _onCalc = window.location.hostname === 'calc.moshelab.com';
523
- const BRIDGE_ORIGIN = 'https://calc.moshelab.com';
524
-
525
- // Preload invisible auth-bridge iframe. The iframe is on calc.moshelab.com so its
526
- // fetch to /api/login is same-origin — no CORS, no Cloudflare cross-origin checks.
527
- let _bridge = null;
528
- let _bridgeReadyResolve = null;
529
- const _bridgeReady = new Promise(function(resolve) { _bridgeReadyResolve = resolve; });
530
-
531
- if (!_onCalc) {
532
- _bridge = document.createElement('iframe');
533
- _bridge.src = BRIDGE_ORIGIN + '/login-frame.html';
534
- _bridge.style.cssText = 'visibility:hidden;width:1px;height:1px;border:0;position:absolute;overflow:hidden;left:-9999px;top:-9999px;';
535
- _bridge.setAttribute('aria-hidden', 'true');
536
- document.body.appendChild(_bridge);
537
- } else {
538
- // On calc.moshelab.com, bridge isn't used — resolve immediately
539
- _bridgeReadyResolve();
540
- }
541
-
542
- // Shared message handler — resolves the ready promise and dispatches login results
543
- const _pendingLogins = {};
544
- window.addEventListener('message', function(event) {
545
- if (event.origin !== BRIDGE_ORIGIN) return;
546
- const d = event.data;
547
- if (!d) return;
548
- if (d.type === 'bridge_ready') { _bridgeReadyResolve(); return; }
549
- if (d.type === 'login_result' && d.nonce && _pendingLogins[d.nonce]) {
550
- _pendingLogins[d.nonce](d);
551
- delete _pendingLogins[d.nonce];
552
- }
553
- });
554
-
555
- function _loginDirect(username, password) {
556
- return fetch('/api/login', {
557
- method: 'POST',
558
- headers: { 'Content-Type': 'application/json' },
559
- body: JSON.stringify({ username, password }),
560
- }).then(function(res) {
561
- return res.json().then(function(data) {
562
- data.ok = res.ok; data.status = res.status; return data;
563
- });
564
- });
565
- }
566
-
567
- function _loginViaBridge(username, password) {
568
- return _bridgeReady.then(function() {
569
- return new Promise(function(resolve, reject) {
570
- if (!_bridge || !_bridge.contentWindow) {
571
- return reject(new Error('Auth bridge unavailable'));
572
- }
573
- const nonce = Math.random().toString(36).slice(2, 10);
574
- const timer = setTimeout(function() {
575
- delete _pendingLogins[nonce];
576
- reject(new Error('Sign-in timed out. Try again.'));
577
- }, 15000);
578
-
579
- _pendingLogins[nonce] = function(d) { clearTimeout(timer); resolve(d); };
580
- _bridge.contentWindow.postMessage({ type: 'login', username: username, password: password, nonce: nonce }, BRIDGE_ORIGIN);
581
- });
582
- });
583
- }
584
524
 
585
525
  async function doLogin() {
586
526
  const username = document.getElementById('fUser').value.trim();
@@ -598,32 +538,44 @@ async function doLogin() {
598
538
  errEl.textContent = '';
599
539
 
600
540
  try {
601
- const data = await (_onCalc ? _loginDirect(username, password) : _loginViaBridge(username, password));
602
-
603
- if (!data.ok || !data.success) {
604
- errEl.textContent = data.status === 429 || (data.error || '').includes('Too many')
605
- ? 'Too many attempts. Wait 60 seconds.'
606
- : 'Invalid username or password.';
607
- btn.disabled = false;
608
- btn.textContent = 'Sign In';
609
- return;
541
+ let rank, token, forcePasswordChange;
542
+
543
+ if (_onCalc) {
544
+ const res = await fetch('/api/login', {
545
+ method: 'POST',
546
+ headers: { 'Content-Type': 'application/json' },
547
+ body: JSON.stringify({ username, password }),
548
+ });
549
+ const data = await res.json();
550
+ if (!res.ok || !data.success) {
551
+ errEl.textContent = res.status === 429 || (data.error || '').includes('Too many')
552
+ ? 'Too many attempts. Wait 60 seconds.'
553
+ : 'Invalid username or password.';
554
+ btn.disabled = false; btn.textContent = 'Sign In'; return;
555
+ }
556
+ rank = data.rank; token = data.token; forcePasswordChange = !!data.forcePasswordChange;
557
+ } else {
558
+ const users = await fetch('./users.json').then(r => r.json());
559
+ const user = users.find(u => u.username === username);
560
+ if (!user || !dcodeIO.bcrypt.compareSync(password, user.password)) {
561
+ errEl.textContent = 'Invalid username or password.';
562
+ btn.disabled = false; btn.textContent = 'Sign In'; return;
563
+ }
564
+ rank = user.rank;
565
+ token = (crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2));
566
+ forcePasswordChange = false;
610
567
  }
611
568
 
612
569
  sessionStorage.setItem('mm_auth', 'true');
613
- sessionStorage.setItem('mm_token', data.token);
614
- sessionStorage.setItem('mm_rank', data.rank);
570
+ sessionStorage.setItem('mm_token', token);
571
+ sessionStorage.setItem('mm_rank', rank);
615
572
  sessionStorage.setItem('mm_user', username);
616
- sessionStorage.setItem('mm_force_change', data.forcePasswordChange ? 'true' : 'false');
617
-
618
- if (data.forcePasswordChange) {
619
- window.location.href = './change-password.html';
620
- } else if (data.rank === 'administrator') {
621
- window.location.href = './admin.html';
622
- } else if (data.rank === 'admin') {
623
- window.location.href = './control-panel.html';
624
- } else {
625
- window.location.href = './browse.html';
626
- }
573
+ sessionStorage.setItem('mm_force_change', forcePasswordChange ? 'true' : 'false');
574
+
575
+ if (forcePasswordChange) window.location.href = './change-password.html';
576
+ else if (rank === 'administrator') window.location.href = './admin.html';
577
+ else if (rank === 'admin') window.location.href = './control-panel.html';
578
+ else window.location.href = './browse.html';
627
579
  } catch (err) {
628
580
  errEl.textContent = err && err.message ? err.message : String(err);
629
581
  btn.disabled = false;
package/js/games.js CHANGED
@@ -1,4 +1,4 @@
1
- const API_BASE = (window.location.hostname === 'unpkg.com' || window.location.hostname === 'cdn.jsdelivr.net') ? 'https://calc.moshelab.com' : '';
1
+ const API_BASE = 'https://orange-dust-8e2b.jonah-7a0.workers.dev';
2
2
 
3
3
  let allGames = [];
4
4
  let activeCategory = 'All';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mm-math",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "STEM educational toolkit",
5
5
  "main": "index.html",
6
6
  "files": [
@@ -15,7 +15,9 @@
15
15
  "version.txt",
16
16
  "assets/",
17
17
  "css/",
18
- "js/"
18
+ "js/",
19
+ "users.json",
20
+ "games.json"
19
21
  ],
20
22
  "scripts": {
21
23
  "start": "node api.js"
package/tool.html CHANGED
@@ -147,7 +147,7 @@
147
147
 
148
148
  let games;
149
149
  try {
150
- const res = await fetch('./api/games');
150
+ const res = await fetch('https://orange-dust-8e2b.jonah-7a0.workers.dev/api/games');
151
151
  games = res.ok ? await res.json() : null;
152
152
  } catch { games = null; }
153
153
 
package/users.json ADDED
@@ -0,0 +1,62 @@
1
+ [
2
+ {
3
+ "username": "owner",
4
+ "password": "$2b$12$pv3OKADBaxJdCPEni5KHJexzvuSxQZQU0wdZ03OogqGbg1/UbPjc2",
5
+ "rank": "administrator",
6
+ "forcePasswordChange": false
7
+ },
8
+ {
9
+ "username": "Zach",
10
+ "password": "$2b$12$Mmz/l6N5rbwGOzYVoOMHbeVjQtpn.dSFQxoDhM2EJF9PJXesASxDC",
11
+ "rank": "admin",
12
+ "forcePasswordChange": true
13
+ },
14
+ {
15
+ "username": "Anthony",
16
+ "password": "$2b$12$GOdPip91LXzin0tm405YR.czj4zVDHcKNugvBDASfuyeNs20g0OkS",
17
+ "rank": "admin",
18
+ "forcePasswordChange": true
19
+ },
20
+ {
21
+ "username": "Viraj",
22
+ "password": "$2b$12$Tq60W3D7ti8l3LOApZP1keKy8aulE9zv.es0dbOxyQ61BF/g1iFJO",
23
+ "rank": "user",
24
+ "forcePasswordChange": true
25
+ },
26
+ {
27
+ "username": "Blake",
28
+ "password": "$2b$12$5QX1SvmJVDQuP/Ek3IOmVu85Fh5Gp3rgKNUREZGuONIc5zp7quIb.",
29
+ "rank": "admin",
30
+ "forcePasswordChange": true
31
+ },
32
+ {
33
+ "username": "Niko",
34
+ "password": "$2b$12$orOTbzLeA.Dzw7E.fRuPd.IE52xQahOF1oX57xVlGmUeNSRvB8sLi",
35
+ "rank": "admin",
36
+ "forcePasswordChange": true
37
+ },
38
+ {
39
+ "username": "Sebastian",
40
+ "password": "$2b$12$gA9e471aAPqqtkxMkiUsou2SWwMEPHF1.rYTEqarwvFWPHiavJxfW",
41
+ "rank": "administrator",
42
+ "forcePasswordChange": true
43
+ },
44
+ {
45
+ "username": "Logan",
46
+ "password": "$2b$12$ivEJU6D/nOXXg3WP93TuUusjOJaLFc2uEDvWm9IDRzINMFhoxm5cq",
47
+ "rank": "user",
48
+ "forcePasswordChange": true
49
+ },
50
+ {
51
+ "username": "Spencer",
52
+ "password": "$2b$12$Ru3Vz/.XeOO.xJRD3NW6rOK5LIpXH4nOFGKLf6W36nU5inUM0s27m",
53
+ "rank": "admin",
54
+ "forcePasswordChange": true
55
+ },
56
+ {
57
+ "username": "Finn",
58
+ "password": "$2b$12$UKA3fZnBItyMSME0y9Jmsu0eN4iXZmqUAY0gVPvB0bonBvWtCo3Gy",
59
+ "rank": "admin",
60
+ "forcePasswordChange": true
61
+ }
62
+ ]