From c1272502ba9629927b6e18a75e4e82ac09e996e0 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Tue, 6 Aug 2024 19:17:33 +0100 Subject: [PATCH 1/6] Implemented Add Authenticator --- .../Clients/AccountClient.cs | 15 +++++++++++++++ .../Clients/IAccountClient.cs | 8 ++++++++ .../Internals/IAccountApi.cs | 3 +++ src/PinguApps.Appwrite.Playground/App.cs | 3 +-- .../Responses/MfaType.cs | 13 +++++++++++++ 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 src/PinguApps.Appwrite.Shared/Responses/MfaType.cs diff --git a/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs b/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs index 2463a682..d170fe91 100644 --- a/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs @@ -297,4 +297,19 @@ public async Task> ListLogs(List? queries = null return e.GetExceptionResponse(); } } + + /// + public async Task> AddAuthenticator(string type = "totp") + { + try + { + var result = await _accountApi.AddAuthenticator(Session, type); + + 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 7245f3b2..d5c30634 100644 --- a/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs @@ -141,4 +141,12 @@ public interface IAccountClient /// Array of query strings generated using the Query class provided by the SDK. Learn more about queries. Only supported methods are limit and offset /// The Logs List Task> ListLogs(List? queries = null); + + /// + /// Add an authenticator app to be used as an MFA factor. Verify the authenticator using the verify authenticator method + /// Appwrite Docs + /// + /// Type of authenticator. Must be `totp` + /// The MfaType + Task> AddAuthenticator(string type = "totp"); } diff --git a/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs b/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs index d382268e..8290cd9a 100644 --- a/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs +++ b/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs @@ -56,4 +56,7 @@ internal interface IAccountApi : IBaseApi [Get("/account/logs")] [QueryUriFormat(System.UriFormat.Unescaped)] Task> ListLogs([Header("x-appwrite-session")] string? session, [Query(CollectionFormat.Multi), AliasAs("queries[]")] IEnumerable queries); + + [Post("/account/mfa/authenticators/{type}")] + Task> AddAuthenticator([Header("x-appwrite-session")] string? session, string type); } diff --git a/src/PinguApps.Appwrite.Playground/App.cs b/src/PinguApps.Appwrite.Playground/App.cs index 391ca95c..569ae22d 100644 --- a/src/PinguApps.Appwrite.Playground/App.cs +++ b/src/PinguApps.Appwrite.Playground/App.cs @@ -1,7 +1,6 @@ using Microsoft.Extensions.Configuration; using PinguApps.Appwrite.Client; using PinguApps.Appwrite.Server.Servers; -using PinguApps.Appwrite.Shared.Utils; namespace PinguApps.Appwrite.Playground; internal class App @@ -21,7 +20,7 @@ public async Task Run(string[] args) { _client.SetSession(_session); - var response = await _client.Account.ListLogs([Query.Limit(2)]); + var response = await _client.Account.AddAuthenticator(); Console.WriteLine(response.Result.Match( account => account.ToString(), diff --git a/src/PinguApps.Appwrite.Shared/Responses/MfaType.cs b/src/PinguApps.Appwrite.Shared/Responses/MfaType.cs new file mode 100644 index 00000000..7abcd0bb --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/MfaType.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite Mfa Type object +/// +/// Secret token used for TOTP factor +/// URI for authenticator apps +public record MfaType( + [property: JsonPropertyName("secret")] string Secret, + [property: JsonPropertyName("uri")] string Uri +); From cc565b675d11b9a99bd46f4eaa7f703ce042d588 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Tue, 6 Aug 2024 19:23:35 +0100 Subject: [PATCH 2/6] Added tests for shared --- .../Constants.cs | 7 ++++ .../Responses/MfaTypeTests.cs | 33 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 tests/PinguApps.Appwrite.Shared.Tests/Responses/MfaTypeTests.cs diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Constants.cs b/tests/PinguApps.Appwrite.Shared.Tests/Constants.cs index 1f9f31b7..a77d8c55 100644 --- a/tests/PinguApps.Appwrite.Shared.Tests/Constants.cs +++ b/tests/PinguApps.Appwrite.Shared.Tests/Constants.cs @@ -163,4 +163,11 @@ public static class Constants ] } """; + + public const string MfaTypeResponse = """ + { + "secret": "The_Secret", + "uri": "otpauth://totp/Appwrite%20Test%3Apingu%40appwrite.com?issuer=Appwrite%20Test&secret=The_Secret" + } + """; } diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/MfaTypeTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/MfaTypeTests.cs new file mode 100644 index 00000000..6e493c1d --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/MfaTypeTests.cs @@ -0,0 +1,33 @@ +using System.Text.Json; +using PinguApps.Appwrite.Shared.Responses; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class MfaTypeTests +{ + [Fact] + public void LogsList_Constructor_AssignsPropertiesCorrectly() + { + // Arrange + var secret = "The_Secret"; + var uri = "otpauth://totp/Appwrite%20Test%3Apingu%40appwrite.com?issuer=Appwrite%20Test&secret=The_Secret"; + + // Act + var mfaType = new MfaType(secret, uri); + + // Assert + Assert.Equal(secret, mfaType.Secret); + Assert.Equal(uri, mfaType.Uri); + } + + [Fact] + public void LogsList_CanBeDeserialized_FromJson() + { + // Act + var mfaType = JsonSerializer.Deserialize(Constants.MfaTypeResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + // Assert + Assert.NotNull(mfaType); + Assert.Equal("The_Secret", mfaType.Secret); + Assert.Equal("otpauth://totp/Appwrite%20Test%3Apingu%40appwrite.com?issuer=Appwrite%20Test&secret=The_Secret", mfaType.Uri); + } +} From 65059e9310838f157447716abf3cf1283b85ecdf Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Wed, 7 Aug 2024 03:12:55 +0100 Subject: [PATCH 3/6] Swapped all progress bars, as previous ones were hosted on a now-dead domain --- README.md | 93 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 81 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 96b04bed..96027922 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,22 @@ + + # Appwrite SDK This repository contains the source to both the Client and Server .net implimentation for Appwrite API. This is not a first party SDK, rather a third party SDK. @@ -139,11 +158,25 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( ## ⌛ Progress ### Server & Client -![18 / 288](https://progress-bar.dev/18/?scale=288&suffix=%20/%20288&width=500) + +
+ +
18 / 288
+
+ ### Server Only -![2 / 195](https://progress-bar.dev/2/?scale=195&suffix=%20/%20195&width=300) + +
+ +
2 / 195
+
+ ### Client Only -![16 / 93](https://progress-bar.dev/16/?scale=93&suffix=%20/%2093&width=300) + +
+ +
16 / 93
+
### 🔑 Key | Icon | Definition | @@ -153,7 +186,11 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | ❌ | There is currently no intention to implement the endpoint for the given SDK type (client or server) | ### Account -![18 / 52](https://progress-bar.dev/18/?scale=52&suffix=%20/%2052&width=120) + +
+ +
18 / 52
+
| Endpoint | Client | Server | |:-:|:-:|:-:| @@ -206,7 +243,11 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | [Create Phone Verification (Confirmation)](https://appwrite.io/docs/references/1.5.x/client-rest/account#updatePhoneVerification) | ⬛ | ❌ | ### Users -![0 / 41](https://progress-bar.dev/0/?scale=41&suffix=%20/%2041&width=120) + +
+ +
0 / 41
+
| Endpoint | Client | Server | |:-:|:-:|:-:| @@ -253,7 +294,11 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | [Update Phone Verification](https://appwrite.io/docs/references/1.5.x/server-rest/users#updatePhoneVerification) | ❌ | ⬛ | ### Teams -![0 / 26](https://progress-bar.dev/0/?scale=26&suffix=%20/%2026&width=120) + +
+ +
0 / 26
+
| Endpoint | Client | Server | |:-:|:-:|:-:| @@ -272,7 +317,11 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | [Update Preferences](https://appwrite.io/docs/references/1.5.x/client-rest/teams#updatePrefs) | ⬛ | ⬛ | ### Databases -![0 / 47](https://progress-bar.dev/0/?scale=47&suffix=%20/%2047&width=120) + +
+ +
0 / 47
+
| Endpoint | Client | Server | |:-:|:-:|:-:| @@ -320,7 +369,11 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | [Delete Index](https://appwrite.io/docs/references/1.5.x/server-rest/databases#deleteIndex) | ❌ | ⬛ | ### Storage -![0 / 21](https://progress-bar.dev/0/?scale=21&suffix=%20/%2021&width=120) + +
+ +
0 / 21
+
| Endpoint | Client | Server | |:-:|:-:|:-:| @@ -339,7 +392,11 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | [Get File For View](https://appwrite.io/docs/references/1.5.x/client-rest/storage#getFileView) | ⬛ | ⬛ | ### Functions -![0 / 24](https://progress-bar.dev/0/?scale=24&suffix=%20/%2024&width=120) + +
+ +
0 / 24
+
| Endpoint | Client | Server | |:-:|:-:|:-:| @@ -366,7 +423,11 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | [Delete Variable](https://appwrite.io/docs/references/1.5.x/server-rest/functions#deleteVariable) | ❌ | ⬛ | ### Messaging -![0 / 48](https://progress-bar.dev/0/?scale=48&suffix=%20/%2048&width=120) + +
+ +
0 / 48
+
| Endpoint | Client | Server | |:-:|:-:|:-:| @@ -418,7 +479,11 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | [Delete Subscriber](https://appwrite.io/docs/references/1.5.x/client-rest/messaging#deleteSubscriber) | ⬛ | ⬛ | ### Locale -![0 / 15](https://progress-bar.dev/0/?scale=15&suffix=%20/%2015&width=120) + +
+ +
0 / 15
+
| Endpoint | Client | Server | |:-:|:-:|:-:| @@ -432,7 +497,11 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | [List Languages](https://appwrite.io/docs/references/1.5.x/client-rest/locale#listLanguages) | ⬛ | ⬛ | ### Avatars -![0 / 14](https://progress-bar.dev/0/?scale=14&suffix=%20/%2014&width=120) + +
+ +
0 / 14
+
| Endpoint | Client | Server | |:-:|:-:|:-:| From c257305259c297592778ea1d0885eeb9c42771dd Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Wed, 7 Aug 2024 03:15:44 +0100 Subject: [PATCH 4/6] Added tests for client --- .../AccountClientTests.AddAuthenticator.cs | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.AddAuthenticator.cs diff --git a/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.AddAuthenticator.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.AddAuthenticator.cs new file mode 100644 index 00000000..47b12a33 --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.AddAuthenticator.cs @@ -0,0 +1,82 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Tests; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Client.Tests.Clients.Account; +public partial class AccountClientTests +{ + [Fact] + public async Task AddAuthenticator_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + _mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account/mfa/authenticators/totp") + .ExpectedHeaders(true) + .Respond(Constants.AppJson, Constants.MfaTypeResponse); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.AddAuthenticator(); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task AddAuthenticator_ShouldHitDifferentEndpoint_WhenNewTypeIsUsed() + { + // Arrange + var type = "newAuth"; + var requestUri = $"{Constants.Endpoint}/account/mfa/authenticators/{type}"; + var request = _mockHttp.Expect(HttpMethod.Post, requestUri) + .ExpectedHeaders(true) + .Respond(Constants.AppJson, Constants.MfaTypeResponse); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.AddAuthenticator(type); + + // Assert + _mockHttp.VerifyNoOutstandingExpectation(); + var matches = _mockHttp.GetMatchCount(request); + Assert.Equal(1, matches); + } + + [Fact] + public async Task AddAuthenticator_ShouldHandleException_WhenApiCallFails() + { + // Arrange + _mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account/mfa/authenticators/totp") + .ExpectedHeaders(true) + .Respond(HttpStatusCode.BadRequest, Constants.AppJson, Constants.AppwriteError); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.AddAuthenticator(); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task AddAuthenticator_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + _mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account/mfa/authenticators/totp") + .ExpectedHeaders(true) + .Throw(new HttpRequestException("An error occurred")); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.AddAuthenticator(); + + // Assert + Assert.False(result.Success); + Assert.True(result.IsInternalError); + Assert.Equal("An error occurred", result.Result.AsT2.Message); + } +} From 85d4a6ee56d6eb957245c7209fac2ec64882ce9f Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Wed, 7 Aug 2024 03:18:04 +0100 Subject: [PATCH 5/6] Update README.md --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 96027922..ea65507f 100644 --- a/README.md +++ b/README.md @@ -158,10 +158,10 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( ## ⌛ Progress ### Server & Client - +
- -
18 / 288
+ +
19 / 288
### Server Only @@ -172,10 +172,10 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( ### Client Only - +
- -
16 / 93
+ +
17 / 93
### 🔑 Key @@ -186,10 +186,10 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | ❌ | There is currently no intention to implement the endpoint for the given SDK type (client or server) | ### Account - +
- -
18 / 52
+ +
19 / 52
| Endpoint | Client | Server | @@ -202,7 +202,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | [Create JWT](https://appwrite.io/docs/references/1.5.x/client-rest/account#createJWT) | ✅ | ❌ | | [List Logs](https://appwrite.io/docs/references/1.5.x/client-rest/account#listLogs) | ✅ | ❌ | | [Update MFA](https://appwrite.io/docs/references/1.5.x/client-rest/account#updateMFA) | ⬛ | ❌ | -| [Add Authenticator](https://appwrite.io/docs/references/1.5.x/client-rest/account#createMfaAuthenticator) | ⬛ | ❌ | +| [Add Authenticator](https://appwrite.io/docs/references/1.5.x/client-rest/account#createMfaAuthenticator) | ✅ | ❌ | | [Verify Authenticator](https://appwrite.io/docs/references/1.5.x/client-rest/account#updateMfaAuthenticator) | ⬛ | ❌ | | [Delete Authenticator](https://appwrite.io/docs/references/1.5.x/client-rest/account#deleteMfaAuthenticator) | ⬛ | ❌ | | [Create 2FA Challenge](https://appwrite.io/docs/references/1.5.x/client-rest/account#createMfaChallenge) | ⬛ | ❌ | From 545587076c88b467732459a6dbc3db5187512205 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Wed, 7 Aug 2024 03:42:21 +0100 Subject: [PATCH 6/6] Updated progress display again --- README.md | 82 ++++++++----------------------------------------------- 1 file changed, 12 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index ea65507f..5466021d 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,3 @@ - - # Appwrite SDK This repository contains the source to both the Client and Server .net implimentation for Appwrite API. This is not a first party SDK, rather a third party SDK. @@ -157,26 +138,14 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( ``` ## ⌛ Progress -### Server & Client -
- -
19 / 288
-
+![Server & Client - 19 / 288](https://img.shields.io/badge/Server_&_Client-19%20%2F%20288-red?style=for-the-badge) -### Server Only -
- -
2 / 195
-
+![Server - 2 / 195](https://img.shields.io/badge/Server-2%20%2F%20195-red?style=for-the-badge) -### Client Only -
- -
17 / 93
-
+![Client - 17 / 93](https://img.shields.io/badge/Client-17%20%2F%2093-red?style=for-the-badge) ### 🔑 Key | Icon | Definition | @@ -187,10 +156,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( ### Account -
- -
19 / 52
-
+![Account - 19 / 52](https://img.shields.io/badge/Account-19%20%2F%2052-yellow?style=for-the-badge) | Endpoint | Client | Server | |:-:|:-:|:-:| @@ -244,10 +210,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( ### Users -
- -
0 / 41
-
+![Account - 0 / 41](https://img.shields.io/badge/Users-0%20%2F%2041-red?style=for-the-badge) | Endpoint | Client | Server | |:-:|:-:|:-:| @@ -295,10 +258,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( ### Teams -
- -
0 / 26
-
+![Teams - 0 / 26](https://img.shields.io/badge/Teams-0%20%2F%2026-red?style=for-the-badge) | Endpoint | Client | Server | |:-:|:-:|:-:| @@ -318,10 +278,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( ### Databases -
- -
0 / 47
-
+![Databases - 0 / 47](https://img.shields.io/badge/Databases-0%20%2F%2047-red?style=for-the-badge) | Endpoint | Client | Server | |:-:|:-:|:-:| @@ -370,10 +327,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( ### Storage -
- -
0 / 21
-
+![storage - 0 / 21](https://img.shields.io/badge/Storage-0%20%2F%2021-red?style=for-the-badge) | Endpoint | Client | Server | |:-:|:-:|:-:| @@ -393,10 +347,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( ### Functions -
- -
0 / 24
-
+![Functions - 0 / 24](https://img.shields.io/badge/Functions-0%20%2F%2024-red?style=for-the-badge) | Endpoint | Client | Server | |:-:|:-:|:-:| @@ -424,10 +375,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( ### Messaging -
- -
0 / 48
-
+![Messaging - 0 / 48](https://img.shields.io/badge/Messaging-0%20%2F%2048-red?style=for-the-badge) | Endpoint | Client | Server | |:-:|:-:|:-:| @@ -480,10 +428,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( ### Locale -
- -
0 / 15
-
+![Locale - 0 / 15](https://img.shields.io/badge/Locale-0%20%2F%2015-red?style=for-the-badge) | Endpoint | Client | Server | |:-:|:-:|:-:| @@ -498,10 +443,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( ### Avatars -
- -
0 / 14
-
+![Avatars - 0 / 14](https://img.shields.io/badge/Avatars-0%20%2F%2014-red?style=for-the-badge) | Endpoint | Client | Server | |:-:|:-:|:-:|