From b8dce53b837ec8c89865acb31b393842d016ed10 Mon Sep 17 00:00:00 2001
From: Ferdinando Papale <4850119+papafe@users.noreply.github.com>
Date: Fri, 3 Nov 2023 14:08:53 +0100
Subject: [PATCH 1/3] Added RetryCustomConfirmationAsync (#3468)
---
CHANGELOG.md | 1 +
.../Realm/Handles/AppHandle.EmailPassword.cs | 22 ++++++++++++
Realm/Realm/Sync/App.cs | 15 ++++++++
Tests/Realm.Tests/Sync/SyncTestHelpers.cs | 2 ++
Tests/Realm.Tests/Sync/UserManagementTests.cs | 36 +++++++++++++++++++
Tools/DeployApps/BaasClient.cs | 27 ++++++++++++--
wrappers/src/app_cs.cpp | 8 +++++
7 files changed, 108 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b6b39a742f..c9ee145455 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
## vNext (TBD)
### Enhancements
+* Added the `App.EmailPasswordAuth.RetryCustomConfirmationAsync` method to be able to run again the confirmation function on the server for a given email. (Issue [#3463](https://github.com/realm/realm-dotnet/issues/3463))
* Added `User.Changed` event that can be used to notify subscribers that something about the user changed - typically this would be the user state or the access token. (Issue [#3429](https://github.com/realm/realm-dotnet/issues/3429))
* Added support for customizing the ignore attribute applied on certain generated properties of Realm models. The configuration option is called `realm.custom_ignore_attribute` and can be set in a global configuration file (more information about global configuration files can be found in the [.NET documentation](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/configuration-files)). The Realm generator will treat this as an opaque string, that will be appended to the `IgnoreDataMember` and `XmlIgnore` attributes already applied on these members. The attributes must be fully qualified unless the namespace they reside in is added to a global usings file. For example, this is how you would add `JsonIgnore` from `System.Text.Json`:
diff --git a/Realm/Realm/Handles/AppHandle.EmailPassword.cs b/Realm/Realm/Handles/AppHandle.EmailPassword.cs
index 9917f77e86..cd23cbf375 100644
--- a/Realm/Realm/Handles/AppHandle.EmailPassword.cs
+++ b/Realm/Realm/Handles/AppHandle.EmailPassword.cs
@@ -43,6 +43,11 @@ public static extern void resend_confirmation_email(AppHandle app,
[MarshalAs(UnmanagedType.LPWStr)] string email, IntPtr email_len,
IntPtr tcs_ptr, out NativeException ex);
+ [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_retry_custom_confirmation", CallingConvention = CallingConvention.Cdecl)]
+ public static extern void retry_custom_comfirmation(AppHandle app,
+ [MarshalAs(UnmanagedType.LPWStr)] string email, IntPtr email_len,
+ IntPtr tcs_ptr, out NativeException ex);
+
[DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_app_email_send_reset_password_email", CallingConvention = CallingConvention.Cdecl)]
public static extern void send_reset_password_email(AppHandle app,
[MarshalAs(UnmanagedType.LPWStr)] string email, IntPtr email_len,
@@ -125,6 +130,23 @@ public async Task ResendConfirmationEmailAsync(string email)
}
}
+ public async Task RetryCustomConfirmationAsync(string email)
+ {
+ var tcs = new TaskCompletionSource();
+ var tcsHandle = GCHandle.Alloc(tcs);
+
+ try
+ {
+ EmailNativeMethods.retry_custom_comfirmation(_appHandle, email, (IntPtr)email.Length, GCHandle.ToIntPtr(tcsHandle), out var ex);
+ ex.ThrowIfNecessary();
+ await tcs.Task;
+ }
+ finally
+ {
+ tcsHandle.Free();
+ }
+ }
+
public async Task SendResetPasswordEmailAsync(string username)
{
var tcs = new TaskCompletionSource();
diff --git a/Realm/Realm/Sync/App.cs b/Realm/Realm/Sync/App.cs
index 53abd96088..febfbb525b 100644
--- a/Realm/Realm/Sync/App.cs
+++ b/Realm/Realm/Sync/App.cs
@@ -408,6 +408,21 @@ public Task ResendConfirmationEmailAsync(string email)
return _app.Handle.EmailPassword.ResendConfirmationEmailAsync(email);
}
+ ///
+ /// Rerun the custom confirmation function for the given mail.
+ ///
+ /// The email of the user.
+ ///
+ /// An awaitable representing the asynchronous request to the server that the custom confirmation function is run again. Successful
+ /// completion indicates that the user has been confirmed on the server.
+ ///
+ public Task RetryCustomConfirmationAsync(string email)
+ {
+ Argument.NotNullOrEmpty(email, nameof(email));
+
+ return _app.Handle.EmailPassword.RetryCustomConfirmationAsync(email);
+ }
+
///
/// Sends a password reset email to the specified address.
///
diff --git a/Tests/Realm.Tests/Sync/SyncTestHelpers.cs b/Tests/Realm.Tests/Sync/SyncTestHelpers.cs
index 43b2890311..fae2c57766 100644
--- a/Tests/Realm.Tests/Sync/SyncTestHelpers.cs
+++ b/Tests/Realm.Tests/Sync/SyncTestHelpers.cs
@@ -92,6 +92,8 @@ public static void RunBaasTestAsync(Func testFunc, int timeout = 30000)
public static string GetVerifiedUsername() => $"realm_tests_do_autoverify-{Guid.NewGuid()}";
+ public static string GetUnconfirmedUsername() => $"realm_tests_do_not_confirm-{Guid.NewGuid()}@g.it";
+
public static async Task TriggerClientResetOnServer(SyncConfigurationBase config)
{
var userId = config.User.Id;
diff --git a/Tests/Realm.Tests/Sync/UserManagementTests.cs b/Tests/Realm.Tests/Sync/UserManagementTests.cs
index 1c94a40000..505e4ca335 100644
--- a/Tests/Realm.Tests/Sync/UserManagementTests.cs
+++ b/Tests/Realm.Tests/Sync/UserManagementTests.cs
@@ -399,6 +399,42 @@ public void User_LinkCredentials_WhenInUse_Throws()
});
}
+ [Test]
+ public void User_RetryCustomConfirmationAsync_WorksInAllScenarios()
+ {
+ SyncTestHelpers.RunBaasTestAsync(async () =>
+ {
+ // Standard case
+ var unconfirmedMail = SyncTestHelpers.GetUnconfirmedUsername();
+ var credentials = Credentials.EmailPassword(unconfirmedMail, SyncTestHelpers.DefaultPassword);
+
+ // The first time the confirmation function is called we return "pending", so the user needs to be confirmed.
+ // At the same time we save the user email in a collection.
+ await DefaultApp.EmailPasswordAuth.RegisterUserAsync(unconfirmedMail, SyncTestHelpers.DefaultPassword).Timeout(10_000, detail: "Failed to register user");
+
+ var ex3 = await TestHelpers.AssertThrows(() => DefaultApp.LogInAsync(credentials));
+ Assert.That(ex3.Message, Does.Contain("confirmation required"));
+
+ // The second time we call the confirmation function we find the email we saved in the collection and return "success", so the user
+ // gets confirmed and can log in.
+ await DefaultApp.EmailPasswordAuth.RetryCustomConfirmationAsync(unconfirmedMail);
+ var user = await DefaultApp.LogInAsync(credentials);
+ Assert.That(user.State, Is.EqualTo(UserState.LoggedIn));
+
+ // Logged in user case
+ var loggedInUser = await GetUserAsync();
+ var ex = await TestHelpers.AssertThrows(() => DefaultApp.EmailPasswordAuth.RetryCustomConfirmationAsync(loggedInUser.Profile.Email!));
+ Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
+ Assert.That(ex.Message, Does.Contain("already confirmed"));
+
+ // Unknown user case
+ var invalidEmail = "test@gmail.com";
+ var ex2 = await TestHelpers.AssertThrows(() => DefaultApp.EmailPasswordAuth.RetryCustomConfirmationAsync(invalidEmail));
+ Assert.That(ex2.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
+ Assert.That(ex2.Message, Does.Contain("user not found"));
+ });
+ }
+
[Test]
public void User_JWT_LogsInAndReadsDataFromToken()
{
diff --git a/Tools/DeployApps/BaasClient.cs b/Tools/DeployApps/BaasClient.cs
index fa3ece4eee..dc2998369c 100644
--- a/Tools/DeployApps/BaasClient.cs
+++ b/Tools/DeployApps/BaasClient.cs
@@ -88,12 +88,33 @@ public class FunctionReturn
}
private const string ConfirmFuncSource =
- @"exports = ({ token, tokenId, username }) => {
+ @"exports = async function ({ token, tokenId, username }) {
// process the confirm token, tokenId and username
if (username.includes(""realm_tests_do_autoverify"")) {
- return { status: 'success' }
+ return { status: 'success' };
}
- // do not confirm the user
+
+ if (username.includes(""realm_tests_do_not_confirm"")) {
+ const mongodb = context.services.get('BackingDB');
+ let collection = mongodb.db('test_db').collection('not_confirmed');
+ let result = await collection.findOne({'email': username});
+
+ if(result === null)
+ {
+ let newVal = {
+ 'email': username,
+ 'token': token,
+ 'tokenId': tokenId,
+ }
+
+ await collection.insertOne(newVal);
+ return { status: 'pending' };
+ }
+
+ return { status: 'success' };
+ }
+
+ // fail the user confirmation
return { status: 'fail' };
};";
diff --git a/wrappers/src/app_cs.cpp b/wrappers/src/app_cs.cpp
index ead310cf4f..b7e5d48383 100644
--- a/wrappers/src/app_cs.cpp
+++ b/wrappers/src/app_cs.cpp
@@ -367,6 +367,14 @@ extern "C" {
});
}
+ REALM_EXPORT void shared_app_email_retry_custom_confirmation(SharedApp& app, uint16_t* email_buf, size_t email_len, void* tcs_ptr, NativeException::Marshallable& ex)
+ {
+ handle_errors(ex, [&]() {
+ Utf16StringAccessor email(email_buf, email_len);
+ app->provider_client().retry_custom_confirmation(email, get_callback_handler(tcs_ptr));
+ });
+ }
+
REALM_EXPORT void shared_app_email_send_reset_password_email(SharedApp& app, uint16_t* email_buf, size_t email_len, void* tcs_ptr, NativeException::Marshallable& ex)
{
handle_errors(ex, [&]() {
From a922b2a6f7eab47ca4a64514eca54befada12b4b Mon Sep 17 00:00:00 2001
From: Realm CI
Date: Sat, 4 Nov 2023 01:31:06 +0100
Subject: [PATCH 2/3] Prepare for 11.6.0 (#3470)
* Prepare for 11.6.0
* Change how rules are created
---------
Co-authored-by: nirinchev
Co-authored-by: nirinchev
---
CHANGELOG.md | 2 +-
Realm/AssemblyInfo.props | 2 +-
Realm/Realm.Unity/package.json | 2 +-
Tools/DeployApps/BaasClient.cs | 32 ++++++++++++++++++++------------
4 files changed, 23 insertions(+), 15 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c9ee145455..728e9b2bfa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,4 @@
-## vNext (TBD)
+## 11.6.0 (2023-11-03)
### Enhancements
* Added the `App.EmailPasswordAuth.RetryCustomConfirmationAsync` method to be able to run again the confirmation function on the server for a given email. (Issue [#3463](https://github.com/realm/realm-dotnet/issues/3463))
diff --git a/Realm/AssemblyInfo.props b/Realm/AssemblyInfo.props
index 6329542431..a0106e035b 100644
--- a/Realm/AssemblyInfo.props
+++ b/Realm/AssemblyInfo.props
@@ -1,7 +1,7 @@
Realm .NET
- 11.5.0
+ 11.6.0
Realm is a mobile database: a replacement for SQLite
Realm Inc.
Copyright © $([System.DateTime]::Now.ToString(yyyy)) Realm Inc.
diff --git a/Realm/Realm.Unity/package.json b/Realm/Realm.Unity/package.json
index 9aec66953a..c595f466d1 100644
--- a/Realm/Realm.Unity/package.json
+++ b/Realm/Realm.Unity/package.json
@@ -1,6 +1,6 @@
{
"name": "io.realm.unity",
- "version": "11.5.0",
+ "version": "11.6.0",
"displayName": "Realm",
"description": "Realm is an embedded, object-oriented database that lets you build real-time, always-on applications. With Realm, data is directly exposed as objects and queryable by code, removing the need for ORM's riddled with performance & maintenance issues. Additionally, objects and collections in Realm are always live, meaning that they always reflect the latest data stored in the database. You can subscribe to changes, letting you keep your UI consistently up to date.\nThe .NET Realm SDK also provide access to Atlas App Services, a secure backend that can sync data between devices, authenticate and manage users, and run serverless JavaScript functions.",
"unity": "2021.1",
diff --git a/Tools/DeployApps/BaasClient.cs b/Tools/DeployApps/BaasClient.cs
index dc2998369c..473b1678aa 100644
--- a/Tools/DeployApps/BaasClient.cs
+++ b/Tools/DeployApps/BaasClient.cs
@@ -18,6 +18,7 @@
using System;
using System.Collections.Generic;
+using System.Data;
using System.IO;
using System.Linq;
using System.Net;
@@ -418,27 +419,34 @@ private async Task CreateFlxApp(string name)
{
_output.WriteLine($"Creating FLX app {name}...");
- var (app, _) = await CreateAppCore(name, new
+ var (app, mongoServiceId) = await CreateAppCore(name, new
{
flexible_sync = new
{
state = "enabled",
database_name = $"FLX_{Differentiator}",
queryable_fields_names = new[] { "Int64Property", "GuidProperty", "DoubleProperty", "Int", "Guid", "Id", "PartitionLike" },
- permissions = new
+ }
+ });
+
+ await PostAsync($"groups/{_groupId}/apps/{app}/services/{mongoServiceId}/default_rule", new
+ {
+ roles = new[]
+ {
+ new
{
- rules = new { },
- defaultRoles = new[]
+ name = "all",
+ apply_when = new { },
+ read = true,
+ write = true,
+ insert = true,
+ delete = true,
+ document_filters = new
{
- new
- {
- name = "all",
- applyWhen = new { },
- read = true,
- write = true,
- }
+ read = true,
+ write = true,
}
- },
+ }
}
});
From 057dce811ce91c0c19a652fa2220f60f0026b4ed Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Sat, 4 Nov 2023 00:31:31 +0000
Subject: [PATCH 3/3] Prepare for vNext (#3473)
Co-authored-by: nirinchev
---
CHANGELOG.md | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 728e9b2bfa..8e8aea042a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,17 @@
+## vNext (TBD)
+
+### Enhancements
+* None
+
+### Fixed
+* None
+
+### Compatibility
+* Realm Studio: 13.0.0 or later.
+
+### Internal
+* Using Core x.y.z.
+
## 11.6.0 (2023-11-03)
### Enhancements