Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Including optional time for isValid in server #780

Merged
merged 11 commits into from
Nov 23, 2023
10 changes: 9 additions & 1 deletion packages/api/src/api/ProviderApi.ts
Original file line number Diff line number Diff line change
@@ -72,14 +72,22 @@ export default class ProviderApi extends HttpClientBase {
return this.post(ApiPaths.SubmitCaptchaSolution, captchaSolutionBody)
}

public verifyDappUser(userAccount: string, commitmentId?: string): Promise<VerificationResponse> {
public verifyDappUser(
userAccount: string,
commitmentId?: string,
maxVerifiedTime?: number
): Promise<VerificationResponse> {
const payload: {
user: string
commitmentId?: string
maxVerifiedTime?: number
} = { user: userAccount }
if (commitmentId) {
payload['commitmentId'] = commitmentId
}
if (maxVerifiedTime) {
payload['maxVerifiedTime'] = maxVerifiedTime
}
return this.post(ApiPaths.VerifyCaptchaSolution, payload as VerifySolutionBodyType)
}

6 changes: 5 additions & 1 deletion packages/procaptcha/src/modules/Manager.ts
Original file line number Diff line number Diff line change
@@ -235,7 +235,11 @@ export function Manager(
// if the provider was already in storage, the user may have already solved some captchas but they have not been put on chain yet
// so contact the provider to check if this is the case
try {
const verifyDappUserResponse = await providerApi.verifyDappUser(account.account.address)
const verifyDappUserResponse = await providerApi.verifyDappUser(
account.account.address,
undefined,
configOptional.challengeValidLength
)
if (verifyDappUserResponse.solutionApproved) {
updateState({ isHuman: true, loading: false })
events.onHuman({
39 changes: 20 additions & 19 deletions packages/provider/src/api/captcha.ts
Original file line number Diff line number Diff line change
@@ -25,7 +25,6 @@ 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 { UserCommitmentRecord } from '@prosopo/types-database'
import { parseBlockNumber } from '../util.js'
import { parseCaptchaAssets } from '@prosopo/datasets'
import { validateAddress } from '@polkadot/util-crypto/address'
@@ -118,6 +117,7 @@ export function prosopoRouter(env: ProviderEnvironment): Router {
*
* @param {string} userAccount - Dapp User id
* @param {string} commitmentId - The captcha solution to look up
* @param {number} maxVerifiedTime - The maximum time in milliseconds since the blockNumber
*/
router.post(ApiPaths.VerifyCaptchaSolution, async (req, res, next) => {
let parsed: VerifySolutionBodyType
@@ -128,28 +128,29 @@ export function prosopoRouter(env: ProviderEnvironment): Router {
return next(new ProsopoApiError(err as Error, undefined, 400))
}
try {
let solution: UserCommitmentRecord | undefined
let statusMessage = 'API.USER_NOT_VERIFIED'
if (!parsed.commitmentId) {
solution = await tasks.getDappUserCommitmentByAccount(parsed.user)
} else {
solution = await tasks.getDappUserCommitmentById(parsed.commitmentId)
const solution = await (parsed.commitmentId
? tasks.getDappUserCommitmentById(parsed.commitmentId)
: tasks.getDappUserCommitmentByAccount(parsed.user))

if (!solution) {
return res.json({ status: req.t('API.USER_NOT_VERIFIED'), solutionApproved: false })
}
if (solution) {
let approved = false
if (solution.status === CaptchaStatus.approved) {
statusMessage = 'API.USER_VERIFIED'
approved = true

if (parsed.maxVerifiedTime) {
const currentBlockNumber = await tasks.getCurrentBlockNumber()
const blockTimeMs = await tasks.getBlockTimeMs()
const timeSinceCompletion = (currentBlockNumber - solution.completedAt) * blockTimeMs

if (timeSinceCompletion > parsed.maxVerifiedTime) {
return res.json({ status: req.t('API.USER_NOT_VERIFIED'), solutionApproved: false })
}
return res.json({
status: req.t(statusMessage),
solutionApproved: approved,
commitmentId: solution.id,
})
}

const isApproved = solution.status === CaptchaStatus.approved
return res.json({
status: req.t(statusMessage),
solutionApproved: false,
status: req.t(isApproved ? 'API.USER_VERIFIED' : 'API.USER_NOT_VERIFIED'),
solutionApproved: isApproved,
commitmentId: solution.id,
})
} catch (err) {
// TODO fix error handling
15 changes: 15 additions & 0 deletions packages/provider/src/tasks/tasks.ts
Original file line number Diff line number Diff line change
@@ -541,4 +541,19 @@ export class Tasks {
async getProviderDataset(datasetId: string): Promise<DatasetWithIds> {
return await this.db.getDataset(datasetId)
}

/**
* Get the current block number
*/
async getCurrentBlockNumber(): Promise<number> {
HughParry marked this conversation as resolved.
Show resolved Hide resolved
return (await getBlockNumber(this.contract.api)).toNumber()
}

/**
* Get the current block time in milliseconds
*/
async getBlockTimeMs(): Promise<number> {
HughParry marked this conversation as resolved.
Show resolved Hide resolved
const blockTime = this.contract.api.consts.babe.expectedBlockTime
return blockTime.toNumber()
}
}
13 changes: 10 additions & 3 deletions packages/server/src/server.ts
Original file line number Diff line number Diff line change
@@ -111,9 +111,16 @@ export class ProsopoServer {
return this.contract
}

public async isVerified(payload: ProcaptchaOutput): Promise<boolean> {
/**
*
* @param payload Info output by procaptcha on completion of the captcha process
* @param maxVerifiedTime Maximum time in milliseconds since the blockNumber
* @returns
*/
public async isVerified(payload: ProcaptchaOutput, maxVerifiedTime?: number): Promise<boolean> {
const { user, dapp, providerUrl, commitmentId, blockNumber } = payload
// first check if the provider was actually chosen at blockNumber

// Check if the provider was actually chosen at blockNumber
const contractApi = await this.getContractApi()
const block = (await this.getApi().rpc.chain.getBlockHash(blockNumber)) as BlockHash
const getRandomProviderResponse = await this.getContract().queryAtBlock<RandomProvider>(
@@ -128,7 +135,7 @@ export class ProsopoServer {
console.log('providerUrlTrimmed', providerUrlTrimmed, 'commitmentId', commitmentId)
if (providerUrlTrimmed) {
const providerApi = await this.getProviderApi(providerUrl)
const result = await providerApi.verifyDappUser(user, commitmentId)
const result = await providerApi.verifyDappUser(user, commitmentId, maxVerifiedTime)
return result.solutionApproved
} else {
return (await contractApi.query.dappOperatorIsHumanUser(user, this.config.solutionThreshold)).value
4 changes: 3 additions & 1 deletion packages/types/src/provider/api.ts
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { CaptchaSolutionSchema } from '../datasets/index.js'
import { array, object, string, infer as zInfer } from 'zod'
import { array, number, object, string, infer as zInfer } from 'zod'

export enum ApiPaths {
GetCaptchaChallenge = '/v1/prosopo/provider/captcha',
@@ -33,6 +33,7 @@ export enum ApiParams {
commitmentId = 'commitmentId',
providerUrl = 'providerUrl',
procaptchaResponse = 'procaptcha-response',
maxVerifiedTime = 'maxVerifiedTime',
}

export interface DappUserSolutionResult {
@@ -68,6 +69,7 @@ export type CaptchaSolutionBodyType = zInfer<typeof CaptchaSolutionBody>
export const VerifySolutionBody = object({
[ApiParams.user]: string(),
[ApiParams.commitmentId]: string().optional(),
[ApiParams.maxVerifiedTime]: number().optional(),
HughParry marked this conversation as resolved.
Show resolved Hide resolved
})

export type VerifySolutionBodyType = zInfer<typeof VerifySolutionBody>
Loading