feat: refactor auth flow and enable exchange token flow (#95)
* feat: refactor auth flow and enable exchange token flow * fix: do not cache to local storage for exchange token * chore: remove logging * chore: lint * feat: pkce alignment with oauth endpoint * feat: default log in via accountBinding.authenticateAccount if available * feat: do not show legacy sign in if connectors has accountBinding.authenticateAccount flow * fix: base64url safe
This commit is contained in:
@@ -3,17 +3,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { md5 } from '@speckle/shared'
|
||||
import { ToastNotificationType } from '@speckle/ui-components'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useAuthManager } from '~/lib/authn/useAuthManager'
|
||||
import type { Account } from '~/lib/bindings/definitions/IAccountBinding'
|
||||
import { useTokenExchange } from '~/lib/authn/useTokenExchange'
|
||||
import { useHostAppStore } from '~/store/hostApp'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { getChallenge, getChallengeUrl } = useAuthManager()
|
||||
const { $accountBinding } = useNuxtApp()
|
||||
const { getChallenge, getCodeVerifier, getChallengeUrl } = useAuthManager()
|
||||
const { exchangeAccessCode } = useTokenExchange()
|
||||
const hostApp = useHostAppStore()
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -22,69 +21,11 @@ onMounted(async () => {
|
||||
const accessCode = route.query.access_code as string | undefined
|
||||
if (accessCode && origin) {
|
||||
const challenge = getChallenge()
|
||||
const body = {
|
||||
appId: 'sdui',
|
||||
appSecret: 'sdui',
|
||||
accessCode,
|
||||
challenge
|
||||
if (!challenge) {
|
||||
throw new Error('No challenge found in storage.')
|
||||
}
|
||||
|
||||
// Exchange the access code for a real token (optional)
|
||||
const response = await fetch(new URL('/auth/token', origin), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
|
||||
hostApp.setNotification({
|
||||
title: 'Log In',
|
||||
type: ToastNotificationType.Danger,
|
||||
description: `Token exchange failed with status ${response.status}: ${errorText}`
|
||||
})
|
||||
// Stop processing and redirect immediately on failure
|
||||
return router.replace('/')
|
||||
}
|
||||
|
||||
const { token, refreshToken } = (await response.json()) as {
|
||||
token: string
|
||||
refreshToken: string
|
||||
}
|
||||
|
||||
const graphqlQuery = {
|
||||
query:
|
||||
'query { activeUser { id name email company avatar } serverInfo { name company adminContact description version } }'
|
||||
}
|
||||
|
||||
const userAndServerInfoResponse = await fetch(new URL('/graphql', origin), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}` // Add the token as a Bearer token
|
||||
},
|
||||
body: JSON.stringify(graphqlQuery)
|
||||
})
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const userAndServerInfo = await userAndServerInfoResponse.json()
|
||||
const accountId = md5(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
userAndServerInfo.data.activeUser.email + origin
|
||||
).toUpperCase()
|
||||
|
||||
const account: Account = {
|
||||
id: accountId,
|
||||
token,
|
||||
refreshToken,
|
||||
isDefault: true,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
||||
serverInfo: { url: origin, ...userAndServerInfo.data.serverInfo },
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
||||
userInfo: userAndServerInfo.data.activeUser
|
||||
}
|
||||
|
||||
await $accountBinding.addAccount(accountId, account)
|
||||
const codeVerifier = getCodeVerifier() ?? undefined
|
||||
await exchangeAccessCode(origin, accessCode, challenge, codeVerifier)
|
||||
} else {
|
||||
throw new Error('No access code is found.')
|
||||
}
|
||||
@@ -92,7 +33,7 @@ onMounted(async () => {
|
||||
hostApp.setNotification({
|
||||
type: ToastNotificationType.Danger,
|
||||
title: 'Failed to add your Speckle account.',
|
||||
description: error as string
|
||||
description: error instanceof Error ? error.message : (error as string)
|
||||
})
|
||||
} finally {
|
||||
router.replace('/')
|
||||
|
||||
Reference in New Issue
Block a user