netlify/gotrue-js

Set role/metadata on initial call to /invite

DavidChouinard opened this issue · 9 comments

Our application programmatically invites users (on Netlify Functions) following the example in the documentation.

However, if we send a payload that includes app_metadata while inviting a user (e.g. {email: test@example.com, app_metadata: {roles: ['admin']}}), the user test@example.com gets successfully created but any app_metadata is ignored.

Is it possible to set the role/app_metadata directly in the initial call to create the user? I realize it's possible to update the user in a separate followup API call but I'd like to limit requests if possible.

Is this supported? Not sure if I'm using the API correctly and/or this might be a bug since the API response example in the documentation seems to suggest it might be possible to set roles directly in the initial call.

fyi bcomnes has left the company to greener pastures :)

hmm, i'll be honest i haven't done this myself but in principle yes you should be able to set app_metadata in the initial call, although our serverside implementation might (unknowingly?) prevent that. tagging @futuregerald in case he knows

note that gotrue-js is a js client for gotrue, and i found this piece of code here that seems to do it https://github.com/netlify/gotrue/blob/49e0866b91fa6b28b098163765d513621c171c98/models/user.go#L168

if you know any go, this might be a fruitful place to look.

it seems that updating app metadata requires the user to have admin privileges and doing both at the same time may need some extra rewiring (if our platform team is ok with that on a security basis)

imo its probably a oneliner change to update this conditional: https://github.com/netlify/gotrue/blob/49e0866b91fa6b28b098163765d513621c171c98/api/user.go#L95

Got it! Don't know go on my end :(

This is with an admin token, so not sure why it's failing. It's as boilerplate as it gets, here's the snippet in the Netlify Function file:

    const identity = context.clientContext.identity;
    ...
    const inviteUrl = `${identity.url}/invite`;
    const adminAuthHeader = "Bearer " + identity.token;

    const response = await fetch(inviteUrl, {
      method: "POST",
      headers: { Authorization: adminAuthHeader },
      body: JSON.stringify({email: customer.email, app_metadata: { roles: ["admin"]}})
    });

The response object simply has app_metadata: { provider: 'email' } (i.e. role isn't set) and no roles are showed when I look up the user on the Netlify dashboard.

Could it be that whatever is initializing the user's app_metadata is overwriting it with the boilerplate provider: 'email' and clobbering what was initially there?

Hey @DavidChouinard, generally, email is saved under user_metadata, so you could do something like this:

    const identity = context.clientContext.identity;
    ...
    const inviteUrl = `${identity.url}/invite`;
    const adminAuthHeader = "Bearer " + identity.token;

    const response = await fetch(inviteUrl, {
      method: "POST",
      headers: { Authorization: adminAuthHeader },
      body: JSON.stringify({
        user_metadata: {
          ...event.body.user.user_metadata,
          email: customer.email,
        },
        app_metadata: {  roles: ["admin"] }
      })
    });

You can see an example of this in a function here, which shows how to add extra metadata to a user during the sign up flow.

@shortdiv appreciate the help! I just tried that snippet and I get { code: 422, msg: 'An email address is required' }. are you sure that's right?

@sw-yx any plausibility to the theory that the default/boilerplate app_metadata: { provider: 'email' } is overwriting what the user provided on signup?

Don't know Go enough but here's the line where the provider metadata field get hardcoded on signup: https://github.com/netlify/gotrue/blob/49e0866b91fa6b28b098163765d513621c171c98/api/signup.go#L107

not sure. cc @futuregerald

Hi @DavidChouinard , the user has to exist before you can add a role. If I were you I'd create a webhook that fires on signup or validation that assigns the role. It can be similar to what @shortdiv is doing in her example: https://github.com/shortdiv/identity-with-role-based-access/blob/master/src/lambda/handle-signup.js

Ah I see. Appreciate the clarification @futuregerald @shortdiv.

In our case, it's more than just role: at the point the user is created we have a lot of metadata we need to attach to the user (e.g. a payment token from Stripe). To be able to implement the webhook solution, we'd have to spin up a database to store that metadata temporarily to be ready for when the webhook comes back — a really clunk solution. Wish it could be as simple as setting the metadata in the initial call, auth0 supports that :(

We were looking for a solution for keeping track of simple user data that didn't require maintaining a separate database. Appreciate the help, looks like we'll probably move to auth0 instead.

Thank you for all the help.