@loomcore/api 0.2.7 → 0.2.8
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/dist/__tests__/common-test.utils.d.ts +5 -5
- package/dist/__tests__/common-test.utils.js +24 -21
- package/dist/controllers/auth.controller.d.ts +9 -7
- package/dist/controllers/auth.controller.js +69 -27
- package/dist/controllers/authorizations.controller.d.ts +3 -3
- package/dist/controllers/authorizations.controller.js +4 -4
- package/dist/controllers/roles.controller.d.ts +1 -1
- package/dist/controllers/roles.controller.js +3 -3
- package/dist/controllers/users.controller.d.ts +5 -6
- package/dist/controllers/users.controller.js +4 -15
- package/dist/databases/postgres/commands/postgres-create.command.d.ts +2 -2
- package/dist/databases/postgres/commands/postgres-create.command.js +5 -5
- package/dist/databases/postgres/postgres.database.d.ts +2 -2
- package/dist/databases/postgres/postgres.database.js +6 -8
- package/dist/middleware/is-authorized.d.ts +2 -2
- package/dist/middleware/is-authorized.js +13 -11
- package/dist/models/refresh-token.model.d.ts +1 -1
- package/dist/models/refresh-token.model.js +3 -4
- package/dist/services/generic-api-service/generic-api-service.interface.d.ts +6 -6
- package/dist/services/generic-api-service/generic-api.service.d.ts +8 -8
- package/dist/services/generic-api-service/generic-api.service.js +16 -16
- package/dist/services/index.d.ts +11 -12
- package/dist/services/index.js +11 -12
- package/dist/services/multi-tenant-api.service.js +6 -2
- package/dist/services/password-reset-token.service.d.ts +2 -3
- package/dist/services/password-reset-token.service.js +4 -9
- package/dist/services/refresh-token.service.d.ts +6 -0
- package/dist/services/refresh-token.service.js +7 -0
- package/dist/services/user.service.js +4 -4
- package/dist/utils/auth/attempt-login.util.d.ts +4 -0
- package/dist/utils/auth/attempt-login.util.js +47 -0
- package/dist/utils/auth/change-password.util.d.ts +4 -0
- package/dist/utils/auth/change-password.util.js +14 -0
- package/dist/utils/auth/create-new-refresh-token.util.d.ts +4 -0
- package/dist/utils/auth/create-new-refresh-token.util.js +29 -0
- package/dist/utils/auth/create-new-tokens.util.d.ts +3 -0
- package/dist/utils/auth/create-new-tokens.util.js +13 -0
- package/dist/utils/auth/generate-device-id.util.d.ts +1 -0
- package/dist/utils/auth/generate-device-id.util.js +4 -0
- package/dist/utils/auth/generate-refresh-token.util.d.ts +1 -0
- package/dist/utils/auth/generate-refresh-token.util.js +4 -0
- package/dist/utils/auth/get-active-refresh-token.util.d.ts +4 -0
- package/dist/utils/auth/get-active-refresh-token.util.js +19 -0
- package/dist/utils/auth/get-and-set-device-id-cookie.util.d.ts +2 -0
- package/dist/utils/auth/get-and-set-device-id-cookie.util.js +23 -0
- package/dist/utils/auth/get-auth-config.util.d.ts +2 -0
- package/dist/utils/auth/get-auth-config.util.js +8 -0
- package/dist/utils/auth/get-device-id-from-cookie.util.d.ts +2 -0
- package/dist/utils/auth/get-device-id-from-cookie.util.js +3 -0
- package/dist/utils/auth/get-expires-on-from-days.util.d.ts +1 -0
- package/dist/utils/auth/get-expires-on-from-days.util.js +3 -0
- package/dist/utils/auth/get-expires-on-from-minutes.util.d.ts +1 -0
- package/dist/utils/auth/get-expires-on-from-minutes.util.js +3 -0
- package/dist/utils/auth/get-expires-on-from-seconds.util.d.ts +1 -0
- package/dist/utils/auth/get-expires-on-from-seconds.util.js +3 -0
- package/dist/utils/auth/index.d.ts +18 -0
- package/dist/utils/auth/index.js +18 -0
- package/dist/utils/auth/log-user-in.util.d.ts +3 -0
- package/dist/utils/auth/log-user-in.util.js +18 -0
- package/dist/utils/auth/request-token-using-refresh-token.util.d.ts +3 -0
- package/dist/utils/auth/request-token-using-refresh-token.util.js +9 -0
- package/dist/utils/auth/reset-password.util.d.ts +4 -0
- package/dist/utils/auth/reset-password.util.js +50 -0
- package/dist/utils/auth/send-reset-password-email.util.d.ts +3 -0
- package/dist/utils/auth/send-reset-password-email.util.js +30 -0
- package/dist/utils/auth/update-last-logged-in.util.d.ts +3 -0
- package/dist/utils/auth/update-last-logged-in.util.js +15 -0
- package/dist/utils/index.d.ts +8 -6
- package/dist/utils/index.js +8 -6
- package/dist/utils/jwt.utils.d.ts +2 -0
- package/dist/utils/jwt.utils.js +12 -0
- package/dist/utils/password.utils.js +8 -8
- package/package.json +2 -2
- package/dist/services/auth.service.d.ts +0 -49
- package/dist/services/auth.service.js +0 -277
- package/dist/services/jwt.service.d.ts +0 -4
- package/dist/services/jwt.service.js +0 -18
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { IQueryOptions, IUser, IUserContext } from "@loomcore/common/models";
|
|
2
|
-
import { Application, NextFunction, Request, Response } from "express";
|
|
1
|
+
import { type IQueryOptions, type IUser, type IUserContext } from "@loomcore/common/models";
|
|
2
|
+
import type { Application, NextFunction, Request, Response } from "express";
|
|
3
3
|
import { ApiController } from "../controllers/api.controller.js";
|
|
4
|
-
import { IDatabase } from "../databases/models/index.js";
|
|
5
|
-
import { Operation } from "../databases/operations/operation.js";
|
|
4
|
+
import type { IDatabase } from "../databases/models/index.js";
|
|
5
|
+
import type { Operation } from "../databases/operations/operation.js";
|
|
6
6
|
import { GenericApiService } from "../services/index.js";
|
|
7
7
|
import { MultiTenantApiService } from "../services/multi-tenant-api.service.js";
|
|
8
8
|
import type { DbType } from "../databases/db-type.type.js";
|
|
9
9
|
import { type ICategory } from "./models/category.model.js";
|
|
10
10
|
import { type IProduct } from "./models/product.model.js";
|
|
11
11
|
import { type IProductWithCategory } from "./models/product-with-category.model.js";
|
|
12
|
-
declare function initialize(
|
|
12
|
+
declare function initialize(db: IDatabase): void;
|
|
13
13
|
declare function getRandomId(): string;
|
|
14
14
|
declare function isMongoDatabase(database: IDatabase): boolean;
|
|
15
15
|
declare function isPostgresDatabase(database: IDatabase): boolean;
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
1
2
|
import { EmptyUserContext, } from "@loomcore/common/models";
|
|
2
3
|
import { Type } from "@sinclair/typebox";
|
|
3
4
|
import { Value } from "@sinclair/typebox/value";
|
|
4
|
-
import crypto from "crypto";
|
|
5
5
|
import jwt from "jsonwebtoken";
|
|
6
6
|
import { ObjectId } from "mongodb";
|
|
7
7
|
import { ApiController } from "../controllers/api.controller.js";
|
|
8
8
|
import { MongoDBDatabase } from "../databases/mongo-db/mongo-db.database.js";
|
|
9
9
|
import { LeftJoin } from "../databases/operations/left-join.operation.js";
|
|
10
10
|
import { PostgresDatabase } from "../databases/postgres/postgres.database.js";
|
|
11
|
-
import {
|
|
12
|
-
import { JwtService } from "../services/jwt.service.js";
|
|
11
|
+
import { GenericApiService, UserService } from "../services/index.js";
|
|
13
12
|
import { MultiTenantApiService } from "../services/multi-tenant-api.service.js";
|
|
14
13
|
import { OrganizationService } from "../services/organization.service.js";
|
|
15
14
|
import * as testObjectsModule from "./test-objects.js";
|
|
16
15
|
const { getTestMetaOrg, getTestOrg, getTestMetaOrgUser, getTestMetaOrgUserContext, getTestOrgUserContext, setTestOrgId, setTestMetaOrgId, setTestMetaOrgUserId, setTestOrgUserId, } = testObjectsModule;
|
|
17
16
|
import { entityUtils } from "@loomcore/common/utils";
|
|
18
17
|
import { config, setBaseApiConfig } from "../config/index.js";
|
|
18
|
+
import { attemptLogin } from "../utils/auth/index.js";
|
|
19
19
|
import { apiUtils } from "../utils/index.js";
|
|
20
20
|
import { CategorySpec } from "./models/category.model.js";
|
|
21
21
|
import { ProductSpec } from "./models/product.model.js";
|
|
@@ -23,15 +23,17 @@ import { ProductWithCategoryPublicSpec, ProductWithCategorySpec, } from "./model
|
|
|
23
23
|
import { TestEmailClient } from "./test-email-client.js";
|
|
24
24
|
import { getTestOrgUser } from "./test-objects.js";
|
|
25
25
|
let deviceIdCookie;
|
|
26
|
-
let
|
|
26
|
+
let database;
|
|
27
27
|
let organizationService;
|
|
28
|
+
let userService;
|
|
28
29
|
const JWT_SECRET = "test-secret";
|
|
29
30
|
const newUser1Email = "one@test.com";
|
|
30
31
|
const newUser1Password = "testone";
|
|
31
32
|
const constDeviceIdCookie = crypto.randomBytes(16).toString("hex");
|
|
32
|
-
function initialize(
|
|
33
|
-
|
|
34
|
-
organizationService = new OrganizationService(
|
|
33
|
+
function initialize(db) {
|
|
34
|
+
database = db;
|
|
35
|
+
organizationService = new OrganizationService(db);
|
|
36
|
+
userService = new UserService(db);
|
|
35
37
|
deviceIdCookie = constDeviceIdCookie;
|
|
36
38
|
}
|
|
37
39
|
function getRandomId() {
|
|
@@ -95,7 +97,7 @@ async function setupTestUsers() {
|
|
|
95
97
|
}
|
|
96
98
|
}
|
|
97
99
|
async function createTestUsers() {
|
|
98
|
-
if (!
|
|
100
|
+
if (!organizationService || !userService) {
|
|
99
101
|
throw new Error("Database not initialized. Call initialize() first.");
|
|
100
102
|
}
|
|
101
103
|
try {
|
|
@@ -115,8 +117,8 @@ async function createTestUsers() {
|
|
|
115
117
|
else {
|
|
116
118
|
setTestOrgId(existingTestOrg._id);
|
|
117
119
|
}
|
|
118
|
-
const createdTestOrgUser = await
|
|
119
|
-
const createdMetaOrgUser = await
|
|
120
|
+
const createdTestOrgUser = await userService.create(getTestOrgUserContext(), getTestOrgUser());
|
|
121
|
+
const createdMetaOrgUser = await userService.create(getTestMetaOrgUserContext(), getTestMetaOrgUser());
|
|
120
122
|
if (!createdTestOrgUser || !createdMetaOrgUser) {
|
|
121
123
|
throw new Error("Failed to create test user");
|
|
122
124
|
}
|
|
@@ -130,18 +132,18 @@ async function createTestUsers() {
|
|
|
130
132
|
}
|
|
131
133
|
}
|
|
132
134
|
async function deleteTestUser() {
|
|
133
|
-
if (!
|
|
135
|
+
if (!organizationService || !userService) {
|
|
134
136
|
return;
|
|
135
137
|
}
|
|
136
|
-
await
|
|
138
|
+
await userService
|
|
137
139
|
.deleteById(getTestMetaOrgUserContext(), getTestMetaOrgUser()._id)
|
|
138
140
|
.catch((error) => {
|
|
139
|
-
|
|
141
|
+
console.log("Error deleting test user:", error);
|
|
140
142
|
});
|
|
141
143
|
await organizationService
|
|
142
144
|
.deleteById(getTestMetaOrgUserContext(), getTestOrg()._id)
|
|
143
145
|
.catch((error) => {
|
|
144
|
-
|
|
146
|
+
console.log("Error deleting test organization:", error);
|
|
145
147
|
});
|
|
146
148
|
}
|
|
147
149
|
async function simulateloginWithTestUser() {
|
|
@@ -149,20 +151,20 @@ async function simulateloginWithTestUser() {
|
|
|
149
151
|
cookies: {},
|
|
150
152
|
};
|
|
151
153
|
if (deviceIdCookie) {
|
|
152
|
-
req.cookies
|
|
154
|
+
req.cookies.deviceId = deviceIdCookie;
|
|
153
155
|
}
|
|
154
156
|
const res = {
|
|
155
|
-
cookie:
|
|
157
|
+
cookie: (name, value) => {
|
|
156
158
|
if (name === "deviceId") {
|
|
157
159
|
deviceIdCookie = value;
|
|
158
160
|
}
|
|
159
161
|
return res;
|
|
160
162
|
},
|
|
161
163
|
};
|
|
162
|
-
if (!
|
|
163
|
-
throw new Error("
|
|
164
|
+
if (!database) {
|
|
165
|
+
throw new Error("Database not initialized. Call initialize() first.");
|
|
164
166
|
}
|
|
165
|
-
const loginResponse = await
|
|
167
|
+
const loginResponse = await attemptLogin(database, getTestMetaOrgUser().email, testObjectsModule.TEST_META_ORG_USER_PASSWORD, deviceIdCookie, getTestMetaOrg()._id);
|
|
166
168
|
if (!loginResponse?.tokens?.accessToken) {
|
|
167
169
|
throw new Error("Failed to login with test user");
|
|
168
170
|
}
|
|
@@ -170,11 +172,11 @@ async function simulateloginWithTestUser() {
|
|
|
170
172
|
}
|
|
171
173
|
function getAuthToken() {
|
|
172
174
|
const userContext = getTestMetaOrgUserContext();
|
|
173
|
-
const token =
|
|
175
|
+
const token = jwt.sign(userContext, JWT_SECRET, { expiresIn: 3600 });
|
|
174
176
|
return `Bearer ${token}`;
|
|
175
177
|
}
|
|
176
178
|
function verifyToken(token) {
|
|
177
|
-
return
|
|
179
|
+
return jwt.verify(token, JWT_SECRET);
|
|
178
180
|
}
|
|
179
181
|
export class CategoryService extends GenericApiService {
|
|
180
182
|
constructor(database) {
|
|
@@ -339,6 +341,7 @@ async function loginWithTestUser(agent) {
|
|
|
339
341
|
const response = await agent.post("/api/auth/login").send({
|
|
340
342
|
email: testUser.email,
|
|
341
343
|
password: testUser.password,
|
|
344
|
+
organizationId: testUser._orgId,
|
|
342
345
|
});
|
|
343
346
|
if (!response.body?.data?.tokens?.accessToken) {
|
|
344
347
|
console.error("Login failed:", response.body);
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import type { Application,
|
|
1
|
+
import type { Application, Request, Response } from "express";
|
|
2
2
|
import type { IDatabase } from "../databases/models/index.js";
|
|
3
|
-
import {
|
|
3
|
+
import { OrganizationService, UserService } from "../services/index.js";
|
|
4
4
|
export declare class AuthController {
|
|
5
|
-
|
|
5
|
+
database: IDatabase;
|
|
6
|
+
userService: UserService;
|
|
7
|
+
organizationService: OrganizationService;
|
|
6
8
|
constructor(app: Application, database: IDatabase);
|
|
7
9
|
mapRoutes(app: Application): void;
|
|
8
|
-
login(req: Request, res: Response
|
|
10
|
+
login(req: Request, res: Response): Promise<void>;
|
|
9
11
|
registerUser(req: Request, res: Response): Promise<void>;
|
|
10
|
-
requestTokenUsingRefreshToken(req: Request, res: Response
|
|
11
|
-
getUserContext(req: Request, res: Response
|
|
12
|
-
afterAuth(
|
|
12
|
+
requestTokenUsingRefreshToken(req: Request, res: Response): Promise<void>;
|
|
13
|
+
getUserContext(req: Request, res: Response): Promise<void>;
|
|
14
|
+
afterAuth(_req: Request, _res: Response, _loginResponse: any): void;
|
|
13
15
|
changePassword(req: Request, res: Response): Promise<void>;
|
|
14
16
|
forgotPassword(req: Request, res: Response): Promise<void>;
|
|
15
17
|
resetPassword(req: Request, res: Response): Promise<void>;
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
import { LoginResponseSpec, PublicUserContextSpec, PublicUserSpec, passwordValidator, TokenResponseSpec, UserSpec, } from "@loomcore/common/models";
|
|
1
|
+
import { EmptyUserContext, LoginResponseSpec, PublicUserContextSpec, PublicUserSpec, passwordValidator, TokenResponseSpec, UserSpec, } from "@loomcore/common/models";
|
|
2
2
|
import { entityUtils } from "@loomcore/common/utils";
|
|
3
|
-
import {
|
|
3
|
+
import { config } from "../config/base-api-config.js";
|
|
4
|
+
import { BadRequestError, ServerError, UnauthenticatedError, } from "../errors/index.js";
|
|
4
5
|
import { isAuthorized } from "../middleware/index.js";
|
|
5
|
-
import {
|
|
6
|
+
import { OrganizationService, UserService } from "../services/index.js";
|
|
7
|
+
import { attemptLogin, changePassword, getAndSetDeviceIdCookie, getDeviceIdFromCookie, requestTokenUsingRefreshToken, resetPassword, sendResetPasswordEmail, } from "../utils/auth/index.js";
|
|
6
8
|
import { apiUtils } from "../utils/index.js";
|
|
7
9
|
export class AuthController {
|
|
8
|
-
|
|
10
|
+
database;
|
|
11
|
+
userService;
|
|
12
|
+
organizationService;
|
|
9
13
|
constructor(app, database) {
|
|
10
|
-
this.
|
|
14
|
+
this.database = database;
|
|
15
|
+
this.userService = new UserService(database);
|
|
16
|
+
this.organizationService = new OrganizationService(database);
|
|
11
17
|
this.mapRoutes(app);
|
|
12
18
|
}
|
|
13
19
|
mapRoutes(app) {
|
|
@@ -19,10 +25,20 @@ export class AuthController {
|
|
|
19
25
|
app.post(`/api/auth/forgot-password`, this.forgotPassword.bind(this));
|
|
20
26
|
app.post(`/api/auth/reset-password`, this.resetPassword.bind(this));
|
|
21
27
|
}
|
|
22
|
-
async login(req, res
|
|
23
|
-
const { email, password } = req.body;
|
|
28
|
+
async login(req, res) {
|
|
29
|
+
const { email, password, organizationId } = req.body;
|
|
30
|
+
if (!email || typeof email !== "string") {
|
|
31
|
+
throw new BadRequestError("Missing required fields: email is required.");
|
|
32
|
+
}
|
|
33
|
+
if (!password || typeof password !== "string") {
|
|
34
|
+
throw new BadRequestError("Missing required fields: password is required.");
|
|
35
|
+
}
|
|
36
|
+
if (config.app.isMultiTenant && !organizationId) {
|
|
37
|
+
throw new BadRequestError("Missing required fields: organizationId is required.");
|
|
38
|
+
}
|
|
24
39
|
res.set("Content-Type", "application/json");
|
|
25
|
-
const
|
|
40
|
+
const deviceId = getAndSetDeviceIdCookie(req, res);
|
|
41
|
+
const loginResponse = await attemptLogin(this.database, email, password, deviceId, organizationId);
|
|
26
42
|
apiUtils.apiResponse(res, 200, { data: loginResponse }, LoginResponseSpec);
|
|
27
43
|
}
|
|
28
44
|
async registerUser(req, res) {
|
|
@@ -31,30 +47,35 @@ export class AuthController {
|
|
|
31
47
|
throw new BadRequestError("Missing required fields: userContext is required.");
|
|
32
48
|
}
|
|
33
49
|
const body = req.body;
|
|
34
|
-
const validationErrors = this.
|
|
50
|
+
const validationErrors = this.userService.validate(body.user);
|
|
35
51
|
entityUtils.handleValidationResult(validationErrors, "AuthController.registerUser");
|
|
36
|
-
const user = await this.
|
|
37
|
-
|
|
52
|
+
const user = await this.userService.create(userContext, body.user);
|
|
53
|
+
if (!user) {
|
|
54
|
+
throw new ServerError("Failed to create user");
|
|
55
|
+
}
|
|
56
|
+
apiUtils.apiResponse(res, 201, { data: user }, UserSpec, PublicUserSpec);
|
|
38
57
|
}
|
|
39
|
-
async requestTokenUsingRefreshToken(req, res
|
|
58
|
+
async requestTokenUsingRefreshToken(req, res) {
|
|
59
|
+
const userContext = req.userContext;
|
|
60
|
+
if (!userContext) {
|
|
61
|
+
throw new BadRequestError("Missing required fields: userContext is required.");
|
|
62
|
+
}
|
|
40
63
|
const refreshToken = req.query.refreshToken;
|
|
41
64
|
if (!refreshToken || typeof refreshToken !== "string") {
|
|
42
65
|
throw new BadRequestError("Missing required fields: refreshToken is required.");
|
|
43
66
|
}
|
|
44
|
-
const deviceId =
|
|
45
|
-
const tokens = await this.
|
|
46
|
-
if (tokens) {
|
|
47
|
-
apiUtils.apiResponse(res, 200, { data: tokens }, TokenResponseSpec);
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
67
|
+
const deviceId = getDeviceIdFromCookie(req);
|
|
68
|
+
const tokens = await requestTokenUsingRefreshToken(this.database, userContext, refreshToken, deviceId);
|
|
69
|
+
if (!tokens) {
|
|
50
70
|
throw new UnauthenticatedError();
|
|
51
71
|
}
|
|
72
|
+
apiUtils.apiResponse(res, 200, { data: tokens }, TokenResponseSpec);
|
|
52
73
|
}
|
|
53
|
-
async getUserContext(req, res
|
|
74
|
+
async getUserContext(req, res) {
|
|
54
75
|
const userContext = req.userContext;
|
|
55
76
|
apiUtils.apiResponse(res, 200, { data: userContext }, PublicUserContextSpec);
|
|
56
77
|
}
|
|
57
|
-
afterAuth(
|
|
78
|
+
afterAuth(_req, _res, _loginResponse) {
|
|
58
79
|
console.log("in afterAuth");
|
|
59
80
|
}
|
|
60
81
|
async changePassword(req, res) {
|
|
@@ -62,31 +83,52 @@ export class AuthController {
|
|
|
62
83
|
if (!userContext) {
|
|
63
84
|
throw new BadRequestError("Missing required fields: userContext is required.");
|
|
64
85
|
}
|
|
65
|
-
const
|
|
66
|
-
const validationErrors = entityUtils.validate(UserSpec, { password:
|
|
86
|
+
const password = req.body?.password;
|
|
87
|
+
const validationErrors = entityUtils.validate(UserSpec, { password: password }, true, passwordValidator);
|
|
67
88
|
entityUtils.handleValidationResult(validationErrors, "AuthController.changePassword");
|
|
68
|
-
const updateResult = await this.
|
|
89
|
+
const updateResult = await changePassword(this.database, userContext, password);
|
|
69
90
|
apiUtils.apiResponse(res, 200, { data: updateResult });
|
|
70
91
|
}
|
|
71
92
|
async forgotPassword(req, res) {
|
|
72
93
|
const email = req.body?.email;
|
|
94
|
+
const organizationId = req.body?.organizationId;
|
|
95
|
+
if (!email || typeof email !== "string") {
|
|
96
|
+
throw new BadRequestError("Missing required fields: email is required.");
|
|
97
|
+
}
|
|
98
|
+
if (config.app.isMultiTenant && !organizationId) {
|
|
99
|
+
throw new BadRequestError("Missing required fields: organizationId is required.");
|
|
100
|
+
}
|
|
73
101
|
let referer = req.get("referer") || req.headers.referer;
|
|
74
102
|
if (!referer) {
|
|
75
103
|
throw new BadRequestError("Missing required fields: referer is required.");
|
|
76
104
|
}
|
|
77
105
|
referer = referer.replace(/\/$/, "");
|
|
78
|
-
const
|
|
106
|
+
const organization = organizationId
|
|
107
|
+
? await this.organizationService.findOne(EmptyUserContext, {
|
|
108
|
+
filters: { _id: { eq: organizationId } },
|
|
109
|
+
})
|
|
110
|
+
: null;
|
|
111
|
+
const userContext = {
|
|
112
|
+
...EmptyUserContext,
|
|
113
|
+
organization: organization || undefined,
|
|
114
|
+
};
|
|
115
|
+
const user = await this.userService.findOne(userContext, {
|
|
116
|
+
filters: { email: { eq: email.toLowerCase() } },
|
|
117
|
+
});
|
|
79
118
|
if (user) {
|
|
80
|
-
await this.
|
|
119
|
+
await sendResetPasswordEmail(this.database, email, referer);
|
|
81
120
|
}
|
|
82
121
|
apiUtils.apiResponse(res, 200);
|
|
83
122
|
}
|
|
84
123
|
async resetPassword(req, res) {
|
|
85
|
-
const { email, token, password } = req.body;
|
|
124
|
+
const { email, token, password, organizationId, } = req.body;
|
|
86
125
|
if (!email || !token || !password) {
|
|
87
126
|
throw new BadRequestError("Missing required fields: email, token, and password are required.");
|
|
88
127
|
}
|
|
89
|
-
|
|
128
|
+
if (config.app.isMultiTenant && !organizationId) {
|
|
129
|
+
throw new BadRequestError("Missing required fields: organizationId is required.");
|
|
130
|
+
}
|
|
131
|
+
const response = await resetPassword(this.database, email, token, password, organizationId);
|
|
90
132
|
apiUtils.apiResponse(res, 200, { data: response });
|
|
91
133
|
}
|
|
92
134
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Application } from "express";
|
|
2
|
+
import type { IDatabase } from "../databases/models/index.js";
|
|
3
|
+
import { type IAuthorization } from "../models/authorization.model.js";
|
|
2
4
|
import { ApiController } from "./api.controller.js";
|
|
3
|
-
import { Application } from "express";
|
|
4
|
-
import { IDatabase } from "../databases/models/index.js";
|
|
5
5
|
export declare class AuthorizationsController extends ApiController<IAuthorization> {
|
|
6
6
|
constructor(app: Application, database: IDatabase);
|
|
7
7
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { AuthorizationModelSpec } from "../models/authorization.model.js";
|
|
2
|
-
import { ApiController } from "./api.controller.js";
|
|
1
|
+
import { AuthorizationModelSpec, } from "../models/authorization.model.js";
|
|
3
2
|
import { MultiTenantApiService } from "../services/multi-tenant-api.service.js";
|
|
3
|
+
import { ApiController } from "./api.controller.js";
|
|
4
4
|
export class AuthorizationsController extends ApiController {
|
|
5
5
|
constructor(app, database) {
|
|
6
|
-
const authorizationService = new MultiTenantApiService(database,
|
|
7
|
-
super(
|
|
6
|
+
const authorizationService = new MultiTenantApiService(database, "authorizations", "authorization", AuthorizationModelSpec);
|
|
7
|
+
super("authorizations", app, authorizationService, "authorization", AuthorizationModelSpec);
|
|
8
8
|
}
|
|
9
9
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { Application } from "express";
|
|
1
2
|
import { IDatabase } from "../databases/models/index.js";
|
|
2
3
|
import { IRole } from "../models/role.model.js";
|
|
3
4
|
import { ApiController } from "./api.controller.js";
|
|
4
|
-
import { Application } from "express";
|
|
5
5
|
export declare class RolesController extends ApiController<IRole> {
|
|
6
6
|
constructor(app: Application, database: IDatabase);
|
|
7
7
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { RoleModelSpec } from "../models/role.model.js";
|
|
2
|
-
import { ApiController } from "./api.controller.js";
|
|
3
2
|
import { MultiTenantApiService } from "../services/multi-tenant-api.service.js";
|
|
3
|
+
import { ApiController } from "./api.controller.js";
|
|
4
4
|
export class RolesController extends ApiController {
|
|
5
5
|
constructor(app, database) {
|
|
6
|
-
const roleService = new MultiTenantApiService(database,
|
|
7
|
-
super(
|
|
6
|
+
const roleService = new MultiTenantApiService(database, "roles", "role", RoleModelSpec);
|
|
7
|
+
super("roles", app, roleService, "role", RoleModelSpec, RoleModelSpec);
|
|
8
8
|
}
|
|
9
9
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { UserService } from
|
|
5
|
-
import {
|
|
1
|
+
import { type IUser } from "@loomcore/common/models";
|
|
2
|
+
import type { Application } from "express";
|
|
3
|
+
import type { IDatabase } from "../databases/models/index.js";
|
|
4
|
+
import { UserService } from "../services/index.js";
|
|
5
|
+
import { ApiController } from "./api.controller.js";
|
|
6
6
|
export declare class UsersController extends ApiController<IUser> {
|
|
7
7
|
userService: UserService;
|
|
8
8
|
constructor(app: Application, database: IDatabase);
|
|
9
|
-
mapRoutes(app: Application): void;
|
|
10
9
|
}
|
|
@@ -1,22 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { UserService } from '../services/index.js';
|
|
1
|
+
import { PublicUserSpec, UserSpec } from "@loomcore/common/models";
|
|
2
|
+
import { UserService } from "../services/index.js";
|
|
3
|
+
import { ApiController } from "./api.controller.js";
|
|
5
4
|
export class UsersController extends ApiController {
|
|
6
5
|
userService;
|
|
7
6
|
constructor(app, database) {
|
|
8
7
|
const userService = new UserService(database);
|
|
9
|
-
super(
|
|
8
|
+
super("users", app, userService, "user", UserSpec, PublicUserSpec);
|
|
10
9
|
this.userService = userService;
|
|
11
10
|
}
|
|
12
|
-
mapRoutes(app) {
|
|
13
|
-
app.get(`/api/${this.slug}`, isAuthorized(), this.get.bind(this));
|
|
14
|
-
app.get(`/api/${this.slug}/all`, isAuthorized(), this.getAll.bind(this));
|
|
15
|
-
app.get(`/api/${this.slug}/find`, isAuthorized(), this.get.bind(this));
|
|
16
|
-
app.get(`/api/${this.slug}/count`, isAuthorized(), this.getCount.bind(this));
|
|
17
|
-
app.get(`/api/${this.slug}/:id`, isAuthorized(), this.getById.bind(this));
|
|
18
|
-
app.post(`/api/${this.slug}`, isAuthorized(), this.create.bind(this));
|
|
19
|
-
app.patch(`/api/${this.slug}/:id`, isAuthorized(), this.partialUpdateById.bind(this));
|
|
20
|
-
app.delete(`/api/${this.slug}/:id`, isAuthorized(), this.deleteById.bind(this));
|
|
21
|
-
}
|
|
22
11
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { IEntity } from "@loomcore/common/models";
|
|
1
|
+
import type { IEntity } from "@loomcore/common/models";
|
|
3
2
|
import type { AppIdType } from "@loomcore/common/types";
|
|
3
|
+
import type { PostgresConnection } from "../postgres-connection.js";
|
|
4
4
|
export declare function create<T extends IEntity>(client: PostgresConnection, pluralResourceName: string, entity: Partial<T>): Promise<{
|
|
5
5
|
insertedId: AppIdType;
|
|
6
6
|
entity: T;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { BadRequestError, DuplicateKeyError } from "../../../errors/index.js";
|
|
2
|
-
import { columnsAndValuesFromEntity } from
|
|
2
|
+
import { columnsAndValuesFromEntity } from "../utils/columns-and-values-from-entity.js";
|
|
3
3
|
export async function create(client, pluralResourceName, entity) {
|
|
4
4
|
try {
|
|
5
5
|
delete entity._id;
|
|
6
6
|
const { columns, values } = columnsAndValuesFromEntity(entity);
|
|
7
|
-
const placeholders = columns.map((_, index) => `$${index + 1}`).join(
|
|
7
|
+
const placeholders = columns.map((_, index) => `$${index + 1}`).join(", ");
|
|
8
8
|
const query = `
|
|
9
|
-
INSERT INTO "${pluralResourceName}" (${columns.join(
|
|
9
|
+
INSERT INTO "${pluralResourceName}" (${columns.join(", ")})
|
|
10
10
|
VALUES (${placeholders})
|
|
11
11
|
RETURNING *
|
|
12
12
|
`;
|
|
@@ -18,11 +18,11 @@ export async function create(client, pluralResourceName, entity) {
|
|
|
18
18
|
const insertedId = insertedRow._id;
|
|
19
19
|
return {
|
|
20
20
|
insertedId: insertedId,
|
|
21
|
-
entity: insertedRow
|
|
21
|
+
entity: insertedRow,
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
catch (err) {
|
|
25
|
-
if (err.code ===
|
|
25
|
+
if (err.code === "23505") {
|
|
26
26
|
throw new DuplicateKeyError(`${pluralResourceName} already exists`);
|
|
27
27
|
}
|
|
28
28
|
throw new BadRequestError(`Error creating ${pluralResourceName}: ${err.message}`);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IEntity, IModelSpec, IPagedResult, IQueryOptions, IUserContextAuthorization } from "@loomcore/common/models";
|
|
2
2
|
import type { AppIdType } from "@loomcore/common/types";
|
|
3
3
|
import { TSchema } from "@sinclair/typebox";
|
|
4
4
|
import { DeleteResult, IDatabase } from "../models/index.js";
|
|
5
5
|
import { Operation } from "../operations/operation.js";
|
|
6
|
-
import type { PostgresConnection } from
|
|
6
|
+
import type { PostgresConnection } from "./postgres-connection.js";
|
|
7
7
|
export declare class PostgresDatabase implements IDatabase {
|
|
8
8
|
private connection;
|
|
9
9
|
constructor(connection: PostgresConnection);
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
+
import { batchUpdate as batchUpdateCommand } from "./commands/postgres-batch-update.command.js";
|
|
1
2
|
import { create as createCommand } from "./commands/postgres-create.command.js";
|
|
2
3
|
import { createMany as createManyCommand } from "./commands/postgres-create-many.command.js";
|
|
3
|
-
import {
|
|
4
|
+
import { deleteById as deleteByIdCommand } from "./commands/postgres-delete-by-id.command.js";
|
|
5
|
+
import { deleteMany as deleteManyCommand } from "./commands/postgres-delete-many.command.js";
|
|
4
6
|
import { fullUpdateById as fullUpdateByIdCommand } from "./commands/postgres-full-update-by-id.command.js";
|
|
5
7
|
import { partialUpdateById as partialUpdateByIdCommand } from "./commands/postgres-partial-update-by-id.command.js";
|
|
6
8
|
import { update as updateCommand } from "./commands/postgres-update.command.js";
|
|
7
|
-
import { deleteById as deleteByIdCommand } from "./commands/postgres-delete-by-id.command.js";
|
|
8
|
-
import { deleteMany as deleteManyCommand } from "./commands/postgres-delete-many.command.js";
|
|
9
|
-
import { findOne as findOneQuery } from "./queries/postgres-find-one.query.js";
|
|
10
9
|
import { find as findQuery } from "./queries/postgres-find.query.js";
|
|
11
|
-
import {
|
|
10
|
+
import { findOne as findOneQuery } from "./queries/postgres-find-one.query.js";
|
|
12
11
|
import { get as getQuery } from "./queries/postgres-get.query.js";
|
|
12
|
+
import { getAll as getAllQuery } from "./queries/postgres-get-all.query.js";
|
|
13
13
|
import { getById as getByIdQuery } from "./queries/postgres-get-by-id.query.js";
|
|
14
14
|
import { getCount as getCountQuery } from "./queries/postgres-get-count.query.js";
|
|
15
|
+
import { convertKeysToCamelCase, convertKeysToSnakeCase, } from "./utils/convert-keys.util.js";
|
|
15
16
|
import { convertNullToUndefined } from "./utils/convert-null-to-undefined.util.js";
|
|
16
|
-
import { convertKeysToSnakeCase, convertKeysToCamelCase } from "./utils/convert-keys.util.js";
|
|
17
17
|
export class PostgresDatabase {
|
|
18
18
|
connection;
|
|
19
19
|
constructor(connection) {
|
|
@@ -72,7 +72,6 @@ export class PostgresDatabase {
|
|
|
72
72
|
const now = new Date();
|
|
73
73
|
let query = `
|
|
74
74
|
SELECT DISTINCT
|
|
75
|
-
ur."user_id" as "userId",
|
|
76
75
|
r."name" as "role",
|
|
77
76
|
f."name" as "feature",
|
|
78
77
|
a."config",
|
|
@@ -96,7 +95,6 @@ export class PostgresDatabase {
|
|
|
96
95
|
const result = await this.connection.query(query, values);
|
|
97
96
|
const authorizations = [];
|
|
98
97
|
for (const row of result.rows) {
|
|
99
|
-
const userId = row.userId;
|
|
100
98
|
authorizations.push({
|
|
101
99
|
_id: row._id,
|
|
102
100
|
_orgId: row._orgId,
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { Request, Response
|
|
2
|
-
declare const isAuthorized: (allowedFeatures?: string[]) => (req: Request,
|
|
1
|
+
import type { NextFunction, Request, Response } from "express";
|
|
2
|
+
declare const isAuthorized: (allowedFeatures?: string[]) => (req: Request, _res: Response, next: NextFunction) => void;
|
|
3
3
|
export { isAuthorized };
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { UserContextSpec } from
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { UserContextSpec } from "@loomcore/common/models";
|
|
2
|
+
import jwt from "jsonwebtoken";
|
|
3
|
+
import { UnauthenticatedError, UnauthorizedError } from "../errors/index.js";
|
|
4
|
+
import { getAuthConfig } from "../utils/auth/get-auth-config.util.js";
|
|
5
5
|
const isAuthorized = (allowedFeatures) => {
|
|
6
|
-
return (req,
|
|
6
|
+
return (req, _res, next) => {
|
|
7
7
|
let token = null;
|
|
8
8
|
if (req.headers?.authorization) {
|
|
9
|
-
|
|
10
|
-
const authHeaderArray = authHeader.split(
|
|
9
|
+
const authHeader = req.headers.authorization;
|
|
10
|
+
const authHeaderArray = authHeader.split("Bearer ");
|
|
11
11
|
if (authHeaderArray?.length > 1) {
|
|
12
12
|
token = authHeaderArray[1];
|
|
13
13
|
}
|
|
@@ -15,15 +15,16 @@ const isAuthorized = (allowedFeatures) => {
|
|
|
15
15
|
if (!token) {
|
|
16
16
|
throw new UnauthenticatedError();
|
|
17
17
|
}
|
|
18
|
+
const authConfig = getAuthConfig();
|
|
18
19
|
try {
|
|
19
|
-
const rawPayload =
|
|
20
|
+
const rawPayload = jwt.verify(token, authConfig.clientSecret);
|
|
20
21
|
const userContext = UserContextSpec.decode(rawPayload);
|
|
21
22
|
req.userContext = userContext;
|
|
22
|
-
if (userContext.authorizations.some(authorization => authorization.feature ===
|
|
23
|
+
if (userContext.authorizations.some((authorization) => authorization.feature === "admin")) {
|
|
23
24
|
next();
|
|
24
25
|
}
|
|
25
|
-
else if (allowedFeatures
|
|
26
|
-
if (!userContext.authorizations.some(authorization => allowedFeatures
|
|
26
|
+
else if (allowedFeatures?.length) {
|
|
27
|
+
if (!userContext.authorizations.some((authorization) => allowedFeatures.includes(authorization.feature))) {
|
|
27
28
|
throw new UnauthorizedError();
|
|
28
29
|
}
|
|
29
30
|
next();
|
|
@@ -33,6 +34,7 @@ const isAuthorized = (allowedFeatures) => {
|
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
catch (err) {
|
|
37
|
+
console.error(err);
|
|
36
38
|
throw new UnauthenticatedError();
|
|
37
39
|
}
|
|
38
40
|
};
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { entityUtils } from "@loomcore/common/utils";
|
|
2
|
-
import {
|
|
2
|
+
import { getIdSchema, TypeboxIsoDate } from "@loomcore/common/validation";
|
|
3
3
|
import { Type } from "@sinclair/typebox";
|
|
4
|
-
;
|
|
5
4
|
export const refreshTokenSchema = Type.Object({
|
|
6
5
|
token: Type.String({ minLength: 1 }),
|
|
7
6
|
deviceId: Type.String({ minLength: 1 }),
|
|
8
7
|
userId: getIdSchema(),
|
|
9
8
|
expiresOn: Type.Number(),
|
|
10
|
-
created: TypeboxIsoDate({ title:
|
|
11
|
-
createdBy: getIdSchema()
|
|
9
|
+
created: TypeboxIsoDate({ title: "Created Date" }),
|
|
10
|
+
createdBy: getIdSchema(),
|
|
12
11
|
});
|
|
13
12
|
export const refreshTokenModelSpec = entityUtils.getModelSpec(refreshTokenSchema);
|