@loomcore/api 0.2.7 → 0.2.9
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 +8 -6
- package/dist/__tests__/common-test.utils.js +61 -37
- package/dist/__tests__/test-express-app.d.ts +20 -19
- package/dist/__tests__/test-express-app.js +72 -68
- package/dist/__tests__/test-objects.d.ts +5 -0
- package/dist/__tests__/test-objects.js +16 -0
- package/dist/config/base-api-config.d.ts +2 -2
- package/dist/config/base-api-config.js +2 -2
- package/dist/controllers/auth.controller.d.ts +9 -8
- package/dist/controllers/auth.controller.js +76 -29
- 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/migrations/__tests__/test-migration-helper.js +2 -1
- package/dist/databases/postgres/migrations/postgres-initial-schema.js +7 -5
- package/dist/databases/postgres/postgres.database.d.ts +2 -2
- package/dist/databases/postgres/postgres.database.js +6 -8
- package/dist/middleware/index.d.ts +4 -4
- package/dist/middleware/index.js +4 -4
- package/dist/middleware/is-authorized.d.ts +2 -2
- package/dist/middleware/is-authorized.js +13 -11
- package/dist/models/app-config.interface.d.ts +1 -1
- package/dist/models/base-api-config.interface.d.ts +5 -5
- package/dist/models/initial-database-config.interface.d.ts +4 -3
- 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 +3 -0
- package/dist/utils/auth/attempt-login.util.js +37 -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 +39 -0
- package/dist/utils/auth/send-reset-password-email.util.d.ts +3 -0
- package/dist/utils/auth/send-reset-password-email.util.js +23 -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 +3 -3
- 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,60 +1,76 @@
|
|
|
1
|
-
import { LoginResponseSpec, PublicUserContextSpec,
|
|
1
|
+
import { EmptyUserContext, LoginResponseSpec, PublicUserContextSpec, passwordValidator, TokenResponseSpec, UserSpec, } from "@loomcore/common/models";
|
|
2
2
|
import { entityUtils } from "@loomcore/common/utils";
|
|
3
|
+
import { config } from "../config/base-api-config.js";
|
|
3
4
|
import { BadRequestError, 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) {
|
|
14
20
|
app.post(`/api/auth/login`, this.login.bind(this), this.afterAuth.bind(this));
|
|
15
|
-
app.post(`/api/auth/register`, isAuthorized(), this.registerUser.bind(this));
|
|
16
21
|
app.get(`/api/auth/refresh`, this.requestTokenUsingRefreshToken.bind(this));
|
|
17
22
|
app.get(`/api/auth/get-user-context`, isAuthorized(), this.getUserContext.bind(this));
|
|
18
23
|
app.patch(`/api/auth/change-password`, isAuthorized(), this.changePassword.bind(this));
|
|
19
24
|
app.post(`/api/auth/forgot-password`, this.forgotPassword.bind(this));
|
|
20
25
|
app.post(`/api/auth/reset-password`, this.resetPassword.bind(this));
|
|
21
26
|
}
|
|
22
|
-
async login(req, res
|
|
27
|
+
async login(req, res) {
|
|
23
28
|
const { email, password } = req.body;
|
|
29
|
+
if (!email || typeof email !== "string") {
|
|
30
|
+
throw new BadRequestError("Missing required fields: email is required.");
|
|
31
|
+
}
|
|
32
|
+
if (!password || typeof password !== "string") {
|
|
33
|
+
throw new BadRequestError("Missing required fields: password is required.");
|
|
34
|
+
}
|
|
35
|
+
let organization = null;
|
|
36
|
+
if (config.app.isMultiTenant) {
|
|
37
|
+
const referer = req.get("referer") || req.headers.referer;
|
|
38
|
+
if (!referer) {
|
|
39
|
+
throw new BadRequestError("Missing required fields: referer is required.");
|
|
40
|
+
}
|
|
41
|
+
organization = await this.organizationService.findOne(EmptyUserContext, {
|
|
42
|
+
filters: { domain: { eq: referer.split("/")[2] } },
|
|
43
|
+
});
|
|
44
|
+
if (!organization) {
|
|
45
|
+
throw new BadRequestError("Missing required fields: organization is required.");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
24
48
|
res.set("Content-Type", "application/json");
|
|
25
|
-
const
|
|
49
|
+
const deviceId = getAndSetDeviceIdCookie(req, res);
|
|
50
|
+
const loginResponse = await attemptLogin(this.database, email, password, deviceId, organization);
|
|
26
51
|
apiUtils.apiResponse(res, 200, { data: loginResponse }, LoginResponseSpec);
|
|
27
52
|
}
|
|
28
|
-
async
|
|
53
|
+
async requestTokenUsingRefreshToken(req, res) {
|
|
29
54
|
const userContext = req.userContext;
|
|
30
55
|
if (!userContext) {
|
|
31
56
|
throw new BadRequestError("Missing required fields: userContext is required.");
|
|
32
57
|
}
|
|
33
|
-
const body = req.body;
|
|
34
|
-
const validationErrors = this.authService.validate(body.user);
|
|
35
|
-
entityUtils.handleValidationResult(validationErrors, "AuthController.registerUser");
|
|
36
|
-
const user = await this.authService.createUser(userContext, body.user);
|
|
37
|
-
apiUtils.apiResponse(res, 201, { data: user || undefined }, UserSpec, PublicUserSpec);
|
|
38
|
-
}
|
|
39
|
-
async requestTokenUsingRefreshToken(req, res, next) {
|
|
40
58
|
const refreshToken = req.query.refreshToken;
|
|
41
59
|
if (!refreshToken || typeof refreshToken !== "string") {
|
|
42
60
|
throw new BadRequestError("Missing required fields: refreshToken is required.");
|
|
43
61
|
}
|
|
44
|
-
const deviceId =
|
|
45
|
-
const tokens = await this.
|
|
46
|
-
if (tokens) {
|
|
47
|
-
apiUtils.apiResponse(res, 200, { data: tokens }, TokenResponseSpec);
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
62
|
+
const deviceId = getDeviceIdFromCookie(req);
|
|
63
|
+
const tokens = await requestTokenUsingRefreshToken(this.database, userContext, refreshToken, deviceId);
|
|
64
|
+
if (!tokens) {
|
|
50
65
|
throw new UnauthenticatedError();
|
|
51
66
|
}
|
|
67
|
+
apiUtils.apiResponse(res, 200, { data: tokens }, TokenResponseSpec);
|
|
52
68
|
}
|
|
53
|
-
async getUserContext(req, res
|
|
69
|
+
async getUserContext(req, res) {
|
|
54
70
|
const userContext = req.userContext;
|
|
55
71
|
apiUtils.apiResponse(res, 200, { data: userContext }, PublicUserContextSpec);
|
|
56
72
|
}
|
|
57
|
-
afterAuth(
|
|
73
|
+
afterAuth(_req, _res, _loginResponse) {
|
|
58
74
|
console.log("in afterAuth");
|
|
59
75
|
}
|
|
60
76
|
async changePassword(req, res) {
|
|
@@ -62,22 +78,40 @@ export class AuthController {
|
|
|
62
78
|
if (!userContext) {
|
|
63
79
|
throw new BadRequestError("Missing required fields: userContext is required.");
|
|
64
80
|
}
|
|
65
|
-
const
|
|
66
|
-
const validationErrors = entityUtils.validate(UserSpec, { password:
|
|
81
|
+
const password = req.body?.password;
|
|
82
|
+
const validationErrors = entityUtils.validate(UserSpec, { password: password }, true, passwordValidator);
|
|
67
83
|
entityUtils.handleValidationResult(validationErrors, "AuthController.changePassword");
|
|
68
|
-
const updateResult = await this.
|
|
84
|
+
const updateResult = await changePassword(this.database, userContext, password);
|
|
69
85
|
apiUtils.apiResponse(res, 200, { data: updateResult });
|
|
70
86
|
}
|
|
71
87
|
async forgotPassword(req, res) {
|
|
72
88
|
const email = req.body?.email;
|
|
89
|
+
if (!email || typeof email !== "string") {
|
|
90
|
+
throw new BadRequestError("Missing required fields: email is required.");
|
|
91
|
+
}
|
|
73
92
|
let referer = req.get("referer") || req.headers.referer;
|
|
74
93
|
if (!referer) {
|
|
75
94
|
throw new BadRequestError("Missing required fields: referer is required.");
|
|
76
95
|
}
|
|
77
96
|
referer = referer.replace(/\/$/, "");
|
|
78
|
-
|
|
97
|
+
let organization = null;
|
|
98
|
+
if (config.app.isMultiTenant) {
|
|
99
|
+
organization = await this.organizationService.findOne(EmptyUserContext, {
|
|
100
|
+
filters: { domain: { eq: referer.split("/")[2] } },
|
|
101
|
+
});
|
|
102
|
+
if (!organization) {
|
|
103
|
+
throw new BadRequestError("Missing required fields: organization is required.");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const userContext = {
|
|
107
|
+
...EmptyUserContext,
|
|
108
|
+
organization: organization || undefined,
|
|
109
|
+
};
|
|
110
|
+
const user = await this.userService.findOne(userContext, {
|
|
111
|
+
filters: { email: { eq: email.toLowerCase() } },
|
|
112
|
+
});
|
|
79
113
|
if (user) {
|
|
80
|
-
await this.
|
|
114
|
+
await sendResetPasswordEmail(this.database, email, referer);
|
|
81
115
|
}
|
|
82
116
|
apiUtils.apiResponse(res, 200);
|
|
83
117
|
}
|
|
@@ -86,7 +120,20 @@ export class AuthController {
|
|
|
86
120
|
if (!email || !token || !password) {
|
|
87
121
|
throw new BadRequestError("Missing required fields: email, token, and password are required.");
|
|
88
122
|
}
|
|
89
|
-
|
|
123
|
+
let organization = null;
|
|
124
|
+
if (config.app.isMultiTenant) {
|
|
125
|
+
const referer = req.get("referer") || req.headers.referer;
|
|
126
|
+
if (!referer) {
|
|
127
|
+
throw new BadRequestError("Missing required fields: referer is required.");
|
|
128
|
+
}
|
|
129
|
+
organization = await this.organizationService.findOne(EmptyUserContext, {
|
|
130
|
+
filters: { domain: { eq: referer.split("/")[2] } },
|
|
131
|
+
});
|
|
132
|
+
if (!organization) {
|
|
133
|
+
throw new BadRequestError("Missing required fields: organization is required.");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const response = await resetPassword(this.database, email, token, password, organization);
|
|
90
137
|
apiUtils.apiResponse(res, 200, { data: response });
|
|
91
138
|
}
|
|
92
139
|
}
|
|
@@ -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,4 +1,5 @@
|
|
|
1
1
|
import { Umzug } from 'umzug';
|
|
2
|
+
import { TEST_META_ORG_DOMAIN } from '../../../../__tests__/test-objects.js';
|
|
2
3
|
import { getPostgresInitialSchema } from '../postgres-initial-schema.js';
|
|
3
4
|
import { getPostgresTestSchema } from '../../../../__tests__/postgres-test-migrations/postgres-test-schema.js';
|
|
4
5
|
export async function runInitialSchemaMigrations(pool, config) {
|
|
@@ -7,7 +8,7 @@ export async function runInitialSchemaMigrations(pool, config) {
|
|
|
7
8
|
app: config.app,
|
|
8
9
|
database: config.database,
|
|
9
10
|
adminUser: config.adminUser ?? { email: 'admin@test.com', password: 'admin-password' },
|
|
10
|
-
multiTenant: config.multiTenant ?? (config.app.isMultiTenant ? { metaOrgName: 'Test Meta Organization', metaOrgCode: 'TEST_META_ORG' } : { metaOrgName: '', metaOrgCode: '' }),
|
|
11
|
+
multiTenant: config.multiTenant ?? (config.app.isMultiTenant ? { metaOrgName: 'Test Meta Organization', metaOrgCode: 'TEST_META_ORG', metaOrgDomain: TEST_META_ORG_DOMAIN } : { metaOrgName: '', metaOrgCode: '', metaOrgDomain: '' }),
|
|
11
12
|
email: config.email,
|
|
12
13
|
};
|
|
13
14
|
const initialSchema = getPostgresInitialSchema(migrationConfig);
|
|
@@ -33,7 +33,8 @@ export const getPostgresInitialSchema = (dbConfig) => {
|
|
|
33
33
|
CREATE TABLE IF NOT EXISTS "organizations" (
|
|
34
34
|
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
35
35
|
"name" VARCHAR(255) NOT NULL UNIQUE,
|
|
36
|
-
"code" VARCHAR(255)
|
|
36
|
+
"code" VARCHAR(255) UNIQUE,
|
|
37
|
+
"domain" VARCHAR(255) UNIQUE,
|
|
37
38
|
"description" TEXT,
|
|
38
39
|
"status" INTEGER NOT NULL,
|
|
39
40
|
"is_meta_org" BOOLEAN NOT NULL,
|
|
@@ -297,12 +298,13 @@ export const getPostgresInitialSchema = (dbConfig) => {
|
|
|
297
298
|
name: "00000000000011_data-meta-org",
|
|
298
299
|
up: async ({ context: pool }) => {
|
|
299
300
|
const result = await pool.query(`
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
301
|
+
INSERT INTO "organizations"("name", "code", "domain", "status", "is_meta_org", "_created", "_createdBy")
|
|
302
|
+
VALUES($1, $2, $3, 1, true, NOW(), 0)
|
|
303
|
+
RETURNING "_id", "name", "code", "domain", "status", "is_meta_org", "_created", "_createdBy"
|
|
304
|
+
`, [
|
|
304
305
|
dbConfig.multiTenant?.metaOrgName,
|
|
305
306
|
dbConfig.multiTenant?.metaOrgCode,
|
|
307
|
+
dbConfig.multiTenant?.metaOrgDomain,
|
|
306
308
|
]);
|
|
307
309
|
if (result.rowCount === 0) {
|
|
308
310
|
throw new Error("Failed to create meta organization");
|
|
@@ -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,4 +1,4 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
1
|
+
export * from "./ensure-user-context.js";
|
|
2
|
+
export * from "./error-handler.js";
|
|
3
|
+
export * from "./is-authorized.js";
|
|
4
|
+
export * from "./request-lifecycle.js";
|
package/dist/middleware/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
1
|
+
export * from "./ensure-user-context.js";
|
|
2
|
+
export * from "./error-handler.js";
|
|
3
|
+
export * from "./is-authorized.js";
|
|
4
|
+
export * from "./request-lifecycle.js";
|
|
@@ -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,8 +1,8 @@
|
|
|
1
|
-
import { IAppConfig } from "./app-config.interface.js";
|
|
2
|
-
import { IAuthConfig } from "./auth-config.interface.js";
|
|
3
|
-
import { IDatabaseConfig } from "./database-config.interface.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import type { IAppConfig } from "./app-config.interface.js";
|
|
2
|
+
import type { IAuthConfig } from "./auth-config.interface.js";
|
|
3
|
+
import type { IDatabaseConfig } from "./database-config.interface.js";
|
|
4
|
+
import type { IEmailClient } from "./email-client.interface.js";
|
|
5
|
+
import type { IEmailConfig } from "./email-config.interface.js";
|
|
6
6
|
export interface IBaseApiConfig {
|
|
7
7
|
app: IAppConfig;
|
|
8
8
|
auth?: IAuthConfig;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { IAppConfig } from "./app-config.interface.js";
|
|
2
|
-
import { IDatabaseConfig } from "./database-config.interface.js";
|
|
3
|
-
import { IEmailConfig } from "./email-config.interface.js";
|
|
1
|
+
import type { IAppConfig } from "./app-config.interface.js";
|
|
2
|
+
import type { IDatabaseConfig } from "./database-config.interface.js";
|
|
3
|
+
import type { IEmailConfig } from "./email-config.interface.js";
|
|
4
4
|
export interface IInitialDbMigrationConfig {
|
|
5
5
|
env: string;
|
|
6
6
|
app: IAppConfig;
|
|
@@ -12,6 +12,7 @@ export interface IInitialDbMigrationConfig {
|
|
|
12
12
|
multiTenant: {
|
|
13
13
|
metaOrgName: string;
|
|
14
14
|
metaOrgCode: string;
|
|
15
|
+
metaOrgDomain: string;
|
|
15
16
|
};
|
|
16
17
|
email?: IEmailConfig;
|
|
17
18
|
}
|
|
@@ -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);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import type { IEntity, IPagedResult, IQueryOptions, IUserContext } from "@loomcore/common/models";
|
|
2
|
+
import type { AppIdType } from "@loomcore/common/types";
|
|
3
|
+
import type { ValueError } from "@sinclair/typebox/errors";
|
|
4
|
+
import type { PostProcessEntityCustomFunction, PrepareQueryCustomFunction } from "../../controllers/types.js";
|
|
5
|
+
import type { DeleteResult } from "../../databases/models/delete-result.js";
|
|
6
|
+
import type { Operation } from "../../databases/operations/operation.js";
|
|
7
7
|
export interface IGenericApiService<T extends IEntity> {
|
|
8
8
|
validate(doc: any, isPartial?: boolean): ValueError[] | null;
|
|
9
9
|
validateMany(docs: any[], isPartial?: boolean): ValueError[] | null;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
1
|
+
import { type IEntity, type IModelSpec, type IPagedResult, type IQueryOptions, type IUserContext } from "@loomcore/common/models";
|
|
2
|
+
import type { AppIdType } from "@loomcore/common/types";
|
|
3
|
+
import type { ValueError } from "@sinclair/typebox/errors";
|
|
4
|
+
import type { PostProcessEntityCustomFunction, PrepareQueryCustomFunction } from "../../controllers/types.js";
|
|
5
|
+
import type { DeleteResult } from "../../databases/models/delete-result.js";
|
|
6
|
+
import type { IDatabase } from "../../databases/models/index.js";
|
|
7
|
+
import type { Operation } from "../../databases/operations/operation.js";
|
|
8
|
+
import type { IGenericApiService } from "./generic-api-service.interface.js";
|
|
9
9
|
export declare class GenericApiService<T extends IEntity> implements IGenericApiService<T> {
|
|
10
10
|
protected database: IDatabase;
|
|
11
11
|
protected pluralResourceName: string;
|