mirror of
https://github.com/Since-3/since3-portal.git
synced 2026-10-12 21:41:16 +00:00
101 lines
5.9 KiB
PL/PgSQL
101 lines
5.9 KiB
PL/PgSQL
-- Interne Zugänge von Since3 (eigene Hosting-, Tool- und Domain-Accounts). Nur für Admins.
|
|
-- Gleiches Prinzip wie bei den Kunden-Passwörtern (credentials): In der Tabelle steht nie das Passwort,
|
|
-- nur die Vault-Referenz. Schreiben und Klartext nur über die Funktionen unten, jeder Abruf wird protokolliert.
|
|
|
|
create table public.internal_credentials (
|
|
id uuid primary key default gen_random_uuid(),
|
|
service text not null, -- Schlüssel aus SERVICES (services-data.ts)
|
|
title text not null default '', -- z. B. "Since3 Team"; leer = Name des Dienstes
|
|
username text not null,
|
|
url text not null default '', -- Login-Adresse (optional)
|
|
plan text not null default 'free', -- 'free' | 'paid'
|
|
price numeric(10,2), -- Euro, nur bei 'paid'
|
|
billing_cycle text, -- 'monthly' | 'yearly' | 'once', nur bei 'paid'
|
|
vault_secret_id uuid not null, -- -> vault.secrets
|
|
created_by uuid references public.profiles(id) on delete set null default auth.uid(),
|
|
updated_by uuid references public.profiles(id) on delete set null,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now(),
|
|
-- Kostenlos = weder Preis noch Abrechnung; bezahlt = beides
|
|
constraint internal_credentials_cost_check check (
|
|
(plan = 'free' and price is null and billing_cycle is null)
|
|
or (plan = 'paid' and price is not null and price >= 0 and billing_cycle in ('monthly', 'yearly', 'once'))
|
|
)
|
|
);
|
|
|
|
create trigger set_updated_at before update on public.internal_credentials
|
|
for each row execute function public.set_updated_at();
|
|
|
|
-- Vault-Eintrag beim Löschen mit aufräumen (gleiche Funktion wie bei credentials)
|
|
create trigger internal_credentials_delete_vault_secret
|
|
after delete on public.internal_credentials
|
|
for each row execute function public.delete_vault_secret();
|
|
|
|
-- Lesen (ohne Passwort) und Löschen nur Admins; Anlegen/Ändern nur über die Funktionen
|
|
alter table public.internal_credentials enable row level security;
|
|
create policy admin_select on public.internal_credentials for select to authenticated
|
|
using ((select public.is_admin()));
|
|
create policy admin_delete on public.internal_credentials for delete to authenticated
|
|
using ((select public.is_admin()));
|
|
|
|
create or replace function public.create_internal_credential(
|
|
p_service text, p_title text, p_username text, p_url text,
|
|
p_plan text, p_price numeric, p_billing_cycle text, p_password text
|
|
) returns uuid language plpgsql security definer set search_path = '' as $$
|
|
declare v_secret uuid; v_id uuid;
|
|
begin
|
|
if not public.is_admin() then raise exception 'forbidden' using errcode = '42501'; end if;
|
|
v_secret := vault.create_secret(p_password, 'internal_' || gen_random_uuid()::text, p_service);
|
|
insert into public.internal_credentials
|
|
(service, title, username, url, plan, price, billing_cycle, vault_secret_id, updated_by)
|
|
values
|
|
(p_service, coalesce(p_title, ''), p_username, coalesce(p_url, ''), p_plan,
|
|
case when p_plan = 'paid' then p_price end,
|
|
case when p_plan = 'paid' then p_billing_cycle end,
|
|
v_secret, auth.uid())
|
|
returning id into v_id;
|
|
return v_id;
|
|
end $$;
|
|
|
|
-- p_password = NULL oder leer -> Passwort bleibt unverändert
|
|
create or replace function public.update_internal_credential(
|
|
p_id uuid, p_service text, p_title text, p_username text, p_url text,
|
|
p_plan text, p_price numeric, p_billing_cycle text, p_password text default null
|
|
) returns void language plpgsql security definer set search_path = '' as $$
|
|
declare v_secret uuid;
|
|
begin
|
|
if not public.is_admin() then raise exception 'forbidden' using errcode = '42501'; end if;
|
|
update public.internal_credentials
|
|
set service = p_service, title = coalesce(p_title, ''), username = p_username,
|
|
url = coalesce(p_url, ''), plan = p_plan,
|
|
price = case when p_plan = 'paid' then p_price end,
|
|
billing_cycle = case when p_plan = 'paid' then p_billing_cycle end,
|
|
updated_by = auth.uid()
|
|
where id = p_id
|
|
returning vault_secret_id into v_secret;
|
|
if not found then raise exception 'internal credential not found'; end if;
|
|
if p_password is not null and p_password <> '' then
|
|
perform vault.update_secret(v_secret, p_password);
|
|
end if;
|
|
end $$;
|
|
|
|
-- Klartext nur für Admins; landet im selben Zugriffsprotokoll wie die Kunden-Passwörter
|
|
create or replace function public.reveal_internal_credential(p_id uuid)
|
|
returns text language plpgsql security definer set search_path = '' as $$
|
|
declare v_secret uuid; v_pw text;
|
|
begin
|
|
if not public.is_admin() then raise exception 'forbidden' using errcode = '42501'; end if;
|
|
select vault_secret_id into v_secret from public.internal_credentials where id = p_id;
|
|
if not found then raise exception 'internal credential not found'; end if;
|
|
select decrypted_secret into v_pw from vault.decrypted_secrets where id = v_secret;
|
|
insert into public.credential_access_log (credential_id, user_id, action)
|
|
values (p_id, auth.uid(), 'reveal_internal');
|
|
return v_pw;
|
|
end $$;
|
|
|
|
revoke execute on function public.create_internal_credential(text, text, text, text, text, numeric, text, text) from public, anon;
|
|
revoke execute on function public.update_internal_credential(uuid, text, text, text, text, text, numeric, text, text) from public, anon;
|
|
revoke execute on function public.reveal_internal_credential(uuid) from public, anon;
|
|
grant execute on function public.create_internal_credential(text, text, text, text, text, numeric, text, text) to authenticated;
|
|
grant execute on function public.update_internal_credential(uuid, text, text, text, text, text, numeric, text, text) to authenticated;
|
|
grant execute on function public.reveal_internal_credential(uuid) to authenticated;
|