Skip to content

Commit

Permalink
Pass procaptcha token through to Provider endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
forgetso committed Jun 4, 2024
1 parent b3706fc commit 6cf74b3
Show file tree
Hide file tree
Showing 15 changed files with 2,423 additions and 2,113 deletions.
4 changes: 2 additions & 2 deletions dev/scripts/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// limitations under the License.
import { LogLevel, getLogger } from '@prosopo/common'
import { NetworkConfigSchema, NetworkNamesSchema, networks as getNetworks } from '@prosopo/types'
import { decodeOutput } from '@prosopo/procaptcha-common'
import { decodeProcaptchaOutput } from '@prosopo/types'
import { deployDapp, deployProtocol } from '../contract/deploy/index.js'
import { exec } from '../util/index.js'
import { run as fundDapps } from '../contract/fundDapps.js'
Expand Down Expand Up @@ -272,7 +272,7 @@ export async function processArgs(args: string[]) {
log.error('Token must be a hex string')
process.exit(1)
}
log.info(decodeOutput(argv.tokenHex))
log.info(decodeProcaptchaOutput(argv.tokenHex))
},
}).argv
}
Expand Down
4,182 changes: 2,252 additions & 1,930 deletions package-lock.json

Large diffs are not rendered by default.

31 changes: 7 additions & 24 deletions packages/api/src/api/ProviderApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
ImageVerificationResponse,
NetworkConfig,
PowCaptchaSolutionResponse,
ProcaptchaToken,
ProviderRegistered,
ServerPowCaptchaVerifyRequestBodyType,
StoredEvents,
Expand Down Expand Up @@ -76,22 +77,14 @@ export default class ProviderApi extends HttpClientBase implements ProviderApi {
}

public verifyDappUser(
dapp: AccountId,
userAccount: AccountId,
blockNumber: number,
token: ProcaptchaToken,
dappUserSignature: string,
commitmentId?: string,
maxVerifiedTime?: number
): Promise<ImageVerificationResponse> {
const payload: VerifySolutionBodyTypeInput = {
[ApiParams.dapp]: dapp.toString(),
[ApiParams.user]: userAccount.toString(),
[ApiParams.blockNumber]: blockNumber,
[ApiParams.token]: token,
[ApiParams.dappUserSignature]: dappUserSignature,
}
if (commitmentId) {
payload[ApiParams.commitmentId] = commitmentId
}
if (maxVerifiedTime) {
payload[ApiParams.maxVerifiedTime] = maxVerifiedTime
}
Expand All @@ -100,19 +93,13 @@ export default class ProviderApi extends HttpClientBase implements ProviderApi {
}

public verifyUser(
dapp: AccountId,
userAccount: AccountId,
blockNumber: number,
token: ProcaptchaToken,
dappUserSignature: string,
commitmentId?: string,
maxVerifiedTime?: number
): Promise<ImageVerificationResponse> {
const payload: VerifySolutionBodyTypeInput = {
[ApiParams.dapp]: dapp.toString(),
[ApiParams.user]: userAccount.toString(),
[ApiParams.blockNumber]: blockNumber,
[ApiParams.token]: token,
[ApiParams.dappUserSignature]: dappUserSignature,
...(commitmentId && { [ApiParams.commitmentId]: commitmentId }),
...(maxVerifiedTime && { [ApiParams.maxVerifiedTime]: maxVerifiedTime }),
}

Expand Down Expand Up @@ -163,17 +150,13 @@ export default class ProviderApi extends HttpClientBase implements ProviderApi {
}

public submitPowCaptchaVerify(
challenge: string,
dapp: string,
token: string,
signatureHex: string,
blockNumber: number,
recencyLimit: number
): Promise<VerificationResponse> {
const body: ServerPowCaptchaVerifyRequestBodyType = {
[ApiParams.challenge]: challenge,
[ApiParams.dapp]: dapp,
[ApiParams.token]: token,
[ApiParams.dappUserSignature]: signatureHex,
[ApiParams.blockNumber]: blockNumber,
[ApiParams.verifiedTimeout]: recencyLimit,
}
return this.post(ApiPaths.ServerPowCaptchaVerify, {
Expand Down
1 change: 0 additions & 1 deletion packages/procaptcha-common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,3 @@
// limitations under the License.
export * from './events.js'
export * from './state/builder.js'
export * from './token.js'
33 changes: 0 additions & 33 deletions packages/procaptcha-common/src/token.ts

This file was deleted.

5 changes: 3 additions & 2 deletions packages/procaptcha-pow/src/Services/Manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
ProcaptchaConfigSchema,
ProcaptchaState,
ProcaptchaStateUpdateFn,
encodeProcaptchaOutput,
} from '@prosopo/types'
import { ApiPromise } from '@polkadot/api/promise/Api'
import { ExtensionWeb2 } from '@prosopo/account'
Expand All @@ -30,7 +31,7 @@ import { ProviderApi } from '@prosopo/api'
import { RandomProvider } from '@prosopo/captcha-contract/types-returns'
import { WsProvider } from '@polkadot/rpc-provider/ws'
import { ContractAbi as abiJson } from '@prosopo/captcha-contract/contract-info'
import { buildUpdateState, encodeOutput, getDefaultEvents } from '@prosopo/procaptcha-common'
import { buildUpdateState, getDefaultEvents } from '@prosopo/procaptcha-common'
import { sleep } from '@prosopo/procaptcha'
import { solvePoW } from '@prosopo/util'

Expand Down Expand Up @@ -215,7 +216,7 @@ export const Manager = (
loading: false,
})
events.onHuman(
encodeOutput({
encodeProcaptchaOutput({
[ApiParams.providerUrl]: providerUrl,
[ApiParams.user]: getAccount().account.address,
[ApiParams.dapp]: getDappAccount(),
Expand Down
21 changes: 12 additions & 9 deletions packages/procaptcha/src/modules/Manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
ProcaptchaStateUpdateFn,
StoredEvents,
TCaptchaSubmitResult,
encodeProcaptchaOutput,
} from '@prosopo/types'
import { ApiPromise } from '@polkadot/api/promise/Api'
import { ExtensionWeb2, ExtensionWeb3 } from '@prosopo/account'
Expand All @@ -42,7 +43,7 @@ import { RandomProvider } from '@prosopo/captcha-contract/types-returns'
import { WsProvider } from '@polkadot/rpc-provider/ws'
import { ContractAbi as abiJson } from '@prosopo/captcha-contract/contract-info'
import { at, hashToHex } from '@prosopo/util'
import { buildUpdateState, encodeOutput, getDefaultEvents } from '@prosopo/procaptcha-common'
import { buildUpdateState, getDefaultEvents } from '@prosopo/procaptcha-common'
import { randomAsHex } from '@polkadot/util-crypto/random'
import { sleep } from '../utils/utils.js'
import ProsopoCaptchaApi from './ProsopoCaptchaApi.js'
Expand Down Expand Up @@ -169,9 +170,10 @@ export function Manager(
if (contractIsHuman) {
updateState({ isHuman: true, loading: false })
events.onHuman(
encodeOutput({
encodeProcaptchaOutput({
[ApiParams.user]: account.account.address,
[ApiParams.dapp]: getDappAccount(),
[ApiParams.blockNumber]: getBlockNumber(),
})
)
setValidChallengeTimeout()
Expand All @@ -198,13 +200,14 @@ export function Manager(
data: procaptchaStorage.blockNumber.toString(),
type: 'bytes',
})

const token = encodeProcaptchaOutput({
[ApiParams.user]: account.account.address,
[ApiParams.dapp]: getDappAccount(),
[ApiParams.blockNumber]: procaptchaStorage.blockNumber,
})
const verifyDappUserResponse = await providerApi.verifyUser(
getDappAccount(),
account.account.address,
procaptchaStorage.blockNumber,
token,
signature,
undefined,
configOptional.captchas.image.cachedTimeout
)
if (verifyDappUserResponse.verified) {
Expand All @@ -216,7 +219,7 @@ export function Manager(
[ApiParams.commitmentId]: hashToHex(verifyDappUserResponse.commitmentId),
[ApiParams.blockNumber]: verifyDappUserResponse.blockNumber,
}
events.onHuman(encodeOutput(output))
events.onHuman(encodeProcaptchaOutput(output))
setValidChallengeTimeout()
return
}
Expand Down Expand Up @@ -342,7 +345,7 @@ export function Manager(
// cache this provider for future use
storage.setProcaptchaStorage({ ...storage.getProcaptchaStorage(), providerUrl, blockNumber })
events.onHuman(
encodeOutput({
encodeProcaptchaOutput({
[ApiParams.providerUrl]: providerUrl,
[ApiParams.user]: account.account.address,
[ApiParams.dapp]: getDappAccount(),
Expand Down
23 changes: 17 additions & 6 deletions packages/provider/src/api/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { CaptchaStatus } from '@prosopo/captcha-contract/types-returns'
import { ProsopoApiError } from '@prosopo/common'
import { ProviderEnvironment } from '@prosopo/types-env'
import { Tasks } from '../tasks/tasks.js'
import { decodeProcaptchaOutput } from '@prosopo/types'
import { getBlockTimeMs, getCurrentBlockNumber } from '@prosopo/contract'
import { verifySignature } from './authMiddleware.js'
import express, { NextFunction, Request, Response, Router } from 'express'
Expand All @@ -47,17 +48,18 @@ export function prosopoVerifyRouter(env: ProviderEnvironment): Router {
async function verifySolution(res: Response, req: Request, next: NextFunction, isDapp: boolean) {
const parsed = VerifySolutionBody.parse(req.body)
try {
const { dappUserSignature, blockNumber, user, dapp } = parsed
const { dappUserSignature, token } = parsed
const { user, dapp, blockNumber, commitmentId } = decodeProcaptchaOutput(token)

// Verify using the appropriate pair based on isDapp flag
const keyPair = isDapp ? env.keyring.addFromAddress(dapp) : env.keyring.addFromAddress(user)

// Will throw an error if the signature is invalid
verifySignature(dappUserSignature, blockNumber.toString(), keyPair)

const solution = await (parsed.commitmentId
? tasks.getDappUserCommitmentById(parsed.commitmentId)
: tasks.getDappUserCommitmentByAccount(parsed.user))
const solution = await (commitmentId
? tasks.getDappUserCommitmentById(commitmentId)
: tasks.getDappUserCommitmentByAccount(user))

// No solution exists
if (!solution) {
Expand Down Expand Up @@ -151,8 +153,17 @@ export function prosopoVerifyRouter(env: ProviderEnvironment): Router {
*/
router.post(ApiPaths.ServerPowCaptchaVerify, async (req, res, next) => {
try {
const { challenge, dapp, dappUserSignature, blockNumber, verifiedTimeout } =
ServerPowCaptchaVerifyRequestBody.parse(req.body)
const { token, dappUserSignature, verifiedTimeout } = ServerPowCaptchaVerifyRequestBody.parse(req.body)

const { dapp, blockNumber, challenge } = decodeProcaptchaOutput(token)

if (!challenge) {
const unverifiedResponse: VerificationResponse = {
status: req.t('API.USER_NOT_VERIFIED'),
[ApiParams.verified]: false,
}
return res.json(unverifiedResponse)
}

// Verify using the dapp pair passed in the request
const dappPair = env.keyring.addFromAddress(dapp)
Expand Down
50 changes: 12 additions & 38 deletions packages/server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { ProviderApi } from '@prosopo/api'
import { RandomProvider } from '@prosopo/captcha-contract/types-returns'
import { WsProvider } from '@polkadot/rpc-provider/ws'
import { ContractAbi as abiJson } from '@prosopo/captcha-contract/contract-info'
import { decodeOutput } from '@prosopo/procaptcha-common'
import { decodeProcaptchaOutput } from '@prosopo/types'
import { get } from '@prosopo/util'
import { isHex, u8aToHex } from '@polkadot/util'

Expand Down Expand Up @@ -181,40 +181,30 @@ export class ProsopoServer {
/**
* Verify the user with the provider URL passed in. If a challenge is provided, we use the challenge to verify the
* user. If not, we use the user, dapp, and optionally the commitmentID, to verify the user.
* @param providerUrl
* @param dapp
* @param user
* @param token
* @param blockNumber
* @param timeouts
* @param providerUrl
* @param challenge
* @param commitmentId
*/
public async verifyProvider(
providerUrl: string,
dapp: string,
user: string,
token: string,
blockNumber: number,
timeouts: CaptchaTimeoutOutput,
challenge?: string,
commitmentId?: string
providerUrl: string,
challenge?: string
) {
this.logger.info('Verifying with provider.')
const blockNumberString = blockNumber.toString()
const dappUserSignature = this.pair?.sign(blockNumberString)
if (!dappUserSignature) {
throw new ProsopoContractError('CAPTCHA.INVALID_BLOCK_NO', { context: { error: 'Block number not found' } })
throw new ProsopoContractError('GENERAL.INVALID_SIGNATURE', { context: { blockNumber } })
}
const signatureHex = u8aToHex(dappUserSignature)

const providerApi = await this.getProviderApi(providerUrl)
if (challenge) {
const result = await providerApi.submitPowCaptchaVerify(
challenge,
dapp,
signatureHex,
blockNumber,
timeouts.pow.cachedTimeout
)
const result = await providerApi.submitPowCaptchaVerify(token, signatureHex, timeouts.pow.cachedTimeout)
// We don't care about recency with PoW challenges as they are single use, so just return the verified result
return result.verified
}
Expand All @@ -224,15 +214,7 @@ export class ProsopoServer {
// bail early if the block is too old. This saves us calling the Provider.
return false
}
const result = await providerApi.verifyDappUser(
dapp,
user,
blockNumber,
signatureHex,
commitmentId,

timeouts.image.cachedTimeout
)
const result = await providerApi.verifyDappUser(token, signatureHex, timeouts.image.cachedTimeout)

return result.verified
}
Expand All @@ -248,9 +230,9 @@ export class ProsopoServer {
return false
}

const payload = decodeOutput(token)
const payload = decodeProcaptchaOutput(token)

const { user, dapp, providerUrl, commitmentId, blockNumber, challenge } = ProcaptchaOutputSchema.parse(payload)
const { user, providerUrl, blockNumber, challenge } = ProcaptchaOutputSchema.parse(payload)

if (providerUrl && blockNumber) {
// By requiring block number, we load balance requests to the providers by requiring that the random
Expand All @@ -266,15 +248,7 @@ export class ProsopoServer {

// If we have a providerURL and a blockNumber, we verify with the provider

return await this.verifyProvider(
providerUrl,
dapp,
user,
blockNumber,
this.config.timeouts,
challenge,
commitmentId
)
return await this.verifyProvider(token, blockNumber, this.config.timeouts, providerUrl, challenge)
} else {
// If we don't have a providerURL, we verify with the contract
return await this.verifyContract(user, this.config.timeouts.contract.maxVerifiedTime)
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
// See the License for the specific language governing permissions and
// limitations under the License.
export * from './api.js'
export * from './params.js'
Loading

0 comments on commit 6cf74b3

Please sign in to comment.