Files
nocodb/packages/nc-gui/components/auth/oAuth/index.vue
mertmit 69a29568c7 chore: sync
Signed-off-by: mertmit <mertmit99@gmail.com>
2026-01-10 00:21:02 +03:00

204 lines
7.0 KiB
Vue

<script setup lang="ts">
const route = useRoute()
const { api } = useApi()
const { user } = useGlobal()
const { getPossibleAttachmentSrc } = useAttachment()
const clientId = computed(() => route.query.client_id as string)
const redirect_uri = computed(() => route.query.redirect_uri as string)
const responseType = computed(() => route.query.response_type as string)
const state = computed(() => route.query.state as string)
const codeChallenge = computed(() => route.query.code_challenge as string)
const resource = computed(() => route.query.resource as string)
const codeChallengeMethod = computed(() => route.query.code_challenge_method as string)
const loading = ref(true)
const error = ref('')
const authorizing = ref(false)
const clientInfo = ref<any>({})
const selectedWorkspace = ref()
const selectedBase = ref()
const isValidRequest = computed(() => {
return clientId.value && redirect_uri.value && responseType.value === 'code'
})
const isSelectionValid = computed(() => {
if (isEeUI) {
return selectedWorkspace.value && selectedBase.value
} else {
return selectedBase.value
}
})
const canAuthorize = computed(() => {
return isValidRequest.value && isSelectionValid.value && !authorizing.value
})
async function loadAuthorizationRequest() {
if (!isValidRequest.value) {
error.value = 'Invalid OAuth request parameters'
loading.value = false
return
}
try {
const response = await api.oAuth.v2PublicOauthClientDetail(clientId.value)
if (!response.redirect_uris.includes(redirect_uri.value)) {
error.value = 'Invalid redirect URI'
return
}
clientInfo.value = response
} catch (err: any) {
console.error('Failed to load client info:', err)
if (err.response?.status === 404) {
error.value = 'Application not found'
} else {
error.value = 'Failed to load application information'
}
} finally {
loading.value = false
}
}
async function approveAuthorization() {
if (!canAuthorize.value) {
return
}
authorizing.value = true
try {
const authParams: any = {
client_id: clientId.value,
redirect_uri: redirect_uri.value,
state: state.value,
approved: true,
code_challenge: codeChallenge.value,
code_challenge_method: codeChallengeMethod.value,
base_id: selectedBase.value,
resource: resource.value,
}
if (isEeUI && selectedWorkspace.value) {
authParams.workspace_id = selectedWorkspace.value
}
const response = await api.oAuth.authorize(authParams)
window.location.href = response.redirect_url
} catch (err: any) {
console.error('Authorization failed:', err)
error.value = 'Authorization failed. Please try again.'
} finally {
authorizing.value = false
}
}
function denyAuthorization() {
const errorUrl = new URL(redirect_uri.value)
errorUrl.searchParams.set('error', 'access_denied')
errorUrl.searchParams.set('error_description', 'User denied the request')
if (state.value) {
errorUrl.searchParams.set('state', state.value)
}
window.location.href = errorUrl.toString()
}
onMounted(() => {
loadAuthorizationRequest()
})
</script>
<template>
<div class="oauth-authorize-page bg-nc-bg-gray-extralight min-h-screen flex items-center justify-center p-4">
<div v-if="loading" class="flex items-center bg-nc-bg-default flex-col gap-4">
<GeneralLoader size="xlarge" />
<p class="text-nc-content-gray-subtle2">Loading authorization request...</p>
</div>
<div
v-else-if="error"
class="rounded-xl shadow-sm bg-nc-bg-default border-1 border-nc-border-gray-medium p-8 max-w-md w-full text-center"
>
<GeneralIcon icon="alertTriangle" class="w-12 h-12 text-nc-content-red-medium mx-auto mb-4" />
<h2 class="text-xl font-semibold text-nc-content-gray-extreme mb-2">Authorization Error</h2>
<p class="text-nc-content-gray-subtle2 mb-6">{{ error }}</p>
<NcButton size="small" type="primary" @click="$router.push('/')"> Back to NocoDB </NcButton>
</div>
<div v-else class="rounded-xl bg-nc-bg-default shadow-sm border-1 border-nc-border-gray-medium p-8 max-w-lg w-full">
<div class="flex items-center justify-center flex-1 w-full">
<CellAttachmentPreviewImage
v-if="clientInfo.logo_uri"
:srcs="getPossibleAttachmentSrc(clientInfo.logo_uri)"
class="!object-contain max-h-12 max-w-12 !rounded-full"
:is-cell-preview="false"
/>
<div v-else class="w-16 h-16 rounded-full bg-primary flex items-center justify-center text-white text-xl font-bold">
{{ clientInfo.client_name?.charAt(0) || 'A' }}
</div>
</div>
<h1 class="text-xl font-bold text-center mt-4 text-nc-content-gray-extreme">
Authorize {{ clientInfo.client_name }} to access your account?
</h1>
<div class="text-nc-content-gray-muted text-center">
<span class="font-semibold"> {{ clientInfo.client_name }} </span> needs access to your account to provide you service. We
value your privacy and only request necessary permissions.
</div>
<div class="flex items-center justify-center my-6 gap-2">
<GeneralUserIcon :user="user" size="medium" />
{{ user.display_name || user.email }}
</div>
<NcDivider />
<div class="my-6">
<div class="flex gap-3 flex-col">
<NcListWorkspaceSelector v-if="isEeUI" v-model:value="selectedWorkspace" force-layout="vertical" />
<NcListBaseSelector v-model:value="selectedBase" force-layout="vertical" :workspace-id="selectedWorkspace" />
</div>
</div>
<div class="bg-nc-bg-gray-extralight mt-4 rounded-lg p-4">
<h3 class="font-medium text-nc-content-gray-emphasis mb-3">This application will be able to:</h3>
<ul class="space-y-2">
<li class="flex items-center text-sm text-nc-content-gray-subtle">
<GeneralIcon icon="ncCheck" class="w-4 h-4 text-green-600 mr-2 flex-shrink-0" />
Access the selected base
</li>
<li class="flex items-center text-sm text-nc-content-gray-subtle">
<GeneralIcon icon="ncCheck" class="w-4 h-4 text-green-600 mr-2 flex-shrink-0" />
Read, create, update, and delete records in this base
</li>
<li class="flex items-center text-sm text-nc-content-gray-subtle">
<GeneralIcon icon="ncCheck" class="w-4 h-4 text-green-600 mr-2 flex-shrink-0" />
Act on your behalf within the selected resources
</li>
</ul>
</div>
<div class="flex gap-3 mt-4">
<NcButton type="secondary" :disabled="authorizing" class="flex-1" @click="denyAuthorization"> Cancel </NcButton>
<NcButton :disabled="!canAuthorize" :loading="authorizing" class="flex-1" @click="approveAuthorization">
Authorize
</NcButton>
</div>
<div v-if="!isSelectionValid && !loading" class="text-sm text-red-600 text-center mt-2">
{{ isEeUI ? 'Please select both a workspace and base to continue.' : 'Please select a base to continue.' }}
</div>
</div>
</div>
</template>