From a166716db44db8e765e67c154093c9d3c3f24c75 Mon Sep 17 00:00:00 2001 From: Haris Chaniotakis Date: Mon, 16 May 2022 23:35:40 +0300 Subject: [PATCH] feat(types,clerk-js): Enhance Web3 wallet resource with relevant operations Enhance Web3Wallet resource with new operations. Those are create() which creates a new web3 wallet prepareVerification() to prepare the relevant web3 wallet verification, attemptVerification() to verify the web3 wallet and finally destroy() to delete it. --- .../clerk-js/src/core/resources/SignUp.ts | 20 +++- .../clerk-js/src/core/resources/User.test.ts | 44 +++++++-- packages/clerk-js/src/core/resources/User.ts | 11 +++ .../src/core/resources/Web3Wallet.test.ts | 94 +++++++++++++++++++ .../clerk-js/src/core/resources/Web3Wallet.ts | 61 +++++++++++- packages/types/src/signUp.ts | 6 +- packages/types/src/user.ts | 2 + packages/types/src/web3Wallet.ts | 15 +++ 8 files changed, 233 insertions(+), 20 deletions(-) create mode 100644 packages/clerk-js/src/core/resources/Web3Wallet.test.ts diff --git a/packages/clerk-js/src/core/resources/SignUp.ts b/packages/clerk-js/src/core/resources/SignUp.ts index f27a320148..d0742d389f 100644 --- a/packages/clerk-js/src/core/resources/SignUp.ts +++ b/packages/clerk-js/src/core/resources/SignUp.ts @@ -138,7 +138,12 @@ export class SignUp extends BaseResource implements SignUpResource { }; attemptWeb3WalletVerification = async (params: AttemptWeb3WalletVerificationParams): Promise => { - const { generateSignature } = params || {}; + const { signature, generateSignature } = params || {}; + + if (signature) { + return this.attemptVerification({ signature, strategy: 'web3_metamask_signature' }); + } + if (!(typeof generateSignature === 'function')) { clerkMissingOptionError('generateSignature'); } @@ -148,8 +153,8 @@ export class SignUp extends BaseResource implements SignUpResource { clerkVerifyWeb3WalletCalledBeforeCreate('SignUp'); } - const signature = await generateSignature({ identifier: this.web3wallet!, nonce }); - return this.attemptVerification({ signature, strategy: 'web3_metamask_signature' }); + const generatedSignature = await generateSignature({ identifier: this.web3wallet!, nonce }); + return this.attemptVerification({ signature: generatedSignature, strategy: 'web3_metamask_signature' }); }; public authenticateWithWeb3 = async (params: AuthenticateWithWeb3Params): Promise => { @@ -157,7 +162,14 @@ export class SignUp extends BaseResource implements SignUpResource { const web3Wallet = identifier || this.web3wallet!; await this.create({ web3Wallet }); await this.prepareWeb3WalletVerification(); - return this.attemptWeb3WalletVerification({ generateSignature }); + + const { nonce } = this.verifications.web3Wallet; + if (!nonce) { + clerkVerifyWeb3WalletCalledBeforeCreate('SignUp'); + } + + const signature = await generateSignature({ identifier, nonce }); + return this.attemptWeb3WalletVerification({ signature }); }; public authenticateWithMetamask = async (): Promise => { diff --git a/packages/clerk-js/src/core/resources/User.test.ts b/packages/clerk-js/src/core/resources/User.test.ts index dfa20ce6fe..34643b9fc7 100644 --- a/packages/clerk-js/src/core/resources/User.test.ts +++ b/packages/clerk-js/src/core/resources/User.test.ts @@ -1,10 +1,9 @@ -import { ExternalAccountJSON, UserJSON, VerificationJSON } from '@clerk/types'; +import { ExternalAccountJSON, UserJSON, VerificationJSON, Web3WalletJSON } from '@clerk/types'; import { BaseResource, ExternalAccount } from 'core/resources/internal'; import { User } from './User'; describe('User', () => { - it('creates an external account', async () => { const externalAccountJSON = { object: 'external_account', @@ -15,9 +14,7 @@ describe('User', () => { }; // @ts-ignore - BaseResource._fetch = jest.fn().mockReturnValue( - Promise.resolve({ response: externalAccountJSON }), - ); + BaseResource._fetch = jest.fn().mockReturnValue(Promise.resolve({ response: externalAccountJSON })); const user = new User({ email_addresses: [], @@ -32,12 +29,39 @@ describe('User', () => { expect(BaseResource._fetch).toHaveBeenCalledWith({ method: 'POST', path: '/me/external_accounts', - body: - { - redirect_url: 'https://www.example.com', - strategy: 'oauth_dropbox', - } + body: { + redirect_url: 'https://www.example.com', + strategy: 'oauth_dropbox', + }, }); }); + it('creates a web3 wallet', async () => { + const targetWeb3Wallet = '0x0000000000000000000000000000000000000000'; + const web3WalletJSON = { + object: 'web3_wallet', + web3_wallet: targetWeb3Wallet, + }; + + // @ts-ignore + BaseResource._fetch = jest.fn().mockReturnValue(Promise.resolve({ response: web3WalletJSON })); + + const user = new User({ + email_addresses: [], + phone_numbers: [], + web3_wallets: [], + external_accounts: [], + } as unknown as UserJSON); + + await user.createWeb3Wallet({ web3Wallet: targetWeb3Wallet }); + + // @ts-ignore + expect(BaseResource._fetch).toHaveBeenCalledWith({ + method: 'POST', + path: '/me/web3_wallets/', + body: { + web3_wallet: targetWeb3Wallet, + }, + }); + }); }); diff --git a/packages/clerk-js/src/core/resources/User.ts b/packages/clerk-js/src/core/resources/User.ts index 3e677d09a8..bd2be9172b 100644 --- a/packages/clerk-js/src/core/resources/User.ts +++ b/packages/clerk-js/src/core/resources/User.ts @@ -1,6 +1,7 @@ import type { CreateEmailAddressParams, CreatePhoneNumberParams, + CreateWeb3WalletParams, EmailAddressResource, ExternalAccountJSON, ExternalAccountResource, @@ -117,6 +118,16 @@ export class User extends BaseResource implements UserResource { ).create(); }; + createWeb3Wallet = (params: CreateWeb3WalletParams): Promise => { + const { web3Wallet } = params || {}; + return new Web3Wallet( + { + web3_wallet: web3Wallet, + }, + this.path() + '/web3_wallets/', + ).create(); + }; + createExternalAccount = async ({ strategy, redirect_url, diff --git a/packages/clerk-js/src/core/resources/Web3Wallet.test.ts b/packages/clerk-js/src/core/resources/Web3Wallet.test.ts new file mode 100644 index 0000000000..cb4fc08d0d --- /dev/null +++ b/packages/clerk-js/src/core/resources/Web3Wallet.test.ts @@ -0,0 +1,94 @@ +import { Web3WalletJSON } from '@clerk/types'; +import { BaseResource, Web3Wallet } from 'core/resources/internal'; + +describe('Web3 wallet', () => { + it('create', async () => { + const web3WalletJSON = { + object: 'web3_wallet', + web3_wallet: '0x0000000000000000000000000000000000000000', + } as Web3WalletJSON; + + // @ts-ignore + BaseResource._fetch = jest.fn().mockReturnValue(Promise.resolve({ response: web3WalletJSON })); + + const web3Wallet = new Web3Wallet(web3WalletJSON, '/me/web3_wallets'); + await web3Wallet.create(); + + // @ts-ignore + expect(BaseResource._fetch).toHaveBeenCalledWith({ + method: 'POST', + path: '/me/web3_wallets', + body: { + web3_wallet: web3WalletJSON.web3_wallet, + }, + }); + }); + + it('prepareVerification', async () => { + const web3WalletJSON = { + id: 'test-id', + object: 'web3_wallet', + web3_wallet: '0x0000000000000000000000000000000000000000', + } as Web3WalletJSON; + + // @ts-ignore + BaseResource._fetch = jest.fn().mockReturnValue(Promise.resolve({ response: web3WalletJSON })); + + const web3Wallet = new Web3Wallet(web3WalletJSON, '/me/web3_wallets'); + await web3Wallet.prepareVerification({ strategy: 'web3_metamask_signature' }); + + // @ts-ignore + expect(BaseResource._fetch).toHaveBeenCalledWith({ + method: 'POST', + path: `/me/web3_wallets/${web3WalletJSON.id}/prepare_verification`, + body: { + strategy: 'web3_metamask_signature', + }, + }); + }); + + it('attemptVerification', async () => { + const web3WalletJSON = { + id: 'test-id', + object: 'web3_wallet', + web3_wallet: '0x0000000000000000000000000000000000000000', + } as Web3WalletJSON; + + // @ts-ignore + BaseResource._fetch = jest.fn().mockReturnValue(Promise.resolve({ response: web3WalletJSON })); + + const web3Wallet = new Web3Wallet(web3WalletJSON, '/me/web3_wallets'); + await web3Wallet.attemptVerification({ signature: 'mock-signature' }); + + // @ts-ignore + expect(BaseResource._fetch).toHaveBeenCalledWith({ + method: 'POST', + path: `/me/web3_wallets/${web3WalletJSON.id}/attempt_verification`, + body: { + signature: 'mock-signature', + }, + }); + }); + + it('destroy', async () => { + const targetId = 'test_id'; + + const deletedObjectJSON = { + object: 'web3_wallet', + id: targetId, + deleted: true, + }; + + // @ts-ignore + BaseResource._fetch = jest.fn().mockReturnValue(Promise.resolve({ response: deletedObjectJSON })); + + const web3Wallet = new Web3Wallet({ id: targetId }, '/me/web3_wallets'); + await web3Wallet.destroy(); + + // @ts-ignore + expect(BaseResource._fetch).toHaveBeenCalledWith({ + method: 'DELETE', + path: `/me/web3_wallets/${targetId}`, + }); + }); +}); diff --git a/packages/clerk-js/src/core/resources/Web3Wallet.ts b/packages/clerk-js/src/core/resources/Web3Wallet.ts index 1afa94a1d5..8da3ec096a 100644 --- a/packages/clerk-js/src/core/resources/Web3Wallet.ts +++ b/packages/clerk-js/src/core/resources/Web3Wallet.ts @@ -1,4 +1,11 @@ -import type { VerificationResource, Web3WalletJSON, Web3WalletResource } from '@clerk/types'; +import type { + AttemptWeb3WalletVerificationParams, + PrepareWeb3WalletVerificationParams, + VerificationResource, + Web3WalletJSON, + Web3WalletResource, +} from '@clerk/types'; +import { clerkMissingOptionError, clerkVerifyWeb3WalletCalledBeforeCreate } from 'core/errors'; import { BaseResource, Verification } from './internal'; @@ -7,12 +14,64 @@ export class Web3Wallet extends BaseResource implements Web3WalletResource { web3Wallet = ''; verification!: VerificationResource; + public constructor(data: Partial, pathRoot: string); public constructor(data: Web3WalletJSON, pathRoot: string) { super(); this.pathRoot = pathRoot; this.fromJSON(data); } + create(): Promise { + return this._basePost({ + body: { web3_wallet: this.web3Wallet }, + }); + } + + prepareVerification = (params: PrepareWeb3WalletVerificationParams): Promise => { + return this._basePost({ + action: 'prepare_verification', + body: { ...params }, + }); + }; + + attemptVerification = (params: AttemptWeb3WalletVerificationParams): Promise => { + const { signature, generateSignature } = params || {}; + + if (signature) { + return this._basePost({ + action: 'attempt_verification', + body: { signature }, + }); + } + + if (!(typeof generateSignature === 'function')) { + clerkMissingOptionError('generateSignature'); + } + + const generateSignatureForNonce = async (): Promise => { + if (!(typeof generateSignature === 'function')) { + clerkMissingOptionError('generateSignature'); + } + + const { nonce } = this.verification; + if (!nonce) { + clerkVerifyWeb3WalletCalledBeforeCreate('SignUp'); + } + + const generatedSignature = await generateSignature({ identifier: this.web3Wallet, nonce }); + return this._basePost({ + action: 'attempt_verification', + body: { signature: generatedSignature }, + }); + }; + + return generateSignatureForNonce(); + }; + + destroy(): Promise { + return this._baseDelete(); + } + toString(): string { return this.web3Wallet; } diff --git a/packages/types/src/signUp.ts b/packages/types/src/signUp.ts index 1061b4dba8..8ce95aba9d 100644 --- a/packages/types/src/signUp.ts +++ b/packages/types/src/signUp.ts @@ -26,7 +26,7 @@ import { } from './strategies'; import type { SnakeToCamel } from './utils'; import { CreateMagicLinkFlowReturn, StartMagicLinkFlowParams, VerificationResource } from './verification'; -import { AuthenticateWithWeb3Params, GenerateSignature } from './web3Wallet'; +import { AttemptWeb3WalletVerificationParams, AuthenticateWithWeb3Params } from './web3Wallet'; export interface SignUpResource extends ClerkResource { status: SignUpStatus | null; @@ -110,10 +110,6 @@ export type AttemptVerificationParams = signature: string; }; -export type AttemptWeb3WalletVerificationParams = { - generateSignature: GenerateSignature; -}; - export type SignUpAttributeField = | FirstNameAttribute | LastNameAttribute diff --git a/packages/types/src/user.ts b/packages/types/src/user.ts index fc02cfadf9..7a01bef663 100644 --- a/packages/types/src/user.ts +++ b/packages/types/src/user.ts @@ -59,6 +59,7 @@ export interface UserResource extends ClerkResource { update: (params: UpdateUserParams) => Promise; createEmailAddress: (params: CreateEmailAddressParams) => Promise; createPhoneNumber: (params: CreatePhoneNumberParams) => Promise; + createWeb3Wallet: (params: CreateWeb3WalletParams) => Promise; twoFactorEnabled: () => boolean; isPrimaryIdentification: (ident: EmailAddressResource | PhoneNumberResource) => boolean; getSessions: () => Promise; @@ -76,6 +77,7 @@ export interface UserResource extends ClerkResource { export type CreateEmailAddressParams = { email: string }; export type CreatePhoneNumberParams = { phoneNumber: string }; +export type CreateWeb3WalletParams = { web3Wallet: string }; export type SetProfileImageParams = { file: Blob | File }; type UpdateUserJSON = Pick< diff --git a/packages/types/src/web3Wallet.ts b/packages/types/src/web3Wallet.ts index 769efcc105..88bc506af0 100644 --- a/packages/types/src/web3Wallet.ts +++ b/packages/types/src/web3Wallet.ts @@ -1,10 +1,25 @@ import { ClerkResource } from './resource'; +import { Web3Strategy } from './strategies'; import { VerificationResource } from './verification'; +export type PrepareWeb3WalletVerificationParams = { + strategy: Web3Strategy; +}; + +export type AttemptWeb3WalletVerificationParams = { + signature?: string; + /** @deprecated Use signature field instead */ + generateSignature?: GenerateSignature; +}; + export interface Web3WalletResource extends ClerkResource { id: string; web3Wallet: string; verification: VerificationResource; + toString: () => string; + prepareVerification: (params: PrepareWeb3WalletVerificationParams) => Promise; + attemptVerification: (params: AttemptWeb3WalletVerificationParams) => Promise; + destroy: () => Promise; } export type GenerateSignature = (opts: GenerateSignatureParams) => Promise;