registry: add sqlx compile-time checks for auth and secrets
This commit is contained in:
@@ -7,7 +7,8 @@ impl PostgresRegistry {
|
||||
display_name: &str,
|
||||
password_hash: &str,
|
||||
) -> Result<UserId, RegistryError> {
|
||||
let row = sqlx::query(
|
||||
let user_id = format!("user_{}", uuid::Uuid::now_v7().simple());
|
||||
let row = sqlx::query!(
|
||||
"insert into users (
|
||||
id,
|
||||
email,
|
||||
@@ -23,15 +24,15 @@ impl PostgresRegistry {
|
||||
password_hash = excluded.password_hash,
|
||||
status = 'active'
|
||||
returning id",
|
||||
user_id,
|
||||
email,
|
||||
display_name,
|
||||
password_hash,
|
||||
)
|
||||
.bind(format!("user_{}", uuid::Uuid::now_v7().simple()))
|
||||
.bind(email)
|
||||
.bind(display_name)
|
||||
.bind(password_hash)
|
||||
.fetch_one(&self.pool)
|
||||
.await?;
|
||||
|
||||
Ok(UserId::new(row.try_get::<String, _>("id")?))
|
||||
Ok(UserId::new(row.id))
|
||||
}
|
||||
|
||||
pub async fn ensure_membership(
|
||||
@@ -65,46 +66,70 @@ impl PostgresRegistry {
|
||||
&self,
|
||||
email: &str,
|
||||
) -> Result<Option<AuthUserRecord>, RegistryError> {
|
||||
let row = sqlx::query(
|
||||
let row = sqlx::query!(
|
||||
"select
|
||||
id,
|
||||
email,
|
||||
display_name,
|
||||
password_hash,
|
||||
password_hash as \"password_hash!\",
|
||||
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!\"
|
||||
from users
|
||||
where email = $1
|
||||
limit 1",
|
||||
email,
|
||||
)
|
||||
.bind(email)
|
||||
.fetch_optional(&self.pool)
|
||||
.await?;
|
||||
|
||||
row.as_ref().map(map_auth_user_record).transpose()
|
||||
row.map(|row| {
|
||||
Ok(AuthUserRecord {
|
||||
user: User {
|
||||
id: UserId::new(row.id),
|
||||
email: row.email,
|
||||
display_name: row.display_name,
|
||||
status: deserialize_enum_text(&row.status, "status")?,
|
||||
created_at: row.created_at,
|
||||
},
|
||||
password_hash: row.password_hash,
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub async fn get_auth_user_by_id(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> Result<Option<AuthUserRecord>, RegistryError> {
|
||||
let row = sqlx::query(
|
||||
let row = sqlx::query!(
|
||||
"select
|
||||
id,
|
||||
email,
|
||||
display_name,
|
||||
password_hash,
|
||||
password_hash as \"password_hash!\",
|
||||
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!\"
|
||||
from users
|
||||
where id = $1
|
||||
limit 1",
|
||||
user_id.as_str(),
|
||||
)
|
||||
.bind(user_id.as_str())
|
||||
.fetch_optional(&self.pool)
|
||||
.await?;
|
||||
|
||||
row.as_ref().map(map_auth_user_record).transpose()
|
||||
row.map(|row| {
|
||||
Ok(AuthUserRecord {
|
||||
user: User {
|
||||
id: UserId::new(row.id),
|
||||
email: row.email,
|
||||
display_name: row.display_name,
|
||||
status: deserialize_enum_text(&row.status, "status")?,
|
||||
created_at: row.created_at,
|
||||
},
|
||||
password_hash: row.password_hash,
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub async fn update_user_profile(
|
||||
@@ -204,7 +229,7 @@ impl PostgresRegistry {
|
||||
session_id: &UserSessionId,
|
||||
secret_hash: &str,
|
||||
) -> Result<Option<SessionRecord>, RegistryError> {
|
||||
let row = sqlx::query(
|
||||
let row = sqlx::query!(
|
||||
"select
|
||||
s.id,
|
||||
s.user_id,
|
||||
@@ -212,7 +237,7 @@ impl PostgresRegistry {
|
||||
u.email,
|
||||
u.display_name,
|
||||
u.status,
|
||||
to_char(u.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at
|
||||
to_char(u.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\"
|
||||
from user_sessions s
|
||||
join users u on u.id = s.user_id
|
||||
where s.id = $1
|
||||
@@ -220,9 +245,9 @@ impl PostgresRegistry {
|
||||
and s.status = 'active'
|
||||
and s.expires_at > now()
|
||||
limit 1",
|
||||
session_id.as_str(),
|
||||
secret_hash,
|
||||
)
|
||||
.bind(session_id.as_str())
|
||||
.bind(secret_hash)
|
||||
.fetch_optional(&self.pool)
|
||||
.await?;
|
||||
|
||||
@@ -230,18 +255,16 @@ impl PostgresRegistry {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let user_id = UserId::new(row.try_get::<String, _>("user_id")?);
|
||||
let user_id = UserId::new(row.user_id);
|
||||
let user = User {
|
||||
id: user_id.clone(),
|
||||
email: row.try_get("email")?,
|
||||
display_name: row.try_get("display_name")?,
|
||||
status: deserialize_enum_text(&row.try_get::<String, _>("status")?, "status")?,
|
||||
created_at: row.try_get("created_at")?,
|
||||
email: row.email,
|
||||
display_name: row.display_name,
|
||||
status: deserialize_enum_text(&row.status, "status")?,
|
||||
created_at: row.created_at,
|
||||
};
|
||||
let memberships = self.list_workspaces_for_user(&user_id).await?;
|
||||
let stored_workspace_id = row
|
||||
.try_get::<Option<String>, _>("current_workspace_id")?
|
||||
.map(WorkspaceId::new);
|
||||
let stored_workspace_id = row.current_workspace_id.map(WorkspaceId::new);
|
||||
let current_workspace_id = stored_workspace_id
|
||||
.filter(|workspace_id| {
|
||||
memberships
|
||||
@@ -255,7 +278,7 @@ impl PostgresRegistry {
|
||||
});
|
||||
|
||||
Ok(Some(SessionRecord {
|
||||
session_id: UserSessionId::new(row.try_get::<String, _>("id")?),
|
||||
session_id: UserSessionId::new(row.id),
|
||||
user,
|
||||
memberships,
|
||||
current_workspace_id,
|
||||
|
||||
@@ -530,13 +530,6 @@ fn map_membership_record(row: &PgRow) -> Result<MembershipRecord, RegistryError>
|
||||
})
|
||||
}
|
||||
|
||||
fn map_auth_user_record(row: &PgRow) -> Result<AuthUserRecord, RegistryError> {
|
||||
Ok(AuthUserRecord {
|
||||
user: map_user(row)?,
|
||||
password_hash: row.try_get("password_hash")?,
|
||||
})
|
||||
}
|
||||
|
||||
fn map_user(row: &PgRow) -> Result<User, RegistryError> {
|
||||
Ok(User {
|
||||
id: UserId::new(row.try_get::<String, _>("id")?),
|
||||
@@ -593,40 +586,6 @@ fn map_platform_api_key_record(row: &PgRow) -> Result<PlatformApiKeyRecord, Regi
|
||||
})
|
||||
}
|
||||
|
||||
fn map_secret_record(row: &PgRow) -> Result<SecretRecord, RegistryError> {
|
||||
Ok(SecretRecord {
|
||||
secret: Secret {
|
||||
id: SecretId::new(row.try_get::<String, _>("id")?),
|
||||
workspace_id: WorkspaceId::new(row.try_get::<String, _>("workspace_id")?),
|
||||
name: row.try_get("name")?,
|
||||
kind: deserialize_enum_text(&row.try_get::<String, _>("kind")?, "kind")?,
|
||||
status: deserialize_enum_text(&row.try_get::<String, _>("status")?, "status")?,
|
||||
current_version: from_db_version(
|
||||
row.try_get::<i32, _>("current_version")?,
|
||||
"current_version",
|
||||
)?,
|
||||
created_at: row.try_get("created_at")?,
|
||||
updated_at: row.try_get("updated_at")?,
|
||||
last_used_at: row.try_get("last_used_at")?,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn map_secret_version_record(row: &PgRow) -> Result<SecretVersionRecord, RegistryError> {
|
||||
Ok(SecretVersionRecord {
|
||||
secret_version: SecretVersion {
|
||||
secret_id: SecretId::new(row.try_get::<String, _>("secret_id")?),
|
||||
version: from_db_version(row.try_get::<i32, _>("version")?, "version")?,
|
||||
ciphertext: row.try_get("ciphertext")?,
|
||||
key_version: row.try_get("key_version")?,
|
||||
created_at: row.try_get("created_at")?,
|
||||
created_by: row
|
||||
.try_get::<Option<String>, _>("created_by")?
|
||||
.map(UserId::new),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn map_stream_session(row: &PgRow) -> Result<StreamSession, RegistryError> {
|
||||
Ok(StreamSession {
|
||||
id: StreamSessionId::new(row.try_get::<String, _>("id")?),
|
||||
|
||||
@@ -5,7 +5,7 @@ impl PostgresRegistry {
|
||||
&self,
|
||||
workspace_id: &WorkspaceId,
|
||||
) -> Result<Vec<SecretRecord>, RegistryError> {
|
||||
let rows = sqlx::query(
|
||||
let rows = sqlx::query!(
|
||||
"select
|
||||
id,
|
||||
workspace_id,
|
||||
@@ -13,18 +13,34 @@ impl PostgresRegistry {
|
||||
kind,
|
||||
status,
|
||||
current_version,
|
||||
to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at,
|
||||
to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as updated_at,
|
||||
to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",
|
||||
to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\",
|
||||
to_char(last_used_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as last_used_at
|
||||
from secrets
|
||||
where workspace_id = $1
|
||||
order by name asc",
|
||||
workspace_id.as_str(),
|
||||
)
|
||||
.bind(workspace_id.as_str())
|
||||
.fetch_all(&self.pool)
|
||||
.await?;
|
||||
|
||||
rows.iter().map(map_secret_record).collect()
|
||||
rows.into_iter()
|
||||
.map(|row| {
|
||||
Ok(SecretRecord {
|
||||
secret: Secret {
|
||||
id: SecretId::new(row.id),
|
||||
workspace_id: WorkspaceId::new(row.workspace_id),
|
||||
name: row.name,
|
||||
kind: deserialize_enum_text(&row.kind, "kind")?,
|
||||
status: deserialize_enum_text(&row.status, "status")?,
|
||||
current_version: from_db_version(row.current_version, "current_version")?,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at,
|
||||
last_used_at: row.last_used_at,
|
||||
},
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub async fn get_secret(
|
||||
@@ -32,7 +48,7 @@ impl PostgresRegistry {
|
||||
workspace_id: &WorkspaceId,
|
||||
secret_id: &SecretId,
|
||||
) -> Result<Option<SecretRecord>, RegistryError> {
|
||||
let row = sqlx::query(
|
||||
let row = sqlx::query!(
|
||||
"select
|
||||
id,
|
||||
workspace_id,
|
||||
@@ -40,18 +56,33 @@ impl PostgresRegistry {
|
||||
kind,
|
||||
status,
|
||||
current_version,
|
||||
to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at,
|
||||
to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as updated_at,
|
||||
to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",
|
||||
to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\",
|
||||
to_char(last_used_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as last_used_at
|
||||
from secrets
|
||||
where workspace_id = $1 and id = $2",
|
||||
workspace_id.as_str(),
|
||||
secret_id.as_str(),
|
||||
)
|
||||
.bind(workspace_id.as_str())
|
||||
.bind(secret_id.as_str())
|
||||
.fetch_optional(&self.pool)
|
||||
.await?;
|
||||
|
||||
row.as_ref().map(map_secret_record).transpose()
|
||||
row.map(|row| {
|
||||
Ok(SecretRecord {
|
||||
secret: Secret {
|
||||
id: SecretId::new(row.id),
|
||||
workspace_id: WorkspaceId::new(row.workspace_id),
|
||||
name: row.name,
|
||||
kind: deserialize_enum_text(&row.kind, "kind")?,
|
||||
status: deserialize_enum_text(&row.status, "status")?,
|
||||
current_version: from_db_version(row.current_version, "current_version")?,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at,
|
||||
last_used_at: row.last_used_at,
|
||||
},
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub async fn get_current_secret_version(
|
||||
@@ -59,25 +90,37 @@ impl PostgresRegistry {
|
||||
workspace_id: &WorkspaceId,
|
||||
secret_id: &SecretId,
|
||||
) -> Result<Option<SecretVersionRecord>, RegistryError> {
|
||||
let row = sqlx::query(
|
||||
let row = sqlx::query!(
|
||||
"select
|
||||
sv.secret_id,
|
||||
sv.version,
|
||||
sv.ciphertext,
|
||||
sv.key_version,
|
||||
to_char(sv.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at,
|
||||
to_char(sv.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",
|
||||
sv.created_by
|
||||
from secrets s
|
||||
join secret_versions sv
|
||||
on sv.secret_id = s.id and sv.version = s.current_version
|
||||
where s.workspace_id = $1 and s.id = $2",
|
||||
workspace_id.as_str(),
|
||||
secret_id.as_str(),
|
||||
)
|
||||
.bind(workspace_id.as_str())
|
||||
.bind(secret_id.as_str())
|
||||
.fetch_optional(&self.pool)
|
||||
.await?;
|
||||
|
||||
row.as_ref().map(map_secret_version_record).transpose()
|
||||
row.map(|row| {
|
||||
Ok(SecretVersionRecord {
|
||||
secret_version: SecretVersion {
|
||||
secret_id: SecretId::new(row.secret_id),
|
||||
version: from_db_version(row.version, "version")?,
|
||||
ciphertext: row.ciphertext,
|
||||
key_version: row.key_version,
|
||||
created_at: row.created_at,
|
||||
created_by: row.created_by.map(UserId::new),
|
||||
},
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub async fn create_secret(
|
||||
|
||||
Reference in New Issue
Block a user