diff --git a/Acumatica.RESTClient/AuthApi/AuthApi.cs b/Acumatica.RESTClient/AuthApi/AuthApi.cs index 888852a4..e5c42c7d 100644 --- a/Acumatica.RESTClient/AuthApi/AuthApi.cs +++ b/Acumatica.RESTClient/AuthApi/AuthApi.cs @@ -31,7 +31,7 @@ public async static Task RefreshAccessTokenAsync(this ApiClient client, string c { if (client == null || string.IsNullOrEmpty(client.Token?.Refresh_token)) ThrowMissingParameter(nameof(RefreshAccessToken), "Refresh_Token"); - + var time = DateTime.UtcNow; HttpResponseMessage response = await client!.CallApiAsync( resourcePath: "/identity/connect/token", method: HttpMethod.Post, @@ -51,6 +51,7 @@ public async static Task RefreshAccessTokenAsync(this ApiClient client, string c response.EnsureSuccessStatusCode(); client.Token = await DeserializeAsync(response).ConfigureAwait(false); + client.Token?.SetTokenObtainedDT(time); } /// /// Receives access token for OAuth 2.0 authentication (Resource owner password credentials flow) @@ -84,6 +85,7 @@ public async static Task ReceiveAccessTokenAsync( OAuthScope scope, CancellationToken cancellationToken = default) { + var time = DateTime.UtcNow; HttpResponseMessage response = await client.CallApiAsync( resourcePath: "identity/connect/token", method: HttpMethod.Post, @@ -103,7 +105,8 @@ public async static Task ReceiveAccessTokenAsync( await VerifyResponseAsync(client, response, nameof(ReceiveAccessTokenAsync)).ConfigureAwait(false); - client.Token = await DeserializeAsync(response).ConfigureAwait(false); + client.Token = await DeserializeAsync(response).ConfigureAwait(false); + client.Token?.SetTokenObtainedDT(time); } /// @@ -193,6 +196,7 @@ public static async Task ReceiveAccessTokenAuthCodeAsync( string code, CancellationToken cancellationToken = default) { + var time = DateTime.UtcNow; HttpResponseMessage response = await client.CallApiAsync( resourcePath: "/identity/connect/token", method: HttpMethod.Post, @@ -212,7 +216,8 @@ public static async Task ReceiveAccessTokenAuthCodeAsync( await VerifyResponseAsync(client, response, "RequestToken").ConfigureAwait(false); - client.Token = await DeserializeAsync(response).ConfigureAwait(false); + client.Token = await DeserializeAsync(response).ConfigureAwait(false); + client.Token?.SetTokenObtainedDT(time); } #endregion @@ -398,4 +403,4 @@ private static string PrepareScopeParameter(OAuthScope scope) #endregion } -} +} \ No newline at end of file diff --git a/Acumatica.RESTClient/AuthApi/Model/Token.cs b/Acumatica.RESTClient/AuthApi/Model/Token.cs index 0ef4e0f4..47611ee4 100644 --- a/Acumatica.RESTClient/AuthApi/Model/Token.cs +++ b/Acumatica.RESTClient/AuthApi/Model/Token.cs @@ -1,6 +1,6 @@ -using System.Runtime.Serialization; - using Newtonsoft.Json; +using System; +using System.Runtime.Serialization; namespace Acumatica.RESTClient.AuthApi.Model { @@ -22,6 +22,7 @@ public Token( Refresh_token = refreshToken; Scope = scope; Token_type = token_type; + SetTokenObtainedDT(); } [DataMember(Name = "access_token", EmitDefaultValue = false)] @@ -39,6 +40,9 @@ public Token( [DataMember(Name = "token_type", EmitDefaultValue = false)] public string? Token_type { get; set; } + public DateTime ObtainedAtUTC{ get; private set; } + + public bool IsValid { get => isValid(); } /// /// Returns the JSON string presentation of the object /// @@ -47,6 +51,16 @@ public virtual string ToJson() { return JsonConvert.SerializeObject(this, Formatting.Indented); } + + public virtual void SetTokenObtainedDT(DateTime? dt = null) + { + ObtainedAtUTC = dt ?? DateTime.UtcNow; + } + + private bool isValid() + { + return !string.IsNullOrEmpty(Access_token) && (DateTime.UtcNow - ObtainedAtUTC).TotalSeconds < Convert.ToUInt32(Expires_in); + } } } diff --git a/Acumatica.RESTClient/Client/ApiClient.cs b/Acumatica.RESTClient/Client/ApiClient.cs index 751adb56..8d2c1d23 100644 --- a/Acumatica.RESTClient/Client/ApiClient.cs +++ b/Acumatica.RESTClient/Client/ApiClient.cs @@ -249,7 +249,7 @@ public async Task CallApiAsync( public bool HasToken() { - return Token != null; + return Token?.IsValid == true; } public void Dispose() {