Browse Source

Merge pull request #7444 from nocodb/nc-fix/refresh-token-issue

Nc fix: Refresh token related issues
pull/7449/head
Pranav C 10 months ago committed by GitHub
parent
commit
866a21c72c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 28
      packages/nc-gui/components/smartsheet/details/Fields.vue
  2. 6
      packages/nc-gui/composables/useApi/index.ts
  3. 14
      packages/nc-gui/composables/useApi/interceptors.ts
  4. 9
      packages/nc-gui/composables/useGlobal/actions.ts
  5. 2
      packages/nc-gui/composables/useGlobal/types.ts
  6. 2
      packages/nocodb/src/helpers/PagedResponse.ts
  7. 4
      packages/nocodb/src/middlewares/extract-ids/extract-ids.middleware.ts
  8. 5
      packages/nocodb/src/strategies/jwt.strategy.ts

28
packages/nc-gui/components/smartsheet/details/Fields.vue

@ -819,8 +819,8 @@ watch(
<template v-else>
<div class="flex w-full justify-between py-2">
<a-input
data-testid="nc-field-search-input"
v-model:value="searchQuery"
data-testid="nc-field-search-input"
class="!h-8 !px-1 !rounded-lg !w-72"
:placeholder="$t('placeholder.searchFields')"
>
@ -832,8 +832,8 @@ watch(
v-if="searchQuery.length > 0"
icon="close"
class="mx-1 h-3.5 w-3.5 text-gray-500 group-hover:text-black"
@click="searchQuery = ''"
data-testid="nc-field-clear-search"
@click="searchQuery = ''"
/>
</template>
</a-input>
@ -888,16 +888,16 @@ watch(
:model-value="fields"
:disabled="isLocked"
item-key="id"
@change="onMove($event)"
data-testid="nc-field-list-wrapper"
@change="onMove($event)"
>
<template #item="{ element: field }">
<div
v-if="field.title.toLowerCase().includes(searchQuery.toLowerCase()) && !field.pv"
class="flex px-2 hover:bg-gray-100 first:rounded-t-lg border-b-1 last:rounded-b-none border-gray-200 pl-5 group"
:class="` ${compareCols(field, activeField) ? 'selected' : ''}`"
@click="changeField(field, $event)"
:data-testid="`nc-field-item-${fieldState(field)?.title || field.title}`"
@click="changeField(field, $event)"
>
<div class="flex items-center flex-1 py-2.5 gap-1 w-2/6">
<component
@ -913,12 +913,12 @@ watch(
:checked="
visibilityOps.find((op) => op.column.fk_column_id === field.id)?.visible ?? viewFieldsMap[field.id].show
"
data-testid="nc-field-visibility-checkbox"
@change="
(event: any) => {
toggleVisibility(event.target.checked, viewFieldsMap[field.id])
}
"
data-testid="nc-field-visibility-checkbox"
/>
<NcCheckbox v-else :disabled="true" class="opacity-0" :checked="true" />
<SmartsheetHeaderVirtualCellIcon
@ -994,8 +994,8 @@ watch(
size="small"
class="no-action mr-2"
:disabled="loading"
@click="recoverField(field)"
data-testid="nc-field-restore-changes"
@click="recoverField(field)"
>
<div class="flex items-center text-xs gap-1">
<GeneralIcon icon="reload" />
@ -1030,8 +1030,8 @@ watch(
<div
class="flex flex-row px-3 py-2 w-46 justify-between items-center group hover:bg-gray-100 cursor-pointer"
@click="onClickCopyFieldUrl(field)"
data-testid="nc-field-item-action-copy-id"
@click="onClickCopyFieldUrl(field)"
>
<div class="flex flex-row items-baseline gap-x-1 font-bold text-xs">
<div class="text-gray-600">{{ $t('labels.idColon') }}</div>
@ -1051,16 +1051,16 @@ watch(
<template v-if="!isLocked">
<NcMenuItem
key="table-explorer-duplicate"
@click="duplicateField(field)"
data-testid="nc-field-item-action-duplicate"
@click="duplicateField(field)"
>
<Icon class="iconify text-gray-800" icon="lucide:copy" /><span>{{ $t('general.duplicate') }}</span>
</NcMenuItem>
<NcMenuItem
v-if="!field.pv"
key="table-explorer-insert-above"
@click="addField(field, true)"
data-testid="nc-field-item-action-insert-above"
@click="addField(field, true)"
>
<Icon class="iconify text-gray-800" icon="lucide:arrow-up" /><span>{{
$t('general.insertAbove')
@ -1068,8 +1068,8 @@ watch(
</NcMenuItem>
<NcMenuItem
key="table-explorer-insert-below"
@click="addField(field)"
data-testid="nc-field-item-action-insert-below"
@click="addField(field)"
>
<Icon class="iconify text-gray-800" icon="lucide:arrow-down" /><span>{{
$t('general.insertBelow')
@ -1081,8 +1081,8 @@ watch(
<NcMenuItem
key="table-explorer-delete"
class="!hover:bg-red-50"
@click="onFieldDelete(field)"
data-testid="nc-field-item-action-delete"
@click="onFieldDelete(field)"
>
<div class="text-red-500">
<GeneralIcon icon="delete" class="group-hover:text-accent -ml-0.25 -mt-0.75 mr-0.5" />
@ -1111,8 +1111,8 @@ watch(
<div
class="flex px-2 bg-white hover:bg-gray-100 border-b-1 border-gray-200 first:rounded-tl-lg last:border-b-1 pl-5 group"
:class="` ${compareCols(displayColumn, activeField) ? 'selected' : ''}`"
@click="changeField(displayColumn, $event)"
:data-testid="`nc-field-item-${fieldState(displayColumn)?.title || displayColumn.title}`"
@click="changeField(displayColumn, $event)"
>
<div class="flex items-center flex-1 py-2.5 gap-1 w-2/6">
<component
@ -1171,8 +1171,8 @@ watch(
size="small"
class="no-action mr-2"
:disabled="loading"
@click="recoverField(displayColumn)"
data-testid="nc-field-restore-changes"
@click="recoverField(displayColumn)"
>
<div class="flex items-center text-xs gap-1">
<GeneralIcon icon="reload" />
@ -1212,8 +1212,8 @@ watch(
<div
class="flex flex-row px-3 py-2 w-46 justify-between items-center group hover:bg-gray-100 cursor-pointer"
@click="onClickCopyFieldUrl(displayColumn)"
data-testid="nc-field-item-action-copy-id"
@click="onClickCopyFieldUrl(displayColumn)"
>
<div class="flex flex-row items-baseline gap-x-1 font-bold text-xs">
<div class="text-gray-600">{{ $t('labels.idColon') }}</div>

6
packages/nc-gui/composables/useApi/index.ts

@ -6,13 +6,13 @@ import { addAxiosInterceptors } from './interceptors'
import { BASE_FALLBACK_URL, createEventHook, extractSdkResponseErrorMsg, ref, unref, useCounter, useNuxtApp } from '#imports'
export function createApiInstance<SecurityDataType = any>({
baseURL = BASE_FALLBACK_URL,
baseURL: _baseUrl = BASE_FALLBACK_URL,
}: CreateApiOptions = {}): Api<SecurityDataType> {
const config = useRuntimeConfig()
const baseURL = config.public.ncBackendUrl || _baseUrl
return addAxiosInterceptors(
new Api<SecurityDataType>({
baseURL: config.public.ncBackendUrl || baseURL,
baseURL,
}),
)
}

14
packages/nc-gui/composables/useApi/interceptors.ts

@ -11,7 +11,9 @@ export function addAxiosInterceptors(api: Api<any>) {
const route = router.currentRoute
const optimisedQuery = useState('optimisedQuery', () => true)
api.instance.interceptors.request.use((config) => {
const axiosInstance = api.instance
axiosInstance.interceptors.request.use((config) => {
config.headers['xc-gui'] = 'true'
if (state.token.value) config.headers['xc-auth'] = state.token.value
@ -38,7 +40,7 @@ export function addAxiosInterceptors(api: Api<any>) {
})
// Return a successful response back to the calling service
api.instance.interceptors.response.use(
axiosInstance.interceptors.response.use(
(response) => {
return response
},
@ -70,7 +72,7 @@ export function addAxiosInterceptors(api: Api<any>) {
return new Promise((resolve, reject) => {
const config = error.config
config.headers['xc-auth'] = token
api.instance
axiosInstance
.request(config)
.then((response) => {
resolve(response)
@ -96,7 +98,7 @@ export function addAxiosInterceptors(api: Api<any>) {
}
// Try request again with new token
return api.instance
return axiosInstance
.post('/auth/token/refresh', null, {
withCredentials: true,
})
@ -104,14 +106,14 @@ export function addAxiosInterceptors(api: Api<any>) {
// New request with new token
const config = error.config
config.headers['xc-auth'] = token.data.token
state.signIn(token.data.token)
state.signIn(token.data.token, true)
// resolve the refresh token promise and reset
refreshTokenPromiseRes(token.data.token)
refreshTokenPromise = null
return new Promise((resolve, reject) => {
api.instance
axiosInstance
.request(config)
.then((response) => {
resolve(response)

9
packages/nc-gui/composables/useGlobal/actions.ts

@ -29,12 +29,15 @@ export function useGlobalActions(state: State): Actions {
}
}
/** Sign in by setting the token in localStorage */
const signIn: Actions['signIn'] = async (newToken) => {
/** Sign in by setting the token in localStorage
* keepProps - is for keeping any existing role info if user id is same as previous user
* */
const signIn: Actions['signIn'] = async (newToken, keepProps = false) => {
state.token.value = newToken
if (state.jwtPayload.value) {
state.user.value = {
...(keepProps && state.user.value?.id === state.jwtPayload.value.id ? state.user.value || {} : {}),
id: state.jwtPayload.value.id,
email: state.jwtPayload.value.email,
firstname: state.jwtPayload.value.firstname,
@ -57,7 +60,7 @@ export function useGlobalActions(state: State): Actions {
})
.then((response) => {
if (response.data?.token) {
signIn(response.data.token)
signIn(response.data.token, true)
}
})
.catch(async () => {

2
packages/nc-gui/composables/useGlobal/types.ts

@ -70,7 +70,7 @@ export interface Getters {
export interface Actions {
signOut: (skipRedirect?: boolean) => void
signIn: (token: string) => void
signIn: (token: string, keepProps?: boolean) => void
refreshToken: () => void
loadAppInfo: () => void
setIsMobileMode: (isMobileMode: boolean) => void

2
packages/nocodb/src/helpers/PagedResponse.ts

@ -1,6 +1,6 @@
import { extractLimitAndOffset } from '.';
import type { PaginatedType } from 'nocodb-sdk';
import {NcError} from "~/helpers/catchError";
import { NcError } from '~/helpers/catchError';
export class PagedResponseImpl<T> {
constructor(

4
packages/nocodb/src/middlewares/extract-ids/extract-ids.middleware.ts

@ -216,6 +216,10 @@ export class AclMiddleware implements NestInterceptor {
const req = context.switchToHttp().getRequest();
if (!req.user?.isAuthorized) {
NcError.unauthorized('Invalid token');
}
const userScopeRole =
req.user.roles?.[OrgUserRoles.SUPER_ADMIN] === true
? OrgUserRoles.SUPER_ADMIN

5
packages/nocodb/src/strategies/jwt.strategy.ts

@ -25,10 +25,11 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
) {
throw new Error('Token Expired. Please login again.');
}
return User.getWithRoles(user.id, {
const userWithRoles = await User.getWithRoles(user.id, {
user,
baseId: req.ncBaseId,
});
return userWithRoles && { ...userWithRoles, isAuthorized: true };
}
}

Loading…
Cancel
Save