Documentação
GuiasAutorizaçãoConvites

Convites

O ciclo completo de trazer alguém para dentro de um recurso, status por status.

Um convite é um grant que existe antes de estar ativo. Essa única ideia explica o resto desta página: não há tabela separada de convites, não há estado de "usuário pendente", e não há nada para reconciliar quando a pessoa finalmente aceita.

O status é o ciclo de vida

Todo grant carrega um:

invitedCriado pelo invite(). Ainda não ativo.
requestedA pessoa pediu acesso. Ainda não ativo.
grantedAtivo. É o que o verify() honra.
blockedO registro sobrevive, o acesso não.

Só grants granted passam numa verificação de permissão. Um grant invited é inerte — emitir um não entrega nada até ser aceito.

Um verify contra um grant ainda invited ou requested responde 403 com o código forbidden_access_waiting_accept, e não com o forbidden_resource_access genérico. Vale tratar os dois de formas diferentes na interface.

Enviando

Convites saem em lote, contra um recurso:

import { forwardUserHeaders } from '@nulvless/nulvless-js';

const { data: grants } = await nulvless.auth.grants.invite(
  {
    invites: [
      { email: '[email protected]', role_id: adminRoleId },
      { email: '[email protected]', role_id: memberRoleId },
    ],
    resource: 'acme',
    message: 'Acme Inc',
  },
  {
    headers: forwardUserHeaders(req),
    redirect_uri: 'https://app.example.com/join',
  },
);

Três coisas nessa chamada são fáceis de errar:

role_id é um id, não um nome. Leia os ids de um contexto uma vez com auth.admin.roles.list('org') e mapeie no seu próprio código — 'admin' não é aceito aqui.

resource, não context. O contexto veio junto com o papel. Você nomeia a coisa que está sendo compartilhada.

redirect_uri é opção, não parâmetro. Fica no segundo argumento, junto dos headers.

message é mostrado a quem recebe o convite — na prática, o nome legível do recurso, já que o resource costuma ser um slug.

Convites funcionam antes da conta existir

Você convida um e-mail, não um id de usuário. O grant é criado com status: 'invited' e sem granted_to — esse campo é preenchido quando a pessoa passa a ter conta. Nada no seu código precisa distinguir "convidei um usuário que já existe" de "convidei um desconhecido".

Repasse os headers do usuário

auth.grants.invite age como o usuário que chamou: ele aplica a permissão dela sobre o recurso e registra ela como remetente. Sem o forwardUserHeaders, o convite parece ter vindo da sua infraestrutura.

Aceitando

Quem recebe segue o link até o seu redirect_uri, que carrega o id do grant. Sua aplicação chama:

await nulvless.auth.grants.accept(grantId, {
  headers: forwardUserHeaders(req),
});

O status passa para granted e o acesso começa. Recusar tem o mesmo formato:

await nulvless.auth.grants.cancel(grantId, { headers: forwardUserHeaders(req) });

Aceitar não exige permissão no recurso

Essa é a parte que costuma ser super-projetada. Aceitar o próprio convite não pode exigir um papel no recurso ao qual você está entrando — você ainda não tem nenhum. O id do grant é a autorização. Proteja essas rotas só com autenticação, não com verificação de permissão.

Vale enunciar essa assimetria com clareza:

OperaçãoExige papel no recurso
inviteSim — normalmente owner ou admin
revoke, block, updateSim
accept, cancel, leaveNão — só autenticação

A tela de membros

getMembers devolve os quatro grupos já separados, então a tela não faz agrupamento nenhum:

const { data } = await nulvless.auth.grants.getMembers('acme');

data.granted;      // membros ativos
data.invitations;  // enviados, aguardando resposta
data.requesteds;   // pediram para entrar, aguardando aprovação
data.blockeds;     // bloqueados

Removendo alguém

Três finais diferentes, e eles não são intercambiáveis:

await nulvless.auth.grants.revoke({ id });  // remove o grant
await nulvless.auth.grants.block({ id });   // mantém o registro, nega o acesso
await nulvless.auth.grants.leave({ id });   // o usuário se remove
  • revoke apaga a relação. Use quando o acesso foi concedido por engano, ou a pessoa simplesmente saiu.
  • block mantém o registro de que essa pessoa esteve aqui e agora está negada — o histórico por trás das ações passadas dela continua resolvendo.
  • leave é o caminho self-service, e não exige permissão no recurso.

Exige sudo

invite, revoke, block e update são protegidos por sudo. Comece com auth.sessions.sudo() de qualquer forma — é ele que envia o código. Depois gaste esse código na sessão inteira, ou só nesta chamada:

// Só esta chamada. O `nonce` é o código que o usuário digitou.
await nulvless.auth.grants.revoke({ id, nonce: code });

Repare que vai dentro dos params, não nas options.

Veja Modo sudo.

Mudando o que alguém tem

await nulvless.auth.grants.update(grantId, {
  scopes: ['projects:read'],
});

Isso muda um grant. Para mudar o que todo mundo com um papel pode fazer, edite o papel — todos os portadores se movem de uma vez, e nenhum grant é tocado:

await nulvless.auth.admin.roles.update(roleId, {
  scopes: ['members:read', 'members:write', 'projects:read'],
});

Recorrer ao grants.update quando você queria roles.update é como modelos de permissão viram um punhado de exceções por pessoa que ninguém consegue auditar.

Próximos

Nesta página