feat: shift admin user creation responsibility to gotrue service

This commit is contained in:
khorshuheng 2024-12-03 12:43:02 +08:00
parent a598aed5ec
commit a8c3adf166
7 changed files with 62 additions and 89 deletions

View File

@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE auth.users\n SET role = 'supabase_admin', email_confirmed_at = NOW()\n WHERE id = $1\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": []
},
"hash": "884c44d3a87ca4e520f9e8cec6ba673ea4e196920636e4a4db9d42fad3ef4d73"
}

View File

@ -56,11 +56,14 @@ GOTRUE_SMTP_USER=email_sender@some_company.com
GOTRUE_SMTP_PASS=email_sender_password
GOTRUE_SMTP_ADMIN_EMAIL=comp_admin@some_company.com
# This user will be created when AppFlowy Cloud starts successfully
# This user will be created when GoTrue starts successfully
# You can use this user to login to the admin panel
GOTRUE_ADMIN_EMAIL=admin@example.com
GOTRUE_ADMIN_PASSWORD=password
# Set this to true if users can only join by invite
GOTRUE_DISABLE_SIGNUP=false
# User will be redirected to this after Email or OAuth login
# Change this to your own domain where you host the docker-compose or gotrue
# If you are using a different domain, you need to change the redirect_uri in the OAuth2 configuration

View File

@ -39,6 +39,9 @@ GOTRUE_MAILER_TEMPLATES_EMAIL_CHANGE=https://raw.githubusercontent.com/AppFlowy-
GOTRUE_ADMIN_EMAIL=admin@example.com
GOTRUE_ADMIN_PASSWORD=password
# Set this to true if users can only join by invite
GOTRUE_DISABLE_SIGNUP=false
# The email verification link provided to users will redirect them to this specified host.
# You should update this setting to reflect the domain where you are hosting your application with docker-compose or gotrue.
# If you're using an Nginx proxy as part of your setup, this host should be set to the domain managed by the proxy.

View File

@ -33,6 +33,11 @@ services:
image: pgvector/pgvector:pg16
ports:
- "5432:5432"
healthcheck:
test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER}"]
interval: 5s
timeout: 5s
retries: 6
environment:
- POSTGRES_USER=${POSTGRES_USER:-postgres}
- POSTGRES_DB=${POSTGRES_DB:-postgres}
@ -50,14 +55,28 @@ services:
gotrue:
restart: on-failure
image: supabase/gotrue:v2.159.1
image: appflowyinc/gotrue:${GOTRUE_VERSION:-latest}
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "nc", "-z", "localhost", "9999"]
interval: 5s
timeout: 5s
retries: 6
environment:
# There are a lot of options to configure GoTrue. You can reference the example config:
# https://github.com/supabase/gotrue/blob/master/example.env
- GOTRUE_ADMIN_EMAIL=${GOTRUE_ADMIN_EMAIL}
- GOTRUE_ADMIN_PASSWORD=${GOTRUE_ADMIN_PASSWORD}
- GOTRUE_DISABLE_SIGNUP=${GOTRUE_DISABLE_SIGNUP:-false}
- GOTRUE_SITE_URL=appflowy-flutter:// # redirected to AppFlowy application
- URI_ALLOW_LIST=* # adjust restrict if necessary
- GOTRUE_JWT_SECRET=${GOTRUE_JWT_SECRET} # authentication secret
- GOTRUE_JWT_EXP=${GOTRUE_JWT_EXP}
# Without this environment variable, the createuser command will create an admin
# with the `admin` role as opposed to `supabase_admin`
- GOTRUE_JWT_ADMIN_GROUP_NAME=supabase_admin
- GOTRUE_DB_DRIVER=postgres
- API_EXTERNAL_URL=${API_EXTERNAL_URL}
- DATABASE_URL=${GOTRUE_DATABASE_URL}
@ -122,6 +141,9 @@ services:
FEATURES: ""
PROFILE: ci
image: appflowyinc/appflowy_cloud:${APPFLOWY_CLOUD_VERSION:-latest}
depends_on:
gotrue:
condition: service_healthy
admin_frontend:
restart: on-failure

View File

@ -43,15 +43,21 @@ services:
gotrue:
restart: on-failure
image: supabase/gotrue:v2.159.1
image: appflowyinc/gotrue:${GOTRUE_VERSION:-latest}
depends_on:
- postgres
environment:
# Gotrue config: https://github.com/supabase/gotrue/blob/master/example.env
- GOTRUE_ADMIN_EMAIL=${GOTRUE_ADMIN_EMAIL}
- GOTRUE_ADMIN_PASSWORD=${GOTRUE_ADMIN_PASSWORD}
- GOTRUE_DISABLE_SIGNUP=${GOTRUE_DISABLE_SIGNUP:-false}
- GOTRUE_SITE_URL=appflowy-flutter:// # redirected to AppFlowy application
- URI_ALLOW_LIST=* # adjust restrict if necessary
- GOTRUE_JWT_SECRET=${GOTRUE_JWT_SECRET} # authentication secret
- GOTRUE_JWT_EXP=${GOTRUE_JWT_EXP}
# Without this environment variable, the createuser command will create an admin
# with the `admin` role as opposed to `supabase_admin`
- GOTRUE_JWT_ADMIN_GROUP_NAME=supabase_admin
- GOTRUE_DB_DRIVER=postgres
- API_EXTERNAL_URL=${API_EXTERNAL_URL}
- DATABASE_URL=${GOTRUE_DATABASE_URL}

View File

@ -33,6 +33,11 @@ services:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password}
- POSTGRES_HOST=${POSTGRES_HOST:-postgres}
- SUPABASE_PASSWORD=${SUPABASE_PASSWORD:-root}
healthcheck:
test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER}"]
interval: 5s
timeout: 5s
retries: 6
volumes:
- ./migrations/before:/docker-entrypoint-initdb.d
- postgres_data:/var/lib/postgresql/data
@ -46,14 +51,28 @@ services:
build:
context: docker/gotrue
dockerfile: Dockerfile
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "nc", "-z", "localhost", "9999"]
interval: 5s
timeout: 5s
retries: 6
image: appflowyinc/gotrue:${GOTRUE_VERSION:-latest}
environment:
# There are a lot of options to configure GoTrue. You can reference the example config:
# https://github.com/supabase/gotrue/blob/master/example.env
- GOTRUE_ADMIN_EMAIL=${GOTRUE_ADMIN_EMAIL}
- GOTRUE_ADMIN_PASSWORD=${GOTRUE_ADMIN_PASSWORD}
- GOTRUE_DISABLE_SIGNUP=${GOTRUE_DISABLE_SIGNUP:-false}
- GOTRUE_SITE_URL=appflowy-flutter:// # redirected to AppFlowy application
- URI_ALLOW_LIST=* # adjust restrict if necessary
- GOTRUE_JWT_SECRET=${GOTRUE_JWT_SECRET} # authentication secret
- GOTRUE_JWT_EXP=${GOTRUE_JWT_EXP}
# Without this environment variable, the createuser command will create an admin
# with the `admin` role as opposed to `supabase_admin`
- GOTRUE_JWT_ADMIN_GROUP_NAME=supabase_admin
- GOTRUE_DB_DRIVER=postgres
- API_EXTERNAL_URL=${API_EXTERNAL_URL}
- DATABASE_URL=${GOTRUE_DATABASE_URL}

View File

@ -32,7 +32,7 @@ use openssl::x509::X509;
use secrecy::{ExposeSecret, Secret};
use sqlx::{postgres::PgPoolOptions, PgPool};
use tokio::sync::RwLock;
use tracing::{error, info, warn};
use tracing::{error, info};
use appflowy_ai_client::client::AppFlowyAIClient;
use appflowy_collaborate::actix_ws::server::RealtimeServerActor;
@ -43,7 +43,6 @@ use appflowy_collaborate::indexer::IndexerProvider;
use appflowy_collaborate::snapshot::SnapshotControl;
use appflowy_collaborate::CollaborationServer;
use database::file::s3_client_impl::{AwsS3BucketClientImpl, S3BucketStorage};
use gotrue::grant::{Grant, PasswordGrant};
use mailer::sender::Mailer;
use snowflake::Snowflake;
use tonic_proto::history::history_client::HistoryClient;
@ -240,7 +239,7 @@ pub async fn init_state(config: &Config, rt_cmd_tx: CLCommandSender) -> Result<A
// Gotrue
info!("Connecting to GoTrue...");
let gotrue_client = get_gotrue_client(&config.gotrue).await?;
let gotrue_admin = setup_admin_account(gotrue_client.clone(), &pg_pool, &config.gotrue).await?;
let gotrue_admin = get_admin_client(gotrue_client.clone(), &config.gotrue);
// Redis
info!("Connecting to Redis...");
@ -345,82 +344,17 @@ pub async fn init_state(config: &Config, rt_cmd_tx: CLCommandSender) -> Result<A
})
}
async fn setup_admin_account(
fn get_admin_client(
gotrue_client: gotrue::api::Client,
pg_pool: &PgPool,
gotrue_setting: &GoTrueSetting,
) -> Result<GoTrueAdmin, Error> {
) -> GoTrueAdmin {
let admin_email = gotrue_setting.admin_email.as_str();
let password = gotrue_setting.admin_password.expose_secret();
let gotrue_admin = GoTrueAdmin::new(
GoTrueAdmin::new(
admin_email.to_owned(),
password.to_owned(),
gotrue_client.clone(),
);
match gotrue_client
.token(&Grant::Password(PasswordGrant {
email: admin_email.to_owned(),
password: password.clone(),
}))
.await
{
Ok(_token) => return Ok(gotrue_admin),
Err(err) => tracing::warn!("Failed to get token: {:?}", err),
};
let res_resp = gotrue_client.sign_up(admin_email, password, None).await;
match res_resp {
Err(err) => {
if let app_error::gotrue::GoTrueError::Internal(err) = err {
match (err.code, err.msg.as_str()) {
(400..=499, "User already registered") => {
info!("Admin user already registered");
Ok(gotrue_admin)
},
_ => Err(err.into()),
}
} else {
Err(err.into())
}
},
Ok(resp) => {
let admin_user = {
match resp {
gotrue_entity::dto::SignUpResponse::Authenticated(resp) => resp.user,
gotrue_entity::dto::SignUpResponse::NotAuthenticated(user) => user,
}
};
match admin_user.role.as_str() {
"supabase_admin" => {
info!("Admin user already created and set role to supabase_admin");
Ok(gotrue_admin)
},
_ => {
let user_id = admin_user.id.parse::<uuid::Uuid>()?;
let result = sqlx::query!(
r#"
UPDATE auth.users
SET role = 'supabase_admin', email_confirmed_at = NOW()
WHERE id = $1
"#,
user_id,
)
.execute(pg_pool)
.await
.context("failed to update the admin user")?;
if result.rows_affected() != 1 {
warn!("Failed to update the admin user");
} else {
info!("Admin user created and set role to supabase_admin");
}
Ok(gotrue_admin)
},
}
},
}
)
}
async fn get_redis_client(redis_uri: &str) -> Result<redis::aio::ConnectionManager, Error> {