diff --git a/client/src/components/windows/settings/IntegrationSettingsEditor.tsx b/client/src/components/windows/settings/IntegrationSettingsEditor.tsx
index ccfb08a..7bd4615 100644
--- a/client/src/components/windows/settings/IntegrationSettingsEditor.tsx
+++ b/client/src/components/windows/settings/IntegrationSettingsEditor.tsx
@@ -9,7 +9,7 @@ import DeleteIcon from '@material-ui/icons/Delete';
import * as serverApi from '../../../api';
import StoreLinkIcon, { ExternalStore } from '../../common/StoreLinkIcon';
import { v4 as genUuid } from 'uuid';
-import { getAuthToken } from '../../../lib/integration/spotify/spotify';
+import { testSpotify } from '../../../lib/integration/spotify/spotifyClientCreds';
let _ = require('lodash')
interface EditIntegrationProps {
@@ -113,7 +113,7 @@ function EditIntegration(props: EditIntegrationProps) {
onClick={() => { props.onDelete(); }}
>}
{!props.submitting && }
{props.submitting && }
diff --git a/client/src/lib/integration/spotify/spotify.tsx b/client/src/lib/integration/spotify/spotify.tsx
deleted file mode 100644
index 02aa5ca..0000000
--- a/client/src/lib/integration/spotify/spotify.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-export async function getAuthToken(clientId: string, clientSecret: string) {
- let requestOpts = {
- method: "POST",
- headers: { "Authorization": "Basic " + clientId + ":" + clientSecret },
- }
-
- const response = await fetch("https://accounts.spotify.com/api/token?grant_type=client_credentials", requestOpts)
- return await response.json();
-}
-
-export default {}
\ No newline at end of file
diff --git a/client/src/lib/integration/spotify/spotifyClientCreds.tsx b/client/src/lib/integration/spotify/spotifyClientCreds.tsx
new file mode 100644
index 0000000..641c106
--- /dev/null
+++ b/client/src/lib/integration/spotify/spotifyClientCreds.tsx
@@ -0,0 +1,14 @@
+export async function testSpotify() {
+ const requestOpts = {
+ method: 'GET',
+ };
+
+ const response = await fetch(
+ (process.env.REACT_APP_BACKEND || "") + '/spotifycc/v1/search?q=queens&type=artist',
+ requestOpts
+ );
+ if (!response.ok) {
+ throw new Error("Response to tag merge not OK: " + JSON.stringify(response));
+ }
+ console.log("Spotify response: ", response);
+}
\ No newline at end of file
diff --git a/server/app.ts b/server/app.ts
index d0d2682..09d0388 100644
--- a/server/app.ts
+++ b/server/app.ts
@@ -14,6 +14,7 @@ import { RegisterUser } from './endpoints/RegisterUser';
import * as endpointTypes from './endpoints/types';
import { sha512 } from 'js-sha512';
+import { useSpotifyClientCreds } from './integrations/spotifyClientCreds';
// For authentication
var passport = require('passport');
@@ -100,6 +101,9 @@ const SetupApp = (app: any, knex: Knex, apiBaseUrl: string) => {
}
}
+ // Set up integration proxies
+ useSpotifyClientCreds(app);
+
// Set up REST API endpoints
app.post(apiBaseUrl + api.CreateSongEndpoint, checkLogin(), _invoke(PostSong));
app.put(apiBaseUrl + api.ModifySongEndpoint, checkLogin(), _invoke(PutSong));
diff --git a/server/integrations/spotifyClientCreds.ts b/server/integrations/spotifyClientCreds.ts
index 7bcd887..5ebfd70 100644
--- a/server/integrations/spotifyClientCreds.ts
+++ b/server/integrations/spotifyClientCreds.ts
@@ -1,15 +1,65 @@
+const { createProxyMiddleware } = require('http-proxy-middleware');
+let axios = require('axios')
+let qs = require('querystring')
+
// The authorization token to use with the Spotify API.
// Will need to be refreshed once in a while.
-let authToken: string | null = null;
+var authToken: string | null = null;
+
+async function updateToken(clientId: string, clientSecret: string) {
+ if (authToken) { return; }
+
+ let buf = Buffer.from(clientId + ':' + clientSecret)
+ let encoded = buf.toString('base64');
+
+ let response = await axios.post(
+ 'https://accounts.spotify.com/api/token',
+ qs.stringify({ 'grant_type': 'client_credentials' }),
+ {
+ 'headers': {
+ 'Authorization': 'Basic ' + encoded,
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ }
+ }
+ );
+
+ authToken = (await response).data.access_token;
+}
-export async function getAuthToken(clientId: string, clientSecret: string) {
- let requestOpts = {
- method: "POST",
- headers: { "Authorization": "Basic " + clientId + ":" + clientSecret },
- }
+let onProxyReq = (proxyReq: any, req: any, res: any) => {
+ proxyReq.setHeader("Authorization", "Bearer " + req._access_token)
- const response = await fetch("https://accounts.spotify.com/api/token?grant_type=client_credentials", requestOpts)
- return await response.json();
+ console.log("Proxying request",
+ {
+ 'path': req.path,
+ 'originalUrl': req.originalUrl,
+ 'baseUrl': req.baseUrl,
+ },
+ {
+ 'path': proxyReq.path,
+ 'originalUrl': proxyReq.originalUrl,
+ 'baseUrl': req.baseUrl,
+ },
+ );
}
-export async function
\ No newline at end of file
+export function useSpotifyClientCreds(app: any) {
+ // First add a layer which creates a token and saves it in the request.
+ app.use((req: any, res: any, next: any) => {
+ updateToken('c3e5e605e7814cdf94cd86eeba6f4c4f', '5d870c84a3c34aa3a4cf803aa95cb96a')
+ .then(() => {
+ req._access_token = authToken;
+ next();
+ })
+ })
+ app.use(
+ '/spotifycc',
+ createProxyMiddleware({
+ target: 'https://api.spotify.com/',
+ changeOrigin: true,
+ onProxyReq: onProxyReq,
+ logLevel: 'debug',
+ pathRewrite: { '^/spotifycc': '' },
+ })
+ )
+}
\ No newline at end of file
diff --git a/server/package-lock.json b/server/package-lock.json
index 8b45277..7d49ca9 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -24,6 +24,14 @@
"xml2js": "^0.4.19"
},
"dependencies": {
+ "axios": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
+ "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
+ "requires": {
+ "follow-redirects": "1.5.10"
+ }
+ },
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
@@ -69,6 +77,14 @@
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.1.tgz",
"integrity": "sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw=="
},
+ "@types/http-proxy": {
+ "version": "1.17.4",
+ "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.4.tgz",
+ "integrity": "sha512-IrSHl2u6AWXduUaDLqYpt45tLVCtYv7o4Z0s1KghBCDgIIS9oW5K1H8mZG/A2CfeLdEa7rTd1ACOiHBc1EMT2Q==",
+ "requires": {
+ "@types/node": "*"
+ }
+ },
"@types/node": {
"version": "14.6.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.2.tgz",
@@ -304,11 +320,18 @@
"integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA=="
},
"axios": {
- "version": "0.19.2",
- "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
- "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz",
+ "integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==",
"requires": {
- "follow-redirects": "1.5.10"
+ "follow-redirects": "^1.10.0"
+ },
+ "dependencies": {
+ "follow-redirects": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz",
+ "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA=="
+ }
}
},
"balanced-match": {
@@ -1034,6 +1057,11 @@
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
},
+ "eventemitter3": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
+ },
"expand-brackets": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
@@ -1654,6 +1682,68 @@
"toidentifier": "1.0.0"
}
},
+ "http-proxy": {
+ "version": "1.18.1",
+ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
+ "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
+ "requires": {
+ "eventemitter3": "^4.0.0",
+ "follow-redirects": "^1.0.0",
+ "requires-port": "^1.0.0"
+ }
+ },
+ "http-proxy-middleware": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-1.0.6.tgz",
+ "integrity": "sha512-NyL6ZB6cVni7pl+/IT2W0ni5ME00xR0sN27AQZZrpKn1b+qRh+mLbBxIq9Cq1oGfmTc7BUq4HB77mxwCaxAYNg==",
+ "requires": {
+ "@types/http-proxy": "^1.17.4",
+ "http-proxy": "^1.18.1",
+ "is-glob": "^4.0.1",
+ "lodash": "^4.17.20",
+ "micromatch": "^4.0.2"
+ },
+ "dependencies": {
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
+ },
+ "micromatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
+ "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
+ "requires": {
+ "braces": "^3.0.1",
+ "picomatch": "^2.0.5"
+ }
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ }
+ }
+ },
"http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
@@ -2474,6 +2564,11 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz",
"integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA=="
},
+ "node-fetch": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
+ "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
+ },
"node-gyp": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
@@ -3052,6 +3147,11 @@
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
},
+ "querystring": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
+ },
"random-bytes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
@@ -3206,6 +3306,11 @@
}
}
},
+ "requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
+ },
"resolve": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
diff --git a/server/package.json b/server/package.json
index 762d412..a05b69b 100644
--- a/server/package.json
+++ b/server/package.json
@@ -8,11 +8,13 @@
"test": "ts-node node_modules/jasmine/bin/jasmine --config=test/jasmine.json"
},
"dependencies": {
+ "axios": "^0.21.0",
"body-parser": "^1.18.3",
"chai": "^4.2.0",
"chai-http": "^4.3.0",
"express": "^4.16.4",
"express-session": "^1.17.1",
+ "http-proxy-middleware": "^1.0.6",
"jasmine": "^3.5.0",
"js-sha512": "^0.8.0",
"knex": "^0.21.5",
@@ -20,11 +22,13 @@
"mssql": "^6.2.1",
"mysql": "^2.18.1",
"mysql2": "^2.1.0",
+ "node-fetch": "^2.6.1",
"nodemon": "^2.0.4",
"oracledb": "^5.0.0",
"passport": "^0.4.1",
"passport-local": "^1.0.0",
"pg": "^8.3.3",
+ "querystring": "^0.2.0",
"sqlite3": "^5.0.0",
"ts-node": "^8.10.2",
"typescript": "~3.7.2"