Make organization deletion cascade through credentials and immutable audit logs
This commit is contained in:
1 parent
e14a4bfce8
commit
c6e29d7fbe
5 files changed
+154
-1
No files matched your search
@@ -0,0 +1,46 @@
|
||||
// Deleting an organization cascades into its `credentials`, but
|
||||
// `provider_auth_profiles.credential_id` referenced `credentials(id)` with
|
||||
// `on delete restrict`, which blocked the cascade and made the whole DELETE
|
||||
// throw — so deleting an organization silently failed. This rebuilds the
|
||||
// (leaf) table with `on delete cascade`, preserving every existing row and
|
||||
// index. Revocation only deactivates a profile (active = 0) and never deletes
|
||||
// a credential row, so cascade has no effect on normal operation.
|
||||
export const providerAuthProfileCascadeSchema = `
|
||||
create table provider_auth_profiles_rebuild (
|
||||
id text primary key,
|
||||
org_id text not null references organizations(id) on delete cascade,
|
||||
provider_id text not null references providers(id) on delete cascade,
|
||||
credential_id text not null references credentials(id) on delete cascade,
|
||||
provider_key text not null,
|
||||
auth_type text not null,
|
||||
profile_kind text not null,
|
||||
display_hint text not null,
|
||||
source_type text not null,
|
||||
source_ref text,
|
||||
capability_json text not null,
|
||||
active integer not null check(active in (0, 1)),
|
||||
created_at text not null
|
||||
);
|
||||
|
||||
insert into provider_auth_profiles_rebuild(
|
||||
id, org_id, provider_id, credential_id, provider_key, auth_type,
|
||||
profile_kind, display_hint, source_type, source_ref, capability_json, active, created_at
|
||||
)
|
||||
select
|
||||
id, org_id, provider_id, credential_id, provider_key, auth_type,
|
||||
profile_kind, display_hint, source_type, source_ref, capability_json, active, created_at
|
||||
from provider_auth_profiles;
|
||||
|
||||
drop table provider_auth_profiles;
|
||||
alter table provider_auth_profiles_rebuild rename to provider_auth_profiles;
|
||||
|
||||
create index if not exists idx_provider_auth_profiles_org_provider
|
||||
on provider_auth_profiles(org_id, provider_id, created_at);
|
||||
create index if not exists idx_provider_auth_profiles_credential
|
||||
on provider_auth_profiles(credential_id);
|
||||
create unique index if not exists idx_provider_auth_profiles_active
|
||||
on provider_auth_profiles(provider_id) where active = 1;
|
||||
|
||||
insert into schema_migrations(version, name, applied_at)
|
||||
values (38, 'provider_auth_profile_cascade', datetime('now'));
|
||||
`;
|
||||
@@ -0,0 +1,28 @@
|
||||
// The audit_events and social_audit_records immutability triggers raised ABORT
|
||||
// on EVERY delete, including the legitimate cascade when an organization is torn
|
||||
// down — so deleting an organization always rolled back. These rebuilt triggers
|
||||
// still block tampering deletes of individual records (the owning org exists),
|
||||
// but allow the cascade once the organization row itself is gone. Direct
|
||||
// deletes of audit rows whose org still exists remain forbidden.
|
||||
export const auditImmutabilityOrgTeardownSchema = `
|
||||
drop trigger if exists trg_audit_events_no_delete;
|
||||
create trigger trg_audit_events_no_delete
|
||||
before delete on audit_events
|
||||
for each row
|
||||
when exists (select 1 from organizations where id = old.org_id)
|
||||
begin
|
||||
select raise(abort, 'Audit events are immutable.');
|
||||
end;
|
||||
|
||||
drop trigger if exists trg_social_audit_records_no_delete;
|
||||
create trigger trg_social_audit_records_no_delete
|
||||
before delete on social_audit_records
|
||||
for each row
|
||||
when exists (select 1 from organizations where id = old.org_id)
|
||||
begin
|
||||
select raise(abort, 'Social audit records are immutable.');
|
||||
end;
|
||||
|
||||
insert into schema_migrations(version, name, applied_at)
|
||||
values (39, 'audit_immutability_org_teardown', datetime('now'));
|
||||
`;
|
||||
@@ -36,6 +36,8 @@ import { providerAuthSessionsSchema } from "./migrations/034-provider-auth-sessi
|
||||
import { taskRunIsolationSchema } from "./migrations/035-task-run-isolation";
|
||||
import { additionalConnectorsSchema } from "./migrations/036-additional-connectors";
|
||||
import { customProvidersSchema } from "./migrations/037-custom-providers";
|
||||
import { providerAuthProfileCascadeSchema } from "./migrations/038-provider-auth-profile-cascade";
|
||||
import { auditImmutabilityOrgTeardownSchema } from "./migrations/039-audit-immutability-org-teardown";
|
||||
|
||||
interface Migration {
|
||||
version: number;
|
||||
@@ -81,6 +83,8 @@ const MIGRATIONS: Migration[] = [
|
||||
{ version: 35, name: "task_run_isolation", sql: taskRunIsolationSchema },
|
||||
{ version: 36, name: "additional_connectors", sql: additionalConnectorsSchema },
|
||||
{ version: 37, name: "custom_providers", sql: customProvidersSchema },
|
||||
{ version: 38, name: "provider_auth_profile_cascade", sql: providerAuthProfileCascadeSchema },
|
||||
{ version: 39, name: "audit_immutability_org_teardown", sql: auditImmutabilityOrgTeardownSchema },
|
||||
];
|
||||
|
||||
const SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version;
|
||||
|
||||
Reference in new issue
Block a user