-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathapp.js
More file actions
107 lines (92 loc) · 3.51 KB
/
Copy pathapp.js
File metadata and controls
107 lines (92 loc) · 3.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// Cryptohopper OAuth2 sample — Node.js 18+, no third-party HTTP client.
//
// Demonstrates the three-leg OAuth2 authorization-code flow against
// cryptohopper.com and one authenticated call against api.cryptohopper.com.
//
// Set CRYPTOHOPPER_CLIENT_ID and CRYPTOHOPPER_CLIENT_SECRET in your env
// (issue these from https://www.cryptohopper.com developer dashboard).
const express = require('express');
const crypto = require('crypto');
const app = express();
const port = process.env.PORT || 3000;
const CRYPTOHOPPER_HOST = 'https://www.cryptohopper.com';
const API_HOST = 'https://api.cryptohopper.com';
const clientID = process.env.CRYPTOHOPPER_CLIENT_ID || '';
const clientSecret = process.env.CRYPTOHOPPER_CLIENT_SECRET || '';
const redirectURI = `http://localhost:${port}/callback`;
if (!clientID || !clientSecret) {
console.warn(
'⚠ Set CRYPTOHOPPER_CLIENT_ID and CRYPTOHOPPER_CLIENT_SECRET in your\n' +
' environment before starting this sample. The /auth flow will fail\n' +
' without them.'
);
}
let token = null;
// '/' is the protected resource. It calls one API endpoint with the access
// token to demonstrate that the token works.
app.get('/', async (req, res, next) => {
if (!token?.access_token) {
return res.send(
`Unauthorized. Visit <a href="/auth">/auth</a> to log in.`
);
}
try {
// The Cryptohopper public API uses the `access-token` HTTP header,
// NOT the OAuth2-conventional `Authorization: Bearer`. The AWS API
// Gateway in front of the production API rejects Bearer with a
// SigV4 parser error. See:
// https://www.cryptohopper.com/api-documentation/how-the-api-works
const r = await fetch(`${API_HOST}/v1/hopper`, {
headers: { 'access-token': token.access_token },
});
const data = await r.json();
res.json(data);
} catch (err) {
next(err);
}
});
// '/auth' redirects the browser to cryptohopper.com for the user to grant
// authorization to this app.
app.get('/auth', (req, res) => {
const params = new URLSearchParams({
client_id: clientID,
redirect_uri: redirectURI,
response_type: 'code',
scope: 'read',
// Random CSRF state. In production you'd persist this per-session
// and verify it on the callback.
state: crypto.randomBytes(16).toString('hex'),
});
res.redirect(`${CRYPTOHOPPER_HOST}/oauth2/authorize?${params}`);
});
// '/callback' receives the OAuth `code` and exchanges it for an access token.
app.get('/callback', async (req, res, next) => {
if (!req.query.code) {
return res.status(400).send('missing authorization code.');
}
try {
const body = new URLSearchParams({
grant_type: 'authorization_code',
client_id: clientID,
client_secret: clientSecret,
redirect_uri: redirectURI,
code: req.query.code,
});
const r = await fetch(`${CRYPTOHOPPER_HOST}/oauth2/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString(),
});
if (!r.ok) {
const text = await r.text();
return res.status(502).send(`token exchange failed (${r.status}): ${text}`);
}
token = await r.json();
res.redirect('/');
} catch (err) {
next(err);
}
});
app.listen(port, () => {
console.log(`Server started at http://localhost:${port}`);
});