diff --git a/client/src/components/windows/settings/IntegrationSettingsEditor.tsx b/client/src/components/windows/settings/IntegrationSettingsEditor.tsx index 7bd4615..18df746 100644 --- a/client/src/components/windows/settings/IntegrationSettingsEditor.tsx +++ b/client/src/components/windows/settings/IntegrationSettingsEditor.tsx @@ -13,6 +13,7 @@ import { testSpotify } from '../../../lib/integration/spotify/spotifyClientCreds let _ = require('lodash') interface EditIntegrationProps { + upstreamId: number | null, integration: serverApi.IntegrationDetailsResponse, original: serverApi.IntegrationDetailsResponse, editing: boolean, @@ -112,8 +113,8 @@ function EditIntegration(props: EditIntegrationProps) { {!props.submitting && { props.onDelete(); }} >} - {!props.submitting && } {props.submitting && } @@ -231,6 +232,7 @@ export default function IntegrationSettingsEditor(props: {}) { {editors === null && } {editors && <> {editors.map((state: EditorState) => { } // Set up integration proxies - useSpotifyClientCreds(app); + app.use('/integrations', checkLogin(), createIntegrations(knex)); // Set up REST API endpoints app.post(apiBaseUrl + api.CreateSongEndpoint, checkLogin(), _invoke(PostSong)); diff --git a/server/integrations/integrations.ts b/server/integrations/integrations.ts new file mode 100644 index 0000000..1b34416 --- /dev/null +++ b/server/integrations/integrations.ts @@ -0,0 +1,110 @@ +import Knex from "knex"; +import { IntegrationType } from "../../client/src/api"; + +const { createProxyMiddleware } = require('http-proxy-middleware'); +let axios = require('axios') +let qs = require('querystring') + +async function getSpotifyCCAuthToken(clientId: string, clientSecret: string) { + console.log("Details: ", clientId, clientSecret); + + 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' + } + } + ); + + if (response.status != 200) { + throw new Error("Unable to get a Spotify auth token.") + } + + return (await response).data.access_token; +} + +export function createIntegrations(knex: Knex) { + // This will enable the app to redirect requests like: + // /integrations/5/v1/search?q=query + // To the external API represented by integration 5, e.g. for spotify: + // https://api.spotify.com/v1/search?q=query + // Requests need to already have a .user.id set. + + let proxySpotifyCC = createProxyMiddleware({ + target: 'https://api.spotify.com/', + changeOrigin: true, + logLevel: 'debug', + pathRewrite: (path: string, req: any) => { + // Remove e.g. "/integrations/5" + console.log("Rewrite URL:", path); + return path.replace(/^\/integrations\/[0-9]+/, ''); + } + }); + + // In the first layer, retrieve integration details and save details + // in the request. + return async (req: any, res: any, next: any) => { + // Determine the integration to use. + req._integrationId = parseInt(req.url.match(/^\/([0-9]+)/)[1]); + console.log("URL:", req.url, 'match:', req._integrationId) + if (!req._integrationId) { + res.status(400).send({ reason: "An integration ID should be provided in the URL." }); + return; + } + req._integration = (await knex.select(['id', 'name', 'type', 'details']) + .from('integrations') + .where({ 'user': req.user.id, 'id': req._integrationId }))[0]; + if (!req._integration) { + res.status(404).send(); + return; + } + + req._integration.details = JSON.parse(req._integration.details); + + switch (req._integration.type) { + case IntegrationType.SpotifyClientCredentials: { + console.log("Integration: ", req._integration) + // FIXME: persist the token + req._access_token = await getSpotifyCCAuthToken( + req._integration.details.clientId, + req._integration.details.clientSecret, + ) + if (!req._access_token) { + res.status(500).send({ reason: "Unable to get Spotify auth token." }) + } + req.headers["Authorization"] = "Bearer " + req._access_token; + return proxySpotifyCC(req, res, next); + } + default: { + res.status(500).send({ reason: "Unsupported integration type " + req._integration.type }) + } + } + }; + + + + // // 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/integrations/spotifyClientCreds.ts b/server/integrations/spotifyClientCreds.ts deleted file mode 100644 index 5ebfd70..0000000 --- a/server/integrations/spotifyClientCreds.ts +++ /dev/null @@ -1,65 +0,0 @@ -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. -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; -} - -let onProxyReq = (proxyReq: any, req: any, res: any) => { - proxyReq.setHeader("Authorization", "Bearer " + req._access_token) - - console.log("Proxying request", - { - 'path': req.path, - 'originalUrl': req.originalUrl, - 'baseUrl': req.baseUrl, - }, - { - 'path': proxyReq.path, - 'originalUrl': proxyReq.originalUrl, - 'baseUrl': req.baseUrl, - }, - ); -} - -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