diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 000000000..7a71598c1
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,19 @@
+# Please sort lines alphabetically, this will ensure we don't accidentally add duplicates.
+#
+# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
+
+# The following owners will be the default owners for everything in the repo.
+# Unless a later match takes precedence
+# @bitwarden/team-leads
+
+## Auth team files ##
+
+## Platform team files ##
+appIcons @bitwarden/team-platform-dev
+build.cake @bitwarden/team-platform-dev
+
+## Vault team files ##
+
+src/watchOS @bitwarden/team-vault-dev
+
+## Tools team files ##
diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx
index 144bcc070..c83e6646a 100644
--- a/src/App/Resources/AppResources.resx
+++ b/src/App/Resources/AppResources.resx
@@ -2678,7 +2678,7 @@ Do you want to switch to this account?
Master password re-prompt help
- Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.
+ Unlocking may fail due to insufficient memory. Decrease your KDF memory settings or set up biometric unlock to resolve.
Invalid API key
diff --git a/src/App/Services/PushNotificationListenerService.cs b/src/App/Services/PushNotificationListenerService.cs
index b08a43f1c..d51ad85db 100644
--- a/src/App/Services/PushNotificationListenerService.cs
+++ b/src/App/Services/PushNotificationListenerService.cs
@@ -131,6 +131,24 @@ namespace Bit.App.Services
_messagingService.Value.Send("logout");
}
break;
+ case NotificationType.SyncSendCreate:
+ case NotificationType.SyncSendUpdate:
+ var sendCreateUpdateMessage = JsonConvert.DeserializeObject(
+ notification.Payload);
+ if (isAuthenticated && sendCreateUpdateMessage.UserId == myUserId)
+ {
+ await _syncService.Value.SyncUpsertSendAsync(sendCreateUpdateMessage,
+ notification.Type == NotificationType.SyncSendUpdate);
+ }
+ break;
+ case NotificationType.SyncSendDelete:
+ var sendDeleteMessage = JsonConvert.DeserializeObject(
+ notification.Payload);
+ if (isAuthenticated && sendDeleteMessage.UserId == myUserId)
+ {
+ await _syncService.Value.SyncDeleteSendAsync(sendDeleteMessage);
+ }
+ break;
case NotificationType.AuthRequest:
var passwordlessLoginMessage = JsonConvert.DeserializeObject(notification.Payload);
diff --git a/src/Core/Abstractions/ISyncService.cs b/src/Core/Abstractions/ISyncService.cs
index aeb0205b1..52c686aac 100644
--- a/src/Core/Abstractions/ISyncService.cs
+++ b/src/Core/Abstractions/ISyncService.cs
@@ -12,8 +12,10 @@ namespace Bit.Core.Abstractions
Task SetLastSyncAsync(DateTime date);
Task SyncDeleteCipherAsync(SyncCipherNotification notification);
Task SyncDeleteFolderAsync(SyncFolderNotification notification);
+ Task SyncDeleteSendAsync(SyncSendNotification notification);
Task SyncUpsertCipherAsync(SyncCipherNotification notification, bool isEdit);
Task SyncUpsertFolderAsync(SyncFolderNotification notification, bool isEdit);
+ Task SyncUpsertSendAsync(SyncSendNotification notification, bool isEdit);
// Passwordless code will be moved to an independent service in future techdept
Task SyncPasswordlessLoginRequestsAsync();
}
diff --git a/src/Core/Models/Response/NotificationResponse.cs b/src/Core/Models/Response/NotificationResponse.cs
index ae5405d9b..3976012d5 100644
--- a/src/Core/Models/Response/NotificationResponse.cs
+++ b/src/Core/Models/Response/NotificationResponse.cs
@@ -34,6 +34,13 @@ namespace Bit.Core.Models.Response
public DateTime Date { get; set; }
}
+ public class SyncSendNotification
+ {
+ public string Id { get; set; }
+ public string UserId { get; set; }
+ public DateTime RevisionDate { get; set; }
+ }
+
public class PasswordlessRequestNotification
{
public string UserId { get; set; }
diff --git a/src/Core/Services/SyncService.cs b/src/Core/Services/SyncService.cs
index 40abff9d3..5a2bf1a8a 100644
--- a/src/Core/Services/SyncService.cs
+++ b/src/Core/Services/SyncService.cs
@@ -274,6 +274,66 @@ namespace Bit.Core.Services
return SyncCompleted(false);
}
+ public async Task SyncUpsertSendAsync(SyncSendNotification notification, bool isEdit)
+ {
+ SyncStarted();
+ if (!await _stateService.IsAuthenticatedAsync())
+ {
+ return SyncCompleted(false);
+ }
+
+ try
+ {
+ var localSend = await _sendService.GetAsync(notification.Id);
+ if ((localSend != null && localSend.RevisionDate >= notification.RevisionDate)
+ || (isEdit && localSend == null) || (!isEdit && localSend != null))
+ {
+ return SyncCompleted(false);
+ }
+
+ var remoteSend = await _apiService.GetSendAsync(notification.Id);
+ if (remoteSend != null)
+ {
+ var userId = await _stateService.GetActiveUserIdAsync();
+ await _sendService.UpsertAsync(new SendData(remoteSend, userId));
+ _messagingService.Send("syncedUpsertedSend", new Dictionary
+ {
+ ["sendId"] = notification.Id
+ });
+ return SyncCompleted(true);
+ }
+ }
+ catch (ApiException e)
+ {
+ if (e.Error != null && e.Error.StatusCode == System.Net.HttpStatusCode.NotFound && isEdit)
+ {
+ await _sendService.DeleteAsync(notification.Id);
+ _messagingService.Send("syncedDeletedSend", new Dictionary
+ {
+ ["sendId"] = notification.Id
+ });
+ return SyncCompleted(true);
+ }
+ }
+
+ return SyncCompleted(false);
+ }
+
+ public async Task SyncDeleteSendAsync(SyncSendNotification notification)
+ {
+ SyncStarted();
+ if (await _stateService.IsAuthenticatedAsync())
+ {
+ await _sendService.DeleteAsync(notification.Id);
+ _messagingService.Send("syncedDeletedSend", new Dictionary
+ {
+ ["sendId"] = notification.Id
+ });
+ return SyncCompleted(true);
+ }
+ return SyncCompleted(false);
+ }
+
// Helpers
private void SyncStarted()