feat: use email for af workspace invitation table
This commit is contained in:
parent
ba1fa8f307
commit
9b28edb5bc
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE public.af_workspace_invitation\n SET status = 1\n WHERE invitee = (SELECT uid FROM public.af_user WHERE uuid = $1)\n AND id = $2\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "15b177321fc6b28e249bd1ae6863bda18a7d0e4461f2239c25e6ee448b279d3e"
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n workspace_id,\n inviter AS inviter_uid,\n invitee AS invitee_uid,\n status,\n role_id AS role\n FROM\n public.af_workspace_invitation\n WHERE id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "inviter_uid",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "invitee_uid",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "status",
|
||||
"type_info": "Int2"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "role",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "5479b2f4b07cf39ff655024d7436223b5812940ae3801cd76cbf5276530ac851"
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n id AS invite_id,\n workspace_id,\n (SELECT workspace_name FROM public.af_workspace WHERE workspace_id = af_workspace_invitation.workspace_id),\n (SELECT email FROM public.af_user WHERE uid = af_workspace_invitation.inviter) AS inviter_email,\n (SELECT name FROM public.af_user WHERE uid = af_workspace_invitation.inviter) AS inviter_name,\n status,\n updated_at\n FROM\n public.af_workspace_invitation\n WHERE af_workspace_invitation.invitee = (SELECT uid FROM public.af_user WHERE uuid = $1)\n AND ($2::SMALLINT IS NULL OR status = $2)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "invite_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "workspace_name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "inviter_email",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "inviter_name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "status",
|
||||
"type_info": "Int2"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "updated_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Int2"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "bd39ce152d3468956db3896d892242603963e3a5435c4c9e2457c5afcbbdc510"
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO public.af_workspace_invitation (\n workspace_id,\n inviter,\n invitee,\n role_id\n )\n VALUES (\n $1,\n (SELECT uid FROM public.af_user WHERE uuid = $2),\n (SELECT uid FROM public.af_user WHERE email = $3),\n $4\n )\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Uuid",
|
||||
"Text",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e3e2533f5b58394d3eaef0d90c2ff9a5e00eb7bfd696e5156ffc840ae401dfc1"
|
||||
}
|
||||
|
|
@ -146,7 +146,7 @@ pub async fn admin_users_handler(
|
|||
) -> Result<Html<String>, WebAppError> {
|
||||
let users = state
|
||||
.gotrue_client
|
||||
.admin_list_user(&session.token.access_token)
|
||||
.admin_list_user(&session.token.access_token, None)
|
||||
.await
|
||||
.map_or_else(
|
||||
|err| {
|
||||
|
|
|
|||
|
|
@ -430,6 +430,19 @@ impl Client {
|
|||
)
|
||||
}
|
||||
|
||||
// filter is postgre sql like filter
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
pub async fn admin_list_users(
|
||||
&self,
|
||||
filter: Option<&str>,
|
||||
) -> Result<Vec<User>, AppResponseError> {
|
||||
let user = self
|
||||
.gotrue_client
|
||||
.admin_list_user(&self.access_token()?, filter)
|
||||
.await?;
|
||||
Ok(user.users)
|
||||
}
|
||||
|
||||
/// Only expose this method for testing
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn token(&self) -> Arc<RwLock<ClientToken>> {
|
||||
|
|
|
|||
|
|
@ -175,11 +175,11 @@ pub struct AFSnapshotRow {
|
|||
pub workspace_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(FromRow, Deserialize, Serialize)]
|
||||
#[derive(Debug, FromRow, Deserialize, Serialize)]
|
||||
pub struct AFWorkspaceInvitationMinimal {
|
||||
pub workspace_id: Uuid,
|
||||
pub inviter_uid: i64,
|
||||
pub invitee_uid: i64,
|
||||
pub invitee_uid: Option<i64>,
|
||||
pub status: AFWorkspaceInvitationStatus,
|
||||
pub role: AFRole,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -248,13 +248,13 @@ pub async fn insert_workspace_invitation(
|
|||
INSERT INTO public.af_workspace_invitation (
|
||||
workspace_id,
|
||||
inviter,
|
||||
invitee,
|
||||
invitee_email,
|
||||
role_id
|
||||
)
|
||||
VALUES (
|
||||
$1,
|
||||
(SELECT uid FROM public.af_user WHERE uuid = $2),
|
||||
(SELECT uid FROM public.af_user WHERE email = $3),
|
||||
$3,
|
||||
$4
|
||||
)
|
||||
"#,
|
||||
|
|
@ -269,7 +269,7 @@ pub async fn insert_workspace_invitation(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_workspace_invitation_set_invited(
|
||||
pub async fn update_workspace_invitation_set_status_accepted(
|
||||
txn: &mut Transaction<'_, sqlx::Postgres>,
|
||||
invitee_uuid: &Uuid,
|
||||
invite_id: &Uuid,
|
||||
|
|
@ -278,7 +278,7 @@ pub async fn update_workspace_invitation_set_invited(
|
|||
r#"
|
||||
UPDATE public.af_workspace_invitation
|
||||
SET status = 1
|
||||
WHERE invitee = (SELECT uid FROM public.af_user WHERE uuid = $1)
|
||||
WHERE invitee_email = (SELECT email FROM public.af_user WHERE uuid = $1)
|
||||
AND id = $2
|
||||
"#,
|
||||
invitee_uuid,
|
||||
|
|
@ -312,7 +312,7 @@ pub async fn get_invitation_by_id(
|
|||
SELECT
|
||||
workspace_id,
|
||||
inviter AS inviter_uid,
|
||||
invitee AS invitee_uid,
|
||||
(SELECT uid FROM public.af_user WHERE email = invitee_email) AS invitee_uid,
|
||||
status,
|
||||
role_id AS role
|
||||
FROM
|
||||
|
|
@ -346,7 +346,7 @@ pub async fn select_workspace_invitations_for_user(
|
|||
updated_at
|
||||
FROM
|
||||
public.af_workspace_invitation
|
||||
WHERE af_workspace_invitation.invitee = (SELECT uid FROM public.af_user WHERE uuid = $1)
|
||||
WHERE af_workspace_invitation.invitee_email = (SELECT email FROM public.af_user WHERE uuid = $1)
|
||||
AND ($2::SMALLINT IS NULL OR status = $2)
|
||||
"#,
|
||||
invitee_uuid,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use super::grant::Grant;
|
||||
use crate::params::{
|
||||
AdminDeleteUserParams, AdminUserParams, CreateSSOProviderParams, GenerateLinkParams,
|
||||
GenerateLinkResponse, MagicLinkParams,
|
||||
GenerateLinkResponse, InviteUserParams, MagicLinkParams,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use gotrue_entity::dto::{
|
||||
|
|
@ -139,12 +139,14 @@ impl Client {
|
|||
pub async fn admin_list_user(
|
||||
&self,
|
||||
access_token: &str,
|
||||
filter: Option<&str>,
|
||||
) -> Result<AdminListUsersResponse, GoTrueError> {
|
||||
let url = format!("{}/admin/users", self.base_url);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::GET, &url, access_token)
|
||||
.send()
|
||||
.await?;
|
||||
let mut req = self.http_client_with_auth(Method::GET, &url, access_token);
|
||||
if let Some(filter) = filter {
|
||||
req = req.query(&[("filter", filter)]);
|
||||
}
|
||||
let resp = req.send().await?;
|
||||
to_gotrue_result(resp).await
|
||||
}
|
||||
|
||||
|
|
@ -191,6 +193,20 @@ impl Client {
|
|||
to_gotrue_result(resp).await
|
||||
}
|
||||
|
||||
pub async fn admin_invite_user(
|
||||
&self,
|
||||
access_token: &str,
|
||||
admin_user_params: &InviteUserParams,
|
||||
) -> Result<User, GoTrueError> {
|
||||
let url = format!("{}/invite", self.base_url);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::POST, &url, access_token)
|
||||
.json(&admin_user_params)
|
||||
.send()
|
||||
.await?;
|
||||
to_gotrue_result(resp).await
|
||||
}
|
||||
|
||||
pub async fn admin_add_user(
|
||||
&self,
|
||||
access_token: &str,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,12 @@ pub struct AdminDeleteUserParams {
|
|||
pub should_soft_delete: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize)]
|
||||
pub struct InviteUserParams {
|
||||
pub email: String,
|
||||
pub data: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct AdminUserParams {
|
||||
pub aud: String,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ use crate::middleware::access_control_mw::MiddlewareAccessControlTransform;
|
|||
use crate::middleware::metrics_mw::MetricsMiddleware;
|
||||
use crate::middleware::request_id::RequestIdMiddleware;
|
||||
use crate::self_signed::create_self_signed_certificate;
|
||||
use crate::state::{AppMetrics, AppState, UserCache};
|
||||
use crate::state::{AppMetrics, AppState, GoTrueAdmin, UserCache};
|
||||
use actix::Actor;
|
||||
use actix_identity::IdentityMiddleware;
|
||||
use actix_session::storage::RedisSessionStore;
|
||||
|
|
@ -182,7 +182,7 @@ pub async fn init_state(config: &Config, rt_cmd_tx: RTCommandSender) -> Result<A
|
|||
// Gotrue
|
||||
info!("Connecting to GoTrue...");
|
||||
let gotrue_client = get_gotrue_client(&config.gotrue).await?;
|
||||
setup_admin_account(&gotrue_client, &pg_pool, &config.gotrue).await?;
|
||||
let gotrue_admin = setup_admin_account(&gotrue_client, &pg_pool, &config.gotrue).await?;
|
||||
|
||||
// Redis
|
||||
info!("Connecting to Redis...");
|
||||
|
|
@ -244,6 +244,7 @@ pub async fn init_state(config: &Config, rt_cmd_tx: RTCommandSender) -> Result<A
|
|||
pg_listeners,
|
||||
access_control,
|
||||
metrics,
|
||||
gotrue_admin,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -251,9 +252,14 @@ async fn setup_admin_account(
|
|||
gotrue_client: &gotrue::api::Client,
|
||||
pg_pool: &PgPool,
|
||||
gotrue_setting: &GoTrueSetting,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<GoTrueAdmin, Error> {
|
||||
let admin_email = gotrue_setting.admin_email.as_str();
|
||||
let password = gotrue_setting.admin_password.as_str();
|
||||
let gotrue_admin = GoTrueAdmin {
|
||||
admin_email: admin_email.to_owned(),
|
||||
password: admin_email.to_owned().into(),
|
||||
};
|
||||
|
||||
let res_resp = gotrue_client.sign_up(admin_email, password, None).await;
|
||||
match res_resp {
|
||||
Err(err) => {
|
||||
|
|
@ -261,7 +267,7 @@ async fn setup_admin_account(
|
|||
match (err.code, err.msg.as_str()) {
|
||||
(400, "User already registered") => {
|
||||
info!("Admin user already registered");
|
||||
Ok(())
|
||||
Ok(gotrue_admin)
|
||||
},
|
||||
_ => Err(err.into()),
|
||||
}
|
||||
|
|
@ -279,7 +285,7 @@ async fn setup_admin_account(
|
|||
match admin_user.role.as_str() {
|
||||
"supabase_admin" => {
|
||||
info!("Admin user already created and set role to supabase_admin");
|
||||
Ok(())
|
||||
Ok(gotrue_admin)
|
||||
},
|
||||
_ => {
|
||||
let user_id = admin_user.id.parse::<uuid::Uuid>()?;
|
||||
|
|
@ -298,7 +304,7 @@ async fn setup_admin_account(
|
|||
assert_eq!(result.rows_affected(), 1);
|
||||
info!("Admin user created and set role to supabase_admin");
|
||||
|
||||
Ok(())
|
||||
Ok(gotrue_admin)
|
||||
},
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use app_error::AppError;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::{self, AsyncRead, ReadBuf};
|
||||
|
|
@ -42,3 +43,19 @@ where
|
|||
&self.reader
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn check_user_exists(
|
||||
admin_token: &str,
|
||||
gotrue_client: &gotrue::api::Client,
|
||||
email: &str,
|
||||
) -> Result<bool, AppError> {
|
||||
let users = gotrue_client
|
||||
.admin_list_user(admin_token, Some(email))
|
||||
.await?;
|
||||
for user in users.users {
|
||||
if user.email == email {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ use database::workspace::{
|
|||
change_workspace_icon, delete_from_workspace, delete_workspace_members, get_invitation_by_id,
|
||||
insert_user_workspace, insert_workspace_invitation, rename_workspace, select_all_user_workspaces,
|
||||
select_workspace, select_workspace_invitations_for_user, select_workspace_member_list,
|
||||
update_updated_at_of_workspace, update_workspace_invitation_set_invited, upsert_workspace_member,
|
||||
upsert_workspace_member_with_txn,
|
||||
update_updated_at_of_workspace, update_workspace_invitation_set_status_accepted,
|
||||
upsert_workspace_member, upsert_workspace_member_with_txn,
|
||||
};
|
||||
use database_entity::dto::{
|
||||
AFAccessLevel, AFRole, AFWorkspace, AFWorkspaceInvitation, AFWorkspaceInvitationStatus,
|
||||
|
|
@ -144,10 +144,13 @@ pub async fn accept_workspace_invite(
|
|||
invite_id: &Uuid,
|
||||
) -> Result<(), AppError> {
|
||||
let mut txn = pg_pool.begin().await?;
|
||||
update_workspace_invitation_set_invited(&mut txn, user_uuid, invite_id).await?;
|
||||
update_workspace_invitation_set_status_accepted(&mut txn, user_uuid, invite_id).await?;
|
||||
let inv = get_invitation_by_id(&mut txn, invite_id).await?;
|
||||
let invited_uid = inv
|
||||
.invitee_uid
|
||||
.ok_or_else(|| AppError::Internal(anyhow::anyhow!("Invitee uid is missing for {:?}", inv)))?;
|
||||
workspace_access_control
|
||||
.insert_role(&inv.invitee_uid, &inv.workspace_id, inv.role)
|
||||
.insert_role(&invited_uid, &inv.workspace_id, inv.role)
|
||||
.await?;
|
||||
txn.commit().await?;
|
||||
Ok(())
|
||||
|
|
|
|||
28
src/state.rs
28
src/state.rs
|
|
@ -4,6 +4,8 @@ use crate::biz::pg_listener::PgListeners;
|
|||
|
||||
use crate::config::config::Config;
|
||||
use app_error::AppError;
|
||||
use gotrue::grant::{Grant, PasswordGrant};
|
||||
use secrecy::{ExposeSecret, Secret};
|
||||
|
||||
use crate::api::metrics::RequestMetrics;
|
||||
use crate::biz::casbin::access_control::AccessControl;
|
||||
|
|
@ -40,6 +42,7 @@ pub struct AppState {
|
|||
pub pg_listeners: Arc<PgListeners>,
|
||||
pub access_control: AccessControl,
|
||||
pub metrics: AppMetrics,
|
||||
pub gotrue_admin: GoTrueAdmin,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
|
|
@ -132,3 +135,28 @@ impl AppMetrics {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GoTrueAdmin {
|
||||
pub admin_email: String,
|
||||
pub password: Secret<String>,
|
||||
}
|
||||
|
||||
impl GoTrueAdmin {
|
||||
pub fn new(admin_email: String, password: String) -> Self {
|
||||
Self {
|
||||
admin_email,
|
||||
password: password.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn token(&self, client: &gotrue::api::Client) -> Result<String, AppError> {
|
||||
let token = client
|
||||
.token(&Grant::Password(PasswordGrant {
|
||||
email: self.admin_email.clone(),
|
||||
password: self.password.expose_secret().clone(),
|
||||
}))
|
||||
.await?;
|
||||
Ok(token.access_token)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ async fn admin_user_create_list_edit_delete() {
|
|||
|
||||
// list users
|
||||
let users = gotrue_client
|
||||
.admin_list_user(&admin_token.access_token)
|
||||
.admin_list_user(&admin_token.access_token, None)
|
||||
.await
|
||||
.unwrap()
|
||||
.users;
|
||||
|
|
@ -94,7 +94,7 @@ async fn admin_user_create_list_edit_delete() {
|
|||
.unwrap();
|
||||
|
||||
let users = gotrue_client
|
||||
.admin_list_user(&admin_token.access_token)
|
||||
.admin_list_user(&admin_token.access_token, None)
|
||||
.await
|
||||
.unwrap()
|
||||
.users;
|
||||
|
|
|
|||
Loading…
Reference in New Issue