mirror of
https://github.com/modrinth/code.git
synced 2026-09-02 04:56:52 +00:00
feat: initial implementation of new sign-in oauth
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
<div class="create-account-card">
|
<div class="create-account-card">
|
||||||
<h1 class="create-account-title">{{ formatMessage(messages.title) }}</h1>
|
<h1 class="create-account-title">{{ formatMessage(messages.title) }}</h1>
|
||||||
|
|
||||||
<section class="create-account-section">
|
<section v-if="requiresDob" class="create-account-section">
|
||||||
<label class="create-account-label" for="create-account-dob">
|
<label class="create-account-label" for="create-account-dob">
|
||||||
{{ formatMessage(messages.dateOfBirthLabel) }}
|
{{ formatMessage(messages.dateOfBirthLabel) }}
|
||||||
</label>
|
</label>
|
||||||
@@ -98,6 +98,10 @@ const props = defineProps({
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
requiresDob: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
sourceCodeUrl: {
|
sourceCodeUrl: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'https://github.com/modrinth/labrinth/blob/main/apps/labrinth/src/routes/internal/flows.rs',
|
default: 'https://github.com/modrinth/labrinth/blob/main/apps/labrinth/src/routes/internal/flows.rs',
|
||||||
|
|||||||
@@ -58,20 +58,10 @@ export const initAuth = async (oldToken: string | null | undefined = null) => {
|
|||||||
authCookie.value = oldToken
|
authCookie.value = oldToken
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authCode && !route.fullPath.includes('new_account=true')) {
|
if (authCode) {
|
||||||
authCookie.value = authCode
|
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) {
|
if (authCookie.value) {
|
||||||
auth.token = authCookie.value
|
auth.token = authCookie.value
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
<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>
|
||||||
|
import { commonMessages, defineMessages, injectModrinthClient, injectNotificationManager, useVIntl } from '@modrinth/ui'
|
||||||
|
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||||
|
|
||||||
|
import CreateAccountView from '@/components/ui/auth/CreateAccount.vue'
|
||||||
|
import { getLauncherRedirectUrl } from '@/composables/auth.ts'
|
||||||
|
|
||||||
|
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 : 'user'
|
||||||
|
})
|
||||||
|
|
||||||
|
const dateOfBirth = ref('')
|
||||||
|
const username = ref(defaultUsername.value)
|
||||||
|
const token = ref('')
|
||||||
|
const subscribe = ref(false)
|
||||||
|
const subtleLauncherRedirectUri = ref()
|
||||||
|
|
||||||
|
const captcha = ref()
|
||||||
|
const setCaptchaRef = (captchaRef) => {
|
||||||
|
captcha.value = captchaRef
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: globals } = useQuery({
|
||||||
|
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: err.data ? err.data.description : err,
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
captcha.value?.reset()
|
||||||
|
}
|
||||||
|
stopLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function finishSignIn(sessionToken) {
|
||||||
|
if (route.query.launcher) {
|
||||||
|
let token = sessionToken
|
||||||
|
if (!token) {
|
||||||
|
token = auth.value.token
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route.query.redirect) {
|
||||||
|
const redirect = decodeURIComponent(route.query.redirect)
|
||||||
|
await navigateTo(redirect, {
|
||||||
|
replace: true,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await navigateTo('/dashboard')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,20 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<SignInView
|
||||||
<SignInView
|
v-model:email="email"
|
||||||
v-model:email="email"
|
v-model:password="password"
|
||||||
v-model:password="password"
|
v-model:token="token"
|
||||||
v-model:token="token"
|
v-model:two-factor-code="twoFactorCode"
|
||||||
v-model:two-factor-code="twoFactorCode"
|
:subtle-launcher-redirect-uri="subtleLauncherRedirectUri"
|
||||||
:subtle-launcher-redirect-uri="subtleLauncherRedirectUri"
|
:flow="flow"
|
||||||
:flow="flow"
|
:redirect-target="redirectTarget"
|
||||||
:redirect-target="redirectTarget"
|
:route-query="route.query"
|
||||||
:route-query="route.query"
|
:globals="globals"
|
||||||
:globals="globals"
|
:on-password-sign-in="beginPasswordSignIn"
|
||||||
:on-password-sign-in="beginPasswordSignIn"
|
:on-two-factor-sign-in="begin2FASignIn"
|
||||||
:on-two-factor-sign-in="begin2FASignIn"
|
:on-set-captcha-ref="setCaptchaRef"
|
||||||
:on-set-captcha-ref="setCaptchaRef"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -54,7 +52,7 @@ const route = useNativeRoute()
|
|||||||
const redirectTarget = route.query.redirect || ''
|
const redirectTarget = route.query.redirect || ''
|
||||||
const subtleLauncherRedirectUri = ref()
|
const subtleLauncherRedirectUri = ref()
|
||||||
|
|
||||||
if (route.query.code && !route.fullPath.includes('new_account=true')) {
|
if (route.query.code) {
|
||||||
await finishSignIn()
|
await finishSignIn()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,23 @@ export class LabrinthAuthV2Module extends AbstractModule {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new account from an OAuth callback flow state
|
||||||
|
*
|
||||||
|
* @param data - OAuth account creation data
|
||||||
|
* @returns Promise resolving to a session response
|
||||||
|
*/
|
||||||
|
public async createOAuthAccount(
|
||||||
|
data: Labrinth.Auth.v2.CreateOAuthAccountRequest,
|
||||||
|
): Promise<Labrinth.Auth.v2.CreateOAuthAccountResponse> {
|
||||||
|
return this.client.request<Labrinth.Auth.v2.CreateOAuthAccountResponse>(`/auth/create/oauth`, {
|
||||||
|
api: 'labrinth',
|
||||||
|
version: 2,
|
||||||
|
method: 'POST',
|
||||||
|
body: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Begin a password reset flow by sending a recovery email
|
* Begin a password reset flow by sending a recovery email
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -278,6 +278,17 @@ export namespace Labrinth {
|
|||||||
session: string
|
session: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CreateOAuthAccountRequest = {
|
||||||
|
username: string
|
||||||
|
state: string
|
||||||
|
challenge: string
|
||||||
|
sign_up_newsletter: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CreateOAuthAccountResponse = {
|
||||||
|
session: string
|
||||||
|
}
|
||||||
|
|
||||||
export type ResetPasswordRequest = {
|
export type ResetPasswordRequest = {
|
||||||
username: string
|
username: string
|
||||||
challenge: string
|
challenge: string
|
||||||
|
|||||||
Reference in New Issue
Block a user