From ceefd78c66af61729a4ab2eab112712a13aade5c Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Fri, 2 Aug 2024 00:13:44 +0200 Subject: [PATCH 1/6] Added implementation for Create Email Verification --- .../Clients/AccountClient.cs | 17 +++++++++++++++++ .../Clients/IAccountClient.cs | 9 +++++++++ .../Internals/IAccountApi.cs | 3 +++ .../Requests/CreateEmailVerificationRequest.cs | 16 ++++++++++++++++ .../CreateEmailVerificationRequestValidator.cs | 11 +++++++++++ 5 files changed, 56 insertions(+) create mode 100644 src/PinguApps.Appwrite.Shared/Requests/CreateEmailVerificationRequest.cs create mode 100644 src/PinguApps.Appwrite.Shared/Requests/Validators/CreateEmailVerificationRequestValidator.cs diff --git a/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs b/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs index 3cc3748a..88e6bfe0 100644 --- a/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs @@ -229,4 +229,21 @@ public async Task> UpdateSession(string sessionId = "cur return e.GetExceptionResponse(); } } + + /// + public async Task> CreateEmailVerification(CreateEmailVerificationRequest request) + { + try + { + request.Validate(true); + + var result = await _accountApi.CreateEmailVerification(Session, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } } diff --git a/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs b/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs index b05c29aa..3d53e61b 100644 --- a/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs @@ -108,4 +108,13 @@ public interface IAccountClient /// Session ID. Use the string 'current' to update the current device session. /// The session Task> UpdateSession(string sessionId = "current"); + + /// + /// Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the userId and secret arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the userId and secret parameters. Learn more about how to complete the verification process. The verification link sent to the user's email address is valid for 7 days. + /// Please note that in order to avoid a Redirect Attack, the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface. + /// Appwrite Docs + /// + /// The request content + /// The token + Task> CreateEmailVerification(CreateEmailVerificationRequest request); } diff --git a/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs b/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs index bf8a9164..e6ed1dd9 100644 --- a/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs +++ b/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs @@ -43,4 +43,7 @@ internal interface IAccountApi : IBaseApi [Patch("/account/sessions/{sessionId}")] Task> UpdateSession([Header("x-appwrite-session")] string? session, string sessionId); + + [Post(("account/verification"))] + Task> CreateEmailVerification([Header("x-appwrite-session")] string? session, CreateEmailVerificationRequest request); } diff --git a/src/PinguApps.Appwrite.Shared/Requests/CreateEmailVerificationRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/CreateEmailVerificationRequest.cs new file mode 100644 index 00000000..c4f215a6 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/CreateEmailVerificationRequest.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Requests.Validators; + +namespace PinguApps.Appwrite.Shared.Requests; + +/// +/// The request for creating an email verification (will email the user a link to verify their email) +/// +public class CreateEmailVerificationRequest : BaseRequest +{ + /// + /// URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an open redirect attack against your project API. + /// + [JsonPropertyName("url")] + public string Url { get; set; } = string.Empty; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Validators/CreateEmailVerificationRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Validators/CreateEmailVerificationRequestValidator.cs new file mode 100644 index 00000000..be9dcd0b --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Validators/CreateEmailVerificationRequestValidator.cs @@ -0,0 +1,11 @@ +using System; +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Validators; +public class CreateEmailVerificationRequestValidator : AbstractValidator +{ + public CreateEmailVerificationRequestValidator() + { + RuleFor(x => x.Url).NotEmpty().Must(uri => Uri.TryCreate(uri, UriKind.Absolute, out _)); + } +} From e2a9d3717dbadea1e49e33bc0853b9d06b5551ee Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Fri, 2 Aug 2024 00:20:53 +0200 Subject: [PATCH 2/6] Fixed bug and created playground example --- .../Internals/IAccountApi.cs | 2 +- src/PinguApps.Appwrite.Playground/App.cs | 27 +++++-------------- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs b/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs index e6ed1dd9..2985dd38 100644 --- a/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs +++ b/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs @@ -44,6 +44,6 @@ internal interface IAccountApi : IBaseApi [Patch("/account/sessions/{sessionId}")] Task> UpdateSession([Header("x-appwrite-session")] string? session, string sessionId); - [Post(("account/verification"))] + [Post(("/account/verification"))] Task> CreateEmailVerification([Header("x-appwrite-session")] string? session, CreateEmailVerificationRequest request); } diff --git a/src/PinguApps.Appwrite.Playground/App.cs b/src/PinguApps.Appwrite.Playground/App.cs index 6f70b111..f0d5173c 100644 --- a/src/PinguApps.Appwrite.Playground/App.cs +++ b/src/PinguApps.Appwrite.Playground/App.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Configuration; using PinguApps.Appwrite.Client; using PinguApps.Appwrite.Server.Servers; +using PinguApps.Appwrite.Shared.Requests; namespace PinguApps.Appwrite.Playground; internal class App @@ -20,32 +21,16 @@ public async Task Run(string[] args) { _client.SetSession(_session); - Console.WriteLine("Getting Session..."); + var request = new CreateEmailVerificationRequest + { + Url = "https://localhost:5001/abc123" + }; - //var response = await _client.Account.CreateEmailToken(new CreateEmailTokenRequest - //{ - // Email = "pingu@pinguapps.com", - // UserId = "664aac1a00113f82e620" - //}); - - //var response = await _client.Account.CreateSession(new CreateSessionRequest - //{ - // UserId = "664aac1a00113f82e620", - // Secret = "623341" - //}); - - var response = await _client.Account.GetSession("66a810f2e55b1329e25b"); - - var response2 = await _client.Account.UpdateSession("66a810f2e55b1329e25b"); + var response = await _client.Account.CreateEmailVerification(request); Console.WriteLine(response.Result.Match( account => account.ToString(), appwriteError => appwriteError.Message, internalERror => internalERror.Message)); - - Console.WriteLine(response2.Result.Match( - account => account.ToString(), - appwriteError => appwriteError.Message, - internalERror => internalERror.Message)); } } From fa9fa9cf8d55a8af6b586b21fb93e946bfcb46cb Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Sun, 4 Aug 2024 12:27:25 +0200 Subject: [PATCH 3/6] Added client tests --- ...ountClientTests.CreateEmailVerification.cs | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.CreateEmailVerification.cs diff --git a/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.CreateEmailVerification.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.CreateEmailVerification.cs new file mode 100644 index 00000000..e9781649 --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.CreateEmailVerification.cs @@ -0,0 +1,80 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests; +using PinguApps.Appwrite.Shared.Tests; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Client.Tests.Clients.Account; +public partial class AccountClientTests +{ + [Fact] + public async Task CreateEmailVerification_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new CreateEmailVerificationRequest() + { + Url = "https://localhost:5001/abc123" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account/verification") + .ExpectedHeaders(true) + .WithJsonContent(request) + .Respond(Constants.AppJson, Constants.TokenResponse); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.CreateEmailVerification(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task CreateEmailVerification_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new CreateEmailVerificationRequest() + { + Url = "https://localhost:5001/abc123" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account/verification") + .ExpectedHeaders(true) + .WithJsonContent(request) + .Respond(HttpStatusCode.BadRequest, Constants.AppJson, Constants.AppwriteError); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.CreateEmailVerification(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task CreateEmailVerification_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new CreateEmailVerificationRequest() + { + Url = "https://localhost:5001/abc123" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account/verification") + .ExpectedHeaders(true) + .WithJsonContent(request) + .Throw(new HttpRequestException("An error occurred")); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.CreateEmailVerification(request); + + // Assert + Assert.False(result.Success); + Assert.True(result.IsInternalError); + Assert.Equal("An error occurred", result.Result.AsT2.Message); + } +} From d8ac2fd534633786e49bd9a71100f46a23950927 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Sun, 4 Aug 2024 12:54:00 +0200 Subject: [PATCH 4/6] Added shared tests --- .../CreateEmailVerificationRequestTests.cs | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 tests/PinguApps.Appwrite.Shared.Tests/Requests/CreateEmailVerificationRequestTests.cs diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/CreateEmailVerificationRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/CreateEmailVerificationRequestTests.cs new file mode 100644 index 00000000..b942fc4a --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/CreateEmailVerificationRequestTests.cs @@ -0,0 +1,98 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests; + +namespace PinguApps.Appwrite.Shared.Tests.Requests; +public class CreateEmailVerificationRequestTests +{ + + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new CreateEmailVerificationRequest(); + + // Assert + Assert.NotNull(request.Url); + Assert.Equal(string.Empty, request.Url); + } + + [Theory] + [InlineData("https://google.com")] + [InlineData("https://localhost:1234")] + public void Properties_CanBeSet(string url) + { + // Arrange + var request = new CreateEmailVerificationRequest(); + + // Act + request.Url = url; + + // Assert + Assert.Equal(url, request.Url); + } + + [Theory] + [InlineData("https://google.com")] + [InlineData("https://localhost:1234")] + public void IsValid_WithValidData_ReturnsTrue(string url) + { + // Arrange + var request = new CreateEmailVerificationRequest + { + Url = url + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + [Theory] + [InlineData("")] + [InlineData("not a url")] + public void IsValid_WithInvalidData_ReturnsFalse(string url) + { + // Arrange + var request = new CreateEmailVerificationRequest + { + Url = url + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new CreateEmailVerificationRequest + { + Url = "" + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new CreateEmailVerificationRequest + { + Url = "" + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} From da46355138587b955a9928c79ae8f76996587866 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Sun, 4 Aug 2024 12:55:23 +0200 Subject: [PATCH 5/6] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5188eef8..19aa45a7 100644 --- a/README.md +++ b/README.md @@ -139,11 +139,11 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( ## ⌛ Progress ### Server & Client -![14 / 288](https://progress-bar.dev/14/?scale=288&suffix=%20/%20288&width=500) +![15 / 288](https://progress-bar.dev/15/?scale=288&suffix=%20/%20288&width=500) ### Server Only ![2 / 195](https://progress-bar.dev/2/?scale=195&suffix=%20/%20195&width=300) ### Client Only -![12 / 93](https://progress-bar.dev/12/?scale=93&suffix=%20/%2093&width=300) +![13 / 93](https://progress-bar.dev/13/?scale=93&suffix=%20/%2093&width=300) ### 🔑 Key | Icon | Definition | @@ -153,7 +153,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | ❌ | There is currently no intention to implement the endpoint for the given SDK type (client or server) | ### Account -![14 / 52](https://progress-bar.dev/14/?scale=52&suffix=%20/%2052&width=120) +![15 / 52](https://progress-bar.dev/15/?scale=52&suffix=%20/%2052&width=120) | Endpoint | Client | Server | |:-:|:-:|:-:| @@ -200,7 +200,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | [Create Magic URL Token](https://appwrite.io/docs/references/1.5.x/client-rest/account#createMagicURLToken) | ⬛ | ⬛ | | [Create OAuth2 Token](https://appwrite.io/docs/references/1.5.x/client-rest/account#createOAuth2Token) | ⬛ | ⬛ | | [Create Phone Token](https://appwrite.io/docs/references/1.5.x/client-rest/account#createPhoneToken) | ⬛ | ⬛ | -| [Create Email Verification](https://appwrite.io/docs/references/1.5.x/client-rest/account#createVerification) | ⬛ | ❌ | +| [Create Email Verification](https://appwrite.io/docs/references/1.5.x/client-rest/account#createVerification) | ✅ | ❌ | | [Create Email Verification (Confirmation)](https://appwrite.io/docs/references/1.5.x/client-rest/account#updateVerification) | ⬛ | ❌ | | [Create Phone Verification](https://appwrite.io/docs/references/1.5.x/client-rest/account#createPhoneVerification) | ⬛ | ❌ | | [Create Phone Verification (Confirmation)](https://appwrite.io/docs/references/1.5.x/client-rest/account#updatePhoneVerification) | ⬛ | ❌ | From f2a54f97920157e20e0ba454d32d549c2a3183b4 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Sun, 4 Aug 2024 11:56:38 +0100 Subject: [PATCH 6/6] Update tests/PinguApps.Appwrite.Shared.Tests/Requests/CreateEmailVerificationRequestTests.cs Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com> --- .../Requests/CreateEmailVerificationRequestTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/CreateEmailVerificationRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/CreateEmailVerificationRequestTests.cs index b942fc4a..255af5a6 100644 --- a/tests/PinguApps.Appwrite.Shared.Tests/Requests/CreateEmailVerificationRequestTests.cs +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/CreateEmailVerificationRequestTests.cs @@ -4,7 +4,6 @@ namespace PinguApps.Appwrite.Shared.Tests.Requests; public class CreateEmailVerificationRequestTests { - [Fact] public void Constructor_InitializesWithExpectedValues() {