@flusys/nestjs-shared 3.0.0-rc → 3.0.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.
@@ -72,11 +72,11 @@ function _ts_param(paramIndex, decorator) {
72
72
  // Apply PermissionGuard to check permissions from cache
73
73
  decorators.push((0, _common.UseGuards)(_guards.JwtAuthGuard, _guards.PermissionGuard));
74
74
  decorators.push((0, _swagger.ApiBearerAuth)());
75
- // Check for complex nested condition first
76
- if (security.condition) {
77
- decorators.push((0, _decorators.RequirePermissionCondition)(security.condition));
75
+ // Check for complex logic first
76
+ if (security.logic) {
77
+ decorators.push((0, _decorators.RequirePermissionLogic)(security.logic));
78
78
  } else if (security.permissions && security.permissions.length > 0) {
79
- if (security.operator === 'or') {
79
+ if (security.operator === 'OR') {
80
80
  decorators.push((0, _decorators.RequireAnyPermission)(...security.permissions));
81
81
  } else {
82
82
  // AND is default and most secure
@@ -17,16 +17,20 @@ _export(exports, {
17
17
  },
18
18
  get RequirePermissionCondition () {
19
19
  return RequirePermissionCondition;
20
+ },
21
+ get RequirePermissionLogic () {
22
+ return RequirePermissionLogic;
20
23
  }
21
24
  });
22
25
  const _common = require("@nestjs/common");
23
26
  const _constants = require("../constants");
24
27
  const RequirePermission = (...permissions)=>(0, _common.SetMetadata)(_constants.PERMISSIONS_KEY, {
25
28
  permissions,
26
- operator: 'and'
29
+ operator: 'AND'
27
30
  });
28
31
  const RequireAnyPermission = (...permissions)=>(0, _common.SetMetadata)(_constants.PERMISSIONS_KEY, {
29
32
  permissions,
30
- operator: 'or'
33
+ operator: 'OR'
31
34
  });
32
- const RequirePermissionCondition = (condition)=>(0, _common.SetMetadata)(_constants.PERMISSIONS_KEY, condition);
35
+ const RequirePermissionLogic = (logic)=>(0, _common.SetMetadata)(_constants.PERMISSIONS_KEY, logic);
36
+ const RequirePermissionCondition = RequirePermissionLogic;
@@ -30,8 +30,8 @@ let PermissionSystemUnavailableException = class PermissionSystemUnavailableExce
30
30
  }
31
31
  };
32
32
  let InsufficientPermissionsException = class InsufficientPermissionsException extends _common.ForbiddenException {
33
- constructor(missingPermissions, operator = 'and'){
34
- const message = operator === 'or' ? `Requires at least one of: ${missingPermissions.join(', ')}` : `Missing required permissions: ${missingPermissions.join(', ')}`;
33
+ constructor(missingPermissions, operator = 'AND'){
34
+ const message = operator === 'OR' ? `Requires at least one of: ${missingPermissions.join(', ')}` : `Missing required permissions: ${missingPermissions.join(', ')}`;
35
35
  super({
36
36
  success: false,
37
37
  message,
@@ -55,8 +55,6 @@ let PermissionGuard = class PermissionGuard {
55
55
  context.getClass()
56
56
  ]);
57
57
  if (!permissionConfig) return true;
58
- const { permissions: requiredPermissions, operator } = this.normalizePermissionConfig(permissionConfig);
59
- if (!requiredPermissions || requiredPermissions.length === 0) return true;
60
58
  const request = context.switchToHttp().getRequest();
61
59
  const user = request.user;
62
60
  if (!user) throw new _common.UnauthorizedException('Authentication required');
@@ -69,90 +67,93 @@ let PermissionGuard = class PermissionGuard {
69
67
  this.logger.warn(`No permissions found (userId: ${user.id})`, 'PermissionGuard');
70
68
  throw new _permissionexception.NoPermissionsFoundException();
71
69
  }
72
- if (this.isNestedCondition(permissionConfig)) {
73
- const result = this.evaluateCondition(permissionConfig, userPermissions);
74
- if (!result.passed) {
75
- this.logger.warn(`Permission denied (userId: ${user.id})`, 'PermissionGuard');
76
- throw new _permissionexception.InsufficientPermissionsException(result.missingPermissions, result.operator);
77
- }
78
- } else {
79
- this.validateSimplePermissions(requiredPermissions, userPermissions, operator);
70
+ const logicNode = this.normalizeToLogicNode(permissionConfig);
71
+ if (!logicNode) return true;
72
+ const result = this.evaluateLogicNode(logicNode, userPermissions);
73
+ if (!result.passed) {
74
+ this.logger.warn(`Permission denied (userId: ${user.id})`, 'PermissionGuard');
75
+ throw new _permissionexception.InsufficientPermissionsException(result.missingPermissions, result.operator);
80
76
  }
81
77
  return true;
82
78
  }
83
- normalizePermissionConfig(config) {
84
- if (Array.isArray(config)) return {
85
- permissions: config,
86
- operator: 'and'
87
- };
88
- return {
89
- permissions: config.permissions || [],
90
- operator: config.operator || 'and'
91
- };
92
- }
93
- validateSimplePermissions(requiredPermissions, userPermissions, operator) {
94
- if (operator === 'or') {
95
- const hasAny = requiredPermissions.some((p)=>this.hasPermission(userPermissions, p));
96
- if (!hasAny) throw new _permissionexception.InsufficientPermissionsException(requiredPermissions, 'or');
97
- } else {
98
- const hasAll = requiredPermissions.every((p)=>this.hasPermission(userPermissions, p));
99
- if (!hasAll) {
100
- const missing = requiredPermissions.filter((p)=>!this.hasPermission(userPermissions, p));
101
- throw new _permissionexception.InsufficientPermissionsException(missing, 'and');
102
- }
79
+ normalizeToLogicNode(config) {
80
+ // string - single permission
81
+ if (typeof config === 'string') {
82
+ return config ? {
83
+ type: 'action',
84
+ actionId: config
85
+ } : null;
103
86
  }
87
+ // string[] - treat as AND of multiple permissions
88
+ if (Array.isArray(config)) {
89
+ if (config.length === 0) return null;
90
+ if (config.length === 1) return {
91
+ type: 'action',
92
+ actionId: config[0]
93
+ };
94
+ return {
95
+ type: 'group',
96
+ operator: 'AND',
97
+ children: config.map((p)=>({
98
+ type: 'action',
99
+ actionId: p
100
+ }))
101
+ };
102
+ }
103
+ // SimplePermissionConfig - { permissions: string[], operator: 'AND'|'OR' }
104
+ if ('permissions' in config && Array.isArray(config.permissions)) {
105
+ const simple = config;
106
+ if (simple.permissions.length === 0) return null;
107
+ if (simple.permissions.length === 1) return {
108
+ type: 'action',
109
+ actionId: simple.permissions[0]
110
+ };
111
+ return {
112
+ type: 'group',
113
+ operator: simple.operator || 'AND',
114
+ children: simple.permissions.map((p)=>({
115
+ type: 'action',
116
+ actionId: p
117
+ }))
118
+ };
119
+ }
120
+ // ILogicNode
121
+ return config;
104
122
  }
105
- isNestedCondition(config) {
106
- if (Array.isArray(config)) return false;
107
- return 'children' in config && Array.isArray(config.children) && config.children.length > 0;
108
- }
109
- evaluateCondition(condition, userPermissions) {
110
- const { permissions = [], operator, children = [] } = condition;
111
- // SECURITY: Fail-closed - deny access when no permissions configured (empty condition)
112
- if (permissions.length === 0 && children.length === 0) {
123
+ evaluateLogicNode(node, userPermissions) {
124
+ if (node.type === 'action') {
125
+ const passed = this.hasPermission(userPermissions, node.actionId);
126
+ return {
127
+ passed,
128
+ missingPermissions: passed ? [] : [
129
+ node.actionId
130
+ ],
131
+ operator: 'AND'
132
+ };
133
+ }
134
+ // Group node
135
+ const { operator, children } = node;
136
+ // SECURITY: Fail-closed - deny access when no children configured
137
+ if (!children || children.length === 0) {
113
138
  return {
114
139
  passed: false,
115
- message: 'No permissions configured - access denied by default',
116
140
  missingPermissions: [],
117
141
  operator
118
142
  };
119
143
  }
120
144
  const results = [];
121
- const failureDetails = [];
122
145
  const missingPermissions = [];
123
- if (permissions.length > 0) {
124
- if (operator === 'or') {
125
- const hasAny = permissions.some((p)=>this.hasPermission(userPermissions, p));
126
- results.push(hasAny);
127
- if (!hasAny) {
128
- failureDetails.push(`needs one of: [${permissions.join(', ')}]`);
129
- missingPermissions.push(...permissions);
130
- }
131
- } else {
132
- const hasAll = permissions.every((p)=>this.hasPermission(userPermissions, p));
133
- results.push(hasAll);
134
- if (!hasAll) {
135
- const missing = permissions.filter((p)=>!this.hasPermission(userPermissions, p));
136
- failureDetails.push(`missing: [${missing.join(', ')}]`);
137
- missingPermissions.push(...missing);
138
- }
139
- }
140
- }
141
146
  for (const child of children){
142
- const childResult = this.evaluateCondition(child, userPermissions);
147
+ const childResult = this.evaluateLogicNode(child, userPermissions);
143
148
  results.push(childResult.passed);
144
149
  if (!childResult.passed) {
145
- failureDetails.push(`(${childResult.message})`);
146
150
  missingPermissions.push(...childResult.missingPermissions);
147
151
  }
148
152
  }
149
- // Evaluate based on operator - empty results already handled above
150
- const passed = operator === 'or' ? results.some((r)=>r) : results.every((r)=>r);
151
- const message = passed ? 'OK' : `Denied: ${failureDetails.join(` ${operator.toUpperCase()} `)}`;
153
+ const passed = operator === 'OR' ? results.some((r)=>r) : results.every((r)=>r);
152
154
  return {
153
155
  passed,
154
- message,
155
- missingPermissions,
156
+ missingPermissions: passed ? [] : missingPermissions,
156
157
  operator
157
158
  };
158
159
  }
@@ -1,4 +1,4 @@
1
- "use strict";
1
+ /** Action node - checks a single permission/action code */ "use strict";
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
@@ -1,11 +1,4 @@
1
- /**
2
- * HTML Sanitizer Utilities
3
- *
4
- * Provides functions for escaping HTML content to prevent XSS attacks.
5
- * Use these utilities when interpolating user-provided variables into HTML content.
6
- */ /**
7
- * HTML entity mapping for escaping special characters
8
- */ "use strict";
1
+ "use strict";
9
2
  Object.defineProperty(exports, "__esModule", {
10
3
  value: true
11
4
  });
@@ -23,7 +16,14 @@ _export(exports, {
23
16
  return escapeHtmlVariables;
24
17
  }
25
18
  });
26
- const HTML_ESCAPE_MAP = {
19
+ /**
20
+ * HTML Sanitizer Utilities
21
+ *
22
+ * Provides functions for escaping HTML content to prevent XSS attacks.
23
+ * Use these utilities when interpolating user-provided variables into HTML content.
24
+ */ /**
25
+ * HTML entity mapping for escaping special characters
26
+ */ const HTML_ESCAPE_MAP = {
27
27
  '&': '&',
28
28
  '<': '&lt;',
29
29
  '>': '&gt;',
@@ -19,6 +19,7 @@ _export(exports, {
19
19
  return parseDurationToMs;
20
20
  }
21
21
  });
22
+ const _config = require("@flusys/nestjs-core/config");
22
23
  const _constants = require("../constants");
23
24
  /** Time unit multipliers in milliseconds */ const TIME_UNIT_MS = {
24
25
  s: 1000,
@@ -45,7 +46,7 @@ function isBrowserRequest(req) {
45
46
  function buildCookieOptions(req) {
46
47
  const hostname = req.hostname || '';
47
48
  const origin = req.headers.origin || '';
48
- const isProduction = process.env.NODE_ENV === 'production';
49
+ const isProduction = _config.envConfig.isProduction();
49
50
  const forwardedProto = req.headers['x-forwarded-proto'];
50
51
  const isHttps = isProduction || forwardedProto === 'https' || origin.startsWith('https://') || req.secure;
51
52
  let domain;
@@ -1,14 +1,14 @@
1
1
  import { BulkResponseDto, DeleteDto, FilterAndPaginationDto, GetByIdBodyDto, ListResponseDto, MessageResponseDto, SingleResponseDto } from '../dtos';
2
2
  import { Identity } from '../entities';
3
- import { ILoggedUserInfo, IService, PermissionCondition, PermissionOperator } from '../interfaces';
3
+ import { ILoggedUserInfo, IPermissionLogic, IService } from '../interfaces';
4
4
  import { Type } from '@nestjs/common';
5
5
  export type ApiEndpoint = 'insert' | 'insertMany' | 'getById' | 'getAll' | 'update' | 'updateMany' | 'delete';
6
6
  export type SecurityLevel = 'public' | 'jwt' | 'permission';
7
7
  export interface EndpointSecurity {
8
8
  level: SecurityLevel;
9
9
  permissions?: string[];
10
- operator?: PermissionOperator;
11
- condition?: PermissionCondition;
10
+ operator?: 'AND' | 'OR';
11
+ logic?: IPermissionLogic;
12
12
  }
13
13
  export type ApiSecurityConfig = {
14
14
  [K in ApiEndpoint]?: EndpointSecurity | SecurityLevel;
@@ -1,4 +1,5 @@
1
- import { PermissionCondition } from '../interfaces/permission.interface';
1
+ import { IPermissionLogic } from '../interfaces/permission.interface';
2
2
  export declare const RequirePermission: (...permissions: string[]) => import("@nestjs/common").CustomDecorator<string>;
3
3
  export declare const RequireAnyPermission: (...permissions: string[]) => import("@nestjs/common").CustomDecorator<string>;
4
- export declare const RequirePermissionCondition: (condition: PermissionCondition) => import("@nestjs/common").CustomDecorator<string>;
4
+ export declare const RequirePermissionLogic: (logic: IPermissionLogic) => import("@nestjs/common").CustomDecorator<string>;
5
+ export declare const RequirePermissionCondition: (logic: IPermissionLogic) => import("@nestjs/common").CustomDecorator<string>;
@@ -3,7 +3,7 @@ export declare class PermissionSystemUnavailableException extends InternalServer
3
3
  constructor(message?: string);
4
4
  }
5
5
  export declare class InsufficientPermissionsException extends ForbiddenException {
6
- constructor(missingPermissions: string[], operator?: 'and' | 'or');
6
+ constructor(missingPermissions: string[], operator?: 'AND' | 'OR');
7
7
  }
8
8
  export declare class NoPermissionsFoundException extends ForbiddenException {
9
9
  constructor();
@@ -25,7 +25,7 @@ function _ts_param(paramIndex, decorator) {
25
25
  decorator(target, key, paramIndex);
26
26
  };
27
27
  }
28
- import { CurrentUser, Public, RequirePermission, RequireAnyPermission, RequirePermissionCondition } from '../decorators';
28
+ import { CurrentUser, Public, RequirePermission, RequireAnyPermission, RequirePermissionLogic } from '../decorators';
29
29
  import { DeleteDto, FilterAndPaginationDto, GetByIdBodyDto } from '../dtos';
30
30
  import { JwtAuthGuard, PermissionGuard } from '../guards';
31
31
  import { IdempotencyInterceptor, SetCreatedByOnBody, SetDeletedByOnBody, SetUpdateByOnBody, Slug } from '../interceptors';
@@ -62,11 +62,11 @@ import { ApiResponseDto } from '../decorators/api-response.decorator';
62
62
  // Apply PermissionGuard to check permissions from cache
63
63
  decorators.push(UseGuards(JwtAuthGuard, PermissionGuard));
64
64
  decorators.push(ApiBearerAuth());
65
- // Check for complex nested condition first
66
- if (security.condition) {
67
- decorators.push(RequirePermissionCondition(security.condition));
65
+ // Check for complex logic first
66
+ if (security.logic) {
67
+ decorators.push(RequirePermissionLogic(security.logic));
68
68
  } else if (security.permissions && security.permissions.length > 0) {
69
- if (security.operator === 'or') {
69
+ if (security.operator === 'OR') {
70
70
  decorators.push(RequireAnyPermission(...security.permissions));
71
71
  } else {
72
72
  // AND is default and most secure
@@ -1,75 +1,28 @@
1
1
  import { SetMetadata } from '@nestjs/common';
2
2
  import { PERMISSIONS_KEY } from '../constants';
3
- /**
4
- * Decorator to require specific permissions for a route
5
- *
6
- * By default uses AND logic (user must have ALL permissions).
7
- * This is the most common and secure pattern.
8
- *
9
- * @param permissions - One or more permission keys required (AND logic)
10
- *
11
- * @example
12
- * // Require single permission
13
- * @RequirePermission('users.read')
14
- *
15
- * @example
16
- * // Require ALL permissions (AND)
17
- * @RequirePermission('users.read', 'users.write')
18
- *
19
- * @example
20
- * // For OR logic, use RequireAnyPermission instead
21
- * @RequireAnyPermission('admin.access', 'manager.access')
22
- */ export const RequirePermission = (...permissions)=>SetMetadata(PERMISSIONS_KEY, {
3
+ /** Require ALL permissions (AND logic) - most secure pattern */ export const RequirePermission = (...permissions)=>SetMetadata(PERMISSIONS_KEY, {
23
4
  permissions,
24
- operator: 'and'
5
+ operator: 'AND'
25
6
  });
26
- /**
27
- * Decorator to require ANY of the specified permissions (OR logic)
28
- *
29
- * User must have at least ONE of the specified permissions.
30
- * Less secure than RequirePermission (AND), use carefully.
31
- *
32
- * @param permissions - User must have at least ONE of these permissions
33
- *
34
- * @example
35
- * @RequireAnyPermission('admin.access', 'manager.access')
36
- */ export const RequireAnyPermission = (...permissions)=>SetMetadata(PERMISSIONS_KEY, {
7
+ /** Require ANY permission (OR logic) - use carefully */ export const RequireAnyPermission = (...permissions)=>SetMetadata(PERMISSIONS_KEY, {
37
8
  permissions,
38
- operator: 'or'
9
+ operator: 'OR'
39
10
  });
40
11
  /**
41
- * Decorator for complex permission conditions with nested logic
42
- *
43
- * Supports building complex permission trees:
44
- * - AND/OR operators
45
- * - Nested children for complex logic
46
- *
47
- * @param condition - Complex permission condition
48
- *
49
- * @example
50
- * // Simple: User needs 'admin' OR 'manager'
51
- * @RequirePermissionCondition({
52
- * operator: 'or',
53
- * permissions: ['admin', 'manager'],
54
- * })
55
- *
12
+ * Require complex permission logic using ILogicNode tree
56
13
  * @example
57
- * // Complex: User needs 'users.read' AND ('admin' OR 'manager')
58
- * @RequirePermissionCondition({
59
- * operator: 'and',
60
- * permissions: ['users.read'],
61
- * children: [
62
- * { operator: 'or', permissions: ['admin', 'manager'] }
63
- * ]
64
- * })
65
- *
14
+ * // Single permission
15
+ * @RequirePermissionLogic('users.read')
66
16
  * @example
67
- * // Very complex: (A AND B) OR (C AND D)
68
- * @RequirePermissionCondition({
69
- * operator: 'or',
70
- * children: [
71
- * { operator: 'and', permissions: ['finance.read', 'finance.write'] },
72
- * { operator: 'and', permissions: ['admin.full', 'reports.access'] }
73
- * ]
17
+ * // Complex: users.read AND (admin OR manager)
18
+ * @RequirePermissionLogic({
19
+ * type: 'group', operator: 'AND', children: [
20
+ * { type: 'action', actionId: 'users.read' },
21
+ * { type: 'group', operator: 'OR', children: [
22
+ * { type: 'action', actionId: 'admin' },
23
+ * { type: 'action', actionId: 'manager' },
24
+ * ]},
25
+ * ],
74
26
  * })
75
- */ export const RequirePermissionCondition = (condition)=>SetMetadata(PERMISSIONS_KEY, condition);
27
+ */ export const RequirePermissionLogic = (logic)=>SetMetadata(PERMISSIONS_KEY, logic);
28
+ /** @deprecated Use RequirePermissionLogic instead */ export const RequirePermissionCondition = RequirePermissionLogic;
@@ -13,8 +13,8 @@ import { ForbiddenException, InternalServerErrorException } from '@nestjs/common
13
13
  /**
14
14
  * Exception thrown when user lacks required permissions
15
15
  */ export class InsufficientPermissionsException extends ForbiddenException {
16
- constructor(missingPermissions, operator = 'and'){
17
- const message = operator === 'or' ? `Requires at least one of: ${missingPermissions.join(', ')}` : `Missing required permissions: ${missingPermissions.join(', ')}`;
16
+ constructor(missingPermissions, operator = 'AND'){
17
+ const message = operator === 'OR' ? `Requires at least one of: ${missingPermissions.join(', ')}` : `Missing required permissions: ${missingPermissions.join(', ')}`;
18
18
  super({
19
19
  success: false,
20
20
  message,
@@ -45,8 +45,6 @@ export class PermissionGuard {
45
45
  context.getClass()
46
46
  ]);
47
47
  if (!permissionConfig) return true;
48
- const { permissions: requiredPermissions, operator } = this.normalizePermissionConfig(permissionConfig);
49
- if (!requiredPermissions || requiredPermissions.length === 0) return true;
50
48
  const request = context.switchToHttp().getRequest();
51
49
  const user = request.user;
52
50
  if (!user) throw new UnauthorizedException('Authentication required');
@@ -59,90 +57,93 @@ export class PermissionGuard {
59
57
  this.logger.warn(`No permissions found (userId: ${user.id})`, 'PermissionGuard');
60
58
  throw new NoPermissionsFoundException();
61
59
  }
62
- if (this.isNestedCondition(permissionConfig)) {
63
- const result = this.evaluateCondition(permissionConfig, userPermissions);
64
- if (!result.passed) {
65
- this.logger.warn(`Permission denied (userId: ${user.id})`, 'PermissionGuard');
66
- throw new InsufficientPermissionsException(result.missingPermissions, result.operator);
67
- }
68
- } else {
69
- this.validateSimplePermissions(requiredPermissions, userPermissions, operator);
60
+ const logicNode = this.normalizeToLogicNode(permissionConfig);
61
+ if (!logicNode) return true;
62
+ const result = this.evaluateLogicNode(logicNode, userPermissions);
63
+ if (!result.passed) {
64
+ this.logger.warn(`Permission denied (userId: ${user.id})`, 'PermissionGuard');
65
+ throw new InsufficientPermissionsException(result.missingPermissions, result.operator);
70
66
  }
71
67
  return true;
72
68
  }
73
- normalizePermissionConfig(config) {
74
- if (Array.isArray(config)) return {
75
- permissions: config,
76
- operator: 'and'
77
- };
78
- return {
79
- permissions: config.permissions || [],
80
- operator: config.operator || 'and'
81
- };
82
- }
83
- validateSimplePermissions(requiredPermissions, userPermissions, operator) {
84
- if (operator === 'or') {
85
- const hasAny = requiredPermissions.some((p)=>this.hasPermission(userPermissions, p));
86
- if (!hasAny) throw new InsufficientPermissionsException(requiredPermissions, 'or');
87
- } else {
88
- const hasAll = requiredPermissions.every((p)=>this.hasPermission(userPermissions, p));
89
- if (!hasAll) {
90
- const missing = requiredPermissions.filter((p)=>!this.hasPermission(userPermissions, p));
91
- throw new InsufficientPermissionsException(missing, 'and');
92
- }
69
+ normalizeToLogicNode(config) {
70
+ // string - single permission
71
+ if (typeof config === 'string') {
72
+ return config ? {
73
+ type: 'action',
74
+ actionId: config
75
+ } : null;
93
76
  }
77
+ // string[] - treat as AND of multiple permissions
78
+ if (Array.isArray(config)) {
79
+ if (config.length === 0) return null;
80
+ if (config.length === 1) return {
81
+ type: 'action',
82
+ actionId: config[0]
83
+ };
84
+ return {
85
+ type: 'group',
86
+ operator: 'AND',
87
+ children: config.map((p)=>({
88
+ type: 'action',
89
+ actionId: p
90
+ }))
91
+ };
92
+ }
93
+ // SimplePermissionConfig - { permissions: string[], operator: 'AND'|'OR' }
94
+ if ('permissions' in config && Array.isArray(config.permissions)) {
95
+ const simple = config;
96
+ if (simple.permissions.length === 0) return null;
97
+ if (simple.permissions.length === 1) return {
98
+ type: 'action',
99
+ actionId: simple.permissions[0]
100
+ };
101
+ return {
102
+ type: 'group',
103
+ operator: simple.operator || 'AND',
104
+ children: simple.permissions.map((p)=>({
105
+ type: 'action',
106
+ actionId: p
107
+ }))
108
+ };
109
+ }
110
+ // ILogicNode
111
+ return config;
94
112
  }
95
- isNestedCondition(config) {
96
- if (Array.isArray(config)) return false;
97
- return 'children' in config && Array.isArray(config.children) && config.children.length > 0;
98
- }
99
- evaluateCondition(condition, userPermissions) {
100
- const { permissions = [], operator, children = [] } = condition;
101
- // SECURITY: Fail-closed - deny access when no permissions configured (empty condition)
102
- if (permissions.length === 0 && children.length === 0) {
113
+ evaluateLogicNode(node, userPermissions) {
114
+ if (node.type === 'action') {
115
+ const passed = this.hasPermission(userPermissions, node.actionId);
116
+ return {
117
+ passed,
118
+ missingPermissions: passed ? [] : [
119
+ node.actionId
120
+ ],
121
+ operator: 'AND'
122
+ };
123
+ }
124
+ // Group node
125
+ const { operator, children } = node;
126
+ // SECURITY: Fail-closed - deny access when no children configured
127
+ if (!children || children.length === 0) {
103
128
  return {
104
129
  passed: false,
105
- message: 'No permissions configured - access denied by default',
106
130
  missingPermissions: [],
107
131
  operator
108
132
  };
109
133
  }
110
134
  const results = [];
111
- const failureDetails = [];
112
135
  const missingPermissions = [];
113
- if (permissions.length > 0) {
114
- if (operator === 'or') {
115
- const hasAny = permissions.some((p)=>this.hasPermission(userPermissions, p));
116
- results.push(hasAny);
117
- if (!hasAny) {
118
- failureDetails.push(`needs one of: [${permissions.join(', ')}]`);
119
- missingPermissions.push(...permissions);
120
- }
121
- } else {
122
- const hasAll = permissions.every((p)=>this.hasPermission(userPermissions, p));
123
- results.push(hasAll);
124
- if (!hasAll) {
125
- const missing = permissions.filter((p)=>!this.hasPermission(userPermissions, p));
126
- failureDetails.push(`missing: [${missing.join(', ')}]`);
127
- missingPermissions.push(...missing);
128
- }
129
- }
130
- }
131
136
  for (const child of children){
132
- const childResult = this.evaluateCondition(child, userPermissions);
137
+ const childResult = this.evaluateLogicNode(child, userPermissions);
133
138
  results.push(childResult.passed);
134
139
  if (!childResult.passed) {
135
- failureDetails.push(`(${childResult.message})`);
136
140
  missingPermissions.push(...childResult.missingPermissions);
137
141
  }
138
142
  }
139
- // Evaluate based on operator - empty results already handled above
140
- const passed = operator === 'or' ? results.some((r)=>r) : results.every((r)=>r);
141
- const message = passed ? 'OK' : `Denied: ${failureDetails.join(` ${operator.toUpperCase()} `)}`;
143
+ const passed = operator === 'OR' ? results.some((r)=>r) : results.every((r)=>r);
142
144
  return {
143
145
  passed,
144
- message,
145
- missingPermissions,
146
+ missingPermissions: passed ? [] : missingPermissions,
146
147
  operator
147
148
  };
148
149
  }
@@ -1,3 +1 @@
1
- /**
2
- * Configuration for the permission guard
3
- */ export { };
1
+ /** Action node - checks a single permission/action code */ /** Configuration for the permission guard */ export { };
@@ -1,3 +1,4 @@
1
+ import { envConfig } from '@flusys/nestjs-core/config';
1
2
  import { CLIENT_TYPE_HEADER } from '../constants';
2
3
  /** Time unit multipliers in milliseconds */ const TIME_UNIT_MS = {
3
4
  s: 1000,
@@ -30,7 +31,7 @@ import { CLIENT_TYPE_HEADER } from '../constants';
30
31
  */ export function buildCookieOptions(req) {
31
32
  const hostname = req.hostname || '';
32
33
  const origin = req.headers.origin || '';
33
- const isProduction = process.env.NODE_ENV === 'production';
34
+ const isProduction = envConfig.isProduction();
34
35
  const forwardedProto = req.headers['x-forwarded-proto'];
35
36
  const isHttps = isProduction || forwardedProto === 'https' || origin.startsWith('https://') || req.secure;
36
37
  let domain;
@@ -10,10 +10,8 @@ export declare class PermissionGuard implements CanActivate {
10
10
  private readonly logger;
11
11
  constructor(reflector: Reflector, cache?: HybridCache, config?: PermissionGuardConfig, logger?: ILogger);
12
12
  canActivate(context: ExecutionContext): Promise<boolean>;
13
- private normalizePermissionConfig;
14
- private validateSimplePermissions;
15
- private isNestedCondition;
16
- private evaluateCondition;
13
+ private normalizeToLogicNode;
14
+ private evaluateLogicNode;
17
15
  private getUserPermissions;
18
16
  private buildPermissionCacheKey;
19
17
  private hasPermission;