mirror of
https://tangled.org/tranquil.farm/tranquil-pds
synced 2026-07-20 23:12:48 +00:00
identity: Handle type for stored handles & extract_handle
Lewis: May this revision serve well! <lu5a@proton.me>
This commit is contained in:
@@ -106,7 +106,7 @@ pub async fn update_account_handle(
|
||||
}
|
||||
let _ = state
|
||||
.cache
|
||||
.delete(&tranquil_pds::cache_keys::handle_key(&handle))
|
||||
.delete(&tranquil_pds::cache_keys::handle_key(&handle_for_check))
|
||||
.await;
|
||||
if let Err(e) = tranquil_pds::repo_ops::sequence_identity_event(
|
||||
&state,
|
||||
|
||||
@@ -41,8 +41,7 @@ pub async fn list_controllers(
|
||||
.fetch_did_document(c.did.as_str())
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|doc| tranquil_types::did_doc::extract_handle(&doc))
|
||||
.map(Into::into);
|
||||
.and_then(|doc| tranquil_types::did_doc::extract_handle(&doc));
|
||||
}
|
||||
c
|
||||
}
|
||||
@@ -380,7 +379,6 @@ pub async fn create_delegated_account(
|
||||
e
|
||||
})?;
|
||||
let did = plc.did;
|
||||
let handle: Handle = handle.parse().map_err(|_| ApiError::InvalidHandle(None))?;
|
||||
info!(did = %did, handle = %handle, controller = %can_control.did(), "Created DID for delegated account");
|
||||
|
||||
let repo = init_genesis_repo(&state, &did, &plc.signing_key, &plc.signing_key_bytes).await?;
|
||||
@@ -465,14 +463,13 @@ pub async fn resolve_controller(
|
||||
.parse()
|
||||
.map_err(|_| ApiError::ControllerNotFound)?
|
||||
} else {
|
||||
let local_handle: Option<Handle> = identifier.parse().ok();
|
||||
let local_user = match local_handle {
|
||||
Some(ref h) => state.repos.user.get_by_handle(h).await.ok().flatten(),
|
||||
None => None,
|
||||
};
|
||||
let handle: Handle = identifier
|
||||
.parse()
|
||||
.map_err(|_| ApiError::ControllerNotFound)?;
|
||||
let local_user = state.repos.user.get_by_handle(&handle).await.ok().flatten();
|
||||
match local_user {
|
||||
Some(user) => user.did,
|
||||
None => tranquil_pds::handle::resolve_handle(identifier)
|
||||
None => tranquil_pds::handle::resolve_handle(&handle)
|
||||
.await
|
||||
.map_err(|_| ApiError::ControllerNotFound)?
|
||||
.parse()
|
||||
|
||||
@@ -168,10 +168,11 @@ async fn handle_command(state: AppState, interaction: Interaction) -> Response {
|
||||
"Received /start from Discord user"
|
||||
);
|
||||
|
||||
let handle = handle.map(Handle::from);
|
||||
match state
|
||||
.repos
|
||||
.user
|
||||
.store_discord_user_id(&discord_username, &discord_user_id, handle.as_deref())
|
||||
.store_discord_user_id(&discord_username, &discord_user_id, handle.as_ref())
|
||||
.await
|
||||
{
|
||||
Ok(Some(user_id)) => {
|
||||
|
||||
@@ -48,22 +48,14 @@ pub struct CreateAccountOutput {
|
||||
async fn try_reactivate_migration(
|
||||
state: &AppState,
|
||||
did: &str,
|
||||
handle: &str,
|
||||
handle: &Handle,
|
||||
email: &Option<String>,
|
||||
verification_channel: tranquil_db_traits::CommsChannel,
|
||||
verification_recipient: Option<&str>,
|
||||
) -> Option<Response> {
|
||||
let did_typed: Did = match did.parse() {
|
||||
Ok(d) => d,
|
||||
Err(_) => return Some(ApiError::InternalError(Some("Invalid DID".into())).into_response()),
|
||||
};
|
||||
let handle_typed: Handle = match handle.parse() {
|
||||
Ok(h) => h,
|
||||
Err(_) => return Some(ApiError::InvalidHandle(None).into_response()),
|
||||
};
|
||||
let reactivate_input = tranquil_db_traits::MigrationReactivationInput {
|
||||
did: did_typed.clone(),
|
||||
new_handle: handle_typed.clone(),
|
||||
did: did.clone(),
|
||||
new_handle: handle.clone(),
|
||||
new_email: email.clone(),
|
||||
};
|
||||
match state
|
||||
@@ -153,8 +145,8 @@ async fn try_reactivate_migration(
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(CreateAccountOutput {
|
||||
handle: handle.to_string().into(),
|
||||
did: did_typed.clone(),
|
||||
handle: handle.clone(),
|
||||
did: did.clone(),
|
||||
did_doc: state
|
||||
.did_resolver
|
||||
.fetch_did_document(did)
|
||||
@@ -342,7 +334,7 @@ pub async fn create_account(
|
||||
}
|
||||
if !is_did_web_byod
|
||||
&& let Err(e) =
|
||||
verify_did_web(d, hostname, &input.handle, input.signing_key.as_deref()).await
|
||||
verify_did_web(d, hostname, &input.handle, input.signing_key.as_ref()).await
|
||||
{
|
||||
return ApiError::InvalidDid(e.to_string()).into_response();
|
||||
}
|
||||
@@ -357,7 +349,7 @@ pub async fn create_account(
|
||||
} else if d.starts_with("did:web:") {
|
||||
if !is_did_web_byod
|
||||
&& let Err(e) =
|
||||
verify_did_web(d, hostname, &input.handle, input.signing_key.as_deref())
|
||||
verify_did_web(d, hostname, &input.handle, input.signing_key.as_ref())
|
||||
.await
|
||||
{
|
||||
return ApiError::InvalidDid(e.to_string()).into_response();
|
||||
@@ -397,14 +389,10 @@ pub async fn create_account(
|
||||
return response;
|
||||
}
|
||||
|
||||
let handle_typed: Handle = match handle.parse() {
|
||||
Ok(h) => h,
|
||||
Err(_) => return ApiError::InvalidHandle(None).into_response(),
|
||||
};
|
||||
let handle_available = match state
|
||||
.repos
|
||||
.user
|
||||
.check_handle_available_for_new_account(&handle_typed)
|
||||
.check_handle_available_for_new_account(&handle)
|
||||
.await
|
||||
{
|
||||
Ok(available) => available,
|
||||
@@ -466,7 +454,7 @@ pub async fn create_account(
|
||||
let repo_for_seq = repo.clone();
|
||||
|
||||
let create_input = tranquil_db_traits::CreatePasswordAccountInput {
|
||||
handle: handle_typed.clone(),
|
||||
handle: handle.clone(),
|
||||
email: email.clone(),
|
||||
did: did_for_commit.clone(),
|
||||
password_hash,
|
||||
@@ -512,14 +500,8 @@ pub async fn create_account(
|
||||
};
|
||||
let user_id = create_result.user_id;
|
||||
if !is_migration && !is_did_web_byod {
|
||||
super::provision::sequence_new_account(
|
||||
&state,
|
||||
&did_for_commit,
|
||||
&handle_typed,
|
||||
&repo_for_seq,
|
||||
&input.handle,
|
||||
)
|
||||
.await;
|
||||
super::provision::sequence_new_account(&state, &did, &handle, &repo_for_seq, &input.handle)
|
||||
.await;
|
||||
}
|
||||
if !is_migration {
|
||||
if let Some(ref recipient) = verification_recipient {
|
||||
@@ -569,8 +551,8 @@ pub async fn create_account(
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(CreateAccountOutput {
|
||||
handle: handle.clone().into(),
|
||||
did: did_for_commit,
|
||||
handle: handle.clone(),
|
||||
did,
|
||||
did_doc: did_doc.map(|f| (*f).clone()),
|
||||
access_jwt: session.access_jwt,
|
||||
refresh_jwt: session.refresh_jwt,
|
||||
|
||||
@@ -19,7 +19,7 @@ use tranquil_pds::rate_limit::{
|
||||
HandleUpdateDailyLimit, HandleUpdateLimit, check_user_rate_limit_with_message,
|
||||
};
|
||||
use tranquil_pds::state::AppState;
|
||||
use tranquil_pds::types::Handle;
|
||||
use tranquil_pds::types::{Did, Handle};
|
||||
use tranquil_pds::util::get_header_str;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -54,6 +54,10 @@ pub async fn resolve_handle(
|
||||
return ApiError::InvalidHandle(Some("Invalid handle format".into())).into_response();
|
||||
}
|
||||
};
|
||||
let cache_key = tranquil_pds::cache_keys::handle_key(&handle);
|
||||
if let Some(did) = state.cache.get(&cache_key).await {
|
||||
return DidResponse::response(did).into_response();
|
||||
}
|
||||
let user = state.repos.user.get_by_handle(&handle).await;
|
||||
match user {
|
||||
Ok(Some(row)) => {
|
||||
@@ -63,7 +67,7 @@ pub async fn resolve_handle(
|
||||
.await;
|
||||
DidResponse::response(row.did).into_response()
|
||||
}
|
||||
Ok(None) => match tranquil_pds::handle::resolve_handle(handle.as_str()).await {
|
||||
Ok(None) => match tranquil_pds::handle::resolve_handle(&handle).await {
|
||||
Ok(did) => {
|
||||
let _ = state
|
||||
.cache
|
||||
@@ -212,8 +216,7 @@ async fn serve_handle_did_doc(state: &AppState, handle: &str, hostname: &str) ->
|
||||
pub async fn user_did_doc(State(state): State<AppState>, Path(handle): Path<String>) -> Response {
|
||||
let hostname = &tranquil_config::get().server.hostname;
|
||||
let hostname_for_handles = tranquil_config::get().server.hostname_without_port();
|
||||
let current_handle = format!("{}.{}", handle, hostname_for_handles);
|
||||
let current_handle_typed: Handle = match current_handle.parse() {
|
||||
let current_handle: Handle = match format!("{}.{}", handle, hostname_for_handles).parse() {
|
||||
Ok(h) => h,
|
||||
Err(_) => {
|
||||
return ApiError::InvalidHandle(Some("Invalid handle format".into())).into_response();
|
||||
@@ -583,7 +586,7 @@ pub async fn update_handle(
|
||||
.max_by_key(|d| d.len())
|
||||
.cloned();
|
||||
let is_domain_itself = handle_domains.iter().any(|d| d == &new_handle);
|
||||
let handle = if (!new_handle.contains('.') || matched_handle_domain.is_some())
|
||||
let handle: Handle = if (!new_handle.contains('.') || matched_handle_domain.is_some())
|
||||
&& !is_domain_itself
|
||||
{
|
||||
let (short_part, full_handle) = match &matched_handle_domain {
|
||||
@@ -598,13 +601,12 @@ pub async fn update_handle(
|
||||
}
|
||||
};
|
||||
if full_handle == current_handle {
|
||||
let handle_typed: Handle = match full_handle.parse() {
|
||||
let handle: Handle = match full_handle.parse() {
|
||||
Ok(h) => h,
|
||||
Err(_) => return Err(ApiError::InvalidHandle(None)),
|
||||
};
|
||||
if let Err(e) =
|
||||
tranquil_pds::repo_ops::sequence_identity_event(&state, &did, Some(&handle_typed))
|
||||
.await
|
||||
tranquil_pds::repo_ops::sequence_identity_event(&state, &did, Some(&handle)).await
|
||||
{
|
||||
warn!("Failed to sequence identity event for handle update: {}", e);
|
||||
}
|
||||
@@ -622,21 +624,25 @@ pub async fn update_handle(
|
||||
return Err(ApiError::InvalidHandle(Some("Handle too long".into())));
|
||||
}
|
||||
full_handle
|
||||
.parse()
|
||||
.map_err(|_| ApiError::InvalidHandle(Some("Invalid handle format".into())))?
|
||||
} else {
|
||||
let handle: Handle = new_handle
|
||||
.parse()
|
||||
.map_err(|_| ApiError::InvalidHandle(Some("Invalid handle format".into())))?;
|
||||
if new_handle == current_handle {
|
||||
let handle_typed: Handle = match new_handle.parse() {
|
||||
Ok(h) => h,
|
||||
Err(_) => return Err(ApiError::InvalidHandle(None)),
|
||||
};
|
||||
if let Err(e) =
|
||||
tranquil_pds::repo_ops::sequence_identity_event(&state, &did, Some(&handle_typed))
|
||||
.await
|
||||
tranquil_pds::repo_ops::sequence_identity_event(&state, &did, Some(&handle)).await
|
||||
{
|
||||
warn!("Failed to sequence identity event for handle update: {}", e);
|
||||
}
|
||||
return Ok(Json(EmptyResponse {}));
|
||||
}
|
||||
match tranquil_pds::handle::verify_handle_ownership(&new_handle, &did).await {
|
||||
match tranquil_pds::handle::verify_handle_ownership(&handle, &did).await {
|
||||
Ok(()) => {}
|
||||
Err(tranquil_pds::handle::HandleResolutionError::NotFound) => {
|
||||
return Err(ApiError::HandleNotAvailable(None));
|
||||
@@ -655,15 +661,12 @@ pub async fn update_handle(
|
||||
))));
|
||||
}
|
||||
}
|
||||
new_handle.clone()
|
||||
handle
|
||||
};
|
||||
let handle_typed: Handle = handle
|
||||
.parse()
|
||||
.map_err(|_| ApiError::InvalidHandle(Some("Invalid handle format".into())))?;
|
||||
let handle_exists = state
|
||||
.repos
|
||||
.user
|
||||
.check_handle_exists(&handle_typed, user_id)
|
||||
.check_handle_exists(&handle, user_id)
|
||||
.await
|
||||
.log_db_err("checking handle existence")?;
|
||||
if handle_exists {
|
||||
@@ -672,7 +675,7 @@ pub async fn update_handle(
|
||||
state
|
||||
.repos
|
||||
.user
|
||||
.update_handle(user_id, &handle_typed)
|
||||
.update_handle(user_id, &handle)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("DB error updating handle: {:?}", e);
|
||||
@@ -690,11 +693,11 @@ pub async fn update_handle(
|
||||
.delete(&tranquil_pds::cache_keys::handle_key(&handle))
|
||||
.await;
|
||||
if let Err(e) =
|
||||
tranquil_pds::repo_ops::sequence_identity_event(&state, &did, Some(&handle_typed)).await
|
||||
tranquil_pds::repo_ops::sequence_identity_event(&state, &did, Some(&handle)).await
|
||||
{
|
||||
warn!("Failed to sequence identity event for handle update: {}", e);
|
||||
}
|
||||
if let Err(e) = update_plc_handle(&state, &did, &handle_typed).await {
|
||||
if let Err(e) = update_plc_handle(&state, &did, &handle).await {
|
||||
warn!("Failed to update PLC handle: {}", e);
|
||||
}
|
||||
Ok(Json(EmptyResponse {}))
|
||||
|
||||
@@ -26,10 +26,9 @@ pub async fn verify_handle_ownership(
|
||||
_rate_limit: RateLimited<HandleVerificationLimit>,
|
||||
Json(input): Json<VerifyHandleOwnershipInput>,
|
||||
) -> Response {
|
||||
let handle_str = input.handle.as_str();
|
||||
let did_str = input.did.as_str();
|
||||
|
||||
let dns_mismatch = match tranquil_pds::handle::resolve_handle_dns(handle_str).await {
|
||||
let dns_mismatch = match tranquil_pds::handle::resolve_handle_dns(&input.handle).await {
|
||||
Ok(did) if did == did_str => {
|
||||
return Json(VerifyHandleOwnershipOutput {
|
||||
verified: true,
|
||||
@@ -45,7 +44,7 @@ pub async fn verify_handle_ownership(
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
match tranquil_pds::handle::resolve_handle_http(handle_str).await {
|
||||
match tranquil_pds::handle::resolve_handle_http(&input.handle).await {
|
||||
Ok(did) if did == did_str => Json(VerifyHandleOwnershipOutput {
|
||||
verified: true,
|
||||
method: Some("http".to_string()),
|
||||
|
||||
@@ -14,7 +14,7 @@ pub struct PlcDidResult {
|
||||
pub signing_key: SigningKey,
|
||||
}
|
||||
|
||||
pub async fn create_plc_did(state: &AppState, handle: &str) -> Result<PlcDidResult, ApiError> {
|
||||
pub async fn create_plc_did(state: &AppState, handle: &Handle) -> Result<PlcDidResult, ApiError> {
|
||||
use k256::SecretKey;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
@@ -25,10 +25,7 @@ pub async fn create_plc_did(state: &AppState, handle: &str) -> Result<PlcDidResu
|
||||
ApiError::InternalError(None)
|
||||
})?;
|
||||
|
||||
let did_str = submit_plc_genesis(state, &signing_key, handle).await?;
|
||||
let did: Did = did_str
|
||||
.parse()
|
||||
.map_err(|_| ApiError::InternalError(Some("PLC genesis returned invalid DID".into())))?;
|
||||
let did = submit_plc_genesis(state, &signing_key, handle).await?;
|
||||
|
||||
Ok(PlcDidResult {
|
||||
did,
|
||||
@@ -40,7 +37,7 @@ pub async fn create_plc_did(state: &AppState, handle: &str) -> Result<PlcDidResu
|
||||
pub async fn submit_plc_genesis(
|
||||
state: &AppState,
|
||||
signing_key: &SigningKey,
|
||||
handle: &str,
|
||||
handle: &Handle,
|
||||
) -> Result<String, ApiError> {
|
||||
let hostname = &tranquil_config::get().server.hostname;
|
||||
let pds_endpoint = format!("https://{}", hostname);
|
||||
|
||||
@@ -6,7 +6,7 @@ use tranquil_db_traits::{CommsChannel, CommsStatus, CommsType};
|
||||
use tranquil_pds::api::error::{ApiError, DbResultExt};
|
||||
use tranquil_pds::auth::{Active, Auth};
|
||||
use tranquil_pds::state::AppState;
|
||||
use tranquil_types::Did;
|
||||
use tranquil_types::{Did, Handle};
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -137,7 +137,7 @@ pub async fn request_channel_verification(
|
||||
did: &Did,
|
||||
channel: CommsChannel,
|
||||
identifier: &str,
|
||||
handle: Option<&str>,
|
||||
handle: Option<&Handle>,
|
||||
) -> Result<String, ApiError> {
|
||||
let token = tranquil_pds::auth::verification_token::generate_channel_update_token(
|
||||
did, channel, identifier,
|
||||
@@ -147,12 +147,12 @@ pub async fn request_channel_verification(
|
||||
match channel {
|
||||
CommsChannel::Email => {
|
||||
let hostname = &tranquil_config::get().server.hostname;
|
||||
let handle_str = handle.unwrap_or("user");
|
||||
let fallback_handle = Handle::from("user".to_string());
|
||||
tranquil_pds::comms::comms_repo::enqueue_email_update(
|
||||
state.repos.infra.as_ref(),
|
||||
user_id,
|
||||
identifier,
|
||||
handle_str,
|
||||
handle.unwrap_or(&fallback_handle),
|
||||
&formatted_token,
|
||||
hostname,
|
||||
)
|
||||
|
||||
@@ -423,10 +423,8 @@ pub async fn activate_account(
|
||||
"[MIGRATION] activateAccount: Sequencing identity event for did={} handle={:?}",
|
||||
did, handle
|
||||
);
|
||||
let handle_typed = handle.clone();
|
||||
if let Err(e) =
|
||||
tranquil_pds::repo_ops::sequence_identity_event(&state, &did, handle_typed.as_ref())
|
||||
.await
|
||||
tranquil_pds::repo_ops::sequence_identity_event(&state, &did, handle.as_ref()).await
|
||||
{
|
||||
warn!(
|
||||
"[MIGRATION] activateAccount: Failed to sequence identity event for activation: {}",
|
||||
|
||||
@@ -294,10 +294,6 @@ pub async fn create_passkey_account(
|
||||
None
|
||||
};
|
||||
|
||||
let handle_typed: Handle = match handle.parse() {
|
||||
Ok(h) => h,
|
||||
Err(_) => return Err(ApiError::InvalidHandle(None)),
|
||||
};
|
||||
let repo_for_seq = repo.clone();
|
||||
let comms = crate::identity::provision::normalize_comms_usernames(
|
||||
input.discord_username.as_deref(),
|
||||
@@ -305,7 +301,7 @@ pub async fn create_passkey_account(
|
||||
input.signal_username.as_deref(),
|
||||
);
|
||||
let create_input = tranquil_db_traits::CreatePasskeyAccountInput {
|
||||
handle: handle_typed.clone(),
|
||||
handle: handle.clone(),
|
||||
email: email.clone().unwrap_or_default(),
|
||||
did: did_typed.clone(),
|
||||
preferred_comms_channel: verification_channel,
|
||||
@@ -346,10 +342,10 @@ pub async fn create_passkey_account(
|
||||
if !is_byod_did_web {
|
||||
crate::identity::provision::sequence_new_account(
|
||||
&state,
|
||||
&did_typed,
|
||||
&handle_typed,
|
||||
&repo_for_seq,
|
||||
&did,
|
||||
&handle,
|
||||
&repo_for_seq,
|
||||
handle.as_str(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -398,8 +394,8 @@ pub async fn create_passkey_account(
|
||||
};
|
||||
|
||||
Ok(Json(CreatePasskeyAccountOutput {
|
||||
did: did.into(),
|
||||
handle: handle.into(),
|
||||
did,
|
||||
handle,
|
||||
setup_token,
|
||||
setup_expires_at,
|
||||
access_jwt,
|
||||
|
||||
@@ -10,7 +10,7 @@ use tranquil_pds::auth::{
|
||||
};
|
||||
use tranquil_pds::rate_limit::{PasswordResetLimit, RateLimited, ResetPasswordLimit};
|
||||
use tranquil_pds::state::AppState;
|
||||
use tranquil_pds::types::PlainPassword;
|
||||
use tranquil_pds::types::{Handle, PlainPassword};
|
||||
use tranquil_pds::validation::validate_password;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -48,7 +48,10 @@ pub async fn request_password_reset(
|
||||
let user_id = match state
|
||||
.repos
|
||||
.user
|
||||
.get_id_by_email_or_handle(normalized, normalized_handle.as_str())
|
||||
.get_id_by_email_or_handle(
|
||||
normalized,
|
||||
&Handle::from(normalized_handle.as_str().to_string()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Some(id)) => id,
|
||||
|
||||
@@ -20,7 +20,7 @@ use tranquil_pds::rate_limit::{
|
||||
check_user_rate_limit_with_message,
|
||||
};
|
||||
use tranquil_pds::state::AppState;
|
||||
use tranquil_pds::types::{AccountState, Did, Handle, PlainPassword};
|
||||
use tranquil_pds::types::{AccountState, AtIdentifier, Did, Handle, PlainPassword};
|
||||
use tranquil_types::TokenId;
|
||||
|
||||
pub fn verification_blocks_login(channel_verification: &ChannelVerificationStatus) -> bool {
|
||||
@@ -78,6 +78,16 @@ pub async fn create_session(
|
||||
"Normalized identifier: {} -> {}",
|
||||
input.identifier, normalized_identifier
|
||||
);
|
||||
let Ok(login_identifier) = AtIdentifier::new(normalized_identifier.as_str()) else {
|
||||
let _ = verify(
|
||||
&input.password,
|
||||
"$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.VTtYw1ZzQKZqmK",
|
||||
);
|
||||
warn!("Login identifier is not a valid handle or DID");
|
||||
return Err(ApiError::AuthenticationFailed(Some(
|
||||
"Invalid identifier or password".into(),
|
||||
)));
|
||||
};
|
||||
let row = match state
|
||||
.repos
|
||||
.user
|
||||
|
||||
@@ -62,7 +62,8 @@ pub async fn handle_telegram_webhook(
|
||||
&& let Some(from) = message.from
|
||||
&& let Some(username) = from.username
|
||||
{
|
||||
let handle = parse_start_handle(message.text.as_deref());
|
||||
let handle =
|
||||
parse_start_handle(message.text.as_deref()).map(tranquil_types::Handle::from);
|
||||
|
||||
debug!(
|
||||
telegram_username = %username,
|
||||
@@ -73,7 +74,7 @@ pub async fn handle_telegram_webhook(
|
||||
match state
|
||||
.repos
|
||||
.user
|
||||
.store_telegram_chat_id(&username, from.id, handle.as_deref())
|
||||
.store_telegram_chat_id(&username, from.id, handle.as_ref())
|
||||
.await
|
||||
{
|
||||
Ok(Some(user_id)) => {
|
||||
|
||||
@@ -264,7 +264,7 @@ pub trait UserRepository: Send + Sync {
|
||||
&self,
|
||||
telegram_username: &str,
|
||||
chat_id: i64,
|
||||
handle: Option<&str>,
|
||||
handle: Option<&Handle>,
|
||||
) -> Result<Option<Uuid>, DbError>;
|
||||
|
||||
async fn get_telegram_chat_id(&self, user_id: Uuid) -> Result<Option<i64>, DbError>;
|
||||
@@ -279,7 +279,7 @@ pub trait UserRepository: Send + Sync {
|
||||
&self,
|
||||
discord_username: &str,
|
||||
discord_id: &str,
|
||||
handle: Option<&str>,
|
||||
handle: Option<&Handle>,
|
||||
) -> Result<Option<Uuid>, DbError>;
|
||||
|
||||
async fn get_verification_info(
|
||||
@@ -442,7 +442,7 @@ pub trait UserRepository: Send + Sync {
|
||||
async fn get_id_by_email_or_handle(
|
||||
&self,
|
||||
email: &str,
|
||||
handle: &str,
|
||||
handle: &Handle,
|
||||
) -> Result<Option<Uuid>, DbError>;
|
||||
|
||||
async fn count_accounts_by_email(&self, email: &str) -> Result<i64, DbError>;
|
||||
|
||||
@@ -1732,12 +1732,12 @@ impl UserRepository for PostgresUserRepository {
|
||||
async fn get_id_by_email_or_handle(
|
||||
&self,
|
||||
email: &str,
|
||||
handle: &str,
|
||||
handle: &Handle,
|
||||
) -> Result<Option<Uuid>, DbError> {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT id FROM users WHERE LOWER(email) = $1 OR handle = $2",
|
||||
email,
|
||||
handle
|
||||
handle.as_str()
|
||||
)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
@@ -3225,7 +3225,7 @@ impl UserRepository for PostgresUserRepository {
|
||||
&self,
|
||||
discord_username: &str,
|
||||
discord_id: &str,
|
||||
handle: Option<&str>,
|
||||
handle: Option<&Handle>,
|
||||
) -> Result<Option<Uuid>, DbError> {
|
||||
let result = match handle {
|
||||
Some(h) => sqlx::query_scalar!(
|
||||
@@ -3287,7 +3287,7 @@ impl UserRepository for PostgresUserRepository {
|
||||
&self,
|
||||
telegram_username: &str,
|
||||
chat_id: i64,
|
||||
handle: Option<&str>,
|
||||
handle: Option<&Handle>,
|
||||
) -> Result<Option<Uuid>, DbError> {
|
||||
let result = match handle {
|
||||
Some(h) => sqlx::query_scalar!(
|
||||
|
||||
@@ -797,7 +797,7 @@ pub async fn check_handle_available(
|
||||
}
|
||||
let domain = query.domain.as_deref().unwrap_or(&available_domains[0]);
|
||||
let full_handle = format!("{}.{}", validated, domain);
|
||||
let handle_typed: tranquil_pds::types::Handle = match full_handle.parse() {
|
||||
let handle: tranquil_pds::types::Handle = match full_handle.parse() {
|
||||
Ok(h) => h,
|
||||
Err(_) => return Err(ApiError::InvalidHandle(None)),
|
||||
};
|
||||
@@ -805,7 +805,7 @@ pub async fn check_handle_available(
|
||||
let db_available = state
|
||||
.repos
|
||||
.user
|
||||
.check_handle_available_for_new_account(&handle_typed)
|
||||
.check_handle_available_for_new_account(&handle)
|
||||
.await
|
||||
.unwrap_or(false);
|
||||
|
||||
@@ -840,7 +840,7 @@ pub struct CompleteRegistrationInput {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompleteRegistrationResponse {
|
||||
pub did: String,
|
||||
pub handle: String,
|
||||
pub handle: tranquil_pds::types::Handle,
|
||||
pub redirect_url: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub access_jwt: Option<String>,
|
||||
@@ -889,24 +889,27 @@ pub async fn complete_registration(
|
||||
.filter(|d| input.handle.ends_with(&format!(".{}", d)))
|
||||
.max_by_key(|d| d.len());
|
||||
|
||||
let handle = if !input.handle.contains('.') || matched_domain.is_some() {
|
||||
let handle_to_validate = match matched_domain {
|
||||
Some(domain) => input
|
||||
.handle
|
||||
.strip_suffix(&format!(".{}", domain))
|
||||
.unwrap_or(&input.handle),
|
||||
None => &input.handle,
|
||||
let handle: tranquil_pds::types::Handle =
|
||||
if !input.handle.contains('.') || matched_domain.is_some() {
|
||||
let handle_to_validate = match matched_domain {
|
||||
Some(domain) => input
|
||||
.handle
|
||||
.strip_suffix(&format!(".{}", domain))
|
||||
.unwrap_or(&input.handle),
|
||||
None => &input.handle,
|
||||
};
|
||||
match tranquil_pds::api::validation::validate_short_handle(handle_to_validate) {
|
||||
Ok(h) => format!("{}.{}", h, matched_domain.unwrap_or(&available_domains[0]))
|
||||
.parse()
|
||||
.map_err(|_| ApiError::InvalidHandle(None))?,
|
||||
Err(_) => return Err(ApiError::InvalidHandle(None)),
|
||||
}
|
||||
} else {
|
||||
match tranquil_pds::api::validation::validate_full_domain_handle(&input.handle) {
|
||||
Ok(h) => h,
|
||||
Err(_) => return Err(ApiError::InvalidHandle(None)),
|
||||
}
|
||||
};
|
||||
match tranquil_pds::api::validation::validate_short_handle(handle_to_validate) {
|
||||
Ok(h) => format!("{}.{}", h, matched_domain.unwrap_or(&available_domains[0])),
|
||||
Err(_) => return Err(ApiError::InvalidHandle(None)),
|
||||
}
|
||||
} else {
|
||||
match tranquil_pds::api::validation::validate_full_domain_handle(&input.handle) {
|
||||
Ok(h) => h,
|
||||
Err(_) => return Err(ApiError::InvalidHandle(None)),
|
||||
}
|
||||
};
|
||||
|
||||
let verification_channel = input
|
||||
.verification_channel
|
||||
@@ -989,12 +992,10 @@ pub async fn complete_registration(
|
||||
let invite_registration =
|
||||
check_registration_invite(&state, input.invite_code.as_deref()).await?;
|
||||
|
||||
let handle_typed: tranquil_pds::types::Handle =
|
||||
handle.parse().map_err(|_| ApiError::InvalidHandle(None))?;
|
||||
let reserved = state
|
||||
.repos
|
||||
.user
|
||||
.reserve_handle(&handle_typed, client_ip)
|
||||
.reserve_handle(&handle, client_ip)
|
||||
.await
|
||||
.unwrap_or(false);
|
||||
|
||||
@@ -1130,7 +1131,7 @@ pub async fn complete_registration(
|
||||
};
|
||||
|
||||
let create_input = tranquil_db_traits::CreateSsoAccountInput {
|
||||
handle: handle_typed.clone(),
|
||||
handle: handle.clone(),
|
||||
email: email.clone(),
|
||||
did: did_typed.clone(),
|
||||
preferred_comms_channel: verification_channel,
|
||||
@@ -1190,15 +1191,10 @@ pub async fn complete_registration(
|
||||
}
|
||||
};
|
||||
|
||||
let _ = state
|
||||
.repos
|
||||
.user
|
||||
.release_handle_reservation(&handle_typed)
|
||||
.await;
|
||||
let _ = state.repos.user.release_handle_reservation(&handle).await;
|
||||
|
||||
if let Err(e) =
|
||||
tranquil_pds::repo_ops::sequence_identity_event(&state, &did_typed, Some(&handle_typed))
|
||||
.await
|
||||
tranquil_pds::repo_ops::sequence_identity_event(&state, &did, Some(&handle)).await
|
||||
{
|
||||
tracing::warn!("Failed to sequence identity event for {}: {}", did, e);
|
||||
}
|
||||
@@ -1214,7 +1210,7 @@ pub async fn complete_registration(
|
||||
|
||||
let profile_record = json!({
|
||||
"$type": "app.bsky.actor.profile",
|
||||
"displayName": handle_typed.as_str()
|
||||
"displayName": handle.as_str()
|
||||
});
|
||||
if let Err(e) = tranquil_pds::repo_ops::create_record_internal(
|
||||
&state,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::types::Handle;
|
||||
use std::fmt;
|
||||
|
||||
pub const MAX_EMAIL_LENGTH: usize = 254;
|
||||
@@ -146,7 +147,7 @@ pub enum ReservedHandlePolicy {
|
||||
Reject,
|
||||
}
|
||||
|
||||
pub fn validate_full_domain_handle(handle: &str) -> Result<String, HandleValidationError> {
|
||||
pub fn validate_full_domain_handle(handle: &str) -> Result<Handle, HandleValidationError> {
|
||||
let handle = handle.trim();
|
||||
|
||||
if handle.is_empty() {
|
||||
@@ -189,14 +190,14 @@ pub fn validate_full_domain_handle(handle: &str) -> Result<String, HandleValidat
|
||||
return Err(HandleValidationError::BannedWord);
|
||||
}
|
||||
|
||||
Ok(handle_lower)
|
||||
Ok(Handle::from(handle_lower))
|
||||
}
|
||||
|
||||
pub fn validate_short_handle(handle: &str) -> Result<String, HandleValidationError> {
|
||||
validate_service_handle(handle, ReservedHandlePolicy::Reject)
|
||||
}
|
||||
|
||||
pub fn resolve_handle_input(input: &str) -> Result<String, HandleValidationError> {
|
||||
pub fn resolve_handle_input(input: &str) -> Result<Handle, HandleValidationError> {
|
||||
let available_domains = tranquil_config::get().server.available_user_domain_list();
|
||||
let matched_domain = available_domains
|
||||
.iter()
|
||||
@@ -209,7 +210,7 @@ pub fn resolve_handle_input(input: &str) -> Result<String, HandleValidationError
|
||||
None => input,
|
||||
};
|
||||
let validated = validate_short_handle(handle_to_validate)?;
|
||||
Ok(format!(
|
||||
Ok(Handle::from(format!(
|
||||
"{}.{}",
|
||||
validated,
|
||||
matched_domain.unwrap_or(&available_domains[0])
|
||||
|
||||
@@ -12,7 +12,7 @@ pub fn user_status_key(did: &str) -> String {
|
||||
format!("auth:status:{}", did)
|
||||
}
|
||||
|
||||
pub fn handle_key(handle: &str) -> String {
|
||||
pub fn handle_key(handle: &Handle) -> String {
|
||||
format!("handle:{}", handle)
|
||||
}
|
||||
|
||||
|
||||
@@ -308,7 +308,7 @@ pub mod repo {
|
||||
infra_repo: &dyn InfraRepository,
|
||||
user_id: Uuid,
|
||||
new_email: &str,
|
||||
handle: &str,
|
||||
handle: &crate::types::Handle,
|
||||
code: &str,
|
||||
hostname: &str,
|
||||
) -> Result<Uuid, DbError> {
|
||||
@@ -323,7 +323,7 @@ pub mod repo {
|
||||
let body = format_message(
|
||||
strings.email_update_body,
|
||||
&[
|
||||
("handle", handle),
|
||||
("handle", handle.as_str()),
|
||||
("code", code),
|
||||
("verify_page", &verify_page),
|
||||
("verify_link", &verify_link),
|
||||
|
||||
@@ -12,14 +12,14 @@ pub use tranquil_db_traits::DelegationActionType;
|
||||
|
||||
use crate::did::DidResolutionError;
|
||||
use crate::state::AppState;
|
||||
use crate::types::Did;
|
||||
use crate::types::{Did, Handle};
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ResolvedIdentity {
|
||||
pub did: Did,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub handle: Option<String>,
|
||||
pub handle: Option<Handle>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub pds_url: Option<String>,
|
||||
pub is_local: bool,
|
||||
@@ -52,7 +52,8 @@ pub async fn resolve_identity(
|
||||
let handle = did_doc
|
||||
.also_known_as
|
||||
.iter()
|
||||
.find_map(|alias| alias.strip_prefix("at://").map(|s| s.to_string()));
|
||||
.find_map(|alias| alias.strip_prefix("at://"))
|
||||
.and_then(|s| Handle::new(s).ok());
|
||||
|
||||
Ok(ResolvedIdentity {
|
||||
did: did.clone(),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod reserved;
|
||||
|
||||
use crate::types::{Did, Handle};
|
||||
use hickory_resolver::TokioAsyncResolver;
|
||||
use hickory_resolver::config::{ResolverConfig, ResolverOpts};
|
||||
use thiserror::Error;
|
||||
@@ -18,7 +19,7 @@ pub enum HandleResolutionError {
|
||||
DidMismatch { expected: String, actual: String },
|
||||
}
|
||||
|
||||
pub async fn resolve_handle_dns(handle: &str) -> Result<String, HandleResolutionError> {
|
||||
pub async fn resolve_handle_dns(handle: &Handle) -> Result<Did, HandleResolutionError> {
|
||||
let resolver = TokioAsyncResolver::tokio_from_system_conf().unwrap_or_else(|e| {
|
||||
tracing::warn!("falling back to default DNS resolvers: {}", e);
|
||||
TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default())
|
||||
@@ -41,7 +42,7 @@ pub async fn resolve_handle_dns(handle: &str) -> Result<String, HandleResolution
|
||||
.ok_or(HandleResolutionError::NotFound)
|
||||
}
|
||||
|
||||
pub async fn resolve_handle_http(handle: &str) -> Result<String, HandleResolutionError> {
|
||||
pub async fn resolve_handle_http(handle: &Handle) -> Result<Did, HandleResolutionError> {
|
||||
let url = format!("https://{}/.well-known/atproto-did", handle);
|
||||
let client = crate::api::proxy_client::handle_resolution_client();
|
||||
let response = client
|
||||
@@ -65,7 +66,7 @@ pub async fn resolve_handle_http(handle: &str) -> Result<String, HandleResolutio
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn resolve_handle(handle: &str) -> Result<String, HandleResolutionError> {
|
||||
pub async fn resolve_handle(handle: &Handle) -> Result<Did, HandleResolutionError> {
|
||||
match resolve_handle_dns(handle).await {
|
||||
Ok(did) => return Ok(did),
|
||||
Err(e) => {
|
||||
@@ -76,7 +77,7 @@ pub async fn resolve_handle(handle: &str) -> Result<String, HandleResolutionErro
|
||||
}
|
||||
|
||||
pub async fn verify_handle_ownership(
|
||||
handle: &str,
|
||||
handle: &Handle,
|
||||
expected_did: &str,
|
||||
) -> Result<(), HandleResolutionError> {
|
||||
let resolved_did = resolve_handle(handle).await?;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::cache::Cache;
|
||||
use crate::types::{Did, Handle};
|
||||
use base32::Alphabet;
|
||||
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
|
||||
use k256::ecdsa::{Signature, SigningKey, signature::Signer};
|
||||
@@ -518,7 +519,7 @@ pub struct GenesisResult {
|
||||
pub fn create_genesis_operation(
|
||||
signing_key: &SigningKey,
|
||||
configured_rotation_key: Option<&str>,
|
||||
handle: &str,
|
||||
handle: &Handle,
|
||||
pds_endpoint: &str,
|
||||
) -> Result<GenesisResult, PlcError> {
|
||||
let signing_did_key = signing_key_to_did_key(signing_key);
|
||||
@@ -780,9 +781,13 @@ mod tests {
|
||||
let key = SigningKey::random(&mut rand::thread_rng());
|
||||
let signing_did_key = signing_key_to_did_key(&key);
|
||||
let operator_key = "did:key:zQ3shWhelkOperatorKey";
|
||||
let result =
|
||||
create_genesis_operation(&key, Some(operator_key), "whelk.nel.pet", "https://nel.pet")
|
||||
.unwrap();
|
||||
let result = create_genesis_operation(
|
||||
&key,
|
||||
Some(operator_key),
|
||||
&crate::types::Handle::from("whelk.nel.pet".to_string()),
|
||||
"https://nel.pet",
|
||||
)
|
||||
.unwrap();
|
||||
let rotation_keys = result.signed_operation["rotationKeys"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
|
||||
@@ -65,8 +65,8 @@ pub struct RepoOp {
|
||||
pub struct IdentityFrame {
|
||||
pub did: Did,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub handle: Option<String>,
|
||||
pub seq: i64,
|
||||
pub handle: Option<Handle>,
|
||||
pub seq: SequenceNumber,
|
||||
pub time: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -245,8 +245,8 @@ fn format_identity_event(event: &SequencedEvent) -> Result<Vec<u8>, SyncFrameErr
|
||||
FrameType::Identity,
|
||||
&IdentityFrame {
|
||||
did: event.did.clone(),
|
||||
handle: event.handle.as_ref().map(|h| h.to_string()),
|
||||
seq: event.seq.as_i64(),
|
||||
handle: event.handle.clone(),
|
||||
seq: event.seq,
|
||||
time: format_atproto_time(event.created_at),
|
||||
},
|
||||
256,
|
||||
|
||||
@@ -3858,14 +3858,14 @@ impl<S: StorageIO + 'static> tranquil_db_traits::UserRepository for MetastoreCli
|
||||
&self,
|
||||
telegram_username: &str,
|
||||
chat_id: i64,
|
||||
handle: Option<&str>,
|
||||
handle: Option<&Handle>,
|
||||
) -> Result<Option<Uuid>, DbError> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.pool
|
||||
.send(MetastoreRequest::User(UserRequest::StoreTelegramChatId {
|
||||
telegram_username: telegram_username.to_owned(),
|
||||
chat_id,
|
||||
handle: handle.map(str::to_owned),
|
||||
handle: handle.map(|h| h.to_string()),
|
||||
tx,
|
||||
}))?;
|
||||
recv(rx).await
|
||||
@@ -3900,14 +3900,14 @@ impl<S: StorageIO + 'static> tranquil_db_traits::UserRepository for MetastoreCli
|
||||
&self,
|
||||
discord_username: &str,
|
||||
discord_id: &str,
|
||||
handle: Option<&str>,
|
||||
handle: Option<&Handle>,
|
||||
) -> Result<Option<Uuid>, DbError> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.pool
|
||||
.send(MetastoreRequest::User(UserRequest::StoreDiscordUserId {
|
||||
discord_username: discord_username.to_owned(),
|
||||
discord_id: discord_id.to_owned(),
|
||||
handle: handle.map(str::to_owned),
|
||||
handle: handle.map(|h| h.to_string()),
|
||||
tx,
|
||||
}))?;
|
||||
recv(rx).await
|
||||
@@ -4473,13 +4473,13 @@ impl<S: StorageIO + 'static> tranquil_db_traits::UserRepository for MetastoreCli
|
||||
async fn get_id_by_email_or_handle(
|
||||
&self,
|
||||
email: &str,
|
||||
handle: &str,
|
||||
handle: &Handle,
|
||||
) -> Result<Option<Uuid>, DbError> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.pool
|
||||
.send(MetastoreRequest::User(UserRequest::GetIdByEmailOrHandle {
|
||||
email: email.to_owned(),
|
||||
handle: handle.to_owned(),
|
||||
handle: handle.to_string(),
|
||||
tx,
|
||||
}))?;
|
||||
recv(rx).await
|
||||
|
||||
@@ -873,7 +873,7 @@ pub mod did_doc {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn extract_handle(doc: &serde_json::Value) -> Option<String> {
|
||||
pub fn extract_handle(doc: &serde_json::Value) -> Option<crate::Handle> {
|
||||
doc.get("alsoKnownAs")
|
||||
.and_then(|a| a.as_array())
|
||||
.and_then(|aliases| {
|
||||
@@ -881,7 +881,7 @@ pub mod did_doc {
|
||||
alias
|
||||
.as_str()
|
||||
.and_then(|s| s.strip_prefix("at://"))
|
||||
.map(|h| h.to_string())
|
||||
.and_then(|h| crate::Handle::new(h).ok())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user