Skip to content

Commit

Permalink
Merge pull request #118 from PinguApps/87-feat-account-add-authenticator
Browse files Browse the repository at this point in the history
Implemented add authenticator
  • Loading branch information
pingu2k4 authored Aug 7, 2024
2 parents 8d9b35c + 5455870 commit 0bb15bd
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 18 deletions.
43 changes: 27 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,14 @@ string emailAddressOrErrorMessage = userResponse.Result.Match(
```

## ⌛ Progress
### Server & Client
![18 / 288](https://progress-bar.dev/18/?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
![16 / 93](https://progress-bar.dev/16/?scale=93&suffix=%20/%2093&width=300)
<!-- ![19 / 288](https://progress-bar.dev/19/?scale=288&suffix=%20/%20288&width=500) -->
![Server & Client - 19 / 288](https://img.shields.io/badge/Server_&_Client-19%20%2F%20288-red?style=for-the-badge)

<!-- ![2 / 195](https://progress-bar.dev/2/?scale=195&suffix=%20/%20195&width=300) -->
![Server - 2 / 195](https://img.shields.io/badge/Server-2%20%2F%20195-red?style=for-the-badge)

<!-- ![17 / 93](https://progress-bar.dev/17/?scale=93&suffix=%20/%2093&width=300) -->
![Client - 17 / 93](https://img.shields.io/badge/Client-17%20%2F%2093-red?style=for-the-badge)

### 🔑 Key
| Icon | Definition |
Expand All @@ -153,7 +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
![18 / 52](https://progress-bar.dev/18/?scale=52&suffix=%20/%2052&width=120)
<!-- ![19 / 52](https://progress-bar.dev/19/?scale=52&suffix=%20/%2052&width=120) -->
![Account - 19 / 52](https://img.shields.io/badge/Account-19%20%2F%2052-yellow?style=for-the-badge)

| Endpoint | Client | Server |
|:-:|:-:|:-:|
Expand All @@ -165,7 +168,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) |||
Expand Down Expand Up @@ -206,7 +209,8 @@ 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](https://progress-bar.dev/0/?scale=41&suffix=%20/%2041&width=120) -->
![Account - 0 / 41](https://img.shields.io/badge/Users-0%20%2F%2041-red?style=for-the-badge)

| Endpoint | Client | Server |
|:-:|:-:|:-:|
Expand Down Expand Up @@ -253,7 +257,8 @@ 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](https://progress-bar.dev/0/?scale=26&suffix=%20/%2026&width=120) -->
![Teams - 0 / 26](https://img.shields.io/badge/Teams-0%20%2F%2026-red?style=for-the-badge)

| Endpoint | Client | Server |
|:-:|:-:|:-:|
Expand All @@ -272,7 +277,8 @@ 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](https://progress-bar.dev/0/?scale=47&suffix=%20/%2047&width=120) -->
![Databases - 0 / 47](https://img.shields.io/badge/Databases-0%20%2F%2047-red?style=for-the-badge)

| Endpoint | Client | Server |
|:-:|:-:|:-:|
Expand Down Expand Up @@ -320,7 +326,8 @@ 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](https://progress-bar.dev/0/?scale=21&suffix=%20/%2021&width=120) -->
![storage - 0 / 21](https://img.shields.io/badge/Storage-0%20%2F%2021-red?style=for-the-badge)

| Endpoint | Client | Server |
|:-:|:-:|:-:|
Expand All @@ -339,7 +346,8 @@ 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](https://progress-bar.dev/0/?scale=24&suffix=%20/%2024&width=120) -->
![Functions - 0 / 24](https://img.shields.io/badge/Functions-0%20%2F%2024-red?style=for-the-badge)

| Endpoint | Client | Server |
|:-:|:-:|:-:|
Expand All @@ -366,7 +374,8 @@ 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](https://progress-bar.dev/0/?scale=48&suffix=%20/%2048&width=120) -->
![Messaging - 0 / 48](https://img.shields.io/badge/Messaging-0%20%2F%2048-red?style=for-the-badge)

| Endpoint | Client | Server |
|:-:|:-:|:-:|
Expand Down Expand Up @@ -418,7 +427,8 @@ 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](https://progress-bar.dev/0/?scale=15&suffix=%20/%2015&width=120) -->
![Locale - 0 / 15](https://img.shields.io/badge/Locale-0%20%2F%2015-red?style=for-the-badge)

| Endpoint | Client | Server |
|:-:|:-:|:-:|
Expand All @@ -432,7 +442,8 @@ 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](https://progress-bar.dev/0/?scale=14&suffix=%20/%2014&width=120) -->
![Avatars - 0 / 14](https://img.shields.io/badge/Avatars-0%20%2F%2014-red?style=for-the-badge)

| Endpoint | Client | Server |
|:-:|:-:|:-:|
Expand Down
15 changes: 15 additions & 0 deletions src/PinguApps.Appwrite.Client/Clients/AccountClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -297,4 +297,19 @@ public async Task<AppwriteResult<LogsList>> ListLogs(List<Query>? queries = null
return e.GetExceptionResponse<LogsList>();
}
}

/// <inheritdoc/>
public async Task<AppwriteResult<MfaType>> AddAuthenticator(string type = "totp")
{
try
{
var result = await _accountApi.AddAuthenticator(Session, type);

return result.GetApiResponse();
}
catch (Exception e)
{
return e.GetExceptionResponse<MfaType>();
}
}
}
8 changes: 8 additions & 0 deletions src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,12 @@ public interface IAccountClient
/// <param name="queries">Array of query strings generated using the Query class provided by the SDK. <see href="https://appwrite.io/docs/queries">Learn more about queries</see>. Only supported methods are limit and offset</param>
/// <returns>The Logs List</returns>
Task<AppwriteResult<LogsList>> ListLogs(List<Query>? queries = null);

/// <summary>
/// Add an authenticator app to be used as an MFA factor. Verify the authenticator using the verify authenticator method
/// <para><see href="https://appwrite.io/docs/references/1.5.x/client-rest/account#createMfaAuthenticator">Appwrite Docs</see></para>
/// </summary>
/// <param name="type">Type of authenticator. Must be `totp`</param>
/// <returns>The MfaType</returns>
Task<AppwriteResult<MfaType>> AddAuthenticator(string type = "totp");
}
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 @@ -56,4 +56,7 @@ internal interface IAccountApi : IBaseApi
[Get("/account/logs")]
[QueryUriFormat(System.UriFormat.Unescaped)]
Task<IApiResponse<LogsList>> ListLogs([Header("x-appwrite-session")] string? session, [Query(CollectionFormat.Multi), AliasAs("queries[]")] IEnumerable<string> queries);

[Post("/account/mfa/authenticators/{type}")]
Task<IApiResponse<MfaType>> AddAuthenticator([Header("x-appwrite-session")] string? session, string type);
}
3 changes: 1 addition & 2 deletions src/PinguApps.Appwrite.Playground/App.cs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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(),
Expand Down
13 changes: 13 additions & 0 deletions src/PinguApps.Appwrite.Shared/Responses/MfaType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Text.Json.Serialization;

namespace PinguApps.Appwrite.Shared.Responses;

/// <summary>
/// An Appwrite Mfa Type object
/// </summary>
/// <param name="Secret">Secret token used for TOTP factor</param>
/// <param name="Uri">URI for authenticator apps</param>
public record MfaType(
[property: JsonPropertyName("secret")] string Secret,
[property: JsonPropertyName("uri")] string Uri
);
Original file line number Diff line number Diff line change
@@ -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);
}
}
7 changes: 7 additions & 0 deletions tests/PinguApps.Appwrite.Shared.Tests/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
""";
}
33 changes: 33 additions & 0 deletions tests/PinguApps.Appwrite.Shared.Tests/Responses/MfaTypeTests.cs
Original file line number Diff line number Diff line change
@@ -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<MfaType>(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);
}
}

0 comments on commit 0bb15bd

Please sign in to comment.