From 215dea81c6ad808a8f1af0b2b9cad614c538b1c0 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Tue, 30 May 2023 15:47:17 +0530 Subject: [PATCH 01/11] fix: set refresh-token on login and correction in refresh-token based api Signed-off-by: Pranav C --- .../src/controllers/users/users.controller.ts | 118 +++++++----------- 1 file changed, 43 insertions(+), 75 deletions(-) diff --git a/packages/nocodb/src/controllers/users/users.controller.ts b/packages/nocodb/src/controllers/users/users.controller.ts index 8ffb6dbf6a..c68c06a4cd 100644 --- a/packages/nocodb/src/controllers/users/users.controller.ts +++ b/packages/nocodb/src/controllers/users/users.controller.ts @@ -1,5 +1,3 @@ -import { promisify } from 'util'; -import { AuditOperationSubTypes, AuditOperationTypes } from 'nocodb-sdk'; import { Body, Controller, @@ -15,27 +13,18 @@ import * as ejs from 'ejs'; import { AuthGuard } from '@nestjs/passport'; import { GlobalGuard } from '../../guards/global/global.guard'; import { NcError } from '../../helpers/catchError'; +import { Acl } from '../../middlewares/extract-project-id/extract-project-id.middleware'; +import { User } from '../../models'; import { - Acl, - ExtractProjectIdMiddleware, -} from '../../middlewares/extract-project-id/extract-project-id.middleware'; -import Noco from '../../Noco'; -import { GoogleStrategy } from '../../strategies/google.strategy/google.strategy'; -import extractRolesObj from '../../utils/extractRolesObj'; -import { Audit, User } from '../../models'; -import { - genJwt, randomTokenString, setTokenCookie, } from '../../services/users/helpers'; import { UsersService } from '../../services/users/users.service'; +import extractRolesObj from '../../utils/extractRolesObj'; @Controller() export class UsersController { - constructor( - private readonly usersService: UsersService, - private googleStrategy: GoogleStrategy, - ) {} + constructor(private readonly usersService: UsersService) {} @Post([ '/auth/user/signup', @@ -59,56 +48,14 @@ export class UsersController { '/api/v1/auth/token/refresh', ]) @HttpCode(200) - async refreshToken(@Request() req: any, @Request() res: any): Promise { - return await this.usersService.refreshToken({ - body: req.body, - req, - res, - }); - } - - async successfulSignIn({ user, err, info, req, res, auditDescription }) { - try { - if (!user || !user.email) { - if (err) { - return res.status(400).send(err); - } - if (info) { - return res.status(400).send(info); - } - return res.status(400).send({ msg: 'Your signin has failed' }); - } - - await promisify((req as any).login.bind(req))(user); - - const refreshToken = randomTokenString(); - - if (!user.token_version) { - user.token_version = randomTokenString(); - } - - await User.update(user.id, { - refresh_token: refreshToken, - email: user.email, - token_version: user.token_version, - }); - setTokenCookie(res, refreshToken); - - await Audit.insert({ - op_type: AuditOperationTypes.AUTHENTICATION, - op_sub_type: AuditOperationSubTypes.SIGNIN, - user: user.email, - ip: req.clientIp, - description: auditDescription, - }); - - res.json({ - token: genJwt(user, Noco.getConfig()), - } as any); - } catch (e) { - console.log(e); - throw e; - } + async refreshToken(@Request() req: any, @Response() res: any): Promise { + res.json( + await this.usersService.refreshToken({ + body: req.body, + req, + res, + }), + ); } @Post([ @@ -118,8 +65,9 @@ export class UsersController { ]) @UseGuards(AuthGuard('local')) @HttpCode(200) - async signin(@Request() req) { - return this.usersService.login(req.user); + async signin(@Request() req, @Response() res) { + await this.setRefreshToken({ req, res }); + res.json(this.usersService.login(req.user)); } @Post('/api/v1/auth/user/signout') @@ -136,22 +84,19 @@ export class UsersController { @Post(`/auth/google/genTokenByCode`) @HttpCode(200) @UseGuards(AuthGuard('google')) - async googleSignin(@Request() req) { - return this.usersService.login(req.user); + async googleSignin(@Request() req, @Response() res) { + await this.setRefreshToken({ req, res }); + res.json(this.usersService.login(req.user)); } @Get('/auth/google') @UseGuards(AuthGuard('google')) googleAuthenticate(@Request() req) { - // this.googleStrategy.authenticate(req, { - // scope: ['profile', 'email'], - // state: req.query.state, - // callbackURL: req.ncSiteUrl + Noco.getConfig().dashboardPath, - // }); + // google strategy will take care the request } @Get(['/auth/user/me', '/api/v1/db/auth/user/me', '/api/v1/auth/user/me']) - @UseGuards(ExtractProjectIdMiddleware, GlobalGuard) + @UseGuards(GlobalGuard) async me(@Request() req) { const user = { ...req.user, @@ -269,4 +214,27 @@ export class UsersController { return res.status(400).json({ msg: e.message }); } } + + async setRefreshToken({ res, req }) { + const userId = req.user?.id; + + if (!userId) return; + + const user: any = await User.get(userId); + + if (!user) return; + + const refreshToken = randomTokenString(); + + if (!user.token_version) { + user.token_version = randomTokenString(); + } + + await User.update(user.id, { + refresh_token: refreshToken, + email: user.email, + token_version: user.token_version, + }); + setTokenCookie(res, refreshToken); + } } From 66ad98761c63ecc22f5292cc87be52a70cc3491a Mon Sep 17 00:00:00 2001 From: Pranav C Date: Wed, 31 May 2023 01:41:12 +0530 Subject: [PATCH 02/11] fix: remove unnecessary async Signed-off-by: Pranav C --- packages/nocodb/src/services/users/users.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nocodb/src/services/users/users.service.ts b/packages/nocodb/src/services/users/users.service.ts index 6d60b76336..48a3f4928a 100644 --- a/packages/nocodb/src/services/users/users.service.ts +++ b/packages/nocodb/src/services/users/users.service.ts @@ -486,9 +486,9 @@ export class UsersService { return this.login(user); } - async login(user: any) { + login(user: any) { return { - token: genJwt(user, Noco.getConfig()), //this.jwtService.sign(payload), + token: genJwt(user, Noco.getConfig()), }; } From 957f04e1fd6f1b271aff4d77560d874ce1afd850 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 31 May 2023 13:24:28 +0800 Subject: [PATCH 03/11] fix(nocodb): use includes to check if NONE is in target list --- packages/nocodb/src/cache/RedisCacheMgr.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nocodb/src/cache/RedisCacheMgr.ts b/packages/nocodb/src/cache/RedisCacheMgr.ts index e0dafae4a1..7fc39c554d 100644 --- a/packages/nocodb/src/cache/RedisCacheMgr.ts +++ b/packages/nocodb/src/cache/RedisCacheMgr.ts @@ -135,7 +135,7 @@ export default class RedisCacheMgr extends CacheMgr { // e.g. arr = ["nc:::", "nc:::"] const arr = (await this.get(key, CacheGetType.TYPE_ARRAY)) || []; log(`RedisCacheMgr::getList: getting list with key ${key}`); - const isNoneList = arr.length && arr[0] === 'NONE'; + const isNoneList = arr.length && arr.includes('NONE'); if (isNoneList) { return Promise.resolve({ @@ -248,7 +248,7 @@ export default class RedisCacheMgr extends CacheMgr { : `${this.prefix}:${scope}:${subListKeys.join(':')}:list`; log(`RedisCacheMgr::appendToList: append key ${key} to ${listKey}`); let list = (await this.get(listKey, CacheGetType.TYPE_ARRAY)) || []; - if (list.length && list[0] === 'NONE') { + if (list.length && list.includes('NONE')) { list = []; await this.del(listKey); } From e3a06191e37f87a25eeddca0bc25d3212d7e1240 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 31 May 2023 13:24:51 +0800 Subject: [PATCH 04/11] fix(nocodb): revise user project list delete logic --- packages/nocodb/src/models/ProjectUser.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/nocodb/src/models/ProjectUser.ts b/packages/nocodb/src/models/ProjectUser.ts index f3d8897541..31eda80fbb 100644 --- a/packages/nocodb/src/models/ProjectUser.ts +++ b/packages/nocodb/src/models/ProjectUser.ts @@ -194,11 +194,16 @@ export default class ProjectUser { const { isNoneList } = cachedList; if (!isNoneList && cachedProjectList?.length) { cachedProjectList = cachedProjectList.filter((p) => p.id !== projectId); - await NocoCache.setList( - CacheScope.USER_PROJECT, - [userId], - cachedProjectList, - ); + // delete the whole list first so that the old one won't be included + await NocoCache.del(`${CacheScope.USER_PROJECT}:${userId}:list`); + if (cachedProjectList.length > 0) { + // set the updated list (i.e. excluding the to-be-deleted project id) + await NocoCache.setList( + CacheScope.USER_PROJECT, + [userId], + cachedProjectList, + ); + } } await NocoCache.del(`${CacheScope.PROJECT_USER}:${projectId}:${userId}`); From 283cd8f5660d4c7375804918c72763e8f8961a5f Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 31 May 2023 13:26:44 +0800 Subject: [PATCH 05/11] refactor(nocodb): remove unused code --- packages/nocodb/src/models/ProjectUser.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/nocodb/src/models/ProjectUser.ts b/packages/nocodb/src/models/ProjectUser.ts index 31eda80fbb..5042466fbf 100644 --- a/packages/nocodb/src/models/ProjectUser.ts +++ b/packages/nocodb/src/models/ProjectUser.ts @@ -174,11 +174,6 @@ export default class ProjectUser { } static async delete(projectId: string, userId: string, ncMeta = Noco.ncMeta) { - // await NocoCache.deepDel( - // CacheScope.PROJECT_USER, - // `${CacheScope.PROJECT_USER}:${projectId}:${userId}`, - // CacheDelDirection.CHILD_TO_PARENT - // ); const { email } = await ncMeta.metaGet2(null, null, MetaTable.USERS, { id: userId, }); From 268d8367b7a4f864a9067b320e09cf81139a57fc Mon Sep 17 00:00:00 2001 From: Pranav C Date: Wed, 31 May 2023 11:02:24 +0530 Subject: [PATCH 06/11] refactor: type correction Signed-off-by: Pranav C --- packages/nocodb-sdk/src/lib/Api.ts | 2 ++ packages/nocodb/src/controllers/users/users.controller.ts | 2 +- packages/nocodb/src/schema/swagger.json | 4 ++++ packages/nocodb/src/services/users/users.service.ts | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/nocodb-sdk/src/lib/Api.ts b/packages/nocodb-sdk/src/lib/Api.ts index 06493d405c..5a3fa5ebe1 100644 --- a/packages/nocodb-sdk/src/lib/Api.ts +++ b/packages/nocodb-sdk/src/lib/Api.ts @@ -2255,6 +2255,8 @@ export interface UserType { * @example org-level-viewer */ roles?: string; + /** Access token version */ + token_version?: string; } /** diff --git a/packages/nocodb/src/controllers/users/users.controller.ts b/packages/nocodb/src/controllers/users/users.controller.ts index c68c06a4cd..02f4bf13c2 100644 --- a/packages/nocodb/src/controllers/users/users.controller.ts +++ b/packages/nocodb/src/controllers/users/users.controller.ts @@ -220,7 +220,7 @@ export class UsersController { if (!userId) return; - const user: any = await User.get(userId); + const user = await User.get(userId); if (!user) return; diff --git a/packages/nocodb/src/schema/swagger.json b/packages/nocodb/src/schema/swagger.json index b7d5a6e102..34fae1d3d8 100644 --- a/packages/nocodb/src/schema/swagger.json +++ b/packages/nocodb/src/schema/swagger.json @@ -20000,6 +20000,10 @@ "description": "The roles of the user", "example": "org-level-viewer", "type": "string" + }, + "token_version": { + "description": "Access token version", + "type": "string" } }, "required": ["email", "email_verified", "firstname", "id", "lastname"], diff --git a/packages/nocodb/src/services/users/users.service.ts b/packages/nocodb/src/services/users/users.service.ts index 48a3f4928a..0753329060 100644 --- a/packages/nocodb/src/services/users/users.service.ts +++ b/packages/nocodb/src/services/users/users.service.ts @@ -486,7 +486,7 @@ export class UsersService { return this.login(user); } - login(user: any) { + login(user: UserType) { return { token: genJwt(user, Noco.getConfig()), }; From 78fdabfde76fe3dc311ad64c9de58b24bc785fd3 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Wed, 31 May 2023 11:58:37 +0530 Subject: [PATCH 07/11] fix: add extractProjectId middleware to /me endpoint Signed-off-by: Pranav C --- packages/nc-gui/composables/useApi/interceptors.ts | 1 + packages/nocodb/src/controllers/users/users.controller.ts | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/nc-gui/composables/useApi/interceptors.ts b/packages/nc-gui/composables/useApi/interceptors.ts index 4c12bc083c..c09c8c3b2e 100644 --- a/packages/nc-gui/composables/useApi/interceptors.ts +++ b/packages/nc-gui/composables/useApi/interceptors.ts @@ -69,6 +69,7 @@ export function addAxiosInterceptors(api: Api) { await state.signOut() // todo: handle new user + debugger navigateTo('/signIn') return Promise.reject(error) diff --git a/packages/nocodb/src/controllers/users/users.controller.ts b/packages/nocodb/src/controllers/users/users.controller.ts index 02f4bf13c2..e618890e7a 100644 --- a/packages/nocodb/src/controllers/users/users.controller.ts +++ b/packages/nocodb/src/controllers/users/users.controller.ts @@ -13,7 +13,10 @@ import * as ejs from 'ejs'; import { AuthGuard } from '@nestjs/passport'; import { GlobalGuard } from '../../guards/global/global.guard'; import { NcError } from '../../helpers/catchError'; -import { Acl } from '../../middlewares/extract-project-id/extract-project-id.middleware'; +import { + Acl, + ExtractProjectIdMiddleware, +} from '../../middlewares/extract-project-id/extract-project-id.middleware'; import { User } from '../../models'; import { randomTokenString, @@ -96,7 +99,7 @@ export class UsersController { } @Get(['/auth/user/me', '/api/v1/db/auth/user/me', '/api/v1/auth/user/me']) - @UseGuards(GlobalGuard) + @UseGuards(ExtractProjectIdMiddleware, GlobalGuard) async me(@Request() req) { const user = { ...req.user, From 51855e8893ffaca0b2e16da29a8113db1600879a Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 31 May 2023 16:07:52 +0800 Subject: [PATCH 08/11] fix(nocodb): add missing acl middleware --- .../nocodb/src/controllers/projects.controller.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/nocodb/src/controllers/projects.controller.ts b/packages/nocodb/src/controllers/projects.controller.ts index 7b905cba2e..65949ca778 100644 --- a/packages/nocodb/src/controllers/projects.controller.ts +++ b/packages/nocodb/src/controllers/projects.controller.ts @@ -11,15 +11,13 @@ import { Request, UseGuards, } from '@nestjs/common'; -import { AuthGuard } from '@nestjs/passport'; import isDocker from 'is-docker'; import { ProjectReqType } from 'nocodb-sdk'; import { GlobalGuard } from '../guards/global/global.guard'; import { PagedResponseImpl } from '../helpers/PagedResponse'; import { ExtractProjectIdMiddleware, - UseAclMiddleware, - UseProjectIdMiddleware, + Acl, } from '../middlewares/extract-project-id/extract-project-id.middleware'; import Noco from '../Noco'; import { packageVersion } from '../utils/packageVersion'; @@ -31,9 +29,7 @@ import type { ProjectType } from 'nocodb-sdk'; export class ProjectsController { constructor(private readonly projectsService: ProjectsService) {} - @UseAclMiddleware({ - permissionName: 'projectList', - }) + @Acl('projectList') @Get('/api/v1/db/meta/projects/') async list(@Query() queryParams: Record, @Request() req) { const projects = await this.projectsService.projectList({ @@ -57,7 +53,7 @@ export class ProjectsController { PackageVersion: packageVersion, }; } - + @Acl('projectGet') @Get('/api/v1/db/meta/projects/:projectId') async projectGet(@Param('projectId') projectId: string) { const project = await this.projectsService.getProjectWithInfo({ @@ -68,7 +64,7 @@ export class ProjectsController { return project; } - + @Acl('projectUpdate') @Patch('/api/v1/db/meta/projects/:projectId') async projectUpdate( @Param('projectId') projectId: string, @@ -82,6 +78,7 @@ export class ProjectsController { return project; } + @Acl('projectDelete') @Delete('/api/v1/db/meta/projects/:projectId') async projectDelete(@Param('projectId') projectId: string) { const deleted = await this.projectsService.projectSoftDelete({ @@ -91,6 +88,7 @@ export class ProjectsController { return deleted; } + @Acl('projectCreate') @Post('/api/v1/db/meta/projects') @HttpCode(200) async projectCreate(@Body() projectBody: ProjectReqType, @Request() req) { From 2d151627c31dd2b3e39138551ece0a746606d497 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 31 May 2023 16:08:05 +0800 Subject: [PATCH 09/11] refactor(nocodb): remove unused code --- .../src/controllers/projects.controller.ts | 63 ------------------- 1 file changed, 63 deletions(-) diff --git a/packages/nocodb/src/controllers/projects.controller.ts b/packages/nocodb/src/controllers/projects.controller.ts index 65949ca778..afde4b59a5 100644 --- a/packages/nocodb/src/controllers/projects.controller.ts +++ b/packages/nocodb/src/controllers/projects.controller.ts @@ -100,66 +100,3 @@ export class ProjectsController { return project; } } - -/* -// // Project CRUD - - - -export async function projectCost(req, res) { - let cost = 0; - const project = await Project.getWithInfo(req.params.projectId); - - for (const base of project.bases) { - const sqlClient = await NcConnectionMgrv2.getSqlClient(base); - const userCount = await ProjectUser.getUsersCount(req.query); - const recordCount = (await sqlClient.totalRecords())?.data.TotalRecords; - - if (recordCount > 100000) { - // 36,000 or $79/user/month - cost = Math.max(36000, 948 * userCount); - } else if (recordCount > 50000) { - // $36,000 or $50/user/month - cost = Math.max(36000, 600 * userCount); - } else if (recordCount > 10000) { - // $240/user/yr - cost = Math.min(240 * userCount, 36000); - } else if (recordCount > 1000) { - // $120/user/yr - cost = Math.min(120 * userCount, 36000); - } - } - - T.event({ - event: 'a:project:cost', - data: { - cost, - }, - }); - - res.json({ cost }); -} - -export async function hasEmptyOrNullFilters(req, res) { - res.json(await Filter.hasEmptyOrNullFilters(req.params.projectId)); -} - -export default (router) => { - - - router.get( - '/api/v1/db/meta/projects/:projectId/cost', - metaApiMetrics, - ncMetaAclMw(projectCost, 'projectCost') - ); - - - - router.get( - '/api/v1/db/meta/projects/:projectId/has-empty-or-null-filters', - metaApiMetrics, - ncMetaAclMw(hasEmptyOrNullFilters, 'hasEmptyOrNullFilters') - ); -}; - -* */ From 07ab34e650f3c0b8beb9f7852fdd356b7f6eb54c Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 31 May 2023 16:27:53 +0800 Subject: [PATCH 10/11] fix(nc-gui): add missing isUIAllowed --- packages/nc-gui/pages/index/index/index.vue | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/nc-gui/pages/index/index/index.vue b/packages/nc-gui/pages/index/index/index.vue index 486786b72b..8aeedb8bc6 100644 --- a/packages/nc-gui/pages/index/index/index.vue +++ b/packages/nc-gui/pages/index/index/index.vue @@ -308,6 +308,7 @@ const copyProjectMeta = async () => {
{ - + Date: Wed, 31 May 2023 14:22:55 +0530 Subject: [PATCH 11/11] fix: remove unnecessary debugger statement Signed-off-by: Pranav C --- packages/nc-gui/composables/useApi/interceptors.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/nc-gui/composables/useApi/interceptors.ts b/packages/nc-gui/composables/useApi/interceptors.ts index c09c8c3b2e..8e2609b3ab 100644 --- a/packages/nc-gui/composables/useApi/interceptors.ts +++ b/packages/nc-gui/composables/useApi/interceptors.ts @@ -67,9 +67,8 @@ export function addAxiosInterceptors(api: Api) { }) .catch(async (error) => { await state.signOut() - // todo: handle new user - debugger + // todo: handle new user navigateTo('/signIn') return Promise.reject(error)