@@ -116,7 +116,7 @@
Create
+ Enter your email below and we'll send a recovery link to allow you to recover your account.
+ Enter your new password below to gain access to your account.
+ Don't have an account yet?
+
+ By creating an account, you agree to Modrinth's
+
+ Already have an account yet?
+ Your email is already verified! Your email address has been successfully verified!
+ We were unable to verify your email.
+
+ Try re-sending the verification email through the button below.
+
+
+ Try re-sending the verification email through your dashboard by signing in.
+
+
+ Thank you for creating an account. You can now follow and create projects, receive updates
+ about your favorite projects, and more!
+
+ By creating an account, you agree to Modrinth's
+
- Revoking your Modrinth token can have unintended consequences. Please be aware that the
- following could break:
- Your account information is not displayed publicly. If you are willing to continue, complete the following steps: Once you have completed those steps, press the continue button below.
-
- This will log you out of Modrinth, however, when you log back in, your token will be
- regenerated.
-
- The code entered is incorrect!
+ Two-factor authentication keeps your account secure by requiring access to a second
+ device in order to sign in.
+
+ If the QR code does not scan, you can manually enter the secret:
+ {{ twoFactorSecret }}
+ The code entered is incorrect!
+ Download and save these back-up codes in a safe place. You can use these in-place of a
+ 2FA code if you ever lose access to your device! You should protect these codes like
+ your password.
+ Backup codes can only be used once. Visit your user profile to edit your profile information. Your account information is not displayed publicly.
- Your authorization token can be used with the Modrinth API, the Minotaur Gradle plugin, and
- other applications that interact with Modrinth's API. Be sure to keep this secret!
-
+ PATs can be used to access Modrinth's API. For more information, see
+ Modrinth's API documentation. They
+ can be created and revoked at any time.
+
+ Here are all the devices that are currently logged in with your Modrinth account. You can log
+ out of each one individually.
+ Upload additional files
-
+
Used for additional files such as required/optional resource packs
Used for files such as sources or Javadocs.
@@ -566,14 +566,14 @@
v-if="isEditing"
v-model="version.loaders"
:options="
- $tag.loaders
+ tags.loaders
.filter((x) =>
x.supported_project_types.includes(project.actualProjectType.toLowerCase())
)
.map((it) => it.name)
"
:custom-label="(value) => $formatCategory(value)"
- :loading="$tag.loaders.length === 0"
+ :loading="tags.loaders.length === 0"
:multiple="true"
:searchable="false"
:show-no-results="false"
@@ -593,12 +593,12 @@
v-model="version.game_versions"
:options="
showSnapshots
- ? $tag.gameVersions.map((x) => x.version)
- : $tag.gameVersions
+ ? tags.gameVersions.map((x) => x.version)
+ : tags.gameVersions
.filter((it) => it.version_type === 'release')
.map((x) => x.version)
"
- :loading="$tag.gameVersions.length === 0"
+ :loading="tags.gameVersions.length === 0"
:multiple="true"
:searchable="true"
:show-no-results="false"
@@ -772,6 +772,9 @@ export default defineNuxtComponent({
const data = useNuxtApp()
const route = useRoute()
+ const auth = await useAuth()
+ const tags = useTags()
+
const path = route.name.split('-')
const mode = path[path.length - 1]
@@ -828,7 +831,7 @@ export default defineNuxtComponent({
const inferredData = await inferVersionInfo(
replaceFile,
props.project,
- data.$tag.gameVersions
+ tags.value.gameVersions
)
version = {
@@ -895,6 +898,8 @@ export default defineNuxtComponent({
const order = ['required', 'optional', 'incompatible', 'embedded']
return {
+ auth,
+ tags,
fileTypes: ref(fileTypes),
oldFileTypes: ref(oldFileTypes),
isCreating: ref(isCreating),
@@ -1110,7 +1115,6 @@ export default defineNuxtComponent({
body: formData,
headers: {
'Content-Disposition': formData,
- Authorization: this.$auth.token,
},
})
}
@@ -1135,13 +1139,11 @@ export default defineNuxtComponent({
}
}),
},
- ...this.$defaultHeaders(),
})
for (const hash of this.deleteFiles) {
await useBaseFetch(`version_file/${hash}?version_id=${this.version.id}`, {
method: 'DELETE',
- ...this.$defaultHeaders(),
})
}
@@ -1246,7 +1248,6 @@ export default defineNuxtComponent({
body: formData,
headers: {
'Content-Disposition': formData,
- Authorization: this.$auth.token,
},
})
@@ -1263,7 +1264,6 @@ export default defineNuxtComponent({
await useBaseFetch(`version/${this.version.id}`, {
method: 'DELETE',
- ...this.$defaultHeaders(),
})
await this.resetProjectVersions()
@@ -1279,7 +1279,7 @@ export default defineNuxtComponent({
this.version,
this.primaryFile,
this.members,
- this.$tag.gameVersions,
+ this.tags.gameVersions,
this.packageLoaders
)
@@ -1324,12 +1324,9 @@ export default defineNuxtComponent({
},
async resetProjectVersions() {
const [versions, featuredVersions, dependencies] = await Promise.all([
- useBaseFetch(`project/${this.version.project_id}/version`, this.$defaultHeaders()),
- useBaseFetch(
- `project/${this.version.project_id}/version?featured=true`,
- this.$defaultHeaders()
- ),
- useBaseFetch(`project/${this.version.project_id}/dependencies`, this.$defaultHeaders()),
+ useBaseFetch(`project/${this.version.project_id}/version`),
+ useBaseFetch(`project/${this.version.project_id}/version?featured=true`),
+ useBaseFetch(`project/${this.version.project_id}/dependencies`),
])
const newCreatedVersions = this.$computeVersions(versions, this.members)
diff --git a/pages/auth.vue b/pages/auth.vue
new file mode 100644
index 0000000000..beb5d8ffb0
--- /dev/null
+++ b/pages/auth.vue
@@ -0,0 +1,156 @@
+
+ Reset your password
+
+ Continue with
+
+ Create your account
+
+ Email already verified
+ Email verification
+ Email verification failed
+ Welcome to Modrinth!
+
- {{ $auth.user.username }}
+ {{ auth.user.username }}
- Reports you've filed
-
-
+
-
-
-
+ Scan the QR code with Authy,
+
+ Microsoft Authenticator, or any other 2FA app to begin.
+
+
+
+ User profile
Account information
-
-
-
-
- Account security
- Authorization token
- Personal Access Tokens
+ Sessions
+
+ If you see an entry you don't recognize, log out of that device and change your Modrinth
+ account password immediately.
+
-
+
You don't have any projects.
Would you like to
create one?
@@ -242,7 +232,6 @@ import ProjectCard from '~/components/ui/ProjectCard.vue'
import Badge from '~/components/ui/Badge.vue'
import Promotion from '~/components/ads/Promotion.vue'
-import GitHubIcon from '~/assets/images/utils/github.svg'
import ReportIcon from '~/assets/images/utils/report.svg'
import SunriseIcon from '~/assets/images/utils/sunrise.svg'
import DownloadIcon from '~/assets/images/utils/download.svg'
@@ -266,23 +255,25 @@ import Avatar from '~/components/ui/Avatar.vue'
const data = useNuxtApp()
const route = useRoute()
+const auth = await useAuth()
+const cosmetics = useCosmetics()
+const tags = useTags()
let user, projects
try {
;[{ data: user }, { data: projects }] = await Promise.all([
- useAsyncData(`user/${route.params.id}`, () =>
- useBaseFetch(`user/${route.params.id}`, data.$defaultHeaders())
- ),
+ useAsyncData(`user/${route.params.id}`, () => useBaseFetch(`user/${route.params.id}`)),
useAsyncData(
`user/${route.params.id}/projects`,
- () => useBaseFetch(`user/${route.params.id}/projects`, data.$defaultHeaders()),
+ () => useBaseFetch(`user/${route.params.id}/projects`),
{
transform: (projects) => {
for (const project of projects) {
project.categories = project.categories.concat(project.loaders)
project.project_type = data.$getProjectTypeForUrl(
project.project_type,
- project.categories
+ project.categories,
+ tags.value
)
}
@@ -307,12 +298,6 @@ if (!user.value) {
})
}
-let githubUrl
-try {
- const githubUser = await $fetch(`https://api.github.com/user/` + user.value.github_id)
- githubUrl = ref(githubUser.html_url)
-} catch {}
-
if (user.value.username !== route.params.id) {
await navigateTo(`/user/${user.value.username}`, { redirectCode: 301 })
}
@@ -369,13 +354,12 @@ async function saveChanges() {
try {
if (icon.value) {
await useBaseFetch(
- `user/${data.$auth.user.id}/icon?ext=${
+ `user/${auth.value.user.id}/icon?ext=${
icon.value.type.split('/')[icon.value.type.split('/').length - 1]
}`,
{
method: 'PATCH',
body: icon.value,
- ...data.$defaultHeaders(),
}
)
}
@@ -384,16 +368,15 @@ async function saveChanges() {
email: user.value.email,
bio: user.value.bio,
}
- if (user.value.username !== data.$auth.user.username) {
+ if (user.value.username !== auth.value.user.username) {
reqData.username = user.value.username
}
- await useBaseFetch(`user/${data.$auth.user.id}`, {
+ await useBaseFetch(`user/${auth.value.user.id}`, {
method: 'PATCH',
body: reqData,
- ...data.$defaultHeaders(),
})
- await useAuth(data.$auth.token)
+ await useAuth(auth.value.token)
isEditing.value = false
} catch (err) {
@@ -409,9 +392,9 @@ async function saveChanges() {
}
function cycleSearchDisplayMode() {
- data.$cosmetics.searchDisplayMode.user = data.$cycleValue(
- data.$cosmetics.searchDisplayMode.user,
- data.$tag.projectViewModes
+ cosmetics.value.searchDisplayMode.user = data.$cycleValue(
+ cosmetics.value.searchDisplayMode.user,
+ tags.value.projectViewModes
)
saveCosmetics()
}
@@ -504,10 +487,6 @@ export default defineNuxtComponent({
cursor: default;
}
-.github-button {
- display: inline-flex;
-}
-
.inputs {
margin-bottom: 1rem;
diff --git a/plugins/1.theme.js b/plugins/1.theme.js
index 6ed6b4be7f..29fa1cf1c1 100644
--- a/plugins/1.theme.js
+++ b/plugins/1.theme.js
@@ -1,4 +1,6 @@
-export default defineNuxtPlugin((nuxtApp) => {
+export default defineNuxtPlugin(async (nuxtApp) => {
+ await useAuth()
+ await useUser()
const themeStore = useTheme()
nuxtApp.hook('app:mounted', () => {
diff --git a/plugins/2.state.js b/plugins/2.state.js
deleted file mode 100644
index 85504747c5..0000000000
--- a/plugins/2.state.js
+++ /dev/null
@@ -1,11 +0,0 @@
-export default defineNuxtPlugin(async (nuxtApp) => {
- const authStore = await useAuth()
- await useUser()
- const cosmeticsStore = useCosmetics()
- const tagsStore = useTags()
-
- nuxtApp.provide('auth', authStore.value)
- nuxtApp.provide('cosmetics', cosmeticsStore.value)
- nuxtApp.provide('tag', tagsStore.value)
- nuxtApp.provide('notify', (notif) => addNotification(notif))
-})
diff --git a/plugins/shorthands.js b/plugins/shorthands.js
index 54ba23ada3..b92e269cc8 100644
--- a/plugins/shorthands.js
+++ b/plugins/shorthands.js
@@ -1,29 +1,10 @@
import { getProjectTypeForUrlShorthand } from '~/helpers/projects.js'
export default defineNuxtPlugin((nuxtApp) => {
- const tagStore = nuxtApp.$tag
- const authStore = nuxtApp.$auth
-
- nuxtApp.provide('defaultHeaders', () => {
- const obj = { headers: {} }
-
- if (process.server) {
- const config = useRuntimeConfig()
- if (config.rateLimitKey) {
- obj.headers['x-ratelimit-key'] = config.rateLimitKey || ''
- }
- }
-
- if (authStore.user) {
- obj.headers.Authorization = authStore.token
- }
-
- return obj
- })
nuxtApp.provide('formatNumber', formatNumber)
nuxtApp.provide('capitalizeString', capitalizeString)
nuxtApp.provide('formatMoney', formatMoney)
- nuxtApp.provide('formatVersion', (versionsArray) => formatVersions(versionsArray, tagStore))
+ nuxtApp.provide('formatVersion', (versionsArray) => formatVersions(versionsArray))
nuxtApp.provide('orElse', (first, otherwise) => first ?? otherwise)
nuxtApp.provide('external', () => {
const cosmeticsStore = useCosmetics().value
@@ -95,15 +76,17 @@ export default defineNuxtPlugin((nuxtApp) => {
.sort((a, b) => nuxtApp.$dayjs(b.date_published) - nuxtApp.$dayjs(a.date_published))
})
nuxtApp.provide('getProjectTypeForDisplay', (type, categories) => {
+ const tagStore = useTags()
+
if (type === 'mod') {
const isPlugin = categories.some((category) => {
- return tagStore.loaderData.allPluginLoaders.includes(category)
+ return tagStore.value.loaderData.allPluginLoaders.includes(category)
})
const isMod = categories.some((category) => {
- return tagStore.loaderData.modLoaders.includes(category)
+ return tagStore.value.loaderData.modLoaders.includes(category)
})
const isDataPack = categories.some((category) => {
- return tagStore.loaderData.dataPackLoaders.includes(category)
+ return tagStore.value.loaderData.dataPackLoaders.includes(category)
})
if (isMod && isPlugin && isDataPack) {
@@ -123,25 +106,29 @@ export default defineNuxtPlugin((nuxtApp) => {
return type
})
- nuxtApp.provide('getProjectTypeForUrl', (type, loaders) =>
- getProjectTypeForUrlShorthand(nuxtApp, type, loaders)
+ nuxtApp.provide('getProjectTypeForUrl', (type, loaders, tags) =>
+ getProjectTypeForUrlShorthand(type, loaders, tags)
)
nuxtApp.provide('cycleValue', cycleValue)
- const sortedCategories = tagStore.categories.slice().sort((a, b) => {
- const headerCompare = a.header.localeCompare(b.header)
- if (headerCompare !== 0) {
- return headerCompare
- }
- if (a.header === 'resolutions' && b.header === 'resolutions') {
- return a.name.replace(/\D/g, '') - b.name.replace(/\D/g, '')
- } else if (a.header === 'performance impact' && b.header === 'performance impact') {
- const x = ['potato', 'low', 'medium', 'high', 'screenshot']
+ nuxtApp.provide('sortedCategories', () => {
+ const tagStore = useTags()
- return x.indexOf(a.name) - x.indexOf(b.name)
- }
- return 0
+ return tagStore.value.categories.slice().sort((a, b) => {
+ const headerCompare = a.header.localeCompare(b.header)
+ if (headerCompare !== 0) {
+ return headerCompare
+ }
+ if (a.header === 'resolutions' && b.header === 'resolutions') {
+ return a.name.replace(/\D/g, '') - b.name.replace(/\D/g, '')
+ } else if (a.header === 'performance impact' && b.header === 'performance impact') {
+ const x = ['potato', 'low', 'medium', 'high', 'screenshot']
+
+ return x.indexOf(a.name) - x.indexOf(b.name)
+ }
+ return 0
+ })
})
- nuxtApp.provide('sortedCategories', sortedCategories)
+ nuxtApp.provide('notify', (notif) => addNotification(notif))
})
export const formatNumber = (number, abbreviate = true) => {
const x = +number
@@ -257,8 +244,9 @@ export const formatProjectStatus = (name) => {
return capitalizeString(name)
}
-export const formatVersions = (versionArray, tag) => {
- const allVersions = tag.gameVersions.slice().reverse()
+export const formatVersions = (versionArray) => {
+ const tag = useTags()
+ const allVersions = tag.value.gameVersions.slice().reverse()
const allReleases = allVersions.filter((x) => x.version_type === 'release')
const intervals = []
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 070489a30d..970a5f6e09 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -34,6 +34,9 @@ dependencies:
omorphia:
specifier: ^0.4.31
version: 0.4.31
+ qrcode.vue:
+ specifier: ^3.4.0
+ version: 3.4.0(vue@3.3.4)
vue-multiselect:
specifier: ^3.0.0-alpha.2
version: 3.0.0-alpha.2
@@ -48,6 +51,9 @@ devDependencies:
'@nuxtjs/eslint-config-typescript':
specifier: ^12.0.0
version: 12.0.0(eslint@8.41.0)(typescript@5.0.4)
+ '@nuxtjs/turnstile':
+ specifier: ^0.5.0
+ version: 0.5.0
'@types/node':
specifier: ^20.1.0
version: 20.1.0
@@ -1357,6 +1363,17 @@ packages:
- supports-color
dev: true
+ /@nuxtjs/turnstile@0.5.0:
+ resolution: {integrity: sha512-EmEnYNDRavdmv9HXnInHVR5nSvjDG92s6pOrxd+ugqImkrnIQJttSd4DPq9UNhwUI/wLUL7z3VclVyQwNT2O7Q==}
+ dependencies:
+ '@nuxt/kit': 3.6.1
+ defu: 6.1.2
+ pathe: 1.1.1
+ transitivePeerDependencies:
+ - rollup
+ - supports-color
+ dev: true
+
/@pkgjs/parseargs@0.11.0:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@@ -6024,6 +6041,14 @@ packages:
engines: {node: '>=6'}
dev: true
+ /qrcode.vue@3.4.0(vue@3.3.4):
+ resolution: {integrity: sha512-4XeImbv10Fin16Fl2DArCMhGyAdvIg2jb7vDT+hZiIAMg/6H6mz9nUZr/dR8jBcun5VzNzkiwKhiqOGbloinwA==}
+ peerDependencies:
+ vue: ^3.0.0
+ dependencies:
+ vue: 3.3.4
+ dev: false
+
/queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
dev: true