Pular para o conteúdo

Sistema de Permissões


O sistema de permissões utiliza uma abordagem híbrida:

  • TokenScope: Define o nível de acesso hierárquico (Admin, Owner, User)
  • Permission: Permissões granulares para colaboradores (Users)
  • CollaborationRole: Papel do usuário na colaboração
Tipo de UsuárioComportamento
Admin/OwnerAcesso total automático - ignora verificação de permissions
User (Colaborador)Acesso controlado - verifica array de permissions

packages/common/src/auth/types.ts
const enum TokenScope {
Refresh = "refresh", // Apenas para renovar tokens
User = "user", // Colaborador com permissões limitadas
Owner = "owner", // Dono da conta - acesso total
Admin = "admin", // Administrador do sistema - acesso total + funções admin
}
packages/common/src/auth/types.ts
const enum Permission {
DASHBOARD = "dashboard",
INBOX = "inbox",
LIVECHAT = "livechat",
CONTACTS = "contacts",
RANKING = "ranking",
WORKFLOWS = "workflows",
TEMPLATES = "templates",
SETTINGS = "settings",
TEAM = "team",
BILLING = "billing",
}
const ALL_PERMISSIONS: Permission[] = [
Permission.DASHBOARD,
Permission.INBOX,
Permission.LIVECHAT,
Permission.CONTACTS,
Permission.RANKING,
Permission.WORKFLOWS,
Permission.TEMPLATES,
Permission.SETTINGS,
Permission.TEAM,
Permission.BILLING,
];
packages/console/src/collaborations/types.ts
const enum CollaborationRole {
User = "user", // Colaborador padrão
Owner = "owner", // Proprietário
}
packages/common/src/auth/types.ts
type UserIdentity = {
scopes: TokenScope[];
permissions: Permission[];
accountId: string;
userId: string;
};

Os authorizers são funções que validam o token JWT e extraem a identity do usuário.

AuthorizerScopes AceitosUso
publicUsersAdmin, Owner, UserOperações gerais
publicOwnerUsersAdmin, OwnerGerenciamento de equipe, billing
publicAdminUsersAdminFunções administrativas do sistema
public-user.ts
export function publicUsers(
request: AuthorizeHeaderRequest,
): AuthorizeResponse {
const token = getAuthorizationToken(request.headers);
const identity = parseToken(token, [
TokenScope.Admin,
TokenScope.Owner,
TokenScope.User,
]);
return { identity };
}
public-owner.ts
export function publicOwnerUsers(
request: AuthorizeHeaderRequest,
): AuthorizeResponse {
const token = getAuthorizationToken(request.headers);
const identity = parseToken(token, [TokenScope.Admin, TokenScope.Owner]);
return { identity };
}
public-admin.ts
export function publicAdminUsers(
request: AuthorizeHeaderRequest,
): AuthorizeResponse {
const token = getAuthorizationToken(request.headers);
const identity = parseToken(token, [TokenScope.Admin]);
return { identity };
}
routes.ts
export type InvitationsRoutes = [
{
path: "POST /api/console/create-invitation";
authorizer: typeof publicUsers; // Qualquer usuário pode chamar
handler: typeof createInvitationHandler;
},
{
path: "PUT /api/console/update-user-permissions/{userId}";
authorizer: typeof publicOwnerUsers; // Apenas owner/admin
handler: typeof updateUserPermissionsHandler;
},
];

packages/console/src/permissions/utils/check-permission.ts
import { HttpForbiddenError } from "@ez4/gateway";
import { TokenScope, type Permission, type UserIdentity } from "@gaio/common";
export const requirePermission = (
identity: UserIdentity,
requiredPermission: Permission,
): void => {
// Admin e Owner sempre têm acesso
if (hasElevatedScope(identity.scopes)) {
return;
}
// User precisa ter a permissão específica
if (!identity.permissions?.includes(requiredPermission)) {
throw new HttpForbiddenError(`Missing permission: ${requiredPermission}`);
}
};
const hasElevatedScope = (scopes: TokenScope[]): boolean => {
return scopes.includes(TokenScope.Admin) || scopes.includes(TokenScope.Owner);
};
1. Verifica se scope = 'Admin' ou 'Owner'
└─ Sim → Acesso liberado (return)
└─ Não → Continua
2. Verifica se permissions[] contém a permissão requerida
└─ Sim → Acesso liberado (return)
└─ Não → Lança HttpForbiddenError (403)

CódigoTipoQuando Ocorre
401 UnauthorizedAutenticaçãoToken inválido, expirado ou ausente
403 ForbiddenAutorizaçãoUsuário autenticado mas sem permissão

O erro 403 é retornado quando:

  1. Scope insuficiente: Usuário tenta acessar rota que exige scope maior

    • Exemplo: Usuário tentando acessar rota com publicOwnerUsers
  2. Permission ausente: Usuário (scope User) não possui a permission requerida

    • Exemplo: Usuário sem TEAM tentando criar convite
{
"message": "Missing permission: team"
}
CenárioResultado
Usuário sem TEAM chama POST /create-invitation403 - Missing permission: team
Usuário sem WORKFLOWS chama POST /create-workflow403 - Missing permission: workflows
Usuário tenta PUT /update-user-permissions (rota Owner)403 - Forbidden
Token expirado em qualquer rota401 - Unauthorized
try {
await api.createInvitation(data);
} catch (error) {
if (error.status === 401) {
// Redirecionar para login - token inválido
redirectToLogin();
} else if (error.status === 403) {
// Mostrar mensagem de permissão negada
showError("Você não tem permissão para esta ação");
}
}

import { Permission } from "@gaio/common";
import { requirePermission } from "../../permissions/utils/check-permission.js";
export async function createInvitationHandler(
request: CreateInvitationRequest,
context: Service.Context<InvitationsProvider>,
): Promise<Http.SuccessEmptyResponse> {
// Verifica permissão ANTES de qualquer operação
requirePermission(request.identity, Permission.TEAM);
const { accountId, userId } = request.identity;
// ... resto da implementação
}
export async function updateWorkflowHandler(
request: UpdateWorkflowRequest,
context: Service.Context<WorkflowsProvider>,
): Promise<UpdateWorkflowResponse> {
requirePermission(request.identity, Permission.WORKFLOWS);
const { accountId } = request.identity;
const { workflowId } = request.parameters;
// Se for template, precisa de permissão adicional
const workflow = await readWorkflow(consoleDb, accountId, workflowId);
if (workflow.is_template) {
requirePermission(request.identity, Permission.TEMPLATES);
}
// ... resto da implementação
}
DomínioPermissionDescrição
TagsCONTACTSCRUD de tags
ContactsCONTACTSCRUD de contatos
WorkflowsWORKFLOWSCRUD de workflows
RepliesWORKFLOWSRespostas rápidas
TemplatesTEMPLATESMarketplace
InvitationsTEAMConvites de equipe
CollaborationsTEAMGerenciamento de colaboradores
SettingsSETTINGSConfigurações da conta
BillingBILLINGFaturamento

packages/console/src/authentications/utils/token.ts
export const issueToken = (
accountId: string,
userId: string,
scopes: TokenScope[],
permissions: Permission[] = [],
options?: IssueTokenOptions,
) => {
const accessPayload = {
iss: accountId,
sub: userId,
scopes,
permissions,
};
return {
accessToken: jwt.sign(accessPayload, issuerSecret, {
expiresIn: issuerAccessTTL,
}),
refreshToken: jwt.sign(refreshPayload, issuerSecret, {
expiresIn: issuerRefreshTTL,
}),
};
};
packages/console/src/authentications/repository.ts
export const fetchUserScopeAndPermissions = async (
client: DbClient,
accountId: string,
userId: string,
): Promise<FetchUserScopeAndPermissionsOutput | undefined> => {
const [account, collaboration] = await Promise.all([
// Verifica se usuário e owner da conta
client.accounts.findOne({
select: { id: true, status: true, type: true },
where: { deleted_at: null, owner_user_id: userId, id: accountId },
}),
// Verifica se usuário tem colaboração
client.collaborations.findOne({
select: { role: true },
where: { deleted_at: null, account_id: accountId, user_id: userId },
}),
]);
// Owner da conta: todas as permissões
if (account) {
return {
scope: getAccountScope(account), // owner ou admin
permissions: getAllPermissions(),
};
}
// Colaborador: permissões do banco
if (collaboration) {
const permissions = await fetchUserPermissions(client, accountId, userId);
return {
scope: roleToScope(collaboration.role), // user
permissions,
};
}
return undefined;
};
const getAccountScope = (account: Pick<AccountSchema, "status" | "type">) => {
if (account.type === AccountType.System) {
return TokenScope.Admin;
}
return TokenScope.Owner;
};
export const roleToScope = (role: CollaborationRole) => {
switch (role) {
case CollaborationRole.Owner:
return TokenScope.Owner;
default:
return TokenScope.User;
}
};

tags/endpoints/create.ts
requirePermission(request.identity, Permission.CONTACTS);
// tags/endpoints/update.ts
requirePermission(request.identity, Permission.CONTACTS);
// tags/endpoints/delete.ts
requirePermission(request.identity, Permission.CONTACTS);
// contacts/endpoints/*.ts
requirePermission(request.identity, Permission.CONTACTS);
// workflows/endpoints/*.ts
requirePermission(request.identity, Permission.WORKFLOWS);
// replies/endpoints/*.ts
requirePermission(request.identity, Permission.WORKFLOWS);
template-packages/endpoints/list-created.ts
requirePermission(request.identity, Permission.TEMPLATES);
// template-packages/endpoints/install.ts
requirePermission(request.identity, Permission.TEMPLATES);
invitations/endpoints/create.ts
requirePermission(request.identity, Permission.TEAM);
// invitations/endpoints/update.ts
requirePermission(request.identity, Permission.TEAM);
// collaborations/endpoints/update.ts
requirePermission(request.identity, Permission.TEAM);

GET /api/console/list-permissions
Authorizer: publicUsers

Retorna array com todas as permissões disponíveis (ALL_PERMISSIONS).

GET /api/console/read-user-permissions/{userId}
Authorizer: publicOwnerUsers

Retorna as permissões atribuídas a um colaborador específico.

PUT /api/console/update-user-permissions/{userId}
Authorizer: publicOwnerUsers
Body: { "permissions": ["inbox", "contacts"] }

Define as permissões de um colaborador (substitui todas).


  • Definir authorizer apropriado na rota (publicUsers, publicOwnerUsers, publicAdminUsers)
  • Adicionar requirePermission() no início do handler se necessário
  • Usar a Permission correta para o domínio
  • Testar com usuário Owner (deve ter acesso)
  • Testar com usuário User sem a permissão (deve retornar 403)
  • Testar com usuário User com a permissão (deve ter acesso)