feat: supabase vault (#1605)

# Description

https://github.com/StanGirard/quivr/issues/1551

## Checklist before requesting a review

Please delete options that are not relevant.

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented hard-to-understand areas
- [ ] I have ideally added tests that prove my fix is effective or that
my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged

## Screenshots (if appropriate):
This commit is contained in:
Zineb El Bachiri 2023-11-07 16:57:25 +01:00 committed by GitHub
parent dd2bc1f6fc
commit ed5de8b80c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 216 additions and 6 deletions

View File

@ -0,0 +1,3 @@
from .create_secret import create_secret
from .delete_secret import delete_secret
from .read_secret import read_secret

View File

@ -0,0 +1,21 @@
from uuid import UUID
from models import get_supabase_client
from utils import build_secret_unique_name
def create_secret(
user_id: UUID, brain_id: UUID, secret_name: str, secret_value
) -> UUID | None:
supabase_client = get_supabase_client()
response = supabase_client.rpc(
"insert_secret",
{
"name": build_secret_unique_name(
user_id=user_id, brain_id=brain_id, secret_name=secret_name
),
"secret": secret_value,
},
).execute()
return response.data

View File

@ -0,0 +1,18 @@
from uuid import UUID
from models import get_supabase_client
from utils import build_secret_unique_name
def delete_secret(user_id: UUID, brain_id: UUID, secret_name: str) -> bool:
supabase_client = get_supabase_client()
response = supabase_client.rpc(
"delete_secret",
{
"name": build_secret_unique_name(
user_id=user_id, brain_id=brain_id, secret_name=secret_name
),
},
).execute()
return response.data

View File

@ -0,0 +1,20 @@
from uuid import UUID
from models import get_supabase_client
from utils import build_secret_unique_name
def read_secret(
user_id: UUID, brain_id: UUID, secret_name: str, secret_value
) -> UUID | None:
supabase_client = get_supabase_client()
response = supabase_client.rpc(
"read_secret",
{
"secret_name": build_secret_unique_name(
user_id=user_id, brain_id=brain_id, secret_name=secret_name
),
},
).execute()
return response.data

View File

@ -0,0 +1,5 @@
from uuid import UUID
def build_secret_unique_name(user_id: UUID, brain_id: UUID, secret_name: str):
return f"{user_id}-{brain_id}-{secret_name}"

View File

@ -0,0 +1,53 @@
CREATE OR REPLACE FUNCTION insert_secret(name text, secret text)
returns uuid
language plpgsql
security definer
set search_path = public
as $$
begin
return vault.create_secret(secret, name);
end;
$$;
create or replace function read_secret(secret_name text)
returns text
language plpgsql
security definer set search_path = public
as $$
declare
secret text;
begin
select decrypted_secret from vault.decrypted_secrets where name =
secret_name into secret;
return secret;
end;
$$;
create or replace function delete_secret(secret_name text)
returns text
language plpgsql
security definer set search_path = public
as $$
declare
deleted_rows int;
begin
delete from vault.decrypted_secrets where name = secret_name;
get diagnostics deleted_rows = row_count;
if deleted_rows = 0 then
return false;
else
return true;
end if;
end;
$$;
-- Insert a migration record if it doesn't exist
INSERT INTO migrations (name)
SELECT '20231107104700_setup_vault'
WHERE NOT EXISTS (
SELECT 1 FROM migrations WHERE name = '20231107104700_setup_vault'
);
-- Commit the changes
COMMIT;

View File

@ -167,13 +167,59 @@ CREATE TABLE IF NOT EXISTS brain_subscription_invitations (
FOREIGN KEY (brain_id) REFERENCES brains (brain_id) FOREIGN KEY (brain_id) REFERENCES brains (brain_id)
); );
-- Create functions for secrets in vault
CREATE OR REPLACE FUNCTION insert_secret(name text, secret text)
returns uuid
language plpgsql
security definer
set search_path = public
as $$
begin
return vault.create_secret(secret, name);
end;
$$;
create or replace function read_secret(secret_name text)
returns text
language plpgsql
security definer set search_path = public
as $$
declare
secret text;
begin
select decrypted_secret from vault.decrypted_secrets where name =
secret_name into secret;
return secret;
end;
$$;
create or replace function delete_secret(secret_name text)
returns text
language plpgsql
security definer set search_path = public
as $$
declare
deleted_rows int;
begin
delete from vault.decrypted_secrets where name = secret_name;
get diagnostics deleted_rows = row_count;
if deleted_rows = 0 then
return false;
else
return true;
end if;
end;
$$;
CREATE TABLE IF NOT EXISTS migrations ( CREATE TABLE IF NOT EXISTS migrations (
name VARCHAR(255) PRIMARY KEY, name VARCHAR(255) PRIMARY KEY,
executed_at TIMESTAMPTZ DEFAULT current_timestamp executed_at TIMESTAMPTZ DEFAULT current_timestamp
); );
INSERT INTO migrations (name) INSERT INTO migrations (name)
SELECT '202307111517031_change_vectors_id_type' SELECT '20231107104700_setup_vault'
WHERE NOT EXISTS ( WHERE NOT EXISTS (
SELECT 1 FROM migrations WHERE name = '202307111517031_change_vectors_id_type' SELECT 1 FROM migrations WHERE name = '20231107104700_setup_vault'
); );

View File

@ -398,10 +398,54 @@ CREATE POLICY "Access Quivr Storage 1jccrwz_2" ON storage.objects FOR UPDATE TO
CREATE POLICY "Access Quivr Storage 1jccrwz_3" ON storage.objects FOR DELETE TO anon USING (bucket_id = 'quivr'); CREATE POLICY "Access Quivr Storage 1jccrwz_3" ON storage.objects FOR DELETE TO anon USING (bucket_id = 'quivr');
-- Create functions for secrets in vault
CREATE OR REPLACE FUNCTION insert_secret(name text, secret text)
returns uuid
language plpgsql
security definer
set search_path = public
as $$
begin
return vault.create_secret(secret, name);
end;
$$;
create or replace function read_secret(secret_name text)
returns text
language plpgsql
security definer set search_path = public
as $$
declare
secret text;
begin
select decrypted_secret from vault.decrypted_secrets where name =
secret_name into secret;
return secret;
end;
$$;
create or replace function delete_secret(secret_name text)
returns text
language plpgsql
security definer set search_path = public
as $$
declare
deleted_rows int;
begin
delete from vault.decrypted_secrets where name = secret_name;
get diagnostics deleted_rows = row_count;
if deleted_rows = 0 then
return false;
else
return true;
end if;
end;
$$;
INSERT INTO migrations (name) INSERT INTO migrations (name)
SELECT '20231106110000_add_field_brain_type_to_brain_table' SELECT '20231107104700_setup_vault'
WHERE NOT EXISTS ( WHERE NOT EXISTS (
SELECT 1 FROM migrations WHERE name = '20231106110000_add_field_brain_type_to_brain_table' SELECT 1 FROM migrations WHERE name = '20231107104700_setup_vault'
); );