diff --git a/rest-api/javascript/authentication/client-credential-flow/.env.example b/rest-api/javascript/authentication/client-credential-flow/.env.example index b5bc13d..aa352ab 100644 --- a/rest-api/javascript/authentication/client-credential-flow/.env.example +++ b/rest-api/javascript/authentication/client-credential-flow/.env.example @@ -1,5 +1,5 @@ # Base Url endpoint -BASE_URL='https://api-sandbox.uphold.com' +BASE_URL = 'https://api-sandbox.uphold.com' CLIENT_ID = '' CLIENT_SECRET = '' diff --git a/rest-api/javascript/authentication/client-credential-flow/README.md b/rest-api/javascript/authentication/client-credential-flow/README.md index 8e5946e..b141cc0 100644 --- a/rest-api/javascript/authentication/client-credential-flow/README.md +++ b/rest-api/javascript/authentication/client-credential-flow/README.md @@ -1,6 +1,7 @@ # Client credentials flow -This sample project demonstrates how to authenticate in the Uphold API using the client credentials flow. For further background, please refer to the [API documentation](https://uphold.com/en/developer/api/documentation). +This sample project demonstrates how to authenticate in the Uphold API using the [OAuth 2.0 client credentials](https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/) flow. +For further background, please refer to the [API documentation](https://uphold.com/en/developer/api/documentation). ## Summary @@ -13,7 +14,7 @@ This sample project performs the following actions: **Important notice:** In Uphold's production environment, client credentials authentication is only available for **business accounts**, and requires manual approval from Uphold. Please [contact Uphold](mailto:developer@uphold.com) to obtain this permission. -For applications that use the sandbox environment, as is the case with this demo project, those requirements can be skipped. +For requests made in the sandbox environment, as is the case with this demo project, those requirements can be skipped. ## Requirements diff --git a/rest-api/javascript/authentication/client-credential-flow/cc-flow.js b/rest-api/javascript/authentication/client-credential-flow/cc-flow.js index 584440a..287cdb9 100644 --- a/rest-api/javascript/authentication/client-credential-flow/cc-flow.js +++ b/rest-api/javascript/authentication/client-credential-flow/cc-flow.js @@ -12,11 +12,13 @@ const { encode } = b64Pkg; /** * Dotenv configuration. */ + dotenv.config({ path: path.resolve() + "/.env" }); /** * Get Token. */ + export async function getToken() { // Base64-encoded authentication credentials const auth = encode(process.env.CLIENT_ID + ":" + process.env.CLIENT_SECRET); @@ -49,6 +51,7 @@ export async function getToken() { /** * Get assets. */ + export async function getAssets(token) { try { const response = await axios.get(`${process.env.BASE_URL}/v0/assets`, { diff --git a/rest-api/javascript/authentication/personal-access-token/.env.example b/rest-api/javascript/authentication/personal-access-token/.env.example new file mode 100644 index 0000000..0b3f22a --- /dev/null +++ b/rest-api/javascript/authentication/personal-access-token/.env.example @@ -0,0 +1,6 @@ +# Base Url endpoint +BASE_URL = 'https://api-sandbox.uphold.com' + +USERNAME = 'my_registration_email@domain.com' +PASSWORD = 'my_password_in_clear_text' +PAT_DESCRIPTION = 'Put a Description Here' diff --git a/rest-api/javascript/authentication/personal-access-token/.gitignore b/rest-api/javascript/authentication/personal-access-token/.gitignore new file mode 100644 index 0000000..28a76fd --- /dev/null +++ b/rest-api/javascript/authentication/personal-access-token/.gitignore @@ -0,0 +1,3 @@ +.env +node_modules/ +package-lock.json diff --git a/rest-api/javascript/authentication/personal-access-token/README.md b/rest-api/javascript/authentication/personal-access-token/README.md new file mode 100644 index 0000000..d8118ea --- /dev/null +++ b/rest-api/javascript/authentication/personal-access-token/README.md @@ -0,0 +1,29 @@ +# Client credentials PAT + +This sample project demonstrates how to authenticate in the Uphold API using a Personal Access Token (PAT). +For further background, please refer to the [API documentation](https://uphold.com/en/developer/api/documentation) + +## Summary + +**Ideal for scripts**, automated tools and command-line programs which remain under the control of your personal Uphold account. + +This sample project performs the following actions: + +- Create a new PAT +- List all created PAT's associated to this account + +**Important notice:** If the account has two-factor authentication (2FA) active, a one-time-password (OTP) must be passed when creating PATs. +In the Sandbox environment, the special OTP value `000000` can be passed for convenience, as can be seen in the `index.js` file. In production, you would need to use an actual TOTP code provided by your chosen authenticator app (e.g. Google Authenticator). + +## Requirements + +- `node` v13.14.0 + + +## Setup + +- run `npm install` (or `yarn install`) +- create a `.env` file based on the `.env.example` file, and populate it with the required data + +## Run + +- run `node index.js` diff --git a/rest-api/javascript/authentication/personal-access-token/cc-pat.js b/rest-api/javascript/authentication/personal-access-token/cc-pat.js new file mode 100644 index 0000000..a93f387 --- /dev/null +++ b/rest-api/javascript/authentication/personal-access-token/cc-pat.js @@ -0,0 +1,109 @@ +/** + * Dependencies. + */ + +import axios from "axios"; +import b64Pkg from "js-base64"; +import dotenv from "dotenv"; +import path from "path"; + +const { encode } = b64Pkg; +dotenv.config({ path: path.resolve() + "/.env" }); + +/** + * Get list of authentication methods, using basic authentication (username and password). + */ + +export async function getAuthenticationMethods() { + // Base64-encoded authentication credentials + const auth = encode(process.env.USERNAME + ":" + process.env.PASSWORD); + + // Set GET options for Axios + const options = { + method: "GET", + headers: { + Authorization: "Basic " + auth, + }, + url: `${process.env.BASE_URL}/v0/me/authentication_methods`, + }; + + const data = axios(options) + .then((response) => { + return response.data; + }) + .catch((error) => { + error.response.data.errors + ? console.log(JSON.stringify(error.response.data.errors, null, 2)) + : console.log(JSON.stringify(error, null, 2)); + throw error; + }); + + return data; +} + +/** + * Create a Personal Access Token (PAT), using basic authentication (username and password). + * The time-based one-time password (TOTP) parameter + * is typically provided by an OTP application, e.g. Google Authenticator. + */ + +export async function createNewPAT(totp) { + // Base64-encoded authentication credentials + const auth = encode(process.env.USERNAME + ":" + process.env.PASSWORD); + + let headers = { + "Authorization": "Basic " + auth, + "content-type": "application/json", + }; + + // Set OTP headers if the totp parameter is passed + const otpHeaders = { + "OTP-Method-Id": totp.OTPMethodId, + "OTP-Token": totp.OTPToken, + }; + + if (totp.OTPMethodId) { + headers = { ...headers, ...otpHeaders }; + } + + // Set post options for axios + const options = { + method: "POST", + headers, + data: { + description: process.env.PAT_DESCRIPTION, + }, + url: `${process.env.BASE_URL}/v0/me/tokens`, + }; + + const data = axios(options) + .then((response) => { + return response.data; + }) + .catch((error) => { + error.response.data.errors + ? console.log(JSON.stringify(error.response.data.errors, null, 2)) + : console.log(JSON.stringify(error, null, 2)); + throw error; + }); + + return data; +} + +/** + * Get list of Personal Access Tokens (PATs), using a bearer token (client credentials. + */ + +export async function getMyPATs(accessToken) { + try { + const r = await axios.get(`${process.env.BASE_URL}/v0/me/tokens`, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }); + return r.data; + } catch (error) { + console.log(JSON.stringify(error, null, 2)); + throw error; + } +} diff --git a/rest-api/javascript/authentication/personal-access-token/index.js b/rest-api/javascript/authentication/personal-access-token/index.js new file mode 100644 index 0000000..35e8711 --- /dev/null +++ b/rest-api/javascript/authentication/personal-access-token/index.js @@ -0,0 +1,40 @@ +/** + * Dependencies. + */ + +import { + createNewPAT, + getAuthenticationMethods, + getMyPATs, +} from "./cc-pat.js"; + +(async () => { + // Get list of authentication methods + const authMethods = await getAuthenticationMethods(); + + // In the Sandbox environment, the special OTP value `000000` can be passed for convenience. + const totp = { + OTPMethodId: "", + OTPToken: "000000", + }; + + // Try to determine if the authenticated account has two-factor authentication (2FA) active, + // as that will require a one-time password (OTP) for the PAT creation request. + const totpCheck = + authMethods != null ? authMethods.find((x) => x.type === "totp") : null; + if (totpCheck) { + totp.OTPMethodId = totpCheck.id; + } + + // Create a PAT + const newPAT = await createNewPAT(totp); + + if (newPAT.accessToken) { + console.log("New Personal Access Token (PAT) created with success"); + console.debug(newPAT.accessToken); + + // Use the newly created PAT to list all PATs for this account + console.log("List of available PATs"); + console.log(await getMyPATs(newPAT.accessToken)); + } +})(); diff --git a/rest-api/javascript/authentication/personal-access-token/package-lock.json b/rest-api/javascript/authentication/personal-access-token/package-lock.json new file mode 100644 index 0000000..67e339e --- /dev/null +++ b/rest-api/javascript/authentication/personal-access-token/package-lock.json @@ -0,0 +1,41 @@ +{ + "name": "uphold-ccp", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "axios": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", + "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==", + "requires": { + "follow-redirects": "^1.10.0" + } + }, + "btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==" + }, + "dotenv": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", + "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" + }, + "follow-redirects": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", + "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" + }, + "js-base64": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.6.0.tgz", + "integrity": "sha512-wVdUBYQeY2gY73RIlPrysvpYx+2vheGo8Y1SNQv/BzHToWpAZzJU7Z6uheKMAe+GLSBig5/Ps2nxg/8tRB73xg==" + }, + "qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" + } + } +} diff --git a/rest-api/javascript/authentication/personal-access-token/package.json b/rest-api/javascript/authentication/personal-access-token/package.json new file mode 100644 index 0000000..45fd771 --- /dev/null +++ b/rest-api/javascript/authentication/personal-access-token/package.json @@ -0,0 +1,21 @@ +{ + "name": "uphold-ccp", + "version": "0.0.1", + "description": "Uphold rest Api test client credential PAT example", + "license": "MIT", + "main": "index.js", + "type": "module", + "dependencies": { + "axios": "^0.20.0", + "btoa": "^1.2.1", + "dotenv": "^8.2.0", + "js-base64": "^3.5.2", + "qs": "^6.9.4" + }, + "engines": { + "node": ">=13.14" + }, + "scripts": { + "run": "node index.js " + } +} diff --git a/rest-api/javascript/authentication/personal-access-token/yarn.lock b/rest-api/javascript/authentication/personal-access-token/yarn.lock new file mode 100644 index 0000000..494b63e --- /dev/null +++ b/rest-api/javascript/authentication/personal-access-token/yarn.lock @@ -0,0 +1,35 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +axios@^0.20.0: + version "0.20.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.20.0.tgz#057ba30f04884694993a8cd07fa394cff11c50bd" + integrity sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA== + dependencies: + follow-redirects "^1.10.0" + +btoa@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.2.1.tgz#01a9909f8b2c93f6bf680ba26131eb30f7fa3d73" + integrity sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g== + +dotenv@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" + integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== + +follow-redirects@^1.10.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" + integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== + +js-base64@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.5.2.tgz#3cc800e4f10812b55fb5ec53e7cabaef35dc6d3c" + integrity sha512-VG2qfvV5rEQIVxq9UmAVyWIaOdZGt9M16BLu8vFkyWyhv709Hyg4nKUb5T+Ru+HmAr9RHdF+kQDKAhbJlcdKeQ== + +qs@^6.9.4: + version "6.9.4" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" + integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==