diff --git a/SpotifyAPI.Web/Clients/OAuthClient.cs b/SpotifyAPI.Web/Clients/OAuthClient.cs index 75e42f70..6c9819b3 100644 --- a/SpotifyAPI.Web/Clients/OAuthClient.cs +++ b/SpotifyAPI.Web/Clients/OAuthClient.cs @@ -16,8 +16,16 @@ namespace SpotifyAPI.Web public OAuthClient(SpotifyClientConfig config) : base(ValidateConfig(config)) { } public Task RequestToken(ClientCredentialsRequest request) + { + return RequestToken(request, API); + } + + public static Task RequestToken( + ClientCredentialsRequest request, IAPIConnector apiConnector + ) { Ensure.ArgumentNotNull(request, nameof(request)); + Ensure.ArgumentNotNull(apiConnector, nameof(apiConnector)); var form = new List> { @@ -30,7 +38,7 @@ namespace SpotifyAPI.Web { "Authorization", $"Basic {base64}"} }; - return API.Post(SpotifyUrls.OAuthToken, null, new FormUrlEncodedContent(form), headers); + return apiConnector.Post(SpotifyUrls.OAuthToken, null, new FormUrlEncodedContent(form), headers); } private static APIConnector ValidateConfig(SpotifyClientConfig config) diff --git a/SpotifyAPI.Web/Http/APIConnector.cs b/SpotifyAPI.Web/Http/APIConnector.cs index 0a540d45..ef76661c 100644 --- a/SpotifyAPI.Web/Http/APIConnector.cs +++ b/SpotifyAPI.Web/Http/APIConnector.cs @@ -192,10 +192,7 @@ namespace SpotifyAPI.Web.Http private async Task DoRequest(IRequest request) { - if (_authenticator != null) - { - await _authenticator.Apply(request, this).ConfigureAwait(false); - } + await ApplyAuthenticator(request).ConfigureAwait(false); _httpLogger?.OnRequest(request); IResponse response = await _httpClient.DoRequest(request).ConfigureAwait(false); _httpLogger?.OnResponse(response); @@ -203,10 +200,7 @@ namespace SpotifyAPI.Web.Http { response = await _retryHandler.HandleRetry(request, response, async (newRequest) => { - if (_authenticator != null) - { - await _authenticator.Apply(request, this).ConfigureAwait(false); - } + await ApplyAuthenticator(request).ConfigureAwait(false); var newResponse = await _httpClient.DoRequest(request).ConfigureAwait(false); _httpLogger?.OnResponse(newResponse); return newResponse; @@ -216,6 +210,14 @@ namespace SpotifyAPI.Web.Http return response; } + private async Task ApplyAuthenticator(IRequest request) + { + if (_authenticator != null && !request.Endpoint.IsAbsoluteUri) + { + await _authenticator.Apply(request, this).ConfigureAwait(false); + } + } + public Task SendRawRequest( Uri uri, HttpMethod method, diff --git a/SpotifyAPI.Web/Http/Authenticators/CredentialsAuthenticator.cs b/SpotifyAPI.Web/Http/Authenticators/CredentialsAuthenticator.cs new file mode 100644 index 00000000..99d92901 --- /dev/null +++ b/SpotifyAPI.Web/Http/Authenticators/CredentialsAuthenticator.cs @@ -0,0 +1,54 @@ +using System.Threading.Tasks; + +namespace SpotifyAPI.Web.Http +{ + /// + /// This Authenticator requests new credentials token on demand and stores them into memory. + /// It is unable to query user specifc details. + /// + public class CredentialsAuthenticator : IAuthenticator + { + private TokenResponse _token; + + /// + /// Initiate a new instance. The first token will be fetched when the first API call occurs + /// + /// + /// The ClientID, defined in a spotify application in your Spotify Developer Dashboard + /// + /// + /// The ClientID, defined in a spotify application in your Spotify Developer Dashboard + /// + public CredentialsAuthenticator(string clientId, string clientSecret) + { + Ensure.ArgumentNotNullOrEmptyString(clientId, nameof(clientId)); + Ensure.ArgumentNotNullOrEmptyString(clientSecret, nameof(clientSecret)); + + ClientId = clientId; + ClientSecret = clientSecret; + } + + /// + /// The ClientID, defined in a spotify application in your Spotify Developer Dashboard + /// + public string ClientId { get; } + + /// + /// The ClientID, defined in a spotify application in your Spotify Developer Dashboard + /// + public string ClientSecret { get; set; } + + public async Task Apply(IRequest request, IAPIConnector apiConnector) + { + Ensure.ArgumentNotNull(request, nameof(request)); + + if (_token == null || _token.IsExpired) + { + var tokenRequest = new ClientCredentialsRequest(ClientId, ClientSecret); + _token = await OAuthClient.RequestToken(tokenRequest, apiConnector).ConfigureAwait(false); + } + + request.Headers["Authorization"] = $"{_token.TokenType} {_token.AccessToken}"; + } + } +} diff --git a/SpotifyAPI.Web/Models/Response/Token.cs b/SpotifyAPI.Web/Models/Response/Token.cs index 79461ed0..45b1fda7 100644 --- a/SpotifyAPI.Web/Models/Response/Token.cs +++ b/SpotifyAPI.Web/Models/Response/Token.cs @@ -1,3 +1,4 @@ +using System; namespace SpotifyAPI.Web { public class TokenResponse @@ -5,5 +6,13 @@ namespace SpotifyAPI.Web public string AccessToken { get; set; } public string TokenType { get; set; } public int ExpiresIn { get; set; } + + /// + /// Auto-Initalized to UTC Now + /// + /// + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + public bool IsExpired { get => CreatedAt.AddSeconds(ExpiresIn) <= DateTime.UtcNow; } } }