Skip to content
This repository was archived by the owner on Sep 1, 2026. It is now read-only.

feat(auth): auto-assign super/system admin metadata for GoTrue users via database migration - #1639

Open
tonicofonico wants to merge 7 commits into
AppFlowy-IO:mainfrom
tonicofonico:fix/auto-grant-gotrue-super-admin
Open

feat(auth): auto-assign super/system admin metadata for GoTrue users via database migration#1639
tonicofonico wants to merge 7 commits into
AppFlowy-IO:mainfrom
tonicofonico:fix/auto-grant-gotrue-super-admin

Conversation

@tonicofonico

@tonicofonico tonicofonico commented Aug 1, 2026

Copy link
Copy Markdown

πŸ“ Description of Changes

Fixes #1638.

This PR adds a SQL migration (20260801100000_auto_grant_super_admin.sql) that creates a PostgreSQL trigger on auth.users to automatically assign is_super_admin: true and is_system_admin: true flags to GoTrue admin users on initial setup and insertion.

πŸ”— Related Pull Requests & Issues


πŸ” Problem & Motivation

When deploying AppFlowy Cloud with GOTRUE_ADMIN_EMAIL, GoTrue initializes the user account in auth.users with basic metadata ({"provider": "email"}).

However, admin_frontend (/console) and appflowy_cloud admin APIs require is_super_admin: true or is_system_admin: true in raw_app_meta_data. Without these flags, logging in for the first time results in HTTP 401 / "User not allowed" errors when navigating /console.

πŸ› οΈ Changes Included:

  • migrations/20260801100000_auto_grant_super_admin.sql: Added a PL/pgSQL trigger function auto_grant_super_admin_func() on auth.users that automatically merges is_system_admin: true for users with is_super_admin: true.

πŸ§ͺ Verification:

  • Verified migration executes cleanly in PostgreSQL 16.
  • Verified raw_app_meta_data is automatically populated on admin user insertion.

Summary by Sourcery

Add a database migration to automatically synchronize super/system admin metadata for GoTrue users and expose a configuration flag to disable server actions in the admin frontend.

New Features:

  • Introduce a PostgreSQL trigger-based migration that ensures auth.users records with admin flags always have both is_super_admin and is_system_admin set in raw_app_meta_data.
  • Expose the NEXT_PUBLIC_DISABLE_SERVER_ACTIONS environment variable in docker-compose to control server actions behavior for the admin frontend.

@sourcery-ai

sourcery-ai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a PostgreSQL trigger-based migration to automatically synchronize super/system admin flags in GoTrue user metadata, and exposes a new environment variable for disabling server actions in the admin frontend Docker Compose configuration.

Sequence diagram for automatic super/system admin metadata synchronization

sequenceDiagram
    actor AdminUser
    participant GoTrue
    participant auth_users
    participant auto_grant_super_admin_func
    participant admin_frontend

    AdminUser->>GoTrue: POST /signup
    GoTrue->>auth_users: INSERT auth.users
    auth_users->>auto_grant_super_admin_func: auto_grant_super_admin_func
    auto_grant_super_admin_func->>auth_users: set NEW.raw_app_meta_data

    AdminUser->>admin_frontend: GET /console
    admin_frontend->>auth_users: SELECT raw_app_meta_data
    auth_users-->>admin_frontend: raw_app_meta_data with is_super_admin and is_system_admin
    admin_frontend-->>AdminUser: console access granted
Loading

File-Level Changes

Change Details Files
Introduce a PostgreSQL trigger function and trigger on auth.users to automatically synchronize is_super_admin and is_system_admin flags in raw_app_meta_data for GoTrue users.
  • Add a migration script that conditionally executes only when the auth.users table exists.
  • Define auth.auto_grant_super_admin_func() to normalize raw_app_meta_data when either admin flag is set to true.
  • Create a BEFORE INSERT OR UPDATE trigger on auth.users that invokes the new function, replacing any existing trigger with the same name.
migrations/20260801100000_auto_grant_super_admin.sql
Expose a configuration flag to disable server actions in the admin frontend container via Docker Compose.
  • Add NEXT_PUBLIC_DISABLE_SERVER_ACTIONS environment variable to the admin_frontend service with a default of false.
docker-compose.yml

Assessment against linked issues

Issue Objective Addressed Explanation
#1638 Automatically set is_super_admin: true and is_system_admin: true in auth.users.raw_app_meta_data for the initial GoTrue admin user configured via GOTRUE_ADMIN_EMAIL, so that first login to /console works without 401 errors. ❌ The migration adds a trigger that only synchronizes the flags when raw_app_meta_data already contains is_super_admin or is_system_admin set to true. The initial GoTrue admin user created via GOTRUE_ADMIN_EMAIL has metadata { "provider": "email" } without these flags, so the trigger will not add them, and the first login problem remains.
#1638 Implement a database trigger or startup bootstrap task in appflowy_cloud that removes the need for manual SQL updates by automatically merging { "is_super_admin": true, "is_system_admin": true } into auth.users.raw_app_meta_data for the configured admin user. ❌ While the PR introduces a PostgreSQL trigger on auth.users, it does not check the GOTRUE_ADMIN_EMAIL or otherwise identify the initial admin user and does not unconditionally merge the admin flags. It only normalizes flags when one of them is already present, so manual SQL intervention is still required for newly created admin users lacking those flags.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The trigger and backfill logic only checks for is_super_admin and will skip adding is_system_admin if is_super_admin already exists, which can leave is_system_admin unset for some users; consider checking and merging both flags independently.
  • When raw_app_meta_data is NULL, the trigger overwrites it with a hard-coded JSON payload including provider and providers, which may not match non-email or preconfigured providers; preserving existing provider-related metadata or only injecting the admin flags when missing would be safer.
  • The trigger function auto_grant_super_admin_func is not schema-qualified in the CREATE TRIGGER statement; explicitly qualifying the schema (e.g., EXECUTE FUNCTION public.auto_grant_super_admin_func()) can avoid issues if search_path changes or if a function with the same name exists in another schema.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The trigger and backfill logic only checks for `is_super_admin` and will skip adding `is_system_admin` if `is_super_admin` already exists, which can leave `is_system_admin` unset for some users; consider checking and merging both flags independently.
- When `raw_app_meta_data` is `NULL`, the trigger overwrites it with a hard-coded JSON payload including `provider` and `providers`, which may not match non-email or preconfigured providers; preserving existing provider-related metadata or only injecting the admin flags when missing would be safer.
- The trigger function `auto_grant_super_admin_func` is not schema-qualified in the `CREATE TRIGGER` statement; explicitly qualifying the schema (e.g., `EXECUTE FUNCTION public.auto_grant_super_admin_func()`) can avoid issues if `search_path` changes or if a function with the same name exists in another schema.

## Individual Comments

### Comment 1
<location path="migrations/20260801100000_auto_grant_super_admin.sql" line_range="2-10" />
<code_context>
+-- Trigger function to automatically ensure super/system admin metadata for GoTrue admin users
+CREATE OR REPLACE FUNCTION auto_grant_super_admin_func()
+RETURNS TRIGGER AS $$
+BEGIN
+    IF NEW.raw_app_meta_data IS NULL THEN
+        NEW.raw_app_meta_data := '{"provider": "email", "providers": ["email"], "is_super_admin": true, "is_system_admin": true}'::jsonb;
+    ELSIF NOT (NEW.raw_app_meta_data ? 'is_super_admin') THEN
+        NEW.raw_app_meta_data := NEW.raw_app_meta_data || '{"is_super_admin": true, "is_system_admin": true}'::jsonb;
+    END IF;
+    RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
</code_context>
<issue_to_address>
**🚨 issue (security):** Trigger function currently promotes every inserted user to super/system admin, which appears broader than the stated intent.

This trigger runs for every insert into `auth.users`, and for any row where `raw_app_meta_data` is null or missing `is_super_admin`, it sets both `is_super_admin` and `is_system_admin` to true. That means all new users are promoted to super/system admin, which is a serious privilege escalation unless this table is guaranteed to hold only admin accounts. If you intend to affect only specific admins, add a restrictive condition (e.g., on email, role, or existing metadata) so the trigger does not grant admin rights to all users.
</issue_to_address>

### Comment 2
<location path="migrations/20260801100000_auto_grant_super_admin.sql" line_range="24-26" />
<code_context>
+        FOR EACH ROW
+        EXECUTE FUNCTION auto_grant_super_admin_func();
+
+        UPDATE auth.users
+        SET raw_app_meta_data = COALESCE(raw_app_meta_data, '{}'::jsonb) || '{"is_super_admin": true, "is_system_admin": true}'::jsonb
+        WHERE NOT (COALESCE(raw_app_meta_data, '{}'::jsonb) ? 'is_super_admin');
+    END IF;
+END $$;
</code_context>
<issue_to_address>
**🚨 issue (security):** Backfilling `is_super_admin`/`is_system_admin` for all existing users missing the flag may unintentionally elevate regular users.

The `UPDATE` currently sets both flags for every user whose metadata lacks `is_super_admin`, effectively granting super/system admin to all existing users. If the intent is to align metadata for users who are already admins, this should filter on an existing admin signal (e.g., `role` or another indicator) instead of applying to all rows.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click πŸ‘ or πŸ‘Ž on each comment and I'll use the feedback to improve your reviews.

Comment thread migrations/20260801100000_auto_grant_super_admin.sql Outdated
Comment thread migrations/20260801100000_auto_grant_super_admin.sql Outdated
@tonicofonico

Copy link
Copy Markdown
Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • The migration only adds is_system_admin when is_super_admin is already true, but does not auto-assign is_super_admin: true itself as described in the PR; consider aligning the trigger’s behavior with the stated goal of automatically granting both flags to the initial GoTrue admin user.
  • The trigger is defined as BEFORE INSERT only, so subsequent updates to raw_app_meta_data (e.g., toggling admin flags after creation) will not be synchronized; consider extending it to BEFORE INSERT OR UPDATE if you want consistent behavior for changes after user creation.
  • The trigger function is created in the public schema but operates on auth.users; for better isolation and consistency, consider placing the function in the auth schema or another dedicated schema used for auth-related logic.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The migration only adds `is_system_admin` when `is_super_admin` is already true, but does not auto-assign `is_super_admin: true` itself as described in the PR; consider aligning the trigger’s behavior with the stated goal of automatically granting both flags to the initial GoTrue admin user.
- The trigger is defined as `BEFORE INSERT` only, so subsequent updates to `raw_app_meta_data` (e.g., toggling admin flags after creation) will not be synchronized; consider extending it to `BEFORE INSERT OR UPDATE` if you want consistent behavior for changes after user creation.
- The trigger function is created in the `public` schema but operates on `auth.users`; for better isolation and consistency, consider placing the function in the `auth` schema or another dedicated schema used for auth-related logic.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click πŸ‘ or πŸ‘Ž on each comment and I'll use the feedback to improve your reviews.

@tonicofonico

Copy link
Copy Markdown
Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The trigger currently runs on both INSERT and UPDATE and will force is_super_admin and is_system_admin to true whenever either flag is present and set to true, which may unintentionally override manual demotions or partial privilege configurations; consider scoping it to INSERT only or tightening the conditions.
  • The implementation sets both is_super_admin and is_system_admin to true when either is true, but the PR description suggests only synchronizing is_system_admin for super admins; align the trigger logic with the intended behavior to avoid unintended elevation of system admins to super admins.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The trigger currently runs on both INSERT and UPDATE and will force `is_super_admin` and `is_system_admin` to `true` whenever either flag is present and set to `true`, which may unintentionally override manual demotions or partial privilege configurations; consider scoping it to INSERT only or tightening the conditions.
- The implementation sets both `is_super_admin` and `is_system_admin` to `true` when either is true, but the PR description suggests only synchronizing `is_system_admin` for super admins; align the trigger logic with the intended behavior to avoid unintended elevation of system admins to super admins.

## Individual Comments

### Comment 1
<location path="migrations/20260801100000_auto_grant_super_admin.sql" line_range="19-24" />
<code_context>
+-- Attach trigger to auth.users if auth schema exists
+DO $$
+BEGIN
+    IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'auth' AND table_name = 'users') THEN
+        DROP TRIGGER IF EXISTS trigger_auto_grant_super_admin ON auth.users;
+        CREATE TRIGGER trigger_auto_grant_super_admin
+        BEFORE INSERT OR UPDATE ON auth.users
+        FOR EACH ROW
+        EXECUTE FUNCTION auth.auto_grant_super_admin_func();
+    END IF;
+END $$;
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Consider aligning trigger installation check with function/schema existence.

The trigger is only created if `auth.users` exists, but `auth.auto_grant_super_admin_func` is assumed to exist unconditionally. In setups where the `auth` schema/tables are created later or via a different migration, this can cause ordering issues. To keep them consistent, consider guarding the function creation with a similar `IF EXISTS` check or defining the function inside the same conditional block as the trigger.

Suggested implementation:

```
-- Attach trigger to auth.users if auth schema and function exist
DO $$
BEGIN
    IF EXISTS (
        SELECT 1
        FROM information_schema.tables
        WHERE table_schema = 'auth'
          AND table_name = 'users'
    )
    AND EXISTS (
        SELECT 1
        FROM pg_proc p
        JOIN pg_namespace n ON n.oid = p.pronamespace
        WHERE n.nspname = 'auth'
          AND p.proname = 'auto_grant_super_admin_func'
    )
    THEN
        DROP TRIGGER IF EXISTS trigger_auto_grant_super_admin ON auth.users;
        CREATE TRIGGER trigger_auto_grant_super_admin
        BEFORE INSERT OR UPDATE ON auth.users
        FOR EACH ROW
        EXECUTE FUNCTION auth.auto_grant_super_admin_func();
    END IF;
END $$;

```

To fully align trigger installation with function/schema existence and avoid migration ordering issues, consider:
1. Wrapping the `CREATE FUNCTION auth.auto_grant_super_admin_func` definition in a similar conditional block that checks for the `auth` schema (and optionally `auth.users`) before creating/replacing the function.
2. Alternatively, ensure that the migration which creates the `auth` schema and `auth.auto_grant_super_admin_func` runs before this migration, and document that dependency in your migration tooling (e.g., by ordering or explicit dependency metadata).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click πŸ‘ or πŸ‘Ž on each comment and I'll use the feedback to improve your reviews.

Comment thread migrations/20260801100000_auto_grant_super_admin.sql
@tonicofonico

Copy link
Copy Markdown
Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • The trigger currently skips rows where raw_app_meta_data is NULL; if the goal is to auto-populate admin metadata for newly-created admin users, consider initializing raw_app_meta_data to a JSON object when it’s NULL so the flags can still be applied.
  • In the trigger condition you compare JSON values to the string 'true'; if these fields might be stored as JSON booleans, it would be more robust to cast the JSONB values or use -> to check for true explicitly rather than relying on text comparison.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The trigger currently skips rows where raw_app_meta_data is NULL; if the goal is to auto-populate admin metadata for newly-created admin users, consider initializing raw_app_meta_data to a JSON object when it’s NULL so the flags can still be applied.
- In the trigger condition you compare JSON values to the string 'true'; if these fields might be stored as JSON booleans, it would be more robust to cast the JSONB values or use -> to check for true explicitly rather than relying on text comparison.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click πŸ‘ or πŸ‘Ž on each comment and I'll use the feedback to improve your reviews.

@tonicofonico

Copy link
Copy Markdown
Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click πŸ‘ or πŸ‘Ž on each comment and I'll use the feedback to improve your reviews.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(auth): auto-assign super/system admin metadata for GOTRUE_ADMIN_EMAIL on initial setup

1 participant