Skip to content

Commit

Permalink
refactor, use nonce
Browse files Browse the repository at this point in the history
  • Loading branch information
JP Angelle committed Oct 23, 2023
1 parent b7ae20f commit 49b28c7
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 75 deletions.
2 changes: 2 additions & 0 deletions centrifuge-app/src/components/OnboardingAuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ Issued At: ${new Date().toISOString()}`
safeAddress: address,
messageHash,
evmChainId,
network: 'evmOnSafe',
nonce,
})
} else {
throw new Error('Invalid signature')
Expand Down
76 changes: 4 additions & 72 deletions onboarding-api/src/controllers/auth/authenticateWallet.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { InfuraProvider } from '@ethersproject/providers'
import { isAddress } from '@polkadot/util-crypto'
import { BigNumber, ethers } from 'ethers'
import { Request, Response } from 'express'
import * as jwt from 'jsonwebtoken'
import fetch from 'node-fetch'
import { InferType, number, object, string, StringSchema } from 'yup'
import { SupportedNetworks } from '../../database'
import { reportHttpError } from '../../utils/httpError'
Expand Down Expand Up @@ -48,24 +45,18 @@ const verifySafeInput = object({
},
}),
evmChainId: number().required(),
network: string().oneOf(['evmOnSafe']).required() as StringSchema<'evmOnSafe'>,
nonce: string().required(),
})

export const authenticateWalletController = async (
req: Request<{}, {}, InferType<typeof verifyWalletInput | typeof verifySafeInput>>,
res: Response
) => {
try {
let payload

if ('safeAddress' in req.body) {
await validateInput(req.body, verifySafeInput)
await validateInput(req.body, req.body.network === 'evmOnSafe' ? verifySafeInput : verifyWalletInput)

const provider = new InfuraProvider(req.body.evmChainId, process.env.INFURA_KEY)
payload = await verifySafeWallet(provider, req.body.safeAddress, req.body.messageHash, req.body.evmChainId)
} else {
await validateInput(req.body, verifyWalletInput)
payload = await new NetworkSwitch(req.body.network).verifyWallet(req, res)
}
const payload = await new NetworkSwitch(req.body.network).verifyWallet(req, res)

const token = jwt.sign(payload, process.env.JWT_SECRET, {
expiresIn: '8h',
Expand All @@ -77,62 +68,3 @@ export const authenticateWalletController = async (
return res.status(error.code).send({ error: error.message, e })
}
}

const verifySafeWallet = async (provider: any, safeAddress: string, messageHash: string, evmChainId: number) => {
try {
const MAGIC_VALUE_BYTES = '0x20c13b0b'

const safeContract = new ethers.Contract(
safeAddress,
[
'function isValidSignature(bytes calldata _data, bytes calldata _signature) public view returns (bytes4)',
'function getMessageHash(bytes memory message) public view returns (bytes32)',
'function getThreshold() public view returns (uint256)',
],
provider
)

const safeMessageHash = await safeContract.getMessageHash(messageHash)

const safeMessage = await fetchSafeMessage(safeMessageHash, evmChainId)

if (!safeMessage) {
throw new Error('Unable to fetch SafeMessage')
}

const threshold = BigNumber.from(await safeContract.getThreshold()).toNumber()

if (!threshold || threshold > safeMessage.confirmations.length) {
throw new Error('Threshold has not been met')
}

const response = await safeContract.isValidSignature(messageHash, safeMessage?.preparedSignature)

if (response === MAGIC_VALUE_BYTES) {
return {
address: safeAddress,
chainId: evmChainId,
network: 'evm',
}
}

throw new Error('Invalid signature')
} catch {
throw new Error('Something went wrong')
}
}

const TX_SERVICE_URLS: Record<string, string> = {
'1': 'https://safe-transaction-mainnet.safe.global/api',
'5': 'https://safe-transaction-goerli.staging.5afe.dev/api',
}

const fetchSafeMessage = async (safeMessageHash: string, chainId: number) => {
const TX_SERVICE_URL = TX_SERVICE_URLS[chainId.toString()]

const response = await fetch(`${TX_SERVICE_URL}/v1/messages/${safeMessageHash}/`, {
headers: { 'Content-Type': 'application/json' },
})

return response.json()
}
71 changes: 71 additions & 0 deletions onboarding-api/src/utils/networks/evm.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { isAddress } from '@ethersproject/address'
import { Contract } from '@ethersproject/contracts'
import { InfuraProvider, JsonRpcProvider, Provider } from '@ethersproject/providers'
import { BigNumber, ethers } from 'ethers'
import { Request, Response } from 'express'
import fetch from 'node-fetch'
import { SiweMessage } from 'siwe'
import { InferType } from 'yup'
import { signAndSendDocumentsInput } from '../../controllers/emails/signAndSendDocuments'
Expand Down Expand Up @@ -66,3 +68,72 @@ export async function verifyEvmWallet(req: Request, res: Response): Promise<Requ
chainId,
}
}

export const verifySafeWallet = async (req: Request, res: Response) => {
const { safeAddress, messageHash, evmChainId, nonce } = req.body
const MAGIC_VALUE_BYTES = '0x20c13b0b'

if (!isAddress(safeAddress)) {
throw new HttpError(400, 'Invalid address')
}

const cookieNonce = req.signedCookies[`onboarding-auth-${safeAddress.toLowerCase()}`]

if (!cookieNonce || cookieNonce !== nonce) {
throw new HttpError(400, 'Invalid nonce')
}

const provider = new InfuraProvider(req.body.evmChainId, process.env.INFURA_KEY)
const safeContract = new ethers.Contract(
safeAddress,
[
'function isValidSignature(bytes calldata _data, bytes calldata _signature) public view returns (bytes4)',
'function getMessageHash(bytes memory message) public view returns (bytes32)',
'function getThreshold() public view returns (uint256)',
],
provider
)

const safeMessageHash = await safeContract.getMessageHash(messageHash)

const safeMessage = await fetchSafeMessage(safeMessageHash, evmChainId)

if (!safeMessage) {
throw new HttpError(400, 'Unable to fetch SafeMessage')
}

const threshold = BigNumber.from(await safeContract.getThreshold()).toNumber()

if (!threshold || threshold > safeMessage.confirmations.length) {
throw new HttpError(400, 'Threshold has not been met')
}

const response = await safeContract.isValidSignature(messageHash, safeMessage?.preparedSignature)

if (response === MAGIC_VALUE_BYTES) {
res.clearCookie(`onboarding-auth-${safeAddress.toLowerCase()}`)

return {
address: safeAddress,
chainId: evmChainId,
network: 'evm',
}
}

throw new HttpError(400, 'Invalid signature')
}

const TX_SERVICE_URLS: Record<string, string> = {
'1': 'https://safe-transaction-mainnet.safe.global/api',
'5': 'https://safe-transaction-goerli.staging.5afe.dev/api',
}

const fetchSafeMessage = async (safeMessageHash: string, chainId: number) => {
const TX_SERVICE_URL = TX_SERVICE_URLS[chainId.toString()]

const response = await fetch(`${TX_SERVICE_URL}/v1/messages/${safeMessageHash}/`, {
headers: { 'Content-Type': 'application/json' },
})

return response.json()
}
8 changes: 5 additions & 3 deletions onboarding-api/src/utils/networks/networkSwitch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,20 @@ import {
validateSubstrateRemark,
verifySubstrateWallet,
} from './centrifuge'
import { validateEvmRemark, verifyEvmWallet } from './evm'
import { validateEvmRemark, verifyEvmWallet, verifySafeWallet } from './evm'
import { addTinlakeInvestorToMemberList, getTinlakePoolById } from './tinlake'

export class NetworkSwitch {
network: SupportedNetworks
constructor(network: SupportedNetworks = 'substrate') {
network: SupportedNetworks | 'evmOnSafe'
constructor(network: SupportedNetworks | 'evmOnSafe' = 'substrate') {
this.network = network
}

verifyWallet = (req: Request, res: Response) => {
if (this.network === 'substrate') {
return verifySubstrateWallet(req, res)
} else if (this.network === 'evmOnSafe') {
return verifySafeWallet(req, res)
} else if (this.network === 'evm' || this.network === 'evmOnSubstrate') {
return verifyEvmWallet(req, res)
}
Expand Down

0 comments on commit 49b28c7

Please sign in to comment.