Browse Source

Merge branch 'develop' into feat/ajv-validation-followup

pull/5222/head
Wing-Kam Wong 2 years ago
parent
commit
437cb670d1
  1. 2
      packages/nc-gui/components/account/ResetPassword.vue
  2. 4
      packages/nc-gui/components/general/MiniSidebar.vue
  3. 4
      packages/nc-gui/components/smartsheet/Grid.vue
  4. 5
      packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
  5. 2
      packages/nc-gui/components/smartsheet/column/SpecificDBTypeOptions.vue
  6. 18
      packages/nc-gui/components/smartsheet/header/Cell.vue
  7. 4
      packages/nc-gui/components/smartsheet/header/CellIcon.ts
  8. 2
      packages/nc-gui/composables/useApi/interceptors.ts
  9. 12
      packages/nc-gui/composables/useGlobal/actions.ts
  10. 4
      packages/nc-gui/layouts/base.vue
  11. 4
      packages/nc-gui/pages/[projectType]/[projectId]/index.vue
  12. 2
      packages/nc-gui/pages/index/index/user.vue
  13. 1
      packages/nc-gui/utils/columnUtils.ts
  14. 3
      packages/noco-docs/content/en/setup-and-usages/column-types.md
  15. 25
      packages/nocodb-sdk/src/lib/Api.ts
  16. 34
      packages/nocodb/src/lib/meta/api/userApi/userApis.ts
  17. 28
      packages/nocodb/src/schema/swagger.json

2
packages/nc-gui/components/account/ResetPassword.vue

@ -54,7 +54,7 @@ const passwordChange = async () => {
message.success(t('msg.success.passwordChanged')) message.success(t('msg.success.passwordChanged'))
signOut() await signOut()
navigateTo('/signin') navigateTo('/signin')
} }

4
packages/nc-gui/components/general/MiniSidebar.vue

@ -11,8 +11,8 @@ const route = useRoute()
const email = computed(() => user.value?.email ?? '---') const email = computed(() => user.value?.email ?? '---')
const logout = () => { const logout = async () => {
signOut() await signOut()
navigateTo('/signin') navigateTo('/signin')
} }
</script> </script>

4
packages/nc-gui/components/smartsheet/Grid.vue

@ -1056,7 +1056,7 @@ const closeAddColumnDropdown = () => {
position: sticky !important; position: sticky !important;
left: 80px; left: 80px;
z-index: 5; z-index: 5;
@apply border-r-1 border-r-gray-300; @apply border-r-2 border-r-gray-300;
} }
tbody td:nth-child(2) { tbody td:nth-child(2) {
@ -1064,7 +1064,7 @@ const closeAddColumnDropdown = () => {
left: 80px; left: 80px;
z-index: 4; z-index: 4;
background: white; background: white;
@apply shadow-lg border-r-1 border-r-gray-300; @apply border-r-2 border-r-gray-300;
} }
} }

5
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue

@ -232,7 +232,10 @@ useEventListener('keydown', (e: KeyboardEvent) => {
v-model:value="formState" v-model:value="formState"
/> />
<LazySmartsheetColumnAdvancedOptions v-model:value="formState" :advanced-db-options="advancedDbOptions" /> <LazySmartsheetColumnAdvancedOptions
v-model:value="formState"
:advanced-db-options="advancedDbOptions || formState.uidt === UITypes.SpecificDBType"
/>
</div> </div>
</Transition> </Transition>

2
packages/nc-gui/components/smartsheet/column/SpecificDBTypeOptions.vue

@ -1,3 +1,3 @@
<template> <template>
<div class="mt-4 mb-2" /> <div />
</template> </template>

18
packages/nc-gui/components/smartsheet/header/Cell.vue

@ -7,6 +7,7 @@ interface Props {
required?: boolean | number required?: boolean | number
hideMenu?: boolean hideMenu?: boolean
} }
const props = defineProps<Props>() const props = defineProps<Props>()
const hideMenu = toRef(props, 'hideMenu') const hideMenu = toRef(props, 'hideMenu')
@ -34,6 +35,12 @@ const closeAddColumnDropdown = () => {
columnOrder.value = null columnOrder.value = null
editColumnDropdown.value = false editColumnDropdown.value = false
} }
const openHeaderMenu = () => {
if (!isForm.value && isUIAllowed('edit-column')) {
editColumnDropdown.value = true
}
}
</script> </script>
<template> <template>
@ -44,10 +51,11 @@ const closeAddColumnDropdown = () => {
<SmartsheetHeaderCellIcon v-if="column" /> <SmartsheetHeaderCellIcon v-if="column" />
<span <span
v-if="column" v-if="column"
class="name cursor-pointer" class="name"
:class="{ 'cursor-pointer': !isForm && isUIAllowed('edit-column') }"
style="white-space: nowrap" style="white-space: nowrap"
:title="column.title" :title="column.title"
@dblclick="editColumnDropdown = true" @dblclick="openHeaderMenu"
>{{ column.title }}</span >{{ column.title }}</span
> >
@ -56,11 +64,7 @@ const closeAddColumnDropdown = () => {
<template v-if="!hideMenu"> <template v-if="!hideMenu">
<div class="flex-1" /> <div class="flex-1" />
<LazySmartsheetHeaderMenu <LazySmartsheetHeaderMenu v-if="!isForm && isUIAllowed('edit-column')" @add-column="addField" @edit="openHeaderMenu" />
v-if="!isForm && isUIAllowed('edit-column')"
@add-column="addField"
@edit="editColumnDropdown = true"
/>
</template> </template>
<a-dropdown <a-dropdown

4
packages/nc-gui/components/smartsheet/header/CellIcon.ts

@ -60,6 +60,8 @@ import DurationIcon from '~icons/mdi/timer-outline'
const renderIcon = (column: ColumnType, abstractType: any) => { const renderIcon = (column: ColumnType, abstractType: any) => {
if (isPrimaryKey(column)) { if (isPrimaryKey(column)) {
return KeyIcon return KeyIcon
} else if (isSpecificDBType(column)) {
return SpecificDBTypeIcon
} else if (isJSON(column)) { } else if (isJSON(column)) {
return JSONIcon return JSONIcon
} else if (isDate(column, abstractType)) { } else if (isDate(column, abstractType)) {
@ -102,8 +104,6 @@ const renderIcon = (column: ColumnType, abstractType: any) => {
return NumericIcon return NumericIcon
} else if (isString(column, abstractType)) { } else if (isString(column, abstractType)) {
return StringIcon return StringIcon
} else if (isSpecificDBType(column)) {
return SpecificDBTypeIcon
} else { } else {
return GenericIcon return GenericIcon
} }

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

@ -66,7 +66,7 @@ export function addAxiosInterceptors(api: Api<any>) {
}) })
}) })
.catch(async (error) => { .catch(async (error) => {
state.signOut() await state.signOut()
// todo: handle new user // todo: handle new user
navigateTo('/signIn') navigateTo('/signIn')

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

@ -3,9 +3,15 @@ import { message, useNuxtApp } from '#imports'
export function useGlobalActions(state: State): Actions { export function useGlobalActions(state: State): Actions {
/** Sign out by deleting the token from localStorage */ /** Sign out by deleting the token from localStorage */
const signOut: Actions['signOut'] = () => { const signOut: Actions['signOut'] = async () => {
state.token.value = null state.token.value = null
state.user.value = null state.user.value = null
try {
if (state.token.value) {
const nuxtApp = useNuxtApp()
await nuxtApp.$api.auth.signout()
}
} catch {}
} }
/** Sign in by setting the token in localStorage */ /** Sign in by setting the token in localStorage */
@ -38,9 +44,9 @@ export function useGlobalActions(state: State): Actions {
signIn(response.data.token) signIn(response.data.token)
} }
}) })
.catch((err) => { .catch(async (err) => {
message.error(err.message || t('msg.error.youHaveBeenSignedOut')) message.error(err.message || t('msg.error.youHaveBeenSignedOut'))
signOut() await signOut()
}) })
.finally(resolve) .finally(resolve)
}) })

4
packages/nc-gui/layouts/base.vue

@ -13,8 +13,8 @@ const hasSider = ref(false)
const sidebar = ref<HTMLDivElement>() const sidebar = ref<HTMLDivElement>()
const logout = () => { const logout = async () => {
signOut() await signOut()
navigateTo('/signin') navigateTo('/signin')
} }

4
packages/nc-gui/pages/[projectType]/[projectId]/index.vue

@ -70,8 +70,8 @@ const sidebar = ref()
const email = computed(() => user.value?.email ?? '---') const email = computed(() => user.value?.email ?? '---')
const logout = () => { const logout = async () => {
signOut() await signOut()
navigateTo('/signin') navigateTo('/signin')
} }

2
packages/nc-gui/pages/index/index/user.vue

@ -57,7 +57,7 @@ const passwordChange = async () => {
message.success(t('msg.success.passwordChanged')) message.success(t('msg.success.passwordChanged'))
signOut() await signOut()
navigateTo('/signin') navigateTo('/signin')
} }

1
packages/nc-gui/utils/columnUtils.ts

@ -221,6 +221,7 @@ const isTypableInputColumn = (colOrUidt: ColumnType | UITypes) => {
UITypes.Duration, UITypes.Duration,
UITypes.JSON, UITypes.JSON,
UITypes.URL, UITypes.URL,
UITypes.SpecificDBType,
].includes(uidt) ].includes(uidt)
} }

3
packages/noco-docs/content/en/setup-and-usages/column-types.md

@ -10,9 +10,7 @@ menuTitle: 'Column Types'
| Type | Description | | Type | Description |
|---|---| |---|---|
<!-- | [ID](#id) | Primary column of the table | -->
| [LinkToAnotherRecord](#linktoanotherrecord) | Has Many or Many To Many columns | | [LinkToAnotherRecord](#linktoanotherrecord) | Has Many or Many To Many columns |
<!-- | [ForeignKey](#foreignkey)| Belongs To relation | -->
| [SingleLineText](#singlelinetext) | For short text | | [SingleLineText](#singlelinetext) | For short text |
| [LongText](#longtext) | For lengthy string content | | [LongText](#longtext) | For lengthy string content |
| [Attachment](#attachment) | File attachment column | | [Attachment](#attachment) | File attachment column |
@ -36,7 +34,6 @@ menuTitle: 'Column Types'
| [DateTime](#datetime)| Date & Time selector | | [DateTime](#datetime)| Date & Time selector |
| [QR Code](#qr-code)| QR Code visualization of another referenced column | | [QR Code](#qr-code)| QR Code visualization of another referenced column |
| [Barcode](#barcode)| Barcode visualization of another referenced column | | [Barcode](#barcode)| Barcode visualization of another referenced column |
<!-- | [CreateTime](#createtime)| | -->
| [Geometry](#geometry)| Geometry column | | [Geometry](#geometry)| Geometry column |
| [GeoData](#geodata)| GeoData column | | [GeoData](#geodata)| GeoData column |
| [Json](#json)| Json column | | [Json](#json)| Json column |

25
packages/nocodb-sdk/src/lib/Api.ts

@ -1224,6 +1224,31 @@ export class Api<
...params, ...params,
}), }),
/**
* @description Clear refresh token from the database and cookie.
*
* @tags Auth
* @name Signout
* @summary Signout
* @request POST:/api/v1/auth/user/signout
* @response `200` `{
msg?: string,
}` OK
*/
signout: (params: RequestParams = {}) =>
this.request<
{
msg?: string;
},
any
>({
path: `/api/v1/auth/user/signout`,
method: 'POST',
format: 'json',
...params,
}),
/** /**
* @description Authenticate existing user with their email and password. Successful login will return a JWT access-token. * @description Authenticate existing user with their email and password. Successful login will return a JWT access-token.
* *

34
packages/nocodb/src/lib/meta/api/userApi/userApis.ts

@ -279,13 +279,15 @@ async function googleSignin(req, res, next) {
)(req, res, next); )(req, res, next);
} }
const REFRESH_TOKEN_COOKIE_KEY = 'refresh_token';
function setTokenCookie(res, token): void { function setTokenCookie(res, token): void {
// create http only cookie with refresh token that expires in 7 days // create http only cookie with refresh token that expires in 7 days
const cookieOptions = { const cookieOptions = {
httpOnly: true, httpOnly: true,
expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
}; };
res.cookie('refresh_token', token, cookieOptions); res.cookie(REFRESH_TOKEN_COOKIE_KEY, token, cookieOptions);
} }
async function me(req, res): Promise<any> { async function me(req, res): Promise<any> {
@ -485,7 +487,9 @@ async function refreshToken(req, res): Promise<any> {
return res.status(400).json({ msg: 'Missing refresh token' }); return res.status(400).json({ msg: 'Missing refresh token' });
} }
const user = await User.getByRefreshToken(req.cookies.refresh_token); const user = await User.getByRefreshToken(
req.cookies[REFRESH_TOKEN_COOKIE_KEY]
);
if (!user) { if (!user) {
return res.status(400).json({ msg: 'Invalid refresh token' }); return res.status(400).json({ msg: 'Invalid refresh token' });
@ -522,6 +526,30 @@ async function renderPasswordReset(req, res): Promise<any> {
} }
} }
// clear refresh token cookie and update user refresh token to null
const signout = async (req, res): Promise<any> => {
const resBody = { msg: 'Success' };
if (!req.cookies[REFRESH_TOKEN_COOKIE_KEY]) {
return res.json(resBody);
}
const user = await User.getByRefreshToken(
req.cookies[REFRESH_TOKEN_COOKIE_KEY]
);
if (!user) {
return res.json(resBody);
}
res.clearCookie(REFRESH_TOKEN_COOKIE_KEY);
await User.update(user.id, {
refresh_token: null,
});
res.json(resBody);
};
const mapRoutes = (router) => { const mapRoutes = (router) => {
// todo: old api - /auth/signup?tool=1 // todo: old api - /auth/signup?tool=1
router.post( router.post(
@ -534,6 +562,7 @@ const mapRoutes = (router) => {
getAjvValidatorMw('swagger.json#/components/schemas/SignInReq'), getAjvValidatorMw('swagger.json#/components/schemas/SignInReq'),
catchError(signin) catchError(signin)
); );
router.post('/auth/user/signout', catchError(signout));
router.get('/auth/user/me', extractProjectIdAndAuthenticate, catchError(me)); router.get('/auth/user/me', extractProjectIdAndAuthenticate, catchError(me));
router.post( router.post(
'/auth/password/forgot', '/auth/password/forgot',
@ -622,6 +651,7 @@ const mapRoutes = (router) => {
getAjvValidatorMw('swagger.json#/components/schemas/SignInReq'), getAjvValidatorMw('swagger.json#/components/schemas/SignInReq'),
catchError(signin) catchError(signin)
); );
router.post('/api/v1/auth/user/signout', catchError(signout));
router.get( router.get(
'/api/v1/auth/user/me', '/api/v1/auth/user/me',
extractProjectIdAndAuthenticate, extractProjectIdAndAuthenticate,

28
packages/nocodb/src/schema/swagger.json

@ -95,6 +95,34 @@
"description": "Create a new user with provided email and password and first user is marked as super admin. " "description": "Create a new user with provided email and password and first user is marked as super admin. "
} }
}, },
"/api/v1/auth/user/signout": {
"post": {
"summary": "Signout",
"operationId": "auth-signout",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"msg": {
"type": "string"
}
}
}
}
}
}
},
"tags": [
"Auth"
],
"description": "Clear refresh token from the database and cookie."
},
"parameters": []
},
"/api/v1/auth/user/signin": { "/api/v1/auth/user/signin": {
"post": { "post": {
"summary": "Signin", "summary": "Signin",

Loading…
Cancel
Save