Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5715651fb7 | ||
|
|
73653b5e2d |
No files matched your search
@@ -1,4 +1,4 @@
|
|||||||
# TeamVis — Self-Hosting-Bundle (v0.59.0)
|
# TeamVis — Self-Hosting-Bundle (v0.60.0)
|
||||||
|
|
||||||
Dieses Bundle enthält alles zum **Betreiben** von TeamVis auf eigener
|
Dieses Bundle enthält alles zum **Betreiben** von TeamVis auf eigener
|
||||||
Infrastruktur — **keinen** App-Quellcode. Die App selbst kommt als fertiges
|
Infrastruktur — **keinen** App-Quellcode. Die App selbst kommt als fertiges
|
||||||
@@ -36,4 +36,4 @@ Vollständige Schritt-für-Schritt-Anleitung (von der nackten VM bis live):
|
|||||||
| `supabase/migrations/` | Datenbank-Schema (DDL) |
|
| `supabase/migrations/` | Datenbank-Schema (DDL) |
|
||||||
| `docs/` | Anleitungen |
|
| `docs/` | Anleitungen |
|
||||||
|
|
||||||
Stand: TeamVis 0.59.0.
|
Stand: TeamVis 0.60.0.
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "teamvis-selfhost",
|
"name": "teamvis-selfhost",
|
||||||
"version": "0.59.0",
|
"version": "0.60.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Self-Hosting-Bundle für TeamVis (Installer + Migrationen, ohne App-Quellcode).",
|
"description": "Self-Hosting-Bundle für TeamVis (Installer + Migrationen, ohne App-Quellcode).",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": "0.59.0",
|
"version": "0.60.0",
|
||||||
"released": "2026-10-14",
|
"released": "2026-10-29",
|
||||||
"security": true,
|
"security": true,
|
||||||
"summary": "Neuer KI-Anbieter mit eigener Adresse (OpenAI-kompatibel), etwa für einen Hoster mit Sitz und Verarbeitung in der EU. Zwei KI-Funktionen im Portal folgen jetzt dem Modul „KI“. Eine optionale Migration (0071), keine neuen Pflicht-Variablen."
|
"summary": "Konto löschen aus der App (Apple 5.1.1(v)) und Rechte-Härtung M22. SQL-first: Migrationen 0073 und 0074 vor dem Image-Update einspielen."
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
-- ====================================================================
|
||||||
|
-- 0073_anon_rechte_minimal — anon nur noch dort, wo die Karte liest
|
||||||
|
-- ====================================================================
|
||||||
|
-- Sicherheitsplan M22 (24.10.2026). Supabase vergibt über seine
|
||||||
|
-- Default-Privileges jeder neuen Tabelle volle Rechte an anon (inkl.
|
||||||
|
-- TRUNCATE, das RLS nicht erfasst). RLS hat das bisher abgefangen; jetzt
|
||||||
|
-- werden die Rechte selbst abgebaut. Öffentlich gelesen werden nur
|
||||||
|
-- employees, persons und site_settings — dort gelten weiter die
|
||||||
|
-- Spalten-Grants aus 0014/0045 ff. (ohne Geheimnis-Spalten).
|
||||||
|
--
|
||||||
|
-- Bewusst KEIN "revoke ... on all tables in schema public" und KEIN
|
||||||
|
-- "alter default privileges": Auf gemeinsam genutzten Datenbanken (etwa
|
||||||
|
-- supabase.stwhas.de) liegen fremde Tabellen derselben Eigentümerrolle im
|
||||||
|
-- Schema public; deren Rechte dürfen sich nicht ändern. Deshalb eine
|
||||||
|
-- ausdrückliche Liste der TeamVis-Tabellen. Neue Tabellen nehmen anon die
|
||||||
|
-- Rechte in ihrer eigenen Migration (siehe 0060, 0072 und CLAUDE.md).
|
||||||
|
--
|
||||||
|
-- Ebenfalls entfernt: die Policies admin_write und public_read auf employees (ALL für
|
||||||
|
-- authenticated, true). Sie stammt aus keiner TeamVis-Migration — TeamVis
|
||||||
|
-- nutzt keine Supabase-Auth —, lag aber auf einer Bestandsinstanz und hätte
|
||||||
|
-- jedem Inhaber eines authenticated-JWT Schreibzugriff gegeben.
|
||||||
|
|
||||||
|
do $$
|
||||||
|
declare t text;
|
||||||
|
begin
|
||||||
|
foreach t in array array[
|
||||||
|
'admin_invites',
|
||||||
|
'admin_users',
|
||||||
|
'appointment_requests',
|
||||||
|
'bookings',
|
||||||
|
'calendar_connections',
|
||||||
|
'card_leads',
|
||||||
|
'card_view_log',
|
||||||
|
'change_log',
|
||||||
|
'compliance_role_bindings',
|
||||||
|
'deputies',
|
||||||
|
'external_parties',
|
||||||
|
'job_description_blocks',
|
||||||
|
'job_description_versions',
|
||||||
|
'job_descriptions',
|
||||||
|
'locations',
|
||||||
|
'magic_link_tokens',
|
||||||
|
'org_units',
|
||||||
|
'organigram_versions',
|
||||||
|
'portal_api_tokens',
|
||||||
|
'position_assignments',
|
||||||
|
'positions',
|
||||||
|
'push_preferences',
|
||||||
|
'push_subscriptions',
|
||||||
|
'reception_kiosks',
|
||||||
|
'reception_visit_audit',
|
||||||
|
'service_relations',
|
||||||
|
'teamvis_schema_migrations',
|
||||||
|
'trusted_access_log',
|
||||||
|
'visitor_preregistrations',
|
||||||
|
'wallet_pass_registrations',
|
||||||
|
'webhook_deliveries',
|
||||||
|
'webhook_subscriptions'
|
||||||
|
] loop
|
||||||
|
if to_regclass('public.' || t) is not null then
|
||||||
|
execute format('revoke all on table public.%I from anon', t);
|
||||||
|
end if;
|
||||||
|
end loop;
|
||||||
|
end $$;
|
||||||
|
|
||||||
|
drop policy if exists admin_write on public.employees;
|
||||||
|
|
||||||
|
-- authenticated: dieselben engen Rechte wie anon. TeamVis nutzt keine
|
||||||
|
-- Supabase-Auth; auf gemeinsam genutzten Datenbanken kann es aber Inhaber
|
||||||
|
-- eines authenticated-JWT geben. Übrige Tabellen: keine Rechte; die drei
|
||||||
|
-- Kartentabellen: genau die Lesespalten, die anon hat.
|
||||||
|
do $$
|
||||||
|
declare t text; cols text;
|
||||||
|
begin
|
||||||
|
foreach t in array array[
|
||||||
|
'admin_invites', 'admin_users', 'appointment_requests', 'bookings',
|
||||||
|
'calendar_connections', 'card_leads', 'card_view_log', 'change_log',
|
||||||
|
'compliance_role_bindings', 'deputies', 'external_parties',
|
||||||
|
'job_description_blocks', 'job_description_versions', 'job_descriptions',
|
||||||
|
'locations', 'magic_link_tokens', 'org_units', 'organigram_versions',
|
||||||
|
'portal_api_tokens', 'position_assignments', 'positions',
|
||||||
|
'push_preferences', 'push_subscriptions', 'reception_kiosks',
|
||||||
|
'reception_visit_audit', 'service_relations', 'teamvis_schema_migrations',
|
||||||
|
'trusted_access_log', 'visitor_preregistrations',
|
||||||
|
'wallet_pass_registrations', 'webhook_deliveries', 'webhook_subscriptions'
|
||||||
|
] loop
|
||||||
|
if to_regclass('public.' || t) is not null then
|
||||||
|
execute format('revoke all on table public.%I from authenticated', t);
|
||||||
|
end if;
|
||||||
|
end loop;
|
||||||
|
foreach t in array array['employees', 'persons', 'site_settings'] loop
|
||||||
|
if to_regclass('public.' || t) is null then continue; end if;
|
||||||
|
select string_agg(quote_ident(column_name), ', ' order by column_name) into cols
|
||||||
|
from information_schema.column_privileges
|
||||||
|
where table_schema = 'public' and table_name = t
|
||||||
|
and grantee = 'anon' and privilege_type = 'SELECT';
|
||||||
|
execute format('revoke all on table public.%I from authenticated', t);
|
||||||
|
if cols is not null then
|
||||||
|
execute format('grant select (%s) on public.%I to authenticated', cols, t);
|
||||||
|
end if;
|
||||||
|
end loop;
|
||||||
|
end $$;
|
||||||
|
|
||||||
|
-- Ebenfalls nicht aus TeamVis: Policy public_read (Rolle public, aktive
|
||||||
|
-- Karten). Für anon gilt public_read_active_employees (0001/0014).
|
||||||
|
drop policy if exists public_read on public.employees;
|
||||||
|
|
||||||
|
do $$
|
||||||
|
begin
|
||||||
|
if to_regclass('public.teamvis_schema_migrations') is not null then
|
||||||
|
alter table public.teamvis_schema_migrations enable row level security;
|
||||||
|
revoke all on table public.teamvis_schema_migrations from anon;
|
||||||
|
end if;
|
||||||
|
end $$;
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
-- Konto löschen aus der App (Apple Guideline 5.1.1(v))
|
||||||
|
-- ====================================================================
|
||||||
|
-- Bisher schickte die App nur einen Antrag an die Verwaltung. Apple
|
||||||
|
-- verlangt, dass sich das Konto in der App selbst löschen lässt. Ablauf
|
||||||
|
-- ab jetzt:
|
||||||
|
--
|
||||||
|
-- 1. sofort: App-Zugang weg (Tokens, Login-Codes, Push), Karte
|
||||||
|
-- deaktiviert und öffentlich nicht mehr erreichbar — auch nicht als
|
||||||
|
-- „nicht mehr im Unternehmen"-Seite
|
||||||
|
-- 2. nach `account_deletion_days` (Default 30): Datensatz samt Foto
|
||||||
|
-- endgültig gelöscht, die Person bekommt eine Bestätigung
|
||||||
|
--
|
||||||
|
-- Die Frist dazwischen erlaubt der Verwaltung, versehentliche Anträge
|
||||||
|
-- zurückzunehmen und Kontakte/Termine der Karte zu übernehmen (sie hängen
|
||||||
|
-- per ON DELETE CASCADE an der Karte).
|
||||||
|
|
||||||
|
alter table public.employees
|
||||||
|
add column if not exists deletion_requested_at timestamptz;
|
||||||
|
|
||||||
|
comment on column public.employees.deletion_requested_at is
|
||||||
|
'Zeitpunkt, zu dem die Person ihr Konto in der App gelöscht hat. Gesetzt → Karte offline, endgültige Löschung nach site_settings.account_deletion_days.';
|
||||||
|
|
||||||
|
create index if not exists employees_deletion_requested_at_idx
|
||||||
|
on public.employees (deletion_requested_at)
|
||||||
|
where deletion_requested_at is not null;
|
||||||
|
|
||||||
|
alter table public.site_settings
|
||||||
|
add column if not exists account_deletion_days integer not null default 30;
|
||||||
|
|
||||||
|
comment on column public.site_settings.account_deletion_days is
|
||||||
|
'Tage zwischen Kontolöschung in der App und endgültiger Löschung des Kartendatensatzes.';
|
||||||
|
|
||||||
|
-- Mindestens ein Tag (sonst gibt es kein Zurücknehmen), höchstens ein
|
||||||
|
-- Jahr (die App nennt die Frist der Person; darüber ist sie für einen
|
||||||
|
-- Datensatz ohne weiteren Zweck nicht mehr begründbar).
|
||||||
|
alter table public.site_settings
|
||||||
|
drop constraint if exists site_settings_account_deletion_days_check;
|
||||||
|
alter table public.site_settings
|
||||||
|
add constraint site_settings_account_deletion_days_check
|
||||||
|
check (account_deletion_days between 1 and 365);
|
||||||
|
|
||||||
|
-- Beide Spalten sind nur für den Server bestimmt. Spaltenrechte hat
|
||||||
|
-- 0073 für anon/authenticated explizit vergeben — neue Spalten sind
|
||||||
|
-- davon nicht erfasst; das revoke hält es auch auf Instanzen fest, auf
|
||||||
|
-- denen noch ein Tabellenrecht liegt.
|
||||||
|
do $$
|
||||||
|
begin
|
||||||
|
revoke select (deletion_requested_at) on public.employees from anon, authenticated;
|
||||||
|
revoke select (account_deletion_days) on public.site_settings from anon, authenticated;
|
||||||
|
exception when others then
|
||||||
|
raise notice 'Spaltenrechte nicht angepasst (%).', sqlerrm;
|
||||||
|
end $$;
|
||||||
Reference in new issue
Block a user