@floristcloud/api-lib 1.2.90 → 1.2.94
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/build/commands/access-policy/get-access-policy.query.js +13 -0
- package/build/commands/access-policy/index.js +18 -0
- package/build/commands/access-policy/upsert-access-policy.command.js +15 -0
- package/build/commands/index.js +1 -0
- package/build/commands/product/generate-product-qr.query.js +1 -0
- package/build/commands/user/get-me.query.js +17 -0
- package/build/commands/user/index.js +1 -0
- package/build/constant/error.js +2 -0
- package/build/enum/index.js +1 -0
- package/build/enum/permission-key.enum.js +22 -0
- package/build/schemas/access-policy/access-policy.schema.js +72 -0
- package/build/schemas/access-policy/index.js +17 -0
- package/build/schemas/index.js +1 -0
- package/commands/access-policy/get-access-policy.query.ts +12 -0
- package/commands/access-policy/index.ts +2 -0
- package/commands/access-policy/upsert-access-policy.command.ts +17 -0
- package/commands/index.ts +1 -0
- package/commands/product/generate-product-qr.query.ts +1 -0
- package/commands/user/get-me.query.ts +16 -0
- package/commands/user/index.ts +1 -0
- package/constant/error.ts +2 -0
- package/enum/index.ts +1 -0
- package/enum/permission-key.enum.ts +19 -0
- package/package.json +1 -1
- package/schemas/access-policy/access-policy.schema.ts +80 -0
- package/schemas/access-policy/index.ts +1 -0
- package/schemas/index.ts +1 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GetAccessPolicyContractQuery = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const access_policy_schema_1 = require("../../schemas/access-policy/access-policy.schema");
|
|
6
|
+
const GetAccessPolicyResponseSchema = zod_1.z.object({
|
|
7
|
+
message: zod_1.z.string().optional(),
|
|
8
|
+
data: access_policy_schema_1.AccessPolicySchema,
|
|
9
|
+
});
|
|
10
|
+
var GetAccessPolicyContractQuery;
|
|
11
|
+
(function (GetAccessPolicyContractQuery) {
|
|
12
|
+
GetAccessPolicyContractQuery.ResponseSchema = GetAccessPolicyResponseSchema;
|
|
13
|
+
})(GetAccessPolicyContractQuery || (exports.GetAccessPolicyContractQuery = GetAccessPolicyContractQuery = {}));
|
|
@@ -0,0 +1,18 @@
|
|
|
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("./get-access-policy.query"), exports);
|
|
18
|
+
__exportStar(require("./upsert-access-policy.command"), exports);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UpsertAccessPolicyContractCommand = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const access_policy_schema_1 = require("../../schemas/access-policy/access-policy.schema");
|
|
6
|
+
const UpsertAccessPolicyRequestSchema = access_policy_schema_1.AccessPolicySchema;
|
|
7
|
+
const UpsertAccessPolicyResponseSchema = zod_1.z.object({
|
|
8
|
+
message: zod_1.z.string().optional(),
|
|
9
|
+
data: access_policy_schema_1.AccessPolicySchema,
|
|
10
|
+
});
|
|
11
|
+
var UpsertAccessPolicyContractCommand;
|
|
12
|
+
(function (UpsertAccessPolicyContractCommand) {
|
|
13
|
+
UpsertAccessPolicyContractCommand.RequestSchema = UpsertAccessPolicyRequestSchema;
|
|
14
|
+
UpsertAccessPolicyContractCommand.ResponseSchema = UpsertAccessPolicyResponseSchema;
|
|
15
|
+
})(UpsertAccessPolicyContractCommand || (exports.UpsertAccessPolicyContractCommand = UpsertAccessPolicyContractCommand = {}));
|
package/build/commands/index.js
CHANGED
|
@@ -15,6 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.ValidateWsTicketS2SContractQuery = exports.CreateWsTicketContractCommand = exports.ValidateSessionS2SContractQuery = exports.GetActiveSessionsContractQuery = exports.SessionRevokeAllContractCommand = exports.SessionLogoutContractCommand = void 0;
|
|
18
|
+
__exportStar(require("./access-policy"), exports);
|
|
18
19
|
__exportStar(require("./auth"), exports);
|
|
19
20
|
__exportStar(require("./branch"), exports);
|
|
20
21
|
__exportStar(require("./category"), exports);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GetMeContractQuery = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const schemas_1 = require("../../schemas");
|
|
6
|
+
const access_policy_schema_1 = require("../../schemas/access-policy/access-policy.schema");
|
|
7
|
+
const GetMeResponseSchema = zod_1.z.object({
|
|
8
|
+
message: zod_1.z.string().optional(),
|
|
9
|
+
data: zod_1.z.object({
|
|
10
|
+
user: schemas_1.UserSchema.omit({ passwordHash: true, restoreTokenHash: true }),
|
|
11
|
+
permissions: access_policy_schema_1.EffectivePermissionsSchema,
|
|
12
|
+
}),
|
|
13
|
+
});
|
|
14
|
+
var GetMeContractQuery;
|
|
15
|
+
(function (GetMeContractQuery) {
|
|
16
|
+
GetMeContractQuery.ResponseSchema = GetMeResponseSchema;
|
|
17
|
+
})(GetMeContractQuery || (exports.GetMeContractQuery = GetMeContractQuery = {}));
|
|
@@ -19,6 +19,7 @@ __exportStar(require("./attachUserToCompany.command"), exports);
|
|
|
19
19
|
__exportStar(require("./createUserByClientId.command"), exports);
|
|
20
20
|
__exportStar(require("./updateRole.command"), exports);
|
|
21
21
|
__exportStar(require("./getUser.command"), exports);
|
|
22
|
+
__exportStar(require("./get-me.query"), exports);
|
|
22
23
|
__exportStar(require("./createUserForCompany.command"), exports);
|
|
23
24
|
__exportStar(require("./get-user-list.query"), exports);
|
|
24
25
|
__exportStar(require("./get-user-list-s2s.query"), exports);
|
package/build/constant/error.js
CHANGED
|
@@ -1229,4 +1229,6 @@ exports.ERRORS = {
|
|
|
1229
1229
|
httpCode: 404,
|
|
1230
1230
|
},
|
|
1231
1231
|
CONSIGNMENT_TRANSFER_ACCEPT_FAILED: { code: 'CTR013', message: 'Failed to accept consignment transfer request', httpCode: 500 },
|
|
1232
|
+
ACCESS_POLICY_FETCH_FAILED: { code: 'AP001', message: 'Error when retrieving the access policy', httpCode: 500 },
|
|
1233
|
+
ACCESS_POLICY_NOT_UPDATED: { code: 'AP002', message: 'Failed to update the access policy', httpCode: 500 },
|
|
1232
1234
|
};
|
package/build/enum/index.js
CHANGED
|
@@ -108,3 +108,4 @@ __exportStar(require("./moysklad-sync-status.enum"), exports);
|
|
|
108
108
|
__exportStar(require("./consignment-transfer-request-status.enum"), exports);
|
|
109
109
|
__exportStar(require("./consignment-transfer-direction.enum"), exports);
|
|
110
110
|
__exportStar(require("./legal-identifier-field.enum"), exports);
|
|
111
|
+
__exportStar(require("./permission-key.enum"), exports);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PERMISSION_KEY_LIST = exports.PermissionKeyEnum = void 0;
|
|
4
|
+
var PermissionKeyEnum;
|
|
5
|
+
(function (PermissionKeyEnum) {
|
|
6
|
+
PermissionKeyEnum["PRODUCT_VIEW"] = "product.view";
|
|
7
|
+
PermissionKeyEnum["PRODUCT_EDIT"] = "product.edit";
|
|
8
|
+
PermissionKeyEnum["PRODUCT_DELETE"] = "product.delete";
|
|
9
|
+
PermissionKeyEnum["CONSIGNMENT_VIEW"] = "consignment.view";
|
|
10
|
+
PermissionKeyEnum["CONSIGNMENT_EDIT"] = "consignment.edit";
|
|
11
|
+
PermissionKeyEnum["CONSIGNMENT_DELETE"] = "consignment.delete";
|
|
12
|
+
PermissionKeyEnum["COST_VIEW"] = "cost.view";
|
|
13
|
+
})(PermissionKeyEnum || (exports.PermissionKeyEnum = PermissionKeyEnum = {}));
|
|
14
|
+
exports.PERMISSION_KEY_LIST = [
|
|
15
|
+
PermissionKeyEnum.PRODUCT_VIEW,
|
|
16
|
+
PermissionKeyEnum.PRODUCT_EDIT,
|
|
17
|
+
PermissionKeyEnum.PRODUCT_DELETE,
|
|
18
|
+
PermissionKeyEnum.CONSIGNMENT_VIEW,
|
|
19
|
+
PermissionKeyEnum.CONSIGNMENT_EDIT,
|
|
20
|
+
PermissionKeyEnum.CONSIGNMENT_DELETE,
|
|
21
|
+
PermissionKeyEnum.COST_VIEW,
|
|
22
|
+
];
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sanitizeStoredAccessPolicy = exports.EffectivePermissionsSchema = exports.AccessPolicySchema = exports.PermissionMapSchema = exports.CONFIGURABLE_PANEL_ROLE_LIST = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const permission_key_enum_1 = require("../../enum/permission-key.enum");
|
|
6
|
+
const userRole_enum_1 = require("../../enum/userRole.enum");
|
|
7
|
+
/**
|
|
8
|
+
* Panel roles that can actually be configured. ADMIN is intentionally excluded:
|
|
9
|
+
* it always bypasses UI restrictions, so any ADMIN entry in a policy is dead
|
|
10
|
+
* config. The admin panel must not offer it, and the sanitizer drops it.
|
|
11
|
+
*/
|
|
12
|
+
exports.CONFIGURABLE_PANEL_ROLE_LIST = [userRole_enum_1.UserPanelRoleEnum.MANAGER, userRole_enum_1.UserPanelRoleEnum.EMPLOYEE];
|
|
13
|
+
/**
|
|
14
|
+
* A sparse map of permission key -> boolean.
|
|
15
|
+
* Semantics are default-allow: a missing key means "allowed", so in practice
|
|
16
|
+
* only explicit `false` (denials) are ever stored.
|
|
17
|
+
*/
|
|
18
|
+
exports.PermissionMapSchema = zod_1.z.partialRecord(zod_1.z.nativeEnum(permission_key_enum_1.PermissionKeyEnum), zod_1.z.boolean());
|
|
19
|
+
/**
|
|
20
|
+
* Full company access policy — the raw configuration edited by an admin.
|
|
21
|
+
* `roles` holds per-role denials; `users` holds per-employee overrides keyed by userUUID.
|
|
22
|
+
*/
|
|
23
|
+
exports.AccessPolicySchema = zod_1.z.object({
|
|
24
|
+
roles: zod_1.z.partialRecord(zod_1.z.nativeEnum(userRole_enum_1.UserPanelRoleEnum), exports.PermissionMapSchema).default({}),
|
|
25
|
+
users: zod_1.z.record(zod_1.z.uuid(), exports.PermissionMapSchema).default({}),
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* Resolved permissions for a single user — the sparse denial map after merging
|
|
29
|
+
* role defaults with the user's own overrides. Empty for ADMIN / global users.
|
|
30
|
+
* Frontend contract: `can(key) === effective[key] !== false`.
|
|
31
|
+
*/
|
|
32
|
+
exports.EffectivePermissionsSchema = exports.PermissionMapSchema;
|
|
33
|
+
const pickPermissionMap = (value) => {
|
|
34
|
+
const map = {};
|
|
35
|
+
if (value && typeof value === 'object') {
|
|
36
|
+
const record = value;
|
|
37
|
+
for (const key of permission_key_enum_1.PERMISSION_KEY_LIST) {
|
|
38
|
+
if (typeof record[key] === 'boolean') {
|
|
39
|
+
map[key] = record[key];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return map;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Tolerant reader for a stored policy blob. Unlike `AccessPolicySchema.parse`,
|
|
47
|
+
* this NEVER throws on unknown/renamed keys — it strips anything not in the
|
|
48
|
+
* current `PermissionKeyEnum` / configurable roles and keeps the rest.
|
|
49
|
+
*
|
|
50
|
+
* Why: `AccessPolicySchema` uses `partialRecord`, which throws "Invalid key in
|
|
51
|
+
* record" on an unknown key. If a permission key or role is ever renamed/removed
|
|
52
|
+
* while a company still has the old value persisted, a strict parse at the read
|
|
53
|
+
* boundary would bubble up as a 500 from `getMe` for every user in that company.
|
|
54
|
+
* Adding keys is safe; removing/renaming must degrade gracefully — hence this.
|
|
55
|
+
*/
|
|
56
|
+
const sanitizeStoredAccessPolicy = (raw) => {
|
|
57
|
+
const source = raw && typeof raw === 'object' ? raw : {};
|
|
58
|
+
const roles = {};
|
|
59
|
+
const rawRoles = source.roles && typeof source.roles === 'object' ? source.roles : {};
|
|
60
|
+
for (const role of exports.CONFIGURABLE_PANEL_ROLE_LIST) {
|
|
61
|
+
if (role in rawRoles) {
|
|
62
|
+
roles[role] = pickPermissionMap(rawRoles[role]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const users = {};
|
|
66
|
+
const rawUsers = source.users && typeof source.users === 'object' ? source.users : {};
|
|
67
|
+
for (const userUUID of Object.keys(rawUsers)) {
|
|
68
|
+
users[userUUID] = pickPermissionMap(rawUsers[userUUID]);
|
|
69
|
+
}
|
|
70
|
+
return { roles, users };
|
|
71
|
+
};
|
|
72
|
+
exports.sanitizeStoredAccessPolicy = sanitizeStoredAccessPolicy;
|
|
@@ -0,0 +1,17 @@
|
|
|
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("./access-policy.schema"), exports);
|
package/build/schemas/index.js
CHANGED
|
@@ -83,3 +83,4 @@ __exportStar(require("./messenger-profile"), exports);
|
|
|
83
83
|
__exportStar(require("./error-message.schema"), exports);
|
|
84
84
|
__exportStar(require("./account-deletion-request/account-deletion-request.schema"), exports);
|
|
85
85
|
__exportStar(require("./consignment-transfer"), exports);
|
|
86
|
+
__exportStar(require("./access-policy"), exports);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AccessPolicySchema } from '../../schemas/access-policy/access-policy.schema';
|
|
3
|
+
|
|
4
|
+
const GetAccessPolicyResponseSchema = z.object({
|
|
5
|
+
message: z.string().optional(),
|
|
6
|
+
data: AccessPolicySchema,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export namespace GetAccessPolicyContractQuery {
|
|
10
|
+
export const ResponseSchema = GetAccessPolicyResponseSchema;
|
|
11
|
+
export type Response = z.infer<typeof ResponseSchema>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AccessPolicySchema } from '../../schemas/access-policy/access-policy.schema';
|
|
3
|
+
|
|
4
|
+
const UpsertAccessPolicyRequestSchema = AccessPolicySchema;
|
|
5
|
+
|
|
6
|
+
const UpsertAccessPolicyResponseSchema = z.object({
|
|
7
|
+
message: z.string().optional(),
|
|
8
|
+
data: AccessPolicySchema,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export namespace UpsertAccessPolicyContractCommand {
|
|
12
|
+
export const RequestSchema = UpsertAccessPolicyRequestSchema;
|
|
13
|
+
export type Request = z.infer<typeof RequestSchema>;
|
|
14
|
+
|
|
15
|
+
export const ResponseSchema = UpsertAccessPolicyResponseSchema;
|
|
16
|
+
export type Response = z.infer<typeof ResponseSchema>;
|
|
17
|
+
}
|
package/commands/index.ts
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { UserSchema } from '../../schemas';
|
|
3
|
+
import { EffectivePermissionsSchema } from '../../schemas/access-policy/access-policy.schema';
|
|
4
|
+
|
|
5
|
+
const GetMeResponseSchema = z.object({
|
|
6
|
+
message: z.string().optional(),
|
|
7
|
+
data: z.object({
|
|
8
|
+
user: UserSchema.omit({ passwordHash: true, restoreTokenHash: true }),
|
|
9
|
+
permissions: EffectivePermissionsSchema,
|
|
10
|
+
}),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export namespace GetMeContractQuery {
|
|
14
|
+
export const ResponseSchema = GetMeResponseSchema;
|
|
15
|
+
export type Response = z.infer<typeof ResponseSchema>;
|
|
16
|
+
}
|
package/commands/user/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './attachUserToCompany.command';
|
|
|
3
3
|
export * from './createUserByClientId.command';
|
|
4
4
|
export * from './updateRole.command';
|
|
5
5
|
export * from './getUser.command';
|
|
6
|
+
export * from './get-me.query';
|
|
6
7
|
export * from './createUserForCompany.command';
|
|
7
8
|
export * from './get-user-list.query';
|
|
8
9
|
export * from './get-user-list-s2s.query';
|
package/constant/error.ts
CHANGED
|
@@ -1294,4 +1294,6 @@ export const ERRORS = {
|
|
|
1294
1294
|
httpCode: 404,
|
|
1295
1295
|
},
|
|
1296
1296
|
CONSIGNMENT_TRANSFER_ACCEPT_FAILED: { code: 'CTR013', message: 'Failed to accept consignment transfer request', httpCode: 500 },
|
|
1297
|
+
ACCESS_POLICY_FETCH_FAILED: { code: 'AP001', message: 'Error when retrieving the access policy', httpCode: 500 },
|
|
1298
|
+
ACCESS_POLICY_NOT_UPDATED: { code: 'AP002', message: 'Failed to update the access policy', httpCode: 500 },
|
|
1297
1299
|
} as const;
|
package/enum/index.ts
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export enum PermissionKeyEnum {
|
|
2
|
+
PRODUCT_VIEW = 'product.view',
|
|
3
|
+
PRODUCT_EDIT = 'product.edit',
|
|
4
|
+
PRODUCT_DELETE = 'product.delete',
|
|
5
|
+
CONSIGNMENT_VIEW = 'consignment.view',
|
|
6
|
+
CONSIGNMENT_EDIT = 'consignment.edit',
|
|
7
|
+
CONSIGNMENT_DELETE = 'consignment.delete',
|
|
8
|
+
COST_VIEW = 'cost.view',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const PERMISSION_KEY_LIST: PermissionKeyEnum[] = [
|
|
12
|
+
PermissionKeyEnum.PRODUCT_VIEW,
|
|
13
|
+
PermissionKeyEnum.PRODUCT_EDIT,
|
|
14
|
+
PermissionKeyEnum.PRODUCT_DELETE,
|
|
15
|
+
PermissionKeyEnum.CONSIGNMENT_VIEW,
|
|
16
|
+
PermissionKeyEnum.CONSIGNMENT_EDIT,
|
|
17
|
+
PermissionKeyEnum.CONSIGNMENT_DELETE,
|
|
18
|
+
PermissionKeyEnum.COST_VIEW,
|
|
19
|
+
];
|
package/package.json
CHANGED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { PermissionKeyEnum, PERMISSION_KEY_LIST } from '../../enum/permission-key.enum';
|
|
3
|
+
import { UserPanelRoleEnum } from '../../enum/userRole.enum';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Panel roles that can actually be configured. ADMIN is intentionally excluded:
|
|
7
|
+
* it always bypasses UI restrictions, so any ADMIN entry in a policy is dead
|
|
8
|
+
* config. The admin panel must not offer it, and the sanitizer drops it.
|
|
9
|
+
*/
|
|
10
|
+
export const CONFIGURABLE_PANEL_ROLE_LIST: UserPanelRoleEnum[] = [UserPanelRoleEnum.MANAGER, UserPanelRoleEnum.EMPLOYEE];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A sparse map of permission key -> boolean.
|
|
14
|
+
* Semantics are default-allow: a missing key means "allowed", so in practice
|
|
15
|
+
* only explicit `false` (denials) are ever stored.
|
|
16
|
+
*/
|
|
17
|
+
export const PermissionMapSchema = z.partialRecord(z.nativeEnum(PermissionKeyEnum), z.boolean());
|
|
18
|
+
export type PermissionMap = z.infer<typeof PermissionMapSchema>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Full company access policy — the raw configuration edited by an admin.
|
|
22
|
+
* `roles` holds per-role denials; `users` holds per-employee overrides keyed by userUUID.
|
|
23
|
+
*/
|
|
24
|
+
export const AccessPolicySchema = z.object({
|
|
25
|
+
roles: z.partialRecord(z.nativeEnum(UserPanelRoleEnum), PermissionMapSchema).default({}),
|
|
26
|
+
users: z.record(z.uuid(), PermissionMapSchema).default({}),
|
|
27
|
+
});
|
|
28
|
+
export type AccessPolicy = z.infer<typeof AccessPolicySchema>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resolved permissions for a single user — the sparse denial map after merging
|
|
32
|
+
* role defaults with the user's own overrides. Empty for ADMIN / global users.
|
|
33
|
+
* Frontend contract: `can(key) === effective[key] !== false`.
|
|
34
|
+
*/
|
|
35
|
+
export const EffectivePermissionsSchema = PermissionMapSchema;
|
|
36
|
+
export type EffectivePermissions = z.infer<typeof EffectivePermissionsSchema>;
|
|
37
|
+
|
|
38
|
+
const pickPermissionMap = (value: unknown): PermissionMap => {
|
|
39
|
+
const map: PermissionMap = {};
|
|
40
|
+
if (value && typeof value === 'object') {
|
|
41
|
+
const record = value as Record<string, unknown>;
|
|
42
|
+
for (const key of PERMISSION_KEY_LIST) {
|
|
43
|
+
if (typeof record[key] === 'boolean') {
|
|
44
|
+
map[key] = record[key] as boolean;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return map;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Tolerant reader for a stored policy blob. Unlike `AccessPolicySchema.parse`,
|
|
53
|
+
* this NEVER throws on unknown/renamed keys — it strips anything not in the
|
|
54
|
+
* current `PermissionKeyEnum` / configurable roles and keeps the rest.
|
|
55
|
+
*
|
|
56
|
+
* Why: `AccessPolicySchema` uses `partialRecord`, which throws "Invalid key in
|
|
57
|
+
* record" on an unknown key. If a permission key or role is ever renamed/removed
|
|
58
|
+
* while a company still has the old value persisted, a strict parse at the read
|
|
59
|
+
* boundary would bubble up as a 500 from `getMe` for every user in that company.
|
|
60
|
+
* Adding keys is safe; removing/renaming must degrade gracefully — hence this.
|
|
61
|
+
*/
|
|
62
|
+
export const sanitizeStoredAccessPolicy = (raw: unknown): AccessPolicy => {
|
|
63
|
+
const source = raw && typeof raw === 'object' ? (raw as Record<string, unknown>) : {};
|
|
64
|
+
|
|
65
|
+
const roles: AccessPolicy['roles'] = {};
|
|
66
|
+
const rawRoles = source.roles && typeof source.roles === 'object' ? (source.roles as Record<string, unknown>) : {};
|
|
67
|
+
for (const role of CONFIGURABLE_PANEL_ROLE_LIST) {
|
|
68
|
+
if (role in rawRoles) {
|
|
69
|
+
roles[role] = pickPermissionMap(rawRoles[role]);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const users: AccessPolicy['users'] = {};
|
|
74
|
+
const rawUsers = source.users && typeof source.users === 'object' ? (source.users as Record<string, unknown>) : {};
|
|
75
|
+
for (const userUUID of Object.keys(rawUsers)) {
|
|
76
|
+
users[userUUID] = pickPermissionMap(rawUsers[userUUID]);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { roles, users };
|
|
80
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './access-policy.schema';
|
package/schemas/index.ts
CHANGED