Skip to content

Commit

Permalink
Implement external properties/soft-deleting features
Browse files Browse the repository at this point in the history
  • Loading branch information
npavlyk82 committed Feb 7, 2025
1 parent 38c5855 commit e423582
Show file tree
Hide file tree
Showing 11 changed files with 145 additions and 11 deletions.
7 changes: 7 additions & 0 deletions src/Core/Token.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace Bewit
Expand All @@ -22,6 +23,12 @@ protected Token(string nonce, DateTime expirationDate)

public DateTime ExpirationDate { get; private set; }

[JsonIgnore]
public bool? IsDeleted { get; set; } = false;

[JsonIgnore]
public Dictionary<string, string> ExtraProperties { get; set; }

public static Token Create(string nonce, DateTime expirationDate)
{
if (string.IsNullOrWhiteSpace(nonce))
Expand Down
3 changes: 2 additions & 1 deletion src/Extensions.HotChocolate/Generation/BewitMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ public async Task InvokeAsync(
if (context.Result is TPayload result)
{
BewitToken<TPayload> bewit
= await tokenGenerator.GenerateBewitTokenAsync(result, context.RequestAborted);
= await tokenGenerator.GenerateBewitTokenAsync(
result, context.RequestAborted, context.GetBewitTokenExtraProperties());

context.Result = (string)bewit;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Collections.Generic;
using System.Linq;
using HotChocolate.Resolvers;

namespace Bewit.Extensions.HotChocolate.Generation;

public static class BewitTokenExtraPropertiesHelper

{
private const string ExtraPropertyPrefix = "BewitTokenExtraProperty:";

public static void AddBewitTokenExtraProperties(
this IResolverContext resolverContext, Dictionary<string, string> extraProperties)
{
if (extraProperties == null)
{
return;
}

resolverContext.ScopedContextData =
resolverContext.ScopedContextData.SetItems(
extraProperties.ToDictionary(
ctx => $"{ExtraPropertyPrefix}{ctx.Key}",
ctx => (object)ctx.Value));
}

public static Dictionary<string, string> GetBewitTokenExtraProperties(this IMiddlewareContext context)
{
Dictionary<string, string> extraProperties = new Dictionary<string, string>();

foreach (var key in context.ScopedContextData.Keys)
{
if (!key.StartsWith(ExtraPropertyPrefix))
{
continue;
}

object extraPropertyValue = context.ScopedContextData.GetValueOrDefault(key);

if (extraPropertyValue != null)
{
extraProperties.Add(
key.Substring(ExtraPropertyPrefix.Length),
extraPropertyValue.ToString());
}
}

return extraProperties;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public async Task InvokeAsync(

BewitToken<string> bewit =
await tokenGenerator.GenerateBewitTokenAsync(
uri.PathAndQuery, context.RequestAborted);
uri.PathAndQuery, context.RequestAborted, context.GetBewitTokenExtraProperties());

var parametersToAdd = new Dictionary<string, string>
{
Expand Down
9 changes: 7 additions & 2 deletions src/Generation/BewitTokenGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Threading;
Expand Down Expand Up @@ -43,19 +44,23 @@ public BewitTokenGenerator(

public Task<BewitToken<T>> GenerateBewitTokenAsync(
T payload,
CancellationToken cancellationToken)
CancellationToken cancellationToken,
Dictionary<string, string> extraProperties = null)
{
var token = Token.Create(CreateNextToken(), CreateExpirationDate());
token.ExtraProperties = extraProperties;
return GenerateBewitTokenImplAsync(payload, token, cancellationToken);

}

public Task<BewitToken<T>> GenerateIdentifiableBewitTokenAsync(
T payload,
string identifier,
CancellationToken cancellationToken)
CancellationToken cancellationToken,
Dictionary<string, string> extraProperties = null)
{
var token = new IdentifiableToken(identifier, CreateNextToken(), CreateExpirationDate());
token.ExtraProperties = extraProperties;
return GenerateBewitTokenImplAsync(payload, token, cancellationToken);
}

Expand Down
4 changes: 3 additions & 1 deletion src/Generation/IBewitTokenGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -7,6 +8,7 @@ public interface IBewitTokenGenerator<T>
{
Task<BewitToken<T>> GenerateBewitTokenAsync(
T payload,
CancellationToken cancellationToken);
CancellationToken cancellationToken,
Dictionary<string, string> extraProperties = null);
}
}
4 changes: 3 additions & 1 deletion src/Generation/IIdentifiableBewitTokenGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -8,7 +9,8 @@ public interface IIdentifiableBewitTokenGenerator<T>
Task<BewitToken<T>> GenerateIdentifiableBewitTokenAsync(
T payload,
string identifier,
CancellationToken cancellationToken);
CancellationToken cancellationToken,
Dictionary<string, string> extraProperties = null);

Task InvalidateIdentifier(
string identifier,
Expand Down
3 changes: 3 additions & 0 deletions src/Storage.MongoDB/MongoNonceOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,8 @@ public class MongoNonceOptions
/// ReUse will keep it in the storage.
/// </summary>
public NonceUsage NonceUsage { get; set; } = NonceUsage.OneTime;


public int RecordExpireAfterDays { get; set; } = 365 * 2;
}
}
34 changes: 32 additions & 2 deletions src/Storage.MongoDB/MongoNonceRepository.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Bson.Serialization;
Expand Down Expand Up @@ -27,6 +28,8 @@ static MongoNonceRepository()
{
cm.MapIdMember(c => c.Nonce);
cm.MapField(c => c.ExpirationDate);
cm.MapField(c => c.IsDeleted);
cm.MapField(c => c.ExtraProperties);
cm.SetIgnoreExtraElements(true);
});
}
Expand All @@ -38,24 +41,51 @@ public MongoNonceRepository(IMongoDatabase database, MongoNonceOptions options)

_collection.Indexes.CreateOne(new CreateIndexModel<Token>(
Builders<Token>.IndexKeys.Ascending(nameof(IdentifiableToken.Identifier))));

_collection.Indexes.CreateOne(new CreateIndexModel<Token>(
Builders<Token>.IndexKeys.Combine(
Builders<Token>.IndexKeys.Ascending(nameof(Token.Nonce)),
Builders<Token>.IndexKeys.Ascending(nameof(Token.IsDeleted)))));

_collection.Indexes.CreateOne(new CreateIndexModel<Token>(
Builders<Token>.IndexKeys.Ascending(nameof(Token.ExpirationDate)),
new CreateIndexOptions
{
ExpireAfter = TimeSpan.FromDays(options.RecordExpireAfterDays)
}));
}

public async ValueTask InsertOneAsync(
Token token, CancellationToken cancellationToken)
{
if (token.ExtraProperties != null)
{
foreach (KeyValuePair<string, string> searchAttribute in token.ExtraProperties)
{
_collection.Indexes.CreateOne(new CreateIndexModel<Token>(
Builders<Token>.IndexKeys.Ascending($"{nameof(token.ExtraProperties)}.{searchAttribute.Key}")));
}
}

await _collection.InsertOneAsync(token, cancellationToken: cancellationToken);
}

public async ValueTask<Token?> TakeOneAsync(
string token,
CancellationToken cancellationToken)
{
FilterDefinition<Token> findFilter = Builders<Token>.Filter.Eq(n => n.Nonce, token);
FilterDefinition<Token> findFilter =
Builders<Token>.Filter.Eq(n => n.Nonce, token) &
(Builders<Token>.Filter.Not(Builders<Token>.Filter.Exists(n => n.IsDeleted)) |
Builders<Token>.Filter.Eq(n => n.IsDeleted, false));

UpdateDefinition<Token> updateDefinition =
Builders<Token>.Update.Set(x => x.IsDeleted, true);

if (_options.NonceUsage == NonceUsage.OneTime)
{
return await _collection
.FindOneAndDeleteAsync(findFilter, cancellationToken: cancellationToken);
.FindOneAndUpdateAsync(findFilter, updateDefinition, cancellationToken: cancellationToken);
}

return await _collection
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -59,7 +60,34 @@ await gqlClient.QueryAsync<GiveMeAccessResult>(query,
res.Data.RequestAccess.Should().Be("https://www.google.com/a/b/?c=d&bewit=eyJUb2tlbiI6eyJOb25jZSI6IjcyNGU3YWNjLWJlNTctNDlhMS04MTk1LTQ2YTAzYzYyNzFjNiIsIkV4cGlyYXRpb25EYXRlIjoiMjAxNy0wMS0wMVQwMTowMjowMS4wMDFaIn0sIlBheWxvYWQiOiIvYS9iLz9jPWQiLCJIYXNoIjoiOWxaak9tTlFqVG0xbUlUVjZ2LzNtU1NBTFBXRndmNXNaQ3pqc3J6eXhwQT0ifQ%253D%253D");
}

private static TestServer CreateTestServer()
[Fact]
public async Task InvokeAsync_WithExtraProperties_ShouldNotImpactBewitToken()
{
//Arrange
TestServer testServer = CreateTestServer(
new Dictionary<string, string> { ["foo"] = "bar" });
HttpClient client = testServer.CreateClient();
GraphQLClient gqlClient = new GraphQLClient(client);
QueryRequest query = new QueryRequest(
string.Empty,
@"mutation giveMeAccess {
RequestAccess
}",
"giveMeAccess",
new Dictionary<string, object>());

//Act
QueryResponse<GiveMeAccessResult> res =
await gqlClient.QueryAsync<GiveMeAccessResult>(query,
CancellationToken.None);

//Assert
res.Data.Should().NotBeNull();
res.Data.RequestAccess.Should().Be("https://www.google.com/a/b/?c=d&bewit=eyJUb2tlbiI6eyJOb25jZSI6IjcyNGU3YWNjLWJlNTctNDlhMS04MTk1LTQ2YTAzYzYyNzFjNiIsIkV4cGlyYXRpb25EYXRlIjoiMjAxNy0wMS0wMVQwMTowMjowMS4wMDFaIn0sIlBheWxvYWQiOiIvYS9iLz9jPWQiLCJIYXNoIjoiOWxaak9tTlFqVG0xbUlUVjZ2LzNtU1NBTFBXRndmNXNaQ3pqc3J6eXhwQT0ifQ%253D%253D");
}

private static TestServer CreateTestServer(
Dictionary<string, string>? extraProperties = null)
{
IWebHostBuilder hostBuilder = new WebHostBuilder()
.ConfigureServices(services =>
Expand All @@ -79,7 +107,12 @@ private static TestServer CreateTestServer()
d.Name("Mutation");
d.Field("RequestAccess")
.Type<NonNullType<StringType>>()
.Resolve(ctx => "https://www.google.com/a/b/?c=d")
.Resolve(ctx =>
{
ctx.AddBewitTokenExtraProperties(extraProperties);

return "https://www.google.com/a/b/?c=d";
})
.UseBewitUrlProtection();
});
})
Expand Down
2 changes: 1 addition & 1 deletion test/Storage.MongoDB.Tests/NonceRepositoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ await repository.TakeOneAsync(nextToken,
//Assert
var items = (
await collection.FindAsync(
Builders<Token>.Filter.Empty,
Builders<Token>.Filter.Eq(x => x.IsDeleted, false),
cancellationToken: CancellationToken.None)
).ToList();
items.Should().BeEmpty();
Expand Down

0 comments on commit e423582

Please sign in to comment.