diff --git a/.github/workflows/single-file-openapi-spec.yaml b/.github/workflows/single-file-openapi-spec.yaml deleted file mode 100644 index e593a507..00000000 --- a/.github/workflows/single-file-openapi-spec.yaml +++ /dev/null @@ -1,67 +0,0 @@ -name: single-file-openapi-spec -run-name: Generate and publish single-file OpenAPI specs -on: - push: - branches: - - master - paths-ignore: - - 'generated/artifacts/**' - pull_request: - branches: - - master -jobs: - build-single-spec: - name: Generate single-file OpenAPI specifications - runs-on: ubuntu-latest - container: - image: openapitools/openapi-generator-cli:v7.3.0 - env: - OPENAPI_GENERATOR_COMMAND: docker-entrypoint.sh - steps: - - uses: actions/checkout@v4 - - name: Install pre-requisites - run: | - apt-get update - apt-get install -yqq \ - gettext-base - - name: Validate multi-file OpenAPI specification - run: | - $OPENAPI_GENERATOR_COMMAND validate -i openapi.yaml - - name: Refresh single-file OpenAPI specifications - run: | - ./generator/script/generate.sh \ - generator/configuration/openapi.yaml \ - generator/configuration/openapi-yaml.yaml - - name: Store generated artifacts - uses: actions/upload-artifact@v4 - with: - name: artifacts-${{ github.workflow }}-${{ github.run_id }}-${{ github.run_number }} - path: generated/artifacts - commit-single-spec: - name: Commit and push single-file OpenAPI specifications - runs-on: ubuntu-latest - needs: build-single-spec - if: github.event_name == 'push' - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.GH_ACTION_ACCESS_TOKEN }} - ref: ${{ github.ref_name }} - - uses: actions/download-artifact@v4 - with: - name: artifacts-${{ github.workflow }}-${{ github.run_id }}-${{ github.run_number }} - path: generated/artifacts - - name: Commit and push single-file OpenAPI specifications - run: | - git --version - if [ -z "$(git status --porcelain=v1 generated/artifacts)" ]; - then - echo "no change detected" - else - echo "changes detected" - git config user.name "GitHub Actions Bot" - git config user.email "<>" - git status generated/artifacts - git commit -m "Single file specifications refresh" generated/artifacts - git push - fi \ No newline at end of file diff --git a/.github/workflows/update-specs-and-client-libraries.yaml b/.github/workflows/update-specs-and-client-libraries.yaml new file mode 100644 index 00000000..511d3925 --- /dev/null +++ b/.github/workflows/update-specs-and-client-libraries.yaml @@ -0,0 +1,131 @@ +name: update-specs-and-client-libraries +run-name: Update specs and client libraries +on: + push: + branches: + - master + paths-ignore: + - 'generated/artifacts/**' + pull_request: + branches: + - master + workflow_dispatch: + inputs: + type-of-change: + description: 'Type of change?' + required: true + default: Patch + type: choice + options: + - Major + - Minor + - Patch + update-onfido-node: + description: 'Update onfido-node?' + required: true + default: false + type: boolean +jobs: + generate_specs_and_libraries: + name: Build single-file OpenAPI specifications and client libraries + runs-on: ubuntu-latest + outputs: + typescript_axios_version: ${{ steps.generator.outputs.typescript_axios_version }} + container: + image: openapitools/openapi-generator-cli:v7.3.0 + env: + OPENAPI_GENERATOR_COMMAND: docker-entrypoint.sh + BUMP_CLIENT_LIBRARY_VERSION: ${{ inputs.type-of-change || 'Minor' }} + steps: + - uses: actions/checkout@v4 + - name: Install pre-requisites + run: | + apt-get update + apt-get install -yqq \ + gettext-base \ + git \ + jq + - name: Validate multi-file OpenAPI specification + run: | + $OPENAPI_GENERATOR_COMMAND validate -i openapi.yaml + - name: Generate specifications and client libraries + id: generator + run: | + ./shell/generate.sh + - name: Store generated artifacts and finalization scripts + uses: actions/upload-artifact@v4 + with: + name: artifacts-${{ github.workflow }}-${{ github.run_id }}-${{ github.run_number }} + path: | + generated/artifacts + generators/*/exclusions.txt + update_specs: + name: Update and commit single-file OpenAPI specifications + runs-on: ubuntu-latest + needs: generate_specs_and_libraries + if: github.event_name == 'push' + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GH_ACTION_ACCESS_TOKEN }} + ref: ${{ github.ref_name }} + - uses: actions/download-artifact@v4 + with: + name: artifacts-${{ github.workflow }}-${{ github.run_id }}-${{ github.run_number }} + - name: Commit and push single-file OpenAPI specifications + run: | + if [ -z "$(git status --porcelain=v1 generated/artifacts/openapi-*)" ]; + then + echo "no change detected" + else + echo "changes detected" + git config user.name "GitHub Actions Bot" + git config user.email "<>" + git status generated/artifacts/openapi-* + git commit -m "Single file specifications refresh" generated/artifacts/openapi-* + git push + fi + update_client_libraries: + name: "Create pull request to ${{ matrix.git_repo_id }} repository with ${{ matrix.generator }}:${{matrix.version}}" + runs-on: ubuntu-latest + needs: generate_specs_and_libraries + strategy: + matrix: + include: + - generator: typescript-axios + git_repo_id: onfido-node + preCommit: npm install + version: ${{ needs.generate_specs_and_libraries.outputs.typescript_axios_version }} + update: ${{ inputs.update-onfido-node }} + if: github.event_name == 'workflow_dispatch' + steps: + - uses: actions/checkout@v4 + if: ${{ matrix.update }} + with: + repository: onfido/${{ matrix.git_repo_id }} + - uses: actions/download-artifact@v4 + if: ${{ matrix.update }} + with: + name: artifacts-${{ github.workflow }}-${{ github.run_id }}-${{ github.run_number }} + - name: Integrate generated code (${{ matrix.version }}) + if: ${{ matrix.update }} + id: generator + run: | + rsync -r --delete-after --exclude='/.git*' \ + --exclude-from=generators/${{ matrix.generator }}/exclusions.txt \ + generated/artifacts/${{ matrix.generator }}/ . + + ${{ matrix.preCommit }} + + echo any_change=$(git status --porcelain=v1 | wc -l | sed -e 's/^[[:space:]]*//') >> $GITHUB_OUTPUT + - name: Create Pull Request with changes after library update + uses: peter-evans/create-pull-request@v6 + if: ${{ matrix.update && steps.generator.outputs.any_change }} + with: + token: ${{ secrets.GH_ACTION_ACCESS_TOKEN }} + commit-message: Upgrade after onfido-openapi-spec change ${{ github.sha }} + title: Releasing ${{ matrix.git_repo_id }} ${{ matrix.version }} after onfido-openapi-spec update + body: | + - Auto-generated PR + + branch: release-upgrade diff --git a/generator/script/generate.sh b/generator/script/generate.sh deleted file mode 100755 index d9f68452..00000000 --- a/generator/script/generate.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash - -# Don't carry on when any command fails -set -e - -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -BASEDIR=${SCRIPT_DIR}/../.. - -CONFIG_FILES=${*:-`ls generator/configuration/*.yaml`} -GENERATED_CONFIG_FILES="" - -OPENAPI_GENERATOR_COMMAND=${OPENAPI_GENERATOR_COMMAND:-\ - docker run --rm -v "$(pwd):/local" -w /local \ - openapitools/openapi-generator-cli:v7.3.0} - -cd ${BASEDIR} -rm -rf generated; mkdir -p generated/configuration - -for CONFIG_FILE in $CONFIG_FILES -do - GENERATOR=$(basename $CONFIG_FILE .yaml) - - if [ "${GENERATOR}" != "common" ]; - then - GENERATED_CONFIG_FILE=generated/configuration/${GENERATOR}.yaml - - ( cat generator/configuration/common.yaml && echo && - cat generator/configuration/${GENERATOR}.yaml ) | \ - GENERATOR=${GENERATOR} envsubst >| \ - ${GENERATED_CONFIG_FILE} - - echo "Configuration for generator ${GENERATOR} built." - - GENERATED_CONFIG_FILES="${GENERATED_CONFIG_FILES} ${GENERATED_CONFIG_FILE}" - fi -done - -${OPENAPI_GENERATOR_COMMAND} batch --clean ${GENERATED_CONFIG_FILES} diff --git a/generator/configuration/common.yaml b/generators/common/config.yaml similarity index 52% rename from generator/configuration/common.yaml rename to generators/common/config.yaml index 0c8b60f2..d97769de 100644 --- a/generator/configuration/common.yaml +++ b/generators/common/config.yaml @@ -1,7 +1,9 @@ inputSpec: openapi.yaml gitUserId: onfido generatorName: ${GENERATOR} -templateDir: generator/templates/${GENERATOR} +templateDir: generators/${GENERATOR}/templates outputDir: generated/artifacts/${GENERATOR} useOneOfDiscriminatorLookup: true -disallowAdditionalPropertiesIfNotPresent: false \ No newline at end of file +disallowAdditionalPropertiesIfNotPresent: false +additionalProperties: + apiVersion: v3.6 \ No newline at end of file diff --git a/generator/configuration/openapi-yaml.yaml b/generators/common/templates/.openapi-generator-ignore similarity index 100% rename from generator/configuration/openapi-yaml.yaml rename to generators/common/templates/.openapi-generator-ignore diff --git a/generators/common/templates/LICENSE.mustache b/generators/common/templates/LICENSE.mustache new file mode 100644 index 00000000..fd273ed6 --- /dev/null +++ b/generators/common/templates/LICENSE.mustache @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2024 Onfido + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/generator/configuration/openapi.yaml b/generators/openapi-yaml/config.yaml similarity index 100% rename from generator/configuration/openapi.yaml rename to generators/openapi-yaml/config.yaml diff --git a/generator/templates/openapi-yaml/.openapi-generator-ignore b/generators/openapi-yaml/templates/.openapi-generator-ignore similarity index 100% rename from generator/templates/openapi-yaml/.openapi-generator-ignore rename to generators/openapi-yaml/templates/.openapi-generator-ignore diff --git a/generators/openapi/config.yaml b/generators/openapi/config.yaml new file mode 100644 index 00000000..e69de29b diff --git a/generator/templates/openapi/.openapi-generator-ignore b/generators/openapi/templates/.openapi-generator-ignore similarity index 100% rename from generator/templates/openapi/.openapi-generator-ignore rename to generators/openapi/templates/.openapi-generator-ignore diff --git a/generators/typescript-axios/config.yaml b/generators/typescript-axios/config.yaml new file mode 100644 index 00000000..8dff275e --- /dev/null +++ b/generators/typescript-axios/config.yaml @@ -0,0 +1,15 @@ +gitRepoId: onfido-node +npmName: "@onfido/api" +npmRepository: github:onfido/onfido-node +npmVersion: ${CLIENT_LIBRARY_VERSION} +enumUnknownDefaultCase: true +supportsES6: true +apiPackage: api +modelPackage: model +withNodeImports: true +withSeparateModelsAndApi: true +files: + LICENSE.mustache: + destinationFilename: LICENSE + webhook-event-verifier.mustache: + destinationFilename: webhook-event-verifier.ts \ No newline at end of file diff --git a/generators/typescript-axios/exclusions.txt b/generators/typescript-axios/exclusions.txt new file mode 100644 index 00000000..65e3ba2e --- /dev/null +++ b/generators/typescript-axios/exclusions.txt @@ -0,0 +1 @@ +test/ diff --git a/generators/typescript-axios/templates/.openapi-generator-ignore b/generators/typescript-axios/templates/.openapi-generator-ignore new file mode 120000 index 00000000..1e6cec82 --- /dev/null +++ b/generators/typescript-axios/templates/.openapi-generator-ignore @@ -0,0 +1 @@ +../../common/templates/.openapi-generator-ignore \ No newline at end of file diff --git a/generators/typescript-axios/templates/LICENSE.mustache b/generators/typescript-axios/templates/LICENSE.mustache new file mode 120000 index 00000000..028e9c54 --- /dev/null +++ b/generators/typescript-axios/templates/LICENSE.mustache @@ -0,0 +1 @@ +../../common/templates/LICENSE.mustache \ No newline at end of file diff --git a/generators/typescript-axios/templates/README.mustache b/generators/typescript-axios/templates/README.mustache new file mode 100644 index 00000000..67c5fd7a --- /dev/null +++ b/generators/typescript-axios/templates/README.mustache @@ -0,0 +1,157 @@ +# Onfido Node.js Library + +The official Node.js library for integrating with the Onfido API. + +Documentation can be found at + +This library is only for use on the backend, as it uses Onfido API tokens which must be kept secret. If you do need to collect applicant data in the frontend of your application, we recommend that you use the Onfido SDKs: [iOS](https://github.com/onfido/onfido-ios-sdk), [Android](https://github.com/onfido/onfido-android-sdk), [Web](https://github.com/onfido/onfido-sdk-ui), and [React Native](https://github.com/onfido/react-native-sdk). + +This version uses Onfido API {{ apiVersion }}. Refer to our [API versioning guide](https://developers.onfido.com/guide/api-versioning-policy#client-libraries) for details of which client library versions use which versions of the API. + +## Installation + +For npm: + +```sh +npm install @onfido/api +``` + +For Yarn: + +```sh +yarn add @onfido/api +``` + +## Getting started + +Require the package: + +```js +const { DefaultApi, Configuration, WebhookEventVerifier } = require("@onfido/api"); +const { isAxiosError } = require('axios'); +``` + +For TypeScript users, types are available as well: + +```ts +import { DefaultApi, Configuration, Region, WebhookEventVerifier } from "@onfido/api"; +import { isAxiosError } from 'axios'; +``` + +Configure with your API token and region: + +```js +const onfido = new DefaultApi( + new Configuration({ + apiToken: process.env.ONFIDO_API_TOKEN, + region: Region.EU, // Supports Region.EU, Region.US and Region.CA + baseOptions: { timeout: 30_000 }, // Additional Axios options (timeout, etc.) + }) +); +``` + +Using with `async`/`await` (in an `async function`): + +```js +(async () => { + try { + const applicant = await onfido.createApplicant({ + first_name: "Jane", + last_name: "Doe", + location: { + ip_address: "127.0.0.1", + country_of_residence: "GBR", + }, + }); + + const check = await onfido.createCheck({ + applicant_id: applicant.data.id, + report_names: ["identity_enhanced"], + }); + + // ... + + // Webhook verification + const verifier = new WebhookEventVerifier("_ABC123abc...3ABC123_"); + const signature = "a0...760e"; + + const event = verifier.readPayload(`{"payload":{"r...3"}}}`, signature); + } catch (error) { + if (isAxiosError(error)) { + console.log(`status code: ${error.response?.status}`); + const error_details = error.response?.data.error; + // An error response was received from the Onfido API, extra info is available. + if (error_details) { + console.log(error_details.message); + console.log(error_details.type); + } else { + // No response was received for some reason e.g. a network error. + console.log(error.message); + } + } else { + console.log(error.message); + } + } +})(); +``` + +Please find more information regarding Axios errors in library [documentation](https://axios-http.com/docs/handling_errors). + +Using with promises: + +```js +onfido + .createApplicant({ + first_name: "Jane", + last_name: "Doe", + location: { + ip_address: "127.0.0.1", + country_of_residence: "GBR" + } + }) + .then(applicant => + onfido.createCheck({ + applicant_id: applicant.data.id, + report_names: ["identity_enhanced"] + }) + ) + .then(check => + // Handle successfully created check. + ) + .catch(error => { + // Handle error. + }); +``` + +## File download + +File downloads, for example `onfido.downloadDocument(documentId, {responseType: 'arraybuffer'})`, will return an instance of a specific object for this endpoint. + +These objects will have a content type, e.g. `image/png`. + +```js +download.headers['content-type']; +``` + +Call `slice()` to get a `Blob` of the download: + +```js +const blob = download.data.slice(); +``` + +## File upload + +For some common types of streams, like instances of `fs.ReadStream`, you can provide the stream directly: + +```js +onfido.uploadDocument("passport", "", fs.createReadStream("path/to/passport.png")); +``` + +## More documentation + +More documentation and code examples can be found at + +## Support + +Should you encounter any technical issues during integration, please contact Onfido’s Customer Support team +via the [Customer Experience Portal](https://public.support.onfido.com/) which also include support documentation. diff --git a/generators/typescript-axios/templates/SHA256SUM b/generators/typescript-axios/templates/SHA256SUM new file mode 100644 index 00000000..c7aea0d0 --- /dev/null +++ b/generators/typescript-axios/templates/SHA256SUM @@ -0,0 +1,6 @@ + +e13ee7c0cad9a79bab00e8b2a7942bc5a78f63115ece3dfd71a6472bdb02dd22 README.mustache +d8d68213e1a9a9983f89f688aaf31360f69d34a871cab4fc94f59a40279b13d9 configuration.mustache +5030fcd1954b4d981f2d06fce4900fe29c97c7b74ee66f2eb5f45e81b9252a94 index.mustache +c7e61e2b36fbf70d84b75162b09b4f571466b59d0b72c7f1e3e517c6d7342622 package.mustache +89b381b068d1849cc98721643c923ee153a5bdd69e19306dda59114ba0a2e243 tsconfig.mustache diff --git a/generators/typescript-axios/templates/configuration.mustache b/generators/typescript-axios/templates/configuration.mustache new file mode 100644 index 00000000..7adab839 --- /dev/null +++ b/generators/typescript-axios/templates/configuration.mustache @@ -0,0 +1,114 @@ +/* tslint:disable */ +/* eslint-disable */ +{{>licenseInfo}} + +{{! Added code - BEGIN }} +import { BASE_PATH } from "./base"; + +export enum Region{ + EU, + US, + CA +} +{{! Added code - END }} + +export interface ConfigurationParameters { + {{! Updated parameters (add apiToken and region, remove unused apiKey, username, password, accessToken and serverIndex) }} + apiToken?: string; + region?: Region; + basePath?: string; + baseOptions?: any; + formDataCtor?: new () => any; +} + +export class Configuration { + /** + * parameter for apiKey security + * @param name security name + * @memberof Configuration + */ + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + username?: string; + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + password?: string; + /** + * parameter for oauth2 security + * @param name security name + * @param scopes oauth2 scope + * @memberof Configuration + */ + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + /** + * override base path + * + * @type {string} + * @memberof Configuration + */ + basePath?: string; + /** + * override server index + * + * @type {number} + * @memberof Configuration + */ + serverIndex?: number; + /** + * base options for axios calls + * + * @type {any} + * @memberof Configuration + */ + baseOptions?: any; + /** + * The FormData constructor that will be used to create multipart form data + * requests. You can inject this here so that execution environments that + * do not support the FormData class can still run the generated client. + * + * @type {new () => FormData} + */ + formDataCtor?: new () => any; + + {{! Updated constructor (remove unused username, password, accessToken and serverIndex) }} + constructor(param: ConfigurationParameters = {}) { + if (!param.apiToken) { + throw new Error("No apiToken provided"); + } + + if (param.region && !Object.values(Region).includes(param.region)) { + throw new Error(`Unknown or missing region '${param.region}'`); + } + + this.apiKey = 'Token token=' + param.apiToken; + this.basePath = param.basePath || BASE_PATH.replace('.eu.', `.${Region[param.region || Region.EU].toLowerCase()}.`); + this.baseOptions = {...param.baseOptions, + ...{ headers: {...param.baseOptions?.headers, + ...{'User-Agent': 'onfido-node/{{npmVersion}}'}}}}; + this.formDataCtor = param.formDataCtor || require('form-data'); // Injiect form data constructor (if needed) + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + public isJsonMime(mime: string): boolean { + const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i'); + return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json'); + } +} diff --git a/generators/typescript-axios/templates/index.mustache b/generators/typescript-axios/templates/index.mustache new file mode 100644 index 00000000..33eac20d --- /dev/null +++ b/generators/typescript-axios/templates/index.mustache @@ -0,0 +1,9 @@ +/* tslint:disable */ +/* eslint-disable */ +{{>licenseInfo}} + +export * from "./api"; +export * from "./configuration"; +{{#withSeparateModelsAndApi}}export * from "./{{tsModelPackage}}";{{/withSeparateModelsAndApi}} +{{! Additional import for webhook event verifier }} +export * from "./webhook-event-verifier"; diff --git a/generators/typescript-axios/templates/package.mustache b/generators/typescript-axios/templates/package.mustache new file mode 100644 index 00000000..5c2c94d6 --- /dev/null +++ b/generators/typescript-axios/templates/package.mustache @@ -0,0 +1,61 @@ +{ + "name": "{{npmName}}", + "version": "{{npmVersion}}", + "description": "OpenAPI client for {{npmName}}", + "author": "OpenAPI-Generator Contributors", + "repository": { + "type": "git", + "url": "https://{{gitHost}}/{{gitUserId}}/{{gitRepoId}}.git" + }, + "keywords": [ + "axios", + "typescript", + "openapi-client", + "openapi-generator", + "{{npmName}}" + ], + "license": "MIT", {{! Updated license }} + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", +{{#supportsES6}} + "module": "./dist/esm/index.js", + "sideEffects": false, +{{/supportsES6}} + "scripts": { + "test": "jest", {{! Added command for integration tests }} + "build": "tsc{{#supportsES6}} && tsc -p tsconfig.esm.json{{/supportsES6}}", + "prepare": "npm run build" + }, + {{! Added block for integration tests }} + "jest": { + "preset": "ts-jest", + "testEnvironment": "node", + "moduleNameMapper": { + "onfido-node": "/index", + "axios": "axios/dist/node/axios.cjs" + }, + "globals": { + "ts-jest": { + "tsconfig": "test/tsconfig.json" + } + } + }, + "dependencies": { + "axios": "^1.6.1" + }, + "devDependencies": { + {{! Added dependencies for tests development - BEGIN }} + "@types/jest": "^26.0.15", + "jest": "^26.6.3", + "prettier": "^1.18.2", + "ts-jest": "^26.4.4", + {{! Added dependencies for tests development - END }} + "@types/node": "^12.11.5", + "typescript": "^4.0" + }{{#npmRepository}},{{/npmRepository}} +{{#npmRepository}} + "publishConfig": { + "registry": "{{npmRepository}}" + } +{{/npmRepository}} +} diff --git a/generators/typescript-axios/templates/tsconfig.mustache b/generators/typescript-axios/templates/tsconfig.mustache new file mode 100644 index 00000000..8792c7b5 --- /dev/null +++ b/generators/typescript-axios/templates/tsconfig.mustache @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "declaration": true, + "target": "{{#supportsES6}}ES6{{/supportsES6}}{{^supportsES6}}ES5{{/supportsES6}}", + "module": "commonjs", + "noImplicitAny": true, + "outDir": "dist", + "rootDir": ".", + {{#supportsES6}} + "moduleResolution": "node", + {{/supportsES6}} + {{^supportsES6}} + "lib": [ + "es6", + "dom" + ], + {{/supportsES6}} + "typeRoots": [ + "node_modules/@types" + ] + }, + {{! Replace exclude with include to allow running integration tests }} + "include": [ + "api", + "model", + "index.ts" + ] +} diff --git a/generators/typescript-axios/templates/webhook-event-verifier.mustache b/generators/typescript-axios/templates/webhook-event-verifier.mustache new file mode 100644 index 00000000..4a638f93 --- /dev/null +++ b/generators/typescript-axios/templates/webhook-event-verifier.mustache @@ -0,0 +1,42 @@ +// Require crypto instead of importing, because Node can be built without crypto support. +let crypto: typeof import("crypto") | undefined; +try { + // tslint:disable-next-line: no-var-requires + crypto = require("crypto"); +} catch { + // We throw an error when verifying webhooks instead. +} + +export class OnfidoInvalidSignatureError extends Error {} + +export class WebhookEventVerifier { + private readonly webhookToken: string; + + constructor(webhookToken: string) { + this.webhookToken = webhookToken; + } + + public readPayload( + rawEventBody: string | Buffer, + hexSignature: string + ) { + if (!crypto) { + throw new Error("Verifying webhook events requires crypto support"); + } + + const givenSignature = Buffer.from(hexSignature, "hex"); + + // Compute the the actual HMAC signature from the raw request body. + const hmac = crypto.createHmac("sha256", this.webhookToken); + hmac.update(rawEventBody); + const eventSignature = hmac.digest(); + + // Use timingSafeEqual to prevent against timing attacks. + if (!crypto.timingSafeEqual(givenSignature, eventSignature)) { + throw new OnfidoInvalidSignatureError("Invalid signature for webhook event"); + } + + const { payload } = JSON.parse(rawEventBody.toString()); + return payload; + } +} diff --git a/openapi.yaml b/openapi.yaml index 1d0570ca..c384712d 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -59,6 +59,8 @@ paths: $ref: paths/live_videos.yaml#/live_videos /live_videos/{live_video_id}: $ref: paths/live_videos.yaml#/live_video + /live_videos/{live_video_id}/download: + $ref: paths/live_videos.yaml#/download /live_videos/{live_video_id}/frame: $ref: paths/live_videos.yaml#/frame # Workflow Runs and Tasks diff --git a/paths/applicants.yaml b/paths/applicants.yaml index 9f51d7f7..175597fd 100644 --- a/paths/applicants.yaml +++ b/paths/applicants.yaml @@ -111,7 +111,7 @@ delete: summary: Delete Applicant - operationId: destroy_applicant + operationId: delete_applicant parameters: - name: applicant_id in: path diff --git a/paths/live_videos.yaml b/paths/live_videos.yaml index 79678ce3..7cc879ad 100644 --- a/paths/live_videos.yaml +++ b/paths/live_videos.yaml @@ -48,6 +48,29 @@ default: $ref: ../responses/default_error.yaml + download: + get: + summary: Download live video + description: Live videos are downloaded using this endpoint. + operationId: download_live_video + parameters: + - name: live_video_id + in: path + required: true + description: The live video's unique identifier. + schema: + type: string + responses: + "200": + description: The live video's binary data. + content: + "*/*": + schema: + type: string + format: binary + default: + $ref: ../responses/default_error.yaml + frame: get: summary: Download live video frame diff --git a/paths/motion_captures.yaml b/paths/motion_captures.yaml index 23390115..2396222f 100644 --- a/paths/motion_captures.yaml +++ b/paths/motion_captures.yaml @@ -75,7 +75,7 @@ get: summary: Download motion capture frame description: Instead of the whole capture binary, a single frame can be downloaded using this endpoint. Returns the binary data representing the frame. - operationId: frame_motion_capture + operationId: download_motion_capture_frame parameters: - name: motion_capture_id in: path diff --git a/paths/watchlist_monitors.yaml b/paths/watchlist_monitors.yaml index a02fad46..7c3cb22b 100644 --- a/paths/watchlist_monitors.yaml +++ b/paths/watchlist_monitors.yaml @@ -70,7 +70,7 @@ delete: summary: Deactivates the given monitor - operationId: destroy_watchlist_monitor + operationId: delete_watchlist_monitor parameters: - name: monitor_id in: path diff --git a/paths/webhooks.yaml b/paths/webhooks.yaml index 07aff3e8..e5812dbd 100644 --- a/paths/webhooks.yaml +++ b/paths/webhooks.yaml @@ -59,7 +59,7 @@ put: summary: Edit a webhook - operationId: edit_webhook + operationId: update_webhook parameters: - name: webhook_id in: path diff --git a/paths/workflow_runs.yaml b/paths/workflow_runs.yaml index b0d0e055..2a6660de 100644 --- a/paths/workflow_runs.yaml +++ b/paths/workflow_runs.yaml @@ -68,7 +68,7 @@ retrieve: get: summary: A single workflow run can be retrieved by calling this endpoint with the unique identifier of the Workflow Run. - operationId: retrieve_workflow_run + operationId: find_workflow_run parameters: - name: workflow_run_id in: path @@ -138,10 +138,10 @@ schema: type: string responses: - "302": - description: Redirects to the evidence file destination + "200": + description: The signed evidence file PDF content: - "*/*": + binary/octet-stream: schema: type: string format: binary diff --git a/schemas/workflow_runs/definitions.yaml b/schemas/workflow_runs/definitions.yaml index 79b5606f..e3d70d6d 100644 --- a/schemas/workflow_runs/definitions.yaml +++ b/schemas/workflow_runs/definitions.yaml @@ -11,7 +11,6 @@ workflow_run_shared: type: array maxItems: 30 description: Tags or labels assigned to the workflow run. - uniqueItems: true items: type: string minLength: 1 diff --git a/shell/generate.sh b/shell/generate.sh new file mode 100755 index 00000000..911860a8 --- /dev/null +++ b/shell/generate.sh @@ -0,0 +1,156 @@ +#!/usr/bin/env bash + +# Don't carry on when any command fails +set -e + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +BASEDIR=${SCRIPT_DIR}/.. + +COMMON_CONFIG_FILE="generators/common/config.yaml" +GENERATORS=${*:-`ls generators`} +GENERATED_CONFIG_FILES="" + +OPENAPI_GENERATOR_VERSION=${OPENAPI_GENERATOR_VERSION:-v7.3.0} +OPENAPI_GENERATOR_COMMAND=${OPENAPI_GENERATOR_COMMAND:-\ + docker run --rm -v "$(pwd):/local" -w /local \ + openapitools/openapi-generator-cli:${OPENAPI_GENERATOR_VERSION}} + +BUMP_CLIENT_LIBRARY_VERSION=${BUMP_CLIENT_LIBRARY_VERSION:-""} + +# $1: version, $2: bump type (Major, Minor, Patch) +function semver_bump() { + INPUT_VERSION=$1 + BUMP_TYPE=$2 + POS=0 + RESET= + OUTPUT_VERSION= + OUTPUT_SEPARATOR="." + + IFS='.' read -ra NUMBERS <<< "$INPUT_VERSION" + for NUMBER in "${NUMBERS[@]}"; do + if [ -z "${RESET}" ]; + then + if ([ $POS -eq 0 ] && [ "$BUMP_TYPE" == "Major" ]) || \ + ([ $POS -eq 1 ] && [ "$BUMP_TYPE" == "Minor" ]) || \ + ([ $POS -eq 2 ] && [ "$BUMP_TYPE" == "Patch" ]) ; + then + NUMBER=$((NUMBER+1)) + RESET=1 + fi + else + NUMBER=0 + fi + + echo -n "${NUMBER}${OUTPUT_SEPARATOR}" + + if [ $POS -ge 1 ]; + then + OUTPUT_SEPARATOR="" + fi + + POS=$((POS+1)) + done +} + +function validate_templates_checksum() { + GENERATOR=$1 + LIBRARY=$2 + RESULT=0 + + if [ ! -z "$LIBRARY" ]; + then + LIBRARY="--library $LIBRARY" + fi + + $OPENAPI_GENERATOR_COMMAND author template -g $GENERATOR $LIBRARY -o generated/templates/${GENERATOR} > /dev/null + + pushd generated/templates/${GENERATOR} > /dev/null + shasum -a 256 * >| SHA256SUM + popd > /dev/null + + OUR_SHASUMS=generators/${GENERATOR}/templates/SHA256SUM + echo >| $OUR_SHASUMS.new + + for TEMPLATE in $(ls generators/${GENERATOR}/templates/ | grep -v SHA256SUM) ; + do + FILENAME=$(basename $TEMPLATE) + + LIB_SHASUM=$(grep $FILENAME generated/templates/${GENERATOR}/SHA256SUM | awk '{print $1}' || true) + OUR_SHASUM=$(grep $FILENAME $OUR_SHASUMS | awk '{print $1}' || true) + + if [[ ! -z "$LIB_SHASUM" ]]; # File is provided by lib + then + if [ ! -z "$OUR_SHASUM" ] && [[ "$LIB_SHASUM" != "$OUR_SHASUM" ]]; # We do have checksum and doesn't match + then + echo -e "\n################################################################################\n" + echo -e " !!! Error while building generator ${GENERATOR}!!!\n" + echo " SHA256SUM for template $FILENAME changed, diff reported below. To overwrite template, run:" + echo " cp generated/templates/${GENERATOR}/$FILENAME generators/${GENERATOR}/templates/" + echo + diff generated/templates/${GENERATOR}/$FILENAME generators/${GENERATOR}/templates/$FILENAME || true + RESULT=1 + fi + + echo "$LIB_SHASUM $FILENAME" >> $OUR_SHASUMS.new + fi + done + + # Remove if empty or rename otherwise + if [[ "$(grep -e '\s' $OUR_SHASUMS.new)" == "" ]]; + then + unlink $OUR_SHASUMS.new + else + mv $OUR_SHASUMS.new $OUR_SHASUMS + fi + + return $RESULT +} + +cd ${BASEDIR} +rm -rf generated; mkdir -p generated/configuration + +for GENERATOR in $GENERATORS +do + if [ "${GENERATOR}" != "common" ]; + then + echo "Building configuration for generator ${GENERATOR}..." + + GENERATOR_FOLDER=generators/${GENERATOR} + CONFIG_FILE=${GENERATOR_FOLDER}/config.yaml + GENERATED_CONFIG_FILE=generated/configuration/${GENERATOR}.yaml + + GIT_REPO_ID=$(grep gitRepoId: $CONFIG_FILE | sed 's/gitRepoId: //g') + LIBRARY=$(grep library: $CONFIG_FILE | sed 's/library: //g') + + if [ -n "${GIT_REPO_ID}" ]; + then + LATEST_LIBRARY_VERSION=$(curl -s https://api.github.com/repos/onfido/${GIT_REPO_ID}/releases/latest | jq .name | sed 's/[v"]//g') + CURRENT_LIBRARY_VERSION=$(semver_bump ${LATEST_LIBRARY_VERSION} ${BUMP_CLIENT_LIBRARY_VERSION}) + + echo "Latest delivered version was: ${LATEST_LIBRARY_VERSION}" + echo "Current version is going to be: ${CURRENT_LIBRARY_VERSION}" + echo "Client library version bump?: ${BUMP_CLIENT_LIBRARY_VERSION}" + else + CURRENT_LIBRARY_VERSION="" + fi + + validate_templates_checksum $GENERATOR $LIBRARY + + ( cat $COMMON_CONFIG_FILE && echo && cat $CONFIG_FILE ) | \ + GENERATOR=${GENERATOR} \ + CLIENT_LIBRARY_VERSION=${CURRENT_LIBRARY_VERSION} \ + envsubst >| ${GENERATED_CONFIG_FILE} + + echo "Configuration for generator ${GENERATOR} built." + + # Dump version number aside to have github action retrieving them later on + if [ -n "${GITHUB_OUTPUT}" -a -n "${CURRENT_LIBRARY_VERSION}" ]; + then + echo "$(echo $GENERATOR | tr - _ )_version=${CURRENT_LIBRARY_VERSION}" >> $GITHUB_OUTPUT + fi + + GENERATED_CONFIG_FILES="${GENERATED_CONFIG_FILES} ${GENERATED_CONFIG_FILE}" + fi +done + +${OPENAPI_GENERATOR_COMMAND} batch --clean ${GENERATED_CONFIG_FILES}