What keys can 'credentials' store? Could I store a map called 'verificationCodes' inside Credentials and query 'credentials' to validate an OTP?

// 20 characters are necessary

When you create a document containing a credentials field, as a peer of the data field, that is used to create a Credential document. The credentials field is never stored with the specified document, so you cannot query it for any other fields that you might specify. Any field other than password is ignored. See Credentials | Fauna Documentation for more information.

Like other documents, you could add a data field to a Credential document to store additional fields. Or, you could add those fields to the original document. It might help if you described more about your use case.

I would like to verify a user using a code sent to their email. This code should ideally not be seen inside the database, just like the password.

The only facility within Fauna that “hides” a user-definable field value is the password field inside a credentials object. And the only built-in verification functions are Login and Identify, which both use the password.

Several possibilities come to mind:

  • You could create a codes collection, and each document within that collection could include a reference to the user document that you want to verify, and store the code as the code document’s credentials.password. Then you could call Login or Identify to validate the code.

  • You could try writing a UDF that creates a Credential document using the code instead of a password, and then extract the hashed_password field’s value to store in the user document. The code would be obfuscated, but would still be comparable. Note that Credential documents must use a unique instance field, so your UDF would need to create un

  • You could store the email verification codes in a separate collection, and restrict access to that collection by using Roles.

1 Like

Thank you for this reply. It helped me a lot.

It does bring me to two questions, though:

  1. Why is credentials.password the only field that can be hidden? Could other fields not be set to hidden by the person managing the database?
  2. What happens if there are two or three codes? I could choose to verify a user using an e-mail address and a phone number, and this would create an issue, because I cannot create two code entries for the same user; it would be messy and difficult to manage.

When you run a query like this:

Create(Collection("users"), {
  data: {
    name: "Me",
  },
  credentials: {
    password: "abc123"
  }
)

What gets stored in the users document is:

{
  name: "Me"
}

And a Credential document is created using the password abc123:

{
      ref: Ref(Credentials(), "292529712503194112"),
      ts: 1615236923980000,
      hashed_password: '$2a$05$vxW3nOGCDqt3zESAQy01pebyb1mzc9ZxGW6zCFLfux21TCIOLqz7S',
      instance: Ref(Collection("users"), "1")
    }

The operation that “hides” the password is the creation of the cryptographic hash, which appears in the Credential document in the hashed_password field. You cannot derive the password from that hash, but when Login or Identify are called with a password, a hash is generated and compared to the hashed_password field.

There is no other mechanism that can be used to “hide” a field value. All you can do is restrict access to a document if you want to prevent someone from reading its fields and values. If a document contains a “special” field, there is no requirement for your application to show it to a user.

As for what happens if there are two or more codes that you want to use to verify users, you’ll have to create your own verification mechanism. Fauna access control can use keys or tokens, and tokens are created upon successfully calling Login. Anything else you want to do for verification is logic and data design that is up to you.

If you’d like more specific advice, it would help if you could describe your use case. What does it mean to “verify” a user? What if verification fails? Or succeeds? How should that process inform or influence the queries that you make?

1 Like

My users are not connected directly to the database, they’re connected to a server which handles queries such as login, register or verify.

When a user signs up, I would ideally like them to be a human with a legitimate account. The first step to this is verifying that their e-mail address is real and accessible by them. The code is stored in the database and sent to the user’s email, and when the user receives the code, they input it in the application. The server verifies that the code is valid, verifies the user, and removes the code from the user entry in the database.

If your users never directly interact with the database and the document contents, why do you need to hide the verification fields?

Your workflow sounds pretty standard. When the user first signs up, they only need to provide their email address. Then your app could create a user document like:

Create(Collection("users"), { data: {
  email: 'user@email',
  verificationCode: 'xyz123',
  emailVerified: false
}})

After you send the verification code to them, and they hit the verification link, and they type in the code, you can then fetch the document and compare verification codes. If they match, you set emailVerified to true, and do whatever newly verified users need. If not, get them to try again. If a new code has to be mailed, update the document with the new code, and make sure emailVerified is set to false.

Would this work for you? Do you have enough information now to proceed?

1 Like

Yes. This is the system I wanted to implement.

I have one more question related to Credentials: When I delete dummy entries from the database that were created during testing, their corresponding credentials fields are not deleted together with the user. How can I remove the dozens of these credentials that belong to accounts that no longer exist?

You have two options: use the Delete function, or set the ttl field for each Credential document that you want to no longer keep.

For example, using Delete:

> Paginate(Credentials())
{
  data: [
    Ref(Credentials(), "1"),
    Ref(Credentials(), "292529712503194112"),
    Ref(Credentials(), "292529712622731776")
  ]
}
> Delete(Ref(Credentials(), "292529712503194112"))
{
  ref: Ref(Credentials(), "292529712503194112"),
  ts: 1615236923980000,
  hashed_password: '$2a$05$vxW3nOGCDqt3zESAQy01pebyb1mzc9ZxGW6zCFLfux21TCIOLqz7S',
  instance: Ref(Collection("users"), "1")
}
> Paginate(Credentials())
{
  data: [ Ref(Credentials(), "1"), Ref(Credentials(), "292529712622731776") ]
}

The advantage with this approach is that the document is removed immediately.

Option 2, updating the ttl field:

> Update(Ref(Credentials(), "292529712622731776"), { ttl: TimeAdd(Now(), 10, 'seconds') })
{
  ref: Ref(Credentials(), "292529712622731776"),
  ts: 1615313440750000,
  hashed_password: '$2a$05$.Xp6DmZ2xi7/TU3OZLC.8.Kylp4Ij08dWolLcPqEdF4WIHNYn2zry',
  instance: Ref(Collection("characters"), "181388642114077184"),
  ttl: Time("2021-03-09T18:10:50.725Z")
}

The drawback with this approach is that document removal upon ttl expiry is handled by a background task, so removal might occur hours or days later (or potentially never: there are no guarantees about the success of background tasks).