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>
|
||||
/// 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>
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// A controller that shows how to work the Email Api.
|
||||
/// A controller that shows how to work with the Email Api.
|
||||
/// </summary>
|
||||
public class EmailApiSampleController : ArchControllerBase
|
||||
{
|
||||
@@ -16,7 +16,7 @@ namespace TriunniaLabs.Arch.Sample.Server
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <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)
|
||||
{
|
||||
_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
|
||||
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);
|
||||
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);
|
||||
|
||||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// A controller that shows how to work the Keys Api.
|
||||
/// A controller that shows how to work with the Keys Api.
|
||||
/// </summary>
|
||||
public class KeysApiSampleController : ArchControllerBase
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ using TriunniaLabs.Memory.Protected;
|
||||
namespace TriunniaLabs.Arch.Sample.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// A controller that shows how to work the Tokens Api.
|
||||
/// A controller that shows how to work with the Tokens Api.
|
||||
/// </summary>
|
||||
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.Sample.Server;
|
||||
using TriunniaLabs.Memory.Protected;
|
||||
using TriunniaLabs.ZeroKnowledge.Client;
|
||||
|
||||
// Start the ASP.NET application builder
|
||||
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.AddTriunniaLabsEntityApi(builder.Configuration, builder.Services.AddStandardMessagePackFormatters);
|
||||
builder.Services.AddTriunniaLabsFilesApi(builder.Configuration, builder.Services.AddStandardMessagePackFormatters);
|
||||
builder.Services.AddZeroKnowledge();
|
||||
|
||||
// Configuration
|
||||
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.Users.Client" Version="0.2.44" />
|
||||
<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.Users.Blazor" 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>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user