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/browse.html +1 -1
- package/change-password.html +1 -1
- package/games.json +29537 -0
- package/index.html +35 -83
- package/js/games.js +1 -1
- package/package.json +4 -2
- package/tool.html +1 -1
- package/users.json +62 -0
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
|
-
|
|
602
|
-
|
|
603
|
-
if (
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
: '
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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',
|
|
614
|
-
sessionStorage.setItem('mm_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',
|
|
617
|
-
|
|
618
|
-
if (
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mm-math",
|
|
3
|
-
"version": "0.0.
|
|
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
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
|
+
]
|