@devopsplaybook.io/common-utils 1.10.1 → 1.11.0-beta.24.d8f9aef
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 +73 -27
- package/dist/src/ConfigBase.d.ts +31 -0
- package/dist/src/ConfigBase.js +58 -16
- package/dist/src/DbUtils.d.ts +20 -3
- package/dist/src/DbUtils.js +93 -2
- package/dist/src/DbUtilsNoTelemetry.d.ts +4 -1
- package/dist/src/DbUtilsNoTelemetry.js +62 -6
- package/dist/src/PostgresDbUtils.d.ts +41 -10
- package/dist/src/PostgresDbUtils.js +357 -304
- package/dist/src/SqlDbUtils.d.ts +12 -4
- package/dist/src/SqlDbUtils.js +76 -30
- package/dist/src/users/Auth.d.ts +11 -1
- package/dist/src/users/Auth.js +160 -44
- package/dist/src/users/User.d.ts +10 -0
- package/dist/src/users/User.js +30 -10
- package/dist/src/users/UserApiToken.d.ts +4 -0
- package/dist/src/users/UserApiToken.js +11 -0
- package/dist/src/users/UsersApiTokensData.d.ts +12 -0
- package/dist/src/users/UsersApiTokensData.js +130 -33
- package/dist/src/users/UsersData.d.ts +20 -1
- package/dist/src/users/UsersData.js +150 -52
- package/dist/src/users/UsersRoutes.js +178 -61
- package/dist/src/users/index.d.ts +8 -0
- package/dist/src/users/index.js +24 -0
- package/package.json +58 -1
- package/.github/workflows/main-build.yml +0 -18
- package/.github/workflows/pr-check.yml +0 -27
- package/.github/workflows/reusable-merge-build.yml +0 -197
- package/.github/workflows/reusable-npm-merge.yml +0 -135
- package/.github/workflows/reusable-npm-pr.yml +0 -183
- package/.github/workflows/reusable-npm-upgrade.yml +0 -92
- package/.github/workflows/reusable-pr-verify.yml +0 -181
- package/AGENTS.md +0 -105
- package/index.ts +0 -18
- package/jest.config.js +0 -17
- package/prettierrc.json +0 -5
- package/src/ConfigBase.spec.ts +0 -108
- package/src/ConfigBase.ts +0 -297
- package/src/DbUtils.spec.ts +0 -23
- package/src/DbUtils.ts +0 -116
- package/src/DbUtilsNoTelemetry.spec.ts +0 -168
- package/src/DbUtilsNoTelemetry.ts +0 -117
- package/src/LLM.spec.ts +0 -303
- package/src/LLM.ts +0 -204
- package/src/Notifications.spec.ts +0 -265
- package/src/Notifications.ts +0 -201
- package/src/OTelContext.spec.ts +0 -58
- package/src/OTelContext.ts +0 -63
- package/src/PostgresDbUtils.spec.ts +0 -153
- package/src/PostgresDbUtils.ts +0 -666
- package/src/SqlDbUtils.spec.ts +0 -108
- package/src/SqlDbUtils.ts +0 -152
- package/src/SystemCommand.spec.ts +0 -18
- package/src/SystemCommand.ts +0 -23
- package/src/Timeout.spec.ts +0 -18
- package/src/Timeout.ts +0 -12
- package/src/users/Auth.spec.ts +0 -268
- package/src/users/Auth.ts +0 -202
- package/src/users/User.ts +0 -75
- package/src/users/UserApiToken.ts +0 -55
- package/src/users/UserPassword.spec.ts +0 -28
- package/src/users/UserPassword.ts +0 -20
- package/src/users/UserSession.ts +0 -9
- package/src/users/UsersApiTokensData.spec.ts +0 -158
- package/src/users/UsersApiTokensData.ts +0 -125
- package/src/users/UsersData.ts +0 -141
- package/src/users/UsersRoutes.ts +0 -374
- package/tsconfig.json +0 -15
- package/tsconfig.spec.json +0 -8
|
@@ -6,9 +6,21 @@ import { UserApiToken } from "./UserApiToken";
|
|
|
6
6
|
* Must be called once at startup, before any `UsersApiTokensData*` function.
|
|
7
7
|
*/
|
|
8
8
|
export declare function UsersApiTokensDataSetOTel(tracerIn: StandardTracer): void;
|
|
9
|
+
/**
|
|
10
|
+
* One-time capability probe for the optional `users_api_tokens` columns
|
|
11
|
+
* (`expiresAt`, `lastUsedAt`) added by the documented migration.
|
|
12
|
+
*/
|
|
13
|
+
export declare function UsersApiTokensDataSupportsOptionalColumns(context: Span | undefined): Promise<boolean>;
|
|
9
14
|
export declare function UsersApiTokensDataGet(context: Span | undefined, id: string): Promise<UserApiToken | null>;
|
|
10
15
|
export declare function UsersApiTokensDataGetByTokenHash(context: Span | undefined, tokenHash: string): Promise<UserApiToken | null>;
|
|
11
16
|
export declare function UsersApiTokensDataListByUser(context: Span | undefined, userId: string): Promise<UserApiToken[]>;
|
|
17
|
+
/** Number of tokens owned by a user (per-user cap check). */
|
|
18
|
+
export declare function UsersApiTokensDataCountByUser(context: Span | undefined, userId: string): Promise<number>;
|
|
12
19
|
export declare function UsersApiTokensDataAdd(context: Span | undefined, apiToken: UserApiToken): Promise<void>;
|
|
13
20
|
export declare function UsersApiTokensDataDelete(context: Span | undefined, id: string): Promise<void>;
|
|
14
21
|
export declare function UsersApiTokensDataDeleteByUser(context: Span | undefined, userId: string): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Record a token's last successful use. Best effort: only attempted when the
|
|
24
|
+
* optional columns exist, and failures never break authentication.
|
|
25
|
+
*/
|
|
26
|
+
export declare function UsersApiTokensDataSetLastUsed(context: Span | undefined, id: string, lastUsedAt: string): Promise<void>;
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.UsersApiTokensDataSetOTel = UsersApiTokensDataSetOTel;
|
|
4
|
+
exports.UsersApiTokensDataSupportsOptionalColumns = UsersApiTokensDataSupportsOptionalColumns;
|
|
4
5
|
exports.UsersApiTokensDataGet = UsersApiTokensDataGet;
|
|
5
6
|
exports.UsersApiTokensDataGetByTokenHash = UsersApiTokensDataGetByTokenHash;
|
|
6
7
|
exports.UsersApiTokensDataListByUser = UsersApiTokensDataListByUser;
|
|
8
|
+
exports.UsersApiTokensDataCountByUser = UsersApiTokensDataCountByUser;
|
|
7
9
|
exports.UsersApiTokensDataAdd = UsersApiTokensDataAdd;
|
|
8
10
|
exports.UsersApiTokensDataDelete = UsersApiTokensDataDelete;
|
|
9
11
|
exports.UsersApiTokensDataDeleteByUser = UsersApiTokensDataDeleteByUser;
|
|
12
|
+
exports.UsersApiTokensDataSetLastUsed = UsersApiTokensDataSetLastUsed;
|
|
10
13
|
const DbUtils_1 = require("../DbUtils");
|
|
11
14
|
const UserApiToken_1 = require("./UserApiToken");
|
|
12
15
|
let tracer;
|
|
16
|
+
/**
|
|
17
|
+
* Whether the optional `expiresAt` / `lastUsedAt` columns exist. Probed once
|
|
18
|
+
* per process so a database that did not run the documented migration never
|
|
19
|
+
* breaks the auth path.
|
|
20
|
+
*/
|
|
21
|
+
let optionalColumnsAvailable = null;
|
|
13
22
|
/**
|
|
14
23
|
* Injects the OTel tracer instance used by the API tokens data module.
|
|
15
24
|
* Must be called once at startup, before any `UsersApiTokensData*` function.
|
|
@@ -17,67 +26,152 @@ let tracer;
|
|
|
17
26
|
function UsersApiTokensDataSetOTel(tracerIn) {
|
|
18
27
|
tracer = tracerIn;
|
|
19
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* One-time capability probe for the optional `users_api_tokens` columns
|
|
31
|
+
* (`expiresAt`, `lastUsedAt`) added by the documented migration.
|
|
32
|
+
*/
|
|
33
|
+
async function UsersApiTokensDataSupportsOptionalColumns(context) {
|
|
34
|
+
if (optionalColumnsAvailable === null) {
|
|
35
|
+
try {
|
|
36
|
+
await (0, DbUtils_1.DbUtilsQuerySQL)(context, 'SELECT "expiresAt" FROM users_api_tokens LIMIT 1');
|
|
37
|
+
optionalColumnsAvailable = true;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
optionalColumnsAvailable = false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return optionalColumnsAvailable;
|
|
44
|
+
}
|
|
20
45
|
async function UsersApiTokensDataGet(context, id) {
|
|
21
46
|
const span = tracer.startSpan("UsersApiTokensDataGet", context);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
47
|
+
try {
|
|
48
|
+
const tokensRaw = await (0, DbUtils_1.DbUtilsQuerySQL)(span, SQL_QUERIES.GET_TOKEN_BY_ID, [
|
|
49
|
+
id,
|
|
50
|
+
]);
|
|
51
|
+
if (tokensRaw.length > 0) {
|
|
52
|
+
return fromRaw(tokensRaw[0]);
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
span.end();
|
|
58
|
+
}
|
|
31
59
|
}
|
|
32
60
|
async function UsersApiTokensDataGetByTokenHash(context, tokenHash) {
|
|
33
61
|
const span = tracer.startSpan("UsersApiTokensDataGetByTokenHash", context);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
62
|
+
try {
|
|
63
|
+
const tokensRaw = await (0, DbUtils_1.DbUtilsQuerySQL)(span, SQL_QUERIES.GET_TOKEN_BY_HASH, [tokenHash]);
|
|
64
|
+
if (tokensRaw.length > 0) {
|
|
65
|
+
return fromRaw(tokensRaw[0]);
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
span.end();
|
|
38
71
|
}
|
|
39
|
-
span.end();
|
|
40
|
-
return apiToken;
|
|
41
72
|
}
|
|
42
73
|
async function UsersApiTokensDataListByUser(context, userId) {
|
|
43
74
|
const span = tracer.startSpan("UsersApiTokensDataListByUser", context);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
75
|
+
try {
|
|
76
|
+
const tokensRaw = await (0, DbUtils_1.DbUtilsQuerySQL)(span, SQL_QUERIES.LIST_TOKENS_BY_USER, [userId]);
|
|
77
|
+
return tokensRaw.map(fromRaw);
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
span.end();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** Number of tokens owned by a user (per-user cap check). */
|
|
84
|
+
async function UsersApiTokensDataCountByUser(context, userId) {
|
|
85
|
+
var _a;
|
|
86
|
+
var _b;
|
|
87
|
+
const span = tracer.startSpan("UsersApiTokensDataCountByUser", context);
|
|
88
|
+
try {
|
|
89
|
+
const rows = await (0, DbUtils_1.DbUtilsQuerySQL)(span, SQL_QUERIES.COUNT_TOKENS_BY_USER, [
|
|
90
|
+
userId,
|
|
91
|
+
]);
|
|
92
|
+
return Number((_b = (_a = rows[0]) === null || _a === void 0 ? void 0 : _a.count) !== null && _b !== void 0 ? _b : 0);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
span.end();
|
|
48
96
|
}
|
|
49
|
-
span.end();
|
|
50
|
-
return apiTokens;
|
|
51
97
|
}
|
|
52
98
|
async function UsersApiTokensDataAdd(context, apiToken) {
|
|
53
99
|
const span = tracer.startSpan("UsersApiTokensDataAdd", context);
|
|
54
|
-
|
|
55
|
-
apiToken.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
100
|
+
try {
|
|
101
|
+
if (apiToken.expiresAt) {
|
|
102
|
+
if (!(await UsersApiTokensDataSupportsOptionalColumns(context))) {
|
|
103
|
+
throw new Error("users_api_tokens.expiresAt does not exist: run the documented migration to enable API token expiry");
|
|
104
|
+
}
|
|
105
|
+
await (0, DbUtils_1.DbUtilsExecSQL)(span, SQL_QUERIES.INSERT_TOKEN_WITH_EXPIRY, [
|
|
106
|
+
apiToken.id,
|
|
107
|
+
apiToken.name,
|
|
108
|
+
apiToken.userId,
|
|
109
|
+
apiToken.tokenHash,
|
|
110
|
+
apiToken.dateCreated,
|
|
111
|
+
apiToken.expiresAt,
|
|
112
|
+
]);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
await (0, DbUtils_1.DbUtilsExecSQL)(span, SQL_QUERIES.INSERT_TOKEN, [
|
|
116
|
+
apiToken.id,
|
|
117
|
+
apiToken.name,
|
|
118
|
+
apiToken.userId,
|
|
119
|
+
apiToken.tokenHash,
|
|
120
|
+
apiToken.dateCreated,
|
|
121
|
+
]);
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
span.end();
|
|
125
|
+
}
|
|
62
126
|
}
|
|
63
127
|
async function UsersApiTokensDataDelete(context, id) {
|
|
64
128
|
const span = tracer.startSpan("UsersApiTokensDataDelete", context);
|
|
65
|
-
|
|
66
|
-
|
|
129
|
+
try {
|
|
130
|
+
await (0, DbUtils_1.DbUtilsExecSQL)(span, SQL_QUERIES.DELETE_TOKEN, [id]);
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
span.end();
|
|
134
|
+
}
|
|
67
135
|
}
|
|
68
136
|
async function UsersApiTokensDataDeleteByUser(context, userId) {
|
|
69
137
|
const span = tracer.startSpan("UsersApiTokensDataDeleteByUser", context);
|
|
70
|
-
|
|
71
|
-
|
|
138
|
+
try {
|
|
139
|
+
await (0, DbUtils_1.DbUtilsExecSQL)(span, SQL_QUERIES.DELETE_TOKENS_BY_USER, [userId]);
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
span.end();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Record a token's last successful use. Best effort: only attempted when the
|
|
147
|
+
* optional columns exist, and failures never break authentication.
|
|
148
|
+
*/
|
|
149
|
+
async function UsersApiTokensDataSetLastUsed(context, id, lastUsedAt) {
|
|
150
|
+
if (!(await UsersApiTokensDataSupportsOptionalColumns(context))) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const span = tracer.startSpan("UsersApiTokensDataSetLastUsed", context);
|
|
154
|
+
try {
|
|
155
|
+
await (0, DbUtils_1.DbUtilsExecSQL)(span, SQL_QUERIES.UPDATE_TOKEN_LAST_USED, [
|
|
156
|
+
lastUsedAt,
|
|
157
|
+
id,
|
|
158
|
+
]);
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
span.end();
|
|
162
|
+
}
|
|
72
163
|
}
|
|
73
164
|
// Private Functions
|
|
74
165
|
function fromRaw(tokenRaw) {
|
|
166
|
+
var _a, _b;
|
|
75
167
|
const apiToken = new UserApiToken_1.UserApiToken();
|
|
76
168
|
apiToken.id = tokenRaw.id;
|
|
77
169
|
apiToken.name = tokenRaw.name;
|
|
78
170
|
apiToken.userId = tokenRaw.userId;
|
|
79
171
|
apiToken.tokenHash = tokenRaw.tokenHash;
|
|
80
172
|
apiToken.dateCreated = tokenRaw.dateCreated;
|
|
173
|
+
apiToken.expiresAt = (_a = tokenRaw.expiresAt) !== null && _a !== void 0 ? _a : null;
|
|
174
|
+
apiToken.lastUsedAt = (_b = tokenRaw.lastUsedAt) !== null && _b !== void 0 ? _b : null;
|
|
81
175
|
return apiToken;
|
|
82
176
|
}
|
|
83
177
|
// SQL
|
|
@@ -87,7 +181,10 @@ const SQL_QUERIES = {
|
|
|
87
181
|
GET_TOKEN_BY_ID: 'SELECT * FROM users_api_tokens WHERE "id" = ?',
|
|
88
182
|
GET_TOKEN_BY_HASH: 'SELECT * FROM users_api_tokens WHERE "tokenHash" = ?',
|
|
89
183
|
LIST_TOKENS_BY_USER: 'SELECT * FROM users_api_tokens WHERE "userId" = ?',
|
|
184
|
+
COUNT_TOKENS_BY_USER: 'SELECT COUNT(*) as count FROM users_api_tokens WHERE "userId" = ?',
|
|
90
185
|
INSERT_TOKEN: 'INSERT INTO users_api_tokens ("id", "name", "userId", "tokenHash", "dateCreated") VALUES (?, ?, ?, ?, ?)',
|
|
186
|
+
INSERT_TOKEN_WITH_EXPIRY: 'INSERT INTO users_api_tokens ("id", "name", "userId", "tokenHash", "dateCreated", "expiresAt") VALUES (?, ?, ?, ?, ?, ?)',
|
|
187
|
+
UPDATE_TOKEN_LAST_USED: 'UPDATE users_api_tokens SET "lastUsedAt" = ? WHERE "id" = ?',
|
|
91
188
|
DELETE_TOKEN: 'DELETE FROM users_api_tokens WHERE "id" = ?',
|
|
92
189
|
DELETE_TOKENS_BY_USER: 'DELETE FROM users_api_tokens WHERE "userId" = ?',
|
|
93
190
|
};
|
|
@@ -8,8 +8,27 @@ import { User } from "./User";
|
|
|
8
8
|
export declare function UsersDataSetOTel(tracerIn: StandardTracer): void;
|
|
9
9
|
export declare function UsersDataGet(context: Span | undefined, id: string): Promise<User | null>;
|
|
10
10
|
export declare function UsersDataGetByName(context: Span | undefined, name: string): Promise<User | null>;
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* List users, optionally paginated. `offset` is only applied together with a
|
|
13
|
+
* `limit` (SQLite requires a LIMIT clause for OFFSET).
|
|
14
|
+
*/
|
|
15
|
+
export declare function UsersDataList(context: Span | undefined, limit?: number, offset?: number): Promise<User[]>;
|
|
16
|
+
/** Number of users (COUNT(*) – used instead of loading the full list). */
|
|
17
|
+
export declare function UsersDataCount(context: Span | undefined): Promise<number>;
|
|
18
|
+
/** Number of admin users (COUNT(*) – used by the last-admin guards). */
|
|
19
|
+
export declare function UsersDataCountAdmins(context: Span | undefined): Promise<number>;
|
|
12
20
|
export declare function UsersDataAdd(context: Span | undefined, user: User): Promise<void>;
|
|
13
21
|
export declare function UsersDataUpdatePassword(context: Span | undefined, user: User): Promise<void>;
|
|
14
22
|
export declare function UsersDataUpdateUser(context: Span | undefined, user: User): Promise<void>;
|
|
15
23
|
export declare function UsersDataDelete(context: Span | undefined, id: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Invalidate the JWTs issued for a user by incrementing `tokenVersion`.
|
|
26
|
+
* Only called when `JWT_REVOCATION_ENABLED` is on: the column is part of the
|
|
27
|
+
* documented `users` migration and absent otherwise.
|
|
28
|
+
*/
|
|
29
|
+
export declare function UsersDataBumpTokenVersion(context: Span | undefined, id: string): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Whether an error is a unique-constraint violation (`SQLITE_CONSTRAINT*` /
|
|
32
|
+
* Postgres `23505`) – used to turn the user-name uniqueness race into a 400.
|
|
33
|
+
*/
|
|
34
|
+
export declare function isUniqueViolationError(error: unknown): boolean;
|
|
@@ -4,10 +4,14 @@ exports.UsersDataSetOTel = UsersDataSetOTel;
|
|
|
4
4
|
exports.UsersDataGet = UsersDataGet;
|
|
5
5
|
exports.UsersDataGetByName = UsersDataGetByName;
|
|
6
6
|
exports.UsersDataList = UsersDataList;
|
|
7
|
+
exports.UsersDataCount = UsersDataCount;
|
|
8
|
+
exports.UsersDataCountAdmins = UsersDataCountAdmins;
|
|
7
9
|
exports.UsersDataAdd = UsersDataAdd;
|
|
8
10
|
exports.UsersDataUpdatePassword = UsersDataUpdatePassword;
|
|
9
11
|
exports.UsersDataUpdateUser = UsersDataUpdateUser;
|
|
10
12
|
exports.UsersDataDelete = UsersDataDelete;
|
|
13
|
+
exports.UsersDataBumpTokenVersion = UsersDataBumpTokenVersion;
|
|
14
|
+
exports.isUniqueViolationError = isUniqueViolationError;
|
|
11
15
|
const DbUtils_1 = require("../DbUtils");
|
|
12
16
|
const User_1 = require("./User");
|
|
13
17
|
let tracer;
|
|
@@ -20,85 +24,176 @@ function UsersDataSetOTel(tracerIn) {
|
|
|
20
24
|
}
|
|
21
25
|
async function UsersDataGet(context, id) {
|
|
22
26
|
const span = tracer.startSpan("UsersDataGet", context);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
try {
|
|
28
|
+
const usersRaw = await (0, DbUtils_1.DbUtilsQuerySQL)(span, SQL_QUERIES.GET_USER_BY_ID, [
|
|
29
|
+
id,
|
|
30
|
+
]);
|
|
31
|
+
if (usersRaw.length > 0) {
|
|
32
|
+
return fromRaw(usersRaw[0]);
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
span.end();
|
|
38
|
+
}
|
|
32
39
|
}
|
|
33
40
|
async function UsersDataGetByName(context, name) {
|
|
34
41
|
const span = tracer.startSpan("UsersDataGetByName", context);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
try {
|
|
43
|
+
const usersRaw = await (0, DbUtils_1.DbUtilsQuerySQL)(span, SQL_QUERIES.GET_USER_BY_NAME, [
|
|
44
|
+
name,
|
|
45
|
+
]);
|
|
46
|
+
if (usersRaw.length > 0) {
|
|
47
|
+
return fromRaw(usersRaw[0]);
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
span.end();
|
|
53
|
+
}
|
|
44
54
|
}
|
|
45
|
-
|
|
55
|
+
/**
|
|
56
|
+
* List users, optionally paginated. `offset` is only applied together with a
|
|
57
|
+
* `limit` (SQLite requires a LIMIT clause for OFFSET).
|
|
58
|
+
*/
|
|
59
|
+
async function UsersDataList(context, limit, offset) {
|
|
46
60
|
const span = tracer.startSpan("UsersDataList", context);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
61
|
+
try {
|
|
62
|
+
let sql = SQL_QUERIES.LIST_USERS;
|
|
63
|
+
const params = [];
|
|
64
|
+
if (limit !== undefined) {
|
|
65
|
+
sql += " LIMIT ?";
|
|
66
|
+
params.push(limit);
|
|
67
|
+
if (offset !== undefined) {
|
|
68
|
+
sql += " OFFSET ?";
|
|
69
|
+
params.push(offset);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const usersRaw = await (0, DbUtils_1.DbUtilsQuerySQL)(span, sql, params);
|
|
73
|
+
return usersRaw.map(fromRaw);
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
span.end();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Number of users (COUNT(*) – used instead of loading the full list). */
|
|
80
|
+
async function UsersDataCount(context) {
|
|
81
|
+
var _a;
|
|
82
|
+
var _b;
|
|
83
|
+
const span = tracer.startSpan("UsersDataCount", context);
|
|
84
|
+
try {
|
|
85
|
+
const rows = await (0, DbUtils_1.DbUtilsQuerySQL)(span, SQL_QUERIES.COUNT_USERS);
|
|
86
|
+
return Number((_b = (_a = rows[0]) === null || _a === void 0 ? void 0 : _a.count) !== null && _b !== void 0 ? _b : 0);
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
span.end();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** Number of admin users (COUNT(*) – used by the last-admin guards). */
|
|
93
|
+
async function UsersDataCountAdmins(context) {
|
|
94
|
+
var _a;
|
|
95
|
+
var _b;
|
|
96
|
+
const span = tracer.startSpan("UsersDataCountAdmins", context);
|
|
97
|
+
try {
|
|
98
|
+
const rows = await (0, DbUtils_1.DbUtilsQuerySQL)(span, SQL_QUERIES.COUNT_ADMINS);
|
|
99
|
+
return Number((_b = (_a = rows[0]) === null || _a === void 0 ? void 0 : _a.count) !== null && _b !== void 0 ? _b : 0);
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
span.end();
|
|
51
103
|
}
|
|
52
|
-
span.end();
|
|
53
|
-
return users;
|
|
54
104
|
}
|
|
55
105
|
async function UsersDataAdd(context, user) {
|
|
56
106
|
const span = tracer.startSpan("UsersDataAdd", context);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
107
|
+
try {
|
|
108
|
+
await (0, DbUtils_1.DbUtilsExecSQL)(span, SQL_QUERIES.INSERT_USER, [
|
|
109
|
+
user.id,
|
|
110
|
+
user.name,
|
|
111
|
+
user.passwordEncrypted,
|
|
112
|
+
user.role,
|
|
113
|
+
JSON.stringify(user.scopes),
|
|
114
|
+
]);
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
span.end();
|
|
118
|
+
}
|
|
65
119
|
}
|
|
66
120
|
async function UsersDataUpdatePassword(context, user) {
|
|
67
121
|
const span = tracer.startSpan("UsersDataUpdatePassword", context);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
122
|
+
try {
|
|
123
|
+
await (0, DbUtils_1.DbUtilsExecSQL)(span, SQL_QUERIES.UPDATE_PASSWORD, [
|
|
124
|
+
user.passwordEncrypted,
|
|
125
|
+
user.id,
|
|
126
|
+
]);
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
span.end();
|
|
130
|
+
}
|
|
73
131
|
}
|
|
74
132
|
async function UsersDataUpdateUser(context, user) {
|
|
75
133
|
const span = tracer.startSpan("UsersDataUpdateUser", context);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
134
|
+
try {
|
|
135
|
+
await (0, DbUtils_1.DbUtilsExecSQL)(span, SQL_QUERIES.UPDATE_USER, [
|
|
136
|
+
user.role,
|
|
137
|
+
JSON.stringify(user.scopes),
|
|
138
|
+
user.id,
|
|
139
|
+
]);
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
span.end();
|
|
143
|
+
}
|
|
82
144
|
}
|
|
83
145
|
async function UsersDataDelete(context, id) {
|
|
84
146
|
const span = tracer.startSpan("UsersDataDelete", context);
|
|
85
|
-
|
|
86
|
-
|
|
147
|
+
try {
|
|
148
|
+
await (0, DbUtils_1.DbUtilsExecSQL)(span, SQL_QUERIES.DELETE_USER, [id]);
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
span.end();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Invalidate the JWTs issued for a user by incrementing `tokenVersion`.
|
|
156
|
+
* Only called when `JWT_REVOCATION_ENABLED` is on: the column is part of the
|
|
157
|
+
* documented `users` migration and absent otherwise.
|
|
158
|
+
*/
|
|
159
|
+
async function UsersDataBumpTokenVersion(context, id) {
|
|
160
|
+
const span = tracer.startSpan("UsersDataBumpTokenVersion", context);
|
|
161
|
+
try {
|
|
162
|
+
await (0, DbUtils_1.DbUtilsExecSQL)(span, SQL_QUERIES.BUMP_TOKEN_VERSION, [id]);
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
span.end();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Whether an error is a unique-constraint violation (`SQLITE_CONSTRAINT*` /
|
|
170
|
+
* Postgres `23505`) – used to turn the user-name uniqueness race into a 400.
|
|
171
|
+
*/
|
|
172
|
+
function isUniqueViolationError(error) {
|
|
173
|
+
if (!error) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
const err = error;
|
|
177
|
+
if (err.code === "23505" ||
|
|
178
|
+
err.code === "SQLITE_CONSTRAINT_UNIQUE" ||
|
|
179
|
+
err.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
const message = typeof err.message === "string" ? err.message : "";
|
|
183
|
+
return (/UNIQUE constraint failed/i.test(message) ||
|
|
184
|
+
/duplicate key value violates unique constraint/i.test(message));
|
|
87
185
|
}
|
|
88
186
|
// Private Functions
|
|
89
187
|
function fromRaw(userRaw) {
|
|
188
|
+
var _a;
|
|
90
189
|
const user = new User_1.User();
|
|
91
190
|
user.id = userRaw.id;
|
|
92
191
|
user.name = userRaw.name;
|
|
93
192
|
user.passwordEncrypted = userRaw.passwordEncrypted;
|
|
94
193
|
user.role = userRaw.role || "user";
|
|
194
|
+
user.tokenVersion = Number((_a = userRaw.tokenVersion) !== null && _a !== void 0 ? _a : 0) || 0;
|
|
95
195
|
if (userRaw.scopes) {
|
|
96
|
-
|
|
97
|
-
user.scopes = JSON.parse(userRaw.scopes);
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
user.scopes = [...User_1.User.DEFAULT_SCOPES];
|
|
101
|
-
}
|
|
196
|
+
user.scopes = User_1.User.normalizeScopes(userRaw.scopes);
|
|
102
197
|
}
|
|
103
198
|
return user;
|
|
104
199
|
}
|
|
@@ -109,8 +204,11 @@ const SQL_QUERIES = {
|
|
|
109
204
|
GET_USER_BY_ID: 'SELECT * FROM users WHERE "id" = ?',
|
|
110
205
|
GET_USER_BY_NAME: 'SELECT * FROM users WHERE "name" = ?',
|
|
111
206
|
LIST_USERS: "SELECT * FROM users",
|
|
207
|
+
COUNT_USERS: "SELECT COUNT(*) as count FROM users",
|
|
208
|
+
COUNT_ADMINS: "SELECT COUNT(*) as count FROM users WHERE \"role\" = 'admin'",
|
|
112
209
|
INSERT_USER: 'INSERT INTO users ("id", "name", "passwordEncrypted", "role", "scopes") VALUES (?, ?, ?, ?, ?)',
|
|
113
210
|
UPDATE_USER: 'UPDATE users SET "role" = ?, "scopes" = ? WHERE "id" = ?',
|
|
114
211
|
UPDATE_PASSWORD: 'UPDATE users SET "passwordEncrypted" = ? WHERE "id" = ?',
|
|
212
|
+
BUMP_TOKEN_VERSION: 'UPDATE users SET "tokenVersion" = COALESCE("tokenVersion", 0) + 1 WHERE "id" = ?',
|
|
115
213
|
DELETE_USER: 'DELETE FROM users WHERE "id" = ?',
|
|
116
214
|
};
|