Skip to content

Commit

Permalink
improve caip25permission caveat type validation
Browse files Browse the repository at this point in the history
  • Loading branch information
adonesky1 committed Nov 12, 2024
1 parent 76a0afd commit d10f7d0
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 9 deletions.
21 changes: 14 additions & 7 deletions packages/multichain/src/caip25Permission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@ import {
} from '@metamask/permission-controller';
import type { CaipAccountId, Json } from '@metamask/utils';
import {
hasProperty,
parseCaipAccountId,
type Hex,
type NonEmptyArray,
} from '@metamask/utils';
import { cloneDeep, isEqual } from 'lodash';

import { getEthAccounts } from './adapters/caip-permission-adapter-eth-accounts';
import { assertScopesSupported } from './scope/assert';
import {
assertScopesSupported,
assertIsExternalScopesObject,
} from './scope/assert';
import { validateAndNormalizeScopes } from './scope/authorization';
import type {
ExternalScopeString,
Expand Down Expand Up @@ -89,18 +93,21 @@ const specificationBuilder: PermissionSpecificationBuilder<
);
}

const { requiredScopes, optionalScopes, isMultichainOrigin } =
caip25Caveat.value as Caip25CaveatValue;

if (
!requiredScopes ||
!optionalScopes ||
typeof isMultichainOrigin !== 'boolean'
!caip25Caveat.value ||
!hasProperty(caip25Caveat.value, 'requiredScopes') ||
!hasProperty(caip25Caveat.value, 'optionalScopes') ||
!hasProperty(caip25Caveat.value, 'isMultichainOrigin') ||
typeof caip25Caveat.value.isMultichainOrigin !== 'boolean'
) {
throw new Error(
`${Caip25EndowmentPermissionName} error: Received invalid value for caveat of type "${Caip25CaveatType}".`,
);
}
const { requiredScopes, optionalScopes } = caip25Caveat.value;

assertIsExternalScopesObject(requiredScopes);
assertIsExternalScopesObject(optionalScopes);

const { normalizedRequiredScopes, normalizedOptionalScopes } =
validateAndNormalizeScopes(requiredScopes, optionalScopes);
Expand Down
125 changes: 123 additions & 2 deletions packages/multichain/src/scope/assert.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
import type { Hex } from '@metamask/utils';
import {
hasProperty,
isCaipAccountId,
isCaipChainId,
isCaipNamespace,
isCaipReference,
type Hex,
} from '@metamask/utils';

import { Caip25Errors } from './errors';
import {
isSupportedMethod,
isSupportedNotification,
isSupportedScopeString,
} from './supported';
import type { InternalScopeObject, InternalScopesObject } from './types';
import type {
ExternalScopeObject,
ExternalScopesObject,
ExternalScopeString,
InternalScopeObject,
InternalScopesObject,
} from './types';

/**
* Asserts that a scope string and its associated scope object are supported.
Expand Down Expand Up @@ -67,3 +80,111 @@ export const assertScopesSupported = (
});
}
};
/**
* Asserts that an object is a valid ExternalScopeObject.
* @param obj - The object to assert.
*/
function assertIsExternalScopeObject(
obj: unknown,
): asserts obj is ExternalScopeObject {
if (typeof obj !== 'object' || obj === null) {
throw new Error('ExternalScopeObject must be an object');
}

if (hasProperty(obj, 'references')) {
if (
!Array.isArray(obj.references) ||
!obj.references.every(isCaipReference)
) {
throw new Error(
'ExternalScopeObject.references must be an array of CaipReference',
);
}
}

if (hasProperty(obj, 'accounts')) {
if (!Array.isArray(obj.accounts) || !obj.accounts.every(isCaipAccountId)) {
throw new Error(
'ExternalScopeObject.accounts must be an array of CaipAccountId',
);
}
}

if (hasProperty(obj, 'methods')) {
if (
!Array.isArray(obj.methods) ||
!obj.methods.every((method) => typeof method === 'string')
) {
throw new Error(
'ExternalScopeObject.methods must be an array of strings',
);
}
}

if (hasProperty(obj, 'notifications')) {
if (
!Array.isArray(obj.notifications) ||
!obj.notifications.every(
(notification) => typeof notification === 'string',
)
) {
throw new Error(
'ExternalScopeObject.notifications must be an array of strings',
);
}
}

if (hasProperty(obj, 'rpcDocuments')) {
if (
!Array.isArray(obj.rpcDocuments) ||
!obj.rpcDocuments.every((doc) => typeof doc === 'string')
) {
throw new Error(
'ExternalScopeObject.rpcDocuments must be an array of strings',
);
}
}

if (hasProperty(obj, 'rpcEndpoints')) {
if (
!Array.isArray(obj.rpcEndpoints) ||
!obj.rpcEndpoints.every((endpoint) => typeof endpoint === 'string')
) {
throw new Error(
'ExternalScopeObject.rpcEndpoints must be an array of strings',
);
}
}
}

/**
* Asserts that a scope string is a valid ExternalScopeString.
* @param scopeString - The scope string to assert.
*/
function assertIsExternalScopeString(
scopeString: unknown,
): asserts scopeString is ExternalScopeString {
if (
typeof scopeString !== 'string' ||
(!isCaipNamespace(scopeString) && !isCaipChainId(scopeString))
) {
throw new Error('scopeString is not a valid ExternalScopeString');
}
}

/**
* Asserts that an object is a valid ExternalScopesObject.
* @param obj - The object to assert.
*/
export function assertIsExternalScopesObject(
obj: unknown,
): asserts obj is ExternalScopesObject {
if (typeof obj !== 'object' || obj === null) {
throw new Error('Object is not an ExternalScopesObject');
}

for (const [scopeString, scopeObject] of Object.entries(obj)) {
assertIsExternalScopeString(scopeString);
assertIsExternalScopeObject(scopeObject);
}
}

0 comments on commit d10f7d0

Please sign in to comment.