Setting the user_id/owner_id field of a document server-side

When creating a traditional server, I set the user_id/owner_id field of a document on the server by reading the cookies of the (authenticated) POST request. This prevents clients from setting an arbitrary user_id.

Can Realm Sync automatically set user_id?

My goal is to let users use the app offline without logging in. (I would rather not use anonymous login to not collect data (privacy) and in case they are inactive for 90 days, after which the data will be deleted (maybe it’s just deleted in Atlas but not locally?)) They can decide later to login if they need to sync or share data.

Since I wouldn’t be able to do item.owner_id = app.currentUser.id client-side if the user is not logged in, I am hoping Realm Sync can automatically add this field when syncing data.

Hi, we do not automatically add in that data since not everyone would want to do that. We have discussed the idea of some sort of “computed field” that allows a function to be run when we receive an upload but we have yet to hear that the need for this is worth the complexity.

We have people who do things similar to this where their app has a free tier that is only local realm and a paid tier that involves syncing data and they do this by just manually copying the objects and inserting them into a new synced realm. The easiest thing might be to implement this sort of logic where during this migration your code appends the user_id field.

Let me know if the above works or if you have any other ideas. I would be happy to help think of solutions to your issue.

Best,
Tyler

1 Like

Should I be concerned about validating user_id? For example, if there is an API endpoint for the web version (not sure if that’s the case), someone could send POST requests with an injected user_id pretending to be someone else. However, guessing a valid user_id would be difficult (?).

When you use permissions in Device Sync you can set them up with something like:

 "document_filters": {
    "read": { "owner_id": "%%user.id" },
    "write": { "owner_id": "%%user.id" }
  }

This means that you can upload documents with the owner_id set to your user_id and we will verify that the value provided is indeed the user.id of the connected client. So you could figure out someone’s user.id and send documents with owner_id equal to that value, but our permission system would reject that since you are not logged in and authenticated as that %%user.id. Spoofing that is not possible.

Let me know if that makes sense?
Tyler

1 Like

Makes sense! Thank you. Does the local version get deleted when server-side validation fails? (Or is there a way to tell that synchronization failed? I notice that @ObservedRealmObject has a realm?.syncSession field)

In this case a compensating write is sent. I am not sure which SDK you are using but here are the swift docs on it: https://www.mongodb.com/docs/realm/sdk/swift/sync/write-to-synced-realm/#compensating-writes

1 Like

My approach is to let userId be an empty string if there are no users.

class Item: Object, ObjectKeyIdentifiable {
    @Persisted(primaryKey: true) var _id: ObjectId
    @Persisted var userId: String
    @Persisted var name: String
    
    convenience init(name: String, userId: String? = nil) {
        self.init()
        self.name = name
        if let userId = userId {
            self.userId = userId
        }
    }
}

I change the configuration environment depending on whether there is a logged in user.

struct ContentView: View {
    
    @ObservedObject var app = Realm.app
    
    var body: some View {
        if let config = app.createFlexibleConfiguration() {
            MainScreenSync()
                .environment(\.realmConfiguration, config)
                .environmentObject(app)
        } else {
            MainScreen()
                .environmentObject(app)
        }
    }
}

Then, I rely on the SwiftUI syntax to easily access the synced realm.

struct MainScreenSync: View{
    @EnvironmentObject var app: RealmSwift.App
    //    @Environment(\.realm) var syncedRealm
    @ObservedResults(Item.self) var syncedItems
    
    var body: some View {
        VStack {
            MainScreen()
            Text(app.currentUser?.description ?? "not logged in")
        }
        .onAppear {
            if let localRealm = try? Realm(), let user = app.currentUser {
                let localItems = localRealm.objects(Item.self)
                for item in localItems {
                    // local -> synced
                    let syncedItem = Item(value: item)
                    syncedItem.userId = user.id
                    $syncedItems.append(syncedItem)
                    // delete local
                    try? localRealm.write {
                        localRealm.delete(item)
                    }
                }
            }
        }
    }
}

Full code

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.