@mondaydotcomorg/monday-authorization 3.5.1-fix-authorize-profile-picker-7481de0 → 3.5.2-feat-shaime-support-entity-attributes-4-ddec1d3
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/dist/authorization-attributes-ms-service.d.ts +88 -0
- package/dist/authorization-attributes-ms-service.d.ts.map +1 -0
- package/dist/authorization-attributes-ms-service.js +290 -0
- package/dist/authorization-attributes-service.d.ts +25 -47
- package/dist/authorization-attributes-service.d.ts.map +1 -1
- package/dist/authorization-attributes-service.js +32 -171
- package/dist/authorization-attributes-sns-service.d.ts +91 -0
- package/dist/authorization-attributes-sns-service.d.ts.map +1 -0
- package/dist/authorization-attributes-sns-service.js +217 -0
- package/dist/authorization-service.d.ts.map +1 -1
- package/dist/authorization-service.js +7 -34
- package/dist/base-attribute-assignment.d.ts +20 -0
- package/dist/base-attribute-assignment.d.ts.map +1 -0
- package/dist/base-attribute-assignment.js +36 -0
- package/dist/constants/sns.d.ts +12 -2
- package/dist/constants/sns.d.ts.map +1 -1
- package/dist/constants/sns.js +22 -2
- package/dist/entity-attribute-assignment.d.ts +20 -0
- package/dist/entity-attribute-assignment.d.ts.map +1 -0
- package/dist/entity-attribute-assignment.js +32 -0
- package/dist/entity-attributes-constants.d.ts +7 -0
- package/dist/entity-attributes-constants.d.ts.map +1 -0
- package/dist/entity-attributes-constants.js +11 -0
- package/dist/errors/argument-error.d.ts +4 -0
- package/dist/errors/argument-error.d.ts.map +1 -0
- package/dist/errors/argument-error.js +11 -0
- package/dist/esm/authorization-attributes-ms-service.d.ts +88 -0
- package/dist/esm/authorization-attributes-ms-service.d.ts.map +1 -0
- package/dist/esm/authorization-attributes-ms-service.mjs +288 -0
- package/dist/esm/authorization-attributes-service.d.ts +25 -47
- package/dist/esm/authorization-attributes-service.d.ts.map +1 -1
- package/dist/esm/authorization-attributes-service.mjs +32 -167
- package/dist/esm/authorization-attributes-sns-service.d.ts +91 -0
- package/dist/esm/authorization-attributes-sns-service.d.ts.map +1 -0
- package/dist/esm/authorization-attributes-sns-service.mjs +211 -0
- package/dist/esm/authorization-service.d.ts.map +1 -1
- package/dist/esm/authorization-service.mjs +7 -34
- package/dist/esm/base-attribute-assignment.d.ts +20 -0
- package/dist/esm/base-attribute-assignment.d.ts.map +1 -0
- package/dist/esm/base-attribute-assignment.mjs +30 -0
- package/dist/esm/constants/sns.d.ts +12 -2
- package/dist/esm/constants/sns.d.ts.map +1 -1
- package/dist/esm/constants/sns.mjs +17 -3
- package/dist/esm/entity-attribute-assignment.d.ts +20 -0
- package/dist/esm/entity-attribute-assignment.d.ts.map +1 -0
- package/dist/esm/entity-attribute-assignment.mjs +30 -0
- package/dist/esm/entity-attributes-constants.d.ts +7 -0
- package/dist/esm/entity-attributes-constants.d.ts.map +1 -0
- package/dist/esm/entity-attributes-constants.mjs +9 -0
- package/dist/esm/errors/argument-error.d.ts +4 -0
- package/dist/esm/errors/argument-error.d.ts.map +1 -0
- package/dist/esm/errors/argument-error.mjs +9 -0
- package/dist/esm/resource-attribute-assignment.d.ts +20 -0
- package/dist/esm/resource-attribute-assignment.d.ts.map +1 -0
- package/dist/esm/resource-attribute-assignment.mjs +30 -0
- package/dist/esm/resource-attributes-constants.d.ts +25 -0
- package/dist/esm/resource-attributes-constants.d.ts.map +1 -0
- package/dist/esm/resource-attributes-constants.mjs +28 -0
- package/dist/esm/testKit/index.d.ts +4 -4
- package/dist/esm/testKit/index.d.ts.map +1 -1
- package/dist/esm/types/authorization-attributes-contracts.d.ts +26 -11
- package/dist/esm/types/authorization-attributes-contracts.d.ts.map +1 -1
- package/dist/esm/types/authorization-attributes-contracts.mjs +6 -6
- package/dist/esm/types/authorization-attributes-service.interface.d.ts +57 -0
- package/dist/esm/types/authorization-attributes-service.interface.d.ts.map +1 -0
- package/dist/esm/types/authorization-attributes-service.interface.mjs +1 -0
- package/dist/esm/utils/validation.d.ts +45 -0
- package/dist/esm/utils/validation.d.ts.map +1 -0
- package/dist/esm/utils/validation.mjs +152 -0
- package/dist/resource-attribute-assignment.d.ts +20 -0
- package/dist/resource-attribute-assignment.d.ts.map +1 -0
- package/dist/resource-attribute-assignment.js +32 -0
- package/dist/resource-attributes-constants.d.ts +25 -0
- package/dist/resource-attributes-constants.d.ts.map +1 -0
- package/dist/resource-attributes-constants.js +31 -0
- package/dist/testKit/index.d.ts +4 -4
- package/dist/testKit/index.d.ts.map +1 -1
- package/dist/types/authorization-attributes-contracts.d.ts +26 -11
- package/dist/types/authorization-attributes-contracts.d.ts.map +1 -1
- package/dist/types/authorization-attributes-contracts.js +5 -5
- package/dist/types/authorization-attributes-service.interface.d.ts +57 -0
- package/dist/types/authorization-attributes-service.interface.d.ts.map +1 -0
- package/dist/types/authorization-attributes-service.interface.js +1 -0
- package/dist/utils/validation.d.ts +45 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +158 -0
- package/package.json +3 -1
- package/src/authorization-attributes-ms-service.ts +438 -0
- package/src/authorization-attributes-service.ts +34 -222
- package/src/authorization-attributes-sns-service.ts +312 -0
- package/src/authorization-service.ts +11 -71
- package/src/base-attribute-assignment.ts +46 -0
- package/src/constants/sns.ts +19 -2
- package/src/entity-attribute-assignment.ts +30 -0
- package/src/entity-attributes-constants.ts +7 -0
- package/src/errors/argument-error.ts +7 -0
- package/src/resource-attribute-assignment.ts +38 -0
- package/src/resource-attributes-constants.ts +27 -0
- package/src/testKit/index.ts +5 -5
- package/src/types/authorization-attributes-contracts.ts +34 -11
- package/src/types/authorization-attributes-service.interface.ts +101 -0
- package/src/utils/validation.ts +171 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ValidationUtils } from './utils/validation';
|
|
2
|
+
import isEqual from 'lodash/isEqual.js';
|
|
3
|
+
import { EntityAttributeAssignment, ResourceAttributeDelete } from './types/authorization-attributes-contracts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Base class for attribute assignments (Resource or Entity)
|
|
7
|
+
* Provides common validation and functionality
|
|
8
|
+
*/
|
|
9
|
+
export abstract class BaseAttributeAssignment<TId extends number, TType extends string> {
|
|
10
|
+
public readonly id: TId;
|
|
11
|
+
public readonly type: TType;
|
|
12
|
+
public readonly attributeKey: string;
|
|
13
|
+
public readonly attributeValue: string;
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
id: TId,
|
|
17
|
+
type: string,
|
|
18
|
+
attributeKey: string,
|
|
19
|
+
attributeValue: string,
|
|
20
|
+
validTypes: readonly string[],
|
|
21
|
+
idFieldName: string,
|
|
22
|
+
typeFieldName: string
|
|
23
|
+
) {
|
|
24
|
+
const validated = ValidationUtils.validateAssignment<TType>(
|
|
25
|
+
{ id, type, attributeKey, attributeValue },
|
|
26
|
+
validTypes as readonly TType[],
|
|
27
|
+
{ id: idFieldName, type: typeFieldName }
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
this.id = validated.id as TId;
|
|
31
|
+
this.type = validated.type as TType;
|
|
32
|
+
this.attributeKey = validated.attributeKey;
|
|
33
|
+
this.attributeValue = validated.attributeValue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Compares two assignments for equality
|
|
38
|
+
* @param other Another assignment instance
|
|
39
|
+
* @returns true if all properties are equal
|
|
40
|
+
*/
|
|
41
|
+
equals(other: BaseAttributeAssignment<TId, TType>): boolean {
|
|
42
|
+
return isEqual(this, other);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
abstract toDataTransferObject(): EntityAttributeAssignment | ResourceAttributeDelete;
|
|
46
|
+
}
|
package/src/constants/sns.ts
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
1
|
+
export enum SnsTopicType {
|
|
2
|
+
RESOURCE = 'resource',
|
|
3
|
+
ENTITY = 'entity',
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// Resource SNS constants
|
|
7
|
+
export const RESOURCE_SNS_ARN_ENV_VAR_NAME = 'SHARED_AUTHORIZATION_SNS_ENDPOINT_RESOURCE_ATTRIBUTES';
|
|
8
|
+
export const RESOURCE_SNS_DEV_TEST_NAME =
|
|
3
9
|
'arn:aws:sns:us-east-1:000000000000:monday-authorization-resource-attributes-sns-local';
|
|
10
|
+
|
|
11
|
+
// Entity SNS constants
|
|
12
|
+
export const ENTITY_SNS_ARN_ENV_VAR_NAME = 'SHARED_AUTHORIZATION_SNS_ENDPOINT_ENTITY_ATTRIBUTES';
|
|
13
|
+
export const ENTITY_SNS_DEV_TEST_NAME =
|
|
14
|
+
'arn:aws:sns:us-east-1:000000000000:monday-authorization-entity-attributes-sns-local';
|
|
4
15
|
export const RESOURCE_ATTRIBUTES_SNS_UPDATE_OPERATION_MESSAGE_KIND = 'resourceAttributeModification';
|
|
16
|
+
export const ENTITY_ATTRIBUTES_SNS_UPDATE_OPERATION_MESSAGE_KIND = 'entityAttributeModification';
|
|
5
17
|
export const ASYNC_RESOURCE_ATTRIBUTES_MAX_OPERATIONS_PER_MESSAGE = 100;
|
|
18
|
+
export const ASYNC_ENTITY_ATTRIBUTES_MAX_OPERATIONS_PER_MESSAGE = 100;
|
|
19
|
+
|
|
20
|
+
// Legacy exports for backward compatibility
|
|
21
|
+
export const SNS_ARN_ENV_VAR_NAME = RESOURCE_SNS_ARN_ENV_VAR_NAME;
|
|
22
|
+
export const SNS_DEV_TEST_NAME = RESOURCE_SNS_DEV_TEST_NAME;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ENTITY_TYPES, EntityType } from './entity-attributes-constants';
|
|
2
|
+
import { BaseAttributeAssignment } from './base-attribute-assignment';
|
|
3
|
+
|
|
4
|
+
export class EntityAttributeAssignment extends BaseAttributeAssignment<number, EntityType> {
|
|
5
|
+
public readonly entityId: number;
|
|
6
|
+
public readonly entityType: EntityType;
|
|
7
|
+
|
|
8
|
+
constructor(entityId: number, entityType: string, attributeKey: string, attributeValue: string) {
|
|
9
|
+
super(entityId, entityType, attributeKey, attributeValue, ENTITY_TYPES, 'entityId', 'entityType');
|
|
10
|
+
this.entityId = entityId;
|
|
11
|
+
this.entityType = this.type;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
toDataTransferObject() {
|
|
15
|
+
return {
|
|
16
|
+
entityId: this.entityId,
|
|
17
|
+
entityType: this.entityType,
|
|
18
|
+
key: this.attributeKey,
|
|
19
|
+
value: this.attributeValue,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Compares two assignments for equality
|
|
24
|
+
* @param other Another EntityAttributeAssignment instance
|
|
25
|
+
* @returns true if all properties are equal
|
|
26
|
+
*/
|
|
27
|
+
equals(other: EntityAttributeAssignment): boolean {
|
|
28
|
+
return super.equals(other);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { RESOURCE_TYPES, ResourceType } from './resource-attributes-constants';
|
|
2
|
+
import { BaseAttributeAssignment } from './base-attribute-assignment';
|
|
3
|
+
|
|
4
|
+
export class ResourceAttributeAssignment extends BaseAttributeAssignment<number, ResourceType> {
|
|
5
|
+
public readonly resourceId: number;
|
|
6
|
+
public readonly resourceType: ResourceType;
|
|
7
|
+
|
|
8
|
+
constructor(resourceId: number, resourceType: ResourceType, attributeKey: string, attributeValue: string) {
|
|
9
|
+
super(
|
|
10
|
+
resourceId,
|
|
11
|
+
resourceType,
|
|
12
|
+
attributeKey,
|
|
13
|
+
attributeValue,
|
|
14
|
+
Object.values(RESOURCE_TYPES),
|
|
15
|
+
'resourceId',
|
|
16
|
+
'resourceType'
|
|
17
|
+
);
|
|
18
|
+
this.resourceId = resourceId;
|
|
19
|
+
this.resourceType = this.type;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
toDataTransferObject() {
|
|
23
|
+
return {
|
|
24
|
+
resourceId: this.resourceId,
|
|
25
|
+
resourceType: this.resourceType,
|
|
26
|
+
key: this.attributeKey,
|
|
27
|
+
value: this.attributeValue,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Compares two assignments for equality
|
|
32
|
+
* @param other Another ResourceAttributeAssignment instance
|
|
33
|
+
* @returns true if all properties are equal
|
|
34
|
+
*/
|
|
35
|
+
equals(other: ResourceAttributeAssignment): boolean {
|
|
36
|
+
return super.equals(other);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const RESOURCE_ATTRIBUTES_CONSTANTS = {
|
|
2
|
+
ACCOUNT_RESOURCE_ATTRIBUTES: {
|
|
3
|
+
ENABLE_MEMBERS_INVITE_FROM_NON_AUTH_DOMAIN: 'enable_members_invite_from_non_auth_domain',
|
|
4
|
+
},
|
|
5
|
+
WORKSPACE_RESOURCE_ATTRIBUTES: {
|
|
6
|
+
IS_DEFAULT_WORKSPACE: 'is_default_workspace',
|
|
7
|
+
},
|
|
8
|
+
BOARD_RESOURCE_ATTRIBUTES: {
|
|
9
|
+
IS_SYNCABLE_CHILD_ENTITY: 'is_syncable_child_entity',
|
|
10
|
+
SYSTEM_ENTITY_TYPE: 'system_entity_type',
|
|
11
|
+
},
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
export enum ResourceType {
|
|
15
|
+
Account = 'account',
|
|
16
|
+
AccountProduct = 'account_product',
|
|
17
|
+
Workspace = 'workspace',
|
|
18
|
+
Board = 'board',
|
|
19
|
+
Item = 'item',
|
|
20
|
+
Team = 'team',
|
|
21
|
+
Overview = 'overview',
|
|
22
|
+
Document = 'document',
|
|
23
|
+
Crm = 'crm',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Define the array of strings and use 'as const' to make its contents literal types
|
|
27
|
+
export const RESOURCE_TYPES = Object.freeze(Object.values(ResourceType));
|
package/src/testKit/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BaseRequest, BaseResponse, ContextGetter, Resource, ResourceGetter } from '../types/general';
|
|
2
2
|
import { defaultContextGetter } from '../authorization-middleware';
|
|
3
3
|
import { AuthorizationInternalService } from '../authorization-internal-service';
|
|
4
4
|
import type { NextFunction } from 'express';
|
|
@@ -7,11 +7,11 @@ export type TestPermittedAction = {
|
|
|
7
7
|
accountId: number;
|
|
8
8
|
userId: number;
|
|
9
9
|
resources: Resource[];
|
|
10
|
-
action:
|
|
10
|
+
action: string;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
let testPermittedActions: TestPermittedAction[] = [];
|
|
14
|
-
export const addTestPermittedAction = (accountId: number, userId: number, resources: Resource[], action:
|
|
14
|
+
export const addTestPermittedAction = (accountId: number, userId: number, resources: Resource[], action: string) => {
|
|
15
15
|
testPermittedActions.push({ accountId, userId, resources, action });
|
|
16
16
|
};
|
|
17
17
|
|
|
@@ -19,7 +19,7 @@ export const clearTestPermittedActions = () => {
|
|
|
19
19
|
testPermittedActions = [];
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
const isActionAuthorized = (accountId: number, userId: number, resources: Resource[], action:
|
|
22
|
+
const isActionAuthorized = (accountId: number, userId: number, resources: Resource[], action: string) => {
|
|
23
23
|
// If no resources to check, deny access
|
|
24
24
|
if (resources.length === 0) {
|
|
25
25
|
return { isAuthorized: false };
|
|
@@ -46,7 +46,7 @@ const isActionAuthorized = (accountId: number, userId: number, resources: Resour
|
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
export const getTestAuthorizationMiddleware = (
|
|
49
|
-
action:
|
|
49
|
+
action: string,
|
|
50
50
|
resourceGetter: ResourceGetter,
|
|
51
51
|
contextGetter?: ContextGetter
|
|
52
52
|
) => {
|
|
@@ -1,33 +1,56 @@
|
|
|
1
1
|
import { Resource } from './general';
|
|
2
|
+
import type { EntityType } from '../entity-attributes-constants';
|
|
3
|
+
import type { ResourceType } from '../resource-attributes-constants';
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
resourceType: Resource['type'];
|
|
5
|
-
resourceId: Resource['id'];
|
|
5
|
+
interface AttributeAssignment {
|
|
6
6
|
key: string;
|
|
7
7
|
value: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
// Resource Attribute Assignment - matching API contract
|
|
11
|
+
export interface ResourceAttributeAssignment extends AttributeAssignment {
|
|
12
|
+
resourceId: number;
|
|
13
|
+
resourceType: ResourceType;
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
// Entity Attribute Assignment - matching API contract
|
|
17
|
+
// Note: For validation, use the EntityAttributeAssignment class from '../entity-attribute-assignment'
|
|
18
|
+
export interface EntityAttributeAssignment extends AttributeAssignment {
|
|
19
|
+
entityId: number;
|
|
20
|
+
entityType: EntityType;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Legacy types for backward compatibility
|
|
14
24
|
export interface ResourceAttributeDelete {
|
|
15
25
|
resourceType: Resource['type'];
|
|
16
26
|
resourceId: Resource['id'];
|
|
17
27
|
key: string;
|
|
18
28
|
}
|
|
19
29
|
|
|
20
|
-
export
|
|
30
|
+
export interface EntityAttributeDelete {
|
|
31
|
+
entityType: EntityType;
|
|
32
|
+
entityId: number;
|
|
33
|
+
key: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export enum AttributeOperation {
|
|
21
37
|
UPSERT = 'upsert',
|
|
22
38
|
DELETE = 'delete',
|
|
23
39
|
}
|
|
24
40
|
|
|
25
|
-
|
|
26
|
-
|
|
41
|
+
// Response types
|
|
42
|
+
export interface ResourceAttributeResponse {
|
|
43
|
+
attributes: ResourceAttributeAssignment[];
|
|
27
44
|
}
|
|
28
45
|
|
|
29
|
-
interface
|
|
30
|
-
|
|
46
|
+
export interface EntityAttributeResponse {
|
|
47
|
+
attributes: EntityAttributeAssignment[];
|
|
31
48
|
}
|
|
32
49
|
|
|
33
|
-
export
|
|
50
|
+
export interface ResourceAttributeOperation extends ResourceAttributeAssignment {
|
|
51
|
+
operationType: AttributeOperation;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface EntityAttributeOperation extends EntityAttributeAssignment {
|
|
55
|
+
operationType: AttributeOperation;
|
|
56
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ResourceAttributeAssignment as ResourceAttributeAssignmentContract,
|
|
3
|
+
EntityAttributeAssignment as EntityAttributeAssignmentContract,
|
|
4
|
+
ResourceAttributeOperation,
|
|
5
|
+
EntityAttributeOperation,
|
|
6
|
+
} from './authorization-attributes-contracts';
|
|
7
|
+
import { ResourceAttributeAssignment } from '../resource-attribute-assignment';
|
|
8
|
+
import { EntityAttributeAssignment } from '../entity-attribute-assignment';
|
|
9
|
+
import { Resource } from './general';
|
|
10
|
+
import { EntityType } from '../entity-attributes-constants';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resource type compatible with both MS and SNS services
|
|
14
|
+
*/
|
|
15
|
+
export interface CompatibleResource {
|
|
16
|
+
resourceType?: string;
|
|
17
|
+
resourceId?: number;
|
|
18
|
+
type?: string;
|
|
19
|
+
id?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Interface for authorization attributes operations.
|
|
24
|
+
* Both MS (direct) and SNS (async) services implement this interface.
|
|
25
|
+
*/
|
|
26
|
+
export interface AuthorizationAttributesService {
|
|
27
|
+
/**authorization-attributes-service.ts
|
|
28
|
+
* Upserts resource attributes.
|
|
29
|
+
* For MS service: returns Promise<void>
|
|
30
|
+
* For SNS service: requires appName and callerActionIdentifier, returns Promise<ResourceAttributesOperation[]>
|
|
31
|
+
*/
|
|
32
|
+
upsertResourceAttributes(
|
|
33
|
+
accountId: number,
|
|
34
|
+
resourceAttributeAssignments: ResourceAttributeAssignment[] | ResourceAttributeAssignmentContract[],
|
|
35
|
+
appName?: string,
|
|
36
|
+
callerActionIdentifier?: string
|
|
37
|
+
): Promise<void | ResourceAttributeOperation[]>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Deletes resource attributes.
|
|
41
|
+
* For MS service: returns Promise<void>
|
|
42
|
+
* For SNS service: requires appName and callerActionIdentifier, returns Promise<ResourceAttributesOperation[]>
|
|
43
|
+
*/
|
|
44
|
+
deleteResourceAttributes(
|
|
45
|
+
accountId: number,
|
|
46
|
+
resource: CompatibleResource | Resource,
|
|
47
|
+
attributeKeys: string[],
|
|
48
|
+
appName?: string,
|
|
49
|
+
callerActionIdentifier?: string
|
|
50
|
+
): Promise<void | ResourceAttributeOperation[]>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Upserts entity attributes.
|
|
54
|
+
* For MS service: returns Promise<void>
|
|
55
|
+
* For SNS service: requires appName and callerActionIdentifier, returns Promise<EntityAttributesOperation[]>
|
|
56
|
+
*/
|
|
57
|
+
upsertEntityAttributes(
|
|
58
|
+
accountId: number,
|
|
59
|
+
entityAttributeAssignments: EntityAttributeAssignment[] | EntityAttributeAssignmentContract[],
|
|
60
|
+
appName?: string,
|
|
61
|
+
callerActionIdentifier?: string
|
|
62
|
+
): Promise<void | EntityAttributeOperation[]>;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Deletes entity attributes.
|
|
66
|
+
* For MS service: returns Promise<void>
|
|
67
|
+
* For SNS service: requires appName and callerActionIdentifier, returns Promise<EntityAttributesOperation[]>
|
|
68
|
+
*/
|
|
69
|
+
deleteEntityAttributes(
|
|
70
|
+
accountId: number,
|
|
71
|
+
entityType: EntityType | string,
|
|
72
|
+
entityId: number,
|
|
73
|
+
attributeKeys: string[],
|
|
74
|
+
appName?: string,
|
|
75
|
+
callerActionIdentifier?: string
|
|
76
|
+
): Promise<void | EntityAttributeOperation[]>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Updates resource attributes (batch operations).
|
|
80
|
+
* For MS service: may throw error or delegate to upsert/delete
|
|
81
|
+
* For SNS service: returns Promise<ResourceAttributesOperation[]>
|
|
82
|
+
*/
|
|
83
|
+
updateResourceAttributes(
|
|
84
|
+
accountId: number,
|
|
85
|
+
appName: string,
|
|
86
|
+
callerActionIdentifier: string,
|
|
87
|
+
resourceAttributeOperations: ResourceAttributeOperation[]
|
|
88
|
+
): Promise<ResourceAttributeOperation[]>;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Updates entity attributes (batch operations).
|
|
92
|
+
* For MS service: may throw error or delegate to upsert/delete
|
|
93
|
+
* For SNS service: returns Promise<EntityAttributesOperation[]>
|
|
94
|
+
*/
|
|
95
|
+
updateEntityAttributes(
|
|
96
|
+
accountId: number,
|
|
97
|
+
appName: string,
|
|
98
|
+
callerActionIdentifier: string,
|
|
99
|
+
entityAttributeOperations: EntityAttributeOperation[]
|
|
100
|
+
): Promise<EntityAttributeOperation[]>;
|
|
101
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import Ajv from 'ajv';
|
|
2
|
+
import { ArgumentError } from '../errors/argument-error';
|
|
3
|
+
import { Resource } from '../types/general';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Utility class for common validation operations using AJV
|
|
7
|
+
*/
|
|
8
|
+
export class ValidationUtils {
|
|
9
|
+
private static ajv = new Ajv({ allErrors: true });
|
|
10
|
+
/**
|
|
11
|
+
* Validates that a value is an integer. Throws ArgumentError with a descriptive message on failure.
|
|
12
|
+
*/
|
|
13
|
+
static validateInteger(value: number): void {
|
|
14
|
+
const numberSchema = { type: 'number', multipleOf: 1 };
|
|
15
|
+
const validate = ValidationUtils.ajv.compile(numberSchema);
|
|
16
|
+
const isValid = validate(value);
|
|
17
|
+
if (!isValid) {
|
|
18
|
+
throw new ArgumentError(`Value must be an integer, got: ${value}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Validates that a value is a non-empty string. Throws ArgumentError on failure.
|
|
24
|
+
*/
|
|
25
|
+
static validateString(value: string): void {
|
|
26
|
+
const non_empty_string_schema = {
|
|
27
|
+
type: 'string',
|
|
28
|
+
minLength: 1,
|
|
29
|
+
pattern: '\\S',
|
|
30
|
+
};
|
|
31
|
+
const validate = ValidationUtils.ajv.compile(non_empty_string_schema);
|
|
32
|
+
const isValid = validate(value);
|
|
33
|
+
if (!isValid) {
|
|
34
|
+
throw new ArgumentError(`Value must be a non-empty string, got: ${value}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validates that a value is an array of non-empty strings. Throws ArgumentError on failure.
|
|
40
|
+
* Allows empty arrays - caller should handle early return.
|
|
41
|
+
*/
|
|
42
|
+
static validateStringArray(value: string[]): void {
|
|
43
|
+
const string_array_schema = {
|
|
44
|
+
type: 'array',
|
|
45
|
+
items: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
minLength: 1,
|
|
48
|
+
pattern: '\\S',
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
const validateStringArray = ValidationUtils.ajv.compile(string_array_schema);
|
|
52
|
+
const isValid = validateStringArray(value);
|
|
53
|
+
if (!isValid) {
|
|
54
|
+
throw new ArgumentError(`Value must be an array of non-empty strings, got: ${value}`);
|
|
55
|
+
}
|
|
56
|
+
// Allow empty arrays to pass validation - caller should handle early return
|
|
57
|
+
// Non-empty arrays will be validated for string content above
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Validates an attribute assignment object using a single AJV schema.
|
|
62
|
+
* Preserves legacy error messages for each field.
|
|
63
|
+
*/
|
|
64
|
+
static validateAssignment<TType extends string>(
|
|
65
|
+
data: { id: any; type: string; attributeKey: any; attributeValue: any },
|
|
66
|
+
validTypes: readonly TType[],
|
|
67
|
+
fieldNames: { id: string; type: string }
|
|
68
|
+
): { id: number; type: TType; attributeKey: string; attributeValue: string } {
|
|
69
|
+
const schema = {
|
|
70
|
+
type: 'object',
|
|
71
|
+
properties: {
|
|
72
|
+
id: { type: 'number', multipleOf: 1 },
|
|
73
|
+
type: { type: 'string', enum: validTypes as TType[] },
|
|
74
|
+
attributeKey: { type: 'string', minLength: 1 },
|
|
75
|
+
attributeValue: { type: 'string', minLength: 1 },
|
|
76
|
+
},
|
|
77
|
+
required: ['id', 'type', 'attributeKey', 'attributeValue'],
|
|
78
|
+
additionalProperties: false,
|
|
79
|
+
} as const;
|
|
80
|
+
|
|
81
|
+
const validate = this.ajv.compile(schema);
|
|
82
|
+
const valid = validate(data);
|
|
83
|
+
if (!valid) {
|
|
84
|
+
// Map to legacy error messages deterministically
|
|
85
|
+
const { id, type, attributeKey, attributeValue } = data;
|
|
86
|
+
|
|
87
|
+
// id must be integer
|
|
88
|
+
const isInteger = typeof id === 'number' && Number.isFinite(id) && Math.floor(id) === id;
|
|
89
|
+
if (!isInteger) {
|
|
90
|
+
throw new ArgumentError(`${fieldNames.id} must be an integer, got: ${id}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// type must be within enum
|
|
94
|
+
if (typeof type !== 'string' || !validTypes.includes(type as TType)) {
|
|
95
|
+
throw new ArgumentError(`${fieldNames.type} must be one of [${validTypes.join(', ')}], got: ${type}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// attributeKey
|
|
99
|
+
if (typeof attributeKey !== 'string') {
|
|
100
|
+
throw new ArgumentError(`attributeKey must be a string, got: ${typeof attributeKey}`);
|
|
101
|
+
}
|
|
102
|
+
if (!attributeKey) {
|
|
103
|
+
throw new ArgumentError('attributeKey must be a non-empty string');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// attributeValue
|
|
107
|
+
if (typeof attributeValue !== 'string') {
|
|
108
|
+
throw new ArgumentError(`attributeValue must be a string, got: ${typeof attributeValue}`);
|
|
109
|
+
}
|
|
110
|
+
if (!attributeValue) {
|
|
111
|
+
throw new ArgumentError('attributeValue must be a non-empty string');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Fallback
|
|
115
|
+
throw new ArgumentError('Invalid attribute assignment');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
id: data.id as number,
|
|
120
|
+
type: data.type as TType,
|
|
121
|
+
attributeKey: data.attributeKey as string,
|
|
122
|
+
attributeValue: data.attributeValue as string,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Validates a Resource-like object shape: { id: number; type: string }.
|
|
128
|
+
* Throws ArgumentError with legacy-compatible messages.
|
|
129
|
+
*/
|
|
130
|
+
static validateResource(resource: Resource): void {
|
|
131
|
+
if (!resource || typeof resource !== 'object') {
|
|
132
|
+
throw new ArgumentError('resource must be an object');
|
|
133
|
+
}
|
|
134
|
+
const schema = {
|
|
135
|
+
type: 'object',
|
|
136
|
+
properties: {
|
|
137
|
+
id: { type: 'number', multipleOf: 1 },
|
|
138
|
+
type: { type: 'string', minLength: 1 },
|
|
139
|
+
wrapperData: { type: 'string' },
|
|
140
|
+
},
|
|
141
|
+
required: ['type', 'id'],
|
|
142
|
+
additionalProperties: false,
|
|
143
|
+
} as const;
|
|
144
|
+
const validate = this.ajv.compile(schema);
|
|
145
|
+
const isValid = validate(resource);
|
|
146
|
+
if (!isValid) {
|
|
147
|
+
throw new ArgumentError('Invalid resource');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
static validateArrayTypeOf<T>(attributesMessages: T[], messageClass: abstract new (...args: any[]) => T): void {
|
|
152
|
+
const arraySchema = {
|
|
153
|
+
type: 'array',
|
|
154
|
+
};
|
|
155
|
+
const validateArray = ValidationUtils.ajv.compile(arraySchema);
|
|
156
|
+
const isArrayValid = validateArray(attributesMessages);
|
|
157
|
+
if (!isArrayValid) {
|
|
158
|
+
throw new ArgumentError(`attributesMessages must be an array`);
|
|
159
|
+
}
|
|
160
|
+
for (let i = 0; i < attributesMessages.length; i++) {
|
|
161
|
+
const item = attributesMessages[i];
|
|
162
|
+
if (!(item instanceof messageClass)) {
|
|
163
|
+
throw new ArgumentError(
|
|
164
|
+
`attributesMessages[${i}] must be an instance of ${messageClass.name}, got: ${
|
|
165
|
+
item?.constructor?.name || typeof item
|
|
166
|
+
}`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|