Browse Source

feat(gui): block open google signup if invite only signup enabled

re #4618

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/4696/head
Pranav C 2 years ago
parent
commit
fd8dae40e9
  1. 2
      packages/nc-gui/components.d.ts
  2. 14
      packages/nc-gui/components/dashboard/TreeView.vue
  3. 8
      packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue
  4. 7
      packages/nc-gui/middleware/auth.global.ts
  5. 12
      packages/nc-gui/pages/index/index/create-external.vue
  6. 26
      packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts
  7. 81
      packages/nocodb/src/lib/meta/api/userApi/userApis.ts

2
packages/nc-gui/components.d.ts vendored

@ -143,6 +143,7 @@ declare module '@vue/runtime-core' {
MdiCloseCircleOutline: typeof import('~icons/mdi/close-circle-outline')['default']
MdiCloseThick: typeof import('~icons/mdi/close-thick')['default']
MdiCodeJson: typeof import('~icons/mdi/code-json')['default']
MdiCodeTags: typeof import('~icons/mdi/code-tags')['default']
MdiCog: typeof import('~icons/mdi/cog')['default']
MdiCommentTextOutline: typeof import('~icons/mdi/comment-text-outline')['default']
MdiContentCopy: typeof import('~icons/mdi/content-copy')['default']
@ -227,7 +228,6 @@ declare module '@vue/runtime-core' {
MdiStarOutline: typeof import('~icons/mdi/star-outline')['default']
MdiStorefrontOutline: typeof import('~icons/mdi/storefront-outline')['default']
MdiTable: typeof import('~icons/mdi/table')['default']
MdiTableArrowRight: typeof import('~icons/mdi/table-arrow-right')['default']
MdiTableColumnPlusAfter: typeof import('~icons/mdi/table-column-plus-after')['default']
MdiTableColumnPlusBefore: typeof import('~icons/mdi/table-column-plus-before')['default']
MdiTableLarge: typeof import('~icons/mdi/table-large')['default']

14
packages/nc-gui/components/dashboard/TreeView.vue

@ -18,6 +18,7 @@ import {
ref,
resolveComponent,
useDialog,
useGlobal,
useNuxtApp,
useProject,
useRoute,
@ -26,7 +27,6 @@ import {
useToggle,
useUIPermission,
watchEffect,
useGlobal
} from '#imports'
import MdiView from '~icons/mdi/eye-circle-outline'
import MdiTableLarge from '~icons/mdi/table-large'
@ -417,7 +417,11 @@ const setIcon = async (icon: string, table: TableType) => {
MSSQL
</div>
</a-menu-item>
<a-menu-item v-if="appInfo.ee" key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.SNOWFLAKE)">
<a-menu-item
v-if="appInfo.ee"
key="connect-new-source"
@click="toggleDialog(true, 'dataSources', ClientType.SNOWFLAKE)"
>
<div class="color-transition nc-project-menu-item group">
<LogosSnowflakeIcon class="group-hover:text-accent" />
Snowflake
@ -533,7 +537,11 @@ const setIcon = async (icon: string, table: TableType) => {
MSSQL
</div>
</a-menu-item>
<a-menu-item v-if="appInfo.ee" key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.SNOWFLAKE)">
<a-menu-item
v-if="appInfo.ee"
key="connect-new-source"
@click="toggleDialog(true, 'dataSources', ClientType.SNOWFLAKE)"
>
<div class="color-transition nc-project-menu-item group">
<LogosSnowflakeIcon class="group-hover:text-accent" />
Snowflake

8
packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue

@ -179,7 +179,13 @@ function onStopEdit() {
</a-dropdown>
</div>
<a-input v-if="isEditing" :ref="focusInput" v-model:value="vModel.title" @blur="onCancel" @keydown.stop="onKeyDown($event)" />
<a-input
v-if="isEditing"
:ref="focusInput"
v-model:value="vModel.title"
@blur="onCancel"
@keydown.stop="onKeyDown($event)"
/>
<div v-else>
<LazyGeneralTruncateText>{{ vModel.alias || vModel.title }}</LazyGeneralTruncateText>

7
packages/nc-gui/middleware/auth.global.ts

@ -1,6 +1,7 @@
import type { Api } from 'nocodb-sdk'
import type { Actions } from '~/composables/useGlobal/types'
import { defineNuxtRouteMiddleware, message, navigateTo, useApi, useGlobal, useRoles } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils'
/**
* Global auth middleware
@ -98,10 +99,8 @@ async function tryGoogleAuth(api: Api<any>, signIn: Actions['signIn']) {
)
signIn(token)
} catch (e: any) {
if (e.response && e.response.data && e.response.data.msg) {
message.error({ content: e.response.data.msg })
}
} catch (e) {
message.error({ content: await extractSdkResponseErrorMsg(e) })
}
const newURL = window.location.href.split('?')[0]

12
packages/nc-gui/pages/index/index/create-external.vue

@ -497,10 +497,8 @@ onMounted(async () => {
</div>
</template>
<a-form-item label="SSL mode">
<a-select v-model:value="formState.sslUse" dropdown-class-name="nc-dropdown-ssl-mode"
@select="onSSLModeChange">
<a-select-option v-for="opt in Object.values(SSLUsage)" :key="opt" :value="opt">{{ opt }}
</a-select-option>
<a-select v-model:value="formState.sslUse" dropdown-class-name="nc-dropdown-ssl-mode" @select="onSSLModeChange">
<a-select-option v-for="opt in Object.values(SSLUsage)" :key="opt" :value="opt">{{ opt }} </a-select-option>
</a-select>
</a-form-item>
@ -542,16 +540,14 @@ onMounted(async () => {
<input ref="caFileInput" type="file" class="!hidden" @change="onFileSelect(CertTypes.ca, caFileInput)" />
<input ref="certFileInput" type="file" class="!hidden"
@change="onFileSelect(CertTypes.cert, certFileInput)" />
<input ref="certFileInput" type="file" class="!hidden" @change="onFileSelect(CertTypes.cert, certFileInput)" />
<input ref="keyFileInput" type="file" class="!hidden" @change="onFileSelect(CertTypes.key, keyFileInput)" />
<a-divider />
<!-- Extra connection parameters -->
<a-form-item class="mb-2" :label="$t('labels.extraConnectionParameters')"
v-bind="validateInfos.extraParameters">
<a-form-item class="mb-2" :label="$t('labels.extraConnectionParameters')" v-bind="validateInfos.extraParameters">
<a-card>
<div v-for="(item, index) of formState.extraParameters" :key="index">
<div class="flex py-1 items-center gap-1">

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

@ -7,7 +7,6 @@ import passport from 'passport';
import passportJWT from 'passport-jwt';
import { Strategy as AuthTokenStrategy } from 'passport-auth-token';
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
import { randomTokenString } from '../../helpers/stringHelpers';
const PassportLocalStrategy = require('passport-local').Strategy;
const ExtractJwt = passportJWT.ExtractJwt;
@ -24,6 +23,7 @@ import { CacheGetType, CacheScope } from '../../../utils/globals';
import ApiToken from '../../../models/ApiToken';
import Noco from '../../../Noco';
import Plugin from '../../../models/Plugin';
import { registerNewUserIfAllowed } from './userApis';
export function initStrategies(router): void {
passport.use(
@ -284,6 +284,8 @@ export function initStrategies(router): void {
User.getByEmail(email)
.then(async (user) => {
if (user) {
// if project id defined extract project level roles
if (req.ncProjectId) {
ProjectUser.get(req.ncProjectId, user.id)
.then(async (projectUser) => {
@ -296,30 +298,22 @@ export function initStrategies(router): void {
})
.catch((e) => done(e));
} else {
// const roles = projectUser?.roles ? JSON.parse(projectUser.roles) : {guest: true};
if (user) {
return done(null, user);
} else {
let roles = 'editor';
if (!(await User.isFirst())) {
roles = 'owner';
}
if (roles === 'editor') {
return done(new Error('User not found'));
}
// if user not found create new user if allowed
// or return error
} else {
const salt = await promisify(bcrypt.genSalt)(10);
user = await await User.insert({
const user = await registerNewUserIfAllowed({
firstname: null,
lastname: null,
email_verification_token: null,
email: profile.emails[0].value,
password: '',
salt,
roles,
email_verified: true,
token_version: randomTokenString(),
});
return done(null, user);
}
}
})
.catch((err) => {
return done(err);

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

@ -25,6 +25,58 @@ import Noco from '../../../Noco';
import { genJwt } from './helpers';
import { randomTokenString } from '../../helpers/stringHelpers';
export async function registerNewUserIfAllowed({
firstname,
lastname,
email,
salt,
password,
email_verification_token,
}: {
firstname;
lastname;
email: string;
salt: any;
password;
email_verification_token;
}) {
let roles: string = OrgUserRoles.CREATOR;
if (await User.isFirst()) {
roles = `${OrgUserRoles.CREATOR},${OrgUserRoles.SUPER_ADMIN}`;
// todo: update in nc_store
// roles = 'owner,creator,editor'
Tele.emit('evt', {
evt_type: 'project:invite',
count: 1,
});
} else {
let settings: { invite_only_signup?: boolean } = {};
try {
settings = JSON.parse((await Store.get(NC_APP_SETTINGS))?.value);
} catch {}
if (settings?.invite_only_signup) {
NcError.badRequest('Not allowed to signup, contact super admin.');
} else {
roles = OrgUserRoles.VIEWER;
}
}
const token_version = randomTokenString();
return await User.insert({
firstname,
lastname,
email,
salt,
password,
email_verification_token,
roles,
token_version,
});
}
export async function signup(req: Request, res: Response<TableType>) {
const {
email: _email,
@ -88,40 +140,13 @@ export async function signup(req: Request, res: Response<TableType>) {
NcError.badRequest('User already exist');
}
} else {
let roles: string = OrgUserRoles.CREATOR;
if (await User.isFirst()) {
roles = `${OrgUserRoles.CREATOR},${OrgUserRoles.SUPER_ADMIN}`;
// todo: update in nc_store
// roles = 'owner,creator,editor'
Tele.emit('evt', {
evt_type: 'project:invite',
count: 1,
});
} else {
let settings: { invite_only_signup?: boolean } = {};
try {
settings = JSON.parse((await Store.get(NC_APP_SETTINGS))?.value);
} catch {}
if (settings?.invite_only_signup) {
NcError.badRequest('Not allowed to signup, contact super admin.');
} else {
roles = OrgUserRoles.VIEWER;
}
}
const token_version = randomTokenString();
await User.insert({
await registerNewUserIfAllowed({
firstname,
lastname,
email,
salt,
password,
email_verification_token,
roles,
token_version,
});
}
user = await User.getByEmail(email);

Loading…
Cancel
Save