Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/labrinth/src/database/advisory_lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub enum AdvisoryLock {
DelphiFile(DBFileId),
DelphiScan(Uuid),
DiscordRoleEmailCampaign,
PayPalAccount(String),
}

impl AdvisoryLock {
Expand Down Expand Up @@ -57,6 +58,9 @@ impl Display for AdvisoryLock {
Self::DiscordRoleEmailCampaign => {
formatter.write_str("discord_role_email_campaign")
}
Self::PayPalAccount(payer_id) => {
write!(formatter, "labrinth:paypal-account:{payer_id}")
}
}
}
}
45 changes: 35 additions & 10 deletions apps/labrinth/src/routes/internal/flows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1135,6 +1135,7 @@ pub struct AuthorizationInit {
pub token: Option<String>,
/// If the user is already logged in, and is linking a PayPal account,
/// this will be set to the user's auth token from the frontend.
/// Only first-party session tokens may authorize linking.
pub auth_token: Option<String>,
}
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
Expand Down Expand Up @@ -1170,18 +1171,26 @@ pub async fn init(
// This can happen when linking to a PayPal account (logging in) when already
// logged in.
let existing_user_id = if let Some(auth_token) = &info.auth_token {
get_user_record_from_bearer_token(
if !auth_token.starts_with("mra_") {
return Err(AuthenticationError::InvalidCredentials);
}

let (scopes, user) = get_user_record_from_bearer_token(
&req,
Some(auth_token),
&**client,
&redis,
&session_queue,
false,
)
.await
.ok()
.flatten()
.map(|(_scopes, user)| user.id)
.await?
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;

if !scopes.contains(Scopes::USER_AUTH_WRITE) {
return Err(AuthenticationError::InvalidCredentials);
}

Some(user.id)
} else {
None
};
Expand Down Expand Up @@ -1342,11 +1351,6 @@ pub async fn auth_callback(
.await
.wrap_err("failed to get user from provider")?;

let user_id_opt = provider
.get_user_id(&oauth_user.id, &**client)
.await
.wrap_err("failed to get user ID from provider")?;

let mut transaction = client
.begin()
.await
Expand All @@ -1367,6 +1371,22 @@ pub async fn auth_callback(
return Err(AuthenticationError::AccountLocked);
}

crate::database::advisory_lock::AdvisoryLock::PayPalAccount(
oauth_user.id.clone(),
)
.acquire(&mut transaction)
.await
.wrap_err("failed to lock PayPal account for linking")?;

let linked_user_id = provider
.get_user_id(&oauth_user.id, &mut transaction)
.await
.wrap_err("failed to get linked user ID from PayPal")?;

if linked_user_id.is_some_and(|id| id != existing_user_id) {
return Err(AuthenticationError::ProviderAlreadyLinked);
}

sqlx::query!(
"
UPDATE users
Expand Down Expand Up @@ -1398,6 +1418,11 @@ pub async fn auth_callback(
.json(serde_json::json!({ "url": url })));
}

let user_id_opt = provider
.get_user_id(&oauth_user.id, &**client)
.await
.wrap_err("failed to get user ID from provider")?;

if let Some(id) = user_id {
if user_id_opt.is_some() {
return Err(AuthenticationError::ProviderAlreadyLinked);
Expand Down
Loading