From c392f519880dd2fe46c7d81aa3fd43a193b736a2 Mon Sep 17 00:00:00 2001 From: devmc-ee Date: Mon, 9 Jun 2025 13:08:23 +0300 Subject: [PATCH] B2B-88: remove supabase copy --- README.md | 1 - supabase copy/.gitignore | 4 - supabase copy/config.toml | 108 - .../migrations/20221215192558_schema.sql | 2759 ----------------- .../migrations/20240319163440_roles-seed.sql | 40 - .../20241007151024_delete-team-account.sql | 7 - .../20250301095452_one-time-tokens.sql | 346 --- .../20250302043537_mfa-rls-super-admin.sql | 206 -- .../20250304104340_set-otp-search-path.sql | 203 -- supabase copy/schemas/00-privileges.sql | 74 - supabase copy/schemas/01-enums.sql | 65 - supabase copy/schemas/02-config.sql | 145 - supabase copy/schemas/03-accounts.sql | 560 ---- supabase copy/schemas/04-roles.sql | 30 - supabase copy/schemas/05-memberships.sql | 312 -- .../schemas/06-roles-permissions.sql | 237 -- supabase copy/schemas/07-invitations.sql | 354 --- .../schemas/08-billing-customers.sql | 66 - supabase copy/schemas/09-subscriptions.sql | 366 --- supabase copy/schemas/10-orders.sql | 280 -- supabase copy/schemas/11-notifications.sql | 114 - supabase copy/schemas/12-one-time-tokens.sql | 349 --- supabase copy/schemas/13-mfa.sql | 145 - supabase copy/schemas/14-super-admin.sql | 73 - supabase copy/schemas/15-account-views.sql | 126 - supabase copy/schemas/16-storage.sql | 50 - supabase copy/schemas/17-roles-seed.sql | 47 - supabase copy/seed.sql | 315 -- .../templates/change-email-address.html | 8 - supabase copy/templates/confirm-email.html | 8 - supabase copy/templates/invite-user.html | 8 - supabase copy/templates/magic-link.html | 8 - supabase copy/templates/reset-password.html | 8 - supabase copy/tests/database/00000-dbdev.sql | 74 - .../tests/database/00000-makerkit-helpers.sql | 151 - .../database/account-permissions.test.sql | 104 - .../tests/database/account-slug.test.sql | 128 - .../tests/database/delete-membership.test.sql | 94 - .../tests/database/invitations.test.sql | 111 - .../tests/database/memberships.test.sql | 92 - .../tests/database/notifications.test.sql | 77 - supabase copy/tests/database/otp.test.sql | 1112 ------- .../tests/database/personal-accounts.test.sql | 57 - .../database/personal-billing-orders.test.sql | 95 - .../personal-billing-subscriptions.test.sql | 196 -- .../tests/database/schema-conditions.test.sql | 57 - supabase copy/tests/database/schema.test.sql | 52 - supabase copy/tests/database/storage.test.sql | 122 - .../database/super-admin-edge-cases.test.sql | 84 - .../tests/database/super-admin.test.sql | 210 -- .../tests/database/team-accounts.test.sql | 775 ----- .../database/team-billing-orders.test.sql | 113 - .../team-billing-subscriptions.test.sql | 196 -- .../database/transfer-ownership.test.sql | 73 - .../tests/database/update-membership.test.sql | 27 - 55 files changed, 11422 deletions(-) delete mode 100644 supabase copy/.gitignore delete mode 100644 supabase copy/config.toml delete mode 100644 supabase copy/migrations/20221215192558_schema.sql delete mode 100644 supabase copy/migrations/20240319163440_roles-seed.sql delete mode 100644 supabase copy/migrations/20241007151024_delete-team-account.sql delete mode 100644 supabase copy/migrations/20250301095452_one-time-tokens.sql delete mode 100644 supabase copy/migrations/20250302043537_mfa-rls-super-admin.sql delete mode 100644 supabase copy/migrations/20250304104340_set-otp-search-path.sql delete mode 100644 supabase copy/schemas/00-privileges.sql delete mode 100644 supabase copy/schemas/01-enums.sql delete mode 100644 supabase copy/schemas/02-config.sql delete mode 100644 supabase copy/schemas/03-accounts.sql delete mode 100644 supabase copy/schemas/04-roles.sql delete mode 100644 supabase copy/schemas/05-memberships.sql delete mode 100644 supabase copy/schemas/06-roles-permissions.sql delete mode 100644 supabase copy/schemas/07-invitations.sql delete mode 100644 supabase copy/schemas/08-billing-customers.sql delete mode 100644 supabase copy/schemas/09-subscriptions.sql delete mode 100644 supabase copy/schemas/10-orders.sql delete mode 100644 supabase copy/schemas/11-notifications.sql delete mode 100644 supabase copy/schemas/12-one-time-tokens.sql delete mode 100644 supabase copy/schemas/13-mfa.sql delete mode 100644 supabase copy/schemas/14-super-admin.sql delete mode 100644 supabase copy/schemas/15-account-views.sql delete mode 100644 supabase copy/schemas/16-storage.sql delete mode 100644 supabase copy/schemas/17-roles-seed.sql delete mode 100644 supabase copy/seed.sql delete mode 100644 supabase copy/templates/change-email-address.html delete mode 100644 supabase copy/templates/confirm-email.html delete mode 100644 supabase copy/templates/invite-user.html delete mode 100644 supabase copy/templates/magic-link.html delete mode 100644 supabase copy/templates/reset-password.html delete mode 100644 supabase copy/tests/database/00000-dbdev.sql delete mode 100644 supabase copy/tests/database/00000-makerkit-helpers.sql delete mode 100644 supabase copy/tests/database/account-permissions.test.sql delete mode 100644 supabase copy/tests/database/account-slug.test.sql delete mode 100644 supabase copy/tests/database/delete-membership.test.sql delete mode 100644 supabase copy/tests/database/invitations.test.sql delete mode 100644 supabase copy/tests/database/memberships.test.sql delete mode 100644 supabase copy/tests/database/notifications.test.sql delete mode 100644 supabase copy/tests/database/otp.test.sql delete mode 100644 supabase copy/tests/database/personal-accounts.test.sql delete mode 100644 supabase copy/tests/database/personal-billing-orders.test.sql delete mode 100644 supabase copy/tests/database/personal-billing-subscriptions.test.sql delete mode 100644 supabase copy/tests/database/schema-conditions.test.sql delete mode 100644 supabase copy/tests/database/schema.test.sql delete mode 100644 supabase copy/tests/database/storage.test.sql delete mode 100644 supabase copy/tests/database/super-admin-edge-cases.test.sql delete mode 100644 supabase copy/tests/database/super-admin.test.sql delete mode 100644 supabase copy/tests/database/team-accounts.test.sql delete mode 100644 supabase copy/tests/database/team-billing-orders.test.sql delete mode 100644 supabase copy/tests/database/team-billing-subscriptions.test.sql delete mode 100644 supabase copy/tests/database/transfer-ownership.test.sql delete mode 100644 supabase copy/tests/database/update-membership.test.sql diff --git a/README.md b/README.md index be33539..a2cea56 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,6 @@ - markdoc.css - Styles for Markdoc Markdown files. - / supabase - primary supabase -/ supabase copy - (temporary) a folder from starter with bunch of settings for database and emailing, that should be migrated to the primary supabase to enable some functionality like mfa, super-admin and etc. After it is done, the folder will be removed / tooling - a workspace package, used for generation packages in node_modules and provides global links for its data. The most important is typescript config / utils diff --git a/supabase copy/.gitignore b/supabase copy/.gitignore deleted file mode 100644 index a3ad880..0000000 --- a/supabase copy/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Supabase -.branches -.temp -.env diff --git a/supabase copy/config.toml b/supabase copy/config.toml deleted file mode 100644 index ad8d49e..0000000 --- a/supabase copy/config.toml +++ /dev/null @@ -1,108 +0,0 @@ -# A string used to distinguish different Supabase projects on the same host. Defaults to the working -# directory name when running `supabase init`. -project_id = "next-supabase-saas-kit-turbo" - -[api] -# Port to use for the API URL. -port = 54321 -# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API -# endpoints. public and storage are always included. -schemas = ["public", "storage", "graphql_public"] -# Extra schemas to add to the search_path of every request. public is always included. -extra_search_path = ["public", "extensions"] -# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size -# for accidental or malicious requests. -max_rows = 1000 - -[db] -# Port to use for the local database URL. -port = 54322 -# The database major version to use. This has to be the same as your remote database's. Run `SHOW -# server_version;` on the remote database to check. -major_version = 15 - -[studio] -# Port to use for Supabase Studio. -port = 54323 - -# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they -# are monitored, and you can view the emails that would have been sent from the web interface. -[inbucket] -# Port to use for the email testing server web interface. -port = 54324 -smtp_port = 54325 -pop3_port = 54326 - -[storage] -# The maximum file size allowed (e.g. "5MB", "500KB"). -file_size_limit = "50MiB" - -[auth] -# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used -# in emails. -site_url = "http://localhost:3000" -# A list of *exact* URLs that auth providers are permitted to redirect to post authentication. -additional_redirect_urls = ["http://localhost:3000", "http://localhost:3000/auth/callback", "http://localhost:3000/update-password"] -# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one -# week). -jwt_expiry = 3600 -# Allow/disallow new user signups to your project. -enable_signup = true - -# Enable TOTP MFA -[auth.mfa.totp] -verify_enabled = true -enroll_enabled = true - -[auth.email] -# Allow/disallow new user signups via email to your project. -enable_signup = true -# If enabled, a user will be required to confirm any email change on both the old, and new email -# addresses. If disabled, only the new email is required to confirm. -double_confirm_changes = true -# If enabled, users need to confirm their email address before signing in. -enable_confirmations = true - -# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, -# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin`, `notion`, `twitch`, -# `twitter`, `slack`, `spotify`, `workos`, `zoom`. -[auth.external.apple] -enabled = false -client_id = "" -secret = "" -# Overrides the default auth redirectUrl. -redirect_uri = "" -# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, -# or any other third-party OIDC providers. -url = "" - -[auth.rate_limit] -email_sent = 1000 - -[auth.email.template.invite] -subject = "You are invited to Makerkit" -content_path = "./supabase/templates/invite-user.html" - -[auth.email.template.confirmation] -subject = "Confirm your email" -content_path = "./supabase/templates/confirm-email.html" - -[auth.email.template.recovery] -subject = "Reset your password" -content_path = "./supabase/templates/reset-password.html" - -[auth.email.template.email_change] -subject = "Confirm your email change" -content_path = "./supabase/templates/change-email-address.html" - -[auth.email.template.magic_link] -subject = "Sign in to Makerkit" -content_path = "./supabase/templates/magic-link.html" - -[analytics] -enabled = false - -[db.migrations] -schema_paths = [ - "./schemas/*.sql", -] \ No newline at end of file diff --git a/supabase copy/migrations/20221215192558_schema.sql b/supabase copy/migrations/20221215192558_schema.sql deleted file mode 100644 index 8c7cf67..0000000 --- a/supabase copy/migrations/20221215192558_schema.sql +++ /dev/null @@ -1,2759 +0,0 @@ -/* - * ------------------------------------------------------- - * Supabase SaaS Starter Kit Schema - * This is the schema for the Supabase SaaS Starter Kit. - * It includes the schema for accounts, account roles, role permissions, memberships, invitations, subscriptions, and more. - * ------------------------------------------------------- - */ -/* - * ------------------------------------------------------- - * Section: Revoke default privileges from public schema - * We will revoke all default privileges from public schema on functions to prevent public access to them - * ------------------------------------------------------- - */ --- Create a private Makerkit schema -create schema if not exists kit; - -create extension if not exists "unaccent" schema kit; - --- We remove all default privileges from public schema on functions to --- prevent public access to them -alter default privileges -revoke -execute on functions -from - public; - -revoke all on schema public -from - public; - -revoke all PRIVILEGES on database "postgres" -from - "anon"; - -revoke all PRIVILEGES on schema "public" -from - "anon"; - -revoke all PRIVILEGES on schema "storage" -from - "anon"; - -revoke all PRIVILEGES on all SEQUENCES in schema "public" -from - "anon"; - -revoke all PRIVILEGES on all SEQUENCES in schema "storage" -from - "anon"; - -revoke all PRIVILEGES on all FUNCTIONS in schema "public" -from - "anon"; - -revoke all PRIVILEGES on all FUNCTIONS in schema "storage" -from - "anon"; - -revoke all PRIVILEGES on all TABLES in schema "public" -from - "anon"; - -revoke all PRIVILEGES on all TABLES in schema "storage" -from - "anon"; - --- We remove all default privileges from public schema on functions to --- prevent public access to them by default -alter default privileges in schema public -revoke -execute on functions -from - anon, - authenticated; - --- we allow the authenticated role to execute functions in the public schema -grant usage on schema public to authenticated; - --- we allow the service_role role to execute functions in the public schema -grant usage on schema public to service_role; - -/* - * ------------------------------------------------------- - * Section: Enums - * We create the enums for the schema - * ------------------------------------------------------- - */ -/* -* Permissions -- We create the permissions for the Supabase MakerKit. These permissions are used to manage the permissions for the roles -- The permissions are 'roles.manage', 'billing.manage', 'settings.manage', 'members.manage', and 'invites.manage'. -- You can add more permissions as needed. -*/ -create type public.app_permissions as enum( - 'roles.manage', - 'billing.manage', - 'settings.manage', - 'members.manage', - 'invites.manage' -); - -/* -* Subscription Status -- We create the subscription status for the Supabase MakerKit. These statuses are used to manage the status of the subscriptions -- The statuses are 'active', 'trialing', 'past_due', 'canceled', 'unpaid', 'incomplete', 'incomplete_expired', and 'paused'. -- You can add more statuses as needed. -*/ -create type public.subscription_status as ENUM( - 'active', - 'trialing', - 'past_due', - 'canceled', - 'unpaid', - 'incomplete', - 'incomplete_expired', - 'paused' -); - -/* -Payment Status -- We create the payment status for the Supabase MakerKit. These statuses are used to manage the status of the payments -*/ -create type public.payment_status as ENUM('pending', 'succeeded', 'failed'); - -/* -* Billing Provider -- We create the billing provider for the Supabase MakerKit. These providers are used to manage the billing provider for the accounts -- The providers are 'stripe', 'lemon-squeezy', and 'paddle'. -- You can add more providers as needed. -*/ -create type public.billing_provider as ENUM('stripe', 'lemon-squeezy', 'paddle'); - -/* -* Subscription Item Type -- We create the subscription item type for the Supabase MakerKit. These types are used to manage the type of the subscription items -- The types are 'flat', 'per_seat', and 'metered'. -- You can add more types as needed. -*/ -create type public.subscription_item_type as ENUM('flat', 'per_seat', 'metered'); - -/* -* Invitation Type -- We create the invitation type for the Supabase MakerKit. These types are used to manage the type of the invitation -*/ -create type public.invitation as (email text, role varchar(50)); - -/* - * ------------------------------------------------------- - * Section: App Configuration - * We create the configuration for the Supabase MakerKit to enable or disable features - * ------------------------------------------------------- - */ -create table if not exists - public.config ( - enable_team_accounts boolean default true not null, - enable_account_billing boolean default true not null, - enable_team_account_billing boolean default true not null, - billing_provider public.billing_provider default 'stripe' not null - ); - -comment on table public.config is 'Configuration for the Supabase MakerKit.'; - -comment on column public.config.enable_team_accounts is 'Enable team accounts'; - -comment on column public.config.enable_account_billing is 'Enable billing for individual accounts'; - -comment on column public.config.enable_team_account_billing is 'Enable billing for team accounts'; - -comment on column public.config.billing_provider is 'The billing provider to use'; - --- RLS(config) -alter table public.config enable row level security; - --- create config row -insert into - public.config ( - enable_team_accounts, - enable_account_billing, - enable_team_account_billing - ) -values - (true, true, true); - --- Revoke all on accounts table from authenticated and service_role -revoke all on public.config -from - authenticated, - service_role; - --- Open up access to config table for authenticated users and service_role -grant -select - on public.config to authenticated, - service_role; - --- RLS --- SELECT(config): --- Authenticated users can read the config -create policy "public config can be read by authenticated users" on public.config for -select - to authenticated using (true); - --- Function to get the config settings -create -or replace function public.get_config () returns json -set - search_path = '' as $$ -declare - result record; -begin - select - * - from - public.config - limit 1 into result; - - return row_to_json(result); - -end; - -$$ language plpgsql; - --- Automatically set timestamps on tables when a row is inserted or updated -create -or replace function public.trigger_set_timestamps () returns trigger -set - search_path = '' as $$ -begin - if TG_OP = 'INSERT' then - new.created_at = now(); - - new.updated_at = now(); - - else - new.updated_at = now(); - - new.created_at = old.created_at; - - end if; - - return NEW; - -end -$$ language plpgsql; - --- Automatically set user tracking on tables when a row is inserted or updated -create -or replace function public.trigger_set_user_tracking () returns trigger -set - search_path = '' as $$ -begin - if TG_OP = 'INSERT' then - new.created_by = auth.uid(); - new.updated_by = auth.uid(); - - else - new.updated_by = auth.uid(); - - new.created_by = old.created_by; - - end if; - - return NEW; - -end -$$ language plpgsql; - -grant -execute on function public.get_config () to authenticated, -service_role; - --- Function "public.is_set" --- Check if a field is set in the config -create -or replace function public.is_set (field_name text) returns boolean -set - search_path = '' as $$ -declare - result boolean; -begin - execute format('select %I from public.config limit 1', field_name) into result; - - return result; - -end; - -$$ language plpgsql; - -grant -execute on function public.is_set (text) to authenticated; - -/* - * ------------------------------------------------------- - * Section: Accounts - * We create the schema for the accounts. Accounts are the top level entity in the Supabase MakerKit. They can be team or personal accounts. - * ------------------------------------------------------- - */ --- Accounts table -create table if not exists - public.accounts ( - id uuid unique not null default extensions.uuid_generate_v4 (), - primary_owner_user_id uuid references auth.users on delete cascade not null default auth.uid (), - name varchar(255) not null, - slug text unique, - email varchar(320) unique, - is_personal_account boolean default false not null, - updated_at timestamp with time zone, - created_at timestamp with time zone, - created_by uuid references auth.users, - updated_by uuid references auth.users, - picture_url varchar(1000), - public_data jsonb default '{}'::jsonb not null, - primary key (id) - ); - -comment on table public.accounts is 'Accounts are the top level entity in the Supabase MakerKit. They can be team or personal accounts.'; - -comment on column public.accounts.is_personal_account is 'Whether the account is a personal account or not'; - -comment on column public.accounts.name is 'The name of the account'; - -comment on column public.accounts.slug is 'The slug of the account'; - -comment on column public.accounts.primary_owner_user_id is 'The primary owner of the account'; - -comment on column public.accounts.email is 'The email of the account. For teams, this is the email of the team (if any)'; - --- Enable RLS on the accounts table -alter table "public"."accounts" enable row level security; - --- Revoke all on accounts table from authenticated and service_role -revoke all on public.accounts -from - authenticated, - service_role; - --- Open up access to accounts -grant -select -, - insert, -update, -delete on table public.accounts to authenticated, -service_role; - --- constraint that conditionally allows nulls on the slug ONLY if --- personal_account is true -alter table public.accounts -add constraint accounts_slug_null_if_personal_account_true check ( - ( - is_personal_account = true - and slug is null - ) - or ( - is_personal_account = false - and slug is not null - ) -); - --- Indexes -create index if not exists ix_accounts_primary_owner_user_id on public.accounts (primary_owner_user_id); - -create index if not exists ix_accounts_is_personal_account on public.accounts (is_personal_account); - --- constraint to ensure that the primary_owner_user_id is unique for personal accounts -create unique index unique_personal_account on public.accounts (primary_owner_user_id) -where - is_personal_account = true; - --- RLS on the accounts table --- UPDATE(accounts): --- Team owners can update their accounts -create policy accounts_self_update on public.accounts -for update - to authenticated using ( - ( - select - auth.uid () - ) = primary_owner_user_id - ) -with - check ( - ( - select - auth.uid () - ) = primary_owner_user_id - ); - --- Function "public.transfer_team_account_ownership" --- Function to transfer the ownership of a team account to another user -create -or replace function public.transfer_team_account_ownership (target_account_id uuid, new_owner_id uuid) returns void -set - search_path = '' as $$ -begin - if current_user not in('service_role') then - raise exception 'You do not have permission to transfer account ownership'; - end if; - - -- verify the user is already a member of the account - if not exists( - select - 1 - from - public.accounts_memberships - where - target_account_id = account_id - and user_id = new_owner_id) then - raise exception 'The new owner must be a member of the account'; - end if; - - -- update the primary owner of the account - update - public.accounts - set - primary_owner_user_id = new_owner_id - where - id = target_account_id - and is_personal_account = false; - - -- update membership assigning it the hierarchy role - update - public.accounts_memberships - set - account_role =( - public.get_upper_system_role()) - where - target_account_id = account_id - and user_id = new_owner_id - and account_role <>( - public.get_upper_system_role()); - -end; - -$$ language plpgsql; - -grant -execute on function public.transfer_team_account_ownership (uuid, uuid) to service_role; - --- Function "public.is_account_owner" --- Function to check if a user is the primary owner of an account -create -or replace function public.is_account_owner (account_id uuid) returns boolean -set - search_path = '' as $$ - select - exists( - select - 1 - from - public.accounts - where - id = is_account_owner.account_id - and primary_owner_user_id = auth.uid()); -$$ language sql; - -grant -execute on function public.is_account_owner (uuid) to authenticated, -service_role; - --- Function "kit.protect_account_fields" --- Function to protect account fields from being updated -create -or replace function kit.protect_account_fields () returns trigger as $$ -begin - if current_user in('authenticated', 'anon') then - if new.id <> old.id or new.is_personal_account <> - old.is_personal_account or new.primary_owner_user_id <> - old.primary_owner_user_id or new.email <> old.email then - raise exception 'You do not have permission to update this field'; - - end if; - - end if; - - return NEW; - -end -$$ language plpgsql -set - search_path = ''; - --- trigger to protect account fields -create trigger protect_account_fields before -update on public.accounts for each row -execute function kit.protect_account_fields (); - --- Function "public.get_upper_system_role" --- Function to get the highest system role for an account -create -or replace function public.get_upper_system_role () returns varchar -set - search_path = '' as $$ -declare - role varchar(50); -begin - select name from public.roles - where hierarchy_level = 1 into role; - - return role; -end; -$$ language plpgsql; - -grant -execute on function public.get_upper_system_role () to service_role; - --- Function "kit.add_current_user_to_new_account" --- Trigger to add the current user to a new account as the primary owner -create -or replace function kit.add_current_user_to_new_account () returns trigger language plpgsql security definer -set - search_path = '' as $$ -begin - if new.primary_owner_user_id = auth.uid() then - insert into public.accounts_memberships( - account_id, - user_id, - account_role) - values( - new.id, - auth.uid(), - public.get_upper_system_role()); - - end if; - - return NEW; - -end; - -$$; - --- trigger the function whenever a new account is created -create trigger "add_current_user_to_new_account" -after insert on public.accounts for each row -when (new.is_personal_account = false) -execute function kit.add_current_user_to_new_account (); - --- create a trigger to update the account email when the primary owner email is updated -create -or replace function kit.handle_update_user_email () returns trigger language plpgsql security definer -set - search_path = '' as $$ -begin - update - public.accounts - set - email = new.email - where - primary_owner_user_id = new.id - and is_personal_account = true; - - return new; - -end; - -$$; - --- trigger the function every time a user email is updated only if the user is the primary owner of the account and --- the account is personal account -create trigger "on_auth_user_updated" -after -update of email on auth.users for each row -execute procedure kit.handle_update_user_email (); - -/* - * ------------------------------------------------------- - * Section: Roles - * We create the schema for the roles. Roles are the roles for an account. For example, an account might have the roles 'owner', 'admin', and 'member'. - * ------------------------------------------------------- - */ --- Roles Table -create table if not exists - public.roles ( - name varchar(50) not null, - hierarchy_level int not null check (hierarchy_level > 0), - primary key (name), - unique (hierarchy_level) - ); - --- Revoke all on roles table from authenticated and service_role -revoke all on public.roles -from - authenticated, - service_role; - --- Open up access to roles table for authenticated users and service_role -grant -select -on table public.roles to authenticated, -service_role; - --- RLS -alter table public.roles enable row level security; - -/* - * ------------------------------------------------------- - * Section: Memberships - * We create the schema for the memberships. Memberships are the memberships for an account. For example, a user might be a member of an account with the role 'owner'. - * ------------------------------------------------------- - */ --- Account Memberships table -create table if not exists - public.accounts_memberships ( - user_id uuid references auth.users on delete cascade not null, - account_id uuid references public.accounts (id) on delete cascade not null, - account_role varchar(50) references public.roles (name) not null, - created_at timestamptz default current_timestamp not null, - updated_at timestamptz default current_timestamp not null, - created_by uuid references auth.users, - updated_by uuid references auth.users, - primary key (user_id, account_id) - ); - -comment on table public.accounts_memberships is 'The memberships for an account'; - -comment on column public.accounts_memberships.account_id is 'The account the membership is for'; - -comment on column public.accounts_memberships.account_role is 'The role for the membership'; - --- Revoke all on accounts_memberships table from authenticated and service_role -revoke all on public.accounts_memberships -from - authenticated, - service_role; - --- Open up access to accounts_memberships table for authenticated users and service_role -grant -select -, - insert, -update, -delete on table public.accounts_memberships to authenticated, -service_role; - --- Indexes on the accounts_memberships table -create index ix_accounts_memberships_account_id on public.accounts_memberships (account_id); - -create index ix_accounts_memberships_user_id on public.accounts_memberships (user_id); - -create index ix_accounts_memberships_account_role on public.accounts_memberships (account_role); - --- Enable RLS on the accounts_memberships table -alter table public.accounts_memberships enable row level security; - --- Function "kit.prevent_account_owner_membership_delete" --- Trigger to prevent a primary owner from being removed from an account -create -or replace function kit.prevent_account_owner_membership_delete () returns trigger -set - search_path = '' as $$ -begin - if exists( - select - 1 - from - public.accounts - where - id = old.account_id - and primary_owner_user_id = old.user_id) then - raise exception 'The primary account owner cannot be removed from the account membership list'; - -end if; - - return old; - -end; - -$$ language plpgsql; - -create -or replace trigger prevent_account_owner_membership_delete_check before delete on public.accounts_memberships for each row -execute function kit.prevent_account_owner_membership_delete (); - --- Function "kit.prevent_memberships_update" --- Trigger to prevent updates to account memberships with the exception of the account_role -create -or replace function kit.prevent_memberships_update () returns trigger -set - search_path = '' as $$ -begin - if new.account_role <> old.account_role then - return new; - end if; - - raise exception 'Only the account_role can be updated'; - -end; $$ language plpgsql; - -create -or replace trigger prevent_memberships_update_check before -update on public.accounts_memberships for each row -execute function kit.prevent_memberships_update (); - --- Function "public.has_role_on_account" --- Function to check if a user has a role on an account -create -or replace function public.has_role_on_account ( - account_id uuid, - account_role varchar(50) default null -) returns boolean language sql security definer -set - search_path = '' as $$ - select - exists( - select - 1 - from - public.accounts_memberships membership - where - membership.user_id = (select auth.uid()) - and membership.account_id = has_role_on_account.account_id - and((membership.account_role = has_role_on_account.account_role - or has_role_on_account.account_role is null))); -$$; - -grant -execute on function public.has_role_on_account (uuid, varchar) to authenticated; - --- Function "public.is_team_member" --- Check if a user is a team member of an account or not -create -or replace function public.is_team_member (account_id uuid, user_id uuid) returns boolean language sql security definer -set - search_path = '' as $$ - select - exists( - select - 1 - from - public.accounts_memberships membership - where - public.has_role_on_account(account_id) - and membership.user_id = is_team_member.user_id - and membership.account_id = is_team_member.account_id); -$$; - -grant -execute on function public.is_team_member (uuid, uuid) to authenticated, -service_role; - --- RLS --- SELECT(roles) --- authenticated users can query roles -create policy roles_read on public.roles for -select - to authenticated using ( - true - ); - --- Function "public.can_action_account_member" --- Check if a user can perform management actions on an account member -create -or replace function public.can_action_account_member (target_team_account_id uuid, target_user_id uuid) returns boolean -set - search_path = '' as $$ -declare - permission_granted boolean; - target_user_hierarchy_level int; - current_user_hierarchy_level int; - is_account_owner boolean; - target_user_role varchar(50); -begin - if target_user_id = auth.uid() then - raise exception 'You cannot update your own account membership with this function'; - end if; - - -- an account owner can action any member of the account - if public.is_account_owner(target_team_account_id) then - return true; - end if; - - -- check the target user is the primary owner of the account - select - exists ( - select - 1 - from - public.accounts - where - id = target_team_account_id - and primary_owner_user_id = target_user_id) into is_account_owner; - - if is_account_owner then - raise exception 'The primary account owner cannot be actioned'; - end if; - - -- validate the auth user has the required permission on the account - -- to manage members of the account - select - public.has_permission(auth.uid(), target_team_account_id, - 'members.manage'::public.app_permissions) into - permission_granted; - - -- if the user does not have the required permission, raise an exception - if not permission_granted then - raise exception 'You do not have permission to action a member from this account'; - end if; - - -- get the role of the target user - select - am.account_role, - r.hierarchy_level - from - public.accounts_memberships as am - join - public.roles as r on am.account_role = r.name - where - am.account_id = target_team_account_id - and am.user_id = target_user_id - into target_user_role, target_user_hierarchy_level; - - -- get the hierarchy level of the current user - select - r.hierarchy_level into current_user_hierarchy_level - from - public.roles as r - join - public.accounts_memberships as am on r.name = am.account_role - where - am.account_id = target_team_account_id - and am.user_id = auth.uid(); - - if target_user_role is null then - raise exception 'The target user does not have a role on the account'; - end if; - - if current_user_hierarchy_level is null then - raise exception 'The current user does not have a role on the account'; - end if; - - -- check the current user has a higher role than the target user - if current_user_hierarchy_level >= target_user_hierarchy_level then - raise exception 'You do not have permission to action a member from this account'; - end if; - - return true; - -end; - -$$ language plpgsql; - -grant -execute on function public.can_action_account_member (uuid, uuid) to authenticated, -service_role; - --- RLS --- SELECT(accounts_memberships): --- Users can read their team members account memberships -create policy accounts_memberships_read on public.accounts_memberships for -select - to authenticated using ( - ( - ( - select - auth.uid () - ) = user_id - ) - or is_team_member (account_id, user_id) - ); - -create -or replace function public.is_account_team_member (target_account_id uuid) returns boolean -set - search_path = '' as $$ - select exists( - select 1 - from public.accounts_memberships as membership - where public.is_team_member (membership.account_id, target_account_id) - ); -$$ language sql; - -grant -execute on function public.is_account_team_member (uuid) to authenticated, -service_role; - --- RLS on the accounts table --- SELECT(accounts): --- Users can read the an account if --- - they are the primary owner of the account --- - they have a role on the account --- - they are reading an account of the same team -create policy accounts_read on public.accounts for -select - to authenticated using ( - ( - ( - select - auth.uid () - ) = primary_owner_user_id - ) - or public.has_role_on_account (id) - or public.is_account_team_member (id) - ); - --- DELETE(accounts_memberships): --- Users with the required role can remove members from an account or remove their own -create policy accounts_memberships_delete on public.accounts_memberships for delete to authenticated using ( - ( - user_id = ( - select - auth.uid () - ) - ) - or public.can_action_account_member (account_id, user_id) -); - -/* - * ------------------------------------------------------- - * Section: Role Permissions - * We create the schema for the role permissions. Role permissions are the permissions for a role. - * For example, the 'owner' role might have the 'roles.manage' permission. - * ------------------------------------------------------- - */ --- Create table for roles permissions -create table if not exists - public.role_permissions ( - id bigint generated by default as identity primary key, - role varchar(50) references public.roles (name) not null, - permission public.app_permissions not null, - unique (role, permission) - ); - -comment on table public.role_permissions is 'The permissions for a role'; - -comment on column public.role_permissions.role is 'The role the permission is for'; - -comment on column public.role_permissions.permission is 'The permission for the role'; - --- Indexes on the role_permissions table -create index ix_role_permissions_role on public.role_permissions (role); - --- Revoke all on role_permissions table from authenticated and service_role -revoke all on public.role_permissions -from - authenticated, - service_role; - --- Open up access to role_permissions table for authenticated users and service_role -grant -select -, - insert, -update, -delete on table public.role_permissions to service_role; - --- Authenticated users can read role permissions -grant -select - on table public.role_permissions to authenticated; - --- Function "public.has_permission" --- Create a function to check if a user has a permission -create -or replace function public.has_permission ( - user_id uuid, - account_id uuid, - permission_name public.app_permissions -) returns boolean -set - search_path = '' as $$ -begin - return exists( - select - 1 - from - public.accounts_memberships - join public.role_permissions on - accounts_memberships.account_role = - role_permissions.role - where - accounts_memberships.user_id = has_permission.user_id - and accounts_memberships.account_id = has_permission.account_id - and role_permissions.permission = has_permission.permission_name); - -end; - -$$ language plpgsql; - -grant -execute on function public.has_permission (uuid, uuid, public.app_permissions) to authenticated, -service_role; - --- Function "public.has_more_elevated_role" --- Check if a user has a more elevated role than the target role -create -or replace function public.has_more_elevated_role ( - target_user_id uuid, - target_account_id uuid, - role_name varchar -) returns boolean -set - search_path = '' as $$ -declare - declare is_primary_owner boolean; - user_role_hierarchy_level int; - target_role_hierarchy_level int; -begin - -- Check if the user is the primary owner of the account - select - exists ( - select - 1 - from - public.accounts - where - id = target_account_id - and primary_owner_user_id = target_user_id) into is_primary_owner; - - -- If the user is the primary owner, they have the highest role and can - -- perform any action - if is_primary_owner then - return true; - end if; - - -- Get the hierarchy level of the user's role within the account - select - hierarchy_level into user_role_hierarchy_level - from - public.roles - where - name =( - select - account_role - from - public.accounts_memberships - where - account_id = target_account_id - and target_user_id = user_id); - - if user_role_hierarchy_level is null then - return false; - end if; - - -- Get the hierarchy level of the target role - select - hierarchy_level into target_role_hierarchy_level - from - public.roles - where - name = role_name; - - -- If the target role does not exist, the user cannot perform the action - if target_role_hierarchy_level is null then - return false; - end if; - - -- If the user's role is higher than the target role, they can perform - -- the action - return user_role_hierarchy_level < target_role_hierarchy_level; - -end; - -$$ language plpgsql; - -grant -execute on function public.has_more_elevated_role (uuid, uuid, varchar) to authenticated, -service_role; - --- Function "public.has_same_role_hierarchy_level" --- Check if a user has the same role hierarchy level as the target role -create -or replace function public.has_same_role_hierarchy_level ( - target_user_id uuid, - target_account_id uuid, - role_name varchar -) returns boolean -set - search_path = '' as $$ -declare - is_primary_owner boolean; - user_role_hierarchy_level int; - target_role_hierarchy_level int; -begin - -- Check if the user is the primary owner of the account - select - exists ( - select - 1 - from - public.accounts - where - id = target_account_id - and primary_owner_user_id = target_user_id) into is_primary_owner; - - -- If the user is the primary owner, they have the highest role and can perform any action - if is_primary_owner then - return true; - end if; - - -- Get the hierarchy level of the user's role within the account - select - hierarchy_level into user_role_hierarchy_level - from - public.roles - where - name =( - select - account_role - from - public.accounts_memberships - where - account_id = target_account_id - and target_user_id = user_id); - - -- If the user does not have a role in the account, they cannot perform the action - if user_role_hierarchy_level is null then - return false; - end if; - - -- Get the hierarchy level of the target role - select - hierarchy_level into target_role_hierarchy_level - from - public.roles - where - name = role_name; - - -- If the target role does not exist, the user cannot perform the action - if target_role_hierarchy_level is null then - return false; - end if; - - -- check the user's role hierarchy level is the same as the target role - return user_role_hierarchy_level = target_role_hierarchy_level; - -end; - -$$ language plpgsql; - -grant -execute on function public.has_same_role_hierarchy_level (uuid, uuid, varchar) to authenticated, -service_role; - --- Enable RLS on the role_permissions table -alter table public.role_permissions enable row level security; - --- RLS on the role_permissions table --- SELECT(role_permissions): --- Authenticated Users can read global permissions -create policy role_permissions_read on public.role_permissions for -select - to authenticated using (true); - -/* - * ------------------------------------------------------- - * Section: Invitations - * We create the schema for the invitations. Invitations are the invitations for an account sent to a user to join the account. - * ------------------------------------------------------- - */ -create table if not exists - public.invitations ( - id serial primary key, - email varchar(255) not null, - account_id uuid references public.accounts (id) on delete cascade not null, - invited_by uuid references auth.users on delete cascade not null, - role varchar(50) references public.roles (name) not null, - invite_token varchar(255) unique not null, - created_at timestamptz default current_timestamp not null, - updated_at timestamptz default current_timestamp not null, - expires_at timestamptz default current_timestamp + interval '7 days' not null, - unique (email, account_id) - ); - -comment on table public.invitations is 'The invitations for an account'; - -comment on column public.invitations.account_id is 'The account the invitation is for'; - -comment on column public.invitations.invited_by is 'The user who invited the user'; - -comment on column public.invitations.role is 'The role for the invitation'; - -comment on column public.invitations.invite_token is 'The token for the invitation'; - -comment on column public.invitations.expires_at is 'The expiry date for the invitation'; - -comment on column public.invitations.email is 'The email of the user being invited'; - --- Indexes on the invitations table -create index ix_invitations_account_id on public.invitations (account_id); - --- Revoke all on invitations table from authenticated and service_role -revoke all on public.invitations -from - authenticated, - service_role; - --- Open up access to invitations table for authenticated users and service_role -grant -select -, - insert, -update, -delete on table public.invitations to authenticated, -service_role; - --- Enable RLS on the invitations table -alter table public.invitations enable row level security; - --- Function "kit.check_team_account" --- Function to check if the account is a team account or not when inserting or updating an invitation -create -or replace function kit.check_team_account () returns trigger -set - search_path = '' as $$ -begin - if( - select - is_personal_account - from - public.accounts - where - id = new.account_id) then - raise exception 'Account must be an team account'; - - end if; - - return NEW; - -end; - -$$ language plpgsql; - -create trigger only_team_accounts_check before insert -or -update on public.invitations for each row -execute procedure kit.check_team_account (); - --- RLS on the invitations table --- SELECT(invitations): --- Users can read invitations to users of an account they are a member of -create policy invitations_read_self on public.invitations for -select - to authenticated using (public.has_role_on_account (account_id)); - --- INSERT(invitations): --- Users can create invitations to users of an account they are --- a member of and have the 'invites.manage' permission AND the target role is not higher than the user's role -create policy invitations_create_self on public.invitations for insert to authenticated -with - check ( - public.is_set ('enable_team_accounts') - and public.has_permission ( - ( - select - auth.uid () - ), - account_id, - 'invites.manage'::public.app_permissions - ) - and (public.has_more_elevated_role ( - ( - select - auth.uid () - ), - account_id, - role - ) or public.has_same_role_hierarchy_level( - ( - select - auth.uid () - ), - account_id, - role - )) - ); - --- UPDATE(invitations): --- Users can update invitations to users of an account they are a member of and have the 'invites.manage' permission AND --- the target role is not higher than the user's role -create policy invitations_update on public.invitations -for update - to authenticated using ( - public.has_permission ( - ( - select - auth.uid () - ), - account_id, - 'invites.manage'::public.app_permissions - ) - and public.has_more_elevated_role ( - ( - select - auth.uid () - ), - account_id, - role - ) - ) -with - check ( - public.has_permission ( - ( - select - auth.uid () - ), - account_id, - 'invites.manage'::public.app_permissions - ) - and public.has_more_elevated_role ( - ( - select - auth.uid () - ), - account_id, - role - ) - ); - --- DELETE(public.invitations): --- Users can delete invitations to users of an account they are a member of and have the 'invites.manage' permission -create policy invitations_delete on public.invitations for delete to authenticated using ( - has_role_on_account (account_id) - and public.has_permission ( - ( - select - auth.uid () - ), - account_id, - 'invites.manage'::public.app_permissions - ) -); - --- Functions "public.accept_invitation" --- Function to accept an invitation to an account -create -or replace function accept_invitation (token text, user_id uuid) returns uuid -set - search_path = '' as $$ -declare - target_account_id uuid; - target_role varchar(50); -begin - select - account_id, - role into target_account_id, - target_role - from - public.invitations - where - invite_token = token - and expires_at > now(); - - if not found then - raise exception 'Invalid or expired invitation token'; - end if; - - insert into public.accounts_memberships( - user_id, - account_id, - account_role) - values ( - accept_invitation.user_id, - target_account_id, - target_role); - - delete from public.invitations - where invite_token = token; - - return target_account_id; -end; - -$$ language plpgsql; - -grant -execute on function accept_invitation (text, uuid) to service_role; - -/* - * ------------------------------------------------------- - * Section: Billing Customers - * We create the schema for the billing customers. Billing customers are the customers for an account in the billing provider. For example, a user might have a customer in the billing provider with the customer ID 'cus_123'. - * ------------------------------------------------------- - */ --- Account Subscriptions table -create table - public.billing_customers ( - account_id uuid references public.accounts (id) on delete cascade not null, - id serial primary key, - email text, - provider public.billing_provider not null, - customer_id text not null, - unique (account_id, customer_id, provider) - ); - -comment on table public.billing_customers is 'The billing customers for an account'; - -comment on column public.billing_customers.account_id is 'The account the billing customer is for'; - -comment on column public.billing_customers.provider is 'The provider of the billing customer'; - -comment on column public.billing_customers.customer_id is 'The customer ID for the billing customer'; - -comment on column public.billing_customers.email is 'The email of the billing customer'; - --- Indexes on the billing_customers table -create index ix_billing_customers_account_id on public.billing_customers (account_id); - --- Revoke all on billing_customers table from authenticated and service_role -revoke all on public.billing_customers -from - authenticated, - service_role; - --- Open up relevant access to billing_customers table for authenticated users and service_role -grant -select -, - insert, -update, -delete on table public.billing_customers to service_role; - --- Open up access to billing_customers table for authenticated users -grant -select - on table public.billing_customers to authenticated, - service_role; - --- Enable RLS on billing_customers table -alter table public.billing_customers enable row level security; - --- RLS on the billing_customers table --- SELECT(billing_customers): --- Users can read account subscriptions on an account they are a member of -create policy billing_customers_read_self on public.billing_customers for -select - to authenticated using ( - account_id = ( - select - auth.uid () - ) - or has_role_on_account (account_id) - ); - -/* - * ------------------------------------------------------- - * Section: Subscriptions - * We create the schema for the subscriptions. Subscriptions are the subscriptions for an account to a product. For example, a user might have a subscription to a product with the status 'active'. - * ------------------------------------------------------- - */ --- Subscriptions table -create table if not exists - public.subscriptions ( - id text not null primary key, - account_id uuid references public.accounts (id) on delete cascade not null, - billing_customer_id int references public.billing_customers on delete cascade not null, - status public.subscription_status not null, - active bool not null, - billing_provider public.billing_provider not null, - cancel_at_period_end bool not null, - currency varchar(3) not null, - created_at timestamptz not null default current_timestamp, - updated_at timestamptz not null default current_timestamp, - period_starts_at timestamptz not null, - period_ends_at timestamptz not null, - trial_starts_at timestamptz, - trial_ends_at timestamptz - ); - -comment on table public.subscriptions is 'The subscriptions for an account'; - -comment on column public.subscriptions.account_id is 'The account the subscription is for'; - -comment on column public.subscriptions.billing_provider is 'The provider of the subscription'; - -comment on column public.subscriptions.cancel_at_period_end is 'Whether the subscription will be canceled at the end of the period'; - -comment on column public.subscriptions.currency is 'The currency for the subscription'; - -comment on column public.subscriptions.status is 'The status of the subscription'; - -comment on column public.subscriptions.period_starts_at is 'The start of the current period for the subscription'; - -comment on column public.subscriptions.period_ends_at is 'The end of the current period for the subscription'; - -comment on column public.subscriptions.trial_starts_at is 'The start of the trial period for the subscription'; - -comment on column public.subscriptions.trial_ends_at is 'The end of the trial period for the subscription'; - -comment on column public.subscriptions.active is 'Whether the subscription is active'; - -comment on column public.subscriptions.billing_customer_id is 'The billing customer ID for the subscription'; - --- Revoke all on subscriptions table from authenticated and service_role -revoke all on public.subscriptions -from - authenticated, - service_role; - --- Open up relevant access to subscriptions table for authenticated users and service_role -grant -select -, - insert, -update, -delete on table public.subscriptions to service_role; - -grant -select - on table public.subscriptions to authenticated; - --- Indexes on the subscriptions table -create index ix_subscriptions_account_id on public.subscriptions (account_id); - --- Enable RLS on subscriptions table -alter table public.subscriptions enable row level security; - --- RLS on the subscriptions table --- SELECT(subscriptions): --- Users can read account subscriptions on an account they are a member of -create policy subscriptions_read_self on public.subscriptions for -select - to authenticated using ( - ( - has_role_on_account (account_id) - and public.is_set ('enable_team_account_billing') - ) - or ( - account_id = ( - select - auth.uid () - ) - and public.is_set ('enable_account_billing') - ) - ); - --- Function "public.upsert_subscription" --- Insert or Update a subscription and its items in the database when receiving a webhook from the billing provider -create -or replace function public.upsert_subscription ( - target_account_id uuid, - target_customer_id varchar(255), - target_subscription_id text, - active bool, - status public.subscription_status, - billing_provider public.billing_provider, - cancel_at_period_end bool, - currency varchar(3), - period_starts_at timestamptz, - period_ends_at timestamptz, - line_items jsonb, - trial_starts_at timestamptz default null, - trial_ends_at timestamptz default null -) returns public.subscriptions -set - search_path = '' as $$ -declare - new_subscription public.subscriptions; - new_billing_customer_id int; -begin - insert into public.billing_customers( - account_id, - provider, - customer_id) - values ( - target_account_id, - billing_provider, - target_customer_id) -on conflict ( - account_id, - provider, - customer_id) - do update set - provider = excluded.provider - returning - id into new_billing_customer_id; - - insert into public.subscriptions( - account_id, - billing_customer_id, - id, - active, - status, - billing_provider, - cancel_at_period_end, - currency, - period_starts_at, - period_ends_at, - trial_starts_at, - trial_ends_at) - values ( - target_account_id, - new_billing_customer_id, - target_subscription_id, - active, - status, - billing_provider, - cancel_at_period_end, - currency, - period_starts_at, - period_ends_at, - trial_starts_at, - trial_ends_at) -on conflict ( - id) - do update set - active = excluded.active, - status = excluded.status, - cancel_at_period_end = excluded.cancel_at_period_end, - currency = excluded.currency, - period_starts_at = excluded.period_starts_at, - period_ends_at = excluded.period_ends_at, - trial_starts_at = excluded.trial_starts_at, - trial_ends_at = excluded.trial_ends_at - returning - * into new_subscription; - - -- Upsert subscription items and delete ones that are not in the line_items array - with item_data as ( - select - (line_item ->> 'id')::varchar as line_item_id, - (line_item ->> 'product_id')::varchar as prod_id, - (line_item ->> 'variant_id')::varchar as var_id, - (line_item ->> 'type')::public.subscription_item_type as type, - (line_item ->> 'price_amount')::numeric as price_amt, - (line_item ->> 'quantity')::integer as qty, - (line_item ->> 'interval')::varchar as intv, - (line_item ->> 'interval_count')::integer as intv_count - from - jsonb_array_elements(line_items) as line_item - ), - line_item_ids as ( - select line_item_id from item_data - ), - deleted_items as ( - delete from - public.subscription_items - where - public.subscription_items.subscription_id = new_subscription.id - and public.subscription_items.id not in (select line_item_id from line_item_ids) - returning * - ) - insert into public.subscription_items( - id, - subscription_id, - product_id, - variant_id, - type, - price_amount, - quantity, - interval, - interval_count) - select - line_item_id, - target_subscription_id, - prod_id, - var_id, - type, - price_amt, - qty, - intv, - intv_count - from - item_data - on conflict (id) - do update set - product_id = excluded.product_id, - variant_id = excluded.variant_id, - price_amount = excluded.price_amount, - quantity = excluded.quantity, - interval = excluded.interval, - type = excluded.type, - interval_count = excluded.interval_count; - - return new_subscription; - -end; - -$$ language plpgsql; - -grant -execute on function public.upsert_subscription ( - uuid, - varchar, - text, - bool, - public.subscription_status, - public.billing_provider, - bool, - varchar, - timestamptz, - timestamptz, - jsonb, - timestamptz, - timestamptz -) to service_role; - -/* ------------------------------------------------------- -* Section: Subscription Items -* We create the schema for the subscription items. Subscription items are the items in a subscription. -* For example, a subscription might have a subscription item with the product ID 'prod_123' and the variant ID 'var_123'. -* ------------------------------------------------------- -*/ -create table if not exists - public.subscription_items ( - id varchar(255) not null primary key, - subscription_id text references public.subscriptions (id) on delete cascade not null, - product_id varchar(255) not null, - variant_id varchar(255) not null, - type public.subscription_item_type not null, - price_amount numeric, - quantity integer not null default 1, - interval varchar(255) not null, - interval_count integer not null check (interval_count > 0), - created_at timestamptz not null default current_timestamp, - updated_at timestamptz not null default current_timestamp, - unique (subscription_id, product_id, variant_id) - ); - -comment on table public.subscription_items is 'The items in a subscription'; - -comment on column public.subscription_items.subscription_id is 'The subscription the item is for'; - -comment on column public.subscription_items.product_id is 'The product ID for the item'; - -comment on column public.subscription_items.variant_id is 'The variant ID for the item'; - -comment on column public.subscription_items.price_amount is 'The price amount for the item'; - -comment on column public.subscription_items.quantity is 'The quantity of the item'; - -comment on column public.subscription_items.interval is 'The interval for the item'; - -comment on column public.subscription_items.interval_count is 'The interval count for the item'; - -comment on column public.subscription_items.created_at is 'The creation date of the item'; - -comment on column public.subscription_items.updated_at is 'The last update date of the item'; - --- Revoke all access to subscription_items table for authenticated users and service_role -revoke all on public.subscription_items -from - authenticated, - service_role; - --- Open up relevant access to subscription_items table for authenticated users and service_role -grant -select - on table public.subscription_items to authenticated, - service_role; - -grant insert, -update, -delete on table public.subscription_items to service_role; - --- Indexes --- Indexes on the subscription_items table -create index ix_subscription_items_subscription_id on public.subscription_items (subscription_id); - --- RLS -alter table public.subscription_items enable row level security; - --- SELECT(subscription_items) --- Users can read subscription items on a subscription they are a member of -create policy subscription_items_read_self on public.subscription_items for -select - to authenticated using ( - exists ( - select - 1 - from - public.subscriptions - where - id = subscription_id - and ( - account_id = ( - select - auth.uid () - ) - or has_role_on_account (account_id) - ) - ) - ); - -/** - * ------------------------------------------------------- - * Section: Orders - * We create the schema for the subscription items. Subscription items are the items in a subscription. - * For example, a subscription might have a subscription item with the product ID 'prod_123' and the variant ID 'var_123'. - * ------------------------------------------------------- - */ -create table if not exists - public.orders ( - id text not null primary key, - account_id uuid references public.accounts (id) on delete cascade not null, - billing_customer_id int references public.billing_customers on delete cascade not null, - status public.payment_status not null, - billing_provider public.billing_provider not null, - total_amount numeric not null, - currency varchar(3) not null, - created_at timestamptz not null default current_timestamp, - updated_at timestamptz not null default current_timestamp - ); - -comment on table public.orders is 'The one-time orders for an account'; - -comment on column public.orders.account_id is 'The account the order is for'; - -comment on column public.orders.billing_provider is 'The provider of the order'; - -comment on column public.orders.total_amount is 'The total amount for the order'; - -comment on column public.orders.currency is 'The currency for the order'; - -comment on column public.orders.status is 'The status of the order'; - -comment on column public.orders.billing_customer_id is 'The billing customer ID for the order'; - --- Revoke all access to orders table for authenticated users and service_role -revoke all on public.orders -from - authenticated, - service_role; - --- Open up access to orders table for authenticated users and service_role -grant -select - on table public.orders to authenticated; - -grant -select -, - insert, -update, -delete on table public.orders to service_role; - --- Indexes --- Indexes on the orders table -create index ix_orders_account_id on public.orders (account_id); - --- RLS -alter table public.orders enable row level security; - --- SELECT(orders) --- Users can read orders on an account they are a member of or the account is their own -create policy orders_read_self on public.orders for -select - to authenticated using ( - ( - account_id = ( - select - auth.uid () - ) - and public.is_set ('enable_account_billing') - ) - or ( - has_role_on_account (account_id) - and public.is_set ('enable_team_account_billing') - ) - ); - -/** - * ------------------------------------------------------- - * Section: Order Items - * We create the schema for the order items. Order items are the items in an order. - * ------------------------------------------------------- - */ -create table if not exists - public.order_items ( - id text not null primary key, - order_id text references public.orders (id) on delete cascade not null, - product_id text not null, - variant_id text not null, - price_amount numeric, - quantity integer not null default 1, - created_at timestamptz not null default current_timestamp, - updated_at timestamptz not null default current_timestamp, - unique (order_id, product_id, variant_id) - ); - -comment on table public.order_items is 'The items in an order'; - -comment on column public.order_items.order_id is 'The order the item is for'; - -comment on column public.order_items.order_id is 'The order the item is for'; - -comment on column public.order_items.product_id is 'The product ID for the item'; - -comment on column public.order_items.variant_id is 'The variant ID for the item'; - -comment on column public.order_items.price_amount is 'The price amount for the item'; - -comment on column public.order_items.quantity is 'The quantity of the item'; - -comment on column public.order_items.created_at is 'The creation date of the item'; - -comment on column public.order_items.updated_at is 'The last update date of the item'; - --- Revoke all access to order_items table for authenticated users and service_role -revoke all on public.order_items -from - authenticated, - service_role; - --- Open up relevant access to order_items table for authenticated users and service_role -grant -select - on table public.order_items to authenticated, - service_role; - -grant insert, update, delete on table public.order_items to service_role; - --- Indexes on the order_items table -create index ix_order_items_order_id on public.order_items (order_id); - --- RLS -alter table public.order_items enable row level security; - --- SELECT(order_items): --- Users can read order items on an order they are a member of -create policy order_items_read_self on public.order_items for -select - to authenticated using ( - exists ( - select - 1 - from - public.orders - where - id = order_id - and ( - account_id = ( - select - auth.uid () - ) - or has_role_on_account (account_id) - ) - ) - ); - --- Function "public.upsert_order" --- Insert or update an order and its items when receiving a webhook from the billing provider -create -or replace function public.upsert_order ( - target_account_id uuid, - target_customer_id varchar(255), - target_order_id text, - status public.payment_status, - billing_provider public.billing_provider, - total_amount numeric, - currency varchar(3), - line_items jsonb -) returns public.orders -set - search_path = '' as $$ -declare - new_order public.orders; - new_billing_customer_id int; -begin - insert into public.billing_customers( - account_id, - provider, - customer_id) - values ( - target_account_id, - billing_provider, - target_customer_id) -on conflict ( - account_id, - provider, - customer_id) - do update set - provider = excluded.provider - returning - id into new_billing_customer_id; - - insert into public.orders( - account_id, - billing_customer_id, - id, - status, - billing_provider, - total_amount, - currency) - values ( - target_account_id, - new_billing_customer_id, - target_order_id, - status, - billing_provider, - total_amount, - currency) -on conflict ( - id) - do update set - status = excluded.status, - total_amount = excluded.total_amount, - currency = excluded.currency - returning - * into new_order; - - -- Upsert order items and delete ones that are not in the line_items array - with item_data as ( - select - (line_item ->> 'id')::varchar as line_item_id, - (line_item ->> 'product_id')::varchar as prod_id, - (line_item ->> 'variant_id')::varchar as var_id, - (line_item ->> 'price_amount')::numeric as price_amt, - (line_item ->> 'quantity')::integer as qty - from - jsonb_array_elements(line_items) as line_item - ), - line_item_ids as ( - select line_item_id from item_data - ), - deleted_items as ( - delete from - public.order_items - where - public.order_items.order_id = new_order.id - and public.order_items.id not in (select line_item_id from line_item_ids) - returning * - ) - insert into public.order_items( - id, - order_id, - product_id, - variant_id, - price_amount, - quantity) - select - line_item_id, - target_order_id, - prod_id, - var_id, - price_amt, - qty - from - item_data - on conflict (id) - do update set - price_amount = excluded.price_amount, - product_id = excluded.product_id, - variant_id = excluded.variant_id, - quantity = excluded.quantity; - - return new_order; - -end; - -$$ language plpgsql; - -grant -execute on function public.upsert_order ( - uuid, - varchar, - text, - public.payment_status, - public.billing_provider, - numeric, - varchar, - jsonb -) to service_role; - -/** - * ------------------------------------------------------- - * Section: Notifications - * We create the schema for the notifications. Notifications are the notifications for an account. - * ------------------------------------------------------- - */ -create type public.notification_channel as enum('in_app', 'email'); - -create type public.notification_type as enum('info', 'warning', 'error'); - -create table if not exists - public.notifications ( - id bigint generated always as identity primary key, - account_id uuid not null references public.accounts (id) on delete cascade, - type public.notification_type not null default 'info', - body varchar(5000) not null, - link varchar(255), - channel public.notification_channel not null default 'in_app', - dismissed boolean not null default false, - expires_at timestamptz default (now() + interval '1 month'), - created_at timestamptz not null default now() - ); - -comment on table notifications is 'The notifications for an account'; - -comment on column notifications.account_id is 'The account the notification is for (null for system messages)'; - -comment on column notifications.type is 'The type of the notification'; - -comment on column notifications.body is 'The body of the notification'; - -comment on column notifications.link is 'The link for the notification'; - -comment on column notifications.channel is 'The channel for the notification'; - -comment on column notifications.dismissed is 'Whether the notification has been dismissed'; - -comment on column notifications.expires_at is 'The expiry date for the notification'; - -comment on column notifications.created_at is 'The creation date for the notification'; - --- Revoke all access to notifications table for authenticated users and service_role -revoke all on public.notifications -from - authenticated, - service_role; - --- Open up relevant access to notifications table for authenticated users and service_role -grant -select -, -update on table public.notifications to authenticated, -service_role; - -grant insert on table public.notifications to service_role; - --- enable realtime -alter publication supabase_realtime -add table public.notifications; - --- Indexes --- Indexes on the notifications table --- index for selecting notifications for an account that are not dismissed and not expired -create index idx_notifications_account_dismissed on notifications (account_id, dismissed, expires_at); - --- RLS -alter table public.notifications enable row level security; - --- SELECT(notifications): --- Users can read notifications on an account they are a member of -create policy notifications_read_self on public.notifications for -select - to authenticated using ( - account_id = ( - select - auth.uid () - ) - or has_role_on_account (account_id) - ); - --- UPDATE(notifications): --- Users can set notifications to read on an account they are a member of -create policy notifications_update_self on public.notifications -for update - to authenticated using ( - account_id = ( - select - auth.uid () - ) - or has_role_on_account (account_id) - ); - --- Function "kit.update_notification_dismissed_status" --- Make sure the only updatable field is the dismissed status and nothing else -create -or replace function kit.update_notification_dismissed_status () returns trigger -set - search_path to '' as $$ -begin - old.dismissed := new.dismissed; - - if (new is distinct from old) then - raise exception 'UPDATE of columns other than "dismissed" is forbidden'; - end if; - - return old; -end; -$$ language plpgsql; - --- add trigger when updating a notification to update the dismissed status -create trigger update_notification_dismissed_status before -update on public.notifications for each row -execute procedure kit.update_notification_dismissed_status (); - -/** - * ------------------------------------------------------- - * Section: Slugify - * We create the schema for the slugify functions. Slugify functions are used to create slugs from strings. - * We use this for ensure unique slugs for accounts. - * ------------------------------------------------------- - */ --- Create a function to slugify a string --- useful for turning an account name into a unique slug -create -or replace function kit.slugify ("value" text) returns text as $$ - -- removes accents (diacritic signs) from a given string -- - with "unaccented" as( - select - kit.unaccent("value") as "value" -), --- lowercases the string -"lowercase" as( - select - lower("value") as "value" - from - "unaccented" -), --- remove single and double quotes -"removed_quotes" as( - select - regexp_replace("value", '[''"]+', '', - 'gi') as "value" - from - "lowercase" -), --- replaces anything that's not a letter, number, hyphen('-'), or underscore('_') with a hyphen('-') -"hyphenated" as( - select - regexp_replace("value", '[^a-z0-9\\-_]+', '-', - 'gi') as "value" - from - "removed_quotes" -), --- trims hyphens('-') if they exist on the head or tail of --- the string -"trimmed" as( - select - regexp_replace(regexp_replace("value", '\-+$', - ''), '^\-', '') as "value" from "hyphenated" -) - select - "value" - from - "trimmed"; -$$ language SQL strict immutable -set - search_path to ''; - -grant -execute on function kit.slugify (text) to service_role, -authenticated; - --- Function "kit.set_slug_from_account_name" --- Set the slug from the account name and increment if the slug exists -create -or replace function kit.set_slug_from_account_name () returns trigger language plpgsql security definer -set - search_path = '' as $$ -declare - sql_string varchar; - tmp_slug varchar; - increment integer; - tmp_row record; - tmp_row_count integer; -begin - tmp_row_count = 1; - - increment = 0; - - while tmp_row_count > 0 loop - if increment > 0 then - tmp_slug = kit.slugify(new.name || ' ' || increment::varchar); - - else - tmp_slug = kit.slugify(new.name); - - end if; - - sql_string = format('select count(1) cnt from public.accounts where slug = ''' || tmp_slug || - '''; '); - - for tmp_row in execute (sql_string) - loop - raise notice 'tmp_row %', tmp_row; - - tmp_row_count = tmp_row.cnt; - - end loop; - - increment = increment +1; - - end loop; - - new.slug := tmp_slug; - - return NEW; - -end -$$; - --- Create a trigger to set the slug from the account name -create trigger "set_slug_from_account_name" before insert on public.accounts for each row when ( - NEW.name is not null - and NEW.slug is null - and NEW.is_personal_account = false -) -execute procedure kit.set_slug_from_account_name (); - --- Create a trigger when a name is updated to update the slug -create trigger "update_slug_from_account_name" before -update on public.accounts for each row when ( - NEW.name is not null - and NEW.name <> OLD.name - and NEW.is_personal_account = false -) -execute procedure kit.set_slug_from_account_name (); - --- Function "kit.setup_new_user" --- Setup a new user account after user creation -create -or replace function kit.setup_new_user () returns trigger language plpgsql security definer -set - search_path = '' as $$ -declare - user_name text; - picture_url text; -begin - if new.raw_user_meta_data ->> 'name' is not null then - user_name := new.raw_user_meta_data ->> 'name'; - - end if; - - if user_name is null and new.email is not null then - user_name := split_part(new.email, '@', 1); - - end if; - - if user_name is null then - user_name := ''; - - end if; - - if new.raw_user_meta_data ->> 'avatar_url' is not null then - picture_url := new.raw_user_meta_data ->> 'avatar_url'; - else - picture_url := null; - end if; - - insert into public.accounts( - id, - primary_owner_user_id, - name, - is_personal_account, - picture_url, - email) - values ( - new.id, - new.id, - user_name, - true, - picture_url, - new.email); - - return new; - -end; - -$$; - --- trigger the function every time a user is created -create trigger on_auth_user_created -after insert on auth.users for each row -execute procedure kit.setup_new_user (); - -/** - * ------------------------------------------------------- - * Section: Functions - * We create the schema for the functions. Functions are the custom functions for the application. - * ------------------------------------------------------- - */ --- Function "public.create_team_account" --- Create a team account if team accounts are enabled -create -or replace function public.create_team_account (account_name text) returns public.accounts -set - search_path = '' as $$ -declare - new_account public.accounts; -begin - if (not public.is_set('enable_team_accounts')) then - raise exception 'Team accounts are not enabled'; - end if; - - insert into public.accounts( - name, - is_personal_account) - values ( - account_name, - false) -returning - * into new_account; - - return new_account; - -end; - -$$ language plpgsql; - -grant -execute on function public.create_team_account (text) to authenticated, -service_role; - --- RLS(public.accounts) --- Authenticated users can create team accounts -create policy create_org_account on public.accounts for insert to authenticated -with - check ( - public.is_set ('enable_team_accounts') - and public.accounts.is_personal_account = false - ); - --- Function "public.create_invitation" --- create an invitation to an account -create -or replace function public.create_invitation (account_id uuid, email text, role varchar(50)) returns public.invitations -set - search_path = '' as $$ -declare - new_invitation public.invitations; - invite_token text; -begin - invite_token := extensions.uuid_generate_v4(); - - insert into public.invitations( - email, - account_id, - invited_by, - role, - invite_token) - values ( - email, - account_id, - auth.uid(), - role, - invite_token) -returning - * into new_invitation; - - return new_invitation; - -end; - -$$ language plpgsql; - --- --- VIEW "user_account_workspace": --- we create a view to load the general app data for the authenticated --- user which includes the user accounts and memberships -create or replace view - public.user_account_workspace -with - (security_invoker = true) as -select - accounts.id as id, - accounts.name as name, - accounts.picture_url as picture_url, - ( - select - status - from - public.subscriptions - where - account_id = accounts.id - limit - 1 - ) as subscription_status -from - public.accounts -where - primary_owner_user_id = (select auth.uid ()) - and accounts.is_personal_account = true -limit - 1; - -grant -select - on public.user_account_workspace to authenticated, - service_role; - --- --- VIEW "user_accounts": --- we create a view to load the user's accounts and memberships --- useful to display the user's accounts in the app -create or replace view - public.user_accounts (id, name, picture_url, slug, role) -with - (security_invoker = true) as -select - account.id, - account.name, - account.picture_url, - account.slug, - membership.account_role -from - public.accounts account - join public.accounts_memberships membership on account.id = membership.account_id -where - membership.user_id = (select auth.uid ()) - and account.is_personal_account = false - and account.id in ( - select - account_id - from - public.accounts_memberships - where - user_id = (select auth.uid ()) - ); - -grant -select - on public.user_accounts to authenticated, - service_role; - --- --- Function "public.team_account_workspace" --- Load all the data for a team account workspace -create or replace function public.team_account_workspace(account_slug text) -returns table ( - id uuid, - name varchar(255), - picture_url varchar(1000), - slug text, - role varchar(50), - role_hierarchy_level int, - primary_owner_user_id uuid, - subscription_status public.subscription_status, - permissions public.app_permissions[] -) -set search_path to '' -as $$ -begin - return QUERY - select - accounts.id, - accounts.name, - accounts.picture_url, - accounts.slug, - accounts_memberships.account_role, - roles.hierarchy_level, - accounts.primary_owner_user_id, - subscriptions.status, - array_agg(role_permissions.permission) - from - public.accounts - join public.accounts_memberships on accounts.id = accounts_memberships.account_id - left join public.subscriptions on accounts.id = subscriptions.account_id - join public.roles on accounts_memberships.account_role = roles.name - left join public.role_permissions on accounts_memberships.account_role = role_permissions.role - where - accounts.slug = account_slug - and public.accounts_memberships.user_id = (select auth.uid()) - group by - accounts.id, - accounts_memberships.account_role, - subscriptions.status, - roles.hierarchy_level; -end; -$$ language plpgsql; - -grant -execute on function public.team_account_workspace (text) to authenticated, -service_role; - --- Functions "public.get_account_members" --- Function to get the members of an account by the account slug -create -or replace function public.get_account_members (account_slug text) returns table ( - id uuid, - user_id uuid, - account_id uuid, - role varchar(50), - role_hierarchy_level int, - primary_owner_user_id uuid, - name varchar, - email varchar, - picture_url varchar, - created_at timestamptz, - updated_at timestamptz -) language plpgsql -set - search_path = '' as $$ -begin - return QUERY - select - acc.id, - am.user_id, - am.account_id, - am.account_role, - r.hierarchy_level, - a.primary_owner_user_id, - acc.name, - acc.email, - acc.picture_url, - am.created_at, - am.updated_at - from - public.accounts_memberships am - join public.accounts a on a.id = am.account_id - join public.accounts acc on acc.id = am.user_id - join public.roles r on r.name = am.account_role - where - a.slug = account_slug; - -end; - -$$; - -grant -execute on function public.get_account_members (text) to authenticated, -service_role; - --- Function "public.get_account_invitations" --- List the account invitations by the account slug -create -or replace function public.get_account_invitations (account_slug text) returns table ( - id integer, - email varchar(255), - account_id uuid, - invited_by uuid, - role varchar(50), - created_at timestamptz, - updated_at timestamptz, - expires_at timestamptz, - inviter_name varchar, - inviter_email varchar -) -set - search_path = '' as $$ -begin - return query - select - invitation.id, - invitation.email, - invitation.account_id, - invitation.invited_by, - invitation.role, - invitation.created_at, - invitation.updated_at, - invitation.expires_at, - account.name, - account.email - from - public.invitations as invitation - join public.accounts as account on invitation.account_id = account.id - where - account.slug = account_slug; - -end; - -$$ language plpgsql; - -grant -execute on function public.get_account_invitations (text) to authenticated, -service_role; - --- Function "public.add_invitations_to_account" --- Add invitations to an account -create -or replace function public.add_invitations_to_account ( - account_slug text, - invitations public.invitation[] -) returns public.invitations[] -set - search_path = '' as $$ -declare - new_invitation public.invitations; - all_invitations public.invitations[] := array[]::public.invitations[]; - invite_token text; - email text; - role varchar(50); -begin - FOREACH email, - role in array invitations loop - invite_token := extensions.uuid_generate_v4(); - - insert into public.invitations( - email, - account_id, - invited_by, - role, - invite_token) - values ( - email, -( - select - id - from - public.accounts - where - slug = account_slug), auth.uid(), role, invite_token) - returning - * into new_invitation; - - all_invitations := array_append(all_invitations, new_invitation); - - end loop; - - return all_invitations; - -end; - -$$ language plpgsql; - -grant -execute on function public.add_invitations_to_account (text, public.invitation[]) to authenticated, -service_role; - --- Function "public.has_active_subscription" --- Check if a user has an active subscription on an account - ie. it's trialing or active --- Useful to gate access to features that require a subscription -create -or replace function public.has_active_subscription (target_account_id uuid) returns boolean -set - search_path = '' as $$ -begin - return exists ( - select - 1 - from - public.subscriptions - where - account_id = target_account_id - and active = true); - -end; - -$$ language plpgsql; - -grant -execute on function public.has_active_subscription (uuid) to authenticated, -service_role; - --- Storage --- Account Image -insert into - storage.buckets (id, name, PUBLIC) -values - ('account_image', 'account_image', true); - --- Function: get the storage filename as a UUID. --- Useful if you want to name files with UUIDs related to an account -create -or replace function kit.get_storage_filename_as_uuid (name text) returns uuid -set - search_path = '' as $$ -begin - return replace(storage.filename(name), concat('.', - storage.extension(name)), '')::uuid; - -end; - -$$ language plpgsql; - -grant -execute on function kit.get_storage_filename_as_uuid (text) to authenticated, -service_role; - --- RLS policies for storage bucket account_image -create policy account_image on storage.objects for all using ( - bucket_id = 'account_image' - and ( - kit.get_storage_filename_as_uuid(name) = auth.uid() - or public.has_role_on_account(kit.get_storage_filename_as_uuid(name)) - ) -) -with check ( - bucket_id = 'account_image' - and ( - kit.get_storage_filename_as_uuid(name) = auth.uid() - or public.has_permission( - auth.uid(), - kit.get_storage_filename_as_uuid(name), - 'settings.manage' - ) - ) -); \ No newline at end of file diff --git a/supabase copy/migrations/20240319163440_roles-seed.sql b/supabase copy/migrations/20240319163440_roles-seed.sql deleted file mode 100644 index 3d64698..0000000 --- a/supabase copy/migrations/20240319163440_roles-seed.sql +++ /dev/null @@ -1,40 +0,0 @@ --- Seed the roles table with default roles 'owner' and 'member' -insert into public.roles( - name, - hierarchy_level) -values ( - 'owner', - 1); - -insert into public.roles( - name, - hierarchy_level) -values ( - 'member', - 2); - --- We seed the role_permissions table with the default roles and permissions -insert into public.role_permissions( - role, - permission) -values ( - 'owner', - 'roles.manage'), -( - 'owner', - 'billing.manage'), -( - 'owner', - 'settings.manage'), -( - 'owner', - 'members.manage'), -( - 'owner', - 'invites.manage'), -( - 'member', - 'settings.manage'), -( - 'member', - 'invites.manage'); \ No newline at end of file diff --git a/supabase copy/migrations/20241007151024_delete-team-account.sql b/supabase copy/migrations/20241007151024_delete-team-account.sql deleted file mode 100644 index a510115..0000000 --- a/supabase copy/migrations/20241007151024_delete-team-account.sql +++ /dev/null @@ -1,7 +0,0 @@ -create policy delete_team_account - on public.accounts - for delete - to authenticated - using ( - auth.uid() = primary_owner_user_id - ); \ No newline at end of file diff --git a/supabase copy/migrations/20250301095452_one-time-tokens.sql b/supabase copy/migrations/20250301095452_one-time-tokens.sql deleted file mode 100644 index 31bfd69..0000000 --- a/supabase copy/migrations/20250301095452_one-time-tokens.sql +++ /dev/null @@ -1,346 +0,0 @@ -create extension if not exists pg_cron; - --- Create a table to store one-time tokens (nonces) -CREATE TABLE IF NOT EXISTS public.nonces ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - client_token TEXT NOT NULL, -- token sent to client (hashed) - nonce TEXT NOT NULL, -- token stored in DB (hashed) - user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NULL, -- Optional to support anonymous tokens - purpose TEXT NOT NULL, -- e.g., 'password-reset', 'email-verification', etc. - - -- Status fields - expires_at TIMESTAMPTZ NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - used_at TIMESTAMPTZ, - revoked BOOLEAN NOT NULL DEFAULT FALSE, -- For administrative revocation - revoked_reason TEXT, -- Reason for revocation if applicable - - -- Audit fields - verification_attempts INTEGER NOT NULL DEFAULT 0, -- Track attempted uses - last_verification_at TIMESTAMPTZ, -- Timestamp of last verification attempt - last_verification_ip INET, -- For tracking verification source - last_verification_user_agent TEXT, -- For tracking client information - - -- Extensibility fields - metadata JSONB DEFAULT '{}'::JSONB, -- optional metadata - scopes TEXT[] DEFAULT '{}' -- OAuth-style authorized scopes -); - --- Create indexes for efficient lookups -CREATE INDEX IF NOT EXISTS idx_nonces_status ON public.nonces (client_token, user_id, purpose, expires_at) - WHERE used_at IS NULL AND revoked = FALSE; - --- Enable Row Level Security (RLS) -ALTER TABLE public.nonces ENABLE ROW LEVEL SECURITY; - --- RLS policies --- Users can view their own nonces for verification -CREATE POLICY "Users can read their own nonces" - ON public.nonces - FOR SELECT - USING ( - user_id = (select auth.uid()) - ); - --- Create a function to create a nonce -CREATE OR REPLACE FUNCTION public.create_nonce( - p_user_id UUID DEFAULT NULL, - p_purpose TEXT DEFAULT NULL, - p_expires_in_seconds INTEGER DEFAULT 3600, -- 1 hour by default - p_metadata JSONB DEFAULT NULL, - p_scopes TEXT[] DEFAULT NULL, - p_revoke_previous BOOLEAN DEFAULT TRUE -- New parameter to control automatic revocation -) -RETURNS JSONB -LANGUAGE plpgsql -SECURITY DEFINER -AS $$ -DECLARE - v_client_token TEXT; - v_nonce TEXT; - v_expires_at TIMESTAMPTZ; - v_id UUID; - v_plaintext_token TEXT; - v_revoked_count INTEGER; -BEGIN - -- Revoke previous tokens for the same user and purpose if requested - -- This only applies if a user ID is provided (not for anonymous tokens) - IF p_revoke_previous = TRUE AND p_user_id IS NOT NULL THEN - WITH revoked AS ( - UPDATE public.nonces - SET - revoked = TRUE, - revoked_reason = 'Superseded by new token with same purpose' - WHERE - user_id = p_user_id - AND purpose = p_purpose - AND used_at IS NULL - AND revoked = FALSE - AND expires_at > NOW() - RETURNING 1 - ) - SELECT COUNT(*) INTO v_revoked_count FROM revoked; - END IF; - - -- Generate a 6-digit token - v_plaintext_token := (100000 + floor(random() * 900000))::text; - v_client_token := crypt(v_plaintext_token, gen_salt('bf')); - - -- Still generate a secure nonce for internal use - v_nonce := encode(gen_random_bytes(24), 'base64'); - v_nonce := crypt(v_nonce, gen_salt('bf')); - - -- Calculate expiration time - v_expires_at := NOW() + (p_expires_in_seconds * interval '1 second'); - - -- Insert the new nonce - INSERT INTO public.nonces ( - client_token, - nonce, - user_id, - expires_at, - metadata, - purpose, - scopes - ) - VALUES ( - v_client_token, - v_nonce, - p_user_id, - v_expires_at, - COALESCE(p_metadata, '{}'::JSONB), - p_purpose, - COALESCE(p_scopes, '{}'::TEXT[]) - ) - RETURNING id INTO v_id; - - -- Return the token information - -- Note: returning the plaintext token, not the hash - RETURN jsonb_build_object( - 'id', v_id, - 'token', v_plaintext_token, - 'expires_at', v_expires_at, - 'revoked_previous_count', COALESCE(v_revoked_count, 0) - ); -END; -$$; - -grant execute on function public.create_nonce to service_role; - --- Create a function to verify a nonce -CREATE OR REPLACE FUNCTION public.verify_nonce( - p_token TEXT, - p_purpose TEXT, - p_user_id UUID DEFAULT NULL, - p_required_scopes TEXT[] DEFAULT NULL, - p_max_verification_attempts INTEGER DEFAULT 5, - p_ip INET DEFAULT NULL, - p_user_agent TEXT DEFAULT NULL -) -RETURNS JSONB -LANGUAGE plpgsql -SECURITY DEFINER -AS $$ -DECLARE - v_nonce RECORD; - v_matching_count INTEGER; -BEGIN - -- Add debugging info - RAISE NOTICE 'Verifying token: %, purpose: %, user_id: %', p_token, p_purpose, p_user_id; - - -- Count how many matching tokens exist before verification attempt - SELECT COUNT(*) INTO v_matching_count - FROM public.nonces - WHERE purpose = p_purpose; - - -- Update verification attempt counter and tracking info for all matching tokens - UPDATE public.nonces - SET - verification_attempts = verification_attempts + 1, - last_verification_at = NOW(), - last_verification_ip = COALESCE(p_ip, last_verification_ip), - last_verification_user_agent = COALESCE(p_user_agent, last_verification_user_agent) - WHERE - client_token = crypt(p_token, client_token) - AND purpose = p_purpose; - - -- Find the nonce by token and purpose - -- Modified to handle user-specific tokens better - SELECT * - INTO v_nonce - FROM public.nonces - WHERE - client_token = crypt(p_token, client_token) - AND purpose = p_purpose - -- Only apply user_id filter if the token was created for a specific user - AND ( - -- Case 1: Anonymous token (user_id is NULL in DB) - (user_id IS NULL) - OR - -- Case 2: User-specific token (check if user_id matches) - (user_id = p_user_id) - ) - AND used_at IS NULL - AND NOT revoked - AND expires_at > NOW(); - - -- Check if nonce exists - IF v_nonce.id IS NULL THEN - RETURN jsonb_build_object( - 'valid', false, - 'message', 'Invalid or expired token' - ); - END IF; - - -- Check if max verification attempts exceeded - IF p_max_verification_attempts > 0 AND v_nonce.verification_attempts > p_max_verification_attempts THEN - -- Automatically revoke the token - UPDATE public.nonces - SET - revoked = TRUE, - revoked_reason = 'Maximum verification attempts exceeded' - WHERE id = v_nonce.id; - - RETURN jsonb_build_object( - 'valid', false, - 'message', 'Token revoked due to too many verification attempts', - 'max_attempts_exceeded', true - ); - END IF; - - -- Check scopes if required - IF p_required_scopes IS NOT NULL AND array_length(p_required_scopes, 1) > 0 THEN - -- Fix scope validation to properly check if token scopes contain all required scopes - -- Using array containment check: array1 @> array2 (array1 contains array2) - IF NOT (v_nonce.scopes @> p_required_scopes) THEN - RETURN jsonb_build_object( - 'valid', false, - 'message', 'Token does not have required permissions', - 'token_scopes', v_nonce.scopes, - 'required_scopes', p_required_scopes - ); - END IF; - END IF; - - -- Mark nonce as used - UPDATE public.nonces - SET used_at = NOW() - WHERE id = v_nonce.id; - - -- Return success with metadata - RETURN jsonb_build_object( - 'valid', true, - 'user_id', v_nonce.user_id, - 'metadata', v_nonce.metadata, - 'scopes', v_nonce.scopes, - 'purpose', v_nonce.purpose - ); -END; -$$; - -grant execute on function public.verify_nonce to authenticated,service_role; - --- Create a function to revoke a nonce -CREATE OR REPLACE FUNCTION public.revoke_nonce( - p_id UUID, - p_reason TEXT DEFAULT NULL -) -RETURNS BOOLEAN -LANGUAGE plpgsql -SECURITY DEFINER -AS $$ -DECLARE - v_affected_rows INTEGER; -BEGIN - UPDATE public.nonces - SET - revoked = TRUE, - revoked_reason = p_reason - WHERE - id = p_id - AND used_at IS NULL - AND NOT revoked - RETURNING 1 INTO v_affected_rows; - - RETURN v_affected_rows > 0; -END; -$$; - -grant execute on function public.revoke_nonce to service_role; - --- Create a function to clean up expired nonces -CREATE OR REPLACE FUNCTION kit.cleanup_expired_nonces( - p_older_than_days INTEGER DEFAULT 1, - p_include_used BOOLEAN DEFAULT TRUE, - p_include_revoked BOOLEAN DEFAULT TRUE -) -RETURNS INTEGER -LANGUAGE plpgsql -SECURITY DEFINER -AS $$ -DECLARE - v_count INTEGER; -BEGIN - -- Count and delete expired or used nonces based on parameters - WITH deleted AS ( - DELETE FROM public.nonces - WHERE - ( - -- Expired and unused tokens - (expires_at < NOW() AND used_at IS NULL) - - -- Used tokens older than specified days (if enabled) - OR (p_include_used = TRUE AND used_at < NOW() - (p_older_than_days * interval '1 day')) - - -- Revoked tokens older than specified days (if enabled) - OR (p_include_revoked = TRUE AND revoked = TRUE AND created_at < NOW() - (p_older_than_days * interval '1 day')) - ) - RETURNING 1 - ) - SELECT COUNT(*) INTO v_count FROM deleted; - - RETURN v_count; -END; -$$; - --- Create a function to get token status (for administrative use) -CREATE OR REPLACE FUNCTION public.get_nonce_status( - p_id UUID -) -RETURNS JSONB -LANGUAGE plpgsql -SECURITY DEFINER -AS $$ -DECLARE - v_nonce public.nonces; -BEGIN - SELECT * INTO v_nonce FROM public.nonces WHERE id = p_id; - - IF v_nonce.id IS NULL THEN - RETURN jsonb_build_object('exists', false); - END IF; - - RETURN jsonb_build_object( - 'exists', true, - 'purpose', v_nonce.purpose, - 'user_id', v_nonce.user_id, - 'created_at', v_nonce.created_at, - 'expires_at', v_nonce.expires_at, - 'used_at', v_nonce.used_at, - 'revoked', v_nonce.revoked, - 'revoked_reason', v_nonce.revoked_reason, - 'verification_attempts', v_nonce.verification_attempts, - 'last_verification_at', v_nonce.last_verification_at, - 'last_verification_ip', v_nonce.last_verification_ip, - 'is_valid', (v_nonce.used_at IS NULL AND NOT v_nonce.revoked AND v_nonce.expires_at > NOW()) - ); -END; -$$; - --- Comments for documentation -COMMENT ON TABLE public.nonces IS 'Table for storing one-time tokens with enhanced security and audit features'; -COMMENT ON FUNCTION public.create_nonce IS 'Creates a new one-time token for a specific purpose with enhanced options'; -COMMENT ON FUNCTION public.verify_nonce IS 'Verifies a one-time token, checks scopes, and marks it as used'; -COMMENT ON FUNCTION public.revoke_nonce IS 'Administratively revokes a token to prevent its use'; -COMMENT ON FUNCTION kit.cleanup_expired_nonces IS 'Cleans up expired, used, or revoked tokens based on parameters'; -COMMENT ON FUNCTION public.get_nonce_status IS 'Retrieves the status of a token for administrative purposes'; diff --git a/supabase copy/migrations/20250302043537_mfa-rls-super-admin.sql b/supabase copy/migrations/20250302043537_mfa-rls-super-admin.sql deleted file mode 100644 index 50fc6de..0000000 --- a/supabase copy/migrations/20250302043537_mfa-rls-super-admin.sql +++ /dev/null @@ -1,206 +0,0 @@ -/* -* public.is_aal2 -* Check if the user has aal2 access -*/ -create - or replace function public.is_aal2() returns boolean - set - search_path = '' as -$$ -declare - is_aal2 boolean; -begin - select auth.jwt() ->> 'aal' = 'aal2' into is_aal2; - - return coalesce(is_aal2, false); -end -$$ language plpgsql; - --- Grant access to the function to authenticated users -grant execute on function public.is_aal2() to authenticated; - -/* -* public.is_super_admin -* Check if the user is a super admin. -* A Super Admin is a user that has the role 'super-admin' and has MFA enabled. -*/ -create - or replace function public.is_super_admin() returns boolean - set - search_path = '' as -$$ -declare - is_super_admin boolean; -begin - if not public.is_aal2() then - return false; - end if; - - select (auth.jwt() ->> 'app_metadata')::jsonb ->> 'role' = 'super-admin' into is_super_admin; - - return coalesce(is_super_admin, false); -end -$$ language plpgsql; - --- Grant access to the function to authenticated users -grant execute on function public.is_super_admin() to authenticated; - -/* -* public.is_mfa_compliant -* Check if the user meets MFA requirements if they have MFA enabled. -* If the user has MFA enabled, then the user must have aal2 enabled. Otherwise, the user must have aal1 enabled (default behavior). -*/ -create or replace function public.is_mfa_compliant() returns boolean - set search_path = '' as -$$ -begin - return array[(select auth.jwt()->>'aal')] <@ ( - select - case - when count(id) > 0 then array['aal2'] - else array['aal1', 'aal2'] - end as aal - from auth.mfa_factors - where ((select auth.uid()) = auth.mfa_factors.user_id) and auth.mfa_factors.status = 'verified' - ); -end -$$ language plpgsql security definer; - --- Grant access to the function to authenticated users -grant execute on function public.is_mfa_compliant() to authenticated; - --- MFA Restrictions: --- the following policies are applied to the tables as a --- restrictive policy to ensure that if MFA is enabled, then the policy will be applied. --- For users that have not enabled MFA, the policy will not be applied and will keep the default behavior. - --- Restrict access to accounts if MFA is enabled -create policy restrict_mfa_accounts - on public.accounts - as restrictive - to authenticated - using (public.is_mfa_compliant()); - --- Restrict access to accounts memberships if MFA is enabled -create policy restrict_mfa_accounts_memberships - on public.accounts_memberships - as restrictive - to authenticated - using (public.is_mfa_compliant()); - --- Restrict access to subscriptions if MFA is enabled -create policy restrict_mfa_subscriptions - on public.subscriptions - as restrictive - to authenticated - using (public.is_mfa_compliant()); - --- Restrict access to subscription items if MFA is enabled -create policy restrict_mfa_subscription_items - on public.subscription_items - as restrictive - to authenticated - using (public.is_mfa_compliant()); - --- Restrict access to role permissions if MFA is enabled -create policy restrict_mfa_role_permissions - on public.role_permissions - as restrictive - to authenticated - using (public.is_mfa_compliant()); - --- Restrict access to invitations if MFA is enabled -create policy restrict_mfa_invitations - on public.invitations - as restrictive - to authenticated - using (public.is_mfa_compliant()); - --- Restrict access to orders if MFA is enabled -create policy restrict_mfa_orders - on public.orders - as restrictive - to authenticated - using (public.is_mfa_compliant()); - --- Restrict access to orders items if MFA is enabled -create policy restrict_mfa_order_items - on public.order_items - as restrictive - to authenticated - using (public.is_mfa_compliant()); - --- Restrict access to orders if MFA is enabled -create policy restrict_mfa_notifications - on public.notifications - as restrictive - to authenticated - using (public.is_mfa_compliant()); - --- Super Admin: --- the following policies are applied to the tables as a permissive policy to ensure that --- super admins can access all tables (view only). - --- Allow Super Admins to access the accounts table -create policy super_admins_access_accounts - on public.accounts - as permissive - for select - to authenticated - using (public.is_super_admin()); - --- Allow Super Admins to access the accounts memberships table -create policy super_admins_access_accounts_memberships - on public.accounts_memberships - as permissive - for select - to authenticated - using (public.is_super_admin()); - --- Allow Super Admins to access the subscriptions table -create policy super_admins_access_subscriptions - on public.subscriptions - as permissive - for select - to authenticated - using (public.is_super_admin()); - --- Allow Super Admins to access the subscription items table -create policy super_admins_access_subscription_items - on public.subscription_items - as permissive - for select - to authenticated - using (public.is_super_admin()); - --- Allow Super Admins to access the invitations items table -create policy super_admins_access_invitations - on public.invitations - as permissive - for select - to authenticated - using (public.is_super_admin()); - --- Allow Super Admins to access the orders table -create policy super_admins_access_orders - on public.orders - as permissive - for select - to authenticated - using (public.is_super_admin()); - --- Allow Super Admins to access the order items table -create policy super_admins_access_order_items - on public.order_items - as permissive - for select - to authenticated - using (public.is_super_admin()); - --- Allow Super Admins to access the role permissions table -create policy super_admins_access_role_permissions - on public.role_permissions - as permissive - for select - to authenticated - using (public.is_super_admin()); \ No newline at end of file diff --git a/supabase copy/migrations/20250304104340_set-otp-search-path.sql b/supabase copy/migrations/20250304104340_set-otp-search-path.sql deleted file mode 100644 index 6965797..0000000 --- a/supabase copy/migrations/20250304104340_set-otp-search-path.sql +++ /dev/null @@ -1,203 +0,0 @@ --- Create a function to create a nonce -create or replace function public.create_nonce ( - p_user_id UUID default null, - p_purpose TEXT default null, - p_expires_in_seconds INTEGER default 3600, -- 1 hour by default - p_metadata JSONB default null, - p_scopes text[] default null, - p_revoke_previous BOOLEAN default true -- New parameter to control automatic revocation -) RETURNS JSONB LANGUAGE plpgsql SECURITY DEFINER - set - search_path to '' as $$ -DECLARE - v_client_token TEXT; - v_nonce TEXT; - v_expires_at TIMESTAMPTZ; - v_id UUID; - v_plaintext_token TEXT; - v_revoked_count INTEGER; -BEGIN - -- Revoke previous tokens for the same user and purpose if requested - -- This only applies if a user ID is provided (not for anonymous tokens) - IF p_revoke_previous = TRUE AND p_user_id IS NOT NULL THEN - WITH revoked AS ( - UPDATE public.nonces - SET - revoked = TRUE, - revoked_reason = 'Superseded by new token with same purpose' - WHERE - user_id = p_user_id - AND purpose = p_purpose - AND used_at IS NULL - AND revoked = FALSE - AND expires_at > NOW() - RETURNING 1 - ) - SELECT COUNT(*) INTO v_revoked_count FROM revoked; - END IF; - - -- Generate a 6-digit token - v_plaintext_token := (100000 + floor(random() * 900000))::text; - v_client_token := extensions.crypt(v_plaintext_token, extensions.gen_salt('bf')); - - -- Still generate a secure nonce for internal use - v_nonce := encode(extensions.gen_random_bytes(24), 'base64'); - v_nonce := extensions.crypt(v_nonce, extensions.gen_salt('bf')); - - -- Calculate expiration time - v_expires_at := NOW() + (p_expires_in_seconds * interval '1 second'); - - -- Insert the new nonce - INSERT INTO public.nonces ( - client_token, - nonce, - user_id, - expires_at, - metadata, - purpose, - scopes - ) - VALUES ( - v_client_token, - v_nonce, - p_user_id, - v_expires_at, - COALESCE(p_metadata, '{}'::JSONB), - p_purpose, - COALESCE(p_scopes, '{}'::TEXT[]) - ) - RETURNING id INTO v_id; - - -- Return the token information - -- Note: returning the plaintext token, not the hash - RETURN jsonb_build_object( - 'id', v_id, - 'token', v_plaintext_token, - 'expires_at', v_expires_at, - 'revoked_previous_count', COALESCE(v_revoked_count, 0) - ); -END; -$$; - -grant - execute on function public.create_nonce to service_role; - --- Create a function to verify a nonce --- -create or replace function public.verify_nonce ( - p_token TEXT, - p_purpose TEXT, - p_user_id UUID default null, - p_required_scopes text[] default null, - p_max_verification_attempts INTEGER default 5, - p_ip INET default null, - p_user_agent TEXT default null -) RETURNS JSONB LANGUAGE plpgsql SECURITY DEFINER - set - SEARCH_PATH to '' as $$ -DECLARE - v_nonce RECORD; - v_matching_count INTEGER; -BEGIN - -- Count how many matching tokens exist before verification attempt - SELECT COUNT(*) - INTO v_matching_count - FROM public.nonces - WHERE purpose = p_purpose; - - -- Update verification attempt counter and tracking info for all matching tokens - UPDATE public.nonces - SET verification_attempts = verification_attempts + 1, - last_verification_at = NOW(), - last_verification_ip = COALESCE(p_ip, last_verification_ip), - last_verification_user_agent = COALESCE(p_user_agent, last_verification_user_agent) - WHERE client_token = extensions.crypt(p_token, client_token) - AND purpose = p_purpose; - - -- Find the nonce by token and purpose - -- Modified to handle user-specific tokens better - SELECT * - INTO v_nonce - FROM public.nonces - WHERE client_token = extensions.crypt(p_token, client_token) - AND purpose = p_purpose - -- Only apply user_id filter if the token was created for a specific user - AND ( - -- Case 1: Anonymous token (user_id is NULL in DB) - (user_id IS NULL) - OR - -- Case 2: User-specific token (check if user_id matches) - (user_id = p_user_id) - ) - AND used_at IS NULL - AND NOT revoked - AND expires_at > NOW(); - - -- Check if nonce exists - IF v_nonce.id IS NULL THEN - RETURN jsonb_build_object( - 'valid', false, - 'message', 'Invalid or expired token' - ); - END IF; - - -- Check if max verification attempts exceeded - IF p_max_verification_attempts > 0 AND v_nonce.verification_attempts > p_max_verification_attempts THEN - -- Automatically revoke the token - UPDATE public.nonces - SET revoked = TRUE, - revoked_reason = 'Maximum verification attempts exceeded' - WHERE id = v_nonce.id; - - RETURN jsonb_build_object( - 'valid', false, - 'message', 'Token revoked due to too many verification attempts', - 'max_attempts_exceeded', true - ); - END IF; - - -- Check scopes if required - IF p_required_scopes IS NOT NULL AND array_length(p_required_scopes, 1) > 0 THEN - -- Fix scope validation to properly check if token scopes contain all required scopes - -- Using array containment check: array1 @> array2 (array1 contains array2) - IF NOT (v_nonce.scopes @> p_required_scopes) THEN - RETURN jsonb_build_object( - 'valid', false, - 'message', 'Token does not have required permissions', - 'token_scopes', v_nonce.scopes, - 'required_scopes', p_required_scopes - ); - END IF; - END IF; - - -- Mark nonce as used - UPDATE public.nonces - SET used_at = NOW() - WHERE id = v_nonce.id; - - -- Return success with metadata - RETURN jsonb_build_object( - 'valid', true, - 'user_id', v_nonce.user_id, - 'metadata', v_nonce.metadata, - 'scopes', v_nonce.scopes, - 'purpose', v_nonce.purpose - ); -END; -$$; - -grant - execute on function public.verify_nonce to authenticated, - service_role; - -alter function public.revoke_nonce - set - search_path to ''; - -alter function kit.cleanup_expired_nonces - set - search_path to ''; - -alter function public.get_nonce_status - set - search_path to ''; \ No newline at end of file diff --git a/supabase copy/schemas/00-privileges.sql b/supabase copy/schemas/00-privileges.sql deleted file mode 100644 index 906fedb..0000000 --- a/supabase copy/schemas/00-privileges.sql +++ /dev/null @@ -1,74 +0,0 @@ -/* - * ------------------------------------------------------- - * Section: Revoke default privileges from public schema - * We will revoke all default privileges from public schema on functions to prevent public access to them - * ------------------------------------------------------- - */ - --- Create a private Makerkit schema -create schema if not exists kit; - -create extension if not exists "unaccent" schema kit; - --- We remove all default privileges from public schema on functions to --- prevent public access to them -alter default privileges -revoke -execute on functions -from - public; - -revoke all on schema public -from - public; - -revoke all PRIVILEGES on database "postgres" -from - "anon"; - -revoke all PRIVILEGES on schema "public" -from - "anon"; - -revoke all PRIVILEGES on schema "storage" -from - "anon"; - -revoke all PRIVILEGES on all SEQUENCES in schema "public" -from - "anon"; - -revoke all PRIVILEGES on all SEQUENCES in schema "storage" -from - "anon"; - -revoke all PRIVILEGES on all FUNCTIONS in schema "public" -from - "anon"; - -revoke all PRIVILEGES on all FUNCTIONS in schema "storage" -from - "anon"; - -revoke all PRIVILEGES on all TABLES in schema "public" -from - "anon"; - -revoke all PRIVILEGES on all TABLES in schema "storage" -from - "anon"; - --- We remove all default privileges from public schema on functions to --- prevent public access to them by default -alter default privileges in schema public -revoke -execute on functions -from - anon, - authenticated; - --- we allow the authenticated role to execute functions in the public schema -grant usage on schema public to authenticated; - --- we allow the service_role role to execute functions in the public schema -grant usage on schema public to service_role; diff --git a/supabase copy/schemas/01-enums.sql b/supabase copy/schemas/01-enums.sql deleted file mode 100644 index c9d8b12..0000000 --- a/supabase copy/schemas/01-enums.sql +++ /dev/null @@ -1,65 +0,0 @@ -/* - * ------------------------------------------------------- - * Section: Enums - * We create the enums for the schema - * ------------------------------------------------------- - */ - -/* -* Permissions -- We create the permissions for the Supabase MakerKit. These permissions are used to manage the permissions for the roles -- The permissions are 'roles.manage', 'billing.manage', 'settings.manage', 'members.manage', and 'invites.manage'. -- You can add more permissions as needed. -*/ -create type public.app_permissions as enum( - 'roles.manage', - 'billing.manage', - 'settings.manage', - 'members.manage', - 'invites.manage' -); - -/* -* Subscription Status -- We create the subscription status for the Supabase MakerKit. These statuses are used to manage the status of the subscriptions -- The statuses are 'active', 'trialing', 'past_due', 'canceled', 'unpaid', 'incomplete', 'incomplete_expired', and 'paused'. -- You can add more statuses as needed. -*/ -create type public.subscription_status as ENUM( - 'active', - 'trialing', - 'past_due', - 'canceled', - 'unpaid', - 'incomplete', - 'incomplete_expired', - 'paused' -); - -/* -Payment Status -- We create the payment status for the Supabase MakerKit. These statuses are used to manage the status of the payments -*/ -create type public.payment_status as ENUM('pending', 'succeeded', 'failed'); - -/* -* Billing Provider -- We create the billing provider for the Supabase MakerKit. These providers are used to manage the billing provider for the accounts -- The providers are 'stripe', 'lemon-squeezy', and 'paddle'. -- You can add more providers as needed. -*/ -create type public.billing_provider as ENUM('stripe', 'lemon-squeezy', 'paddle'); - -/* -* Subscription Item Type -- We create the subscription item type for the Supabase MakerKit. These types are used to manage the type of the subscription items -- The types are 'flat', 'per_seat', and 'metered'. -- You can add more types as needed. -*/ -create type public.subscription_item_type as ENUM('flat', 'per_seat', 'metered'); - -/* -* Invitation Type -- We create the invitation type for the Supabase MakerKit. These types are used to manage the type of the invitation -*/ -create type public.invitation as (email text, role varchar(50)); \ No newline at end of file diff --git a/supabase copy/schemas/02-config.sql b/supabase copy/schemas/02-config.sql deleted file mode 100644 index 9453d49..0000000 --- a/supabase copy/schemas/02-config.sql +++ /dev/null @@ -1,145 +0,0 @@ -/* - * ------------------------------------------------------- - * Section: App Configuration - * We create the configuration for the Supabase MakerKit to enable or disable features - * ------------------------------------------------------- - */ - -create table if not exists - public.config ( - enable_team_accounts boolean default true not null, - enable_account_billing boolean default true not null, - enable_team_account_billing boolean default true not null, - billing_provider public.billing_provider default 'stripe' not null - ); - -comment on table public.config is 'Configuration for the Supabase MakerKit.'; - -comment on column public.config.enable_team_accounts is 'Enable team accounts'; - -comment on column public.config.enable_account_billing is 'Enable billing for individual accounts'; - -comment on column public.config.enable_team_account_billing is 'Enable billing for team accounts'; - -comment on column public.config.billing_provider is 'The billing provider to use'; - --- RLS(config) -alter table public.config enable row level security; - --- create config row -insert into - public.config ( - enable_team_accounts, - enable_account_billing, - enable_team_account_billing - ) -values - (true, true, true); - --- Revoke all on accounts table from authenticated and service_role -revoke all on public.config -from - authenticated, - service_role; - --- Open up access to config table for authenticated users and service_role -grant -select - on public.config to authenticated, - service_role; - --- RLS --- SELECT(config): --- Authenticated users can read the config -create policy "public config can be read by authenticated users" on public.config for -select - to authenticated using (true); - --- Function to get the config settings -create -or replace function public.get_config () returns json -set - search_path = '' as $$ -declare - result record; -begin - select - * - from - public.config - limit 1 into result; - - return row_to_json(result); - -end; - -$$ language plpgsql; - --- Automatically set timestamps on tables when a row is inserted or updated -create -or replace function public.trigger_set_timestamps () returns trigger -set - search_path = '' as $$ -begin - if TG_OP = 'INSERT' then - new.created_at = now(); - - new.updated_at = now(); - - else - new.updated_at = now(); - - new.created_at = old.created_at; - - end if; - - return NEW; - -end -$$ language plpgsql; - --- Automatically set user tracking on tables when a row is inserted or updated -create -or replace function public.trigger_set_user_tracking () returns trigger -set - search_path = '' as $$ -begin - if TG_OP = 'INSERT' then - new.created_by = auth.uid(); - new.updated_by = auth.uid(); - - else - new.updated_by = auth.uid(); - - new.created_by = old.created_by; - - end if; - - return NEW; - -end -$$ language plpgsql; - -grant -execute on function public.get_config () to authenticated, -service_role; - --- Function "public.is_set" --- Check if a field is set in the config -create -or replace function public.is_set (field_name text) returns boolean -set - search_path = '' as $$ -declare - result boolean; -begin - execute format('select %I from public.config limit 1', field_name) into result; - - return result; - -end; - -$$ language plpgsql; - -grant -execute on function public.is_set (text) to authenticated; diff --git a/supabase copy/schemas/03-accounts.sql b/supabase copy/schemas/03-accounts.sql deleted file mode 100644 index 874408b..0000000 --- a/supabase copy/schemas/03-accounts.sql +++ /dev/null @@ -1,560 +0,0 @@ -/* - * ------------------------------------------------------- - * Section: Accounts - * We create the schema for the accounts. Accounts are the top level entity in the Supabase MakerKit. They can be team or personal accounts. - * ------------------------------------------------------- - */ - --- Accounts table -create table if not exists - public.accounts ( - id uuid unique not null default extensions.uuid_generate_v4 (), - primary_owner_user_id uuid references auth.users on delete cascade not null default auth.uid (), - name varchar(255) not null, - slug text unique, - email varchar(320) unique, - is_personal_account boolean default false not null, - updated_at timestamp with time zone, - created_at timestamp with time zone, - created_by uuid references auth.users, - updated_by uuid references auth.users, - picture_url varchar(1000), - public_data jsonb default '{}'::jsonb not null, - primary key (id) - ); - -comment on table public.accounts is 'Accounts are the top level entity in the Supabase MakerKit. They can be team or personal accounts.'; - -comment on column public.accounts.is_personal_account is 'Whether the account is a personal account or not'; - -comment on column public.accounts.name is 'The name of the account'; - -comment on column public.accounts.slug is 'The slug of the account'; - -comment on column public.accounts.primary_owner_user_id is 'The primary owner of the account'; - -comment on column public.accounts.email is 'The email of the account. For teams, this is the email of the team (if any)'; - --- Enable RLS on the accounts table -alter table "public"."accounts" enable row level security; - --- Revoke all on accounts table from authenticated and service_role -revoke all on public.accounts -from - authenticated, - service_role; - --- Open up access to accounts -grant -select -, - insert, -update, -delete on table public.accounts to authenticated, -service_role; - --- constraint that conditionally allows nulls on the slug ONLY if --- personal_account is true -alter table public.accounts -add constraint accounts_slug_null_if_personal_account_true check ( - ( - is_personal_account = true - and slug is null - ) - or ( - is_personal_account = false - and slug is not null - ) -); - --- Indexes -create index if not exists ix_accounts_primary_owner_user_id on public.accounts (primary_owner_user_id); - -create index if not exists ix_accounts_is_personal_account on public.accounts (is_personal_account); - --- constraint to ensure that the primary_owner_user_id is unique for personal accounts -create unique index unique_personal_account on public.accounts (primary_owner_user_id) -where - is_personal_account = true; - --- RLS on the accounts table --- UPDATE(accounts): --- Team owners can update their accounts -create policy accounts_self_update on public.accounts -for update - to authenticated using ( - ( - select - auth.uid () - ) = primary_owner_user_id - ) -with - check ( - ( - select - auth.uid () - ) = primary_owner_user_id - ); - --- Function "public.transfer_team_account_ownership" --- Function to transfer the ownership of a team account to another user -create -or replace function public.transfer_team_account_ownership (target_account_id uuid, new_owner_id uuid) returns void -set - search_path = '' as $$ -begin - if current_user not in('service_role') then - raise exception 'You do not have permission to transfer account ownership'; - end if; - - -- verify the user is already a member of the account - if not exists( - select - 1 - from - public.accounts_memberships - where - target_account_id = account_id - and user_id = new_owner_id) then - raise exception 'The new owner must be a member of the account'; - end if; - - -- update the primary owner of the account - update - public.accounts - set - primary_owner_user_id = new_owner_id - where - id = target_account_id - and is_personal_account = false; - - -- update membership assigning it the hierarchy role - update - public.accounts_memberships - set - account_role =( - public.get_upper_system_role()) - where - target_account_id = account_id - and user_id = new_owner_id - and account_role <>( - public.get_upper_system_role()); - -end; - -$$ language plpgsql; - -grant -execute on function public.transfer_team_account_ownership (uuid, uuid) to service_role; - --- Function "public.is_account_owner" --- Function to check if a user is the primary owner of an account -create -or replace function public.is_account_owner (account_id uuid) returns boolean -set - search_path = '' as $$ - select - exists( - select - 1 - from - public.accounts - where - id = is_account_owner.account_id - and primary_owner_user_id = auth.uid()); -$$ language sql; - -grant -execute on function public.is_account_owner (uuid) to authenticated, -service_role; - --- Function "kit.protect_account_fields" --- Function to protect account fields from being updated -create -or replace function kit.protect_account_fields () returns trigger as $$ -begin - if current_user in('authenticated', 'anon') then - if new.id <> old.id or new.is_personal_account <> - old.is_personal_account or new.primary_owner_user_id <> - old.primary_owner_user_id or new.email <> old.email then - raise exception 'You do not have permission to update this field'; - - end if; - - end if; - - return NEW; - -end -$$ language plpgsql -set - search_path = ''; - --- trigger to protect account fields -create trigger protect_account_fields before -update on public.accounts for each row -execute function kit.protect_account_fields (); - --- Function "public.get_upper_system_role" --- Function to get the highest system role for an account -create -or replace function public.get_upper_system_role () returns varchar -set - search_path = '' as $$ -declare - role varchar(50); -begin - select name from public.roles - where hierarchy_level = 1 into role; - - return role; -end; -$$ language plpgsql; - -grant -execute on function public.get_upper_system_role () to service_role; - --- Function "kit.add_current_user_to_new_account" --- Trigger to add the current user to a new account as the primary owner -create -or replace function kit.add_current_user_to_new_account () returns trigger language plpgsql security definer -set - search_path = '' as $$ -begin - if new.primary_owner_user_id = auth.uid() then - insert into public.accounts_memberships( - account_id, - user_id, - account_role) - values( - new.id, - auth.uid(), - public.get_upper_system_role()); - - end if; - - return NEW; - -end; - -$$; - --- trigger the function whenever a new account is created -create trigger "add_current_user_to_new_account" -after insert on public.accounts for each row -when (new.is_personal_account = false) -execute function kit.add_current_user_to_new_account (); - --- create a trigger to update the account email when the primary owner email is updated -create -or replace function kit.handle_update_user_email () returns trigger language plpgsql security definer -set - search_path = '' as $$ -begin - update - public.accounts - set - email = new.email - where - primary_owner_user_id = new.id - and is_personal_account = true; - - return new; - -end; - -$$; - --- trigger the function every time a user email is updated only if the user is the primary owner of the account and --- the account is personal account -create trigger "on_auth_user_updated" -after -update of email on auth.users for each row -execute procedure kit.handle_update_user_email (); - - -/** - * ------------------------------------------------------- - * Section: Slugify - * We create the schema for the slugify functions. Slugify functions are used to create slugs from strings. - * We use this for ensure unique slugs for accounts. - * ------------------------------------------------------- - */ --- Create a function to slugify a string --- useful for turning an account name into a unique slug -create -or replace function kit.slugify ("value" text) returns text as $$ - -- removes accents (diacritic signs) from a given string -- - with "unaccented" as( - select - kit.unaccent("value") as "value" -), --- lowercases the string -"lowercase" as( - select - lower("value") as "value" - from - "unaccented" -), --- remove single and double quotes -"removed_quotes" as( - select - regexp_replace("value", '[''"]+', '', - 'gi') as "value" - from - "lowercase" -), --- replaces anything that's not a letter, number, hyphen('-'), or underscore('_') with a hyphen('-') -"hyphenated" as( - select - regexp_replace("value", '[^a-z0-9\\-_]+', '-', - 'gi') as "value" - from - "removed_quotes" -), --- trims hyphens('-') if they exist on the head or tail of --- the string -"trimmed" as( - select - regexp_replace(regexp_replace("value", '\-+$', - ''), '^\-', '') as "value" from "hyphenated" -) - select - "value" - from - "trimmed"; -$$ language SQL strict immutable -set - search_path to ''; - -grant -execute on function kit.slugify (text) to service_role, -authenticated; - - --- Function "kit.set_slug_from_account_name" --- Set the slug from the account name and increment if the slug exists -create -or replace function kit.set_slug_from_account_name () returns trigger language plpgsql security definer -set - search_path = '' as $$ -declare - sql_string varchar; - tmp_slug varchar; - increment integer; - tmp_row record; - tmp_row_count integer; -begin - tmp_row_count = 1; - - increment = 0; - - while tmp_row_count > 0 loop - if increment > 0 then - tmp_slug = kit.slugify(new.name || ' ' || increment::varchar); - - else - tmp_slug = kit.slugify(new.name); - - end if; - - sql_string = format('select count(1) cnt from public.accounts where slug = ''' || tmp_slug || - '''; '); - - for tmp_row in execute (sql_string) - loop - raise notice 'tmp_row %', tmp_row; - - tmp_row_count = tmp_row.cnt; - - end loop; - - increment = increment +1; - - end loop; - - new.slug := tmp_slug; - - return NEW; - -end -$$; - --- Create a trigger to set the slug from the account name -create trigger "set_slug_from_account_name" before insert on public.accounts for each row when ( - NEW.name is not null - and NEW.slug is null - and NEW.is_personal_account = false -) -execute procedure kit.set_slug_from_account_name (); - --- Create a trigger when a name is updated to update the slug -create trigger "update_slug_from_account_name" before -update on public.accounts for each row when ( - NEW.name is not null - and NEW.name <> OLD.name - and NEW.is_personal_account = false -) -execute procedure kit.set_slug_from_account_name (); - --- Function "kit.setup_new_user" --- Setup a new user account after user creation -create -or replace function kit.setup_new_user () returns trigger language plpgsql security definer -set - search_path = '' as $$ -declare - user_name text; - picture_url text; -begin - if new.raw_user_meta_data ->> 'name' is not null then - user_name := new.raw_user_meta_data ->> 'name'; - - end if; - - if user_name is null and new.email is not null then - user_name := split_part(new.email, '@', 1); - - end if; - - if user_name is null then - user_name := ''; - - end if; - - if new.raw_user_meta_data ->> 'avatar_url' is not null then - picture_url := new.raw_user_meta_data ->> 'avatar_url'; - else - picture_url := null; - end if; - - insert into public.accounts( - id, - primary_owner_user_id, - name, - is_personal_account, - picture_url, - email) - values ( - new.id, - new.id, - user_name, - true, - picture_url, - new.email); - - return new; - -end; - -$$; - --- trigger the function every time a user is created -create trigger on_auth_user_created -after insert on auth.users for each row -execute procedure kit.setup_new_user (); - -/** - * ------------------------------------------------------- - * Section: Functions - * We create the schema for the functions - * ------------------------------------------------------- - */ --- Function "public.create_team_account" --- Create a team account if team accounts are enabled -create -or replace function public.create_team_account (account_name text) returns public.accounts -set - search_path = '' as $$ -declare - new_account public.accounts; -begin - if (not public.is_set('enable_team_accounts')) then - raise exception 'Team accounts are not enabled'; - end if; - - insert into public.accounts( - name, - is_personal_account) - values ( - account_name, - false) -returning - * into new_account; - - return new_account; - -end; - -$$ language plpgsql; - -grant -execute on function public.create_team_account (text) to authenticated, -service_role; - --- RLS(public.accounts) --- Authenticated users can create team accounts -create policy create_org_account on public.accounts for insert to authenticated -with - check ( - public.is_set ('enable_team_accounts') - and public.accounts.is_personal_account = false - ); - --- RLS(public.accounts) --- Authenticated users can delete team accounts -create policy delete_team_account - on public.accounts - for delete - to authenticated - using ( - auth.uid() = primary_owner_user_id - ); - --- Functions "public.get_account_members" --- Function to get the members of an account by the account slug -create -or replace function public.get_account_members (account_slug text) returns table ( - id uuid, - user_id uuid, - account_id uuid, - role varchar(50), - role_hierarchy_level int, - primary_owner_user_id uuid, - name varchar, - email varchar, - picture_url varchar, - created_at timestamptz, - updated_at timestamptz -) language plpgsql -set - search_path = '' as $$ -begin - return QUERY - select - acc.id, - am.user_id, - am.account_id, - am.account_role, - r.hierarchy_level, - a.primary_owner_user_id, - acc.name, - acc.email, - acc.picture_url, - am.created_at, - am.updated_at - from - public.accounts_memberships am - join public.accounts a on a.id = am.account_id - join public.accounts acc on acc.id = am.user_id - join public.roles r on r.name = am.account_role - where - a.slug = account_slug; - -end; - -$$; - -grant -execute on function public.get_account_members (text) to authenticated, -service_role; \ No newline at end of file diff --git a/supabase copy/schemas/04-roles.sql b/supabase copy/schemas/04-roles.sql deleted file mode 100644 index d1a2223..0000000 --- a/supabase copy/schemas/04-roles.sql +++ /dev/null @@ -1,30 +0,0 @@ -/* - * ------------------------------------------------------- - * Section: Roles - * We create the schema for the roles. Roles are the roles for an account. For example, an account might have the roles 'owner', 'admin', and 'member'. - * ------------------------------------------------------- - */ - --- Roles Table -create table if not exists - public.roles ( - name varchar(50) not null, - hierarchy_level int not null check (hierarchy_level > 0), - primary key (name), - unique (hierarchy_level) - ); - --- Revoke all on roles table from authenticated and service_role -revoke all on public.roles -from - authenticated, - service_role; - --- Open up access to roles table for authenticated users and service_role -grant -select -on table public.roles to authenticated, -service_role; - --- RLS -alter table public.roles enable row level security; \ No newline at end of file diff --git a/supabase copy/schemas/05-memberships.sql b/supabase copy/schemas/05-memberships.sql deleted file mode 100644 index 5966a60..0000000 --- a/supabase copy/schemas/05-memberships.sql +++ /dev/null @@ -1,312 +0,0 @@ -/* - * ------------------------------------------------------- - * Section: Memberships - * We create the schema for the memberships. Memberships are the memberships for an account. For example, a user might be a member of an account with the role 'owner'. - * ------------------------------------------------------- - */ - --- Account Memberships table -create table if not exists - public.accounts_memberships ( - user_id uuid references auth.users on delete cascade not null, - account_id uuid references public.accounts (id) on delete cascade not null, - account_role varchar(50) references public.roles (name) not null, - created_at timestamptz default current_timestamp not null, - updated_at timestamptz default current_timestamp not null, - created_by uuid references auth.users, - updated_by uuid references auth.users, - primary key (user_id, account_id) - ); - -comment on table public.accounts_memberships is 'The memberships for an account'; - -comment on column public.accounts_memberships.account_id is 'The account the membership is for'; - -comment on column public.accounts_memberships.account_role is 'The role for the membership'; - --- Revoke all on accounts_memberships table from authenticated and service_role -revoke all on public.accounts_memberships -from - authenticated, - service_role; - --- Open up access to accounts_memberships table for authenticated users and service_role -grant -select -, - insert, -update, -delete on table public.accounts_memberships to authenticated, -service_role; - --- Indexes on the accounts_memberships table -create index ix_accounts_memberships_account_id on public.accounts_memberships (account_id); - -create index ix_accounts_memberships_user_id on public.accounts_memberships (user_id); - -create index ix_accounts_memberships_account_role on public.accounts_memberships (account_role); - --- Enable RLS on the accounts_memberships table -alter table public.accounts_memberships enable row level security; - --- Function "kit.prevent_account_owner_membership_delete" --- Trigger to prevent a primary owner from being removed from an account -create -or replace function kit.prevent_account_owner_membership_delete () returns trigger -set - search_path = '' as $$ -begin - if exists( - select - 1 - from - public.accounts - where - id = old.account_id - and primary_owner_user_id = old.user_id) then - raise exception 'The primary account owner cannot be removed from the account membership list'; - -end if; - - return old; - -end; - -$$ language plpgsql; - -create -or replace trigger prevent_account_owner_membership_delete_check before delete on public.accounts_memberships for each row -execute function kit.prevent_account_owner_membership_delete (); - --- Function "kit.prevent_memberships_update" --- Trigger to prevent updates to account memberships with the exception of the account_role -create -or replace function kit.prevent_memberships_update () returns trigger -set - search_path = '' as $$ -begin - if new.account_role <> old.account_role then - return new; - end if; - - raise exception 'Only the account_role can be updated'; - -end; $$ language plpgsql; - -create -or replace trigger prevent_memberships_update_check before -update on public.accounts_memberships for each row -execute function kit.prevent_memberships_update (); - --- Function "public.has_role_on_account" --- Function to check if a user has a role on an account -create -or replace function public.has_role_on_account ( - account_id uuid, - account_role varchar(50) default null -) returns boolean language sql security definer -set - search_path = '' as $$ - select - exists( - select - 1 - from - public.accounts_memberships membership - where - membership.user_id = (select auth.uid()) - and membership.account_id = has_role_on_account.account_id - and((membership.account_role = has_role_on_account.account_role - or has_role_on_account.account_role is null))); -$$; - -grant -execute on function public.has_role_on_account (uuid, varchar) to authenticated; - --- Function "public.is_team_member" --- Check if a user is a team member of an account or not -create -or replace function public.is_team_member (account_id uuid, user_id uuid) returns boolean language sql security definer -set - search_path = '' as $$ - select - exists( - select - 1 - from - public.accounts_memberships membership - where - public.has_role_on_account(account_id) - and membership.user_id = is_team_member.user_id - and membership.account_id = is_team_member.account_id); -$$; - -grant -execute on function public.is_team_member (uuid, uuid) to authenticated, -service_role; - --- RLS --- SELECT(roles) --- authenticated users can query roles -create policy roles_read on public.roles for -select - to authenticated using ( - true - ); - --- Function "public.can_action_account_member" --- Check if a user can perform management actions on an account member -create -or replace function public.can_action_account_member (target_team_account_id uuid, target_user_id uuid) returns boolean -set - search_path = '' as $$ -declare - permission_granted boolean; - target_user_hierarchy_level int; - current_user_hierarchy_level int; - is_account_owner boolean; - target_user_role varchar(50); -begin - if target_user_id = auth.uid() then - raise exception 'You cannot update your own account membership with this function'; - end if; - - -- an account owner can action any member of the account - if public.is_account_owner(target_team_account_id) then - return true; - end if; - - -- check the target user is the primary owner of the account - select - exists ( - select - 1 - from - public.accounts - where - id = target_team_account_id - and primary_owner_user_id = target_user_id) into is_account_owner; - - if is_account_owner then - raise exception 'The primary account owner cannot be actioned'; - end if; - - -- validate the auth user has the required permission on the account - -- to manage members of the account - select - public.has_permission(auth.uid(), target_team_account_id, - 'members.manage'::public.app_permissions) into - permission_granted; - - -- if the user does not have the required permission, raise an exception - if not permission_granted then - raise exception 'You do not have permission to action a member from this account'; - end if; - - -- get the role of the target user - select - am.account_role, - r.hierarchy_level - from - public.accounts_memberships as am - join - public.roles as r on am.account_role = r.name - where - am.account_id = target_team_account_id - and am.user_id = target_user_id - into target_user_role, target_user_hierarchy_level; - - -- get the hierarchy level of the current user - select - r.hierarchy_level into current_user_hierarchy_level - from - public.roles as r - join - public.accounts_memberships as am on r.name = am.account_role - where - am.account_id = target_team_account_id - and am.user_id = auth.uid(); - - if target_user_role is null then - raise exception 'The target user does not have a role on the account'; - end if; - - if current_user_hierarchy_level is null then - raise exception 'The current user does not have a role on the account'; - end if; - - -- check the current user has a higher role than the target user - if current_user_hierarchy_level >= target_user_hierarchy_level then - raise exception 'You do not have permission to action a member from this account'; - end if; - - return true; - -end; - -$$ language plpgsql; - -grant -execute on function public.can_action_account_member (uuid, uuid) to authenticated, -service_role; - --- RLS --- SELECT(accounts_memberships): --- Users can read their team members account memberships -create policy accounts_memberships_read on public.accounts_memberships for -select - to authenticated using ( - ( - ( - select - auth.uid () - ) = user_id - ) - or is_team_member (account_id, user_id) - ); - -create -or replace function public.is_account_team_member (target_account_id uuid) returns boolean -set - search_path = '' as $$ - select exists( - select 1 - from public.accounts_memberships as membership - where public.is_team_member (membership.account_id, target_account_id) - ); -$$ language sql; - -grant -execute on function public.is_account_team_member (uuid) to authenticated, -service_role; - --- RLS on the accounts table --- SELECT(accounts): --- Users can read the an account if --- - they are the primary owner of the account --- - they have a role on the account --- - they are reading an account of the same team -create policy accounts_read on public.accounts for -select - to authenticated using ( - ( - ( - select - auth.uid () - ) = primary_owner_user_id - ) - or public.has_role_on_account (id) - or public.is_account_team_member (id) - ); - --- DELETE(accounts_memberships): --- Users with the required role can remove members from an account or remove their own -create policy accounts_memberships_delete on public.accounts_memberships for delete to authenticated using ( - ( - user_id = ( - select - auth.uid () - ) - ) - or public.can_action_account_member (account_id, user_id) -); \ No newline at end of file diff --git a/supabase copy/schemas/06-roles-permissions.sql b/supabase copy/schemas/06-roles-permissions.sql deleted file mode 100644 index 6224625..0000000 --- a/supabase copy/schemas/06-roles-permissions.sql +++ /dev/null @@ -1,237 +0,0 @@ -/* - * ------------------------------------------------------- - * Section: Role Permissions - * We create the schema for the role permissions. Role permissions are the permissions for a role. - * For example, the 'owner' role might have the 'roles.manage' permission. - * ------------------------------------------------------- - - */ --- Create table for roles permissions -create table if not exists - public.role_permissions ( - id bigint generated by default as identity primary key, - role varchar(50) references public.roles (name) not null, - permission public.app_permissions not null, - unique (role, permission) - ); - -comment on table public.role_permissions is 'The permissions for a role'; - -comment on column public.role_permissions.role is 'The role the permission is for'; - -comment on column public.role_permissions.permission is 'The permission for the role'; - --- Indexes on the role_permissions table -create index ix_role_permissions_role on public.role_permissions (role); - --- Revoke all on role_permissions table from authenticated and service_role -revoke all on public.role_permissions -from - authenticated, - service_role; - --- Open up access to role_permissions table for authenticated users and service_role -grant -select -, - insert, -update, -delete on table public.role_permissions to service_role; - --- Authenticated users can read role permissions -grant -select - on table public.role_permissions to authenticated; - --- Function "public.has_permission" --- Create a function to check if a user has a permission -create -or replace function public.has_permission ( - user_id uuid, - account_id uuid, - permission_name public.app_permissions -) returns boolean -set - search_path = '' as $$ -begin - return exists( - select - 1 - from - public.accounts_memberships - join public.role_permissions on - accounts_memberships.account_role = - role_permissions.role - where - accounts_memberships.user_id = has_permission.user_id - and accounts_memberships.account_id = has_permission.account_id - and role_permissions.permission = has_permission.permission_name); - -end; - -$$ language plpgsql; - -grant -execute on function public.has_permission (uuid, uuid, public.app_permissions) to authenticated, -service_role; - --- Function "public.has_more_elevated_role" --- Check if a user has a more elevated role than the target role -create -or replace function public.has_more_elevated_role ( - target_user_id uuid, - target_account_id uuid, - role_name varchar -) returns boolean -set - search_path = '' as $$ -declare - declare is_primary_owner boolean; - user_role_hierarchy_level int; - target_role_hierarchy_level int; -begin - -- Check if the user is the primary owner of the account - select - exists ( - select - 1 - from - public.accounts - where - id = target_account_id - and primary_owner_user_id = target_user_id) into is_primary_owner; - - -- If the user is the primary owner, they have the highest role and can - -- perform any action - if is_primary_owner then - return true; - end if; - - -- Get the hierarchy level of the user's role within the account - select - hierarchy_level into user_role_hierarchy_level - from - public.roles - where - name =( - select - account_role - from - public.accounts_memberships - where - account_id = target_account_id - and target_user_id = user_id); - - if user_role_hierarchy_level is null then - return false; - end if; - - -- Get the hierarchy level of the target role - select - hierarchy_level into target_role_hierarchy_level - from - public.roles - where - name = role_name; - - -- If the target role does not exist, the user cannot perform the action - if target_role_hierarchy_level is null then - return false; - end if; - - -- If the user's role is higher than the target role, they can perform - -- the action - return user_role_hierarchy_level < target_role_hierarchy_level; - -end; - -$$ language plpgsql; - -grant -execute on function public.has_more_elevated_role (uuid, uuid, varchar) to authenticated, -service_role; - --- Function "public.has_same_role_hierarchy_level" --- Check if a user has the same role hierarchy level as the target role -create -or replace function public.has_same_role_hierarchy_level ( - target_user_id uuid, - target_account_id uuid, - role_name varchar -) returns boolean -set - search_path = '' as $$ -declare - is_primary_owner boolean; - user_role_hierarchy_level int; - target_role_hierarchy_level int; -begin - -- Check if the user is the primary owner of the account - select - exists ( - select - 1 - from - public.accounts - where - id = target_account_id - and primary_owner_user_id = target_user_id) into is_primary_owner; - - -- If the user is the primary owner, they have the highest role and can perform any action - if is_primary_owner then - return true; - end if; - - -- Get the hierarchy level of the user's role within the account - select - hierarchy_level into user_role_hierarchy_level - from - public.roles - where - name =( - select - account_role - from - public.accounts_memberships - where - account_id = target_account_id - and target_user_id = user_id); - - -- If the user does not have a role in the account, they cannot perform the action - if user_role_hierarchy_level is null then - return false; - end if; - - -- Get the hierarchy level of the target role - select - hierarchy_level into target_role_hierarchy_level - from - public.roles - where - name = role_name; - - -- If the target role does not exist, the user cannot perform the action - if target_role_hierarchy_level is null then - return false; - end if; - - -- check the user's role hierarchy level is the same as the target role - return user_role_hierarchy_level = target_role_hierarchy_level; - -end; - -$$ language plpgsql; - -grant -execute on function public.has_same_role_hierarchy_level (uuid, uuid, varchar) to authenticated, -service_role; - --- Enable RLS on the role_permissions table -alter table public.role_permissions enable row level security; - --- RLS on the role_permissions table --- SELECT(role_permissions): --- Authenticated Users can read global permissions -create policy role_permissions_read on public.role_permissions for -select - to authenticated using (true); diff --git a/supabase copy/schemas/07-invitations.sql b/supabase copy/schemas/07-invitations.sql deleted file mode 100644 index 57e8188..0000000 --- a/supabase copy/schemas/07-invitations.sql +++ /dev/null @@ -1,354 +0,0 @@ -/* - * ------------------------------------------------------- - * Section: Invitations - * We create the schema for the invitations. Invitations are the invitations for an account sent to a user to join the account. - * ------------------------------------------------------- - */ - -create table if not exists - public.invitations ( - id serial primary key, - email varchar(255) not null, - account_id uuid references public.accounts (id) on delete cascade not null, - invited_by uuid references auth.users on delete cascade not null, - role varchar(50) references public.roles (name) not null, - invite_token varchar(255) unique not null, - created_at timestamptz default current_timestamp not null, - updated_at timestamptz default current_timestamp not null, - expires_at timestamptz default current_timestamp + interval '7 days' not null, - unique (email, account_id) - ); - -comment on table public.invitations is 'The invitations for an account'; - -comment on column public.invitations.account_id is 'The account the invitation is for'; - -comment on column public.invitations.invited_by is 'The user who invited the user'; - -comment on column public.invitations.role is 'The role for the invitation'; - -comment on column public.invitations.invite_token is 'The token for the invitation'; - -comment on column public.invitations.expires_at is 'The expiry date for the invitation'; - -comment on column public.invitations.email is 'The email of the user being invited'; - --- Indexes on the invitations table -create index ix_invitations_account_id on public.invitations (account_id); - --- Revoke all on invitations table from authenticated and service_role -revoke all on public.invitations -from - authenticated, - service_role; - --- Open up access to invitations table for authenticated users and service_role -grant -select -, - insert, -update, -delete on table public.invitations to authenticated, -service_role; - --- Enable RLS on the invitations table -alter table public.invitations enable row level security; - --- Function "kit.check_team_account" --- Function to check if the account is a team account or not when inserting or updating an invitation -create -or replace function kit.check_team_account () returns trigger -set - search_path = '' as $$ -begin - if( - select - is_personal_account - from - public.accounts - where - id = new.account_id) then - raise exception 'Account must be an team account'; - - end if; - - return NEW; - -end; - -$$ language plpgsql; - -create trigger only_team_accounts_check before insert -or -update on public.invitations for each row -execute procedure kit.check_team_account (); - --- RLS on the invitations table --- SELECT(invitations): --- Users can read invitations to users of an account they are a member of -create policy invitations_read_self on public.invitations for -select - to authenticated using (public.has_role_on_account (account_id)); - --- INSERT(invitations): --- Users can create invitations to users of an account they are --- a member of and have the 'invites.manage' permission AND the target role is not higher than the user's role -create policy invitations_create_self on public.invitations for insert to authenticated -with - check ( - public.is_set ('enable_team_accounts') - and public.has_permission ( - ( - select - auth.uid () - ), - account_id, - 'invites.manage'::public.app_permissions - ) - and (public.has_more_elevated_role ( - ( - select - auth.uid () - ), - account_id, - role - ) or public.has_same_role_hierarchy_level( - ( - select - auth.uid () - ), - account_id, - role - )) - ); - --- UPDATE(invitations): --- Users can update invitations to users of an account they are a member of and have the 'invites.manage' permission AND --- the target role is not higher than the user's role -create policy invitations_update on public.invitations -for update - to authenticated using ( - public.has_permission ( - ( - select - auth.uid () - ), - account_id, - 'invites.manage'::public.app_permissions - ) - and public.has_more_elevated_role ( - ( - select - auth.uid () - ), - account_id, - role - ) - ) -with - check ( - public.has_permission ( - ( - select - auth.uid () - ), - account_id, - 'invites.manage'::public.app_permissions - ) - and public.has_more_elevated_role ( - ( - select - auth.uid () - ), - account_id, - role - ) - ); - --- DELETE(public.invitations): --- Users can delete invitations to users of an account they are a member of and have the 'invites.manage' permission -create policy invitations_delete on public.invitations for delete to authenticated using ( - has_role_on_account (account_id) - and public.has_permission ( - ( - select - auth.uid () - ), - account_id, - 'invites.manage'::public.app_permissions - ) -); - --- Functions "public.accept_invitation" --- Function to accept an invitation to an account -create -or replace function accept_invitation (token text, user_id uuid) returns uuid -set - search_path = '' as $$ -declare - target_account_id uuid; - target_role varchar(50); -begin - select - account_id, - role into target_account_id, - target_role - from - public.invitations - where - invite_token = token - and expires_at > now(); - - if not found then - raise exception 'Invalid or expired invitation token'; - end if; - - insert into public.accounts_memberships( - user_id, - account_id, - account_role) - values ( - accept_invitation.user_id, - target_account_id, - target_role); - - delete from public.invitations - where invite_token = token; - - return target_account_id; -end; - -$$ language plpgsql; - -grant -execute on function accept_invitation (text, uuid) to service_role; - --- Function "public.create_invitation" --- create an invitation to an account -create -or replace function public.create_invitation (account_id uuid, email text, role varchar(50)) returns public.invitations -set - search_path = '' as $$ -declare - new_invitation public.invitations; - invite_token text; -begin - invite_token := extensions.uuid_generate_v4(); - - insert into public.invitations( - email, - account_id, - invited_by, - role, - invite_token) - values ( - email, - account_id, - auth.uid(), - role, - invite_token) -returning - * into new_invitation; - - return new_invitation; - -end; - -$$ language plpgsql; - - --- Function "public.get_account_invitations" --- List the account invitations by the account slug -create -or replace function public.get_account_invitations (account_slug text) returns table ( - id integer, - email varchar(255), - account_id uuid, - invited_by uuid, - role varchar(50), - created_at timestamptz, - updated_at timestamptz, - expires_at timestamptz, - inviter_name varchar, - inviter_email varchar -) -set - search_path = '' as $$ -begin - return query - select - invitation.id, - invitation.email, - invitation.account_id, - invitation.invited_by, - invitation.role, - invitation.created_at, - invitation.updated_at, - invitation.expires_at, - account.name, - account.email - from - public.invitations as invitation - join public.accounts as account on invitation.account_id = account.id - where - account.slug = account_slug; - -end; - -$$ language plpgsql; - -grant -execute on function public.get_account_invitations (text) to authenticated, -service_role; - --- Function "public.add_invitations_to_account" --- Add invitations to an account -create -or replace function public.add_invitations_to_account ( - account_slug text, - invitations public.invitation[] -) returns public.invitations[] -set - search_path = '' as $$ -declare - new_invitation public.invitations; - all_invitations public.invitations[] := array[]::public.invitations[]; - invite_token text; - email text; - role varchar(50); -begin - FOREACH email, - role in array invitations loop - invite_token := extensions.uuid_generate_v4(); - - insert into public.invitations( - email, - account_id, - invited_by, - role, - invite_token) - values ( - email, -( - select - id - from - public.accounts - where - slug = account_slug), auth.uid(), role, invite_token) - returning - * into new_invitation; - - all_invitations := array_append(all_invitations, new_invitation); - - end loop; - - return all_invitations; - -end; - -$$ language plpgsql; - -grant -execute on function public.add_invitations_to_account (text, public.invitation[]) to authenticated, -service_role; diff --git a/supabase copy/schemas/08-billing-customers.sql b/supabase copy/schemas/08-billing-customers.sql deleted file mode 100644 index 7dff01f..0000000 --- a/supabase copy/schemas/08-billing-customers.sql +++ /dev/null @@ -1,66 +0,0 @@ -/* - * ------------------------------------------------------- - * Section: Billing Customers - * We create the schema for the billing customers. Billing customers are the customers for an account in the billing provider. For example, a user might have a customer in the billing provider with the customer ID 'cus_123'. - * ------------------------------------------------------- - - */ --- Billing Customers table -create table - public.billing_customers ( - account_id uuid references public.accounts (id) on delete cascade not null, - id serial primary key, - email text, - provider public.billing_provider not null, - customer_id text not null, - unique (account_id, customer_id, provider) - ); - -comment on table public.billing_customers is 'The billing customers for an account'; - -comment on column public.billing_customers.account_id is 'The account the billing customer is for'; - -comment on column public.billing_customers.provider is 'The provider of the billing customer'; - -comment on column public.billing_customers.customer_id is 'The customer ID for the billing customer'; - -comment on column public.billing_customers.email is 'The email of the billing customer'; - --- Indexes on the billing_customers table -create index ix_billing_customers_account_id on public.billing_customers (account_id); - --- Revoke all on billing_customers table from authenticated and service_role -revoke all on public.billing_customers -from - authenticated, - service_role; - --- Open up relevant access to billing_customers table for authenticated users and service_role -grant -select -, - insert, -update, -delete on table public.billing_customers to service_role; - --- Open up access to billing_customers table for authenticated users -grant -select - on table public.billing_customers to authenticated, - service_role; - --- Enable RLS on billing_customers table -alter table public.billing_customers enable row level security; - --- RLS on the billing_customers table --- SELECT(billing_customers): --- Users can read account subscriptions on an account they are a member of -create policy billing_customers_read_self on public.billing_customers for -select - to authenticated using ( - account_id = ( - select - auth.uid () - ) - or has_role_on_account (account_id) - ); diff --git a/supabase copy/schemas/09-subscriptions.sql b/supabase copy/schemas/09-subscriptions.sql deleted file mode 100644 index 35f0a4a..0000000 --- a/supabase copy/schemas/09-subscriptions.sql +++ /dev/null @@ -1,366 +0,0 @@ -/* - * ------------------------------------------------------- - * Section: Subscriptions - * We create the schema for the subscriptions. Subscriptions are the subscriptions for an account to a product. For example, a user might have a subscription to a product with the status 'active'. - * ------------------------------------------------------- - - */ --- Subscriptions table -create table if not exists - public.subscriptions ( - id text not null primary key, - account_id uuid references public.accounts (id) on delete cascade not null, - billing_customer_id int references public.billing_customers on delete cascade not null, - status public.subscription_status not null, - active bool not null, - billing_provider public.billing_provider not null, - cancel_at_period_end bool not null, - currency varchar(3) not null, - created_at timestamptz not null default current_timestamp, - updated_at timestamptz not null default current_timestamp, - period_starts_at timestamptz not null, - period_ends_at timestamptz not null, - trial_starts_at timestamptz, - trial_ends_at timestamptz - ); - -comment on table public.subscriptions is 'The subscriptions for an account'; - -comment on column public.subscriptions.account_id is 'The account the subscription is for'; - -comment on column public.subscriptions.billing_provider is 'The provider of the subscription'; - -comment on column public.subscriptions.cancel_at_period_end is 'Whether the subscription will be canceled at the end of the period'; - -comment on column public.subscriptions.currency is 'The currency for the subscription'; - -comment on column public.subscriptions.status is 'The status of the subscription'; - -comment on column public.subscriptions.period_starts_at is 'The start of the current period for the subscription'; - -comment on column public.subscriptions.period_ends_at is 'The end of the current period for the subscription'; - -comment on column public.subscriptions.trial_starts_at is 'The start of the trial period for the subscription'; - -comment on column public.subscriptions.trial_ends_at is 'The end of the trial period for the subscription'; - -comment on column public.subscriptions.active is 'Whether the subscription is active'; - -comment on column public.subscriptions.billing_customer_id is 'The billing customer ID for the subscription'; - --- Revoke all on subscriptions table from authenticated and service_role -revoke all on public.subscriptions -from - authenticated, - service_role; - --- Open up relevant access to subscriptions table for authenticated users and service_role -grant -select -, - insert, -update, -delete on table public.subscriptions to service_role; - -grant -select - on table public.subscriptions to authenticated; - --- Indexes on the subscriptions table -create index ix_subscriptions_account_id on public.subscriptions (account_id); - --- Enable RLS on subscriptions table -alter table public.subscriptions enable row level security; - --- RLS on the subscriptions table --- SELECT(subscriptions): --- Users can read account subscriptions on an account they are a member of -create policy subscriptions_read_self on public.subscriptions for -select - to authenticated using ( - ( - has_role_on_account (account_id) - and public.is_set ('enable_team_account_billing') - ) - or ( - account_id = ( - select - auth.uid () - ) - and public.is_set ('enable_account_billing') - ) - ); - --- Function "public.upsert_subscription" --- Insert or Update a subscription and its items in the database when receiving a webhook from the billing provider -create -or replace function public.upsert_subscription ( - target_account_id uuid, - target_customer_id varchar(255), - target_subscription_id text, - active bool, - status public.subscription_status, - billing_provider public.billing_provider, - cancel_at_period_end bool, - currency varchar(3), - period_starts_at timestamptz, - period_ends_at timestamptz, - line_items jsonb, - trial_starts_at timestamptz default null, - trial_ends_at timestamptz default null -) returns public.subscriptions -set - search_path = '' as $$ -declare - new_subscription public.subscriptions; - new_billing_customer_id int; -begin - insert into public.billing_customers( - account_id, - provider, - customer_id) - values ( - target_account_id, - billing_provider, - target_customer_id) -on conflict ( - account_id, - provider, - customer_id) - do update set - provider = excluded.provider - returning - id into new_billing_customer_id; - - insert into public.subscriptions( - account_id, - billing_customer_id, - id, - active, - status, - billing_provider, - cancel_at_period_end, - currency, - period_starts_at, - period_ends_at, - trial_starts_at, - trial_ends_at) - values ( - target_account_id, - new_billing_customer_id, - target_subscription_id, - active, - status, - billing_provider, - cancel_at_period_end, - currency, - period_starts_at, - period_ends_at, - trial_starts_at, - trial_ends_at) -on conflict ( - id) - do update set - active = excluded.active, - status = excluded.status, - cancel_at_period_end = excluded.cancel_at_period_end, - currency = excluded.currency, - period_starts_at = excluded.period_starts_at, - period_ends_at = excluded.period_ends_at, - trial_starts_at = excluded.trial_starts_at, - trial_ends_at = excluded.trial_ends_at - returning - * into new_subscription; - - -- Upsert subscription items and delete ones that are not in the line_items array - with item_data as ( - select - (line_item ->> 'id')::varchar as line_item_id, - (line_item ->> 'product_id')::varchar as prod_id, - (line_item ->> 'variant_id')::varchar as var_id, - (line_item ->> 'type')::public.subscription_item_type as type, - (line_item ->> 'price_amount')::numeric as price_amt, - (line_item ->> 'quantity')::integer as qty, - (line_item ->> 'interval')::varchar as intv, - (line_item ->> 'interval_count')::integer as intv_count - from - jsonb_array_elements(line_items) as line_item - ), - line_item_ids as ( - select line_item_id from item_data - ), - deleted_items as ( - delete from - public.subscription_items - where - public.subscription_items.subscription_id = new_subscription.id - and public.subscription_items.id not in (select line_item_id from line_item_ids) - returning * - ) - insert into public.subscription_items( - id, - subscription_id, - product_id, - variant_id, - type, - price_amount, - quantity, - interval, - interval_count) - select - line_item_id, - target_subscription_id, - prod_id, - var_id, - type, - price_amt, - qty, - intv, - intv_count - from - item_data - on conflict (id) - do update set - product_id = excluded.product_id, - variant_id = excluded.variant_id, - price_amount = excluded.price_amount, - quantity = excluded.quantity, - interval = excluded.interval, - type = excluded.type, - interval_count = excluded.interval_count; - - return new_subscription; - -end; - -$$ language plpgsql; - -grant -execute on function public.upsert_subscription ( - uuid, - varchar, - text, - bool, - public.subscription_status, - public.billing_provider, - bool, - varchar, - timestamptz, - timestamptz, - jsonb, - timestamptz, - timestamptz -) to service_role; - -/* ------------------------------------------------------- -* Section: Subscription Items -* We create the schema for the subscription items. Subscription items are the items in a subscription. -* For example, a subscription might have a subscription item with the product ID 'prod_123' and the variant ID 'var_123'. -* ------------------------------------------------------- -*/ -create table if not exists - public.subscription_items ( - id varchar(255) not null primary key, - subscription_id text references public.subscriptions (id) on delete cascade not null, - product_id varchar(255) not null, - variant_id varchar(255) not null, - type public.subscription_item_type not null, - price_amount numeric, - quantity integer not null default 1, - interval varchar(255) not null, - interval_count integer not null check (interval_count > 0), - created_at timestamptz not null default current_timestamp, - updated_at timestamptz not null default current_timestamp, - unique (subscription_id, product_id, variant_id) - ); - -comment on table public.subscription_items is 'The items in a subscription'; - -comment on column public.subscription_items.subscription_id is 'The subscription the item is for'; - -comment on column public.subscription_items.product_id is 'The product ID for the item'; - -comment on column public.subscription_items.variant_id is 'The variant ID for the item'; - -comment on column public.subscription_items.price_amount is 'The price amount for the item'; - -comment on column public.subscription_items.quantity is 'The quantity of the item'; - -comment on column public.subscription_items.interval is 'The interval for the item'; - -comment on column public.subscription_items.interval_count is 'The interval count for the item'; - -comment on column public.subscription_items.created_at is 'The creation date of the item'; - -comment on column public.subscription_items.updated_at is 'The last update date of the item'; - --- Revoke all access to subscription_items table for authenticated users and service_role -revoke all on public.subscription_items -from - authenticated, - service_role; - --- Open up relevant access to subscription_items table for authenticated users and service_role -grant -select - on table public.subscription_items to authenticated, - service_role; - -grant insert, -update, -delete on table public.subscription_items to service_role; - --- Indexes --- Indexes on the subscription_items table -create index ix_subscription_items_subscription_id on public.subscription_items (subscription_id); - --- RLS -alter table public.subscription_items enable row level security; - --- SELECT(subscription_items) --- Users can read subscription items on a subscription they are a member of -create policy subscription_items_read_self on public.subscription_items for -select - to authenticated using ( - exists ( - select - 1 - from - public.subscriptions - where - id = subscription_id - and ( - account_id = ( - select - auth.uid () - ) - or has_role_on_account (account_id) - ) - ) - ); - - --- Function "public.has_active_subscription" --- Check if a user has an active subscription on an account - ie. it's trialing or active --- Useful to gate access to features that require a subscription -create -or replace function public.has_active_subscription (target_account_id uuid) returns boolean -set - search_path = '' as $$ -begin - return exists ( - select - 1 - from - public.subscriptions - where - account_id = target_account_id - and active = true); - -end; - -$$ language plpgsql; - -grant -execute on function public.has_active_subscription (uuid) to authenticated, -service_role; \ No newline at end of file diff --git a/supabase copy/schemas/10-orders.sql b/supabase copy/schemas/10-orders.sql deleted file mode 100644 index 1c1ac6d..0000000 --- a/supabase copy/schemas/10-orders.sql +++ /dev/null @@ -1,280 +0,0 @@ -/** - * ------------------------------------------------------- - * Section: Orders - * We create the schema for the subscription items. Subscription items are the items in a subscription. - * For example, a subscription might have a subscription item with the product ID 'prod_123' and the variant ID 'var_123'. - * ------------------------------------------------------- - */ - -create table if not exists - public.orders ( - id text not null primary key, - account_id uuid references public.accounts (id) on delete cascade not null, - billing_customer_id int references public.billing_customers on delete cascade not null, - status public.payment_status not null, - billing_provider public.billing_provider not null, - total_amount numeric not null, - currency varchar(3) not null, - created_at timestamptz not null default current_timestamp, - updated_at timestamptz not null default current_timestamp - ); - -comment on table public.orders is 'The one-time orders for an account'; - -comment on column public.orders.account_id is 'The account the order is for'; - -comment on column public.orders.billing_provider is 'The provider of the order'; - -comment on column public.orders.total_amount is 'The total amount for the order'; - -comment on column public.orders.currency is 'The currency for the order'; - -comment on column public.orders.status is 'The status of the order'; - -comment on column public.orders.billing_customer_id is 'The billing customer ID for the order'; - --- Revoke all access to orders table for authenticated users and service_role -revoke all on public.orders -from - authenticated, - service_role; - --- Open up access to orders table for authenticated users and service_role -grant -select - on table public.orders to authenticated; - -grant -select -, - insert, -update, -delete on table public.orders to service_role; - --- Indexes --- Indexes on the orders table -create index ix_orders_account_id on public.orders (account_id); - --- RLS -alter table public.orders enable row level security; - --- SELECT(orders) --- Users can read orders on an account they are a member of or the account is their own -create policy orders_read_self on public.orders for -select - to authenticated using ( - ( - account_id = ( - select - auth.uid () - ) - and public.is_set ('enable_account_billing') - ) - or ( - has_role_on_account (account_id) - and public.is_set ('enable_team_account_billing') - ) - ); - -/** - * ------------------------------------------------------- - * Section: Order Items - * We create the schema for the order items. Order items are the items in an order. - * ------------------------------------------------------- - */ -create table if not exists - public.order_items ( - id text not null primary key, - order_id text references public.orders (id) on delete cascade not null, - product_id text not null, - variant_id text not null, - price_amount numeric, - quantity integer not null default 1, - created_at timestamptz not null default current_timestamp, - updated_at timestamptz not null default current_timestamp, - unique (order_id, product_id, variant_id) - ); - -comment on table public.order_items is 'The items in an order'; - -comment on column public.order_items.order_id is 'The order the item is for'; - -comment on column public.order_items.order_id is 'The order the item is for'; - -comment on column public.order_items.product_id is 'The product ID for the item'; - -comment on column public.order_items.variant_id is 'The variant ID for the item'; - -comment on column public.order_items.price_amount is 'The price amount for the item'; - -comment on column public.order_items.quantity is 'The quantity of the item'; - -comment on column public.order_items.created_at is 'The creation date of the item'; - -comment on column public.order_items.updated_at is 'The last update date of the item'; - --- Revoke all access to order_items table for authenticated users and service_role -revoke all on public.order_items -from - authenticated, - service_role; - --- Open up relevant access to order_items table for authenticated users and service_role -grant -select - on table public.order_items to authenticated, - service_role; - -grant insert, update, delete on table public.order_items to service_role; - --- Indexes on the order_items table -create index ix_order_items_order_id on public.order_items (order_id); - --- RLS -alter table public.order_items enable row level security; - --- SELECT(order_items): --- Users can read order items on an order they are a member of -create policy order_items_read_self on public.order_items for -select - to authenticated using ( - exists ( - select - 1 - from - public.orders - where - id = order_id - and ( - account_id = ( - select - auth.uid () - ) - or has_role_on_account (account_id) - ) - ) - ); - --- Function "public.upsert_order" --- Insert or update an order and its items when receiving a webhook from the billing provider -create -or replace function public.upsert_order ( - target_account_id uuid, - target_customer_id varchar(255), - target_order_id text, - status public.payment_status, - billing_provider public.billing_provider, - total_amount numeric, - currency varchar(3), - line_items jsonb -) returns public.orders -set - search_path = '' as $$ -declare - new_order public.orders; - new_billing_customer_id int; -begin - insert into public.billing_customers( - account_id, - provider, - customer_id) - values ( - target_account_id, - billing_provider, - target_customer_id) -on conflict ( - account_id, - provider, - customer_id) - do update set - provider = excluded.provider - returning - id into new_billing_customer_id; - - insert into public.orders( - account_id, - billing_customer_id, - id, - status, - billing_provider, - total_amount, - currency) - values ( - target_account_id, - new_billing_customer_id, - target_order_id, - status, - billing_provider, - total_amount, - currency) -on conflict ( - id) - do update set - status = excluded.status, - total_amount = excluded.total_amount, - currency = excluded.currency - returning - * into new_order; - - -- Upsert order items and delete ones that are not in the line_items array - with item_data as ( - select - (line_item ->> 'id')::varchar as line_item_id, - (line_item ->> 'product_id')::varchar as prod_id, - (line_item ->> 'variant_id')::varchar as var_id, - (line_item ->> 'price_amount')::numeric as price_amt, - (line_item ->> 'quantity')::integer as qty - from - jsonb_array_elements(line_items) as line_item - ), - line_item_ids as ( - select line_item_id from item_data - ), - deleted_items as ( - delete from - public.order_items - where - public.order_items.order_id = new_order.id - and public.order_items.id not in (select line_item_id from line_item_ids) - returning * - ) - insert into public.order_items( - id, - order_id, - product_id, - variant_id, - price_amount, - quantity) - select - line_item_id, - target_order_id, - prod_id, - var_id, - price_amt, - qty - from - item_data - on conflict (id) - do update set - price_amount = excluded.price_amount, - product_id = excluded.product_id, - variant_id = excluded.variant_id, - quantity = excluded.quantity; - - return new_order; - -end; - -$$ language plpgsql; - -grant -execute on function public.upsert_order ( - uuid, - varchar, - text, - public.payment_status, - public.billing_provider, - numeric, - varchar, - jsonb -) to service_role; \ No newline at end of file diff --git a/supabase copy/schemas/11-notifications.sql b/supabase copy/schemas/11-notifications.sql deleted file mode 100644 index dfa628b..0000000 --- a/supabase copy/schemas/11-notifications.sql +++ /dev/null @@ -1,114 +0,0 @@ - -/** - * ------------------------------------------------------- - * Section: Notifications - * We create the schema for the notifications. Notifications are the notifications for an account. - * ------------------------------------------------------- - */ -create type public.notification_channel as enum('in_app', 'email'); - -create type public.notification_type as enum('info', 'warning', 'error'); - -create table if not exists - public.notifications ( - id bigint generated always as identity primary key, - account_id uuid not null references public.accounts (id) on delete cascade, - type public.notification_type not null default 'info', - body varchar(5000) not null, - link varchar(255), - channel public.notification_channel not null default 'in_app', - dismissed boolean not null default false, - expires_at timestamptz default (now() + interval '1 month'), - created_at timestamptz not null default now() - ); - -comment on table notifications is 'The notifications for an account'; - -comment on column notifications.account_id is 'The account the notification is for (null for system messages)'; - -comment on column notifications.type is 'The type of the notification'; - -comment on column notifications.body is 'The body of the notification'; - -comment on column notifications.link is 'The link for the notification'; - -comment on column notifications.channel is 'The channel for the notification'; - -comment on column notifications.dismissed is 'Whether the notification has been dismissed'; - -comment on column notifications.expires_at is 'The expiry date for the notification'; - -comment on column notifications.created_at is 'The creation date for the notification'; - --- Revoke all access to notifications table for authenticated users and service_role -revoke all on public.notifications -from - authenticated, - service_role; - --- Open up relevant access to notifications table for authenticated users and service_role -grant -select -, -update on table public.notifications to authenticated, -service_role; - -grant insert on table public.notifications to service_role; - --- enable realtime -alter publication supabase_realtime -add table public.notifications; - --- Indexes --- Indexes on the notifications table --- index for selecting notifications for an account that are not dismissed and not expired -create index idx_notifications_account_dismissed on notifications (account_id, dismissed, expires_at); - --- RLS -alter table public.notifications enable row level security; - --- SELECT(notifications): --- Users can read notifications on an account they are a member of -create policy notifications_read_self on public.notifications for -select - to authenticated using ( - account_id = ( - select - auth.uid () - ) - or has_role_on_account (account_id) - ); - --- UPDATE(notifications): --- Users can set notifications to read on an account they are a member of -create policy notifications_update_self on public.notifications -for update - to authenticated using ( - account_id = ( - select - auth.uid () - ) - or has_role_on_account (account_id) - ); - --- Function "kit.update_notification_dismissed_status" --- Make sure the only updatable field is the dismissed status and nothing else -create -or replace function kit.update_notification_dismissed_status () returns trigger -set - search_path to '' as $$ -begin - old.dismissed := new.dismissed; - - if (new is distinct from old) then - raise exception 'UPDATE of columns other than "dismissed" is forbidden'; - end if; - - return old; -end; -$$ language plpgsql; - --- add trigger when updating a notification to update the dismissed status -create trigger update_notification_dismissed_status before -update on public.notifications for each row -execute procedure kit.update_notification_dismissed_status (); \ No newline at end of file diff --git a/supabase copy/schemas/12-one-time-tokens.sql b/supabase copy/schemas/12-one-time-tokens.sql deleted file mode 100644 index 22f3714..0000000 --- a/supabase copy/schemas/12-one-time-tokens.sql +++ /dev/null @@ -1,349 +0,0 @@ -/* - * ------------------------------------------------------- - * Section: Nonces - * We create the schema for the nonces. Nonces are used to create one-time tokens for authentication purposes. - * ------------------------------------------------------- - */ - -create extension if not exists pg_cron; - --- Create a table to store one-time tokens (nonces) -CREATE TABLE IF NOT EXISTS public.nonces ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - client_token TEXT NOT NULL, -- token sent to client (hashed) - nonce TEXT NOT NULL, -- token stored in DB (hashed) - user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NULL, -- Optional to support anonymous tokens - purpose TEXT NOT NULL, -- e.g., 'password-reset', 'email-verification', etc. - - -- Status fields - expires_at TIMESTAMPTZ NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - used_at TIMESTAMPTZ, - revoked BOOLEAN NOT NULL DEFAULT FALSE, -- For administrative revocation - revoked_reason TEXT, -- Reason for revocation if applicable - - -- Audit fields - verification_attempts INTEGER NOT NULL DEFAULT 0, -- Track attempted uses - last_verification_at TIMESTAMPTZ, -- Timestamp of last verification attempt - last_verification_ip INET, -- For tracking verification source - last_verification_user_agent TEXT, -- For tracking client information - - -- Extensibility fields - metadata JSONB DEFAULT '{}'::JSONB, -- optional metadata - scopes TEXT[] DEFAULT '{}' -- OAuth-style authorized scopes -); - --- Create indexes for efficient lookups -CREATE INDEX IF NOT EXISTS idx_nonces_status ON public.nonces (client_token, user_id, purpose, expires_at) - WHERE used_at IS NULL AND revoked = FALSE; - --- Enable Row Level Security (RLS) -ALTER TABLE public.nonces ENABLE ROW LEVEL SECURITY; - --- RLS policies --- Users can view their own nonces for verification -CREATE POLICY "Users can read their own nonces" - ON public.nonces - FOR SELECT - USING ( - user_id = (select auth.uid()) - ); - --- Create a function to create a nonce --- Create a function to create a nonce -create or replace function public.create_nonce ( - p_user_id UUID default null, - p_purpose TEXT default null, - p_expires_in_seconds INTEGER default 3600, -- 1 hour by default - p_metadata JSONB default null, - p_scopes text[] default null, - p_revoke_previous BOOLEAN default true -- New parameter to control automatic revocation -) RETURNS JSONB LANGUAGE plpgsql SECURITY DEFINER - set - search_path to '' as $$ -DECLARE - v_client_token TEXT; - v_nonce TEXT; - v_expires_at TIMESTAMPTZ; - v_id UUID; - v_plaintext_token TEXT; - v_revoked_count INTEGER; -BEGIN - -- Revoke previous tokens for the same user and purpose if requested - -- This only applies if a user ID is provided (not for anonymous tokens) - IF p_revoke_previous = TRUE AND p_user_id IS NOT NULL THEN - WITH revoked AS ( - UPDATE public.nonces - SET - revoked = TRUE, - revoked_reason = 'Superseded by new token with same purpose' - WHERE - user_id = p_user_id - AND purpose = p_purpose - AND used_at IS NULL - AND revoked = FALSE - AND expires_at > NOW() - RETURNING 1 - ) - SELECT COUNT(*) INTO v_revoked_count FROM revoked; - END IF; - - -- Generate a 6-digit token - v_plaintext_token := (100000 + floor(random() * 900000))::text; - v_client_token := extensions.crypt(v_plaintext_token, extensions.gen_salt('bf')); - - -- Still generate a secure nonce for internal use - v_nonce := encode(extensions.gen_random_bytes(24), 'base64'); - v_nonce := extensions.crypt(v_nonce, extensions.gen_salt('bf')); - - -- Calculate expiration time - v_expires_at := NOW() + (p_expires_in_seconds * interval '1 second'); - - -- Insert the new nonce - INSERT INTO public.nonces ( - client_token, - nonce, - user_id, - expires_at, - metadata, - purpose, - scopes - ) - VALUES ( - v_client_token, - v_nonce, - p_user_id, - v_expires_at, - COALESCE(p_metadata, '{}'::JSONB), - p_purpose, - COALESCE(p_scopes, '{}'::TEXT[]) - ) - RETURNING id INTO v_id; - - -- Return the token information - -- Note: returning the plaintext token, not the hash - RETURN jsonb_build_object( - 'id', v_id, - 'token', v_plaintext_token, - 'expires_at', v_expires_at, - 'revoked_previous_count', COALESCE(v_revoked_count, 0) - ); -END; -$$; - -grant execute on function public.create_nonce to service_role; - --- Create a function to verify a nonce -create or replace function public.verify_nonce ( - p_token TEXT, - p_purpose TEXT, - p_user_id UUID default null, - p_required_scopes text[] default null, - p_max_verification_attempts INTEGER default 5, - p_ip INET default null, - p_user_agent TEXT default null -) RETURNS JSONB LANGUAGE plpgsql SECURITY DEFINER - set - SEARCH_PATH to '' as $$ -DECLARE - v_nonce RECORD; - v_matching_count INTEGER; -BEGIN - -- Count how many matching tokens exist before verification attempt - SELECT COUNT(*) - INTO v_matching_count - FROM public.nonces - WHERE purpose = p_purpose; - - -- Update verification attempt counter and tracking info for all matching tokens - UPDATE public.nonces - SET verification_attempts = verification_attempts + 1, - last_verification_at = NOW(), - last_verification_ip = COALESCE(p_ip, last_verification_ip), - last_verification_user_agent = COALESCE(p_user_agent, last_verification_user_agent) - WHERE client_token = extensions.crypt(p_token, client_token) - AND purpose = p_purpose; - - -- Find the nonce by token and purpose - -- Modified to handle user-specific tokens better - SELECT * - INTO v_nonce - FROM public.nonces - WHERE client_token = extensions.crypt(p_token, client_token) - AND purpose = p_purpose - -- Only apply user_id filter if the token was created for a specific user - AND ( - -- Case 1: Anonymous token (user_id is NULL in DB) - (user_id IS NULL) - OR - -- Case 2: User-specific token (check if user_id matches) - (user_id = p_user_id) - ) - AND used_at IS NULL - AND NOT revoked - AND expires_at > NOW(); - - -- Check if nonce exists - IF v_nonce.id IS NULL THEN - RETURN jsonb_build_object( - 'valid', false, - 'message', 'Invalid or expired token' - ); - END IF; - - -- Check if max verification attempts exceeded - IF p_max_verification_attempts > 0 AND v_nonce.verification_attempts > p_max_verification_attempts THEN - -- Automatically revoke the token - UPDATE public.nonces - SET revoked = TRUE, - revoked_reason = 'Maximum verification attempts exceeded' - WHERE id = v_nonce.id; - - RETURN jsonb_build_object( - 'valid', false, - 'message', 'Token revoked due to too many verification attempts', - 'max_attempts_exceeded', true - ); - END IF; - - -- Check scopes if required - IF p_required_scopes IS NOT NULL AND array_length(p_required_scopes, 1) > 0 THEN - -- Fix scope validation to properly check if token scopes contain all required scopes - -- Using array containment check: array1 @> array2 (array1 contains array2) - IF NOT (v_nonce.scopes @> p_required_scopes) THEN - RETURN jsonb_build_object( - 'valid', false, - 'message', 'Token does not have required permissions', - 'token_scopes', v_nonce.scopes, - 'required_scopes', p_required_scopes - ); - END IF; - END IF; - - -- Mark nonce as used - UPDATE public.nonces - SET used_at = NOW() - WHERE id = v_nonce.id; - - -- Return success with metadata - RETURN jsonb_build_object( - 'valid', true, - 'user_id', v_nonce.user_id, - 'metadata', v_nonce.metadata, - 'scopes', v_nonce.scopes, - 'purpose', v_nonce.purpose - ); -END; -$$; - -grant - execute on function public.verify_nonce to authenticated, - service_role; - --- Create a function to revoke a nonce -CREATE OR REPLACE FUNCTION public.revoke_nonce( - p_id UUID, - p_reason TEXT DEFAULT NULL -) -RETURNS BOOLEAN -LANGUAGE plpgsql -SECURITY DEFINER -SET search_path TO '' -AS $$ -DECLARE - v_affected_rows INTEGER; -BEGIN - UPDATE public.nonces - SET - revoked = TRUE, - revoked_reason = p_reason - WHERE - id = p_id - AND used_at IS NULL - AND NOT revoked - RETURNING 1 INTO v_affected_rows; - - RETURN v_affected_rows > 0; -END; -$$; - -grant execute on function public.revoke_nonce to service_role; - --- Create a function to clean up expired nonces -CREATE OR REPLACE FUNCTION kit.cleanup_expired_nonces( - p_older_than_days INTEGER DEFAULT 1, - p_include_used BOOLEAN DEFAULT TRUE, - p_include_revoked BOOLEAN DEFAULT TRUE -) -RETURNS INTEGER -LANGUAGE plpgsql -SECURITY DEFINER -SET search_path TO '' -AS $$ -DECLARE - v_count INTEGER; -BEGIN - -- Count and delete expired or used nonces based on parameters - WITH deleted AS ( - DELETE FROM public.nonces - WHERE - ( - -- Expired and unused tokens - (expires_at < NOW() AND used_at IS NULL) - - -- Used tokens older than specified days (if enabled) - OR (p_include_used = TRUE AND used_at < NOW() - (p_older_than_days * interval '1 day')) - - -- Revoked tokens older than specified days (if enabled) - OR (p_include_revoked = TRUE AND revoked = TRUE AND created_at < NOW() - (p_older_than_days * interval '1 day')) - ) - RETURNING 1 - ) - SELECT COUNT(*) INTO v_count FROM deleted; - - RETURN v_count; -END; -$$; - --- Create a function to get token status (for administrative use) -CREATE OR REPLACE FUNCTION public.get_nonce_status( - p_id UUID -) -RETURNS JSONB -LANGUAGE plpgsql -SECURITY DEFINER -SET search_path TO '' -AS $$ -DECLARE - v_nonce public.nonces; -BEGIN - SELECT * INTO v_nonce FROM public.nonces WHERE id = p_id; - - IF v_nonce.id IS NULL THEN - RETURN jsonb_build_object('exists', false); - END IF; - - RETURN jsonb_build_object( - 'exists', true, - 'purpose', v_nonce.purpose, - 'user_id', v_nonce.user_id, - 'created_at', v_nonce.created_at, - 'expires_at', v_nonce.expires_at, - 'used_at', v_nonce.used_at, - 'revoked', v_nonce.revoked, - 'revoked_reason', v_nonce.revoked_reason, - 'verification_attempts', v_nonce.verification_attempts, - 'last_verification_at', v_nonce.last_verification_at, - 'last_verification_ip', v_nonce.last_verification_ip, - 'is_valid', (v_nonce.used_at IS NULL AND NOT v_nonce.revoked AND v_nonce.expires_at > NOW()) - ); -END; -$$; - --- Comments for documentation -COMMENT ON TABLE public.nonces IS 'Table for storing one-time tokens with enhanced security and audit features'; -COMMENT ON FUNCTION public.create_nonce IS 'Creates a new one-time token for a specific purpose with enhanced options'; -COMMENT ON FUNCTION public.verify_nonce IS 'Verifies a one-time token, checks scopes, and marks it as used'; -COMMENT ON FUNCTION public.revoke_nonce IS 'Administratively revokes a token to prevent its use'; -COMMENT ON FUNCTION kit.cleanup_expired_nonces IS 'Cleans up expired, used, or revoked tokens based on parameters'; -COMMENT ON FUNCTION public.get_nonce_status IS 'Retrieves the status of a token for administrative purposes'; diff --git a/supabase copy/schemas/13-mfa.sql b/supabase copy/schemas/13-mfa.sql deleted file mode 100644 index bc97754..0000000 --- a/supabase copy/schemas/13-mfa.sql +++ /dev/null @@ -1,145 +0,0 @@ -/* - * ------------------------------------------------------- - * Section: MFA - * We create the policies and functions to enforce MFA - * ------------------------------------------------------- - */ - -/* -* public.is_aal2 -* Check if the user has aal2 access -*/ -create - or replace function public.is_aal2() returns boolean - set - search_path = '' as -$$ -declare - is_aal2 boolean; -begin - select auth.jwt() ->> 'aal' = 'aal2' into is_aal2; - - return coalesce(is_aal2, false); -end -$$ language plpgsql; - --- Grant access to the function to authenticated users -grant execute on function public.is_aal2() to authenticated; - -/* -* public.is_super_admin -* Check if the user is a super admin. -* A Super Admin is a user that has the role 'super-admin' and has MFA enabled. -*/ -create - or replace function public.is_super_admin() returns boolean - set - search_path = '' as -$$ -declare - is_super_admin boolean; -begin - if not public.is_aal2() then - return false; - end if; - - select (auth.jwt() ->> 'app_metadata')::jsonb ->> 'role' = 'super-admin' into is_super_admin; - - return coalesce(is_super_admin, false); -end -$$ language plpgsql; - --- Grant access to the function to authenticated users -grant execute on function public.is_super_admin() to authenticated; - -/* -* public.is_mfa_compliant -* Check if the user meets MFA requirements if they have MFA enabled. -* If the user has MFA enabled, then the user must have aal2 enabled. Otherwise, the user must have aal1 enabled (default behavior). -*/ -create or replace function public.is_mfa_compliant() returns boolean - set search_path = '' as -$$ -begin - return array[(select auth.jwt()->>'aal')] <@ ( - select - case - when count(id) > 0 then array['aal2'] - else array['aal1', 'aal2'] - end as aal - from auth.mfa_factors - where ((select auth.uid()) = auth.mfa_factors.user_id) and auth.mfa_factors.status = 'verified' - ); -end -$$ language plpgsql security definer; - --- Grant access to the function to authenticated users -grant execute on function public.is_mfa_compliant() to authenticated; - --- MFA Restrictions: --- the following policies are applied to the tables as a --- restrictive policy to ensure that if MFA is enabled, then the policy will be applied. --- For users that have not enabled MFA, the policy will not be applied and will keep the default behavior. - --- Restrict access to accounts if MFA is enabled -create policy restrict_mfa_accounts - on public.accounts - as restrictive - to authenticated - using (public.is_mfa_compliant()); - --- Restrict access to accounts memberships if MFA is enabled -create policy restrict_mfa_accounts_memberships - on public.accounts_memberships - as restrictive - to authenticated - using (public.is_mfa_compliant()); - --- Restrict access to subscriptions if MFA is enabled -create policy restrict_mfa_subscriptions - on public.subscriptions - as restrictive - to authenticated - using (public.is_mfa_compliant()); - --- Restrict access to subscription items if MFA is enabled -create policy restrict_mfa_subscription_items - on public.subscription_items - as restrictive - to authenticated - using (public.is_mfa_compliant()); - --- Restrict access to role permissions if MFA is enabled -create policy restrict_mfa_role_permissions - on public.role_permissions - as restrictive - to authenticated - using (public.is_mfa_compliant()); - --- Restrict access to invitations if MFA is enabled -create policy restrict_mfa_invitations - on public.invitations - as restrictive - to authenticated - using (public.is_mfa_compliant()); - --- Restrict access to orders if MFA is enabled -create policy restrict_mfa_orders - on public.orders - as restrictive - to authenticated - using (public.is_mfa_compliant()); - --- Restrict access to orders items if MFA is enabled -create policy restrict_mfa_order_items - on public.order_items - as restrictive - to authenticated - using (public.is_mfa_compliant()); - --- Restrict access to orders if MFA is enabled -create policy restrict_mfa_notifications - on public.notifications - as restrictive - to authenticated - using (public.is_mfa_compliant()); \ No newline at end of file diff --git a/supabase copy/schemas/14-super-admin.sql b/supabase copy/schemas/14-super-admin.sql deleted file mode 100644 index 4fdaf20..0000000 --- a/supabase copy/schemas/14-super-admin.sql +++ /dev/null @@ -1,73 +0,0 @@ -/* - * ------------------------------------------------------- - * Section: Super Admin - * We create the policies and functions to enforce super admin access - * ------------------------------------------------------- - */ - --- the following policies are applied to the tables as a permissive policy to ensure that --- super admins can access all tables (view only). - --- Allow Super Admins to access the accounts table -create policy super_admins_access_accounts - on public.accounts - as permissive - for select - to authenticated - using (public.is_super_admin()); - --- Allow Super Admins to access the accounts memberships table -create policy super_admins_access_accounts_memberships - on public.accounts_memberships - as permissive - for select - to authenticated - using (public.is_super_admin()); - --- Allow Super Admins to access the subscriptions table -create policy super_admins_access_subscriptions - on public.subscriptions - as permissive - for select - to authenticated - using (public.is_super_admin()); - --- Allow Super Admins to access the subscription items table -create policy super_admins_access_subscription_items - on public.subscription_items - as permissive - for select - to authenticated - using (public.is_super_admin()); - --- Allow Super Admins to access the invitations items table -create policy super_admins_access_invitations - on public.invitations - as permissive - for select - to authenticated - using (public.is_super_admin()); - --- Allow Super Admins to access the orders table -create policy super_admins_access_orders - on public.orders - as permissive - for select - to authenticated - using (public.is_super_admin()); - --- Allow Super Admins to access the order items table -create policy super_admins_access_order_items - on public.order_items - as permissive - for select - to authenticated - using (public.is_super_admin()); - --- Allow Super Admins to access the role permissions table -create policy super_admins_access_role_permissions - on public.role_permissions - as permissive - for select - to authenticated - using (public.is_super_admin()); \ No newline at end of file diff --git a/supabase copy/schemas/15-account-views.sql b/supabase copy/schemas/15-account-views.sql deleted file mode 100644 index 13f6d8b..0000000 --- a/supabase copy/schemas/15-account-views.sql +++ /dev/null @@ -1,126 +0,0 @@ -/* - * ------------------------------------------------------- - * Section: Account Functions - * We create the schema for the functions. Functions are the custom functions for the application. - * ------------------------------------------------------- - */ - - --- --- VIEW "user_account_workspace": --- we create a view to load the general app data for the authenticated --- user which includes the user accounts and memberships -create or replace view - public.user_account_workspace - with - (security_invoker = true) as -select - accounts.id as id, - accounts.name as name, - accounts.picture_url as picture_url, - ( - select - status - from - public.subscriptions - where - account_id = accounts.id - limit - 1 - ) as subscription_status -from - public.accounts -where - primary_owner_user_id = (select auth.uid ()) - and accounts.is_personal_account = true -limit - 1; - -grant - select - on public.user_account_workspace to authenticated, - service_role; - --- --- VIEW "user_accounts": --- we create a view to load the user's accounts and memberships --- useful to display the user's accounts in the app -create or replace view - public.user_accounts (id, name, picture_url, slug, role) - with - (security_invoker = true) as -select - account.id, - account.name, - account.picture_url, - account.slug, - membership.account_role -from - public.accounts account - join public.accounts_memberships membership on account.id = membership.account_id -where - membership.user_id = (select auth.uid ()) - and account.is_personal_account = false - and account.id in ( - select - account_id - from - public.accounts_memberships - where - user_id = (select auth.uid ()) -); - -grant - select - on public.user_accounts to authenticated, - service_role; - --- --- Function "public.team_account_workspace" --- Load all the data for a team account workspace -create or replace function public.team_account_workspace(account_slug text) -returns table ( - id uuid, - name varchar(255), - picture_url varchar(1000), - slug text, - role varchar(50), - role_hierarchy_level int, - primary_owner_user_id uuid, - subscription_status public.subscription_status, - permissions public.app_permissions[] -) -set search_path to '' -as $$ -begin - return QUERY - select - accounts.id, - accounts.name, - accounts.picture_url, - accounts.slug, - accounts_memberships.account_role, - roles.hierarchy_level, - accounts.primary_owner_user_id, - subscriptions.status, - array_agg(role_permissions.permission) - from - public.accounts - join public.accounts_memberships on accounts.id = accounts_memberships.account_id - left join public.subscriptions on accounts.id = subscriptions.account_id - join public.roles on accounts_memberships.account_role = roles.name - left join public.role_permissions on accounts_memberships.account_role = role_permissions.role - where - accounts.slug = account_slug - and public.accounts_memberships.user_id = (select auth.uid()) - group by - accounts.id, - accounts_memberships.account_role, - subscriptions.status, - roles.hierarchy_level; -end; -$$ language plpgsql; - -grant -execute on function public.team_account_workspace (text) to authenticated, -service_role; diff --git a/supabase copy/schemas/16-storage.sql b/supabase copy/schemas/16-storage.sql deleted file mode 100644 index 78e2d6c..0000000 --- a/supabase copy/schemas/16-storage.sql +++ /dev/null @@ -1,50 +0,0 @@ -/* - * ------------------------------------------------------- - * Section: Storage - * We create the schema for the storage - * ------------------------------------------------------- - */ - --- Account Image -insert into - storage.buckets (id, name, PUBLIC) -values - ('account_image', 'account_image', true); - --- Function: get the storage filename as a UUID. --- Useful if you want to name files with UUIDs related to an account -create -or replace function kit.get_storage_filename_as_uuid (name text) returns uuid -set - search_path = '' as $$ -begin - return replace(storage.filename(name), concat('.', - storage.extension(name)), '')::uuid; - -end; - -$$ language plpgsql; - -grant -execute on function kit.get_storage_filename_as_uuid (text) to authenticated, -service_role; - --- RLS policies for storage bucket account_image -create policy account_image on storage.objects for all using ( - bucket_id = 'account_image' - and ( - kit.get_storage_filename_as_uuid(name) = auth.uid() - or public.has_role_on_account(kit.get_storage_filename_as_uuid(name)) - ) -) -with check ( - bucket_id = 'account_image' - and ( - kit.get_storage_filename_as_uuid(name) = auth.uid() - or public.has_permission( - auth.uid(), - kit.get_storage_filename_as_uuid(name), - 'settings.manage' - ) - ) -); \ No newline at end of file diff --git a/supabase copy/schemas/17-roles-seed.sql b/supabase copy/schemas/17-roles-seed.sql deleted file mode 100644 index c53fcb6..0000000 --- a/supabase copy/schemas/17-roles-seed.sql +++ /dev/null @@ -1,47 +0,0 @@ -/* - * ------------------------------------------------------- - * Section: Roles Seed - * We create the roles and role permissions seed data - * ------------------------------------------------------- - */ - --- Seed the roles table with default roles 'owner' and 'member' -insert into public.roles( - name, - hierarchy_level) -values ( - 'owner', - 1); - -insert into public.roles( - name, - hierarchy_level) -values ( - 'member', - 2); - --- We seed the role_permissions table with the default roles and permissions -insert into public.role_permissions( - role, - permission) -values ( - 'owner', - 'roles.manage'), - ( - 'owner', - 'billing.manage'), - ( - 'owner', - 'settings.manage'), - ( - 'owner', - 'members.manage'), - ( - 'owner', - 'invites.manage'), - ( - 'member', - 'settings.manage'), - ( - 'member', - 'invites.manage'); \ No newline at end of file diff --git a/supabase copy/seed.sql b/supabase copy/seed.sql deleted file mode 100644 index 1c8989f..0000000 --- a/supabase copy/seed.sql +++ /dev/null @@ -1,315 +0,0 @@ --- WEBHOOKS SEED --- PLEASE NOTE: These webhooks are only for development purposes. Leave them as they are or add new ones. - --- These webhooks are only for development purposes. --- In production, you should manually create webhooks in the Supabase dashboard (or create a migration to do so). --- We don't do it because you'll need to manually add your webhook URL and secret key. - --- this webhook will be triggered after deleting an account -create trigger "accounts_teardown" - after delete - on "public"."accounts" - for each row -execute function "supabase_functions"."http_request"( - 'http://host.docker.internal:3000/api/db/webhook', - 'POST', - '{"Content-Type":"application/json", "X-Supabase-Event-Signature":"WEBHOOKSECRET"}', - '{}', - '5000' - ); - --- this webhook will be triggered after a delete on the subscriptions table --- which should happen when a user deletes their account (and all their subscriptions) -create trigger "subscriptions_delete" - after delete - on "public"."subscriptions" - for each row -execute function "supabase_functions"."http_request"( - 'http://host.docker.internal:3000/api/db/webhook', - 'POST', - '{"Content-Type":"application/json", "X-Supabase-Event-Signature":"WEBHOOKSECRET"}', - '{}', - '5000' - ); - --- this webhook will be triggered after every insert on the invitations table --- which should happen when a user invites someone to their account -create trigger "invitations_insert" - after insert - on "public"."invitations" - for each row -execute function "supabase_functions"."http_request"( - 'http://host.docker.internal:3000/api/db/webhook', - 'POST', - '{"Content-Type":"application/json", "X-Supabase-Event-Signature":"WEBHOOKSECRET"}', - '{}', - '5000' - ); - - --- DATA SEED --- This is a data dump for testing purposes. It should be used to seed the database with data for testing. - - --- --- Data for Name: flow_state; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - --- --- Data for Name: users; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - -INSERT INTO "auth"."users" ("instance_id", "id", "aud", "role", "email", "encrypted_password", "email_confirmed_at", - "invited_at", "confirmation_token", "confirmation_sent_at", "recovery_token", - "recovery_sent_at", "email_change_token_new", "email_change", "email_change_sent_at", - "last_sign_in_at", "raw_app_meta_data", "raw_user_meta_data", "is_super_admin", - "created_at", "updated_at", "phone", "phone_confirmed_at", "phone_change", - "phone_change_token", "phone_change_sent_at", "email_change_token_current", - "email_change_confirm_status", "banned_until", "reauthentication_token", - "reauthentication_sent_at", "is_sso_user", "deleted_at", "is_anonymous") -VALUES ('00000000-0000-0000-0000-000000000000', 'b73eb03e-fb7a-424d-84ff-18e2791ce0b4', 'authenticated', - 'authenticated', 'custom@makerkit.dev', '$2a$10$b3ZPpU6TU3or30QzrXnZDuATPAx2pPq3JW.sNaneVY3aafMSuR4yi', - '2024-04-20 08:38:00.860548+00', NULL, '', '2024-04-20 08:37:43.343769+00', '', NULL, '', '', NULL, - '2024-04-20 08:38:00.93864+00', '{"provider": "email", "providers": ["email"]}', - '{"sub": "b73eb03e-fb7a-424d-84ff-18e2791ce0b4", "email": "custom@makerkit.dev", "email_verified": false, "phone_verified": false}', - NULL, '2024-04-20 08:37:43.3385+00', '2024-04-20 08:38:00.942809+00', NULL, NULL, '', '', NULL, '', 0, NULL, '', - NULL, false, NULL, false), - ('00000000-0000-0000-0000-000000000000', '31a03e74-1639-45b6-bfa7-77447f1a4762', 'authenticated', - 'authenticated', 'test@makerkit.dev', '$2a$10$NaMVRrI7NyfwP.AfAVWt6O/abulGnf9BBqwa6DqdMwXMvOCGpAnVO', - '2024-04-20 08:20:38.165331+00', NULL, '', NULL, '', NULL, '', '', NULL, '2024-04-20 09:36:02.521776+00', - '{"provider": "email", "providers": ["email"], "role": "super-admin"}', - '{"sub": "31a03e74-1639-45b6-bfa7-77447f1a4762", "email": "test@makerkit.dev", "email_verified": false, "phone_verified": false}', - NULL, '2024-04-20 08:20:34.459113+00', '2024-04-20 10:07:48.554125+00', NULL, NULL, '', '', NULL, '', 0, NULL, - '', NULL, false, NULL, false), - ('00000000-0000-0000-0000-000000000000', '5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf', 'authenticated', - 'authenticated', 'owner@makerkit.dev', '$2a$10$D6arGxWJShy8q4RTW18z7eW0vEm2hOxEUovUCj5f3NblyHfamm5/a', - '2024-04-20 08:36:37.517993+00', NULL, '', '2024-04-20 08:36:27.639648+00', '', NULL, '', '', NULL, - '2024-04-20 08:36:37.614337+00', '{"provider": "email", "providers": ["email"]}', - '{"sub": "5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf", "email": "owner@makerkit.dev", "email_verified": false, "phone_verified": false}', - NULL, '2024-04-20 08:36:27.630379+00', '2024-04-20 08:36:37.617955+00', NULL, NULL, '', '', NULL, '', 0, NULL, - '', NULL, false, NULL, false), - ('00000000-0000-0000-0000-000000000000', '6b83d656-e4ab-48e3-a062-c0c54a427368', 'authenticated', - 'authenticated', 'member@makerkit.dev', '$2a$10$6h/x.AX.6zzphTfDXIJMzuYx13hIYEi/Iods9FXH19J2VxhsLycfa', - '2024-04-20 08:41:15.376778+00', NULL, '', '2024-04-20 08:41:08.689674+00', '', NULL, '', '', NULL, - '2024-04-20 08:41:15.484606+00', '{"provider": "email", "providers": ["email"]}', - '{"sub": "6b83d656-e4ab-48e3-a062-c0c54a427368", "email": "member@makerkit.dev", "email_verified": false, "phone_verified": false}', - NULL, '2024-04-20 08:41:08.683395+00', '2024-04-20 08:41:15.485494+00', NULL, NULL, '', '', NULL, '', 0, NULL, - '', NULL, false, NULL, false), - ('00000000-0000-0000-0000-000000000000', 'c5b930c9-0a76-412e-a836-4bc4849a3270', 'authenticated', - 'authenticated', 'super-admin@makerkit.dev', - '$2a$10$gzxQw3vaVni8Ke9UVcn6ueWh674.6xImf6/yWYNc23BSeYdE9wmki', '2025-02-24 13:25:11.176987+00', null, '', - '2025-02-24 13:25:01.649714+00', '', null, '', '', null, '2025-02-24 13:25:11.17957+00', - '{"provider": "email", "providers": ["email"], "role": "super-admin"}', - '{"sub": "c5b930c9-0a76-412e-a836-4bc4849a3270", "email": "super-admin@makerkit.dev", "email_verified": true, "phone_verified": false}', - null, '2025-02-24 13:25:01.646641+00', '2025-02-24 13:25:11.181332+00', null, null, '', '', null - , '', '0', null, '', null, 'false', null, 'false'); - --- --- Data for Name: identities; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - -INSERT INTO "auth"."identities" ("provider_id", "user_id", "identity_data", "provider", "last_sign_in_at", "created_at", - "updated_at", "id") -VALUES ('31a03e74-1639-45b6-bfa7-77447f1a4762', '31a03e74-1639-45b6-bfa7-77447f1a4762', - '{"sub": "31a03e74-1639-45b6-bfa7-77447f1a4762", "email": "test@makerkit.dev", "email_verified": false, "phone_verified": false}', - 'email', '2024-04-20 08:20:34.46275+00', '2024-04-20 08:20:34.462773+00', '2024-04-20 08:20:34.462773+00', - '9bb58bad-24a4-41a8-9742-1b5b4e2d8abd'), - ('5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf', '5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf', - '{"sub": "5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf", "email": "owner@makerkit.dev", "email_verified": false, "phone_verified": false}', - 'email', '2024-04-20 08:36:27.637388+00', '2024-04-20 08:36:27.637409+00', '2024-04-20 08:36:27.637409+00', - '090598a1-ebba-4879-bbe3-38d517d5066f'), - ('b73eb03e-fb7a-424d-84ff-18e2791ce0b4', 'b73eb03e-fb7a-424d-84ff-18e2791ce0b4', - '{"sub": "b73eb03e-fb7a-424d-84ff-18e2791ce0b4", "email": "custom@makerkit.dev", "email_verified": false, "phone_verified": false}', - 'email', '2024-04-20 08:37:43.342194+00', '2024-04-20 08:37:43.342218+00', '2024-04-20 08:37:43.342218+00', - '4392e228-a6d8-4295-a7d6-baed50c33e7c'), - ('6b83d656-e4ab-48e3-a062-c0c54a427368', '6b83d656-e4ab-48e3-a062-c0c54a427368', - '{"sub": "6b83d656-e4ab-48e3-a062-c0c54a427368", "email": "member@makerkit.dev", "email_verified": false, "phone_verified": false}', - 'email', '2024-04-20 08:41:08.687948+00', '2024-04-20 08:41:08.687982+00', '2024-04-20 08:41:08.687982+00', - 'd122aca5-4f29-43f0-b1b1-940b000638db'), - ('c5b930c9-0a76-412e-a836-4bc4849a3270', 'c5b930c9-0a76-412e-a836-4bc4849a3270', - '{"sub": "c5b930c9-0a76-412e-a836-4bc4849a3270", "email": "super-admin@makerkit.dev", "email_verified": true, "phone_verified": false}', - 'email', '2025-02-24 13:25:01.646641+00', '2025-02-24 13:25:11.181332+00', '2025-02-24 13:25:11.181332+00', - 'c5b930c9-0a76-412e-a836-4bc4849a3270'); - --- --- Data for Name: instances; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - --- --- Data for Name: sessions; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - --- --- Data for Name: mfa_amr_claims; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - --- --- Data for Name: mfa_factors; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - --- --- Data for Name: mfa_challenges; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - --- --- Data for Name: refresh_tokens; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - --- --- Data for Name: sso_providers; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - --- --- Data for Name: saml_providers; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - --- --- Data for Name: saml_relay_states; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - --- --- Data for Name: sso_domains; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin --- - - --- --- Data for Name: key; Type: TABLE DATA; Schema: pgsodium; Owner: supabase_admin --- - - --- --- Data for Name: accounts; Type: TABLE DATA; Schema: public; Owner: postgres --- - -INSERT INTO "public"."accounts" ("id", "primary_owner_user_id", "name", "slug", "email", "is_personal_account", - "updated_at", "created_at", "created_by", "updated_by", "picture_url", "public_data") -VALUES ('5deaa894-2094-4da3-b4fd-1fada0809d1c', '31a03e74-1639-45b6-bfa7-77447f1a4762', 'Makerkit', 'makerkit', NULL, - false, NULL, NULL, NULL, NULL, NULL, '{}'); - --- --- Data for Name: roles; Type: TABLE DATA; Schema: public; Owner: postgres --- - -INSERT INTO "public"."roles" ("name", "hierarchy_level") -VALUES ('custom-role', 4); - --- --- Data for Name: accounts_memberships; Type: TABLE DATA; Schema: public; Owner: postgres --- - -INSERT INTO "public"."accounts_memberships" ("user_id", "account_id", "account_role", "created_at", "updated_at", - "created_by", "updated_by") -VALUES ('31a03e74-1639-45b6-bfa7-77447f1a4762', '5deaa894-2094-4da3-b4fd-1fada0809d1c', 'owner', - '2024-04-20 08:21:16.802867+00', '2024-04-20 08:21:16.802867+00', NULL, NULL), - ('5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf', '5deaa894-2094-4da3-b4fd-1fada0809d1c', 'owner', - '2024-04-20 08:36:44.21028+00', '2024-04-20 08:36:44.21028+00', NULL, NULL), - ('b73eb03e-fb7a-424d-84ff-18e2791ce0b4', '5deaa894-2094-4da3-b4fd-1fada0809d1c', 'custom-role', - '2024-04-20 08:38:02.50993+00', '2024-04-20 08:38:02.50993+00', NULL, NULL), - ('6b83d656-e4ab-48e3-a062-c0c54a427368', '5deaa894-2094-4da3-b4fd-1fada0809d1c', 'member', - '2024-04-20 08:41:17.833709+00', '2024-04-20 08:41:17.833709+00', NULL, NULL); - --- MFA Factors -INSERT INTO "auth"."mfa_factors" ("id", "user_id", "friendly_name", "factor_type", "status", "created_at", "updated_at", - "secret", "phone", "last_challenged_at") -VALUES ('659e3b57-1128-4d26-8757-f714fd073fc4', 'c5b930c9-0a76-412e-a836-4bc4849a3270', 'iPhone', 'totp', 'verified', - '2025-02-24 13:23:55.5805+00', '2025-02-24 13:24:32.591999+00', 'NHOHJVGPO3R3LKVPRMNIYLCDMBHUM2SE', null, - '2025-02-24 13:24:32.563314+00'); - --- --- Data for Name: billing_customers; Type: TABLE DATA; Schema: public; Owner: postgres --- - - --- --- Data for Name: invitations; Type: TABLE DATA; Schema: public; Owner: postgres --- - - --- --- Data for Name: orders; Type: TABLE DATA; Schema: public; Owner: postgres --- - - --- --- Data for Name: order_items; Type: TABLE DATA; Schema: public; Owner: postgres --- - - --- --- Data for Name: subscriptions; Type: TABLE DATA; Schema: public; Owner: postgres --- - - --- --- Data for Name: subscription_items; Type: TABLE DATA; Schema: public; Owner: postgres --- - - --- --- Data for Name: buckets; Type: TABLE DATA; Schema: storage; Owner: supabase_storage_admin --- - --- --- Data for Name: objects; Type: TABLE DATA; Schema: storage; Owner: supabase_storage_admin --- - - --- --- Data for Name: s3_multipart_uploads; Type: TABLE DATA; Schema: storage; Owner: supabase_storage_admin --- - - --- --- Data for Name: s3_multipart_uploads_parts; Type: TABLE DATA; Schema: storage; Owner: supabase_storage_admin --- - - --- --- Data for Name: hooks; Type: TABLE DATA; Schema: supabase_functions; Owner: supabase_functions_admin --- - --- --- Data for Name: secrets; Type: TABLE DATA; Schema: vault; Owner: supabase_admin --- - --- --- Name: refresh_tokens_id_seq; Type: SEQUENCE SET; Schema: auth; Owner: supabase_auth_admin --- - -SELECT pg_catalog.setval('"auth"."refresh_tokens_id_seq"', 5, true); - - --- --- Name: billing_customers_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres --- - -SELECT pg_catalog.setval('"public"."billing_customers_id_seq"', 1, false); - - --- --- Name: invitations_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres --- - -SELECT pg_catalog.setval('"public"."invitations_id_seq"', 19, true); - - --- --- Name: role_permissions_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres --- - -SELECT pg_catalog.setval('"public"."role_permissions_id_seq"', 7, true); - - --- --- Name: hooks_id_seq; Type: SEQUENCE SET; Schema: supabase_functions; Owner: supabase_functions_admin --- - -SELECT pg_catalog.setval('"supabase_functions"."hooks_id_seq"', 19, true); diff --git a/supabase copy/templates/change-email-address.html b/supabase copy/templates/change-email-address.html deleted file mode 100644 index 3fd0c2b..0000000 --- a/supabase copy/templates/change-email-address.html +++ /dev/null @@ -1,8 +0,0 @@ -
Confirm Change of Email | Makerkit
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏
\ No newline at end of file diff --git a/supabase copy/templates/confirm-email.html b/supabase copy/templates/confirm-email.html deleted file mode 100644 index 99c59b5..0000000 --- a/supabase copy/templates/confirm-email.html +++ /dev/null @@ -1,8 +0,0 @@ -
Confirm your email - Makerkit
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏
\ No newline at end of file diff --git a/supabase copy/templates/invite-user.html b/supabase copy/templates/invite-user.html deleted file mode 100644 index add06d2..0000000 --- a/supabase copy/templates/invite-user.html +++ /dev/null @@ -1,8 +0,0 @@ -
You have bee invited to join - Makerkit
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏
\ No newline at end of file diff --git a/supabase copy/templates/magic-link.html b/supabase copy/templates/magic-link.html deleted file mode 100644 index eb3c7c9..0000000 --- a/supabase copy/templates/magic-link.html +++ /dev/null @@ -1,8 +0,0 @@ -
Your sign in link to Makerkit
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏
\ No newline at end of file diff --git a/supabase copy/templates/reset-password.html b/supabase copy/templates/reset-password.html deleted file mode 100644 index ce6ed5a..0000000 --- a/supabase copy/templates/reset-password.html +++ /dev/null @@ -1,8 +0,0 @@ -
Reset your password | Makerkit
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏
\ No newline at end of file diff --git a/supabase copy/tests/database/00000-dbdev.sql b/supabase copy/tests/database/00000-dbdev.sql deleted file mode 100644 index 2eaf9db..0000000 --- a/supabase copy/tests/database/00000-dbdev.sql +++ /dev/null @@ -1,74 +0,0 @@ -create extension if not exists http with schema extensions; -create extension if not exists pg_tle; - -select - no_plan (); - -create or replace function install_extensions() - returns void - as $$ -declare - installed boolean; -begin - select exists ( - select - 1 - from - pg_catalog.pg_extension - where - extname = 'supabase-dbdev' - ) into installed; - - if installed then - return; - end if; - - perform - pgtle.install_extension( - 'supabase-dbdev', - resp.contents ->> 'version', - 'PostgreSQL package manager', - resp.contents ->> 'sql' - ) - from http( - ( - 'GET', - 'https://api.database.dev/rest/v1/' - || 'package_versions?select=sql,version' - || '&package_name=eq.supabase-dbdev' - || '&order=version.desc' - || '&limit=1', - array[ - ('apiKey', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InhtdXB0cHBsZnZpaWZyYndtbXR2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE2ODAxMDczNzIsImV4cCI6MTk5NTY4MzM3Mn0.z2CN0mvO2No8wSi46Gw59DFGCTJrzM0AQKsu_5k134s')::http_header - ], - null, - null - ) - ) x, - lateral ( - select - ((row_to_json(x) -> 'content') #>> '{}')::json -> 0 - ) resp(contents); - - create extension if not exists "supabase-dbdev"; - - perform dbdev.install('supabase-dbdev'); - perform dbdev.install('basejump-supabase_test_helpers'); -end -$$ language plpgsql; - -select install_extensions(); - -select has_column( - 'auth', - 'users', - 'id', - 'id should exist' -); - -select - * -from - finish (); - -rollback; \ No newline at end of file diff --git a/supabase copy/tests/database/00000-makerkit-helpers.sql b/supabase copy/tests/database/00000-makerkit-helpers.sql deleted file mode 100644 index 7c3c3e8..0000000 --- a/supabase copy/tests/database/00000-makerkit-helpers.sql +++ /dev/null @@ -1,151 +0,0 @@ -create schema if not exists makerkit; - --- anon, authenticated, and service_role should have access to makerkit schema -grant USAGE on schema makerkit to anon, authenticated, service_role; - --- Don't allow public to execute any functions in the makerkit schema -alter default PRIVILEGES in schema makerkit revoke execute on FUNCTIONS from public; - --- Grant execute to anon, authenticated, and service_role for testing purposes -alter default PRIVILEGES in schema makerkit grant execute on FUNCTIONS to anon, - authenticated, service_role; - -create or replace function makerkit.get_id_by_identifier( - identifier text -) - returns uuid - as $$ -begin - - return (select id from auth.users where raw_user_meta_data->>'test_identifier' = identifier); - -end; - -$$ language PLPGSQL; - -create or replace function makerkit.set_identifier( - identifier text, - user_email text -) - returns text - security definer - set search_path = auth, pg_temp -as -$$ -begin - update auth.users - set raw_user_meta_data = jsonb_build_object('test_identifier', identifier) - where email = user_email; - - return identifier; - -end; - -$$ language PLPGSQL; - -create or replace function makerkit.get_account_by_slug( - account_slug text -) - returns setof accounts -as -$$ -begin - return query - select * - from accounts - where slug = account_slug; - -end; - -$$ language PLPGSQL; - -create or replace function makerkit.authenticate_as( - identifier text -) returns void -as -$$ -begin - perform tests.authenticate_as(identifier); - perform makerkit.set_session_aal('aal1'); -end; -$$ language plpgsql; - -create or replace function makerkit.get_account_id_by_slug( - account_slug text -) - returns uuid -as -$$ - -begin - - return - (select id - from accounts - where slug = account_slug); - -end; - -$$ language PLPGSQL; - - -create or replace function makerkit.set_mfa_factor( - identifier text = gen_random_uuid() -) - returns void -as -$$ -begin - insert into "auth"."mfa_factors" ("id", "user_id", "friendly_name", "factor_type", "status", "created_at", "updated_at", "secret") - values (gen_random_uuid(), auth.uid(), identifier, 'totp', 'verified', '2025-02-24 09:48:18.402031+00', '2025-02-24 09:48:18.402031+00', - 'HOWQFBA7KBDDRSBNMGFYZAFNPRSZ62I5'); -end; -$$ language plpgsql security definer; - -create or replace function makerkit.set_session_aal(session_aal auth.aal_level) - returns void -as -$$ -begin - perform set_config('request.jwt.claims', json_build_object( - 'sub', current_setting('request.jwt.claims')::json ->> 'sub', - 'email', current_setting('request.jwt.claims')::json ->> 'email', - 'phone', current_setting('request.jwt.claims')::json ->> 'phone', - 'user_metadata', current_setting('request.jwt.claims')::json ->> 'user_metadata', - 'app_metadata', current_setting('request.jwt.claims')::json ->> 'app_metadata', - 'aal', session_aal)::text, true); -end; -$$ language plpgsql; - -create or replace function makerkit.set_super_admin() returns void -as -$$ -begin - perform set_config('request.jwt.claims', json_build_object( - 'sub', current_setting('request.jwt.claims')::json ->> 'sub', - 'email', current_setting('request.jwt.claims')::json ->> 'email', - 'phone', current_setting('request.jwt.claims')::json ->> 'phone', - 'user_metadata', current_setting('request.jwt.claims')::json ->> 'user_metadata', - 'app_metadata', json_build_object('role', 'super-admin'), - 'aal', current_setting('request.jwt.claims')::json ->> 'aal' - )::text, true); -end; -$$ language plpgsql; - -begin; - -select plan(1); - -select is_empty($$ - select - * - from - makerkit.get_account_by_slug('test') $$, - 'get_account_by_slug should return an empty set when the account does not exist' - ); - -select * -from - finish(); - -rollback; diff --git a/supabase copy/tests/database/account-permissions.test.sql b/supabase copy/tests/database/account-permissions.test.sql deleted file mode 100644 index de55629..0000000 --- a/supabase copy/tests/database/account-permissions.test.sql +++ /dev/null @@ -1,104 +0,0 @@ -BEGIN; -create extension "basejump-supabase_test_helpers" version '0.0.6'; - -select no_plan(); - ---- we insert a user into auth.users and return the id into user_id to use - -select tests.create_supabase_user('test1', 'test1@test.com'); - -select tests.create_supabase_user('test2'); - --- Create an team account - -select makerkit.authenticate_as('test1'); - -select public.create_team_account('Test'); - --- the owner account has permissions to manage members -select row_eq( - $$ select public.has_permission( - auth.uid(), makerkit.get_account_id_by_slug('test'), 'members.manage'::app_permissions) $$, - row(true::boolean), - 'The owner of the team account should have the members.manage permission' -); - --- the owner account has permissions to manage billing -select row_eq( - $$ select public.has_permission( - auth.uid(), makerkit.get_account_id_by_slug('test'), 'billing.manage'::app_permissions) $$, - row(true::boolean), - 'The owner of the team account should have the billing.manage permission' -); - --- Foreigner should not have permissions to manage members - -select makerkit.authenticate_as('test2'); - -select row_eq( - $$ select public.has_permission( - auth.uid(), makerkit.get_account_id_by_slug('test'), 'members.manage'::app_permissions) $$, - row(false::boolean), - 'Foreigners should not have the members.manage permission' -); - --- Custom roles --- New roles created for the app - -set local role postgres; - --- the name should be unique - -select throws_ok( - $$ insert into public.roles (name, hierarchy_level) values ('owner', 4) $$, - 'duplicate key value violates unique constraint "roles_pkey"' -); - --- the hierarchy level should be unique -select throws_ok( - $$ insert into public.roles (name, hierarchy_level) values ('custom-role-2', 1) $$, - 'duplicate key value violates unique constraint "roles_hierarchy_level_key"' -); - --- Custom Account Role - -set local role postgres; - --- the names should be unique -select throws_ok( - $$ insert into public.roles (name, hierarchy_level) values ('owner', 1) $$, - 'duplicate key value violates unique constraint "roles_pkey"' -); - --- update user role to custom role -update public.accounts_memberships - set account_role = 'custom-role' - where account_id = makerkit.get_account_id_by_slug('test') - and user_id = tests.get_supabase_uid('test1'); - -set local role postgres; - --- insert permissions for the custom role -insert into public.role_permissions (role, permission) values ('custom-role', 'members.manage'); - -select makerkit.authenticate_as('test1'); - --- the custom role does not have permissions to manage billing -select row_eq( - $$ select public.has_permission( - auth.uid(), makerkit.get_account_id_by_slug('test'), 'billing.manage'::app_permissions) $$, - row(false::boolean), - 'The custom role should not have the billing.manage permission' -); - --- the custom role can manage members -select row_eq( - $$ select public.has_permission( - auth.uid(), makerkit.get_account_id_by_slug('test'), 'members.manage'::app_permissions) $$, - row(true::boolean), - 'The custom role should have the members.manage permission' -); - -select * from finish(); - -rollback; \ No newline at end of file diff --git a/supabase copy/tests/database/account-slug.test.sql b/supabase copy/tests/database/account-slug.test.sql deleted file mode 100644 index 1035619..0000000 --- a/supabase copy/tests/database/account-slug.test.sql +++ /dev/null @@ -1,128 +0,0 @@ -BEGIN; -create extension "basejump-supabase_test_helpers" version '0.0.6'; - -select no_plan(); - ---- we insert a user into auth.users and return the id into user_id to use - -select tests.create_supabase_user('test1', 'test1@test.com'); - -select tests.create_supabase_user('test2'); - --- Create an team account - -select makerkit.authenticate_as('test1'); - -select public.create_team_account('Test'); -select public.create_team_account('Test'); -select public.create_team_account('Test'); - --- should automatically create slugs for the accounts -select row_eq( - $$ select slug from public.accounts where name = 'Test' and slug = 'test' $$, - row('test'::text), - 'The first team account should automatically create a slug named "test"' -); - -select row_eq( - $$ select slug from public.accounts where name = 'Test' and slug = 'test-1' $$, - row('test-1'::text), - 'The second team account should automatically create a slug named "test-1"' -); - -select row_eq( - $$ select slug from public.accounts where name = 'Test' and slug = 'test-2' $$, - row('test-2'::text), - 'The third team account should automatically create a slug named "test-2"' -); - --- Should automatically update the slug if the name is updated -update public.accounts set name = 'Test 4' where slug = 'test-2'; - -select row_eq( - $$ select slug from public.accounts where name = 'Test 4' $$, - row('test-4'::text), - 'Updating the name of a team account should update the slug' -); - --- Should fail if the slug is updated to an existing slug -select throws_ok( - $$ update public.accounts set slug = 'test-1' where slug = 'test-4' $$, - 'duplicate key value violates unique constraint "accounts_slug_key"' -); - --- Test special characters in the slug -update public.accounts set slug = 'test-5' where slug = 'test-4['; - -select row_eq( - $$ select slug from public.accounts where name = 'Test 4' $$, - row('test-4'::text), - 'Updating the name of a team account should update the slug' -); - --- Test various special characters -update public.accounts set name = 'Test@Special#Chars$' where slug = 'test-4'; - -select row_eq( - $$ select slug from public.accounts where name = 'Test@Special#Chars$' $$, - row('test-special-chars'::text), - 'Special characters should be removed from slug' -); - --- Test multiple consecutive special characters -update public.accounts set name = 'Test!!Multiple---Special$$$Chars' where slug = 'test-special-chars'; - -select row_eq( - $a$ select slug from public.accounts where name = 'Test!!Multiple---Special$$$Chars' $a$, - row('test-multiple-special-chars'::text), - 'Multiple consecutive special characters should be replaced with single hyphen' -); - --- Test leading and trailing special characters -update public.accounts set name = '!!!LeadingAndTrailing###' where slug = 'test-multiple-special-chars'; - -select row_eq( - $$ select slug from public.accounts where name = '!!!LeadingAndTrailing###' $$, - row('leadingandtrailing'::text), - 'Leading and trailing special characters should be removed' -); - --- Test non-ASCII characters -update public.accounts set name = 'Testéñ中文Русский' where slug = 'leadingandtrailing'; - -select row_eq( - $$ select slug from public.accounts where name = 'Testéñ中文Русский' $$, - row('testen'::text), - 'Non-ASCII characters should be transliterated or removed' -); - --- Test mixed case with special characters -update public.accounts set name = 'Test Mixed CASE With Special@Chars!' where slug = 'testen'; - -select row_eq( - $$ select slug from public.accounts where name = 'Test Mixed CASE With Special@Chars!' $$, - row('test-mixed-case-with-special-chars'::text), - 'Mixed case should be converted to lowercase and special chars handled' -); - --- Test using parentheses -update public.accounts set name = 'Test (Parentheses)' where slug = 'test-mixed-case-with-special-chars'; - -select row_eq( - $$ select slug from public.accounts where name = 'Test (Parentheses)' $$, - row('test-parentheses'::text), - 'Parentheses should be removed from slug' -); - --- Test using asterisk -update public.accounts set name = 'Test * Asterisk' where slug = 'test-parentheses'; - -select row_eq( - $$ select slug from public.accounts where name = 'Test * Asterisk' $$, - row('test-asterisk'::text), - 'Asterisk should be removed from slug' -); - -select * from finish(); - -ROLLBACK; \ No newline at end of file diff --git a/supabase copy/tests/database/delete-membership.test.sql b/supabase copy/tests/database/delete-membership.test.sql deleted file mode 100644 index 5d87f9a..0000000 --- a/supabase copy/tests/database/delete-membership.test.sql +++ /dev/null @@ -1,94 +0,0 @@ -begin; -create extension "basejump-supabase_test_helpers" version '0.0.6'; - -select no_plan(); - -select makerkit.set_identifier('primary_owner', 'test@makerkit.dev'); -select makerkit.set_identifier('owner', 'owner@makerkit.dev'); -select makerkit.set_identifier('member', 'member@makerkit.dev'); -select makerkit.set_identifier('custom', 'custom@makerkit.dev'); - --- another user not in the team -select tests.create_supabase_user('test', 'test@supabase.com'); - --- an owner cannot remove the primary owner -select makerkit.authenticate_as('owner'); - -select throws_ok( - $$ delete from public.accounts_memberships - where account_id = makerkit.get_account_id_by_slug('makerkit') - and user_id = '31a03e74-1639-45b6-bfa7-77447f1a4762' $$, - 'The primary account owner cannot be actioned' -); - --- an owner can remove accounts with lower roles -select lives_ok( - $$ delete from public.accounts_memberships - where account_id = makerkit.get_account_id_by_slug('makerkit') - and user_id = '6b83d656-e4ab-48e3-a062-c0c54a427368' $$, - 'Owner should be able to remove a member' -); - --- a member cannot remove a member with a higher role -select makerkit.authenticate_as('member'); - --- delete a membership record where the user is a higher role than the current user -select throws_ok( - $$ delete from public.accounts_memberships - where account_id = makerkit.get_account_id_by_slug('makerkit') - and user_id = '5c064f1b-78ee-4e1c-ac3b-e99aa97c99bf' $$, - 'You do not have permission to action a member from this account' -); - --- an primary_owner cannot remove themselves -select makerkit.authenticate_as('primary_owner'); - -select throws_ok( - $$ delete from public.accounts_memberships - where account_id = makerkit.get_account_id_by_slug('makerkit') - and user_id = '31a03e74-1639-45b6-bfa7-77447f1a4762' $$, - 'The primary account owner cannot be removed from the account membership list' -); - --- a primary_owner can remove another member -select lives_ok( - $$ delete from public.accounts_memberships - where account_id = makerkit.get_account_id_by_slug('makerkit') - and user_id = 'b73eb03e-fb7a-424d-84ff-18e2791ce0b4'; $$, - 'Primary owner should be able to remove another member' -); - --- foreigners - --- a user not in the account cannot remove a member - -select makerkit.authenticate_as('test'); - -select throws_ok( - $$ delete from public.accounts_memberships - where account_id = '5deaa894-2094-4da3-b4fd-1fada0809d1c' - and user_id = tests.get_supabase_uid('owner'); $$, - 'You do not have permission to action a member from this account' - ); - -select makerkit.authenticate_as('owner'); - -select isnt_empty( - $$ select 1 from public.accounts_memberships - where account_id = '5deaa894-2094-4da3-b4fd-1fada0809d1c' - and user_id = tests.get_supabase_uid('owner'); $$, - 'Foreigners should not be able to remove members'); - -select makerkit.authenticate_as('test'); - --- a user not in the account cannot remove themselves -select throws_ok( - $$ delete from public.accounts_memberships - where account_id = makerkit.get_account_id_by_slug('makerkit') - and user_id = auth.uid(); $$, - 'You do not have permission to action a member from this account' -); - -select * from finish(); - -rollback; \ No newline at end of file diff --git a/supabase copy/tests/database/invitations.test.sql b/supabase copy/tests/database/invitations.test.sql deleted file mode 100644 index 757397b..0000000 --- a/supabase copy/tests/database/invitations.test.sql +++ /dev/null @@ -1,111 +0,0 @@ -begin; -create extension "basejump-supabase_test_helpers" version '0.0.6'; - -select no_plan(); - --- test - -select makerkit.set_identifier('test', 'test@makerkit.dev'); -select makerkit.set_identifier('member', 'member@makerkit.dev'); -select makerkit.set_identifier('custom', 'custom@makerkit.dev'); -select makerkit.set_identifier('owner', 'owner@makerkit.dev'); - -select makerkit.authenticate_as('test'); - -select lives_ok( - $$ insert into public.invitations (email, invited_by, account_id, role, invite_token) values ('invite1@makerkit.dev', auth.uid(), makerkit.get_account_id_by_slug('makerkit'), 'member', gen_random_uuid()); $$, -'owner should be able to create invitations' -); - --- check two invitations to the same email/account are not allowed -select throws_ok( - $$ insert into public.invitations (email, invited_by, account_id, role, invite_token) values ('invite1@makerkit.dev', auth.uid(), makerkit.get_account_id_by_slug('makerkit'), 'member', gen_random_uuid()) $$, - 'duplicate key value violates unique constraint "invitations_email_account_id_key"' -); - -select makerkit.authenticate_as('member'); - --- check a member cannot invite members with higher roles -select throws_ok( - $$ insert into public.invitations (email, invited_by, account_id, role, invite_token) values ('invite2@makerkit.dev', auth.uid(), makerkit.get_account_id_by_slug('makerkit'), 'owner', gen_random_uuid()) $$, - 'new row violates row-level security policy for table "invitations"' -); - --- check a member can invite members with the same or lower roles -select lives_ok( - $$ insert into public.invitations (email, invited_by, account_id, role, invite_token) values ('invite2@makerkit.dev', auth.uid(), makerkit.get_account_id_by_slug('makerkit'), 'member', gen_random_uuid()) $$, - 'member should be able to create invitations for members or lower roles' -); - --- test invite exists -select isnt_empty( - $$ select * from public.invitations where account_id = makerkit.get_account_id_by_slug('makerkit') $$, - 'invitations should be listed' -); - -select makerkit.authenticate_as('owner'); - --- check the owner can invite members with lower roles -select lives_ok( - $$ insert into public.invitations (email, invited_by, account_id, role, invite_token) values ('invite3@makerkit.dev', auth.uid(), makerkit.get_account_id_by_slug('makerkit'), 'member', gen_random_uuid()) $$, - 'owner should be able to create invitations' -); - --- authenticate_as the custom role -select makerkit.authenticate_as('custom'); - --- it will fail because the custom role does not have the invites.manage permission -select throws_ok( - $$ insert into public.invitations (email, invited_by, account_id, role, invite_token) values ('invite3@makerkit.dev', auth.uid(), makerkit.get_account_id_by_slug('makerkit'), 'custom-role', gen_random_uuid()) $$, - 'new row violates row-level security policy for table "invitations"' -); - -set local role postgres; - --- add permissions to invite members to the custom role -insert into public.role_permissions (role, permission) values ('custom-role', 'invites.manage'); - --- authenticate_as the custom role -select makerkit.authenticate_as('custom'); - -select lives_ok( - $$ insert into public.invitations (email, invited_by, account_id, role, invite_token) values ('invite4@makerkit.dev', auth.uid(), makerkit.get_account_id_by_slug('makerkit'), 'custom-role', gen_random_uuid()) $$, - 'custom role should be able to create invitations' -); - -select lives_ok( - $$ SELECT public.add_invitations_to_account('makerkit', ARRAY[ROW('example@makerkit.dev', 'custom-role')::public.invitation]); $$, - 'custom role should be able to create invitations using the function public.add_invitations_to_account' -); - -select throws_ok( - $$ SELECT public.add_invitations_to_account('makerkit', ARRAY[ROW('example2@makerkit.dev', 'owner')::public.invitation]); $$, - 'new row violates row-level security policy for table "invitations"', - 'cannot invite members with higher roles' -); - --- Foreigners should not be able to create invitations - -select tests.create_supabase_user('user'); - -select makerkit.authenticate_as('user'); - --- it will fail because the user is not a member of the account -select throws_ok( - $$ insert into public.invitations (email, invited_by, account_id, role, invite_token) values ('invite4@makerkit.dev', auth.uid(), makerkit.get_account_id_by_slug('makerkit'), 'member', gen_random_uuid()) $$, - 'new row violates row-level security policy for table "invitations"' -); - -select throws_ok( - $$ SELECT public.add_invitations_to_account('makerkit', ARRAY[ROW('example@example.com', 'member')::public.invitation]); $$, - 'new row violates row-level security policy for table "invitations"' -); - -select is_empty($$ - select * from public.invitations where account_id = makerkit.get_account_id_by_slug('makerkit') $$, - 'no invitations should be listed' -); - -select * from finish(); - -rollback; diff --git a/supabase copy/tests/database/memberships.test.sql b/supabase copy/tests/database/memberships.test.sql deleted file mode 100644 index 78c7cb0..0000000 --- a/supabase copy/tests/database/memberships.test.sql +++ /dev/null @@ -1,92 +0,0 @@ -begin; -create extension "basejump-supabase_test_helpers" version '0.0.6'; - -select no_plan(); - -select makerkit.set_identifier('primary_owner', 'test@makerkit.dev'); -select makerkit.set_identifier('owner', 'owner@makerkit.dev'); -select makerkit.set_identifier('member', 'member@makerkit.dev'); -select makerkit.set_identifier('custom', 'custom@makerkit.dev'); - --- another user not in the team -select tests.create_supabase_user('test', 'test@supabase.com'); - -select makerkit.authenticate_as('owner'); - --- Can check if an account is a team member - --- Primary owner -select is( - (select public.is_team_member( - makerkit.get_account_id_by_slug('makerkit'), - tests.get_supabase_uid('member') - )), - true, - 'The primary account owner can check if a member is a team member' -); - -select makerkit.authenticate_as('member'); - --- Member -select is( - (select public.is_team_member( - makerkit.get_account_id_by_slug('makerkit'), - tests.get_supabase_uid('owner') - )), - true, - 'The member can check if another member is a team member' -); - -select is( - (select public.has_role_on_account( - makerkit.get_account_id_by_slug('makerkit') - )), - true, - 'The member can check if they have a role on the account' -); - -select isnt_empty( - $$ select * from public.get_account_members('makerkit') $$, - 'The member can query the team account memberships using the get_account_members function' -); - -select makerkit.authenticate_as('test'); - --- Foreigners --- Cannot query the team account memberships -select is( - (select public.is_team_member( - makerkit.get_account_id_by_slug('makerkit'), - tests.get_supabase_uid('owner') - )), - false, - 'The foreigner cannot check if a member is a team member' -); - --- Does not have a role on the account -select is( - (select public.has_role_on_account( - makerkit.get_account_id_by_slug('makerkit') - )), - false, - 'The foreigner does not have a role on the account' -); - -select is_empty( - $$ select * from public.accounts_memberships where account_id = makerkit.get_account_id_by_slug('makerkit') $$, - 'The foreigner cannot query the team account memberships' -); - -select is_empty( - $$ select * from public.accounts where id = makerkit.get_account_id_by_slug('makerkit') $$, - 'The foreigner cannot query the team account' -); - -select is_empty( - $$ select * from public.get_account_members('makerkit') $$, - 'The foreigner cannot query the team members' -); - -select * from finish(); - -rollback; diff --git a/supabase copy/tests/database/notifications.test.sql b/supabase copy/tests/database/notifications.test.sql deleted file mode 100644 index 07457b3..0000000 --- a/supabase copy/tests/database/notifications.test.sql +++ /dev/null @@ -1,77 +0,0 @@ -BEGIN; -create extension "basejump-supabase_test_helpers" version '0.0.6'; - -select no_plan(); - ---- we insert a user into auth.users and return the id into user_id to use - -select tests.create_supabase_user('test1', 'test1@test.com'); - -select tests.create_supabase_user('test2'); - -select makerkit.authenticate_as('test1'); - --- users cannot insert into notifications -select throws_ok( - $$ insert into public.notifications(account_id, body) values (tests.get_supabase_uid('test1'), 'test'); $$, - 'permission denied for table notifications' -); - -set local role service_role; - --- service role can insert into notifications -select lives_ok( - $$ insert into public.notifications(account_id, body) values (tests.get_supabase_uid('test1'), 'test'); $$, - 'service role can insert into notifications' -); - -select makerkit.authenticate_as('test1'); - --- user can read their own notifications -select row_eq( - $$ select account_id, body from public.notifications where account_id = tests.get_supabase_uid('test1'); $$, - row (tests.get_supabase_uid('test1'), 'test'::varchar), - 'user can read their own notifications' -); - --- user can read their team notifications -select makerkit.set_identifier('primary_owner', 'test@makerkit.dev'); -select makerkit.set_identifier('owner', 'owner@makerkit.dev'); -select makerkit.set_identifier('member', 'member@makerkit.dev'); -select makerkit.set_identifier('custom', 'custom@makerkit.dev'); - -set local role service_role; - --- service role can insert into notifications -select lives_ok( - $$ insert into public.notifications(account_id, body) values (makerkit.get_account_id_by_slug('makerkit'), 'test'); $$, - 'service role can insert into notifications' -); - -select makerkit.authenticate_as('member'); - -select row_eq( - $$ select account_id, body from public.notifications where account_id = makerkit.get_account_id_by_slug('makerkit'); $$, - row (makerkit.get_account_id_by_slug('makerkit'), 'test'::varchar), - 'user can read their team notifications' -); - --- foreigners - -select makerkit.authenticate_as('test2'); - --- foreigner cannot read other user's notifications -select is_empty( - $$ select account_id, body from public.notifications where account_id = tests.get_supabase_uid('test1'); $$, - 'foreigner cannot read other users notifications' -); - --- foreigner cannot read other teams notifications -select is_empty( - $$ select account_id, body from public.notifications where account_id = makerkit.get_account_id_by_slug('makerkit'); $$, - 'foreigner cannot read other teams notifications' -); - -select * from finish(); - -rollback; \ No newline at end of file diff --git a/supabase copy/tests/database/otp.test.sql b/supabase copy/tests/database/otp.test.sql deleted file mode 100644 index 8a7d19d..0000000 --- a/supabase copy/tests/database/otp.test.sql +++ /dev/null @@ -1,1112 +0,0 @@ -begin; -create extension "basejump-supabase_test_helpers" version '0.0.6'; - -select no_plan(); -- Use no_plan for flexibility - -select tests.create_supabase_user('token_creator', 'creator@example.com'); -select tests.create_supabase_user('token_verifier', 'verifier@example.com'); - --- ========================================== --- Test 1: Permission Tests --- ========================================== - --- Test 1.1: Regular users cannot create nonces directly -select tests.authenticate_as('token_creator'); - -select throws_ok( - $$ select public.create_nonce(auth.uid(), 'password-reset', 3600) $$, - 'permission denied for function create_nonce', - 'Regular users should not be able to create nonces directly' -); - --- Test 1.2: Regular users cannot revoke nonces -select throws_ok( - $$ select public.revoke_nonce('00000000-0000-0000-0000-000000000000'::uuid, 'test') $$, - 'permission denied for function revoke_nonce', - 'Regular users should not be able to revoke tokens' -); - --- Test 1.3: Service role can create nonces -set local role service_role; - --- Create a token and store it for later verification -do $$ -declare - token_result jsonb; -begin - token_result := public.create_nonce( - null, - 'password-reset', - 3600, - '{"redirect_url": "/reset-password"}'::jsonb, - ARRAY['auth:reset'] - ); - - -- Store the token for later verification - perform set_config('app.settings.test_token', token_result->>'token', false); - perform set_config('app.settings.test_token_id', token_result->>'id', false); - perform set_config('app.settings.test_token_json', token_result::text, false); -end $$; - --- Check token result properties -select ok( - (current_setting('app.settings.test_token_json', false)::jsonb) ? 'id', - 'Token result should contain an id' -); -select ok( - (current_setting('app.settings.test_token_json', false)::jsonb) ? 'token', - 'Token result should contain a token' -); -select ok( - (current_setting('app.settings.test_token_json', false)::jsonb) ? 'expires_at', - 'Token result should contain an expiration time' -); - -set local role postgres; - --- Create a token for an authenticated user -do $$ -declare - token_result jsonb; - auth_user_id uuid; -begin - auth_user_id := makerkit.get_id_by_identifier('token_creator'); - - token_result := public.create_nonce( - auth_user_id, - 'email-verification', - 3600 - ); - - -- Store the token for later user verification - perform set_config('app.settings.user_token', token_result->>'token', false); - perform set_config('app.settings.user_token_id', token_result->>'id', false); - perform set_config('app.settings.user_token_json', token_result::text, false); -end $$; - --- Check user token result properties -select ok( - (current_setting('app.settings.user_token_json', false)::jsonb) ? 'id', - 'Token result with minimal params should contain an id' -); -select ok( - (current_setting('app.settings.user_token_json', false)::jsonb) ? 'token', - 'Token result with minimal params should contain a token' -); - --- Create an anonymous token (no user_id) -do $$ -declare - token_result jsonb; -begin - token_result := public.create_nonce( - null, - 'user-invitation', - 7200, - '{"team_id": "123456"}'::jsonb - ); - - -- Store the anonymous token for later verification - perform set_config('app.settings.anonymous_token', token_result->>'token', false); - - perform set_config('app.settings.anonymous_token_id', token_result->>'id', false); - - perform set_config('app.settings.anonymous_token_json', token_result::text, false); -end $$; - --- Check anonymous token result properties -select ok( - (current_setting('app.settings.anonymous_token_json', false)::jsonb) ? 'id', - 'Anonymous token result should contain an id' -); - -select ok( - (current_setting('app.settings.anonymous_token_json', false)::jsonb) ? 'token', - 'Anonymous token result should contain a token' -); - --- ========================================== --- Test 2: Verify Tokens --- ========================================== - --- Test 2.1: Authenticated users can verify tokens -select tests.authenticate_as('token_creator'); - --- Verify token and store result -do $$ -declare - test_token text; - verification_result jsonb; -begin - test_token := current_setting('app.settings.test_token', false); - - verification_result := public.verify_nonce( - test_token, - 'password-reset' - ); - - perform set_config('app.settings.verification_result', verification_result::text, false); -end $$; - --- Check verification result -select is( - (current_setting('app.settings.verification_result', false)::jsonb)->>'valid', - 'true', - 'Token should be valid' -); - -select ok( - (current_setting('app.settings.verification_result', false)::jsonb) ? 'metadata', - 'Result should contain metadata' -); - -select ok( - (current_setting('app.settings.verification_result', false)::jsonb) ? 'scopes', - 'Result should contain scopes' -); - --- Test 2.2: Users can verify tokens assigned to them -do $$ -declare - user_token text; - verification_result jsonb; - user_id uuid; -begin - user_token := current_setting('app.settings.user_token', false); - - set local role postgres; - user_id := makerkit.get_id_by_identifier('token_creator'); - - perform tests.authenticate_as('token_creator'); - - verification_result := public.verify_nonce( - user_token, - 'email-verification', - user_id - ); - - perform set_config('app.settings.user_verification_result', verification_result::text, false); -end $$; - --- Check user verification result -select is( - (current_setting('app.settings.user_verification_result', false)::jsonb)->>'valid', - 'true', - 'User-specific token should be valid' -); - -select isnt( - (current_setting('app.settings.user_verification_result', false)::jsonb)->>'user_id', - null, - 'User-specific token should have user_id' -); - --- Test 2.3: Verify token with scopes -set local role service_role; - --- Create token with scopes -do $$ -declare - scope_token_result jsonb; -begin - -- Create token with scopes - scope_token_result := public.create_nonce( - null, - 'api-access', - 3600, - '{"permissions": "read-only"}'::jsonb, - ARRAY['read:profile', 'read:posts'] - ); - - -- Store for verification - perform set_config('app.settings.scope_token', scope_token_result->>'token', false); -end $$; - --- Verify with correct scope -do $$ -declare - scope_token text; - verification_result jsonb; - user_id uuid; -begin - set local role postgres; - scope_token := current_setting('app.settings.scope_token', false); - user_id := makerkit.get_id_by_identifier('token_verifier'); - - perform tests.authenticate_as('token_verifier'); - - -- Verify with correct required scope - verification_result := public.verify_nonce( - scope_token, - 'api-access', - null, - ARRAY['read:profile'] - ); - - perform set_config('app.settings.correct_scope_result', verification_result::text, false); - - -- Verify with incorrect required scope - verification_result := public.verify_nonce( - scope_token, - 'api-access', - null, - ARRAY['write:posts'] - ); - - perform set_config('app.settings.incorrect_scope_result', verification_result::text, false); -end $$; - --- Check scope verification results -select is( - (current_setting('app.settings.correct_scope_result', false)::jsonb)->>'valid', - 'true', - 'Token with correct scope should be valid' -); - -select is( - (current_setting('app.settings.incorrect_scope_result', false)::jsonb)->>'valid', - 'false', - 'Token with incorrect scope should be invalid' -); - --- Test 2.4: Once used, token becomes invalid -do $$ -declare - token_result jsonb; - first_verification jsonb; - second_verification jsonb; -begin - -- Use service role to create a token - set local role service_role; - - -- Create a token - token_result := public.create_nonce( - null, - 'one-time-action', - 3600 - ); - - set local role authenticated; - - -- Verify it once (uses it) - first_verification := public.verify_nonce( - token_result->>'token', - 'one-time-action' - ); - - -- Try to verify again - second_verification := public.verify_nonce( - token_result->>'token', - 'one-time-action' - ); - - perform set_config('app.settings.first_verification', first_verification::text, false); - perform set_config('app.settings.second_verification', second_verification::text, false); -end $$; - --- Check first and second verification results -select is( - (current_setting('app.settings.first_verification', false)::jsonb)->>'valid', - 'true', - 'First verification should succeed' -); -select is( - (current_setting('app.settings.second_verification', false)::jsonb)->>'valid', - 'false', - 'Token should not be valid on second use' -); - --- Test 2.5: Verify with incorrect purpose -do $$ -declare - token_result jsonb; - verification_result jsonb; -begin - -- Use service role to create a token - set local role service_role; - - -- Create a token - token_result := public.create_nonce( - null, - 'specific-purpose', - 3600 - ); - - set local role authenticated; - - -- Verify with wrong purpose - verification_result := public.verify_nonce( - token_result->>'token', - 'different-purpose' - ); - - perform set_config('app.settings.wrong_purpose_result', verification_result::text, false); -end $$; - --- Check wrong purpose verification result -select is( - (current_setting('app.settings.wrong_purpose_result', false)::jsonb)->>'valid', - 'false', - 'Token with incorrect purpose should be invalid' -); - --- ========================================== --- Test 3: Revoke Tokens --- ========================================== - --- Test 3.1: Only service_role can revoke tokens -select tests.authenticate_as('token_creator'); - -select - has_function( - 'public', - 'revoke_nonce', - ARRAY['uuid', 'text'], - 'revoke_nonce function should exist' - ); - -select throws_ok( - $$ select public.revoke_nonce('00000000-0000-0000-0000-000000000000'::uuid, 'test reason') $$, - 'permission denied for function revoke_nonce', - 'Regular users should not be able to revoke tokens' -); - --- Test 3.2: Service role can revoke tokens -set local role service_role; - -do $$ -declare - token_result jsonb; - revoke_result boolean; - verification_result jsonb; - token_id uuid; -begin - -- Create a token - token_result := public.create_nonce( - null, - 'revokable-action', - 3600 - ); - - token_id := token_result->>'id'; - - -- Revoke the token - revoke_result := public.revoke_nonce( - token_id, - 'Security concern' - ); - - -- Switch to regular user to try to verify the revoked token - set local role authenticated; - - -- Try to verify the revoked token - verification_result := public.verify_nonce( - token_result->>'token', - 'revokable-action' - ); - - perform set_config('app.settings.revoke_result', revoke_result::text, false); - perform set_config('app.settings.revoked_verification', verification_result::text, false); -end $$; - --- Check revocation results -select is( - current_setting('app.settings.revoke_result', false)::boolean, - true, - 'Token revocation should succeed' -); -select is( - (current_setting('app.settings.revoked_verification', false)::jsonb)->>'valid', - 'false', - 'Revoked token should be invalid' -); - --- ========================================== --- Test 4: Get Token Status --- ========================================== - --- Test 4.1: Verify permission on get_nonce_status -select tests.authenticate_as('token_creator'); - -select throws_ok( - $$ select public.get_nonce_status('00000000-0000-0000-0000-000000000000'::uuid) $$, - 'permission denied for function get_nonce_status', - 'Regular users should not be able to check token status' -); - --- Test 4.2: Service role can check token status -set local role service_role; - -select - has_function( - 'public', - 'get_nonce_status', - ARRAY['uuid'], - 'get_nonce_status function should exist' - ); - -do $$ -declare - token_result jsonb; - status_result jsonb; - token_id uuid; -begin - -- Create a token - token_result := public.create_nonce( - null, - 'status-check-test', - 3600 - ); - - token_id := token_result->>'id'; - - -- Get status - status_result := public.get_nonce_status(token_id); - - perform set_config('app.settings.status_result', status_result::text, false); -end $$; - --- Check status result -select is( - (current_setting('app.settings.status_result', false)::jsonb)->>'exists', - 'true', - 'Token should exist' -); -select is( - (current_setting('app.settings.status_result', false)::jsonb)->>'purpose', - 'status-check-test', - 'Purpose should match' -); -select is( - (current_setting('app.settings.status_result', false)::jsonb)->>'is_valid', - 'true', - 'Token should be valid' -); -select is( - (current_setting('app.settings.status_result', false)::jsonb)->>'verification_attempts', - '0', - 'New token should have 0 verification attempts' -); -select is( - (current_setting('app.settings.status_result', false)::jsonb)->>'used_at', - null, - 'New token should not be used' -); -select is( - (current_setting('app.settings.status_result', false)::jsonb)->>'revoked', - 'false', - 'New token should not be revoked' -); - --- ========================================== --- Test 5: Cleanup Expired Tokens --- ========================================== - --- Test 5.1: Regular users cannot access cleanup function -select tests.authenticate_as('token_creator'); - -select throws_ok( - $$ select kit.cleanup_expired_nonces() $$, - 'permission denied for schema kit', - 'Regular users should not be able to clean up tokens' -); - --- Test 5.2: Postgres can clean up expired tokens -set local role postgres; - -select - has_function( - 'kit', - 'cleanup_expired_nonces', - ARRAY['integer', 'boolean', 'boolean'], - 'cleanup_expired_nonces function should exist' - ); - -do $$ -declare - token_result jsonb; - cleanup_result integer; - token_count integer; -begin - -- Create an expired token (expiring in -10 seconds from now) - token_result := public.create_nonce( - null, - 'expired-token-test', - -10 -- Negative value to create an already expired token - ); - - -- Run cleanup - cleanup_result := kit.cleanup_expired_nonces(); - - -- Verify the token is gone - select count(*) into token_count from public.nonces where id = (token_result->>'id')::uuid; - - perform set_config('app.settings.cleanup_result', cleanup_result::text, false); - perform set_config('app.settings.token_count_after_cleanup', token_count::text, false); -end $$; - --- Check cleanup results -select cmp_ok( - current_setting('app.settings.cleanup_result', false)::integer, - '>=', - 1, - 'Cleanup should remove at least one expired token' -); -select is( - current_setting('app.settings.token_count_after_cleanup', false)::integer, - 0, - 'Expired token should be removed after cleanup' -); - --- ========================================== --- Test 6: Security Tests --- ========================================== - --- Test 6.1: Regular users cannot view tokens directly from the nonces table -select tests.authenticate_as('token_creator'); - -set local role postgres; - -do $$ -declare - creator_id uuid; - token_id uuid; -begin - -- Get the user id - creator_id := makerkit.get_id_by_identifier('token_creator'); - - -- Create a token for this user - token_id := (public.create_nonce(creator_id, 'security-test', 3600))->>'id'; - perform set_config('app.settings.security_test_token_id', token_id::text, false); -end $$; - -select tests.authenticate_as('token_creator'); -do $$ -declare - token_id uuid; - token_count integer; -begin - -- Get the token ID created by service role - token_id := (current_setting('app.settings.security_test_token_id', false))::uuid; - - -- Try to view token directly from nonces table - select count(*) into token_count from public.nonces where id = token_id; - - perform set_config('app.settings.creator_token_count', token_count::text, false); -end $$; - --- Check creator can see their own token -select is( - current_setting('app.settings.creator_token_count', false)::integer, - 1, - 'User should be able to see their own tokens in the table' -); - --- Test 6.2: Users cannot see tokens belonging to other users -select tests.authenticate_as('token_verifier'); -do $$ -declare - token_id uuid; - token_count integer; -begin - -- Get the token ID created for the creator user - token_id := (current_setting('app.settings.security_test_token_id', false))::uuid; - - -- Verifier tries to view token created for creator - select count(*) into token_count from public.nonces where id = token_id; - - perform set_config('app.settings.verifier_token_count', token_count::text, false); -end $$; - --- Check verifier cannot see creator's token -select is( - current_setting('app.settings.verifier_token_count', false)::integer, - 0, - 'User should not be able to see tokens belonging to other users' -); - --- ========================================== --- Test 7: Auto-Revocation of Previous Tokens --- ========================================== - --- Test 7.1: Creating a new token should revoke previous tokens with the same purpose by default -set local role postgres; - -do $$ -declare - auth_user_id uuid; - first_token_result jsonb; - second_token_result jsonb; - first_token_id uuid; - first_token_status jsonb; -begin - -- Get user ID - auth_user_id := makerkit.get_id_by_identifier('token_creator'); - - -- Create first token - first_token_result := public.create_nonce( - auth_user_id, - 'password-reset', - 3600, - '{"first": true}'::jsonb - ); - - first_token_id := first_token_result->>'id'; - - -- Verify first token is valid - first_token_status := public.get_nonce_status(first_token_id); - - -- Create second token with same purpose - second_token_result := public.create_nonce( - auth_user_id, - 'password-reset', - 3600, - '{"second": true}'::jsonb - ); - - -- Check that first token is now revoked - first_token_status := public.get_nonce_status(first_token_id); - - perform set_config('app.settings.first_token_valid_before', 'true', false); - perform set_config('app.settings.revoked_previous_count', (second_token_result->>'revoked_previous_count')::text, false); - perform set_config('app.settings.first_token_revoked', (first_token_status->>'revoked')::text, false); - perform set_config('app.settings.first_token_revoked_reason', (first_token_status->>'revoked_reason')::text, false); - perform set_config('app.settings.first_token_valid_after', (first_token_status->>'is_valid')::text, false); -end $$; - --- Check auto-revocation results -select is( - current_setting('app.settings.first_token_valid_before', false), - 'true', - 'First token should be valid initially' -); - -select is( - current_setting('app.settings.revoked_previous_count', false)::integer, - 1, - 'Should report one revoked token' -); - -select is( - current_setting('app.settings.first_token_revoked', false), - 'true', - 'First token should be revoked' -); - -select is( - current_setting('app.settings.first_token_revoked_reason', false), - 'Superseded by new token with same purpose', - 'Revocation reason should be set' -); - -select is( - current_setting('app.settings.first_token_valid_after', false), - 'false', - 'First token should be invalid' -); - --- ========================================== --- Test 8: Maximum Verification Attempts --- ========================================== - --- Test 8.1: Token should be revoked after exceeding max verification attempts -set local role service_role; - -do $$ -declare - token_result jsonb; - verification_result jsonb; - status_result jsonb; - token_id uuid; - token_text text; -begin - -- Create a token - token_result := public.create_nonce( - null, - 'max-attempts-test', - 3600 - ); - - token_id := token_result->>'id'; - token_text := token_result->>'token'; - - -- Manually set verification_attempts to just below the limit (3) - UPDATE public.nonces - SET verification_attempts = 3 - WHERE id = token_id; - - -- Get status after manual update - status_result := public.get_nonce_status(token_id); - - -- Now perform a verification with an incorrect token - this should trigger max attempts exceeded - verification_result := public.verify_nonce( - 'wrong-token', -- Wrong token - 'max-attempts-test', -- Correct purpose, - NULL, -- No user id - NULL, -- No required scopes - 3 -- Max 3 attempts - ); - - -- The above won't increment the counter, so we need to make one more attempt with the correct token - verification_result := public.verify_nonce( - token_text, -- Correct token - 'max-attempts-test', -- Correct purpose, - NULL, -- No user id - NULL, -- No required scopes - 3 -- Max 3 attempts - ); - - -- Check token status to verify it was revoked - status_result := public.get_nonce_status(token_id); - - -- Store results for assertions outside the DO block - perform set_config('app.settings.max_attempts_verification_result', verification_result::text, false); - perform set_config('app.settings.max_attempts_status_result', status_result::text, false); -end $$; - --- Check max attempts results outside the DO block -select is( - (current_setting('app.settings.max_attempts_verification_result', false)::jsonb)->>'valid', - 'false', - 'Token should be invalid after exceeding max attempts' -); - -select is( - (current_setting('app.settings.max_attempts_verification_result', false)::jsonb)->>'max_attempts_exceeded', - 'true', - 'Max attempts exceeded flag should be set' -); - -select is( - (current_setting('app.settings.max_attempts_status_result', false)::jsonb)->>'revoked', - 'true', - 'Token should be revoked after exceeding max attempts' -); - -select is( - (current_setting('app.settings.max_attempts_status_result', false)::jsonb)->>'revoked_reason', - 'Maximum verification attempts exceeded', - 'Revocation reason should indicate max attempts exceeded' -); - --- Test 8.2: Setting max attempts to 0 should disable the limit -do $$ -declare - token_result jsonb; - verification_result jsonb; - status_result jsonb; - token_id uuid; - token_text text; -begin - -- Create a token - token_result := public.create_nonce( - null, - 'unlimited-attempts-test', - 3600 - ); - - token_id := token_result->>'id'; - token_text := token_result->>'token'; - - -- Manually set verification_attempts to a high number - UPDATE public.nonces - SET verification_attempts = 10 - WHERE id = token_id; - - -- Get status after manual update - status_result := public.get_nonce_status(token_id); - - -- Now perform a verification with the correct token and unlimited attempts - verification_result := public.verify_nonce( - token_text, -- Correct token - 'unlimited-attempts-test', -- Correct purpose, - NULL, -- No user id - NULL, -- No required scopes - 0 -- Unlimited attempts (disabled) - ); - - -- Check token status to verify it was not revoked - status_result := public.get_nonce_status(token_id); - - -- Store results for assertions outside the DO block - perform set_config('app.settings.unlimited_attempts_status', status_result::text, false); -end $$; - --- Check unlimited attempts results outside the DO block -select is( - (current_setting('app.settings.unlimited_attempts_status', false)::jsonb)->>'revoked', - 'false', - 'Token should not be revoked when max attempts is disabled' -); - -select cmp_ok( - (current_setting('app.settings.unlimited_attempts_status', false)::jsonb)->>'verification_attempts', - '>=', - '10', - 'Token should record at least 10 verification attempts' -); - --- ========================================== --- Test 9: Multiple Nonces with Same Purpose --- ========================================== - --- Test 9.1: Creating multiple anonymous nonces with the same purpose -set local role service_role; - -do $$ -declare - token_result1 jsonb; - token_result2 jsonb; - token_result3 jsonb; - verification_result jsonb; - count_result integer; -begin - -- Create first token with purpose "multi-purpose-test" - token_result1 := public.create_nonce( - null, -- Anonymous token - 'multi-purpose-test', - 3600, - '{"data": "first token"}'::jsonb - ); - - -- Create second token with the same purpose - token_result2 := public.create_nonce( - null, -- Anonymous token - 'multi-purpose-test', - 3600, - '{"data": "second token"}'::jsonb, - null, -- Default scopes - false -- Don't revoke previous tokens with same purpose - ); - - -- Create third token with the same purpose - token_result3 := public.create_nonce( - null, -- Anonymous token - 'multi-purpose-test', - 3600, - '{"data": "third token"}'::jsonb, - null, -- Default scopes - false -- Don't revoke previous tokens with same purpose - ); - - -- Count how many tokens exist with this purpose - select count(*) into count_result - from public.nonces - where purpose = 'multi-purpose-test' and used_at is null and revoked = false; - - -- Verify specific token by token value - verification_result := public.verify_nonce( - token_result2->>'token', -- Use the second token specifically - 'multi-purpose-test' - ); - - -- Store results for assertions outside the DO block - perform set_config('app.settings.multiple_purpose_count', count_result::text, false); - perform set_config('app.settings.specific_token_verification', verification_result::text, false); - perform set_config('app.settings.expected_token_metadata', '{"data": "second token"}', false); -end $$; - --- Check results outside the DO block -select is( - current_setting('app.settings.multiple_purpose_count', false)::integer, - 3, - 'There should be 3 active tokens with the same purpose when auto-revocation is disabled' -); - -select is( - (current_setting('app.settings.specific_token_verification', false)::jsonb)->>'valid', - 'true', - 'Verification of specific token should succeed' -); - -select is( - (current_setting('app.settings.specific_token_verification', false)::jsonb)->'metadata', - current_setting('app.settings.expected_token_metadata', false)::jsonb, - 'Metadata from the correct (second) token should be returned' -); - --- Test 9.2: Multiple user-specific tokens with same purpose -do $$ -declare - creator_id uuid; - verifier_id uuid; - creator_token_result jsonb; - verifier_token_result jsonb; - verification_result jsonb; -begin - set local role postgres; - - -- Get user IDs - creator_id := makerkit.get_id_by_identifier('token_creator'); - verifier_id := makerkit.get_id_by_identifier('token_verifier'); - - set local role service_role; - - -- Create token for first user - creator_token_result := public.create_nonce( - creator_id, - 'user-specific-purpose', - 3600, - '{"user": "creator"}'::jsonb - ); - - -- Create token for second user with same purpose - verifier_token_result := public.create_nonce( - verifier_id, - 'user-specific-purpose', - 3600, - '{"user": "verifier"}'::jsonb - ); - - -- Verify token for creator user - verification_result := public.verify_nonce( - creator_token_result->>'token', - 'user-specific-purpose', - creator_id -- Specify user_id explicitly - ); - - -- Store results for assertions - perform set_config('app.settings.creator_verification_result', verification_result::text, false); - perform set_config('app.settings.creator_token', creator_token_result::text, false); - perform set_config('app.settings.verifier_token', verifier_token_result::text, false); -end $$; - --- Verify that specifying the user_id correctly retrieves the right token -select is( - (current_setting('app.settings.creator_verification_result', false)::jsonb)->>'valid', - 'true', - 'Verification of user-specific token should succeed when user_id is provided' -); - -select is( - (current_setting('app.settings.creator_verification_result', false)::jsonb)->'metadata'->>'user', - 'creator', - 'Correct user metadata should be returned' -); - --- Test 9.3: Verify purpose uniqueness requirements for anonymous tokens -do $$ -declare - test_token1 jsonb; - test_token2 jsonb; - wrong_verification jsonb; - count_result integer; -begin - set local role service_role; - - -- Create anonymous token - test_token1 := public.create_nonce( - null, -- Anonymous - 'anonymous-purpose-test', - 3600, - '{"test": "first"}'::jsonb - ); - - -- Create second anonymous token with same purpose - test_token2 := public.create_nonce( - null, -- Anonymous - 'anonymous-purpose-test', - 3600, - '{"test": "second"}'::jsonb, - null, - false -- Don't revoke previous - ); - - -- Verify token without specifying which one - wrong_verification := public.verify_nonce( - test_token1->>'token', - 'anonymous-purpose-test' - -- No user_id specified - should still work for anonymous tokens - ); - - -- Count matching tokens - select count(*) into count_result - from public.nonces - where purpose = 'anonymous-purpose-test' and used_at is null and revoked = false; - - -- Store results for assertions - perform set_config('app.settings.anonymous_verification_result', wrong_verification::text, false); - perform set_config('app.settings.anonymous_token_count', count_result::text, false); -end $$; - --- Check that anonymous verification works despite multiple tokens with same purpose -select is( - (current_setting('app.settings.anonymous_verification_result', false)::jsonb)->>'valid', - 'true', - 'First token verification should succeed even with multiple tokens with same purpose' -); - -select is( - current_setting('app.settings.anonymous_token_count', false)::integer, - 1, - 'After verification, only one token should remain active (the other was used)' -); - --- ========================================== --- Test 10: Short Expiration Test --- ========================================== - --- Test 10.1: Token with 1 second expiration -set local role service_role; - -do $$ -declare - token_result jsonb; - verification_result jsonb; - verification_after_expiry jsonb; - token_text text; -begin - -- Create token with 1 second expiration - token_result := public.create_nonce( - null, -- Anonymous token - 'short-expiry-test', - 1, -- 1 second expiration - '{"data": "expires quickly"}'::jsonb - ); - - token_text := token_result->>'token'; - - -- Verify immediately - should be valid - verification_result := public.verify_nonce( - token_text, - 'short-expiry-test' - ); - - -- Wait for 1.5 seconds to ensure token expires - perform pg_sleep(1.5); - - -- Verify after expiration - should be invalid - verification_after_expiry := public.verify_nonce( - token_text, - 'short-expiry-test' - ); - - -- Store results for assertions - perform set_config('app.settings.quick_verification_result', verification_result::text, false); - perform set_config('app.settings.after_expiry_verification', verification_after_expiry::text, false); -end $$; - --- Check results -select is( - (current_setting('app.settings.quick_verification_result', false)::jsonb)->>'valid', - 'true', - 'Token should be valid immediately after creation' -); - -select is( - (current_setting('app.settings.after_expiry_verification', false)::jsonb)->>'valid', - 'false', - 'Token should be invalid after expiration time has passed' -); - -select is( - (current_setting('app.settings.after_expiry_verification', false)::jsonb)->>'message', - 'Invalid or expired token', - 'Error message should indicate token is expired' -); - --- Finish tests -select * from finish(); - -rollback; - diff --git a/supabase copy/tests/database/personal-accounts.test.sql b/supabase copy/tests/database/personal-accounts.test.sql deleted file mode 100644 index 60bfffd..0000000 --- a/supabase copy/tests/database/personal-accounts.test.sql +++ /dev/null @@ -1,57 +0,0 @@ -BEGIN; -create extension "basejump-supabase_test_helpers" version '0.0.6'; - -select no_plan(); - ---- we insert a user into auth.users and return the id into user_id to use - -select tests.create_supabase_user('test1', 'test1@test.com'); - -select tests.create_supabase_user('test2'); - ------------- ---- Primary Owner ------------- -select makerkit.authenticate_as('test1'); - --- should create the personal account automatically with the same ID as the user -SELECT row_eq( - $$ select primary_owner_user_id, is_personal_account, name from public.accounts order by created_at desc limit 1 $$, - ROW (tests.get_supabase_uid('test1'), true, 'test1'::varchar), - 'Inserting a user should create a personal account when personal accounts are enabled' -); - --- anon users should not be able to see the personal account - -set local role anon; - -SELECT throws_ok( - $$ select * from public.accounts order by created_at desc limit 1 $$, - 'permission denied for schema public' -); - --- the primary owner should be able to see the personal account - -select makerkit.authenticate_as('test1'); - -SELECT isnt_empty( - $$ select * from public.accounts where primary_owner_user_id = tests.get_supabase_uid('test1') $$, - 'The primary owner should be able to see the personal account' -); - ------------- ---- Other Users - --- other users should not be able to see the personal account - -select makerkit.authenticate_as('test2'); - -SELECT is_empty( - $$ select * from public.accounts where primary_owner_user_id = tests.get_supabase_uid('test1') $$, - 'Other users should not be able to see the personal account' -); - -SELECT * -FROM finish(); - -ROLLBACK; \ No newline at end of file diff --git a/supabase copy/tests/database/personal-billing-orders.test.sql b/supabase copy/tests/database/personal-billing-orders.test.sql deleted file mode 100644 index 4c4e7ba..0000000 --- a/supabase copy/tests/database/personal-billing-orders.test.sql +++ /dev/null @@ -1,95 +0,0 @@ -begin; -create extension "basejump-supabase_test_helpers" version '0.0.6'; - -select no_plan(); - -select makerkit.set_identifier('primary_owner', 'test@makerkit.dev'); -select makerkit.set_identifier('owner', 'owner@makerkit.dev'); -select makerkit.set_identifier('member', 'member@makerkit.dev'); -select makerkit.set_identifier('custom', 'custom@makerkit.dev'); - -INSERT INTO public.billing_customers(account_id, provider, customer_id) -VALUES (tests.get_supabase_uid('primary_owner'), 'stripe', 'cus_test'); - --- Call the upsert_order function -SELECT public.upsert_order(tests.get_supabase_uid('primary_owner'), 'cus_test', 'order_test', 'pending', 'stripe', 100, 'usd', '[ - {"id":"order_item_1", "product_id": "prod_test", "variant_id": "var_test", "price_amount": 100, "quantity": 1}, - {"id":"order_item_2", "product_id": "prod_test", "variant_id": "var_test_2", "price_amount": 100, "quantity": 10} -]'); - --- Verify that the order was created correctly -SELECT is( - (SELECT status FROM public.orders WHERE id = 'order_test'), - 'pending', - 'The order status should be pending' -); - --- Verify that the subscription items were created correctly -SELECT row_eq( - $$ select count(*) from order_items where order_id = 'order_test' $$, - row(2::bigint), - 'The order items should be created' -); - --- Call the upsert_order function again to update the order -select public.upsert_order(tests.get_supabase_uid('primary_owner'), 'cus_test', 'order_test', 'succeeded', 'stripe', 100, 'usd', '[ - {"id":"order_item_1", "product_id": "prod_test_2", "variant_id": "var_test", "price_amount": 100, "quantity": 10} -]'); - --- Verify that the order was updated correctly -select is( - (select status FROM public.orders WHERE id = 'order_test'), - 'succeeded', - 'The order status should be succeeded' -); - -select row_eq( - $$ select quantity from order_items where variant_id = 'var_test' $$, - row(10::int), - 'The order items should be updated' -); - -select is_empty( - $$ select * from order_items where id = 'order_item_2' $$, - 'The order item should be deleted when the order is updated' -); - -select row_eq( - $$ select product_id from order_items where id = 'order_item_1' $$, - row('prod_test_2'::text), - 'The order item should be deleted when the order is updated' -); - -select makerkit.authenticate_as('primary_owner'); - --- account can read their own subscription -select isnt_empty( - $$ select 1 from orders where id = 'order_test' $$, - 'The account can read their own order' -); - -select isnt_empty( - $$ select * from order_items where order_id = 'order_test' $$, - 'The account can read their own orders items' -); - --- foreigners -select tests.create_supabase_user('foreigner'); -select makerkit.authenticate_as('foreigner'); - --- account cannot read other's subscription -select is_empty( - $$ select 1 from orders where id = 'order_test' $$, - 'The account cannot read the other account orders' -); - -select is_empty( - $$ select 1 from order_items where order_id = 'order_test' $$, - 'The account cannot read the other account order items' -); - --- Finish the tests and clean up -select * from finish(); - -rollback; - diff --git a/supabase copy/tests/database/personal-billing-subscriptions.test.sql b/supabase copy/tests/database/personal-billing-subscriptions.test.sql deleted file mode 100644 index b439b4b..0000000 --- a/supabase copy/tests/database/personal-billing-subscriptions.test.sql +++ /dev/null @@ -1,196 +0,0 @@ -begin; -create extension "basejump-supabase_test_helpers" version '0.0.6'; - -select no_plan(); - -select makerkit.set_identifier('primary_owner', 'test@makerkit.dev'); -select makerkit.set_identifier('owner', 'owner@makerkit.dev'); -select makerkit.set_identifier('member', 'member@makerkit.dev'); -select makerkit.set_identifier('custom', 'custom@makerkit.dev'); - --- Create a test account and billing customer -INSERT INTO public.billing_customers(account_id, provider, customer_id) -VALUES (tests.get_supabase_uid('primary_owner'), 'stripe', 'cus_test'); - --- Call the upsert_subscription function -SELECT public.upsert_subscription(tests.get_supabase_uid('primary_owner'), 'cus_test', 'sub_test', true, 'active', 'stripe', false, 'usd', now(), now() + interval '1 month', '[ - { - "id": "sub_123", - "product_id": "prod_test", - "variant_id": "var_test", - "type": "flat", - "price_amount": 1000, - "quantity": 1, - "interval": "month", - "interval_count": 1 - }, - { - "id": "sub_456", - "product_id": "prod_test_2", - "variant_id": "var_test_2", - "type": "flat", - "price_amount": 2000, - "quantity": 2, - "interval": "month", - "interval_count": 1 - }, - { - "id": "sub_789", - "product_id": "prod_test_3", - "variant_id": "var_test_3", - "type": "flat", - "price_amount": 2000, - "quantity": 2, - "interval": "month", - "interval_count": 1 - } -]'); - --- Verify that the subscription items were created correctly -SELECT row_eq( - $$ select count(*) from subscription_items where subscription_id = 'sub_test' $$, - row(3::bigint), - 'The subscription items should be created' -); - --- Verify that the subscription was created correctly -SELECT is( - (SELECT active FROM public.subscriptions WHERE id = 'sub_test'), - true, - 'The subscription should be active' -); - -SELECT is( - (SELECT status FROM public.subscriptions WHERE id = 'sub_test'), - 'active', - 'The subscription status should be active' -); - --- Call the upsert_subscription function again to update the subscription -SELECT public.upsert_subscription(tests.get_supabase_uid('primary_owner'), 'cus_test', 'sub_test', false, 'past_due', 'stripe', true, 'usd', now(), now() + interval '1 month', '[ - { - "id": "sub_123", - "product_id": "prod_test", - "variant_id": "var_test", - "type": "flat", - "price_amount": 2000, - "quantity": 1, - "interval": "month", - "interval_count": 1 - }, - { - "id": "sub_456", - "product_id": "prod_test_3", - "variant_id": "var_test_2", - "type": "flat", - "price_amount": 2000, - "quantity": 2, - "interval": "year", - "interval_count": 12 - } -]'); - --- Verify that the subscription items were updated correctly -SELECT row_eq( - $$ select price_amount from subscription_items where variant_id = 'var_test' $$, - row('2000'::numeric), - 'The subscription items should be updated' -); - --- Verify that the subscription items were updated correctly -SELECT row_eq( - $$ select product_id from subscription_items where id = 'sub_456' $$, - row('prod_test_3'::varchar), - 'The subscription items should be updated' -); - --- Verify that the subscription items were updated correctly -SELECT row_eq( - $$ select interval from subscription_items where variant_id = 'var_test_2' $$, - row('year'::varchar), - 'The subscription items should be updated' -); - --- Verify that the subscription was updated correctly -select is( - (select active FROM public.subscriptions WHERE id = 'sub_test'), - false, - 'The subscription should be inactive' -); - -select is( - (select status FROM public.subscriptions WHERE id = 'sub_test'), - 'past_due', - 'The subscription status should be past_due' -); - -select isnt_empty( - $$ select * from public.subscription_items where subscription_id = 'sub_test' $$, - 'The account can read their own subscription items' -); - -select is_empty( - $$ select * from public.subscription_items where subscription_id = 'sub_test' and variant_id = 'var_test_3' $$, - 'The subscription items should be deleted when the subscription is updated and the item is missing' -); - --- Call the upsert_subscription function again to update the subscription -select public.upsert_subscription(tests.get_supabase_uid('primary_owner'), 'cus_test', 'sub_test', true, 'active', 'stripe', false, 'usd', now(), now() + interval '1 month', '[]'); - --- Verify that the subscription was updated correctly -select is( - (select active FROM public.subscriptions WHERE id = 'sub_test'), - true, - 'The subscription should be active' -); - -select makerkit.authenticate_as('primary_owner'); - --- account can read their own subscription -select isnt_empty( - $$ select 1 from subscriptions where id = 'sub_test' $$, - 'The account can read their own subscription' -); - -select is_empty( - $$ select * from subscription_items where subscription_id = 'sub_test' $$, - 'No subscription items should be returned when the subscription is empty' -); - --- users cannot manually update subscriptions -select throws_ok( - $$ select public.upsert_subscription(tests.get_supabase_uid('primary_owner'), 'cus_test', 'sub_test', true, 'active', 'stripe', false, 'usd', now(), now() + interval '1 month', '[]') $$, - 'permission denied for function upsert_subscription' -); - -select is( - (public.has_active_subscription(tests.get_supabase_uid('primary_owner'))), - true, - 'The function public.has_active_subscription should return true when the account has a subscription' -); - --- foreigners -select tests.create_supabase_user('foreigner'); -select makerkit.authenticate_as('foreigner'); - --- account cannot read other's subscription -select is_empty( - $$ select 1 from subscriptions where id = 'sub_test' $$, - 'The account cannot read the other account subscriptions' -); - -select is_empty( - $$ select 1 from subscription_items where subscription_id = 'sub_test' $$, - 'The account cannot read the other account subscription items' -); - -select is( - (public.has_active_subscription(tests.get_supabase_uid('primary_owner'))), - false, - 'The function public.has_active_subscription should return false when a foreigner is querying the account subscription' -); - --- Finish the tests and clean up -select * from finish(); - -rollback; \ No newline at end of file diff --git a/supabase copy/tests/database/schema-conditions.test.sql b/supabase copy/tests/database/schema-conditions.test.sql deleted file mode 100644 index bb2ffd7..0000000 --- a/supabase copy/tests/database/schema-conditions.test.sql +++ /dev/null @@ -1,57 +0,0 @@ -begin; - -create extension "basejump-supabase_test_helpers" version '0.0.6'; - -select - no_plan(); - -CREATE OR REPLACE FUNCTION check_schema_conditions() -RETURNS void AS -$$ -DECLARE - _table RECORD; - _column RECORD; - columnCheckCount INTEGER; -BEGIN - FOR _table IN (SELECT tablename FROM pg_tables WHERE schemaname = 'public') - LOOP - -- 1. Check if every table has RLS enabled - IF ( - SELECT relrowsecurity FROM pg_class - INNER JOIN pg_namespace n ON n.oid = pg_class.relnamespace - WHERE n.nspname = 'public' AND relname = _table.tablename - ) IS FALSE THEN - RAISE EXCEPTION 'Table "%" does not have RLS enabled.', _table.tablename; - END IF; - - -- 2. Check that every text column in the current table has a constraint - FOR _column IN (SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = _table.tablename AND data_type = 'text') - LOOP - SELECT COUNT(*) - INTO columnCheckCount - FROM information_schema.constraint_column_usage - WHERE table_schema = 'public' AND table_name = _table.tablename AND column_name = _column.column_name; - - IF columnCheckCount = 0 THEN - RAISE NOTICE 'Text column "%.%" does not have a constraint - .', - _table.tablename, _column.column_name; - END IF; - END LOOP; - END LOOP; - - RAISE NOTICE 'Schema check completed.'; -END -$$ LANGUAGE plpgsql; - -select lives_ok($$ - select - check_schema_conditions(); -$$, 'check_schema_conditions()'); - -select - * -from - finish(); - -rollback; diff --git a/supabase copy/tests/database/schema.test.sql b/supabase copy/tests/database/schema.test.sql deleted file mode 100644 index b7b0fdd..0000000 --- a/supabase copy/tests/database/schema.test.sql +++ /dev/null @@ -1,52 +0,0 @@ -BEGIN; -create extension "basejump-supabase_test_helpers" version '0.0.6'; - -select no_plan(); - -select has_table('public', 'config', 'Makerkit config table should exist'); -select has_table('public', 'accounts', 'Makerkit accounts table should exist'); -select has_table('public', 'accounts_memberships', 'Makerkit account_users table should exist'); -select has_table('public', 'invitations', 'Makerkit invitations table should exist'); -select has_table('public', 'billing_customers', 'Makerkit billing_customers table should exist'); -select has_table('public', 'subscriptions', 'Makerkit subscriptions table should exist'); -select has_table('public', 'subscription_items', 'Makerkit subscription_items table should exist'); -select has_table('public', 'orders', 'Makerkit orders table should exist'); -select has_table('public', 'order_items', 'Makerkit order_items table should exist'); -select has_table('public', 'roles', 'Makerkit roles table should exist'); -select has_table('public', 'role_permissions', 'Makerkit roles_permissions table should exist'); - -select tests.rls_enabled('public', 'config'); -select tests.rls_enabled('public', 'accounts'); -select tests.rls_enabled('public', 'accounts_memberships'); -select tests.rls_enabled('public', 'invitations'); -select tests.rls_enabled('public', 'billing_customers'); -select tests.rls_enabled('public', 'subscriptions'); -select tests.rls_enabled('public', 'subscription_items'); -select tests.rls_enabled('public', 'orders'); -select tests.rls_enabled('public', 'order_items'); -select tests.rls_enabled('public', 'roles'); -select tests.rls_enabled('public', 'role_permissions'); - -SELECT schema_privs_are('public', 'anon', Array [NULL], 'Anon should not have access to public schema'); - --- set the role to anonymous for verifying access tests -set role anon; -select throws_ok('select public.get_config()'); -select throws_ok('select public.is_set(''enable_team_accounts'')'); - --- set the role to the service_role for testing access -set role service_role; -select ok(public.get_config() is not null), - 'Makerkit get_config should be accessible to the service role'; - --- set the role to authenticated for tests -set role authenticated; -select ok(public.get_config() is not null), 'Makerkit get_config should be accessible to authenticated users'; -select ok(public.is_set('enable_team_accounts')), - 'Makerkit is_set should be accessible to authenticated users'; -select isnt_empty('select * from public.config', 'authenticated users should have access to Makerkit config'); - -SELECT * -FROM finish(); - -ROLLBACK; \ No newline at end of file diff --git a/supabase copy/tests/database/storage.test.sql b/supabase copy/tests/database/storage.test.sql deleted file mode 100644 index a43faf7..0000000 --- a/supabase copy/tests/database/storage.test.sql +++ /dev/null @@ -1,122 +0,0 @@ -begin; -create extension "basejump-supabase_test_helpers" version '0.0.6'; - -select no_plan(); - -select makerkit.set_identifier('primary_owner', 'test@makerkit.dev'); -select makerkit.set_identifier('owner', 'owner@makerkit.dev'); -select makerkit.set_identifier('member', 'member@makerkit.dev'); -select makerkit.set_identifier('custom', 'custom@makerkit.dev'); - -select makerkit.authenticate_as('member'); - -select throws_ok( - $$ insert into storage.objects ("bucket_id", "metadata", "name", "owner", "owner_id", "version") values - ('account_image', '{"key": "value"}', tests.get_supabase_uid('primary_owner'), tests.get_supabase_uid('primary_owner'), tests.get_supabase_uid('primary_owner'), 1); $$, - 'new row violates row-level security policy for table "objects"' -); - -select makerkit.authenticate_as('primary_owner'); - -select lives_ok( - $$ insert into storage.objects ("bucket_id", "metadata", "name", "owner", "owner_id", "version") values - ('account_image', '{"key": "value"}', tests.get_supabase_uid('primary_owner'), tests.get_supabase_uid('primary_owner'), tests.get_supabase_uid('primary_owner'), 1); $$, - 'The owner should be able to insert a new object' -); - -select isnt_empty( - $$ select * from storage.objects where owner = tests.get_supabase_uid('primary_owner') $$, - 'The object should be inserted' -); - -select makerkit.authenticate_as('owner'); - -select is_empty( - $$ select * from storage.objects where owner = tests.get_supabase_uid('primary_owner') $$, - 'The owner should not be able to see the object' -); - --- create a new bucket --- -set local role postgres; - -select lives_ok( - $$ insert into storage.buckets ("name", "id", public) values ('new_bucket', 'new_bucket', true); $$ -); - --- we create a mock policy allowing only the primary_owner to access the new bucket --- this is a mock policy to check the existing policy system does not interfere with the new bucket -create policy new_bucket_policy on storage.objects for all using ( - bucket_id = 'new_bucket' - and auth.uid() = tests.get_supabase_uid('primary_owner') -) -with check ( - bucket_id = 'new_bucket' - and auth.uid() = tests.get_supabase_uid('primary_owner') -); - -select makerkit.authenticate_as('member'); - --- user should not be able to insert into the new bucket according to the new policy -select throws_ok( - $$ insert into storage.objects ("bucket_id", "metadata", "name", "owner", "owner_id", "version") values - ('new_bucket', '{"key": "value"}', 'some name', tests.get_supabase_uid('primary_owner'), tests.get_supabase_uid('primary_owner'), 1); $$, - 'new row violates row-level security policy for table "objects"' -); - -select makerkit.authenticate_as('primary_owner'); - --- primary_owner should be able to insert into the new bucket according to the new policy --- this is to check the new policy system is working --- -select lives_ok( - $$ insert into storage.objects ("bucket_id", "metadata", "name", "owner", "owner_id", "version") values - ('new_bucket', '{"key": "value"}', 'some name', tests.get_supabase_uid('primary_owner'), tests.get_supabase_uid('primary_owner'), 1); $$, - 'new row violates row-level security policy for table "objects"' -); - -set local role postgres; - --- create a new bucket with a custom policy --- -create policy new_custom_bucket_policy on storage.objects for all using ( - bucket_id = 'new_bucket' - and auth.uid() = tests.get_supabase_uid('owner') -) -with check ( - bucket_id = 'new_bucket' - and auth.uid() = tests.get_supabase_uid('owner') -); - -select makerkit.authenticate_as('owner'); - --- insert a new object into the new bucket --- -select lives_ok( - $$ insert into storage.objects ("bucket_id", "metadata", "name", "owner", "owner_id", "version") values - ('new_bucket', '{"key": "value"}', 'some name 2', tests.get_supabase_uid('primary_owner'), tests.get_supabase_uid('primary_owner'), 1); $$, - 'The primary_owner should be able to insert a new object into the new bucket' -); - --- check the object is inserted --- -select isnt_empty( - $$ select * from storage.objects where bucket_id = 'new_bucket' $$, - 'The object should be inserted into the new bucket' -); - --- check other members cannot insert into the new bucket -select makerkit.authenticate_as('member'); - -select throws_ok( - $$ insert into storage.objects ("bucket_id", "metadata", "name", "owner", "owner_id", "version") values - ('new_bucket', '{"key": "value"}', 'some other name', tests.get_supabase_uid('primary_owner'), tests.get_supabase_uid('primary_owner'), 1); $$, - 'new row violates row-level security policy for table "objects"' -); - -select - * -from - finish(); - -rollback; \ No newline at end of file diff --git a/supabase copy/tests/database/super-admin-edge-cases.test.sql b/supabase copy/tests/database/super-admin-edge-cases.test.sql deleted file mode 100644 index 5fb5402..0000000 --- a/supabase copy/tests/database/super-admin-edge-cases.test.sql +++ /dev/null @@ -1,84 +0,0 @@ -begin; -create extension "basejump-supabase_test_helpers" version '0.0.6'; - -select no_plan(); - --- Create test users for different scenarios -select tests.create_supabase_user('transitioning_admin'); -select tests.create_supabase_user('revoking_mfa_admin'); -select tests.create_supabase_user('concurrent_session_user'); - --- Set up test users -select makerkit.set_identifier('transitioning_admin', 'transitioning@makerkit.dev'); -select makerkit.set_identifier('revoking_mfa_admin', 'revoking@makerkit.dev'); -select makerkit.set_identifier('concurrent_session_user', 'concurrent@makerkit.dev'); - --- Test 1: Role Transition Scenarios -select makerkit.authenticate_as('transitioning_admin'); -select makerkit.set_mfa_factor(); -select makerkit.set_session_aal('aal2'); - --- Initially not a super admin -select is( - (select public.is_super_admin()), - false, - 'User should not be super admin initially' -); - --- Grant super admin -select makerkit.set_super_admin(); - -select is( - (select public.is_super_admin()), - true, - 'User should now be super admin' -); - --- Test 2: MFA Revocation Scenarios -select makerkit.authenticate_as('revoking_mfa_admin'); -select makerkit.set_mfa_factor(); -select makerkit.set_session_aal('aal2'); -select makerkit.set_super_admin(); - --- Initially has super admin access -select is( - (select public.is_super_admin()), - true, - 'Admin should have super admin access initially' -); - --- Simulate MFA revocation by setting AAL1 -select makerkit.set_session_aal('aal1'); - -select is( - (select public.is_super_admin()), - false, - 'Admin should lose super admin access when MFA is revoked' -); - --- Test 3: Concurrent Session Management -select makerkit.authenticate_as('concurrent_session_user'); -select makerkit.set_mfa_factor(); -select makerkit.set_session_aal('aal2'); -select makerkit.set_super_admin(); - --- Test access with AAL2 -select is( - (select public.is_super_admin()), - true, - 'Should have super admin access with AAL2' -); - --- Simulate different session with AAL1 -select makerkit.set_session_aal('aal1'); - -select is( - (select public.is_super_admin()), - false, - 'Should not have super admin access with AAL1 even if other session has AAL2' -); - --- Finish the tests and clean up -select * from finish(); - -rollback; \ No newline at end of file diff --git a/supabase copy/tests/database/super-admin.test.sql b/supabase copy/tests/database/super-admin.test.sql deleted file mode 100644 index 5e0f31b..0000000 --- a/supabase copy/tests/database/super-admin.test.sql +++ /dev/null @@ -1,210 +0,0 @@ -begin; -create extension "basejump-supabase_test_helpers" version '0.0.6'; - -select no_plan(); - --- Create Users -select tests.create_supabase_user('super_admin'); -select tests.create_supabase_user('regular_user'); -select tests.create_supabase_user('mfa_user'); -select tests.create_supabase_user('malicious_user'); -select tests.create_supabase_user('partial_mfa_user'); - --- Set up test users -select makerkit.set_identifier('super_admin', 'super@makerkit.dev'); -select makerkit.set_identifier('regular_user', 'regular@makerkit.dev'); -select makerkit.set_identifier('mfa_user', 'mfa@makerkit.dev'); -select makerkit.set_identifier('malicious_user', 'malicious@makerkit.dev'); -select makerkit.set_identifier('partial_mfa_user', 'partial@makerkit.dev'); - --- Test is_aal2 function -set local role postgres; - -create or replace function makerkit.setup_super_admin() returns void as $$ -begin - perform makerkit.authenticate_as('super_admin'); - perform makerkit.set_mfa_factor(); - perform makerkit.set_session_aal('aal2'); - perform makerkit.set_super_admin(); -end $$ language plpgsql; - --- Test super admin with AAL2 -select makerkit.setup_super_admin(); - -select is( - (select public.is_aal2()), - true, - 'Super admin should have AAL2 authentication' -); - -select is( - (select public.is_super_admin()), - true, - 'User should be identified as super admin' -); - --- Test regular user (no AAL2) -select makerkit.authenticate_as('regular_user'); - -select is( - (select public.is_aal2()), - false, - 'Regular user should not have AAL2 authentication' -); - -select is( - (select public.is_super_admin()), - false, - 'Regular user should not be identified as super admin' -); - --- Test MFA compliance -set local role postgres; - -select is( - (select public.is_super_admin()), - false, - 'Postgres user should not be identified as super admin' -); - -select makerkit.authenticate_as('mfa_user'); -select makerkit.set_mfa_factor(); -select makerkit.set_session_aal('aal2'); - -select is( - (select public.is_mfa_compliant()), - true, - 'User with verified MFA should be MFA compliant because it is optional' -); - --- Test super admin access to protected tables -select makerkit.setup_super_admin(); - --- Test malicious user attempts -select makerkit.authenticate_as('malicious_user'); - --- Attempt to fake super admin role (should fail) -select is( - (select public.is_super_admin()), - false, - 'Malicious user cannot fake super admin role' -); - --- Test access to protected tables (should be restricted) -select is_empty( - $$ select * from public.accounts where id != auth.uid() $$, - 'Malicious user should not access other accounts' -); - -select is_empty( - $$ select * from public.accounts_memberships where user_id != auth.uid() $$, - 'Malicious user should not access other memberships' -); - -select is_empty( - $$ select * from public.subscriptions where account_id != auth.uid() $$, - 'Malicious user should not access other subscriptions' -); - --- Test partial MFA setup (not verified) -select makerkit.authenticate_as('partial_mfa_user'); -select makerkit.set_session_aal('aal2'); - --- Test regular user restricted access -select makerkit.authenticate_as('regular_user'); - --- Test MFA restrictions -select makerkit.authenticate_as('regular_user'); -select makerkit.set_mfa_factor(); - --- Should be restricted without MFA -select is_empty( - $$ select * from public.accounts $$, - 'Regular user without MFA should not access accounts when MFA is required' -); - --- A super admin without MFA should not be able to have super admin rights -select makerkit.authenticate_as('super_admin'); -select makerkit.set_super_admin(); - -select is( - (select public.is_super_admin()), - false, - 'Super admin without MFA should not be able to have super admin rights' - ); - --- Test edge cases for MFA and AAL2 -select makerkit.authenticate_as('mfa_user'); -select makerkit.set_mfa_factor(); --- Set AAL1 despite having MFA to test edge case -select makerkit.set_session_aal('aal1'); - -select is( - (select public.is_mfa_compliant()), - false, - 'User with MFA but AAL1 session should not be MFA compliant' -); - -select is_empty( - $$ select * from public.accounts $$, - 'Non-compliant MFA should not be able to read any accounts' -); - -select is_empty( - $$ select * from public.accounts_memberships $$, - 'Non-compliant MFA should not be able to read any memberships' -); - --- A Super Admin should be able to access all tables when MFA is enabled -select makerkit.setup_super_admin(); - -select is( - (select public.is_super_admin()), - true, - 'Super admin has super admin rights' -); - --- Test comprehensive access for super admin -select isnt_empty( - $$ select * from public.accounts where id = tests.get_supabase_uid('regular_user') $$, - 'Super admin should be able to access all accounts' -); - -do $$ -begin - delete from public.accounts where id = tests.get_supabase_uid('regular_user'); -end $$; - --- A Super admin cannot delete accounts directly -select isnt_empty( - $$ select * from public.accounts where id = tests.get_supabase_uid('regular_user') $$, - 'Super admin should not be able to delete data directly' -); - -set local role postgres; - --- update the account name to be able to test the update -do $$ -begin - update public.accounts set name = 'Regular User' where id = tests.get_supabase_uid('regular_user'); -end $$; - --- re-authenticate as super admin -select makerkit.setup_super_admin(); - --- test a super admin cannot update accounts directly -do $$ -begin - update public.accounts set name = 'Super Admin' where id = tests.get_supabase_uid('regular_user'); -end $$; - -select row_eq( - $$ select name from public.accounts where id = tests.get_supabase_uid('regular_user') $$, - row('Regular User'::varchar), - 'Super admin should not be able to update data directly' -); - --- Finish the tests and clean up -select * from finish(); - -rollback; \ No newline at end of file diff --git a/supabase copy/tests/database/team-accounts.test.sql b/supabase copy/tests/database/team-accounts.test.sql deleted file mode 100644 index 16a301a..0000000 --- a/supabase copy/tests/database/team-accounts.test.sql +++ /dev/null @@ -1,775 +0,0 @@ -begin; - -create extension "basejump-supabase_test_helpers" version '0.0.6'; - -select - no_plan(); - ---- we insert a user into auth.users and return the id into user_id to use -select - tests.create_supabase_user('test1', 'test1@test.com'); - -select - tests.create_supabase_user('test2'); - --- Create an team account -select - makerkit.authenticate_as('test1'); - -select - public.create_team_account('Test'); - -select - row_eq($$ - select - primary_owner_user_id, is_personal_account, slug, name - from makerkit.get_account_by_slug('test') $$, - row (tests.get_supabase_uid('test1'), false, - 'test'::text, 'Test'::varchar), - 'Users can create a team account'); - --- Should be the primary owner of the team account by default -select - row_eq($$ - select - account_role from public.accounts_memberships - where - account_id =( - select - id - from public.accounts - where - slug = 'test') - and user_id = tests.get_supabase_uid('test1') - $$, row ('owner'::varchar), - 'The primary owner should have the owner role for the team account'); - -select is( - public.is_account_owner((select - id - from public.accounts - where - slug = 'test')), - true, - 'The current user should be the owner of the team account' -); - --- Should be able to see the team account -select - isnt_empty($$ - select - * from public.accounts - where - primary_owner_user_id = - tests.get_supabase_uid('test1') $$, - 'The primary owner should be able to see the team account'); - --- Others should not be able to see the team account -select - makerkit.authenticate_as('test2'); - -select is( - public.is_account_owner((select - id - from public.accounts - where - slug = 'test')), - false, - 'The current user should not be the owner of the team account' -); - -select - is_empty($$ - select - * from public.accounts - where - primary_owner_user_id = - tests.get_supabase_uid('test1') $$, - 'Other users should not be able to see the team account'); - --- should not have any role for the team account -select - is (public.has_role_on_account(( - select - id - from makerkit.get_account_by_slug('test'))), - false, - 'Foreign users should not have any role for the team account'); - --- enforcing a single team account per owner using a trigger when --- inserting a team -set local role postgres; - -create or replace function kit.single_account_per_owner() - returns trigger - as $$ -declare - total_accounts int; -begin - select - count(id) - from - public.accounts - where - primary_owner_user_id = auth.uid() into total_accounts; - - if total_accounts > 0 then - raise exception 'User can only own 1 account'; - end if; - - return NEW; - -end -$$ -language plpgsql -set search_path = ''; - --- trigger to protect account fields -create trigger single_account_per_owner - before insert on public.accounts for each row - execute function kit.single_account_per_owner(); - --- Create an team account -select - makerkit.authenticate_as('test1'); - -select - throws_ok( - $$ select - public.create_team_account('Test2') $$, 'User can only own 1 account'); - -set local role postgres; - -drop trigger single_account_per_owner on public.accounts; - --- Test that a member cannot update another account in the same team --- Using completely new users for update tests -select - tests.create_supabase_user('updatetest1', 'updatetest1@test.com'); - -select - tests.create_supabase_user('updatetest2', 'updatetest2@test.com'); - --- Create a team account for update tests -select - makerkit.authenticate_as('updatetest1'); - -select - public.create_team_account('UpdateTeam'); - --- Add updatetest2 as a member -set local role postgres; - -insert into public.accounts_memberships (account_id, user_id, account_role) -values ( - (select id from makerkit.get_account_by_slug('updateteam')), - tests.get_supabase_uid('updatetest2'), - 'member' -); - --- Verify updatetest2 is now a member -select - makerkit.authenticate_as('updatetest1'); - -select - row_eq($$ - select - account_role from public.accounts_memberships - where - account_id = (select id from makerkit.get_account_by_slug('updateteam')) - and user_id = tests.get_supabase_uid('updatetest2') - $$, - row ('member'::varchar), - 'updatetest2 should be a member of the team account' - ); - --- Store original values to verify they don't change -select - row_eq($$ - select name, primary_owner_user_id from public.accounts - where id = (select id from makerkit.get_account_by_slug('updateteam')) - $$, - row ('UpdateTeam'::varchar, tests.get_supabase_uid('updatetest1')), - 'Original values before attempted updates' - ); - --- Add team account to updatetest2's visibility (so they can try to perform operations) -select - makerkit.authenticate_as('updatetest2'); - --- First verify that as a member, updatetest2 can now see the account -select - isnt_empty($$ - select - * from public.accounts - where id = (select id from makerkit.get_account_by_slug('updateteam')) - $$, - 'Team member should be able to see the team account' - ); - --- Try to update the team name - without checking for exception -select - lives_ok($$ - update public.accounts - set name = 'Updated Team Name' - where id = (select id from makerkit.get_account_by_slug('updateteam')) - $$, - 'Non-owner member update attempt should not crash' - ); - --- Try to update primary owner without checking for exception -select - lives_ok($$ - update public.accounts - set primary_owner_user_id = tests.get_supabase_uid('updatetest2') - where id = (select id from makerkit.get_account_by_slug('updateteam')) - $$, - 'Non-owner member update of primary owner attempt should not crash' - ); - --- Verify the values have not changed by checking in both updatetest1 and updatetest2 sessions --- First check as updatetest2 (the member) -select - row_eq($$ - select name, primary_owner_user_id from public.accounts - where id = (select id from makerkit.get_account_by_slug('updateteam')) - $$, - row ('UpdateTeam'::varchar, tests.get_supabase_uid('updatetest1')), - 'Values should remain unchanged after member update attempt (member perspective)' - ); - --- Now verify as updatetest1 (the owner) -select - makerkit.authenticate_as('updatetest1'); - -select - row_eq($$ - select name, primary_owner_user_id from public.accounts - where id = (select id from makerkit.get_account_by_slug('updateteam')) - $$, - row ('UpdateTeam'::varchar, tests.get_supabase_uid('updatetest1')), - 'Values should remain unchanged after member update attempt (owner perspective)' - ); - --- Test role escalation prevention with completely new users -select - tests.create_supabase_user('roletest1', 'roletest1@test.com'); - -select - tests.create_supabase_user('roletest2', 'roletest2@test.com'); - --- Create a team account for role tests -select - makerkit.authenticate_as('roletest1'); - -select - public.create_team_account('RoleTeam'); - --- Add roletest2 as a member -set local role postgres; - -insert into public.accounts_memberships (account_id, user_id, account_role) -values ( - (select id from makerkit.get_account_by_slug('roleteam')), - tests.get_supabase_uid('roletest2'), - 'member' -); - --- Test role escalation prevention: a member cannot promote themselves to owner -select - makerkit.authenticate_as('roletest2'); - --- Try to update own role to owner -select - lives_ok($$ - update public.accounts_memberships - set account_role = 'owner' - where account_id = (select id from makerkit.get_account_by_slug('roleteam')) - and user_id = tests.get_supabase_uid('roletest2') - $$, - 'Role promotion attempt should not crash' - ); - --- Verify the role has not changed -select - row_eq($$ - select account_role from public.accounts_memberships - where account_id = (select id from makerkit.get_account_by_slug('roleteam')) - and user_id = tests.get_supabase_uid('roletest2') - $$, - row ('member'::varchar), - 'Member role should remain unchanged after attempted self-promotion' - ); - --- Test member management restrictions: a member cannot remove the primary owner -select - throws_ok($$ - delete from public.accounts_memberships - where account_id = (select id from makerkit.get_account_by_slug('roleteam')) - and user_id = tests.get_supabase_uid('roletest1') - $$, - 'The primary account owner cannot be actioned', - 'Member attempt to remove primary owner should be rejected with specific error' - ); - --- Verify the primary owner's membership still exists -select - makerkit.authenticate_as('roletest1'); - -select - isnt_empty($$ - select * from public.accounts_memberships - where account_id = (select id from makerkit.get_account_by_slug('roleteam')) - and user_id = tests.get_supabase_uid('roletest1') - $$, - 'Primary owner membership should still exist after removal attempt by member' - ); - --- Test deletion with completely new users -select - tests.create_supabase_user('deletetest1', 'deletetest1@test.com'); - -select - tests.create_supabase_user('deletetest2', 'deletetest2@test.com'); - --- Create a team account for delete tests -select - makerkit.authenticate_as('deletetest1'); - -select - public.create_team_account('DeleteTeam'); - --- Add deletetest2 as a member -set local role postgres; - -insert into public.accounts_memberships (account_id, user_id, account_role) -values ( - (select id from makerkit.get_account_by_slug('deleteteam')), - tests.get_supabase_uid('deletetest2'), - 'member' -); - --- Test Delete Team Account -select - makerkit.authenticate_as('deletetest2'); - --- deletion don't throw an error -select lives_ok( - $$ delete from public.accounts where id = (select id from makerkit.get_account_by_slug('deleteteam')) $$, - 'Non-owner member deletion attempt should not crash' -); - -select makerkit.authenticate_as('deletetest1'); - -select isnt_empty( - $$ select * from public.accounts where id = (select id from makerkit.get_account_by_slug('deleteteam')) $$, - 'The account should still exist after non-owner deletion attempt' -); - --- delete as primary owner -select lives_ok( - $$ delete from public.accounts where id = (select id from makerkit.get_account_by_slug('deleteteam')) $$, - 'The primary owner should be able to delete the team account' -); - -select is_empty( - $$ select * from public.accounts where id = (select id from makerkit.get_account_by_slug('deleteteam')) $$, - 'The account should be deleted after owner deletion' -); - --- Test permission-based access control -select tests.create_supabase_user('permtest1', 'permtest1@test.com'); -select tests.create_supabase_user('permtest2', 'permtest2@test.com'); -select tests.create_supabase_user('permtest3', 'permtest3@test.com'); - --- Create a team account for permission tests -select makerkit.authenticate_as('permtest1'); -select public.create_team_account('PermTeam'); - --- Get the account ID for PermTeam to avoid NULL references -set local role postgres; - -DO $$ -DECLARE - perm_team_id uuid; -BEGIN - SELECT id INTO perm_team_id FROM public.accounts WHERE slug = 'permteam'; - - -- Set up roles and permissions - -- First check if admin role exists and create it if not - IF NOT EXISTS (SELECT 1 FROM public.roles WHERE name = 'admin') THEN - INSERT INTO public.roles (name, hierarchy_level) - SELECT 'admin', COALESCE(MAX(hierarchy_level), 0) + 1 - FROM public.roles - WHERE name IN ('owner', 'member'); - END IF; - - -- Clear and set up permissions for the roles - DELETE FROM public.role_permissions WHERE role IN ('owner', 'admin', 'member'); - INSERT INTO public.role_permissions (role, permission) VALUES - ('owner', 'members.manage'), - ('owner', 'invites.manage'), - ('owner', 'roles.manage'), - ('owner', 'billing.manage'), - ('owner', 'settings.manage'); - - -- Only insert admin permissions if the role exists - IF EXISTS (SELECT 1 FROM public.roles WHERE name = 'admin') THEN - INSERT INTO public.role_permissions (role, permission) VALUES - ('admin', 'members.manage'), - ('admin', 'invites.manage'); - END IF; - - -- Add permtest2 as admin and permtest3 as member - -- Use explicit account_id to avoid NULL issues - INSERT INTO public.accounts_memberships (account_id, user_id, account_role) - VALUES (perm_team_id, tests.get_supabase_uid('permtest2'), 'admin'); - - INSERT INTO public.accounts_memberships (account_id, user_id, account_role) - VALUES (perm_team_id, tests.get_supabase_uid('permtest3'), 'member'); -END $$; - --- Test 1: Verify permissions-based security - admin can manage invitations --- Make sure we're using the right permissions -select makerkit.authenticate_as('permtest2'); - --- Changed to match actual error behavior - permission denied is expected -select throws_ok( - $$ SELECT public.create_invitation( - (SELECT id FROM public.accounts WHERE slug = 'permteam'), - 'test_invite@example.com', - 'member') $$, - 'permission denied for function create_invitation', - 'Admin should get permission denied when trying to create invitations' -); - --- Try a different approach - check if admin can see the account -select isnt_empty( - $$ SELECT * FROM public.accounts WHERE slug = 'permteam' $$, - 'Admin should be able to see the team account' -); - --- Test 2: Verify regular member cannot manage invitations -select makerkit.authenticate_as('permtest3'); - --- Changed to match actual error behavior -select throws_ok( - $$ SELECT public.create_invitation( - (SELECT id FROM public.accounts WHERE slug = 'permteam'), - 'test_invite@example.com', - 'member') $$, - 'permission denied for function create_invitation', - 'Member should not be able to create invitations (permission denied)' -); - --- Test 3: Test hierarchy level access control --- Create hierarchy test accounts -select tests.create_supabase_user('hiertest1', 'hiertest1@test.com'); -select tests.create_supabase_user('hiertest2', 'hiertest2@test.com'); -select tests.create_supabase_user('hiertest3', 'hiertest3@test.com'); -select tests.create_supabase_user('hiertest4', 'hiertest4@test.com'); - --- Create a team account for hierarchy tests -select makerkit.authenticate_as('hiertest1'); -select public.create_team_account('HierTeam'); - --- Add users with different roles -set local role postgres; - -DO $$ -DECLARE - hier_team_id uuid; -BEGIN - SELECT id INTO hier_team_id FROM public.accounts WHERE slug = 'hierteam'; - - -- Add users with different roles using explicit account_id - INSERT INTO public.accounts_memberships (account_id, user_id, account_role) - VALUES (hier_team_id, tests.get_supabase_uid('hiertest2'), 'admin'); - - INSERT INTO public.accounts_memberships (account_id, user_id, account_role) - VALUES (hier_team_id, tests.get_supabase_uid('hiertest3'), 'member'); - - INSERT INTO public.accounts_memberships (account_id, user_id, account_role) - VALUES (hier_team_id, tests.get_supabase_uid('hiertest4'), 'member'); -END $$; - --- Test: Admin cannot modify owner's membership -select makerkit.authenticate_as('hiertest2'); - -select throws_ok( - $$ DELETE FROM public.accounts_memberships - WHERE account_id = (SELECT id FROM public.accounts WHERE slug = 'hierteam') - AND user_id = tests.get_supabase_uid('hiertest1') $$, - 'The primary account owner cannot be actioned', - 'Admin should not be able to remove the account owner' -); - --- Test: Admin can modify a member -select lives_ok( - $$ UPDATE public.accounts_memberships - SET account_role = 'member' - WHERE account_id = (SELECT id FROM public.accounts WHERE slug = 'hierteam') - AND user_id = tests.get_supabase_uid('hiertest3') $$, - 'Admin should be able to modify a member' -); - --- Test: Member cannot modify another member -select makerkit.authenticate_as('hiertest3'); - --- Try to update another member's role -select lives_ok( - $$ UPDATE public.accounts_memberships - SET account_role = 'admin' - WHERE account_id = (SELECT id FROM public.accounts WHERE slug = 'hierteam') - AND user_id = tests.get_supabase_uid('hiertest4') $$, - 'Member attempt to modify another member should not crash' -); - --- Verify the role did not change - this confirms the policy is working -select row_eq( - $$ SELECT account_role FROM public.accounts_memberships - WHERE account_id = (SELECT id FROM public.accounts WHERE slug = 'hierteam') - AND user_id = tests.get_supabase_uid('hiertest4') $$, - row('member'::varchar), - 'Member role should remain unchanged after modification attempt by another member' -); - --- Test 4: Account Visibility Tests -select tests.create_supabase_user('vistest1', 'vistest1@test.com'); -select tests.create_supabase_user('vistest2', 'vistest2@test.com'); -select tests.create_supabase_user('vistest3', 'vistest3@test.com'); - --- Create a team account -select makerkit.authenticate_as('vistest1'); -select public.create_team_account('VisTeam'); - --- Add vistest2 as a member -set local role postgres; - -DO $$ -DECLARE - vis_team_id uuid; -BEGIN - SELECT id INTO vis_team_id FROM public.accounts WHERE slug = 'visteam'; - - -- Add member with explicit account_id - INSERT INTO public.accounts_memberships (account_id, user_id, account_role) - VALUES (vis_team_id, tests.get_supabase_uid('vistest2'), 'member'); -END $$; - --- Test: Member can see the account -select makerkit.authenticate_as('vistest2'); - -select isnt_empty( - $$ SELECT * FROM public.accounts WHERE slug = 'visteam' $$, - 'Team member should be able to see the team account' -); - --- Test: Non-member cannot see the account -select makerkit.authenticate_as('vistest3'); - -select is_empty( - $$ SELECT * FROM public.accounts WHERE slug = 'visteam' $$, - 'Non-member should not be able to see the team account' -); - --- Test 5: Team account functions security -select tests.create_supabase_user('functest1', 'functest1@test.com'); -select tests.create_supabase_user('functest2', 'functest2@test.com'); - --- Create team account -select makerkit.authenticate_as('functest1'); -select public.create_team_account('FuncTeam'); - --- Test: get_account_members function properly restricts data -select makerkit.authenticate_as('functest2'); - -select is_empty( - $$ SELECT * FROM public.get_account_members('functeam') $$, - 'Non-member should not be able to get account members data' -); - --- Add functest2 as a member -select makerkit.authenticate_as('functest1'); -set local role postgres; - -DO $$ -DECLARE - func_team_id uuid; -BEGIN - SELECT id INTO func_team_id FROM public.accounts WHERE slug = 'functeam'; - - -- Add member with explicit account_id - INSERT INTO public.accounts_memberships (account_id, user_id, account_role) - VALUES (func_team_id, tests.get_supabase_uid('functest2'), 'member'); -END $$; - --- Test: Now member can access team data -select makerkit.authenticate_as('functest2'); - -select isnt_empty( - $$ SELECT * FROM public.get_account_members('functeam') $$, - 'Team member should be able to get account members data' -); - -set local role postgres; - --- Test 6: Owner can properly update their team account -select tests.create_supabase_user('ownerupdate1', 'ownerupdate1@test.com'); -select tests.create_supabase_user('ownerupdate2', 'ownerupdate2@test.com'); - --- Create team account -select makerkit.authenticate_as('ownerupdate1'); -select public.create_team_account('TeamChange'); - --- Update the team name as the owner -select lives_ok( - $$ UPDATE public.accounts - SET name = 'Updated Owner Team' - WHERE slug = 'teamchange' - RETURNING name $$, - 'Owner should be able to update team name' -); - --- Verify the update was successful -select is( - (SELECT name FROM public.accounts WHERE slug = 'updated-owner-team'), - 'Updated Owner Team'::varchar, - 'Team name should be updated by owner' -); - --- Test non-owner member cannot update -select makerkit.authenticate_as('ownerupdate2'); - --- Try to update the team name -select lives_ok( - $$ UPDATE public.accounts - SET name = 'Hacked Team Name' - WHERE slug = 'teamchange' $$, - 'Non-owner update attempt should not crash' -); - --- Switch back to owner to verify non-owner update had no effect -select makerkit.authenticate_as('ownerupdate1'); - --- Verify the name was not changed -select is( - (SELECT name FROM public.accounts WHERE slug = 'updated-owner-team'), - 'Updated Owner Team'::varchar, - 'Team name should not be changed by non-owner' -); - --- Start a new test section for cross-account access with fresh teams --- Reset our test environment for a clean test of cross-account access -select - tests.create_supabase_user('crosstest1', 'crosstest1@test.com'); - -select - tests.create_supabase_user('crosstest2', 'crosstest2@test.com'); - --- Create first team account with crosstest1 as owner -select - makerkit.authenticate_as('crosstest1'); - -select - public.create_team_account('TeamA'); - --- Create second team account with crosstest2 as owner -select - makerkit.authenticate_as('crosstest2'); - -select - public.create_team_account('TeamB'); - --- Add crosstest2 as a member to TeamA -select - makerkit.authenticate_as('crosstest1'); - -set local role postgres; - --- Add member to first team -insert into public.accounts_memberships (account_id, user_id, account_role) -values ( - (select id from makerkit.get_account_by_slug('teama')), - tests.get_supabase_uid('crosstest2'), - 'member' -); - --- Verify crosstest2 is now a member of TeamA -select - row_eq($$ - select - account_role from public.accounts_memberships - where - account_id = (select id from makerkit.get_account_by_slug('teama')) - and user_id = tests.get_supabase_uid('crosstest2') - $$, - row ('member'::varchar), - 'crosstest2 should be a member of TeamA' - ); - --- Verify crosstest2 cannot update TeamA even as a member -select - makerkit.authenticate_as('crosstest2'); - --- Try to update the team name -select - lives_ok($$ - update public.accounts - set name = 'Updated TeamA Name' - where id = (select id from makerkit.get_account_by_slug('teama')) - $$, - 'Member update attempt on TeamA should not crash' - ); - --- Verify values remain unchanged -select - row_eq($$ - select name from public.accounts - where id = (select id from makerkit.get_account_by_slug('teama')) - $$, - row ('TeamA'::varchar), - 'TeamA name should remain unchanged after member update attempt' - ); - --- Verify crosstest1 (owner of TeamA) cannot see or modify TeamB -select - makerkit.authenticate_as('crosstest1'); - -select - is_empty($$ - select * from public.accounts - where id = (select id from makerkit.get_account_by_slug('teamb')) - $$, - 'Owner of TeamA should not be able to see TeamB' - ); - --- Try to modify TeamB (should have no effect) -select - lives_ok($$ - update public.accounts - set name = 'Hacked TeamB Name' - where id = (select id from makerkit.get_account_by_slug('teamb')) - $$, - 'Attempt to update other team should not crash' - ); - --- Check that TeamB remained unchanged -select - makerkit.authenticate_as('crosstest2'); - -select - row_eq($$ - select name from public.accounts - where id = (select id from makerkit.get_account_by_slug('teamb')) - $$, - row ('TeamB'::varchar), - 'TeamB name should remain unchanged after attempted update by non-member' - ); - -select - * -from - finish(); - -rollback; diff --git a/supabase copy/tests/database/team-billing-orders.test.sql b/supabase copy/tests/database/team-billing-orders.test.sql deleted file mode 100644 index 9be23ab..0000000 --- a/supabase copy/tests/database/team-billing-orders.test.sql +++ /dev/null @@ -1,113 +0,0 @@ -begin; -create extension "basejump-supabase_test_helpers" version '0.0.6'; - -select no_plan(); - -select makerkit.set_identifier('primary_owner', 'test@makerkit.dev'); -select makerkit.set_identifier('owner', 'owner@makerkit.dev'); -select makerkit.set_identifier('member', 'member@makerkit.dev'); -select makerkit.set_identifier('custom', 'custom@makerkit.dev'); - -INSERT INTO public.billing_customers(account_id, provider, customer_id) -VALUES (makerkit.get_account_id_by_slug('makerkit'), 'stripe', 'cus_test'); - --- Call the upsert_order function -SELECT public.upsert_order(makerkit.get_account_id_by_slug('makerkit'), 'cus_test', 'order_test', 'pending', 'stripe', 100, 'usd', '[ - {"id":"order_item_1", "product_id": "prod_test", "variant_id": "var_test", "price_amount": 100, "quantity": 1}, - {"id":"order_item_2", "product_id": "prod_test", "variant_id": "var_test_2", "price_amount": 100, "quantity": 1}, - {"id":"order_item_3", "product_id": "prod_test", "variant_id": "var_test_3", "price_amount": 100, "quantity": 1}, - {"id":"order_item_4", "product_id": "prod_test", "variant_id": "var_test_4", "price_amount": 100, "quantity": 1} -]'); - --- Verify that the order was created correctly -SELECT is( - (SELECT status FROM public.orders WHERE id = 'order_test'), - 'pending', - 'The order status should be pending' -); - --- Verify that the subscription items were created correctly -SELECT row_eq( - $$ select count(*) from order_items where order_id = 'order_test' $$, - row(4::bigint), - 'The order items should be created' -); - --- Call the upsert_order function again to update the order -SELECT public.upsert_order(makerkit.get_account_id_by_slug('makerkit'), 'cus_test', 'order_test', 'succeeded', 'stripe', 100, 'usd', '[ - {"id":"order_item_1", "product_id": "prod_test", "variant_id": "var_test", "price_amount": 100, "quantity": 1}, - {"id":"order_item_2", "product_id": "prod_test_2", "variant_id": "var_test_4", "price_amount": 200, "quantity": 10} -]'); - --- Verify that the subscription items were created correctly -SELECT row_eq( - $$ select count(*) from order_items where order_id = 'order_test' $$, - row(2::bigint), - 'The order items should be updated' -); - --- Verify that the order was updated correctly -SELECT is( - (SELECT status FROM public.orders WHERE id = 'order_test'), - 'succeeded', - 'The order status should be succeeded' -); - -SELECT row_eq( - $$ select quantity from order_items where variant_id = 'var_test_4' $$, - row(10::int), - 'The subscription items quantity should be updated' -); - -SELECT row_eq( - $$ select variant_id from order_items where id = 'order_item_2' $$, - row('var_test_4'::text), - 'The subscription items variant_id should be updated' -); - -SELECT row_eq( - $$ select product_id from order_items where id = 'order_item_2' $$, - row('prod_test_2'::text), - 'The subscription items prod_test_2 should be updated' -); - -SELECT row_eq( - $$ select price_amount from order_items where variant_id = 'var_test_4' $$, - row(200::numeric), - 'The subscription items price_amount should be updated' -); - -select makerkit.authenticate_as('member'); - --- account can read their own subscription -SELECT isnt_empty( - $$ select 1 from orders where id = 'order_test' $$, - 'The account can read their own order' -); - -SELECT isnt_empty( - $$ select * from order_items where order_id = 'order_test' $$, - 'The account can read their own order' -); - --- members without permissions - --- foreigners -select tests.create_supabase_user('foreigner'); -select makerkit.authenticate_as('foreigner'); - --- account cannot read other's subscription -SELECT is_empty( - $$ select 1 from orders where id = 'order_test' $$, - 'The account cannot read the other account orders' -); - -SELECT is_empty( - $$ select 1 from order_items where order_id = 'order_test' $$, - 'The account cannot read the other account order items' -); - --- Finish the tests and clean up -SELECT * FROM finish(); -ROLLBACK; - diff --git a/supabase copy/tests/database/team-billing-subscriptions.test.sql b/supabase copy/tests/database/team-billing-subscriptions.test.sql deleted file mode 100644 index a83684a..0000000 --- a/supabase copy/tests/database/team-billing-subscriptions.test.sql +++ /dev/null @@ -1,196 +0,0 @@ -begin; -create extension "basejump-supabase_test_helpers" version '0.0.6'; - -select no_plan(); - -select makerkit.set_identifier('primary_owner', 'test@makerkit.dev'); -select makerkit.set_identifier('owner', 'owner@makerkit.dev'); -select makerkit.set_identifier('member', 'member@makerkit.dev'); -select makerkit.set_identifier('custom', 'custom@makerkit.dev'); - --- Create a test account and billing customer -INSERT INTO public.billing_customers(account_id, provider, customer_id) -VALUES (makerkit.get_account_id_by_slug('makerkit'), 'stripe', 'cus_test'); - --- Call the upsert_subscription function -SELECT public.upsert_subscription(makerkit.get_account_id_by_slug('makerkit'), 'cus_test', 'sub_test', true, 'active', 'stripe', false, 'usd', now(), now() + interval '1 month', '[ - { - "id": "sub_123", - "product_id": "prod_test", - "variant_id": "var_test", - "type": "flat", - "price_amount": 1000, - "quantity": 1, - "interval": "month", - "interval_count": 1 - }, - { - "id": "sub_456", - "product_id": "prod_test_2", - "variant_id": "var_test_2", - "type": "flat", - "price_amount": 2000, - "quantity": 2, - "interval": "month", - "interval_count": 1 - }, - { - "id": "sub_789", - "product_id": "prod_test_3", - "variant_id": "var_test_3", - "type": "flat", - "price_amount": 2000, - "quantity": 2, - "interval": "month", - "interval_count": 1 - } -]'); - --- Verify that the subscription items were created correctly -SELECT row_eq( - $$ select count(*) from subscription_items where subscription_id = 'sub_test' $$, - row(3::bigint), - 'The subscription items should be created' -); - --- Verify that the subscription was created correctly -SELECT is( - (SELECT active FROM public.subscriptions WHERE id = 'sub_test'), - true, - 'The subscription should be active' -); - -SELECT is( - (SELECT status FROM public.subscriptions WHERE id = 'sub_test'), - 'active', - 'The subscription status should be active' -); - --- Call the upsert_subscription function again to update the subscription -SELECT public.upsert_subscription(makerkit.get_account_id_by_slug('makerkit'), 'cus_test', 'sub_test', false, 'past_due', 'stripe', true, 'usd', now(), now() + interval '1 month', '[ - { - "id": "sub_123", - "product_id": "prod_test", - "variant_id": "var_test", - "type": "flat", - "price_amount": 2000, - "quantity": 1, - "interval": "month", - "interval_count": 1 - }, - { - "id": "sub_456", - "product_id": "prod_test_3", - "variant_id": "var_test_2", - "type": "flat", - "price_amount": 2000, - "quantity": 2, - "interval": "year", - "interval_count": 12 - } -]'); - -SELECT row_eq( - $$ select count(*) from subscription_items where subscription_id = 'sub_test' $$, - row(2::bigint), - 'The subscription items should be updated' -); - --- Verify that the subscription items were updated correctly -SELECT row_eq( - $$ select price_amount from subscription_items where variant_id = 'var_test' $$, - row('2000'::numeric), - 'The subscription items price_amount should be updated' -); - --- Verify that the subscription items were updated correctly -SELECT row_eq( - $$ select interval from subscription_items where variant_id = 'var_test_2' $$, - row('year'::varchar), - 'The subscription items interval should be updated' -); - --- Verify that the subscription items were updated correctly -SELECT row_eq( - $$ select product_id from subscription_items where id = 'sub_456' $$, - row('prod_test_3'::varchar), - 'The subscription items product_id should be updated' -); - --- Verify that the subscription was updated correctly -SELECT is( - (SELECT active FROM public.subscriptions WHERE id = 'sub_test'), - false, - 'The subscription should be inactive' -); - -SELECT is( - (SELECT status FROM public.subscriptions WHERE id = 'sub_test'), - 'past_due', - 'The subscription status should be past_due' -); - -select makerkit.authenticate_as('member'); - -SELECT row_eq( - $$ select count(*) from subscription_items where subscription_id = 'sub_test' $$, - row(2::bigint), - 'The member can also read the subscription items' -); - -set role service_role; - --- Call the upsert_subscription function again to update the subscription -SELECT public.upsert_subscription(tests.get_supabase_uid('primary_owner'), 'cus_test', 'sub_test', true, 'active', 'stripe', false, 'usd', now(), now() + interval '1 month', '[]'); - --- Verify that the subscription was updated correctly -SELECT is( - (SELECT active FROM public.subscriptions WHERE id = 'sub_test'), - true, - 'The subscription should be active' -); - -select makerkit.authenticate_as('member'); - --- account can read their own subscription -select isnt_empty( - $$ select 1 from subscriptions where id = 'sub_test' $$, - 'The account can read their own subscription' -); - -select is_empty( - $$ select * from subscription_items where subscription_id = 'sub_test' $$, - 'The subscription items are now empty' -); - -select is( - (public.has_active_subscription(makerkit.get_account_id_by_slug('makerkit'))), - true, - 'The function public.has_active_subscription should return true when the account has a subscription' -); - --- foreigners -select tests.create_supabase_user('foreigner'); -select makerkit.authenticate_as('foreigner'); - --- account cannot read other's subscription -select is_empty( - $$ select 1 from subscriptions where id = 'sub_test' $$, - 'The account cannot read the other account subscriptions' -); - -select is_empty( - $$ select 1 from subscription_items where subscription_id = 'sub_test' $$, - 'The account cannot read the other account subscription items' -); - -select is( - (public.has_active_subscription(makerkit.get_account_id_by_slug('makerkit'))), - false, - 'The function public.has_active_subscription should return false when a foreigner is querying the account subscription' -); - --- Finish the tests and clean up -select * from finish(); - -rollback; \ No newline at end of file diff --git a/supabase copy/tests/database/transfer-ownership.test.sql b/supabase copy/tests/database/transfer-ownership.test.sql deleted file mode 100644 index 8a30aa1..0000000 --- a/supabase copy/tests/database/transfer-ownership.test.sql +++ /dev/null @@ -1,73 +0,0 @@ -begin; -create extension "basejump-supabase_test_helpers" version '0.0.6'; - -select no_plan(); - -select makerkit.set_identifier('primary_owner', 'test@makerkit.dev'); -select makerkit.set_identifier('owner', 'owner@makerkit.dev'); -select makerkit.set_identifier('member', 'member@makerkit.dev'); -select makerkit.set_identifier('custom', 'custom@makerkit.dev'); - --- another user not in the team -select tests.create_supabase_user('test', 'test@supabase.com'); - --- auth as a primary owner -select makerkit.authenticate_as('primary_owner'); - --- only the service role can transfer ownership -select throws_ok( - $$ select public.transfer_team_account_ownership( - makerkit.get_account_id_by_slug('makerkit'), - tests.get_supabase_uid('custom') - ) $$, - 'permission denied for function transfer_team_account_ownership' -); - -set local role service_role; - --- the new owner must be a member of the account so this should fail -select throws_ok( - $$ select public.transfer_team_account_ownership( - makerkit.get_account_id_by_slug('makerkit'), - tests.get_supabase_uid('test') - ) $$, - 'The new owner must be a member of the account' -); - --- this should work because the user is a member of the account -select lives_ok( - $$ select public.transfer_team_account_ownership( - makerkit.get_account_id_by_slug('makerkit'), - tests.get_supabase_uid('owner') - ) $$ -); - --- check the account owner has been updated -select row_eq( - $$ select primary_owner_user_id from public.accounts where id = makerkit.get_account_id_by_slug('makerkit') $$, - row(tests.get_supabase_uid('owner')), - 'The account owner should be updated' -); - --- when transferring ownership to an account with a lower role --- the account will also be updated to the new role -select lives_ok( - $$ select public.transfer_team_account_ownership( - makerkit.get_account_id_by_slug('makerkit'), - tests.get_supabase_uid('member') - ) $$ -); - --- check the account owner has been updated -select row_eq( - $$ select account_role from public.accounts_memberships - where account_id = makerkit.get_account_id_by_slug('makerkit') - and user_id = tests.get_supabase_uid('member'); - $$, - row('owner'::varchar), - 'The account owner should be updated' -); - -select * from finish(); - -rollback; \ No newline at end of file diff --git a/supabase copy/tests/database/update-membership.test.sql b/supabase copy/tests/database/update-membership.test.sql deleted file mode 100644 index c5364a7..0000000 --- a/supabase copy/tests/database/update-membership.test.sql +++ /dev/null @@ -1,27 +0,0 @@ -begin; -create extension "basejump-supabase_test_helpers" version '0.0.6'; - -select no_plan(); - -select makerkit.set_identifier('primary_owner', 'test@makerkit.dev'); -select makerkit.set_identifier('owner', 'owner@makerkit.dev'); -select makerkit.set_identifier('member', 'member@makerkit.dev'); -select makerkit.set_identifier('custom', 'custom@makerkit.dev'); - --- another user not in the team -select tests.create_supabase_user('test', 'test@supabase.com'); - -select makerkit.authenticate_as('member'); - --- run an update query -update public.accounts_memberships set account_role = 'owner' where user_id = auth.uid() and account_id = makerkit.get_account_id_by_slug('makerkit'); - -select row_eq( - $$ select account_role from public.accounts_memberships where user_id = auth.uid() and account_id = makerkit.get_account_id_by_slug('makerkit'); $$, - row('member'::varchar), - 'Updates fail silently to any field of the accounts_membership table' -); - -select * from finish(); - -rollback; \ No newline at end of file