Autenticación y Multi-tenencia
Cómo funcionan las organizaciones como workspaces, cómo fluye el contexto de sesión a través de la API y el modelo de seguridad para búsqueda multi-tenant.
AACsearch es multi-tenant por diseño. Cada recurso — índices de búsqueda, API keys, eventos de uso, espacios de conocimiento — está limitado a una organización. Las lecturas entre organizaciones nunca están permitidas.
Stack de autenticación
La autenticación es proporcionada por Better Auth configurado en packages/auth/auth.ts.
Características habilitadas:
| Característica | Notas |
|---|---|
| Correo electrónico + contraseña | Flujo de inicio de sesión estándar |
| Magic links | Inicio de sesión sin contraseña por correo electrónico |
| Passkeys | WebAuthn |
| 2FA (TOTP) | Contraseñas de un solo uso basadas en tiempo |
| OAuth (Google, GitHub) | Inicio de sesión social |
| Organizaciones | Workspaces con membresía basada en roles |
| Organizaciones solo por invitación | Opcional; configurable |
| Panel de administración | Gestión interna de usuarios/organizaciones |
| Suplantación de sesión | Para soporte/depuración |
Las organizaciones como workspaces
Una organización es la unidad principal del workspace. Los usuarios pueden pertenecer a múltiples organizaciones y cambiar entre ellas en el panel. Cada índice de búsqueda, API key y evento de analíticas pertenece exactamente a una organización.
Usuario → Member(rol) → Organización
│
SearchIndex(es)
SearchApiKey(s)
SearchUsageEvent(s)
KnowledgeSpace(s)Contexto de sesión en oRPC
Los procedimientos oRPC se llaman como uno de tres tipos:
| Tipo de procedimiento | Contexto disponible | Usado para |
|---|---|---|
publicProcedure | No se requiere sesión | Manejador de búsqueda pública, verificación de salud |
protectedProcedure | context.user, context.session | Operaciones autenticadas del panel |
adminProcedure | context.user + verificación de rol de administrador | Operaciones solo de administrador |
Acceso a la sesión en un componente de servidor:
import { getSession } from "@auth/lib/server";
const session = await getSession();Acceso a la sesión en un componente cliente:
"use client";
import { useSession } from "@auth/hooks/use-session";
const { user, loaded } = useSession();Acceso a la organización activa:
"use client";
import { useActiveOrganization } from "@organizations/hooks/use-active-organization";
const { activeOrganization, isOrganizationAdmin } = useActiveOrganization();Aislamiento de tenant — Invariante duro
Cada llamada de búsqueda DEBE pasar tenantId: verified.organizationId. Esto se aplica en
packages/api/modules/search/public-handler.ts:
// Siempre combinar con AND con el filtro de tenant
const tenantFilter = `organization_id:=${organizationId}`;
const combinedFilter = combineFilters(tenantFilter, callerFilter);No existe ninguna ruta de código que devuelva documentos entre organizaciones. Si está escribiendo un nuevo procedimiento de búsqueda, este invariante debe mantenerse explícitamente — no es automático.
Modelo de seguridad de las API keys
Las API keys son el mecanismo de autenticación principal para los llamadores externos (módulos CMS y SDKs del navegador).
Tipos de claves y prefijos
| Prefijo | Ámbito | Usado por |
|---|---|---|
ss_search_* | search | SDK del navegador, widget — solo lectura |
ss_connector_* | connector_write | Módulos CMS — escritura + sincronización |
ss_scoped_* | Reducido desde ss_search_* | Tokens por usuario, por filtro |
Garantías de seguridad
- Almacenamiento solo hash:
SearchApiKey.hashedKeyalmacena solo el hash bcrypt. El texto plano se muestra una vez en la creación y nunca se registra, nunca se devuelve en operaciones de lista y nunca se repite. - Restricción de origen: Cada clave tiene
allowedOrigins[]. Las solicitudes de orígenes no incluidos en la lista se rechazan a nivel de API antes de cualquier llamada a AACSearch. - Límite de velocidad: Ventana deslizante por clave aplicada medianteSearchRateLimitBucket. - Cuota: Cuota del plan por organización aplicada mediante el middleware
quotaCheckantes de búsqueda/ingestión. - Expiración: Las claves pueden tener
expiresAt; las claves expiradas se rechazan y el trabajo de mantenimiento las limpia.
Aislamiento de clave de administrador
La clave de administrador de AACSearch nunca sale del servidor. Vive en packages/search/lib/client.ts,
instanciada una vez desde la variable de entorno AACSEARCH_API_KEY. Nunca se devuelve a ningún
cliente, nunca se pasa a módulos CMS y nunca se incrusta en el bundle del widget.
Los módulos CMS reciben solo tokens ss_connector_* que pasan por la API de conector de AACsearch,
que luego usa la clave de administrador internamente.
Tokens con ámbito
Los tokens con ámbito reducen los permisos de una clave ss_search_* existente. Son JWT sin estado firmados con HMAC
(no almacenados en la base de datos) emitidos por issueScopedSearchToken().
Un caso de uso común: emitir un token por usuario que limite los resultados solo a los productos de ese usuario.
// Lado del servidor: emitir un token con ámbito para un rango de precios específico
const token = await orpc.search.createScopedToken.call({
organizationId,
indexSlug: "products",
scopedFilter: "price:<100",
expiresInSeconds: 3600,
});El scopedFilter siempre se combina con AND con los propios filtros del llamador mediante combineFilters().
Solo puede reducir permisos — no puede ampliarlos. Omitir combineFilters es una violación del Invariante Duro.
Lista de permisiones de origen por clave
Cada clave ss_search_* tiene un array allowedOrigins[]. Solo pasan las solicitudes de esos orígenes.
En el plan gratuito, solo se permiten subdominios de aacsearch.com. Pro y superior admiten orígenes personalizados.
Consulte Planes y límites para la política por plan.
Acceso basado en roles dentro de las organizaciones
| Rol | Capacidades |
|---|---|
owner | Acceso completo, puede transferir organización, gestionar facturación |
admin | Gestionar índices, claves, miembros, configuración |
member | Ver panel, ejecutar búsquedas en vista previa |
Las verificaciones de roles en los procedimientos oRPC usan el objeto context.session proporcionado por Better Auth.
Modelo de Dominio
Entidades lógicas, límites de propiedad y qué modelos están persistidos hoy versus planificados para futuras migraciones de base de datos.
Plans & Limits
The canonical AACsearch plan matrix, Search-Unit definition, quota catalog, soft/hard semantics, and the code paths that enforce them.