Skip to content

Commit 022dfb7

Browse files
committed
cache auth tickets / avoid hammering GitHub auth API
1 parent 148caeb commit 022dfb7

File tree

6 files changed

+67
-6
lines changed

6 files changed

+67
-6
lines changed

rubberduckvba.Server/Api/Features/FeaturesController.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,8 @@ public async Task<ActionResult<FeatureEditViewModel>> Create([FromBody] FeatureE
168168
return BadRequest("Model is invalid for this endpoint.");
169169
}
170170

171-
var existing = await db.GetFeatureId(model.RepositoryId, model.Name);
172-
if (existing != null)
171+
var existingId = await db.GetFeatureId(model.RepositoryId, model.Name);
172+
if (existingId != null)
173173
{
174174
return BadRequest($"Model [Name] must be unique; feature '{model.Name}' already exists.");
175175
}
@@ -191,8 +191,8 @@ public async Task<ActionResult<FeatureEditViewModel>> Update([FromBody] FeatureE
191191
return BadRequest("Model is invalid for this endpoint.");
192192
}
193193

194-
var existing = await db.ResolveFeature(model.RepositoryId, model.Name);
195-
if (existing is null)
194+
var existingId = await db.GetFeatureId(model.RepositoryId, model.Name);
195+
if (existingId is null)
196196
{
197197
return BadRequest("Model is invalid for this endpoint.");
198198
}
@@ -205,7 +205,22 @@ public async Task<ActionResult<FeatureEditViewModel>> Update([FromBody] FeatureE
205205
return new FeatureEditViewModel(result, features, RepositoryOptions);
206206
}
207207

208+
[HttpPost("features/delete")]
209+
[Authorize("github")]
210+
public async Task Delete([FromBody] IFeature model)
211+
{
212+
if (model.Id == default)
213+
{
214+
throw new ArgumentException("Model is invalid for this endpoint.");
215+
}
216+
var existingId = await db.GetFeatureId(RepositoryId.Rubberduck, model.Name);
217+
if (existingId is null)
218+
{
219+
throw new ArgumentException("Model is invalid for this endpoint.");
220+
}
208221

222+
await db.DeleteFeature(existingId.Value);
223+
}
209224

210225
[HttpPost("markdown/format")]
211226
public MarkdownContentViewModel FormatMarkdown([FromBody] MarkdownContentViewModel model)

rubberduckvba.Server/Data/Repository.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public interface IRepository<TEntity> where TEntity : Entity
1818
IEnumerable<TEntity> Insert(IEnumerable<TEntity> entities);
1919
void Update(TEntity entity);
2020
void Update(IEnumerable<TEntity> entities);
21+
void Delete(int id);
2122
}
2223

2324
public abstract class QueryableRepository<T> where T : class
@@ -88,6 +89,18 @@ public virtual bool TryGetId(string name, out int id)
8889

8990
public virtual int GetId(string name) => Get(db => db.QuerySingle<int>($"SELECT [Id] FROM [dbo].[{TableName}] WHERE [Name]=@name", new { name }));
9091
public virtual TEntity GetById(int id) => Get(db => db.QuerySingle<TEntity>(SelectSql + " WHERE a.[Id]=@id", new { id }));
92+
93+
public virtual void Delete(int id)
94+
{
95+
using var db = new SqlConnection(ConnectionString);
96+
db.Open();
97+
98+
using var transaction = db.BeginTransaction();
99+
db.Execute($"DELETE FROM [dbo].[{TableName}] WHERE [Id]=@id", new { id }, transaction);
100+
101+
transaction.Commit();
102+
}
103+
91104
public virtual TEntity Insert(TEntity entity) => Insert([entity]).Single();
92105
public virtual IEnumerable<TEntity> Insert(IEnumerable<TEntity> entities)
93106
{

rubberduckvba.Server/GitHubAuthenticationHandler.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
using Microsoft.AspNetCore.Authentication;
3+
using Microsoft.Extensions.Caching.Memory;
34
using Microsoft.Extensions.Options;
45
using rubberduckvba.Server.Services;
56
using System.Security.Claims;
@@ -10,12 +11,19 @@ namespace rubberduckvba.Server;
1011
public class GitHubAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
1112
{
1213
private readonly IGitHubClientService _github;
14+
private readonly IMemoryCache _cache;
1315

14-
public GitHubAuthenticationHandler(IGitHubClientService github,
16+
private static readonly MemoryCacheEntryOptions _options = new MemoryCacheEntryOptions
17+
{
18+
SlidingExpiration = TimeSpan.FromMinutes(60),
19+
};
20+
21+
public GitHubAuthenticationHandler(IGitHubClientService github, IMemoryCache cache,
1522
IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder)
1623
: base(options, logger, encoder)
1724
{
1825
_github = github;
26+
_cache = cache;
1927
}
2028

2129
protected async override Task<AuthenticateResult> HandleAuthenticateAsync()
@@ -28,12 +36,27 @@ protected async override Task<AuthenticateResult> HandleAuthenticateAsync()
2836
return AuthenticateResult.Fail("Access token was not provided");
2937
}
3038

39+
if (_cache.TryGetValue(token, out var cached) && cached is AuthenticationTicket cachedTicket)
40+
{
41+
var cachedPrincipal = cachedTicket.Principal;
42+
43+
Context.User = cachedPrincipal;
44+
Thread.CurrentPrincipal = cachedPrincipal;
45+
46+
Logger.LogInformation($"Successfully retrieved authentication ticket from cached token for {cachedPrincipal.Identity!.Name}; token will not be revalidated.");
47+
return AuthenticateResult.Success(cachedTicket);
48+
}
49+
3150
var principal = await _github.ValidateTokenAsync(token);
3251
if (principal is ClaimsPrincipal)
3352
{
3453
Context.User = principal;
3554
Thread.CurrentPrincipal = principal;
36-
return AuthenticateResult.Success(new AuthenticationTicket(principal, "github"));
55+
56+
var ticket = new AuthenticationTicket(principal, "github");
57+
_cache.Set(token, ticket, _options);
58+
59+
return AuthenticateResult.Success(ticket);
3760
}
3861

3962
return AuthenticateResult.Fail("An invalid access token was provided");

rubberduckvba.Server/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Hangfire.Dashboard;
44
using Hangfire.SqlServer;
55
using Microsoft.Extensions.Caching.Distributed;
6+
using Microsoft.Extensions.Caching.Memory;
67
using Microsoft.Extensions.Options;
78
using NLog.Config;
89
using NLog.Extensions.Logging;
@@ -226,6 +227,7 @@ private static void ConfigureServices(IServiceCollection services)
226227
services.AddSingleton<CacheService>();
227228

228229
services.AddSession(ConfigureSession);
230+
services.AddSingleton<IMemoryCache>(provider => new MemoryCache(new MemoryCacheOptions(), provider.GetRequiredService<ILoggerFactory>()));
229231
}
230232

231233
private static void ConfigureLogging(IServiceCollection services)

rubberduckvba.Server/Services/RubberduckDbService.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ public interface IRubberduckDbService
6969
Task<FeatureGraph> ResolveFeature(RepositoryId repositoryId, string name);
7070
Task<int?> GetFeatureId(RepositoryId repositoryId, string name);
7171
Task<Feature> SaveFeature(Feature feature);
72+
Task DeleteFeature(int id);
7273
}
7374

7475
public class RubberduckDbService : IRubberduckDbService
@@ -238,6 +239,11 @@ public async Task<FeatureGraph> ResolveFeature(RepositoryId repositoryId, string
238239
// return graph;
239240
}
240241

242+
public async Task DeleteFeature(int id)
243+
{
244+
_featureServices.DeleteFeature(id);
245+
}
246+
241247
public async Task<Feature> SaveFeature(Feature feature)
242248
{
243249
if (feature.Id == default)

rubberduckvba.Server/Services/rubberduckdb/FeatureServices.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,6 @@ feature with
9494
public void Insert(IEnumerable<Inspection> inspections) => inspectionRepository.Insert(inspections.Select(inspection => inspection.ToEntity()));
9595
public void Insert(IEnumerable<QuickFix> quickFixes) => quickfixRepository.Insert(quickFixes.Select(quickfix => quickfix.ToEntity()));
9696
public void Insert(IEnumerable<Annotation> annotations) => annotationRepository.Insert(annotations.Select(annotation => annotation.ToEntity()));
97+
98+
public void DeleteFeature(int id) => featureRepository.Delete(id);
9799
}

0 commit comments

Comments
 (0)