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 configure(authFile?: string | null, projectName?: string): void;
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 configure(authFile, projectName) {
31
- MonitorAuth.authFile = authFile && authFile.length > 0 ? authFile : null;
32
- MonitorAuth.projectKey = (projectName && projectName.length > 0 ? projectName : "default");
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: no monitorAuthFile configured — enrollments live in memory only and are LOST on restart");
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 generateTotpSecret(login, issuer = "Monitor") {
194
+ static base32Encode(buf) {
183
195
  const A = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
184
- const raw = crypto_1.default.randomBytes(20);
185
- let secret = "";
186
- for (let i = 0; i < raw.length; i++)
187
- secret += A[raw[i] % 32];
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-14T12:52:03.029Z | Version: 1.0.18 -->
5
- <script>var __ver = "1.0.18";</script>
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>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "badmfck-api-server",
3
- "version": "5.0.9",
3
+ "version": "5.0.11",
4
4
  "description": "Simple API http server based on express",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",