From ef60a9f1f262d37fbb9a3814fea5d7b04a33aeca Mon Sep 17 00:00:00 2001 From: Jonas Dellinger Date: Tue, 2 Jun 2020 16:34:24 +0200 Subject: [PATCH] Added TokenSwapAuth and console example for it #451 --- .../Example.TokenSwap/Client/Client.csproj | 13 + .../Example.TokenSwap/Client/Program.cs | 57 +++ .../Example.TokenSwap/Server/.gitignore | 1 + .../Example.TokenSwap/Server/index.js | 48 +++ .../Example.TokenSwap/Server/package.json | 14 + .../Example.TokenSwap/Server/yarn.lock | 389 ++++++++++++++++++ SpotifyAPI.Web/Clients/OAuthClient.cs | 45 +- .../Models/Request/TokenSwapRefreshRequest.cs | 20 + .../Models/Request/TokenSwapTokenRequest.cs | 20 + SpotifyAPI.sln | 26 ++ 10 files changed, 632 insertions(+), 1 deletion(-) create mode 100644 SpotifyAPI.Web.Examples/Example.TokenSwap/Client/Client.csproj create mode 100644 SpotifyAPI.Web.Examples/Example.TokenSwap/Client/Program.cs create mode 100644 SpotifyAPI.Web.Examples/Example.TokenSwap/Server/.gitignore create mode 100644 SpotifyAPI.Web.Examples/Example.TokenSwap/Server/index.js create mode 100644 SpotifyAPI.Web.Examples/Example.TokenSwap/Server/package.json create mode 100644 SpotifyAPI.Web.Examples/Example.TokenSwap/Server/yarn.lock create mode 100644 SpotifyAPI.Web/Models/Request/TokenSwapRefreshRequest.cs create mode 100644 SpotifyAPI.Web/Models/Request/TokenSwapTokenRequest.cs diff --git a/SpotifyAPI.Web.Examples/Example.TokenSwap/Client/Client.csproj b/SpotifyAPI.Web.Examples/Example.TokenSwap/Client/Client.csproj new file mode 100644 index 00000000..49665eb7 --- /dev/null +++ b/SpotifyAPI.Web.Examples/Example.TokenSwap/Client/Client.csproj @@ -0,0 +1,13 @@ + + + + + + + + + Exe + netcoreapp3.1 + + + diff --git a/SpotifyAPI.Web.Examples/Example.TokenSwap/Client/Program.cs b/SpotifyAPI.Web.Examples/Example.TokenSwap/Client/Program.cs new file mode 100644 index 00000000..8fe7cd54 --- /dev/null +++ b/SpotifyAPI.Web.Examples/Example.TokenSwap/Client/Program.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using SpotifyAPI.Web; +using SpotifyAPI.Web.Auth; + +namespace Client +{ + public class Program + { + private static readonly string clientId = Environment.GetEnvironmentVariable("SPOTIFY_CLIENT_ID"); + private static EmbedIOAuthServer _server; + + public static async Task Main(string[] args) + { + _server = new EmbedIOAuthServer(new Uri("http://localhost:5000/callback"), 5000); + await _server.Start(); + + _server.AuthorizationCodeReceived += OnAuthorizationCodeReceived; + + var request = new LoginRequest(_server.BaseUri, clientId, LoginRequest.ResponseType.Code) + { + Scope = new List { Scopes.UserReadEmail } + }; + + Uri uri = request.ToUri(); + try + { + BrowserUtil.Open(uri); + } + catch (Exception) + { + Console.WriteLine("Unable to open URL, manually open: {0}", uri); + } + + Console.ReadKey(); + } + + private static async Task OnAuthorizationCodeReceived(object sender, AuthorizationCodeResponse response) + { + var oauth = new OAuthClient(); + + var tokenRequest = new TokenSwapTokenRequest(new Uri("http://localhost:5001/swap"), response.Code); + var tokenResponse = await oauth.RequestToken(tokenRequest); + + Console.WriteLine($"We got an access token from server: {tokenResponse.AccessToken}"); + + var refreshRequest = new TokenSwapRefreshRequest( + new Uri("http://localhost:5001/refresh"), + tokenResponse.RefreshToken + ); + var refreshResponse = await oauth.RequestToken(refreshRequest); + + Console.WriteLine($"We got a new refreshed access token from server: {refreshResponse.AccessToken}"); + } + } +} diff --git a/SpotifyAPI.Web.Examples/Example.TokenSwap/Server/.gitignore b/SpotifyAPI.Web.Examples/Example.TokenSwap/Server/.gitignore new file mode 100644 index 00000000..c2658d7d --- /dev/null +++ b/SpotifyAPI.Web.Examples/Example.TokenSwap/Server/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/SpotifyAPI.Web.Examples/Example.TokenSwap/Server/index.js b/SpotifyAPI.Web.Examples/Example.TokenSwap/Server/index.js new file mode 100644 index 00000000..bba97378 --- /dev/null +++ b/SpotifyAPI.Web.Examples/Example.TokenSwap/Server/index.js @@ -0,0 +1,48 @@ +const express = require('express'); +const bodyParser = require('body-parser'); +const axios = require('axios'); +const { default: Axios } = require('axios'); + +const PORT = process.env.PORT || '5001'; +const SPOTIFY_CLIENT_SECRET = process.env.SPOTIFY_CLIENT_SECRET; +const SPOTIFY_CLIENT_ID = process.env.SPOTIFY_CLIENT_ID; +if (!SPOTIFY_CLIENT_SECRET || !SPOTIFY_CLIENT_ID) { + console.log("SPOTIFY_CLIENT_SECRET or SPOTIFY_CLIENT_ID environment variable is not set!"); + process.exit(1); +} + +const app = express(); +app.use(bodyParser.urlencoded({ extended: true })); + +app.post('/swap', async (req, res) => { + const { code } = req.body; + + const params = new URLSearchParams(); + params.append('grant_type', 'authorization_code'); + params.append('code', code); + params.append('redirect_uri', 'http://localhost:5000/callback'); + params.append('client_secret', SPOTIFY_CLIENT_SECRET); + params.append('client_id', SPOTIFY_CLIENT_ID); + + const { data } = await Axios.post('https://accounts.spotify.com/api/token', params); + + return res.send(data); +}); + +app.post('/refresh', async (req, res) => { + const { refresh_token } = req.body; + + const params = new URLSearchParams(); + params.append('grant_type', 'refresh_token'); + params.append('refresh_token', refresh_token); + params.append('client_secret', SPOTIFY_CLIENT_SECRET); + params.append('client_id', SPOTIFY_CLIENT_ID); + + const { data } = await Axios.post('https://accounts.spotify.com/api/token', params); + + return res.send(data); +}); + +app.listen(PORT, () => { + console.log(`Server listening on ${PORT} 🚀`); +}); diff --git a/SpotifyAPI.Web.Examples/Example.TokenSwap/Server/package.json b/SpotifyAPI.Web.Examples/Example.TokenSwap/Server/package.json new file mode 100644 index 00000000..355747a6 --- /dev/null +++ b/SpotifyAPI.Web.Examples/Example.TokenSwap/Server/package.json @@ -0,0 +1,14 @@ +{ + "name": "Server", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "axios": "^0.19.2", + "body-parser": "^1.19.0", + "express": "^4.17.1" + } +} diff --git a/SpotifyAPI.Web.Examples/Example.TokenSwap/Server/yarn.lock b/SpotifyAPI.Web.Examples/Example.TokenSwap/Server/yarn.lock new file mode 100644 index 00000000..2cecb34a --- /dev/null +++ b/SpotifyAPI.Web.Examples/Example.TokenSwap/Server/yarn.lock @@ -0,0 +1,389 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +accepts@~1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== + dependencies: + mime-types "~2.1.24" + negotiator "0.6.2" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + +axios@^0.19.2: + version "0.19.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" + integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== + dependencies: + follow-redirects "1.5.10" + +body-parser@1.19.0, body-parser@^1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" + integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== + dependencies: + bytes "3.1.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "1.7.2" + iconv-lite "0.4.24" + on-finished "~2.3.0" + qs "6.7.0" + raw-body "2.4.0" + type-is "~1.6.17" + +bytes@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== + +content-disposition@0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" + integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== + dependencies: + safe-buffer "5.1.2" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + +cookie@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" + integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@=3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +express@^4.17.1: + version "4.17.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" + integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== + dependencies: + accepts "~1.3.7" + array-flatten "1.1.1" + body-parser "1.19.0" + content-disposition "0.5.3" + content-type "~1.0.4" + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "~1.1.2" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.5" + qs "6.7.0" + range-parser "~1.2.1" + safe-buffer "5.1.2" + send "0.17.1" + serve-static "1.14.1" + setprototypeof "1.1.1" + statuses "~1.5.0" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +finalhandler@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== + dependencies: + debug "=3.1.0" + +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +http-errors@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" + integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +inherits@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + +mime-db@1.44.0: + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== + +mime-types@~2.1.24: + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== + dependencies: + mime-db "1.44.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +negotiator@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + +proxy-addr@~2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" + integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.9.1" + +qs@6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" + integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== + dependencies: + bytes "3.1.0" + http-errors "1.7.2" + iconv-lite "0.4.24" + unpipe "1.0.0" + +safe-buffer@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serve-static@1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + +"statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + +type-is@~1.6.17, type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= diff --git a/SpotifyAPI.Web/Clients/OAuthClient.cs b/SpotifyAPI.Web/Clients/OAuthClient.cs index e6e92ab2..849e9b35 100644 --- a/SpotifyAPI.Web/Clients/OAuthClient.cs +++ b/SpotifyAPI.Web/Clients/OAuthClient.cs @@ -9,8 +9,8 @@ namespace SpotifyAPI.Web { public class OAuthClient : APIClient, IOAuthClient { - public OAuthClient(IAPIConnector apiConnector) : base(apiConnector) { } public OAuthClient() : this(SpotifyClientConfig.CreateDefault()) { } + public OAuthClient(IAPIConnector apiConnector) : base(apiConnector) { } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062")] public OAuthClient(SpotifyClientConfig config) : base(ValidateConfig(config)) { } @@ -30,6 +30,49 @@ namespace SpotifyAPI.Web return RequestToken(request, API); } + public Task RequestToken(TokenSwapTokenRequest request) + { + return RequestToken(request, API); + } + + public Task RequestToken(TokenSwapRefreshRequest request) + { + return RequestToken(request, API); + } + + public static Task RequestToken( + TokenSwapRefreshRequest request, IAPIConnector apiConnector + ) + { + Ensure.ArgumentNotNull(request, nameof(request)); + Ensure.ArgumentNotNull(apiConnector, nameof(apiConnector)); + + var form = new List> + { + new KeyValuePair("refresh_token", request.RefreshToken) + }; + + return apiConnector.Post( + request.RefreshUri, null, new FormUrlEncodedContent(form) + ); + } + + public static Task RequestToken( + TokenSwapTokenRequest request, IAPIConnector apiConnector + ) + { + Ensure.ArgumentNotNull(request, nameof(request)); + Ensure.ArgumentNotNull(apiConnector, nameof(apiConnector)); + + var form = new List> + { + new KeyValuePair("code", request.Code) + }; + + return apiConnector.Post( + request.TokenUri, null, new FormUrlEncodedContent(form) + ); + } public static Task RequestToken( ClientCredentialsRequest request, IAPIConnector apiConnector diff --git a/SpotifyAPI.Web/Models/Request/TokenSwapRefreshRequest.cs b/SpotifyAPI.Web/Models/Request/TokenSwapRefreshRequest.cs new file mode 100644 index 00000000..8f0ddd9e --- /dev/null +++ b/SpotifyAPI.Web/Models/Request/TokenSwapRefreshRequest.cs @@ -0,0 +1,20 @@ +using System; + +namespace SpotifyAPI.Web +{ + public class TokenSwapRefreshRequest + { + public TokenSwapRefreshRequest(Uri refreshUri, string refreshToken) + { + Ensure.ArgumentNotNull(refreshUri, nameof(refreshUri)); + Ensure.ArgumentNotNullOrEmptyString(refreshToken, nameof(refreshToken)); + + RefreshUri = refreshUri; + RefreshToken = refreshToken; + } + + public string RefreshToken { get; } + + public Uri RefreshUri { get; } + } +} diff --git a/SpotifyAPI.Web/Models/Request/TokenSwapTokenRequest.cs b/SpotifyAPI.Web/Models/Request/TokenSwapTokenRequest.cs new file mode 100644 index 00000000..068dc7ac --- /dev/null +++ b/SpotifyAPI.Web/Models/Request/TokenSwapTokenRequest.cs @@ -0,0 +1,20 @@ +using System; + +namespace SpotifyAPI.Web +{ + public class TokenSwapTokenRequest + { + public TokenSwapTokenRequest(Uri tokenUri, string code) + { + Ensure.ArgumentNotNull(tokenUri, nameof(tokenUri)); + Ensure.ArgumentNotNullOrEmptyString(code, nameof(code)); + + TokenUri = tokenUri; + Code = code; + } + + public string Code { get; } + + public Uri TokenUri { get; } + } +} diff --git a/SpotifyAPI.sln b/SpotifyAPI.sln index 4fb854cf..b75d5325 100644 --- a/SpotifyAPI.sln +++ b/SpotifyAPI.sln @@ -26,6 +26,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.ASP", "SpotifyAPI.W EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.ASPBlazor", "SpotifyAPI.Web.Examples\Example.ASPBlazor\Example.ASPBlazor.csproj", "{258D1593-5B3B-485F-A71F-54D7FAB221FA}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Example.TokenSwap", "Example.TokenSwap", "{A2A74C61-9AEE-4802-9E29-518CB8053E6F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client", "SpotifyAPI.Web.Examples\Example.TokenSwap\Client\Client.csproj", "{D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -206,6 +210,26 @@ Global {258D1593-5B3B-485F-A71F-54D7FAB221FA}.Release|x64.Build.0 = Release|Any CPU {258D1593-5B3B-485F-A71F-54D7FAB221FA}.Release|x86.ActiveCfg = Release|Any CPU {258D1593-5B3B-485F-A71F-54D7FAB221FA}.Release|x86.Build.0 = Release|Any CPU + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}.Debug|ARM.ActiveCfg = Debug|Any CPU + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}.Debug|ARM.Build.0 = Debug|Any CPU + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}.Debug|ARM64.Build.0 = Debug|Any CPU + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}.Debug|x64.ActiveCfg = Debug|Any CPU + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}.Debug|x64.Build.0 = Debug|Any CPU + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}.Debug|x86.ActiveCfg = Debug|Any CPU + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}.Debug|x86.Build.0 = Debug|Any CPU + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}.Release|Any CPU.Build.0 = Release|Any CPU + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}.Release|ARM.ActiveCfg = Release|Any CPU + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}.Release|ARM.Build.0 = Release|Any CPU + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}.Release|ARM64.ActiveCfg = Release|Any CPU + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}.Release|ARM64.Build.0 = Release|Any CPU + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}.Release|x64.ActiveCfg = Release|Any CPU + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}.Release|x64.Build.0 = Release|Any CPU + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}.Release|x86.ActiveCfg = Release|Any CPU + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -216,6 +240,8 @@ Global {70D529B5-CEC0-4D3D-B50D-8ECF921DEC70} = {48A7DE65-29BB-409C-AC45-77F6586C0B15} {F48EADCE-546B-4B32-9954-CFDAF8AB6126} = {48A7DE65-29BB-409C-AC45-77F6586C0B15} {258D1593-5B3B-485F-A71F-54D7FAB221FA} = {48A7DE65-29BB-409C-AC45-77F6586C0B15} + {A2A74C61-9AEE-4802-9E29-518CB8053E6F} = {48A7DE65-29BB-409C-AC45-77F6586C0B15} + {D5C85F68-BE15-4CF3-A84B-98EEBCA584AD} = {A2A74C61-9AEE-4802-9E29-518CB8053E6F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {097062B8-0E87-43C8-BD98-61843A68BE6D}