Skip to content

Commit

Permalink
Merge pull request #80 from PinguApps/46-create-email-verification
Browse files Browse the repository at this point in the history
create email verification
  • Loading branch information
pingu2k4 authored Aug 4, 2024
2 parents ce476c4 + f2a54f9 commit 43b30ec
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 25 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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 |
|:-:|:-:|:-:|
Expand Down Expand Up @@ -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) |||
Expand Down
17 changes: 17 additions & 0 deletions src/PinguApps.Appwrite.Client/Clients/AccountClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,21 @@ public async Task<AppwriteResult<Session>> UpdateSession(string sessionId = "cur
return e.GetExceptionResponse<Session>();
}
}

/// <inheritdoc/>
public async Task<AppwriteResult<Token>> CreateEmailVerification(CreateEmailVerificationRequest request)
{
try
{
request.Validate(true);

var result = await _accountApi.CreateEmailVerification(Session, request);

return result.GetApiResponse();
}
catch (Exception e)
{
return e.GetExceptionResponse<Token>();
}
}
}
9 changes: 9 additions & 0 deletions src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,13 @@ public interface IAccountClient
/// <param name="sessionId">Session ID. Use the string 'current' to update the current device session.</param>
/// <returns>The session</returns>
Task<AppwriteResult<Session>> UpdateSession(string sessionId = "current");

/// <summary>
/// <para>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 <see href="https://appwrite.io/docs/references/cloud/client-web/account#updateVerification">complete the verification process</see>. The verification link sent to the user's email address is valid for 7 days.</para>
/// <para>Please note that in order to avoid a <see href="https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md">Redirect Attack</see>, the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.</para>
/// <para><see href="https://appwrite.io/docs/references/1.5.x/client-rest/account#createVerification">Appwrite Docs</see></para>
/// </summary>
/// <param name="request">The request content</param>
/// <returns>The token</returns>
Task<AppwriteResult<Token>> CreateEmailVerification(CreateEmailVerificationRequest request);
}
3 changes: 3 additions & 0 deletions src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,7 @@ internal interface IAccountApi : IBaseApi

[Patch("/account/sessions/{sessionId}")]
Task<IApiResponse<Session>> UpdateSession([Header("x-appwrite-session")] string? session, string sessionId);

[Post(("/account/verification"))]
Task<IApiResponse<Token>> CreateEmailVerification([Header("x-appwrite-session")] string? session, CreateEmailVerificationRequest request);
}
27 changes: 6 additions & 21 deletions src/PinguApps.Appwrite.Playground/App.cs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 = "[email protected]",
// 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));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
using PinguApps.Appwrite.Shared.Requests.Validators;

namespace PinguApps.Appwrite.Shared.Requests;

/// <summary>
/// The request for creating an email verification (will email the user a link to verify their email)
/// </summary>
public class CreateEmailVerificationRequest : BaseRequest<CreateEmailVerificationRequest, CreateEmailVerificationRequestValidator>
{
/// <summary>
/// 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 <see href="https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html">open redirect</see> attack against your project API.
/// </summary>
[JsonPropertyName("url")]
public string Url { get; set; } = string.Empty;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;
using FluentValidation;

namespace PinguApps.Appwrite.Shared.Requests.Validators;
public class CreateEmailVerificationRequestValidator : AbstractValidator<CreateEmailVerificationRequest>
{
public CreateEmailVerificationRequestValidator()
{
RuleFor(x => x.Url).NotEmpty().Must(uri => Uri.TryCreate(uri, UriKind.Absolute, out _));
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
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<ValidationException>(() => 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);
}
}

0 comments on commit 43b30ec

Please sign in to comment.