Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consumer API: Password-protected RelationshipTemplates #900

Merged
merged 60 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
413b9fd
feat: support password protected relationship templates, wip
NikolaVetnic Sep 25, 2024
696dcde
Merge branch 'main' into abl-19-password-protected-relationship-templ…
tnotheis Sep 25, 2024
8c2164c
test: remove redundant scenarios
tnotheis Oct 1, 2024
a9d24c3
test: move step definitions to correct places and only use SINGLE_THI…
tnotheis Oct 1, 2024
d08b398
test: rename SINGLE_THING_OR_DEFAULT to OPTIONAL_SINGLE_THING to bett…
tnotheis Oct 1, 2024
b4af9fb
test: revert change to ThenTheResponseStatusCodeIs step definition
tnotheis Oct 1, 2024
4e94502
test: use GetBytes extension method to convert string to bytes
tnotheis Oct 1, 2024
b18cb7d
feat: when establishing a relationship, make sure the template is all…
tnotheis Oct 1, 2024
d4e5b04
chore: fix a nullability issue
tnotheis Oct 1, 2024
8cc56ea
refactor: expose RelationshipTemplate.CanBeCollectedWithPassword as s…
tnotheis Oct 1, 2024
b337080
test: use Convert.FromBase64String again
tnotheis Oct 1, 2024
d2f8b9f
refactor: renaming and simplifcations
tnotheis Oct 1, 2024
5c075f9
fix: change implementation of CanBeCollectedWithPassword2 to not comp…
tnotheis Oct 1, 2024
a125f9b
test: fix assertion
tnotheis Oct 1, 2024
9beb9a5
chore: rename scenario
NikolaVetnic Oct 3, 2024
bc4a67b
test: remove redundant test case
NikolaVetnic Oct 3, 2024
f1df528
test: state explicitly which relations templates are expected
NikolaVetnic Oct 3, 2024
97c28f2
test: tidy up
NikolaVetnic Oct 3, 2024
61b88db
feat: update relationship template validators
NikolaVetnic Oct 3, 2024
3f21ba3
chore: update method name
NikolaVetnic Oct 3, 2024
a56272d
chore: rename method
NikolaVetnic Oct 3, 2024
3f5bea1
chore: clean up message
NikolaVetnic Oct 3, 2024
d02332b
chore: remove redundant method
NikolaVetnic Oct 3, 2024
31500df
feat: update sdk to allow for password protected templates
NikolaVetnic Oct 3, 2024
f8b4e7f
Merge branch 'main' into abl-19-password-protected-relationship-templ…
mergify[bot] Oct 3, 2024
cf3950b
fix: add endpoint expected by transport tests
NikolaVetnic Oct 3, 2024
872db66
Merge branch 'abl-19-password-protected-relationship-templates' of gi…
NikolaVetnic Oct 3, 2024
b7f6c93
fix: add temporary endpoint to satisfy transport tests
NikolaVetnic Oct 3, 2024
27452f1
Merge branch 'main' into abl-19-password-protected-relationship-templ…
mergify[bot] Oct 7, 2024
a8b7425
Merge branch 'main' into abl-19-password-protected-relationship-templ…
mergify[bot] Oct 7, 2024
470fd38
Merge branch 'main' into abl-19-password-protected-relationship-templ…
mergify[bot] Oct 8, 2024
976c245
Merge branch 'main' into abl-19-password-protected-relationship-templ…
mergify[bot] Oct 8, 2024
a4beabb
feat: address pr change requests
NikolaVetnic Oct 10, 2024
32486f1
Merge branch 'main' of github.com:nmshd/backbone into abl-19-password…
NikolaVetnic Oct 10, 2024
6dd8e83
Merge branch 'abl-19-password-protected-relationship-templates' of gi…
NikolaVetnic Oct 10, 2024
9d0f943
fix: address issues causing tests to fail
NikolaVetnic Oct 10, 2024
c4d1d54
chore: fix formatting
NikolaVetnic Oct 10, 2024
f2d5be2
feat: catch and process json parsing exception
NikolaVetnic Oct 10, 2024
8bdfe30
fix: revert manually edited snapshots
NikolaVetnic Oct 10, 2024
76feb80
feat: add migration limiting the password length
NikolaVetnic Oct 10, 2024
9663f2e
test: limit the scope of resharper warning disabling
tnotheis Oct 10, 2024
f429315
test: improve property names
tnotheis Oct 10, 2024
f183215
refactor: rename RelationshipTemplateQuery to RelationshipTemplateQue…
tnotheis Oct 10, 2024
ee9f785
refactor: don't set error message and code when using NumberOfBytes e…
tnotheis Oct 10, 2024
bf1a4be
fix: allow camelCase in GetAll query string
tnotheis Oct 10, 2024
b2499e8
refactor: add curly braces for better readability
tnotheis Oct 10, 2024
1179eb6
refactor: use generic application error in case the input cannot be p…
tnotheis Oct 10, 2024
cb98858
fix: fix merge error
tnotheis Oct 10, 2024
ef7236a
chore: delete redundant methods added by merge
tnotheis Oct 10, 2024
6b380bc
chore: fix formatting
tnotheis Oct 10, 2024
ef499cd
feat: revert to state before adding the password field
NikolaVetnic Oct 10, 2024
6809747
feat: add password with required length in a single migration
NikolaVetnic Oct 10, 2024
83969cf
chore: remove comment
NikolaVetnic Oct 10, 2024
b58d6b2
Merge branch 'abl-19-password-protected-relationship-templates' of gi…
NikolaVetnic Oct 10, 2024
b9afa8e
Merge branch 'main' into abl-19-password-protected-relationship-templ…
mergify[bot] Oct 11, 2024
818ffd5
refactor: add migration "the correct way"
tnotheis Oct 11, 2024
c00ba5f
refactor: use new MAX_PASSWORD_LENGTH constant in ListRelationshipTem…
tnotheis Oct 11, 2024
56a6735
feat: validate password max length in GetRelationshipTemplateQuery
tnotheis Oct 11, 2024
c8253d6
refactor: remove database configuration from db context
tnotheis Oct 11, 2024
927b3ee
chore: fix formatting
tnotheis Oct 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
@Integration
Feature: GET /RelationshipTemplates

User requests Relationship Templates

Scenario Outline: Requesting a list of Relationship Templates in a variety of scenarios
Given Identities i1, i2, i3 and i4
And the following Relationship Templates
| rTempName | rTempOwner | forIdentity | password |
| rt1 | i1 | - | - |
| rt2 | i2 | - | - |
| rt3 | i1 | - | - |
| rt4 | i2 | - | - |
| rt5 | i1 | - | password |
| rt6 | i1 | - | password |
| rt7 | i2 | - | password |
| rt8 | i2 | - | password |
| rt9 | i1 | i1 | - |
| rt10 | i2 | i3 | - |
| rt11 | i2 | i2 | - |
| rt12 | i2 | i3 | - |
| rt13 | i2 | i3 | password |
| rt14 | i2 | i3 | password |
When <activeIdentity> sends a GET request to the /RelationshipTemplate endpoint with the following payloads
| rTempName | passwordOnGet |
| rt1 | - |
| rt2 | - |
| rt3 | password |
| rt4 | password |
| rt5 | password |
| rt6 | - |
| rt7 | password |
| rt8 | - |
| rt9 | - |
| rt10 | - |
| rt11 | - |
| rt12 | - |
| rt13 | password |
| rt14 | wordpass |
Then the response contains Relationship Template(s) <retreivedTemplates>

Examples:
| activeIdentity | retreivedTemplates |
| i1 | rt1, rt2, rt3, rt4, rt5, rt6, rt7, rt9 |
| i2 | rt1, rt2, rt3, rt4, rt5, rt7, rt8, rt10, rt11, rt12, rt13, rt14 |
| i3 | rt1, rt2, rt3, rt4, rt5, rt7, rt10, rt12, rt13 |
| i4 | rt1, rt2, rt3, rt4, rt5, rt7 |
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,14 @@ User creates a Relationship Template
When i sends a POST request to the /RelationshipTemplates endpoint
Then the response status code is 201 (Created)
And the response contains a RelationshipMetadata

Scenario: Creating a relationship template with a password
Given Identity i
When i sends a POST request to the /RelationshipTemplates endpoint with the password "my-password"
Then the response status code is 201 (Created)
And the response contains a RelationshipMetadata

Scenario: Create a personalized Relationship Template with a password
Given Identities i1 and i2
When i1 sends a POST request to the /RelationshipTemplate endpoint with password "my-password" and forIdentity i2
Then the response status code is 201 (Created)
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,26 @@ Feature: GET /RelationshipTemplates/{id}

User requests a Relationship Template

Scenario: Requesting a Relationship Template with ForIdentity created for me
Given Identities i1 and i2
And Relationship Template rt created by i1 where ForIdentity is the address of i2
When i2 sends a GET request to the /RelationshipTemplates/{id} endpoint with rt.Id
Then the response status code is 200 (Ok)
And the response contains a rt
Scenario Outline: Requesting a Relationship Template in a variety of scenarios
Given Identities <givenIdentities>
And Relationship Template rt created by <rTempOwner> with password "<password>" and forIdentity <forIdentity>
When <activeIdentity> sends a GET request to the /RelationshipTemplate/rt.Id endpoint with password "<passwordOnGet>"
Then the response status code is <responseStatusCode>

Scenario: Requesting a Relationship Template with ForIdentity created by me
Given Identities i1 and i2
And Relationship Template rt created by i1 where ForIdentity is the address of i2
When i1 sends a GET request to the /RelationshipTemplates/{id} endpoint with rt.Id
Then the response status code is 200 (Ok)
And the response contains a rt

Scenario: Requesting a Relationship Template with ForIdentity created for someone else
Given Identities i1, i2 and i3
And Relationship Template rt created by i1 where ForIdentity is the address of i2
When i3 sends a GET request to the /RelationshipTemplates/{id} endpoint with rt.Id
Then the response status code is 404 (Not Found)
Examples:
| givenIdentities | rTempOwner | forIdentity | password | activeIdentity | passwordOnGet | responseStatusCode | description |
| i | i | - | - | i | - | 200 (OK) | owner tries to get |
| i1 and i2 | i1 | - | - | i2 | - | 200 (OK) | non-owner tries to get |
| i | i | - | - | i | password | 200 (OK) | owner passes password even though none is set |
| i1 and i2 | i1 | - | - | i2 | password | 200 (OK) | non-owner identity passes password even though none is set |
| i | i | - | password | i | password | 200 (OK) | owner passes correct password |
| i | i | - | password | i | - | 200 (OK) | owner doesn't pass password, even though one is set |
| i1 and i2 | i1 | - | password | i2 | password | 200 (OK) | non-owner identity passes correct password |
| i1 and i2 | i1 | - | password | i2 | - | 404 (Not Found) | non-owner identity passes no password even though one is set |
| i | i | i | - | i | - | 200 (OK) | owner is forIdentity and tries to get |
| i1 and i2 | i1 | i2 | - | i1 | - | 200 (OK) | non-owner is forIdentity, creator tries to get |
| i1 and i2 | i1 | i1 | - | i2 | - | 404 (Not Found) | owner is forIdentity and non-owner tries to get |
| i1 and i2 | i1 | i2 | - | i2 | - | 200 (OK) | non-owner is forIdentity and tries to get |
| i1 and i2 | i1 | i2 | password | i2 | password | 200 (OK) | non-owner is forIdentity and tries to get with correct password |
| i1 and i2 | i1 | i2 | password | i2 | wordpass | 404 (Not Found) | non-owner is forIdentity and tries to get with incorrect password |
| i1, i2 and i3 | i1 | i2 | password | i3 | password | 404 (Not Found) | non-owner is forIdentity, and thirdParty tries to get |
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
public static class RegexFor
{
public const string SINGLE_THING = "([a-zA-Z0-9]+)";
public const string OPTIONAL_SINGLE_THING = "([a-zA-Z0-9-]+)";
public const string LIST_OF_THINGS = "([a-zA-Z0-9, ]+)";
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
using Backbone.ConsumerApi.Sdk.Endpoints.RelationshipTemplates.Types.Requests;
using System.Text;
using Backbone.BuildingBlocks.SDK.Endpoints.Common.Types;
using Backbone.ConsumerApi.Sdk.Endpoints.RelationshipTemplates.Types.Requests;
using Backbone.ConsumerApi.Sdk.Endpoints.RelationshipTemplates.Types.Responses;
using Backbone.ConsumerApi.Tests.Integration.Contexts;
using Backbone.ConsumerApi.Tests.Integration.Helpers;
using TechTalk.SpecFlow.Assist;

// ReSharper disable ClassNeverInstantiated.Local
// ReSharper disable UnusedAutoPropertyAccessor.Local

namespace Backbone.ConsumerApi.Tests.Integration.StepDefinitions;

Expand All @@ -13,6 +20,8 @@ internal class RelationshipTemplatesStepDefinitions
private readonly RelationshipTemplatesContext _relationshipTemplatesContext;
private readonly ClientPool _clientPool;

private ApiResponse<ListRelationshipTemplatesResponse>? _listRelationshipTemplatesResponse;

public RelationshipTemplatesStepDefinitions(ResponseContext responseContext, RelationshipTemplatesContext relationshipTemplatesContext, ClientPool clientPool)
{
_responseContext = responseContext;
Expand All @@ -24,18 +33,37 @@ public RelationshipTemplatesStepDefinitions(ResponseContext responseContext, Rel

#region Given

[Given($"Relationship Template {RegexFor.SINGLE_THING} created by {RegexFor.SINGLE_THING} where ForIdentity is the address of {RegexFor.SINGLE_THING}")]
public async Task GivenRelationshipTemplateCreatedByIWhereForIdentityIsTheAddressOfI(string relationshipTemplateName, string identityName, string forIdentityName)
[Given($@"Relationship Template {RegexFor.SINGLE_THING} created by {RegexFor.SINGLE_THING} with password ""([^""]*)"" and forIdentity {RegexFor.OPTIONAL_SINGLE_THING}")]
public async Task GivenRelationshipTemplateCreatedByTokenOwnerWithPasswordAndForIdentity(string relationshipTemplateName, string identityName, string passwordString, string forIdentityName)
{
var client = _clientPool.FirstForIdentityName(identityName);
var forClient = _clientPool.FirstForIdentityName(forIdentityName);
var forClient = forIdentityName != "-" ? _clientPool.FirstForIdentityName(forIdentityName).IdentityData!.Address : null;
var password = passwordString.Trim() != "-" ? Convert.FromBase64String(passwordString.Trim()) : null;

var response = await client.RelationshipTemplates
.CreateTemplate(new CreateRelationshipTemplateRequest { Content = TestData.SOME_BYTES, ForIdentity = forClient.IdentityData!.Address });
.CreateTemplate(new CreateRelationshipTemplateRequest { Content = TestData.SOME_BYTES, ForIdentity = forClient, Password = password });

_relationshipTemplatesContext.CreateRelationshipTemplatesResponses[relationshipTemplateName] = response.Result!;
}

[Given(@"the following Relationship Templates")]
public async Task GivenRelationshipTemplatesWithTheFollowingProperties(Table table)
tnotheis marked this conversation as resolved.
Show resolved Hide resolved
{
var relationshipTemplatePropertiesSet = table.CreateSet<RelationshipTemplateProperties>();

foreach (var relationshipTemplateProperties in relationshipTemplatePropertiesSet)
{
var client = _clientPool.FirstForIdentityName(relationshipTemplateProperties.RTempOwner);
var forClient = relationshipTemplateProperties.ForIdentity != "-" ? _clientPool.FirstForIdentityName(relationshipTemplateProperties.ForIdentity).IdentityData!.Address : null;
var password = relationshipTemplateProperties.Password.Trim() != "-" ? Convert.FromBase64String(relationshipTemplateProperties.Password.Trim()) : null;

var response = await client.RelationshipTemplates
.CreateTemplate(new CreateRelationshipTemplateRequest { Content = TestData.SOME_BYTES, ForIdentity = forClient, Password = password });

_relationshipTemplatesContext.CreateRelationshipTemplatesResponses[relationshipTemplateProperties.RTempName] = response.Result!;
}
}

#endregion

#region When
Expand All @@ -45,19 +73,85 @@ public async Task WhenIdentitySendsAPostRequestToTheRelationshipTemplatesEndpoin
{
var client = _clientPool.FirstForIdentityName(identityName);

_responseContext.WhenResponse = await client.RelationshipTemplates.CreateTemplate(new CreateRelationshipTemplateRequest { Content = TestData.SOME_BYTES });
_responseContext.WhenResponse = await client.RelationshipTemplates.CreateTemplate(
new CreateRelationshipTemplateRequest { Content = TestData.SOME_BYTES });
}

[When($@"{RegexFor.SINGLE_THING} sends a POST request to the /RelationshipTemplates endpoint with the password ""(.*)""")]
public async Task WhenIdentitySendsAPostRequestToTheRelationshipTemplatesEndpointWithThePassword(string identityName, string passwordString)
{
var client = _clientPool.FirstForIdentityName(identityName);
var password = Encoding.UTF8.GetBytes(passwordString);

_responseContext.WhenResponse = await client.RelationshipTemplates.CreateTemplate(
new CreateRelationshipTemplateRequest { Content = TestData.SOME_BYTES, Password = password });
}

[When($@"{RegexFor.SINGLE_THING} sends a POST request to the /RelationshipTemplate endpoint with password ""(.*)"" and forIdentity {RegexFor.SINGLE_THING}")]
public async Task WhenIdentitySendsAPostRequestToTheRelationshipTemplatesEndpointWithPasswordAndForIdentity(string identityName, string passwordString, string forIdentityName)
{
var client = _clientPool.FirstForIdentityName(identityName);
var forClient = _clientPool.FirstForIdentityName(forIdentityName);
var password = Encoding.UTF8.GetBytes(passwordString);

_responseContext.WhenResponse = await client.RelationshipTemplates.CreateTemplate(
new CreateRelationshipTemplateRequest { Content = TestData.SOME_BYTES, ForIdentity = forClient.IdentityData!.Address, Password = password });
}

[When($"{RegexFor.SINGLE_THING} sends a GET request to the /RelationshipTemplates/{{id}} endpoint with {RegexFor.SINGLE_THING}.Id")]
public async Task WhenISendsAGetRequestToTheRelationshipTemplatesIdEndpointWithId(string identityName, string relationshipTemplateName)
[When($@"{RegexFor.SINGLE_THING} sends a GET request to the /RelationshipTemplate/{RegexFor.SINGLE_THING}.Id endpoint with password ""([^""]*)""")]
public async Task WhenIdentitySendsAGetRequestToTheRelationshipTemplatesIdEndpointWithPassword(string identityName, string relationshipTemplateName, string password)
{
var client = _clientPool.FirstForIdentityName(identityName);

var relationshipTemplateId = _relationshipTemplatesContext.CreateRelationshipTemplatesResponses[relationshipTemplateName].Id;

_responseContext.WhenResponse = await client.RelationshipTemplates.GetTemplate(relationshipTemplateId);
_responseContext.WhenResponse = password != "-"
? await client.RelationshipTemplates.GetTemplate(relationshipTemplateId, Convert.FromBase64String(password.Trim()))
: await client.RelationshipTemplates.GetTemplate(relationshipTemplateId);
}

[When($@"{RegexFor.SINGLE_THING} sends a GET request to the /RelationshipTemplate endpoint with the following payloads")]
public async Task WhenISendsAGETRequestToTheRelationshipTemplateEndpointWithTheFollowingPayloads(string identityName, Table table)
{
var client = _clientPool.FirstForIdentityName(identityName);

var getRequestPayloadSet = table.CreateSet<GetRequestPayload>();

var queries = getRequestPayloadSet.Select(payload =>
{
var relationshipTemplateId = _relationshipTemplatesContext.CreateRelationshipTemplatesResponses[payload.RTempName].Id;
var password = payload.PasswordOnGet == "-" ? null : Convert.FromBase64String(payload.PasswordOnGet.Trim());

return new RelationshipTemplateQuery { Id = relationshipTemplateId, Password = password };
}).ToList();

_responseContext.WhenResponse = _listRelationshipTemplatesResponse = await client.RelationshipTemplates.ListTemplates(queries);
}

#endregion

#region Then

[Then($@"the response contains Relationship Template\(s\) {RegexFor.LIST_OF_THINGS}")]
public void ThenTheResponseContainsRelationshipTemplates(string relationshipTemplateNames)
{
var relationshipTemplates = relationshipTemplateNames.Split(',').Select(item => _relationshipTemplatesContext.CreateRelationshipTemplatesResponses[item.Trim()]).ToList();
_listRelationshipTemplatesResponse!.Result!.Should().BeEquivalentTo(relationshipTemplates, options => options.WithStrictOrdering());
}

#endregion
}

file class RelationshipTemplateProperties
{
public required string RTempName { get; set; }
public required string RTempOwner { get; set; }
public required string ForIdentity { get; set; }
public required string Password { get; set; }
}

file class GetRequestPayload
{
public required string RTempName { get; set; }
public required string PasswordOnGet { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public static class NumberOfBytesValidatorRuleBuilderExtensions
.WithErrorCode(GenericApplicationErrors.Validation.InvalidPropertyValue().Code);
}

public static IRuleBuilderOptions<T, byte[]?> NumberOfBytes<T>(this IRuleBuilder<T, byte[]> ruleBuilder,
public static IRuleBuilderOptions<T, byte[]?> NumberOfBytes<T>(this IRuleBuilder<T, byte[]?> ruleBuilder,
int numberOfBytes)
{
return ruleBuilder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
using Backbone.BuildingBlocks.Application.Abstractions.Infrastructure.Persistence.Database;
using Backbone.BuildingBlocks.Application.Pagination;
using Backbone.DevelopmentKit.Identity.ValueObjects;
using Backbone.Modules.Relationships.Application.RelationshipTemplates.Queries.ListRelationshipTemplates;
using Backbone.Modules.Relationships.Domain.Aggregates.RelationshipTemplates;

namespace Backbone.Modules.Relationships.Application.Infrastructure.Persistence.Repository;

public interface IRelationshipTemplatesRepository
{
Task<DbPaginationResult<RelationshipTemplate>> FindTemplatesWithIds(IEnumerable<RelationshipTemplateId> ids, IdentityAddress identityAddress, PaginationFilter paginationFilter,
Task<DbPaginationResult<RelationshipTemplate>> FindTemplatesWithIds(IEnumerable<RelationshipTemplateQueryItem> queryItems, IdentityAddress activeIdentity, PaginationFilter paginationFilter,
CancellationToken cancellationToken, bool track = false);

Task<RelationshipTemplate?> Find(RelationshipTemplateId id, IdentityAddress identityAddress, CancellationToken cancellationToken, bool track = false);
Task Add(RelationshipTemplate template, CancellationToken cancellationToken);
Task Update(RelationshipTemplate template);
Expand All @@ -21,5 +23,4 @@ Task<DbPaginationResult<RelationshipTemplate>> FindTemplatesWithIds(IEnumerable<
Task UpdateRelationshipTemplateAllocations(List<RelationshipTemplateAllocation> templateAllocations, CancellationToken cancellationToken);

#endregion RelationshipTemplateAllocations

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ public class CreateRelationshipTemplateCommand : IRequest<CreateRelationshipTemp
public int? MaxNumberOfAllocations { get; set; }
public required byte[] Content { get; set; }
public string? ForIdentity { get; set; }
public byte[]? Password { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public async Task<CreateRelationshipTemplateResponse> Handle(CreateRelationshipT
request.MaxNumberOfAllocations,
request.ExpiresAt,
request.Content,
forIdentity);
forIdentity,
request.Password);

await _relationshipTemplatesRepository.Add(template, cancellationToken);

Expand Down
Loading