mbkauthe 4.7.1 → 4.8.0

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.
@@ -0,0 +1,324 @@
1
+ import path from "path";
2
+ import { AsyncLocalStorage } from "async_hooks";
3
+
4
+ const isDev = process.env.env === "dev" && process.env.dbLogs === "true";
5
+ const requestContext = isDev ? new AsyncLocalStorage() : null;
6
+
7
+ const GLOBAL_MAX_QUERY_LOG_ENTRIES = 1000;
8
+ const globalQueryState = {
9
+ totalCount: 0,
10
+ log: [],
11
+ };
12
+ let autoPoolId = 0;
13
+
14
+ const safeValue = (value, depth = 0, seen = new WeakSet()) => {
15
+ if (value == null) return value;
16
+ if (typeof value === "string") {
17
+ return value.length > 300 ? `${value.slice(0, 300)}...` : value;
18
+ }
19
+ if (typeof value === "number" || typeof value === "boolean") return value;
20
+ if (typeof value === "bigint") return value.toString();
21
+ if (value instanceof Date) return value.toISOString();
22
+ if (Buffer.isBuffer(value)) return `[buffer:${value.length}]`;
23
+
24
+ if (Array.isArray(value)) {
25
+ if (depth >= 4) return `[array:${value.length}]`;
26
+ const sample = value.slice(0, 8).map((v) => safeValue(v, depth + 1, seen));
27
+ if (value.length > 8) sample.push(`...(${value.length - 8} more)`);
28
+ return sample;
29
+ }
30
+
31
+ if (typeof value === "object") {
32
+ if (seen.has(value)) return "[circular]";
33
+ seen.add(value);
34
+
35
+ const keys = Object.keys(value);
36
+ if (depth >= 4) {
37
+ const head = keys.slice(0, 5).join(", ");
38
+ return keys.length > 5 ? `[object:${head}, ...]` : `[object:${head}]`;
39
+ }
40
+
41
+ const out = {};
42
+ const entries = Object.entries(value).slice(0, 20);
43
+ for (const [k, v] of entries) {
44
+ out[k] = safeValue(v, depth + 1, seen);
45
+ }
46
+ if (keys.length > 20) {
47
+ out.__truncated = `${keys.length - 20} more keys`;
48
+ }
49
+
50
+ seen.delete(value);
51
+ return out;
52
+ }
53
+
54
+ return String(value);
55
+ };
56
+
57
+ const toWorkspacePath = (filePath) => {
58
+ const rel = path.relative(process.cwd(), filePath) || filePath;
59
+ return rel.replace(/\\/g, "/");
60
+ };
61
+
62
+ const buildCallsite = () => {
63
+ try {
64
+ const stack = new Error().stack || "";
65
+ const lines = stack.split("\n").map((l) => l.trim());
66
+ const frame = lines.find(
67
+ (line) =>
68
+ line.startsWith("at ") &&
69
+ !line.includes("/lib/utils/dbQueryLogger.js") &&
70
+ !line.includes("node:internal") &&
71
+ !line.includes("internal/process")
72
+ );
73
+
74
+ if (!frame) return null;
75
+
76
+ const withFunc = /^at\s+([^\s(]+)\s+\((.+):([0-9]+):([0-9]+)\)$/.exec(frame);
77
+ const noFunc = /^at\s+(.+):([0-9]+):([0-9]+)$/.exec(frame);
78
+
79
+ if (withFunc) {
80
+ return {
81
+ function: withFunc[1],
82
+ file: toWorkspacePath(withFunc[2]),
83
+ line: Number(withFunc[3]),
84
+ column: Number(withFunc[4]),
85
+ };
86
+ }
87
+
88
+ if (noFunc) {
89
+ return {
90
+ function: null,
91
+ file: toWorkspacePath(noFunc[1]),
92
+ line: Number(noFunc[2]),
93
+ column: Number(noFunc[3]),
94
+ };
95
+ }
96
+ } catch {
97
+ return null;
98
+ }
99
+
100
+ return null;
101
+ };
102
+
103
+ const buildRequestContext = () => {
104
+ const store = getRequestContext();
105
+ const req = store?.req;
106
+ if (!req) return null;
107
+
108
+ const user = req.session?.user || null;
109
+ return {
110
+ method: req.method,
111
+ url: req.originalUrl || req.url,
112
+ ip: req.ip,
113
+ userId: user?.id || null,
114
+ username: user?.username || null,
115
+ };
116
+ };
117
+
118
+ const buildReturnValue = (result) => {
119
+ if (!result || typeof result !== "object") return undefined;
120
+
121
+ const returnValue = {
122
+ command: result.command || undefined,
123
+ rowCount: typeof result.rowCount === "number" ? result.rowCount : undefined,
124
+ };
125
+
126
+ if (Array.isArray(result.rows)) {
127
+ const previewSize = 3;
128
+ returnValue.returnedRows = result.rows.length;
129
+ returnValue.rowsPreview = result.rows.slice(0, previewSize).map((row) => safeValue(row));
130
+ if (result.rows.length > previewSize) {
131
+ returnValue.rowsTruncated = true;
132
+ }
133
+ }
134
+
135
+ return returnValue;
136
+ };
137
+
138
+ const recordGlobalLog = (entry) => {
139
+ globalQueryState.totalCount += 1;
140
+ globalQueryState.log.push(entry);
141
+ if (globalQueryState.log.length > GLOBAL_MAX_QUERY_LOG_ENTRIES) {
142
+ globalQueryState.log.shift();
143
+ }
144
+ };
145
+
146
+ export const getQueryCount = () => globalQueryState.totalCount;
147
+ export const getQueryLog = (options = {}) => {
148
+ const { limit } = options;
149
+ if (typeof limit === "number") {
150
+ return globalQueryState.log.slice(-limit);
151
+ }
152
+ return [...globalQueryState.log];
153
+ };
154
+
155
+ export const resetQueryCount = () => {
156
+ globalQueryState.totalCount = 0;
157
+ };
158
+
159
+ export const resetQueryLog = () => {
160
+ globalQueryState.log.length = 0;
161
+ };
162
+
163
+ export const runWithRequestContext = (req, fn) => {
164
+ if (!isDev || !requestContext) return fn();
165
+ return requestContext.run({ req }, fn);
166
+ };
167
+
168
+ export const getRequestContext = () => {
169
+ if (!isDev || !requestContext) return undefined;
170
+ return requestContext.getStore();
171
+ };
172
+
173
+ const resolveLoggerPool = (item) => {
174
+ if (item && typeof item === 'object') {
175
+ if (item.pool && item.pool.query) {
176
+ return { pool: item.pool, name: item.name || item.pool.__mbkQueryLoggerName || item.pool.name };
177
+ }
178
+ if (item.query) {
179
+ // Don't force 'default' here; let getPoolName assign a non-default auto name.
180
+ return { pool: item, name: item.__mbkQueryLoggerName || item.name || item.options?.application_name || null };
181
+ }
182
+ }
183
+ return null;
184
+ };
185
+
186
+ const getPoolName = (pool, fallbackName) => {
187
+ if (fallbackName) return fallbackName;
188
+ if (pool.__mbkQueryLoggerName) return pool.__mbkQueryLoggerName;
189
+ if (pool.name) return pool.name;
190
+ if (pool.options && pool.options.application_name) return pool.options.application_name;
191
+ // never return "default"; always provide an actual pool name
192
+ autoPoolId += 1;
193
+ return `pool-${autoPoolId}`;
194
+ };
195
+
196
+ const attachSinglePool = (pool, poolName = null) => {
197
+ if (!pool) return;
198
+
199
+ if (pool.__mbkQueryLoggerInstalled) {
200
+ // Allow late explicit naming to override a previous auto name.
201
+ if (poolName && pool.__mbkQueryLoggerName !== poolName) {
202
+ pool.__mbkQueryLoggerName = poolName;
203
+ }
204
+ return;
205
+ }
206
+
207
+ pool.__mbkQueryLoggerInstalled = true;
208
+ pool.__mbkQueryLoggerName = getPoolName(pool, poolName);
209
+
210
+ let dbQueryCount = 0;
211
+ const dbQueryLog = [];
212
+ const originalQuery = pool.query.bind(pool);
213
+
214
+ const recordPoolLog = (entry) => {
215
+ dbQueryCount += 1;
216
+ dbQueryLog.push(entry);
217
+ if (dbQueryLog.length > GLOBAL_MAX_QUERY_LOG_ENTRIES) {
218
+ dbQueryLog.shift();
219
+ }
220
+ recordGlobalLog(entry);
221
+ };
222
+
223
+ pool.query = (...args) => {
224
+ let queryText = "";
225
+ let queryName = "";
226
+ let queryValues;
227
+
228
+ try {
229
+ if (typeof args[0] === "string") {
230
+ queryText = args[0];
231
+ queryValues = Array.isArray(args[1]) ? args[1] : undefined;
232
+ } else if (args[0] && typeof args[0] === "object") {
233
+ queryText = args[0].text || "";
234
+ queryName = args[0].name || "";
235
+ queryValues = Array.isArray(args[0].values) ? args[0].values : undefined;
236
+ }
237
+ } catch {
238
+ queryText = "";
239
+ }
240
+
241
+ if (!queryText) {
242
+ return originalQuery(...args);
243
+ }
244
+
245
+ const startTime = process.hrtime.bigint();
246
+ const callsiteSnapshot = buildCallsite();
247
+
248
+ const recordLog = (success, error, result) => {
249
+ const durationMs = Number(process.hrtime.bigint() - startTime) / 1_000_000;
250
+ const request = buildRequestContext();
251
+ const returnValue = buildReturnValue(result);
252
+
253
+ const entry = {
254
+ time: new Date().toISOString(),
255
+ query: queryText,
256
+ name: queryName || undefined,
257
+ values: queryValues,
258
+ durationMs,
259
+ success,
260
+ error: error ? { message: error.message, code: error.code } : undefined,
261
+ returnValue,
262
+ request,
263
+ pool: {
264
+ name: pool.__mbkQueryLoggerName,
265
+ total: pool.totalCount,
266
+ idle: pool.idleCount,
267
+ waiting: pool.waitingCount,
268
+ },
269
+ callsite: callsiteSnapshot,
270
+ };
271
+
272
+ recordPoolLog(entry);
273
+ };
274
+
275
+ try {
276
+ const result = originalQuery(...args);
277
+ if (result && typeof result.then === "function") {
278
+ return result
279
+ .then((res) => {
280
+ recordLog(true, null, res);
281
+ return res;
282
+ })
283
+ .catch((err) => {
284
+ recordLog(false, err);
285
+ throw err;
286
+ });
287
+ }
288
+
289
+ recordLog(true, null, result);
290
+ return result;
291
+ } catch (err) {
292
+ recordLog(false, err);
293
+ throw err;
294
+ }
295
+ };
296
+
297
+ pool.getQueryCount = () => dbQueryCount;
298
+ pool.resetQueryCount = () => {
299
+ dbQueryCount = 0;
300
+ };
301
+
302
+ pool.getQueryLog = (options = {}) => {
303
+ const { limit } = options;
304
+ if (typeof limit === "number") {
305
+ return dbQueryLog.slice(-limit);
306
+ }
307
+ return [...dbQueryLog];
308
+ };
309
+
310
+ pool.resetQueryLog = () => {
311
+ dbQueryLog.length = 0;
312
+ };
313
+ };
314
+
315
+ export const attachDevQueryLogger = (poolOrPools) => {
316
+ if (!isDev || !poolOrPools) return;
317
+
318
+ const inputs = Array.isArray(poolOrPools) ? poolOrPools : [poolOrPools];
319
+ for (const item of inputs) {
320
+ const resolved = resolveLoggerPool(item);
321
+ if (!resolved) continue;
322
+ attachSinglePool(resolved.pool, resolved.name);
323
+ }
324
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mbkauthe",
3
- "version": "4.7.1",
3
+ "version": "4.8.0",
4
4
  "description": "MBKTech's reusable authentication system for Node.js applications.",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/public/main.css CHANGED
@@ -12,6 +12,18 @@
12
12
  --success: #43e97b;
13
13
  --warning: #ffd166;
14
14
  --danger: #ff7675;
15
+ --surface-1: #0a1414;
16
+ --surface-2: #101c1c;
17
+ --surface-soft: rgba(255, 255, 255, 0.05);
18
+ --surface-muted: rgba(0, 0, 0, 0.25);
19
+ --glass-bg: rgba(10, 20, 20, 0.95);
20
+ --glass-border: rgba(0, 184, 148, 0.2);
21
+ --input-bg: rgba(0, 0, 0, .3);
22
+ --input-bg-focus: rgba(0, 0, 0, .4);
23
+ --input-border: rgba(0, 184, 148, 0.3);
24
+ --muted-border: rgba(255, 255, 255, 0.12);
25
+ --hero-from: #0a1414;
26
+ --hero-to: #1769aa;
15
27
  --border-radius: 8px;
16
28
  --box-shadow: 0 4px 10px rgba(33, 150, 243, 0.15);
17
29
  --transition: all 0.3s cubic-bezier(.4, 0, .2, 1);
@@ -24,6 +36,29 @@
24
36
  --text-size-xl: 2rem;
25
37
  }
26
38
 
39
+ [data-theme="light"] {
40
+ --dark: #f8fbfd;
41
+ --darker: #eef6fb;
42
+ --light: #06222f;
43
+ --text: #163041;
44
+ --text-light: #4a6475;
45
+ --text-dark: #0e2230;
46
+ --warning: #a66900;
47
+ --surface-1: rgba(255, 255, 255, 0.9);
48
+ --surface-2: #f8fbfd;
49
+ --surface-soft: rgba(255, 255, 255, 0.88);
50
+ --surface-muted: rgba(6, 34, 47, 0.06);
51
+ --glass-bg: rgba(255, 255, 255, 0.88);
52
+ --glass-border: rgba(0, 184, 148, 0.22);
53
+ --input-bg: rgba(255, 255, 255, 0.84);
54
+ --input-bg-focus: rgba(255, 255, 255, 1);
55
+ --input-border: rgba(22, 48, 65, 0.2);
56
+ --muted-border: rgba(22, 48, 65, 0.14);
57
+ --hero-from: #d7e9f6;
58
+ --hero-to: #a7cde4;
59
+ --box-shadow: 0 10px 28px rgba(8, 35, 51, 0.12);
60
+ }
61
+
27
62
  * {
28
63
  margin: 0;
29
64
  padding: 0;
@@ -32,7 +67,7 @@
32
67
  }
33
68
 
34
69
  body {
35
- background-color: var(--dark);
70
+ background: linear-gradient(180deg, var(--surface-2), var(--dark));
36
71
  color: var(--text);
37
72
  min-height: 100vh;
38
73
  display: flex;
@@ -41,13 +76,14 @@ body {
41
76
  }
42
77
 
43
78
  header {
44
- background-color: var(--darker);
79
+ background-color: var(--surface-1);
45
80
  box-shadow: var(--box-shadow);
46
81
  position: fixed;
47
82
  width: 100%;
48
83
  z-index: 1000;
49
84
  transition: var(--transition);
50
- border-bottom: .125rem solid rgba(255, 255, 255, .1);
85
+ border-bottom: .125rem solid var(--muted-border);
86
+ backdrop-filter: blur(8px);
51
87
  }
52
88
 
53
89
  .header-container {
@@ -100,7 +136,7 @@ header {
100
136
  padding: 120px 1.7rem 20px;
101
137
  position: relative;
102
138
  overflow: hidden;
103
- background: linear-gradient(135deg, var(--darker), var(--primary-dark));
139
+ background: linear-gradient(135deg, var(--hero-from), var(--hero-to));
104
140
  }
105
141
 
106
142
  .login-container::before {
@@ -115,15 +151,22 @@ header {
115
151
  z-index: 0;
116
152
  }
117
153
 
154
+ [data-theme="light"] .login-container {
155
+ background:
156
+ radial-gradient(circle at 12% 18%, color-mix(in srgb, var(--primary) 18%, transparent 82%), transparent 48%),
157
+ radial-gradient(circle at 86% 78%, color-mix(in srgb, var(--accent) 14%, transparent 86%), transparent 54%),
158
+ linear-gradient(135deg, var(--hero-from), var(--hero-to));
159
+ }
160
+
118
161
  .login-box {
119
- background: rgba(10, 20, 20, 0.95);
162
+ background: var(--glass-bg);
120
163
  backdrop-filter: blur(10px);
121
164
  border-radius: var(--border-radius);
122
165
  padding: 2.5rem;
123
166
  width: 100%;
124
167
  max-width: 450px;
125
168
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
126
- border: 1px solid rgba(0, 184, 148, 0.2);
169
+ border: 1px solid var(--glass-border);
127
170
  position: relative;
128
171
  z-index: 2;
129
172
  transition: var(--transition);
@@ -191,8 +234,8 @@ header {
191
234
  .form-input {
192
235
  width: 100%;
193
236
  padding: 14px 20px;
194
- background: rgba(0, 0, 0, .3);
195
- border: 2px solid rgba(0, 184, 148, 0.3);
237
+ background: var(--input-bg);
238
+ border: 2px solid var(--input-border);
196
239
  border-radius: var(--border-radius);
197
240
  color: var(--text);
198
241
  font-size: var(--text-size-md);
@@ -201,7 +244,7 @@ header {
201
244
 
202
245
  .form-input:focus {
203
246
  outline: none;
204
- background: rgba(0, 0, 0, .4);
247
+ background: var(--input-bg-focus);
205
248
  border-color: var(--accent);
206
249
  box-shadow: 0 0 0 3px rgba(0, 184, 148, 0.2);
207
250
  }
@@ -228,7 +271,7 @@ header {
228
271
  top: -10px;
229
272
  left: 15px;
230
273
  font-size: 0.8rem;
231
- background: rgba(10, 20, 20, 0.95);
274
+ background: var(--glass-bg);
232
275
  padding: 0 5px;
233
276
  color: var(--accent);
234
277
  }
@@ -276,9 +319,9 @@ header {
276
319
  position: relative;
277
320
  z-index: 1;
278
321
  overflow: hidden;
279
- background: rgba(255, 255, 255, 0.05);
322
+ background: var(--surface-soft);
280
323
  color: var(--text);
281
- border-color: rgba(255, 255, 255, 0.15);
324
+ border-color: var(--muted-border);
282
325
  }
283
326
 
284
327
  .swi:hover {
@@ -292,13 +335,13 @@ header {
292
335
  }
293
336
 
294
337
  .btn-login:hover {
295
- background: var(--dark);
338
+ background: var(--surface-1);
296
339
  color: var(--accent);
297
340
  box-shadow: 0 6px 20px rgba(0, 184, 148, 0.3);
298
341
  }
299
342
 
300
343
  .btn-login:disabled {
301
- background: var(--dark);
344
+ background: var(--surface-1);
302
345
  color: var(--accent);
303
346
  cursor: not-allowed;
304
347
  transform: none;
@@ -327,7 +370,7 @@ header {
327
370
  }
328
371
 
329
372
  .divider span {
330
- background: rgba(10, 20, 20, 0.95);
373
+ background: var(--glass-bg);
331
374
  padding: 0 15px;
332
375
  color: var(--text-light);
333
376
  font-size: 0.9rem;
@@ -463,8 +506,8 @@ header {
463
506
  -webkit-appearance: none;
464
507
  width: 18px;
465
508
  height: 18px;
466
- background: rgba(0, 0, 0, .3);
467
- border: 2px solid rgba(0, 184, 148, 0.3);
509
+ background: var(--input-bg);
510
+ border: 2px solid var(--input-border);
468
511
  border-radius: var(--border-radius);
469
512
  margin-right: 10px;
470
513
  cursor: pointer;
@@ -507,7 +550,7 @@ header {
507
550
  width: 100%;
508
551
  margin: 1.5rem 0;
509
552
  padding: 1rem;
510
- background: rgba(0, 184, 148, 0.05);
553
+ background: color-mix(in srgb, var(--accent) 7%, transparent 93%);
511
554
  border: 1px solid rgba(0, 184, 148, 0.2);
512
555
  border-radius: var(--border-radius);
513
556
  transition: var(--transition);
@@ -531,7 +574,7 @@ header {
531
574
  -webkit-appearance: none;
532
575
  width: 20px;
533
576
  height: 20px;
534
- background: rgba(0, 0, 0, .3);
577
+ background: var(--input-bg);
535
578
  border: 2px solid rgba(0, 184, 148, 0.4);
536
579
  border-radius: 5px;
537
580
  cursor: pointer;
@@ -613,12 +656,12 @@ header {
613
656
  position: fixed;
614
657
  bottom: 20px;
615
658
  right: 20px;
616
- background: rgba(255, 255, 255, .1);
659
+ background: var(--surface-soft);
617
660
  color: var(--text-light);
618
661
  padding: 8px 12px;
619
662
  border-radius: var(--border-radius);
620
663
  font-size: 0.75rem;
621
- border: 1px solid rgba(255, 255, 255, .1);
664
+ border: 1px solid var(--muted-border);
622
665
  z-index: 999;
623
666
  transition: var(--transition);
624
667
  backdrop-filter: blur(5px);
@@ -649,7 +692,7 @@ header {
649
692
  width: 40%;
650
693
  background: linear-gradient(135deg, rgba(33, 150, 243, 0.08), rgba(0, 184, 148, 0.08));
651
694
  padding: 2.5rem 2rem;
652
- border-right: 1px solid rgba(0, 184, 148, 0.3);
695
+ border-right: 1px solid color-mix(in srgb, var(--accent) 32%, transparent 68%);
653
696
  position: relative;
654
697
  overflow: hidden;
655
698
  }
@@ -740,9 +783,9 @@ header {
740
783
  }
741
784
 
742
785
  .btn-google-side {
743
- background: rgba(255, 255, 255, 0.05);
786
+ background: var(--surface-soft);
744
787
  color: var(--text);
745
- border-color: rgba(255, 255, 255, 0.15);
788
+ border-color: var(--muted-border);
746
789
  }
747
790
 
748
791
  .btn-google-side:hover {
@@ -756,9 +799,9 @@ header {
756
799
  }
757
800
 
758
801
  .btn-switch-side {
759
- background: rgba(255, 255, 255, 0.05);
802
+ background: var(--surface-soft);
760
803
  color: var(--text);
761
- border-color: rgba(255, 255, 255, 0.15);
804
+ border-color: var(--muted-border);
762
805
  }
763
806
 
764
807
  .btn-switch-side:hover {
@@ -773,7 +816,7 @@ header {
773
816
  gap: 0.75rem;
774
817
  margin-top: auto;
775
818
  padding-top: 2rem;
776
- border-top: 1px solid rgba(255, 255, 255, 0.12);
819
+ border-top: 1px solid var(--muted-border);
777
820
  position: relative;
778
821
  z-index: 1;
779
822
  }
@@ -809,7 +852,7 @@ header {
809
852
  display: flex;
810
853
  flex-direction: column;
811
854
  justify-content: center;
812
- background: rgba(0, 0, 0, 0.2);
855
+ background: var(--surface-muted);
813
856
  }
814
857
 
815
858
  .login-form {
@@ -843,6 +886,44 @@ header {
843
886
  }
844
887
  }
845
888
 
889
+ .logo-centered {
890
+ justify-content: center;
891
+ margin-bottom: 1rem;
892
+ }
893
+
894
+ .form-row-split {
895
+ display: flex;
896
+ justify-content: space-between;
897
+ align-items: center;
898
+ margin-bottom: 1rem;
899
+ gap: 0.75rem;
900
+ }
901
+
902
+ .remember-me-inline {
903
+ margin-bottom: 0;
904
+ }
905
+
906
+ .login-link-nowrap {
907
+ margin-bottom: 0;
908
+ white-space: nowrap;
909
+ }
910
+
911
+ .hidden-by-default {
912
+ display: none;
913
+ }
914
+
915
+ .btn-login-inline {
916
+ text-decoration: none;
917
+ display: inline-block;
918
+ margin-top: 10px;
919
+ text-align: center;
920
+ line-height: 40px;
921
+ }
922
+
923
+ .link-no-decoration {
924
+ text-decoration: none;
925
+ }
926
+
846
927
  @media (max-width: 400px) {
847
928
 
848
929
  .logo svg,
package/public/main.js CHANGED
@@ -37,14 +37,18 @@ async function selectiveCacheClear() {
37
37
  'sessionId',
38
38
  'mbkauthe.sid',
39
39
  'fullName',
40
- '_csrf'
40
+ '_csrf',
41
+ 'profileImageUser',
42
+ 'profileImageUrl'
41
43
  ];
42
44
 
43
45
  const localStorageToClear = [
44
46
  'sessionId',
45
47
  'mbkauthe.sid',
46
48
  'fullName',
47
- '_csrf'
49
+ '_csrf',
50
+ 'profileImageUser',
51
+ 'profileImageUrl'
48
52
  ];
49
53
 
50
54
  // 1. Clear selected localStorage keys
package/test.spec.js CHANGED
@@ -107,10 +107,22 @@ describe('mbkauthe Routes', () => {
107
107
  const response = await request(BASE_URL).get('/mbkauthe/test');
108
108
  expect([200, 302, 401, 403, 429]).toContain(response.status);
109
109
  });
110
+
111
+ test('GET /mbkauthe/test with curl UA returns JSON 401', async () => {
112
+ const response = await request(BASE_URL)
113
+ .get('/mbkauthe/test')
114
+ .set('User-Agent', 'curl/8.0.1')
115
+ .set('Accept', '*/*');
116
+
117
+ expect(response.status).toBe(401);
118
+ expect(response.headers['content-type']).toContain('application/json');
119
+ expect(response.body).toHaveProperty('success', false);
120
+ expect(response.body).toHaveProperty('errorCode');
121
+ });
110
122
  });
111
123
 
112
124
  describe('OAuth Routes', () => {
113
- test('GET /mbkauthe/api/github/login handles GitHub OAuth', async () => {
125
+ test('GET /mbkauthe/api/github/login handles GitHub App flow', async () => {
114
126
  const response = await request(BASE_URL)
115
127
  .get('/mbkauthe/api/github/login')
116
128
  .redirects(0);
@@ -123,7 +135,7 @@ describe('mbkauthe Routes', () => {
123
135
  }
124
136
  });
125
137
 
126
- test('GET /mbkauthe/api/github/login/callback handles callback', async () => {
138
+ test('GET /mbkauthe/api/github/login/callback handles GitHub App callback', async () => {
127
139
  const response = await request(BASE_URL).get('/mbkauthe/api/github/login/callback');
128
140
  expect([200, 302, 400, 401, 403, 429]).toContain(response.status);
129
141
  });