Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

General refactoring and consolidation #5

Merged
merged 18 commits into from
Dec 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
Platform-samples
================
# API usage samples

This is a public place for all sample projects related to Uphold Rest API.
This repository contains [minimal, self-contained, executable example projects](http://www.sscce.org/)
which demonstrate how to perform specific operations
using [Uphold's REST API](https://uphold.com/en/developer/api/documentation/).

> **Please note** that the examples contained here are optimized for clarity and simplicity.
> They are not meant to illustrate the coding style or structure that we'd recommend for production applications,
> but rather provide an entry point to start experimenting with Uphold's API.

## Usage

To try out each example, navigate to the corresponding folder and follow the instructions in the README.

The currently available examples are:

- Authentication
- [OAuth: Client credentials flow](authentication/oauth-client-credentials/)
- [OAuth: Authorization code flow](authentication/oauth-authorization-code/)
- [Personal Access Token (PAT)](authentication/personal-access-token/)
- Transactions
- [User-to-user transaction](transactions/user-to-user-transaction/)

## Contributing

Feel free to submit your own examples to this repository! Just make sure to follow the
[MRE](https://stackoverflow.com/help/minimal-reproducible-example)/[SSCCE](http://www.sscce.org/) principles,
and describe all steps to run the project in a README file similar to those of the current examples.

The contents of this repository are licensed under the [MIT license](LICENSE.txt).
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Authorization code flow
# Authorization code OAuth flow

This sample project demonstrates how a registered app can request authorization from Uphold users to perform actions on their behalf,
by using the [authorization code OAuth flow](https://oauth.net/2/grant-types/authorization-code/).
Expand Down
73 changes: 73 additions & 0 deletions authentication/oauth-authorization-code/authorization-code-flow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Dependencies.
*/

import axios from "axios";
import dotenv from "dotenv";
import path from "path";

// Dotenv configuration.
dotenv.config({ path: path.resolve() + "/.env" });

// Authentication credentials.
const auth = Buffer.from(process.env.CLIENT_ID + ":" + process.env.CLIENT_SECRET).toString("base64");

/**
* Format API error response for printing in console.
*/

function formatError(error) {
const responseStatus = `${error.response.status} (${error.response.statusText})`;

console.log(
`Request failed with HTTP status code ${responseStatus}`,
JSON.stringify({
url: error.config.url,
response: error.response.data
}, null, 2)
);

throw error;
}

/**
* Exchange OAuth authorization code for an access token.
*/

export async function getAccessToken(code) {
try {
const response = await axios.request({
method: "POST",
url: `${process.env.BASE_URL}/oauth2/token`,
data: `code=${code}&grant_type=authorization_code`,
headers: {
Authorization: `Basic ${auth}`,
"content-type": "application/x-www-form-urlencoded",
},
});

return response.data;
} catch (error) {
formatError(error);
}
}

/**
* Get data about the currently authenticated user.
*/

export async function getUserInfo(accessToken) {
try {
const response = await axios.request({
method: "GET",
url: `${process.env.BASE_URL}/v0/me`,
headers: {
Authorization: `Bearer ${accessToken}`,
},
});

return response.data;
} catch (error) {
formatError(error);
}
}
122 changes: 122 additions & 0 deletions authentication/oauth-authorization-code/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* Dependencies.
*/

import dotenv from "dotenv";
import express from "express";
import fs from "fs";
import https from "https";
import path from "path";
import { randomBytes } from "crypto";
import { getUserInfo, getAccessToken } from "./authorization-code-flow.js";

// Dotenv configuration.
dotenv.config({ path: path.resolve() + "/.env" });

// Server configuration.
const app = express();
const port = process.env.SERVER_PORT || 3000;
const state = randomBytes(8).toString('hex');

/**
* Main page.
*/

app.get("/", async (req, res) => {
// Compose the authorization URL. This assumes the `user:read` scope has been activated for this application.
const authorizationUrl = 'https://sandbox.uphold.com/authorize/'
+ process.env.CLIENT_ID
+ '?scope=user:read'
+ '&state=' + state;

res.send(
`<h1>Demo app server</h1>
<p>Please <a href="${authorizationUrl}">authorize this app</a> on Uphold's Sandbox.</p>`
);
});


/**
* Callback URL endpoint.
*/

app.get("/callback", async (req, res) => {
try {
// Show an error page if the code wasn't returned or the state doesn't match what we sent.
if (!req.query.code || req.query.state !== state) {
res.send(composeErrorPage(req.query, state));
}

// Exchange the short-lived authorization code for a long-lived access token.
const token = await getAccessToken(req.query.code);
console.log(`Successfully exchanged authorization code ${req.query.code} for access token:`, token.access_token);

// Test the new token by making an authenticated call to the API.
const userData = await getUserInfo(token.access_token);
console.log("Output from test API call:", userData);

res.send(
`<h1>Success!</h1>
<p>The OAuth authorization code has been successfully exchanged for an access token.</p>`
);
} catch (error) {
// Unexpected error.
res.send(composeErrorPage(error));
return;
}
});

/**
* Compose error web page.
*/

function composeErrorPage(data, state) {
let content = "<h1>Something went wrong.</h1>";

if (data.state && data.state !== state) {
content += `<p>The received state (${data.state}) does not match the expected value: ${state}.</p>`;
} else if (data instanceof Error) {
const errorData = {
message: data.message,
request: {
url: data.config.url,
method: data.config.method,
data: data.config.data,
headers: data.config.headers
}
};
content += "<p>Here are details of the error (see also the console log):</p>";
content += `<pre>${JSON.stringify(errorData, null, 4)}</pre>`;
} else if (Object.values(data).length) {
content += "<p>Here's what Uphold's servers returned:</p>";
content += `<pre>${JSON.stringify(data, null, 4)}</pre>`;
} else {
content += "<p>This page should be reached at the end of an OAuth authorization process.</p>";
content += "<p>Please confirm that you followed the steps in the README, and check the console log.</p>";
}

return content;
}

/*
* Check for the .env file.
*/

if (fs.existsSync('./.env') === false) {
console.log("Missing .env file. Please follow the steps described in the README.");
process.exit();
}

/**
* Run server.
*/

https
.createServer({
key: fs.readFileSync("./key.pem"),
cert: fs.readFileSync("./cert.pem"),
passphrase: "test",
}, app)
.listen(port, () => {
console.log(`Server running at https://localhost:${port}`);
});
Loading