/// <summary> /// Deletes the refresh tokens that expires before the specified expire date. Called when /// creating a refresh token to cleanup. /// </summary> /// <param name="expires">The expire date.</param> /// <returns>The number of deleted tokens.</returns> public async Task <int> DeleteRefreshTokens(DateTimeOffset expires) { var db = this.GetDatabase(); var tran = db.CreateTransaction(); var keysToDelete = await db.SortedSetRangeByScoreAsync($"{this.Configuration.RefreshTokenPrefix}:_index:expires", 0, expires.ToUnixTime()); // Remove items from indexes tran.SortedSetRemoveRangeByScoreAsync($"{this.Configuration.RefreshTokenPrefix}:_index:expires", 0, expires.ToUnixTime()); // Remove items foreach (var key in keysToDelete) { var data = await db.HashGetAllAsync(key.ToString()); var token = new RedisRefreshToken(data); tran.HashDeleteAsync($"{this.Configuration.RefreshTokenPrefix}:_index:client:{token.ClientId}", key); tran.HashDeleteAsync($"{this.Configuration.RefreshTokenPrefix}:_index:redirecturi:{token.RedirectUri}", key); tran.HashDeleteAsync($"{this.Configuration.RefreshTokenPrefix}:_index:subject:{token.Subject}", key); tran.KeyDeleteAsync(key.ToString()); } var result = await tran.ExecuteAsync(CommandFlags.HighPriority); if (result) { return(keysToDelete.Length); } return(0); }
/// <summary> /// Gets all refresh tokens for the specified user that expires **after** the specified date. /// </summary> /// <param name="subject">The subject.</param> /// <param name="expires">The expire date.</param> /// <returns>The refresh tokens.</returns> public async Task <IEnumerable <IRefreshToken> > GetUserRefreshTokens(string subject, DateTimeOffset expires) { var db = this.GetDatabase(); var min = expires.ToUnixTime(); var tokens = new List <IRefreshToken>(); var keys = db.SortedSetRangeByScore($"{this.Configuration.RefreshTokenPrefix}:_index:expires", min, DateTimeMax); foreach (var key in keys) { var hashEntries = await db.HashGetAllAsync(key.ToString()); if (hashEntries.Any()) { var token = new RedisRefreshToken(hashEntries); if (token.Subject == subject && token.ValidTo > expires) { tokens.Add(token); } } } return(tokens); }
/// <summary>Inserts the specified refresh token. Called when creating a refresh token.</summary> /// <param name="refreshToken">The refresh token.</param> /// <returns>The inserted refresh token. <c>null</c> if the insertion was unsuccessful.</returns> public async Task <IRefreshToken> InsertRefreshToken(IRefreshToken refreshToken) { var token = new RedisRefreshToken(refreshToken); if (!token.IsValid()) { throw new ArgumentException($"The refresh token is invalid: {JsonConvert.SerializeObject(token)}", nameof(refreshToken)); } var key = this.GenerateKeyPath(token); var db = this.GetDatabase(); var tran = db.CreateTransaction(); try { this.Configuration.Log.DebugFormat("Inserting refresh token hash in key {0}", key); // Add hash to key tran.HashSetAsync(key, token.ToHashEntries()); var expires = token.ValidTo.ToUnixTime(); this.Configuration.Log.DebugFormat("Inserting key {0} to refresh token set with score {1}", key, expires); // Add key to index for future reference. The score is the expire time in seconds since epoch. tran.SortedSetAddAsync($"{this.Configuration.RefreshTokenPrefix}:_index:expires", key, expires); // Add key to hashed set for future reference by client id, redirect uri or subject. The value is the expire time in seconds since epoch. tran.HashSetAsync($"{this.Configuration.RefreshTokenPrefix}:_index:client:{token.ClientId}", key, expires); tran.HashSetAsync($"{this.Configuration.RefreshTokenPrefix}:_index:redirecturi:{token.RedirectUri}", key, expires); tran.HashSetAsync($"{this.Configuration.RefreshTokenPrefix}:_index:subject:{token.Subject}", key, expires); this.Configuration.Log.DebugFormat("Making key {0} expire at {1}", key, token.ValidTo); // Make the key expire when the code times out tran.KeyExpireAsync(key, token.ValidTo.UtcDateTime); await tran.ExecuteAsync(); return(token); } catch (Exception ex) { this.Configuration.Log.Error("Error when inserting refresh token", ex); } return(null); }
/// <summary> /// Deletes the specified refresh token. Called when authenticating a refresh token to prevent re- /// use. /// </summary> /// <param name="refreshToken">The refresh token.</param> /// <returns><c>True</c> if successful, <c>false</c> otherwise.</returns> public async Task <bool> DeleteRefreshToken(IRefreshToken refreshToken) { var token = new RedisRefreshToken(refreshToken); var key = this.GenerateKeyPath(token); var db = this.GetDatabase(); var tran = db.CreateTransaction(); // Remove items from indexes tran.SortedSetRemoveAsync($"{this.Configuration.RefreshTokenPrefix}:_index:expires", key); tran.HashDeleteAsync($"{this.Configuration.RefreshTokenPrefix}:_index:client:{token.ClientId}", key); tran.HashDeleteAsync($"{this.Configuration.RefreshTokenPrefix}:_index:redirecturi:{token.RedirectUri}", key); tran.HashDeleteAsync($"{this.Configuration.RefreshTokenPrefix}:_index:subject:{token.Subject}", key); // Remove keys tran.KeyDeleteAsync(key); return(await tran.ExecuteAsync(CommandFlags.HighPriority)); }
/// <summary>Gets the specified refresh token.</summary> /// <param name="identifier">The identifier.</param> /// <returns>The refresh token.</returns> public async Task <IRefreshToken> GetRefreshToken(string identifier) { if (string.IsNullOrEmpty(identifier)) { throw new ArgumentNullException(nameof(identifier)); } var db = this.GetDatabase(); var key = $"{this.Configuration.RefreshTokenPrefix}:{Convert.ToBase64String(Encoding.UTF8.GetBytes(identifier))}"; var hashEntries = await db.HashGetAllAsync(key); if (hashEntries.Any()) { var token = new RedisRefreshToken(hashEntries); return(token); } return(null); }
/// <summary>Generates a key.</summary> /// <param name="refreshToken">The refresh token.</param> /// <returns>The key.</returns> protected string GenerateKeyPath(RedisRefreshToken refreshToken) { return(this.Configuration.RefreshTokenPrefix + ":" + Convert.ToBase64String(Encoding.UTF8.GetBytes(refreshToken.GetIdentifier()))); }