Browse Source

feat: add share base option

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/769/head
Pranav C 3 years ago
parent
commit
bcad9f4fc6
  1. 4
      packages/nc-gui/components/ProjectTreeView.vue
  2. 7
      packages/nc-gui/components/auth/userManagement.vue
  3. 99
      packages/nc-gui/components/base/shareBase.vue
  4. 2
      packages/nc-gui/layouts/default.vue
  5. 168
      packages/nc-gui/pages/nc/base/_shared_base_id.vue
  6. 8
      packages/nc-gui/plugins/axiosInterceptor.js
  7. 57
      packages/nc-gui/store/project.js
  8. 14
      packages/nc-gui/store/sqlMgr.js
  9. 28
      packages/nc-gui/store/users.js
  10. 10
      packages/nocodb/package-lock.json
  11. 1
      packages/nocodb/package.json
  12. 19
      packages/nocodb/src/lib/noco/common/XcMigrationSource.ts
  13. 26
      packages/nocodb/src/lib/noco/meta/NcMetaIOImpl.ts
  14. 107
      packages/nocodb/src/lib/noco/meta/NcMetaMgr.ts
  15. 43
      packages/nocodb/src/lib/noco/migrations/nc_008_add_nc_shared_bases.ts
  16. 43
      packages/nocodb/src/lib/noco/rest/RestAuthCtrl.ts

4
packages/nc-gui/components/ProjectTreeView.vue

@ -280,7 +280,9 @@
</v-list-item-group>
</v-list-group>
<v-list-item
v-else-if="item.type !== 'sqlClientDir' || showSqlClient"
v-else-if="(item.type !== 'sqlClientDir' || showSqlClient) &&
(item.type !=='migrationsDir' || _isUIAllowed('audit'))
"
:key="item.key"
:selectable="false"
:value="`${(item._nodes && item._nodes).type || ''}||${

7
packages/nc-gui/components/auth/userManagement.vue

@ -501,6 +501,10 @@
</x-btn>
</v-card-actions>
</template>
<v-card-text>
<share-base />
</v-card-text>
</v-card>
</v-dialog>
</div>
@ -512,10 +516,11 @@ import SetListCheckboxCell from '@/components/project/spreadsheet/components/edi
import { enumColor } from '@/components/project/spreadsheet/helpers/colors'
import DlgLabelSubmitCancel from '@/components/utils/dlgLabelSubmitCancel'
import { isEmail } from '@/helpers'
import ShareBase from '~/components/base/shareBase'
export default {
name: 'UserManagement',
components: { FeedbackForm, DlgLabelSubmitCancel, SetListCheckboxCell },
components: { ShareBase, FeedbackForm, DlgLabelSubmitCancel, SetListCheckboxCell },
data: () => ({
deleteItem: null,
invite_token: null,

99
packages/nc-gui/components/base/shareBase.vue

@ -0,0 +1,99 @@
<template>
<div class="nc-container">
<v-menu>
<template #activator="{on}">
<div class="my-2" v-on="on">
<template v-if="base.enabled">
Anyone with following link can view base in a readonly mode
</template>
<template v-else>
Generate publicly shareable readonly base
</template>
<v-icon small>
mdi-menu-down-outline
</v-icon>
</div>
</template>
<v-list dense>
<v-list-item dense @click="disableSharedBase">
<v-list-item-title>Disable shared base</v-list-item-title>
</v-list-item>
<v-list-item dense @click="createSharedBase">
<v-list-item-title>Readonly link</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<v-chip v-if="base.enabled" :color="colors[2]">
<div class="nc-url-wrapper d-flex mx-1 align-center d-100">
<span class="nc-url">{{ base.url }}</span>
<v-icon>mdi-reload</v-icon>
<v-icon>mdi-content-copy</v-icon>
<v-icon>mdi-xml</v-icon>
</div>
</v-chip>
</div>
</template>
<script>
import colors from '~/mixins/colors'
export default {
name: 'ShareBase',
mixins: [colors],
data: () => ({
base: {
enable: false
}
}),
mounted() {
this.loadSharedBase()
},
methods: {
async loadSharedBase() {
try {
const sharedBase = await this.$store.dispatch('sqlMgr/ActSqlOp', [null, 'getSharedBaseLink'])
this.base = sharedBase || {}
} catch (e) {
console.log(e)
}
},
async createSharedBase() {
try {
const sharedBase = await this.$store.dispatch('sqlMgr/ActSqlOp', [null, 'createSharedBaseLink'])
this.base = sharedBase || {}
} catch (e) {
this.$toast.error(e.message).goAway(3000)
}
},
async disableSharedBase() {
try {
await this.$store.dispatch('sqlMgr/ActSqlOp', [null, 'disableSharedBaseLink'])
this.base = {}
} catch (e) {
this.$toast.error(e.message).goAway(3000)
}
}
}
}
</script>
<style scoped>
.nc-url-wrapper {
column-gap: 5px;
}
.nc-url {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.nc-container {
border-radius: 4px;
border: 2px solid var(--v-backgroundColor-base);
padding: 20px 20px;
}
</style>

2
packages/nc-gui/layouts/default.vue

@ -44,7 +44,7 @@
{{ ghStarText }}
</gh-btns-star>
<a class="align-self-center caption font-weight-bold ml-1 mr-2 white--text" href="https://docs.nocodb.com" target="_blank">Docs</a>
<templates-modal v-if="isDashboard" class="align-self-center" />
<templates-modal v-if="isDashboard && _isUIAllowed('template-import')" class="align-self-center" />
</v-toolbar-items>
<!-- <template v-if="!isThisMobile ">

168
packages/nc-gui/pages/nc/base/_shared_base_id.vue

@ -0,0 +1,168 @@
<template>
<v-container fluid class="pa-0 ma-0" style="overflow: auto">
<splitpanes style="height:calc(100vh - 40px); position: relative;" class="xc-theme">
<pane :min-size="showProjectTree? 10 : 1.5" :size="showProjectTree ? paneSize : 1.5" :max-size="showProjectTree? 50 : 1.5" style="position: relative;overflow-x: hidden">
<ProjectTreeView v-show="showProjectTree" ref="treeview" />
<v-btn
x-small
icon
color="grey"
class="pane-toggle"
style="transition: all 0.3s cubic-bezier(0.25, 0.8, 0.5, 1);right:-8px"
:class="{'pane-toggle-active': showProjectTree}"
@click="toggleTreeView"
>
<v-icon x-small color="rgba(127,130,139)">
{{ showProjectTree ? 'mdi-arrow-left' : 'mdi-arrow-right' }}
</v-icon>
</v-btn>
</pane>
<pane :size="showProjectTree ? 100 - paneSize : 100">
<ProjectTabs :key="pid" @tableCreate="tableCreate" />
</pane>
</splitpanes>
</v-container>
</template>
<script>
import { Splitpanes, Pane } from 'splitpanes'
import ProjectTabs from '@/components/projectTabs'
import ProjectTreeView from '@/components/ProjectTreeView'
export default {
components: {
ProjectTreeView,
ProjectTabs,
Splitpanes,
Pane
},
data() {
return {
paneSize: 18,
mainPanelSize: 82,
showProjectTree: true
}
},
computed: {
pid() {
return this.$route.params.project_id
}
},
async created() {
this.$store.watch(
state => state.panelSize.treeView && state.panelSize.treeView.size,
(newSize) => { this.paneSize = newSize }
)
},
mounted() {
if ('new' in this.$route.query) {
this.simpleAnim()
this.$router.replace({ query: {} })
}
try {
// eslint-disable-next-line no-undef
hj('stateChange', `${this.$axios.defaults.baseURL}/dashboard/#/nc/`)
} catch (e) {
}
},
methods: {
tableCreate(table) {
if (this.$refs.treeview) {
this.$refs.treeview.mtdTableCreate(table)
}
},
simpleAnim() {
const count = 200
const defaults = {
origin: { y: 0.7 }
}
function fire(particleRatio, opts) {
window.confetti(Object.assign({}, defaults, opts, {
particleCount: Math.floor(count * particleRatio)
}))
}
fire(0.25, {
spread: 26,
startVelocity: 55
})
fire(0.2, {
spread: 60
})
fire(0.35, {
spread: 100,
decay: 0.91,
scalar: 0.8
})
fire(0.1, {
spread: 120,
startVelocity: 25,
decay: 0.92,
scalar: 1.2
})
fire(0.1, {
spread: 120,
startVelocity: 45
})
},
toggleTreeView() {
this.showProjectTree = !this.showProjectTree
}
}
}
</script>
<style scoped>
/deep/ .splitpanes__splitter {
background: #7f828b33 !important;
border: #7f828b33 !important;
}
.pane-toggle {
position: absolute;
right: 0;
top: 50%;
bottom: 50%;
z-index: 2;
transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1), all 0s;
}
.pane-toggle-active {
}
.theme--light .pane-toggle {
background-color: #FFFFFF;
border: 2px solid rgba(0, 0, 0, 0.12);
}
.theme--dark .pane-toggle {
background-color: #363636;
}
</style>
<!--
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd
*
* @author Naveen MR <oof1lab@gmail.com>
* @author Pranav C Balan <pranavxc@gmail.com>
* @author Wing-Kam Wong <wingkwong.code@gmail.com>
* @author Liel Fridman <lielft@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
-->

8
packages/nc-gui/plugins/axiosInterceptor.js

@ -6,7 +6,7 @@
// });
// }
export default ({ store, $axios, redirect, $toast }) => {
export default ({ store, $axios, redirect, $toast, route, app }) => {
// Add a request interceptor
$axios.interceptors.request.use(function(config) {
config.headers['xc-gui'] = 'true'
@ -17,6 +17,12 @@ export default ({ store, $axios, redirect, $toast }) => {
config.headers['xc-preview'] = store.state.users.previewAs
}
if (!config.url.endsWith('/user/me') && !config.url.endsWith('/admin/roles')) {
if (app.context && app.context.route && app.context.route.params && app.context.route.params.shared_base_id) {
config.headers['xc-shared-base-id'] = app.context.route.params.shared_base_id
}
}
return config
})

57
packages/nc-gui/store/project.js

@ -20,13 +20,17 @@ export const state = () => ({
defaultProject,
projectInfo: null,
activeEnv: null,
authDbAlias: null
authDbAlias: null,
projectId: null
});
export const mutations = {
add(state, project) {
state.list.push(project);
},
MutProjectId(state, projectId) {
state.projectId = projectId;
},
update(state, data) {
},
remove(state, {project}) {
@ -78,27 +82,27 @@ export const getters = {
},
GtrFirstDbAlias(state, getters) {
return (state.unserializedList
&& state.unserializedList[0]
&& state.unserializedList[0].projectJson
&& state.unserializedList[0].projectJson.envs
&& getters.GtrEnv
&& state.unserializedList[0].projectJson.envs[getters.GtrEnv]
&& state.unserializedList[0].projectJson.envs[getters.GtrEnv].db
&& state.unserializedList[0].projectJson.envs[getters.GtrEnv].db[0]
&& state.unserializedList[0].projectJson.envs[getters.GtrEnv].db[0].meta
&& state.unserializedList[0].projectJson.envs[getters.GtrEnv].db[0].meta.dbAlias)
&& state.unserializedList[0]
&& state.unserializedList[0].projectJson
&& state.unserializedList[0].projectJson.envs
&& getters.GtrEnv
&& state.unserializedList[0].projectJson.envs[getters.GtrEnv]
&& state.unserializedList[0].projectJson.envs[getters.GtrEnv].db
&& state.unserializedList[0].projectJson.envs[getters.GtrEnv].db[0]
&& state.unserializedList[0].projectJson.envs[getters.GtrEnv].db[0].meta
&& state.unserializedList[0].projectJson.envs[getters.GtrEnv].db[0].meta.dbAlias)
|| 'db';
},
GtrDbAliasList(state, getters) {
return (state.unserializedList
&& state.unserializedList[0]
&& state.unserializedList[0].projectJson
&& state.unserializedList[0].projectJson.envs
&& getters.GtrEnv
&& state.unserializedList[0].projectJson.envs[getters.GtrEnv]
&& state.unserializedList[0].projectJson.envs[getters.GtrEnv].db)
&& state.unserializedList[0]
&& state.unserializedList[0].projectJson
&& state.unserializedList[0].projectJson.envs
&& getters.GtrEnv
&& state.unserializedList[0].projectJson.envs[getters.GtrEnv]
&& state.unserializedList[0].projectJson.envs[getters.GtrEnv].db)
// && state.unserializedList[0].projectJson.envs[gettersGtrEnv].db.map(db => db.meta.dbAlias))
|| [];
},
@ -261,16 +265,27 @@ export const actions = {
}, 5000)
});
try {
let data,projectId;
if (this.$router.currentRoute && this.$router.currentRoute.params && this.$router.currentRoute.params.project_id) {
commit('MutProjectId', projectId = this.$router.currentRoute.params.project_id)
await dispatch('users/ActGetProjectUserDetails', this.$router.currentRoute.params.project_id, {root: true});
data = await this.dispatch('sqlMgr/ActSqlOp', [null, 'PROJECT_READ_BY_WEB']); // unsearialized data
} else if (this.$router.currentRoute && this.$router.currentRoute.params && this.$router.currentRoute.params.shared_base_id) {
const baseData = await this.dispatch('sqlMgr/ActSqlOp', [null, 'sharedBaseGet', {shared_base_id: this.$router.currentRoute.params.shared_base_id}]); // unsearialized data
commit('MutProjectId', projectId = baseData.project_id)
data = await this.dispatch('sqlMgr/ActSqlOp', [{project_id: baseData.project_id}, 'PROJECT_READ_BY_WEB']); // unsearialized data
await dispatch('users/ActGetBaseUserDetails', this.$router.currentRoute.params.shared_base_id, {root: true});
} else {
commit('MutProjectId', null)
return
}
const data = await this.dispatch('sqlMgr/ActSqlOp', [null, 'PROJECT_READ_BY_WEB']); // unsearialized data
commit("list", data.data.list);
commit("meta/MutClear", null, {root: true});
if(this.$ncApis){
this.$ncApis.clear();
this.$ncApis.setProjectId(this.$router.currentRoute.params.project_id);
}
if (this.$ncApis) {
this.$ncApis.clear();
this.$ncApis.setProjectId(projectId);
}
} catch (e) {
this.$toast.error(e).goAway(3000);
this.$router.push('/projects')

14
packages/nc-gui/store/sqlMgr.js

@ -361,9 +361,14 @@ export const actions = {
dispatch
}, [args, op, opArgs, cusHeaders, cusAxiosOptions, queryParams, returnResponse]) {
const params = {}
if (this.$router.currentRoute && this.$router.currentRoute.params && this.$router.currentRoute.params.project_id) {
params.project_id = this.$router.currentRoute.params.project_id
if (this.$router.currentRoute && this.$router.currentRoute.params) {
if (this.$router.currentRoute.params.project_id) {
params.project_id = this.$router.currentRoute.params.project_id
} else if (this.$router.currentRoute.params.shared_base_id) {
params.project_id = rootState.project.projectId
}
}
try {
const headers = {}
if (rootState.project.projectInfo && rootState.project.projectInfo.authType === 'masterKey') {
@ -443,6 +448,11 @@ export const actions = {
if (this.$router.currentRoute && this.$router.currentRoute.params && this.$router.currentRoute.params.project_id) {
params.project_id = this.$router.currentRoute.params.project_id
}
if (this.$router.currentRoute && this.$router.currentRoute.params && this.$router.currentRoute.params.project_id) {
params.project_id = this.$router.currentRoute.params.project_id
}
const headers = {
'Content-Type': 'multipart/form-data'
}

28
packages/nc-gui/store/users.js

@ -49,9 +49,6 @@ export const getters = {
return state.paidUser
},
GtrIsAuthenticated(state, getters, rootState) {
return rootState.project.projectInfo &&
(rootState.project.projectInfo.authType === 'none' ||
@ -80,7 +77,7 @@ export const getters = {
[state.previewAs]: true
}
}
return user && user.roles && Object.entries(roles).some(([name, hasRole]) => {
return Object.entries(roles).some(([name, hasRole]) => {
return hasRole && rolePermissions[name] && (rolePermissions[name] === '*' || rolePermissions[name][page])
})
}
@ -94,11 +91,8 @@ export const getters = {
},
GtrUserEmail(state) {
if(state.user && state.user.email)
return state.user.email;
else
return '';
},
if (state.user && state.user.email) { return state.user.email } else { return '' }
}
}
@ -387,6 +381,22 @@ export const actions = {
console.log('ignoring user/me error')
}
},
async ActGetBaseUserDetails({ commit, state }, sharedBaseId) {
try {
try {
const user = await this.$axios.get('/user/me', {
headers: {
'xc-shared-base-id': sharedBaseId
}
})
commit('MutProjectRole', user && user.data && user.data.roles)
} catch (e) {
console.log('ignoring user/me error')
}
} catch (e) {
console.log('ignoring user/me error')
}
},
async ActGetUserUiAbility({ commit, state }) {
try {

10
packages/nocodb/package-lock.json generated

@ -1,6 +1,6 @@
{
"name": "nocodb",
"version": "0.81.0",
"version": "0.81.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -13929,6 +13929,14 @@
"passport-oauth": "1.0.x"
}
},
"passport-custom": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/passport-custom/-/passport-custom-1.1.1.tgz",
"integrity": "sha512-/2m7jUGxmCYvoqenLB9UrmkCgPt64h8ZtV+UtuQklZ/Tn1NpKBeOorCYkB/8lMRoiZ5hUrCoMmDtxCS/d38mlg==",
"requires": {
"passport-strategy": "1.x.x"
}
},
"passport-github": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/passport-github/-/passport-github-1.1.0.tgz",

1
packages/nocodb/package.json

@ -158,6 +158,7 @@
"passport": "^0.4.1",
"passport-auth-token": "^1.0.1",
"passport-azure-ad-oauth2": "0.0.4",
"passport-custom": "^1.1.1",
"passport-github": "^1.1.0",
"passport-google-oauth20": "^2.0.0",
"passport-jwt": "^4.0.0",

19
packages/nocodb/src/lib/noco/common/XcMigrationSource.ts

@ -5,6 +5,7 @@ import * as viewType from '../migrations/nc_004_add_view_type_column';
import * as viewName from '../migrations/nc_005_add_view_name_column';
import * as nc_006_alter_nc_shared_views from '../migrations/nc_006_alter_nc_shared_views';
import * as nc_007_alter_nc_shared_views_1 from '../migrations/nc_007_alter_nc_shared_views_1';
import * as nc_008_add_nc_shared_bases from '../migrations/nc_008_add_nc_shared_bases';
// Create a custom migration source class
export default class XcMigrationSource {
@ -14,13 +15,14 @@ export default class XcMigrationSource {
public getMigrations(): Promise<any> {
// In this example we are just returning migration names
return Promise.resolve([
'project',
'm2m',
'fkn',
'viewType',
'viewName',
'nc_006_alter_nc_shared_views',
'nc_007_alter_nc_shared_views_1'
'project',
'm2m',
'fkn',
'viewType',
'viewName',
'nc_006_alter_nc_shared_views',
'nc_007_alter_nc_shared_views_1',
'nc_008_add_nc_shared_bases'
]);
}
@ -44,7 +46,8 @@ export default class XcMigrationSource {
return nc_006_alter_nc_shared_views;
case 'nc_007_alter_nc_shared_views_1':
return nc_007_alter_nc_shared_views_1;
case 'nc_008_add_nc_shared_bases':
return nc_008_add_nc_shared_bases;
}
}
}

26
packages/nocodb/src/lib/noco/meta/NcMetaIOImpl.ts

@ -27,12 +27,11 @@ export default class NcMetaIOImpl extends NcMetaIO {
): Promise<{ list: any[]; count: number }> {
const query = this.knexConnection(target);
const countQuery = this.knexConnection(target);
if (projectId !== null) {
if (projectId !== null && projectId !== undefined) {
query.where('project_id', projectId);
countQuery.where('project_id', projectId);
}
if (dbAlias !== null) {
if (dbAlias !== null && dbAlias !== undefined) {
query.where('db_alias', dbAlias);
countQuery.where('db_alias', dbAlias);
}
@ -116,10 +115,10 @@ export default class NcMetaIOImpl extends NcMetaIO {
): Promise<void> {
const query = this.knexConnection(target);
if (project_id !== null) {
if (project_id !== null && project_id !== undefined) {
query.where('project_id', project_id);
}
if (dbAlias !== null) {
if (dbAlias !== null && dbAlias !== undefined) {
query.where('db_alias', dbAlias);
}
@ -154,16 +153,17 @@ export default class NcMetaIOImpl extends NcMetaIO {
query.select(...fields);
}
if (project_id !== null) {
if (project_id !== null && project_id !== undefined) {
query.where('project_id', project_id);
}
if (dbAlias !== null) {
if (dbAlias !== null && dbAlias !== undefined) {
query.where('db_alias', dbAlias);
}
if (!idOrCondition) {
return query.first();
}
if (typeof idOrCondition !== 'object') {
query.where('id', idOrCondition);
} else {
@ -204,10 +204,10 @@ export default class NcMetaIOImpl extends NcMetaIO {
): Promise<any[]> {
const query = this.knexConnection(target);
if (project_id !== null) {
if (project_id !== null && project_id !== undefined) {
query.where('project_id', project_id);
}
if (dbAlias !== null) {
if (dbAlias !== null && dbAlias !== undefined) {
query.where('db_alias', dbAlias);
}
@ -240,10 +240,10 @@ export default class NcMetaIOImpl extends NcMetaIO {
xcCondition?
): Promise<any> {
const query = this.knexConnection(target);
if (project_id !== null) {
if (project_id !== null && project_id !== undefined) {
query.where('project_id', project_id);
}
if (dbAlias !== null) {
if (dbAlias !== null && dbAlias !== undefined) {
query.where('db_alias', dbAlias);
}
@ -280,10 +280,10 @@ export default class NcMetaIOImpl extends NcMetaIO {
dbAlias: string
): Promise<boolean> {
const query = this.knexConnection('nc_models');
if (project_id !== null) {
if (project_id !== null && project_id !== undefined) {
query.where('project_id', project_id);
}
if (dbAlias !== null) {
if (dbAlias !== null && dbAlias !== undefined) {
query.where('db_alias', dbAlias);
}
const data = await query.first();

107
packages/nocodb/src/lib/noco/meta/NcMetaMgr.ts

@ -152,6 +152,7 @@ export default class NcMetaMgr {
if (req?.session?.passport?.user?.isAuthorized) {
if (
req?.body?.project_id &&
!req.session?.passport?.user?.isPublicBase &&
!(await this.xcMeta.isUserHaveAccessToProject(
req?.body?.project_id,
req?.session?.passport?.user?.id
@ -1337,6 +1338,9 @@ export default class NcMetaMgr {
case 'sharedViewGet':
result = await this.sharedViewGet(req, args);
break;
case 'sharedBaseGet':
result = await this.sharedBaseGet(req, args);
break;
case 'sharedViewExportAsCsv':
result = await this.sharedViewExportAsCsv(req, args, res);
break;
@ -1498,6 +1502,15 @@ export default class NcMetaMgr {
case 'createSharedViewLink':
result = await this.createSharedViewLink(req, args);
break;
case 'createSharedBaseLink':
result = await this.createSharedBaseLink(req, args);
break;
case 'disableSharedBaseLink':
result = await this.disableSharedBaseLink(req, args);
break;
case 'getSharedBaseLink':
result = await this.getSharedBaseLink(req, args);
break;
case 'updateSharedViewLinkPassword':
result = await this.updateSharedViewLinkPassword(args);
@ -3385,6 +3398,83 @@ export default class NcMetaMgr {
}
}
protected async createSharedBaseLink(req, args: any): Promise<any> {
try {
let sharedBase = await this.xcMeta.metaGet(
this.getProjectId(args),
this.getDbAlias(args),
'nc_shared_bases',
{
project_id: this.getProjectId(args)
}
);
if (!sharedBase) {
const insertData = {
project_id: args.project_id,
db_alias: this.getDbAlias(args),
shared_base_id: uuidv4(),
password: args?.args?.password
};
await this.xcMeta.metaInsert(
args.project_id,
this.getDbAlias(args),
'nc_shared_bases',
insertData
);
sharedBase = await this.xcMeta.metaGet(
this.getProjectId(args),
this.getDbAlias(args),
'nc_shared_bases',
{},
['id', 'shared_base_id', 'enabled']
);
}
sharedBase.url = `${req.ncSiteUrl}${this.config.dashboardPath}#/nc/base/${sharedBase.shared_base_id}`;
Tele.emit('evt', { evt_type: 'sharedBase:generated-link' });
return sharedBase;
} catch (e) {
console.log(e);
}
}
protected async disableSharedBaseLink(_req, args: any): Promise<any> {
try {
await this.xcMeta.metaDelete(
this.getProjectId(args),
this.getDbAlias(args),
'nc_shared_bases',
{
project_id: this.getProjectId(args)
}
);
} catch (e) {
console.log(e);
}
}
protected async getSharedBaseLink(req, args: any): Promise<any> {
try {
const sharedBase = await this.xcMeta.metaGet(
this.getProjectId(args),
this.getDbAlias(args),
'nc_shared_bases',
{
project_id: this.getProjectId(args)
}
);
if (sharedBase)
sharedBase.url = `${req.ncSiteUrl}${this.config.dashboardPath}#/nc/base/${sharedBase.shared_base_id}`;
return sharedBase;
} catch (e) {
console.log(e);
}
}
protected async updateSharedViewLinkPassword(_args: any): Promise<any> {
// try {
//
@ -3898,6 +3988,23 @@ export default class NcMetaMgr {
return { ...sharedViewMeta, ...viewMeta };
}
protected async sharedBaseGet(_req, args: any): Promise<any> {
const sharedBaseMeta = await this.xcMeta
.knex('nc_shared_bases')
.select('project_id')
.where({
shared_base_id: args.args.shared_base_id,
enabled: true
})
.first();
if (!sharedBaseMeta) {
throw new Error('Meta not found');
}
return sharedBaseMeta;
}
protected async sharedViewExportAsCsv(_req, args: any, res): Promise<any> {
const sharedViewMeta = await this.xcMeta
.knex('nc_shared_views')

43
packages/nocodb/src/lib/noco/migrations/nc_008_add_nc_shared_bases.ts

@ -0,0 +1,43 @@
import Knex from 'knex';
const up = async (knex: Knex) => {
await knex.schema.createTable('nc_shared_bases', table => {
table.increments();
table.string('project_id');
table.string('db_alias');
table.string('roles').defaultTo('viewer');
table.string('shared_base_id');
table.boolean('enabled').defaultTo(true);
table.string('password');
table.timestamps(true, true);
});
};
const down = async knex => {
await knex.schema.dropTable('nc_shared_bases');
};
export { up, down };
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd
*
* @author Naveen MR <oof1lab@gmail.com>
* @author Pranav C Balan <pranavxc@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

43
packages/nocodb/src/lib/noco/rest/RestAuthCtrl.ts

@ -17,6 +17,8 @@ import Noco from '../Noco';
const autoBind = require('auto-bind');
const PassportLocalStrategy = require('passport-local').Strategy;
import { Strategy as CustomStrategy } from 'passport-custom';
const { v4: uuidv4 } = require('uuid');
import * as crypto from 'crypto';
@ -39,12 +41,14 @@ passport.serializeUser(function(
provider,
firstname,
lastname,
isAuthorized
isAuthorized,
isPublicBase
},
done
) {
done(null, {
isAuthorized,
isPublicBase,
id,
email,
email_verified,
@ -249,6 +253,18 @@ export default class RestAuthCtrl {
}
}
)(req, res, next);
} else if (req.headers['xc-shared-base-id']) {
passport.authenticate('baseView', {}, (_err, user, _info) => {
if (user) {
return resolve({
...user,
isAuthorized: true,
isPublicBase: true
});
} else {
resolve({ roles: 'guest' });
}
})(req, res, next);
} else {
resolve({ roles: 'guest' });
}
@ -290,6 +306,7 @@ export default class RestAuthCtrl {
}
})
);
this.initCustomStrategy();
this.initJwtStrategy();
passport.use(
@ -569,6 +586,30 @@ export default class RestAuthCtrl {
);
}
protected initCustomStrategy() {
passport.use(
'baseView',
new CustomStrategy(async (req: any, callback) => {
let user;
if (req.headers['xc-shared-base-id']) {
const sharedBase = await this.xcMeta
.knex('nc_shared_bases')
.where({
enabled: true,
shared_base_id: req.headers['xc-shared-base-id']
})
.first();
user = {
roles: sharedBase?.roles
};
}
callback(null, user);
})
);
}
protected async signin(req, res, next): Promise<any> {
passport.authenticate(
'local',

Loading…
Cancel
Save