From fe2a1037a65d3a859d6fbf5ddb686b1b60429672 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Tue, 24 Sep 2024 18:58:14 +0100 Subject: [PATCH 1/7] Added request for update phone session request --- .../Requests/UpdatePhoneSessionRequest.cs | 22 +++++++++++++++++++ .../UpdatePhoneSessionRequestValidator.cs | 15 +++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 src/PinguApps.Appwrite.Shared/Requests/UpdatePhoneSessionRequest.cs create mode 100644 src/PinguApps.Appwrite.Shared/Requests/Validators/UpdatePhoneSessionRequestValidator.cs diff --git a/src/PinguApps.Appwrite.Shared/Requests/UpdatePhoneSessionRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/UpdatePhoneSessionRequest.cs new file mode 100644 index 00000000..abca37a6 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/UpdatePhoneSessionRequest.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Requests.Validators; + +namespace PinguApps.Appwrite.Shared.Requests; + +/// +/// The request for updating the phone session. +/// +public class UpdatePhoneSessionRequest : BaseRequest +{ + /// + /// User ID. Choose a custom ID or generate a random ID with . Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. + /// + [JsonPropertyName("userId")] + public string UserId { get; set; } = string.Empty; + + /// + /// Valid verification token. + /// + [JsonPropertyName("secret")] + public string Secret { get; set; } = string.Empty; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Validators/UpdatePhoneSessionRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Validators/UpdatePhoneSessionRequestValidator.cs new file mode 100644 index 00000000..e00b1cfc --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Validators/UpdatePhoneSessionRequestValidator.cs @@ -0,0 +1,15 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Validators; +public class UpdatePhoneSessionRequestValidator : AbstractValidator +{ + public UpdatePhoneSessionRequestValidator() + { + RuleFor(x => x.UserId) + .NotEmpty().WithMessage("The user ID is required.") + .Matches(@"^[a-zA-Z0-9\.\-_]{1,36}$").WithMessage("The user ID must be between 1 and 36 characters long and can only contain a-z, A-Z, 0-9, period, hyphen, and underscore."); + + RuleFor(x => x.Secret) + .NotEmpty().WithMessage("The secret is required."); + } +} From 79594f61f606a94e3e2fe2dde147e9b4a82c4b30 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Tue, 24 Sep 2024 19:05:08 +0100 Subject: [PATCH 2/7] implemented update phone session for client --- .../Clients/AccountClient.cs | 17 +++++++++++++++++ .../Clients/IAccountClient.cs | 8 ++++++++ .../Internals/IAccountApi.cs | 3 +++ src/PinguApps.Appwrite.Playground/App.cs | 14 ++++++++++---- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs b/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs index da0ce56c..184ed163 100644 --- a/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs @@ -714,4 +714,21 @@ public async Task> CreatePhoneToken(CreatePhoneTokenReques return e.GetExceptionResponse(); } } + + /// + public async Task> UpdatePhoneSession(UpdatePhoneSessionRequest request) + { + try + { + request.Validate(true); + + var result = await _accountApi.UpdatePhoneSession(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 440e88ca..e6a96497 100644 --- a/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs @@ -342,4 +342,12 @@ public interface IAccountClient /// The request content /// The token Task> CreatePhoneToken(CreatePhoneTokenRequest request); + + + /// + /// Use this endpoint to create a session from token. Provide the userId and secret parameters from the successful response of authentication flows initiated by token creation. For example, magic URL and phone login. + /// + /// The request content + /// The session + Task> UpdatePhoneSession(UpdatePhoneSessionRequest request); } diff --git a/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs b/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs index 223d2e0f..84418152 100644 --- a/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs +++ b/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs @@ -126,4 +126,7 @@ internal interface IAccountApi : IBaseApi [Post("/account/tokens/phone")] Task> CreatePhoneToken(CreatePhoneTokenRequest request); + + [Put("/account/sessions/phone")] + Task> UpdatePhoneSession(UpdatePhoneSessionRequest request); } diff --git a/src/PinguApps.Appwrite.Playground/App.cs b/src/PinguApps.Appwrite.Playground/App.cs index 1e77da0d..90544a6d 100644 --- a/src/PinguApps.Appwrite.Playground/App.cs +++ b/src/PinguApps.Appwrite.Playground/App.cs @@ -19,17 +19,23 @@ public App(IAppwriteClient client, IAppwriteServer server, IConfiguration config public async Task Run(string[] args) { - _client.SetSession(_session); + //_client.SetSession(_session); - var response = await _server.Account.CreatePhoneToken(new CreatePhoneTokenRequest + Console.WriteLine(_client.Session); + + var response = await _client.Account.UpdatePhoneSession(new UpdatePhoneSessionRequest { UserId = "664aac1a00113f82e620", - PhoneNumber = "+447500112374" + //PhoneNumber = "+44123456", + Secret = "242849" }); Console.WriteLine(response.Result.Match( account => account.ToString(), appwriteError => appwriteError.Message, - internalERror => internalERror.Message)); + internalError => internalError.Message)); + + + Console.WriteLine(_client.Session); } } From b381017a46e29f179a058c72589bd4e1777ea769 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Tue, 24 Sep 2024 19:09:47 +0100 Subject: [PATCH 3/7] Implemented update phone session on server --- .../Clients/IAccountClient.cs | 2 +- src/PinguApps.Appwrite.Playground/App.cs | 4 ++-- .../Internals/IAccountApi.cs | 3 +++ .../Servers/AccountServer.cs | 17 +++++++++++++++++ .../Servers/IAccountServer.cs | 8 ++++++++ 5 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs b/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs index e6a96497..062a3bcd 100644 --- a/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs @@ -343,9 +343,9 @@ public interface IAccountClient /// The token Task> CreatePhoneToken(CreatePhoneTokenRequest request); - /// /// Use this endpoint to create a session from token. Provide the userId and secret parameters from the successful response of authentication flows initiated by token creation. For example, magic URL and phone login. + /// Appwrite Docs /// /// The request content /// The session diff --git a/src/PinguApps.Appwrite.Playground/App.cs b/src/PinguApps.Appwrite.Playground/App.cs index 90544a6d..d773b4f0 100644 --- a/src/PinguApps.Appwrite.Playground/App.cs +++ b/src/PinguApps.Appwrite.Playground/App.cs @@ -23,11 +23,11 @@ public async Task Run(string[] args) Console.WriteLine(_client.Session); - var response = await _client.Account.UpdatePhoneSession(new UpdatePhoneSessionRequest + var response = await _server.Account.UpdatePhoneSession(new UpdatePhoneSessionRequest { UserId = "664aac1a00113f82e620", //PhoneNumber = "+44123456", - Secret = "242849" + Secret = "816076" }); Console.WriteLine(response.Result.Match( diff --git a/src/PinguApps.Appwrite.Server/Internals/IAccountApi.cs b/src/PinguApps.Appwrite.Server/Internals/IAccountApi.cs index 949e7e51..f698c7fb 100644 --- a/src/PinguApps.Appwrite.Server/Internals/IAccountApi.cs +++ b/src/PinguApps.Appwrite.Server/Internals/IAccountApi.cs @@ -29,4 +29,7 @@ internal interface IAccountApi : IBaseApi [Post("/account/tokens/phone")] Task> CreatePhoneToken(CreatePhoneTokenRequest request); + + [Put("/account/sessions/phone")] + Task> UpdatePhoneSession(UpdatePhoneSessionRequest request); } diff --git a/src/PinguApps.Appwrite.Server/Servers/AccountServer.cs b/src/PinguApps.Appwrite.Server/Servers/AccountServer.cs index 7e594782..3e7e9550 100644 --- a/src/PinguApps.Appwrite.Server/Servers/AccountServer.cs +++ b/src/PinguApps.Appwrite.Server/Servers/AccountServer.cs @@ -187,4 +187,21 @@ public async Task> CreatePhoneToken(CreatePhoneTokenReques return e.GetExceptionResponse(); } } + + /// + public async Task> UpdatePhoneSession(UpdatePhoneSessionRequest request) + { + try + { + request.Validate(true); + + var result = await _accountApi.UpdatePhoneSession(request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } } diff --git a/src/PinguApps.Appwrite.Server/Servers/IAccountServer.cs b/src/PinguApps.Appwrite.Server/Servers/IAccountServer.cs index 588fffd3..7eeb9edc 100644 --- a/src/PinguApps.Appwrite.Server/Servers/IAccountServer.cs +++ b/src/PinguApps.Appwrite.Server/Servers/IAccountServer.cs @@ -99,4 +99,12 @@ public interface IAccountServer /// The request content /// The token Task> CreatePhoneToken(CreatePhoneTokenRequest request); + + /// + /// Use this endpoint to create a session from token. Provide the userId and secret parameters from the successful response of authentication flows initiated by token creation. For example, magic URL and phone login. + /// Appwrite Docs + /// + /// The request content + /// The session + Task> UpdatePhoneSession(UpdatePhoneSessionRequest request); } From 153e93e7a2d0a1e76f687c45536fc01bae0f6802 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Tue, 24 Sep 2024 19:14:29 +0100 Subject: [PATCH 4/7] added shared tests --- .../UpdatePhoneSessionRequestValidator.cs | 2 +- .../UpdatePhoneSessionRequestTests.cs | 110 ++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 tests/PinguApps.Appwrite.Shared.Tests/Requests/UpdatePhoneSessionRequestTests.cs diff --git a/src/PinguApps.Appwrite.Shared/Requests/Validators/UpdatePhoneSessionRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Validators/UpdatePhoneSessionRequestValidator.cs index e00b1cfc..cf105c85 100644 --- a/src/PinguApps.Appwrite.Shared/Requests/Validators/UpdatePhoneSessionRequestValidator.cs +++ b/src/PinguApps.Appwrite.Shared/Requests/Validators/UpdatePhoneSessionRequestValidator.cs @@ -7,7 +7,7 @@ public UpdatePhoneSessionRequestValidator() { RuleFor(x => x.UserId) .NotEmpty().WithMessage("The user ID is required.") - .Matches(@"^[a-zA-Z0-9\.\-_]{1,36}$").WithMessage("The user ID must be between 1 and 36 characters long and can only contain a-z, A-Z, 0-9, period, hyphen, and underscore."); + .Matches("^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$").WithMessage("The user ID must be between 1 and 36 characters long and can only contain a-z, A-Z, 0-9, period, hyphen, and underscore."); RuleFor(x => x.Secret) .NotEmpty().WithMessage("The secret is required."); diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/UpdatePhoneSessionRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/UpdatePhoneSessionRequestTests.cs new file mode 100644 index 00000000..34ce439a --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/UpdatePhoneSessionRequestTests.cs @@ -0,0 +1,110 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests; + +namespace PinguApps.Appwrite.Shared.Tests.Requests; +public class UpdatePhoneSessionRequestTests +{ + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new UpdatePhoneSessionRequest(); + + // Assert + Assert.NotNull(request.UserId); + Assert.NotNull(request.Secret); + Assert.Equal(string.Empty, request.UserId); + Assert.Equal(string.Empty, request.Secret); + } + + [Theory] + [InlineData("user123", "validSecret")] + [InlineData("anotherUser", "anotherSecret")] + public void Properties_CanBeSet(string userId, string secret) + { + // Arrange + var request = new UpdatePhoneSessionRequest(); + + // Act + request.UserId = userId; + request.Secret = secret; + + // Assert + Assert.Equal(userId, request.UserId); + Assert.Equal(secret, request.Secret); + } + + [Theory] + [InlineData("validUserId", "validSecret")] + [InlineData("anotherValidUserId", "anotherValidSecret")] + public void IsValid_WithValidData_ReturnsTrue(string userId, string secret) + { + // Arrange + var request = new UpdatePhoneSessionRequest + { + UserId = userId, + Secret = secret + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + [Theory] + [InlineData("", "validSecret")] + [InlineData(null, "validSecret")] + [InlineData(".startsWithSymbol", "validSecret")] + [InlineData("contains invalid chars!", "validSecret")] + [InlineData("ThisUserIdContainsFarTooManyCharacters", "validSecret")] + [InlineData("validUserId", "")] + [InlineData("validUserId", null)] + public void IsValid_WithInvalidData_ReturnsFalse(string? userId, string? secret) + { + // Arrange + var request = new UpdatePhoneSessionRequest + { + UserId = userId!, + Secret = secret! + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new UpdatePhoneSessionRequest + { + UserId = "", + Secret = "" + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new UpdatePhoneSessionRequest + { + UserId = "", + Secret = "" + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} From 346792077c0894b7fc3fd1eefd693bb104983937 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Tue, 24 Sep 2024 19:16:57 +0100 Subject: [PATCH 5/7] added client tests --- .../AccountClientTests.UpdatePhoneSession.cs | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.UpdatePhoneSession.cs diff --git a/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.UpdatePhoneSession.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.UpdatePhoneSession.cs new file mode 100644 index 00000000..3cd12043 --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.UpdatePhoneSession.cs @@ -0,0 +1,77 @@ +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 UpdatePhoneSession_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new UpdatePhoneSessionRequest() + { + UserId = "user123", + Secret = "validSecret" + }; + + _mockHttp.Expect(HttpMethod.Put, $"{Constants.Endpoint}/account/sessions/phone") + .ExpectedHeaders() + .WithJsonContent(request) + .Respond(Constants.AppJson, Constants.SessionResponse); + + // Act + var result = await _appwriteClient.Account.UpdatePhoneSession(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task UpdatePhoneSession_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new UpdatePhoneSessionRequest() + { + UserId = "user123", + Secret = "validSecret" + }; + + _mockHttp.Expect(HttpMethod.Put, $"{Constants.Endpoint}/account/sessions/phone") + .ExpectedHeaders() + .WithJsonContent(request) + .Respond(HttpStatusCode.BadRequest, Constants.AppJson, Constants.AppwriteError); + + // Act + var result = await _appwriteClient.Account.UpdatePhoneSession(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task UpdatePhoneSession_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new UpdatePhoneSessionRequest() + { + UserId = "user123", + Secret = "validSecret" + }; + + _mockHttp.Expect(HttpMethod.Put, $"{Constants.Endpoint}/account/sessions/phone") + .ExpectedHeaders() + .WithJsonContent(request) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Account.UpdatePhoneSession(request); + + // Assert + Assert.False(result.Success); + Assert.True(result.IsInternalError); + Assert.Equal("An error occurred", result.Result.AsT2.Message); + } +} From e521b5e6a5d2ebce4d44e002bf49e98b3a0e8a68 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Tue, 24 Sep 2024 19:18:48 +0100 Subject: [PATCH 6/7] added server tests --- .../AccountServerTests.UpdatePhoneSession.cs | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tests/PinguApps.Appwrite.Server.Tests/Servers/Account/AccountServerTests.UpdatePhoneSession.cs diff --git a/tests/PinguApps.Appwrite.Server.Tests/Servers/Account/AccountServerTests.UpdatePhoneSession.cs b/tests/PinguApps.Appwrite.Server.Tests/Servers/Account/AccountServerTests.UpdatePhoneSession.cs new file mode 100644 index 00000000..b8568ecc --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Servers/Account/AccountServerTests.UpdatePhoneSession.cs @@ -0,0 +1,77 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests; +using PinguApps.Appwrite.Shared.Tests; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Servers.Account; +public partial class AccountServerTests +{ + [Fact] + public async Task UpdatePhoneSession_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new UpdatePhoneSessionRequest() + { + UserId = "user123", + Secret = "validSecret" + }; + + _mockHttp.Expect(HttpMethod.Put, $"{Constants.Endpoint}/account/sessions/phone") + .ExpectedHeaders() + .WithJsonContent(request) + .Respond(Constants.AppJson, Constants.SessionResponse); + + // Act + var result = await _appwriteServer.Account.UpdatePhoneSession(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task UpdatePhoneSession_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new UpdatePhoneSessionRequest() + { + UserId = "user123", + Secret = "validSecret" + }; + + _mockHttp.Expect(HttpMethod.Put, $"{Constants.Endpoint}/account/sessions/phone") + .ExpectedHeaders() + .WithJsonContent(request) + .Respond(HttpStatusCode.BadRequest, Constants.AppJson, Constants.AppwriteError); + + // Act + var result = await _appwriteServer.Account.UpdatePhoneSession(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task UpdatePhoneSession_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new UpdatePhoneSessionRequest() + { + UserId = "user123", + Secret = "validSecret" + }; + + _mockHttp.Expect(HttpMethod.Put, $"{Constants.Endpoint}/account/sessions/phone") + .ExpectedHeaders() + .WithJsonContent(request) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteServer.Account.UpdatePhoneSession(request); + + // Assert + Assert.False(result.Success); + Assert.True(result.IsInternalError); + Assert.Equal("An error occurred", result.Result.AsT2.Message); + } +} From f4fb492d0832eba4b96f26be6366422a58a2a85d Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Tue, 24 Sep 2024 19:20:34 +0100 Subject: [PATCH 7/7] Update README.md --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6fa2be50..139c1537 100644 --- a/README.md +++ b/README.md @@ -138,14 +138,14 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( ``` ## ⌛ Progress - -![Server & Client - 51 / 291](https://img.shields.io/badge/Server_&_Client-51%20%2F%20291-red?style=for-the-badge) + +![Server & Client - 53 / 291](https://img.shields.io/badge/Server_&_Client-53%20%2F%20291-red?style=for-the-badge) - -![Server - 10 / 201](https://img.shields.io/badge/Server-10%20%2F%20201-red?style=for-the-badge) + +![Server - 11 / 201](https://img.shields.io/badge/Server-11%20%2F%20201-red?style=for-the-badge) - -![Client - 41 / 90](https://img.shields.io/badge/Client-41%20%2F%2090-gold?style=for-the-badge) + +![Client - 42 / 90](https://img.shields.io/badge/Client-42%20%2F%2090-gold?style=for-the-badge) ### 🔑 Key | Icon | Definition | @@ -155,8 +155,8 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | ❌ | There is currently no intention to implement the endpoint for the given SDK type (client or server) | ### Account - -![Account - 51 / 55](https://img.shields.io/badge/Account-51%20%2F%2055-forestgreen?style=for-the-badge) + +![Account - 53 / 55](https://img.shields.io/badge/Account-53%20%2F%2055-forestgreen?style=for-the-badge) | Endpoint | Client | Server | Notes | |:-:|:-:|:-:|:-:| @@ -202,7 +202,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | [Create Email Token (OTP)](https://appwrite.io/docs/references/1.6.x/client-rest/account#createEmailToken) | ✅ | ✅ | | | [Create Magic URL Token](https://appwrite.io/docs/references/1.6.x/client-rest/account#createMagicURLToken) | ✅ | ✅ | | | [Create OAuth2 Token](https://appwrite.io/docs/references/1.6.x/client-rest/account#createOAuth2Token) | ✅| ✅ | | -| [Create Phone Token](https://appwrite.io/docs/references/1.6.x/client-rest/account#createPhoneToken) | ⬛ | ⬛ | | +| [Create Phone Token](https://appwrite.io/docs/references/1.6.x/client-rest/account#createPhoneToken) | ✅ | ✅ | | | [Create Email Verification](https://appwrite.io/docs/references/1.6.x/client-rest/account#createVerification) | ✅ | ❌ | | | [Create Email Verification (Confirmation)](https://appwrite.io/docs/references/1.6.x/client-rest/account#updateVerification) | ✅ | ❌ | | | [Create Phone Verification](https://appwrite.io/docs/references/1.6.x/client-rest/account#createPhoneVerification) | ⬛ | ❌ | |