From da596085a62002f377b9a489dab41db022b67adc Mon Sep 17 00:00:00 2001 From: derekpierre Date: Tue, 28 Jan 2025 16:49:02 -0500 Subject: [PATCH 01/10] Implement EIP1271Provider and authentication signature. Expand existing AuthSignature type and schema to accomodate new type. Update LocalStorage to be type-specific via generics. --- packages/taco-auth/src/auth-sig.ts | 14 +++++------ .../taco-auth/src/providers/eip1271/auth.ts | 17 +++++++++++++ .../src/providers/eip1271/eip1271.ts | 24 +++++++++++++++++++ .../providers/eip4361/{common.ts => auth.ts} | 9 +++++++ .../src/providers/eip4361/eip4361.ts | 15 +++++++----- .../src/providers/eip4361/external-eip4361.ts | 6 ++--- packages/taco-auth/src/providers/index.ts | 1 + packages/taco-auth/src/storage.ts | 16 ++++++++----- 8 files changed, 78 insertions(+), 24 deletions(-) create mode 100644 packages/taco-auth/src/providers/eip1271/auth.ts create mode 100644 packages/taco-auth/src/providers/eip1271/eip1271.ts rename packages/taco-auth/src/providers/eip4361/{common.ts => auth.ts} (55%) diff --git a/packages/taco-auth/src/auth-sig.ts b/packages/taco-auth/src/auth-sig.ts index 03727842c..92720c750 100644 --- a/packages/taco-auth/src/auth-sig.ts +++ b/packages/taco-auth/src/auth-sig.ts @@ -1,16 +1,14 @@ import { EthAddressSchema } from '@nucypher/shared'; import { z } from 'zod'; -import { - EIP4361_AUTH_METHOD, - EIP4361TypedDataSchema, -} from './providers/eip4361/common'; +import { EIP1271AuthSignature } from './providers/eip1271/auth'; +import { EIP4361AuthSignature } from './providers/eip4361/auth'; -export const authSignatureSchema = z.object({ +export const baseAuthSignatureSchema = z.object({ signature: z.string(), address: EthAddressSchema, - scheme: z.enum([EIP4361_AUTH_METHOD]), - typedData: EIP4361TypedDataSchema, + scheme: z.string(), + typedData: z.unknown(), }); -export type AuthSignature = z.infer; +export type AuthSignature = EIP4361AuthSignature | EIP1271AuthSignature; diff --git a/packages/taco-auth/src/providers/eip1271/auth.ts b/packages/taco-auth/src/providers/eip1271/auth.ts new file mode 100644 index 000000000..4d8c7593c --- /dev/null +++ b/packages/taco-auth/src/providers/eip1271/auth.ts @@ -0,0 +1,17 @@ +import { z } from 'zod'; + +import { baseAuthSignatureSchema } from '../../auth-sig'; + +export const EIP1271_AUTH_METHOD = 'EIP1271'; + +export const EIP1271TypedDataSchema = z.object({ + chain: z.number().int().nonnegative(), + dataHash: z.string().startsWith('0x'), // hex string +}); + +export const eip1271AuthSignatureSchema = baseAuthSignatureSchema.extend({ + scheme: z.literal(EIP1271_AUTH_METHOD), + typedData: EIP1271TypedDataSchema, +}); + +export type EIP1271AuthSignature = z.infer; diff --git a/packages/taco-auth/src/providers/eip1271/eip1271.ts b/packages/taco-auth/src/providers/eip1271/eip1271.ts new file mode 100644 index 000000000..8c31d7512 --- /dev/null +++ b/packages/taco-auth/src/providers/eip1271/eip1271.ts @@ -0,0 +1,24 @@ +import { EIP1271_AUTH_METHOD, EIP1271AuthSignature } from './auth'; + +export const USER_ADDRESS_PARAM_EIP1271 = ':userAddressEIP1271'; + +export class EIP1271AuthProvider { + constructor( + public readonly contractAddress: string, + public readonly chain: number, + public readonly dataHash: string, + public readonly signature: string, + ) {} + + public async getOrCreateAuthSignature(): Promise { + return { + signature: this.signature, + address: this.contractAddress, + scheme: EIP1271_AUTH_METHOD, + typedData: { + chain: this.chain, + dataHash: this.dataHash, + }, + }; + } +} diff --git a/packages/taco-auth/src/providers/eip4361/common.ts b/packages/taco-auth/src/providers/eip4361/auth.ts similarity index 55% rename from packages/taco-auth/src/providers/eip4361/common.ts rename to packages/taco-auth/src/providers/eip4361/auth.ts index e6cff1600..eaae58ee7 100644 --- a/packages/taco-auth/src/providers/eip4361/common.ts +++ b/packages/taco-auth/src/providers/eip4361/auth.ts @@ -1,6 +1,8 @@ import { SiweMessage } from 'siwe'; import { z } from 'zod'; +import { baseAuthSignatureSchema } from '../../auth-sig'; + export const EIP4361_AUTH_METHOD = 'EIP4361'; const isSiweMessage = (message: string): boolean => { @@ -15,3 +17,10 @@ const isSiweMessage = (message: string): boolean => { export const EIP4361TypedDataSchema = z .string() .refine(isSiweMessage, { message: 'Invalid SIWE message' }); + +export const eip4361AuthSignatureSchema = baseAuthSignatureSchema.extend({ + scheme: z.literal(EIP4361_AUTH_METHOD), + typedData: EIP4361TypedDataSchema, +}); + +export type EIP4361AuthSignature = z.infer; diff --git a/packages/taco-auth/src/providers/eip4361/eip4361.ts b/packages/taco-auth/src/providers/eip4361/eip4361.ts index b42522931..777e7f624 100644 --- a/packages/taco-auth/src/providers/eip4361/eip4361.ts +++ b/packages/taco-auth/src/providers/eip4361/eip4361.ts @@ -1,10 +1,13 @@ import { ethers } from 'ethers'; import { generateNonce, SiweMessage } from 'siwe'; -import { AuthSignature } from '../../auth-sig'; import { LocalStorage } from '../../storage'; -import { EIP4361_AUTH_METHOD } from './common'; +import { + EIP4361_AUTH_METHOD, + EIP4361AuthSignature, + eip4361AuthSignatureSchema, +} from './auth'; export const USER_ADDRESS_PARAM_DEFAULT = ':userAddress'; @@ -17,7 +20,7 @@ const TACO_DEFAULT_DOMAIN = 'taco.build'; const TACO_DEFAULT_URI = 'https://taco.build'; export class EIP4361AuthProvider { - private readonly storage: LocalStorage; + private readonly storage: LocalStorage; private readonly providerParams: EIP4361AuthProviderParams; constructor( @@ -26,7 +29,7 @@ export class EIP4361AuthProvider { private readonly signer: ethers.Signer, providerParams?: EIP4361AuthProviderParams, ) { - this.storage = new LocalStorage(); + this.storage = new LocalStorage(eip4361AuthSignatureSchema); if (providerParams) { this.providerParams = providerParams; } else { @@ -50,7 +53,7 @@ export class EIP4361AuthProvider { }; } - public async getOrCreateAuthSignature(): Promise { + public async getOrCreateAuthSignature(): Promise { const address = await this.signer.getAddress(); const storageKey = `eth-${EIP4361_AUTH_METHOD}-message-${address}`; @@ -85,7 +88,7 @@ export class EIP4361AuthProvider { return twoHourWindow < now; } - private async createSIWEAuthMessage(): Promise { + private async createSIWEAuthMessage(): Promise { const address = await this.signer.getAddress(); const { domain, uri } = this.providerParams; const version = '1'; diff --git a/packages/taco-auth/src/providers/eip4361/external-eip4361.ts b/packages/taco-auth/src/providers/eip4361/external-eip4361.ts index 20885224a..bff4ea584 100644 --- a/packages/taco-auth/src/providers/eip4361/external-eip4361.ts +++ b/packages/taco-auth/src/providers/eip4361/external-eip4361.ts @@ -1,8 +1,6 @@ import { SiweMessage } from 'siwe'; -import { AuthSignature } from '../../auth-sig'; - -import { EIP4361_AUTH_METHOD } from './common'; +import { EIP4361_AUTH_METHOD, EIP4361AuthSignature } from './auth'; export const USER_ADDRESS_PARAM_EXTERNAL_EIP4361 = ':userAddressExternalEIP4361'; @@ -30,7 +28,7 @@ export class SingleSignOnEIP4361AuthProvider { private readonly signature: string, ) {} - public async getOrCreateAuthSignature(): Promise { + public async getOrCreateAuthSignature(): Promise { const scheme = EIP4361_AUTH_METHOD; return { signature: this.signature, diff --git a/packages/taco-auth/src/providers/index.ts b/packages/taco-auth/src/providers/index.ts index 82912650d..66605d162 100644 --- a/packages/taco-auth/src/providers/index.ts +++ b/packages/taco-auth/src/providers/index.ts @@ -1,2 +1,3 @@ +export * from './eip1271/eip1271'; export * from './eip4361/eip4361'; export * from './eip4361/external-eip4361'; diff --git a/packages/taco-auth/src/storage.ts b/packages/taco-auth/src/storage.ts index ef08e367d..fc3ef4165 100644 --- a/packages/taco-auth/src/storage.ts +++ b/packages/taco-auth/src/storage.ts @@ -1,4 +1,6 @@ -import { AuthSignature, authSignatureSchema } from './index'; +import { z } from 'zod'; + +import { AuthSignature } from './index'; interface IStorage { getItem(key: string): string | null; @@ -38,25 +40,27 @@ class NodeStorage implements IStorage { } } -export class LocalStorage { +export class LocalStorage { private storage: IStorage; + private signatureSchema: z.ZodSchema; - constructor() { + constructor(signatureSchema: z.ZodSchema) { this.storage = typeof localStorage === 'undefined' ? new NodeStorage() : new BrowserStorage(); + this.signatureSchema = signatureSchema; } - public getAuthSignature(key: string): AuthSignature | null { + public getAuthSignature(key: string): T | null { const asJson = this.storage.getItem(key); if (!asJson) { return null; } - return authSignatureSchema.parse(JSON.parse(asJson)); + return this.signatureSchema.parse(JSON.parse(asJson)); } - public setAuthSignature(key: string, authSignature: AuthSignature): void { + public setAuthSignature(key: string, authSignature: T): void { const asJson = JSON.stringify(authSignature); this.storage.setItem(key, asJson); } From 3520710a3e4de6c27fb18c1619b2a3a02508466d Mon Sep 17 00:00:00 2001 From: derekpierre Date: Tue, 28 Jan 2025 16:50:49 -0500 Subject: [PATCH 02/10] Add EIP1271AuthProvider functionality to the ConditionContext. --- packages/taco/src/conditions/const.ts | 2 ++ packages/taco/src/conditions/context/context.ts | 8 +++++++- packages/taco/src/conditions/schemas/common.ts | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/taco/src/conditions/const.ts b/packages/taco/src/conditions/const.ts index f74b4fbd9..ba75bc4b2 100644 --- a/packages/taco/src/conditions/const.ts +++ b/packages/taco/src/conditions/const.ts @@ -1,5 +1,6 @@ import { USER_ADDRESS_PARAM_DEFAULT, + USER_ADDRESS_PARAM_EIP1271, USER_ADDRESS_PARAM_EXTERNAL_EIP4361, } from '@nucypher/taco-auth'; @@ -17,6 +18,7 @@ export const CONTEXT_PARAM_PREFIX = ':'; export const USER_ADDRESS_PARAMS = [ USER_ADDRESS_PARAM_EXTERNAL_EIP4361, + USER_ADDRESS_PARAM_EIP1271, // Ordering matters, this should always be last USER_ADDRESS_PARAM_DEFAULT, ]; diff --git a/packages/taco/src/conditions/context/context.ts b/packages/taco/src/conditions/context/context.ts index 0dd302d02..8e594c6b5 100644 --- a/packages/taco/src/conditions/context/context.ts +++ b/packages/taco/src/conditions/context/context.ts @@ -3,9 +3,11 @@ import { toJSON } from '@nucypher/shared'; import { AuthProvider, AuthSignature, + EIP1271AuthProvider, EIP4361AuthProvider, SingleSignOnEIP4361AuthProvider, USER_ADDRESS_PARAM_DEFAULT, + USER_ADDRESS_PARAM_EIP1271, USER_ADDRESS_PARAM_EXTERNAL_EIP4361, } from '@nucypher/taco-auth'; @@ -39,14 +41,18 @@ const ERR_AUTH_PROVIDER_NOT_NEEDED_FOR_CONTEXT_PARAM = (param: string) => type AuthProviderType = | typeof EIP4361AuthProvider - | typeof SingleSignOnEIP4361AuthProvider; + | typeof SingleSignOnEIP4361AuthProvider + | typeof EIP1271AuthProvider; + const EXPECTED_AUTH_PROVIDER_TYPES: Record = { [USER_ADDRESS_PARAM_DEFAULT]: EIP4361AuthProvider, [USER_ADDRESS_PARAM_EXTERNAL_EIP4361]: SingleSignOnEIP4361AuthProvider, + [USER_ADDRESS_PARAM_EIP1271]: EIP1271AuthProvider, }; export const RESERVED_CONTEXT_PARAMS = [ USER_ADDRESS_PARAM_EXTERNAL_EIP4361, + USER_ADDRESS_PARAM_EIP1271, USER_ADDRESS_PARAM_DEFAULT, ]; diff --git a/packages/taco/src/conditions/schemas/common.ts b/packages/taco/src/conditions/schemas/common.ts index 182148a86..a92cb94c6 100644 --- a/packages/taco/src/conditions/schemas/common.ts +++ b/packages/taco/src/conditions/schemas/common.ts @@ -1,6 +1,7 @@ import { JSONPath } from '@astronautlabs/jsonpath'; import { USER_ADDRESS_PARAM_DEFAULT, + USER_ADDRESS_PARAM_EIP1271, USER_ADDRESS_PARAM_EXTERNAL_EIP4361, } from '@nucypher/taco-auth'; import { Primitive, z, ZodLiteral } from 'zod'; @@ -20,6 +21,7 @@ export const plainStringSchema = z.string().refine( export const UserAddressSchema = z.enum([ USER_ADDRESS_PARAM_DEFAULT, + USER_ADDRESS_PARAM_EIP1271, USER_ADDRESS_PARAM_EXTERNAL_EIP4361, ]); From b83ddfceb69a773d92c102938317b1ddad5df812 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Wed, 29 Jan 2025 09:02:03 -0500 Subject: [PATCH 03/10] Add tests for EIP1271AuthProvider and authentication signature generation. --- packages/taco-auth/test/auth-provider.test.ts | 34 +++++++++++-- packages/taco-auth/test/auth-sig.test.ts | 49 +++++++++++++++++-- packages/test-utils/src/utils.ts | 19 ++++++- 3 files changed, 93 insertions(+), 9 deletions(-) diff --git a/packages/taco-auth/test/auth-provider.test.ts b/packages/taco-auth/test/auth-provider.test.ts index dba8fa593..7777146f3 100644 --- a/packages/taco-auth/test/auth-provider.test.ts +++ b/packages/taco-auth/test/auth-provider.test.ts @@ -7,12 +7,13 @@ import { SiweMessage } from 'siwe'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { + EIP1271AuthProvider, EIP4361AuthProvider, SingleSignOnEIP4361AuthProvider, } from '../src/providers'; -import { EIP4361TypedDataSchema } from '../src/providers/eip4361/common'; +import { EIP4361TypedDataSchema } from '../src/providers/eip4361/auth'; -describe('auth provider', () => { +describe('eip4361 auth provider', () => { const provider = fakeProvider(bobSecretKeyBytes); const signer = provider.getSigner(); const eip4361Provider = new EIP4361AuthProvider( @@ -77,7 +78,7 @@ describe('auth provider', () => { }); }); -describe('auth provider caching', () => { +describe('eip4361 single sign-on auth provider', () => { beforeEach(() => { // tell vitest we use mocked time vi.useFakeTimers(); @@ -125,7 +126,7 @@ describe('auth provider caching', () => { }); }); -describe('single sign-on auth provider', async () => { +describe('eip4361 single sign-on auth provider', async () => { const provider = fakeProvider(bobSecretKeyBytes); const signer = provider.getSigner(); @@ -155,3 +156,28 @@ describe('single sign-on auth provider', async () => { expect(typedSignature.scheme).toEqual('EIP4361'); }); }); + +describe('eip1271 auth provider', () => { + const dataHash = '0xdeadbeef'; + const contractAddress = '0x100000000000000000000000000000000000000'; + const chainId = 1234; + const signature = '0xabc123'; + + const eip1271Provider = new EIP1271AuthProvider( + contractAddress, + chainId, + dataHash, + signature, + ); + + it('creates a new EIP1271 auth signature', async () => { + const typedSignature = await eip1271Provider.getOrCreateAuthSignature(); + expect(typedSignature.signature).toEqual(signature); + expect(typedSignature.address).toEqual(contractAddress); + expect(typedSignature.scheme).toEqual('EIP1271'); + expect(typedSignature.typedData).toEqual({ + chain: chainId, + dataHash: dataHash, + }); + }); +}); diff --git a/packages/taco-auth/test/auth-sig.test.ts b/packages/taco-auth/test/auth-sig.test.ts index c20e9c30f..8a324d8ea 100644 --- a/packages/taco-auth/test/auth-sig.test.ts +++ b/packages/taco-auth/test/auth-sig.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from 'vitest'; -import { authSignatureSchema } from '../src'; +import { eip1271AuthSignatureSchema } from '../src/providers/eip1271/auth'; +import { eip4361AuthSignatureSchema } from '../src/providers/eip4361/auth'; const eip4361AuthSignature = { signature: 'fake-signature', @@ -10,17 +11,57 @@ const eip4361AuthSignature = { 'localhost wants you to sign in with your Ethereum account:\n0x0000000000000000000000000000000000000000\n\nlocalhost wants you to sign in with your Ethereum account: 0x0000000000000000000000000000000000000000\n\nURI: http://localhost:3000\nVersion: 1\nChain ID: 1234\nNonce: 5ixAg1odyfDnrbfGa\nIssued At: 2024-07-01T10:32:39.631Z', }; +const eip1271AuthSignature = { + signature: '0xdeadbeef', + address: '0x0000000000000000000000000000000000000000', + scheme: 'EIP1271', + typedData: { + chain: 23, + dataHash: '0xdeadbeef', + }, +}; + describe('auth signature', () => { it('accepts a well-formed EIP4361 auth signature', async () => { - authSignatureSchema.parse(eip4361AuthSignature); + eip4361AuthSignatureSchema.parse(eip4361AuthSignature); }); - it('rejects an EIP4361 auth signature with missing fields', async () => { + it('rejects an EIP4361 auth signature with missing/incorrect fields', async () => { expect(() => - authSignatureSchema.parse({ + eip4361AuthSignatureSchema.parse({ ...eip4361AuthSignature, signature: undefined, }), ).toThrow(); + + expect(() => + eip4361AuthSignatureSchema.parse({ + ...eip4361AuthSignature, + scheme: 'EIP1271', + }), + ).toThrow(); + }); + + it('accepts a well-formed EIP1271 auth signature', async () => { + eip1271AuthSignatureSchema.parse(eip1271AuthSignature); + }); + + it('rejects an EIP1271 auth signature with missing/incorrect fields', async () => { + expect(() => + eip1271AuthSignatureSchema.parse({ + ...eip1271AuthSignature, + scheme: 'EIP4361', + }), + ).toThrow(); + + expect(() => + eip1271AuthSignatureSchema.parse({ + ...eip1271AuthSignature, + typedData: { + chain: 21, + dataHash: undefined, + }, + }), + ).toThrow(); }); }); diff --git a/packages/test-utils/src/utils.ts b/packages/test-utils/src/utils.ts index 147d289d4..0d1ce1f14 100644 --- a/packages/test-utils/src/utils.ts +++ b/packages/test-utils/src/utils.ts @@ -38,15 +38,17 @@ import { zip, } from '@nucypher/shared'; import { + EIP1271AuthProvider, EIP4361AuthProvider, SingleSignOnEIP4361AuthProvider, USER_ADDRESS_PARAM_DEFAULT, + USER_ADDRESS_PARAM_EIP1271, USER_ADDRESS_PARAM_EXTERNAL_EIP4361, } from '@nucypher/taco-auth'; import { ethers, providers, Wallet } from 'ethers'; import { expect, SpyInstance, vi } from 'vitest'; -import { TEST_SIWE_PARAMS } from './variables'; +import { TEST_CONTRACT_ADDR, TEST_SIWE_PARAMS } from './variables'; export const bytesEqual = (first: Uint8Array, second: Uint8Array): boolean => first.length === second.length && @@ -101,6 +103,7 @@ export const fakeAuthProviders = async ( [USER_ADDRESS_PARAM_DEFAULT]: fakeEIP4351AuthProvider(signerToUse), [USER_ADDRESS_PARAM_EXTERNAL_EIP4361]: await fakeSingleSignOnEIP4361AuthProvider(signerToUse), + [USER_ADDRESS_PARAM_EIP1271]: await fakeEIP1271AuthProvider(signerToUse), }; }; @@ -123,6 +126,20 @@ const fakeSingleSignOnEIP4361AuthProvider = async ( ); }; +const fakeEIP1271AuthProvider = async ( + signer: ethers.providers.JsonRpcSigner, +) => { + const message = `I'm the owner of the smart contract wallet at ${TEST_CONTRACT_ADDR}`; + const dataHash = ethers.utils.hashMessage(message); + const signature = await signer.signMessage(message); + return new EIP1271AuthProvider( + TEST_CONTRACT_ADDR, + (await signer.provider.getNetwork()).chainId, + dataHash, + signature, + ); +}; + const genChecksumAddress = (i: number): ChecksumAddress => `0x${'0'.repeat(40 - i.toString(16).length)}${i.toString( 16, From 21ca011524ce7877a7fae42132155158d2669f5d Mon Sep 17 00:00:00 2001 From: derekpierre Date: Wed, 29 Jan 2025 09:02:40 -0500 Subject: [PATCH 04/10] Move authentication provider tests around within context tests to be better organized. Add condition context tests for EIP1271 reserved context param. --- packages/taco/test/conditions/context.test.ts | 620 +++++++++--------- 1 file changed, 323 insertions(+), 297 deletions(-) diff --git a/packages/taco/test/conditions/context.test.ts b/packages/taco/test/conditions/context.test.ts index baa0f6bb8..977cd83c9 100644 --- a/packages/taco/test/conditions/context.test.ts +++ b/packages/taco/test/conditions/context.test.ts @@ -2,9 +2,12 @@ import { initialize } from '@nucypher/nucypher-core'; import { AuthProvider, AuthSignature, + EIP1271AuthProvider, + EIP1271AuthSignature, EIP4361AuthProvider, SingleSignOnEIP4361AuthProvider, USER_ADDRESS_PARAM_DEFAULT, + USER_ADDRESS_PARAM_EIP1271, USER_ADDRESS_PARAM_EXTERNAL_EIP4361, } from '@nucypher/taco-auth'; import { fakeAuthProviders, fakeProvider } from '@nucypher/test-utils'; @@ -48,29 +51,333 @@ describe('context', () => { authProviders = await fakeAuthProviders(); }); - describe('serialization', () => { - it('serializes to json', async () => { + describe('reserved context parameters', () => { + it.each([ + USER_ADDRESS_PARAM_DEFAULT, + USER_ADDRESS_PARAM_EXTERNAL_EIP4361, + USER_ADDRESS_PARAM_EIP1271, + ])('serializes to json', async (userAddressParam) => { const rpcCondition = new RpcCondition({ ...testRpcConditionObj, - parameters: [USER_ADDRESS_PARAM_DEFAULT], + parameters: [userAddressParam], returnValueTest: { comparator: '==', - value: USER_ADDRESS_PARAM_DEFAULT, + value: userAddressParam, }, }); const conditionContext = new ConditionContext(rpcCondition); conditionContext.addAuthProvider( - USER_ADDRESS_PARAM_DEFAULT, - authProviders[USER_ADDRESS_PARAM_DEFAULT], + userAddressParam, + authProviders[userAddressParam], ); const asJson = await conditionContext.toJson(); expect(asJson).toBeDefined(); - expect(asJson).toContain(USER_ADDRESS_PARAM_DEFAULT); + expect(asJson).toContain(userAddressParam); + }); + + it.each([ + USER_ADDRESS_PARAM_DEFAULT, + USER_ADDRESS_PARAM_EXTERNAL_EIP4361, + USER_ADDRESS_PARAM_EIP1271, + ])( + 'detects when auth provider is required by parameters', + async (userAddressParam) => { + const conditionObj = { + ...testContractConditionObj, + parameters: [userAddressParam], + returnValueTest: { + comparator: '==', + value: 100, + } as ReturnValueTestProps, + }; + const condition = new ContractCondition(conditionObj); + const conditionContext = new ConditionContext(condition); + await expect(conditionContext.toContextParameters()).rejects.toThrow( + `No matching authentication provider to satisfy ${userAddressParam} context variable in condition`, + ); + }, + ); + + it.each([ + USER_ADDRESS_PARAM_DEFAULT, + USER_ADDRESS_PARAM_EXTERNAL_EIP4361, + USER_ADDRESS_PARAM_EIP1271, + ])( + 'detects when signer is required by return value test', + async (userAddressParam) => { + const conditionObj = { + ...testContractConditionObj, + standardContractType: 'ERC721', + method: 'ownerOf', + parameters: [3591], + returnValueTest: { + comparator: '==', + value: userAddressParam, + }, + } as ContractConditionProps; + const condition = new ContractCondition(conditionObj); + const conditionContext = new ConditionContext(condition); + await expect(conditionContext.toContextParameters()).rejects.toThrow( + `No matching authentication provider to satisfy ${userAddressParam} context variable in condition`, + ); + }, + ); + + it('detects when signer is not required', async () => { + const condition = new RpcCondition(testRpcConditionObj); + const conditionContext = new ConditionContext(condition); + expect( + JSON.stringify(condition.toObj()).includes(USER_ADDRESS_PARAM_DEFAULT), + ).toBe(false); + expect( + JSON.stringify(condition.toObj()).includes( + USER_ADDRESS_PARAM_EXTERNAL_EIP4361, + ), + ).toBe(false); + expect( + JSON.stringify(condition.toObj()).includes(USER_ADDRESS_PARAM_EIP1271), + ).toBe(false); + await expect(conditionContext.toContextParameters()).toBeDefined(); + }); + + it.each([ + USER_ADDRESS_PARAM_DEFAULT, + USER_ADDRESS_PARAM_EXTERNAL_EIP4361, + USER_ADDRESS_PARAM_EIP1271, + ])( + 'return value test rejects on a missing signer', + async (userAddressParam) => { + const conditionObj = { + ...testContractConditionObj, + returnValueTest: { + ...testReturnValueTest, + value: userAddressParam, + }, + }; + const condition = new ContractCondition(conditionObj); + const conditionContext = new ConditionContext(condition); + await expect(conditionContext.toContextParameters()).rejects.toThrow( + `No matching authentication provider to satisfy ${userAddressParam} context variable in condition`, + ); + }, + ); + + it('rejects auth provider for not applicable context param', () => { + const conditionObj = { + ...testContractConditionObj, + returnValueTest: { + ...testReturnValueTest, + value: ':myParam', + }, + }; + const condition = new ContractCondition(conditionObj); + const conditionContext = new ConditionContext(condition); + expect(() => + conditionContext.addAuthProvider( + ':myParam', + authProviders[USER_ADDRESS_PARAM_DEFAULT], + ), + ).toThrow('AuthProvider not necessary for context parameter: :myParam'); + }); + + it('rejects invalid auth provider for :userAddress', () => { + const conditionObj = { + ...testContractConditionObj, + returnValueTest: { + ...testReturnValueTest, + value: USER_ADDRESS_PARAM_DEFAULT, + }, + }; + const condition = new ContractCondition(conditionObj); + const conditionContext = new ConditionContext(condition); + expect(() => + conditionContext.addAuthProvider( + USER_ADDRESS_PARAM_DEFAULT, + authProviders[USER_ADDRESS_PARAM_EXTERNAL_EIP4361], + ), + ).toThrow(`Invalid AuthProvider type for ${USER_ADDRESS_PARAM_DEFAULT}`); + }); + + it('rejects invalid auth provider for :userAddressExternalEIP4361', () => { + const conditionObj = { + ...testContractConditionObj, + returnValueTest: { + ...testReturnValueTest, + value: USER_ADDRESS_PARAM_EXTERNAL_EIP4361, + }, + }; + const condition = new ContractCondition(conditionObj); + const conditionContext = new ConditionContext(condition); + expect(() => + conditionContext.addAuthProvider( + USER_ADDRESS_PARAM_EXTERNAL_EIP4361, + authProviders[USER_ADDRESS_PARAM_DEFAULT], + ), + ).toThrow( + `Invalid AuthProvider type for ${USER_ADDRESS_PARAM_EXTERNAL_EIP4361}`, + ); + }); + + it('rejects invalid auth provider for :userAddressEIP1271', () => { + const conditionObj = { + ...testContractConditionObj, + returnValueTest: { + ...testReturnValueTest, + value: USER_ADDRESS_PARAM_EIP1271, + }, + }; + const condition = new ContractCondition(conditionObj); + const conditionContext = new ConditionContext(condition); + expect(() => + conditionContext.addAuthProvider( + USER_ADDRESS_PARAM_EIP1271, + authProviders[USER_ADDRESS_PARAM_EXTERNAL_EIP4361], + ), + ).toThrow(`Invalid AuthProvider type for ${USER_ADDRESS_PARAM_EIP1271}`); + }); + + it.each([ + USER_ADDRESS_PARAM_DEFAULT, + USER_ADDRESS_PARAM_EXTERNAL_EIP4361, + USER_ADDRESS_PARAM_EIP1271, + ])('it supports just one provider at a time', async (userAddressParam) => { + const conditionObj = { + ...testContractConditionObj, + returnValueTest: { + ...testReturnValueTest, + value: userAddressParam, + }, + }; + const condition = new ContractCondition(conditionObj); + const conditionContext = new ConditionContext(condition); + conditionContext.addAuthProvider( + userAddressParam, + authProviders[userAddressParam], + ); + expect(async () => conditionContext.toContextParameters()).not.toThrow(); }); }); - describe('context parameters', () => { + describe('authentication signature', () => { + let provider: ethers.providers.Web3Provider; + let signer: ethers.providers.JsonRpcSigner; + let authProviders: Record; + + beforeAll(async () => { + await initialize(); + provider = fakeProvider(); + signer = provider.getSigner(); + authProviders = await fakeAuthProviders(signer); + }); + + async function testAuthSignature( + authSignature: AuthSignature, + expectedScheme: string, + expectedAddress?: string, + ) { + expect(authSignature).toBeDefined(); + expect(authSignature.signature).toBeDefined(); + expect(authSignature.scheme).toEqual(expectedScheme); + + const addressToUse = expectedAddress + ? expectedAddress + : await signer.getAddress(); + expect(authSignature.address).toEqual(addressToUse); + + const chainId = (await provider.getNetwork()).chainId; + + if (expectedScheme === 'EIP4361') { + expect(authSignature.typedData).toContain( + `localhost wants you to sign in with your Ethereum account:\n${addressToUse}`, + ); + expect(authSignature.typedData).toContain('URI: http://localhost:3000'); + + expect(authSignature.typedData).toContain(`Chain ID: ${chainId}`); + } else if (expectedScheme === 'EIP1271') { + const authSign = authSignature as EIP1271AuthSignature; + expect(authSign.typedData.chain).toEqual(chainId); + expect(authSign.typedData.dataHash).toBeDefined(); + } else { + throw new Error(`Unknown scheme: ${expectedScheme}`); + } + } + + async function makeAuthSignature(userAddressParam: string) { + const conditionObj = { + ...testContractConditionObj, + returnValueTest: { + ...testReturnValueTest, + value: userAddressParam, + }, + }; + const condition = new ContractCondition(conditionObj); + + const conditionContext = new ConditionContext(condition); + conditionContext.addAuthProvider( + userAddressParam, + authProviders[userAddressParam], + ); + const contextVars = await conditionContext.toContextParameters(); + const authSignature = contextVars[userAddressParam] as AuthSignature; + expect(authSignature).toBeDefined(); + + return authSignature; + } + + it('supports default auth method (eip4361)', async () => { + const eip4361Spy = vi.spyOn( + EIP4361AuthProvider.prototype, + 'getOrCreateAuthSignature', + ); + + const authSignature = await makeAuthSignature(USER_ADDRESS_PARAM_DEFAULT); + await testAuthSignature(authSignature, 'EIP4361'); + expect(eip4361Spy).toHaveBeenCalledOnce(); + }); + + it('supports reusing external eip4361', async () => { + // Spying on the EIP4361 provider to make sure it's not called + const eip4361Spy = vi.spyOn( + SingleSignOnEIP4361AuthProvider.prototype, + 'getOrCreateAuthSignature', + ); + + const authSignature = await makeAuthSignature( + USER_ADDRESS_PARAM_EXTERNAL_EIP4361, + ); + expect(authSignature).toBeDefined(); + await testAuthSignature( + authSignature, + 'EIP4361', + ( + authProviders[ + USER_ADDRESS_PARAM_EXTERNAL_EIP4361 + ] as SingleSignOnEIP4361AuthProvider + ).address, + ); + expect(eip4361Spy).toHaveBeenCalledOnce(); + }); + + it('supports eip1271 auth method', async () => { + const eip1271Spy = vi.spyOn( + EIP1271AuthProvider.prototype, + 'getOrCreateAuthSignature', + ); + + const authSignature = await makeAuthSignature(USER_ADDRESS_PARAM_EIP1271); + expect(authSignature).toBeDefined(); + await testAuthSignature( + authSignature, + 'EIP1271', + (authProviders[USER_ADDRESS_PARAM_EIP1271] as EIP1271AuthProvider) + .contractAddress, + ); + expect(eip1271Spy).toHaveBeenCalledOnce(); + }); + }); + + describe('user-defined context parameters', () => { const customParamKey = ':customParam'; const customParams: Record = {}; customParams[customParamKey] = 1234; @@ -152,79 +459,6 @@ describe('context', () => { ).toThrow(`Unknown custom context parameter: ${badCustomParamKey}`); }); - it('detects when auth provider is required by parameters', async () => { - const conditionObj = { - ...testContractConditionObj, - parameters: [USER_ADDRESS_PARAM_DEFAULT], - returnValueTest: { - comparator: '==', - value: 100, - } as ReturnValueTestProps, - }; - const condition = new ContractCondition(conditionObj); - const conditionContext = new ConditionContext(condition); - await expect(conditionContext.toContextParameters()).rejects.toThrow( - `No matching authentication provider to satisfy ${USER_ADDRESS_PARAM_DEFAULT} context variable in condition`, - ); - }); - - it('detects when signer is required by return value test', async () => { - const conditionObj = { - ...testContractConditionObj, - standardContractType: 'ERC721', - method: 'ownerOf', - parameters: [3591], - returnValueTest: { - comparator: '==', - value: USER_ADDRESS_PARAM_DEFAULT, - }, - } as ContractConditionProps; - const condition = new ContractCondition(conditionObj); - const conditionContext = new ConditionContext(condition); - await expect(conditionContext.toContextParameters()).rejects.toThrow( - `No matching authentication provider to satisfy ${USER_ADDRESS_PARAM_DEFAULT} context variable in condition`, - ); - }); - - it('detects when signer is not required', async () => { - const condition = new RpcCondition(testRpcConditionObj); - const conditionContext = new ConditionContext(condition); - expect( - JSON.stringify(condition.toObj()).includes(USER_ADDRESS_PARAM_DEFAULT), - ).toBe(false); - await expect(conditionContext.toContextParameters()).toBeDefined(); - }); - - it('rejects on a missing signer', async () => { - const conditionObj = { - ...testContractConditionObj, - returnValueTest: { - ...testReturnValueTest, - value: USER_ADDRESS_PARAM_DEFAULT, - }, - }; - const condition = new ContractCondition(conditionObj); - const conditionContext = new ConditionContext(condition); - await expect(conditionContext.toContextParameters()).rejects.toThrow( - `No matching authentication provider to satisfy ${USER_ADDRESS_PARAM_DEFAULT} context variable in condition`, - ); - }); - - it('rejects on a missing signer for single sign-on EIP4361', async () => { - const conditionObj = { - ...testContractConditionObj, - returnValueTest: { - ...testReturnValueTest, - value: USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - }, - }; - const condition = new ContractCondition(conditionObj); - const conditionContext = new ConditionContext(condition); - await expect(conditionContext.toContextParameters()).rejects.toThrow( - `No matching authentication provider to satisfy ${USER_ADDRESS_PARAM_EXTERNAL_EIP4361} context variable in condition`, - ); - }); - describe('custom method parameters', () => { const contractConditionObj = { ...testContractConditionObj, @@ -267,17 +501,17 @@ describe('context', () => { it('accepts on a hard-coded parameter', async () => { const customContractCondition = new ContractCondition({ ...contractConditionObj, - parameters: [USER_ADDRESS_PARAM_DEFAULT, 100], + parameters: [USER_ADDRESS_PARAM_EIP1271, 100], }); const conditionContext = new ConditionContext(customContractCondition); conditionContext.addAuthProvider( - USER_ADDRESS_PARAM_DEFAULT, - authProviders[USER_ADDRESS_PARAM_DEFAULT], + USER_ADDRESS_PARAM_EIP1271, + authProviders[USER_ADDRESS_PARAM_EIP1271], ); const asObj = await conditionContext.toContextParameters(); expect(asObj).toBeDefined(); - expect(asObj[USER_ADDRESS_PARAM_DEFAULT]).toBeDefined(); + expect(asObj[USER_ADDRESS_PARAM_EIP1271]).toBeDefined(); }); it.each([0, ''])( @@ -285,7 +519,7 @@ describe('context', () => { async (falsyParam) => { const customContractCondition = new ContractCondition({ ...contractConditionObj, - parameters: [USER_ADDRESS_PARAM_DEFAULT, customParamKey], + parameters: [USER_ADDRESS_PARAM_EXTERNAL_EIP4361, customParamKey], }); const customParameters: Record = {}; customParameters[customParamKey] = falsyParam; @@ -294,14 +528,14 @@ describe('context', () => { customContractCondition, ); conditionContext.addAuthProvider( - USER_ADDRESS_PARAM_DEFAULT, - authProviders[USER_ADDRESS_PARAM_DEFAULT], + USER_ADDRESS_PARAM_EXTERNAL_EIP4361, + authProviders[USER_ADDRESS_PARAM_EXTERNAL_EIP4361], ); conditionContext.addCustomContextParameterValues(customParameters); const asObj = await conditionContext.toContextParameters(); expect(asObj).toBeDefined(); - expect(asObj[USER_ADDRESS_PARAM_DEFAULT]).toBeDefined(); + expect(asObj[USER_ADDRESS_PARAM_EXTERNAL_EIP4361]).toBeDefined(); expect(asObj[customParamKey]).toBeDefined(); expect(asObj[customParamKey]).toEqual(falsyParam); }, @@ -310,214 +544,6 @@ describe('context', () => { }); }); -// TODO: Move to a separate file -describe('No authentication provider', () => { - let provider: ethers.providers.Web3Provider; - let signer: ethers.providers.JsonRpcSigner; - let authProviders: Record; - - async function testEIP4361AuthSignature( - authSignature: AuthSignature, - expectedAddress?: string, - ) { - expect(authSignature).toBeDefined(); - expect(authSignature.signature).toBeDefined(); - expect(authSignature.scheme).toEqual('EIP4361'); - - const addressToUse = expectedAddress - ? expectedAddress - : await signer.getAddress(); - expect(authSignature.address).toEqual(addressToUse); - - expect(authSignature.typedData).toContain( - `localhost wants you to sign in with your Ethereum account:\n${addressToUse}`, - ); - expect(authSignature.typedData).toContain('URI: http://localhost:3000'); - - const chainId = (await provider.getNetwork()).chainId; - expect(authSignature.typedData).toContain(`Chain ID: ${chainId}`); - } - - beforeAll(async () => { - await initialize(); - provider = fakeProvider(); - signer = provider.getSigner(); - authProviders = await fakeAuthProviders(signer); - }); - - it('throws an error if there is no auth provider', () => { - RESERVED_CONTEXT_PARAMS.forEach(async (userAddressParam) => { - const conditionObj = { - ...testContractConditionObj, - returnValueTest: { - ...testReturnValueTest, - value: userAddressParam, - }, - }; - const condition = new ContractCondition(conditionObj); - const conditionContext = new ConditionContext(condition); - await expect(conditionContext.toContextParameters()).rejects.toThrow( - `No matching authentication provider to satisfy ${userAddressParam} context variable in condition`, - ); - }); - }); - - it('rejects auth provider for not applicable context param', () => { - const conditionObj = { - ...testContractConditionObj, - returnValueTest: { - ...testReturnValueTest, - value: ':myParam', - }, - }; - const condition = new ContractCondition(conditionObj); - const conditionContext = new ConditionContext(condition); - expect(() => - conditionContext.addAuthProvider( - ':myParam', - authProviders[USER_ADDRESS_PARAM_DEFAULT], - ), - ).toThrow('AuthProvider not necessary for context parameter: :myParam'); - }); - - it('rejects invalid auth provider for :userAddress', () => { - const conditionObj = { - ...testContractConditionObj, - returnValueTest: { - ...testReturnValueTest, - value: USER_ADDRESS_PARAM_DEFAULT, - }, - }; - const condition = new ContractCondition(conditionObj); - const conditionContext = new ConditionContext(condition); - expect(() => - conditionContext.addAuthProvider( - USER_ADDRESS_PARAM_DEFAULT, - authProviders[USER_ADDRESS_PARAM_EXTERNAL_EIP4361], - ), - ).toThrow(`Invalid AuthProvider type for ${USER_ADDRESS_PARAM_DEFAULT}`); - }); - - it('rejects invalid auth provider for :userAddressExternalEIP4361', () => { - const conditionObj = { - ...testContractConditionObj, - returnValueTest: { - ...testReturnValueTest, - value: USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - }, - }; - const condition = new ContractCondition(conditionObj); - const conditionContext = new ConditionContext(condition); - expect(() => - conditionContext.addAuthProvider( - USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - authProviders[USER_ADDRESS_PARAM_DEFAULT], - ), - ).toThrow( - `Invalid AuthProvider type for ${USER_ADDRESS_PARAM_EXTERNAL_EIP4361}`, - ); - }); - - it('it supports just one provider at a time', async () => { - const conditionObj = { - ...testContractConditionObj, - returnValueTest: { - ...testReturnValueTest, - value: USER_ADDRESS_PARAM_DEFAULT, - }, - }; - const condition = new ContractCondition(conditionObj); - const conditionContext = new ConditionContext(condition); - conditionContext.addAuthProvider( - USER_ADDRESS_PARAM_DEFAULT, - authProviders[USER_ADDRESS_PARAM_DEFAULT], - ); - expect(async () => conditionContext.toContextParameters()).not.toThrow(); - }); - - async function makeAuthSignature(authMethod: string) { - const conditionObj = { - ...testContractConditionObj, - returnValueTest: { - ...testReturnValueTest, - value: authMethod, - }, - }; - const condition = new ContractCondition(conditionObj); - - const conditionContext = new ConditionContext(condition); - conditionContext.addAuthProvider( - USER_ADDRESS_PARAM_DEFAULT, - authProviders[USER_ADDRESS_PARAM_DEFAULT], - ); - const contextVars = await conditionContext.toContextParameters(); - const authSignature = contextVars[authMethod] as AuthSignature; - expect(authSignature).toBeDefined(); - - return authSignature; - } - - async function testEIP4361AuthMethod(authMethod: string) { - const eip4361Spy = vi.spyOn( - EIP4361AuthProvider.prototype, - 'getOrCreateAuthSignature', - ); - const authSignature = await makeAuthSignature(authMethod); - await testEIP4361AuthSignature(authSignature); - expect(eip4361Spy).toHaveBeenCalledOnce(); - } - - it('supports default auth method (eip4361)', async () => { - await testEIP4361AuthMethod(USER_ADDRESS_PARAM_DEFAULT); - }); - - it('supports reusing external eip4361', async () => { - // Spying on the EIP4361 provider to make sure it's not called - const eip4361Spy = vi.spyOn( - EIP4361AuthProvider.prototype, - 'getOrCreateAuthSignature', - ); - - // Now, creating the condition context to run the actual test - const conditionObj = { - ...testContractConditionObj, - returnValueTest: { - ...testReturnValueTest, - value: USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - }, - }; - const condition = new ContractCondition(conditionObj); - const conditionContext = new ConditionContext(condition); - - // Should throw an error if we don't pass the custom parameter - await expect(conditionContext.toContextParameters()).rejects.toThrow( - `No matching authentication provider to satisfy ${USER_ADDRESS_PARAM_EXTERNAL_EIP4361} context variable in condition`, - ); - - // Remembering to pass in auth provider - conditionContext.addAuthProvider( - USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - authProviders[USER_ADDRESS_PARAM_EXTERNAL_EIP4361], - ); - const contextVars = await conditionContext.toContextParameters(); - expect(eip4361Spy).not.toHaveBeenCalled(); - - // Now, we expect that the auth signature will be available in the context variables - const authSignature = contextVars[ - USER_ADDRESS_PARAM_EXTERNAL_EIP4361 - ] as AuthSignature; - expect(authSignature).toBeDefined(); - await testEIP4361AuthSignature( - authSignature, - ( - authProviders[ - USER_ADDRESS_PARAM_EXTERNAL_EIP4361 - ] as SingleSignOnEIP4361AuthProvider - ).address, - ); - }); -}); - describe('param or context param schema', () => { it('accepts a plain string', () => { expect(paramOrContextParamSchema.safeParse('hello').success).toBe(true); From b507f290b89a8315137ae325511aa0bf2bc270f9 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Wed, 29 Jan 2025 09:37:25 -0500 Subject: [PATCH 05/10] Ensure that AuthSignature types are exported. --- packages/taco-auth/src/providers/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/taco-auth/src/providers/index.ts b/packages/taco-auth/src/providers/index.ts index 66605d162..c306b66c6 100644 --- a/packages/taco-auth/src/providers/index.ts +++ b/packages/taco-auth/src/providers/index.ts @@ -1,3 +1,6 @@ +// TODO: should we export with package names? +export { EIP1271AuthSignature } from './eip1271/auth'; export * from './eip1271/eip1271'; +export { EIP4361AuthSignature } from './eip4361/auth'; export * from './eip4361/eip4361'; export * from './eip4361/external-eip4361'; From 9b4a1d6c0218458d4569e79c98fe2253e3607482 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Mon, 10 Feb 2025 11:09:28 +0100 Subject: [PATCH 06/10] feat: update auth provider type validation Updates AuthProviderType to handle multiple provider types for USER_ADDRESS_PARAM_DEFAULT while maintaining single type for external EIP4361. Updates related tests to properly map auth provider keys. --- .../src/providers/eip1271/eip1271.ts | 1 - packages/taco/src/conditions/const.ts | 2 - .../taco/src/conditions/context/context.ts | 18 ++--- .../taco/src/conditions/schemas/common.ts | 2 - .../test/conditions/base/contract.test.ts | 2 +- .../taco/test/conditions/conditions.test.ts | 2 +- packages/taco/test/conditions/context.test.ts | 76 ++++++------------- packages/test-utils/src/utils.ts | 9 +-- 8 files changed, 40 insertions(+), 72 deletions(-) diff --git a/packages/taco-auth/src/providers/eip1271/eip1271.ts b/packages/taco-auth/src/providers/eip1271/eip1271.ts index 8c31d7512..fb72b6c9d 100644 --- a/packages/taco-auth/src/providers/eip1271/eip1271.ts +++ b/packages/taco-auth/src/providers/eip1271/eip1271.ts @@ -1,6 +1,5 @@ import { EIP1271_AUTH_METHOD, EIP1271AuthSignature } from './auth'; -export const USER_ADDRESS_PARAM_EIP1271 = ':userAddressEIP1271'; export class EIP1271AuthProvider { constructor( diff --git a/packages/taco/src/conditions/const.ts b/packages/taco/src/conditions/const.ts index ba75bc4b2..f74b4fbd9 100644 --- a/packages/taco/src/conditions/const.ts +++ b/packages/taco/src/conditions/const.ts @@ -1,6 +1,5 @@ import { USER_ADDRESS_PARAM_DEFAULT, - USER_ADDRESS_PARAM_EIP1271, USER_ADDRESS_PARAM_EXTERNAL_EIP4361, } from '@nucypher/taco-auth'; @@ -18,7 +17,6 @@ export const CONTEXT_PARAM_PREFIX = ':'; export const USER_ADDRESS_PARAMS = [ USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - USER_ADDRESS_PARAM_EIP1271, // Ordering matters, this should always be last USER_ADDRESS_PARAM_DEFAULT, ]; diff --git a/packages/taco/src/conditions/context/context.ts b/packages/taco/src/conditions/context/context.ts index 8e594c6b5..f8482c7dd 100644 --- a/packages/taco/src/conditions/context/context.ts +++ b/packages/taco/src/conditions/context/context.ts @@ -7,7 +7,6 @@ import { EIP4361AuthProvider, SingleSignOnEIP4361AuthProvider, USER_ADDRESS_PARAM_DEFAULT, - USER_ADDRESS_PARAM_EIP1271, USER_ADDRESS_PARAM_EXTERNAL_EIP4361, } from '@nucypher/taco-auth'; @@ -40,19 +39,17 @@ const ERR_AUTH_PROVIDER_NOT_NEEDED_FOR_CONTEXT_PARAM = (param: string) => `AuthProvider not necessary for context parameter: ${param}`; type AuthProviderType = - | typeof EIP4361AuthProvider - | typeof SingleSignOnEIP4361AuthProvider - | typeof EIP1271AuthProvider; + | (typeof EIP4361AuthProvider | typeof EIP1271AuthProvider)[] + | typeof SingleSignOnEIP4361AuthProvider; + const EXPECTED_AUTH_PROVIDER_TYPES: Record = { - [USER_ADDRESS_PARAM_DEFAULT]: EIP4361AuthProvider, + [USER_ADDRESS_PARAM_DEFAULT]: [EIP4361AuthProvider, EIP1271AuthProvider], [USER_ADDRESS_PARAM_EXTERNAL_EIP4361]: SingleSignOnEIP4361AuthProvider, - [USER_ADDRESS_PARAM_EIP1271]: EIP1271AuthProvider, }; export const RESERVED_CONTEXT_PARAMS = [ USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - USER_ADDRESS_PARAM_EIP1271, USER_ADDRESS_PARAM_DEFAULT, ]; @@ -222,8 +219,12 @@ export class ConditionContext { ERR_AUTH_PROVIDER_NOT_NEEDED_FOR_CONTEXT_PARAM(contextParam), ); } + const expectedType = EXPECTED_AUTH_PROVIDER_TYPES[contextParam]; + const isValid = Array.isArray(expectedType) + ? expectedType.some(type => authProvider instanceof type) + : authProvider instanceof expectedType; - if (!(authProvider instanceof EXPECTED_AUTH_PROVIDER_TYPES[contextParam])) { + if (!isValid) { throw new Error( ERR_INVALID_AUTH_PROVIDER_TYPE(contextParam, typeof authProvider), ); @@ -231,7 +232,6 @@ export class ConditionContext { this.authProviders[contextParam] = authProvider; } - public async toJson(): Promise { const parameters = await this.toContextParameters(); return toJSON(parameters); diff --git a/packages/taco/src/conditions/schemas/common.ts b/packages/taco/src/conditions/schemas/common.ts index a92cb94c6..182148a86 100644 --- a/packages/taco/src/conditions/schemas/common.ts +++ b/packages/taco/src/conditions/schemas/common.ts @@ -1,7 +1,6 @@ import { JSONPath } from '@astronautlabs/jsonpath'; import { USER_ADDRESS_PARAM_DEFAULT, - USER_ADDRESS_PARAM_EIP1271, USER_ADDRESS_PARAM_EXTERNAL_EIP4361, } from '@nucypher/taco-auth'; import { Primitive, z, ZodLiteral } from 'zod'; @@ -21,7 +20,6 @@ export const plainStringSchema = z.string().refine( export const UserAddressSchema = z.enum([ USER_ADDRESS_PARAM_DEFAULT, - USER_ADDRESS_PARAM_EIP1271, USER_ADDRESS_PARAM_EXTERNAL_EIP4361, ]); diff --git a/packages/taco/test/conditions/base/contract.test.ts b/packages/taco/test/conditions/base/contract.test.ts index 634369ee0..f453d701e 100644 --- a/packages/taco/test/conditions/base/contract.test.ts +++ b/packages/taco/test/conditions/base/contract.test.ts @@ -201,7 +201,7 @@ describe('supports custom function abi', async () => { conditionContext.addAuthProvider( USER_ADDRESS_PARAM_DEFAULT, - authProviders[USER_ADDRESS_PARAM_DEFAULT], + authProviders["EIP4361"], ); const asJson = await conditionContext.toJson(); diff --git a/packages/taco/test/conditions/conditions.test.ts b/packages/taco/test/conditions/conditions.test.ts index 6c80d6142..b6de129f3 100644 --- a/packages/taco/test/conditions/conditions.test.ts +++ b/packages/taco/test/conditions/conditions.test.ts @@ -43,7 +43,7 @@ describe('conditions', () => { context.addCustomContextParameterValues({ ':time': 100 }); context.addAuthProvider( USER_ADDRESS_PARAM_DEFAULT, - authProviders[USER_ADDRESS_PARAM_DEFAULT], + authProviders["EIP4361"], ); expect(context).toBeDefined(); diff --git a/packages/taco/test/conditions/context.test.ts b/packages/taco/test/conditions/context.test.ts index 977cd83c9..636dca04c 100644 --- a/packages/taco/test/conditions/context.test.ts +++ b/packages/taco/test/conditions/context.test.ts @@ -7,7 +7,6 @@ import { EIP4361AuthProvider, SingleSignOnEIP4361AuthProvider, USER_ADDRESS_PARAM_DEFAULT, - USER_ADDRESS_PARAM_EIP1271, USER_ADDRESS_PARAM_EXTERNAL_EIP4361, } from '@nucypher/taco-auth'; import { fakeAuthProviders, fakeProvider } from '@nucypher/test-utils'; @@ -53,10 +52,9 @@ describe('context', () => { describe('reserved context parameters', () => { it.each([ - USER_ADDRESS_PARAM_DEFAULT, - USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - USER_ADDRESS_PARAM_EIP1271, - ])('serializes to json', async (userAddressParam) => { + [USER_ADDRESS_PARAM_DEFAULT, "EIP4361"], + [USER_ADDRESS_PARAM_EXTERNAL_EIP4361, "SSO4361"], + ])('serializes to json', async (userAddressParam, authProviderKey) => { const rpcCondition = new RpcCondition({ ...testRpcConditionObj, parameters: [userAddressParam], @@ -68,7 +66,7 @@ describe('context', () => { const conditionContext = new ConditionContext(rpcCondition); conditionContext.addAuthProvider( userAddressParam, - authProviders[userAddressParam], + authProviders[authProviderKey], ); const asJson = await conditionContext.toJson(); @@ -79,7 +77,6 @@ describe('context', () => { it.each([ USER_ADDRESS_PARAM_DEFAULT, USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - USER_ADDRESS_PARAM_EIP1271, ])( 'detects when auth provider is required by parameters', async (userAddressParam) => { @@ -102,7 +99,6 @@ describe('context', () => { it.each([ USER_ADDRESS_PARAM_DEFAULT, USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - USER_ADDRESS_PARAM_EIP1271, ])( 'detects when signer is required by return value test', async (userAddressParam) => { @@ -135,16 +131,12 @@ describe('context', () => { USER_ADDRESS_PARAM_EXTERNAL_EIP4361, ), ).toBe(false); - expect( - JSON.stringify(condition.toObj()).includes(USER_ADDRESS_PARAM_EIP1271), - ).toBe(false); await expect(conditionContext.toContextParameters()).toBeDefined(); }); it.each([ USER_ADDRESS_PARAM_DEFAULT, USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - USER_ADDRESS_PARAM_EIP1271, ])( 'return value test rejects on a missing signer', async (userAddressParam) => { @@ -176,7 +168,7 @@ describe('context', () => { expect(() => conditionContext.addAuthProvider( ':myParam', - authProviders[USER_ADDRESS_PARAM_DEFAULT], + authProviders["EIP4361"], ), ).toThrow('AuthProvider not necessary for context parameter: :myParam'); }); @@ -194,7 +186,7 @@ describe('context', () => { expect(() => conditionContext.addAuthProvider( USER_ADDRESS_PARAM_DEFAULT, - authProviders[USER_ADDRESS_PARAM_EXTERNAL_EIP4361], + authProviders["SSO4361"], ), ).toThrow(`Invalid AuthProvider type for ${USER_ADDRESS_PARAM_DEFAULT}`); }); @@ -212,36 +204,17 @@ describe('context', () => { expect(() => conditionContext.addAuthProvider( USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - authProviders[USER_ADDRESS_PARAM_DEFAULT], + authProviders["EIP4361"], // it should be SSO4361 ), ).toThrow( `Invalid AuthProvider type for ${USER_ADDRESS_PARAM_EXTERNAL_EIP4361}`, ); }); - it('rejects invalid auth provider for :userAddressEIP1271', () => { - const conditionObj = { - ...testContractConditionObj, - returnValueTest: { - ...testReturnValueTest, - value: USER_ADDRESS_PARAM_EIP1271, - }, - }; - const condition = new ContractCondition(conditionObj); - const conditionContext = new ConditionContext(condition); - expect(() => - conditionContext.addAuthProvider( - USER_ADDRESS_PARAM_EIP1271, - authProviders[USER_ADDRESS_PARAM_EXTERNAL_EIP4361], - ), - ).toThrow(`Invalid AuthProvider type for ${USER_ADDRESS_PARAM_EIP1271}`); - }); - it.each([ - USER_ADDRESS_PARAM_DEFAULT, - USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - USER_ADDRESS_PARAM_EIP1271, - ])('it supports just one provider at a time', async (userAddressParam) => { + [USER_ADDRESS_PARAM_DEFAULT, "EIP4361"], + [USER_ADDRESS_PARAM_EXTERNAL_EIP4361, "SSO4361"], + ])('it supports just one provider at a time', async (userAddressParam, authProviderKey) => { const conditionObj = { ...testContractConditionObj, returnValueTest: { @@ -253,7 +226,7 @@ describe('context', () => { const conditionContext = new ConditionContext(condition); conditionContext.addAuthProvider( userAddressParam, - authProviders[userAddressParam], + authProviders[authProviderKey], ); expect(async () => conditionContext.toContextParameters()).not.toThrow(); }); @@ -303,7 +276,7 @@ describe('context', () => { } } - async function makeAuthSignature(userAddressParam: string) { + async function makeAuthSignature(userAddressParam: string, scheme: string) { const conditionObj = { ...testContractConditionObj, returnValueTest: { @@ -316,7 +289,7 @@ describe('context', () => { const conditionContext = new ConditionContext(condition); conditionContext.addAuthProvider( userAddressParam, - authProviders[userAddressParam], + authProviders[scheme], ); const contextVars = await conditionContext.toContextParameters(); const authSignature = contextVars[userAddressParam] as AuthSignature; @@ -331,7 +304,7 @@ describe('context', () => { 'getOrCreateAuthSignature', ); - const authSignature = await makeAuthSignature(USER_ADDRESS_PARAM_DEFAULT); + const authSignature = await makeAuthSignature(USER_ADDRESS_PARAM_DEFAULT, "EIP4361"); await testAuthSignature(authSignature, 'EIP4361'); expect(eip4361Spy).toHaveBeenCalledOnce(); }); @@ -345,6 +318,7 @@ describe('context', () => { const authSignature = await makeAuthSignature( USER_ADDRESS_PARAM_EXTERNAL_EIP4361, + "SSO4361" ); expect(authSignature).toBeDefined(); await testAuthSignature( @@ -352,7 +326,7 @@ describe('context', () => { 'EIP4361', ( authProviders[ - USER_ADDRESS_PARAM_EXTERNAL_EIP4361 + "SSO4361" ] as SingleSignOnEIP4361AuthProvider ).address, ); @@ -365,12 +339,12 @@ describe('context', () => { 'getOrCreateAuthSignature', ); - const authSignature = await makeAuthSignature(USER_ADDRESS_PARAM_EIP1271); + const authSignature = await makeAuthSignature(USER_ADDRESS_PARAM_DEFAULT, "EIP1271"); expect(authSignature).toBeDefined(); await testAuthSignature( authSignature, 'EIP1271', - (authProviders[USER_ADDRESS_PARAM_EIP1271] as EIP1271AuthProvider) + (authProviders["EIP1271"] as EIP1271AuthProvider) .contractAddress, ); expect(eip1271Spy).toHaveBeenCalledOnce(); @@ -427,7 +401,7 @@ describe('context', () => { const conditionContext = new ConditionContext(contractCondition); conditionContext.addAuthProvider( USER_ADDRESS_PARAM_DEFAULT, - authProviders[USER_ADDRESS_PARAM_DEFAULT], + authProviders["EIP4361"], ); await expect(conditionContext.toContextParameters()).rejects.toThrow( `Missing custom context parameter(s): ${customParamKey}`, @@ -488,7 +462,7 @@ describe('context', () => { const conditionContext = new ConditionContext(customContractCondition); conditionContext.addAuthProvider( USER_ADDRESS_PARAM_DEFAULT, - authProviders[USER_ADDRESS_PARAM_DEFAULT], + authProviders["EIP4361"], ); await expect(async () => @@ -501,17 +475,17 @@ describe('context', () => { it('accepts on a hard-coded parameter', async () => { const customContractCondition = new ContractCondition({ ...contractConditionObj, - parameters: [USER_ADDRESS_PARAM_EIP1271, 100], + parameters: [USER_ADDRESS_PARAM_DEFAULT, 100], }); const conditionContext = new ConditionContext(customContractCondition); conditionContext.addAuthProvider( - USER_ADDRESS_PARAM_EIP1271, - authProviders[USER_ADDRESS_PARAM_EIP1271], + USER_ADDRESS_PARAM_DEFAULT, + authProviders["EIP4361"], ); const asObj = await conditionContext.toContextParameters(); expect(asObj).toBeDefined(); - expect(asObj[USER_ADDRESS_PARAM_EIP1271]).toBeDefined(); + expect(asObj[USER_ADDRESS_PARAM_DEFAULT]).toBeDefined(); }); it.each([0, ''])( @@ -529,7 +503,7 @@ describe('context', () => { ); conditionContext.addAuthProvider( USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - authProviders[USER_ADDRESS_PARAM_EXTERNAL_EIP4361], + authProviders["SSO4361"], ); conditionContext.addCustomContextParameterValues(customParameters); diff --git a/packages/test-utils/src/utils.ts b/packages/test-utils/src/utils.ts index 0d1ce1f14..5e9cb23bd 100644 --- a/packages/test-utils/src/utils.ts +++ b/packages/test-utils/src/utils.ts @@ -42,7 +42,6 @@ import { EIP4361AuthProvider, SingleSignOnEIP4361AuthProvider, USER_ADDRESS_PARAM_DEFAULT, - USER_ADDRESS_PARAM_EIP1271, USER_ADDRESS_PARAM_EXTERNAL_EIP4361, } from '@nucypher/taco-auth'; import { ethers, providers, Wallet } from 'ethers'; @@ -100,14 +99,14 @@ export const fakeAuthProviders = async ( ) => { const signerToUse = signer ? signer : fakeProvider().getSigner(); return { - [USER_ADDRESS_PARAM_DEFAULT]: fakeEIP4351AuthProvider(signerToUse), - [USER_ADDRESS_PARAM_EXTERNAL_EIP4361]: + ["EIP4361"]: fakeEIP4361AuthProvider(signerToUse), + ["SSO4361"]: await fakeSingleSignOnEIP4361AuthProvider(signerToUse), - [USER_ADDRESS_PARAM_EIP1271]: await fakeEIP1271AuthProvider(signerToUse), + ["EIP1271"]: await fakeEIP1271AuthProvider(signerToUse) }; }; -const fakeEIP4351AuthProvider = (signer: ethers.providers.JsonRpcSigner) => { +const fakeEIP4361AuthProvider = (signer: ethers.providers.JsonRpcSigner) => { return new EIP4361AuthProvider(signer.provider, signer, TEST_SIWE_PARAMS); }; From acaade344abe5180fbbc79bdd09387ceb6683545 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Tue, 11 Feb 2025 11:55:11 +0100 Subject: [PATCH 07/10] Apply PR suggestion to simplify AuthProviderType --- packages/taco/src/conditions/context/context.ts | 17 +++++++---------- packages/taco/test/conditions/context.test.ts | 9 +++++---- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/taco/src/conditions/context/context.ts b/packages/taco/src/conditions/context/context.ts index f8482c7dd..97ebd8ad9 100644 --- a/packages/taco/src/conditions/context/context.ts +++ b/packages/taco/src/conditions/context/context.ts @@ -39,13 +39,13 @@ const ERR_AUTH_PROVIDER_NOT_NEEDED_FOR_CONTEXT_PARAM = (param: string) => `AuthProvider not necessary for context parameter: ${param}`; type AuthProviderType = - | (typeof EIP4361AuthProvider | typeof EIP1271AuthProvider)[] + | typeof EIP4361AuthProvider + | typeof EIP1271AuthProvider | typeof SingleSignOnEIP4361AuthProvider; - -const EXPECTED_AUTH_PROVIDER_TYPES: Record = { +const EXPECTED_AUTH_PROVIDER_TYPES: Record = { [USER_ADDRESS_PARAM_DEFAULT]: [EIP4361AuthProvider, EIP1271AuthProvider], - [USER_ADDRESS_PARAM_EXTERNAL_EIP4361]: SingleSignOnEIP4361AuthProvider, + [USER_ADDRESS_PARAM_EXTERNAL_EIP4361]: [SingleSignOnEIP4361AuthProvider], }; export const RESERVED_CONTEXT_PARAMS = [ @@ -219,18 +219,15 @@ export class ConditionContext { ERR_AUTH_PROVIDER_NOT_NEEDED_FOR_CONTEXT_PARAM(contextParam), ); } - const expectedType = EXPECTED_AUTH_PROVIDER_TYPES[contextParam]; - const isValid = Array.isArray(expectedType) - ? expectedType.some(type => authProvider instanceof type) - : authProvider instanceof expectedType; - - if (!isValid) { + const expectedTypes = EXPECTED_AUTH_PROVIDER_TYPES[contextParam]; + if (!expectedTypes.some((type) => authProvider instanceof type)) { throw new Error( ERR_INVALID_AUTH_PROVIDER_TYPE(contextParam, typeof authProvider), ); } this.authProviders[contextParam] = authProvider; + } public async toJson(): Promise { const parameters = await this.toContextParameters(); diff --git a/packages/taco/test/conditions/context.test.ts b/packages/taco/test/conditions/context.test.ts index 636dca04c..55be33029 100644 --- a/packages/taco/test/conditions/context.test.ts +++ b/packages/taco/test/conditions/context.test.ts @@ -54,7 +54,7 @@ describe('context', () => { it.each([ [USER_ADDRESS_PARAM_DEFAULT, "EIP4361"], [USER_ADDRESS_PARAM_EXTERNAL_EIP4361, "SSO4361"], - ])('serializes to json', async (userAddressParam, authProviderKey) => { + ])('serializes to json', async (userAddressParam, scheme) => { const rpcCondition = new RpcCondition({ ...testRpcConditionObj, parameters: [userAddressParam], @@ -66,7 +66,7 @@ describe('context', () => { const conditionContext = new ConditionContext(rpcCondition); conditionContext.addAuthProvider( userAddressParam, - authProviders[authProviderKey], + authProviders[scheme], ); const asJson = await conditionContext.toJson(); @@ -213,8 +213,9 @@ describe('context', () => { it.each([ [USER_ADDRESS_PARAM_DEFAULT, "EIP4361"], + [USER_ADDRESS_PARAM_DEFAULT, "EIP1271"], [USER_ADDRESS_PARAM_EXTERNAL_EIP4361, "SSO4361"], - ])('it supports just one provider at a time', async (userAddressParam, authProviderKey) => { + ])('it supports just one provider at a time', async (userAddressParam, scheme) => { const conditionObj = { ...testContractConditionObj, returnValueTest: { @@ -226,7 +227,7 @@ describe('context', () => { const conditionContext = new ConditionContext(condition); conditionContext.addAuthProvider( userAddressParam, - authProviders[authProviderKey], + authProviders[scheme], ); expect(async () => conditionContext.toContextParameters()).not.toThrow(); }); From db875178b1ab3bd5ddc2e32099bc0c0abb10c103 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Tue, 11 Feb 2025 16:21:18 +0100 Subject: [PATCH 08/10] Add bogus provider for failure testing Adds fakeBogusProvider export with proper ExternalProvider implementation to enable testing of authentication failure cases. --- packages/taco/src/conditions/context/context.ts | 2 +- packages/taco/test/conditions/context.test.ts | 4 ++-- packages/test-utils/src/utils.ts | 14 +++++++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/taco/src/conditions/context/context.ts b/packages/taco/src/conditions/context/context.ts index 97ebd8ad9..97d38f284 100644 --- a/packages/taco/src/conditions/context/context.ts +++ b/packages/taco/src/conditions/context/context.ts @@ -44,7 +44,7 @@ type AuthProviderType = | typeof SingleSignOnEIP4361AuthProvider; const EXPECTED_AUTH_PROVIDER_TYPES: Record = { - [USER_ADDRESS_PARAM_DEFAULT]: [EIP4361AuthProvider, EIP1271AuthProvider], + [USER_ADDRESS_PARAM_DEFAULT]: [EIP4361AuthProvider, EIP1271AuthProvider, SingleSignOnEIP4361AuthProvider], [USER_ADDRESS_PARAM_EXTERNAL_EIP4361]: [SingleSignOnEIP4361AuthProvider], }; diff --git a/packages/taco/test/conditions/context.test.ts b/packages/taco/test/conditions/context.test.ts index 55be33029..1c7c0353c 100644 --- a/packages/taco/test/conditions/context.test.ts +++ b/packages/taco/test/conditions/context.test.ts @@ -186,7 +186,7 @@ describe('context', () => { expect(() => conditionContext.addAuthProvider( USER_ADDRESS_PARAM_DEFAULT, - authProviders["SSO4361"], + authProviders["Bogus"], ), ).toThrow(`Invalid AuthProvider type for ${USER_ADDRESS_PARAM_DEFAULT}`); }); @@ -481,7 +481,7 @@ describe('context', () => { const conditionContext = new ConditionContext(customContractCondition); conditionContext.addAuthProvider( USER_ADDRESS_PARAM_DEFAULT, - authProviders["EIP4361"], + authProviders["EIP1271"], ); const asObj = await conditionContext.toContextParameters(); diff --git a/packages/test-utils/src/utils.ts b/packages/test-utils/src/utils.ts index 5e9cb23bd..439409ff8 100644 --- a/packages/test-utils/src/utils.ts +++ b/packages/test-utils/src/utils.ts @@ -102,10 +102,22 @@ export const fakeAuthProviders = async ( ["EIP4361"]: fakeEIP4361AuthProvider(signerToUse), ["SSO4361"]: await fakeSingleSignOnEIP4361AuthProvider(signerToUse), - ["EIP1271"]: await fakeEIP1271AuthProvider(signerToUse) + ["EIP1271"]: await fakeEIP1271AuthProvider(signerToUse), + ["Bogus"]: fakeBogusProvider(signerToUse), }; }; +export const fakeBogusProvider = (signer: ethers.providers.JsonRpcSigner) => { + const externalProvider: ethers.providers.ExternalProvider = { + send: (request, callback) => { + callback(new Error("Bogus provider"), null); + }, + request: () => Promise.reject(new Error("Bogus provider")) + }; + return new ethers.providers.Web3Provider(externalProvider); +}; + + const fakeEIP4361AuthProvider = (signer: ethers.providers.JsonRpcSigner) => { return new EIP4361AuthProvider(signer.provider, signer, TEST_SIWE_PARAMS); }; From 6307d7cdd46c13c2e3b4ac3de5a15944910a74c0 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Tue, 11 Feb 2025 16:38:11 +0100 Subject: [PATCH 09/10] Fix fakeAuthProvider to implement AuthProvider properly --- packages/test-utils/src/utils.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/test-utils/src/utils.ts b/packages/test-utils/src/utils.ts index 439409ff8..ec2243a86 100644 --- a/packages/test-utils/src/utils.ts +++ b/packages/test-utils/src/utils.ts @@ -38,6 +38,8 @@ import { zip, } from '@nucypher/shared'; import { + AuthProvider, + AuthSignature, EIP1271AuthProvider, EIP4361AuthProvider, SingleSignOnEIP4361AuthProvider, @@ -103,21 +105,30 @@ export const fakeAuthProviders = async ( ["SSO4361"]: await fakeSingleSignOnEIP4361AuthProvider(signerToUse), ["EIP1271"]: await fakeEIP1271AuthProvider(signerToUse), - ["Bogus"]: fakeBogusProvider(signerToUse), + ["Bogus"]: fakeBogusAuthProvider(signerToUse), }; }; -export const fakeBogusProvider = (signer: ethers.providers.JsonRpcSigner) => { +class BogusAuthProvider implements AuthProvider { + constructor(private provider: ethers.providers.Web3Provider) {} + + async getOrCreateAuthSignature(): Promise { + throw new Error("Bogus provider"); + } +} + +export const fakeBogusAuthProvider = (signer: ethers.providers.JsonRpcSigner) => { const externalProvider: ethers.providers.ExternalProvider = { send: (request, callback) => { callback(new Error("Bogus provider"), null); }, request: () => Promise.reject(new Error("Bogus provider")) }; - return new ethers.providers.Web3Provider(externalProvider); + return new BogusAuthProvider(new ethers.providers.Web3Provider(externalProvider)); }; + const fakeEIP4361AuthProvider = (signer: ethers.providers.JsonRpcSigner) => { return new EIP4361AuthProvider(signer.provider, signer, TEST_SIWE_PARAMS); }; From b84e8eb4dcad05a0af872c5c305d69d7779d5663 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Tue, 11 Feb 2025 11:16:24 -0500 Subject: [PATCH 10/10] Fix linting errors. --- .../src/providers/eip1271/eip1271.ts | 1 - .../taco/src/conditions/context/context.ts | 7 +- .../test/conditions/base/contract.test.ts | 2 +- .../taco/test/conditions/conditions.test.ts | 2 +- packages/taco/test/conditions/context.test.ts | 110 ++++++++---------- packages/test-utils/src/utils.ts | 27 +++-- 6 files changed, 69 insertions(+), 80 deletions(-) diff --git a/packages/taco-auth/src/providers/eip1271/eip1271.ts b/packages/taco-auth/src/providers/eip1271/eip1271.ts index fb72b6c9d..4465f902b 100644 --- a/packages/taco-auth/src/providers/eip1271/eip1271.ts +++ b/packages/taco-auth/src/providers/eip1271/eip1271.ts @@ -1,6 +1,5 @@ import { EIP1271_AUTH_METHOD, EIP1271AuthSignature } from './auth'; - export class EIP1271AuthProvider { constructor( public readonly contractAddress: string, diff --git a/packages/taco/src/conditions/context/context.ts b/packages/taco/src/conditions/context/context.ts index 97d38f284..ca651ac1b 100644 --- a/packages/taco/src/conditions/context/context.ts +++ b/packages/taco/src/conditions/context/context.ts @@ -44,7 +44,11 @@ type AuthProviderType = | typeof SingleSignOnEIP4361AuthProvider; const EXPECTED_AUTH_PROVIDER_TYPES: Record = { - [USER_ADDRESS_PARAM_DEFAULT]: [EIP4361AuthProvider, EIP1271AuthProvider, SingleSignOnEIP4361AuthProvider], + [USER_ADDRESS_PARAM_DEFAULT]: [ + EIP4361AuthProvider, + EIP1271AuthProvider, + SingleSignOnEIP4361AuthProvider, + ], [USER_ADDRESS_PARAM_EXTERNAL_EIP4361]: [SingleSignOnEIP4361AuthProvider], }; @@ -227,7 +231,6 @@ export class ConditionContext { } this.authProviders[contextParam] = authProvider; - } public async toJson(): Promise { const parameters = await this.toContextParameters(); diff --git a/packages/taco/test/conditions/base/contract.test.ts b/packages/taco/test/conditions/base/contract.test.ts index f453d701e..916357418 100644 --- a/packages/taco/test/conditions/base/contract.test.ts +++ b/packages/taco/test/conditions/base/contract.test.ts @@ -201,7 +201,7 @@ describe('supports custom function abi', async () => { conditionContext.addAuthProvider( USER_ADDRESS_PARAM_DEFAULT, - authProviders["EIP4361"], + authProviders['EIP4361'], ); const asJson = await conditionContext.toJson(); diff --git a/packages/taco/test/conditions/conditions.test.ts b/packages/taco/test/conditions/conditions.test.ts index b6de129f3..2e4285923 100644 --- a/packages/taco/test/conditions/conditions.test.ts +++ b/packages/taco/test/conditions/conditions.test.ts @@ -43,7 +43,7 @@ describe('conditions', () => { context.addCustomContextParameterValues({ ':time': 100 }); context.addAuthProvider( USER_ADDRESS_PARAM_DEFAULT, - authProviders["EIP4361"], + authProviders['EIP4361'], ); expect(context).toBeDefined(); diff --git a/packages/taco/test/conditions/context.test.ts b/packages/taco/test/conditions/context.test.ts index 1c7c0353c..8355977a0 100644 --- a/packages/taco/test/conditions/context.test.ts +++ b/packages/taco/test/conditions/context.test.ts @@ -52,8 +52,8 @@ describe('context', () => { describe('reserved context parameters', () => { it.each([ - [USER_ADDRESS_PARAM_DEFAULT, "EIP4361"], - [USER_ADDRESS_PARAM_EXTERNAL_EIP4361, "SSO4361"], + [USER_ADDRESS_PARAM_DEFAULT, 'EIP4361'], + [USER_ADDRESS_PARAM_EXTERNAL_EIP4361, 'SSO4361'], ])('serializes to json', async (userAddressParam, scheme) => { const rpcCondition = new RpcCondition({ ...testRpcConditionObj, @@ -64,20 +64,14 @@ describe('context', () => { }, }); const conditionContext = new ConditionContext(rpcCondition); - conditionContext.addAuthProvider( - userAddressParam, - authProviders[scheme], - ); + conditionContext.addAuthProvider(userAddressParam, authProviders[scheme]); const asJson = await conditionContext.toJson(); expect(asJson).toBeDefined(); expect(asJson).toContain(userAddressParam); }); - it.each([ - USER_ADDRESS_PARAM_DEFAULT, - USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - ])( + it.each([USER_ADDRESS_PARAM_DEFAULT, USER_ADDRESS_PARAM_EXTERNAL_EIP4361])( 'detects when auth provider is required by parameters', async (userAddressParam) => { const conditionObj = { @@ -96,10 +90,7 @@ describe('context', () => { }, ); - it.each([ - USER_ADDRESS_PARAM_DEFAULT, - USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - ])( + it.each([USER_ADDRESS_PARAM_DEFAULT, USER_ADDRESS_PARAM_EXTERNAL_EIP4361])( 'detects when signer is required by return value test', async (userAddressParam) => { const conditionObj = { @@ -134,10 +125,7 @@ describe('context', () => { await expect(conditionContext.toContextParameters()).toBeDefined(); }); - it.each([ - USER_ADDRESS_PARAM_DEFAULT, - USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - ])( + it.each([USER_ADDRESS_PARAM_DEFAULT, USER_ADDRESS_PARAM_EXTERNAL_EIP4361])( 'return value test rejects on a missing signer', async (userAddressParam) => { const conditionObj = { @@ -166,10 +154,7 @@ describe('context', () => { const condition = new ContractCondition(conditionObj); const conditionContext = new ConditionContext(condition); expect(() => - conditionContext.addAuthProvider( - ':myParam', - authProviders["EIP4361"], - ), + conditionContext.addAuthProvider(':myParam', authProviders['EIP4361']), ).toThrow('AuthProvider not necessary for context parameter: :myParam'); }); @@ -186,7 +171,7 @@ describe('context', () => { expect(() => conditionContext.addAuthProvider( USER_ADDRESS_PARAM_DEFAULT, - authProviders["Bogus"], + authProviders['Bogus'], ), ).toThrow(`Invalid AuthProvider type for ${USER_ADDRESS_PARAM_DEFAULT}`); }); @@ -204,7 +189,7 @@ describe('context', () => { expect(() => conditionContext.addAuthProvider( USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - authProviders["EIP4361"], // it should be SSO4361 + authProviders['EIP4361'], // it should be SSO4361 ), ).toThrow( `Invalid AuthProvider type for ${USER_ADDRESS_PARAM_EXTERNAL_EIP4361}`, @@ -212,25 +197,30 @@ describe('context', () => { }); it.each([ - [USER_ADDRESS_PARAM_DEFAULT, "EIP4361"], - [USER_ADDRESS_PARAM_DEFAULT, "EIP1271"], - [USER_ADDRESS_PARAM_EXTERNAL_EIP4361, "SSO4361"], - ])('it supports just one provider at a time', async (userAddressParam, scheme) => { - const conditionObj = { - ...testContractConditionObj, - returnValueTest: { - ...testReturnValueTest, - value: userAddressParam, - }, - }; - const condition = new ContractCondition(conditionObj); - const conditionContext = new ConditionContext(condition); - conditionContext.addAuthProvider( - userAddressParam, - authProviders[scheme], - ); - expect(async () => conditionContext.toContextParameters()).not.toThrow(); - }); + [USER_ADDRESS_PARAM_DEFAULT, 'EIP4361'], + [USER_ADDRESS_PARAM_DEFAULT, 'EIP1271'], + [USER_ADDRESS_PARAM_EXTERNAL_EIP4361, 'SSO4361'], + ])( + 'it supports just one provider at a time', + async (userAddressParam, scheme) => { + const conditionObj = { + ...testContractConditionObj, + returnValueTest: { + ...testReturnValueTest, + value: userAddressParam, + }, + }; + const condition = new ContractCondition(conditionObj); + const conditionContext = new ConditionContext(condition); + conditionContext.addAuthProvider( + userAddressParam, + authProviders[scheme], + ); + expect(async () => + conditionContext.toContextParameters(), + ).not.toThrow(); + }, + ); }); describe('authentication signature', () => { @@ -288,10 +278,7 @@ describe('context', () => { const condition = new ContractCondition(conditionObj); const conditionContext = new ConditionContext(condition); - conditionContext.addAuthProvider( - userAddressParam, - authProviders[scheme], - ); + conditionContext.addAuthProvider(userAddressParam, authProviders[scheme]); const contextVars = await conditionContext.toContextParameters(); const authSignature = contextVars[userAddressParam] as AuthSignature; expect(authSignature).toBeDefined(); @@ -305,7 +292,10 @@ describe('context', () => { 'getOrCreateAuthSignature', ); - const authSignature = await makeAuthSignature(USER_ADDRESS_PARAM_DEFAULT, "EIP4361"); + const authSignature = await makeAuthSignature( + USER_ADDRESS_PARAM_DEFAULT, + 'EIP4361', + ); await testAuthSignature(authSignature, 'EIP4361'); expect(eip4361Spy).toHaveBeenCalledOnce(); }); @@ -319,17 +309,13 @@ describe('context', () => { const authSignature = await makeAuthSignature( USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - "SSO4361" + 'SSO4361', ); expect(authSignature).toBeDefined(); await testAuthSignature( authSignature, 'EIP4361', - ( - authProviders[ - "SSO4361" - ] as SingleSignOnEIP4361AuthProvider - ).address, + (authProviders['SSO4361'] as SingleSignOnEIP4361AuthProvider).address, ); expect(eip4361Spy).toHaveBeenCalledOnce(); }); @@ -340,13 +326,15 @@ describe('context', () => { 'getOrCreateAuthSignature', ); - const authSignature = await makeAuthSignature(USER_ADDRESS_PARAM_DEFAULT, "EIP1271"); + const authSignature = await makeAuthSignature( + USER_ADDRESS_PARAM_DEFAULT, + 'EIP1271', + ); expect(authSignature).toBeDefined(); await testAuthSignature( authSignature, 'EIP1271', - (authProviders["EIP1271"] as EIP1271AuthProvider) - .contractAddress, + (authProviders['EIP1271'] as EIP1271AuthProvider).contractAddress, ); expect(eip1271Spy).toHaveBeenCalledOnce(); }); @@ -402,7 +390,7 @@ describe('context', () => { const conditionContext = new ConditionContext(contractCondition); conditionContext.addAuthProvider( USER_ADDRESS_PARAM_DEFAULT, - authProviders["EIP4361"], + authProviders['EIP4361'], ); await expect(conditionContext.toContextParameters()).rejects.toThrow( `Missing custom context parameter(s): ${customParamKey}`, @@ -463,7 +451,7 @@ describe('context', () => { const conditionContext = new ConditionContext(customContractCondition); conditionContext.addAuthProvider( USER_ADDRESS_PARAM_DEFAULT, - authProviders["EIP4361"], + authProviders['EIP4361'], ); await expect(async () => @@ -481,7 +469,7 @@ describe('context', () => { const conditionContext = new ConditionContext(customContractCondition); conditionContext.addAuthProvider( USER_ADDRESS_PARAM_DEFAULT, - authProviders["EIP1271"], + authProviders['EIP1271'], ); const asObj = await conditionContext.toContextParameters(); @@ -504,7 +492,7 @@ describe('context', () => { ); conditionContext.addAuthProvider( USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - authProviders["SSO4361"], + authProviders['SSO4361'], ); conditionContext.addCustomContextParameterValues(customParameters); diff --git a/packages/test-utils/src/utils.ts b/packages/test-utils/src/utils.ts index ec2243a86..11e9538ac 100644 --- a/packages/test-utils/src/utils.ts +++ b/packages/test-utils/src/utils.ts @@ -43,8 +43,6 @@ import { EIP1271AuthProvider, EIP4361AuthProvider, SingleSignOnEIP4361AuthProvider, - USER_ADDRESS_PARAM_DEFAULT, - USER_ADDRESS_PARAM_EXTERNAL_EIP4361, } from '@nucypher/taco-auth'; import { ethers, providers, Wallet } from 'ethers'; import { expect, SpyInstance, vi } from 'vitest'; @@ -101,11 +99,10 @@ export const fakeAuthProviders = async ( ) => { const signerToUse = signer ? signer : fakeProvider().getSigner(); return { - ["EIP4361"]: fakeEIP4361AuthProvider(signerToUse), - ["SSO4361"]: - await fakeSingleSignOnEIP4361AuthProvider(signerToUse), - ["EIP1271"]: await fakeEIP1271AuthProvider(signerToUse), - ["Bogus"]: fakeBogusAuthProvider(signerToUse), + ['EIP4361']: fakeEIP4361AuthProvider(signerToUse), + ['SSO4361']: await fakeSingleSignOnEIP4361AuthProvider(signerToUse), + ['EIP1271']: await fakeEIP1271AuthProvider(signerToUse), + ['Bogus']: fakeBogusAuthProvider(signerToUse), }; }; @@ -113,22 +110,24 @@ class BogusAuthProvider implements AuthProvider { constructor(private provider: ethers.providers.Web3Provider) {} async getOrCreateAuthSignature(): Promise { - throw new Error("Bogus provider"); + throw new Error('Bogus provider'); } } -export const fakeBogusAuthProvider = (signer: ethers.providers.JsonRpcSigner) => { +export const fakeBogusAuthProvider = ( + signer: ethers.providers.JsonRpcSigner, +) => { const externalProvider: ethers.providers.ExternalProvider = { send: (request, callback) => { - callback(new Error("Bogus provider"), null); + callback(new Error('Bogus provider'), null); }, - request: () => Promise.reject(new Error("Bogus provider")) + request: () => Promise.reject(new Error('Bogus provider')), }; - return new BogusAuthProvider(new ethers.providers.Web3Provider(externalProvider)); + return new BogusAuthProvider( + new ethers.providers.Web3Provider(externalProvider), + ); }; - - const fakeEIP4361AuthProvider = (signer: ethers.providers.JsonRpcSigner) => { return new EIP4361AuthProvider(signer.provider, signer, TEST_SIWE_PARAMS); };