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

1193 verify endpoint end #1222

Merged
merged 24 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.yarn/*

Check warning on line 1 in .gitignore

View workflow job for this annotation

GitHub Actions / check

File ignored by default.
!.yarn/patches
!.yarn/plugins
!.yarn/releases
Expand Down Expand Up @@ -144,3 +144,6 @@
**/client-bundle-example/src/assets/**

.csv

# ignore captcha
captcha.json
4 changes: 2 additions & 2 deletions demos/client-example-server/env.development
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ PROSOPO_SUBSTRATE_ENDPOINT=ws://localhost:9944
PROSOPO_CONTRACT_ADDRESS=
PROSOPO_WEB2=true
PROSOPO_SERVER_URL=https://localhost
PROSOPO_SITE_PRIVATE_KEY=//Bob
PROSOPO_SITE_PRIVATE_KEY=//Eve
PROSOPO_SERVER_PORT=9228
PROSOPO_DEFAULT_ENVIRONMENT=development
PROSOPO_DEFAULT_NETWORK=development
PROSOPO_MONGO_EVENTS_URI=mongodb+srv://<MONGO_URI_HERE>/frictionless_events
_DEV_ONLY_WATCH_EVENTS=false
_DEV_ONLY_WATCH_EVENTS=false
2 changes: 1 addition & 1 deletion demos/client-example-server/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ async function main() {
const config = getServerConfig()

console.log('config', config)
const pair = await getPairAsync(config.networks[config.defaultNetwork], process.env.PROSOPO_SITE_PRIVATE_KEY)
const pair = await getPairAsync(config.networks[config.defaultNetwork], config.account.secret)
const prosopoServer = new ProsopoServer(config, pair)

app.use(routesFactory(mongoose, prosopoServer, verifyEndpoint, verifyType))
Expand Down
2 changes: 1 addition & 1 deletion demos/client-example/env.development
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ PROSOPO_PORT=9230
PROSOPO_DEFAULT_NETWORK=development
PROSOPO_DEFAULT_ENVIRONMENT=development
PROSOPO_MONGO_EVENTS_URI=mongodb+srv://<MONGO_URI_HERE>/frictionless_events
_DEV_ONLY_WATCH_EVENTS=false
_DEV_ONLY_WATCH_EVENTS=false
forgetso marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 3 additions & 3 deletions demos/provider-mock/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// limitations under the License.
import { ApiPaths, VerifySolutionBody } from '@prosopo/types'
import { ProsopoApiError } from '@prosopo/common'
import { VerifySolutionBodyType } from '@prosopo/types'
import { VerifySolutionBodyTypeOutput } from '@prosopo/types'
import express, { Router } from 'express'

/**
Expand All @@ -30,8 +30,8 @@ export function prosopoRouter(): Router {
* @param {string} userAccount - Dapp User id
* @param {string} commitmentId - The captcha solution to look up
*/
router.post(ApiPaths.VerifyCaptchaSolution, async (req, res, next) => {
let parsed: VerifySolutionBodyType
router.post(ApiPaths.VerifyCaptchaSolutionDapp, async (req, res, next) => {
let parsed: VerifySolutionBodyTypeOutput
try {
parsed = VerifySolutionBody.parse(req.body)
} catch (err) {
Expand Down
51 changes: 39 additions & 12 deletions packages/api/src/api/ProviderApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
StoredEvents,
SubmitPowCaptchaSolutionBody,
VerificationResponse,
VerifySolutionBodyType,
VerifySolutionBodyTypeInput,
} from '@prosopo/types'
import { Provider, RandomProvider } from '@prosopo/captcha-contract/types-returns'
import HttpClientBase from './HttpClientBase.js'
Expand Down Expand Up @@ -79,23 +79,44 @@ export default class ProviderApi extends HttpClientBase implements ProviderApi {
dapp: AccountId,
userAccount: AccountId,
blockNumber: number,
dappUserSignature: string,
commitmentId?: string,
maxVerifiedTime?: number
): Promise<ImageVerificationResponse> {
const payload: {
[ApiParams.dapp]: AccountId
[ApiParams.user]: AccountId
[ApiParams.blockNumber]: number
[ApiParams.commitmentId]?: string
[ApiParams.maxVerifiedTime]?: number
} = { dapp: dapp, user: userAccount, blockNumber }
const payload: VerifySolutionBodyTypeInput = {
[ApiParams.dapp]: dapp.toString(),
[ApiParams.user]: userAccount.toString(),
[ApiParams.blockNumber]: blockNumber,
[ApiParams.dappUserSignature]: dappUserSignature,
}
if (commitmentId) {
payload['commitmentId'] = commitmentId
payload[ApiParams.commitmentId] = commitmentId
}
if (maxVerifiedTime) {
payload['maxVerifiedTime'] = maxVerifiedTime
payload[ApiParams.maxVerifiedTime] = maxVerifiedTime
}
return this.post(ApiPaths.VerifyCaptchaSolution, payload as VerifySolutionBodyType)

return this.post(ApiPaths.VerifyCaptchaSolutionDapp, payload)
}

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

return this.post(ApiPaths.VerifyCaptchaSolutionUser, payload)
}

public getPowCaptchaChallenge(user: AccountId, dapp: AccountId): Promise<GetPowCaptchaResponse> {
Expand Down Expand Up @@ -144,13 +165,19 @@ export default class ProviderApi extends HttpClientBase implements ProviderApi {
public submitPowCaptchaVerify(
challenge: string,
dapp: string,
signatureHex: string,
blockNumber: number,
recencyLimit: number
): Promise<VerificationResponse> {
const body: ServerPowCaptchaVerifyRequestBodyType = {
[ApiParams.challenge]: challenge,
[ApiParams.dapp]: dapp,
[ApiParams.dappUserSignature]: signatureHex,
[ApiParams.blockNumber]: blockNumber,
[ApiParams.verifiedTimeout]: recencyLimit,
}
return this.post(ApiPaths.ServerPowCaptchaVerify, body)
return this.post(ApiPaths.ServerPowCaptchaVerify, {
body,
})
}
}
2 changes: 1 addition & 1 deletion packages/cli/src/commands/providerAccounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@
command: 'provider_accounts',
describe: 'List all provider accounts',
builder: (yargs: Argv) => yargs,
handler: async (argv: ArgumentsCamelCase) => {

Check warning on line 29 in packages/cli/src/commands/providerAccounts.ts

View workflow job for this annotation

GitHub Actions / check

'argv' is defined but never used
try {
const env = new ProviderEnvironment(config, pair)
await env.isReady()
const tasks = new Tasks(env)
const result = await (tasks.contract.contract as any)['providerAccounts']()
const result = (await tasks.contract.query.getAllProviderAccounts()).value.unwrap().unwrap()
logger.info(JSON.stringify(result, null, 2))
} catch (err) {
logger.error(err)
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { getDB, getSecret } from './process.env.js'
import { getPairAsync } from '@prosopo/contract'
import { i18nMiddleware } from '@prosopo/common'
import { loadEnv } from './env.js'
import { prosopoAdminRouter, prosopoRouter } from '@prosopo/provider'
import { prosopoAdminRouter, prosopoRouter, prosopoVerifyRouter } from '@prosopo/provider'
import cors from 'cors'
import express from 'express'
import getConfig from './prosopo.config.js'
Expand All @@ -31,6 +31,7 @@ function startApi(env: ProviderEnvironment, admin = false): Server {
apiApp.use(express.json({ limit: '50mb' }))
apiApp.use(i18nMiddleware({}))
apiApp.use(prosopoRouter(env))
apiApp.use(prosopoVerifyRouter(env))

if (admin) {
apiApp.use(prosopoAdminRouter(env))
Expand All @@ -53,6 +54,7 @@ export async function start(env?: ProviderEnvironment, admin?: boolean) {
solved: { count: 2 },
unsolved: { count: 0 },
})

const pair = await getPairAsync(config.networks[config.defaultNetwork], secret, '')
env = new ProviderEnvironment(config, pair)
}
Expand Down
15 changes: 14 additions & 1 deletion packages/procaptcha/src/modules/Manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,23 @@ 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(
const extension = getExtension(account)
if (!extension || !extension.signer || !extension.signer.signRaw) {
throw new ProsopoEnvError('ACCOUNT.NO_POLKADOT_EXTENSION')
forgetso marked this conversation as resolved.
Show resolved Hide resolved
}

const signRaw = extension.signer.signRaw
const { signature } = await signRaw({
address: account.account.address,
data: procaptchaStorage.blockNumber.toString(),
type: 'bytes',
})

const verifyDappUserResponse = await providerApi.verifyUser(
getDappAccount(),
account.account.address,
procaptchaStorage.blockNumber,
signature,
undefined,
configOptional.captchas.image.cachedTimeout
)
Expand Down
2 changes: 1 addition & 1 deletion packages/provider/src/api/authMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ const verifyBlockNumber = async (blockNumber: string, tasks: Tasks) => {
}
}

const verifySignature = (signature: string, blockNumber: string, pair: KeyringPair) => {
export const verifySignature = (signature: string, blockNumber: string, pair: KeyringPair) => {
const u8Sig = hexToU8a(signature)

if (!pair.verify(blockNumber, u8Sig, pair.publicKey)) {
Expand Down
98 changes: 0 additions & 98 deletions packages/provider/src/api/captcha.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,12 @@
CaptchaWithProof,
DappUserSolutionResult,
GetPowCaptchaChallengeRequestBody,
ImageVerificationResponse,
PowCaptchaSolutionResponse,
ServerPowCaptchaVerifyRequestBody,
SubmitPowCaptchaSolutionBody,
VerificationResponse,
VerifySolutionBody,
VerifySolutionBodyType,
} from '@prosopo/types'
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 { getBlockTimeMs, getCurrentBlockNumber } from '@prosopo/contract'
import { handleErrors } from './errorHandler.js'
import { parseBlockNumber } from '../util.js'
import { parseCaptchaAssets } from '@prosopo/datasets'
Expand Down Expand Up @@ -63,7 +56,7 @@
`${ApiPaths.GetCaptchaChallenge}/:${ApiParams.datasetId}/:${ApiParams.user}/:${ApiParams.dapp}/:${ApiParams.blockNumber}`,
async (req, res, next) => {
try {
const { blockNumber, datasetId, user, dapp } = CaptchaRequestBody.parse(req.params)

Check warning on line 59 in packages/provider/src/api/captcha.ts

View workflow job for this annotation

GitHub Actions / check

'dapp' is assigned a value but never used
const api = env.api
if (api === undefined) {
throw new ProsopoApiError('DEVELOPER.METHOD_NOT_IMPLEMENTED', {
Expand All @@ -71,7 +64,7 @@
})
}
validateAddress(user, false, api.registry.chainSS58)
const blockNumberParsed = parseBlockNumber(blockNumber)

Check warning on line 67 in packages/provider/src/api/captcha.ts

View workflow job for this annotation

GitHub Actions / check

'blockNumberParsed' is assigned a value but never used

// await tasks.validateProviderWasRandomlyChosen(user, dapp, datasetId, blockNumberParsed)

Expand Down Expand Up @@ -130,97 +123,6 @@
}
})

/**
* Verifies a user's solution as being approved or not
*
* @param {string} user - 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
try {
parsed = VerifySolutionBody.parse(req.body)
} catch (err) {
return next(new ProsopoApiError('CAPTCHA.PARSE_ERROR', { context: { code: 400, error: err } }))
}
try {
const solution = await (parsed.commitmentId
? tasks.getDappUserCommitmentById(parsed.commitmentId)
: tasks.getDappUserCommitmentByAccount(parsed.user))

// No solution exists
if (!solution) {
tasks.logger.debug('Not verified - no solution found')
const noSolutionResponse: VerificationResponse = {
[ApiParams.status]: req.t('API.USER_NOT_VERIFIED_NO_SOLUTION'),
[ApiParams.verified]: false,
}
return res.json(noSolutionResponse)
}

// A solution exists but is disapproved
if (solution.status === CaptchaStatus.disapproved) {
const disapprovedResponse: VerificationResponse = {
[ApiParams.status]: req.t('API.USER_NOT_VERIFIED'),
[ApiParams.verified]: false,
}
return res.json(disapprovedResponse)
}

// Check if solution was completed recently
if (parsed.maxVerifiedTime) {
const currentBlockNumber = await getCurrentBlockNumber(tasks.contract.api)
const blockTimeMs = getBlockTimeMs(tasks.contract.api)
const timeSinceCompletion = (currentBlockNumber - solution.completedAt) * blockTimeMs
// A solution exists but has timed out
if (timeSinceCompletion > parsed.maxVerifiedTime) {
const expiredResponse: VerificationResponse = {
[ApiParams.status]: req.t('API.USER_NOT_VERIFIED_TIME_EXPIRED'),
[ApiParams.verified]: false,
}
tasks.logger.debug('Not verified - time run out')
return res.json(expiredResponse)
}
}

const isApproved = solution.status === CaptchaStatus.approved
const response: ImageVerificationResponse = {
[ApiParams.status]: req.t(isApproved ? 'API.USER_VERIFIED' : 'API.USER_NOT_VERIFIED'),
[ApiParams.verified]: isApproved,
[ApiParams.commitmentId]: solution.id.toString(),
[ApiParams.blockNumber]: solution.requestedAt,
}
return res.json(response)
} catch (err) {
return next(new ProsopoApiError('API.BAD_REQUEST', { context: { code: 400, error: err } }))
}
})

/**
* Verifies a user's solution as being approved or not
*
* @param {string} dappAccount - Dapp User id
* @param {string} challenge - The captcha solution to look up
*/
router.post(ApiPaths.ServerPowCaptchaVerify, async (req, res, next) => {
try {
const { challenge, dapp, verifiedTimeout } = ServerPowCaptchaVerifyRequestBody.parse(req.body)

const approved = await tasks.serverVerifyPowCaptchaSolution(dapp, challenge, verifiedTimeout)

const verificationResponse: VerificationResponse = {
status: req.t(approved ? 'API.USER_VERIFIED' : 'API.USER_NOT_VERIFIED'),
[ApiParams.verified]: approved,
}

return res.json(verificationResponse)
} catch (err) {
tasks.logger.error(err)
return next(new ProsopoApiError('API.BAD_REQUEST', { context: { code: 400, error: err } }))
}
})

/**
* Supplies a PoW challenge to a Dapp User
*
Expand Down
Loading
Loading