Browse Source

feat: token api integration

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/4134/head
Pranav C 2 years ago
parent
commit
48197cec59
  1. 99
      packages/nc-gui/components/admin/Token.vue
  2. 240
      packages/nc-gui/components/admin/User.vue
  3. 1
      packages/nc-gui/lang/en.json
  4. 30
      packages/nc-gui/pages/admin/index.vue
  5. 16
      packages/nc-gui/pages/admin/index/[page].vue
  6. 2
      packages/nc-gui/pages/index/index.vue
  7. 2
      packages/nocodb/src/lib/meta/NcMetaMgr.ts
  8. 2
      packages/nocodb/src/lib/meta/NcMetaMgrv2.ts
  9. 3
      packages/nocodb/src/lib/meta/api/apiTokenApis.ts
  10. 28
      packages/nocodb/src/lib/meta/api/orgTokenApis.ts
  11. 13
      packages/nocodb/src/lib/meta/api/orgUserApis.ts
  12. 2
      packages/nocodb/src/lib/meta/api/projectApis.ts
  13. 2
      packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts
  14. 2
      packages/nocodb/src/lib/meta/api/userApi/userApis.ts
  15. 2
      packages/nocodb/src/lib/meta/api/utilApis.ts
  16. 2
      packages/nocodb/src/lib/models/ApiToken.ts
  17. 68
      scripts/sdk/swagger.json

99
packages/nc-gui/components/admin/Token.vue

@ -1,21 +1,25 @@
<script lang="ts" setup>
import { message, Empty, Modal } from 'ant-design-vue'
import type { RequestParams, UserType } from 'nocodb-sdk'
import { extractSdkResponseErrorMsg, useApi , useCopy} from '#imports'
import { Empty, Modal, message } from 'ant-design-vue'
import type { ApiTokenType, RequestParams, UserType } from 'nocodb-sdk'
import { extractSdkResponseErrorMsg, useApi, useCopy, useNuxtApp } from '#imports'
const { api, isLoading } = useApi()
const { $e } = useNuxtApp()
const { copy } = useCopy()
const { t } = useI18n()
let tokens = $ref<UserType[]>([])
let currentPage = $ref(1)
let showNewTokenModal = $ref(false)
const currentLimit = $ref(10)
const showUserModal = ref(false)
let selectedTokenData = $ref<ApiTokenType>({})
const searchText = ref<string>('')
@ -38,40 +42,65 @@ const loadTokens = async (page = currentPage, limit = currentLimit) => {
pagination.pageSize = 10
tokens = response.list as UserType[]
} catch (e: any) {
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
loadTokens()
const deleteToken = async (userId: string) => {
const deleteToken = async (token: string) => {
Modal.confirm({
title: 'Are you sure you want to delete this token?',
type: 'warn',
onOk: async () => {
onOk: async () => {
try {
// todo: delete token
// await api.orgUsers.delete(userId)
// message.success('User deleted successfully')
// await loadUsers()
} catch (e: any) {
await api.orgTokens.delete(token)
message.success('Token deleted successfully')
await loadTokens()
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
},
})
}
const generateToken = async () => {
try {
await api.orgTokens.create(selectedTokenData)
showNewTokenModal = false
// Token generated successfully
message.success(t('msg.success.tokenGenerated'))
selectedTokenData = {}
await loadTokens()
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
$e('a:api-token:generate')
}
const copyToken = (token: string | undefined) => {
if (!token) return
copy(token)
// Copied to clipboard
message.info(t('msg.info.copiedToClipboard'))
$e('c:api-token:copy')
}
</script>
<template>
<div class="h-full overflow-y-scroll scrollbar-thin-dull">
<div class="text-xl mt-4">Token Management</div>
<a-divider class="!my-3" />
<div class="max-w-[700px] mx-auto p-4">
<div class="py-2 flex">
<div class="max-w-[900px] mx-auto p-4">
<div class="py-2 flex gap-4 items-center">
<div class="flex-grow"></div>
<a-button size="small" @click="showUserModal = true">
<MdiReload @click="loadTokens" class="cursor-pointer"/>
<a-button size="small" @click="showNewTokenModal = true">
<div class="flex items-center gap-1">
<MdiAdd />
Add new token
@ -83,27 +112,30 @@ const deleteToken = async (userId: string) => {
:data-source="tokens"
:pagination="{ position: ['bottomCenter'] }"
:loading="isLoading"
@change="loadTokens($event.current)"
size="small"
@change="loadTokens($event.current)"
>
<template #emptyText>
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" />
</template>
<!-- Description -->
<a-table-column key="description" :title="$t('labels.description')" data-index="description">
<template #default="{ text }">
{{ text }}
</template>
</a-table-column>
<!-- Token -->
<a-table-column key="createdBy" :title="$t('labels.createdBy')" data-index="createdBy">
<a-table-column key="created_by" :title="$t('labels.createdBy')" data-index="created_by">
<template #default="{ text }">
<div>
{{ text ?? 'N/A' }}
<div v-if="text">
{{ text }}
</div>
<div v-else class="text-gray-400">N/A</div>
</template>
</a-table-column>
<!-- Token -->
<a-table-column key="token" :title="$t('labels.token')" data-index="token">
<template #default="{ text, record }">
@ -114,12 +146,9 @@ const deleteToken = async (userId: string) => {
</template>
</a-table-column>
<!-- Actions -->
<a-table-column key="id" :title="$t('labels.actions')" data-index="id">
<a-table-column key="actions" :title="$t('labels.actions')" data-index="token">
<template #default="{ record }">
<div class="flex items-center gap-2">
<a-tooltip placement="bottom">
@ -137,16 +166,21 @@ const deleteToken = async (userId: string) => {
</a-tooltip>
<a-tooltip placement="bottom">
<template #title> {{ $t('general.copy') }} </template>
<template #title> {{ $t('general.copy') }}</template>
<a-button type="text" class="!rounded-md" @click="copy(record.token)">
<a-button type="text" class="!rounded-md" @click="copyToken(record.token)">
<template #icon>
<MdiContentCopy class="flex mx-auto h-[1rem]" />
</template>
</a-button>
</a-tooltip>
<a-dropdown :trigger="['click']" class="flex" placement="bottomRight" overlay-class-name="nc-dropdown-api-token-mgmt">
<a-dropdown
:trigger="['click']"
class="flex"
placement="bottomRight"
overlay-class-name="nc-dropdown-api-token-mgmt"
>
<div class="flex flex-row items-center">
<a-button type="text" class="!px-0">
<div class="flex flex-row items-center h-[1.2rem]">
@ -158,7 +192,7 @@ const deleteToken = async (userId: string) => {
<template #overlay>
<a-menu>
<a-menu-item>
<div class="flex flex-row items-center py-3 h-[1rem]" @click="deleteToken(record)">
<div class="flex flex-row items-center py-3 h-[1rem]" @click="deleteToken(record.token)">
<MdiDeleteOutline class="flex" />
<div class="text-xs pl-2">{{ $t('general.remove') }}</div>
</div>
@ -166,13 +200,10 @@ const deleteToken = async (userId: string) => {
</a-menu>
</template>
</a-dropdown>
</div>
</template>
</a-table-column>
</a-table>
</div>
<a-modal

240
packages/nc-gui/components/admin/User.vue

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { Modal, message } from 'ant-design-vue'
import type { RequestParams, UserType } from 'nocodb-sdk'
import { Role, extractSdkResponseErrorMsg, useApi, useDashboard, useNuxtApp , useCopy} from '#imports'
import { Role, extractSdkResponseErrorMsg, useApi, useCopy, useDashboard, useNuxtApp } from '#imports'
import type { User } from '~/lib'
const { api, isLoading } = useApi()
@ -108,9 +108,8 @@ const copyInviteUrl = (user: User) => {
<template>
<div class="h-full overflow-y-scroll scrollbar-thin-dull">
<a-tabs v-model:active-key="selectedTabKey" :open-keys="[]" mode="horizontal" class="nc-auth-tabs ">
<a-tab-pane v-for="(tab, key) of [{label:'Users'},{label:'Settings'}]" :key="key" class="select-none">
<a-tabs v-model:active-key="selectedTabKey" :open-keys="[]" mode="horizontal" class="nc-auth-tabs">
<a-tab-pane v-for="(tab, key) of [{ label: 'Users' }, { label: 'Settings' }]" :key="key" class="select-none">
<template #tab>
<span>
{{ tab.label }}
@ -119,141 +118,142 @@ const copyInviteUrl = (user: User) => {
</a-tab-pane>
</a-tabs>
<template v-if="selectedTabKey === 0">
<!-- <div class="text-xl mt-4">User Management</div>-->
<!-- <a-divider class="!my-3" />-->
<div class="max-w-[700px] mx-auto p-4">
<div class="py-2 flex">
<a-input-search
v-model:value="searchText"
<!-- <div class="text-xl mt-4">User Management</div> -->
<!-- <a-divider class="!my-3" /> -->
<div class="max-w-[900px] mx-auto p-4">
<div class="py-2 flex gap-4 items-center">
<a-input-search
v-model:value="searchText"
size="small"
class="max-w-[300px]"
placeholder="Filter by email"
@blur="loadUsers"
@keydown.enter="loadUsers"
>
</a-input-search>
<div class="flex-grow"></div>
<MdiReload @click="loadUsers" class="cursor-pointer"/>
<a-button size="small" @click="showUserModal = true">
<div class="flex items-center gap-1">
<MdiAdd />
Invite new user
</div>
</a-button>
</div>
<a-table
:row-key="(record) => record.id"
:data-source="users"
:pagination="{ position: ['bottomCenter'] }"
:loading="isLoading"
size="small"
class="max-w-[300px]"
placeholder="Filter by email"
@blur="loadUsers"
@keydown.enter="loadUsers"
@change="loadUsers($event.current)"
>
</a-input-search>
<div class="flex-grow"></div>
<a-button size="small" @click="showUserModal = true">
<div class="flex items-center gap-1">
<MdiAdd />
Invite new user
</div>
</a-button>
</div>
<a-table
:row-key="(record) => record.id"
:data-source="users"
:pagination="{ position: ['bottomCenter'] }"
:loading="isLoading"
@change="loadUsers($event.current)"
size="small"
>
<template #emptyText>
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" />
</template>
<!-- Email -->
<a-table-column key="email" :title="$t('labels.email')" data-index="email">
<template #default="{ text }">
<div>
{{ text }}
</div>
</template>
</a-table-column>
<!-- Role -->
<a-table-column key="roles" :title="$t('objects.role')" data-index="roles">
<template #default="{ record }">
<div>
<div v-if="record.roles.includes('super')" class="font-weight-bold">Super Admin</div>
<a-select
v-else
v-model:value="record.roles"
class="w-[220px]"
:dropdown-match-select-width="false"
@change="updateRole(record.id, record.roles)"
>
<a-select-option :value="Role.OrgLevelCreator" :label="$t(`objects.roleType.orgLevelCreator`)">
<div>{{ $t(`objects.roleType.orgLevelCreator`) }}</div>
<span class="text-gray-500 text-xs whitespace-normal"
>Creator can create new projects and access any invited project.</span
>
</a-select-option>
<a-select-option :value="Role.OrgLevelViewer" :label="$t(`objects.roleType.orgLevelViewer`)">
<div>{{ $t(`objects.roleType.orgLevelViewer`) }}</div>
<span class="text-gray-500 text-xs whitespace-normal"
>Viewer is not allowed to create new projects but they can access any invited project.</span
>
</a-select-option>
</a-select>
</div>
<template #emptyText>
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" />
</template>
</a-table-column>
<!-- &lt;!&ndash; Projects &ndash;&gt;
<!-- Email -->
<a-table-column key="email" :title="$t('labels.email')" data-index="email">
<template #default="{ text }">
<div>
{{ text }}
</div>
</template>
</a-table-column>
<!-- Role -->
<a-table-column key="roles" :title="$t('objects.role')" data-index="roles">
<template #default="{ record }">
<div>
<div v-if="record.roles.includes('super')" class="font-weight-bold">Super Admin</div>
<a-select
v-else
v-model:value="record.roles"
class="w-[220px]"
:dropdown-match-select-width="false"
@change="updateRole(record.id, record.roles)"
>
<a-select-option :value="Role.OrgLevelCreator" :label="$t(`objects.roleType.orgLevelCreator`)">
<div>{{ $t(`objects.roleType.orgLevelCreator`) }}</div>
<span class="text-gray-500 text-xs whitespace-normal"
>Creator can create new projects and access any invited project.</span
>
</a-select-option>
<a-select-option :value="Role.OrgLevelViewer" :label="$t(`objects.roleType.orgLevelViewer`)">
<div>{{ $t(`objects.roleType.orgLevelViewer`) }}</div>
<span class="text-gray-500 text-xs whitespace-normal"
>Viewer is not allowed to create new projects but they can access any invited project.</span
>
</a-select-option>
</a-select>
</div>
</template>
</a-table-column>
<!-- &lt;!&ndash; Projects &ndash;&gt;
<a-table-column key="projectsCount" :title="$t('objects.projects')" data-index="projectsCount">
<template #default="{ text }">
<div>
{{ text }}
</div>
</template>
</a-table-column>-->
<!-- Actions -->
<a-table-column key="id" :title="$t('labels.actions')" data-index="id">
<template #default="{ text, record }">
<div class="flex items-center gap-2" v-if="!record.roles.includes('super')">
<MdiDeleteOutline class="nc-action-btn cursor-pointer" @click="deleteUser(text)" />
<a-dropdown :trigger="['click']" class="flex" placement="bottomRight" overlay-class-name="nc-dropdown-user-mgmt">
<div class="flex flex-row items-center">
<a-button type="text" class="!px-0">
<div class="flex flex-row items-center h-[1.2rem]">
<IcBaselineMoreVert />
</div>
</a-button>
</div>
<template #overlay>
<a-menu>
<a-menu-item>
<!-- Resend invite Email -->
<div class="flex flex-row items-center py-3" @click="resendInvite(record)">
<MdiEmailArrowRightOutline class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('activity.resendInvite') }}</div>
</div>
</a-menu-item>
<a-menu-item>
<div class="flex flex-row items-center py-3" @click="copyInviteUrl(record)">
<MdiContentCopy class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('activity.copyInviteURL') }}</div>
</a-table-column> -->
<!-- Actions -->
<a-table-column key="id" :title="$t('labels.actions')" data-index="id">
<template #default="{ text, record }">
<div v-if="!record.roles.includes('super')" class="flex items-center gap-2">
<MdiDeleteOutline class="nc-action-btn cursor-pointer" @click="deleteUser(text)" />
<a-dropdown :trigger="['click']" class="flex" placement="bottomRight" overlay-class-name="nc-dropdown-user-mgmt">
<div class="flex flex-row items-center">
<a-button type="text" class="!px-0">
<div class="flex flex-row items-center h-[1.2rem]">
<IcBaselineMoreVert />
</div>
</a-menu-item>
<!-- <a-menu-item>
</a-button>
</div>
<template #overlay>
<a-menu>
<a-menu-item>
<!-- Resend invite Email -->
<div class="flex flex-row items-center py-3" @click="resendInvite(record)">
<MdiEmailArrowRightOutline class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('activity.resendInvite') }}</div>
</div>
</a-menu-item>
<a-menu-item>
<div class="flex flex-row items-center py-3" @click="copyInviteUrl(record)">
<MdiContentCopy class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('activity.copyInviteURL') }}</div>
</div>
</a-menu-item>
<!-- <a-menu-item>
<div class="flex flex-row items-center py-3" @click="copyInviteUrl(user)">
<MdiContentCopy class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('activity.copyPasswordResetURL') }}</div>
</div>
</a-menu-item> -->
</a-menu>
</template>
</a-dropdown>
</div>
<span v-else></span>
</template>
</a-table-column>
</a-table>
<LazyAdminUsersModal :show="showUserModal" @closed="showUserModal = false" @reload="loadUsers" />
</div>
</a-menu>
</template>
</a-dropdown>
</div>
<span v-else></span>
</template>
</a-table-column>
</a-table>
<LazyAdminUsersModal :show="showUserModal" @closed="showUserModal = false" @reload="loadUsers" />
</div>
</template>
<template v-else>
<!-- <div class="text-xl mt-4">Settings</div>-->
<!-- <a-divider class="!my-3" />-->
<!-- <div class="text-xl mt-4">Settings</div> -->
<!-- <a-divider class="!my-3" /> -->
<a-form-item>
<a-checkbox name="virtual">Enable user signup</a-checkbox>
<a-checkbox name="virtual">Enable user signup</a-checkbox>
</a-form-item>
</template>
</div>

1
packages/nc-gui/lang/en.json

@ -202,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Notify Via",
"projName": "Project name",
"tableName": "Table name",

30
packages/nc-gui/pages/admin/index.vue

@ -1,3 +1,9 @@
<script lang="ts" setup>
import { navigateTo } from '#imports'
const $route = useRoute()
const selectedTabKeys = computed(() => [$route.params.page])
</script>
<template>
<div class="container mx-auto h-full">
<a-layout class="mt-3 h-full overflow-y-auto flex">
@ -6,40 +12,34 @@
<a-menu :selected-keys="selectedTabKeys" class="tabs-menu h-full" :open-keys="[]">
<a-menu-item
key="users"
@click="navigateTo('/admin/users')"
class="group active:(!ring-0) hover:(!bg-primary !bg-opacity-25)"
@click="navigateTo('/admin/users')"
>
<div class="flex items-center space-x-2">
<MdiAccountSupervisorOutline />
<div class="select-none">
User Management
</div>
<div class="select-none">User Management</div>
</div>
</a-menu-item>
<a-menu-item
key="tokens"
@click="navigateTo('/admin/tokens')"
class="group active:(!ring-0) hover:(!bg-primary !bg-opacity-25)"
@click="navigateTo('/admin/tokens')"
>
<div class="flex items-center space-x-2">
<MdiShieldKeyOutline />
<div class="select-none">
Tokens
</div>
<div class="select-none">Tokens</div>
</div>
</a-menu-item>
<a-menu-item
key="license"
@click="navigateTo('/admin/license')"
class="group active:(!ring-0) hover:(!bg-primary !bg-opacity-25)"
@click="navigateTo('/admin/license')"
>
<div class="flex items-center space-x-2">
<MdiKeyChainVariant />
<div class="select-none">
License
</div>
<div class="select-none">License</div>
</div>
</a-menu-item>
</a-menu>
@ -52,9 +52,3 @@
</a-layout>
</div>
</template>
<script lang="ts" setup>
import { navigateTo } from '#imports'
const $route = useRoute()
const selectedTabKeys = computed(() => [$route.params.page])
</script>

16
packages/nc-gui/pages/admin/index/[page].vue

@ -1,3 +1,9 @@
<script>
export default {
name: 'Index',
}
</script>
<template>
<AdminUser v-if="$route.params.page === 'users'" />
<AdminToken v-else-if="$route.params.page === 'tokens'" />
@ -5,12 +11,4 @@
<span v-else></span>
</template>
<script>
export default {
name: 'index'
};
</script>
<style scoped>
</style>
<style scoped></style>

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

@ -1,11 +1,9 @@
<script lang="ts" setup>
import { useRoute, useSidebar } from '#imports'
const route = useRoute()
useSidebar('nc-left-sidebar', { hasSidebar: false })
</script>
<template>

2
packages/nocodb/src/lib/meta/NcMetaMgr.ts

@ -26,7 +26,7 @@ import ExpressXcTsRoutesBt from '../db/sql-mgr/code/routes/xc-ts/ExpressXcTsRout
import ExpressXcTsRoutesHm from '../db/sql-mgr/code/routes/xc-ts/ExpressXcTsRoutesHm';
import NcHelp from '../utils/NcHelp';
import mimetypes, { mimeIcons } from '../utils/mimeTypes';
import { packageVersion } from '../utils/packageVersion'
import { packageVersion } from '../utils/packageVersion';
import projectAcl from '../utils/projectAcl';
import Noco from '../Noco';
import { GqlApiBuilder } from '../v1-legacy/gql/GqlApiBuilder';

2
packages/nocodb/src/lib/meta/NcMetaMgrv2.ts

@ -4,7 +4,7 @@ import multer from 'multer';
import { NcConfig } from '../../interface/config';
import ProjectMgr from '../db/sql-mgr/ProjectMgr';
import { packageVersion } from '../utils/packageVersion'
import { packageVersion } from '../utils/packageVersion';
import projectAcl from '../utils/projectAcl';
import Noco from '../Noco';
import NcPluginMgr from '../v1-legacy/plugins/NcPluginMgr';

3
packages/nocodb/src/lib/meta/api/apiTokenApis.ts

@ -13,9 +13,12 @@ export async function apiTokenCreate(req: Request, res: Response) {
}
export async function apiTokenDelete(req: Request, res: Response) {
Tele.emit('evt', { evt_type: 'apiToken:deleted' });
// todo: verify token belongs to the user
res.json(await ApiToken.delete(req.params.token));
}
// todo: add reset token api to regenerate token
const router = Router({ mergeParams: true });
router.get(

28
packages/nocodb/src/lib/meta/api/orgTokenApis.ts

@ -1,11 +1,12 @@
import { Router } from 'express';
import { Request, Response, Router } from 'express'
import { OrgUserRoles } from '../../../enums/OrgUserRoles';
import ApiToken from '../../models/ApiToken';
import { Tele } from '../../utils/Tele'
import { metaApiMetrics } from '../helpers/apiMetrics';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { PagedResponseImpl } from '../helpers/PagedResponse';
async function tokensList(req, res) {
async function apiTokenList(req, res) {
res.json(
new PagedResponseImpl(await ApiToken.listWithCreatedBy(req.query), {
...req.query,
@ -14,10 +15,31 @@ async function tokensList(req, res) {
);
}
export async function apiTokenCreate(req: Request, res: Response) {
Tele.emit('evt', { evt_type: 'org:apiToken:created' });
res.json(await ApiToken.insert({ ...req.body, fk_user_id: req['user'].id }));
}
export async function apiTokenDelete(req: Request, res: Response) {
Tele.emit('evt', { evt_type: 'org:apiToken:deleted' });
res.json(await ApiToken.delete(req.params.token));
}
const router = Router({ mergeParams: true });
router.get(
'/api/v1/tokens',
metaApiMetrics,
ncMetaAclMw(tokensList, 'tokensList', [OrgUserRoles.SUPER])
ncMetaAclMw(apiTokenList, 'apiTokenList', [OrgUserRoles.SUPER])
);
router.post(
'/api/v1/tokens',
metaApiMetrics,
ncMetaAclMw(apiTokenCreate, 'apiTokenCreate', [OrgUserRoles.SUPER])
);
router.delete(
'/api/v1/tokens/:token',
metaApiMetrics,
ncMetaAclMw(apiTokenDelete, 'apiTokenDelete', [OrgUserRoles.SUPER])
);
export default router;

13
packages/nocodb/src/lib/meta/api/orgUserApis.ts

@ -165,6 +165,10 @@ async function userAdd(req, res, next) {
}
}
async function userSettings(_req, _res): Promise<any> {
NcError.notImplemented();
}
async function userInviteResend(req, res): Promise<any> {
const user = await User.get(req.params.userId);
@ -217,12 +221,17 @@ router.patch(
router.delete(
'/api/v1/users/:userId',
metaApiMetrics,
ncMetaAclMw(userDelete, 'userAdd', [OrgUserRoles.SUPER])
ncMetaAclMw(userDelete, 'userDelete', [OrgUserRoles.SUPER])
);
router.post(
'/api/v1/users',
metaApiMetrics,
ncMetaAclMw(userAdd, 'userDelete', [OrgUserRoles.SUPER])
ncMetaAclMw(userAdd, 'userAdd', [OrgUserRoles.SUPER])
);
router.post(
'/api/v1/users/settings',
metaApiMetrics,
ncMetaAclMw(userSettings, 'userSettings', [OrgUserRoles.SUPER])
);
router.post(
'/api/v1/users/:userId/resend-invite',

2
packages/nocodb/src/lib/meta/api/projectApis.ts

@ -2,7 +2,7 @@ import { Request, Response } from 'express';
import Project from '../../models/Project';
import { ModelTypes, ProjectListType, UITypes } from 'nocodb-sdk';
import DOMPurify from 'isomorphic-dompurify';
import { packageVersion } from '../../utils/packageVersion'
import { packageVersion } from '../../utils/packageVersion';
import { Tele } from '../../utils/Tele';
import { PagedResponseImpl } from '../helpers/PagedResponse';
import syncMigration from '../helpers/syncMigration';

2
packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts

@ -93,7 +93,7 @@ export function initStrategies(router): void {
async (req, jwtPayload, done) => {
// todo: improve this
// if (req.roles.split(',').includes(OrgUserRoles.SUPER)) {
// return User.getByEmail(jwtPayload?.email).then(async (user) => {
// return User.getByEma,il(jwtPayload?.email).then(async (user) => {
// return done(null, { ...user, roles: 'owner,creator' });
// });
// }

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

@ -1,7 +1,7 @@
import { Request, Response } from 'express';
import { TableType, validatePassword } from 'nocodb-sdk';
import { OrgUserRoles } from '../../../../enums/OrgUserRoles';
import { Tele } from '../../../utils/Tele'
import { Tele } from '../../../utils/Tele';
import catchError, { NcError } from '../../helpers/catchError';
const { isEmail } = require('validator');
import * as ejs from 'ejs';

2
packages/nocodb/src/lib/meta/api/utilApis.ts

@ -6,7 +6,7 @@ import Project from '../../models/Project';
import Noco from '../../Noco';
import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2';
import { MetaTable } from '../../utils/globals';
import { packageVersion } from '../../utils/packageVersion'
import { packageVersion } from '../../utils/packageVersion';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import SqlMgrv2 from '../../db/sql-mgr/v2/SqlMgrv2';
import NcConfigFactory, {

2
packages/nocodb/src/lib/models/ApiToken.ts

@ -11,6 +11,7 @@ import NocoCache from '../cache/NocoCache';
export default class ApiToken {
project_id?: string;
db_alias?: string;
fk_user_id?: string;
description?: string;
permissions?: string;
token?: string;
@ -29,6 +30,7 @@ export default class ApiToken {
await ncMeta.metaInsert(null, null, MetaTable.API_TOKENS, {
description: apiToken.description,
token,
fk_user_id: apiToken.fk_user_id,
});
await NocoCache.appendToList(
CacheScope.API_TOKEN,

68
scripts/sdk/swagger.json

@ -458,10 +458,18 @@
"properties": {
"list": {
"type": "array",
"uniqueItems": true,
"minItems": 1,
"items": {
"$ref": "#/components/schemas/ApiToken"
"allOf": [
{
"$ref": "#/components/schemas/ApiToken"
},
{
created_by: {
type: "string"
}
}
],
"type": "object"
}
},
"pageInfo": {
@ -484,7 +492,52 @@
"Org tokens"
]
},
"parameters": []
"parameters": [],
"post": {
"summary": "",
"operationId": "org-tokens-create",
"responses": {
"200": {
"description": "OK"
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiToken"
}
}
}
},
"tags": [
"Org tokens"
]
}
},
"/api/v1/tokens/{token}": {
"parameters": [
{
"schema": {
"type": "string"
},
"name": "token",
"in": "path",
"required": true
}
],
"delete": {
"summary": "",
"operationId": "org-tokens-delete",
"responses": {
"200": {
"description": "OK"
}
},
"tags": [
"Org tokens"
]
}
},
"/api/v1/users": {
"get": {
@ -8680,7 +8733,12 @@
},
"description": {
"type": "string"
}
},
"fk_user_id": {
"type": "string"
},
"created_at": {},
"updated_at": {}
}
},
"HookLog": {

Loading…
Cancel
Save