Organizaciones
Multi-tenancy con organizaciones, roles y permisos para aplicaciones B2B.
Conceptos
- Organizacion - Un grupo/empresa/equipo que agrupa usuarios
- Miembro - Un usuario que pertenece a una organizacion
- Rol - Define los permisos del miembro (admin, member, viewer, etc.)
- Invitacion - Permite agregar nuevos miembros por email
Modelo de Organizacion
interface Organization {
id: string; // ID unico
name: string; // Nombre de la organizacion
slug: string; // URL-friendly identifier
logoUrl?: string; // Logo de la organizacion
publicMetadata: Record<string, any>;
privateMetadata: Record<string, any>;
createdAt: string;
updatedAt: string;
}
interface Membership {
id: string;
userId: string;
organizationId: string;
role: 'admin' | 'member' | 'viewer';
publicMetadata: Record<string, any>;
createdAt: string;
}
interface Invitation {
id: string;
email: string;
organizationId: string;
role: string;
status: 'pending' | 'accepted' | 'expired';
expiresAt: string;
createdAt: string;
}Cliente SDK
Obtener organizacion activa
import { useOrganization } from '@auris/react';
function OrgDashboard() {
const { organization, membership, isLoaded } = useOrganization();
if (!isLoaded) return <Loading />;
if (!organization) return <NoOrgSelected />;
return (
<div>
<h1>{organization.name}</h1>
<p>Tu rol: {membership.role}</p>
</div>
);
}Listar organizaciones del usuario
import { useOrganizationList } from '@auris/react';
function OrgSwitcher() {
const { organizations, setActive, isLoaded } = useOrganizationList();
if (!isLoaded) return <Loading />;
return (
<select onChange={(e) => setActive(e.target.value)}>
{organizations.map((org) => (
<option key={org.id} value={org.id}>
{org.name}
</option>
))}
</select>
);
}Crear organizacion
import { useOrganization } from '@auris/react';
function CreateOrg() {
const { create } = useOrganization();
const handleCreate = async () => {
const org = await create({
name: 'Mi Empresa',
slug: 'mi-empresa',
});
console.log('Creada:', org.id);
};
return <button onClick={handleCreate}>Crear Organizacion</button>;
}Backend SDK
Crear organizacion
const org = await auris.organizations.create({
name: 'Acme Inc',
slug: 'acme',
createdBy: 'user_xxxxx', // Usuario que sera admin
publicMetadata: {
plan: 'enterprise',
industry: 'healthcare',
},
});Listar organizaciones
const { data, totalCount } = await auris.organizations.list({
limit: 20,
offset: 0,
query: 'acme', // Buscar por nombre
});Obtener organizacion
// Por ID
const org = await auris.organizations.get('org_xxxxx');
// Por slug
const org = await auris.organizations.getBySlug('acme');Actualizar organizacion
const org = await auris.organizations.update('org_xxxxx', {
name: 'Acme Corporation',
publicMetadata: {
plan: 'enterprise-plus',
},
});Eliminar organizacion
await auris.organizations.delete('org_xxxxx');Gestion de Miembros
Listar miembros
const members = await auris.organizations.listMembers('org_xxxxx', {
limit: 50,
role: 'admin', // Filtrar por rol (opcional)
});
for (const member of members) {
console.log(member.user.email, member.role);
}Agregar miembro
await auris.organizations.addMember('org_xxxxx', {
userId: 'user_yyyyy',
role: 'member',
});Actualizar rol
await auris.organizations.updateMember('org_xxxxx', 'user_yyyyy', {
role: 'admin',
});Remover miembro
await auris.organizations.removeMember('org_xxxxx', 'user_yyyyy');Invitaciones
Crear invitacion
const invitation = await auris.organizations.createInvitation('org_xxxxx', {
email: 'nuevo@email.com',
role: 'member',
// La invitacion expira en 7 dias por defecto
});
// Se envia automaticamente un email al usuarioListar invitaciones pendientes
const invitations = await auris.organizations.listInvitations('org_xxxxx', {
status: 'pending',
});Revocar invitacion
await auris.organizations.revokeInvitation('org_xxxxx', 'inv_xxxxx');Aceptar invitacion (cliente)
import { useOrganization } from '@auris/react';
function AcceptInvitation({ token }) {
const { acceptInvitation } = useOrganization();
const handleAccept = async () => {
await acceptInvitation(token);
// Usuario ahora es miembro de la organizacion
};
return <button onClick={handleAccept}>Aceptar Invitacion</button>;
}Roles y Permisos
Roles predefinidos
admin- Control total de la organizacionmember- Acceso estandarviewer- Solo lectura
Verificar permisos
import { useOrganization } from '@auris/react';
function AdminPanel() {
const { membership } = useOrganization();
if (membership?.role !== 'admin') {
return <div>No tienes permisos</div>;
}
return <div>Panel de administracion</div>;
}Roles personalizados
// En el dashboard de Auris, puedes definir roles custom:
{
"roles": [
{
"key": "billing_admin",
"name": "Administrador de Facturacion",
"permissions": ["billing.read", "billing.write", "invoices.read"]
},
{
"key": "support",
"name": "Soporte",
"permissions": ["users.read", "tickets.read", "tickets.write"]
}
]
}
// Asignar rol custom
await auris.organizations.updateMember('org_xxxxx', 'user_yyyyy', {
role: 'billing_admin',
});Organizacion activa en el servidor
// Next.js App Router
import { auth } from '@auris/nextjs/server';
export default async function OrgPage() {
const { orgId, orgRole, userId } = await auth();
if (!orgId) {
return <div>Selecciona una organizacion</div>;
}
// Verificar permisos
if (orgRole !== 'admin') {
return <div>No autorizado</div>;
}
return <div>Dashboard de organizacion</div>;
}Multi-tenancy en la base de datos
// Filtrar datos por organizacion
app.get('/api/projects', async (req, res) => {
const { orgId } = req.auth;
if (!orgId) {
return res.status(400).json({ error: 'Organizacion requerida' });
}
const projects = await db.projects.findMany({
where: { organizationId: orgId },
});
res.json(projects);
});Webhooks de organizaciones
organization.createdorganization.updatedorganization.deletedorganization.member_addedorganization.member_removedorganization.member_updatedorganization.invitation_createdorganization.invitation_accepted