mbkauthe 4.8.4 → 5.0.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 +1 -0
- package/docs/api.md +29 -178
- package/docs/db.md +1 -1
- package/docs/db.sql +305 -253
- package/index.js +6 -3
- package/lib/config/cookies.js +84 -18
- package/lib/config/index.js +3 -1
- package/lib/config/tokenScopes.js +1 -1
- package/lib/createTable.js +95 -8
- package/lib/db/AuthRepository.js +336 -0
- package/lib/db/BaseRepository.js +193 -0
- package/lib/db/dialects/postgres.js +18 -0
- package/lib/main.js +5 -5
- package/lib/middleware/auth.js +213 -259
- package/lib/middleware/index.js +18 -25
- package/lib/middleware/scopeValidator.js +8 -3
- package/lib/pool.js +5 -6
- package/lib/routes/auth.js +95 -169
- package/lib/routes/dbLogs.js +247 -29
- package/lib/routes/misc.js +17 -50
- package/lib/routes/oauth.js +23 -48
- package/lib/utils/dbQueryLogger.js +485 -80
- package/lib/utils/errors.js +1 -1
- package/lib/utils/logger.js +12 -0
- package/lib/utils/timingSafeToken.js +1 -1
- package/package.json +1 -1
- package/public/main.css +36 -3
- package/test.spec.js +515 -48
- package/views/header.handlebars +1 -1
- package/views/pages/2fa.handlebars +9 -5
- package/views/pages/dbLogs.handlebars +618 -420
- package/views/pages/loginmbkauthe.handlebars +42 -25
- package/views/showmessage.handlebars +2 -2
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { postgresDialect } from "./dialects/postgres.js";
|
|
2
|
+
|
|
3
|
+
const isPlainObject = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
4
|
+
|
|
5
|
+
export class BaseRepository {
|
|
6
|
+
constructor({ db, dialect = postgresDialect } = {}) {
|
|
7
|
+
this.db = db;
|
|
8
|
+
this.dialect = dialect;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
quoteIdentifier(name) {
|
|
12
|
+
if (name === "*") return "*";
|
|
13
|
+
return String(name)
|
|
14
|
+
.split(".")
|
|
15
|
+
.map((part) => (part === "*" ? "*" : this.dialect.quoteIdentifier(part)))
|
|
16
|
+
.join(".");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
ident(name) {
|
|
20
|
+
return { kind: "ident", name };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
value(val) {
|
|
24
|
+
return { kind: "param", value: val };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
raw(text) {
|
|
28
|
+
return { kind: "raw", text: text ?? "" };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
list(values) {
|
|
32
|
+
return { kind: "list", values };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
table(name, alias = null) {
|
|
36
|
+
const tableSql = this.quoteIdentifier(name);
|
|
37
|
+
if (alias) {
|
|
38
|
+
return this.raw(`${tableSql} AS ${this.quoteIdentifier(alias)}`);
|
|
39
|
+
}
|
|
40
|
+
return this.raw(tableSql);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
column(name, alias = null) {
|
|
44
|
+
const columnSql = this.quoteIdentifier(name);
|
|
45
|
+
if (alias) {
|
|
46
|
+
return `${columnSql} AS ${this.quoteIdentifier(alias)}`;
|
|
47
|
+
}
|
|
48
|
+
return columnSql;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
columns(list) {
|
|
52
|
+
const items = (list || []).filter(Boolean).map((item) => {
|
|
53
|
+
if (isPlainObject(item) && item.kind) {
|
|
54
|
+
if (item.kind === "ident") return this.quoteIdentifier(item.name);
|
|
55
|
+
if (item.kind === "raw") return item.text;
|
|
56
|
+
}
|
|
57
|
+
return String(item);
|
|
58
|
+
});
|
|
59
|
+
return this.raw(items.join(", "));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
star(alias = null) {
|
|
63
|
+
if (!alias) return this.raw("*");
|
|
64
|
+
return this.raw(`${this.quoteIdentifier(alias)}.*`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
now() {
|
|
68
|
+
return this.raw(this.dialect.now());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
boolean(value) {
|
|
72
|
+
return this.raw(this.dialect.boolean(value));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
returning(columns) {
|
|
76
|
+
if (!this.dialect.supportsReturning) return this.raw("");
|
|
77
|
+
if (!columns) return this.raw("");
|
|
78
|
+
|
|
79
|
+
let columnSql = "";
|
|
80
|
+
if (isPlainObject(columns) && columns.kind === "raw") {
|
|
81
|
+
columnSql = columns.text;
|
|
82
|
+
} else if (Array.isArray(columns)) {
|
|
83
|
+
columnSql = columns.join(", ");
|
|
84
|
+
} else {
|
|
85
|
+
columnSql = String(columns);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!columnSql) return this.raw("");
|
|
89
|
+
return this.raw(this.dialect.returningClause(columnSql));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
limit(limit, offset) {
|
|
93
|
+
return this.raw(this.dialect.limitOffset({ limit, offset }));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
sql(strings, ...exprs) {
|
|
97
|
+
const values = [];
|
|
98
|
+
let text = strings?.[0] ?? "";
|
|
99
|
+
|
|
100
|
+
for (let i = 0; i < exprs.length; i += 1) {
|
|
101
|
+
text += this.renderToken(exprs[i], values);
|
|
102
|
+
text += strings?.[i + 1] ?? "";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { text, values };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
renderToken(token, values) {
|
|
109
|
+
if (token == null) return "";
|
|
110
|
+
|
|
111
|
+
if (!isPlainObject(token) || !token.kind) {
|
|
112
|
+
return String(token);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
switch (token.kind) {
|
|
116
|
+
case "raw":
|
|
117
|
+
return token.text;
|
|
118
|
+
case "ident":
|
|
119
|
+
return this.quoteIdentifier(token.name);
|
|
120
|
+
case "param":
|
|
121
|
+
values.push(token.value);
|
|
122
|
+
return this.dialect.param(values.length);
|
|
123
|
+
case "list":
|
|
124
|
+
if (!Array.isArray(token.values) || token.values.length === 0) {
|
|
125
|
+
return "(NULL)";
|
|
126
|
+
}
|
|
127
|
+
return `(${token.values.map((item) => {
|
|
128
|
+
values.push(item);
|
|
129
|
+
return this.dialect.param(values.length);
|
|
130
|
+
}).join(", ")})`;
|
|
131
|
+
default:
|
|
132
|
+
return "";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async execute(name, query) {
|
|
137
|
+
const { text, values } = query;
|
|
138
|
+
if (name) {
|
|
139
|
+
return this.db.query({ name, text, values });
|
|
140
|
+
}
|
|
141
|
+
return this.db.query({ text, values });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async executeRaw({ name, text, values }) {
|
|
145
|
+
if (name) {
|
|
146
|
+
return this.db.query({ name, text, values });
|
|
147
|
+
}
|
|
148
|
+
return this.db.query({ text, values });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
cloneWithDb(db) {
|
|
152
|
+
return new this.constructor({ db, dialect: this.dialect });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async withTransaction(fn) {
|
|
156
|
+
if (!this.db || typeof this.db.connect !== "function") {
|
|
157
|
+
return fn(this);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const client = await this.db.connect();
|
|
161
|
+
const txRepo = this.cloneWithDb(client);
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
await client.query("BEGIN");
|
|
165
|
+
const result = await fn(txRepo);
|
|
166
|
+
await client.query("COMMIT");
|
|
167
|
+
return result;
|
|
168
|
+
} catch (err) {
|
|
169
|
+
await client.query("ROLLBACK").catch(() => {});
|
|
170
|
+
throw err;
|
|
171
|
+
} finally {
|
|
172
|
+
client.release();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async lockTable(tableName, mode) {
|
|
177
|
+
if (!this.dialect.lockTable) return null;
|
|
178
|
+
const text = this.dialect.lockTable(this.quoteIdentifier(tableName), mode);
|
|
179
|
+
return this.executeRaw({
|
|
180
|
+
name: `lock-${tableName}`,
|
|
181
|
+
text,
|
|
182
|
+
values: []
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async advisoryTransactionLock(lockKey, queryName = "advisory-transaction-lock") {
|
|
187
|
+
return this.executeRaw({
|
|
188
|
+
name: queryName,
|
|
189
|
+
text: "SELECT pg_advisory_xact_lock(hashtext($1))",
|
|
190
|
+
values: [String(lockKey ?? "")]
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const quoteIdentifier = (name) => `"${String(name).replace(/"/g, '""')}"`;
|
|
2
|
+
|
|
3
|
+
export const postgresDialect = {
|
|
4
|
+
name: "postgres",
|
|
5
|
+
quoteIdentifier,
|
|
6
|
+
param: (index) => `$${index}`,
|
|
7
|
+
now: () => "NOW()",
|
|
8
|
+
boolean: (value) => (value ? "TRUE" : "FALSE"),
|
|
9
|
+
supportsReturning: true,
|
|
10
|
+
returningClause: (columns) => ` RETURNING ${columns}`,
|
|
11
|
+
limitOffset: ({ limit, offset } = {}) => {
|
|
12
|
+
const parts = [];
|
|
13
|
+
if (typeof limit === "number") parts.push(`LIMIT ${limit}`);
|
|
14
|
+
if (typeof offset === "number") parts.push(`OFFSET ${offset}`);
|
|
15
|
+
return parts.length ? ` ${parts.join(" ")}` : "";
|
|
16
|
+
},
|
|
17
|
+
lockTable: (tableSql, mode = "ROW EXCLUSIVE") => `LOCK TABLE ${tableSql} IN ${mode} MODE`
|
|
18
|
+
};
|
package/lib/main.js
CHANGED
|
@@ -41,17 +41,17 @@ router.use(cookieParser());
|
|
|
41
41
|
// CORS and security headers
|
|
42
42
|
router.use(corsMiddleware);
|
|
43
43
|
|
|
44
|
+
// Attach request context as early as possible so session-store queries are tied to the request.
|
|
45
|
+
if (process.env.env === 'dev') {
|
|
46
|
+
router.use(requestContextMiddleware);
|
|
47
|
+
}
|
|
48
|
+
|
|
44
49
|
// Session configuration
|
|
45
50
|
router.use(session(sessionConfig));
|
|
46
51
|
|
|
47
52
|
// Session restoration
|
|
48
53
|
router.use(sessionRestorationMiddleware);
|
|
49
54
|
|
|
50
|
-
// Attach request context for DB query logging (dev only)
|
|
51
|
-
if (process.env.env === 'dev') {
|
|
52
|
-
router.use(requestContextMiddleware);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
55
|
// Initialize passport
|
|
56
56
|
router.use(passport.initialize());
|
|
57
57
|
router.use(passport.session());
|