@loomcore/api 0.2.56 → 0.2.58

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 (51) hide show
  1. package/LICENSE +201 -201
  2. package/dist/__tests__/common-test.utils.d.ts +2 -2
  3. package/dist/__tests__/common-test.utils.js +12 -11
  4. package/dist/__tests__/mongo-db.test-database.js +5 -1
  5. package/dist/controllers/auth.controller.d.ts +1 -0
  6. package/dist/controllers/auth.controller.js +39 -5
  7. package/dist/controllers/authorizations.controller.js +58 -6
  8. package/dist/controllers/organizations.controller.d.ts +4 -1
  9. package/dist/controllers/organizations.controller.js +39 -12
  10. package/dist/controllers/types.d.ts +2 -2
  11. package/dist/decorators/allow-anonymous.decorator.d.ts +3 -0
  12. package/dist/decorators/allow-anonymous.decorator.js +13 -0
  13. package/dist/decorators/auth-requirement.interface.d.ts +7 -0
  14. package/dist/decorators/auth-requirement.interface.js +1 -0
  15. package/dist/decorators/authorize.decorator.d.ts +8 -12
  16. package/dist/decorators/authorize.decorator.js +18 -29
  17. package/dist/decorators/index.d.ts +3 -1
  18. package/dist/decorators/index.js +2 -1
  19. package/dist/decorators/resolve-allow-anonymous.util.d.ts +1 -0
  20. package/dist/decorators/resolve-allow-anonymous.util.js +6 -0
  21. package/dist/decorators/resolve-auth-decorator-target.util.d.ts +1 -0
  22. package/dist/decorators/resolve-auth-decorator-target.util.js +9 -0
  23. package/dist/decorators/resolve-auth-requirement.util.d.ts +2 -0
  24. package/dist/decorators/resolve-auth-requirement.util.js +6 -0
  25. package/dist/errors/unauthorized.error.d.ts +1 -1
  26. package/dist/errors/unauthorized.error.js +2 -4
  27. package/dist/middleware/authorize/authorize.middleware.d.ts +2 -2
  28. package/dist/middleware/authorize/authorize.middleware.js +17 -12
  29. package/dist/services/generic-api-service/generic-api-service.interface.d.ts +2 -2
  30. package/dist/services/generic-api-service/generic-api.service.d.ts +6 -6
  31. package/dist/services/generic-api-service/generic-api.service.js +22 -33
  32. package/dist/services/multi-tenant-api.service.d.ts +1 -1
  33. package/dist/services/multi-tenant-api.service.js +8 -3
  34. package/dist/services/organization.service.js +5 -3
  35. package/dist/services/tenant-query-decorator.d.ts +1 -1
  36. package/dist/services/tenant-query-decorator.js +10 -7
  37. package/dist/services/utils/audit-for-create.util.js +1 -2
  38. package/dist/services/utils/audit-for-update.util.js +1 -2
  39. package/dist/utils/auth/authorize-method.util.d.ts +1 -1
  40. package/dist/utils/auth/authorize-method.util.js +14 -3
  41. package/dist/utils/auth/impersonate-user.util.d.ts +6 -0
  42. package/dist/utils/auth/impersonate-user.util.js +35 -0
  43. package/dist/utils/auth/index.d.ts +2 -0
  44. package/dist/utils/auth/index.js +2 -0
  45. package/dist/utils/auth/is-admin.util.d.ts +2 -1
  46. package/dist/utils/auth/is-admin.util.js +9 -1
  47. package/dist/utils/auth/is-system-user.util.d.ts +2 -0
  48. package/dist/utils/auth/is-system-user.util.js +4 -0
  49. package/dist/utils/jwt.utils.d.ts +2 -1
  50. package/dist/utils/jwt.utils.js +3 -2
  51. package/package.json +2 -2
@@ -1,9 +1,61 @@
1
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
2
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
3
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
4
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
5
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
6
+ var _, done = false;
7
+ for (var i = decorators.length - 1; i >= 0; i--) {
8
+ var context = {};
9
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
10
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
11
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
12
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
13
+ if (kind === "accessor") {
14
+ if (result === void 0) continue;
15
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
16
+ if (_ = accept(result.get)) descriptor.get = _;
17
+ if (_ = accept(result.set)) descriptor.set = _;
18
+ if (_ = accept(result.init)) initializers.unshift(_);
19
+ }
20
+ else if (_ = accept(result)) {
21
+ if (kind === "field") initializers.unshift(_);
22
+ else descriptor[key] = _;
23
+ }
24
+ }
25
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
26
+ done = true;
27
+ };
28
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
29
+ var useValue = arguments.length > 2;
30
+ for (var i = 0; i < initializers.length; i++) {
31
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
32
+ }
33
+ return useValue ? value : void 0;
34
+ };
1
35
  import { ApiController } from './api.controller.js';
2
36
  import { AuthorizationModelSpec } from '@loomcore/common/models';
3
37
  import { AuthorizationService } from '../services/authorization.service.js';
4
- export class AuthorizationsController extends ApiController {
5
- constructor(app, database) {
6
- const authorizationService = new AuthorizationService(database);
7
- super('authorizations', app, authorizationService, 'authorization', AuthorizationModelSpec);
8
- }
9
- }
38
+ import { Authorize } from '../decorators/authorize.decorator.js';
39
+ let AuthorizationsController = (() => {
40
+ let _classDecorators = [Authorize('admin')];
41
+ let _classDescriptor;
42
+ let _classExtraInitializers = [];
43
+ let _classThis;
44
+ let _classSuper = ApiController;
45
+ var AuthorizationsController = class extends _classSuper {
46
+ static { _classThis = this; }
47
+ static {
48
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
49
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
50
+ AuthorizationsController = _classThis = _classDescriptor.value;
51
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
52
+ __runInitializers(_classThis, _classExtraInitializers);
53
+ }
54
+ constructor(app, database) {
55
+ const authorizationService = new AuthorizationService(database);
56
+ super('authorizations', app, authorizationService, 'authorization', AuthorizationModelSpec);
57
+ }
58
+ };
59
+ return AuthorizationsController = _classThis;
60
+ })();
61
+ export { AuthorizationsController };
@@ -1,4 +1,4 @@
1
- import type { IOrganization } from '@loomcore/common/models';
1
+ import { type IOrganization } from '@loomcore/common/models';
2
2
  import type { Application, NextFunction, Request, Response } from 'express';
3
3
  import type { IDatabase } from '../databases/models/index.js';
4
4
  import { OrganizationService } from '../services/index.js';
@@ -13,4 +13,7 @@ export declare class OrganizationsController extends ApiController<IOrganization
13
13
  getByCode(req: Request<{
14
14
  code: string;
15
15
  }>, res: Response, next: NextFunction): Promise<void>;
16
+ getById(req: Request<{
17
+ id: string;
18
+ }>, res: Response, next: NextFunction): Promise<void>;
16
19
  }
@@ -1,3 +1,10 @@
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
+ };
1
8
  var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
2
9
  function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
3
10
  var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
@@ -25,34 +32,32 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
25
32
  if (target) Object.defineProperty(target, contextIn.name, descriptor);
26
33
  done = true;
27
34
  };
28
- var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
29
- var useValue = arguments.length > 2;
30
- for (var i = 0; i < initializers.length; i++) {
31
- value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
32
- }
33
- return useValue ? value : void 0;
34
- };
35
- import { BadRequestError } from '../errors/index.js';
35
+ import { getSystemUserContext } from '@loomcore/common/models';
36
+ import { BadRequestError, UnauthenticatedError } from '../errors/index.js';
36
37
  import { OrganizationService } from '../services/index.js';
37
38
  import { apiUtils } from '../utils/index.js';
38
39
  import { ApiController } from './api.controller.js';
39
40
  import { Authorize } from '../decorators/authorize.decorator.js';
40
41
  let OrganizationsController = (() => {
41
- let _classDecorators = [Authorize('admin')];
42
+ let _classDecorators = [Authorize({ features: ['admin'], requireMetaOrg: true })];
42
43
  let _classDescriptor;
43
44
  let _classExtraInitializers = [];
44
45
  let _classThis;
45
46
  let _classSuper = ApiController;
47
+ let _instanceExtraInitializers = [];
48
+ let _getById_decorators;
46
49
  var OrganizationsController = class extends _classSuper {
47
50
  static { _classThis = this; }
48
51
  static {
49
52
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
53
+ _getById_decorators = [Authorize('admin')];
54
+ __esDecorate(this, null, _getById_decorators, { kind: "method", name: "getById", static: false, private: false, access: { has: obj => "getById" in obj, get: obj => obj.getById }, metadata: _metadata }, null, _instanceExtraInitializers);
50
55
  __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
51
56
  OrganizationsController = _classThis = _classDescriptor.value;
52
57
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
53
58
  __runInitializers(_classThis, _classExtraInitializers);
54
59
  }
55
- orgService;
60
+ orgService = __runInitializers(this, _instanceExtraInitializers);
56
61
  constructor(app, database) {
57
62
  const orgService = new OrganizationService(database);
58
63
  super('organizations', app, orgService);
@@ -65,9 +70,13 @@ let OrganizationsController = (() => {
65
70
  }
66
71
  async getByName(req, res, next) {
67
72
  const { name } = req.params;
73
+ const userContext = req.userContext;
74
+ if (!userContext) {
75
+ throw new UnauthenticatedError();
76
+ }
68
77
  try {
69
78
  res.set('Content-Type', 'application/json');
70
- const entity = await this.orgService.findOne(req.userContext, {
79
+ const entity = await this.orgService.findOne(userContext, {
71
80
  filters: { name: { contains: name } },
72
81
  });
73
82
  if (!entity)
@@ -81,9 +90,13 @@ let OrganizationsController = (() => {
81
90
  }
82
91
  async getByCode(req, res, next) {
83
92
  const { code } = req.params;
93
+ const userContext = req.userContext;
94
+ if (!userContext) {
95
+ throw new UnauthenticatedError();
96
+ }
84
97
  try {
85
98
  res.set('Content-Type', 'application/json');
86
- const entity = await this.orgService.findOne(req.userContext, {
99
+ const entity = await this.orgService.findOne(userContext, {
87
100
  filters: { code: { eq: code } },
88
101
  });
89
102
  if (!entity)
@@ -95,6 +108,20 @@ let OrganizationsController = (() => {
95
108
  return;
96
109
  }
97
110
  }
111
+ async getById(req, res, next) {
112
+ const userContext = req.userContext;
113
+ if (!userContext) {
114
+ throw new UnauthenticatedError();
115
+ }
116
+ if (String(req.params.id) === String(userContext.user._orgId)) {
117
+ return super.getById(req, res, next);
118
+ }
119
+ const systemUserContext = getSystemUserContext();
120
+ if (String(userContext.user._orgId) === String(systemUserContext.user._orgId)) {
121
+ return super.getById(req, res, next);
122
+ }
123
+ throw new BadRequestError('User is not authorized to access this organization');
124
+ }
98
125
  };
99
126
  return OrganizationsController = _classThis;
100
127
  })();
@@ -1,7 +1,7 @@
1
1
  import { IEntity, IQueryOptions, IUserContext } from '@loomcore/common/models';
2
2
  import { Operation } from '../databases/index.js';
3
- export type PrepareQueryCustomFunction = (userContext: IUserContext | undefined, queryObject: IQueryOptions, operations: Operation[]) => {
4
- queryObject: IQueryOptions;
3
+ export type PrepareQueryCustomFunction = (userContext: IUserContext | undefined, queryOptions: IQueryOptions, operations: Operation[]) => {
4
+ queryOptions: IQueryOptions;
5
5
  operations: Operation[];
6
6
  };
7
7
  export type PostProcessEntityCustomFunction<TIn extends IEntity, TOut extends IEntity> = (userContext: IUserContext, entity: TIn) => TOut;
@@ -0,0 +1,3 @@
1
+ export declare function getValue(entity: any): boolean | undefined;
2
+ export declare function hasAllowAnonymous(entity: any): boolean;
3
+ export declare function AllowAnonymous(): (target: Function, _context: ClassDecoratorContext | ClassMethodDecoratorContext) => void;
@@ -0,0 +1,13 @@
1
+ const ALLOW_ANONYMOUS_METADATA_KEY = Symbol('auth-decorator:allow-anonymous');
2
+ export function getValue(entity) {
3
+ const value = Reflect.getMetadata(ALLOW_ANONYMOUS_METADATA_KEY, entity);
4
+ return value;
5
+ }
6
+ export function hasAllowAnonymous(entity) {
7
+ return Reflect.hasOwnMetadata(ALLOW_ANONYMOUS_METADATA_KEY, entity);
8
+ }
9
+ export function AllowAnonymous() {
10
+ return function (target, _context) {
11
+ Reflect.defineMetadata(ALLOW_ANONYMOUS_METADATA_KEY, true, target);
12
+ };
13
+ }
@@ -0,0 +1,7 @@
1
+ export type MatchMode = 'all' | 'any';
2
+ export interface IAuthRequirement {
3
+ features?: string[];
4
+ matchMode?: MatchMode;
5
+ denyOnImpersonation?: boolean;
6
+ requireMetaOrg?: boolean;
7
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,13 +1,9 @@
1
1
  import 'reflect-metadata';
2
- export declare const AUTH_METADATA_KEY: unique symbol;
3
- export type MatchMode = 'all' | 'any';
4
- export interface AuthRequirement {
5
- features: string[];
6
- mode: MatchMode;
7
- allowAnonymous?: boolean;
8
- }
9
- export declare function Authorize(features?: string | string[], options?: {
10
- all?: boolean;
11
- }): (value: Function, _context: ClassDecoratorContext | ClassMethodDecoratorContext) => void;
12
- export declare function AllowAnonymous(): (value: Function, _context: ClassDecoratorContext | ClassMethodDecoratorContext) => void;
13
- export declare function resolveAuthRequirement(controllerConstructor: Function, prototype: any, propertyKey: string): AuthRequirement | undefined;
2
+ import { IAuthRequirement, MatchMode } from './index.js';
3
+ export declare function getAuthRequirement(entity: any): IAuthRequirement | undefined;
4
+ export declare function hasAuthRequirement(entity: any): boolean;
5
+ type AuthorizeDecorator = (target: Function, _context: ClassDecoratorContext | ClassMethodDecoratorContext) => void;
6
+ export declare function Authorize(): AuthorizeDecorator;
7
+ export declare function Authorize(authRequirement: IAuthRequirement): AuthorizeDecorator;
8
+ export declare function Authorize(features: string | string[], matchMode?: MatchMode): AuthorizeDecorator;
9
+ export {};
@@ -1,33 +1,22 @@
1
1
  import 'reflect-metadata';
2
- export const AUTH_METADATA_KEY = Symbol('authorize:features');
3
- export function Authorize(features = [], options = {}) {
4
- const requirement = {
5
- features: Array.isArray(features) ? features : [features],
6
- mode: options.all ? 'all' : 'any',
7
- };
8
- return function (value, _context) {
9
- Reflect.defineMetadata(AUTH_METADATA_KEY, requirement, value);
10
- };
2
+ const AUTHORIZE_METADATA_KEY = Symbol('auth-decorator:authorize');
3
+ export function getAuthRequirement(entity) {
4
+ const value = Reflect.getMetadata(AUTHORIZE_METADATA_KEY, entity);
5
+ return value;
11
6
  }
12
- export function AllowAnonymous() {
13
- return function (value, _context) {
14
- const metadataValue = {
15
- features: [],
16
- mode: 'any',
17
- allowAnonymous: true,
18
- };
19
- Reflect.defineMetadata(AUTH_METADATA_KEY, metadataValue, value);
20
- };
7
+ export function hasAuthRequirement(entity) {
8
+ return Reflect.hasOwnMetadata(AUTHORIZE_METADATA_KEY, entity);
21
9
  }
22
- export function resolveAuthRequirement(controllerConstructor, prototype, propertyKey) {
23
- const method = prototype[propertyKey];
24
- if (typeof method === 'function') {
25
- const methodLevel = Reflect.getMetadata(AUTH_METADATA_KEY, method);
26
- if (methodLevel)
27
- return methodLevel;
28
- }
29
- const classLevel = Reflect.getMetadata(AUTH_METADATA_KEY, controllerConstructor);
30
- if (classLevel)
31
- return classLevel;
32
- return undefined;
10
+ export function Authorize(authRequirementOrFeatures, matchMode) {
11
+ return function (target, _context) {
12
+ let authRequirement;
13
+ if (typeof authRequirementOrFeatures === 'string' || Array.isArray(authRequirementOrFeatures)) {
14
+ const featureList = Array.isArray(authRequirementOrFeatures) ? authRequirementOrFeatures : [authRequirementOrFeatures];
15
+ authRequirement = { features: featureList, matchMode: matchMode ?? 'any' };
16
+ }
17
+ else {
18
+ authRequirement = authRequirementOrFeatures;
19
+ }
20
+ Reflect.defineMetadata(AUTHORIZE_METADATA_KEY, authRequirement, target);
21
+ };
33
22
  }
@@ -1 +1,3 @@
1
- export { Authorize, AllowAnonymous } from './authorize.decorator.js';
1
+ export { Authorize } from './authorize.decorator.js';
2
+ export { AllowAnonymous } from './allow-anonymous.decorator.js';
3
+ export type { IAuthRequirement, MatchMode } from './auth-requirement.interface.js';
@@ -1 +1,2 @@
1
- export { Authorize, AllowAnonymous } from './authorize.decorator.js';
1
+ export { Authorize } from './authorize.decorator.js';
2
+ export { AllowAnonymous } from './allow-anonymous.decorator.js';
@@ -0,0 +1 @@
1
+ export declare function resolveAllowAnonymous(controllerConstructor: Function, prototype: any, propertyKey: string): boolean | undefined;
@@ -0,0 +1,6 @@
1
+ import { getValue } from './allow-anonymous.decorator.js';
2
+ import { resolveAuthDecoratorTarget } from './resolve-auth-decorator-target.util.js';
3
+ export function resolveAllowAnonymous(controllerConstructor, prototype, propertyKey) {
4
+ const target = resolveAuthDecoratorTarget(controllerConstructor, prototype, propertyKey);
5
+ return getValue(target);
6
+ }
@@ -0,0 +1 @@
1
+ export declare function resolveAuthDecoratorTarget(controllerConstructor: Function, prototype: any, propertyKey: string): Function;
@@ -0,0 +1,9 @@
1
+ import { hasAllowAnonymous } from './allow-anonymous.decorator.js';
2
+ import { hasAuthRequirement } from './authorize.decorator.js';
3
+ export function resolveAuthDecoratorTarget(controllerConstructor, prototype, propertyKey) {
4
+ const method = prototype?.[propertyKey];
5
+ if (typeof method === 'function' && (hasAuthRequirement(method) || hasAllowAnonymous(method))) {
6
+ return method;
7
+ }
8
+ return controllerConstructor;
9
+ }
@@ -0,0 +1,2 @@
1
+ import { IAuthRequirement } from './auth-requirement.interface.js';
2
+ export declare function resolveAuthRequirement(controllerConstructor: Function, prototype: any, propertyKey: string): IAuthRequirement | undefined;
@@ -0,0 +1,6 @@
1
+ import { getAuthRequirement } from './authorize.decorator.js';
2
+ import { resolveAuthDecoratorTarget } from './resolve-auth-decorator-target.util.js';
3
+ export function resolveAuthRequirement(controllerConstructor, prototype, propertyKey) {
4
+ const target = resolveAuthDecoratorTarget(controllerConstructor, prototype, propertyKey);
5
+ return getAuthRequirement(target);
6
+ }
@@ -1,7 +1,7 @@
1
1
  import { CustomError } from '@loomcore/common/errors';
2
2
  export declare class UnauthorizedError extends CustomError {
3
3
  statusCode: number;
4
- constructor(missing?: string[]);
4
+ constructor(message?: string);
5
5
  serializeErrors(): {
6
6
  message: string;
7
7
  }[];
@@ -1,10 +1,8 @@
1
1
  import { CustomError } from '@loomcore/common/errors';
2
2
  export class UnauthorizedError extends CustomError {
3
3
  statusCode = 403;
4
- constructor(missing) {
5
- super(missing?.length
6
- ? `Missing required feature(s): ${missing.join(', ')}`
7
- : 'Unauthorized');
4
+ constructor(message) {
5
+ super(message ?? 'Unauthorized');
8
6
  Object.setPrototypeOf(this, UnauthorizedError.prototype);
9
7
  }
10
8
  serializeErrors() {
@@ -1,3 +1,3 @@
1
1
  import type { RequestHandler } from 'express';
2
- import { AuthRequirement } from '../../decorators/authorize.decorator.js';
3
- export declare function buildAuthGuard(requirement: AuthRequirement | undefined): RequestHandler;
2
+ import { IAuthRequirement } from '../../decorators/auth-requirement.interface.js';
3
+ export declare function buildAuthGuard(requirement: IAuthRequirement | undefined): RequestHandler;
@@ -1,23 +1,28 @@
1
1
  import { UnauthorizedError } from '../../errors/index.js';
2
2
  import { authenticateRequest } from './authenticate-request.js';
3
+ import { getSystemUserContext } from '@loomcore/common/models';
3
4
  export function buildAuthGuard(requirement) {
4
5
  return (req, _res, next) => {
5
- if (requirement?.allowAnonymous) {
6
- return next();
7
- }
8
6
  const userContext = authenticateRequest(req);
9
7
  req.userContext = userContext;
10
- if (!requirement || requirement.features.length === 0) {
11
- return next();
8
+ if (requirement?.denyOnImpersonation && userContext.impersonatorId !== undefined) {
9
+ throw new UnauthorizedError('Unauthorized: Endpoint requires non-impersonated user.');
10
+ }
11
+ const systemUserContext = getSystemUserContext();
12
+ if (requirement?.requireMetaOrg && userContext.user._orgId !== systemUserContext.user._orgId) {
13
+ throw new UnauthorizedError('Unauthorized: Endpoint requires meta organization user.');
12
14
  }
13
- const userFeatures = new Set(userContext.features);
14
- const hasAccess = requirement.mode === 'all'
15
- ? requirement.features.every((f) => userFeatures.has(f))
16
- : requirement.features.some((f) => userFeatures.has(f));
17
- if (!hasAccess) {
18
- const missing = requirement.features.filter((f) => !userFeatures.has(f));
19
- throw new UnauthorizedError(missing);
15
+ if (requirement?.features?.length) {
16
+ const userFeatures = new Set(userContext.features);
17
+ const hasFeatures = requirement.matchMode === 'all'
18
+ ? requirement.features?.every((f) => userFeatures.has(f))
19
+ : requirement.features?.some((f) => userFeatures.has(f));
20
+ if (!hasFeatures) {
21
+ const missing = requirement.features?.filter((f) => !userFeatures.has(f));
22
+ throw new UnauthorizedError(`Unauthorized: Missing required feature(s): ${missing?.join(', ')}`);
23
+ }
20
24
  }
21
25
  next();
22
26
  };
23
27
  }
28
+ ;
@@ -8,8 +8,8 @@ export interface IGenericApiService<T extends IEntity> {
8
8
  getModelSpec(): IModelSpec;
9
9
  validate(doc: any, isPartial?: boolean): ValueError[] | null;
10
10
  validateMany(docs: any[], isPartial?: boolean): ValueError[] | null;
11
- prepareQuery(userContext: IUserContext | undefined, queryObject: IQueryOptions, operations: Operation[]): {
12
- queryObject: IQueryOptions;
11
+ prepareQuery(userContext: IUserContext | undefined, queryOptions: IQueryOptions, operations: Operation[]): {
12
+ queryOptions: IQueryOptions;
13
13
  operations: Operation[];
14
14
  };
15
15
  preProcessEntity(userContext: IUserContext, entity: Partial<T>, isCreate: boolean, allowId: boolean): Promise<Partial<T>>;
@@ -15,8 +15,8 @@ export declare class GenericApiService<T extends IEntity> implements IGenericApi
15
15
  getModelSpec(): IModelSpec;
16
16
  getAll(userContext: IUserContext): Promise<T[]>;
17
17
  getAll<TCustom extends IEntity>(userContext: IUserContext, prepareQueryCustom: PrepareQueryCustomFunction, postProcessEntityCustom: PostProcessEntityCustomFunction<T, TCustom>): Promise<TCustom[]>;
18
- prepareQuery(userContext: IUserContext | undefined, queryObject: IQueryOptions, operations: Operation[]): {
19
- queryObject: IQueryOptions;
18
+ prepareQuery(userContext: IUserContext | undefined, queryOptions: IQueryOptions, operations: Operation[]): {
19
+ queryOptions: IQueryOptions;
20
20
  operations: Operation[];
21
21
  };
22
22
  validate(doc: any, isPartial?: boolean): ValueError[] | null;
@@ -35,9 +35,9 @@ export declare class GenericApiService<T extends IEntity> implements IGenericApi
35
35
  fullUpdateById(userContext: IUserContext, id: AppIdType, entity: T): Promise<T>;
36
36
  partialUpdateById(userContext: IUserContext, id: AppIdType, entity: Partial<T>): Promise<T>;
37
37
  partialUpdateByIdWithoutPreAndPostProcessing(userContext: IUserContext, id: AppIdType, entity: Partial<T>): Promise<T>;
38
- update(userContext: IUserContext, queryObject: IQueryOptions, entity: Partial<T>): Promise<T[]>;
38
+ update(userContext: IUserContext, queryOptions: IQueryOptions, entity: Partial<T>): Promise<T[]>;
39
39
  deleteById(userContext: IUserContext, id: AppIdType): Promise<DeleteResult>;
40
- deleteMany(userContext: IUserContext, queryObject: IQueryOptions): Promise<DeleteResult>;
41
- find(userContext: IUserContext, queryObject: IQueryOptions): Promise<T[]>;
42
- findOne(userContext: IUserContext, queryObject: IQueryOptions): Promise<T | null>;
40
+ deleteMany(userContext: IUserContext, queryOptions: IQueryOptions): Promise<DeleteResult>;
41
+ find(userContext: IUserContext, queryOptions: IQueryOptions): Promise<T[]>;
42
+ findOne(userContext: IUserContext, queryOptions: IQueryOptions): Promise<T | null>;
43
43
  }
@@ -24,9 +24,7 @@ export class GenericApiService {
24
24
  if (prepareQueryCustom) {
25
25
  operations = prepareQueryCustom(userContext, {}, []).operations;
26
26
  }
27
- else {
28
- operations = this.prepareQuery(userContext, {}, []).operations;
29
- }
27
+ operations = this.prepareQuery(userContext, {}, operations).operations;
30
28
  const entities = await this.database.getAll(operations, this.pluralResourceName);
31
29
  return entities.map((entity) => {
32
30
  const dbProcessed = this.postProcessEntity(userContext, entity);
@@ -36,8 +34,8 @@ export class GenericApiService {
36
34
  return dbProcessed;
37
35
  });
38
36
  }
39
- prepareQuery(userContext, queryObject, operations) {
40
- return { queryObject, operations };
37
+ prepareQuery(userContext, queryOptions, operations) {
38
+ return { queryOptions, operations };
41
39
  }
42
40
  validate(doc, isPartial = false) {
43
41
  return entityUtils.validate(this.modelSpec, doc, isPartial);
@@ -77,15 +75,12 @@ export class GenericApiService {
77
75
  return this.database.postProcessEntity(entity, this.modelSpec.fullSchema);
78
76
  }
79
77
  async get(userContext, queryOptions = { ...DefaultQueryOptions }, prepareQueryCustom, postProcessEntityCustom) {
80
- const preparedOptions = this.prepareQueryOptions(userContext, queryOptions);
81
78
  let operations = [];
82
79
  if (prepareQueryCustom) {
83
- operations = prepareQueryCustom(userContext, {}, []).operations;
80
+ ({ queryOptions, operations } = prepareQueryCustom(userContext, {}, []));
84
81
  }
85
- else {
86
- operations = this.prepareQuery(userContext, {}, []).operations;
87
- }
88
- const pagedResult = await this.database.get(operations, preparedOptions, this.modelSpec, this.pluralResourceName);
82
+ ({ queryOptions, operations } = this.prepareQuery(userContext, queryOptions, operations));
83
+ const pagedResult = await this.database.get(operations, queryOptions, this.modelSpec, this.pluralResourceName);
89
84
  const transformedEntities = (pagedResult.entities || []).map((entity) => {
90
85
  const transformedEntity = this.postProcessEntity(userContext, entity);
91
86
  if (postProcessEntityCustom) {
@@ -103,18 +98,12 @@ export class GenericApiService {
103
98
  }
104
99
  async getById(userContext, id, prepareQueryCustom, postProcessEntityCustom) {
105
100
  let operations = [];
106
- let queryObject = {};
101
+ let queryOptions = {};
107
102
  if (prepareQueryCustom) {
108
- const result = prepareQueryCustom(userContext, {}, []);
109
- operations = result.operations;
110
- queryObject = result.queryObject;
111
- }
112
- else {
113
- const result = this.prepareQuery(userContext, {}, []);
114
- operations = result.operations;
115
- queryObject = result.queryObject;
103
+ ({ operations, queryOptions } = prepareQueryCustom(userContext, {}, []));
116
104
  }
117
- const entity = await this.database.getById(operations, queryObject, id, this.pluralResourceName);
105
+ ({ operations, queryOptions } = this.prepareQuery(userContext, queryOptions, operations));
106
+ const entity = await this.database.getById(operations, queryOptions, id, this.pluralResourceName);
118
107
  if (!entity) {
119
108
  throw new IdNotFoundError();
120
109
  }
@@ -155,14 +144,14 @@ export class GenericApiService {
155
144
  return [];
156
145
  }
157
146
  const preparedEntities = await Promise.all(entities.map((entity) => this.preProcessEntity(userContext, entity, false, true)));
158
- const { queryObject, operations } = this.prepareQuery(userContext, {}, []);
159
- const rawUpdatedEntities = await this.database.batchUpdate(preparedEntities, operations, queryObject, this.pluralResourceName);
147
+ const { queryOptions, operations } = this.prepareQuery(userContext, {}, []);
148
+ const rawUpdatedEntities = await this.database.batchUpdate(preparedEntities, operations, queryOptions, this.pluralResourceName);
160
149
  const updatedEntities = rawUpdatedEntities.map((entity) => this.postProcessEntity(userContext, entity));
161
150
  return updatedEntities;
162
151
  }
163
152
  async fullUpdateById(userContext, id, entity) {
164
- const { operations, queryObject } = this.prepareQuery(userContext, {}, []);
165
- const existingEntity = await this.database.getById(operations, queryObject, id, this.pluralResourceName);
153
+ const { operations, queryOptions } = this.prepareQuery(userContext, {}, []);
154
+ const existingEntity = await this.database.getById(operations, queryOptions, id, this.pluralResourceName);
166
155
  if (!existingEntity) {
167
156
  throw new IdNotFoundError();
168
157
  }
@@ -188,8 +177,8 @@ export class GenericApiService {
188
177
  const rawUpdatedEntity = await this.database.partialUpdateById([], id, preparedEntity, this.pluralResourceName);
189
178
  return this.database.postProcessEntity(rawUpdatedEntity, this.modelSpec.fullSchema);
190
179
  }
191
- async update(userContext, queryObject, entity) {
192
- const { queryObject: preparedQuery, operations } = this.prepareQuery(userContext, queryObject, []);
180
+ async update(userContext, queryOptions, entity) {
181
+ const { queryOptions: preparedQuery, operations } = this.prepareQuery(userContext, queryOptions, []);
193
182
  const preparedEntity = await this.preProcessEntity(userContext, entity, false, true);
194
183
  const rawUpdatedEntities = await this.database.update(preparedQuery, preparedEntity, operations, this.pluralResourceName);
195
184
  const updatedEntities = rawUpdatedEntities.map((entity) => this.postProcessEntity(userContext, entity));
@@ -202,18 +191,18 @@ export class GenericApiService {
202
191
  }
203
192
  return deleteResult;
204
193
  }
205
- async deleteMany(userContext, queryObject) {
206
- const { queryObject: preparedQuery, operations } = this.prepareQuery(userContext, queryObject, []);
194
+ async deleteMany(userContext, queryOptions) {
195
+ const { queryOptions: preparedQuery, operations } = this.prepareQuery(userContext, queryOptions, []);
207
196
  const deleteResult = await this.database.deleteMany(preparedQuery, this.pluralResourceName);
208
197
  return deleteResult;
209
198
  }
210
- async find(userContext, queryObject) {
211
- const { queryObject: preparedQuery, operations } = this.prepareQuery(userContext, queryObject, []);
199
+ async find(userContext, queryOptions) {
200
+ const { queryOptions: preparedQuery, operations } = this.prepareQuery(userContext, queryOptions, []);
212
201
  const rawEntities = await this.database.find(preparedQuery, this.pluralResourceName);
213
202
  return rawEntities.map((entity) => this.postProcessEntity(userContext, entity));
214
203
  }
215
- async findOne(userContext, queryObject) {
216
- const { queryObject: preparedQuery, operations } = this.prepareQuery(userContext, queryObject, []);
204
+ async findOne(userContext, queryOptions) {
205
+ const { queryOptions: preparedQuery, operations } = this.prepareQuery(userContext, queryOptions, []);
217
206
  const rawEntity = await this.database.findOne(preparedQuery, this.pluralResourceName);
218
207
  return rawEntity ? this.postProcessEntity(userContext, rawEntity) : null;
219
208
  }
@@ -6,7 +6,7 @@ export declare class MultiTenantApiService<T extends IEntity> extends GenericApi
6
6
  private tenantDecorator?;
7
7
  constructor(database: IDatabase, pluralResourceName: string, singularResourceName: string, modelSpec: IModelSpec);
8
8
  prepareQuery(userContext: IUserContext, queryOptions: IQueryOptions, operations: Operation[]): {
9
- queryObject: IQueryOptions;
9
+ queryOptions: IQueryOptions;
10
10
  operations: Operation[];
11
11
  };
12
12
  preProcessEntity(userContext: IUserContext, entity: Partial<T>, isCreate: boolean, allowId?: boolean): Promise<Partial<T>>;