feat(nextcloud-files): Modul Dateien mit Nextcloud-Adresse, Verbindungsprüfung und gesicherter Verbindungsschicht
- Migration 20261008180000: NextcloudFilesConfig (Organisation) und NextcloudFilesAccount (Mandant UND Benutzer per Zeilenschutz, nur verschlüsseltes App-Passwort) - Transportschicht ncRequest: feste Pfadanfänge, Segmentcodierung, keine Weiterleitungen, keine Cookies, Zeitgrenzen; Aufrufsperre pausiert den Ursprung nach jedem 429 und sperrt Zugangsschlüssel nach dem ersten 401 - Einstellungen: Adresse speichern/prüfen, Adresswechsel läuft alle Konten ab (mit Bestätigung) - Controller mit Verwalten nur auf Einstellungen, Seed, Modul, Registrierung in Web (Ladefunktion, Symbol folder, Navigation, Layout) und Reiter Einstellungen - RLS-Inventar fortgeschrieben, Testaufbau (tessera-nc-test) und E2E-Skripte Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
-- 261008-mzu — Modul "Nextcloud-Dateien" (Etappe 1).
|
||||
--
|
||||
-- Zweck: zwei Tabellen und zwei Aufzaehlungen.
|
||||
-- * "NextcloudFilesConfig": Einstellungen der Organisation (Singleton), genau
|
||||
-- eine Nextcloud-Adresse (`baseUrl`, Normalform von normalizeCloudUrl).
|
||||
-- * "NextcloudFilesAccount": das persoenliche Konto eines Benutzers in dieser
|
||||
-- Nextcloud. Gespeichert wird NUR das App-Passwort, AES-256-GCM-verschluesselt
|
||||
-- ueber den CryptoService (`encryptedAppPassword`); das echte Passwort des
|
||||
-- Benutzers wird nie abgelegt. `ncUserId` ist die Kennung in der Nextcloud
|
||||
-- (nicht der eingegebene Anmeldename), `connectedVia` merkt sich den Weg
|
||||
-- (Passwort oder Login Flow v2 fuer Zwei-Faktor-Konten).
|
||||
--
|
||||
-- Warum das Konto die Adresse mitfuehrt ("baseUrl"): ein App-Passwort gilt nur
|
||||
-- fuer die Nextcloud, die es ausgestellt hat. Es wird nie an einen anderen Host
|
||||
-- geschickt; wechselt die Adresse der Organisation, laufen alle Konten ab
|
||||
-- (status = EXPIRED, oder Konto-baseUrl weicht von der Einstellung ab) und die
|
||||
-- Benutzer verbinden neu.
|
||||
--
|
||||
-- Der DDL-Teil stammt aus `prisma migrate diff`, damit der Stand ohne Abweichung
|
||||
-- zum Schema passt; die Zeilenschutz-Regeln sind von Hand ergaenzt.
|
||||
--
|
||||
-- Zeilenschutz (Pflicht — sonst schlaegt rls-coverage.spec.ts fehl):
|
||||
-- "NextcloudFilesConfig" — tenant_isolation_policy nur ueber den Mandanten
|
||||
-- (Verwaltungsdaten der Organisation, wie "DomainsConfig").
|
||||
-- "NextcloudFilesAccount" — tenant_isolation_policy ueber Mandant UND Benutzer
|
||||
-- (Form aus 20260929140000_reminder): ohne gesetzten Benutzer gilt nur der
|
||||
-- Mandant, mit Benutzer zusaetzlich "userId". Genau das haelt einen Benutzer
|
||||
-- davon ab, je das Konto eines anderen zu lesen. KEINE system_read_policy:
|
||||
-- es gibt keinen Hintergrunddienst, der ueber alle Mandanten liest.
|
||||
--
|
||||
-- Faellt ein Benutzer weg, faellt sein Konto mit (ON DELETE CASCADE). Das
|
||||
-- App-Passwort bleibt dann in der Nextcloud bestehen (Geraeteliste dort) — das
|
||||
-- ist dokumentiert und fuer Etappe 1 bewusst nicht aufgeraeumt.
|
||||
--
|
||||
-- Rechte fuer die Anwendungsrolle tessera_app: kommen ueber ALTER DEFAULT
|
||||
-- PRIVILEGES aus 20260909130000_rls_app_role automatisch — hier nichts zu tun.
|
||||
--
|
||||
-- WICHTIG: wie alle RLS-Regeln dieses Schemas wirken diese erst, wenn die
|
||||
-- Anwendung als Rolle ohne Umgehungsrecht verbindet (Schalter heute AUS, siehe
|
||||
-- docs/mandantentrennung-datenbankrolle.md). Bis dahin tragen die
|
||||
-- Anwendungspruefungen im Dienst den Schutz allein.
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "NextcloudFilesAccountStatus" AS ENUM ('ACTIVE', 'EXPIRED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "NextcloudFilesConnectMethod" AS ENUM ('PASSWORD', 'LOGIN_FLOW');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "NextcloudFilesConfig" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"baseUrl" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "NextcloudFilesConfig_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "NextcloudFilesAccount" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"baseUrl" TEXT NOT NULL,
|
||||
"ncUserId" TEXT NOT NULL,
|
||||
"ncDisplayName" TEXT,
|
||||
"encryptedAppPassword" TEXT NOT NULL,
|
||||
"status" "NextcloudFilesAccountStatus" NOT NULL DEFAULT 'ACTIVE',
|
||||
"connectedVia" "NextcloudFilesConnectMethod" NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "NextcloudFilesAccount_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "NextcloudFilesConfig_tenantId_key" ON "NextcloudFilesConfig"("tenantId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "NextcloudFilesConfig_tenantId_idx" ON "NextcloudFilesConfig"("tenantId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "NextcloudFilesAccount_tenantId_idx" ON "NextcloudFilesAccount"("tenantId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "NextcloudFilesAccount_tenantId_userId_key" ON "NextcloudFilesAccount"("tenantId", "userId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "NextcloudFilesAccount" ADD CONSTRAINT "NextcloudFilesAccount_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- Zeilenschutz: nur Mandant (Organisations-Einstellung)
|
||||
ALTER TABLE "NextcloudFilesConfig" ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "NextcloudFilesConfig" FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation_policy ON "NextcloudFilesConfig"
|
||||
USING ("tenantId" = current_tenant_id());
|
||||
|
||||
-- Zeilenschutz: Mandant UND Benutzer (Muster 20260929140000_reminder)
|
||||
ALTER TABLE "NextcloudFilesAccount" ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "NextcloudFilesAccount" FORCE ROW LEVEL SECURITY;
|
||||
CREATE POLICY tenant_isolation_policy ON "NextcloudFilesAccount"
|
||||
USING (
|
||||
"tenantId" = current_tenant_id()
|
||||
AND (current_user_id() IS NULL OR "userId" = current_user_id())
|
||||
);
|
||||
@@ -58,6 +58,7 @@ model User {
|
||||
moduleGrants ModuleGrant[]
|
||||
customModules CustomModule[]
|
||||
reminders Reminder[]
|
||||
nextcloudFilesAccounts NextcloudFilesAccount[]
|
||||
nextcloudAlertSubscriptions NextcloudAlertSubscription[]
|
||||
|
||||
@@index([tenantId])
|
||||
@@ -942,6 +943,51 @@ model DomainsOrder {
|
||||
@@index([tenantId])
|
||||
}
|
||||
|
||||
// quick-261008-mzu: Modul "Nextcloud-Dateien". Eine Nextcloud je Organisation
|
||||
// (NextcloudFilesConfig, Singleton), ein eigenes Konto je Benutzer
|
||||
// (NextcloudFilesAccount). Gespeichert wird nur das App-Passwort, AES-verschluesselt
|
||||
// (CryptoService); das echte Passwort wird nie abgelegt. `baseUrl` im Konto ist die
|
||||
// Adresse, fuer die das App-Passwort ausgestellt wurde: es geht nie an einen anderen
|
||||
// Host, und ein Adresswechsel laesst alle Konten ablaufen. Zeilenschutz wie Reminder
|
||||
// (Mandant UND Benutzer), keine Systemleserregel.
|
||||
enum NextcloudFilesAccountStatus {
|
||||
ACTIVE
|
||||
EXPIRED
|
||||
}
|
||||
|
||||
enum NextcloudFilesConnectMethod {
|
||||
PASSWORD
|
||||
LOGIN_FLOW
|
||||
}
|
||||
|
||||
model NextcloudFilesConfig {
|
||||
id String @id @default(uuid())
|
||||
tenantId String @unique
|
||||
baseUrl String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([tenantId])
|
||||
}
|
||||
|
||||
model NextcloudFilesAccount {
|
||||
id String @id @default(uuid())
|
||||
tenantId String
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
baseUrl String
|
||||
ncUserId String
|
||||
ncDisplayName String?
|
||||
encryptedAppPassword String
|
||||
status NextcloudFilesAccountStatus @default(ACTIVE)
|
||||
connectedVia NextcloudFilesConnectMethod
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([tenantId, userId])
|
||||
@@index([tenantId])
|
||||
}
|
||||
|
||||
// Eigene Module (quick-260929-9wc): vom Administrator angelegte Seitenleisten-
|
||||
// Eintraege, die eine externe https-Seite im Rahmen zeigen. Sichtbar fuer alle
|
||||
// Benutzer des Mandanten. Zeilenschutz nach Muster ProxmoxServer (tenantId,
|
||||
|
||||
Reference in New Issue
Block a user