Next.js Supabase V3 (#463)
Version 3 of the kit: - Radix UI replaced with Base UI (using the Shadcn UI patterns) - next-intl replaces react-i18next - enhanceAction deprecated; usage moved to next-safe-action - main layout now wrapped with [locale] path segment - Teams only mode - Layout updates - Zod v4 - Next.js 16.2 - Typescript 6 - All other dependencies updated - Removed deprecated Edge CSRF - Dynamic Github Action runner
This commit is contained in:
committed by
GitHub
parent
4912e402a3
commit
7ebff31475
341
docs/development/database-functions.mdoc
Normal file
341
docs/development/database-functions.mdoc
Normal file
@@ -0,0 +1,341 @@
|
||||
---
|
||||
status: "published"
|
||||
label: "Database Functions"
|
||||
order: 3
|
||||
title: "PostgreSQL Database Functions for Multi-Tenant SaaS"
|
||||
description: "Use built-in database functions for permissions, roles, subscriptions, and MFA checks. Includes has_permission, is_account_owner, has_active_subscription, and more."
|
||||
---
|
||||
|
||||
Makerkit includes built-in PostgreSQL functions for common multi-tenant operations like permission checks, role verification, and subscription status. Use these functions in RLS policies and application code to enforce consistent security rules across your database.
|
||||
|
||||
{% sequence title="Database Functions Reference" description="Built-in functions for multi-tenant operations" %}
|
||||
|
||||
[Call functions from SQL and RPC](#calling-database-functions)
|
||||
|
||||
[Account ownership and membership](#account-functions)
|
||||
|
||||
[Permission checks](#permission-functions)
|
||||
|
||||
[Subscription and billing](#subscription-functions)
|
||||
|
||||
[MFA and authentication](#authentication-functions)
|
||||
|
||||
{% /sequence %}
|
||||
|
||||
## Calling Database Functions
|
||||
|
||||
### From SQL (RLS Policies)
|
||||
|
||||
Use functions directly in SQL schemas and RLS policies:
|
||||
|
||||
```sql
|
||||
-- In an RLS policy
|
||||
create policy "Users can view their projects"
|
||||
on public.projects
|
||||
for select
|
||||
using (
|
||||
public.has_role_on_account(account_id)
|
||||
);
|
||||
```
|
||||
|
||||
### From Application Code (RPC)
|
||||
|
||||
Call functions via Supabase RPC:
|
||||
|
||||
```tsx
|
||||
const { data: isOwner, error } = await supabase.rpc('is_account_owner', {
|
||||
account_id: accountId,
|
||||
});
|
||||
|
||||
if (isOwner) {
|
||||
// User owns this account
|
||||
}
|
||||
```
|
||||
|
||||
## Account Functions
|
||||
|
||||
### is_account_owner
|
||||
|
||||
Check if the current user owns an account. Returns `true` if the account is the user's personal account or if they created a team account.
|
||||
|
||||
```sql
|
||||
public.is_account_owner(account_id uuid) returns boolean
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Restrict account deletion to owners
|
||||
- Gate billing management
|
||||
- Control team settings access
|
||||
|
||||
**Example RLS:**
|
||||
|
||||
```sql
|
||||
create policy "Only owners can delete accounts"
|
||||
on public.accounts
|
||||
for delete
|
||||
using (public.is_account_owner(id));
|
||||
```
|
||||
|
||||
### has_role_on_account
|
||||
|
||||
Check if the current user has membership on an account, optionally with a specific role.
|
||||
|
||||
```sql
|
||||
public.has_role_on_account(
|
||||
account_id uuid,
|
||||
account_role varchar(50) default null
|
||||
) returns boolean
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `account_id`: The account to check
|
||||
- `account_role`: Optional role name (e.g., `'owner'`, `'member'`). If omitted, returns `true` for any membership.
|
||||
|
||||
**Example RLS:**
|
||||
|
||||
```sql
|
||||
-- Any member can view
|
||||
create policy "Members can view projects"
|
||||
on public.projects
|
||||
for select
|
||||
using (public.has_role_on_account(account_id));
|
||||
|
||||
-- Only owners can update
|
||||
create policy "Owners can update projects"
|
||||
on public.projects
|
||||
for update
|
||||
using (public.has_role_on_account(account_id, 'owner'));
|
||||
```
|
||||
|
||||
### is_team_member
|
||||
|
||||
Check if a specific user is a member of a team account.
|
||||
|
||||
```sql
|
||||
public.is_team_member(
|
||||
account_id uuid,
|
||||
user_id uuid
|
||||
) returns boolean
|
||||
```
|
||||
|
||||
**Use case:** Verify team membership when the current user context isn't available.
|
||||
|
||||
### can_action_account_member
|
||||
|
||||
Check if the current user can perform actions on another team member (remove, change role, etc.).
|
||||
|
||||
```sql
|
||||
public.can_action_account_member(
|
||||
target_team_account_id uuid,
|
||||
target_user_id uuid
|
||||
) returns boolean
|
||||
```
|
||||
|
||||
**Logic:**
|
||||
1. If current user is account owner: `true`
|
||||
2. If target user is account owner: `false`
|
||||
3. Otherwise: Compare role hierarchy levels
|
||||
|
||||
**Example:**
|
||||
|
||||
```tsx
|
||||
const { data: canRemove } = await supabase.rpc('can_action_account_member', {
|
||||
target_team_account_id: teamId,
|
||||
target_user_id: memberId,
|
||||
});
|
||||
|
||||
if (!canRemove) {
|
||||
throw new Error('Cannot remove a user with equal or higher role');
|
||||
}
|
||||
```
|
||||
|
||||
## Permission Functions
|
||||
|
||||
### has_permission
|
||||
|
||||
Check if a user has a specific permission on an account. This is the primary function for granular access control.
|
||||
|
||||
```sql
|
||||
public.has_permission(
|
||||
user_id uuid,
|
||||
account_id uuid,
|
||||
permission_name app_permissions
|
||||
) returns boolean
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `user_id`: The user to check (use `auth.uid()` for current user)
|
||||
- `account_id`: The account context
|
||||
- `permission_name`: A value from the `app_permissions` enum
|
||||
|
||||
**Default permissions:**
|
||||
|
||||
```sql
|
||||
create type public.app_permissions as enum(
|
||||
'roles.manage',
|
||||
'billing.manage',
|
||||
'settings.manage',
|
||||
'members.manage',
|
||||
'invites.manage'
|
||||
);
|
||||
```
|
||||
|
||||
**Example RLS:**
|
||||
|
||||
```sql
|
||||
create policy "Users with tasks.write can insert tasks"
|
||||
on public.tasks
|
||||
for insert
|
||||
with check (
|
||||
public.has_permission(auth.uid(), account_id, 'tasks.write'::app_permissions)
|
||||
);
|
||||
```
|
||||
|
||||
**Example RPC:**
|
||||
|
||||
```tsx
|
||||
async function checkTaskWritePermission(accountId: string) {
|
||||
const { data: hasPermission } = await supabase.rpc('has_permission', {
|
||||
user_id: (await supabase.auth.getUser()).data.user?.id,
|
||||
account_id: accountId,
|
||||
permission: 'tasks.write',
|
||||
});
|
||||
|
||||
return hasPermission;
|
||||
}
|
||||
```
|
||||
|
||||
See [Permissions and Roles](/docs/next-supabase-turbo/development/permissions-and-roles) for adding custom permissions.
|
||||
|
||||
## Subscription Functions
|
||||
|
||||
### has_active_subscription
|
||||
|
||||
Check if an account has an active or trialing subscription.
|
||||
|
||||
```sql
|
||||
public.has_active_subscription(account_id uuid) returns boolean
|
||||
```
|
||||
|
||||
**Returns `true` when:**
|
||||
- Subscription status is `active`
|
||||
- Subscription status is `trialing`
|
||||
|
||||
**Returns `false` when:**
|
||||
- No subscription exists
|
||||
- Status is `canceled`, `past_due`, `unpaid`, `incomplete`, etc.
|
||||
|
||||
**Example RLS:**
|
||||
|
||||
```sql
|
||||
create policy "Only paid accounts can create projects"
|
||||
on public.projects
|
||||
for insert
|
||||
with check (
|
||||
public.has_active_subscription(account_id)
|
||||
);
|
||||
```
|
||||
|
||||
**Example application code:**
|
||||
|
||||
```tsx
|
||||
const { data: isPaid } = await supabase.rpc('has_active_subscription', {
|
||||
account_id: accountId,
|
||||
});
|
||||
|
||||
if (!isPaid) {
|
||||
redirect('/pricing');
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication Functions
|
||||
|
||||
### is_super_admin
|
||||
|
||||
Check if the current user is a super admin. Requires:
|
||||
- User is authenticated
|
||||
- User has `super_admin` role
|
||||
- User is currently signed in with MFA (AAL2)
|
||||
|
||||
```sql
|
||||
public.is_super_admin() returns boolean
|
||||
```
|
||||
|
||||
**Example RLS:**
|
||||
|
||||
```sql
|
||||
create policy "Super admins can view all accounts"
|
||||
on public.accounts
|
||||
for select
|
||||
using (public.is_super_admin());
|
||||
```
|
||||
|
||||
### is_mfa_compliant
|
||||
|
||||
Check if the current user meets MFA requirements. Returns `true` when:
|
||||
- User enabled MFA and is signed in with MFA (AAL2)
|
||||
- User has not enabled MFA (AAL1 is sufficient)
|
||||
|
||||
```sql
|
||||
public.is_mfa_compliant() returns boolean
|
||||
```
|
||||
|
||||
**Use case:** Allow users without MFA to continue normally while enforcing MFA for users who enabled it.
|
||||
|
||||
### is_aal2
|
||||
|
||||
Check if the current user is signed in with MFA (AAL2 authentication level).
|
||||
|
||||
```sql
|
||||
public.is_aal2() returns boolean
|
||||
```
|
||||
|
||||
**Use case:** Require MFA for sensitive operations regardless of user settings.
|
||||
|
||||
**Example:**
|
||||
|
||||
```sql
|
||||
-- Require MFA for billing operations
|
||||
create policy "MFA required for billing changes"
|
||||
on public.billing_settings
|
||||
for all
|
||||
using (public.is_aal2());
|
||||
```
|
||||
|
||||
## Configuration Functions
|
||||
|
||||
### is_set
|
||||
|
||||
Check if a configuration value is set in the `public.config` table.
|
||||
|
||||
```sql
|
||||
public.is_set(field_name text) returns boolean
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
```sql
|
||||
-- Check if a feature flag is enabled
|
||||
select public.is_set('enable_new_dashboard');
|
||||
```
|
||||
|
||||
## Function Reference Table
|
||||
|
||||
| Function | Purpose | Common Use |
|
||||
|----------|---------|------------|
|
||||
| `is_account_owner(account_id)` | Check account ownership | Delete, billing access |
|
||||
| `has_role_on_account(account_id, role?)` | Check membership/role | View, edit access |
|
||||
| `is_team_member(account_id, user_id)` | Check specific user membership | Team operations |
|
||||
| `can_action_account_member(account_id, user_id)` | Check member management rights | Remove, role change |
|
||||
| `has_permission(user_id, account_id, permission)` | Check granular permission | Feature access |
|
||||
| `has_active_subscription(account_id)` | Check billing status | Paid features |
|
||||
| `is_super_admin()` | Check admin status | Admin operations |
|
||||
| `is_mfa_compliant()` | Check MFA compliance | Security policies |
|
||||
| `is_aal2()` | Check MFA authentication | Sensitive operations |
|
||||
|
||||
## Related Resources
|
||||
|
||||
- [Permissions and Roles](/docs/next-supabase-turbo/development/permissions-and-roles) for adding custom permissions
|
||||
- [Database Schema](/docs/next-supabase-turbo/development/database-schema) for extending your schema
|
||||
- [Row Level Security](/docs/next-supabase-turbo/security/row-level-security) for RLS patterns
|
||||
- [Database Tests](/docs/next-supabase-turbo/development/database-tests) for testing database functions
|
||||
Reference in New Issue
Block a user