AACsearch

Auth & Mandantenfähigkeit

Wie Organisationen als Workspaces funktionieren, wie der Sitzungskontext durch die API fließt und das Sicherheitsmodell für mandantenfähige Suche.

AACsearch ist von Grund auf mandantenfähig. Jede Ressource – Suchindizes, API-Schlüssel, Verwendungsereignisse, Knowledge-Spaces – ist auf eine Organisation beschränkt. Organisationsübergreifende Lesevorgänge sind niemals erlaubt.

Authentifizierungs-Stack

Auth wird durch Better Auth bereitgestellt, konfiguriert in packages/auth/auth.ts.

Aktivierte Features:

FeatureHinweise
E-Mail + PasswortStandardmäßiger Anmeldeablauf
Magic LinksPasswortloser E-Mail-Login
PasskeysWebAuthn
2FA (TOTP)Zeitbasierte Einmalpasswörter
OAuth (Google, GitHub)Social Login
OrganisationenWorkspaces mit rollenbasierter Mitgliedschaft
Einladungsbasierte OrgsOptional; konfigurierbar
Admin-PanelInternes Benutzer-/Org-Management
SitzungsimitierungFür Support/Debug

Organisationen als Workspaces

Eine Organisation ist die primäre Workspace-Einheit. Benutzer können mehreren Organisationen angehören und im Dashboard zwischen ihnen wechseln. Jeder Suchindex, API-Schlüssel und jedes Analytics-Ereignis gehört zu genau einer Organisation.

Benutzer → Member(Rolle) → Organisation

                        SearchIndex(e)
                        SearchApiKey(s)
                        SearchUsageEvent(s)
                        KnowledgeSpace(s)

Sitzungskontext in oRPC

oRPC-Prozeduren werden als einer von drei Typen aufgerufen:

ProzedurtypVerfügbarer KontextVerwendet für
publicProcedureKeine Sitzung erforderlichÖffentlicher Suchhandler, Health-Check
protectedProcedurecontext.user, context.sessionAuthentifizierte Dashboard-Operationen
adminProcedurecontext.user + Admin-RollenprüfungNur-Admin-Operationen

Zugriff auf die Sitzung in einer Server-Komponente:

import { getSession } from "@auth/lib/server";

const session = await getSession();

Zugriff auf die Sitzung in einer Client-Komponente:

"use client";
import { useSession } from "@auth/hooks/use-session";

const { user, loaded } = useSession();

Zugriff auf die aktive Organisation:

"use client";
import { useActiveOrganization } from "@organizations/hooks/use-active-organization";

const { activeOrganization, isOrganizationAdmin } = useActiveOrganization();

Mandantenisolierung – Hard Invariant

Jeder Suchaufruf MUSS tenantId: verified.organizationId übergeben. Dies wird durchgesetzt in packages/api/modules/search/public-handler.ts:

// Immer UND-verknüpfen mit Mandantenfilter
const tenantFilter = `organization_id:=${organizationId}`;
const combinedFilter = combineFilters(tenantFilter, callerFilter);

Es gibt keinen Codepfad, der Dokumente über Organisationen hinweg zurückgibt. Wenn Sie eine neue Suchprozedur schreiben, muss diese Invariante explizit aufrechterhalten werden – sie ist nicht automatisch.

API-Schlüssel-Sicherheitsmodell

API-Schlüssel sind der primäre Auth-Mechanismus für externe Aufrufer (CMS-Module und Browser-SDKs).

Schlüsseltypen und Präfixe

PräfixBereichVerwendet von
ss_search_*searchBrowser-SDK, Widget – schreibgeschützt
ss_connector_*connector_writeCMS-Module – Schreiben + Sync
ss_scoped_*Eingeschränkt von ss_search_*Pro-Benutzer, pro-Filter-Token

Sicherheitsgarantien

  • Nur-Hash-Speicherung: SearchApiKey.hashedKey speichert nur den bcrypt-Hash. Klartext wird einmal bei der Erstellung angezeigt und wird niemals protokolliert, niemals bei Auflistungsoperationen zurückgegeben und niemals wiederholt.
  • Ursprungsbeschränkung: Jeder Schlüssel hat allowedOrigins[]. Anfragen von nicht aufgelisteten Ursprüngen werden auf API-Ebene vor jedem Aufruf der Suchmaschine abgelehnt.
  • Rate-Limiting: Gleitfenster pro Schlüssel, durchgesetzt über SearchRateLimitBucket.
  • Kontingent: Pro-Org-Plan-Kontingent, durchgesetzt über quotaCheck-Middleware vor Suche/Ingest.
  • Ablauf: Schlüssel können expiresAt haben; abgelaufene Schlüssel werden abgelehnt und durch den Wartungsjob bereinigt.

Admin-Schlüssel-Isolierung

Der Admin-Schlüssel für die Suchmaschine verlässt den Server niemals. Er lebt in packages/search/lib/client.ts, einmalig aus der Umgebungsvariable AACSEARCH_API_KEY instanziiert. Er wird niemals an irgendwelche Clients zurückgegeben, niemals an CMS-Module weitergegeben und niemals in das Widget-Bundle eingebettet.

CMS-Module erhalten nur ss_connector_*-Token, die durch die AACsearch Connector-API gehen, welche dann intern den Admin-Schlüssel verwendet.

Eingeschränkte Token

Eingeschränkte Token schränken die Berechtigungen eines bestehenden ss_search_*-Schlüssels ein. Sie sind HMAC-signierte zustandslose JWTs (nicht in DB gespeichert), ausgestellt von issueScopedSearchToken().

Ein häufiger Anwendungsfall: Ausstellen eines Pro-Benutzer-Tokens, das Ergebnisse auf die Produkte dieses Benutzers beschränkt.

// Serverseitig: eingeschränktes Token für einen bestimmten Preisbereich ausstellen
const token = await orpc.search.createScopedToken.call({
	organizationId,
	indexSlug: "products",
	scopedFilter: "price:<100",
	expiresInSeconds: 3600,
});

Der scopedFilter wird immer über combineFilters() UND-verknüpft mit den eigenen Filtern des Aufrufers. Er kann nur Berechtigungen einschränken – er kann sie nicht erweitern. Das Umgehen von combineFilters ist eine Hard-Invariant-Verletzung.

Ursprungs-Allowlist pro Schlüssel

Jeder ss_search_*-Schlüssel hat ein allowedOrigins[]-Array. Nur Anfragen von diesen Ursprüngen werden durchgelassen.

Im Free-Plan sind nur aacsearch.com-Subdomains erlaubt. Pro und darüber hinaus unterstützen benutzerdefinierte Ursprünge. Siehe Pläne und Limits für die Plan-spezifische Richtlinie.

Rollenbasierter Zugriff innerhalb von Organisationen

RolleFähigkeiten
ownerVollzugriff, kann Org übertragen, Abrechnung verwalten
adminIndizes, Schlüssel, Mitglieder, Einstellungen verwalten
memberDashboard anzeigen, Suchen in der Vorschau ausführen

Rollenprüfungen in oRPC-Prozeduren verwenden das von Better Auth bereitgestellte context.session-Objekt.

On this page