badmfck-api-server 5.0.9 → 5.0.11
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.
|
@@ -58,6 +58,7 @@ export interface APIServiceOptions<TInterceptor = unknown> {
|
|
|
58
58
|
metricsDir?: string;
|
|
59
59
|
blueGreenWatchMs?: number;
|
|
60
60
|
logFiles?: ILogFilesOptions;
|
|
61
|
+
monitorAuthDir?: string;
|
|
61
62
|
monitorAuthFile?: string;
|
|
62
63
|
}
|
|
63
64
|
type InferInterceptor<T> = T extends IInterceptor<infer U> ? U : unknown;
|
|
@@ -40,7 +40,7 @@ class Monitor extends BaseEndpoint_1.BaseEndpoint {
|
|
|
40
40
|
constructor(options) {
|
|
41
41
|
super("sm");
|
|
42
42
|
this.apiServiceOptions = options;
|
|
43
|
-
MonitorAuth_1.MonitorAuth.configure(options.monitorAuthFile, options.projectName);
|
|
43
|
+
MonitorAuth_1.MonitorAuth.configure({ authFile: options.monitorAuthFile, authDir: options.monitorAuthDir, projectName: options.projectName });
|
|
44
44
|
this.registerEndpoints([
|
|
45
45
|
{ ignoreInterceptor: true, endpoint: "html", handler: this.html },
|
|
46
46
|
{ ignoreInterceptor: true, endpoint: "log", handler: this.logs },
|
|
@@ -18,7 +18,12 @@ export declare class MonitorAuth {
|
|
|
18
18
|
private static projectKey;
|
|
19
19
|
private static enrolled;
|
|
20
20
|
private static storeLoaded;
|
|
21
|
-
static
|
|
21
|
+
private static fileSafe;
|
|
22
|
+
static configure(opts?: {
|
|
23
|
+
authFile?: string | null;
|
|
24
|
+
authDir?: string | null;
|
|
25
|
+
projectName?: string;
|
|
26
|
+
}): void;
|
|
22
27
|
private static loadStore;
|
|
23
28
|
private static saveStore;
|
|
24
29
|
static secretFor(user: IMonitorUser): string | null;
|
|
@@ -37,6 +42,7 @@ export declare class MonitorAuth {
|
|
|
37
42
|
private static base32Decode;
|
|
38
43
|
private static hotp;
|
|
39
44
|
static verifyTotp(login: string, secretBase32: string, code: string): string | null;
|
|
45
|
+
private static base32Encode;
|
|
40
46
|
static generateTotpSecret(login: string, issuer?: string): {
|
|
41
47
|
secret: string;
|
|
42
48
|
uri: string;
|
|
@@ -27,14 +27,26 @@ class MonitorAuth {
|
|
|
27
27
|
static projectKey = "default";
|
|
28
28
|
static enrolled = new Map();
|
|
29
29
|
static storeLoaded = false;
|
|
30
|
-
static
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
static fileSafe(name) {
|
|
31
|
+
const safe = String(name || "").replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
|
|
32
|
+
return safe.length > 0 ? safe.slice(0, 64) : "default";
|
|
33
|
+
}
|
|
34
|
+
static configure(opts) {
|
|
35
|
+
const authFile = opts?.authFile;
|
|
36
|
+
const authDir = opts?.authDir;
|
|
37
|
+
const project = opts?.projectName;
|
|
38
|
+
if (authFile && authFile.length > 0)
|
|
39
|
+
MonitorAuth.authFile = authFile;
|
|
40
|
+
else if (authDir && authDir.length > 0)
|
|
41
|
+
MonitorAuth.authFile = path_1.default.join(authDir, `monitor-2fa.${MonitorAuth.fileSafe(project ?? "")}.json`);
|
|
42
|
+
else
|
|
43
|
+
MonitorAuth.authFile = null;
|
|
44
|
+
MonitorAuth.projectKey = (project && project.length > 0 ? project : "default");
|
|
33
45
|
MonitorAuth.enrolled.clear();
|
|
34
46
|
MonitorAuth.storeLoaded = false;
|
|
35
47
|
MonitorAuth.loadStore();
|
|
36
48
|
if (!MonitorAuth.authFile)
|
|
37
|
-
(0, LogService_1.logWarn)("Monitor 2FA:
|
|
49
|
+
(0, LogService_1.logWarn)("Monitor 2FA: neither monitorAuthFile nor monitorAuthDir is set — enrollments live in memory only and are LOST on restart");
|
|
38
50
|
}
|
|
39
51
|
static loadStore() {
|
|
40
52
|
if (MonitorAuth.storeLoaded)
|
|
@@ -56,7 +68,7 @@ class MonitorAuth {
|
|
|
56
68
|
lastCounter: Number(r.lastCounter) || 0,
|
|
57
69
|
});
|
|
58
70
|
}
|
|
59
|
-
(0, LogService_1.logInfo)(`Monitor 2FA: loaded ${MonitorAuth.enrolled.size} enrollment(s) for project "${MonitorAuth.projectKey}"`, "MonitorAuth");
|
|
71
|
+
(0, LogService_1.logInfo)(`Monitor 2FA: loaded ${MonitorAuth.enrolled.size} enrollment(s) for project "${MonitorAuth.projectKey}" from ${MonitorAuth.authFile}`, "MonitorAuth");
|
|
60
72
|
}
|
|
61
73
|
catch (e) {
|
|
62
74
|
(0, LogService_1.logWarn)("Monitor 2FA: cannot read store, treating as empty: " + (e?.message ?? e));
|
|
@@ -179,12 +191,23 @@ class MonitorAuth {
|
|
|
179
191
|
}
|
|
180
192
|
return "TOTP code is invalid";
|
|
181
193
|
}
|
|
182
|
-
static
|
|
194
|
+
static base32Encode(buf) {
|
|
183
195
|
const A = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
196
|
+
let bits = 0, value = 0, out = "";
|
|
197
|
+
for (const b of buf) {
|
|
198
|
+
value = (value << 8) | b;
|
|
199
|
+
bits += 8;
|
|
200
|
+
while (bits >= 5) {
|
|
201
|
+
out += A[(value >>> (bits - 5)) & 31];
|
|
202
|
+
bits -= 5;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (bits > 0)
|
|
206
|
+
out += A[(value << (5 - bits)) & 31];
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
static generateTotpSecret(login, issuer = "Monitor") {
|
|
210
|
+
const secret = MonitorAuth.base32Encode(crypto_1.default.randomBytes(20));
|
|
188
211
|
const safeIssuer = String(issuer || "").replace(/[:\u0000-\u001f]/g, " ").trim() || "Monitor";
|
|
189
212
|
const label = encodeURIComponent(`${safeIssuer}:${login}`);
|
|
190
213
|
const uri = `otpauth://totp/${label}?secret=${secret}&issuer=${encodeURIComponent(safeIssuer)}&algorithm=SHA1&digits=6&period=30`;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
|
-
<!-- Build: 2026-10-
|
|
5
|
-
<script>var __ver = "1.0.
|
|
4
|
+
<!-- Build: 2026-10-14T13:16:34.697Z | Version: 1.0.20 -->
|
|
5
|
+
<script>var __ver = "1.0.20";</script>
|
|
6
6
|
<meta charset="UTF-8" />
|
|
7
7
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
8
8
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
@@ -469,10 +469,10 @@ const h2={background:"#a7a7a7",backgroundSelected:"#66bb6a",toggle:"#FFFFFF"},m2
|
|
|
469
469
|
::-webkit-scrollbar-thumb:hover {
|
|
470
470
|
background-color:rgba(0,0,0,1);
|
|
471
471
|
}
|
|
472
|
-
`,y2=e=>{for(let t in e){const n=e[t];e[n]&&(e[t]=e[n])}},px=(e,t)=>{for(let n in e)if(n!=="colors"){if(typeof e[n]=="object"){e[n]=px(e[n],t);continue}if(typeof e[n]=="string"){const r=e[n];if(t[r])e[n]=t[r];else for(let o in t)r.indexOf(o)>-1&&(e[n]=r.replace(o,t[o]))}}return e},w2=e=>(y2(e.colors),px(e,e.colors),e),S2=({theme:e,children:t})=>i.jsxs(Bw,{theme:w2(e??Op()),children:[i.jsx(v2,{theme:e??Op()}),t]}),j2=x2();function k2(){const e=Ki.use([]);let t;switch(e){case"authorized":t=i.jsx(r2,{});break;case"unauthorized":case"checking":t=i.jsx(u2,{});break;default:t=i.jsxs(O,{sx:{padding:"20px"},children:["Unknown state: ",e]})}return i.jsx(S2,{theme:j2,children:t})}In.setupReact(st.useState,st.useEffect);new Uy;La.createRoot(document.getElementById("root")).render(i.jsx(st.StrictMode,{children:i.jsx(k2,{})}));</script>
|
|
472
|
+
`,y2=e=>{for(let t in e){const n=e[t];e[n]&&(e[t]=e[n])}},px=(e,t)=>{for(let n in e)if(n!=="colors"){if(typeof e[n]=="object"){e[n]=px(e[n],t);continue}if(typeof e[n]=="string"){const r=e[n];if(t[r])e[n]=t[r];else for(let o in t)r.indexOf(o)>-1&&(e[n]=r.replace(o,t[o]))}}return e},w2=e=>(y2(e.colors),px(e,e.colors),e),S2=({theme:e,children:t})=>i.jsxs(Bw,{theme:w2(e??Op()),children:[i.jsx(v2,{theme:e??Op()}),t]}),j2=x2();function k2(){const e=Ki.use([]);let t;switch(e){case"authorized":t=i.jsx(r2,{});break;case"unauthorized":case"checking":case"enroll":t=i.jsx(u2,{});break;default:t=i.jsxs(O,{sx:{padding:"20px"},children:["Unknown state: ",e]})}return i.jsx(S2,{theme:j2,children:t})}In.setupReact(st.useState,st.useEffect);new Uy;La.createRoot(document.getElementById("root")).render(i.jsx(st.StrictMode,{children:i.jsx(k2,{})}));</script>
|
|
473
473
|
</head>
|
|
474
474
|
|
|
475
475
|
<body>
|
|
476
476
|
<div id="root"></div>
|
|
477
477
|
</body>
|
|
478
|
-
</html>
|
|
478
|
+
</html>
|