db: make stored handle optional when it no longer parses

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 01a71ece7c
commit 932b0c07d4
13 changed files with 369 additions and 282 deletions
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id, did, email, email_verified, handle\n FROM users\n WHERE LOWER(email) = $1",
"query": "SELECT id, did, email, email_verified\n FROM users\n WHERE LOWER(email) = $1",
"describe": {
"columns": [
{
@@ -22,11 +22,6 @@
"ordinal": 3,
"name": "email_verified",
"type_info": "Bool"
},
{
"ordinal": 4,
"name": "handle",
"type_info": "Text"
}
],
"parameters": {
@@ -38,9 +33,8 @@
false,
false,
true,
false,
false
]
},
"hash": "b230a27fce54d4f79de4ffcc754bf6c7e5a889623e64cdd7aaec791b3553ed83"
"hash": "55b5e5cb13e09c4261e0452ef7e21240b504096666692c8fbd5c7dcd58607bc7"
}
+4 -3
View File
@@ -65,7 +65,7 @@ async fn try_reactivate_migration(
.await
{
Ok(reactivated) => {
info!(did = %did, old_handle = %reactivated.old_handle, new_handle = %handle, "Preparing existing account for inbound migration");
info!(did = %did, old_handle = ?reactivated.old_handle, new_handle = %handle, "Preparing existing account for inbound migration");
let secret_key_bytes = match state
.repos
.user
@@ -202,7 +202,8 @@ pub async fn create_account(
let token = extracted.token;
if is_service_token(&token) {
let verifier = ServiceTokenVerifier::new();
let create_account_lxm = Nsid::from("com.atproto.server.createAccount".to_string());
let create_account_lxm = Nsid::new("com.atproto.server.createAccount")
.expect("com.atproto.server.createAccount is a valid NSID");
match verifier
.verify_service_token(&token, Some(&create_account_lxm))
.await
@@ -448,7 +449,7 @@ pub async fn create_account(
Ok(r) => r,
Err(e) => return e.into_response(),
};
let commit_cid = CidLink::from(repo.commit_cid.to_string());
let commit_cid = CidLink::from(&repo.commit_cid);
let repo_rev = repo.repo_rev.clone();
let birthdate_pref = if tranquil_config::get().server.age_assurance_override {
@@ -147,12 +147,14 @@ pub async fn request_channel_verification(
match channel {
CommsChannel::Email => {
let hostname = &tranquil_config::get().server.hostname;
let fallback_handle = Handle::from("user".to_string());
let handle = handle.ok_or_else(|| {
ApiError::InternalError(Some("Email verification requires a handle".into()))
})?;
tranquil_pds::comms::comms_repo::enqueue_email_update(
state.repos.infra.as_ref(),
user_id,
identifier,
handle.unwrap_or(&fallback_handle),
handle,
&formatted_token,
hostname,
)
+3 -1
View File
@@ -23,7 +23,8 @@ pub struct DelegationGrant {
#[serde(rename_all = "camelCase")]
pub struct DelegatedAccountInfo {
pub did: Did,
pub handle: Handle,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub handle: Option<Handle>,
pub granted_scopes: DbScope,
pub granted_at: DateTime<Utc>,
}
@@ -32,6 +33,7 @@ pub struct DelegatedAccountInfo {
#[serde(rename_all = "camelCase")]
pub struct ControllerInfo {
pub did: Did,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub handle: Option<Handle>,
pub granted_scopes: DbScope,
pub granted_at: DateTime<Utc>,
+1 -2
View File
@@ -670,7 +670,6 @@ pub struct UserForVerification {
pub did: Did,
pub email: Option<String>,
pub email_verified: bool,
pub handle: Handle,
}
#[derive(Debug, Clone)]
@@ -1119,7 +1118,7 @@ pub struct MigrationReactivationInput {
#[derive(Debug, Clone)]
pub struct ReactivatedAccountInfo {
pub user_id: Uuid,
pub old_handle: Handle,
pub old_handle: Option<Handle>,
}
#[derive(Debug, Clone)]
+55 -41
View File
@@ -7,7 +7,9 @@ use tranquil_db_traits::{
use tranquil_types::Did;
use uuid::Uuid;
use super::col;
use super::user::map_sqlx_error;
use super::{column, legacy_column, opt_column};
#[derive(Debug, Clone, Copy, PartialEq, Eq, sqlx::Type)]
#[sqlx(type_name = "delegation_action_type", rename_all = "snake_case")]
@@ -166,16 +168,19 @@ impl DelegationRepository for PostgresDelegationRepository {
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| DelegationGrant {
id: r.id,
delegated_did: r.delegated_did.into(),
controller_did: r.controller_did.into(),
granted_scopes: DbScope::from_db(r.granted_scopes),
granted_at: r.granted_at,
granted_by: r.granted_by.into(),
revoked_at: r.revoked_at,
revoked_by: r.revoked_by.map(Into::into),
}))
row.map(|r| {
Ok(DelegationGrant {
id: r.id,
delegated_did: column(r.delegated_did, col::ACCOUNT_DELEGATIONS_DELEGATED_DID)?,
controller_did: column(r.controller_did, col::ACCOUNT_DELEGATIONS_CONTROLLER_DID)?,
granted_scopes: DbScope::from_db(r.granted_scopes),
granted_at: r.granted_at,
granted_by: column(r.granted_by, col::ACCOUNT_DELEGATIONS_GRANTED_BY)?,
revoked_at: r.revoked_at,
revoked_by: opt_column(r.revoked_by, col::ACCOUNT_DELEGATIONS_REVOKED_BY)?,
})
})
.transpose()
}
async fn get_delegations_for_account(
@@ -205,17 +210,18 @@ impl DelegationRepository for PostgresDelegationRepository {
.await
.map_err(map_sqlx_error)?;
Ok(rows
.into_iter()
.map(|r| ControllerInfo {
did: r.controller_did.into(),
handle: r.handle.map(Into::into),
granted_scopes: DbScope::from_db(r.granted_scopes),
granted_at: r.granted_at,
is_active: r.is_active,
is_local: r.is_local,
rows.into_iter()
.map(|r| {
Ok(ControllerInfo {
did: column(r.controller_did, col::ACCOUNT_DELEGATIONS_CONTROLLER_DID)?,
handle: r.handle.and_then(|h| legacy_column(h, col::USERS_HANDLE)),
granted_scopes: DbScope::from_db(r.granted_scopes),
granted_at: r.granted_at,
is_active: r.is_active,
is_local: r.is_local,
})
})
.collect())
.collect()
}
async fn get_accounts_controlled_by(
@@ -243,15 +249,16 @@ impl DelegationRepository for PostgresDelegationRepository {
.await
.map_err(map_sqlx_error)?;
Ok(rows
.into_iter()
.map(|r| DelegatedAccountInfo {
did: r.did.into(),
handle: r.handle.into(),
granted_scopes: DbScope::from_db(r.granted_scopes),
granted_at: r.granted_at,
rows.into_iter()
.map(|r| {
Ok(DelegatedAccountInfo {
did: column(r.did, col::USERS_DID)?,
handle: legacy_column(r.handle, col::USERS_HANDLE),
granted_scopes: DbScope::from_db(r.granted_scopes),
granted_at: r.granted_at,
})
})
.collect())
.collect()
}
async fn count_active_controllers(&self, delegated_did: &Did) -> Result<i64, DbError> {
@@ -353,20 +360,27 @@ impl DelegationRepository for PostgresDelegationRepository {
.await
.map_err(map_sqlx_error)?;
Ok(rows
.into_iter()
.map(|r| AuditLogEntry {
id: r.id,
delegated_did: r.delegated_did.into(),
actor_did: r.actor_did.into(),
controller_did: r.controller_did.map(Into::into),
action_type: r.action_type.into(),
action_details: r.action_details,
ip_address: r.ip_address,
user_agent: r.user_agent,
created_at: r.created_at,
rows.into_iter()
.map(|r| {
Ok(AuditLogEntry {
id: r.id,
delegated_did: column(
r.delegated_did,
col::DELEGATION_AUDIT_LOG_DELEGATED_DID,
)?,
actor_did: column(r.actor_did, col::DELEGATION_AUDIT_LOG_ACTOR_DID)?,
controller_did: opt_column(
r.controller_did,
col::DELEGATION_AUDIT_LOG_CONTROLLER_DID,
)?,
action_type: r.action_type.into(),
action_details: r.action_details,
ip_address: r.ip_address,
user_agent: r.user_agent,
created_at: r.created_at,
})
})
.collect())
.collect()
}
async fn count_audit_log_entries(&self, delegated_did: &Did) -> Result<i64, DbError> {
+287 -214
View File
@@ -4,6 +4,8 @@ use sqlx::PgPool;
use tranquil_types::{AtIdentifier, Did, Handle, Jti, PasswordHash, TokenId};
use uuid::Uuid;
use super::col;
use super::{column, legacy_column, opt_column};
use tranquil_db_traits::{
AccountSearchResult, AccountType, ChannelVerificationStatus, CommsChannel, DbError,
DidWebOverrides, NotificationPrefs, OAuthTokenWithUser, PasswordResetResult, SsoProviderType,
@@ -87,17 +89,20 @@ impl UserRepository for PostgresUserRepository {
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| UserRow {
id: r.id,
did: Did::from(r.did),
handle: Handle::from(r.handle),
email: r.email,
created_at: r.created_at,
deactivated_at: r.deactivated_at,
takedown_ref: r.takedown_ref,
is_admin: r.is_admin,
inbound_migration: r.inbound_migration,
}))
row.map(|r| {
Ok(UserRow {
id: r.id,
did: column(r.did, col::USERS_DID)?,
handle: column(r.handle, col::USERS_HANDLE)?,
email: r.email,
created_at: r.created_at,
deactivated_at: r.deactivated_at,
takedown_ref: r.takedown_ref,
is_admin: r.is_admin,
inbound_migration: r.inbound_migration,
})
})
.transpose()
}
async fn get_by_handle(&self, handle: &Handle) -> Result<Option<UserRow>, DbError> {
@@ -110,17 +115,20 @@ impl UserRepository for PostgresUserRepository {
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| UserRow {
id: r.id,
did: Did::from(r.did),
handle: Handle::from(r.handle),
email: r.email,
created_at: r.created_at,
deactivated_at: r.deactivated_at,
takedown_ref: r.takedown_ref,
is_admin: r.is_admin,
inbound_migration: r.inbound_migration,
}))
row.map(|r| {
Ok(UserRow {
id: r.id,
did: column(r.did, col::USERS_DID)?,
handle: column(r.handle, col::USERS_HANDLE)?,
email: r.email,
created_at: r.created_at,
deactivated_at: r.deactivated_at,
takedown_ref: r.takedown_ref,
is_admin: r.is_admin,
inbound_migration: r.inbound_migration,
})
})
.transpose()
}
async fn get_with_key_by_did(&self, did: &Did) -> Result<Option<UserWithKey>, DbError> {
@@ -136,17 +144,20 @@ impl UserRepository for PostgresUserRepository {
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| UserWithKey {
id: r.id,
did: Did::from(r.did),
handle: Handle::from(r.handle),
email: r.email,
deactivated_at: r.deactivated_at,
takedown_ref: r.takedown_ref,
is_admin: r.is_admin,
key_bytes: r.key_bytes,
encryption_version: r.encryption_version,
}))
row.map(|r| {
Ok(UserWithKey {
id: r.id,
did: column(r.did, col::USERS_DID)?,
handle: column(r.handle, col::USERS_HANDLE)?,
email: r.email,
deactivated_at: r.deactivated_at,
takedown_ref: r.takedown_ref,
is_admin: r.is_admin,
key_bytes: r.key_bytes,
encryption_version: r.encryption_version,
})
})
.transpose()
}
async fn get_status_by_did(&self, did: &Did) -> Result<Option<UserStatus>, DbError> {
@@ -207,15 +218,18 @@ impl UserRepository for PostgresUserRepository {
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| OAuthTokenWithUser {
did: Did::from(r.did),
expires_at: r.expires_at,
deactivated_at: r.deactivated_at,
takedown_ref: r.takedown_ref,
is_admin: r.is_admin,
key_bytes: r.key_bytes,
encryption_version: r.encryption_version,
}))
row.map(|r| {
Ok(OAuthTokenWithUser {
did: column(r.did, col::OAUTH_TOKEN_DID)?,
expires_at: r.expires_at,
deactivated_at: r.deactivated_at,
takedown_ref: r.takedown_ref,
is_admin: r.is_admin,
key_bytes: r.key_bytes,
encryption_version: r.encryption_version,
})
})
.transpose()
}
async fn get_user_info_by_did(&self, did: &Did) -> Result<Option<UserInfoForAuth>, DbError> {
@@ -288,14 +302,16 @@ impl UserRepository for PostgresUserRepository {
.map_err(map_sqlx_error)?;
Ok(rows
.into_iter()
.map(|r| AccountSearchResult {
did: Did::from(r.did),
handle: Handle::from(r.handle),
email: r.email,
created_at: r.created_at,
email_verified: r.email_verified,
deactivated_at: r.deactivated_at,
invites_disabled: r.invites_disabled,
.filter_map(|r| {
Some(AccountSearchResult {
did: legacy_column(r.did, col::USERS_DID)?,
handle: legacy_column(r.handle, col::USERS_HANDLE)?,
email: r.email,
created_at: r.created_at,
email_verified: r.email_verified,
deactivated_at: r.deactivated_at,
invites_disabled: r.invites_disabled,
})
})
.collect())
}
@@ -311,24 +327,27 @@ impl UserRepository for PostgresUserRepository {
.fetch_optional(&self.pool)
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| UserAuthInfo {
id: r.id,
did: Did::from(r.did),
password_hash: r.password_hash.map(PasswordHash::new),
deactivated_at: r.deactivated_at,
takedown_ref: r.takedown_ref,
channel_verification: ChannelVerificationStatus::from_db_row(
r.email_verified,
r.discord_verified,
r.telegram_verified,
r.signal_verified,
),
}))
row.map(|r| {
Ok(UserAuthInfo {
id: r.id,
did: column(r.did, col::USERS_DID)?,
password_hash: r.password_hash.map(PasswordHash::new),
deactivated_at: r.deactivated_at,
takedown_ref: r.takedown_ref,
channel_verification: ChannelVerificationStatus::from_db_row(
r.email_verified,
r.discord_verified,
r.telegram_verified,
r.signal_verified,
),
})
})
.transpose()
}
async fn get_by_email(&self, email: &str) -> Result<Option<UserForVerification>, DbError> {
let row = sqlx::query!(
r#"SELECT id, did, email, email_verified, handle
r#"SELECT id, did, email, email_verified
FROM users
WHERE LOWER(email) = $1"#,
email
@@ -336,13 +355,15 @@ impl UserRepository for PostgresUserRepository {
.fetch_optional(&self.pool)
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| UserForVerification {
id: r.id,
did: Did::from(r.did),
email: r.email,
email_verified: r.email_verified,
handle: Handle::from(r.handle),
}))
row.map(|r| {
Ok(UserForVerification {
id: r.id,
did: column(r.did, col::USERS_DID)?,
email: r.email,
email_verified: r.email_verified,
})
})
.transpose()
}
async fn get_comms_prefs(&self, user_id: Uuid) -> Result<Option<UserCommsPrefs>, DbError> {
@@ -354,15 +375,18 @@ impl UserRepository for PostgresUserRepository {
.fetch_optional(&self.pool)
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| UserCommsPrefs {
email: r.email,
handle: Handle::from(r.handle),
preferred_channel: r.preferred_channel,
preferred_locale: r.preferred_locale,
telegram_chat_id: r.telegram_chat_id,
discord_id: r.discord_id,
signal_username: r.signal_username,
}))
row.map(|r| {
Ok(UserCommsPrefs {
email: r.email,
handle: column(r.handle, col::USERS_HANDLE)?,
preferred_channel: r.preferred_channel,
preferred_locale: r.preferred_locale,
telegram_chat_id: r.telegram_chat_id,
discord_id: r.discord_id,
signal_username: r.signal_username,
})
})
.transpose()
}
async fn get_id_by_did(&self, did: &Did) -> Result<Option<Uuid>, DbError> {
@@ -395,10 +419,13 @@ impl UserRepository for PostgresUserRepository {
.fetch_optional(&self.pool)
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| UserIdAndHandle {
id: r.id,
handle: Handle::from(r.handle),
}))
row.map(|r| {
Ok(UserIdAndHandle {
id: r.id,
handle: column(r.handle, col::USERS_HANDLE)?,
})
})
.transpose()
}
async fn get_did_web_info_by_handle(
@@ -412,11 +439,14 @@ impl UserRepository for PostgresUserRepository {
.fetch_optional(&self.pool)
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| UserDidWebInfo {
id: r.id,
did: Did::from(r.did),
migrated_to_pds: r.migrated_to_pds,
}))
row.map(|r| {
Ok(UserDidWebInfo {
id: r.id,
did: column(r.did, col::USERS_DID)?,
migrated_to_pds: r.migrated_to_pds,
})
})
.transpose()
}
async fn get_did_web_overrides(
@@ -441,7 +471,7 @@ impl UserRepository for PostgresUserRepository {
.fetch_optional(&self.pool)
.await
.map_err(map_sqlx_error)?;
Ok(handle.map(Handle::from))
opt_column(handle, col::USERS_HANDLE)
}
async fn check_handle_exists(
@@ -538,12 +568,15 @@ impl UserRepository for PostgresUserRepository {
.fetch_optional(&self.pool)
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| UserEmailInfo {
id: r.id,
handle: Handle::from(r.handle),
email: r.email,
email_verified: r.email_verified,
}))
row.map(|r| {
Ok(UserEmailInfo {
id: r.id,
handle: column(r.handle, col::USERS_HANDLE)?,
email: r.email,
email_verified: r.email_verified,
})
})
.transpose()
}
async fn check_email_exists(
@@ -726,11 +759,14 @@ impl UserRepository for PostgresUserRepository {
.fetch_optional(&self.pool)
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| UserIdHandleEmail {
id: r.id,
handle: Handle::from(r.handle),
email: r.email,
}))
row.map(|r| {
Ok(UserIdHandleEmail {
id: r.id,
handle: column(r.handle, col::USERS_HANDLE)?,
email: r.email,
})
})
.transpose()
}
async fn update_preferred_comms_channel(
@@ -794,17 +830,20 @@ impl UserRepository for PostgresUserRepository {
.fetch_optional(&self.pool)
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| UserVerificationInfo {
id: r.id,
handle: Handle::from(r.handle),
email: r.email,
channel_verification: ChannelVerificationStatus::from_db_row(
r.email_verified,
r.discord_verified,
r.telegram_verified,
r.signal_verified,
),
}))
row.map(|r| {
Ok(UserVerificationInfo {
id: r.id,
handle: column(r.handle, col::USERS_HANDLE)?,
email: r.email,
channel_verification: ChannelVerificationStatus::from_db_row(
r.email_verified,
r.discord_verified,
r.telegram_verified,
r.signal_verified,
),
})
})
.transpose()
}
async fn verify_email_channel(&self, user_id: Uuid, email: &str) -> Result<bool, DbError> {
@@ -954,21 +993,22 @@ impl UserRepository for PostgresUserRepository {
.await
.map_err(map_sqlx_error)?;
Ok(rows
.into_iter()
.map(|r| StoredPasskey {
id: r.id,
did: Did::from(r.did),
credential_id: r.credential_id,
public_key: r.public_key,
sign_count: r.sign_count,
created_at: r.created_at,
last_used: r.last_used,
friendly_name: r.friendly_name,
aaguid: r.aaguid,
transports: r.transports,
rows.into_iter()
.map(|r| {
Ok(StoredPasskey {
id: r.id,
did: column(r.did, col::PASSKEYS_DID)?,
credential_id: r.credential_id,
public_key: r.public_key,
sign_count: r.sign_count,
created_at: r.created_at,
last_used: r.last_used,
friendly_name: r.friendly_name,
aaguid: r.aaguid,
transports: r.transports,
})
})
.collect())
.collect()
}
async fn get_passkey_by_credential_id(
@@ -985,18 +1025,21 @@ impl UserRepository for PostgresUserRepository {
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| StoredPasskey {
id: r.id,
did: Did::from(r.did),
credential_id: r.credential_id,
public_key: r.public_key,
sign_count: r.sign_count,
created_at: r.created_at,
last_used: r.last_used,
friendly_name: r.friendly_name,
aaguid: r.aaguid,
transports: r.transports,
}))
row.map(|r| {
Ok(StoredPasskey {
id: r.id,
did: column(r.did, col::PASSKEYS_DID)?,
credential_id: r.credential_id,
public_key: r.public_key,
sign_count: r.sign_count,
created_at: r.created_at,
last_used: r.last_used,
friendly_name: r.friendly_name,
aaguid: r.aaguid,
transports: r.transports,
})
})
.transpose()
}
async fn save_passkey(
@@ -1431,13 +1474,14 @@ impl UserRepository for PostgresUserRepository {
)
.fetch_optional(&self.pool)
.await
.map_err(map_sqlx_error)
.map(|opt| {
opt.map(|r| UserLoginCheck {
did: Did::from(r.did),
.map_err(map_sqlx_error)?
.map(|r| {
Ok(UserLoginCheck {
did: column(r.did, col::USERS_DID)?,
password_hash: r.password_hash.map(PasswordHash::new),
})
})
.transpose()
}
async fn get_login_info_by_identifier(
@@ -1458,11 +1502,11 @@ impl UserRepository for PostgresUserRepository {
)
.fetch_optional(&self.pool)
.await
.map_err(map_sqlx_error)
.map(|opt| {
opt.map(|row| UserLoginInfo {
.map_err(map_sqlx_error)?
.map(|row| {
Ok(UserLoginInfo {
id: row.id,
did: Did::from(row.did),
did: column(row.did, col::USERS_DID)?,
email: row.email,
password_hash: row.password_hash.map(PasswordHash::new),
password_required: row.password_required,
@@ -1479,6 +1523,7 @@ impl UserRepository for PostgresUserRepository {
account_type: row.account_type,
})
})
.transpose()
}
async fn get_2fa_status_by_did(&self, did: &Did) -> Result<Option<User2faStatus>, DbError> {
@@ -1527,10 +1572,10 @@ impl UserRepository for PostgresUserRepository {
)
.fetch_optional(&self.pool)
.await
.map_err(map_sqlx_error)
.map(|opt| {
opt.map(|row| UserSessionInfo {
handle: Handle::from(row.handle),
.map_err(map_sqlx_error)?
.map(|row| {
Ok(UserSessionInfo {
handle: column(row.handle, col::USERS_HANDLE)?,
email: row.email,
is_admin: row.is_admin,
deactivated_at: row.deactivated_at,
@@ -1549,6 +1594,7 @@ impl UserRepository for PostgresUserRepository {
email_2fa_enabled: row.email_2fa_enabled,
})
})
.transpose()
}
async fn get_legacy_login_pref(
@@ -1620,12 +1666,12 @@ impl UserRepository for PostgresUserRepository {
)
.fetch_optional(&self.pool)
.await
.map_err(map_sqlx_error)
.map(|opt| {
opt.map(|row| UserLoginFull {
.map_err(map_sqlx_error)?
.map(|row| {
Ok(UserLoginFull {
id: row.id,
did: Did::from(row.did),
handle: Handle::from(row.handle),
did: column(row.did, col::USERS_DID)?,
handle: column(row.handle, col::USERS_HANDLE)?,
password_hash: row.password_hash.map(PasswordHash::new),
email: row.email,
deactivated_at: row.deactivated_at,
@@ -1645,6 +1691,7 @@ impl UserRepository for PostgresUserRepository {
email_2fa_enabled: row.email_2fa_enabled,
})
})
.transpose()
}
async fn get_confirm_signup_by_did(
@@ -1664,12 +1711,12 @@ impl UserRepository for PostgresUserRepository {
)
.fetch_optional(&self.pool)
.await
.map_err(map_sqlx_error)
.map(|opt| {
opt.map(|row| UserConfirmSignup {
.map_err(map_sqlx_error)?
.map(|row| {
Ok(UserConfirmSignup {
id: row.id,
did: Did::from(row.did),
handle: Handle::from(row.handle),
did: column(row.did, col::USERS_DID)?,
handle: column(row.handle, col::USERS_HANDLE)?,
email: row.email,
channel: row.channel,
discord_username: row.discord_username,
@@ -1679,6 +1726,7 @@ impl UserRepository for PostgresUserRepository {
encryption_version: row.encryption_version,
})
})
.transpose()
}
async fn get_resend_verification_by_did(
@@ -1697,11 +1745,11 @@ impl UserRepository for PostgresUserRepository {
)
.fetch_optional(&self.pool)
.await
.map_err(map_sqlx_error)
.map(|opt| {
opt.map(|row| UserResendVerification {
.map_err(map_sqlx_error)?
.map(|row| {
Ok(UserResendVerification {
id: row.id,
handle: Handle::from(row.handle),
handle: column(row.handle, col::USERS_HANDLE)?,
email: row.email,
channel: row.channel,
discord_username: row.discord_username,
@@ -1715,6 +1763,7 @@ impl UserRepository for PostgresUserRepository {
),
})
})
.transpose()
}
async fn set_channel_verified(&self, did: &Did, channel: CommsChannel) -> Result<(), DbError> {
@@ -1760,14 +1809,18 @@ impl UserRepository for PostgresUserRepository {
}
async fn get_handles_by_email(&self, email: &str) -> Result<Vec<Handle>, DbError> {
sqlx::query_scalar!(
let handles = sqlx::query_scalar!(
"SELECT handle FROM users WHERE LOWER(email) = LOWER($1) AND deactivated_at IS NULL ORDER BY created_at DESC",
email
)
.fetch_all(&self.pool)
.await
.map(|handles| handles.into_iter().map(Handle::from).collect())
.map_err(map_sqlx_error)
.map_err(map_sqlx_error)?;
Ok(handles
.into_iter()
.filter_map(|h| legacy_column(h, col::USERS_HANDLE))
.collect())
}
async fn set_password_reset_code(
@@ -1798,15 +1851,16 @@ impl UserRepository for PostgresUserRepository {
)
.fetch_optional(&self.pool)
.await
.map_err(map_sqlx_error)
.map(|opt| {
opt.map(|row| UserResetCodeInfo {
.map_err(map_sqlx_error)?
.map(|row| {
Ok(UserResetCodeInfo {
id: row.id,
did: Did::from(row.did),
did: column(row.did, col::USERS_DID)?,
preferred_comms_channel: row.preferred_comms_channel,
expires_at: row.password_reset_code_expires_at,
})
})
.transpose()
}
async fn clear_password_reset_code(&self, user_id: Uuid) -> Result<(), DbError> {
@@ -1894,12 +1948,11 @@ impl UserRepository for PostgresUserRepository {
.await
.map_err(map_sqlx_error)?;
let did = column(user_did, col::USERS_DID)?;
tx.commit().await.map_err(map_sqlx_error)?;
Ok(PasswordResetResult {
did: Did::from(user_did),
session_jtis,
})
Ok(PasswordResetResult { did, session_jtis })
}
async fn activate_account(&self, did: &Did) -> Result<bool, DbError> {
@@ -2004,14 +2057,15 @@ impl UserRepository for PostgresUserRepository {
)
.fetch_optional(&self.pool)
.await
.map_err(map_sqlx_error)
.map(|opt| {
opt.map(|row| UserForDeletion {
.map_err(map_sqlx_error)?
.map(|row| {
Ok(UserForDeletion {
id: row.id,
password_hash: row.password_hash.map(PasswordHash::new),
handle: Handle::from(row.handle),
handle: column(row.handle, col::USERS_HANDLE)?,
})
})
.transpose()
}
async fn get_user_key_by_did(&self, did: &Did) -> Result<Option<UserKeyInfo>, DbError> {
@@ -2154,11 +2208,14 @@ impl UserRepository for PostgresUserRepository {
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| UserForDidDoc {
id: r.id,
handle: Handle::from(r.handle),
deactivated_at: r.deactivated_at,
}))
row.map(|r| {
Ok(UserForDidDoc {
id: r.id,
handle: column(r.handle, col::USERS_HANDLE)?,
deactivated_at: r.deactivated_at,
})
})
.transpose()
}
async fn get_user_for_did_doc_build(
@@ -2173,11 +2230,14 @@ impl UserRepository for PostgresUserRepository {
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| UserForDidDocBuild {
id: r.id,
handle: Handle::from(r.handle),
migrated_to_pds: r.migrated_to_pds,
}))
row.map(|r| {
Ok(UserForDidDocBuild {
id: r.id,
handle: column(r.handle, col::USERS_HANDLE)?,
migrated_to_pds: r.migrated_to_pds,
})
})
.transpose()
}
async fn upsert_did_web_overrides(
@@ -2234,13 +2294,16 @@ impl UserRepository for PostgresUserRepository {
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| UserForPasskeySetup {
id: r.id,
handle: Handle::from(r.handle),
recovery_token: r.recovery_token,
recovery_token_expires_at: r.recovery_token_expires_at,
password_required: r.password_required,
}))
row.map(|r| {
Ok(UserForPasskeySetup {
id: r.id,
handle: column(r.handle, col::USERS_HANDLE)?,
recovery_token: r.recovery_token,
recovery_token_expires_at: r.recovery_token_expires_at,
password_required: r.password_required,
})
})
.transpose()
}
async fn get_user_for_passkey_recovery(
@@ -2257,12 +2320,15 @@ impl UserRepository for PostgresUserRepository {
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| UserForPasskeyRecovery {
id: r.id,
did: Did::from(r.did),
handle: Handle::from(r.handle),
password_required: r.password_required,
}))
row.map(|r| {
Ok(UserForPasskeyRecovery {
id: r.id,
did: column(r.did, col::USERS_DID)?,
handle: column(r.handle, col::USERS_HANDLE)?,
password_required: r.password_required,
})
})
.transpose()
}
async fn set_recovery_token(
@@ -2292,13 +2358,16 @@ impl UserRepository for PostgresUserRepository {
.await
.map_err(map_sqlx_error)?;
Ok(row.map(|r| UserForRecovery {
id: r.id,
did: Did::from(r.did),
preferred_comms_channel: r.preferred_comms_channel,
recovery_token: r.recovery_token,
recovery_token_expires_at: r.recovery_token_expires_at,
}))
row.map(|r| {
Ok(UserForRecovery {
id: r.id,
did: column(r.did, col::USERS_DID)?,
preferred_comms_channel: r.preferred_comms_channel,
recovery_token: r.recovery_token,
recovery_token_expires_at: r.recovery_token_expires_at,
})
})
.transpose()
}
async fn get_accounts_scheduled_for_deletion(
@@ -2322,10 +2391,12 @@ impl UserRepository for PostgresUserRepository {
Ok(rows
.into_iter()
.map(|r| tranquil_db_traits::ScheduledDeletionAccount {
id: r.id,
did: Did::from(r.did),
handle: Handle::from(r.handle),
.filter_map(|r| {
Some(tranquil_db_traits::ScheduledDeletionAccount {
id: r.id,
did: legacy_column(r.did, col::USERS_DID)?,
handle: legacy_column(r.handle, col::USERS_HANDLE)?,
})
})
.collect())
}
@@ -3029,13 +3100,15 @@ impl UserRepository for PostgresUserRepository {
));
}
let old_handle = legacy_column(old_handle, col::USERS_HANDLE);
tx.commit()
.await
.map_err(|e| tranquil_db_traits::MigrationReactivationError::Database(e.to_string()))?;
Ok(tranquil_db_traits::ReactivatedAccountInfo {
user_id: account_id,
old_handle: Handle::from(old_handle),
old_handle,
})
}
@@ -306,9 +306,7 @@ impl DelegationOps {
if let Some(val) = grant_val.filter(|v| v.revoked_at_ms.is_none()) {
let delegated_did = Did::new(val.delegated_did.clone())
.map_err(|_| MetastoreError::CorruptData("invalid delegated_did"))?;
let handle = self
.resolve_handle_for_did(&val.delegated_did)
.unwrap_or_else(|| Handle::new("unknown.invalid").unwrap());
let handle = self.resolve_handle_for_did(&val.delegated_did);
acc.push(DelegatedAccountInfo {
did: delegated_did,
handle,
@@ -460,8 +460,6 @@ impl UserOps {
.map_err(|_| MetastoreError::CorruptData("invalid user did"))?,
email: v.email.clone(),
email_verified: v.email_verified,
handle: Handle::new(v.handle.clone())
.map_err(|_| MetastoreError::CorruptData("invalid user handle"))?,
})
})
.transpose()
@@ -3000,7 +2998,13 @@ impl UserOps {
}
let old_handle = Handle::new(user.handle.clone())
.map_err(|_| MigrationReactivationError::Database("invalid handle".to_owned()))?;
.inspect_err(|_| {
tracing::warn!(
handle = %user.handle,
"ignoring a stored handle that isn't valid"
);
})
.ok();
let user_id = user.id;
let mut batch = self.db.batch();
@@ -26,7 +26,7 @@
interface ControlledAccount {
did: Did
handle: Handle
handle?: Handle
grantedScopes: ScopeSet
grantedAt: string
}
@@ -505,7 +505,7 @@
<div class="item-card">
<div class="item-info">
<div class="item-header">
<span class="item-handle">@{account.handle}</span>
<span class="item-handle">{account.handle ? `@${account.handle}` : account.did}</span>
<span class="badge scope">{getScopeLabel(account.grantedScopes)}</span>
</div>
<div class="item-details">
+1 -1
View File
@@ -355,7 +355,7 @@ function _castDelegationControlledAccount(
const a = raw as Record<string, unknown>;
return {
did: unsafeAsDid(a.did as string),
handle: unsafeAsHandle(a.handle as string),
handle: a.handle ? unsafeAsHandle(a.handle as string) : undefined,
grantedScopes: unsafeAsScopeSet(
(a.granted_scopes ?? a.grantedScopes) as string,
),
+1 -1
View File
@@ -529,7 +529,7 @@ export interface DelegationController {
export interface DelegationControlledAccount {
did: Did;
handle: Handle;
handle?: Handle;
grantedScopes: ScopeSet;
grantedAt: ISODateString;
}
+1 -1
View File
@@ -58,7 +58,7 @@
state: state,
code_challenge: codeChallenge,
code_challenge_method: 'S256',
login_hint: account.handle
login_hint: account.handle ?? account.did
})
})