@alanszp/access-list 10.2.0
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/.gitignore +3 -0
- package/.npmignore +3 -0
- package/LICENSE +21 -0
- package/dist/clients/AccessListClient.d.ts +32 -0
- package/dist/clients/AccessListClient.js +90 -0
- package/dist/clients/AccessListClient.js.map +1 -0
- package/dist/errors/NoPermissionError.d.ts +7 -0
- package/dist/errors/NoPermissionError.js +20 -0
- package/dist/errors/NoPermissionError.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/inputs/AccessListInput.d.ts +8 -0
- package/dist/inputs/AccessListInput.js +26 -0
- package/dist/inputs/AccessListInput.js.map +1 -0
- package/dist/repositories/accessListRepository.d.ts +20 -0
- package/dist/repositories/accessListRepository.js +76 -0
- package/dist/repositories/accessListRepository.js.map +1 -0
- package/package.json +36 -0
- package/src/clients/AccessListClient.ts +104 -0
- package/src/errors/NoPermissionError.ts +19 -0
- package/src/index.ts +4 -0
- package/src/inputs/AccessListInput.ts +18 -0
- package/src/repositories/accessListRepository.ts +94 -0
- package/tsconfig.json +16 -0
package/.gitignore
ADDED
package/.npmignore
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Alan Szpigiel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { JWTUser } from "@alanszp/jwt";
|
|
2
|
+
export declare enum RoleCode {
|
|
3
|
+
ADMIN = "admin",
|
|
4
|
+
HRBP = "hrbp",
|
|
5
|
+
MANAGER = "manager",
|
|
6
|
+
INTEGRATIONS = "integrations",
|
|
7
|
+
VIEWER = "viewer"
|
|
8
|
+
}
|
|
9
|
+
export declare class AccessListClient {
|
|
10
|
+
protected organizationReference: string;
|
|
11
|
+
protected roles: string[];
|
|
12
|
+
protected segmentReference: string | null;
|
|
13
|
+
protected addFormerEmployees: boolean;
|
|
14
|
+
constructor({ roles, segmentReference, organizationReference, }: Pick<JWTUser, "roles" | "segmentReference" | "organizationReference">, addFormerEmployees?: boolean);
|
|
15
|
+
hasAccessToAll(): boolean;
|
|
16
|
+
isAdmin(): boolean;
|
|
17
|
+
needsToValidateAccess(): boolean;
|
|
18
|
+
hasAccessToSomeEmployees(employeeReference: string[]): Promise<boolean>;
|
|
19
|
+
hasAccessTo(employeeReference: string): Promise<boolean>;
|
|
20
|
+
shouldAddFormerEmployees(): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* @param employeeReference Employee reference to validate access to
|
|
23
|
+
* @throws {NoPermissionError} if user has no access to employee
|
|
24
|
+
*/
|
|
25
|
+
validateAccessTo(employeeReference: string): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* @throws {ModelValidationError} if segment reference is not present
|
|
28
|
+
* @returns Segment reference
|
|
29
|
+
*/
|
|
30
|
+
getSegmentReferenceOrFail(): string;
|
|
31
|
+
getSegmentReference(): string | null;
|
|
32
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.AccessListClient = exports.RoleCode = void 0;
|
|
13
|
+
const NoPermissionError_1 = require("../errors/NoPermissionError");
|
|
14
|
+
const accessListRepository_1 = require("../repositories/accessListRepository");
|
|
15
|
+
const validations_1 = require("@alanszp/validations");
|
|
16
|
+
const lodash_1 = require("lodash");
|
|
17
|
+
var RoleCode;
|
|
18
|
+
(function (RoleCode) {
|
|
19
|
+
RoleCode["ADMIN"] = "admin";
|
|
20
|
+
RoleCode["HRBP"] = "hrbp";
|
|
21
|
+
RoleCode["MANAGER"] = "manager";
|
|
22
|
+
RoleCode["INTEGRATIONS"] = "integrations";
|
|
23
|
+
RoleCode["VIEWER"] = "viewer";
|
|
24
|
+
})(RoleCode = exports.RoleCode || (exports.RoleCode = {}));
|
|
25
|
+
class AccessListClient {
|
|
26
|
+
constructor({ roles, segmentReference, organizationReference, }, addFormerEmployees) {
|
|
27
|
+
this.organizationReference = organizationReference;
|
|
28
|
+
this.roles = roles;
|
|
29
|
+
this.segmentReference = segmentReference;
|
|
30
|
+
this.addFormerEmployees = addFormerEmployees !== null && addFormerEmployees !== void 0 ? addFormerEmployees : false;
|
|
31
|
+
}
|
|
32
|
+
hasAccessToAll() {
|
|
33
|
+
return this.isAdmin();
|
|
34
|
+
}
|
|
35
|
+
isAdmin() {
|
|
36
|
+
return this.roles.includes(RoleCode.ADMIN);
|
|
37
|
+
}
|
|
38
|
+
needsToValidateAccess() {
|
|
39
|
+
return !this.hasAccessToAll();
|
|
40
|
+
}
|
|
41
|
+
hasAccessToSomeEmployees(employeeReference) {
|
|
42
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
43
|
+
if (this.hasAccessToAll())
|
|
44
|
+
return true;
|
|
45
|
+
if (!this.segmentReference)
|
|
46
|
+
return false;
|
|
47
|
+
const hasAccess = yield (0, accessListRepository_1.hasAccessToSomeEmployees)(this.segmentReference, (0, lodash_1.castArray)(employeeReference), this.shouldAddFormerEmployees());
|
|
48
|
+
return hasAccess;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
hasAccessTo(employeeReference) {
|
|
52
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
53
|
+
return this.hasAccessToSomeEmployees((0, lodash_1.castArray)(employeeReference));
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
shouldAddFormerEmployees() {
|
|
57
|
+
return this.addFormerEmployees;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* @param employeeReference Employee reference to validate access to
|
|
61
|
+
* @throws {NoPermissionError} if user has no access to employee
|
|
62
|
+
*/
|
|
63
|
+
validateAccessTo(employeeReference) {
|
|
64
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
65
|
+
if (!(yield this.hasAccessTo(employeeReference))) {
|
|
66
|
+
throw new NoPermissionError_1.NoPermissionError();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* @throws {ModelValidationError} if segment reference is not present
|
|
72
|
+
* @returns Segment reference
|
|
73
|
+
*/
|
|
74
|
+
getSegmentReferenceOrFail() {
|
|
75
|
+
if (!this.segmentReference) {
|
|
76
|
+
throw validations_1.ModelValidationError.from({
|
|
77
|
+
property: "segmentReference",
|
|
78
|
+
constraints: {
|
|
79
|
+
segmentReference: "segment reference should be present",
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return this.segmentReference;
|
|
84
|
+
}
|
|
85
|
+
getSegmentReference() {
|
|
86
|
+
return this.segmentReference;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.AccessListClient = AccessListClient;
|
|
90
|
+
//# sourceMappingURL=AccessListClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AccessListClient.js","sourceRoot":"","sources":["../../src/clients/AccessListClient.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,mEAAgE;AAChE,+EAAgF;AAEhF,sDAA4D;AAC5D,mCAAmC;AAEnC,IAAY,QAMX;AAND,WAAY,QAAQ;IAClB,2BAAe,CAAA;IACf,yBAAa,CAAA;IACb,+BAAmB,CAAA;IACnB,yCAA6B,CAAA;IAC7B,6BAAiB,CAAA;AACnB,CAAC,EANW,QAAQ,GAAR,gBAAQ,KAAR,gBAAQ,QAMnB;AAED,MAAa,gBAAgB;IAS3B,YACE,EACE,KAAK,EACL,gBAAgB,EAChB,qBAAqB,GACiD,EACxE,kBAA4B;QAE5B,IAAI,CAAC,qBAAqB,GAAG,qBAAqB,CAAC;QACnD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;QACzC,IAAI,CAAC,kBAAkB,GAAG,kBAAkB,aAAlB,kBAAkB,cAAlB,kBAAkB,GAAI,KAAK,CAAC;IACxD,CAAC;IAEM,cAAc;QACnB,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;IACxB,CAAC;IAEM,OAAO;QACZ,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC7C,CAAC;IAEM,qBAAqB;QAC1B,OAAO,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;IAChC,CAAC;IAEY,wBAAwB,CACnC,iBAA2B;;YAE3B,IAAI,IAAI,CAAC,cAAc,EAAE;gBAAE,OAAO,IAAI,CAAC;YAEvC,IAAI,CAAC,IAAI,CAAC,gBAAgB;gBAAE,OAAO,KAAK,CAAC;YAEzC,MAAM,SAAS,GAAG,MAAM,IAAA,+CAAwB,EAC9C,IAAI,CAAC,gBAAgB,EACrB,IAAA,kBAAS,EAAC,iBAAiB,CAAC,EAC5B,IAAI,CAAC,wBAAwB,EAAE,CAChC,CAAC;YAEF,OAAO,SAAS,CAAC;QACnB,CAAC;KAAA;IAEY,WAAW,CAAC,iBAAyB;;YAChD,OAAO,IAAI,CAAC,wBAAwB,CAAC,IAAA,kBAAS,EAAC,iBAAiB,CAAC,CAAC,CAAC;QACrE,CAAC;KAAA;IAEM,wBAAwB;QAC7B,OAAO,IAAI,CAAC,kBAAkB,CAAC;IACjC,CAAC;IAED;;;OAGG;IACU,gBAAgB,CAAC,iBAAyB;;YACrD,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,iBAAiB,CAAC,CAAC,EAAE;gBAChD,MAAM,IAAI,qCAAiB,EAAE,CAAC;aAC/B;QACH,CAAC;KAAA;IAED;;;OAGG;IACI,yBAAyB;QAC9B,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE;YAC1B,MAAM,kCAAoB,CAAC,IAAI,CAAC;gBAC9B,QAAQ,EAAE,kBAAkB;gBAC5B,WAAW,EAAE;oBACX,gBAAgB,EAAE,qCAAqC;iBACxD;aACF,CAAC,CAAC;SACJ;QAED,OAAO,IAAI,CAAC,gBAAgB,CAAC;IAC/B,CAAC;IAEM,mBAAmB;QACxB,OAAO,IAAI,CAAC,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAzFD,4CAyFC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NoPermissionError = void 0;
|
|
4
|
+
const errors_1 = require("@alanszp/errors");
|
|
5
|
+
class NoPermissionError extends errors_1.BaseError {
|
|
6
|
+
constructor() {
|
|
7
|
+
super("No Permission");
|
|
8
|
+
}
|
|
9
|
+
code() {
|
|
10
|
+
return "no_permission";
|
|
11
|
+
}
|
|
12
|
+
renderMessage() {
|
|
13
|
+
return "You don't have permission to perform this action";
|
|
14
|
+
}
|
|
15
|
+
context() {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.NoPermissionError = NoPermissionError;
|
|
20
|
+
//# sourceMappingURL=NoPermissionError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NoPermissionError.js","sourceRoot":"","sources":["../../src/errors/NoPermissionError.ts"],"names":[],"mappings":";;;AAAA,4CAAgF;AAEhF,MAAa,iBAAkB,SAAQ,kBAAS;IAC9C;QACE,KAAK,CAAC,eAAe,CAAC,CAAC;IACzB,CAAC;IAED,IAAI;QACF,OAAO,eAAe,CAAC;IACzB,CAAC;IAED,aAAa;QACX,OAAO,kDAAkD,CAAC;IAC5D,CAAC;IAED,OAAO;QACL,OAAO,EAAE,CAAC;IACZ,CAAC;CACF;AAhBD,8CAgBC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./inputs/AccessListInput"), exports);
|
|
18
|
+
__exportStar(require("./clients/AccessListClient"), exports);
|
|
19
|
+
__exportStar(require("./repositories/accessListRepository"), exports);
|
|
20
|
+
__exportStar(require("./errors//NoPermissionError"), exports);
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2DAAyC;AACzC,6DAA2C;AAC3C,sEAAoD;AACpD,8DAA4C"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AccessListClient } from "../clients/AccessListClient";
|
|
2
|
+
import { JWTUser } from "@alanszp/jwt";
|
|
3
|
+
import { BaseModel } from "@alanszp/validations";
|
|
4
|
+
export declare class AccessListInput extends BaseModel {
|
|
5
|
+
user: JWTUser;
|
|
6
|
+
constructor(user: JWTUser);
|
|
7
|
+
getAccessList(shouldAddFormerEmployees?: boolean): AccessListClient;
|
|
8
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.AccessListInput = void 0;
|
|
10
|
+
const AccessListClient_1 = require("../clients/AccessListClient");
|
|
11
|
+
const validations_1 = require("@alanszp/validations");
|
|
12
|
+
const class_validator_1 = require("class-validator");
|
|
13
|
+
class AccessListInput extends validations_1.BaseModel {
|
|
14
|
+
constructor(user) {
|
|
15
|
+
super();
|
|
16
|
+
this.user = user;
|
|
17
|
+
}
|
|
18
|
+
getAccessList(shouldAddFormerEmployees) {
|
|
19
|
+
return new AccessListClient_1.AccessListClient(this.user, shouldAddFormerEmployees);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
__decorate([
|
|
23
|
+
(0, class_validator_1.IsDefined)()
|
|
24
|
+
], AccessListInput.prototype, "user", void 0);
|
|
25
|
+
exports.AccessListInput = AccessListInput;
|
|
26
|
+
//# sourceMappingURL=AccessListInput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AccessListInput.js","sourceRoot":"","sources":["../../src/inputs/AccessListInput.ts"],"names":[],"mappings":";;;;;;;;;AAAA,kEAA+D;AAE/D,sDAAiD;AACjD,qDAA4C;AAE5C,MAAa,eAAgB,SAAQ,uBAAS;IAI5C,YAAY,IAAa;QACvB,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAEM,aAAa,CAAC,wBAAkC;QACrD,OAAO,IAAI,mCAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,wBAAwB,CAAC,CAAC;IACnE,CAAC;CACF;AAVC;IADC,IAAA,2BAAS,GAAE;6CACS;AAFvB,0CAYC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { SelectQueryBuilder } from "typeorm";
|
|
2
|
+
import { AccessListClient } from "..";
|
|
3
|
+
/**
|
|
4
|
+
* Check access to list of employees
|
|
5
|
+
* @param segmentReference the segment which wants to know if it has access to the employee
|
|
6
|
+
* @param employeeReference list of employees
|
|
7
|
+
* @param addFormerEmployees consider left employees
|
|
8
|
+
* @returns true if segment reference can access to any of the employees on the list
|
|
9
|
+
*/
|
|
10
|
+
export declare function hasAccessToSomeEmployees(segmentReference: string, employeeReference: string[], addFormerEmployees: boolean): Promise<boolean>;
|
|
11
|
+
/**
|
|
12
|
+
* Adds accessList filters to a given queryBuilder. Should be called at the end, or at least
|
|
13
|
+
* after the first where is set to the queryBuilder.
|
|
14
|
+
* @param queryBuilder The query to be filtered with accessList
|
|
15
|
+
* @param segmentId The segment from which the accessList is calculated.
|
|
16
|
+
* @param fullEmployeeReferenceFieldName The alias and name of the field that has the employee reference
|
|
17
|
+
* @returns a query builder with accessList filters
|
|
18
|
+
*/
|
|
19
|
+
export declare function addFiltersToSelectQuery<T>(queryBuilder: SelectQueryBuilder<T>, accessList: AccessListClient, fullEmployeeReferenceFieldName: string, segmentParamAlias?: string, subTableAlias?: string, segmentReference?: string): SelectQueryBuilder<T>;
|
|
20
|
+
export declare function getSQLJoinFilters(accessList: AccessListClient, fullEmployeeReferenceFieldName: string, segmentReferenceIndex: string, subTableAlias?: string): string;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.getSQLJoinFilters = exports.addFiltersToSelectQuery = exports.hasAccessToSomeEmployees = void 0;
|
|
13
|
+
const typeorm_1 = require("typeorm");
|
|
14
|
+
/**
|
|
15
|
+
* Check access to list of employees
|
|
16
|
+
* @param segmentReference the segment which wants to know if it has access to the employee
|
|
17
|
+
* @param employeeReference list of employees
|
|
18
|
+
* @param addFormerEmployees consider left employees
|
|
19
|
+
* @returns true if segment reference can access to any of the employees on the list
|
|
20
|
+
*/
|
|
21
|
+
function hasAccessToSomeEmployees(segmentReference, employeeReference, addFormerEmployees) {
|
|
22
|
+
var _a, _b;
|
|
23
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24
|
+
const query = `SELECT bool_or(true) as granted
|
|
25
|
+
FROM segments_employee_relation_with_attrition ser
|
|
26
|
+
WHERE ser.segment_id = $2 AND ser.employee_id::text = ANY($1)
|
|
27
|
+
${addFormerEmployees ? "" : " AND left_organization_at IS NULL"};`;
|
|
28
|
+
const response = (yield (0, typeorm_1.getManager)().query(query, [
|
|
29
|
+
employeeReference,
|
|
30
|
+
segmentReference,
|
|
31
|
+
]));
|
|
32
|
+
return (_b = (_a = response[0]) === null || _a === void 0 ? void 0 : _a.granted) !== null && _b !== void 0 ? _b : false;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
exports.hasAccessToSomeEmployees = hasAccessToSomeEmployees;
|
|
36
|
+
/**
|
|
37
|
+
* Adds accessList filters to a given queryBuilder. Should be called at the end, or at least
|
|
38
|
+
* after the first where is set to the queryBuilder.
|
|
39
|
+
* @param queryBuilder The query to be filtered with accessList
|
|
40
|
+
* @param segmentId The segment from which the accessList is calculated.
|
|
41
|
+
* @param fullEmployeeReferenceFieldName The alias and name of the field that has the employee reference
|
|
42
|
+
* @returns a query builder with accessList filters
|
|
43
|
+
*/
|
|
44
|
+
function addFiltersToSelectQuery(queryBuilder, accessList, fullEmployeeReferenceFieldName, segmentParamAlias = "segmentId", subTableAlias, segmentReference) {
|
|
45
|
+
if (accessList.hasAccessToAll() && !segmentReference)
|
|
46
|
+
return queryBuilder;
|
|
47
|
+
let segmentReferenceParam = segmentReference;
|
|
48
|
+
if (!segmentReferenceParam) {
|
|
49
|
+
segmentReferenceParam = accessList.getSegmentReferenceOrFail();
|
|
50
|
+
}
|
|
51
|
+
const subTable = subTableAlias !== null && subTableAlias !== void 0 ? subTableAlias : "ser";
|
|
52
|
+
const query = `(SELECT employee_id
|
|
53
|
+
FROM public.segments_employee_relation_with_attrition
|
|
54
|
+
WHERE segment_id = :${segmentParamAlias}
|
|
55
|
+
${accessList.shouldAddFormerEmployees()
|
|
56
|
+
? ""
|
|
57
|
+
: "AND left_organization_at IS NULL"})`;
|
|
58
|
+
return queryBuilder.innerJoin(query, subTable, `${subTable}.employee_id::text = ${fullEmployeeReferenceFieldName}`, {
|
|
59
|
+
[segmentParamAlias]: segmentReferenceParam,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
exports.addFiltersToSelectQuery = addFiltersToSelectQuery;
|
|
63
|
+
function getSQLJoinFilters(accessList, fullEmployeeReferenceFieldName, segmentReferenceIndex, subTableAlias) {
|
|
64
|
+
if (!segmentReferenceIndex.includes("$")) {
|
|
65
|
+
throw new Error("getSQLJoinFilters#segmentReferenceIndex param should be an index");
|
|
66
|
+
}
|
|
67
|
+
const subTable = subTableAlias !== null && subTableAlias !== void 0 ? subTableAlias : "ser";
|
|
68
|
+
return `JOIN (SELECT employee_id
|
|
69
|
+
FROM public.segments_employee_relation_with_attrition
|
|
70
|
+
WHERE segment_id = ${segmentReferenceIndex}
|
|
71
|
+
${accessList.shouldAddFormerEmployees()
|
|
72
|
+
? ""
|
|
73
|
+
: "AND left_organization_at IS NULL"}) ${subTable} on ${subTable}.employee_id = ${fullEmployeeReferenceFieldName}`;
|
|
74
|
+
}
|
|
75
|
+
exports.getSQLJoinFilters = getSQLJoinFilters;
|
|
76
|
+
//# sourceMappingURL=accessListRepository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accessListRepository.js","sourceRoot":"","sources":["../../src/repositories/accessListRepository.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAAyD;AAGzD;;;;;;GAMG;AACH,SAAsB,wBAAwB,CAC5C,gBAAwB,EACxB,iBAA2B,EAC3B,kBAA2B;;;QAE3B,MAAM,KAAK,GAAG;;;MAGV,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,mCAAmC,GAAG,CAAC;QAErE,MAAM,QAAQ,GAAG,CAAC,MAAM,IAAA,oBAAU,GAAE,CAAC,KAAK,CAAC,KAAK,EAAE;YAChD,iBAAiB;YACjB,gBAAgB;SACjB,CAAC,CAA4B,CAAC;QAE/B,OAAO,MAAA,MAAA,QAAQ,CAAC,CAAC,CAAC,0CAAE,OAAO,mCAAI,KAAK,CAAC;;CACtC;AAhBD,4DAgBC;AAED;;;;;;;GAOG;AACH,SAAgB,uBAAuB,CACrC,YAAmC,EACnC,UAA4B,EAC5B,8BAAsC,EACtC,iBAAiB,GAAG,WAAW,EAC/B,aAAsB,EACtB,gBAAyB;IAEzB,IAAI,UAAU,CAAC,cAAc,EAAE,IAAI,CAAC,gBAAgB;QAAE,OAAO,YAAY,CAAC;IAE1E,IAAI,qBAAqB,GAAG,gBAAgB,CAAC;IAC7C,IAAI,CAAC,qBAAqB,EAAE;QAC1B,qBAAqB,GAAG,UAAU,CAAC,yBAAyB,EAAE,CAAC;KAChE;IAED,MAAM,QAAQ,GAAG,aAAa,aAAb,aAAa,cAAb,aAAa,GAAI,KAAK,CAAC;IAExC,MAAM,KAAK,GAAG;;0BAEU,iBAAiB;MAErC,UAAU,CAAC,wBAAwB,EAAE;QACnC,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,kCACN,GAAG,CAAC;IAEN,OAAO,YAAY,CAAC,SAAS,CAC3B,KAAK,EACL,QAAQ,EACR,GAAG,QAAQ,wBAAwB,8BAA8B,EAAE,EACnE;QACE,CAAC,iBAAiB,CAAC,EAAE,qBAAqB;KAC3C,CACF,CAAC;AACJ,CAAC;AAlCD,0DAkCC;AAED,SAAgB,iBAAiB,CAC/B,UAA4B,EAC5B,8BAAsC,EACtC,qBAA6B,EAC7B,aAAsB;IAEtB,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;QACxC,MAAM,IAAI,KAAK,CACb,kEAAkE,CACnE,CAAC;KACH;IACD,MAAM,QAAQ,GAAG,aAAa,aAAb,aAAa,cAAb,aAAa,GAAI,KAAK,CAAC;IAExC,OAAO;;yBAEgB,qBAAqB;MAExC,UAAU,CAAC,wBAAwB,EAAE;QACnC,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,kCACN,KAAK,QAAQ,OAAO,QAAQ,kBAAkB,8BAA8B,EAAE,CAAC;AACnF,CAAC;AArBD,8CAqBC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alanszp/access-list",
|
|
3
|
+
"version": "10.2.0",
|
|
4
|
+
"description": "Alan's validation utils.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"typings": "dist/index.d.ts",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"files": [
|
|
9
|
+
"**/*"
|
|
10
|
+
],
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"compile": "rm -rf ./dist && tsc --declaration",
|
|
16
|
+
"compile-watch": "tsc -w",
|
|
17
|
+
"build": "yarn run compile",
|
|
18
|
+
"prepack": "yarn run build",
|
|
19
|
+
"yalc-publish": "yarn run yalc publish"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^20.11.17",
|
|
23
|
+
"class-validator": "^0.14.0",
|
|
24
|
+
"ts-node": "^10.0.0",
|
|
25
|
+
"tslint": "^6.1.3",
|
|
26
|
+
"typescript": "^4.3.4"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"class-validator": "^0.14.0"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@alanszp/jwt": "^10.0.1",
|
|
33
|
+
"@alanszp/validations": "^10.0.1"
|
|
34
|
+
},
|
|
35
|
+
"gitHead": "7a365de48d5a7e7c87b28690b2e06f7de7f5a9d3"
|
|
36
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { NoPermissionError } from "../errors/NoPermissionError";
|
|
2
|
+
import { hasAccessToSomeEmployees } from "../repositories/accessListRepository";
|
|
3
|
+
import { JWTUser } from "@alanszp/jwt";
|
|
4
|
+
import { ModelValidationError } from "@alanszp/validations";
|
|
5
|
+
import { castArray } from "lodash";
|
|
6
|
+
|
|
7
|
+
export enum RoleCode {
|
|
8
|
+
ADMIN = "admin",
|
|
9
|
+
HRBP = "hrbp",
|
|
10
|
+
MANAGER = "manager",
|
|
11
|
+
INTEGRATIONS = "integrations",
|
|
12
|
+
VIEWER = "viewer",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class AccessListClient {
|
|
16
|
+
protected organizationReference: string;
|
|
17
|
+
|
|
18
|
+
protected roles: string[];
|
|
19
|
+
|
|
20
|
+
protected segmentReference: string | null;
|
|
21
|
+
|
|
22
|
+
protected addFormerEmployees: boolean;
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
{
|
|
26
|
+
roles,
|
|
27
|
+
segmentReference,
|
|
28
|
+
organizationReference,
|
|
29
|
+
}: Pick<JWTUser, "roles" | "segmentReference" | "organizationReference">,
|
|
30
|
+
addFormerEmployees?: boolean,
|
|
31
|
+
) {
|
|
32
|
+
this.organizationReference = organizationReference;
|
|
33
|
+
this.roles = roles;
|
|
34
|
+
this.segmentReference = segmentReference;
|
|
35
|
+
this.addFormerEmployees = addFormerEmployees ?? false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public hasAccessToAll(): boolean {
|
|
39
|
+
return this.isAdmin();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public isAdmin(): boolean {
|
|
43
|
+
return this.roles.includes(RoleCode.ADMIN);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public needsToValidateAccess(): boolean {
|
|
47
|
+
return !this.hasAccessToAll();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public async hasAccessToSomeEmployees(
|
|
51
|
+
employeeReference: string[],
|
|
52
|
+
): Promise<boolean> {
|
|
53
|
+
if (this.hasAccessToAll()) return true;
|
|
54
|
+
|
|
55
|
+
if (!this.segmentReference) return false;
|
|
56
|
+
|
|
57
|
+
const hasAccess = await hasAccessToSomeEmployees(
|
|
58
|
+
this.segmentReference,
|
|
59
|
+
castArray(employeeReference),
|
|
60
|
+
this.shouldAddFormerEmployees(),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return hasAccess;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public async hasAccessTo(employeeReference: string): Promise<boolean> {
|
|
67
|
+
return this.hasAccessToSomeEmployees(castArray(employeeReference));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public shouldAddFormerEmployees(): boolean {
|
|
71
|
+
return this.addFormerEmployees;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param employeeReference Employee reference to validate access to
|
|
76
|
+
* @throws {NoPermissionError} if user has no access to employee
|
|
77
|
+
*/
|
|
78
|
+
public async validateAccessTo(employeeReference: string): Promise<void> {
|
|
79
|
+
if (!(await this.hasAccessTo(employeeReference))) {
|
|
80
|
+
throw new NoPermissionError();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @throws {ModelValidationError} if segment reference is not present
|
|
86
|
+
* @returns Segment reference
|
|
87
|
+
*/
|
|
88
|
+
public getSegmentReferenceOrFail(): string {
|
|
89
|
+
if (!this.segmentReference) {
|
|
90
|
+
throw ModelValidationError.from({
|
|
91
|
+
property: "segmentReference",
|
|
92
|
+
constraints: {
|
|
93
|
+
segmentReference: "segment reference should be present",
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return this.segmentReference;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public getSegmentReference(): string | null {
|
|
102
|
+
return this.segmentReference;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BaseError, RenderableContext, RenderableError } from "@alanszp/errors";
|
|
2
|
+
|
|
3
|
+
export class NoPermissionError extends BaseError implements RenderableError {
|
|
4
|
+
constructor() {
|
|
5
|
+
super("No Permission");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
code(): string {
|
|
9
|
+
return "no_permission";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
renderMessage(): string {
|
|
13
|
+
return "You don't have permission to perform this action";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
context(): RenderableContext {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AccessListClient } from "../clients/AccessListClient";
|
|
2
|
+
import { JWTUser } from "@alanszp/jwt";
|
|
3
|
+
import { BaseModel } from "@alanszp/validations";
|
|
4
|
+
import { IsDefined } from "class-validator";
|
|
5
|
+
|
|
6
|
+
export class AccessListInput extends BaseModel {
|
|
7
|
+
@IsDefined()
|
|
8
|
+
public user: JWTUser;
|
|
9
|
+
|
|
10
|
+
constructor(user: JWTUser) {
|
|
11
|
+
super();
|
|
12
|
+
this.user = user;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public getAccessList(shouldAddFormerEmployees?: boolean): AccessListClient {
|
|
16
|
+
return new AccessListClient(this.user, shouldAddFormerEmployees);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { getManager, SelectQueryBuilder } from "typeorm";
|
|
2
|
+
import { AccessListClient } from "..";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Check access to list of employees
|
|
6
|
+
* @param segmentReference the segment which wants to know if it has access to the employee
|
|
7
|
+
* @param employeeReference list of employees
|
|
8
|
+
* @param addFormerEmployees consider left employees
|
|
9
|
+
* @returns true if segment reference can access to any of the employees on the list
|
|
10
|
+
*/
|
|
11
|
+
export async function hasAccessToSomeEmployees(
|
|
12
|
+
segmentReference: string,
|
|
13
|
+
employeeReference: string[],
|
|
14
|
+
addFormerEmployees: boolean,
|
|
15
|
+
): Promise<boolean> {
|
|
16
|
+
const query = `SELECT bool_or(true) as granted
|
|
17
|
+
FROM segments_employee_relation_with_attrition ser
|
|
18
|
+
WHERE ser.segment_id = $2 AND ser.employee_id::text = ANY($1)
|
|
19
|
+
${addFormerEmployees ? "" : " AND left_organization_at IS NULL"};`;
|
|
20
|
+
|
|
21
|
+
const response = (await getManager().query(query, [
|
|
22
|
+
employeeReference,
|
|
23
|
+
segmentReference,
|
|
24
|
+
])) as { granted?: boolean }[];
|
|
25
|
+
|
|
26
|
+
return response[0]?.granted ?? false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Adds accessList filters to a given queryBuilder. Should be called at the end, or at least
|
|
31
|
+
* after the first where is set to the queryBuilder.
|
|
32
|
+
* @param queryBuilder The query to be filtered with accessList
|
|
33
|
+
* @param segmentId The segment from which the accessList is calculated.
|
|
34
|
+
* @param fullEmployeeReferenceFieldName The alias and name of the field that has the employee reference
|
|
35
|
+
* @returns a query builder with accessList filters
|
|
36
|
+
*/
|
|
37
|
+
export function addFiltersToSelectQuery<T>(
|
|
38
|
+
queryBuilder: SelectQueryBuilder<T>,
|
|
39
|
+
accessList: AccessListClient,
|
|
40
|
+
fullEmployeeReferenceFieldName: string,
|
|
41
|
+
segmentParamAlias = "segmentId",
|
|
42
|
+
subTableAlias?: string,
|
|
43
|
+
segmentReference?: string,
|
|
44
|
+
): SelectQueryBuilder<T> {
|
|
45
|
+
if (accessList.hasAccessToAll() && !segmentReference) return queryBuilder;
|
|
46
|
+
|
|
47
|
+
let segmentReferenceParam = segmentReference;
|
|
48
|
+
if (!segmentReferenceParam) {
|
|
49
|
+
segmentReferenceParam = accessList.getSegmentReferenceOrFail();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const subTable = subTableAlias ?? "ser";
|
|
53
|
+
|
|
54
|
+
const query = `(SELECT employee_id
|
|
55
|
+
FROM public.segments_employee_relation_with_attrition
|
|
56
|
+
WHERE segment_id = :${segmentParamAlias}
|
|
57
|
+
${
|
|
58
|
+
accessList.shouldAddFormerEmployees()
|
|
59
|
+
? ""
|
|
60
|
+
: "AND left_organization_at IS NULL"
|
|
61
|
+
})`;
|
|
62
|
+
|
|
63
|
+
return queryBuilder.innerJoin(
|
|
64
|
+
query,
|
|
65
|
+
subTable,
|
|
66
|
+
`${subTable}.employee_id::text = ${fullEmployeeReferenceFieldName}`,
|
|
67
|
+
{
|
|
68
|
+
[segmentParamAlias]: segmentReferenceParam,
|
|
69
|
+
},
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getSQLJoinFilters(
|
|
74
|
+
accessList: AccessListClient,
|
|
75
|
+
fullEmployeeReferenceFieldName: string,
|
|
76
|
+
segmentReferenceIndex: string,
|
|
77
|
+
subTableAlias?: string,
|
|
78
|
+
): string {
|
|
79
|
+
if (!segmentReferenceIndex.includes("$")) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
"getSQLJoinFilters#segmentReferenceIndex param should be an index",
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
const subTable = subTableAlias ?? "ser";
|
|
85
|
+
|
|
86
|
+
return `JOIN (SELECT employee_id
|
|
87
|
+
FROM public.segments_employee_relation_with_attrition
|
|
88
|
+
WHERE segment_id = ${segmentReferenceIndex}
|
|
89
|
+
${
|
|
90
|
+
accessList.shouldAddFormerEmployees()
|
|
91
|
+
? ""
|
|
92
|
+
: "AND left_organization_at IS NULL"
|
|
93
|
+
}) ${subTable} on ${subTable}.employee_id = ${fullEmployeeReferenceFieldName}`;
|
|
94
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"rootDir": "src",
|
|
4
|
+
"outDir": "dist",
|
|
5
|
+
"module": "commonjs",
|
|
6
|
+
"target": "es6",
|
|
7
|
+
"types": ["node"],
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"experimentalDecorators": true,
|
|
11
|
+
|
|
12
|
+
"alwaysStrict": true,
|
|
13
|
+
"strictNullChecks": true
|
|
14
|
+
},
|
|
15
|
+
"exclude": ["node_modules"]
|
|
16
|
+
}
|