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.
- package/README.md +3 -3
- package/docs/api.md +12 -12
- package/docs/db.md +3 -1
- package/docs/db.sql +6 -0
- package/docs/env.md +18 -6
- package/index.d.ts +8 -4
- package/index.js +1 -1
- package/lib/config/cookies.js +2 -0
- package/lib/config/index.js +8 -5
- package/lib/middleware/auth.js +53 -0
- package/lib/pool.js +16 -191
- package/lib/routes/auth.js +14 -8
- package/lib/routes/dbLogs.js +46 -8
- package/lib/routes/misc.js +14 -8
- package/lib/routes/oauth.js +361 -355
- package/lib/utils/dbQueryLogger.js +324 -0
- package/package.json +1 -1
- package/public/main.css +109 -28
- package/public/main.js +6 -2
- package/test.spec.js +14 -2
- package/views/head.handlebars +12 -0
- package/views/pages/accountSwitch.handlebars +37 -16
- package/views/pages/dbLogs.handlebars +433 -155
- package/views/pages/errorCodes.handlebars +30 -26
- package/views/pages/info_mbkauthe.handlebars +15 -15
- package/views/pages/loginmbkauthe.handlebars +15 -11
- package/views/pages/test.handlebars +27 -15
- package/views/profilemenu.handlebars +53 -57
- package/views/sharedStyles.handlebars +1 -1
- package/views/showmessage.handlebars +52 -30
|
@@ -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
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
|
|
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(--
|
|
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
|
|
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(--
|
|
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:
|
|
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
|
|
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:
|
|
195
|
-
border: 2px solid
|
|
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:
|
|
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:
|
|
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:
|
|
322
|
+
background: var(--surface-soft);
|
|
280
323
|
color: var(--text);
|
|
281
|
-
border-color:
|
|
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(--
|
|
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(--
|
|
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:
|
|
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:
|
|
467
|
-
border: 2px solid
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
786
|
+
background: var(--surface-soft);
|
|
744
787
|
color: var(--text);
|
|
745
|
-
border-color:
|
|
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:
|
|
802
|
+
background: var(--surface-soft);
|
|
760
803
|
color: var(--text);
|
|
761
|
-
border-color:
|
|
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
|
|
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:
|
|
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
|
|
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
|
});
|