refactor: auth.js to auth.ts

This commit is contained in:
tdgao
2026-04-13 13:37:20 -06:00
parent 202bb20286
commit 6b73118cfc
20 changed files with 213 additions and 187 deletions
+2
View File
@@ -13,6 +13,8 @@ import { I18nDebugPanel, NotificationPanel } from '@modrinth/ui'
import ModrinthLoadingIndicator from '~/components/ui/modrinth-loading-indicator.ts'
import { setupProviders } from '~/providers/setup.ts'
import { useAuth } from './composables/auth'
const auth = await useAuth()
setupProviders(auth)
</script>
@@ -164,9 +164,7 @@ defineExpose({
show,
})
const auth = (await useAuth()) as Ref<{
user: { id: string; username: string; avatar_url: string } | null
}>
const auth = await useAuth()
const messages = defineMessages({
title: {
@@ -120,7 +120,7 @@ import { computed, onMounted, ref, watch } from 'vue'
import RevenueInputField from '@/components/ui/dashboard/RevenueInputField.vue'
import WithdrawFeeBreakdown from '@/components/ui/dashboard/WithdrawFeeBreakdown.vue'
import { getAuthUrl, removeAuthProvider, useAuth } from '@/composables/auth.js'
import { getAuthUrl, removeAuthProvider, useAuth } from '@/composables/auth.ts'
import { useWithdrawContext } from '@/providers/creator-withdraw.ts'
const { withdrawData, maxWithdrawAmount, availableMethods, calculateFees, saveStateToStorage } =
@@ -193,7 +193,6 @@ async function saveVenmoHandle() {
},
})
// @ts-expect-error auth.js is not typed
await useAuth(auth.value.token)
initialVenmoHandle.value = venmoHandle.value.trim()
@@ -358,8 +358,8 @@ import { computed, onMounted, ref, watch } from 'vue'
import RevenueInputField from '@/components/ui/dashboard/RevenueInputField.vue'
import WithdrawFeeBreakdown from '@/components/ui/dashboard/WithdrawFeeBreakdown.vue'
import { useAuth } from '@/composables/auth.js'
import { useWithdrawContext } from '@/providers/creator-withdraw.ts'
import { useAuth } from '~/composables/auth.ts'
const debug = useDebugLogger('TremendousDetailsStage')
const { withdrawData, maxWithdrawAmount, availableMethods, paymentOptions, calculateFees } =
@@ -190,7 +190,6 @@ import {
LinkIcon,
} from '@modrinth/assets'
import { type ExtendedReport, reportQuickReplies } from '@modrinth/moderation'
import { type OverflowMenuOption, useFormatDateTime } from '@modrinth/ui'
import {
Avatar,
ButtonStyled,
@@ -198,6 +197,8 @@ import {
getProjectTypeIcon,
injectNotificationManager,
OverflowMenu,
type OverflowMenuOption,
useFormatDateTime,
useRelativeTime,
} from '@modrinth/ui'
import { formatProjectType } from '@modrinth/utils'
@@ -26,11 +26,11 @@ import {
getProjectTypeIcon,
injectModrinthClient,
injectNotificationManager,
NavTabs,
OverflowMenu,
type OverflowMenuOption,
useFormatDateTime,
} from '@modrinth/ui'
import { NavTabs } from '@modrinth/ui'
import {
capitalizeString,
formatProjectType,
@@ -87,7 +87,7 @@ const currentProjectId = computed(() => projectV3.value?.id)
const { selectedProjectId, selectedVersionId } = injectServerCompatibilityContext()
const { labrinth } = injectModrinthClient()
const { addNotification } = injectNotificationManager()
const auth = (await useAuth()) as { user?: { id: string } }
const auth = await useAuth()
interface VersionInfo {
id: string
-157
View File
@@ -1,157 +0,0 @@
export const useAuth = async (oldToken = null) => {
const auth = useState('auth', () => ({
user: null,
token: '',
headers: {},
}))
if (!auth.value.user || oldToken) {
auth.value = await initAuth(oldToken)
}
return auth
}
export const initAuth = async (oldToken = null) => {
const auth = {
user: null,
token: '',
}
if (oldToken === 'none') {
return auth
}
const route = useRoute()
const authCookie = useCookie('auth-token', {
maxAge: 60 * 60 * 24 * 365 * 10,
sameSite: 'lax',
secure: true,
httpOnly: false,
path: '/',
})
if (oldToken) {
authCookie.value = oldToken
}
if (route.query.code && !route.fullPath.includes('new_account=true')) {
authCookie.value = route.query.code
}
if (route.fullPath.includes('new_account=true') && route.path !== '/auth/welcome') {
const redirect = route.path.startsWith('/auth/') ? null : route.fullPath
await navigateTo(
`/auth/welcome?authToken=${route.query.code}${
redirect ? `&redirect=${encodeURIComponent(redirect)}` : ''
}`,
)
}
if (authCookie.value) {
auth.token = authCookie.value
if (!auth.token || !auth.token.startsWith('mra_')) {
return auth
}
try {
auth.user = await useBaseFetch(
'user',
{
headers: {
Authorization: auth.token,
},
},
true,
)
} catch {
/* empty */
}
}
if (!auth.user && auth.token) {
try {
const session = await useBaseFetch(
'session/refresh',
{
method: 'POST',
headers: {
Authorization: auth.token,
},
},
true,
)
auth.token = session.session
authCookie.value = auth.token
auth.user = await useBaseFetch(
'user',
{
headers: {
Authorization: auth.token,
},
},
true,
)
} catch {
authCookie.value = null
}
}
return auth
}
export const getSignInRedirectPath = (route) => {
const fullPath = route.fullPath
if (fullPath === '/auth' || fullPath.startsWith('/auth/')) {
return '/dashboard'
}
return fullPath
}
export const getSignInRouteObj = (route, redirectOverride) => ({
path: '/auth/sign-in',
query: {
redirect: redirectOverride ?? getSignInRedirectPath(route),
},
})
export const getAuthUrl = (provider, redirect = '/dashboard') => {
const config = useRuntimeConfig()
const route = useNativeRoute()
const fullURL = route.query.launcher
? getLauncherRedirectUrl(route)
: `${config.public.siteUrl}/auth/sign-in?redirect=${encodeURIComponent(redirect)}`
return `${config.public.apiBaseUrl}auth/init?provider=${provider}&url=${encodeURIComponent(fullURL)}`
}
export const removeAuthProvider = async (provider) => {
startLoading()
const auth = await useAuth()
await useBaseFetch('auth/provider', {
method: 'DELETE',
body: {
provider,
},
})
await useAuth(auth.value.token)
stopLoading()
}
export const getLauncherRedirectUrl = (route) => {
const usesLocalhostRedirectionScheme =
['4', '6'].includes(route.query.ipver) && Number(route.query.port) < 65536
return usesLocalhostRedirectionScheme
? `http://${route.query.ipver === '4' ? '127.0.0.1' : '[::1]'}:${route.query.port}`
: `https://launcher-files.modrinth.com`
}
+182
View File
@@ -0,0 +1,182 @@
import type { Labrinth } from '@modrinth/api-client'
import type { LocationQueryValue, RouteLocationNormalizedLoaded } from 'vue-router'
import type { CookieOptions } from '#app'
type AuthState = {
user: Labrinth.Users.v2.User | null
token: string
}
type QueryValue = LocationQueryValue | LocationQueryValue[] | undefined
type FullPathRoute = Pick<RouteLocationNormalizedLoaded, 'fullPath'>
type LauncherRoute = Pick<RouteLocationNormalizedLoaded, 'query'>
const AUTH_COOKIE_OPTIONS = {
maxAge: 60 * 60 * 24 * 365 * 10,
sameSite: 'lax',
secure: true,
httpOnly: false,
path: '/',
} satisfies CookieOptions<string | null>
const getQueryString = (value: QueryValue) => {
if (Array.isArray(value)) {
return value[0] ?? null
}
return value ?? null
}
export const useAuth = async (oldToken: string | null | undefined = null) => {
const auth = useState<AuthState>('auth', () => ({
user: null,
token: '',
}))
if (!auth.value.user || oldToken) {
auth.value = await initAuth(oldToken)
}
return auth
}
export const initAuth = async (oldToken: string | null | undefined = null) => {
const auth: AuthState = {
user: null,
token: '',
}
if (oldToken === 'none') {
return auth
}
const route = useRoute()
const authCookie = useCookie<string | null>('auth-token', AUTH_COOKIE_OPTIONS)
const authCode = getQueryString(route.query.code)
if (oldToken) {
authCookie.value = oldToken
}
if (authCode && !route.fullPath.includes('new_account=true')) {
authCookie.value = authCode
}
if (route.fullPath.includes('new_account=true') && route.path !== '/auth/welcome') {
const redirect = route.path.startsWith('/auth/') ? null : route.fullPath
await navigateTo(
`/auth/welcome?authToken=${authCode ?? ''}${
redirect ? `&redirect=${encodeURIComponent(redirect)}` : ''
}`,
)
}
if (authCookie.value) {
auth.token = authCookie.value
if (!auth.token || !auth.token.startsWith('mra_')) {
return auth
}
try {
auth.user = (await useBaseFetch(
'user',
{
headers: {
Authorization: auth.token,
},
},
true,
)) as Labrinth.Users.v2.User
} catch {
/* empty */
}
}
if (!auth.user && auth.token) {
try {
const session = (await useBaseFetch(
'session/refresh',
{
method: 'POST',
headers: {
Authorization: auth.token,
},
},
true,
)) as { session: string }
auth.token = session.session
authCookie.value = auth.token
auth.user = (await useBaseFetch(
'user',
{
headers: {
Authorization: auth.token,
},
},
true,
)) as Labrinth.Users.v2.User
} catch {
authCookie.value = null
}
}
return auth
}
export const getSignInRedirectPath = (route: FullPathRoute) => {
const fullPath = route.fullPath
if (fullPath === '/auth' || fullPath.startsWith('/auth/')) {
return '/dashboard'
}
return fullPath
}
export const getSignInRouteObj = (route: FullPathRoute, redirectOverride?: string | null) => ({
path: '/auth/sign-in',
query: {
redirect: redirectOverride ?? getSignInRedirectPath(route),
},
})
export const getAuthUrl = (provider: string, redirect = '/dashboard') => {
const config = useRuntimeConfig()
const route = useNativeRoute()
const launcher = getQueryString(route.query.launcher)
const fullURL = launcher
? getLauncherRedirectUrl(route)
: `${config.public.siteUrl}/auth/sign-in?redirect=${encodeURIComponent(redirect)}`
return `${config.public.apiBaseUrl}auth/init?provider=${provider}&url=${encodeURIComponent(fullURL)}`
}
export const removeAuthProvider = async (provider: string) => {
startLoading()
const auth = await useAuth()
await useBaseFetch('auth/provider', {
method: 'DELETE',
body: {
provider,
},
})
await useAuth(auth.value.token)
stopLoading()
}
export const getLauncherRedirectUrl = (route: LauncherRoute) => {
const ipver = getQueryString(route.query.ipver)
const port = Number(getQueryString(route.query.port))
const usesLocalhostRedirectionScheme = ['4', '6'].includes(ipver ?? '') && port < 65536
return usesLocalhostRedirectionScheme
? `http://${ipver === '4' ? '127.0.0.1' : '[::1]'}:${port}`
: 'https://launcher-files.modrinth.com'
}
+1 -1
View File
@@ -762,7 +762,7 @@ import CollectionCreateModal from '~/components/ui/create/CollectionCreateModal.
import OrganizationCreateModal from '~/components/ui/create/OrganizationCreateModal.vue'
import ProjectCreateModal from '~/components/ui/create/ProjectCreateModal.vue'
import ModrinthFooter from '~/components/ui/ModrinthFooter.vue'
import { getSignInRouteObj } from '~/composables/auth.js'
import { getSignInRouteObj } from '~/composables/auth.ts'
import { errors as generatedStateErrors } from '~/generated/state.json'
import { getProjectTypeMessage } from '~/utils/i18n-project-type.ts'
+14 -11
View File
@@ -224,6 +224,9 @@
"auth.authorize.redirect-url": {
"message": "You will be redirected to <redirect-url>{url}</redirect-url>"
},
"auth.continue-with-provider": {
"message": "Continue with {provider}"
},
"auth.reset-password.method-choice.action": {
"message": "Send recovery email"
},
@@ -269,23 +272,26 @@
"auth.sign-in.additional-options": {
"message": "<forgot-password-link>Forgot password</forgot-password-link> • Don't have an account? <create-account-link>Sign up</create-account-link>"
},
"auth.sign-in.continue-with-email": {
"message": "Continue with Email"
},
"auth.sign-in.sign-in-with": {
"message": "Sign into Modrinth"
},
"auth.sign-in.title": {
"message": "Sign In"
},
"auth.sign-in.use-password": {
"message": "Or use a password"
},
"auth.sign-up.action.create-account": {
"message": "Create account"
"auth.sign-up.continue-with-email": {
"message": "Continue with Email"
},
"auth.sign-up.legal-dislaimer": {
"message": "By creating an account, you agree to Modrinth's <terms-link>Terms</terms-link> and <privacy-policy-link>Privacy Policy</privacy-policy-link>."
},
"auth.sign-up.notification.password-mismatch.text": {
"message": "Passwords do not match!"
"auth.sign-up.show-fewer-options": {
"message": "Show fewer options"
},
"auth.sign-up.show-other-options": {
"message": "Show other options"
},
"auth.sign-up.sign-in-option.title": {
"message": "Already have an account?"
@@ -296,11 +302,8 @@
"auth.sign-up.title": {
"message": "Sign Up"
},
"auth.sign-up.title.create-account": {
"message": "Or create an account yourself"
},
"auth.sign-up.title.sign-up-with": {
"message": "Sign up with"
"message": "Create an Account"
},
"auth.verify-email.action.account-settings": {
"message": "Account settings"
+1 -1
View File
@@ -1121,7 +1121,7 @@ import CollectionCreateModal from '~/components/ui/create/CollectionCreateModal.
import MessageBanner from '~/components/ui/MessageBanner.vue'
import ModerationChecklist from '~/components/ui/moderation/checklist/ModerationChecklist.vue'
import ProjectMemberHeader from '~/components/ui/ProjectMemberHeader.vue'
import { getSignInRouteObj } from '~/composables/auth.js'
import { getSignInRouteObj } from '~/composables/auth.ts'
import { saveFeatureFlags } from '~/composables/featureFlags.ts'
import { STALE_TIME, STALE_TIME_LONG } from '~/composables/queries/project'
import { versionQueryOptions } from '~/composables/queries/version'
@@ -308,7 +308,7 @@ import {
import { useTemplateRef } from 'vue'
import CreateProjectVersionModal from '~/components/ui/create-project-version/CreateProjectVersionModal.vue'
import { getSignInRouteObj } from '~/composables/auth.js'
import { getSignInRouteObj } from '~/composables/auth.ts'
import { reportVersion } from '~/utils/report-helpers.ts'
const route = useRoute()
@@ -443,7 +443,7 @@ import { formatBytes, renderHighlightedString } from '@modrinth/utils'
import Breadcrumbs from '~/components/ui/Breadcrumbs.vue'
import CreateProjectVersionModal from '~/components/ui/create-project-version/CreateProjectVersionModal.vue'
import Modal from '~/components/ui/Modal.vue'
import { getSignInRouteObj } from '~/composables/auth.js'
import { getSignInRouteObj } from '~/composables/auth.ts'
import { useImageUpload } from '~/composables/image-upload.ts'
import { inferVersionInfo } from '~/helpers/infer'
import { createDataPackVersion } from '~/helpers/package.js'
@@ -269,7 +269,7 @@ import {
import { onMounted, useTemplateRef } from 'vue'
import CreateProjectVersionModal from '~/components/ui/create-project-version/CreateProjectVersionModal.vue'
import { getSignInRouteObj } from '~/composables/auth.js'
import { getSignInRouteObj } from '~/composables/auth.ts'
import { reportVersion } from '~/utils/report-helpers.ts'
const route = useRoute()
@@ -95,7 +95,6 @@ import {
import { useQuery } from '@tanstack/vue-query'
import { computed } from 'vue'
import { useAuth } from '@/composables/auth.js'
import { useScopes } from '@/composables/auth/scopes.ts'
const client = injectModrinthClient()
@@ -54,7 +54,6 @@ import { Avatar, injectModrinthClient } from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import OrganizationCreateModal from '~/components/ui/create/OrganizationCreateModal.vue'
import { useAuth } from '~/composables/auth.js'
const createOrgModal = ref(null)
+1 -1
View File
@@ -478,7 +478,7 @@ import SteamIcon from 'assets/icons/auth/sso-steam.svg'
import QrcodeVue from 'qrcode.vue'
import Modal from '~/components/ui/Modal.vue'
import { getAuthUrl, removeAuthProvider } from '~/composables/auth.js'
import { getAuthUrl, removeAuthProvider } from '~/composables/auth.ts'
definePageMeta({
middleware: 'auth',
+1 -1
View File
@@ -521,7 +521,7 @@ import UpToDate from '~/assets/images/illustrations/up_to_date.svg?component'
import AdPlaceholder from '~/components/ui/AdPlaceholder.vue'
import CollectionCreateModal from '~/components/ui/create/CollectionCreateModal.vue'
import ModalCreation from '~/components/ui/create/ProjectCreateModal.vue'
import { getSignInRouteObj } from '~/composables/auth.js'
import { getSignInRouteObj } from '~/composables/auth.ts'
import { reportUser } from '~/utils/report-helpers.ts'
const data = useNuxtApp()
+1 -1
View File
@@ -2,7 +2,7 @@ import type { Labrinth } from '@modrinth/api-client'
import { type AuthProvider, provideAuth } from '@modrinth/ui'
import { ref, watchEffect } from 'vue'
import { getSignInRedirectPath } from '~/composables/auth.js'
import { getSignInRedirectPath } from '~/composables/auth.ts'
export function setupAuthProvider(auth: Awaited<ReturnType<typeof useAuth>>) {
const router = useRouter()