React SDK (@aurisid/react)
Componentes y hooks pre-construidos de Auris para aplicaciones React.
📦 Versión actual: 0.7.0 | Ver en npm
Instalación
npm install @aurisid/react
# o
yarn add @aurisid/react
# o
pnpm add @aurisid/reactBot Protection (Opcional)
Para soporte de protección contra bots con Cloudflare Turnstile:
npm install @marsidev/react-turnstileConfiguración
1. Agregar Provider
Envuelve tu aplicación con AurisProvider:
// main.tsx (Vite) o index.tsx (CRA)
import React from 'react';
import ReactDOM from 'react-dom/client';
import { AurisProvider } from '@aurisid/react';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<AurisProvider apiUrl="https://api.aurisid.com">
<App />
</AurisProvider>
</React.StrictMode>
);2. Agregar Banner de Impersonación (Opcional)
Para soporte de impersonación de usuarios por administradores:
import { AurisProvider, ImpersonationBanner } from '@aurisid/react';
<AurisProvider apiUrl="https://api.aurisid.com">
<ImpersonationBanner />
<App />
</AurisProvider>Componentes de Autenticación
<SignIn />
Formulario completo de inicio de sesión con:
- Email/contraseña
- Login social (Google, GitHub)
- Enlace de "Olvidé mi contraseña"
- Protección automática contra bots (si está habilitado)
import { SignIn } from '@aurisid/react';
function SignInPage() {
return (
<div className="auth-container">
<SignIn
redirectUrl="/dashboard"
signUpUrl="/sign-up"
forgotPasswordUrl="/forgot-password"
appearance={{ baseColor: '#4f46e5' }}
onSuccess={(user) => console.log('Usuario autenticado:', user)}
/>
</div>
);
}<SignUp />
Formulario completo de registro con:
- Campos de nombre y apellido
- Email/contraseña
- Registro social (Google, GitHub)
- Checkbox de términos y condiciones
- Detección automática de contraseñas filtradas
- Protección automática contra bots (si está habilitado)
import { SignUp } from '@aurisid/react';
function SignUpPage() {
return (
<div className="auth-container">
<SignUp
redirectUrl="/dashboard"
signInUrl="/sign-in"
appearance={{ baseColor: '#4f46e5' }}
onSuccess={(user) => console.log('Cuenta creada:', user)}
/>
</div>
);
}<MagicLink />
Autenticación sin contraseña mediante enlaces mágicos:
import { MagicLink } from '@aurisid/react';
function MagicLinkPage() {
return (
<div className="auth-container">
<MagicLink
redirectUrl="/dashboard"
callbackUrl="https://myapp.com/magic-link/verify"
appearance={{ baseColor: '#8b5cf6' }}
onSent={(email) => console.log('Enlace enviado a:', email)}
/>
</div>
);
}<UserButton />
Avatar de usuario con menú desplegable:
import { UserButton } from '@aurisid/react';
function Header() {
return (
<header>
<nav>
<Logo />
<UserButton showName afterSignOutUrl="/" />
</nav>
</header>
);
}<SignInButton /> y <SignUpButton />
Botones que redirigen a las páginas de autenticación:
import { SignInButton, SignUpButton } from '@aurisid/react';
function LandingPage() {
return (
<div>
<SignInButton>
Iniciar sesión
</SignInButton>
<SignUpButton>
Crear cuenta gratis
</SignUpButton>
</div>
);
}<SignedIn /> y <SignedOut />
Wrappers condicionales basados en el estado de autenticación:
import { SignedIn, SignedOut, UserButton, SignInButton } from '@aurisid/react';
function Header() {
return (
<header>
<SignedOut>
<SignInButton />
</SignedOut>
<SignedIn>
<UserButton showName />
</SignedIn>
</header>
);
}<ImpersonationBanner />
Banner que se muestra cuando un admin está impersonando a otro usuario:
import { AurisProvider, ImpersonationBanner } from '@aurisid/react';
// En tu layout principal
<AurisProvider apiUrl="https://api.aurisid.com">
<ImpersonationBanner position="top" />
<App />
</AurisProvider>Hooks
useAuris()
Hook principal para acceder al estado de autenticación:
import { useAuris } from '@aurisid/react';
function ProtectedComponent() {
const {
user,
isSignedIn,
isLoaded,
signOut,
isImpersonating,
impersonator,
impersonate,
exitImpersonation
} = useAuris();
if (!isLoaded) {
return <div>Cargando...</div>;
}
if (!isSignedIn) {
return <Navigate to="/sign-in" />;
}
return (
<div>
<p>Bienvenido, {user.firstName || user.email}</p>
{isImpersonating && (
<div className="alert">
Viendo como {user.email} (originalmente {impersonator?.email})
<button onClick={exitImpersonation}>Salir</button>
</div>
)}
<button onClick={signOut}>Cerrar sesión</button>
</div>
);
}Valores retornados:
user: Objeto del usuario actual o nullisSignedIn: Boolean indicando si hay sesión activaisLoaded: Boolean indicando si el estado ya se cargósignOut(): Función para cerrar sesiónisImpersonating: Boolean indicando si es una sesión de impersonaciónimpersonator: Usuario admin original (cuando hay impersonación)impersonate(userId): Función para impersonar a otro usuario (solo admins)exitImpersonation(): Función para salir del modo impersonación
useMagicLink()
Hook para verificar enlaces mágicos en páginas de callback:
import { useMagicLink } from '@aurisid/react';
function MagicLinkVerifyPage() {
const { loading, error, success, user } = useMagicLink({
redirectUrl: '/dashboard',
onSuccess: (user) => console.log('Usuario verificado:', user),
});
if (loading) {
return (
<div>
<h2>Verificando tu enlace mágico...</h2>
<p>Por favor espera</p>
</div>
);
}
if (error) {
return (
<div>
<h2>Error de verificación</h2>
<p>{error}</p>
<a href="/sign-in">Intentar de nuevo</a>
</div>
);
}
if (success) {
return (
<div>
<h2>¡Éxito!</h2>
<p>Redirigiendo al dashboard...</p>
</div>
);
}
return null;
}Proteger Rutas
Con React Router
import { useAuris } from '@aurisid/react';
import { Navigate, Outlet } from 'react-router-dom';
function ProtectedRoute() {
const { isLoaded, isSignedIn } = useAuris();
if (!isLoaded) {
return <div>Cargando...</div>;
}
if (!isSignedIn) {
return <Navigate to="/sign-in" replace />;
}
return <Outlet />;
}
// En tu router
<Routes>
<Route path="/sign-in" element={<SignInPage />} />
<Route path="/sign-up" element={<SignUpPage />} />
<Route path="/magic-link" element={<MagicLinkPage />} />
<Route path="/magic-link/verify" element={<MagicLinkVerifyPage />} />
<Route element={<ProtectedRoute />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Route>
</Routes>Características de Seguridad
🔐 Protección contra Bots (Turnstile)
Los componentes <SignIn /> y <SignUp /> detectan automáticamente si tu backend tiene protección contra bots habilitada y muestran el captcha de Cloudflare Turnstile cuando es necesario.
# Instalar dependencia opcional
npm install @marsidev/react-turnstile
# Los componentes detectan automáticamente si está instalado
# y si el backend lo requiere🔑 Detección de Contraseñas Filtradas
El componente <SignUp /> valida automáticamente que las contraseñas no hayan sido filtradas en brechas de seguridad conocidas y muestra un error si la contraseña está comprometida.
👥 Impersonación de Usuarios (Admin)
Los administradores pueden impersonar usuarios para debugging y soporte, con un banner visible que indica claramente el modo impersonación:
import { useAuris } from '@aurisid/react';
function AdminUserList({ users }) {
const { impersonate } = useAuris();
const handleViewAsUser = async (userId: string) => {
try {
await impersonate(userId);
// La página se recarga con la sesión del usuario impersonado
} catch (error) {
console.error('Error al impersonar:', error);
}
};
return (
<div>
{users.map(user => (
<div key={user.id}>
<span>{user.email}</span>
<button onClick={() => handleViewAsUser(user.id)}>
Ver como usuario
</button>
</div>
))}
</div>
);
}🪄 Magic Links (Passwordless)
Autenticación sin contraseña mediante enlaces seguros enviados por email. Los enlaces expiran en 15 minutos y son de un solo uso.
Llamadas a tu API
El token de autenticación se guarda automáticamente en localStorage como auris_token:
function Dashboard() {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const token = localStorage.getItem('auris_token');
const response = await fetch('https://api.tuapp.com/data', {
headers: {
'Authorization': `Bearer ${token}`,
},
});
const result = await response.json();
setData(result);
};
fetchData();
}, []);
return <div>{/* Renderizar datos */}</div>;
}Organizations
Auris proporciona soporte completo para multi-tenancy con organizaciones:
<OrganizationSwitcher />
Dropdown para cambiar entre organizaciones y crear nuevas:
import { OrganizationSwitcher } from '@aurisid/react';
function Navbar() {
return (
<nav>
<OrganizationSwitcher
showCreateOrganization={true}
organizationProfileUrl="/settings/organization"
/>
</nav>
);
}<OrganizationProfile />
Página completa de configuración de organización:
import { OrganizationProfile } from '@aurisid/react';
function SettingsPage() {
return (
<OrganizationProfile
organizationId="org_123"
onUpdate={() => console.log('Actualizado!')}
onDelete={() => window.location.href = '/'}
/>
);
}<OrganizationMembers />
Gestión de miembros, roles e invitaciones:
import { OrganizationMembers } from '@aurisid/react';
function MembersPage() {
return (
<OrganizationMembers
organizationId="org_123"
showInvitations={true}
/>
);
}Hooks de Organizations
useOrganizationList()
import { useOrganizationList } from '@aurisid/react';
function MyComponent() {
const {
organizations, // Array de organizaciones
activeOrganization, // ID de org activa
isLoading,
error,
createOrganization, // Crear nueva org
setActiveOrganization, // Cambiar org activa
reloadOrganizations // Refrescar lista
} = useOrganizationList();
const handleCreate = async () => {
await createOrganization({
name: 'Mi Organización',
slug: 'mi-org',
description: 'Descripción opcional'
});
};
return (
<div>
{organizations.map(org => (
<button
key={org.id}
onClick={() => setActiveOrganization(org.id)}
>
{org.name}
</button>
))}
</div>
);
}useOrganization()
import { useOrganization } from '@aurisid/react';
function OrgManager() {
const {
organization, // Objeto organization
members, // Array de miembros
invitations, // Invitaciones pendientes
isLoading,
error,
updateOrganization, // Actualizar org
deleteOrganization, // Eliminar org
loadMembers, // Refrescar miembros
inviteMember, // Invitar por email
updateMemberRole, // Cambiar rol
removeMember, // Remover miembro
loadInvitations, // Refrescar invitaciones
cancelInvitation // Cancelar invitación
} = useOrganization({
organizationId: 'org_123'
});
const handleInvite = async () => {
await inviteMember({
email: 'user@example.com',
role: 'member' // 'owner' | 'admin' | 'member'
});
};
return (
<div>
<h1>{organization?.name}</h1>
<p>{members.length} miembros</p>
<button onClick={handleInvite}>Invitar</button>
</div>
);
}💡 Demo interactiva: Ver demos de Organizations →
Multi-Factor Authentication (MFA)
Auris proporciona componentes completos de MFA con TOTP y códigos de respaldo:
<MFASetup />
Wizard completo para configurar MFA (QR code + backup codes + verificación):
import { MFASetup } from '@aurisid/react';
function SecurityPage() {
return (
<MFASetup
onEnabled={() => {
console.log('MFA enabled successfully');
// Redirigir o mostrar éxito
}}
onCancel={() => {
console.log('Setup cancelled');
}}
/>
);
}<MFAVerify />
Componente de verificación para el flujo de signin:
import { MFAVerify } from '@aurisid/react';
function SignInPage() {
const [challengeToken, setChallengeToken] = useState<string | null>(null);
// Después del signin, si requiresMFA: true
if (challengeToken) {
return (
<MFAVerify
challengeToken={challengeToken}
onSuccess={() => {
console.log('MFA verified');
// La página se recarga automáticamente
}}
onCancel={() => setChallengeToken(null)}
/>
);
}
return <SignInForm onSubmit={handleSignIn} />;
}<MFAManage />
Interfaz completa de gestión de MFA para configuración del usuario:
import { MFAManage } from '@aurisid/react';
function SecuritySettingsPage() {
return (
<MFAManage
onEnabled={() => console.log('MFA enabled')}
onDisabled={() => console.log('MFA disabled')}
/>
);
}Hook useMFA()
Hook para gestionar MFA programáticamente:
import { useMFA } from '@aurisid/react';
function CustomMFAFlow() {
const {
setupData, // QR code y backup codes
status, // Estado de MFA
setupTOTP, // Iniciar setup
enableTOTP, // Habilitar con código
disableTOTP, // Deshabilitar con código
getStatus, // Refrescar estado
verifyMFA // Verificar durante signin
} = useMFA();
const handleSetup = async () => {
const setup = await setupTOTP();
console.log('QR:', setup.qrCode);
console.log('Backup codes:', setup.backupCodes);
// Después de que el usuario escanee el QR
await enableTOTP('123456'); // Código del usuario
};
return (
<div>
<button onClick={handleSetup}>Setup MFA</button>
{status?.totpEnabled && (
<p>MFA habilitado. {status.backupCodesCount} códigos restantes</p>
)}
</div>
);
}💡 Demo interactiva: Ver demos de MFA →
Session Management
Gestiona todas las sesiones activas del usuario:
<SessionList />
Componente completo para visualizar y gestionar sesiones activas:
import { SessionList } from '@aurisid/react';
function SecurityPage() {
return (
<SessionList
onSessionRevoked={(sessionId) => {
console.log('Session revoked:', sessionId);
}}
onAllOthersRevoked={() => {
console.log('All other sessions revoked');
}}
/>
);
}Hook useSessions()
Hook para gestionar sesiones programáticamente:
import { useSessions } from '@aurisid/react';
function CustomSessionManager() {
const {
sessions, // List of active sessions
isLoading,
error,
getSessions, // Load sessions
revokeSession, // Revoke a specific session
revokeAllOtherSessions // Revoke all except current
} = useSessions();
useEffect(() => {
getSessions();
}, []);
const handleRevoke = async (sessionId: string) => {
try {
await revokeSession(sessionId);
console.log('Session revoked');
} catch (err) {
console.error('Failed to revoke session');
}
};
return (
<div>
{sessions?.map((session) => (
<div key={session.id}>
<p>{session.device?.browser} on {session.device?.os}</p>
<p>Last activity: {new Date(session.lastActivityAt).toLocaleString()}</p>
{!session.isCurrent && (
<button onClick={() => handleRevoke(session.id)}>
Revoke
</button>
)}
</div>
))}
</div>
);
}💡 Demo interactiva: Ver demo de Sessions →
Webhooks
Configura webhooks para recibir eventos en tiempo real:
<WebhookList />
Componente completo para gestionar webhooks con CRUD:
import { WebhookList } from '@aurisid/react';
function DeveloperSettingsPage() {
return (
<WebhookList
onWebhookCreated={(webhook) => {
console.log('Webhook created:', webhook);
}}
onWebhookUpdated={(webhook) => {
console.log('Webhook updated:', webhook);
}}
onWebhookDeleted={(webhookId) => {
console.log('Webhook deleted:', webhookId);
}}
/>
);
}Hook useWebhooks()
Hook para gestionar webhooks programáticamente:
import { useWebhooks } from '@aurisid/react';
function CustomWebhookManager() {
const {
webhooks, // List of webhooks
isLoading,
error,
getWebhooks, // Load webhooks
createWebhook, // Create new webhook
updateWebhook, // Update existing webhook
deleteWebhook, // Delete webhook
testWebhook, // Send test event
regenerateSecret // Regenerate secret
} = useWebhooks();
useEffect(() => {
getWebhooks();
}, []);
const handleCreate = async () => {
try {
const webhook = await createWebhook({
url: 'https://example.com/webhooks',
events: ['user.created', 'user.updated'],
description: 'User sync webhook'
});
console.log('Webhook created:', webhook);
} catch (err) {
console.error('Failed to create webhook');
}
};
return (
<div>
<button onClick={handleCreate}>Create Webhook</button>
{webhooks?.map((webhook) => (
<div key={webhook.id}>
<p>{webhook.url}</p>
<p>Events: {webhook.events.join(', ')}</p>
<p>Status: {webhook.active ? 'Active' : 'Inactive'}</p>
</div>
))}
</div>
);
}Eventos disponibles: user.created, user.updated, user.deleted, session.created, session.revoked, organization.created, organization.updated, organization.deleted, organization.member.added, organization.member.removed, organization.member.role_updated
💡 Demo interactiva: Ver demo de Webhooks →
Products (Multi-tenancy)
Sistema completo de multi-tenancy para gestionar productos/aplicaciones, suscripciones y licencias:
Flujo típico: Producto (OxideFlow) → Organización suscribe → Admin asigna asientos → Usuario verifica acceso
<ProductList />
Lista de productos disponibles con CRUD para administradores:
import { ProductList } from '@aurisid/react';
function AdminProductsPage() {
return (
<ProductList
showCreate={true} // Mostrar botón de crear (admin)
onSelect={(product) => {
console.log('Selected:', product);
// Navegar a detalles del producto
}}
/>
);
}<ProductSubscriptionManager />
Gestiona qué organizaciones tienen acceso a qué productos:
import { ProductSubscriptionManager } from '@aurisid/react';
// Ver productos de una organización
function OrgProductsPage({ organizationId }) {
return (
<ProductSubscriptionManager
organizationId={organizationId}
mode="organization"
onSelect={(subscription) => {
// Ver detalles de suscripción
}}
/>
);
}
// Ver organizaciones suscritas a un producto
function ProductSubscribersPage({ productId }) {
return (
<ProductSubscriptionManager
productId={productId}
mode="product"
/>
);
}<ProductSeatManager />
Asigna y gestiona licencias/asientos de usuarios dentro de una suscripción:
import { ProductSeatManager } from '@aurisid/react';
function ManageSeatsPage({ subscriptionId, maxSeats }) {
return (
<ProductSeatManager
subscriptionId={subscriptionId}
maxSeats={maxSeats} // Muestra disponibilidad
onSelect={(seat) => {
console.log('Seat selected:', seat);
}}
/>
);
}Hooks de Products
useProducts()
CRUD completo de productos:
import { useProducts } from '@aurisid/react';
function AdminProducts() {
const {
products,
loading,
error,
getProducts,
getProduct,
getProductBySlug,
createProduct,
updateProduct,
deleteProduct
} = useProducts();
const handleCreate = async () => {
await createProduct({
name: 'MediScribe',
slug: 'mediscribe',
description: 'AI-powered medical transcription',
logoUrl: 'https://...',
websiteUrl: 'https://mediscribe.app'
});
};
return (
<div>
<button onClick={handleCreate}>Crear Producto</button>
{products.map(p => <div key={p.id}>{p.name}</div>)}
</div>
);
}useProductSubscriptions()
Gestionar suscripciones de organizaciones a productos:
import { useProductSubscriptions } from '@aurisid/react';
function SubscriptionManager() {
const {
subscriptions,
loading,
error,
getProductSubscriptions, // Orgs suscritas a un producto
getOrganizationProducts, // Productos de una org
subscribe, // Suscribir org a producto
updateSubscription,
cancelSubscription
} = useProductSubscriptions();
const handleSubscribe = async () => {
await subscribe('product_123', {
organizationId: 'org_456',
plan: 'pro',
maxSeats: 10,
expiresAt: '2025-12-31T00:00:00Z'
});
};
return (/* ... */);
}useProductSeats()
Gestionar asientos/licencias de usuarios:
import { useProductSeats } from '@aurisid/react';
function SeatManager({ subscriptionId }) {
const {
seats,
loading,
error,
getSeats,
assignSeat,
updateSeat,
removeSeat
} = useProductSeats();
const handleAssign = async () => {
await assignSeat(subscriptionId, {
userId: 'user_789',
role: 'admin',
permissions: { canEdit: true, canExport: true }
});
};
return (/* ... */);
}useMyProducts()
Obtener productos del usuario actual y verificar acceso (lo más importante para tu app):
import { useMyProducts } from '@aurisid/react';
function App() {
const {
products, // Productos a los que tengo acceso
loading,
error,
getMyProducts, // Cargar mis productos
checkAccess, // Verificar acceso a un producto específico
hasAccess, // Verificación síncrona (de caché)
getProductAccess // Obtener detalles de acceso
} = useMyProducts({ autoLoad: true });
// Verificación síncrona (después de cargar)
if (hasAccess('mediscribe')) {
return <MediScribeApp />;
}
// O verificación asíncrona
const handleCheckAccess = async () => {
const access = await checkAccess('mediscribe');
if (access.hasAccess) {
console.log('Role:', access.seat?.role);
console.log('Permissions:', access.seat?.permissions);
// Mostrar app con permisos según rol
} else {
console.log('No access:', access.reason);
// Mostrar mensaje de "Contacta a tu admin"
}
};
return <button onClick={handleCheckAccess}>Verificar Acceso</button>;
}Ejemplo: Proteger una App por Producto
import { useMyProducts } from '@aurisid/react';
function ProductGate({ slug, children, fallback }) {
const { hasAccess, loading } = useMyProducts({ autoLoad: true });
if (loading) return <div>Verificando acceso...</div>;
if (!hasAccess(slug)) {
return fallback || (
<div>
<h2>Acceso Denegado</h2>
<p>No tienes acceso a esta aplicación.</p>
<p>Contacta a tu administrador para solicitar una licencia.</p>
</div>
);
}
return children;
}
// Uso
function MediScribePage() {
return (
<ProductGate slug="mediscribe">
<MediScribeApp />
</ProductGate>
);
}💡 Demo interactiva: Ver demo de Products →
TypeScript
Todos los componentes y hooks están completamente tipados:
import type {
AurisUser,
AurisContextValue,
SignInProps,
SignUpProps,
MagicLinkProps,
ImpersonationBannerProps,
MagicLinkVerifyResult,
UseMagicLinkOptions,
// Organization types
Organization,
OrganizationMember,
OrganizationInvitation,
OrganizationRole,
OrganizationSwitcherProps,
OrganizationProfileProps,
OrganizationMembersProps,
// MFA types
MFASetupResponse,
MFAStatus,
MFAVerifyParams,
MFAVerifyResponse,
MFASetupProps,
MFAVerifyProps,
MFAManageProps,
// Session types
Session,
SessionsListResponse,
SessionListProps,
// Webhook types
Webhook,
WebhookEventType,
WebhookDelivery,
WebhookListProps,
// Product types (Multi-tenancy)
Product,
OrganizationProduct,
ProductSeat,
ProductAccess,
CreateProductRequest,
SubscribeOrganizationRequest,
AssignSeatRequest,
ProductListProps,
ProductSubscriptionManagerProps,
ProductSeatManagerProps
} from '@aurisid/react';
// Ejemplo de tipo AurisUser
interface AurisUser {
id: string;
email: string;
emailVerified: boolean;
username?: string;
firstName?: string;
lastName?: string;
avatarUrl?: string;
}Personalización
Apariencia con baseColor
<SignIn
appearance={{
baseColor: '#10b981' // Verde
}}
/>
<SignUp
appearance={{
baseColor: '#8b5cf6' // Púrpura
}}
/>
<MagicLink
appearance={{
baseColor: '#ef4444' // Rojo
}}
/>CSS Classes
Cada componente tiene una clase CSS para personalización:
.auris-user-button.auris-sign-in.auris-sign-up.auris-magic-link.auris-impersonation-banner.auris-org-switcher.auris-org-profile.auris-org-members