From a51e6c8c29896e5da11b8eac3f43be5a10155af8 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Mon, 17 Oct 2022 21:47:50 +0530 Subject: [PATCH 01/95] feat(gui): add upgrader for updating role names Signed-off-by: Pranav C --- .../src/lib/version-upgrader/NcUpgrader.ts | 2 ++ .../ncProjectRolesUpgrader.ts | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 packages/nocodb/src/lib/version-upgrader/ncProjectRolesUpgrader.ts diff --git a/packages/nocodb/src/lib/version-upgrader/NcUpgrader.ts b/packages/nocodb/src/lib/version-upgrader/NcUpgrader.ts index 3fab1baf5e..2946253259 100644 --- a/packages/nocodb/src/lib/version-upgrader/NcUpgrader.ts +++ b/packages/nocodb/src/lib/version-upgrader/NcUpgrader.ts @@ -5,6 +5,7 @@ import NcMetaIO from '../meta/NcMetaIO'; import ncProjectEnvUpgrader from './ncProjectEnvUpgrader'; import ncProjectEnvUpgrader0011045 from './ncProjectEnvUpgrader0011045'; import ncProjectUpgraderV2_0090000 from './ncProjectUpgraderV2_0090000'; +import ncProjectRolesUpdate from './ncProjectRolesUpdate'; import ncDataTypesUpgrader from './ncDataTypesUpgrader'; const log = debug('nc:version-upgrader'); @@ -33,6 +34,7 @@ export default class NcUpgrader { { name: '0011045', handler: ncProjectEnvUpgrader0011045 }, { name: '0090000', handler: ncProjectUpgraderV2_0090000 }, { name: '0098004', handler: ncDataTypesUpgrader }, + { name: '0098005', handler: ncProjectRolesUpdate }, ]; if (!(await ctx.ncMeta.knexConnection?.schema?.hasTable?.('nc_store'))) { return; diff --git a/packages/nocodb/src/lib/version-upgrader/ncProjectRolesUpgrader.ts b/packages/nocodb/src/lib/version-upgrader/ncProjectRolesUpgrader.ts new file mode 100644 index 0000000000..17367ee2d5 --- /dev/null +++ b/packages/nocodb/src/lib/version-upgrader/ncProjectRolesUpgrader.ts @@ -0,0 +1,24 @@ +import { MetaTable } from '../utils/globals'; +import { NcUpgraderCtx } from './NcUpgrader'; + +/** Upgrader for upgrading roles */ +export default async function ({ ncMeta }: NcUpgraderCtx) { + const users = await ncMeta.metaList2(null, null, MetaTable.USERS); + + for (const user of users) { + user.roles = user.roles + .split(',') + .map((r) => { + if (r === 'user') { + return 'org-level-creator'; + } else if (r === 'user-new') { + return 'org-level-viewer'; + } + return r; + }) + .join(','); + await ncMeta.metaUpdate(null, null, MetaTable.USERS, user, { + roles: user.roles, + }); + } +} From 489bc503fbb9deaf7501c69efccc9d3b0ed98f07 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Mon, 17 Oct 2022 23:09:24 +0530 Subject: [PATCH 02/95] feat(gui): rename roles in api and ui Signed-off-by: Pranav C --- packages/nc-gui/lib/constants.ts | 2 +- packages/nc-gui/lib/enums.ts | 3 ++- packages/nocodb/src/enums/OrgUserRoles.ts | 5 +++++ packages/nocodb/src/lib/meta/api/userApi/userApis.ts | 7 ++++--- packages/nocodb/src/lib/version-upgrader/NcUpgrader.ts | 4 ++-- .../src/lib/version-upgrader/ncProjectRolesUpgrader.ts | 5 +++-- 6 files changed, 17 insertions(+), 9 deletions(-) create mode 100644 packages/nocodb/src/enums/OrgUserRoles.ts diff --git a/packages/nc-gui/lib/constants.ts b/packages/nc-gui/lib/constants.ts index e8903720ad..5bf9d6b101 100644 --- a/packages/nc-gui/lib/constants.ts +++ b/packages/nc-gui/lib/constants.ts @@ -18,7 +18,7 @@ export const rolePermissions = { [Role.Super]: '*', [Role.Admin]: {} as Record, [Role.Guest]: {} as Record, - [Role.User]: { + [Role.OrgLevelCreator]: { include: { projectCreate: true, projectActions: true, diff --git a/packages/nc-gui/lib/enums.ts b/packages/nc-gui/lib/enums.ts index 43c6aa0888..a96f4f5fb6 100644 --- a/packages/nc-gui/lib/enums.ts +++ b/packages/nc-gui/lib/enums.ts @@ -1,7 +1,8 @@ export enum Role { Super = 'super', Admin = 'admin', - User = 'user', + OrgLevelCreator = 'org-level-creator', + OrgLevelViewer = 'org-level-viewer', Guest = 'guest', } diff --git a/packages/nocodb/src/enums/OrgUserRoles.ts b/packages/nocodb/src/enums/OrgUserRoles.ts new file mode 100644 index 0000000000..dbf53b2c3e --- /dev/null +++ b/packages/nocodb/src/enums/OrgUserRoles.ts @@ -0,0 +1,5 @@ +export enum OrgUserRoles { + SUPER = 'super', + CREATOR = 'org-level-creator', + VIEWER = 'org-level-viewer', +} diff --git a/packages/nocodb/src/lib/meta/api/userApi/userApis.ts b/packages/nocodb/src/lib/meta/api/userApi/userApis.ts index b4ab59de0c..40b1408e45 100644 --- a/packages/nocodb/src/lib/meta/api/userApi/userApis.ts +++ b/packages/nocodb/src/lib/meta/api/userApi/userApis.ts @@ -1,5 +1,6 @@ import { Request, Response } from 'express'; import { TableType, validatePassword } from 'nocodb-sdk'; +import { OrgUserRoles } from '../../../../enums/OrgUserRoles' import catchError, { NcError } from '../../helpers/catchError'; const { isEmail } = require('validator'); import * as ejs from 'ejs'; @@ -84,10 +85,10 @@ export async function signup(req: Request, res: Response) { NcError.badRequest('User already exist'); } } else { - let roles = 'user'; + let roles: string = OrgUserRoles.CREATOR; if (await User.isFirst()) { - roles = 'user,super'; + roles = `${OrgUserRoles.CREATOR},${OrgUserRoles.SUPER}`; // todo: update in nc_store // roles = 'owner,creator,editor' Tele.emit('evt', { @@ -98,7 +99,7 @@ export async function signup(req: Request, res: Response) { if (process.env.NC_INVITE_ONLY_SIGNUP) { NcError.badRequest('Not allowed to signup, contact super admin.'); } else { - roles = 'user_new'; + roles = OrgUserRoles.VIEWER; } } diff --git a/packages/nocodb/src/lib/version-upgrader/NcUpgrader.ts b/packages/nocodb/src/lib/version-upgrader/NcUpgrader.ts index 2946253259..2bac1fbc49 100644 --- a/packages/nocodb/src/lib/version-upgrader/NcUpgrader.ts +++ b/packages/nocodb/src/lib/version-upgrader/NcUpgrader.ts @@ -5,8 +5,8 @@ import NcMetaIO from '../meta/NcMetaIO'; import ncProjectEnvUpgrader from './ncProjectEnvUpgrader'; import ncProjectEnvUpgrader0011045 from './ncProjectEnvUpgrader0011045'; import ncProjectUpgraderV2_0090000 from './ncProjectUpgraderV2_0090000'; -import ncProjectRolesUpdate from './ncProjectRolesUpdate'; import ncDataTypesUpgrader from './ncDataTypesUpgrader'; +import ncProjectRolesUpgrader from './ncProjectRolesUpgrader'; const log = debug('nc:version-upgrader'); import { Tele } from 'nc-help'; @@ -34,7 +34,7 @@ export default class NcUpgrader { { name: '0011045', handler: ncProjectEnvUpgrader0011045 }, { name: '0090000', handler: ncProjectUpgraderV2_0090000 }, { name: '0098004', handler: ncDataTypesUpgrader }, - { name: '0098005', handler: ncProjectRolesUpdate }, + { name: '0098005', handler: ncProjectRolesUpgrader }, ]; if (!(await ctx.ncMeta.knexConnection?.schema?.hasTable?.('nc_store'))) { return; diff --git a/packages/nocodb/src/lib/version-upgrader/ncProjectRolesUpgrader.ts b/packages/nocodb/src/lib/version-upgrader/ncProjectRolesUpgrader.ts index 17367ee2d5..5d272ccac5 100644 --- a/packages/nocodb/src/lib/version-upgrader/ncProjectRolesUpgrader.ts +++ b/packages/nocodb/src/lib/version-upgrader/ncProjectRolesUpgrader.ts @@ -1,3 +1,4 @@ +import { OrgUserRoles } from '../../enums/OrgUserRoles' import { MetaTable } from '../utils/globals'; import { NcUpgraderCtx } from './NcUpgrader'; @@ -10,9 +11,9 @@ export default async function ({ ncMeta }: NcUpgraderCtx) { .split(',') .map((r) => { if (r === 'user') { - return 'org-level-creator'; + return OrgUserRoles.CREATOR; } else if (r === 'user-new') { - return 'org-level-viewer'; + return OrgUserRoles.VIEWER; } return r; }) From d27a9ccb4cd9728e57e8adf44179d9a4a159607f Mon Sep 17 00:00:00 2001 From: Pranav C Date: Mon, 17 Oct 2022 23:15:08 +0530 Subject: [PATCH 03/95] feat(api): update roles name in acl Signed-off-by: Pranav C --- packages/nocodb/src/lib/utils/projectAcl.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/nocodb/src/lib/utils/projectAcl.ts b/packages/nocodb/src/lib/utils/projectAcl.ts index b10a856bb5..7bae7d2b03 100644 --- a/packages/nocodb/src/lib/utils/projectAcl.ts +++ b/packages/nocodb/src/lib/utils/projectAcl.ts @@ -1,3 +1,5 @@ +import { OrgUserRoles } from '../../enums/OrgUserRoles' + export default { owner: { exclude: { @@ -187,7 +189,7 @@ export default { dataGroupBy: true, commentsCount: true, - galleryViewGet: true, + alleryViewGet: true, kanbanViewGet: true, groupedDataList: true, @@ -271,14 +273,14 @@ export default { dataCount: true, }, }, - user_new: { + [OrgUserRoles.VIEWER]: { include: { passwordChange: true, projectList: true, }, }, - super: '*', - user: { + [OrgUserRoles.SUPER]: '*', + [OrgUserRoles.CREATOR]: { include: { upload: true, uploadViaURL: true, @@ -301,4 +303,4 @@ export default { auditRowUpdate: true, }, }, -}; +} From c2de7f5a7b3907dda8cb2f865797bfb13d880e31 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Mon, 17 Oct 2022 23:31:10 +0530 Subject: [PATCH 04/95] feat(api): WIP - allow super admin access to all project Signed-off-by: Pranav C --- .../nocodb/src/lib/meta/api/userApi/initStrategies.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts b/packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts index 37b4c08456..4f71798dda 100644 --- a/packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts +++ b/packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts @@ -1,3 +1,4 @@ +import { OrgUserRoles } from '../../../../enums/OrgUserRoles'; import User from '../../../models/User'; import ProjectUser from '../../../models/ProjectUser'; import { promisify } from 'util'; @@ -91,6 +92,13 @@ export function initStrategies(router): void { ...Noco.getConfig().auth.jwt.options, }, async (req, jwtPayload, done) => { + // todo: improve this + if (req.roles.split(',').includes(OrgUserRoles.SUPER)) { + return User.getByEmail(jwtPayload?.email).then(async (user) => { + return done(null, { ...user, roles: 'owner,creator' }); + }); + } + const keyVals = [jwtPayload?.email]; if (req.ncProjectId) { keyVals.push(req.ncProjectId); From 705f8ead4c970a492b19aec07640faed0f76dc0e Mon Sep 17 00:00:00 2001 From: Pranav C Date: Tue, 18 Oct 2022 12:25:22 +0530 Subject: [PATCH 05/95] chore(swagger): add org users apis in swagger docs Signed-off-by: Pranav C --- packages/nocodb-sdk/src/lib/Api.ts | 40 +++++----- scripts/sdk/swagger.json | 122 ++++++++++++++++++++++++++++- 2 files changed, 139 insertions(+), 23 deletions(-) diff --git a/packages/nocodb-sdk/src/lib/Api.ts b/packages/nocodb-sdk/src/lib/Api.ts index da6597d0ff..726f33c8c2 100644 --- a/packages/nocodb-sdk/src/lib/Api.ts +++ b/packages/nocodb-sdk/src/lib/Api.ts @@ -832,8 +832,8 @@ export class Api< > extends HttpClient { auth = { /** - * @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. + * * @tags Auth * @name Signup * @summary Signup @@ -872,8 +872,8 @@ export class Api< }), /** - * @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. + * * @tags Auth * @name Signin * @summary Signin @@ -960,7 +960,7 @@ export class Api< /** * @description Change password of authenticated user with a new one. - * + * * @tags Auth * @name PasswordChange * @summary Password change @@ -1071,7 +1071,7 @@ export class Api< /** * No description - * + * * @tags Auth * @name ProjectUserList * @summary Project users @@ -1192,7 +1192,7 @@ export class Api< project = { /** * No description - * + * * @tags Project * @name MetaGet * @summary Project info @@ -1377,7 +1377,7 @@ export class Api< /** * @description Read project details - * + * * @tags Project * @name SharedBaseGet * @request GET:/api/v1/db/meta/projects/{projectId}/shared @@ -1420,7 +1420,7 @@ export class Api< /** * No description - * + * * @tags Project * @name SharedBaseCreate * @request POST:/api/v1/db/meta/projects/{projectId}/shared @@ -1457,7 +1457,7 @@ export class Api< /** * No description - * + * * @tags Project * @name SharedBaseUpdate * @request PATCH:/api/v1/db/meta/projects/{projectId}/shared @@ -1543,7 +1543,7 @@ export class Api< /** * No description - * + * * @tags Project * @name AuditList * @request GET:/api/v1/db/meta/projects/{projectId}/audits @@ -2146,7 +2146,7 @@ export class Api< /** * No description - * + * * @tags DB view share * @name Create * @request POST:/api/v1/db/meta/views/{viewId}/share @@ -2265,7 +2265,7 @@ export class Api< dbTableSort = { /** * No description - * + * * @tags DB table sort * @name List * @request GET:/api/v1/db/meta/views/{viewId}/sorts @@ -3455,7 +3455,7 @@ export class Api< /** * @description Read project details - * + * * @tags Public * @name SharedBaseGet * @request GET:/api/v1/db/public/shared-base/{sharedBaseUuid}/meta @@ -3479,7 +3479,7 @@ export class Api< /** * No description - * + * * @tags Public * @name SharedViewMetaGet * @request GET:/api/v1/db/public/shared-view/{sharedViewUuid}/meta @@ -3614,7 +3614,7 @@ export class Api< /** * No description - * + * * @tags Utils * @name TestConnection * @request POST:/api/v1/db/meta/connection/test @@ -3724,7 +3724,7 @@ export class Api< /** * No description - * + * * @tags Utils * @name AggregatedMetaInfo * @request GET:/api/v1/aggregated-meta-info @@ -3841,7 +3841,7 @@ export class Api< dbTableWebhook = { /** * No description - * + * * @tags DB table webhook * @name List * @request GET:/api/v1/db/meta/tables/{tableId}/hooks @@ -3913,7 +3913,7 @@ export class Api< /** * No description - * + * * @tags DB table webhook * @name SamplePayloadGet * @request GET:/api/v1/db/meta/tables/{tableId}/hooks/samplePayload/{operation} @@ -3982,7 +3982,7 @@ export class Api< plugin = { /** * No description - * + * * @tags Plugin * @name List * @request GET:/api/v1/db/meta/plugins diff --git a/scripts/sdk/swagger.json b/scripts/sdk/swagger.json index bb920e9f0d..4fd102da6f 100644 --- a/scripts/sdk/swagger.json +++ b/scripts/sdk/swagger.json @@ -441,6 +441,125 @@ }, "parameters": [] }, + "/api/v1/users": { + "get": { + "summary": "Project users", + "operationId": "org-users-list", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "users": { + "type": "object", + "properties": { + "list": { + "type": "array", + "uniqueItems": true, + "minItems": 1, + "items": { + "$ref": "#/components/schemas/User" + } + }, + "pageInfo": { + "$ref": "#/components/schemas/Paginated" + } + }, + "required": [ + "list", + "pageInfo" + ] + } + } + } + } + } + } + }, + "description": "", + "tags": [ + "OrgUsers" + ] + }, + "parameters": [], + "post": { + "summary": "Project User Add", + "operationId": "rgo-users-add", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": {} + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "tags": [ + "Auth" + ] + } + }, + "/api/v1/users/{userId}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "userId", + "in": "path", + "required": true + } + ], + "patch": { + "summary": "", + "operationId": "org-users-update", + "responses": { + "200": { + "description": "OK" + } + }, + "tags": [ + "OrgUsers" + ], + "description": "", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + }, + "description": "" + } + }, + "delete": { + "summary": "", + "operationId": "org-users-delete", + "responses": { + "200": { + "description": "OK" + } + }, + "tags": [ + "OrgUsers" + ], + "description": "" + } + }, "/api/v1/db/meta/projects/{projectId}/users": { "get": { "summary": "Project users", @@ -4961,9 +5080,6 @@ "description": "", "parameters": [ { - "schema": { - "type": "array" - }, "in": "query", "name": "ids", "required": true From 5587d5857d5dfa89c617a99181647032dc26b41f Mon Sep 17 00:00:00 2001 From: Pranav C Date: Tue, 18 Oct 2022 17:48:03 +0530 Subject: [PATCH 06/95] feat(api): add project count in api Signed-off-by: Pranav C --- packages/nocodb/src/lib/models/User.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/nocodb/src/lib/models/User.ts b/packages/nocodb/src/lib/models/User.ts index 0e3b5ba82c..57213d177a 100644 --- a/packages/nocodb/src/lib/models/User.ts +++ b/packages/nocodb/src/lib/models/User.ts @@ -134,10 +134,21 @@ export default class User implements UserType { return false; } - static async count(ncMeta = Noco.ncMeta) { - return ( - await ncMeta.knex(MetaTable.USERS).count('id', { as: 'count' }).first() - )?.count; + public static async count( + { + query = '', + }: { + query?: string; + } = {}, + ncMeta = Noco.ncMeta + ): Promise { + const qb = ncMeta.knex(MetaTable.USERS); + + if (query) { + qb.where('email', 'like', `%${query.toLowerCase?.()}%`); + } + + return (await qb.count('id', { as: 'count' }).first()).count; } static async get(userId, ncMeta = Noco.ncMeta) { From ca75af11019ae13f5cdcad27f8b779022e1c8978 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Wed, 19 Oct 2022 10:33:15 +0530 Subject: [PATCH 07/95] feat(gui): add users list page Signed-off-by: Pranav C --- packages/nc-gui/components/org-user/index.vue | 72 +++++++++++++++++++ packages/nc-gui/lang/en.json | 4 +- packages/nc-gui/pages/users/index.vue | 11 +++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 packages/nc-gui/components/org-user/index.vue create mode 100644 packages/nc-gui/pages/users/index.vue diff --git a/packages/nc-gui/components/org-user/index.vue b/packages/nc-gui/components/org-user/index.vue new file mode 100644 index 0000000000..39eb22be9a --- /dev/null +++ b/packages/nc-gui/components/org-user/index.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/packages/nc-gui/lang/en.json b/packages/nc-gui/lang/en.json index 87ef39984f..a7f8ac120b 100644 --- a/packages/nc-gui/lang/en.json +++ b/packages/nc-gui/lang/en.json @@ -104,7 +104,9 @@ "creator": "Creator", "editor": "Editor", "commenter": "Commenter", - "viewer": "Viewer" + "viewer": "Viewer", + "orgLevelCreator" : "Organization level creator", + "orgLevelViewer" : "Organization level viewer" }, "sqlVIew": "SQL View" }, diff --git a/packages/nc-gui/pages/users/index.vue b/packages/nc-gui/pages/users/index.vue new file mode 100644 index 0000000000..8383f0018c --- /dev/null +++ b/packages/nc-gui/pages/users/index.vue @@ -0,0 +1,11 @@ + + + + + From eefa1bb2c400a68ee83f3fac1dc69bf35c500df5 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Wed, 19 Oct 2022 10:34:14 +0530 Subject: [PATCH 08/95] feat(api): user update api implementation Signed-off-by: Pranav C --- packages/nocodb/src/lib/meta/api/index.ts | 2 + .../nocodb/src/lib/meta/api/orgUserApis.ts | 65 +++++++++++++++++++ .../lib/meta/api/userApi/initStrategies.ts | 11 ++-- .../src/lib/meta/helpers/ncMetaAclMw.ts | 13 +++- packages/nocodb/src/lib/models/User.ts | 7 +- 5 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 packages/nocodb/src/lib/meta/api/orgUserApis.ts diff --git a/packages/nocodb/src/lib/meta/api/index.ts b/packages/nocodb/src/lib/meta/api/index.ts index 579dd53088..62798bf79a 100644 --- a/packages/nocodb/src/lib/meta/api/index.ts +++ b/packages/nocodb/src/lib/meta/api/index.ts @@ -1,3 +1,4 @@ +import orgUserApis from './orgUserApis' import projectApis from './projectApis'; import tableApis from './tableApis'; import columnApis from './columnApis'; @@ -87,6 +88,7 @@ export default function (router: Router, server) { router.use(hookApis); router.use(pluginApis); router.use(projectUserApis); + router.use(orgUserApis); router.use(sharedBaseApis); router.use(modelVisibilityApis); router.use(metaDiffApis); diff --git a/packages/nocodb/src/lib/meta/api/orgUserApis.ts b/packages/nocodb/src/lib/meta/api/orgUserApis.ts new file mode 100644 index 0000000000..8647f06b24 --- /dev/null +++ b/packages/nocodb/src/lib/meta/api/orgUserApis.ts @@ -0,0 +1,65 @@ +import { Router } from 'express'; +import { OrgUserRoles } from '../../../enums/OrgUserRoles'; +import User from '../../models/User'; +import { metaApiMetrics } from '../helpers/apiMetrics'; +import { extractProps } from '../helpers/extractProps'; +import ncMetaAclMw from '../helpers/ncMetaAclMw'; +import { PagedResponseImpl } from '../helpers/PagedResponse'; + +async function userList(req, res) { + res.json( + new PagedResponseImpl(await User.list(req.query), { + ...req.query, + count: await User.count(req.query), + }) + ); +} + +async function userUpdate(req, res) { + const updteBody = extractProps(req.body, ['role']); + + const user = await User.get(req.params.userId); + + if (user.roles.includes(OrgUserRoles.SUPER)) { + throw new Error('Cannot update super admin roles'); + } + + res.json(await User.update(req.params.userId, updteBody)); +} + +async function userDelete(req, res) { + const user = await User.get(req.params.userId); + + if (user.roles.includes(OrgUserRoles.SUPER)) { + throw new Error('Cannot delete super admin'); + } + + res.json(await User.delete(req.params.userId)); +} + +async function userAdd(req, res) { + +} + +const router = Router({ mergeParams: true }); +router.get( + '/api/v1/users', + metaApiMetrics, + ncMetaAclMw(userList, 'userList', [OrgUserRoles.SUPER]) +); +router.patch( + '/api/v1/db/meta/users/:userId', + metaApiMetrics, + ncMetaAclMw(userUpdate, 'userUpdate', [OrgUserRoles.SUPER]) +); +router.delete( + '/api/v1/db/meta/users/:userId', + metaApiMetrics, + ncMetaAclMw(userAdd, 'userAdd', [OrgUserRoles.SUPER]) +); +router.post( + '/api/v1/db/meta/users/:userId', + metaApiMetrics, + ncMetaAclMw(userDelete, 'userDelete', [OrgUserRoles.SUPER]) +); +export default router; diff --git a/packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts b/packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts index 4f71798dda..8966c18789 100644 --- a/packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts +++ b/packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts @@ -1,4 +1,3 @@ -import { OrgUserRoles } from '../../../../enums/OrgUserRoles'; import User from '../../../models/User'; import ProjectUser from '../../../models/ProjectUser'; import { promisify } from 'util'; @@ -93,11 +92,11 @@ 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 done(null, { ...user, roles: 'owner,creator' }); - }); - } + // if (req.roles.split(',').includes(OrgUserRoles.SUPER)) { + // return User.getByEmail(jwtPayload?.email).then(async (user) => { + // return done(null, { ...user, roles: 'owner,creator' }); + // }); + // } const keyVals = [jwtPayload?.email]; if (req.ncProjectId) { diff --git a/packages/nocodb/src/lib/meta/helpers/ncMetaAclMw.ts b/packages/nocodb/src/lib/meta/helpers/ncMetaAclMw.ts index b024ef7ab4..f8fef0e617 100644 --- a/packages/nocodb/src/lib/meta/helpers/ncMetaAclMw.ts +++ b/packages/nocodb/src/lib/meta/helpers/ncMetaAclMw.ts @@ -1,22 +1,29 @@ +import { OrgUserRoles } from '../../../enums/OrgUserRoles'; import projectAcl from '../../utils/projectAcl'; import { NextFunction, Request, Response } from 'express'; import catchError, { NcError } from './catchError'; import extractProjectIdAndAuthenticate from './extractProjectIdAndAuthenticate'; -export default function (handlerFn, permissionName) { +export default function ( + handlerFn, + permissionName, + allowedRoles?: (OrgUserRoles | string)[] +) { return [ extractProjectIdAndAuthenticate, catchError(async function authMiddleware(req, _res, next) { const roles = req?.session?.passport?.user?.roles; if ( + (!allowedRoles || allowedRoles.some((role) => roles?.[role])) && !( roles?.creator || roles?.owner || roles?.editor || roles?.viewer || roles?.commenter || - roles?.user || - roles?.user_new + roles?.[OrgUserRoles.SUPER] ||a + roles?.[OrgUserRoles.CREATOR] || + roles?.[OrgUserRoles.VIEWER] ) ) { NcError.unauthorized('Unauthorized access'); diff --git a/packages/nocodb/src/lib/models/User.ts b/packages/nocodb/src/lib/models/User.ts index 57213d177a..e3b819005c 100644 --- a/packages/nocodb/src/lib/models/User.ts +++ b/packages/nocodb/src/lib/models/User.ts @@ -1,4 +1,5 @@ import { UserType } from 'nocodb-sdk'; +import { NcError } from '../meta/helpers/catchError' import { CacheGetType, CacheScope, MetaTable } from '../utils/globals'; import Noco from '../Noco'; import { extractProps } from '../meta/helpers/extractProps'; @@ -151,7 +152,7 @@ export default class User implements UserType { return (await qb.count('id', { as: 'count' }).first()).count; } - static async get(userId, ncMeta = Noco.ncMeta) { + static async get(userId, ncMeta = Noco.ncMeta): Promise { let user = userId && (await NocoCache.get( @@ -225,4 +226,8 @@ export default class User implements UserType { await NocoCache.del(`${CacheScope.USER}:${userId}`); await ncMeta.metaDelete(null, null, MetaTable.USERS, userId); } + + static async delete(_userId:string) { + NcError.notImplemented() + } } From 65c0a6f09275588f33ec157777346970e3cefb73 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Wed, 19 Oct 2022 10:36:41 +0530 Subject: [PATCH 09/95] fix(api): typo correction Signed-off-by: Pranav C --- packages/nocodb/src/lib/meta/api/orgUserApis.ts | 5 +++-- packages/nocodb/src/lib/meta/helpers/ncMetaAclMw.ts | 2 +- packages/nocodb/src/lib/models/User.ts | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/nocodb/src/lib/meta/api/orgUserApis.ts b/packages/nocodb/src/lib/meta/api/orgUserApis.ts index 8647f06b24..bd00e1005a 100644 --- a/packages/nocodb/src/lib/meta/api/orgUserApis.ts +++ b/packages/nocodb/src/lib/meta/api/orgUserApis.ts @@ -2,6 +2,7 @@ import { Router } from 'express'; import { OrgUserRoles } from '../../../enums/OrgUserRoles'; import User from '../../models/User'; import { metaApiMetrics } from '../helpers/apiMetrics'; +import { NcError } from '../helpers/catchError'; import { extractProps } from '../helpers/extractProps'; import ncMetaAclMw from '../helpers/ncMetaAclMw'; import { PagedResponseImpl } from '../helpers/PagedResponse'; @@ -37,8 +38,8 @@ async function userDelete(req, res) { res.json(await User.delete(req.params.userId)); } -async function userAdd(req, res) { - +async function userAdd(_req, _res) { + NcError.notImplemented(); } const router = Router({ mergeParams: true }); diff --git a/packages/nocodb/src/lib/meta/helpers/ncMetaAclMw.ts b/packages/nocodb/src/lib/meta/helpers/ncMetaAclMw.ts index f8fef0e617..9454ba6548 100644 --- a/packages/nocodb/src/lib/meta/helpers/ncMetaAclMw.ts +++ b/packages/nocodb/src/lib/meta/helpers/ncMetaAclMw.ts @@ -21,7 +21,7 @@ export default function ( roles?.editor || roles?.viewer || roles?.commenter || - roles?.[OrgUserRoles.SUPER] ||a + roles?.[OrgUserRoles.SUPER] || roles?.[OrgUserRoles.CREATOR] || roles?.[OrgUserRoles.VIEWER] ) diff --git a/packages/nocodb/src/lib/models/User.ts b/packages/nocodb/src/lib/models/User.ts index e3b819005c..670dda2523 100644 --- a/packages/nocodb/src/lib/models/User.ts +++ b/packages/nocodb/src/lib/models/User.ts @@ -1,5 +1,5 @@ import { UserType } from 'nocodb-sdk'; -import { NcError } from '../meta/helpers/catchError' +import { NcError } from '../meta/helpers/catchError'; import { CacheGetType, CacheScope, MetaTable } from '../utils/globals'; import Noco from '../Noco'; import { extractProps } from '../meta/helpers/extractProps'; @@ -227,7 +227,7 @@ export default class User implements UserType { await ncMeta.metaDelete(null, null, MetaTable.USERS, userId); } - static async delete(_userId:string) { - NcError.notImplemented() + static async delete(_userId: string) { + NcError.notImplemented(); } } From 2bfea723c4445d8ae02b5ee9f492d880768df505 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Wed, 19 Oct 2022 10:42:00 +0530 Subject: [PATCH 10/95] chore(swagger): tg nme correction Signed-off-by: Pranav C --- scripts/sdk/swagger.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/sdk/swagger.json b/scripts/sdk/swagger.json index 4fd102da6f..7bed803086 100644 --- a/scripts/sdk/swagger.json +++ b/scripts/sdk/swagger.json @@ -481,7 +481,7 @@ }, "description": "", "tags": [ - "OrgUsers" + "Org users" ] }, "parameters": [], @@ -532,7 +532,7 @@ } }, "tags": [ - "OrgUsers" + "Org users" ], "description": "", "requestBody": { @@ -555,7 +555,7 @@ } }, "tags": [ - "OrgUsers" + "Org users" ], "description": "" } From 13d1dbaac97f4e2d9928ab24e39097e27b4805f8 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Wed, 19 Oct 2022 12:37:50 +0530 Subject: [PATCH 11/95] feat(gui): update ui and integrate api Signed-off-by: Pranav C --- packages/nc-gui/components/org-user/index.vue | 250 +++++++++++++----- packages/nc-gui/plugins/tele.ts | 2 + .../nocodb/src/lib/meta/api/orgUserApis.ts | 10 +- packages/nocodb/src/lib/models/User.ts | 114 ++++---- scripts/sdk/swagger.json | 3 +- 5 files changed, 253 insertions(+), 126 deletions(-) diff --git a/packages/nc-gui/components/org-user/index.vue b/packages/nc-gui/components/org-user/index.vue index 39eb22be9a..80afe34ef6 100644 --- a/packages/nc-gui/components/org-user/index.vue +++ b/packages/nc-gui/components/org-user/index.vue @@ -1,70 +1,200 @@ diff --git a/packages/nc-gui/plugins/tele.ts b/packages/nc-gui/plugins/tele.ts index cd9c0b1ca1..4c21654c36 100644 --- a/packages/nc-gui/plugins/tele.ts +++ b/packages/nc-gui/plugins/tele.ts @@ -37,6 +37,7 @@ export default defineNuxtPlugin(async (nuxtApp) => { socket.emit('page', { path: to.matched[0].path + (to.query && to.query.type ? `?type=${to.query.type}` : ''), + pid: route?.params?.projectId, }) }) @@ -48,6 +49,7 @@ export default defineNuxtPlugin(async (nuxtApp) => { event: evt, ...(data || {}), path: route?.matched?.[0]?.path, + pid: route?.params?.projectId, }) } }, diff --git a/packages/nocodb/src/lib/meta/api/orgUserApis.ts b/packages/nocodb/src/lib/meta/api/orgUserApis.ts index bd00e1005a..18dd4c08d9 100644 --- a/packages/nocodb/src/lib/meta/api/orgUserApis.ts +++ b/packages/nocodb/src/lib/meta/api/orgUserApis.ts @@ -22,7 +22,7 @@ async function userUpdate(req, res) { const user = await User.get(req.params.userId); if (user.roles.includes(OrgUserRoles.SUPER)) { - throw new Error('Cannot update super admin roles'); + NcError.badRequest('Cannot update super admin roles'); } res.json(await User.update(req.params.userId, updteBody)); @@ -32,7 +32,7 @@ async function userDelete(req, res) { const user = await User.get(req.params.userId); if (user.roles.includes(OrgUserRoles.SUPER)) { - throw new Error('Cannot delete super admin'); + NcError.badRequest('Cannot delete super admin'); } res.json(await User.delete(req.params.userId)); @@ -49,17 +49,17 @@ router.get( ncMetaAclMw(userList, 'userList', [OrgUserRoles.SUPER]) ); router.patch( - '/api/v1/db/meta/users/:userId', + '/api/v1/users/:userId', metaApiMetrics, ncMetaAclMw(userUpdate, 'userUpdate', [OrgUserRoles.SUPER]) ); router.delete( - '/api/v1/db/meta/users/:userId', + '/api/v1/users/:userId', metaApiMetrics, ncMetaAclMw(userAdd, 'userAdd', [OrgUserRoles.SUPER]) ); router.post( - '/api/v1/db/meta/users/:userId', + '/api/v1/users/:userId', metaApiMetrics, ncMetaAclMw(userDelete, 'userDelete', [OrgUserRoles.SUPER]) ); diff --git a/packages/nocodb/src/lib/models/User.ts b/packages/nocodb/src/lib/models/User.ts index 670dda2523..fcd5c8ac82 100644 --- a/packages/nocodb/src/lib/models/User.ts +++ b/packages/nocodb/src/lib/models/User.ts @@ -6,28 +6,28 @@ import { extractProps } from '../meta/helpers/extractProps'; import NocoCache from '../cache/NocoCache'; import { NcError } from '../meta/helpers/catchError'; export default class User implements UserType { - id: string; + id: string /** @format email */ - email: string; - - password?: string; - salt?: string; - firstname: string; - lastname: string; - username?: string; - refresh_token?: string; - invite_token?: string; - invite_token_expires?: number | Date; - reset_password_expires?: number | Date; - reset_password_token?: string; - email_verification_token?: string; - email_verified: boolean; - roles?: string; - token_version?: string; + email: string + + password?: string + salt?: string + firstname: string + lastname: string + username?: string + refresh_token?: string + invite_token?: string + invite_token_expires?: number | Date + reset_password_expires?: number | Date + reset_password_token?: string + email_verification_token?: string + email_verified: boolean + roles?: string + token_version?: string constructor(data: User) { - Object.assign(this, data); + Object.assign(this, data) } public static async insert(user: Partial, ncMeta = Noco.ncMeta) { @@ -48,22 +48,22 @@ export default class User implements UserType { 'email_verified', 'roles', 'token_version', - ]); + ]) if (insertObj.email) { - insertObj.email = insertObj.email.toLowerCase(); + insertObj.email = insertObj.email.toLowerCase() } const { id } = await ncMeta.metaInsert2( null, null, MetaTable.USERS, - insertObj - ); + insertObj, + ) - await NocoCache.del(CacheScope.INSTANCE_META); + await NocoCache.del(CacheScope.INSTANCE_META) - return this.get(id, ncMeta); + return this.get(id, ncMeta) } public static async update(id, user: Partial, ncMeta = Noco.ncMeta) { const updateObj = extractProps(user, [ @@ -82,13 +82,13 @@ export default class User implements UserType { 'email_verified', 'roles', 'token_version', - ]); + ]) if (updateObj.email) { - updateObj.email = updateObj.email.toLowerCase(); + updateObj.email = updateObj.email.toLowerCase() } else { // set email prop to avoid generation of invalid cache key - updateObj.email = (await this.get(id, ncMeta))?.email?.toLowerCase(); + updateObj.email = (await this.get(id, ncMeta))?.email?.toLowerCase() } // get existing cache const keys = [ @@ -96,43 +96,43 @@ export default class User implements UserType { `${CacheScope.USER}:${id}`, // update user: `${CacheScope.USER}:${user.email}`, - ]; + ] for (const key of keys) { - let o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT); + let o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT) if (o) { - o = { ...o, ...updateObj }; + o = { ...o, ...updateObj } // set cache - await NocoCache.set(key, o); + await NocoCache.set(key, o) } } // as is unknown, delete user:___ in cache - await NocoCache.delAll(CacheScope.USER, `${user.email}___*`); + await NocoCache.delAll(CacheScope.USER, `${user.email}___*`) // set meta - return await ncMeta.metaUpdate(null, null, MetaTable.USERS, updateObj, id); + return await ncMeta.metaUpdate(null, null, MetaTable.USERS, updateObj, id) } public static async getByEmail(_email: string, ncMeta = Noco.ncMeta) { - const email = _email?.toLowerCase(); + const email = _email?.toLowerCase() let user = email && (await NocoCache.get( `${CacheScope.USER}:${email}`, - CacheGetType.TYPE_OBJECT - )); + CacheGetType.TYPE_OBJECT, + )) if (!user) { user = await ncMeta.metaGet2(null, null, MetaTable.USERS, { email, - }); - await NocoCache.set(`${CacheScope.USER}:${email}`, user); + }) + await NocoCache.set(`${CacheScope.USER}:${email}`, user) } - return user; + return user } static async isFirst(ncMeta = Noco.ncMeta) { - const isFirst = !(await NocoCache.getAll(`${CacheScope.USER}:*`))?.length; + const isFirst = !(await NocoCache.getAll(`${CacheScope.USER}:*`))?.length if (isFirst) - return !(await ncMeta.metaGet2(null, null, MetaTable.USERS, {})); - return false; + return !(await ncMeta.metaGet2(null, null, MetaTable.USERS, {})) + return false } public static async count( @@ -141,15 +141,15 @@ export default class User implements UserType { }: { query?: string; } = {}, - ncMeta = Noco.ncMeta + ncMeta = Noco.ncMeta, ): Promise { - const qb = ncMeta.knex(MetaTable.USERS); + const qb = ncMeta.knex(MetaTable.USERS) if (query) { - qb.where('email', 'like', `%${query.toLowerCase?.()}%`); + qb.where('email', 'like', `%${query.toLowerCase?.()}%`) } - return (await qb.count('id', { as: 'count' }).first()).count; + return (await qb.count('id', { as: 'count' }).first()).count } static async get(userId, ncMeta = Noco.ncMeta): Promise { @@ -157,20 +157,20 @@ export default class User implements UserType { userId && (await NocoCache.get( `${CacheScope.USER}:${userId}`, - CacheGetType.TYPE_OBJECT - )); + CacheGetType.TYPE_OBJECT, + )) if (!user) { - user = await ncMeta.metaGet2(null, null, MetaTable.USERS, userId); - await NocoCache.set(`${CacheScope.USER}:${userId}`, user); + user = await ncMeta.metaGet2(null, null, MetaTable.USERS, userId) + await NocoCache.set(`${CacheScope.USER}:${userId}`, user) } - return user; + return user } static async getByRefreshToken(refresh_token, ncMeta = Noco.ncMeta) { const user = await ncMeta.metaGet2(null, null, MetaTable.USERS, { refresh_token, - }); - return user; + }) + return user } public static async list( @@ -183,7 +183,7 @@ export default class User implements UserType { offset?: number | undefined; query?: string; } = {}, - ncMeta = Noco.ncMeta + ncMeta = Noco.ncMeta, ) { let queryBuilder = ncMeta.knex(MetaTable.USERS); @@ -214,7 +214,7 @@ export default class User implements UserType { .as('projectsCount') ); if (query) { - queryBuilder.where('email', 'like', `%${query.toLowerCase?.()}%`); + queryBuilder.where('email', 'like', `%${query.toLowerCase?.()}%`) } return queryBuilder; @@ -226,8 +226,4 @@ export default class User implements UserType { await NocoCache.del(`${CacheScope.USER}:${userId}`); await ncMeta.metaDelete(null, null, MetaTable.USERS, userId); } - - static async delete(_userId: string) { - NcError.notImplemented(); - } } diff --git a/scripts/sdk/swagger.json b/scripts/sdk/swagger.json index 7bed803086..09e6ce23a1 100644 --- a/scripts/sdk/swagger.json +++ b/scripts/sdk/swagger.json @@ -6407,8 +6407,7 @@ "format": "email" }, "roles": { - "type": "string", - "format": "email" + "type": "string" }, "date_of_birth": { "type": "string", From 30496bf0bb04e161aa4209556c99dfb5e1646d8f Mon Sep 17 00:00:00 2001 From: Pranav C Date: Wed, 19 Oct 2022 12:39:47 +0530 Subject: [PATCH 12/95] fix(gui): on inviting a new user to project provide viewer role Signed-off-by: Pranav C --- packages/nocodb/src/lib/meta/api/projectUserApis.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/nocodb/src/lib/meta/api/projectUserApis.ts b/packages/nocodb/src/lib/meta/api/projectUserApis.ts index de4cf2871a..9d10498de5 100644 --- a/packages/nocodb/src/lib/meta/api/projectUserApis.ts +++ b/packages/nocodb/src/lib/meta/api/projectUserApis.ts @@ -1,3 +1,4 @@ +import { OrgUserRoles } from '../../../enums/OrgUserRoles' import ncMetaAclMw from '../helpers/ncMetaAclMw'; import { Router } from 'express'; import { PagedResponseImpl } from '../helpers/PagedResponse'; @@ -65,7 +66,7 @@ async function userInvite(req, res, next): Promise { // todo : provide a different role await User.update(user.id, { - roles: 'user', + roles: OrgUserRoles.VIEWER, }); await ProjectUser.insert({ @@ -102,7 +103,7 @@ async function userInvite(req, res, next): Promise { invite_token, invite_token_expires: new Date(Date.now() + 24 * 60 * 60 * 1000), email, - roles: 'user', + roles: OrgUserRoles.VIEWER, token_version: randomTokenString(), }); From de71f8914a40853e33bdeefee2129bb927f2ddc0 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Wed, 19 Oct 2022 13:59:49 +0530 Subject: [PATCH 13/95] feat(gui): add filter in user list Signed-off-by: Pranav C --- packages/nc-gui/components.d.ts | 1 + packages/nc-gui/components/org-user/index.vue | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/nc-gui/components.d.ts b/packages/nc-gui/components.d.ts index 72d94ba7b7..db0ae56b3b 100644 --- a/packages/nc-gui/components.d.ts +++ b/packages/nc-gui/components.d.ts @@ -108,6 +108,7 @@ declare module '@vue/runtime-core' { MdiAccountPlusOutline: typeof import('~icons/mdi/account-plus-outline')['default'] MdiAccountSupervisorOutline: typeof import('~icons/mdi/account-supervisor-outline')['default'] MdiAdd: typeof import('~icons/mdi/add')['default'] + MdiAdd: typeof import('~icons/mdi/add')['default'] MdiAlpha: typeof import('~icons/mdi/alpha')['default'] MdiAlphaA: typeof import('~icons/mdi/alpha-a')['default'] MdiApi: typeof import('~icons/mdi/api')['default'] diff --git a/packages/nc-gui/components/org-user/index.vue b/packages/nc-gui/components/org-user/index.vue index 80afe34ef6..bab84d4971 100644 --- a/packages/nc-gui/components/org-user/index.vue +++ b/packages/nc-gui/components/org-user/index.vue @@ -73,8 +73,15 @@ const deleteUser = async (userId: string) => {