registry: add sqlx checks for user profile reads
This commit is contained in:
+48
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "update users\n set email = $2,\n display_name = $3\n where id = $1\n returning\n id,\n email,\n display_name,\n status,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\"",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "email",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 2,
|
||||||
|
"name": "display_name",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 3,
|
||||||
|
"name": "status",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 4,
|
||||||
|
"name": "created_at!",
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Text",
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
null
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "0cd29169ff0987c8e32a6b2fb6cec695ab1d68b38da154567aab5f897fe3bd24"
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "select exists(\n select 1\n from memberships\n where user_id = $1\n and workspace_id = $2\n ) as \"allowed!\"",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "allowed!",
|
||||||
|
"type_info": "Bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
null
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "2b819bad2a2712dab1a182b89ec52d762d66cffdc449ee6f73c74c1d23a17314"
|
||||||
|
}
|
||||||
@@ -138,7 +138,7 @@ impl PostgresRegistry {
|
|||||||
email: &str,
|
email: &str,
|
||||||
display_name: &str,
|
display_name: &str,
|
||||||
) -> Result<User, RegistryError> {
|
) -> Result<User, RegistryError> {
|
||||||
let row = sqlx::query(
|
let row = sqlx::query!(
|
||||||
"update users
|
"update users
|
||||||
set email = $2,
|
set email = $2,
|
||||||
display_name = $3
|
display_name = $3
|
||||||
@@ -148,16 +148,22 @@ impl PostgresRegistry {
|
|||||||
email,
|
email,
|
||||||
display_name,
|
display_name,
|
||||||
status,
|
status,
|
||||||
to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at",
|
to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\"",
|
||||||
|
user_id.as_str(),
|
||||||
|
email,
|
||||||
|
display_name,
|
||||||
)
|
)
|
||||||
.bind(user_id.as_str())
|
|
||||||
.bind(email)
|
|
||||||
.bind(display_name)
|
|
||||||
.fetch_one(&self.pool)
|
.fetch_one(&self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|error| map_user_update_error(error, user_id, email))?;
|
.map_err(|error| map_user_update_error(error, user_id, email))?;
|
||||||
|
|
||||||
map_user(&row)
|
build_user(
|
||||||
|
row.id,
|
||||||
|
row.email,
|
||||||
|
row.display_name,
|
||||||
|
row.status,
|
||||||
|
row.created_at,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_user_password(
|
pub async fn update_user_password(
|
||||||
@@ -340,20 +346,20 @@ impl PostgresRegistry {
|
|||||||
user_id: &UserId,
|
user_id: &UserId,
|
||||||
workspace_id: &WorkspaceId,
|
workspace_id: &WorkspaceId,
|
||||||
) -> Result<bool, RegistryError> {
|
) -> Result<bool, RegistryError> {
|
||||||
let row = sqlx::query(
|
let row = sqlx::query!(
|
||||||
"select exists(
|
"select exists(
|
||||||
select 1
|
select 1
|
||||||
from memberships
|
from memberships
|
||||||
where user_id = $1
|
where user_id = $1
|
||||||
and workspace_id = $2
|
and workspace_id = $2
|
||||||
) as allowed",
|
) as \"allowed!\"",
|
||||||
|
user_id.as_str(),
|
||||||
|
workspace_id.as_str(),
|
||||||
)
|
)
|
||||||
.bind(user_id.as_str())
|
|
||||||
.bind(workspace_id.as_str())
|
|
||||||
.fetch_one(&self.pool)
|
.fetch_one(&self.pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(row.try_get("allowed")?)
|
Ok(row.allowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn save_auth_profile(
|
pub async fn save_auth_profile(
|
||||||
|
|||||||
@@ -484,13 +484,19 @@ fn assert_immutable_fields(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_user(row: &PgRow) -> Result<User, RegistryError> {
|
fn build_user(
|
||||||
|
id: String,
|
||||||
|
email: String,
|
||||||
|
display_name: String,
|
||||||
|
status: String,
|
||||||
|
created_at: String,
|
||||||
|
) -> Result<User, RegistryError> {
|
||||||
Ok(User {
|
Ok(User {
|
||||||
id: UserId::new(row.try_get::<String, _>("id")?),
|
id: UserId::new(id),
|
||||||
email: row.try_get("email")?,
|
email,
|
||||||
display_name: row.try_get("display_name")?,
|
display_name,
|
||||||
status: deserialize_enum_text(&row.try_get::<String, _>("status")?, "status")?,
|
status: deserialize_enum_text(&status, "status")?,
|
||||||
created_at: row.try_get("created_at")?,
|
created_at,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1503,6 +1509,72 @@ mod tests {
|
|||||||
database.cleanup().await;
|
database.cleanup().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn manages_user_profile_and_workspace_access_reads() {
|
||||||
|
let database = TestDatabase::new().await;
|
||||||
|
let registry = database.registry().await;
|
||||||
|
let workspace = Workspace {
|
||||||
|
id: WorkspaceId::new("ws_profile_01"),
|
||||||
|
slug: "profile".to_owned(),
|
||||||
|
display_name: "Profile Workspace".to_owned(),
|
||||||
|
status: crank_core::WorkspaceStatus::Active,
|
||||||
|
settings: json!({}),
|
||||||
|
created_at: "2026-03-25T12:00:00Z".to_owned(),
|
||||||
|
updated_at: "2026-03-25T12:00:00Z".to_owned(),
|
||||||
|
};
|
||||||
|
let other_workspace = Workspace {
|
||||||
|
id: WorkspaceId::new("ws_profile_02"),
|
||||||
|
slug: "profile-other".to_owned(),
|
||||||
|
display_name: "Other Workspace".to_owned(),
|
||||||
|
status: crank_core::WorkspaceStatus::Active,
|
||||||
|
settings: json!({}),
|
||||||
|
created_at: "2026-03-25T12:01:00Z".to_owned(),
|
||||||
|
updated_at: "2026-03-25T12:01:00Z".to_owned(),
|
||||||
|
};
|
||||||
|
let user_id = registry
|
||||||
|
.upsert_bootstrap_user("profile@example.com", "Owner", "hashed-password")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
registry
|
||||||
|
.create_workspace(CreateWorkspaceRequest {
|
||||||
|
workspace: &workspace,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
registry
|
||||||
|
.create_workspace(CreateWorkspaceRequest {
|
||||||
|
workspace: &other_workspace,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
registry
|
||||||
|
.ensure_membership(&workspace.id, &user_id, MembershipRole::Owner)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let updated = registry
|
||||||
|
.update_user_profile(&user_id, "updated@example.com", "Updated Owner")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let has_access = registry
|
||||||
|
.user_has_workspace_access(&user_id, &workspace.id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let lacks_access = registry
|
||||||
|
.user_has_workspace_access(&user_id, &other_workspace.id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(updated.id, user_id);
|
||||||
|
assert_eq!(updated.email, "updated@example.com");
|
||||||
|
assert_eq!(updated.display_name, "Updated Owner");
|
||||||
|
assert!(has_access);
|
||||||
|
assert!(!lacks_access);
|
||||||
|
|
||||||
|
database.cleanup().await;
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn manages_platform_api_key_read_paths() {
|
async fn manages_platform_api_key_read_paths() {
|
||||||
let database = TestDatabase::new().await;
|
let database = TestDatabase::new().await;
|
||||||
|
|||||||
Reference in New Issue
Block a user