@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 ADDED
@@ -0,0 +1,3 @@
1
+ node_modules
2
+ *.log
3
+ dist
package/.npmignore ADDED
@@ -0,0 +1,3 @@
1
+ node_modules
2
+ src
3
+ *.log
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,7 @@
1
+ import { BaseError, RenderableContext, RenderableError } from "@alanszp/errors";
2
+ export declare class NoPermissionError extends BaseError implements RenderableError {
3
+ constructor();
4
+ code(): string;
5
+ renderMessage(): string;
6
+ context(): RenderableContext;
7
+ }
@@ -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"}
@@ -0,0 +1,4 @@
1
+ export * from "./inputs/AccessListInput";
2
+ export * from "./clients/AccessListClient";
3
+ export * from "./repositories/accessListRepository";
4
+ export * from "./errors//NoPermissionError";
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,4 @@
1
+ export * from "./inputs/AccessListInput";
2
+ export * from "./clients/AccessListClient";
3
+ export * from "./repositories/accessListRepository";
4
+ export * from "./errors//NoPermissionError";
@@ -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
+ }