Skip to content

Commit

Permalink
Procaptcha token (#1263)
Browse files Browse the repository at this point in the history
* Make procaptcha return a token

* Package lock

* Update packages and demos to accept new token format

* Use encode function. Remove unused dep

* lint:fix

* Remove trailing comma

* Return false if token is not hex

* Add util to decode procaptcha token

* package-lock.json

* Add nonce to output type

* Split the cypress tests into their own workflow

* don't grab the cypress cache for the provider tests

* lint:fix

* Redo changes

* Redo wiped changes

* package-lock.json

* make secret optional in config

* Undo cache changes2

* Fix passing of parameters to verify endpoint

* package-lock.json

* Fixes for API endpoints

* remove logs

* bundle NODE_ENV
  • Loading branch information
forgetso authored Jun 6, 2024
1 parent ebfb4a8 commit a388b6a
Show file tree
Hide file tree
Showing 33 changed files with 1,272 additions and 1,058 deletions.
137 changes: 137 additions & 0 deletions .github/workflows/cypress.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
name: cypress

on:
pull_request:
branches: [main]
paths:
- 'docker/**'
- 'packages/**'
- 'demos/**'
- 'dev/**'
- 'contracts/**'
- '.github/workflows/tests.yml'
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

env:
CARGO_TERM_COLOR: always
GH_TOKEN: ${{ github.token }}
NODE_ENV: test

jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Print contexts
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
ENV_CONTEXT: ${{ toJson(env) }}
VARS_CONTEXT: ${{ toJson(vars) }}
JOB_CONTEXT: ${{ toJson(job) }}
STEPS_CONTEXT: ${{ toJson(steps) }}
RUNNER_CONTEXT: ${{ toJson(runner) }}
SECRETS_CONTEXT: ${{ toJson(secrets) }}
STRATEGY_CONTEXT: ${{ toJson(strategy) }}
MATRIX_CONTEXT: ${{ toJson(matrix) }}
NEEDS_CONTEXT: ${{ toJson(needs) }}
INPUTS_CONTEXT: ${{ toJson(inputs) }}
run: |
echo "******************************"
echo "github:" "$GITHUB_CONTEXT"
echo "******************************"
echo "env:" "$ENV_CONTEXT"
echo "******************************"
echo "vars:" "$VARS_CONTEXT"
echo "******************************"
echo "job:" "$JOB_CONTEXT"
echo "******************************"
echo "steps:" "$STEPS_CONTEXT"
echo "******************************"
echo "runner:" "$RUNNER_CONTEXT"
echo "******************************"
echo "secrets:" "$SECRETS_CONTEXT"
echo "******************************"
echo "strategy:" "$STRATEGY_CONTEXT"
echo "******************************"
echo "matrix:" "$MATRIX_CONTEXT"
echo "******************************"
echo "needs:" "$NEEDS_CONTEXT"
echo "******************************"
echo "inputs:" "$INPUTS_CONTEXT"
echo "******************************"
- uses: actions/checkout@v3

- run: mkdir -p protocol/cargo-cache
- run: mkdir -p protocol/target
- run: mkdir -p node_modules
- run: mkdir -p ~/.cache/Cypress

- name: Restore cache
uses: actions/cache/restore@v3
with:
path: |
protocol/cargo-cache
protocol/target
node_modules
~/.cache/Cypress
# note that restoring a cache in github is a pain. The trailing '-' matches any string after the '-', therefore 'abc-' would match a cache named 'abc-1234' or 'abc-5678', etc.
# the problem is 'abc-' will not match a cache named 'abc'! So if you're using wildcard cache name selectors like this, you need a field that changes as the suffix to become the wildcard
# here we're setting the key to an unused cache key so it falls back to the wildcard selector in `restore-keys`
key: some-unused-cache-key
restore-keys: |
project-cache-${{ runner.os }}-${{ runner.arch }}-
- uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
- run: npm i -g npm@$(cat package.json | jq -r .engines.npm)

- run: npm ci

# build all packages in workspace
- run: npm run build:all

- name: Setup env
run: |
cp demos/client-example-server/env.development demos/client-example-server/.env.test
cp demos/client-example/env.development demos/client-example/.env.test
cp dev/scripts/env.test .env.test
cp dev/scripts/env.test dev/scripts/.env.test
cp dev/scripts/env.test packages/cli/.env.test
cp dev/scripts/env.test packages/procaptcha-bundle/.env.test
echo NODE_ENV: $NODE_ENV
- name: Start the docker images
run: |
docker compose --file ./docker/docker-compose.test.yml up -d
docker container ls
sleep 10s
# deploy dapp + protocol and run setup to register, stake and load a dataset for a provider
- run: NODE_ENV=test npm run deploy_protocol
- run: NODE_ENV=test npm run setup

# Build a test version of the procaptcha bundle and run the cypress tests on it and on the React client-example
# Running bundle:dev instead of bundle:prod means the bundle will be built with selectors that can be used
# by the cypress tests to find the elements they need to interact with
- run: NODE_ENV=test npm -w @prosopo/procaptcha-bundle run bundle:dev

# Needs concurrently to avoid vite hanging forever https://github.com/vitejs/vite/discussions/8745
- name: Install concurrently and cypress
run: npm i concurrently cypress

- name: Run the cypress tests on client-example
run: |
npx concurrently "npm run start:server" "npm run start:provider" "npm run start:demo" "sleep 10s && npm -w @prosopo/cypress-shared run cypress:run:client-example" --success "first" --kill-others
- name: Run the cypress tests on client-bundle-example
run: |
npx concurrently "npm run start:server" "npm run start:provider" "npm run start:bundle" "sleep 10s && npm -w @prosopo/cypress-shared run cypress:run:client-bundle-example" --success "first" --kill-others
- name: Run the cypress tests on client-bundle-example explicit rendering
run: |
npx concurrently "npm run start:server" "npm run start:provider" "npm run start:bundle" "sleep 10s && npm -w @prosopo/cypress-shared run cypress:run:client-bundle-example:explicit" --success "first" --kill-others
33 changes: 0 additions & 33 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,36 +115,3 @@ jobs:
# deploy protocol and run the unit tests
- run: npm run deploy_protocol
- run: npm run test

# restart the docker images to drop any changes made by the unit tests to the contract(s) before running cypress tests
- name: Reset the docker containers
run: |
docker compose --file ./docker/docker-compose.test.yml down
docker compose --file ./docker/docker-compose.test.yml up -d
docker container ls
sleep 10s
# deploy dapp + protocol and run setup to register, stake and load a dataset for a provider
- run: NODE_ENV=test npm run deploy_protocol
- run: NODE_ENV=test npm run setup

# Build a test version of the procaptcha bundle and run the cypress tests on it and on the React client-example
# Running bundle:dev instead of bundle:prod means the bundle will be built with selectors that can be used
# by the cypress tests to find the elements they need to interact with
- run: NODE_ENV=test npm -w @prosopo/procaptcha-bundle run bundle:dev

# Needs concurrently to avoid vite hanging forever https://github.com/vitejs/vite/discussions/8745
- name: Install concurrently and cypress
run: npm i concurrently cypress

- name: Run the cypress tests on client-example
run: |
npx concurrently "npm run start:server" "npm run start:provider" "npm run start:demo" "sleep 10s && npm -w @prosopo/cypress-shared run cypress:run:client-example" --success "first" --kill-others
- name: Run the cypress tests on client-bundle-example
run: |
npx concurrently "npm run start:server" "npm run start:provider" "npm run start:bundle" "sleep 10s && npm -w @prosopo/cypress-shared run cypress:run:client-bundle-example" --success "first" --kill-others
- name: Run the cypress tests on client-bundle-example explicit rendering
run: |
npx concurrently "npm run start:server" "npm run start:provider" "npm run start:bundle" "sleep 10s && npm -w @prosopo/cypress-shared run cypress:run:client-bundle-example:explicit" --success "first" --kill-others
7 changes: 2 additions & 5 deletions demos/client-example-server/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { ProsopoEnvError, getLoggerDefault } from '@prosopo/common'
import { ProsopoServer, getServerConfig } from '@prosopo/server'
import { getPairAsync } from '@prosopo/contract'
import { getServerConfig } from '@prosopo/server'
import connectionFactory from './utils/connection.js'
import cors from 'cors'
import dotenv from 'dotenv'
Expand Down Expand Up @@ -82,10 +81,8 @@ async function main() {
const config = getServerConfig()

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

app.use(routesFactory(mongoose, prosopoServer, verifyEndpoint, verifyType))
app.use(routesFactory(mongoose, config, verifyEndpoint, verifyType))

app.listen(process.env.PROSOPO_SERVER_PORT)
}
Expand Down
47 changes: 33 additions & 14 deletions demos/client-example-server/src/controllers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { ApiParams, ProcaptchaOutput, ProcaptchaOutputSchema } from '@prosopo/types'
import { ApiParams, ProcaptchaToken, ProsopoServerConfigOutput } from '@prosopo/types'
import { Connection } from 'mongoose'
import { NextFunction, Request, Response } from 'express'
import { ProcaptchaResponse } from '@prosopo/types'
import { ProsopoEnvError } from '@prosopo/common'
import { ProsopoServer } from '@prosopo/server'
import { UserInterface } from '../models/user.js'
import { at } from '@prosopo/util'
import { blake2b } from '@noble/hashes/blake2b'
import { getPairAsync } from '@prosopo/contract'
import { randomAsHex } from '@polkadot/util-crypto'
import { u8aToHex } from '@polkadot/util'
import { z } from 'zod'
Expand All @@ -39,28 +41,29 @@ const verify = async (
prosopoServer: ProsopoServer,
verifyType: string,
verifyEndpoint: string,
data: ProcaptchaOutput
token: ProcaptchaToken,
secret: string
) => {
if (verifyType === 'api') {
// verify using the API endpoint
console.log('verifying using the API endpoint')

const response = await fetch(verifyEndpoint, {
method: 'POST',
body: JSON.stringify(data),
body: JSON.stringify({ [ApiParams.procaptchaResponse]: token, [ApiParams.secret]: secret }),
})
return (await response.json()).verified
} else {
// verify using the TypeScript library
console.log('verifying using the TypeScript library')

return await prosopoServer.isVerified(data)
return await prosopoServer.isVerified(token)
}
}

const signup = async (
mongoose: Connection,
prosopoServer: ProsopoServer,
config: ProsopoServerConfigOutput,
verifyEndpoint: string,
verifyType: string,
req: Request,
Expand All @@ -74,18 +77,26 @@ const signup = async (
email: req.body.email,
})
const payload = SubscribeBodySpec.parse(req.body)
const pair = await getPairAsync(config.networks[config.defaultNetwork], config.account.secret)
const prosopoServer = new ProsopoServer(config, pair)
await prosopoServer.isReady()
if (dbUser) {
return res.status(409).json({ message: 'email already exists' })
}
console.log('payload', payload)
console.log('Request payload', payload)

// get the procaptcha-response token
const token = payload[ApiParams.procaptchaResponse]

// get the contents of the procaptcha-response JSON data
const data = ProcaptchaOutputSchema.parse(payload[ApiParams.procaptchaResponse])
console.log('Sending Procaptcha token', token)

console.log('sending data', data)
if (!config.account.secret) {
throw new ProsopoEnvError('GENERAL.MNEMONIC_UNDEFINED', {
context: { missingParams: ['PROSOPO_SITE_PRIVATE_KEY'] },
})
}

const verified = await verify(prosopoServer, verifyType, verifyEndpoint, data)
const verified = await verify(prosopoServer, verifyType, verifyEndpoint, token, config.account.secret)

if (verified) {
// salt
Expand Down Expand Up @@ -118,13 +129,15 @@ const signup = async (

const login = async (
mongoose: Connection,
prosopoServer: ProsopoServer,
config: ProsopoServerConfigOutput,
verifyEndpoint: string,
verifyType: string,
req: Request,
res: Response
) => {
const User = mongoose.model<UserInterface>('User')
const pair = await getPairAsync(config.networks[config.defaultNetwork], config.account.secret)
const prosopoServer = new ProsopoServer(config, pair)
await prosopoServer.isReady()
// checks if email exists
await User.findOne({
Expand All @@ -134,11 +147,17 @@ const login = async (
if (!dbUser) {
res.status(404).json({ message: 'user not found' })
} else {
const payload = SubscribeBodySpec.parse(req.body)
const payload = SubscribeBodySpec.parse(req.body[ApiParams.procaptchaResponse])

const data = ProcaptchaOutputSchema.parse(payload[ApiParams.procaptchaResponse])
const token = payload[ApiParams.procaptchaResponse]

if (!config.account.secret) {
throw new ProsopoEnvError('GENERAL.MNEMONIC_UNDEFINED', {
context: { missingParams: ['PROSOPO_SITE_PRIVATE_KEY'] },
})
}

const verified = await verify(prosopoServer, verifyType, verifyEndpoint, data)
const verified = await verify(prosopoServer, verifyType, verifyEndpoint, token, config.account.secret)

if (verified) {
// password hash
Expand Down
8 changes: 4 additions & 4 deletions demos/client-example-server/src/routes/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Connection } from 'mongoose'
import { ProsopoServer } from '@prosopo/server'
import { ProsopoServerConfigOutput } from '@prosopo/types'
import { isAuth, login, signup } from '../controllers/auth.js'
import express from 'express'

const router = express.Router()

function getRoutes(
mongoose: Connection,
prosopoServer: ProsopoServer,
config: ProsopoServerConfigOutput,
verifyEndpoint: string,
verifyType: string
): express.Router {
router.post('/login', login.bind(null, mongoose, prosopoServer, verifyEndpoint, verifyType))
router.post('/login', login.bind(null, mongoose, config, verifyEndpoint, verifyType))

router.post('/signup', signup.bind(null, mongoose, prosopoServer, verifyEndpoint, verifyType))
router.post('/signup', signup.bind(null, mongoose, config, verifyEndpoint, verifyType))

router.get('/private', isAuth)

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
Loading

0 comments on commit a388b6a

Please sign in to comment.