aboutsummaryrefslogtreecommitdiff
path: root/Libs/VNLib.Plugins.Essentials.Oauth/Tokens/TokenStore.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Libs/VNLib.Plugins.Essentials.Oauth/Tokens/TokenStore.cs')
-rw-r--r--Libs/VNLib.Plugins.Essentials.Oauth/Tokens/TokenStore.cs171
1 files changed, 171 insertions, 0 deletions
diff --git a/Libs/VNLib.Plugins.Essentials.Oauth/Tokens/TokenStore.cs b/Libs/VNLib.Plugins.Essentials.Oauth/Tokens/TokenStore.cs
new file mode 100644
index 0000000..56bb9f7
--- /dev/null
+++ b/Libs/VNLib.Plugins.Essentials.Oauth/Tokens/TokenStore.cs
@@ -0,0 +1,171 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Microsoft.EntityFrameworkCore;
+
+using VNLib.Utils;
+using VNLib.Plugins.Essentials.Oauth.Applications;
+
+namespace VNLib.Plugins.Essentials.Oauth.Tokens
+{
+ /// <summary>
+ /// Represents a database backed <see cref="ITokenManager"/>
+ /// that allows for communicating token information to
+ /// plugins
+ /// </summary>
+ public sealed class TokenStore : ITokenManager
+ {
+ private readonly DbContextOptions Options;
+
+ /// <summary>
+ /// Initializes a new <see cref="TokenStore"/> that will make quries against
+ /// the supplied <see cref="DbContextOptions"/>
+ /// </summary>
+ /// <param name="options">The DB connection context</param>
+ public TokenStore(DbContextOptions options) => Options = options;
+
+ /// <summary>
+ /// Inserts a new token into the table for a specified application id. Also determines if
+ /// the user has reached the maximum number of allowed tokens
+ /// </summary>
+ /// <param name="token">The token (or session id)</param>
+ /// <param name="appId">The applicaiton the token belongs to</param>
+ /// <param name="refreshToken">The tokens refresh token</param>
+ /// <param name="maxTokens">The maxium number of allowed tokens for a given application</param>
+ /// <param name="cancellation">A token to cancel the operation</param>
+ /// <returns>
+ /// <see cref="ERRNO.SUCCESS"/> if the opreation succeeds (aka 0x01),
+ /// <see cref="ERRNO.E_FAIL"/> if the operation fails, or the number
+ /// of active tokens if the maximum has been reached.
+ /// </returns>
+ public async Task<ERRNO> InsertTokenAsync(string token, string appId, string? refreshToken, int maxTokens, CancellationToken cancellation)
+ {
+ await using UserAppContext ctx = new (Options);
+ await ctx.OpenTransactionAsync(cancellation);
+
+ //Check active token count
+ int count = await (from t in ctx.OAuthTokens
+ where t.ApplicationId == appId
+ select t)
+ .CountAsync(cancellation);
+ //Check count
+ if(count >= maxTokens)
+ {
+ return count;
+ }
+
+ //Try to add the new token
+ ActiveToken newToken = new()
+ {
+ ApplicationId = appId,
+ Id = token,
+ RefreshToken = refreshToken,
+ Created = DateTime.UtcNow,
+ LastModified = DateTime.UtcNow
+ };
+ //Add token to store
+ ctx.OAuthTokens.Add(newToken);
+ //commit changes
+ ERRNO result = await ctx.SaveChangesAsync(cancellation);
+ if (result)
+ {
+ //End transaction
+ await ctx.CommitTransactionAsync(cancellation);
+ }
+ else
+ {
+ await ctx.RollbackTransctionAsync(cancellation);
+ }
+ return result;
+ }
+
+ /// <summary>
+ /// Revokes/removes a single token from the store by its ID
+ /// </summary>
+ /// <param name="token">The token to remove</param>
+ /// <param name="cancellation"></param>
+ /// <returns>A task that revolves when the token is removed from the table if it exists</returns>
+ public async Task RevokeTokenAsync(string token, CancellationToken cancellation)
+ {
+ await using UserAppContext ctx = new (Options);
+ await ctx.OpenTransactionAsync(cancellation);
+ //Get the token from the db if it exists
+ ActiveToken? at = await (from t in ctx.OAuthTokens
+ where t.Id == token
+ select t)
+ .FirstOrDefaultAsync(cancellation);
+ if(at == null)
+ {
+ return;
+ }
+ //delete token
+ ctx.OAuthTokens.Remove(at);
+ //Save changes
+ await ctx.SaveChangesAsync(cancellation);
+ await ctx.CommitTransactionAsync(cancellation);
+ }
+ /// <summary>
+ /// Removes all token entires that were created before the specified time
+ /// </summary>
+ /// <param name="validAfter">The time before which all tokens are invaid</param>
+ /// <param name="cancellation">A token the cancel the operation</param>
+ /// <returns>A task that resolves to a collection of tokens that were removed</returns>
+ public async Task<IReadOnlyCollection<ActiveToken>> CleanupExpiredTokensAsync(DateTimeOffset validAfter, CancellationToken cancellation)
+ {
+ await using UserAppContext ctx = new (Options);
+ await ctx.OpenTransactionAsync(cancellation);
+ //Get the token from the db if it exists
+ ActiveToken[] at = await (from t in ctx.OAuthTokens
+ where t.Created < validAfter
+ select t)
+ .ToArrayAsync(cancellation);
+
+ //delete token
+ ctx.OAuthTokens.RemoveRange(at);
+ //Save changes
+ int count = await ctx.SaveChangesAsync(cancellation);
+ await ctx.CommitTransactionAsync(cancellation);
+ return at;
+ }
+ ///<inheritdoc/>
+ public async Task RevokeTokensAsync(IReadOnlyCollection<string> tokens, CancellationToken cancellation = default)
+ {
+ await using UserAppContext ctx = new (Options);
+ await ctx.OpenTransactionAsync(cancellation);
+ //Get all tokenes that are contained in the collection
+ ActiveToken[] at = await (from t in ctx.OAuthTokens
+ where tokens.Contains(t.Id)
+ select t)
+ .ToArrayAsync(cancellation);
+
+ //delete token
+ ctx.OAuthTokens.RemoveRange(at);
+ //Save changes
+ await ctx.SaveChangesAsync(cancellation);
+ await ctx.CommitTransactionAsync(cancellation);
+ }
+ ///<inheritdoc/>
+ async Task ITokenManager.RevokeTokensForAppAsync(string appId, CancellationToken cancellation)
+ {
+ await using UserAppContext ctx = new (Options);
+ await ctx.OpenTransactionAsync(cancellation);
+ //Get the token from the db if it exists
+ ActiveToken[] at = await (from t in ctx.OAuthTokens
+ where t.ApplicationId == appId
+ select t)
+ .ToArrayAsync(cancellation);
+ //Set created time to 0 to invalidate the token
+ foreach(ActiveToken t in at)
+ {
+ //Expire token so next cleanup round will wipe tokens
+ t.Created = DateTime.MinValue;
+ }
+ //Save changes
+ await ctx.SaveChangesAsync(cancellation);
+ await ctx.CommitTransactionAsync(cancellation);
+ }
+ }
+}