@loomcore/api 0.2.27 → 0.2.28

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.
Files changed (80) hide show
  1. package/README.md +1 -1
  2. package/dist/__tests__/common-test.utils.js +7 -7
  3. package/dist/__tests__/test-objects.js +3 -27
  4. package/dist/controllers/api.controller.d.ts +1 -3
  5. package/dist/controllers/api.controller.js +28 -27
  6. package/dist/controllers/auth.controller.js +174 -126
  7. package/dist/controllers/authorizations.controller.d.ts +1 -1
  8. package/dist/controllers/authorizations.controller.js +4 -5
  9. package/dist/controllers/features.controller.d.ts +1 -1
  10. package/dist/controllers/features.controller.js +4 -5
  11. package/dist/controllers/organizations.controller.js +95 -44
  12. package/dist/controllers/query-api.controller.d.ts +1 -3
  13. package/dist/controllers/query-api.controller.js +11 -13
  14. package/dist/controllers/roles.controller.d.ts +3 -3
  15. package/dist/controllers/roles.controller.js +4 -5
  16. package/dist/controllers/user-roles.controller.d.ts +1 -1
  17. package/dist/controllers/user-roles.controller.js +4 -5
  18. package/dist/controllers/users.controller.d.ts +0 -2
  19. package/dist/controllers/users.controller.js +1 -3
  20. package/dist/databases/postgres/postgres.database.d.ts +2 -2
  21. package/dist/databases/postgres/postgres.database.js +2 -12
  22. package/dist/decorators/authorize.decorator.d.ts +13 -0
  23. package/dist/decorators/authorize.decorator.js +33 -0
  24. package/dist/errors/unauthorized.error.d.ts +1 -1
  25. package/dist/errors/unauthorized.error.js +5 -3
  26. package/dist/middleware/authorize/authenticate-request.d.ts +3 -0
  27. package/dist/middleware/authorize/authenticate-request.js +21 -0
  28. package/dist/middleware/authorize/authorize.middleware.d.ts +3 -0
  29. package/dist/middleware/authorize/authorize.middleware.js +23 -0
  30. package/dist/middleware/index.d.ts +1 -2
  31. package/dist/middleware/index.js +1 -2
  32. package/dist/models/index.d.ts +0 -1
  33. package/dist/models/index.js +0 -1
  34. package/dist/services/authorizations.service.d.ts +7 -0
  35. package/dist/services/authorizations.service.js +12 -0
  36. package/dist/services/features.service.d.ts +7 -0
  37. package/dist/services/features.service.js +12 -0
  38. package/dist/services/index.d.ts +4 -0
  39. package/dist/services/index.js +4 -0
  40. package/dist/services/refresh-token.service.d.ts +1 -1
  41. package/dist/services/refresh-token.service.js +1 -1
  42. package/dist/services/roles.service.d.ts +7 -0
  43. package/dist/services/roles.service.js +12 -0
  44. package/dist/services/user-roles.service.d.ts +7 -0
  45. package/dist/services/user-roles.service.js +12 -0
  46. package/dist/services/user.service.js +3 -3
  47. package/dist/services/utils/getUserContextAuthorizations.util.d.ts +2 -2
  48. package/dist/services/utils/getUserContextAuthorizations.util.js +3 -3
  49. package/dist/utils/auth/assert-owner-or-admin.util.d.ts +2 -3
  50. package/dist/utils/auth/assert-owner-or-admin.util.js +2 -2
  51. package/dist/utils/auth/assert-user-has-feature.util.d.ts +2 -0
  52. package/dist/utils/auth/assert-user-has-feature.util.js +10 -0
  53. package/dist/utils/auth/{assert-self-or-admin.util.d.ts → assert-user-or-admin.util.d.ts} +1 -1
  54. package/dist/utils/auth/{assert-self-or-admin.util.js → assert-user-or-admin.util.js} +1 -1
  55. package/dist/utils/auth/attempt-login.util.js +3 -3
  56. package/dist/utils/auth/auth-user-specs.util.d.ts +0 -1
  57. package/dist/utils/auth/auth-user-specs.util.js +1 -3
  58. package/dist/utils/auth/authorize-method.util.d.ts +2 -0
  59. package/dist/utils/auth/authorize-method.util.js +8 -0
  60. package/dist/utils/auth/create-new-refresh-token.util.d.ts +1 -1
  61. package/dist/utils/auth/create-new-tokens.util.d.ts +1 -2
  62. package/dist/utils/auth/get-active-refresh-token.util.d.ts +1 -2
  63. package/dist/utils/auth/index.d.ts +3 -1
  64. package/dist/utils/auth/index.js +3 -1
  65. package/dist/utils/auth/is-admin.util.js +1 -2
  66. package/package.json +4 -3
  67. package/dist/middleware/is-authorized.d.ts +0 -4
  68. package/dist/middleware/is-authorized.js +0 -57
  69. package/dist/models/authorization.model.d.ts +0 -16
  70. package/dist/models/authorization.model.js +0 -11
  71. package/dist/models/feature.model.d.ts +0 -7
  72. package/dist/models/feature.model.js +0 -6
  73. package/dist/models/method-auth.model.d.ts +0 -9
  74. package/dist/models/method-auth.model.js +0 -12
  75. package/dist/models/refresh-token.model.d.ts +0 -19
  76. package/dist/models/refresh-token.model.js +0 -12
  77. package/dist/models/role.model.d.ts +0 -7
  78. package/dist/models/role.model.js +0 -6
  79. package/dist/models/user-role.model.d.ts +0 -8
  80. package/dist/models/user-role.model.js +0 -7
package/README.md CHANGED
@@ -8,7 +8,7 @@ This library provides a foundational structure for creating robust APIs, with a
8
8
 
9
9
  - **Generic API Controller**: A base `ApiController` that automatically scaffolds a full suite of RESTful endpoints for any data model. This includes support for CRUD operations, batch updates, pagination, and counting.
10
10
  - **Built-in Multi-tenancy**: Optional, configuration-driven multi-tenancy. When enabled, all database operations are automatically scoped to the current user's organization, ensuring strict data isolation.
11
- - **Authentication & Authorization**: Integrated JWT-based authentication middleware (`isAuthorized`) to easily secure endpoints and support for OAuth 2.0 Code Flow, including the use of refresh tokens.
11
+ - **Authentication & Authorization**: Integrated JWT-based authentication with `@Authorize` / `@AllowAnonymous` decorators to secure endpoints, plus support for OAuth 2.0 Code Flow including refresh tokens.
12
12
  - **User & Organization Management**: Pre-built services and controllers for common user and organization management tasks.
13
13
  - **Password Management**: Includes services for handling password reset requests and workflows.
14
14
  - **Email Service Integration**: A ready-to-use service for sending transactional emails (e.g., for password resets or welcome messages).
@@ -5,7 +5,6 @@ import { Value } from "@sinclair/typebox/value";
5
5
  import jwt from "jsonwebtoken";
6
6
  import { ObjectId } from "mongodb";
7
7
  import { ApiController } from "../controllers/api.controller.js";
8
- import { authenticated } from "../middleware/index.js";
9
8
  import { MongoDBDatabase } from "../databases/mongo-db/mongo-db.database.js";
10
9
  import { LeftJoin } from "../databases/operations/left-join.operation.js";
11
10
  import { PostgresDatabase } from "../databases/postgres/postgres.database.js";
@@ -220,7 +219,7 @@ export class CategoryService extends GenericApiService {
220
219
  export class CategoryController extends ApiController {
221
220
  constructor(app, database) {
222
221
  const categoryService = new CategoryService(database);
223
- super("categories", app, categoryService, authenticated, "category", CategorySpec);
222
+ super("categories", app, categoryService, "category", CategorySpec);
224
223
  }
225
224
  }
226
225
  export function setupTestConfig(isMultiTenant = true, dbType) {
@@ -285,7 +284,7 @@ export class ProductService extends GenericApiService {
285
284
  export class ProductsController extends ApiController {
286
285
  constructor(app, database) {
287
286
  const productService = new ProductService(database);
288
- super("products", app, productService, authenticated, "product", ProductSpec);
287
+ super("products", app, productService, "product", ProductSpec);
289
288
  }
290
289
  async get(req, res, next) {
291
290
  res.set("Content-Type", "application/json");
@@ -316,14 +315,15 @@ export class ProductsController extends ApiController {
316
315
  if (!idParam) {
317
316
  throw new Error("ID parameter is required");
318
317
  }
318
+ let id;
319
319
  try {
320
- const id = Value.Convert(this.idSchema, idParam);
321
- const entity = await this.service.getById(userContext, id, prepareQueryCustom, postProcessEntityCustom);
322
- apiUtils.apiResponse(res, 200, { data: entity }, ProductWithCategorySpec, ProductWithCategoryPublicSpec);
320
+ id = Value.Convert(this.idSchema, idParam);
323
321
  }
324
322
  catch (error) {
325
323
  throw new Error(`Invalid ID format: ${error.message || error}`);
326
324
  }
325
+ const entity = await this.service.getById(userContext, id, prepareQueryCustom, postProcessEntityCustom);
326
+ apiUtils.apiResponse(res, 200, { data: entity }, ProductWithCategorySpec, ProductWithCategoryPublicSpec);
327
327
  }
328
328
  }
329
329
  export class MultiTenantProductService extends MultiTenantApiService {
@@ -360,7 +360,7 @@ export class MultiTenantProductsController extends ApiController {
360
360
  "internalNumber",
361
361
  ]);
362
362
  const PublicAggregatedProductSpec = entityUtils.getModelSpec(PublicAggregatedProductSchema);
363
- super("multi-tenant-products", app, productService, authenticated, "product", ProductSpec, PublicAggregatedProductSpec);
363
+ super("multi-tenant-products", app, productService, "product", ProductSpec, PublicAggregatedProductSpec);
364
364
  }
365
365
  }
366
366
  function configureJwtSecret() {
@@ -54,30 +54,14 @@ export function getTestMetaOrgUserContext() {
54
54
  return {
55
55
  user: getTestMetaOrgUser(),
56
56
  organization: getTestMetaOrg(),
57
- authorizations: [
58
- {
59
- _id: "6939c54e57a1c6576a40c590",
60
- _orgId: getTestMetaOrg()._id,
61
- role: "metaorgUser",
62
- feature: "metaorgUser",
63
- config: {},
64
- },
65
- ],
57
+ features: ["metaorgUser"],
66
58
  };
67
59
  }
68
60
  export function getTestMetaOrgAdminUserContext() {
69
61
  return {
70
62
  user: getTestMetaOrgUser(),
71
63
  organization: getTestMetaOrg(),
72
- authorizations: [
73
- {
74
- _id: "6939c54e57a1c6576a40c590",
75
- _orgId: getTestMetaOrg()._id,
76
- role: "admin",
77
- feature: "admin",
78
- config: {},
79
- },
80
- ],
64
+ features: ["admin"],
81
65
  };
82
66
  }
83
67
  let TEST_ORG_ID = "6926167d06c0073a778a124f";
@@ -122,14 +106,6 @@ export function getTestOrgUserContext() {
122
106
  return {
123
107
  user: getTestOrgUser(),
124
108
  organization: getTestOrg(),
125
- authorizations: [
126
- {
127
- _id: "6939c54e57a1c6576a40c591",
128
- _orgId: getTestOrg()._id,
129
- role: "testOrgUser",
130
- feature: "testOrgUser",
131
- config: {},
132
- },
133
- ],
109
+ features: ["testOrgUser"],
134
110
  };
135
111
  }
@@ -2,17 +2,15 @@ import { Application, NextFunction, Request, Response } from 'express';
2
2
  import { IEntity, IModelSpec } from '@loomcore/common/models';
3
3
  import type { TSchema } from '@sinclair/typebox';
4
4
  import { IGenericApiService } from '../services/index.js';
5
- import { type MethodAuth } from '../middleware/index.js';
6
5
  export declare abstract class ApiController<T extends IEntity> {
7
6
  protected app: Application;
8
7
  protected service: IGenericApiService<T>;
9
8
  protected slug: string;
10
9
  protected apiResourceName: string;
11
- protected routeAuth: MethodAuth;
12
10
  protected modelSpec?: IModelSpec;
13
11
  protected publicSpec?: IModelSpec;
14
12
  protected idSchema: TSchema;
15
- protected constructor(slug: string, app: Application, service: IGenericApiService<T>, routeAuth?: MethodAuth, resourceName?: string, modelSpec?: IModelSpec, publicSpec?: IModelSpec);
13
+ protected constructor(slug: string, app: Application, service: IGenericApiService<T>, resourceName?: string, modelSpec?: IModelSpec, publicSpec?: IModelSpec);
16
14
  mapRoutes(app: Application): void;
17
15
  protected validate(entity: any, isPartial?: boolean): void;
18
16
  protected validateMany(entities: any[], isPartial?: boolean): void;
@@ -2,22 +2,19 @@ import { BadRequestError } from '../errors/index.js';
2
2
  import { entityUtils } from '@loomcore/common/utils';
3
3
  import { Value } from '@sinclair/typebox/value';
4
4
  import { getIdSchema } from '@loomcore/common/validation';
5
- import { apiUtils } from '../utils/index.js';
6
- import { authenticated, isAuthorized, } from '../middleware/index.js';
5
+ import { apiUtils, authorizeMethod } from '../utils/index.js';
7
6
  export class ApiController {
8
7
  app;
9
8
  service;
10
9
  slug;
11
10
  apiResourceName;
12
- routeAuth;
13
11
  modelSpec;
14
12
  publicSpec;
15
13
  idSchema;
16
- constructor(slug, app, service, routeAuth = authenticated, resourceName = '', modelSpec, publicSpec) {
14
+ constructor(slug, app, service, resourceName = '', modelSpec, publicSpec) {
17
15
  this.slug = slug;
18
16
  this.app = app;
19
17
  this.service = service;
20
- this.routeAuth = routeAuth;
21
18
  this.apiResourceName = resourceName;
22
19
  this.modelSpec = modelSpec;
23
20
  this.publicSpec = publicSpec;
@@ -25,16 +22,16 @@ export class ApiController {
25
22
  this.mapRoutes(app);
26
23
  }
27
24
  mapRoutes(app) {
28
- const auth = isAuthorized(this.routeAuth);
29
- app.get(`/api/${this.slug}`, auth, this.get.bind(this));
30
- app.get(`/api/${this.slug}/all`, auth, this.getAll.bind(this));
31
- app.get(`/api/${this.slug}/count`, auth, this.getCount.bind(this));
32
- app.get(`/api/${this.slug}/:id`, auth, this.getById.bind(this));
33
- app.post(`/api/${this.slug}`, auth, this.create.bind(this));
34
- app.patch(`/api/${this.slug}/batch`, auth, this.batchUpdate.bind(this));
35
- app.put(`/api/${this.slug}/:id`, auth, this.fullUpdateById.bind(this));
36
- app.patch(`/api/${this.slug}/:id`, auth, this.partialUpdateById.bind(this));
37
- app.delete(`/api/${this.slug}/:id`, auth, this.deleteById.bind(this));
25
+ const authorize = (method) => authorizeMethod(this, method);
26
+ app.get(`/api/${this.slug}`, authorize('get'), this.get.bind(this));
27
+ app.get(`/api/${this.slug}/all`, authorize('getAll'), this.getAll.bind(this));
28
+ app.get(`/api/${this.slug}/count`, authorize('getCount'), this.getCount.bind(this));
29
+ app.get(`/api/${this.slug}/:id`, authorize('getById'), this.getById.bind(this));
30
+ app.post(`/api/${this.slug}`, authorize('create'), this.create.bind(this));
31
+ app.patch(`/api/${this.slug}/batch`, authorize('batchUpdate'), this.batchUpdate.bind(this));
32
+ app.put(`/api/${this.slug}/:id`, authorize('fullUpdateById'), this.fullUpdateById.bind(this));
33
+ app.patch(`/api/${this.slug}/:id`, authorize('partialUpdateById'), this.partialUpdateById.bind(this));
34
+ app.delete(`/api/${this.slug}/:id`, authorize('deleteById'), this.deleteById.bind(this));
38
35
  }
39
36
  validate(entity, isPartial = false) {
40
37
  const validationErrors = this.service.validate(entity, isPartial);
@@ -61,14 +58,15 @@ export class ApiController {
61
58
  if (!idParam) {
62
59
  throw new BadRequestError('ID parameter is required');
63
60
  }
61
+ let id;
64
62
  try {
65
- const id = Value.Convert(this.idSchema, idParam);
66
- const entity = await this.service.getById(req.userContext, id);
67
- apiUtils.apiResponse(res, 200, { data: entity }, this.modelSpec, this.publicSpec);
63
+ id = Value.Convert(this.idSchema, idParam);
68
64
  }
69
65
  catch (error) {
70
66
  throw new BadRequestError(`Invalid ID format: ${error.message || error}`);
71
67
  }
68
+ const entity = await this.service.getById(req.userContext, id);
69
+ apiUtils.apiResponse(res, 200, { data: entity }, this.modelSpec, this.publicSpec);
72
70
  }
73
71
  async getCount(req, res, next) {
74
72
  res.set('Content-Type', 'application/json');
@@ -110,14 +108,15 @@ export class ApiController {
110
108
  if (!idParam) {
111
109
  throw new BadRequestError('ID parameter is required');
112
110
  }
111
+ let id;
113
112
  try {
114
- const id = Value.Convert(this.idSchema, idParam);
115
- const updateResult = await this.service.fullUpdateById(req.userContext, id, req.body);
116
- apiUtils.apiResponse(res, 200, { data: updateResult }, this.modelSpec, this.publicSpec);
113
+ id = Value.Convert(this.idSchema, idParam);
117
114
  }
118
115
  catch (error) {
119
116
  throw new BadRequestError(`Invalid ID format: ${error.message || error}`);
120
117
  }
118
+ const updateResult = await this.service.fullUpdateById(req.userContext, id, req.body);
119
+ apiUtils.apiResponse(res, 200, { data: updateResult }, this.modelSpec, this.publicSpec);
121
120
  }
122
121
  async partialUpdateById(req, res, next) {
123
122
  res.set('Content-Type', 'application/json');
@@ -126,14 +125,15 @@ export class ApiController {
126
125
  if (!idParam) {
127
126
  throw new BadRequestError('ID parameter is required');
128
127
  }
128
+ let id;
129
129
  try {
130
- const id = Value.Convert(this.idSchema, idParam);
131
- const updateResult = await this.service.partialUpdateById(req.userContext, id, req.body);
132
- apiUtils.apiResponse(res, 200, { data: updateResult }, this.modelSpec, this.publicSpec);
130
+ id = Value.Convert(this.idSchema, idParam);
133
131
  }
134
132
  catch (error) {
135
133
  throw new BadRequestError(`Invalid ID format: ${error.message || error}`);
136
134
  }
135
+ const updateResult = await this.service.partialUpdateById(req.userContext, id, req.body);
136
+ apiUtils.apiResponse(res, 200, { data: updateResult }, this.modelSpec, this.publicSpec);
137
137
  }
138
138
  async deleteById(req, res, next) {
139
139
  res.set('Content-Type', 'application/json');
@@ -141,13 +141,14 @@ export class ApiController {
141
141
  if (!idParam) {
142
142
  throw new BadRequestError('ID parameter is required');
143
143
  }
144
+ let id;
144
145
  try {
145
- const id = Value.Convert(this.idSchema, idParam);
146
- const deleteResult = await this.service.deleteById(req.userContext, id);
147
- apiUtils.apiResponse(res, 200, { data: deleteResult }, this.modelSpec, this.publicSpec);
146
+ id = Value.Convert(this.idSchema, idParam);
148
147
  }
149
148
  catch (error) {
150
149
  throw new BadRequestError(`Invalid ID format: ${error.message || error}`);
151
150
  }
151
+ const deleteResult = await this.service.deleteById(req.userContext, id);
152
+ apiUtils.apiResponse(res, 200, { data: deleteResult }, this.modelSpec, this.publicSpec);
152
153
  }
153
154
  }
@@ -1,147 +1,195 @@
1
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
2
+ var useValue = arguments.length > 2;
3
+ for (var i = 0; i < initializers.length; i++) {
4
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
5
+ }
6
+ return useValue ? value : void 0;
7
+ };
8
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
9
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
10
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
11
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
12
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
13
+ var _, done = false;
14
+ for (var i = decorators.length - 1; i >= 0; i--) {
15
+ var context = {};
16
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
17
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
18
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
19
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
20
+ if (kind === "accessor") {
21
+ if (result === void 0) continue;
22
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
23
+ if (_ = accept(result.get)) descriptor.get = _;
24
+ if (_ = accept(result.set)) descriptor.set = _;
25
+ if (_ = accept(result.init)) initializers.unshift(_);
26
+ }
27
+ else if (_ = accept(result)) {
28
+ if (kind === "field") initializers.unshift(_);
29
+ else descriptor[key] = _;
30
+ }
31
+ }
32
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
33
+ done = true;
34
+ };
1
35
  import { EmptyUserContext, passwordValidator, PublicUserContextSpec, PublicUserSpec, TokenResponseSpec, UserSpec, } from "@loomcore/common/models";
2
36
  import { entityUtils } from "@loomcore/common/utils";
3
37
  import { config } from "../config/base-api-config.js";
4
38
  import { BadRequestError, UnauthenticatedError } from "../errors/index.js";
5
- import { isAuthorized } from "../middleware/index.js";
6
39
  import { OrganizationService, UserService } from "../services/index.js";
7
- import { attemptLogin, changePassword, createLoginResponseSpec, getAndSetDeviceIdCookie, getDeviceIdFromCookie, requestTokenUsingRefreshToken, resetPassword, sendResetPasswordEmail, setAuthUserContextSpec, } from "../utils/auth/index.js";
40
+ import { attemptLogin, authorizeMethod, changePassword, createLoginResponseSpec, getAndSetDeviceIdCookie, getDeviceIdFromCookie, requestTokenUsingRefreshToken, resetPassword, sendResetPasswordEmail, } from "../utils/auth/index.js";
8
41
  import { apiUtils } from "../utils/index.js";
9
- export class AuthController {
10
- database;
11
- userService;
12
- organizationService;
13
- userSpec;
14
- publicUserSpec;
15
- userContextSpec;
16
- loginResponseSpec;
17
- constructor(app, database, options = {
18
- userService: new UserService(database),
19
- userSpec: UserSpec,
20
- publicUserSpec: PublicUserSpec,
21
- publicUserContextSpec: PublicUserContextSpec,
22
- }) {
23
- this.database = database;
24
- this.userSpec = options.userSpec;
25
- this.publicUserSpec = options.publicUserSpec;
26
- this.userContextSpec = options.publicUserContextSpec;
27
- setAuthUserContextSpec(this.userContextSpec);
28
- this.loginResponseSpec = createLoginResponseSpec(this.userContextSpec);
29
- this.userService = options.userService;
30
- this.organizationService = new OrganizationService(database);
31
- this.mapRoutes(app);
32
- }
33
- mapRoutes(app) {
34
- app.post(`/api/auth/login`, this.login.bind(this), this.afterAuth.bind(this));
35
- app.get(`/api/auth/refresh`, this.requestTokenUsingRefreshToken.bind(this));
36
- app.get(`/api/auth/get-user-context`, isAuthorized({ read: true }), this.getUserContext.bind(this));
37
- app.patch(`/api/auth/change-password`, isAuthorized({ update: true }), this.changePassword.bind(this));
38
- app.post(`/api/auth/forgot-password`, this.forgotPassword.bind(this));
39
- app.post(`/api/auth/reset-password`, this.resetPassword.bind(this));
40
- }
41
- async login(req, res) {
42
- const { email, password } = req.body;
43
- if (!email || typeof email !== "string") {
44
- throw new BadRequestError("Missing required fields: email is required.");
42
+ import { Authorize } from "../decorators/authorize.decorator.js";
43
+ let AuthController = (() => {
44
+ let _instanceExtraInitializers = [];
45
+ let _getUserContext_decorators;
46
+ let _changePassword_decorators;
47
+ return class AuthController {
48
+ static {
49
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
50
+ _getUserContext_decorators = [Authorize()];
51
+ _changePassword_decorators = [Authorize()];
52
+ __esDecorate(this, null, _getUserContext_decorators, { kind: "method", name: "getUserContext", static: false, private: false, access: { has: obj => "getUserContext" in obj, get: obj => obj.getUserContext }, metadata: _metadata }, null, _instanceExtraInitializers);
53
+ __esDecorate(this, null, _changePassword_decorators, { kind: "method", name: "changePassword", static: false, private: false, access: { has: obj => "changePassword" in obj, get: obj => obj.changePassword }, metadata: _metadata }, null, _instanceExtraInitializers);
54
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
45
55
  }
46
- if (!password || typeof password !== "string") {
47
- throw new BadRequestError("Missing required fields: password is required.");
56
+ database = __runInitializers(this, _instanceExtraInitializers);
57
+ userService;
58
+ organizationService;
59
+ userSpec;
60
+ publicUserSpec;
61
+ userContextSpec;
62
+ loginResponseSpec;
63
+ constructor(app, database, options = {
64
+ userService: new UserService(database),
65
+ userSpec: UserSpec,
66
+ publicUserSpec: PublicUserSpec,
67
+ publicUserContextSpec: PublicUserContextSpec,
68
+ }) {
69
+ this.database = database;
70
+ this.userSpec = options.userSpec;
71
+ this.publicUserSpec = options.publicUserSpec;
72
+ this.userContextSpec = options.publicUserContextSpec;
73
+ this.loginResponseSpec = createLoginResponseSpec(this.userContextSpec);
74
+ this.userService = options.userService;
75
+ this.organizationService = new OrganizationService(database);
76
+ this.mapRoutes(app);
48
77
  }
49
- let organization = null;
50
- if (config.app.isMultiTenant) {
51
- const referer = req.get("referer") || req.headers.referer;
52
- if (!referer) {
53
- throw new BadRequestError("Missing required fields: referer is required.");
78
+ mapRoutes(app) {
79
+ const authorize = (method) => authorizeMethod(this, method);
80
+ app.post(`/api/auth/login`, this.login.bind(this), this.afterAuth.bind(this));
81
+ app.get(`/api/auth/refresh`, this.requestTokenUsingRefreshToken.bind(this));
82
+ app.get(`/api/auth/get-user-context`, authorize('getUserContext'), this.getUserContext.bind(this));
83
+ app.patch(`/api/auth/change-password`, authorize('changePassword'), this.changePassword.bind(this));
84
+ app.post(`/api/auth/forgot-password`, this.forgotPassword.bind(this));
85
+ app.post(`/api/auth/reset-password`, this.resetPassword.bind(this));
86
+ }
87
+ async login(req, res) {
88
+ const { email, password } = req.body;
89
+ if (!email || typeof email !== "string") {
90
+ throw new BadRequestError("Missing required fields: email is required.");
54
91
  }
55
- organization = await this.organizationService.findByDomain(EmptyUserContext, referer.split("/")[2]);
56
- if (!organization) {
57
- throw new BadRequestError("Missing required fields: organization is required.");
92
+ if (!password || typeof password !== "string") {
93
+ throw new BadRequestError("Missing required fields: password is required.");
58
94
  }
95
+ let organization = null;
96
+ if (config.app.isMultiTenant) {
97
+ const referer = req.get("referer") || req.headers.referer;
98
+ if (!referer) {
99
+ throw new BadRequestError("Missing required fields: referer is required.");
100
+ }
101
+ organization = await this.organizationService.findByDomain(EmptyUserContext, referer.split("/")[2]);
102
+ if (!organization) {
103
+ throw new BadRequestError("Missing required fields: organization is required.");
104
+ }
105
+ }
106
+ res.set("Content-Type", "application/json");
107
+ const deviceId = getAndSetDeviceIdCookie(req, res);
108
+ const loginResponse = await attemptLogin(this.database, email, password, deviceId, organization, this.userService);
109
+ apiUtils.apiResponse(res, 200, { data: loginResponse }, this.loginResponseSpec);
59
110
  }
60
- res.set("Content-Type", "application/json");
61
- const deviceId = getAndSetDeviceIdCookie(req, res);
62
- const loginResponse = await attemptLogin(this.database, email, password, deviceId, organization, this.userService);
63
- apiUtils.apiResponse(res, 200, { data: loginResponse }, this.loginResponseSpec);
64
- }
65
- async requestTokenUsingRefreshToken(req, res) {
66
- const userContext = req.userContext;
67
- if (!userContext) {
68
- throw new BadRequestError("Missing required fields: userContext is required.");
69
- }
70
- const refreshToken = req.query.refreshToken;
71
- if (!refreshToken || typeof refreshToken !== "string") {
72
- throw new BadRequestError("Missing required fields: refreshToken is required.");
73
- }
74
- const deviceId = getDeviceIdFromCookie(req);
75
- const tokens = await requestTokenUsingRefreshToken(this.database, userContext, refreshToken, deviceId);
76
- if (!tokens) {
77
- throw new UnauthenticatedError();
78
- }
79
- apiUtils.apiResponse(res, 200, { data: tokens }, TokenResponseSpec);
80
- }
81
- async getUserContext(req, res) {
82
- const userContext = req.userContext;
83
- apiUtils.apiResponse(res, 200, { data: userContext }, this.userContextSpec);
84
- }
85
- afterAuth(_req, _res, _loginResponse) {
86
- console.log("in afterAuth");
87
- }
88
- async changePassword(req, res) {
89
- const userContext = req.userContext;
90
- if (!userContext) {
91
- throw new BadRequestError("Missing required fields: userContext is required.");
111
+ async requestTokenUsingRefreshToken(req, res) {
112
+ const userContext = req.userContext;
113
+ if (!userContext) {
114
+ throw new BadRequestError("Missing required fields: userContext is required.");
115
+ }
116
+ const refreshToken = req.query.refreshToken;
117
+ if (!refreshToken || typeof refreshToken !== "string") {
118
+ throw new BadRequestError("Missing required fields: refreshToken is required.");
119
+ }
120
+ const deviceId = getDeviceIdFromCookie(req);
121
+ const tokens = await requestTokenUsingRefreshToken(this.database, userContext, refreshToken, deviceId);
122
+ if (!tokens) {
123
+ throw new UnauthenticatedError();
124
+ }
125
+ apiUtils.apiResponse(res, 200, { data: tokens }, TokenResponseSpec);
92
126
  }
93
- const password = req.body?.password;
94
- const validationErrors = entityUtils.validate(this.userSpec, { password: password }, true, passwordValidator);
95
- entityUtils.handleValidationResult(validationErrors, "AuthController.changePassword");
96
- const updateResult = await changePassword(this.database, userContext, password, this.userService);
97
- apiUtils.apiResponse(res, 200, { data: updateResult });
98
- }
99
- async forgotPassword(req, res) {
100
- const email = req.body?.email;
101
- if (!email || typeof email !== "string") {
102
- throw new BadRequestError("Missing required fields: email is required.");
127
+ async getUserContext(req, res) {
128
+ const userContext = req.userContext;
129
+ apiUtils.apiResponse(res, 200, { data: userContext }, this.userContextSpec);
103
130
  }
104
- let referer = req.get("referer") || req.headers.referer;
105
- if (!referer) {
106
- throw new BadRequestError("Missing required fields: referer is required.");
131
+ afterAuth(_req, _res, _loginResponse) {
132
+ console.log("in afterAuth");
107
133
  }
108
- referer = referer.replace(/\/$/, "");
109
- let organization = null;
110
- if (config.app.isMultiTenant) {
111
- organization = await this.organizationService.findByDomain(EmptyUserContext, referer.split("/")[2]);
112
- if (!organization) {
113
- throw new BadRequestError("Missing required fields: organization is required.");
134
+ async changePassword(req, res) {
135
+ const userContext = req.userContext;
136
+ if (!userContext) {
137
+ throw new BadRequestError("Missing required fields: userContext is required.");
114
138
  }
139
+ const password = req.body?.password;
140
+ const validationErrors = entityUtils.validate(this.userSpec, { password: password }, true, passwordValidator);
141
+ entityUtils.handleValidationResult(validationErrors, "AuthController.changePassword");
142
+ const updateResult = await changePassword(this.database, userContext, password, this.userService);
143
+ apiUtils.apiResponse(res, 200, { data: updateResult });
115
144
  }
116
- const userContext = {
117
- ...EmptyUserContext,
118
- organization: organization || undefined,
119
- };
120
- const user = await this.userService.findOne(userContext, {
121
- filters: { email: { eq: email.toLowerCase() } },
122
- });
123
- if (user) {
124
- await sendResetPasswordEmail(this.database, email, referer, organization || undefined);
125
- }
126
- apiUtils.apiResponse(res, 200);
127
- }
128
- async resetPassword(req, res) {
129
- const { email, token, password } = req.body;
130
- if (!email || !token || !password) {
131
- throw new BadRequestError("Missing required fields: email, token, and password are required.");
132
- }
133
- let organization = null;
134
- if (config.app.isMultiTenant) {
135
- const referer = req.get("referer") || req.headers.referer;
145
+ async forgotPassword(req, res) {
146
+ const email = req.body?.email;
147
+ if (!email || typeof email !== "string") {
148
+ throw new BadRequestError("Missing required fields: email is required.");
149
+ }
150
+ let referer = req.get("referer") || req.headers.referer;
136
151
  if (!referer) {
137
152
  throw new BadRequestError("Missing required fields: referer is required.");
138
153
  }
139
- organization = await this.organizationService.findByDomain(EmptyUserContext, referer.split("/")[2]);
140
- if (!organization) {
141
- throw new BadRequestError("Missing required fields: organization is required.");
154
+ referer = referer.replace(/\/$/, "");
155
+ let organization = null;
156
+ if (config.app.isMultiTenant) {
157
+ organization = await this.organizationService.findByDomain(EmptyUserContext, referer.split("/")[2]);
158
+ if (!organization) {
159
+ throw new BadRequestError("Missing required fields: organization is required.");
160
+ }
142
161
  }
162
+ const userContext = {
163
+ ...EmptyUserContext,
164
+ organization: organization || undefined,
165
+ };
166
+ const user = await this.userService.findOne(userContext, {
167
+ filters: { email: { eq: email.toLowerCase() } },
168
+ });
169
+ if (user) {
170
+ await sendResetPasswordEmail(this.database, email, referer, organization || undefined);
171
+ }
172
+ apiUtils.apiResponse(res, 200);
143
173
  }
144
- const response = await resetPassword(this.database, email, token, password, organization, this.userService, this.userSpec);
145
- apiUtils.apiResponse(res, 200, { data: response });
146
- }
147
- }
174
+ async resetPassword(req, res) {
175
+ const { email, token, password } = req.body;
176
+ if (!email || !token || !password) {
177
+ throw new BadRequestError("Missing required fields: email, token, and password are required.");
178
+ }
179
+ let organization = null;
180
+ if (config.app.isMultiTenant) {
181
+ const referer = req.get("referer") || req.headers.referer;
182
+ if (!referer) {
183
+ throw new BadRequestError("Missing required fields: referer is required.");
184
+ }
185
+ organization = await this.organizationService.findByDomain(EmptyUserContext, referer.split("/")[2]);
186
+ if (!organization) {
187
+ throw new BadRequestError("Missing required fields: organization is required.");
188
+ }
189
+ }
190
+ const response = await resetPassword(this.database, email, token, password, organization, this.userService, this.userSpec);
191
+ apiUtils.apiResponse(res, 200, { data: response });
192
+ }
193
+ };
194
+ })();
195
+ export { AuthController };
@@ -1,7 +1,7 @@
1
1
  import type { Application } from "express";
2
2
  import type { IDatabase } from "../databases/models/index.js";
3
- import { type IAuthorization } from "../models/authorization.model.js";
4
3
  import { ApiController } from "./api.controller.js";
4
+ import { IAuthorization } from "@loomcore/common/models";
5
5
  export declare class AuthorizationsController extends ApiController<IAuthorization> {
6
6
  constructor(app: Application, database: IDatabase);
7
7
  }
@@ -1,10 +1,9 @@
1
- import { adminWrites } from "../middleware/index.js";
2
- import { AuthorizationModelSpec, } from "../models/authorization.model.js";
3
- import { MultiTenantApiService } from "../services/multi-tenant-api.service.js";
4
1
  import { ApiController } from "./api.controller.js";
2
+ import { AuthorizationModelSpec } from "@loomcore/common/models";
3
+ import { AuthorizationsService } from "../services/authorizations.service.js";
5
4
  export class AuthorizationsController extends ApiController {
6
5
  constructor(app, database) {
7
- const authorizationService = new MultiTenantApiService(database, "authorizations", "authorization", AuthorizationModelSpec);
8
- super("authorizations", app, authorizationService, adminWrites, "authorization", AuthorizationModelSpec);
6
+ const authorizationService = new AuthorizationsService(database);
7
+ super("authorizations", app, authorizationService, "authorization", AuthorizationModelSpec);
9
8
  }
10
9
  }
@@ -1,6 +1,6 @@
1
1
  import type { Application } from "express";
2
2
  import type { IDatabase } from "../databases/models/index.js";
3
- import { type IFeature } from "../models/feature.model.js";
3
+ import { type IFeature } from "@loomcore/common/models";
4
4
  import { ApiController } from "./api.controller.js";
5
5
  export declare class FeaturesController extends ApiController<IFeature> {
6
6
  constructor(app: Application, database: IDatabase);
@@ -1,10 +1,9 @@
1
- import { adminWrites } from "../middleware/index.js";
2
- import { FeatureModelSpec } from "../models/feature.model.js";
3
- import { MultiTenantApiService } from "../services/multi-tenant-api.service.js";
1
+ import { FeatureModelSpec } from "@loomcore/common/models";
2
+ import { FeaturesService } from "../services/features.service.js";
4
3
  import { ApiController } from "./api.controller.js";
5
4
  export class FeaturesController extends ApiController {
6
5
  constructor(app, database) {
7
- const featureService = new MultiTenantApiService(database, "features", "feature", FeatureModelSpec);
8
- super("features", app, featureService, adminWrites, "feature", FeatureModelSpec, FeatureModelSpec);
6
+ const featureService = new FeaturesService(database);
7
+ super("features", app, featureService, "feature", FeatureModelSpec);
9
8
  }
10
9
  }