Skip to content

Commit

Permalink
Merge pull request #11 from iProov/examples-revamp
Browse files Browse the repository at this point in the history
SDK examples revamp
  • Loading branch information
willmorgan authored Jan 2, 2021
2 parents 7c7cbd3 + 0e0db22 commit 5fa3d59
Show file tree
Hide file tree
Showing 47 changed files with 7,695 additions and 476 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
.idea/
node_modules
.env
**/vendor/prod/
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 1.1.2021 3.1.2

## Fixed

- The console object is no longer overridden. Sorry about that.
- Documentation updates and inaccuracies:
- Various iProovSupport documentation relating to payloads and availability on th.e global object
- Documentation relating to the payload of the progress event has been corrected.
- iProovSupport can now be loaded standalone without a bundler; regeneratorRuntime is now defined.
- Language strings can now be customised on slotted elements. Previously this didn't work.
- Fixed double checking for videoInput. Just once is enough.
- iProovSupport prompting in Chrome prompts instantly, previously it delayed.
- Fixed SDK crashing if passing a token not associated with a `user_id`. Unlikely, but displays a clean error instead.

## 24.12.2020 3.1.1

## Fixed
Expand Down
185 changes: 99 additions & 86 deletions README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions demo/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
4 changes: 4 additions & 0 deletions demo/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
EXAMPLE_SERVER_PORT=8080
BASE_URL=https://eu.rp.secure.iproov.me
API_KEY=
API_SECRET=
9 changes: 9 additions & 0 deletions demo/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"bracketSpacing": true,
"printWidth": 120,
"quoteProps": "consistent",
"semi": false,
"singleQuote": false,
"trailingComma": "es5",
"tabWidth": 2
}
14 changes: 14 additions & 0 deletions demo/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM node:lts-alpine

WORKDIR /app
ADD src/yarn.lock .
ADD src/package.json .
RUN yarn
ADD src/static/yarn.lock ./static/
ADD src/static/package.json ./static/
RUN cd /app/static && yarn && cd /app
ADD src/ .

EXPOSE 80

ENTRYPOINT ["node", "server.js"]
43 changes: 43 additions & 0 deletions demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# iProov Web SDK Example

This demo is a self-contained NodeJS server and minimal frontend which covers:

* Running a server with a `BASE_URL`, `API_KEY` and `API_SECRET` that you can obtain from https://portal.iproov.com;
* Creating tokens for Genuine Presence and Liveness transactions;
* Enrolling and verifying;
* Customising iProov Web with web component slots and CustomEvents.

### Running the example:

For a basic example, all you need to run it is Docker and some minimal configuration:

1. Obtain your environment variables and create a `.env` file - see `.env.example` for a template.
2. From this `demo` directory, bring up the container with: `bash run.sh`
3. Navigate to http://localhost:8080 in any desktop or mobile web browser.

For a deeper dive, you can of course run the server locally using [NodeJS 14+](https://nodejs.org/en/download/):

```
export $(cat .env | xargs) && node src/server.js
```

### Features

* **Frontend customisation**:
* You can customise `createSDK` inside `static/js/vendor/iproov-integration.js`
* [Available events](https://github.com/iProov/web#-events)
* [Slot customisation](https://github.com/iProov/web#-slots)
* [Language customisation](https://github.com/iProov/web#-localization)
* **Backend customisation**:
* You can customise `PlatformAPI` inside `src/platform.js`
* The server side is written in JavaScript and uses Hapi.js
* [iProov Platform API v2 docs](https://eu.rp.secure.iproov.me/docs.html)
* **Validate endpoint**: you can extend the example to implement the validate call
* Note that in production, it's important that this is implemented on the server side otherwise your API credentials will be exposed!

### Support and SSL Certificates
To access the device camera and other browser APIs, a "secure context" is required.

In practice this means running the server under TLS, or simply using localhost.

For this demo, we keep things simple with localhost. But to test on another device on your network like a mobile, TLS is required.
17 changes: 17 additions & 0 deletions demo/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

ENV_FILE=$(pwd)/.env

source "$ENV_FILE"

EXAMPLE_SERVER_PORT=${EXAMPLE_SERVER_PORT:=8080}

if [ ! -r "$ENV_FILE" ]
then
echo "Missing environment file: $ENV_FILE"
echo "Use the template in .env.example and populate with keys from https://portal.iproov.com."
exit 1
fi

docker build . -t iproov-web-example
docker run --env-file "$ENV_FILE" -e EXAMPLE_SERVER_PORT="$EXAMPLE_SERVER_PORT" -e IS_DOCKER=1 -p "0.0.0.0:$EXAMPLE_SERVER_PORT":80 --rm iproov-web-example
44 changes: 44 additions & 0 deletions demo/src/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Load and validate configuration
* @todo Use Joi or some better configuration checker
* @param env an object that contains the configuration, like process.env
* @return {{API_KEY: *, API_SECRET: *, BASE_URL: *, EXAMPLE_SERVER_PORT: *}}
*/
export function configure(env) {
const { BASE_URL, API_KEY, API_SECRET } = env
let { EXAMPLE_SERVER_PORT } = env
const errors = []
if (!BASE_URL) {
errors.push("BASE_URL is undefined. Example: https://eu.rp.secure.iproov.me.")
}
if (!API_KEY) {
errors.push("API_KEY is undefined. You can obtain one from https://portal.iproov.com.")
} else if (API_KEY.length < 40) {
errors.push("API_KEY seems incorrect, it should be a 40 character alphanumeric string.")
}
if (!API_SECRET) {
errors.push(
"API_SECRET is undefined. It must correspond to your API_KEY. If lost, reset it at https://portal.iproov.com."
)
} else if (API_SECRET.length < 40) {
errors.push("API_SECRET seems incorrect, it should be a 40 character alphanumeric string.")
}
if (!EXAMPLE_SERVER_PORT) {
console.warn("EXAMPLE_SERVER_PORT not specified, using port 80.")
EXAMPLE_SERVER_PORT = 80
}
if (errors.length) {
console.error("Configuration problems detected:")
errors.forEach((e) => console.error(e))
console.error("Contact [email protected] for assistance.")
process.exit(1)
return
}
const config = {
BASE_URL,
API_KEY,
API_SECRET,
EXAMPLE_SERVER_PORT,
}
return config
}
16 changes: 16 additions & 0 deletions demo/src/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "@iproov/web-example",
"version": "1.0.0",
"description": "An example of iProov's Web SDK integration",
"main": "server.js",
"author": "iProov Team",
"license": "GPL",
"private": true,
"type": "module",
"dependencies": {
"@hapi/hapi": "^20.0.3",
"@hapi/inert": "^6.0.3",
"prettier": "^2.2.1",
"superagent": "^6.1.0"
}
}
111 changes: 111 additions & 0 deletions demo/src/platform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import superagent from "superagent"

/**
* Lightweight Platform v2 wrapper
* @see https://eu.rp.secure.iproov.me/docs.html
*/
export class PlatformAPI {
constructor(logger, baseUrl, apiKey, apiSecret) {
this.logger = logger
this.baseUrl = baseUrl
this.apiKey = apiKey
this.apiSecret = apiSecret
}

apiUrl(endpoint) {
return `${this.baseUrl}/api/v2/${endpoint}`
}

withJsonAuth(payload) {
return {
api_key: this.apiKey,
secret: this.apiSecret,
...payload,
}
}

withHeaders() {
return {
"accept": "json",
"user-agent": "superagent",
}
}

/**
* @todo Add response envelope for consumption by Hapi
* @param request
* @return {Promise<*>}
*/
async unpack(request) {
try {
const response = await request
return { ...response.body, base_url: this.baseUrl }
} catch (e) {
// @todo: indicate this is an error - currently we just pass through
if (e.status === 400) {
const { error, error_description } = e.response.body
this.logger.error("Error %s: %s", error, error_description)
}
return e.response.body
}
}

async token(mode, options = {}) {
const { userId, assuranceType } = options
if (!userId) {
this.logger.warn(`Couldn't create a token because user_id is empty`)
return {
error: "Missing User ID",
error_description: "You must provide a user_id to create a token.",
}
}
this.logger.info(`Creating ${assuranceType} ${mode} token for ${userId}`)
return this.unpack(
superagent
.post(this.apiUrl(`claim/${mode}/token`))
.set(this.withHeaders())
.send(
this.withJsonAuth({
user_id: userId,
assurance_type: assuranceType,
resource: "Web SDK Example",
})
)
)
}

async validate(mode, options = { userId, token }) {
const { userId, token } = options
this.logger.info(`Validating ${mode} for ${userId} - token: ${token}`)
return this.unpack(
superagent
.post(this.apiUrl(`claim/${mode}/validate`))
.set(this.withHeaders())
.send(
this.withJsonAuth({
token,
ip: "127.0.0.1", // @todo: change when IPv6 is fully supported
client: "superagent",
})
)
)
}
}

/**
* Map Hapi request payloads to PlatformAPI method arguments
*/
export const requestToAPIMapper = Object.freeze({
token(request) {
return {
userId: request.payload.user_id,
assuranceType: request.payload.assurance_type || "genuine_presence",
}
},
validate(request) {
return {
token: request.payload.token,
userId: request.payload.user_id,
}
},
})
71 changes: 71 additions & 0 deletions demo/src/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import Hapi from "@hapi/hapi"
import Inert from "@hapi/inert"
import { fileURLToPath } from "url"
import { dirname, resolve } from "path"
import { PlatformAPI, requestToAPIMapper } from "./platform.js"

import { configure } from "./config.js"

const __dirname = fileURLToPath(dirname(import.meta.url))

async function init() {
const { BASE_URL, API_KEY, API_SECRET, EXAMPLE_SERVER_PORT } = configure(process.env)

const ALL_INTERFACES = "0.0.0.0" // listen on all interfaces so Docker can bind
const server = Hapi.server({
port: 80,
host: ALL_INTERFACES,
})

await server.register(Inert)

let platform

server.route({
method: "GET",
path: "/{param*}",
handler: {
directory: {
path: resolve(__dirname, "static"),
index: "index.html",
}
}
})

server.route({
method: "POST",
path: "/api/claim/{claimMode}/{claimAction}",
options: { cors: { origin: ["*"] } },
handler: async (request, h) => {
const { claimMode, claimAction } = request.params
try {
return await platform[claimAction](claimMode, requestToAPIMapper[claimAction](request))
} catch (e) {
console.error("Unhandled: %s", e.message)
throw e
}
},
})

platform = new PlatformAPI(console, BASE_URL, API_KEY, API_SECRET)

console.debug("Starting internal server: %s", server.info.uri)

await server.start()

console.log("iProov tokens will be created on %s with API_KEY %s", BASE_URL, API_KEY)
console.log("Web SDK example available at %s", "http://localhost:" + EXAMPLE_SERVER_PORT)

}

process.on("unhandledRejection", (err) => {
console.log(err)
process.exit(1)
})

process.on("SIGINT", () => {
console.log("Caught SIGINT - shutting down.")
process.exit(0)
})

init()
10 changes: 10 additions & 0 deletions demo/src/static/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# editorconfig.org

root = true

[*]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
Loading

0 comments on commit 5fa3d59

Please sign in to comment.