@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.
- package/LICENSE +201 -201
- package/dist/__tests__/common-test.utils.d.ts +2 -2
- package/dist/__tests__/common-test.utils.js +12 -11
- package/dist/__tests__/mongo-db.test-database.js +5 -1
- package/dist/controllers/auth.controller.d.ts +1 -0
- package/dist/controllers/auth.controller.js +39 -5
- package/dist/controllers/authorizations.controller.js +58 -6
- package/dist/controllers/organizations.controller.d.ts +4 -1
- package/dist/controllers/organizations.controller.js +39 -12
- package/dist/controllers/types.d.ts +2 -2
- package/dist/decorators/allow-anonymous.decorator.d.ts +3 -0
- package/dist/decorators/allow-anonymous.decorator.js +13 -0
- package/dist/decorators/auth-requirement.interface.d.ts +7 -0
- package/dist/decorators/auth-requirement.interface.js +1 -0
- package/dist/decorators/authorize.decorator.d.ts +8 -12
- package/dist/decorators/authorize.decorator.js +18 -29
- package/dist/decorators/index.d.ts +3 -1
- package/dist/decorators/index.js +2 -1
- package/dist/decorators/resolve-allow-anonymous.util.d.ts +1 -0
- package/dist/decorators/resolve-allow-anonymous.util.js +6 -0
- package/dist/decorators/resolve-auth-decorator-target.util.d.ts +1 -0
- package/dist/decorators/resolve-auth-decorator-target.util.js +9 -0
- package/dist/decorators/resolve-auth-requirement.util.d.ts +2 -0
- package/dist/decorators/resolve-auth-requirement.util.js +6 -0
- package/dist/errors/unauthorized.error.d.ts +1 -1
- package/dist/errors/unauthorized.error.js +2 -4
- package/dist/middleware/authorize/authorize.middleware.d.ts +2 -2
- package/dist/middleware/authorize/authorize.middleware.js +17 -12
- package/dist/services/generic-api-service/generic-api-service.interface.d.ts +2 -2
- package/dist/services/generic-api-service/generic-api.service.d.ts +6 -6
- package/dist/services/generic-api-service/generic-api.service.js +22 -33
- package/dist/services/multi-tenant-api.service.d.ts +1 -1
- package/dist/services/multi-tenant-api.service.js +8 -3
- package/dist/services/organization.service.js +5 -3
- package/dist/services/tenant-query-decorator.d.ts +1 -1
- package/dist/services/tenant-query-decorator.js +10 -7
- package/dist/services/utils/audit-for-create.util.js +1 -2
- package/dist/services/utils/audit-for-update.util.js +1 -2
- package/dist/utils/auth/authorize-method.util.d.ts +1 -1
- package/dist/utils/auth/authorize-method.util.js +14 -3
- package/dist/utils/auth/impersonate-user.util.d.ts +6 -0
- package/dist/utils/auth/impersonate-user.util.js +35 -0
- package/dist/utils/auth/index.d.ts +2 -0
- package/dist/utils/auth/index.js +2 -0
- package/dist/utils/auth/is-admin.util.d.ts +2 -1
- package/dist/utils/auth/is-admin.util.js +9 -1
- package/dist/utils/auth/is-system-user.util.d.ts +2 -0
- package/dist/utils/auth/is-system-user.util.js +4 -0
- package/dist/utils/jwt.utils.d.ts +2 -1
- package/dist/utils/jwt.utils.js +3 -2
- 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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
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
|
-
|
|
29
|
-
|
|
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(
|
|
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(
|
|
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,
|
|
4
|
-
|
|
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,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 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export
|
|
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
|
-
|
|
3
|
-
export function
|
|
4
|
-
const
|
|
5
|
-
|
|
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
|
|
13
|
-
return
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
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';
|
package/dist/decorators/index.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export { Authorize
|
|
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,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,10 +1,8 @@
|
|
|
1
1
|
import { CustomError } from '@loomcore/common/errors';
|
|
2
2
|
export class UnauthorizedError extends CustomError {
|
|
3
3
|
statusCode = 403;
|
|
4
|
-
constructor(
|
|
5
|
-
super(
|
|
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 {
|
|
3
|
-
export declare function buildAuthGuard(requirement:
|
|
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 (
|
|
11
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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,
|
|
12
|
-
|
|
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,
|
|
19
|
-
|
|
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,
|
|
38
|
+
update(userContext: IUserContext, queryOptions: IQueryOptions, entity: Partial<T>): Promise<T[]>;
|
|
39
39
|
deleteById(userContext: IUserContext, id: AppIdType): Promise<DeleteResult>;
|
|
40
|
-
deleteMany(userContext: IUserContext,
|
|
41
|
-
find(userContext: IUserContext,
|
|
42
|
-
findOne(userContext: IUserContext,
|
|
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
|
-
|
|
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,
|
|
40
|
-
return {
|
|
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, {}, [])
|
|
80
|
+
({ queryOptions, operations } = prepareQueryCustom(userContext, {}, []));
|
|
84
81
|
}
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
101
|
+
let queryOptions = {};
|
|
107
102
|
if (prepareQueryCustom) {
|
|
108
|
-
|
|
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
|
-
|
|
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 {
|
|
159
|
-
const rawUpdatedEntities = await this.database.batchUpdate(preparedEntities, operations,
|
|
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,
|
|
165
|
-
const existingEntity = await this.database.getById(operations,
|
|
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,
|
|
192
|
-
const {
|
|
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,
|
|
206
|
-
const {
|
|
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,
|
|
211
|
-
const {
|
|
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,
|
|
216
|
-
const {
|
|
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
|
-
|
|
9
|
+
queryOptions: IQueryOptions;
|
|
10
10
|
operations: Operation[];
|
|
11
11
|
};
|
|
12
12
|
preProcessEntity(userContext: IUserContext, entity: Partial<T>, isCreate: boolean, allowId?: boolean): Promise<Partial<T>>;
|