aboutsummaryrefslogtreecommitdiff
path: root/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/VNLib.Plugins.Essentials.Accounts.AppData/src')
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/AppDataEntry.cs2
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/Endpoints/WebEndpoint.cs94
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/Model/HttpExtensions.cs9
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/Stores/Sql/SqlBackingStore.cs3
4 files changed, 74 insertions, 34 deletions
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/AppDataEntry.cs b/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/AppDataEntry.cs
index d6d936f..d39000b 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/AppDataEntry.cs
+++ b/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/AppDataEntry.cs
@@ -23,7 +23,7 @@
*/
using VNLib.Utils.Logging;
-using VNLib.Plugins.Extensions.Loading.Routing;
+using VNLib.Plugins.Extensions.Loading.Routing.Mvc;
using VNLib.Plugins.Essentials.Accounts.AppData.Endpoints;
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/Endpoints/WebEndpoint.cs b/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/Endpoints/WebEndpoint.cs
index 3c4f3e5..41b8b30 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/Endpoints/WebEndpoint.cs
+++ b/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/Endpoints/WebEndpoint.cs
@@ -22,10 +22,11 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
+using System;
using System.Net;
using System.Linq;
-using System.Collections.Generic;
using System.Threading.Tasks;
+using System.Collections.Generic;
using VNLib.Net.Http;
using VNLib.Hashing.Checksums;
@@ -37,14 +38,16 @@ using VNLib.Plugins.Extensions.Loading.Routing;
using VNLib.Plugins.Essentials.Accounts.AppData.Model;
using VNLib.Plugins.Essentials.Accounts.AppData.Stores;
+using VNLib.Plugins.Extensions.Loading.Routing.Mvc;
+using static VNLib.Plugins.Essentials.Endpoints.ResourceEndpointBase;
+using static VNLib.Plugins.Essentials.Accounts.AppData.Model.HttpExtensions;
namespace VNLib.Plugins.Essentials.Accounts.AppData.Endpoints
{
-
- [EndpointPath("{{path}}")]
+
[EndpointLogName("Endpoint")]
[ConfigurationName("web_endpoint")]
- internal sealed class WebEndpoint(PluginBase plugin, IConfigScope config) : ProtectedWebEndpoint
+ internal sealed class WebEndpoint(PluginBase plugin, IConfigScope config) : IHttpController
{
const int DefaultMaxDataSize = 8 * 1024;
@@ -52,19 +55,24 @@ namespace VNLib.Plugins.Essentials.Accounts.AppData.Endpoints
private readonly int MaxDataSize = config.GetValueOrDefault("max_data_size", DefaultMaxDataSize);
private readonly string[] AllowedScopes = config.GetRequiredProperty<string[]>("allowed_scopes");
- protected async override ValueTask<VfReturnType> GetAsync(HttpEntity entity)
+ ///<inheritdoc/>
+ public ProtectionSettings GetProtectionSettings() => default;
+
+ [HttpStaticRoute("{{path}}", HttpMethod.GET)]
+ [HttpRouteProtection(AuthorzationCheckLevel.Critical)]
+ public async ValueTask<VfReturnType> GetDataAsync(HttpEntity entity)
{
WebMessage webm = new();
- string? scopeId = entity.QueryArgs.GetValueOrDefault("scope");
- bool noCache = entity.QueryArgs.ContainsKey("no_cache");
+ string? scopeId = GetScopeId(entity);
+ bool noCache = NoCacheQuery(entity);
if (webm.Assert(scopeId != null, "Missing scope"))
{
return VirtualClose(entity, webm, HttpStatusCode.BadRequest);
}
- if (webm.Assert(AllowedScopes.Contains(scopeId), "Invalid scope"))
+ if (webm.Assert(IsScopeAllowed(scopeId), "Invalid scope"))
{
return VirtualClose(entity, webm, HttpStatusCode.BadRequest);
}
@@ -72,26 +80,28 @@ namespace VNLib.Plugins.Essentials.Accounts.AppData.Endpoints
//If the connection has the no-cache header set, also bypass the cache
noCache |= entity.Server.NoCache();
- //optionally bypass cache if the user requests it
- RecordOpFlags flags = noCache ? RecordOpFlags.NoCache : RecordOpFlags.None;
-
- UserRecordData? record = await _store.GetRecordAsync(entity.Session.UserID, scopeId, flags, entity.EventCancellation);
-
- if (record is null)
- {
- return VirtualClose(entity, webm, HttpStatusCode.NotFound);
- }
+ UserRecordData? record = await _store.GetRecordAsync(
+ entity.Session.UserID,
+ recordKey: scopeId,
+ flags: noCache ? RecordOpFlags.NoCache : RecordOpFlags.None, //optionally bypass cache if the user requests it
+ entity.EventCancellation
+ );
//return the raw data with the checksum header
- entity.SetRecordResponse(record, HttpStatusCode.OK);
- return VfReturnType.VirtualSkip;
+
+ return record is null
+ ? VirtualClose(entity, webm, HttpStatusCode.NotFound)
+ : CloseWithRecord(entity, record, HttpStatusCode.OK);
}
- protected override async ValueTask<VfReturnType> PutAsync(HttpEntity entity)
+
+ [HttpStaticRoute("{{path}}", HttpMethod.PUT)]
+ [HttpRouteProtection(AuthorzationCheckLevel.Critical)]
+ public async ValueTask<VfReturnType> UpdateDataAsync(HttpEntity entity)
{
WebMessage webm = new();
- string? scopeId = entity.QueryArgs.GetValueOrDefault("scope");
- bool flush = entity.QueryArgs.ContainsKey("flush");
+ string? scopeId = GetScopeId(entity);
+ bool flush = NoCacheQuery(entity);
if (webm.Assert(entity.Files.Count == 1, "Invalid file count"))
{
@@ -103,7 +113,7 @@ namespace VNLib.Plugins.Essentials.Accounts.AppData.Endpoints
return VirtualClose(entity, webm, HttpStatusCode.BadRequest);
}
- if (webm.Assert(AllowedScopes.Contains(scopeId), "Invalid scope"))
+ if (webm.Assert(IsScopeAllowed(scopeId), "Invalid scope"))
{
return VirtualClose(entity, webm, HttpStatusCode.BadRequest);
}
@@ -125,7 +135,7 @@ namespace VNLib.Plugins.Essentials.Accounts.AppData.Endpoints
//Compute checksum on sent data and compare to the header if it exists
ulong checksum = FNV1a.Compute64(recordData);
- ulong? userChecksum = entity.Server.GetUserDataChecksum();
+ ulong? userChecksum = GetUserDataChecksum(entity.Server);
if (userChecksum.HasValue)
{
@@ -144,28 +154,54 @@ namespace VNLib.Plugins.Essentials.Accounts.AppData.Endpoints
RecordOpFlags flags = flush ? RecordOpFlags.WriteThrough : RecordOpFlags.None;
//Write the record to the store
- await _store.SetRecordAsync(entity.Session.UserID, scopeId, recordData, checksum, flags, entity.EventCancellation);
+ await _store.SetRecordAsync(
+ userId: entity.Session.UserID,
+ recordKey: scopeId,
+ recordData,
+ checksum,
+ flags,
+ entity.EventCancellation
+ );
+
return VirtualClose(entity, HttpStatusCode.Accepted);
}
- protected override async ValueTask<VfReturnType> DeleteAsync(HttpEntity entity)
+ [HttpStaticRoute("{{path}}", HttpMethod.DELETE)]
+ [HttpRouteProtection(AuthorzationCheckLevel.Critical)]
+ public async ValueTask<VfReturnType> DeleteDataAsync(HttpEntity entity)
{
WebMessage webm = new();
- string? scopeId = entity.QueryArgs.GetValueOrDefault("scope");
+ string? scopeId = GetScopeId(entity);
if (webm.Assert(scopeId != null, "Missing scope"))
{
return VirtualClose(entity, webm, HttpStatusCode.BadRequest);
}
- if (webm.Assert(AllowedScopes.Contains(scopeId), "Invalid scope"))
+ if (webm.Assert(IsScopeAllowed(scopeId), "Invalid scope"))
{
return VirtualClose(entity, webm, HttpStatusCode.BadRequest);
}
//Write the record to the store
- await _store.DeleteRecordAsync(entity.Session.UserID, scopeId, entity.EventCancellation);
+ await _store.DeleteRecordAsync(
+ userId: entity.Session.UserID,
+ recordKey: scopeId,
+ entity.EventCancellation
+ );
+
return VirtualClose(entity, HttpStatusCode.Accepted);
+ }
+
+ private bool IsScopeAllowed(string scopeId)
+ {
+ return AllowedScopes.Contains(scopeId, StringComparer.OrdinalIgnoreCase);
}
+
+ private static string? GetScopeId(HttpEntity entity)
+ => entity.QueryArgs.GetValueOrDefault("scope");
+
+ private static bool NoCacheQuery(HttpEntity entity)
+ => entity.QueryArgs.ContainsKey("no_cache");
}
}
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/Model/HttpExtensions.cs b/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/Model/HttpExtensions.cs
index 9628b79..c3b990c 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/Model/HttpExtensions.cs
+++ b/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/Model/HttpExtensions.cs
@@ -23,6 +23,7 @@
*/
using System;
+using System.Collections.Generic;
using System.Net;
using VNLib.Net.Http;
@@ -33,7 +34,7 @@ namespace VNLib.Plugins.Essentials.Accounts.AppData.Model
{
const string ChecksumHeader = "X-Data-Checksum";
- public static void SetRecordResponse(this HttpEntity entity, UserRecordData record, HttpStatusCode code)
+ public static VfReturnType CloseWithRecord(HttpEntity entity, UserRecordData record, HttpStatusCode code)
{
//Set checksum header
entity.Server.Headers.Append(ChecksumHeader, $"{record.Checksum}");
@@ -44,14 +45,16 @@ namespace VNLib.Plugins.Essentials.Accounts.AppData.Model
ContentType.Binary,
new BinDataRecordReader(record.Data)
);
+
+ return VfReturnType.VirtualSkip;
}
- public static ulong? GetUserDataChecksum(this IConnectionInfo server)
+ public static ulong? GetUserDataChecksum(IConnectionInfo server)
{
string? checksumStr = server.Headers[ChecksumHeader];
return string.IsNullOrWhiteSpace(checksumStr) && ulong.TryParse(checksumStr, out ulong checksum) ? checksum : null;
}
-
+
sealed class BinDataRecordReader(byte[] recordData) : IMemoryResponseReader
{
private int _read;
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/Stores/Sql/SqlBackingStore.cs b/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/Stores/Sql/SqlBackingStore.cs
index 2347c66..3761f07 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/Stores/Sql/SqlBackingStore.cs
+++ b/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/Stores/Sql/SqlBackingStore.cs
@@ -42,7 +42,8 @@ using VNLib.Plugins.Essentials.Accounts.AppData.Model;
namespace VNLib.Plugins.Essentials.Accounts.AppData.Stores.Sql
{
- internal sealed class SqlBackingStore(PluginBase plugin) : IEntityStore<UserRecordData, AppDataRequest>, IAsyncConfigurable
+ internal sealed class SqlBackingStore(PluginBase plugin)
+ : IEntityStore<UserRecordData, AppDataRequest>, IAsyncConfigurable
{
private readonly DbRecordStore _store = new(plugin.GetContextOptionsAsync());