diff --git a/demo/.dockerignore b/demo/.dockerignore index e7b180c..e6905a2 100644 --- a/demo/.dockerignore +++ b/demo/.dockerignore @@ -1,2 +1 @@ -node_modules/ -src/static/yarn.lock +.env* \ No newline at end of file diff --git a/demo/.env.example b/demo/.env.example index b294550..f6d79c7 100644 --- a/demo/.env.example +++ b/demo/.env.example @@ -1,4 +1,7 @@ EXAMPLE_SERVER_PORT=8080 +PORT=8080 BASE_URL=https://eu.rp.secure.iproov.me API_KEY= API_SECRET= + +NPM_ACCESS_TOKEN= \ No newline at end of file diff --git a/demo/.gitignore b/demo/.gitignore new file mode 100644 index 0000000..496ee2c --- /dev/null +++ b/demo/.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/demo/.prettierrc b/demo/.prettierrc deleted file mode 100644 index 0d126be..0000000 --- a/demo/.prettierrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "bracketSpacing": true, - "printWidth": 120, - "quoteProps": "consistent", - "semi": false, - "singleQuote": false, - "trailingComma": "es5", - "tabWidth": 2 -} diff --git a/demo/Dockerfile b/demo/Dockerfile index 7cdf37e..b59cee5 100644 --- a/demo/Dockerfile +++ b/demo/Dockerfile @@ -1,13 +1,42 @@ -FROM node:lts-alpine +# BASE +FROM node:18-alpine as base + +ARG NPM_ACCESS_TOKEN="${NPM_ACCESS_TOKEN}" +ENV NPM_ACCESS_TOKEN $NPM_ACCESS_TOKEN + + +# INSTALLER +FROM base as installer + +WORKDIR /app + +COPY ./src ./src +COPY ./package.json ./package.json +COPY ./yarn.lock ./yarn.lock + +# 1. install server deps +RUN yarn install --ignore-scripts --frozen-lockfile + +# XXX:NOTE: There are no npm build for the FE app +# 2. install separately only web-sdk in "static" as static module +RUN npm config set '//registry.npmjs.org/:_authToken' "${NPM_ACCESS_TOKEN}" +RUN cd src/static && yarn install --ignore-scripts --frozen-lockfile + + +# BUILDER +FROM base as builder + +WORKDIR /app + +# There are no build for the server - just copy what is required +COPY --from=installer /app . + + +# RUNNER +FROM base as runner WORKDIR /app -ADD src/yarn.lock . -ADD src/package.json . -RUN yarn -ADD src/static/package.json ./static/ -RUN cd /app/static && yarn && cd /app -ADD src/ . -EXPOSE 80 +COPY --from=builder /app . -ENTRYPOINT ["node", "server.js"] +CMD yarn start \ No newline at end of file diff --git a/demo/README.md b/demo/README.md index dc911e6..2c51ec0 100644 --- a/demo/README.md +++ b/demo/README.md @@ -2,7 +2,7 @@ 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; +* Running a server with a `BASE_URL`, `API_KEY`, `API_SECRET` that you can obtain from https://portal.iproov.com and `NPM_ACCESS_TOKEN` that you can create once your get access to our private NPM registry (please contact support@iproov.com). * Creating tokens for Genuine Presence and Liveness transactions; * Enrolling and verifying; * Customising iProov Web with web component slots and CustomEvents. diff --git a/demo/package.json b/demo/package.json new file mode 100644 index 0000000..f1b4b3c --- /dev/null +++ b/demo/package.json @@ -0,0 +1,21 @@ +{ + "name": "@iproov/web-demo-sample", + "version": "2.0.0", + "description": "A demo of iProov's Web SDK integration", + "main": "server.js", + "author": "iProov Team", + "license": "GPL", + "private": true, + "type": "module", + "scripts": { + "start": "node src/server.js" + }, + "dependencies": { + "@hapi/basic": "^6.0.0", + "@hapi/hapi": "^20.2.2", + "@hapi/inert": "^6.0.4", + "dotenv": "^8.6.0", + "prettier": "^2.7.1", + "superagent": "^6.1.0" + } +} diff --git a/demo/run.sh b/demo/run.sh index d93c7ba..8be81d7 100644 --- a/demo/run.sh +++ b/demo/run.sh @@ -6,6 +6,8 @@ source "$ENV_FILE" EXAMPLE_SERVER_PORT=${EXAMPLE_SERVER_PORT:=8080} +yarn install + if [ ! -r "$ENV_FILE" ] then echo "Missing environment file: $ENV_FILE" @@ -13,5 +15,5 @@ if [ ! -r "$ENV_FILE" ] 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 +docker build -t iproov-web-demo --build-arg NPM_ACCESS_TOKEN=$NPM_ACCESS_TOKEN . +docker run --env-file "$ENV_FILE" -e EXAMPLE_SERVER_PORT="$EXAMPLE_SERVER_PORT" -e PORT="$PORT" -p "$EXAMPLE_SERVER_PORT":"$PORT" --rm iproov-web-demo \ No newline at end of file diff --git a/demo/src/config.js b/demo/src/config.js index 01e724b..3fb570a 100644 --- a/demo/src/config.js +++ b/demo/src/config.js @@ -5,38 +5,46 @@ * @return {{API_KEY: *, API_SECRET: *, BASE_URL: *, EXAMPLE_SERVER_PORT: *, PORT: *}} */ export function configure(env) { - const { BASE_URL, API_KEY, API_SECRET } = env - let { PORT, EXAMPLE_SERVER_PORT } = env - const errors = [] + const { BASE_URL, API_KEY, API_SECRET } = env; + let { PORT, EXAMPLE_SERVER_PORT } = env; + const errors = []; if (!BASE_URL) { - errors.push("BASE_URL is undefined. Example: https://eu.rp.secure.iproov.me.") + 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.") + 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.") + 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.") + errors.push( + "API_SECRET seems incorrect, it should be a 40 character alphanumeric string." + ); } + if (!PORT) { // This is the internal container port that the EXAMPLE_SERVER_PORT should map to. Best left at 80. - PORT = 80 + PORT = 80; } if (!EXAMPLE_SERVER_PORT) { - console.warn("EXAMPLE_SERVER_PORT not specified, using port 80.") - EXAMPLE_SERVER_PORT = 80 + 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 support@iproov.com for assistance.") - process.exit(1) - return + console.error("Configuration problems detected:"); + errors.forEach((e) => console.error(e)); + console.error("Contact support@iproov.com for assistance."); + process.exit(1); } return { BASE_URL, @@ -44,5 +52,5 @@ export function configure(env) { API_SECRET, PORT, EXAMPLE_SERVER_PORT, - } + }; } diff --git a/demo/src/etc/.gitignore b/demo/src/etc/.gitignore new file mode 100644 index 0000000..275ece2 --- /dev/null +++ b/demo/src/etc/.gitignore @@ -0,0 +1,2 @@ +*.key +*.crt diff --git a/demo/src/package.json b/demo/src/package.json deleted file mode 100644 index 4a4f555..0000000 --- a/demo/src/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "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/basic": "^6.0.0", - "@hapi/hapi": "^20.2.1", - "@hapi/inert": "^6.0.4", - "dotenv": "^8.2.0", - "prettier": "^2.2.1", - "superagent": "^6.1.0" - } -} diff --git a/demo/src/platform.js b/demo/src/platform.js index 3274237..177ef0b 100644 --- a/demo/src/platform.js +++ b/demo/src/platform.js @@ -1,4 +1,4 @@ -import superagent from "superagent" +import superagent from "superagent"; /** * Lightweight Platform v2 wrapper @@ -6,14 +6,14 @@ import superagent from "superagent" */ export class PlatformAPI { constructor(logger, baseUrl, apiKey, apiSecret) { - this.logger = logger - this.baseUrl = baseUrl - this.apiKey = apiKey - this.apiSecret = apiSecret + this.logger = logger; + this.baseUrl = baseUrl; + this.apiKey = apiKey; + this.apiSecret = apiSecret; } apiUrl(endpoint) { - return `${this.baseUrl}/api/v2/${endpoint}` + return `${this.baseUrl}/api/v2/${endpoint}`; } withJsonAuth(payload) { @@ -21,14 +21,14 @@ export class PlatformAPI { api_key: this.apiKey, secret: this.apiSecret, ...payload, - } + }; } withHeaders() { return { - "accept": "json", + accept: "json", "user-agent": "superagent", - } + }; } /** @@ -38,28 +38,28 @@ export class PlatformAPI { */ async unpack(request) { try { - const response = await request - return { ...response.body, base_url: this.baseUrl } + 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) + const { error, error_description } = e.response.body; + this.logger.error("Error %s: %s", error, error_description); } - return e.response.body + return e.response.body; } } async token(mode, options = {}) { - const { userId, assuranceType } = options + const { userId, assuranceType } = options; if (!userId) { - this.logger.warn(`Couldn't create a token because user_id is empty`) + 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}`) + this.logger.info(`Creating ${assuranceType} ${mode} token for ${userId}`); return this.unpack( superagent .post(this.apiUrl(`claim/${mode}/token`)) @@ -68,15 +68,15 @@ export class PlatformAPI { this.withJsonAuth({ user_id: userId, assurance_type: assuranceType, - resource: "Web SDK Example", + resource: "Web SDK Demo", }) ) - ) + ); } async validate(mode, options = { userId, token }) { - const { userId, token } = options - this.logger.info(`Validating ${mode} for ${userId} - token: ${token}`) + const { userId, token } = options; + this.logger.info(`Validating ${mode} for ${userId} - token: ${token}`); return this.unpack( superagent .post(this.apiUrl(`claim/${mode}/validate`)) @@ -89,7 +89,7 @@ export class PlatformAPI { client: "superagent", }) ) - ) + ); } } @@ -101,12 +101,12 @@ export const requestToAPIMapper = Object.freeze({ 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, - } + }; }, -}) +}); diff --git a/demo/src/server.js b/demo/src/server.js index e420866..4a17f6c 100644 --- a/demo/src/server.js +++ b/demo/src/server.js @@ -1,25 +1,31 @@ -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 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" +import { configure } from "./config.js"; -const __dirname = fileURLToPath(dirname(import.meta.url)) +const __dirname = fileURLToPath(dirname(import.meta.url)); async function init() { - const { BASE_URL, API_KEY, API_SECRET, PORT, EXAMPLE_SERVER_PORT } = configure(process.env) + const { + BASE_URL, + API_KEY, + API_SECRET, + PORT, + EXAMPLE_SERVER_PORT, + } = configure(process.env); - const ALL_INTERFACES = "0.0.0.0" // listen on all interfaces so Docker can bind + const ALL_INTERFACES = "0.0.0.0"; // listen on all interfaces so Docker can bind const server = Hapi.server({ port: PORT, host: ALL_INTERFACES, - }) + }); - await server.register(Inert) + await server.register(Inert); - let platform + let platform; server.route({ method: "GET", @@ -31,41 +37,51 @@ async function init() { index: "index.html", }, }, - }) + }); server.route({ method: "POST", path: "/api/claim/{claimMode}/{claimAction}", options: { cors: { origin: ["*"] } }, handler: async (request, h) => { - const { claimMode, claimAction } = request.params + const { claimMode, claimAction } = request.params; try { - return await platform[claimAction](claimMode, requestToAPIMapper[claimAction](request)) + return await platform[claimAction]( + claimMode, + requestToAPIMapper[claimAction](request) + ); } catch (e) { - console.error("Unhandled: %s", e.message) - throw e + console.error("Unhandled: %s", e.message); + throw e; } }, - }) + }); - platform = new PlatformAPI(console, BASE_URL, API_KEY, API_SECRET) + platform = new PlatformAPI(console, BASE_URL, API_KEY, API_SECRET); - console.debug("Starting internal server: %s", server.info.uri) + console.debug("Starting internal server: %s", server.info.uri); - await server.start() + 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) + 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) -}) + console.log(err); + process.exit(1); +}); process.on("SIGINT", () => { - console.log("Caught SIGINT - shutting down.") - process.exit(0) -}) + console.log("Caught SIGINT - shutting down."); + process.exit(0); +}); -init() +init(); diff --git a/demo/src/static/404.html b/demo/src/static/404.html index 260cc4c..2efc797 100644 --- a/demo/src/static/404.html +++ b/demo/src/static/404.html @@ -1,62 +1,58 @@ - + + + + Page Not Found + + - + @media only screen and (max-width: 280px) { + body, + p { + width: 95%; + } - -

Page Not Found

-

Sorry, but the page you were trying to view does not exist.

- + h1 { + font-size: 1.5em; + margin: 0 0 0.3em; + } + } + + + +

Page Not Found

+

Sorry, but the page you were trying to view does not exist.

+ diff --git a/demo/src/static/css/main.css b/demo/src/static/css/main.css index 043f520..75530e4 100644 --- a/demo/src/static/css/main.css +++ b/demo/src/static/css/main.css @@ -24,6 +24,7 @@ --c-black: #000; --c-black-o2: rgba(0, 0, 0, 0.2); --c-black-o4: rgba(0, 0, 0, 0.4); + --c-black-o6: rgba(0, 0, 0, 0.6); --c-black-o8: rgba(0, 0, 0, 0.8); --c-deep-cove: #061a40; --c-deep-cove-o2: rgba(6, 26, 64, 0.2); @@ -40,6 +41,7 @@ --c-matisse-o2: rgba(28, 82, 152, 0.2); --c-mercury: #e9e9e9; --c-mine-shaft: #272727; + --c-dark-green: #2e855e; --c-ocean-green: #46bc86; --c-picton-blue: #1fc8e7; --c-seashell: #f1f1f1; @@ -47,9 +49,11 @@ --c-silver-chalice: #a5a5a5; --c-slate-gray: #6b788e; --c-stratos: #00133e; - --c-texas-rose: #fdbd50; - --c-texas-rose-darker: #e4a437; + --c-texas-rose: #fdbc51; + --c-texas-rose-darker: #986c1f; --c-torch-red: #ff0033; + --c-dark-red: #c70028; + --c-l-pink: #fdd; --c-white: #fff; --c-white-darker: #e6e6e6; --c-white-o3: rgba(255, 255, 255, 0.3); @@ -146,13 +150,16 @@ textarea { border: 1px solid var(--c-mercury); padding: 1rem; margin-bottom: 1rem; - transition: filter, opacity linear .5s; + overflow: hidden; + transition-property: all; + transition-duration: 0.4s; + transition-timing-function: cubic-bezier(0, 1, 0.5, 1); } .card.collapsed { - opacity: .25; - filter: grayscale(50%); - pointer-events: none; + max-height: 0; + padding: 0; + border: hidden; } .card :first-of-type { @@ -185,24 +192,46 @@ header h1 { } header span { - font-size: 1.95rem; + font-size: 1.35rem; line-height: 1; } -@media screen and (min-width: 1024px) { +header a { + text-align: right; + flex: 5; +} + +header a:hover, +header a:active { + text-decoration: underline; +} + +@media screen and (min-width: 400px) { header span { - font-size: 2.6rem; + font-size: 2rem; } } - +@media screen and (min-width: 800px) { + header span { + font-size: 2.6rem; + } +} header .logo { margin-right: 1rem; + flex: 1; +} + +h2 { + color: var(--c-matisse); + font-size: 1.2em; + letter-spacing: -0.04em; + margin-bottom: 0; } h3 { - color: var(--c-deep-cove); + color: var(--c-matisse); letter-spacing: -0.02em; font-family: var(--f-regular); margin-bottom: 0; @@ -211,15 +240,13 @@ h3 { line-height: 1.3em; } -h4 { - color: var(--c-deep-cove); +h3.error { + color: var(--c-torch-red); font-size: 1.2em; - letter-spacing: -0.04em; - margin-bottom: 0; } header + p { - font-size: .9rem; + font-size: 0.9rem; } a { @@ -228,7 +255,8 @@ a { transition: color 0.3s ease-in-out; } -a:focus, a:hover { +a:focus, +a:hover { color: var(--c-deep-cove); } @@ -263,7 +291,7 @@ fieldset label { input[type="text"], input[type="email"] { - border-color: var(--c-black-o4); + border-color: var(--c-black-o6); border-style: solid; border-width: 0 0 1px; padding-left: 0; @@ -282,6 +310,15 @@ input[type="email"] { width: 100%; } +/* + * Form Validation + */ +.error-message { + color: var(--c-torch-red); + font-size: 0.8rem; + display: none; +} + .assurance, .mode { padding-left: 50px; @@ -382,6 +419,7 @@ pre { font-size: 0.8em; padding: 1rem; border-radius: 4px; + white-space: pre-wrap; } footer { @@ -392,7 +430,7 @@ footer { footer a { color: var(--c-dove-gray); font-weight: 600; - text-decoration: none; + text-decoration: underline; } /* iProov Web SDK overrides */ @@ -435,39 +473,39 @@ footer a { padding-right: calc(80px + 1rem); } - .state--passed { background-image: url(../img/gpa-tick.png); } -.state--cancelled, +.state--canceled, .state--error, .state--failed { - background-image: url('../img/No-not-worked-woman-yellow.png'); + background-image: url("../img/No-not-worked-woman-yellow.png"); padding-right: calc(1rem + 80px); } -[slot=ready] h3 { +[slot="ready"] h3 { font-size: 1.5rem; } .error-container { - background-color: var(--c-egg-sour); - color: var(--c-torch-red); + background-color: var(--c-l-pink); + color: var(--c-dark-red); border-radius: 4px; - border: 1px solid var(--c-torch-red); + border: 1px solid var(--c-dark-red); padding: 1rem; margin-top: 1rem; } -.error-container h4, .error-container p { +.error-container h3, +.error-container p { color: inherit; margin-bottom: 0; } .support-container { margin: 1rem 0 0; - font-size: .8rem; + font-size: 0.8rem; background-repeat: no-repeat; padding-left: 2rem; background-size: 1.5rem; @@ -476,7 +514,8 @@ footer a { .support-container--ok { background-image: url(../img/thumbs-up.png); - color: var(--c-ocean-green); + color: var(--c-dark-green); + font-weight: 700; } .support-container--error { diff --git a/demo/src/static/css/normalize.css b/demo/src/static/css/normalize.css index 192eb9c..bb6e2a7 100644 --- a/demo/src/static/css/normalize.css +++ b/demo/src/static/css/normalize.css @@ -174,7 +174,8 @@ textarea { */ button, -input { /* 1 */ +input { + /* 1 */ overflow: visible; } @@ -184,7 +185,8 @@ input { /* 1 */ */ button, -select { /* 1 */ +select { + /* 1 */ text-transform: none; } diff --git a/demo/src/static/index.html b/demo/src/static/index.html index 1df7953..34fc8bd 100644 --- a/demo/src/static/index.html +++ b/demo/src/static/index.html @@ -52,8 +52,8 @@

Failed


         
       
-      
-

Cancelled

+
+

Canceled

User exited out of iProov

@@ -68,18 +68,25 @@

Passed

-
+

- Web SDK Demo + + Web SDK Demo +

-

Demo page for iProov Web integrators. Enter your email address, select claim mode, and iProov!

-
-

Create Token

+

+ Demo page for iProov Web integrators. Enter your email address, select claim mode, and iProov! +

+ +

Create Token

- + +

+ Please enter a valid email address which includes the @ character i.e. name@domain.com +

@@ -115,7 +122,7 @@

Create Token

-