@devopsplaybook.io/common-utils 1.3.0-beta.12.165a8d0 → 1.4.0-beta.13.4edad2b

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/AGENTS.md CHANGED
@@ -20,6 +20,11 @@ src/
20
20
  PostgresDbUtils.ts # PostgreSQL operations (pg.Pool, async/Promise)
21
21
  DbUtils.ts # Unified facade dispatching to Sql or Postgres
22
22
  DbUtilsNoTelemetry.ts # Same DB ops without OTel span overhead
23
+ User.ts / UserSession.ts # User model, roles and application-defined scopes
24
+ Auth.ts # JWT auth (key init, guards, session decode)
25
+ UserPassword.ts # bcrypt password hashing/verification
26
+ UsersData.ts # Users table CRUD (SQLite/Postgres)
27
+ UsersRoutes.ts # Standard fastify user management routes
23
28
  SystemCommand.ts # Promise wrapper around child_process.exec
24
29
  Timeout.ts # Promise wrapper around setTimeout
25
30
  *.spec.ts # Co-located test files
@@ -41,6 +46,7 @@ src/
41
46
  - **Tests**: Jest with `ts-jest`. Spec files live next to source (`*.spec.ts`). Run with `npm run test`. The `tsconfig.spec.json` includes jest types.
42
47
  - **No default exports**: All modules use named exports only.
43
48
  - **OTel dependency injection**: Every DB module exposes a `*SetOTel(tracer, logger)` function that must be called before `*Init()`. OTel instances are stored as module-level singletons.
49
+ - **Auth modules**: `AuthSetOTel(tracer)` and `UsersDataSetOTel(tracer)` must be called before `AuthInit`. Application scopes are registered through `AuthInit(context, config, allScopes)`; `UsersRoutes` relies on `req.tracerSpanApi` set by the `otel-utils-fastify` hooks.
44
50
  - **ModuleLogger pattern**: `StandardLogger` only exposes `createModuleLogger(name)`. DB modules call `logger.createModuleLogger("ModuleName")` internally. Never call `.info()` or `.error()` directly on a `StandardLogger`.
45
51
  - **SQLite-first SQL**: Write SQL with `?` placeholders. The `DbUtils` facade and `DbUtilsNoTelemetry` module auto-convert to `$1, $2, ...` for Postgres via `convertToPostgresPlaceholders()`.
46
52
  - **Migration convention**: SQL files named `init-NNNN.sql`. `init-0000.sql` must create the `metadata` table. Subsequent files are applied in lexicographic order; applied versions are tracked in `metadata` for idempotency.
@@ -65,6 +71,9 @@ All three commands must pass before committing. The CI pipeline (`reusable-npm-m
65
71
  | `pg` | PostgreSQL client with connection pooling |
66
72
  | `uuid` | v14+ (ESM -- requires `jest.mock("uuid")` in tests) |
67
73
  | `fs-extra` | Async/sync file operations, `readJson`/`ensureDir` |
74
+ | `bcrypt` | Password hashing (users module) |
75
+ | `jsonwebtoken` | JWT signing/verification (auth module) |
76
+ | `fastify` | HTTP framework types used by `UsersRoutes` |
68
77
 
69
78
  ## Known Gotchas
70
79
 
package/README.md CHANGED
@@ -34,6 +34,9 @@ npm install @devopsplaybook.io/common-utils
34
34
  | `fs-extra` | File system helpers |
35
35
  | `uuid` | UUID generation for JWT keys |
36
36
  | `axios` | HTTP client for the notifications integration |
37
+ | `bcrypt` | Password hashing for the users module |
38
+ | `jsonwebtoken` | JWT signing/verification for the auth module |
39
+ | `fastify` | HTTP framework types for the users routes |
37
40
 
38
41
  ### Modules
39
42
 
@@ -333,6 +336,47 @@ await client.send({
333
336
 
334
337
  ---
335
338
 
339
+ #### `Auth`, `User`, `UserSession`, `UserPassword`, `UsersData`, `UsersRoutes` -- Authentication and User Management
340
+
341
+ Standard JWT-based authentication and user management shared across all server projects: JWT key persistence in the `metadata` table, request authentication helpers, bcrypt password hashing, users CRUD, and ready-to-register fastify routes (login, user CRUD, password change).
342
+
343
+ ```ts
344
+ import {
345
+ AuthSetOTel,
346
+ AuthInit,
347
+ UsersDataSetOTel,
348
+ UsersRoutes,
349
+ } from "@devopsplaybook.io/common-utils";
350
+
351
+ // At startup, after DbUtilsInit:
352
+ AuthSetOTel(otel.OTelTracer());
353
+ UsersDataSetOTel(otel.OTelTracer());
354
+ await AuthInit(span, config, ["traces", "metrics", "logs"]); // app scopes
355
+
356
+ // Register the standard user routes:
357
+ fastify.register(new UsersRoutes().getRoutes, { prefix: "/api/users" });
358
+ ```
359
+
360
+ | Export | Description |
361
+ | ---------------------------- | ---------------------------------------------------------------------- |
362
+ | `AuthSetOTel` | Injects the OTel tracer used by the auth module (before `AuthInit`) |
363
+ | `AuthInit` | Registers app scopes, loads or generates the JWT key from `metadata` |
364
+ | `AuthGenerateJWT` | Signs a JWT for a user (admins get all scopes) |
365
+ | `AuthMustBeAuthenticated` | 403 guard: any valid JWT |
366
+ | `AuthMustBeAdmin` | 403 guard: `role === "admin"` |
367
+ | `AuthHasScope` | 403 guard: admin or JWT containing the requested scope |
368
+ | `AuthGetUserSession` | Returns the `UserSession` decoded from the request JWT |
369
+ | `User`, `UserRole`, `UserScope` | User model; scopes are application-defined strings |
370
+ | `UserSession` | Decoded session: `isAuthenticated`, `userId`, `userName`, `role`, `scopes` |
371
+ | `UserPasswordSetPassword` / `UserPasswordCheckPassword` | bcrypt hashing and verification |
372
+ | `UsersDataSetOTel` | Injects the OTel tracer used by the users data module |
373
+ | `UsersData*` | Users table CRUD (`Get`, `GetByName`, `List`, `Add`, `UpdateUser`, `UpdatePassword`, `Delete`) |
374
+ | `UsersRoutes` | Fastify routes: `GET /status/initialization`, `POST /session`, user CRUD, `PUT /password` |
375
+
376
+ **Requirements**: a `users` table (columns `id`, `name`, `passwordEncrypted`, `role`, `scopes`) and the standard `metadata` table created by `init-0000.sql`. SQL is written SQLite-first; the `DbUtils` facade converts placeholders for Postgres.
377
+
378
+ ---
379
+
336
380
  #### `SystemCommand` -- Shell Command Execution
337
381
 
338
382
  ```ts
package/dist/index.d.ts CHANGED
@@ -7,3 +7,9 @@ export * from "./src/PostgresDbUtils";
7
7
  export * from "./src/Notifications";
8
8
  export * from "./src/SystemCommand";
9
9
  export * from "./src/Timeout";
10
+ export * from "./src/User";
11
+ export * from "./src/UserSession";
12
+ export * from "./src/Auth";
13
+ export * from "./src/UserPassword";
14
+ export * from "./src/UsersData";
15
+ export * from "./src/UsersRoutes";
package/dist/index.js CHANGED
@@ -23,3 +23,9 @@ __exportStar(require("./src/PostgresDbUtils"), exports);
23
23
  __exportStar(require("./src/Notifications"), exports);
24
24
  __exportStar(require("./src/SystemCommand"), exports);
25
25
  __exportStar(require("./src/Timeout"), exports);
26
+ __exportStar(require("./src/User"), exports);
27
+ __exportStar(require("./src/UserSession"), exports);
28
+ __exportStar(require("./src/Auth"), exports);
29
+ __exportStar(require("./src/UserPassword"), exports);
30
+ __exportStar(require("./src/UsersData"), exports);
31
+ __exportStar(require("./src/UsersRoutes"), exports);
@@ -0,0 +1,34 @@
1
+ import { StandardTracer } from "@devopsplaybook.io/otel-utils";
2
+ import { Span } from "@opentelemetry/sdk-trace-base";
3
+ import { User, UserScope } from "./User";
4
+ import { UserSession } from "./UserSession";
5
+ /**
6
+ * Configuration subset required by the auth module.
7
+ */
8
+ export interface AuthConfig {
9
+ JWT_KEY: string;
10
+ JWT_VALIDITY_DURATION: number;
11
+ DATABASE_TYPE: "sqlite" | "postgres";
12
+ }
13
+ /**
14
+ * Injects the OTel tracer instance used by the auth module.
15
+ * Must be called once at startup, before {@link AuthInit}.
16
+ */
17
+ export declare function AuthSetOTel(tracerIn: StandardTracer): void;
18
+ /**
19
+ * Initialise the auth module.
20
+ *
21
+ * Registers the full scope set of the host application and loads the JWT
22
+ * signing key from the `metadata` table. When no key is stored yet, a fresh
23
+ * one is generated and persisted.
24
+ *
25
+ * @param context Parent OTel span.
26
+ * @param configIn Server configuration (JWT_KEY is updated in place).
27
+ * @param allScopes All scopes supported by the host application.
28
+ */
29
+ export declare function AuthInit(context: Span, configIn: AuthConfig, allScopes?: UserScope[]): Promise<void>;
30
+ export declare function AuthGenerateJWT(user: User): Promise<string>;
31
+ export declare function AuthMustBeAuthenticated(req: any, res: any): Promise<void>;
32
+ export declare function AuthMustBeAdmin(req: any, res: any): Promise<void>;
33
+ export declare function AuthHasScope(req: any, res: any, scope: UserScope): Promise<void>;
34
+ export declare function AuthGetUserSession(req: any): Promise<UserSession>;
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.AuthSetOTel = AuthSetOTel;
37
+ exports.AuthInit = AuthInit;
38
+ exports.AuthGenerateJWT = AuthGenerateJWT;
39
+ exports.AuthMustBeAuthenticated = AuthMustBeAuthenticated;
40
+ exports.AuthMustBeAdmin = AuthMustBeAdmin;
41
+ exports.AuthHasScope = AuthHasScope;
42
+ exports.AuthGetUserSession = AuthGetUserSession;
43
+ const jwt = __importStar(require("jsonwebtoken"));
44
+ const uuid_1 = require("uuid");
45
+ const DbUtils_1 = require("./DbUtils");
46
+ const User_1 = require("./User");
47
+ let tracer;
48
+ let config;
49
+ /**
50
+ * Injects the OTel tracer instance used by the auth module.
51
+ * Must be called once at startup, before {@link AuthInit}.
52
+ */
53
+ function AuthSetOTel(tracerIn) {
54
+ tracer = tracerIn;
55
+ }
56
+ /**
57
+ * Initialise the auth module.
58
+ *
59
+ * Registers the full scope set of the host application and loads the JWT
60
+ * signing key from the `metadata` table. When no key is stored yet, a fresh
61
+ * one is generated and persisted.
62
+ *
63
+ * @param context Parent OTel span.
64
+ * @param configIn Server configuration (JWT_KEY is updated in place).
65
+ * @param allScopes All scopes supported by the host application.
66
+ */
67
+ async function AuthInit(context, configIn, allScopes = []) {
68
+ config = configIn;
69
+ User_1.User.ALL_SCOPES = [...allScopes];
70
+ const span = tracer.startSpan("AuthInit", context);
71
+ const authKeyRaw = await (0, DbUtils_1.DbUtilsQuerySQL)(span, SQL_QUERIES.GET_AUTH_TOKEN);
72
+ if (authKeyRaw.length == 0) {
73
+ configIn.JWT_KEY = (0, uuid_1.v4)();
74
+ await (0, DbUtils_1.DbUtilsQuerySQL)(span, SQL_QUERIES.INSERT_AUTH_TOKEN, [
75
+ configIn.JWT_KEY,
76
+ new Date().toISOString(),
77
+ ]);
78
+ }
79
+ else {
80
+ configIn.JWT_KEY = authKeyRaw[0].value;
81
+ }
82
+ span.end();
83
+ }
84
+ async function AuthGenerateJWT(user) {
85
+ return jwt.sign({
86
+ exp: Math.floor(Date.now() / 1000) + config.JWT_VALIDITY_DURATION,
87
+ userId: user.id,
88
+ userName: user.name,
89
+ role: user.role,
90
+ scopes: user.role === "admin" ? User_1.User.ALL_SCOPES : user.scopes,
91
+ }, config.JWT_KEY);
92
+ }
93
+ /**
94
+ * Decode JWT from request, caching result on req._jwtPayload to avoid
95
+ * redundant verification when multiple auth functions are called per request.
96
+ */
97
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
98
+ function jwtDecodeCached(req) {
99
+ if (req._jwtPayload) {
100
+ return req._jwtPayload;
101
+ }
102
+ if (!req.headers.authorization) {
103
+ return null;
104
+ }
105
+ try {
106
+ const info = jwt.verify(req.headers.authorization.split(" ")[1], config.JWT_KEY);
107
+ req._jwtPayload = info;
108
+ return info;
109
+ }
110
+ catch {
111
+ return null;
112
+ }
113
+ }
114
+ async function AuthMustBeAuthenticated(
115
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
116
+ req,
117
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
118
+ res) {
119
+ if (!jwtDecodeCached(req)) {
120
+ res.status(403).send({ error: "Access Denied" });
121
+ throw new Error("Access Denied");
122
+ }
123
+ }
124
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
125
+ async function AuthMustBeAdmin(req, res) {
126
+ const info = jwtDecodeCached(req);
127
+ if ((info === null || info === void 0 ? void 0 : info.role) === "admin") {
128
+ return;
129
+ }
130
+ res.status(403).send({ error: "Access Denied" });
131
+ throw new Error("Access Denied");
132
+ }
133
+ async function AuthHasScope(
134
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
135
+ req,
136
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
137
+ res, scope) {
138
+ const info = jwtDecodeCached(req);
139
+ if (!info) {
140
+ res.status(403).send({ error: "Access Denied" });
141
+ throw new Error("Access Denied");
142
+ }
143
+ if (info.role === "admin") {
144
+ return;
145
+ }
146
+ const scopes = info.scopes || [];
147
+ if (scopes.includes(scope)) {
148
+ return;
149
+ }
150
+ res.status(403).send({ error: "Access Denied" });
151
+ throw new Error("Access Denied");
152
+ }
153
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
154
+ async function AuthGetUserSession(req) {
155
+ const userSession = { isAuthenticated: false };
156
+ const info = jwtDecodeCached(req);
157
+ if (info) {
158
+ userSession.userId = info.userId;
159
+ userSession.userName = info.userName;
160
+ userSession.role = info.role;
161
+ userSession.scopes = info.scopes;
162
+ userSession.isAuthenticated = true;
163
+ }
164
+ return userSession;
165
+ }
166
+ // SQL
167
+ // Written SQLite-first with quoted identifiers (valid for both backends);
168
+ // the DbUtils facade converts `?` placeholders for Postgres.
169
+ const SQL_QUERIES = {
170
+ GET_AUTH_TOKEN: "SELECT value FROM metadata WHERE \"type\" = 'auth_token' LIMIT 1",
171
+ INSERT_AUTH_TOKEN: 'INSERT INTO metadata ("type", "value", "dateCreated") VALUES (\'auth_token\', ?, ?)',
172
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * User role. `admin` bypasses scope checks; `user` is restricted
3
+ * to its granted scopes.
4
+ */
5
+ export type UserRole = "admin" | "user";
6
+ /**
7
+ * Scope identifier restricting what a non-admin user can access.
8
+ * Each application defines its own scope set (e.g. `"traces"`, `"metrics"`)
9
+ * and registers it through `AuthInit`.
10
+ */
11
+ export type UserScope = string;
12
+ export declare class User {
13
+ static DEFAULT_SCOPES: UserScope[];
14
+ /** Full scope set of the host application, registered via `AuthInit`. */
15
+ static ALL_SCOPES: UserScope[];
16
+ static fromJson(json: any): User | null;
17
+ id: string;
18
+ name: string;
19
+ passwordEncrypted: string;
20
+ role: UserRole;
21
+ scopes: UserScope[];
22
+ constructor();
23
+ toJson(): any;
24
+ toTransportJson(): any;
25
+ }
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.User = void 0;
4
+ const uuid_1 = require("uuid");
5
+ class User {
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ static fromJson(json) {
8
+ if (!json) {
9
+ return null;
10
+ }
11
+ const user = new User();
12
+ if (json.id) {
13
+ user.id = json.id;
14
+ }
15
+ user.id = json.id;
16
+ user.name = json.name;
17
+ user.passwordEncrypted = json.passwordEncrypted;
18
+ user.role = json.role || "user";
19
+ if (json.scopes) {
20
+ try {
21
+ user.scopes =
22
+ typeof json.scopes === "string"
23
+ ? JSON.parse(json.scopes)
24
+ : json.scopes;
25
+ }
26
+ catch {
27
+ user.scopes = [...User.DEFAULT_SCOPES];
28
+ }
29
+ }
30
+ return user;
31
+ }
32
+ constructor() {
33
+ this.role = "user";
34
+ this.scopes = [...User.DEFAULT_SCOPES];
35
+ this.id = (0, uuid_1.v4)();
36
+ }
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ toJson() {
39
+ return {
40
+ id: this.id,
41
+ name: this.name,
42
+ passwordEncrypted: this.passwordEncrypted,
43
+ role: this.role,
44
+ scopes: this.scopes,
45
+ };
46
+ }
47
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
+ toTransportJson() {
49
+ return {
50
+ id: this.id,
51
+ name: this.name,
52
+ role: this.role,
53
+ scopes: this.scopes,
54
+ };
55
+ }
56
+ }
57
+ exports.User = User;
58
+ //
59
+ User.DEFAULT_SCOPES = [];
60
+ /** Full scope set of the host application, registered via `AuthInit`. */
61
+ User.ALL_SCOPES = [];
@@ -0,0 +1,4 @@
1
+ import { Span } from "@opentelemetry/sdk-trace-base";
2
+ import { User } from "./User";
3
+ export declare function UserPasswordSetPassword(context: Span | undefined, user: User, password: string): Promise<void>;
4
+ export declare function UserPasswordCheckPassword(context: Span | undefined, user: User, password: string): Promise<boolean>;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.UserPasswordSetPassword = UserPasswordSetPassword;
37
+ exports.UserPasswordCheckPassword = UserPasswordCheckPassword;
38
+ const bcrypt = __importStar(require("bcrypt"));
39
+ async function UserPasswordSetPassword(context, user, password) {
40
+ const salt = await bcrypt.genSalt(10);
41
+ user.passwordEncrypted = await bcrypt.hash(password, salt);
42
+ }
43
+ async function UserPasswordCheckPassword(context, user, password) {
44
+ return await bcrypt.compare(password, user.passwordEncrypted);
45
+ }
@@ -0,0 +1,8 @@
1
+ import { UserRole, UserScope } from "./User";
2
+ export interface UserSession {
3
+ isAuthenticated: boolean;
4
+ userId?: string;
5
+ userName?: string;
6
+ role?: UserRole;
7
+ scopes?: UserScope[];
8
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,15 @@
1
+ import { StandardTracer } from "@devopsplaybook.io/otel-utils";
2
+ import { Span } from "@opentelemetry/sdk-trace-base";
3
+ import { User } from "./User";
4
+ /**
5
+ * Injects the OTel tracer instance used by the users data module.
6
+ * Must be called once at startup, before any `UsersData*` function.
7
+ */
8
+ export declare function UsersDataSetOTel(tracerIn: StandardTracer): void;
9
+ export declare function UsersDataGet(context: Span | undefined, id: string): Promise<User | null>;
10
+ export declare function UsersDataGetByName(context: Span | undefined, name: string): Promise<User | null>;
11
+ export declare function UsersDataList(context: Span | undefined): Promise<User[]>;
12
+ export declare function UsersDataAdd(context: Span | undefined, user: User): Promise<void>;
13
+ export declare function UsersDataUpdatePassword(context: Span | undefined, user: User): Promise<void>;
14
+ export declare function UsersDataUpdateUser(context: Span | undefined, user: User): Promise<void>;
15
+ export declare function UsersDataDelete(context: Span | undefined, id: string): Promise<void>;
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UsersDataSetOTel = UsersDataSetOTel;
4
+ exports.UsersDataGet = UsersDataGet;
5
+ exports.UsersDataGetByName = UsersDataGetByName;
6
+ exports.UsersDataList = UsersDataList;
7
+ exports.UsersDataAdd = UsersDataAdd;
8
+ exports.UsersDataUpdatePassword = UsersDataUpdatePassword;
9
+ exports.UsersDataUpdateUser = UsersDataUpdateUser;
10
+ exports.UsersDataDelete = UsersDataDelete;
11
+ const DbUtils_1 = require("./DbUtils");
12
+ const User_1 = require("./User");
13
+ let tracer;
14
+ /**
15
+ * Injects the OTel tracer instance used by the users data module.
16
+ * Must be called once at startup, before any `UsersData*` function.
17
+ */
18
+ function UsersDataSetOTel(tracerIn) {
19
+ tracer = tracerIn;
20
+ }
21
+ async function UsersDataGet(context, id) {
22
+ const span = tracer.startSpan("UsersDataGet", context);
23
+ const usersRaw = await (0, DbUtils_1.DbUtilsQuerySQL)(span, SQL_QUERIES.GET_USER_BY_ID, [
24
+ id,
25
+ ]);
26
+ let user = null;
27
+ if (usersRaw.length > 0) {
28
+ user = fromRaw(usersRaw[0]);
29
+ }
30
+ span.end();
31
+ return user;
32
+ }
33
+ async function UsersDataGetByName(context, name) {
34
+ const span = tracer.startSpan("UsersDataGetByName", context);
35
+ const usersRaw = await (0, DbUtils_1.DbUtilsQuerySQL)(span, SQL_QUERIES.GET_USER_BY_NAME, [
36
+ name,
37
+ ]);
38
+ let user = null;
39
+ if (usersRaw.length > 0) {
40
+ user = fromRaw(usersRaw[0]);
41
+ }
42
+ span.end();
43
+ return user;
44
+ }
45
+ async function UsersDataList(context) {
46
+ const span = tracer.startSpan("UsersDataList", context);
47
+ const usersRaw = await (0, DbUtils_1.DbUtilsQuerySQL)(span, SQL_QUERIES.LIST_USERS);
48
+ const users = [];
49
+ for (const userRaw of usersRaw) {
50
+ users.push(fromRaw(userRaw));
51
+ }
52
+ span.end();
53
+ return users;
54
+ }
55
+ async function UsersDataAdd(context, user) {
56
+ const span = tracer.startSpan("UsersDataAdd", context);
57
+ await (0, DbUtils_1.DbUtilsExecSQL)(span, SQL_QUERIES.INSERT_USER, [
58
+ user.id,
59
+ user.name,
60
+ user.passwordEncrypted,
61
+ user.role,
62
+ JSON.stringify(user.scopes),
63
+ ]);
64
+ span.end();
65
+ }
66
+ async function UsersDataUpdatePassword(context, user) {
67
+ const span = tracer.startSpan("UsersDataUpdatePassword", context);
68
+ await (0, DbUtils_1.DbUtilsExecSQL)(span, SQL_QUERIES.UPDATE_PASSWORD, [
69
+ user.passwordEncrypted,
70
+ user.id,
71
+ ]);
72
+ span.end();
73
+ }
74
+ async function UsersDataUpdateUser(context, user) {
75
+ const span = tracer.startSpan("UsersDataUpdateUser", context);
76
+ await (0, DbUtils_1.DbUtilsExecSQL)(span, SQL_QUERIES.UPDATE_USER, [
77
+ user.role,
78
+ JSON.stringify(user.scopes),
79
+ user.id,
80
+ ]);
81
+ span.end();
82
+ }
83
+ async function UsersDataDelete(context, id) {
84
+ const span = tracer.startSpan("UsersDataDelete", context);
85
+ await (0, DbUtils_1.DbUtilsExecSQL)(span, SQL_QUERIES.DELETE_USER, [id]);
86
+ span.end();
87
+ }
88
+ // Private Functions
89
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
90
+ function fromRaw(userRaw) {
91
+ const user = new User_1.User();
92
+ user.id = userRaw.id;
93
+ user.name = userRaw.name;
94
+ user.passwordEncrypted = userRaw.passwordEncrypted;
95
+ user.role = userRaw.role || "user";
96
+ if (userRaw.scopes) {
97
+ try {
98
+ user.scopes = JSON.parse(userRaw.scopes);
99
+ }
100
+ catch {
101
+ user.scopes = [...User_1.User.DEFAULT_SCOPES];
102
+ }
103
+ }
104
+ return user;
105
+ }
106
+ // SQL
107
+ // Written SQLite-first with quoted identifiers (valid for both backends);
108
+ // the DbUtils facade converts `?` placeholders for Postgres.
109
+ const SQL_QUERIES = {
110
+ GET_USER_BY_ID: 'SELECT * FROM users WHERE "id" = ?',
111
+ GET_USER_BY_NAME: 'SELECT * FROM users WHERE "name" = ?',
112
+ LIST_USERS: "SELECT * FROM users",
113
+ INSERT_USER: 'INSERT INTO users ("id", "name", "passwordEncrypted", "role", "scopes") VALUES (?, ?, ?, ?, ?)',
114
+ UPDATE_USER: 'UPDATE users SET "role" = ?, "scopes" = ? WHERE "id" = ?',
115
+ UPDATE_PASSWORD: 'UPDATE users SET "passwordEncrypted" = ? WHERE "id" = ?',
116
+ DELETE_USER: 'DELETE FROM users WHERE "id" = ?',
117
+ };
@@ -0,0 +1,13 @@
1
+ import { FastifyInstance } from "fastify";
2
+ /**
3
+ * Standard user management routes shared across applications:
4
+ * initialization status, login (session), user CRUD and password changes.
5
+ *
6
+ * Register on a fastify instance:
7
+ * ```ts
8
+ * fastify.register(new UsersRoutes().getRoutes, { prefix: "/api/users" });
9
+ * ```
10
+ */
11
+ export declare class UsersRoutes {
12
+ getRoutes(fastify: FastifyInstance): Promise<void>;
13
+ }