Skip to content

Commit

Permalink
feat: allow authorization token for JsonApiCondition to support end…
Browse files Browse the repository at this point in the history
…points requiring OAuth, JWT authorization (#599)
  • Loading branch information
cygnusv authored Oct 30, 2024
2 parents 3b67142 + 0e25a11 commit 8a7e72d
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 19 deletions.
6 changes: 5 additions & 1 deletion packages/shared/src/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ const BlockNumber = z.number().int().nonnegative();
const BlockHash = z.string().regex(BLOCK_HASH_REGEXP, 'Invalid block hash');
const BlockTag = z.enum(['earliest', 'finalized', 'safe', 'latest', 'pending']);

export const BlockIdentifierSchema = z.union([BlockNumber, BlockHash, BlockTag]);
export const BlockIdentifierSchema = z.union([
BlockNumber,
BlockHash,
BlockTag,
]);
5 changes: 3 additions & 2 deletions packages/shared/test/schemas.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';

import { EthAddressSchema, BlockIdentifierSchema } from '../src';
import { BlockIdentifierSchema, EthAddressSchema } from '../src';

describe('ethereum address schema', () => {
it('should accept valid ethereum address', () => {
Expand Down Expand Up @@ -46,7 +46,8 @@ describe('block identifier address schema', () => {
});

it('should accept valid block hashes', () => {
const validBlockHash = '0x1234567890123456789012345678901234567890123456789012345678901234';
const validBlockHash =
'0x1234567890123456789012345678901234567890123456789012345678901234';
BlockIdentifierSchema.parse(validBlockHash);
});

Expand Down
9 changes: 8 additions & 1 deletion packages/taco/src/conditions/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ import {
} from '@nucypher/taco-auth';

// Only allow alphanumeric characters and underscores
export const CONTEXT_PARAM_REGEXP = new RegExp('^:[a-zA-Z_][a-zA-Z0-9_]*$');
const contextParamRegexString = ':[a-zA-Z_][a-zA-Z0-9_]*';

export const CONTEXT_PARAM_REGEXP = new RegExp(contextParamRegexString);

// Entire string is context param
export const CONTEXT_PARAM_FULL_MATCH_REGEXP = new RegExp(
`^${contextParamRegexString}$`,
);

export const CONTEXT_PARAM_PREFIX = ':';

Expand Down
4 changes: 2 additions & 2 deletions packages/taco/src/conditions/context/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import { CompoundConditionType } from '../compound-condition';
import { Condition, ConditionProps } from '../condition';
import { ConditionExpression } from '../condition-expr';
import {
CONTEXT_PARAM_FULL_MATCH_REGEXP,
CONTEXT_PARAM_PREFIX,
CONTEXT_PARAM_REGEXP,
USER_ADDRESS_PARAMS,
} from '../const';

Expand Down Expand Up @@ -138,7 +138,7 @@ export class ConditionContext {
}

private static isContextParameter(param: unknown): boolean {
return !!String(param).match(CONTEXT_PARAM_REGEXP);
return !!String(param).match(CONTEXT_PARAM_FULL_MATCH_REGEXP);
}

private static findContextParameters(condition: ConditionProps) {
Expand Down
6 changes: 4 additions & 2 deletions packages/taco/src/conditions/schemas/context.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { z } from 'zod';

import { CONTEXT_PARAM_REGEXP } from '../const';
import { CONTEXT_PARAM_FULL_MATCH_REGEXP } from '../const';

import { plainStringSchema } from './common';

export const contextParamSchema = z.string().regex(CONTEXT_PARAM_REGEXP);
export const contextParamSchema = z
.string()
.regex(CONTEXT_PARAM_FULL_MATCH_REGEXP);

const paramSchema = z.union([plainStringSchema, z.boolean(), z.number()]);

Expand Down
10 changes: 10 additions & 0 deletions packages/taco/src/conditions/schemas/json-api.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import { JSONPath } from '@astronautlabs/jsonpath';
import { z } from 'zod';

import { CONTEXT_PARAM_REGEXP } from '../const';

import { contextParamSchema } from './context';
import { returnValueTestSchema } from './return-value-test';

export const JsonApiConditionType = 'json-api';

const validateJSONPath = (jsonPath: string): boolean => {
// account for embedded context variables
if (CONTEXT_PARAM_REGEXP.test(jsonPath)) {
// skip validation
return true;
}

try {
JSONPath.parse(jsonPath);
return true;
Expand All @@ -25,6 +34,7 @@ export const jsonApiConditionSchema = z.object({
endpoint: z.string().url(),
parameters: z.record(z.string(), z.unknown()).optional(),
query: jsonPathSchema.optional(),
authorizationToken: contextParamSchema.optional(),
returnValueTest: returnValueTestSchema, // Update to allow multiple return values after expanding supported methods
});

Expand Down
11 changes: 7 additions & 4 deletions packages/taco/src/conditions/schemas/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ export const RpcConditionType = 'rpc';
const EthAddressOrContextVariableSchema = z.union([
EthAddressSchema,
UserAddressSchema,
contextParamSchema
contextParamSchema,
]);
const BlockOrContextParamSchema = z.union([
BlockIdentifierSchema,
contextParamSchema,
]);
const BlockOrContextParamSchema = z.union([BlockIdentifierSchema, contextParamSchema])

// eth_getBalance schema specification
// - Ethereum spec: https://ethereum.github.io/execution-apis/api-documentation/
Expand All @@ -29,8 +32,8 @@ export const rpcConditionSchema = baseConditionSchema.extend({
parameters: z.union([
// Spec requires 2 parameters: an address and a block identifier
z.tuple([EthAddressOrContextVariableSchema, BlockOrContextParamSchema]),
// Block identifier can be omitted, since web3py (which runs on TACo exec layer) defaults to 'latest',
z.tuple([EthAddressOrContextVariableSchema]),
// Block identifier can be omitted, since web3py (which runs on TACo exec layer) defaults to 'latest',
z.tuple([EthAddressOrContextVariableSchema]),
]),
returnValueTest: returnValueTestSchema, // Update to allow multiple return values after expanding supported methods
});
Expand Down
60 changes: 60 additions & 0 deletions packages/taco/test/conditions/base/json.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { describe, expect, it } from 'vitest';
import {
JsonApiCondition,
jsonApiConditionSchema,
JsonApiConditionType,
} from '../../../src/conditions/base/json-api';
import { testJsonApiConditionObj } from '../../test-utils';

Expand Down Expand Up @@ -39,6 +40,39 @@ describe('JsonApiCondition', () => {
});
});

describe('authorizationToken', () => {
it('accepts context variable', () => {
const result = JsonApiCondition.validate(jsonApiConditionSchema, {
...testJsonApiConditionObj,
authorizationToken: ':authToken',
});
expect(result.error).toBeUndefined();
expect(result.data).toEqual({
...testJsonApiConditionObj,
authorizationToken: ':authToken',
});
});
it.each([
'authToken',
'ABCDEF1234567890',
':authToken?',
'$:authToken',
':auth-Token',
])('rejects invalid context variable', (contextVar) => {
const result = JsonApiCondition.validate(jsonApiConditionSchema, {
...testJsonApiConditionObj,
authorizationToken: `${contextVar}`,
});
expect(result.error).toBeDefined();
expect(result.data).toBeUndefined();
expect(result.error?.format()).toMatchObject({
authorizationToken: {
_errors: ['Invalid'],
},
});
});
});

describe('parameters', () => {
it('accepts conditions without query path', () => {
const { query, ...noQueryObj } = testJsonApiConditionObj;
Expand All @@ -62,5 +96,31 @@ describe('JsonApiCondition', () => {
expect(result.data).toEqual(noParamsObj);
});
});

describe('context variables', () => {
it('allow context variables for various values including as substring', () => {
const jsonApiConditionObj = {
conditionType: JsonApiConditionType,
endpoint:
'https://api.coingecko.com/api/:version/simple/:endpointPath',
parameters: {
ids: 'ethereum',
vs_currencies: ':vsCurrency',
},
query: 'ethereum.:vsCurrency',
authorizationToken: ':authToken',
returnValueTest: {
comparator: '==',
value: ':expectedPrice',
},
};
const result = JsonApiCondition.validate(
jsonApiConditionSchema,
jsonApiConditionObj,
);
expect(result.error).toBeUndefined();
expect(result.data).toEqual(jsonApiConditionObj);
});
});
});
});
17 changes: 10 additions & 7 deletions packages/taco/test/conditions/base/rpc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,26 +63,26 @@ describe('validation', () => {
it('accepts a single UserAddress as address', () => {
const result = RpcCondition.validate(rpcConditionSchema, {
...testRpcConditionObj,
parameters: [":userAddress"],
parameters: [':userAddress'],
});

expect(result.error).toBeUndefined();
expect(result.data).toEqual({
...testRpcConditionObj,
parameters: [":userAddress"],
parameters: [':userAddress'],
});
});

it('accepts a single context variable as address', () => {
const result = RpcCondition.validate(rpcConditionSchema, {
...testRpcConditionObj,
parameters: [":testContextVar"],
parameters: [':testContextVar'],
});

expect(result.error).toBeUndefined();
expect(result.data).toEqual({
...testRpcConditionObj,
parameters: [":testContextVar"],
parameters: [':testContextVar'],
});
});

Expand All @@ -102,13 +102,13 @@ describe('validation', () => {
it('accepts context params for address and block number', () => {
const result = RpcCondition.validate(rpcConditionSchema, {
...testRpcConditionObj,
parameters: [":testAddress", ":testBlockNumber"],
parameters: [':testAddress', ':testBlockNumber'],
});

expect(result.error).toBeUndefined();
expect(result.data).toEqual({
...testRpcConditionObj,
parameters: [":testAddress", ":testBlockNumber"],
parameters: [':testAddress', ':testBlockNumber'],
});
});

Expand Down Expand Up @@ -143,7 +143,10 @@ describe('validation', () => {
expect(result.data).toBeUndefined();
expect(result.error?.format()).toMatchObject({
parameters: {
_errors: ['Array must contain at least 2 element(s)', 'Array must contain at least 1 element(s)'],
_errors: [
'Array must contain at least 2 element(s)',
'Array must contain at least 1 element(s)',
],
},
});
});
Expand Down

0 comments on commit 8a7e72d

Please sign in to comment.