Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 14 additions & 18 deletions Gotrue/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -518,14 +518,18 @@ public async Task<Session> SetSession(string accessToken, string refreshToken, b
NotifyAuthStateChange(SignedIn);
return CurrentSession;
}


var iat = payload.IssuedAt;
var exp = payload.ValidTo;
var expiresIn = (long)(exp - iat).TotalSeconds;

CurrentSession = new Session
{
AccessToken = accessToken,
RefreshToken = refreshToken,
TokenType = "bearer",
ExpiresIn = payload.Expiration!.Value,
User = await _api.GetUser(accessToken)
ExpiresIn = expiresIn,
User = await _api.GetUser(accessToken),
};

NotifyAuthStateChange(SignedIn);
Expand Down Expand Up @@ -574,7 +578,7 @@ public async Task<Session> SetSession(string accessToken, string refreshToken, b
ExpiresIn = long.Parse(expiresIn),
RefreshToken = refreshToken,
TokenType = tokenType,
User = user
User = user,
};

if (storeSession)
Expand All @@ -595,14 +599,6 @@ public async Task<Session> SetSession(string accessToken, string refreshToken, b
if (CurrentSession == null)
return null;

// Check to see if the session has expired. If so go ahead and destroy it.
if (CurrentSession != null && CurrentSession.Expired())
{
_debugNotification?.Log($"Loaded session has expired");
DestroySession();
return null;
}

// If we aren't online, we can't refresh the token
if (!Online)
{
Expand Down Expand Up @@ -691,6 +687,9 @@ private void DestroySession()
/// <inheritdoc />
public async Task RefreshToken(string accessToken, string refreshToken)
{
if (!Online)
throw new GotrueException("Only supported when online", Offline);

if (string.IsNullOrEmpty(accessToken) || string.IsNullOrEmpty(refreshToken))
throw new GotrueException("No token provided", NoSessionFound);

Expand All @@ -712,9 +711,6 @@ public async Task RefreshToken()
if (CurrentSession == null || string.IsNullOrEmpty(CurrentSession?.AccessToken) || string.IsNullOrEmpty(CurrentSession?.RefreshToken))
throw new GotrueException("No current session.", NoSessionFound);

if (CurrentSession!.Expired())
throw new GotrueException("Session expired", ExpiredRefreshToken);

var result = await _api.RefreshAccessToken(CurrentSession.AccessToken!, CurrentSession.RefreshToken!);

if (result == null || string.IsNullOrEmpty(result.AccessToken))
Expand Down Expand Up @@ -791,7 +787,7 @@ public void Shutdown()

if (result == null || string.IsNullOrEmpty(result.AccessToken))
throw new GotrueException("Could not verify MFA.", MfaChallengeUnverified);

var session = new Session
{
AccessToken = result.AccessToken,
Expand Down Expand Up @@ -835,14 +831,14 @@ public void Shutdown()

if (result == null || string.IsNullOrEmpty(result.AccessToken))
throw new GotrueException("Could not verify MFA.", MfaChallengeUnverified);

var session = new Session
{
AccessToken = result.AccessToken,
RefreshToken = result.RefreshToken,
TokenType = "bearer",
ExpiresIn = result.ExpiresIn,
User = result.User
User = result.User,
};

UpdateSession(session);
Expand Down
14 changes: 1 addition & 13 deletions Gotrue/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class Session
public string? AccessToken { get; set; }

/// <summary>
/// The number of seconds until the token expires (since it was issued). Returned when a login is confirmed.
/// The number of seconds until the access token expires (since it was issued). Returned when a login is confirmed.
/// </summary>
[JsonProperty("expires_in")]
public long ExpiresIn { get; set; }
Expand Down Expand Up @@ -49,17 +49,5 @@ public class Session

[JsonProperty("created_at")]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;

/// <summary>
/// The expiration date of this session, in UTC time.
/// </summary>
/// <returns></returns>
public DateTime ExpiresAt() => new DateTimeOffset(CreatedAt).AddSeconds(ExpiresIn).ToUniversalTime().DateTime;

/// <summary>
/// Returns true if the session has expired
/// </summary>
/// <returns></returns>
public bool Expired() => ExpiresAt() < DateTime.UtcNow;
}
}
72 changes: 32 additions & 40 deletions Gotrue/TokenRefresh.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Threading;
using Supabase.Gotrue.Exceptions;
using Supabase.Gotrue.Interfaces;
using static Supabase.Gotrue.Constants.AuthState;

Expand Down Expand Up @@ -46,10 +48,11 @@ public void ManageAutoRefresh(IGotrueClient<User, Session> sender, Constants.Aut
case SignedIn:
if (Debug)
_client.Debug("Refresh Timer started");
InitRefreshTimer();
CreateNewTimer();
// Turn on auto-refresh timer
break;
case SignedOut:
case Shutdown:
if (Debug)
_client.Debug("Refresh Timer stopped");
_refreshTimer?.Dispose();
Expand All @@ -58,32 +61,17 @@ public void ManageAutoRefresh(IGotrueClient<User, Session> sender, Constants.Aut
case UserUpdated:
if (Debug)
_client.Debug("Refresh Timer restarted");
InitRefreshTimer();
CreateNewTimer();
break;
case PasswordRecovery:
// Doesn't affect auto refresh
break;
case TokenRefreshed:
case MfaChallengeVerified:
// Doesn't affect auto refresh
break;
case Shutdown:
if (Debug)
_client.Debug("Refresh Timer stopped");
_refreshTimer?.Dispose();
// Turn off auto-refresh timer
break;
default: throw new ArgumentOutOfRangeException(nameof(stateChanged), stateChanged, null);
}
}

/// <summary>
/// Sets up the auto-refresh timer
/// </summary>
private void InitRefreshTimer()
{
CreateNewTimer();
}

/// <summary>
/// The timer calls this method at the configured interval to refresh the token.
///
Expand Down Expand Up @@ -119,29 +107,21 @@ private async void HandleRefreshTimerTick(object _)
/// </summary>
private void CreateNewTimer()
{
if (_client.CurrentSession == null || _client.CurrentSession.ExpiresIn == default)
if (_client.CurrentSession == null)
{
if (Debug)
_client.Debug($"No session, refresh timer not started");
return;
}

if (_client.CurrentSession.Expired())
{
if (Debug)
_client.Debug($"Token expired, signing out");
_client.NotifyAuthStateChange(SignedOut);
return;
}

try
{
TimeSpan interval = GetInterval();
TimeSpan refreshDueTime = GetSecondsUntilNextRefresh();
_refreshTimer?.Dispose();
_refreshTimer = new Timer(HandleRefreshTimerTick, null, interval, Timeout.InfiniteTimeSpan);
_refreshTimer = new Timer(HandleRefreshTimerTick, null, refreshDueTime, Timeout.InfiniteTimeSpan);

if (Debug)
_client.Debug($"Refresh timer scheduled {interval.TotalMinutes} minutes");
_client.Debug($"Refresh timer scheduled {refreshDueTime.TotalMinutes} minutes");
}
catch (Exception e)
{
Expand All @@ -151,23 +131,35 @@ private void CreateNewTimer()
}

/// <summary>
/// Interval should be t - (1/5(n)) (i.e. if session time (t) 3600s, attempt refresh at 2880s or 720s (1/5) seconds before expiration)
/// Returns remaining seconds until the access token should be refreshed.
/// Interval is calculated as:<code>t - (1/5(n))</code> (i.e. if session time (t) 3600s, attempt refresh at 2880s or 720s (1/5) seconds before expiration).
/// <remarks>
/// - The maximum refresh wait time is clamped to <see cref="ClientOptions.MaximumRefreshWaitTime"/>
/// </remarks>
/// <remarks>
/// - If the access token is expired it will refresh immediately.
/// </remarks>
/// </summary>
private TimeSpan GetInterval()
/// <returns>The remaining seconds until the token should be refreshed</returns>
private TimeSpan GetSecondsUntilNextRefresh()
{
if (_client.CurrentSession == null || _client.CurrentSession.ExpiresIn == default)
if (_client.CurrentSession is null || _client.CurrentSession.AccessToken == null)
{
return TimeSpan.Zero;
}

var interval = (long)Math.Floor(_client.CurrentSession.ExpiresIn * 4.0f / 5.0f);

var timeoutSeconds = Convert.ToInt64((_client.CurrentSession.CreatedAt.AddSeconds(interval) - DateTime.UtcNow).TotalSeconds);
var interval = (long)Math.Floor(_client.CurrentSession.ExpiresIn * 4.0 / 5.0);
var refreshAt = _client.CurrentSession.CreatedAt.AddSeconds(interval);

if (timeoutSeconds > _client.Options.MaximumRefreshWaitTime)
timeoutSeconds = _client.Options.MaximumRefreshWaitTime;

return TimeSpan.FromSeconds(timeoutSeconds);
var secondsUntilNextRefresh = Convert.ToInt64((refreshAt - DateTime.UtcNow).TotalSeconds);

if (secondsUntilNextRefresh < 0)
return TimeSpan.Zero;

if (secondsUntilNextRefresh > _client.Options.MaximumRefreshWaitTime)
secondsUntilNextRefresh = _client.Options.MaximumRefreshWaitTime;

return TimeSpan.FromSeconds(secondsUntilNextRefresh);
}
}
}
10 changes: 0 additions & 10 deletions GotrueTests/AnonKeyClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -481,15 +481,5 @@ public async Task ClientCanSetSession()
// As this is being forced to regenerate, the original should be different than the cached.
AreNotEqual(refreshToken, _client.CurrentSession.RefreshToken);
}

[TestMethod("Session: `ExpiresAt` is Calculated Correctly.")]
public async Task SessionCalculatesExpiresAtCorrectly()
{
var email = $"{RandomString(12)}@supabase.io";
var session = await _client.SignUp(email, PASSWORD);

IsFalse(session.Expired());
AreEqual(session.ExpiresAt().Ticks, session.CreatedAt.ToUniversalTime().AddSeconds(session.ExpiresIn).Ticks);
}
}
}
Loading