How to set realm id and partition with custom login?

Hello,

I’m using a custom authentication function so that a mobile client generates a unique id and logs in with it.

My function is

exports = async function(loginPayload) {
  const app_secret = "somesecret"
  
  const users = context.services
    .get("mongodb-atlas")
    .db("app")
    .collection("UserInfo");

  const { clientUserId, secret } = loginPayload;
  
  if(app_secret !== secret) {
    throw "Secret mismatch : " + secret;
  }

  const user = await users.findOne({ clientUserId: clientUserId });
  
  if (user) {
    return user._id.toString();
  } else {
    const newId = new BSON.ObjectId();
    const partition = "user=" + newId.toString();
    const result = await users.insertOne({ _id: newId, _partition: partition, clientUserId: clientUserId, creationDate: new Date() });
    return result.insertedId.toString();
  }
};

My problem is that the “realm user id”, the one that RealmSDK gives me on mobile client is different from the one I generate in the custom function.

And I have set the permissions to

READ 
(can only read PUBLIC and your own data such as partitions are "user=myId123" or "PUBLIC")
{
  "$or": [
    {
      "%%partition": "user=%%user.id"
    },
    {
      "%%partition": "PUBLIC"
    }
  ]
}

WRITE 
(can only write your own data such as partitions are "user=myId123")
{
  "%%partition": "user=%%user.id"
}

How am I supposed to retrieve this realm user id so that I can set up correctly my custom login function and populate correctly my collection with the right _partition and _id?

EDIT: The error I get from Realm Sync Backend is

Error: user does not have permission to sync on partition (ProtocolErrorCode=206)
Partition: user=607ac1b7687c4dca433c3c5e

Because my custom function has set the custom user id to 607ac1b7687c4dca433c3c5b but the actual realm user id is 607ac1b7687c4dca433c3c5e and I have no clue how to fetch this id from the custom login function…

Could You solve the problem? I have the same problem…

Hi Marco,

Yes I found a solution. However, it’s not a straightforward solution, I have to do it in 2 steps:

Schema
I have a collection called UserInfo which I use for saving users.

UserInfo
  _id: ObjectId
  _partition: String
  creationDate: Date
  gameCenterId: Optional String
  appleId: Optional String
  {any_other_login_method_id: Optional String}

Step 1.
I set up my “Custom Function Authentication” - by the way I use GameCenter (an iOS game framework which gives me a unique user id).

Here is the code.

exports = async function(loginPayload) {
  const users = context.services
    .get("mongodb-atlas")
    .db("app")
    .collection("UserInfo");
  
  let findQuery;
  if (loginPayload["gameCenterId"] != null) {
    findQuery = { gameCenterId: loginPayload["gameCenterId"] };
  }

  // Check if user already exists using our own GameCenter_id
  const user = await users.findOne(findQuery);
  
  if (user !== null) {
    return user._id.toString();
  } else {
    // Set the partition to a temporary value.
    // A onCreate trigger will replace it with user={InternalRealmId} which we still don't have here because of custom-function internal logic
    const temporaryId = new BSON.ObjectId();
    if (loginPayload["gameCenterId"] != null) {
      const result = await users.insertOne({
        _id: temporaryId,
        _partition: "temporaryId=" + temporaryId.toString(),
        creationDate: new Date(),
        gameCenterId: loginPayload["gameCenterId"]
      });
      return result.insertedId.toString();
    }
  }
};

Step 2.
Then I create a trigger that will always run for every created user with the custom function login. This trigger’s goal is to inject and override the realm_id into the partition of the previous document created.

Here is its configuration:

  • Trigger type: Authentication
  • Enabled: True
  • Action type: Create
  • Provider: Custom Function Authentication
  • Select an event type: Function
  • Function: {my function called onUserCreated}

And here is my onUserCreated function

exports = async function(authEvent) {
  const user = authEvent.user;
  const realmUserId = user.id; // Now we have the actual realm_id
  const identities = user.identities;
  
  // Update our user's Mongo document's partition with the actual realm_id
  const customIdentity = identities.find(identity => identity.provider_type === "custom-function");
  if (customIdentity !== undefined) {
    const userId = new BSON.ObjectId(customIdentity.id);
    const collection = context.services.get("mongodb-atlas").db("app").collection("UserInfo");
    const findUser = await collection.findOne({ _id: userId });
    if (findUser !== null) {
      await collection.updateOne({ "_id": userId }, { $set: { 
        _partition: "user=" + realmUserId.toString()
      } });
      return;
    } else {
      return;
    }
  }
};

And voila… I only had a few problems (maybe once per month or so) with the shared cluster and according to Realm support, shared clusters can have very rare trigger failures apparently.

Hi Jerome,

You are my hero; thank you so much!

You saved my day!!

You are welcome :slight_smile:

(Would be nice if Realm could add a tutorial to their documentation - so that we would finally know the official way.)