Skip to content

Commit ab90ef7

Browse files
authored
Improve request validation (#187)
* Improve request validation - add validation for rules - deduplicate zod definitions shared between permission types * Add support for rule type descriptors as object * Ensure all instances of rule.type are wrapped in extractDescriptorName to support complex descriptors. Remove circular dependency of zod util schemas.
1 parent 9ee2a2a commit ab90ef7

File tree

31 files changed

+737
-239
lines changed

31 files changed

+737
-239
lines changed

packages/gator-permissions-snap/src/core/permissionHandlerFactory.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { PermissionRequest } from '@metamask/7715-permissions-shared/types';
2-
import { extractPermissionName } from '@metamask/7715-permissions-shared/utils';
2+
import { extractDescriptorName } from '@metamask/7715-permissions-shared/utils';
33
import { InvalidInputError } from '@metamask/snaps-sdk';
44

55
import type { AccountController } from './accountController';
@@ -63,7 +63,7 @@ export class PermissionHandlerFactory {
6363
createPermissionHandler(
6464
permissionRequest: PermissionRequest,
6565
): PermissionHandlerType {
66-
const type = extractPermissionName(permissionRequest.permission.type);
66+
const type = extractDescriptorName(permissionRequest.permission.type);
6767

6868
const createPermissionHandler = <
6969
TRequest extends PermissionRequest,

packages/gator-permissions-snap/src/core/permissionRequestLifecycleOrchestrator.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import type {
33
PermissionRequest,
44
PermissionResponse,
55
} from '@metamask/7715-permissions-shared/types';
6-
import { logger } from '@metamask/7715-permissions-shared/utils';
6+
import {
7+
extractDescriptorName,
8+
logger,
9+
} from '@metamask/7715-permissions-shared/utils';
710
import type { Delegation } from '@metamask/delegation-core';
811
import {
912
createNonceTerms,
@@ -355,7 +358,7 @@ export class PermissionRequestLifecycleOrchestrator {
355358
});
356359

357360
const expiryRule = resolvedRequest.rules?.find(
358-
(rule) => rule.type === 'expiry',
361+
(rule) => extractDescriptorName(rule.type) === 'expiry',
359362
);
360363

361364
if (expiryRule) {

packages/gator-permissions-snap/src/permissions/erc20TokenPeriodic/context.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { extractDescriptorName } from '@metamask/7715-permissions-shared/utils';
12
import { InvalidInputError } from '@metamask/snaps-sdk';
23
import {
34
bigIntToHex,
@@ -55,7 +56,7 @@ export async function applyContext({
5556

5657
const rules: Erc20TokenPeriodicPermissionRequest['rules'] =
5758
originalRequest.rules?.map((rule) => {
58-
if (rule.type === 'expiry') {
59+
if (extractDescriptorName(rule.type) === 'expiry') {
5960
isExpiryRuleFound = true;
6061
return {
6162
...rule,
@@ -157,7 +158,7 @@ export async function buildContext({
157158
: null;
158159

159160
const expiryRule = permissionRequest.rules?.find(
160-
(rule) => rule.type === 'expiry',
161+
(rule) => extractDescriptorName(rule.type) === 'expiry',
161162
);
162163

163164
if (!expiryRule) {

packages/gator-permissions-snap/src/permissions/erc20TokenPeriodic/types.ts

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import {
33
zPermission,
44
zMetaMaskPermissionData,
55
zAddress,
6+
zTimestamp,
7+
zStartTime,
68
} from '@metamask/7715-permissions-shared/types';
79
import { z } from 'zod';
810

@@ -13,7 +15,6 @@ import type {
1315
TimePeriod,
1416
BaseMetadata,
1517
} from '../../core/types';
16-
import { validateStartTimeZod } from '../../utils/validate';
1718

1819
export type Erc20TokenPeriodicMetadata = BaseMetadata & {
1920
validationErrors: {
@@ -40,24 +41,8 @@ export const zErc20TokenPeriodicPermission = zPermission.extend({
4041
zMetaMaskPermissionData,
4142
z.object({
4243
periodAmount: zHexStr,
43-
periodDuration: z.number().int().positive(),
44-
startTime: z
45-
.number()
46-
.int()
47-
.positive()
48-
.nullable()
49-
.optional()
50-
.refine(
51-
(value) => {
52-
if (value === undefined || value === null) {
53-
return true;
54-
}
55-
return validateStartTimeZod(value);
56-
},
57-
{
58-
message: 'Start time must be today or later',
59-
},
60-
),
44+
periodDuration: zTimestamp,
45+
startTime: zStartTime,
6146
tokenAddress: zAddress,
6247
}),
6348
),

packages/gator-permissions-snap/src/permissions/erc20TokenPeriodic/validation.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { PermissionRequest } from '@metamask/7715-permissions-shared/types'
22
import { extractZodError } from '@metamask/7715-permissions-shared/utils';
33
import { InvalidInputError } from '@metamask/snaps-sdk';
44

5-
import { validateHexInteger } from '../validation';
5+
import { validateHexInteger, validateStartTime } from '../validation';
66
import type {
77
Erc20TokenPeriodicPermission,
88
Erc20TokenPeriodicPermissionRequest,
@@ -29,18 +29,7 @@ function validatePermissionData(
2929
allowZero: false,
3030
});
3131

32-
const expiryRule = rules?.find((rule) => rule.type === 'expiry');
33-
34-
if (!expiryRule) {
35-
throw new InvalidInputError('Expiry rule is required');
36-
}
37-
38-
const expiry = Number(expiryRule.data.timestamp);
39-
40-
// If startTime is not provided it default to Date.now(), expiry is always in the future so no need to check.
41-
if (startTime && startTime >= expiry) {
42-
throw new InvalidInputError('Invalid startTime: must be before expiry');
43-
}
32+
validateStartTime(startTime, rules);
4433

4534
return true;
4635
}

packages/gator-permissions-snap/src/permissions/erc20TokenStream/context.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { extractDescriptorName } from '@metamask/7715-permissions-shared/utils';
12
import { InvalidInputError } from '@metamask/snaps-sdk';
23
import {
34
bigIntToHex,
@@ -58,7 +59,7 @@ export async function applyContext({
5859

5960
const rules: Erc20TokenStreamPermissionRequest['rules'] =
6061
originalRequest.rules?.map((rule) => {
61-
if (rule.type === 'expiry') {
62+
if (extractDescriptorName(rule.type) === 'expiry') {
6263
isExpiryRuleFound = true;
6364
return {
6465
...rule,
@@ -175,7 +176,7 @@ export async function buildContext({
175176
: null;
176177

177178
const expiryRule = permissionRequest.rules?.find(
178-
(rule) => rule.type === 'expiry',
179+
(rule) => extractDescriptorName(rule.type) === 'expiry',
179180
);
180181

181182
if (!expiryRule) {

packages/gator-permissions-snap/src/permissions/erc20TokenStream/types.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import {
33
zPermission,
44
zMetaMaskPermissionData,
55
zAddress,
6+
zStartTime,
7+
zHexStrNullableOptional,
68
} from '@metamask/7715-permissions-shared/types';
79
import { z } from 'zod';
810

@@ -13,7 +15,6 @@ import type {
1315
BaseContext,
1416
BaseMetadata,
1517
} from '../../core/types';
16-
import { validateStartTimeZod } from '../../utils/validate';
1718

1819
export type Erc20TokenStreamMetadata = BaseMetadata & {
1920
amountPerSecond: string;
@@ -41,26 +42,10 @@ export const zErc20TokenStreamPermission = zPermission.extend({
4142
data: z.intersection(
4243
zMetaMaskPermissionData,
4344
z.object({
44-
initialAmount: zHexStr.optional().nullable(),
45-
maxAmount: zHexStr.optional().nullable(),
45+
initialAmount: zHexStrNullableOptional,
46+
maxAmount: zHexStrNullableOptional,
4647
amountPerSecond: zHexStr,
47-
startTime: z
48-
.number()
49-
.int()
50-
.positive()
51-
.nullable()
52-
.optional()
53-
.refine(
54-
(value) => {
55-
if (value === undefined || value === null) {
56-
return true;
57-
}
58-
return validateStartTimeZod(value);
59-
},
60-
{
61-
message: 'Start time must be today or later',
62-
},
63-
),
48+
startTime: zStartTime,
6449
tokenAddress: zAddress,
6550
}),
6651
),

packages/gator-permissions-snap/src/permissions/erc20TokenStream/validation.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { PermissionRequest } from '@metamask/7715-permissions-shared/types'
22
import { extractZodError } from '@metamask/7715-permissions-shared/utils';
33
import { InvalidInputError } from '@metamask/snaps-sdk';
44

5-
import { validateHexInteger } from '../validation';
5+
import { validateHexInteger, validateStartTime } from '../validation';
66
import type {
77
Erc20TokenStreamPermission,
88
Erc20TokenStreamPermissionRequest,
@@ -50,18 +50,7 @@ function validatePermissionData(
5050
);
5151
}
5252

53-
const expiryRule = rules?.find((rule) => rule.type === 'expiry');
54-
55-
if (!expiryRule) {
56-
throw new InvalidInputError('Expiry rule is required');
57-
}
58-
59-
const expiry = Number(expiryRule.data.timestamp);
60-
61-
// If startTime is not provided it default to Date.now(), expiry is always in the future so no need to check.
62-
if (startTime && startTime >= expiry) {
63-
throw new InvalidInputError('Invalid startTime: must be before expiry');
64-
}
53+
validateStartTime(startTime, rules);
6554

6655
return true;
6756
}

packages/gator-permissions-snap/src/permissions/nativeTokenPeriodic/context.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { extractDescriptorName } from '@metamask/7715-permissions-shared/utils';
12
import { InvalidInputError } from '@metamask/snaps-sdk';
23
import {
34
bigIntToHex,
@@ -56,7 +57,7 @@ export async function applyContext({
5657

5758
const rules: NativeTokenPeriodicPermissionRequest['rules'] =
5859
originalRequest.rules?.map((rule) => {
59-
if (rule.type === 'expiry') {
60+
if (extractDescriptorName(rule.type) === 'expiry') {
6061
isExpiryRuleFound = true;
6162
return {
6263
...rule,
@@ -157,7 +158,7 @@ export async function buildContext({
157158
: null;
158159

159160
const expiryRule = permissionRequest.rules?.find(
160-
(rule) => rule.type === 'expiry',
161+
(rule) => extractDescriptorName(rule.type) === 'expiry',
161162
);
162163

163164
if (!expiryRule) {

packages/gator-permissions-snap/src/permissions/nativeTokenPeriodic/types.ts

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import {
22
zHexStr,
33
zPermission,
44
zMetaMaskPermissionData,
5+
zTimestamp,
6+
zStartTime,
57
} from '@metamask/7715-permissions-shared/types';
68
import { z } from 'zod';
79

@@ -12,7 +14,6 @@ import type {
1214
TimePeriod,
1315
BaseMetadata,
1416
} from '../../core/types';
15-
import { validateStartTimeZod } from '../../utils/validate';
1617

1718
export type NativeTokenPeriodicMetadata = BaseMetadata & {
1819
validationErrors: {
@@ -39,24 +40,8 @@ export const zNativeTokenPeriodicPermission = zPermission.extend({
3940
zMetaMaskPermissionData,
4041
z.object({
4142
periodAmount: zHexStr,
42-
periodDuration: z.number().int().positive(),
43-
startTime: z
44-
.number()
45-
.int()
46-
.positive()
47-
.nullable()
48-
.optional()
49-
.refine(
50-
(value) => {
51-
if (value === undefined || value === null) {
52-
return true;
53-
}
54-
return validateStartTimeZod(value);
55-
},
56-
{
57-
message: 'Start time must be today or later',
58-
},
59-
),
43+
periodDuration: zTimestamp,
44+
startTime: zStartTime,
6045
}),
6146
),
6247
});

0 commit comments

Comments
 (0)