mirror of
https://github.com/modrinth/code.git
synced 2026-08-26 17:44:50 +00:00
feat: update auth flow (#5790)
* Backend routes for choosing username in OAuth flow * fix up oauth flow routes * improve URL-related OAuth code * Use user-provided callback addr instead of SELF_ADDR * Revert "Use user-provided callback addr instead of SELF_ADDR" This reverts commit7ea0635d86. * fix flow * fix: backend response for create oauth account * feat: new auth flow (#5840) * update auth with new designs * refactor: auth.js to auth.ts * refactor: componentize auth pages * fix: auth pages height * feat: initial implementation of new sign-in oauth * fix create account flow * fix checkbox * remove hard coded username * implement create user validation endpoint and add more specific error responses * feat: implement under 13 DOB guard and email/password validation route * fix: TOCTOU issue * refactor: pnpm prepr * fix: make sure staging uses staging * fix: hcaptcha styles * fix: copy * remove: auth/welcome page as its no longer used * refactor: bring root page card styles into individual components and use tailwind * fix: account settings modals to use new modal and fix lots of bad styles * refactor: pnpm prepr * feat: implement last signed in indicator * fix: append number when generated name from email is taken * refactor: pnpm prepr * fix: last sign in badge color * fix: qa issues * refactor: pnpm prepr * fix: hover effect on native date picker * chore: temp staging undo * Revert "chore: temp staging undo" This reverts commitcad6bd4f92. * feat: handle app create account * fix: last signed in style * fix: add initOnMounted for SSR race * refactor: use typescript * refactor: pnpm prepr * refactor: use typescript for reset-password * refactor: convert verify-email to use typescript * refactor: convert authorize.vue to use typescript * fix: authorize.vue error states * feat: small style updates * feat: implement date picker component * feat: improve UX and styles for range select * refactor: pnpm prepr * fix: range select border styles * feat: implement date picker component in create account * feat: implement preserve date for date picker * update rust toolchain * increase recursion limit * fix: date picker can be null * fix: calculate age based on user's timezone * fix: number input icons color * fix: date picker icons * feat: improve styles * fix: add width on date * fix: hover color bad on number input * fix lints * feat: add default date open view * fmt * fix: account.vue * fix: remove default date to open 13 years ago * fix: edit copy on info banner * fix: cannot hover over project card tooltip items (#6071) fix: cannot hover over project cards * feat: improve add dependency flow (#6075) * fix: shadow on nav * feat: improve add dependency flow * feat: update suggested dependency style * feat: update dependency rows to use version number and update styles * feat: implement combobox select searched text on focus * feat: add Tabs.vue * feat: update nav tabs to use tabs * feat: improve project search dropdown * fix: dependency search not clearing inbound query * fix: combobox no options open state bug * feat: improve dependency project and version search * fix: open modrinth project links in the app (#6072) * pin tanstack versions + set pnpm min age to 7 days * squash commits * fix: 2 factor auth enter code screen styles * update copy * update copy * improve reset password * feat: update sign in screen * fix: unused import * Merge branch 'main' into boris/dev-908-backend-changes * Revert "Merge branch 'main' into boris/dev-908-backend-changes" This reverts commitb9b03796e3. * fix: add stroke * feat: add passkey support (#6375) * feat: add passkey backend * feat: passkey frontend * invalidate sessions on compromised passkey * chore: run sqlx prepare * fix: make passkey button use both collumns to prevent empty space * fix: correctly verify max passkeys in finish route * fix: use structs for response * fix: add rp name default * style: use web::Json * fmt * feat: improve manage passkeys UI * fix copy * pnpm prepr --------- Co-authored-by: tdgao <mr.trumgao@gmail.com> Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com> Co-authored-by: Michael H. <michael@iptables.sh> Co-authored-by: Calum H. (IMB11) <contact@cal.engineer> Co-authored-by: Calum H. <calum@modrinth.com> Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com> Co-authored-by: DeDiamondPro <67508414+DeDiamondPro@users.noreply.github.com>
This commit is contained in:
co-authored by
tdgao
Truman Gao
Michael H.
Calum H.
Calum H.
Prospector
DeDiamondPro
parent
6fc741f7c0
commit
ef4044534f
@@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<div v-if="subtleLauncherRedirectUri">
|
||||
<iframe
|
||||
:src="subtleLauncherRedirectUri"
|
||||
class="fixed left-0 top-0 z-[9999] m-0 h-full w-full border-0 p-0"
|
||||
></iframe>
|
||||
</div>
|
||||
<CreateAccountView
|
||||
v-else
|
||||
v-model:date-of-birth="dateOfBirth"
|
||||
v-model:username="username"
|
||||
v-model:token="token"
|
||||
v-model:subscribe="subscribe"
|
||||
:globals="globals"
|
||||
:requires-dob="requiresDob"
|
||||
:on-complete-sign-up="completeOAuthSignUp"
|
||||
:on-set-captcha-ref="setCaptchaRef"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import type { LocationQueryValue } from 'vue-router'
|
||||
|
||||
import CreateAccountView from '@/components/ui/auth/CreateAccount.vue'
|
||||
import { getLauncherRedirectUrl, promotePendingSignInOAuthProvider } from '@/composables/auth.ts'
|
||||
|
||||
interface AuthGlobalsResponse {
|
||||
captcha_enabled?: boolean
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
interface ApiErrorShape {
|
||||
data?: {
|
||||
description?: string
|
||||
}
|
||||
}
|
||||
|
||||
const getQueryString = (
|
||||
value: LocationQueryValue | LocationQueryValue[] | null | undefined,
|
||||
): string => {
|
||||
const firstValue = Array.isArray(value) ? value[0] : value
|
||||
return typeof firstValue === 'string' ? firstValue : ''
|
||||
}
|
||||
|
||||
const getErrorMessage = (error: unknown): string => {
|
||||
const apiError = error as ApiErrorShape
|
||||
if (typeof apiError?.data?.description === 'string') {
|
||||
return apiError.data.description
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
return error.message
|
||||
}
|
||||
return String(error)
|
||||
}
|
||||
|
||||
const client = injectModrinthClient()
|
||||
const queryClient = useQueryClient()
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const route = useNativeRoute()
|
||||
const auth = await useAuth()
|
||||
|
||||
const messages = defineMessages({
|
||||
createAccountTitle: {
|
||||
id: 'auth.create-account.page-title',
|
||||
defaultMessage: 'Create Account',
|
||||
},
|
||||
})
|
||||
|
||||
useHead({
|
||||
title() {
|
||||
return `${formatMessage(messages.createAccountTitle)} - Modrinth`
|
||||
},
|
||||
})
|
||||
|
||||
const requiresDob = computed(() => {
|
||||
const raw = route.query.requires_dob
|
||||
const value = Array.isArray(raw) ? raw[0] : raw
|
||||
|
||||
if (!value) {
|
||||
return false
|
||||
}
|
||||
|
||||
return value === 'true' || value === '1'
|
||||
})
|
||||
|
||||
const oauthFlowState = computed(() => {
|
||||
const state = route.query.state
|
||||
const value = Array.isArray(state) ? state[0] : state
|
||||
return typeof value === 'string' ? value : ''
|
||||
})
|
||||
|
||||
const defaultUsername = computed(() => {
|
||||
const queryUsername = route.query.username
|
||||
const value = Array.isArray(queryUsername) ? queryUsername[0] : queryUsername
|
||||
return typeof value === 'string' && value.length > 0 ? value : ''
|
||||
})
|
||||
|
||||
const dateOfBirth = ref('')
|
||||
const username = ref(defaultUsername.value)
|
||||
const token = ref('')
|
||||
const subscribe = ref(false)
|
||||
const subtleLauncherRedirectUri = ref<string>()
|
||||
|
||||
const captcha = ref<{ reset?: () => void } | null>(null)
|
||||
const setCaptchaRef = (captchaRef: unknown) => {
|
||||
captcha.value = (captchaRef as { reset?: () => void } | null) ?? null
|
||||
}
|
||||
|
||||
const { data: globals } = useQuery<AuthGlobalsResponse>({
|
||||
queryKey: ['auth-globals'],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
return await client.labrinth.globals_internal.get()
|
||||
} catch (err) {
|
||||
console.error('Error fetching globals:', err)
|
||||
return { captcha_enabled: true, tax_compliance_thresholds: {} }
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
async function completeOAuthSignUp() {
|
||||
startLoading()
|
||||
try {
|
||||
if (!oauthFlowState.value) {
|
||||
throw new Error('Missing OAuth flow state')
|
||||
}
|
||||
|
||||
const res = await client.labrinth.auth_v2.createOAuthAccount({
|
||||
username: username.value.trim() || defaultUsername.value,
|
||||
state: oauthFlowState.value,
|
||||
challenge: token.value,
|
||||
sign_up_newsletter: subscribe.value,
|
||||
})
|
||||
|
||||
await finishSignIn(res.session)
|
||||
} catch (err) {
|
||||
addNotification({
|
||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||
text: getErrorMessage(err),
|
||||
type: 'error',
|
||||
})
|
||||
captcha.value?.reset?.()
|
||||
}
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
async function finishSignIn(sessionToken?: string | null) {
|
||||
if (route.query.launcher) {
|
||||
let token = sessionToken
|
||||
if (!token) {
|
||||
token = auth.value.token
|
||||
}
|
||||
|
||||
promotePendingSignInOAuthProvider()
|
||||
|
||||
const redirectUrl = `${getLauncherRedirectUrl(route)}/?code=${token}`
|
||||
|
||||
if (redirectUrl.startsWith('https://launcher-files.modrinth.com/')) {
|
||||
await navigateTo(redirectUrl, {
|
||||
external: true,
|
||||
})
|
||||
} else {
|
||||
subtleLauncherRedirectUri.value = redirectUrl
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (sessionToken) {
|
||||
await useAuth(sessionToken)
|
||||
await useUser()
|
||||
queryClient.clear()
|
||||
|
||||
promotePendingSignInOAuthProvider()
|
||||
}
|
||||
|
||||
if (route.query.redirect) {
|
||||
const redirect = decodeURIComponent(getQueryString(route.query.redirect))
|
||||
await navigateTo(redirect, {
|
||||
replace: true,
|
||||
})
|
||||
} else {
|
||||
await navigateTo('/dashboard')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user