From 224c28f29dda85aba6172f95554505c8cfb61607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-Xavier=20Talbot?= <108630700+fetchfern@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:20:38 -0400 Subject: [PATCH] feat: more details linked account info for support/moderation (#6897) * feat: show discord + steam ID + github profile in "Show details" user modal * pnpm intl:extract --- apps/frontend/src/locales/en-US/index.json | 15 +++ apps/frontend/src/pages/user/[user].vue | 106 +++++++++++++++++- apps/labrinth/src/models/v2/user.rs | 1 - apps/labrinth/src/models/v3/users.rs | 9 +- apps/labrinth/src/routes/v3/users.rs | 10 +- .../api-client/src/modules/labrinth/types.ts | 2 + 6 files changed, 139 insertions(+), 4 deletions(-) diff --git a/apps/frontend/src/locales/en-US/index.json b/apps/frontend/src/locales/en-US/index.json index 9f6ce185b..49b279eb2 100644 --- a/apps/frontend/src/locales/en-US/index.json +++ b/apps/frontend/src/locales/en-US/index.json @@ -3374,6 +3374,15 @@ "profile.collection.projects-count": { "message": "{count, plural, one {# project} other {# projects}}" }, + "profile.details.error.github-popup-blocked": { + "message": "Allow pop-ups for Modrinth, then try again." + }, + "profile.details.error.github-profile-message": { + "message": "The GitHub profile could not be retrieved. Please try again." + }, + "profile.details.error.github-profile-title": { + "message": "Unable to open GitHub profile" + }, "profile.details.label.auth-providers": { "message": "Auth providers" }, @@ -3389,9 +3398,15 @@ "profile.details.label.has-totp": { "message": "Has TOTP" }, + "profile.details.label.loading-github-profile": { + "message": "Loading..." + }, "profile.details.label.payment-methods": { "message": "Payment methods" }, + "profile.details.label.view-github-profile": { + "message": "View profile" + }, "profile.details.tooltip.email-not-verified": { "message": "Email not verified" }, diff --git a/apps/frontend/src/pages/user/[user].vue b/apps/frontend/src/pages/user/[user].vue index 4ebf1e763..0a86596ec 100644 --- a/apps/frontend/src/pages/user/[user].vue +++ b/apps/frontend/src/pages/user/[user].vue @@ -71,7 +71,37 @@ {{ formatMessage(messages.authProvidersLabel) }} - {{ user.auth_providers.join(', ') }} +
+ + {{ authProviderNames[provider] ?? provider }} + + ({{ user.discord_id }}) + + + + ({{ user.steam_id }}) + + +
@@ -437,6 +467,26 @@ const messages = defineMessages({ id: 'profile.details.label.auth-providers', defaultMessage: 'Auth providers', }, + viewGithubProfileLabel: { + id: 'profile.details.label.view-github-profile', + defaultMessage: 'View profile', + }, + loadingGithubProfileLabel: { + id: 'profile.details.label.loading-github-profile', + defaultMessage: 'Loading...', + }, + githubProfileErrorTitle: { + id: 'profile.details.error.github-profile-title', + defaultMessage: 'Unable to open GitHub profile', + }, + githubProfileErrorMessage: { + id: 'profile.details.error.github-profile-message', + defaultMessage: 'The GitHub profile could not be retrieved. Please try again.', + }, + githubPopupBlockedMessage: { + id: 'profile.details.error.github-popup-blocked', + defaultMessage: 'Allow pop-ups for Modrinth, then try again.', + }, paymentMethodsLabel: { id: 'profile.details.label.payment-methods', defaultMessage: 'Payment methods', @@ -518,6 +568,16 @@ const messages = defineMessages({ const client = injectModrinthClient() const userId = useRouteId('user') +const authProviderNames = { + github: 'GitHub', + discord: 'Discord', + microsoft: 'Microsoft', + gitlab: 'GitLab', + google: 'Google', + steam: 'Steam', + paypal: 'PayPal', +} +const isLoadingGithubProfile = ref(false) const { data: user, @@ -664,6 +724,50 @@ async function copyPermalink() { await navigator.clipboard.writeText(`${config.public.siteUrl}/user/${user.value.id}`) } +async function openGithubProfile() { + const githubId = user.value?.github_id + if (!githubId || isLoadingGithubProfile.value) return + + const profileWindow = window.open('about:blank', '_blank') + if (!profileWindow) { + addNotification({ + type: 'error', + title: formatMessage(messages.githubProfileErrorTitle), + message: formatMessage(messages.githubPopupBlockedMessage), + }) + return + } + + profileWindow.opener = null + isLoadingGithubProfile.value = true + + try { + const githubUser = await client.request(`/${githubId}`, { + api: 'https://api.github.com', + version: 'user', + method: 'GET', + headers: { 'Content-Type': '' }, + skipAuth: true, + }) + + if (!githubUser?.login) { + throw new Error('GitHub user response did not include a login') + } + + profileWindow.location.replace(`https://github.com/${encodeURIComponent(githubUser.login)}`) + } catch (error) { + profileWindow.close() + console.error('Failed to retrieve GitHub profile:', error) + addNotification({ + type: 'error', + title: formatMessage(messages.githubProfileErrorTitle), + message: formatMessage(messages.githubProfileErrorMessage), + }) + } finally { + isLoadingGithubProfile.value = false + } +} + function reportProfileFromHeader() { if (!user.value) return if (auth.value.user) { diff --git a/apps/labrinth/src/models/v2/user.rs b/apps/labrinth/src/models/v2/user.rs index d8fae78f3..35622abf8 100644 --- a/apps/labrinth/src/models/v2/user.rs +++ b/apps/labrinth/src/models/v2/user.rs @@ -24,7 +24,6 @@ pub struct LegacyUser { pub has_totp: Option, pub payout_data: Option, // this was changed in v3, but not ones we want to keep out of v2 - // DEPRECATED. Always returns None pub github_id: Option, } diff --git a/apps/labrinth/src/models/v3/users.rs b/apps/labrinth/src/models/v3/users.rs index 456faa87e..fe5de4c18 100644 --- a/apps/labrinth/src/models/v3/users.rs +++ b/apps/labrinth/src/models/v3/users.rs @@ -70,8 +70,11 @@ pub struct User { #[serde(skip_serializing_if = "Option::is_none")] pub moderation_notes: Option>, - // DEPRECATED. Always returns None pub github_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub discord_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub steam_id: Option, } #[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] @@ -119,6 +122,8 @@ impl From for User { has_password: None, has_totp: None, github_id: None, + discord_id: None, + steam_id: None, stripe_customer_id: None, allow_friend_requests: None, eligibility_verified_at: None, @@ -180,6 +185,8 @@ impl User { has_password: Some(db_user.password.is_some()), has_totp: Some(db_user.totp_secret.is_some()), github_id: None, + discord_id: None, + steam_id: None, payout_data: Some(UserPayoutData { paypal_address: db_user.paypal_email, paypal_country: db_user.paypal_country, diff --git a/apps/labrinth/src/routes/v3/users.rs b/apps/labrinth/src/routes/v3/users.rs index 1ee9de672..59a452c4d 100644 --- a/apps/labrinth/src/routes/v3/users.rs +++ b/apps/labrinth/src/routes/v3/users.rs @@ -481,7 +481,15 @@ pub async fn user_get( let user_id = data.id; let mut response: crate::models::users::User = if is_admin { - crate::models::users::User::from_full(data) + let github_id = + data.github_id.and_then(|id| u64::try_from(id).ok()); + let discord_id = data.discord_id.map(|id| id.to_string()); + let steam_id = data.steam_id.map(|id| id.to_string()); + let mut user = crate::models::users::User::from_full(data); + user.github_id = github_id; + user.discord_id = discord_id; + user.steam_id = steam_id; + user } else { data.into() }; diff --git a/packages/api-client/src/modules/labrinth/types.ts b/packages/api-client/src/modules/labrinth/types.ts index c3c7155c4..337d1d125 100644 --- a/packages/api-client/src/modules/labrinth/types.ts +++ b/packages/api-client/src/modules/labrinth/types.ts @@ -1663,6 +1663,8 @@ export namespace Labrinth { allow_friend_requests?: boolean moderation_notes?: Common.ModerationNote | null github_id?: number + discord_id?: string + steam_id?: string } export type SearchUser = {