more samples
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
namespace TriunniaLabs.Arch.Sample.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines service key constants for protectors.
|
||||||
|
/// </summary>
|
||||||
|
public static class Protectors
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the service key for protected byte array types.
|
||||||
|
/// </summary>
|
||||||
|
public const string ProtectedByteArrayServiceKey = nameof(ProtectedByteArrayServiceKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,7 +41,7 @@ namespace TriunniaLabs.Arch.Sample.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A sample method that shows how to work with the Keys Api.
|
/// A sample method that shows how to work with the Application Api.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal async Task SampleAsync()
|
internal async Task SampleAsync()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using TriunniaLabs.Arch.Api.Comments.Client;
|
||||||
|
using TriunniaLabs.Arch.Microservice;
|
||||||
|
using TriunniaLabs.Memory.Protected;
|
||||||
|
|
||||||
|
namespace TriunniaLabs.Arch.Sample.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A controller that shows how to work with the Comments Api.
|
||||||
|
/// </summary>
|
||||||
|
public class CommentsApiSampleController : ArchControllerBase
|
||||||
|
{
|
||||||
|
// Services
|
||||||
|
private readonly ICommentsApiClient _commentsApiClient;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="microserviceContext">Reference to the microservice context.</param>
|
||||||
|
/// <param name="commentsApiClient">Reference to the Comments Api client.</param>
|
||||||
|
public CommentsApiSampleController(IMicroserviceContext microserviceContext, ICommentsApiClient commentsApiClient) : base(microserviceContext)
|
||||||
|
{
|
||||||
|
_commentsApiClient = commentsApiClient ?? throw new ArgumentNullException(nameof(commentsApiClient));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A sample method that shows how to work with the Comments Api.
|
||||||
|
/// </summary>
|
||||||
|
internal async Task SampleAsync()
|
||||||
|
{
|
||||||
|
// Comments are posted within a context. The context can be anything, like an article, forum category, another comment (in the case of a reply), etc.
|
||||||
|
var contextId = Guid.NewGuid().ToString().AsProtected(); // Id of the context to which the comment applies.
|
||||||
|
var contextType = 1; // Developer-defined type for the context
|
||||||
|
var commentType = 1; // Developer-defined type for the comment
|
||||||
|
var commentStatus = 1; // Developer-defined type for the comment's status
|
||||||
|
var commentText = "This is my comment".AsProtected(); // The encrypted text of the comment
|
||||||
|
var authorUserId = Guid.NewGuid().ToString().AsProtected(); // Id of the user who wrote the comment
|
||||||
|
var encryptionKey = "SampleEncryptionKey".AsProtected(); // Per-row encryption key for the entity
|
||||||
|
var commentId = Guid.NewGuid();
|
||||||
|
|
||||||
|
// Create a new comment in the system
|
||||||
|
await _commentsApiClient.CreateCommentAsync(contextId, contextType, commentType, commentStatus, commentText, authorUserId, encryptionKey);
|
||||||
|
|
||||||
|
// Get the specified comment
|
||||||
|
var comment = await _commentsApiClient.GetCommentAsync(contextId, contextType, commentId, encryptionKey);
|
||||||
|
|
||||||
|
// Get all comments within a context
|
||||||
|
var comments = await _commentsApiClient.GetCommentsAsync(contextId, contextType, encryptionKey);
|
||||||
|
|
||||||
|
// Update an existing comment
|
||||||
|
comment.CommentText = "This is my updated comment".AsProtected();
|
||||||
|
await _commentsApiClient.UpdateCommentAsync(comment, encryptionKey);
|
||||||
|
|
||||||
|
// Delete the specified comment from the context
|
||||||
|
await _commentsApiClient.DeleteCommentAsync(contextId, contextType, comment.CommentId);
|
||||||
|
|
||||||
|
// Delete the entire comment thread
|
||||||
|
await _commentsApiClient.DeleteCommentsAsync(contextId, contextType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using TriunniaLabs.Arch.Microservice;
|
||||||
|
using TriunniaLabs.Arch.Relay.Email.Blazor;
|
||||||
|
|
||||||
|
namespace TriunniaLabs.Arch.Sample.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A controller that shows how to work with the Email Api Relay Client.
|
||||||
|
/// This controller demonstrates using the Relay client from a Blazor page.
|
||||||
|
/// Relay clients contact their corresponding server endpoints that are brought in via TriunniaLabs.Arch.Relay.Email.Server.
|
||||||
|
/// Those server endpoints then contact the normal Email Api Client.
|
||||||
|
/// </summary>
|
||||||
|
public class EmailApiRelaySampleController : ArchControllerBase
|
||||||
|
{
|
||||||
|
// Services
|
||||||
|
private readonly IEmailApiRelayClient _emailApiRelayClient;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="microserviceContext">Reference to the microservice context.</param>
|
||||||
|
/// <param name="emailApiRelayClient">Reference to the Email Api Relay client.</param>
|
||||||
|
public EmailApiRelaySampleController(IMicroserviceContext microserviceContext, IEmailApiRelayClient emailApiRelayClient) : base(microserviceContext)
|
||||||
|
{
|
||||||
|
_emailApiRelayClient = emailApiRelayClient ?? throw new ArgumentNullException(nameof(emailApiRelayClient));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A sample method that shows how to work with the Email Api.
|
||||||
|
/// </summary>
|
||||||
|
internal async Task SampleAsync()
|
||||||
|
{
|
||||||
|
var verifyToken = Guid.NewGuid(); // A verify token from the verification email
|
||||||
|
|
||||||
|
// Verifies the user's email address, given the verify token
|
||||||
|
await _emailApiRelayClient.VerifyEmailAddressAsync(verifyToken);
|
||||||
|
|
||||||
|
// Reports abuse of the email address, given the verify token.
|
||||||
|
// This would be called by the actual owner of the email address, who did not register the email in our system.
|
||||||
|
await _emailApiRelayClient.ReportAbuseAsync(verifyToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ using TriunniaLabs.Memory.Protected;
|
|||||||
namespace TriunniaLabs.Arch.Sample.Server
|
namespace TriunniaLabs.Arch.Sample.Server
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A controller that shows how to work the Email Api.
|
/// A controller that shows how to work with the Email Api.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class EmailApiSampleController : ArchControllerBase
|
public class EmailApiSampleController : ArchControllerBase
|
||||||
{
|
{
|
||||||
@@ -16,7 +16,7 @@ namespace TriunniaLabs.Arch.Sample.Server
|
|||||||
/// Constructor.
|
/// Constructor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="microserviceContext">Reference to the microservice context.</param>
|
/// <param name="microserviceContext">Reference to the microservice context.</param>
|
||||||
/// <param name="emailApiClient">Reference to the Tokens Api client.</param>
|
/// <param name="emailApiClient">Reference to the Email Api client.</param>
|
||||||
public EmailApiSampleController(IMicroserviceContext microserviceContext, IEmailApiClient emailApiClient) : base(microserviceContext)
|
public EmailApiSampleController(IMicroserviceContext microserviceContext, IEmailApiClient emailApiClient) : base(microserviceContext)
|
||||||
{
|
{
|
||||||
_emailApiClient = emailApiClient ?? throw new ArgumentNullException(nameof(emailApiClient));
|
_emailApiClient = emailApiClient ?? throw new ArgumentNullException(nameof(emailApiClient));
|
||||||
@@ -45,9 +45,12 @@ namespace TriunniaLabs.Arch.Sample.Server
|
|||||||
var emailResponse = await _emailApiClient.GetEmailAddressAsync(emailAddress.Hash, encryptionKey); // Get by hash
|
var emailResponse = await _emailApiClient.GetEmailAddressAsync(emailAddress.Hash, encryptionKey); // Get by hash
|
||||||
emailResponse = await _emailApiClient.GetEmailAddressAsync(emailId, encryptionKey); // Get by Id
|
emailResponse = await _emailApiClient.GetEmailAddressAsync(emailId, encryptionKey); // Get by Id
|
||||||
|
|
||||||
// Email verification procedure. This will send an email to the user with the verify token. Record its verification or abuse with the other methods.
|
// Email verification procedure. This will send an email to the user with the verify token.
|
||||||
await _emailApiClient.InitEmailVerifyAsync(emailId, encryptionKey, ipAddress);
|
await _emailApiClient.InitEmailVerifyAsync(emailId, encryptionKey, ipAddress);
|
||||||
var result = await _emailApiClient.CommitEmailVerifyAsync(verifyToken, ipAddress);
|
var result = await _emailApiClient.CommitEmailVerifyAsync(verifyToken, ipAddress);
|
||||||
|
|
||||||
|
// Reports abuse of the email address, given the verify token.
|
||||||
|
// This would be called by the actual owner of the email address, who did not register the email in our system.
|
||||||
result = await _emailApiClient.ReportAbuseAsync(verifyToken);
|
result = await _emailApiClient.ReportAbuseAsync(verifyToken);
|
||||||
|
|
||||||
// When using Postmark, receive their webhooks and forward to the Email Api with this call.
|
// When using Postmark, receive their webhooks and forward to the Email Api with this call.
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using TriunniaLabs.Arch.Api.Entity.Client;
|
||||||
|
using TriunniaLabs.Arch.Microservice;
|
||||||
|
using TriunniaLabs.Memory.Protected;
|
||||||
|
|
||||||
|
namespace TriunniaLabs.Arch.Sample.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A controller that shows how to work with the Entity Api.
|
||||||
|
/// </summary>
|
||||||
|
public class EntityApiSampleController : ArchControllerBase
|
||||||
|
{
|
||||||
|
// Services
|
||||||
|
private readonly IEntityApiClient _entityApiClient;
|
||||||
|
private readonly IProtector<byte[]> _byteArrayProtector;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="microserviceContext">Reference to the microservice context.</param>
|
||||||
|
/// <param name="entityApiClient">Reference to the Entity Api client.</param>
|
||||||
|
/// <param name="byteArrayProtector">A custom protector for protecting byte[] data types.</param>
|
||||||
|
public EntityApiSampleController(IMicroserviceContext microserviceContext, IEntityApiClient entityApiClient,
|
||||||
|
[FromKeyedServices(Protectors.ProtectedByteArrayServiceKey)] IProtector<byte[]> byteArrayProtector)
|
||||||
|
: base(microserviceContext)
|
||||||
|
{
|
||||||
|
_entityApiClient = entityApiClient ?? throw new ArgumentNullException(nameof(entityApiClient));
|
||||||
|
_byteArrayProtector = byteArrayProtector ?? throw new ArgumentNullException(nameof(byteArrayProtector));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A sample method that shows how to work with the Entity Api.
|
||||||
|
/// </summary>
|
||||||
|
internal async Task SampleAsync()
|
||||||
|
{
|
||||||
|
var entity = new SampleEntity { Id = Guid.NewGuid(), Name = "MyEntity", Value = 1234 };
|
||||||
|
var encryptionKey = "SampleEncryptionKey".AsProtected(); // Per-row encryption key for the entity
|
||||||
|
var entityId = entity.Id.AsProtected();
|
||||||
|
var entityType = entity.GetType().FullName!.AsProtected();
|
||||||
|
|
||||||
|
// Serialize the entity to bytes if you want to do your own serialization (and protection)
|
||||||
|
var serialized = JsonSerializer.Serialize(entity);
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(serialized);
|
||||||
|
|
||||||
|
|
||||||
|
// Create a new entity in the system with the specified id
|
||||||
|
await _entityApiClient.CreateEntityAsync(entityId, entity, _byteArrayProtector, encryptionKey);
|
||||||
|
await _entityApiClient.CreateEntityAsync(entityId, entityType, bytes, encryptionKey);
|
||||||
|
await _entityApiClient.CreateEntityAsync<SampleEntity>(entityId, bytes, encryptionKey);
|
||||||
|
|
||||||
|
// Update an existing entity in the system
|
||||||
|
entity.Value = 5678;
|
||||||
|
await _entityApiClient.UpdateEntityAsync(entityId, entity, _byteArrayProtector, encryptionKey);
|
||||||
|
await _entityApiClient.UpdateEntityAsync(entityId, entityType, bytes, encryptionKey);
|
||||||
|
await _entityApiClient.UpdateEntityAsync<SampleEntity>(entityId, bytes, encryptionKey);
|
||||||
|
|
||||||
|
|
||||||
|
// Get the entity and metadata, then deserialize
|
||||||
|
var entityResponse = await _entityApiClient.GetEntityAsync(entityId, entityType, encryptionKey);
|
||||||
|
entityResponse = await _entityApiClient.GetEntityAsync<SampleEntity>(entityId, encryptionKey);
|
||||||
|
entity = JsonSerializer.Deserialize<SampleEntity>(entityResponse!.Data.GetDecryptedValue());
|
||||||
|
|
||||||
|
// Get the deserialized entity immediately
|
||||||
|
entity = await _entityApiClient.GetEntityDeserializedAsync<SampleEntity>(entityId, encryptionKey, _byteArrayProtector);
|
||||||
|
|
||||||
|
|
||||||
|
// The Entity Api records and versions all changes to an entity. Get the history of all changes and their metadata with this call.
|
||||||
|
var history = await _entityApiClient.GetEntityHistoryAsync(entityId, entityType, encryptionKey);
|
||||||
|
history = await _entityApiClient.GetEntityHistoryAsync<SampleEntity>(entityId, encryptionKey);
|
||||||
|
|
||||||
|
// Or just get the history of the entities directly without any metadata
|
||||||
|
var deserializedHistory = await _entityApiClient.GetEntityHistoryDeserializedAsync<SampleEntity>(entityId, encryptionKey, _byteArrayProtector);
|
||||||
|
|
||||||
|
// Gets the archived version of the specified entity and its metadata
|
||||||
|
entityResponse = await _entityApiClient.GetEntityArchivedAsync(entityId, entityType, encryptionKey);
|
||||||
|
entityResponse = await _entityApiClient.GetEntityArchivedAsync<SampleEntity>(entityId, encryptionKey);
|
||||||
|
|
||||||
|
// Or just get the archived version without any metadata
|
||||||
|
entity = await _entityApiClient.GetEntityArchivedDeserializedAsync<SampleEntity>(entityId, encryptionKey, _byteArrayProtector);
|
||||||
|
|
||||||
|
|
||||||
|
// Mark the entity has deleted without actually deleting it
|
||||||
|
await _entityApiClient.SoftDeleteEntityAsync(entityId, entityType);
|
||||||
|
await _entityApiClient.SoftDeleteEntityAsync<SampleEntity>(entityId);
|
||||||
|
|
||||||
|
|
||||||
|
// Purge the entity from the system. This is a hard delete. It also deletes all archives and histories.
|
||||||
|
await _entityApiClient.PurgeEntityAsync(entityId, entityType);
|
||||||
|
await _entityApiClient.PurgeEntityAsync<SampleEntity>(entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SampleEntity
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public int Value { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
using TriunniaLabs.Arch.Api.Files.Client;
|
||||||
|
using TriunniaLabs.Arch.Microservice;
|
||||||
|
using TriunniaLabs.Memory.Protected;
|
||||||
|
|
||||||
|
namespace TriunniaLabs.Arch.Sample.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A controller that shows how to work with the Files Api.
|
||||||
|
/// </summary>
|
||||||
|
public class FilesApiSampleController : ArchControllerBase
|
||||||
|
{
|
||||||
|
// Services
|
||||||
|
private readonly IFilesApiClient _filesApiClient;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="microserviceContext">Reference to the microservice context.</param>
|
||||||
|
/// <param name="filesApiClient">Reference to the Files Api client.</param>
|
||||||
|
public FilesApiSampleController(IMicroserviceContext microserviceContext, IFilesApiClient filesApiClient) : base(microserviceContext)
|
||||||
|
{
|
||||||
|
_filesApiClient = filesApiClient ?? throw new ArgumentNullException(nameof(filesApiClient));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A sample method that shows how to work with the Files Api.
|
||||||
|
/// </summary>
|
||||||
|
internal async Task SampleAsync()
|
||||||
|
{
|
||||||
|
var applicationId = MicroserviceContext.Settings.Application.ApplicationId; // Id of this application
|
||||||
|
var folder = "MySampleFolder".AsProtected(); // Files are stored in folders
|
||||||
|
var filename = "MySampleFile".AsProtected(); // The name of the file
|
||||||
|
var encryptionKey = "SampleEncryptionKey".AsProtected(); // Per-row encryption key for the file
|
||||||
|
var fileContents = new byte[] { 1, 2, 3, 4 };
|
||||||
|
using var fileStream = new MemoryStream(fileContents);
|
||||||
|
|
||||||
|
// Upload a file; overwrite if it exists Protect the stream to upload it.
|
||||||
|
var protectedStream = new Protected<Stream>(fileStream, encryptionKey.GetDecryptedValue());
|
||||||
|
await _filesApiClient.UploadFileAsync(applicationId, folder, filename, protectedStream, encryptionKey);
|
||||||
|
|
||||||
|
// Download a file via streaming
|
||||||
|
protectedStream = await _filesApiClient.DownloadFileAsync(applicationId, folder, filename, encryptionKey);
|
||||||
|
using (var stream = protectedStream.GetDecryptedValueAsStream()) // Decrypt the stream for writing, if you want
|
||||||
|
{
|
||||||
|
// Save the stream to a file
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download a file and get its bytes directly. Does not stream. Use for small files only.
|
||||||
|
var protectedFileBytes = await _filesApiClient.DownloadSmallFileAsync(applicationId, folder, filename, encryptionKey);
|
||||||
|
|
||||||
|
// Check if the file exists
|
||||||
|
var exists = await _filesApiClient.GetFileExistsAsync(applicationId, folder, filename);
|
||||||
|
|
||||||
|
// Gets the list of all files stored in the folder
|
||||||
|
var filenames = await _filesApiClient.GetFilesAsync(applicationId, folder);
|
||||||
|
|
||||||
|
// Get the size of the file, in bytes
|
||||||
|
var size = await _filesApiClient.GetFileSizeAsync(applicationId, folder, filename);
|
||||||
|
|
||||||
|
// Get the list of all folders in the application
|
||||||
|
var folders = await _filesApiClient.GetFoldersAsync(applicationId);
|
||||||
|
|
||||||
|
// Delete the specified file from the server
|
||||||
|
await _filesApiClient.DeleteFileAsync(applicationId, folder, filename);
|
||||||
|
|
||||||
|
// Delete the specified folder and all files within from the server, for the application
|
||||||
|
await _filesApiClient.DeleteFolderAsync(applicationId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ using TriunniaLabs.Memory.Protected;
|
|||||||
namespace TriunniaLabs.Arch.Sample.Server
|
namespace TriunniaLabs.Arch.Sample.Server
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A controller that shows how to work the Keys Api.
|
/// A controller that shows how to work with the Keys Api.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class KeysApiSampleController : ArchControllerBase
|
public class KeysApiSampleController : ArchControllerBase
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using TriunniaLabs.Memory.Protected;
|
|||||||
namespace TriunniaLabs.Arch.Sample.Server
|
namespace TriunniaLabs.Arch.Sample.Server
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A controller that shows how to work the Tokens Api.
|
/// A controller that shows how to work with the Tokens Api.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TokensApiSampleController : ArchControllerBase
|
public class TokensApiSampleController : ArchControllerBase
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
using TriunniaLabs.Arch.Microservice;
|
||||||
|
using TriunniaLabs.Arch.Microservice.Client;
|
||||||
|
using TriunniaLabs.Arch.Relay.Users.Blazor;
|
||||||
|
using TriunniaLabs.Memory.Protected;
|
||||||
|
using TriunniaLabs.Blazor.ZeroKnowledge;
|
||||||
|
|
||||||
|
namespace TriunniaLabs.Arch.Sample.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A controller that shows how to work with the Users Api Relay Client.
|
||||||
|
/// This controller demonstrates using the Relay client from a Blazor page.
|
||||||
|
/// Relay clients contact their corresponding server endpoints that are brought in via TriunniaLabs.Arch.Relay.Users.Server.
|
||||||
|
/// Those server endpoints then contact the normal Users Api Client.
|
||||||
|
/// </summary>
|
||||||
|
public class UsersApiRelaySampleController : ArchControllerBase
|
||||||
|
{
|
||||||
|
// Services
|
||||||
|
private readonly IUsersApiRelayClient _usersApiRelayClient;
|
||||||
|
private readonly IZeroKnowledgeService _zeroKnowledgeService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="microserviceContext">Reference to the microservice context.</param>
|
||||||
|
/// <param name="usersApiRelayClient">Reference to the Users Api Relay client.</param>
|
||||||
|
/// <param name="zeroKnowledgeService">Client-side logic for the zero-knowledge protocol in Blazor.</param>
|
||||||
|
public UsersApiRelaySampleController(IMicroserviceContext microserviceContext, IUsersApiRelayClient usersApiRelayClient, IZeroKnowledgeService zeroKnowledgeService) : base(microserviceContext)
|
||||||
|
{
|
||||||
|
_usersApiRelayClient = usersApiRelayClient ?? throw new ArgumentNullException(nameof(usersApiRelayClient));
|
||||||
|
_zeroKnowledgeService = zeroKnowledgeService ?? throw new ArgumentNullException(nameof(zeroKnowledgeService));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A sample method that shows how to work with the Users Api.
|
||||||
|
/// </summary>
|
||||||
|
internal async Task SampleAsync()
|
||||||
|
{
|
||||||
|
var encryptionKey = "SampleEncryptionKey".AsProtected(); // Per-row encryption key for the email address
|
||||||
|
var emailAddress = "sample@email.address".AsProtected(encryptionKey.GetDecryptedValue()); // Per-row encrypted email address
|
||||||
|
var checkEmailAddress = "sample@email.address".AsRelayProtected(); // Relay encrypted email address (lower security)
|
||||||
|
var ipAddress = IpAddress.AsProtected(); // User's ip address
|
||||||
|
var sessionToken = Guid.NewGuid().ToString().AsProtected(); // Authenticated session token
|
||||||
|
var resetToken = Guid.NewGuid().AsProtected(); // Password reset token
|
||||||
|
var deleteToken = Guid.NewGuid().AsProtected(); // Delete account token
|
||||||
|
|
||||||
|
|
||||||
|
// Construct origin graph -- the mechanism that verifies the user's password without knowing it
|
||||||
|
var originGraph = await _zeroKnowledgeService.GenerateOriginGraphAsync("password");
|
||||||
|
|
||||||
|
// Generate the key file, then save keyFileContents to a file. The file itself is password-protected via metadataPassword
|
||||||
|
var cycle = await _zeroKnowledgeService.GenerateHamiltonianCycleAsync(originGraph, "password");
|
||||||
|
var keyFileContents = await _zeroKnowledgeService.SerializeZeroKnowledgeMetadataAsync(originGraph, cycle, "metadataPassword");
|
||||||
|
|
||||||
|
// Read origin graph from key file
|
||||||
|
var (origin, cycle2) = await _zeroKnowledgeService.DeserializeZeroKnowledgeMetadataAsync(keyFileContents, "metadataPassword", "password");
|
||||||
|
|
||||||
|
// Set http headers on the client for authentication
|
||||||
|
_usersApiRelayClient.SetAuthorizationHeader("MySampleAuthScheme", sessionToken); // Authentication scheme must match appsettings.json
|
||||||
|
|
||||||
|
|
||||||
|
// Register a new user in the system
|
||||||
|
sessionToken = await _usersApiRelayClient.RegisterAsync(emailAddress, originGraph);
|
||||||
|
|
||||||
|
// Sign in to the system. It requires passing up to 12 challenges.
|
||||||
|
// The server knows the user's origin graph. It is asking the user to validate it by issuing challenges that the user answers.
|
||||||
|
// The Relay client does this procedure for you and returns the session token if the user authenticates successfully.
|
||||||
|
sessionToken = await _usersApiRelayClient.LoginAsync(emailAddress, originGraph, cycle);
|
||||||
|
|
||||||
|
|
||||||
|
// End the authenticated session; log the user out of the system
|
||||||
|
await _usersApiRelayClient.LogoutAsync();
|
||||||
|
|
||||||
|
|
||||||
|
// Check if the email address already exists in the system
|
||||||
|
var exists = await _usersApiRelayClient.CheckEmailAsync(checkEmailAddress);
|
||||||
|
|
||||||
|
// Get the user's email address and metadata
|
||||||
|
var emailResponse = await _usersApiRelayClient.GetEmailAsync();
|
||||||
|
|
||||||
|
// Update the user's email address
|
||||||
|
await _usersApiRelayClient.UpdateEmailAsync("updated.sample@email.address".AsProtected());
|
||||||
|
|
||||||
|
// Send a verification email for the user
|
||||||
|
await _usersApiRelayClient.SendVerificationEmailAsync();
|
||||||
|
|
||||||
|
|
||||||
|
// Validate a user's session token. Used to verify that a user is in fact signed in
|
||||||
|
var validated = _usersApiRelayClient.ValidateAsync(sessionToken);
|
||||||
|
|
||||||
|
|
||||||
|
// Password reset procedure
|
||||||
|
await _usersApiRelayClient.InitiatePasswordResetAsync(emailAddress); // Sends a verification email with resetToken
|
||||||
|
await _usersApiRelayClient.ValidatePasswordResetTokenAsync(emailAddress, resetToken);
|
||||||
|
await _usersApiRelayClient.CommitPasswordResetAsync(emailAddress, resetToken, originGraph); // Generate a new origin graph based on a new password for the user
|
||||||
|
|
||||||
|
// Delete account procedure
|
||||||
|
await _usersApiRelayClient.InitiateDeleteAccountAsync(); // Sends a confirmation email with deleteToken
|
||||||
|
await _usersApiRelayClient.CommitDeleteAccountAsync(deleteToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
using TriunniaLabs.Arch.Api.Users.Client;
|
||||||
|
using TriunniaLabs.Arch.Microservice;
|
||||||
|
using TriunniaLabs.Arch.Microservice.Client;
|
||||||
|
using TriunniaLabs.Memory.Protected;
|
||||||
|
using TriunniaLabs.ZeroKnowledge.Client;
|
||||||
|
|
||||||
|
namespace TriunniaLabs.Arch.Sample.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A controller that shows how to work with the Users Api.
|
||||||
|
/// </summary>
|
||||||
|
public class UsersApiSampleController : ArchControllerBase
|
||||||
|
{
|
||||||
|
// Services
|
||||||
|
private readonly IUsersApiClient _usersApiClient;
|
||||||
|
private readonly IZeroKnowledgeService _zeroKnowledgeService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="microserviceContext">Reference to the microservice context.</param>
|
||||||
|
/// <param name="usersApiClient">Reference to the Users Api client.</param>
|
||||||
|
/// <param name="zeroKnowledgeService">Client-side logic for the zero-knowledge protocol.</param>
|
||||||
|
public UsersApiSampleController(IMicroserviceContext microserviceContext, IUsersApiClient usersApiClient, IZeroKnowledgeService zeroKnowledgeService) : base(microserviceContext)
|
||||||
|
{
|
||||||
|
_usersApiClient = usersApiClient ?? throw new ArgumentNullException(nameof(usersApiClient));
|
||||||
|
_zeroKnowledgeService = zeroKnowledgeService ?? throw new ArgumentNullException(nameof(zeroKnowledgeService));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A sample method that shows how to work with the Users Api.
|
||||||
|
/// </summary>
|
||||||
|
internal async Task SampleAsync()
|
||||||
|
{
|
||||||
|
var encryptionKey = "SampleEncryptionKey".AsProtected(); // Per-row encryption key for the email address
|
||||||
|
var emailAddress = "sample@email.address".AsProtected(encryptionKey.GetDecryptedValue()); // Per-row encrypted email address
|
||||||
|
var checkEmailAddress = "sample@email.address".AsRelayProtected(); // Relay encrypted email address (lower security)
|
||||||
|
var ipAddress = IpAddress.AsProtected(); // User's ip address
|
||||||
|
var sessionToken = Guid.NewGuid().ToString().AsProtected(); // Authenticated session token
|
||||||
|
var resetToken = Guid.NewGuid().AsProtected(); // Password reset token
|
||||||
|
var deleteToken = Guid.NewGuid().AsProtected(); // Delete account token
|
||||||
|
|
||||||
|
|
||||||
|
// Construct origin graph -- the mechanism that verifies the user's password without knowing it
|
||||||
|
var originGraph = _zeroKnowledgeService.GenerateOriginGraph("password");
|
||||||
|
var serializedOrigin = _zeroKnowledgeService.SerializeOriginGraph(originGraph);
|
||||||
|
var protectedOrigin = new Protected<string>(serializedOrigin);
|
||||||
|
|
||||||
|
// Generate the key file, then save keyFileContents to a file. The file itself is password-protected via metadataPassword
|
||||||
|
var cycle = _zeroKnowledgeService.GenerateHamiltonianCycle(originGraph, "password");
|
||||||
|
var keyFileContents = _zeroKnowledgeService.SerializeZeroKnowledgeMetadata(originGraph, cycle, "metadataPassword");
|
||||||
|
|
||||||
|
// Read origin graph from key file
|
||||||
|
_zeroKnowledgeService.DeserializeZeroKnowledgeMetadata(keyFileContents, "metadataPassword", "password", out originGraph!, out cycle!);
|
||||||
|
|
||||||
|
// Set http headers on the client for authentication
|
||||||
|
_usersApiClient.SetAuthorizationHeader(sessionToken);
|
||||||
|
_usersApiClient.SetIpAddressOriginHeader(ipAddress);
|
||||||
|
|
||||||
|
|
||||||
|
// Register a new user in the system
|
||||||
|
sessionToken = await _usersApiClient.RegisterAsync(emailAddress, protectedOrigin);
|
||||||
|
|
||||||
|
// Sign in to the system. It requires passing up to 12 challenges.
|
||||||
|
// The server knows the user's origin graph. It is asking the user to validate it by issuing challenges that the user answers.
|
||||||
|
// Challenges have their own session token. Not to be confused with the user's session token.
|
||||||
|
await _usersApiClient.InitiateLoginAsync(emailAddress);
|
||||||
|
for (int i = 0; i < 12; i++)
|
||||||
|
{
|
||||||
|
// Create the challenge
|
||||||
|
var challengeResponse = await _usersApiClient.CreateChallengeAsync(emailAddress);
|
||||||
|
var challenge = new ZeroKnowledge.Client.ChallengeResponse { Seed = challengeResponse.Seed, SessionToken = challengeResponse.SessionToken };
|
||||||
|
|
||||||
|
// Answer the challenge
|
||||||
|
var answer = _zeroKnowledgeService.AnswerChallenge(challenge, originGraph, cycle);
|
||||||
|
sessionToken = await _usersApiClient.EvaluateAnswerAsync(emailAddress, challenge.SessionToken, answer.Isomorphism.ToArray(), answer.Cycle);
|
||||||
|
if (sessionToken is not null && !sessionToken.IsEmpty)
|
||||||
|
{
|
||||||
|
// Session token returned -- User is authenticated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Authentication fails if the loop is finished after 12 tries with no session token
|
||||||
|
|
||||||
|
// End the authenticated session; log the user out of the system
|
||||||
|
await _usersApiClient.LogoutAsync();
|
||||||
|
|
||||||
|
|
||||||
|
// Check if the email address already exists in the system
|
||||||
|
var exists = await _usersApiClient.CheckEmailAsync(checkEmailAddress);
|
||||||
|
|
||||||
|
// Get the user's email address and metadata
|
||||||
|
var emailResponse = await _usersApiClient.GetEmailAsync();
|
||||||
|
|
||||||
|
// Update the user's email address
|
||||||
|
await _usersApiClient.UpdateEmailAsync("updated.sample@email.address".AsProtected());
|
||||||
|
|
||||||
|
// Send a verification email for the user
|
||||||
|
await _usersApiClient.SendVerificationEmailAsync();
|
||||||
|
|
||||||
|
|
||||||
|
// Get user metadata
|
||||||
|
var user = await _usersApiClient.GetUserAsync();
|
||||||
|
|
||||||
|
|
||||||
|
// Validate a user's session token. Used to verify that a user is in fact signed in
|
||||||
|
var userId = _usersApiClient.ValidateSessionAsync(sessionToken, ipAddress);
|
||||||
|
|
||||||
|
|
||||||
|
// Password reset procedure
|
||||||
|
await _usersApiClient.InitiatePasswordResetAsync(emailAddress); // Sends a verification email with resetToken
|
||||||
|
await _usersApiClient.ValidatePasswordResetTokenAsync(emailAddress, resetToken);
|
||||||
|
await _usersApiClient.CommitPasswordResetAsync(emailAddress, resetToken, protectedOrigin); // Generate a new origin graph based on a new password for the user
|
||||||
|
|
||||||
|
// Delete account procedure
|
||||||
|
await _usersApiClient.InitiateDeleteAccountAsync(); // Sends a confirmation email with deleteToken
|
||||||
|
await _usersApiClient.CommitDeleteAccountAsync(deleteToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using TriunniaLabs.Memory.Protected;
|
||||||
|
|
||||||
|
namespace TriunniaLabs.Arch.Sample.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the protectors used by the application.
|
||||||
|
/// </summary>
|
||||||
|
public class ProtectorDefinitions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the protector for byte arrays.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// In Program.cs, the call to AddTriunniaLabsMicroservice() will internally call AddTriunniaLabsMemoryProtection(),
|
||||||
|
/// which will scan types in the assemblies for any protector registrations such as this one and automatically create protectors
|
||||||
|
/// for them. The protector matching this definition can be obtained by injecting
|
||||||
|
/// [FromKeyedServices(Protectors.ProtectedByteArrayServiceKey)] IProtector<byte[]>.
|
||||||
|
/// </remarks>
|
||||||
|
[HashAlgorithm(HmacSha512HashAlgorithm.ServiceKey)]
|
||||||
|
[EncryptionAlgorithm(AesEncryptionAlgorithm.ServiceKey, Order = 1)]
|
||||||
|
[EncryptionAlgorithm(GZipCompressionAlgorithm.ServiceKey, Order = 2)]
|
||||||
|
[StringConverter(Base65536StringConverter.ServiceKey)]
|
||||||
|
[Protector(Protectors.ProtectedByteArrayServiceKey)]
|
||||||
|
public Protected<byte[]> ByteArrayProtector { get; set; } = Protected<byte[]>.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ using TriunniaLabs.Arch.Relay.Email.Server;
|
|||||||
using TriunniaLabs.Arch.Relay.Users.Server;
|
using TriunniaLabs.Arch.Relay.Users.Server;
|
||||||
using TriunniaLabs.Arch.Sample.Server;
|
using TriunniaLabs.Arch.Sample.Server;
|
||||||
using TriunniaLabs.Memory.Protected;
|
using TriunniaLabs.Memory.Protected;
|
||||||
|
using TriunniaLabs.ZeroKnowledge.Client;
|
||||||
|
|
||||||
// Start the ASP.NET application builder
|
// Start the ASP.NET application builder
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
@@ -27,6 +28,7 @@ builder.Services.AddTriunniaLabsUsersApi(builder.Configuration, builder.Services
|
|||||||
builder.Services.AddTriunniaLabsCommentsApi(builder.Configuration, builder.Services.AddStandardMessagePackFormatters);
|
builder.Services.AddTriunniaLabsCommentsApi(builder.Configuration, builder.Services.AddStandardMessagePackFormatters);
|
||||||
builder.Services.AddTriunniaLabsEntityApi(builder.Configuration, builder.Services.AddStandardMessagePackFormatters);
|
builder.Services.AddTriunniaLabsEntityApi(builder.Configuration, builder.Services.AddStandardMessagePackFormatters);
|
||||||
builder.Services.AddTriunniaLabsFilesApi(builder.Configuration, builder.Services.AddStandardMessagePackFormatters);
|
builder.Services.AddTriunniaLabsFilesApi(builder.Configuration, builder.Services.AddStandardMessagePackFormatters);
|
||||||
|
builder.Services.AddZeroKnowledge();
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
builder.Services.AddRouting(options => options.LowercaseUrls = true);
|
builder.Services.AddRouting(options => options.LowercaseUrls = true);
|
||||||
|
|||||||
@@ -15,8 +15,12 @@
|
|||||||
<PackageReference Include="TriunniaLabs.Arch.Api.Tokens.Client" Version="0.3.1" />
|
<PackageReference Include="TriunniaLabs.Arch.Api.Tokens.Client" Version="0.3.1" />
|
||||||
<PackageReference Include="TriunniaLabs.Arch.Api.Users.Client" Version="0.2.44" />
|
<PackageReference Include="TriunniaLabs.Arch.Api.Users.Client" Version="0.2.44" />
|
||||||
<PackageReference Include="TriunniaLabs.Arch.Microservice" Version="0.9.8" />
|
<PackageReference Include="TriunniaLabs.Arch.Microservice" Version="0.9.8" />
|
||||||
|
<PackageReference Include="TriunniaLabs.Arch.Relay.Email.Blazor" Version="0.2.2" />
|
||||||
<PackageReference Include="TriunniaLabs.Arch.Relay.Email.Server" Version="0.2.2" />
|
<PackageReference Include="TriunniaLabs.Arch.Relay.Email.Server" Version="0.2.2" />
|
||||||
|
<PackageReference Include="TriunniaLabs.Arch.Relay.Users.Blazor" Version="0.2.44" />
|
||||||
<PackageReference Include="TriunniaLabs.Arch.Relay.Users.Server" Version="0.2.44" />
|
<PackageReference Include="TriunniaLabs.Arch.Relay.Users.Server" Version="0.2.44" />
|
||||||
|
<PackageReference Include="TriunniaLabs.Blazor.ZeroKnowledge" Version="0.1.4" />
|
||||||
|
<PackageReference Include="TriunniaLabs.ZeroKnowledge.Client" Version="0.1.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
Reference in New Issue
Block a user