db: check column conversions when mapping rows

Lewis: May this revision serve well! <lu5a@proton.me>
This commit is contained in:
Lewis
2026-07-25 08:27:39 +03:00
committed by Tangled
parent 8abb6cc741
commit 01a71ece7c
4 changed files with 158 additions and 127 deletions
+3 -1
View File
@@ -4,6 +4,8 @@ use tranquil_db_traits::{Backlink, BacklinkRepository, DbError};
use tranquil_types::{AtUri, Nsid};
use uuid::Uuid;
use super::col;
use super::column_vec;
use super::user::map_sqlx_error;
pub struct PostgresBacklinkRepository {
@@ -49,7 +51,7 @@ impl BacklinkRepository for PostgresBacklinkRepository {
.await
.map_err(map_sqlx_error)?;
Ok(results.into_iter().map(Into::into).collect())
column_vec(results, col::BACKLINKS_URI)
}
async fn add_backlinks(&self, repo_id: Uuid, backlinks: &[Backlink]) -> Result<(), DbError> {
+28 -18
View File
@@ -6,7 +6,9 @@ use tranquil_db_traits::{
use tranquil_types::{AtUri, CidLink, Did, Tid};
use uuid::Uuid;
use super::col;
use super::user::map_sqlx_error;
use super::{column, column_vec, opt_column};
pub struct PostgresBlobRepository {
pool: PgPool,
@@ -42,7 +44,7 @@ impl BlobRepository for PostgresBlobRepository {
.await
.map_err(map_sqlx_error)?;
Ok(result.map(CidLink::from))
opt_column(result, col::BLOBS_CID)
}
async fn get_blob_metadata(&self, cid: &CidLink) -> Result<Option<BlobMetadata>, DbError> {
@@ -73,10 +75,14 @@ impl BlobRepository for PostgresBlobRepository {
.await
.map_err(map_sqlx_error)?;
Ok(result.map(|r| BlobWithTakedown {
cid: CidLink::from(r.cid),
takedown_ref: r.takedown_ref,
}))
result
.map(|r| {
Ok(BlobWithTakedown {
cid: column(r.cid, col::BLOBS_CID)?,
takedown_ref: r.takedown_ref,
})
})
.transpose()
}
async fn get_blob_storage_key(&self, cid: &CidLink) -> Result<Option<String>, DbError> {
@@ -109,7 +115,7 @@ impl BlobRepository for PostgresBlobRepository {
.await
.map_err(map_sqlx_error)?;
Ok(results.into_iter().map(CidLink::from).collect())
column_vec(results, col::BLOBS_CID)
}
async fn list_blobs_since_rev(&self, did: &Did, since: &Tid) -> Result<Vec<CidLink>, DbError> {
@@ -124,7 +130,7 @@ impl BlobRepository for PostgresBlobRepository {
.await
.map_err(map_sqlx_error)?;
Ok(results.into_iter().map(CidLink::from).collect())
column_vec(results, col::REPO_SEQ_BLOBS)
}
async fn count_blobs_by_user(&self, user_id: Uuid) -> Result<i64, DbError> {
@@ -244,13 +250,15 @@ impl BlobRepository for PostgresBlobRepository {
.await
.map_err(map_sqlx_error)?;
Ok(results
results
.into_iter()
.map(|r| MissingBlobInfo {
blob_cid: CidLink::from(r.blob_cid),
record_uri: AtUri::from(r.record_uri),
.map(|r| {
Ok(MissingBlobInfo {
blob_cid: column(r.blob_cid, col::RECORD_BLOBS_BLOB_CID)?,
record_uri: column(r.record_uri, col::RECORD_BLOBS_RECORD_URI)?,
})
})
.collect())
.collect()
}
async fn count_distinct_record_blobs(&self, repo_id: Uuid) -> Result<i64, DbError> {
@@ -277,13 +285,15 @@ impl BlobRepository for PostgresBlobRepository {
.await
.map_err(map_sqlx_error)?;
Ok(results
results
.into_iter()
.map(|r| BlobForExport {
cid: CidLink::from(r.cid),
storage_key: r.storage_key,
mime_type: r.mime_type,
.map(|r| {
Ok(BlobForExport {
cid: column(r.cid, col::BLOBS_CID)?,
storage_key: r.storage_key,
mime_type: r.mime_type,
})
})
.collect())
.collect()
}
}
+45 -48
View File
@@ -11,11 +11,12 @@ use tranquil_oauth::{
SessionId as OAuthSessionId, TokenData,
};
use tranquil_types::{
AuthorizationCode, ClientId, DPoPProofId, DeviceId, Did, Handle, RefreshToken, RequestId,
TokenId,
AuthorizationCode, ClientId, DPoPProofId, DeviceId, Did, RefreshToken, RequestId, TokenId,
};
use uuid::Uuid;
use super::col;
use super::column;
use super::user::map_sqlx_error;
const REGISTRATION_FLOW_EXTENDED_EXPIRY_SECS: i64 = 600;
@@ -98,7 +99,7 @@ impl OAuthRepository for PostgresOAuthRepository {
did: r
.did
.parse()
.map_err(|_| DbError::Other("Invalid DID in token".into()))?,
.map_err(|_| DbError::InvalidColumn(col::OAUTH_TOKEN_DID))?,
token_id: TokenId::from(r.token_id),
created_at: r.created_at,
updated_at: r.updated_at,
@@ -115,7 +116,7 @@ impl OAuthRepository for PostgresOAuthRepository {
.controller_did
.map(|s| s.parse())
.transpose()
.map_err(|_| DbError::Other("Invalid controller DID".into()))?,
.map_err(|_| DbError::InvalidColumn(col::OAUTH_TOKEN_CONTROLLER_DID))?,
})),
None => Ok(None),
}
@@ -144,7 +145,7 @@ impl OAuthRepository for PostgresOAuthRepository {
did: r
.did
.parse()
.map_err(|_| DbError::Other("Invalid DID in token".into()))?,
.map_err(|_| DbError::InvalidColumn(col::OAUTH_TOKEN_DID))?,
token_id: TokenId::from(r.token_id),
created_at: r.created_at,
updated_at: r.updated_at,
@@ -161,7 +162,7 @@ impl OAuthRepository for PostgresOAuthRepository {
.controller_did
.map(|s| s.parse())
.transpose()
.map_err(|_| DbError::Other("Invalid controller DID".into()))?,
.map_err(|_| DbError::InvalidColumn(col::OAUTH_TOKEN_CONTROLLER_DID))?,
},
))),
None => Ok(None),
@@ -193,7 +194,7 @@ impl OAuthRepository for PostgresOAuthRepository {
did: r
.did
.parse()
.map_err(|_| DbError::Other("Invalid DID in token".into()))?,
.map_err(|_| DbError::InvalidColumn(col::OAUTH_TOKEN_DID))?,
token_id: TokenId::from(r.token_id),
created_at: r.created_at,
updated_at: r.updated_at,
@@ -210,7 +211,7 @@ impl OAuthRepository for PostgresOAuthRepository {
.controller_did
.map(|s| s.parse())
.transpose()
.map_err(|_| DbError::Other("Invalid controller DID".into()))?,
.map_err(|_| DbError::InvalidColumn(col::OAUTH_TOKEN_CONTROLLER_DID))?,
},
))),
None => Ok(None),
@@ -326,7 +327,7 @@ impl OAuthRepository for PostgresOAuthRepository {
did: r
.did
.parse()
.map_err(|_| DbError::Other("Invalid DID in token".into()))?,
.map_err(|_| DbError::InvalidColumn(col::OAUTH_TOKEN_DID))?,
token_id: TokenId::from(r.token_id),
created_at: r.created_at,
updated_at: r.updated_at,
@@ -343,7 +344,7 @@ impl OAuthRepository for PostgresOAuthRepository {
.controller_did
.map(|s| s.parse())
.transpose()
.map_err(|_| DbError::Other("Invalid controller DID".into()))?,
.map_err(|_| DbError::InvalidColumn(col::OAUTH_TOKEN_CONTROLLER_DID))?,
})
})
.collect()
@@ -476,18 +477,14 @@ impl OAuthRepository for PostgresOAuthRepository {
client_auth,
parameters,
expires_at: r.expires_at,
did: r
.did
.map(|s| s.parse())
.transpose()
.map_err(|_| DbError::Other("Invalid DID in DB".into()))?,
did: r.did.map(|s| s.parse()).transpose().map_err(|_| {
DbError::InvalidColumn(col::OAUTH_AUTHORIZATION_REQUEST_DID)
})?,
device_id: r.device_id.map(DeviceId::from),
code: r.code.map(AuthorizationCode::from),
controller_did: r
.controller_did
.map(|s| s.parse())
.transpose()
.map_err(|_| DbError::Other("Invalid controller DID in DB".into()))?,
controller_did: r.controller_did.map(|s| s.parse()).transpose().map_err(
|_| DbError::InvalidColumn(col::OAUTH_AUTHORIZATION_REQUEST_CONTROLLER_DID),
)?,
}))
}
None => Ok(None),
@@ -570,18 +567,14 @@ impl OAuthRepository for PostgresOAuthRepository {
client_auth,
parameters,
expires_at: r.expires_at,
did: r
.did
.map(|s| s.parse())
.transpose()
.map_err(|_| DbError::Other("Invalid DID in DB".into()))?,
did: r.did.map(|s| s.parse()).transpose().map_err(|_| {
DbError::InvalidColumn(col::OAUTH_AUTHORIZATION_REQUEST_DID)
})?,
device_id: r.device_id.map(DeviceId::from),
code: r.code.map(AuthorizationCode::from),
controller_did: r
.controller_did
.map(|s| s.parse())
.transpose()
.map_err(|_| DbError::Other("Invalid controller DID in DB".into()))?,
controller_did: r.controller_did.map(|s| s.parse()).transpose().map_err(
|_| DbError::InvalidColumn(col::OAUTH_AUTHORIZATION_REQUEST_CONTROLLER_DID),
)?,
}))
}
None => Ok(None),
@@ -813,15 +806,16 @@ impl OAuthRepository for PostgresOAuthRepository {
.fetch_all(&self.pool)
.await
.map_err(map_sqlx_error)?;
Ok(rows
.into_iter()
.map(|r| DeviceAccountRow {
did: Did::from(r.did),
handle: Handle::from(r.handle),
email: r.email,
last_used_at: r.last_used_at,
rows.into_iter()
.map(|r| {
Ok(DeviceAccountRow {
did: column(r.did, col::USERS_DID)?,
handle: column(r.handle, col::USERS_HANDLE)?,
email: r.email,
last_used_at: r.last_used_at,
})
})
.collect())
.collect()
}
async fn verify_account_on_device(
@@ -904,7 +898,7 @@ impl OAuthRepository for PostgresOAuthRepository {
.map_err(map_sqlx_error)?;
Ok(TwoFactorChallenge {
id: row.id,
did: Did::from(row.did),
did: column(row.did, col::OAUTH_2FA_CHALLENGE_DID)?,
request_uri: RequestId::from(row.request_uri),
code: row.code,
attempts: row.attempts,
@@ -928,15 +922,18 @@ impl OAuthRepository for PostgresOAuthRepository {
.fetch_optional(&self.pool)
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| TwoFactorChallenge {
id: r.id,
did: Did::from(r.did),
request_uri: RequestId::from(r.request_uri),
code: r.code,
attempts: r.attempts,
created_at: r.created_at,
expires_at: r.expires_at,
}))
row.map(|r| {
Ok(TwoFactorChallenge {
id: r.id,
did: column(r.did, col::OAUTH_2FA_CHALLENGE_DID)?,
request_uri: RequestId::from(r.request_uri),
code: r.code,
attempts: r.attempts,
created_at: r.created_at,
expires_at: r.expires_at,
})
})
.transpose()
}
async fn increment_2fa_attempts(&self, id: Uuid) -> Result<i32, DbError> {
+82 -60
View File
@@ -10,7 +10,9 @@ use tranquil_db_traits::{
use tranquil_types::{Did, Jti, PasswordHash};
use uuid::Uuid;
use super::col;
use super::user::map_sqlx_error;
use super::{column, opt_column};
pub struct PostgresSessionRepository {
pool: PgPool,
@@ -69,21 +71,24 @@ impl SessionRepository for PostgresSessionRepository {
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| SessionToken {
id: SessionId::new(r.id),
did: Did::from(r.did),
access_jti: Jti::from(r.access_jti),
refresh_jti: Jti::from(r.refresh_jti),
access_expires_at: r.access_expires_at,
refresh_expires_at: r.refresh_expires_at,
login_type: LoginType::from_legacy_flag(r.legacy_login),
mfa_verified: r.mfa_verified,
scope: r.scope,
controller_did: r.controller_did.map(Did::from),
app_password_name: r.app_password_name,
created_at: r.created_at,
updated_at: r.updated_at,
}))
row.map(|r| {
Ok(SessionToken {
id: SessionId::new(r.id),
did: column(r.did, col::SESSION_TOKENS_DID)?,
access_jti: Jti::from(r.access_jti),
refresh_jti: Jti::from(r.refresh_jti),
access_expires_at: r.access_expires_at,
refresh_expires_at: r.refresh_expires_at,
login_type: LoginType::from_legacy_flag(r.legacy_login),
mfa_verified: r.mfa_verified,
scope: r.scope,
controller_did: opt_column(r.controller_did, col::SESSION_TOKENS_CONTROLLER_DID)?,
app_password_name: r.app_password_name,
created_at: r.created_at,
updated_at: r.updated_at,
})
})
.transpose()
}
async fn get_session_for_refresh(
@@ -104,14 +109,17 @@ impl SessionRepository for PostgresSessionRepository {
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| SessionForRefresh {
id: SessionId::new(r.id),
did: Did::from(r.did),
scope: r.scope,
controller_did: r.controller_did.map(Did::from),
key_bytes: r.key_bytes,
encryption_version: r.encryption_version.unwrap_or(0),
}))
row.map(|r| {
Ok(SessionForRefresh {
id: SessionId::new(r.id),
did: column(r.did, col::SESSION_TOKENS_DID)?,
scope: r.scope,
controller_did: opt_column(r.controller_did, col::SESSION_TOKENS_CONTROLLER_DID)?,
key_bytes: r.key_bytes,
encryption_version: r.encryption_version.unwrap_or(0),
})
})
.transpose()
}
async fn delete_session_by_access_jti(
@@ -274,9 +282,9 @@ impl SessionRepository for PostgresSessionRepository {
let grace_cutoff = Utc::now() - Duration::seconds(REFRESH_GRACE_PERIOD_SECS);
if r.used_at > grace_cutoff {
Ok(RefreshGraceLookup::Replay(RefreshGraceReplay {
did: Did::from(r.did),
did: column(r.did, col::SESSION_TOKENS_DID)?,
scope: r.scope,
controller_did: r.controller_did.map(Did::from),
controller_did: opt_column(r.controller_did, col::SESSION_TOKENS_CONTROLLER_DID)?,
access_jti: Jti::from(r.access_jti),
refresh_jti: Jti::from(r.refresh_jti),
access_expires_at: r.access_expires_at,
@@ -286,7 +294,7 @@ impl SessionRepository for PostgresSessionRepository {
}))
} else {
Ok(RefreshGraceLookup::Compromised {
did: Did::from(r.did),
did: column(r.did, col::SESSION_TOKENS_DID)?,
session_id: SessionId::new(r.session_id),
key_bytes: r.key_bytes,
encryption_version: r.encryption_version.unwrap_or(0),
@@ -308,19 +316,23 @@ impl SessionRepository for PostgresSessionRepository {
.await
.map_err(map_sqlx_error)?;
Ok(rows
.into_iter()
.map(|r| AppPasswordRecord {
id: r.id,
user_id: r.user_id,
name: r.name,
password_hash: PasswordHash::new(r.password_hash),
created_at: r.created_at,
privilege: AppPasswordPrivilege::from_privileged_flag(r.privileged),
scopes: r.scopes,
created_by_controller_did: r.created_by_controller_did.map(Did::from),
rows.into_iter()
.map(|r| {
Ok(AppPasswordRecord {
id: r.id,
user_id: r.user_id,
name: r.name,
password_hash: PasswordHash::new(r.password_hash),
created_at: r.created_at,
privilege: AppPasswordPrivilege::from_privileged_flag(r.privileged),
scopes: r.scopes,
created_by_controller_did: opt_column(
r.created_by_controller_did,
col::APP_PASSWORDS_CREATED_BY_CONTROLLER_DID,
)?,
})
})
.collect())
.collect()
}
async fn get_app_passwords_for_login(
@@ -341,19 +353,23 @@ impl SessionRepository for PostgresSessionRepository {
.await
.map_err(map_sqlx_error)?;
Ok(rows
.into_iter()
.map(|r| AppPasswordRecord {
id: r.id,
user_id: r.user_id,
name: r.name,
password_hash: PasswordHash::new(r.password_hash),
created_at: r.created_at,
privilege: AppPasswordPrivilege::from_privileged_flag(r.privileged),
scopes: r.scopes,
created_by_controller_did: r.created_by_controller_did.map(Did::from),
rows.into_iter()
.map(|r| {
Ok(AppPasswordRecord {
id: r.id,
user_id: r.user_id,
name: r.name,
password_hash: PasswordHash::new(r.password_hash),
created_at: r.created_at,
privilege: AppPasswordPrivilege::from_privileged_flag(r.privileged),
scopes: r.scopes,
created_by_controller_did: opt_column(
r.created_by_controller_did,
col::APP_PASSWORDS_CREATED_BY_CONTROLLER_DID,
)?,
})
})
.collect())
.collect()
}
async fn get_app_password_by_name(
@@ -374,16 +390,22 @@ impl SessionRepository for PostgresSessionRepository {
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| AppPasswordRecord {
id: r.id,
user_id: r.user_id,
name: r.name,
password_hash: PasswordHash::new(r.password_hash),
created_at: r.created_at,
privilege: AppPasswordPrivilege::from_privileged_flag(r.privileged),
scopes: r.scopes,
created_by_controller_did: r.created_by_controller_did.map(Did::from),
}))
row.map(|r| {
Ok(AppPasswordRecord {
id: r.id,
user_id: r.user_id,
name: r.name,
password_hash: PasswordHash::new(r.password_hash),
created_at: r.created_at,
privilege: AppPasswordPrivilege::from_privileged_flag(r.privileged),
scopes: r.scopes,
created_by_controller_did: opt_column(
r.created_by_controller_did,
col::APP_PASSWORDS_CREATED_BY_CONTROLLER_DID,
)?,
})
})
.transpose()
}
async fn create_app_password(&self, data: &AppPasswordCreate) -> Result<Uuid, DbError> {