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
Follow this link to confirm the update of your email from {{ .Email }} to {{ .NewEmail }} or copy and paste this URL into your browser: {{ .ConfirmationURL }} |
|
|
\ 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
Confirm your email to get started |
|
|
|
\ 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
You have been invited to 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
Reset your Makerkit password |
|
|
|
\ 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