Skip to content

Commit

Permalink
Merge pull request #131 from PinguApps/98-list-sessions
Browse files Browse the repository at this point in the history
Implemented list sessions
  • Loading branch information
pingu2k4 authored Aug 11, 2024
2 parents 6d3d678 + 3ddb0dd commit f90727f
Show file tree
Hide file tree
Showing 10 changed files with 326 additions and 16 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,14 @@ string emailAddressOrErrorMessage = userResponse.Result.Match(
```

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

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

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

| Endpoint | Client | Server |
|:-:|:-:|:-:|
Expand Down Expand Up @@ -184,7 +184,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match(
| [Update Preferences](https://appwrite.io/docs/references/1.5.x/client-rest/account#updatePrefs) |||
| [Create Password Recovery](https://appwrite.io/docs/references/1.5.x/client-rest/account#createRecovery) |||
| [Create Password Recovery (Confirmation)](https://appwrite.io/docs/references/1.5.x/client-rest/account#updateRecovery) |||
| [List Sessions](https://appwrite.io/docs/references/1.5.x/client-rest/account#listSessions) | ||
| [List Sessions](https://appwrite.io/docs/references/1.5.x/client-rest/account#listSessions) | ||
| [Delete Sessions](https://appwrite.io/docs/references/1.5.x/client-rest/account#deleteSessions) |||
| [Create Anonymous Session](https://appwrite.io/docs/references/1.5.x/client-rest/account#createAnonymousSession) |||
| [Create Email Password Session](https://appwrite.io/docs/references/1.5.x/client-rest/account#createEmailPasswordSession) |||
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 @@ -499,4 +499,19 @@ public async Task<AppwriteResult<Token>> CreatePasswordRecoveryConfirmation(Crea
return e.GetExceptionResponse<Token>();
}
}

/// <inheritdoc/>
public async Task<AppwriteResult<SessionsList>> ListSessions()
{
try
{
var result = await _accountApi.ListSessions(GetCurrentSessionOrThrow());

return result.GetApiResponse();
}
catch (Exception e)
{
return e.GetExceptionResponse<SessionsList>();
}
}
}
13 changes: 10 additions & 3 deletions src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ public interface IAccountClient
/// Regenerate recovery codes that can be used as backup for MFA flow. Before regenerating codes, they must be first generated using <see cref="CreateMfaRecoveryCodes"/> method. An OTP challenge is required to regenreate recovery codes
/// <para><see href="https://appwrite.io/docs/references/1.5.x/client-rest/account#updateMfaRecoveryCodes">Appwrite Docs</see></para>
/// </summary>
/// <returns></returns>
/// <returns>The Mfa Recovery Codes</returns>
Task<AppwriteResult<MfaRecoveryCodes>> RegenerateMfaRecoveryCodes();

/// <summary>
Expand All @@ -232,7 +232,14 @@ public interface IAccountClient
/// <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#updateRecovery">Appwrite Docs</see></para>
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
/// <param name="request">The request content</param>
/// <returns>The Token</returns>
Task<AppwriteResult<Token>> CreatePasswordRecoveryConfirmation(CreatePasswordRecoveryConfirmationRequest request);

/// <summary>
/// Get the list of active sessions across different devices for the currently logged in user
/// <para><see href="https://appwrite.io/docs/references/1.5.x/client-rest/account#listSessions">Appwrite Docs</see></para>
/// </summary>
/// <returns>The Sessions List</returns>
Task<AppwriteResult<SessionsList>> ListSessions();
}
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 @@ -92,4 +92,7 @@ internal interface IAccountApi : IBaseApi

[Put("/account/recovery")]
Task<IApiResponse<Token>> CreatePasswordRecoveryConfirmation(CreatePasswordRecoveryConfirmationRequest request);

[Get("/account/sessions")]
Task<IApiResponse<SessionsList>> ListSessions([Header("x-appwrite-session")] string session);
}
7 changes: 1 addition & 6 deletions src/PinguApps.Appwrite.Playground/App.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,7 @@ public async Task Run(string[] args)
{
_client.SetSession(_session);

var response = await _client.Account.CreatePasswordRecoveryConfirmation(new Shared.Requests.CreatePasswordRecoveryConfirmationRequest
{
UserId = "censored",
Secret = "censored",
Password = "MyNewSuperAwesomePassword!"
});
var response = await _client.Account.ListSessions();

Console.WriteLine(response.Result.Match(
account => account.ToString(),
Expand Down
14 changes: 14 additions & 0 deletions src/PinguApps.Appwrite.Shared/Responses/SessionsList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace PinguApps.Appwrite.Shared.Responses;

/// <summary>
/// An Appwrite Sessions List object
/// </summary>
/// <param name="Total">Total number of sessions documents that matched your query</param>
/// <param name="Sessions">List of sessions. Can be one of: <see cref="Session"/></param>
public record SessionsList(
[property: JsonPropertyName("total")] int Total,
[property: JsonPropertyName("sessions")] IReadOnlyList<Session> Sessions
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Net;
using PinguApps.Appwrite.Client.Clients;
using PinguApps.Appwrite.Shared.Tests;
using RichardSzalay.MockHttp;

namespace PinguApps.Appwrite.Client.Tests.Clients.Account;
public partial class AccountClientTests
{
[Fact]
public async Task ListSessions_ShouldReturnSuccess_WhenApiCallSucceeds()
{
// Arrange
_mockHttp.Expect(HttpMethod.Get, $"{Constants.Endpoint}/account/sessions")
.ExpectedHeaders(true)
.Respond(Constants.AppJson, Constants.SessionsListResponse);

_appwriteClient.SetSession(Constants.Session);

// Act
var result = await _appwriteClient.Account.ListSessions();

// Assert
Assert.True(result.Success);
}

[Fact]
public async Task ListSessions_ShouldReturnError_WhenSessionIsNull()
{
// Act
var result = await _appwriteClient.Account.ListSessions();

// Assert
Assert.True(result.IsError);
Assert.True(result.IsInternalError);
Assert.Equal(ISessionAware.SessionExceptionMessage, result.Result.AsT2.Message);
}

[Fact]
public async Task ListSessions_ShouldHandleException_WhenApiCallFails()
{
// Arrange
_mockHttp.Expect(HttpMethod.Get, $"{Constants.Endpoint}/account/sessions")
.ExpectedHeaders(true)
.Respond(HttpStatusCode.BadRequest, Constants.AppJson, Constants.AppwriteError);

_appwriteClient.SetSession(Constants.Session);

// Act
var result = await _appwriteClient.Account.ListSessions();

// Assert
Assert.True(result.IsError);
Assert.True(result.IsAppwriteError);
}

[Fact]
public async Task ListSessions_ShouldReturnErrorResponse_WhenExceptionOccurs()
{
// Arrange
_mockHttp.Expect(HttpMethod.Get, $"{Constants.Endpoint}/account/sessions")
.ExpectedHeaders(true)
.Throw(new HttpRequestException("An error occurred"));

_appwriteClient.SetSession(Constants.Session);

// Act
var result = await _appwriteClient.Account.ListSessions();

// Assert
Assert.False(result.Success);
Assert.True(result.IsInternalError);
Assert.Equal("An error occurred", result.Result.AsT2.Message);
}
}
41 changes: 41 additions & 0 deletions tests/PinguApps.Appwrite.Shared.Tests/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,45 @@ public static class Constants
"recoveryCode": true
}
""";

public const string SessionsListResponse = """
{
"total": 5,
"sessions": [
{
"$id": "5e5ea5c16897e",
"$createdAt": "2020-10-15T06:38:00.000+00:00",
"$updatedAt": "2020-10-15T06:38:00.000+00:00",
"userId": "5e5bb8c16897e",
"expire": "2020-10-15T06:38:00.000+00:00",
"provider": "email",
"providerUid": "[email protected]",
"providerAccessToken": "MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3",
"providerAccessTokenExpiry": "2020-10-15T06:38:00.000+00:00",
"providerRefreshToken": "MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3",
"ip": "127.0.0.1",
"osCode": "Mac",
"osName": "Mac",
"osVersion": "Mac",
"clientType": "browser",
"clientCode": "CM",
"clientName": "Chrome Mobile iOS",
"clientVersion": "84.0",
"clientEngine": "WebKit",
"clientEngineVersion": "605.1.15",
"deviceName": "smartphone",
"deviceBrand": "Google",
"deviceModel": "Nexus 5",
"countryCode": "US",
"countryName": "United States",
"current": true,
"factors": [
"email"
],
"secret": "5e5bb8c16897e",
"mfaUpdatedAt": "2020-10-15T06:38:00.000+00:00"
}
]
}
""";
}
28 changes: 28 additions & 0 deletions tests/PinguApps.Appwrite.Shared.Tests/Responses/SessionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,34 @@ public void Constructor_AssignsPropertiesCorrectly()

// Assert
Assert.Equal(id, session.Id);
Assert.Equal(createdAt.ToUniversalTime(), session.CreatedAt.ToUniversalTime());
Assert.Equal(updatedAt.ToUniversalTime(), session.UpdatedAt.ToUniversalTime());
Assert.Equal(userId, session.UserId);
Assert.Equal(expiresAt.ToUniversalTime(), session.ExpiresAt.ToUniversalTime());
Assert.Equal(provider, session.Provider);
Assert.Equal(providerUserId, session.ProviderUserId);
Assert.Equal(providerAccessToken, session.ProviderAccessToken);
Assert.Equal(providerAccessTokenExpiry.ToUniversalTime(), session.ProviderAccessTokenExpiry?.ToUniversalTime());
Assert.Equal(ProviderRefreshToken, session.ProviderRefreshToken);
Assert.Equal(ip, session.Ip);
Assert.Equal(osCode, session.OsCode);
Assert.Equal(osName, session.OsName);
Assert.Equal(osVersion, session.OsVersion);
Assert.Equal(clientType, session.ClientType);
Assert.Equal(clientCode, session.ClientCode);
Assert.Equal(clientName, session.ClientName);
Assert.Equal(clientVersion, session.ClientVersion);
Assert.Equal(clientEngine, session.ClientEngine);
Assert.Equal(clientEngineVersion, session.ClientEngineVersion);
Assert.Equal(deviceName, session.DeviceName);
Assert.Equal(deviceBrand, session.DeviceBrand);
Assert.Equal(deviceModel, session.DeviceModel);
Assert.Equal(countryCode, session.CountryCode);
Assert.Equal(countryName, session.CountryName);
Assert.Equal(current, session.Current);
Assert.Equal(factors, session.Factors);
Assert.Equal(secret, session.Secret);
Assert.Equal(mfaUpdatedAt.ToUniversalTime(), session.MfaUpdatedAt?.ToUniversalTime());
}

[Fact]
Expand Down
Loading

0 comments on commit f90727f

Please sign in to comment.