From 791f45a7d675fa13eb67d2f6479f4f843fe8fb46 Mon Sep 17 00:00:00 2001 From: djmaze <7229+djmaze@users.noreply.github.com> Date: Sat, 4 Feb 2023 17:30:47 +0100 Subject: [PATCH 01/70] Allow uploads for public base url users with editor role --- packages/nocodb/src/lib/meta/api/attachmentApis.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/nocodb/src/lib/meta/api/attachmentApis.ts b/packages/nocodb/src/lib/meta/api/attachmentApis.ts index 9d619ae592..12860d5324 100644 --- a/packages/nocodb/src/lib/meta/api/attachmentApis.ts +++ b/packages/nocodb/src/lib/meta/api/attachmentApis.ts @@ -16,7 +16,7 @@ import Local from '../../v1-legacy/plugins/adapters/storage/Local'; import { NC_ATTACHMENT_FIELD_SIZE } from '../../constants'; const isUploadAllowed = async (req: Request, _res: Response, next: any) => { - if (!req['user']?.id) { + if (!req['user']) { NcError.unauthorized('Unauthorized'); } @@ -25,6 +25,7 @@ const isUploadAllowed = async (req: Request, _res: Response, next: any) => { if ( req['user'].roles?.includes(OrgUserRoles.SUPER_ADMIN) || req['user'].roles?.includes(OrgUserRoles.CREATOR) || + (req['user'].isPublicBase && req['user'].roles?.includes(ProjectRoles.EDITOR)) || // if viewer then check at-least one project have editor or higher role // todo: cache !!(await Noco.ncMeta From 947f4c7cb3b67ddc02ff61aa5e19084b5268bc08 Mon Sep 17 00:00:00 2001 From: mertmit Date: Mon, 6 Feb 2023 20:39:42 +0300 Subject: [PATCH 02/70] fix: drop url availability check with fetch Signed-off-by: mertmit --- packages/nc-gui/components/cell/attachment/utils.ts | 12 +----------- packages/nc-gui/composables/useKanbanViewStore.ts | 12 +----------- packages/nc-gui/composables/useViewData.ts | 12 +----------- 3 files changed, 3 insertions(+), 33 deletions(-) diff --git a/packages/nc-gui/components/cell/attachment/utils.ts b/packages/nc-gui/components/cell/attachment/utils.ts index 6b202c519c..007e557b95 100644 --- a/packages/nc-gui/components/cell/attachment/utils.ts +++ b/packages/nc-gui/components/cell/attachment/utils.ts @@ -237,17 +237,7 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState( // if path doesn't exist, use `item.url` if (path) { // try ${appInfo.value.ncSiteUrl}/${item.path} first - const url = `${appInfo.value.ncSiteUrl}/${item.path}` - try { - const res = await fetch(url) - if (res.ok) { - // use `url` if it is accessible - return Promise.resolve(url) - } - } catch { - // for some cases, `url` is not accessible as expected - // do nothing here - } + return Promise.resolve(`${appInfo.value.ncSiteUrl}/${item.path}`) } // if it fails, use the original url return Promise.resolve(item.url) diff --git a/packages/nc-gui/composables/useKanbanViewStore.ts b/packages/nc-gui/composables/useKanbanViewStore.ts index a9b46f8f4e..21314d8caf 100644 --- a/packages/nc-gui/composables/useKanbanViewStore.ts +++ b/packages/nc-gui/composables/useKanbanViewStore.ts @@ -149,17 +149,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState( // if path doesn't exist, use `item.url` if (path) { // try ${appInfo.value.ncSiteUrl}/${item.path} first - const url = `${appInfo.value.ncSiteUrl}/${item.path}` - try { - const res = await fetch(url) - if (res.ok) { - // use `url` if it is accessible - return Promise.resolve(url) - } - } catch { - // for some cases, `url` is not accessible as expected - // do nothing here - } + return Promise.resolve(`${appInfo.value.ncSiteUrl}/${item.path}`) } // if it fails, use the original url return Promise.resolve(item.url) diff --git a/packages/nc-gui/composables/useViewData.ts b/packages/nc-gui/composables/useViewData.ts index 670eead85b..69bf5cec66 100644 --- a/packages/nc-gui/composables/useViewData.ts +++ b/packages/nc-gui/composables/useViewData.ts @@ -207,17 +207,7 @@ export function useViewData( // if path doesn't exist, use `item.url` if (path) { // try ${appInfo.value.ncSiteUrl}/${item.path} first - const url = `${appInfo.ncSiteUrl}/${item.path}` - try { - const res = await fetch(url) - if (res.ok) { - // use `url` if it is accessible - return Promise.resolve(url) - } - } catch { - // for some cases, `url` is not accessible as expected - // do nothing here - } + return Promise.resolve(`${appInfo.ncSiteUrl}/${item.path}`) } // if it fails, use the original url return Promise.resolve(item.url) From 7e9aa7b810d926633b1f6fe9682874aab90b78ca Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 7 Feb 2023 11:44:27 +0800 Subject: [PATCH 03/70] fix(nc-gui): remove getAttachmentUrl --- .../components/cell/attachment/utils.ts | 15 -------- .../nc-gui/composables/useKanbanViewStore.ts | 33 +----------------- packages/nc-gui/composables/useViewData.ts | 34 +------------------ 3 files changed, 2 insertions(+), 80 deletions(-) diff --git a/packages/nc-gui/components/cell/attachment/utils.ts b/packages/nc-gui/components/cell/attachment/utils.ts index 007e557b95..d9e0ed6ea9 100644 --- a/packages/nc-gui/components/cell/attachment/utils.ts +++ b/packages/nc-gui/components/cell/attachment/utils.ts @@ -229,20 +229,6 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState( ;(await import('file-saver')).saveAs(item.url || item.data, item.title) } - /** construct the attachment url - * See /packages/nocodb/src/lib/version-upgrader/ncAttachmentUpgrader.ts for the details - * */ - async function getAttachmentUrl(item: AttachmentType) { - const path = item?.path - // if path doesn't exist, use `item.url` - if (path) { - // try ${appInfo.value.ncSiteUrl}/${item.path} first - return Promise.resolve(`${appInfo.value.ncSiteUrl}/${item.path}`) - } - // if it fails, use the original url - return Promise.resolve(item.url) - } - const FileIcon = (icon: string) => { switch (icon) { case 'mdi-pdf-box': @@ -284,7 +270,6 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState( storedFiles, bulkDownloadFiles, defaultAttachmentMeta, - getAttachmentUrl, } }, 'useAttachmentCell', diff --git a/packages/nc-gui/composables/useKanbanViewStore.ts b/packages/nc-gui/composables/useKanbanViewStore.ts index 21314d8caf..58827e8d42 100644 --- a/packages/nc-gui/composables/useKanbanViewStore.ts +++ b/packages/nc-gui/composables/useKanbanViewStore.ts @@ -144,17 +144,6 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState( rowMeta: {}, })) - async function getAttachmentUrl(item: AttachmentType) { - const path = item?.path - // if path doesn't exist, use `item.url` - if (path) { - // try ${appInfo.value.ncSiteUrl}/${item.path} first - return Promise.resolve(`${appInfo.value.ncSiteUrl}/${item.path}`) - } - // if it fails, use the original url - return Promise.resolve(item.url) - } - async function loadKanbanData() { if ((!project?.value?.id || !meta.value?.id || !viewMeta?.value?.id || !groupingFieldColumn?.value?.id) && !isPublic.value) return @@ -183,28 +172,8 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState( } for (const data of groupData) { - const records = [] const key = data.key - // TODO: optimize - // reconstruct the url - // See /packages/nocodb/src/lib/version-upgrader/ncAttachmentUpgrader.ts for the details - for (const record of data.value.list) { - for (const attachmentColumn of attachmentColumns.value) { - // attachment column can be hidden - if (!record[attachmentColumn!]) continue - const oldAttachment = JSON.parse(record[attachmentColumn!]) - const newAttachment = [] - for (const attachmentObj of oldAttachment) { - newAttachment.push({ - ...attachmentObj, - url: await getAttachmentUrl(attachmentObj), - }) - } - record[attachmentColumn!] = newAttachment - } - records.push(record) - } - formattedData.value.set(key, formatData(records)) + formattedData.value.set(key, formatData(data.value.list)) countByStack.value.set(key, data.value.pageInfo.totalRows || 0) } } diff --git a/packages/nc-gui/composables/useViewData.ts b/packages/nc-gui/composables/useViewData.ts index 69bf5cec66..67a9f77728 100644 --- a/packages/nc-gui/composables/useViewData.ts +++ b/packages/nc-gui/composables/useViewData.ts @@ -201,18 +201,6 @@ export function useViewData( } } - // TODO: refactor - async function getAttachmentUrl(item: AttachmentType) { - const path = item?.path - // if path doesn't exist, use `item.url` - if (path) { - // try ${appInfo.value.ncSiteUrl}/${item.path} first - return Promise.resolve(`${appInfo.ncSiteUrl}/${item.path}`) - } - // if it fails, use the original url - return Promise.resolve(item.url) - } - async function loadData(params: Parameters['dbViewRow']['list']>[4] = {}) { if ((!project?.value?.id || !meta.value?.id || !viewMeta.value?.id) && !isPublic.value) return const response = !isPublic.value @@ -224,27 +212,7 @@ export function useViewData( where: where?.value, }) : await fetchSharedViewData({ sortsArr: sorts.value, filtersArr: nestedFilters.value }) - // reconstruct the url - // See /packages/nocodb/src/lib/version-upgrader/ncAttachmentUpgrader.ts for the details - const records = [] - for (const record of response.list) { - for (const attachmentColumn of attachmentColumns.value) { - // attachment column can be hidden - if (!record[attachmentColumn!]) continue - const oldAttachment = - typeof record[attachmentColumn!] === 'string' ? JSON.parse(record[attachmentColumn!]) : record[attachmentColumn!] - const newAttachment = [] - for (const attachmentObj of oldAttachment) { - newAttachment.push({ - ...attachmentObj, - url: await getAttachmentUrl(attachmentObj), - }) - } - record[attachmentColumn!] = newAttachment - } - records.push(record) - } - formattedData.value = formatData(records) + formattedData.value = formatData(response.list) paginationData.value = response.pageInfo // to cater the case like when querying with a non-zero offset From 5f250b54539181cf8c49c6fbcb6bc68b0de18056 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 7 Feb 2023 11:49:07 +0800 Subject: [PATCH 04/70] fix(nc-gui): use fallback image --- .../components/cell/attachment/index.vue | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/packages/nc-gui/components/cell/attachment/index.vue b/packages/nc-gui/components/cell/attachment/index.vue index 7cf0364f86..e8395bc1a0 100644 --- a/packages/nc-gui/components/cell/attachment/index.vue +++ b/packages/nc-gui/components/cell/attachment/index.vue @@ -42,6 +42,8 @@ const attachmentCellRef = ref() const sortableRef = ref() +const { appInfo } = useGlobal() + const currentCellRef = ref(dropZoneInjection.value) const { cellRefs, isSharedForm } = useSmartsheetStoreOrThrow()! @@ -60,7 +62,6 @@ const { selectedImage, isReadonly, storedFiles, - getAttachmentUrl, } = useProvideAttachmentCell(updateModelValue) watch( @@ -95,22 +96,27 @@ const { state: rowState } = useSmartsheetRowStoreOrThrow() const { isOverDropZone } = useDropZone(currentCellRef as any, onDrop) +const getImgSrc = (item: Record) => { + if (item.data) { + return item.data + } else if (item.path) { + return `${appInfo.value.ncSiteUrl}/${item.path}` + } + return item.url +} + +const showFallback = (evt: any, item: Record) => { + evt.onerror = null + evt.target.src = item.url +} + /** on new value, reparse our stored attachments */ watch( () => modelValue, async (nextModel) => { if (nextModel) { try { - let nextAttachments = ((typeof nextModel === 'string' ? JSON.parse(nextModel) : nextModel) || []).filter(Boolean) - - // reconstruct the url - // See /packages/nocodb/src/lib/version-upgrader/ncAttachmentUpgrader.ts for the details - nextAttachments = await Promise.all( - nextAttachments.map(async (attachment: any) => ({ - ...attachment, - url: await getAttachmentUrl(attachment), - })), - ) + const nextAttachments = ((typeof nextModel === 'string' ? JSON.parse(nextModel) : nextModel) || []).filter(Boolean) if (isPublic.value && isForm.value) { storedFiles.value = nextAttachments @@ -230,15 +236,16 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e) => {
{{ item.title }}
- From 127b5b05353884cd6dab77f3bde7a3a5159bb391 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 9 Feb 2023 16:34:45 +0530 Subject: [PATCH 36/70] fix(nocodb): remove acl for refresh token endpoint Signed-off-by: Pranav C --- packages/nocodb/src/lib/meta/api/userApi/userApis.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/nocodb/src/lib/meta/api/userApi/userApis.ts b/packages/nocodb/src/lib/meta/api/userApi/userApis.ts index e72f673fe1..0ce346ead7 100644 --- a/packages/nocodb/src/lib/meta/api/userApi/userApis.ts +++ b/packages/nocodb/src/lib/meta/api/userApi/userApis.ts @@ -533,7 +533,7 @@ const mapRoutes = (router) => { '/user/password/change', ncMetaAclMw(passwordChange, 'passwordChange') ); - router.post('/auth/token/refresh', ncMetaAclMw(refreshToken, 'refreshToken')); + router.post('/auth/token/refresh', catchError(refreshToken)); /* Google auth apis */ @@ -572,10 +572,7 @@ const mapRoutes = (router) => { '/api/v1/db/auth/password/change', ncMetaAclMw(passwordChange, 'passwordChange') ); - router.post( - '/api/v1/db/auth/token/refresh', - ncMetaAclMw(refreshToken, 'refreshToken') - ); + router.post('/api/v1/db/auth/token/refresh', catchError(refreshToken)); router.get( '/api/v1/db/auth/password/reset/:tokenId', catchError(renderPasswordReset) @@ -606,10 +603,7 @@ const mapRoutes = (router) => { '/api/v1/auth/password/change', ncMetaAclMw(passwordChange, 'passwordChange') ); - router.post( - '/api/v1/auth/token/refresh', - ncMetaAclMw(refreshToken, 'refreshToken') - ); + router.post('/api/v1/auth/token/refresh', catchError(refreshToken)); // respond with password reset page router.get('/auth/password/reset/:tokenId', catchError(renderPasswordReset)); }; From eb98bb12ad54f68686560460f73cca43f7c8f65b Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 9 Feb 2023 17:29:56 +0530 Subject: [PATCH 37/70] fix(gui): when navigate to an auth required page try to populate token using refresh token if user is not logged in Signed-off-by: Pranav C --- packages/nc-gui/components.d.ts | 1 - .../nc-gui/components/template/Editor.vue | 16 ++++------ .../nc-gui/composables/useApi/interceptors.ts | 1 - .../nc-gui/composables/useGlobal/actions.ts | 29 ++++++++++--------- .../nc-gui/composables/useGlobal/index.ts | 2 +- packages/nc-gui/middleware/auth.global.ts | 6 +++- 6 files changed, 28 insertions(+), 27 deletions(-) diff --git a/packages/nc-gui/components.d.ts b/packages/nc-gui/components.d.ts index 016c0125a2..18efdf47a7 100644 --- a/packages/nc-gui/components.d.ts +++ b/packages/nc-gui/components.d.ts @@ -81,7 +81,6 @@ declare module '@vue/runtime-core' { ClaritySuccessLine: typeof import('~icons/clarity/success-line')['default'] EvaEmailOutline: typeof import('~icons/eva/email-outline')['default'] IcBaselineMoreVert: typeof import('~icons/ic/baseline-more-vert')['default'] - Icon: typeof import('~icons/ic/on')['default'] IcOutlineInsertDriveFile: typeof import('~icons/ic/outline-insert-drive-file')['default'] IcRoundEdit: typeof import('~icons/ic/round-edit')['default'] IcRoundKeyboardArrowDown: typeof import('~icons/ic/round-keyboard-arrow-down')['default'] diff --git a/packages/nc-gui/components/template/Editor.vue b/packages/nc-gui/components/template/Editor.vue index a6be949176..7a077e045e 100644 --- a/packages/nc-gui/components/template/Editor.vue +++ b/packages/nc-gui/components/template/Editor.vue @@ -501,16 +501,12 @@ async function importTemplate() { } } } - const createdTable = await $api.base.tableCreate( - project.value?.id as string, - (baseId || project.value?.bases?.[0].id)!, - { - table_name: table.table_name, - // leave title empty to get a generated one based on table_name - title: '', - columns: table.columns || [], - }, - ) + const createdTable = await $api.base.tableCreate(project.value?.id as string, (baseId || project.value?.bases?.[0].id)!, { + table_name: table.table_name, + // leave title empty to get a generated one based on table_name + title: '', + columns: table.columns || [], + }) table.id = createdTable.id table.title = createdTable.title diff --git a/packages/nc-gui/composables/useApi/interceptors.ts b/packages/nc-gui/composables/useApi/interceptors.ts index 22e2c139ff..fa1cd35f09 100644 --- a/packages/nc-gui/composables/useApi/interceptors.ts +++ b/packages/nc-gui/composables/useApi/interceptors.ts @@ -40,7 +40,6 @@ export function addAxiosInterceptors(api: Api) { // Logout user if token refresh didn't work or user is disabled if (error.config.url === '/auth/token/refresh') { state.signOut() - return Promise.reject(error) } diff --git a/packages/nc-gui/composables/useGlobal/actions.ts b/packages/nc-gui/composables/useGlobal/actions.ts index 3dcddc296a..8dc1ffc849 100644 --- a/packages/nc-gui/composables/useGlobal/actions.ts +++ b/packages/nc-gui/composables/useGlobal/actions.ts @@ -28,19 +28,22 @@ export function useGlobalActions(state: State): Actions { const nuxtApp = useNuxtApp() const t = nuxtApp.vueApp.i18n.global.t - nuxtApp.$api.instance - .post('/auth/token/refresh', null, { - withCredentials: true, - }) - .then((response) => { - if (response.data?.token) { - signIn(response.data.token) - } - }) - .catch((err) => { - message.error(err.message || t('msg.error.youHaveBeenSignedOut')) - signOut() - }) + return new Promise((resolve) => { + nuxtApp.$api.instance + .post('/auth/token/refresh', null, { + withCredentials: true, + }) + .then((response) => { + if (response.data?.token) { + signIn(response.data.token) + } + }) + .catch((err) => { + message.error(err.message || t('msg.error.youHaveBeenSignedOut')) + signOut() + }) + .finally(resolve) + }) } const loadAppInfo = async () => { diff --git a/packages/nc-gui/composables/useGlobal/index.ts b/packages/nc-gui/composables/useGlobal/index.ts index 31599e1cf1..81556a715c 100644 --- a/packages/nc-gui/composables/useGlobal/index.ts +++ b/packages/nc-gui/composables/useGlobal/index.ts @@ -53,7 +53,7 @@ export const useGlobal = createGlobalState((): UseGlobalReturn => { state.jwtPayload.value.exp && state.jwtPayload.value.exp - 5 * 60 < state.timestamp.value / 1000 ), - async (expiring) => { + async (expiring: boolean) => { if (getters.signedIn.value && state.jwtPayload.value && expiring) { await actions.refreshToken() } diff --git a/packages/nc-gui/middleware/auth.global.ts b/packages/nc-gui/middleware/auth.global.ts index 2fda82fe37..6bd60b4440 100644 --- a/packages/nc-gui/middleware/auth.global.ts +++ b/packages/nc-gui/middleware/auth.global.ts @@ -52,7 +52,11 @@ export default defineNuxtRouteMiddleware(async (to, from) => { return navigateTo('/signup') } - return navigateTo('/signin') + /** try generating access token using refresh token */ + await state.refreshToken() + + /** if user is still not signed in, redirect to signin page */ + if (!state.signedIn.value) return navigateTo('/signin') } else if (to.meta.requiresAuth === false && state.signedIn.value) { /** * if user was turned away from non-auth page but also came from a non-auth page (e.g. user went to /signin and reloaded the page) From a7e63fdb552272c3d9f2f8a1effcc8b3b6b71f91 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 9 Feb 2023 19:19:05 +0530 Subject: [PATCH 38/70] refactor(gui): render attachment lookup in the same way how it renders in normal cell Signed-off-by: Pranav C --- packages/nc-gui/components/virtual-cell/Lookup.vue | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/nc-gui/components/virtual-cell/Lookup.vue b/packages/nc-gui/components/virtual-cell/Lookup.vue index b5a8abc9af..6a38a8f768 100644 --- a/packages/nc-gui/components/virtual-cell/Lookup.vue +++ b/packages/nc-gui/components/virtual-cell/Lookup.vue @@ -106,10 +106,7 @@ const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning, activ