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