Browse Source

Merge pull request #6334 from nocodb/develop

pull/6335/head 0.111.3
github-actions[bot] 1 year ago committed by GitHub
parent
commit
8ce5f2dab6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      packages/nc-gui/middleware/auth.global.ts
  2. 54
      packages/nc-gui/package-lock.json
  3. 2
      packages/nc-gui/package.json
  4. 4
      packages/nocodb-sdk/package-lock.json
  5. 32
      packages/nocodb/package-lock.json
  6. 4
      packages/nocodb/package.json
  7. 267
      packages/nocodb/src/helpers/initAdminFromEnv.ts
  8. 11
      packages/nocodb/src/middlewares/extract-ids/extract-ids.middleware.ts
  9. 2
      packages/nocodb/src/models/User.ts
  10. 1
      packages/nocodb/src/services/api-docs/swagger/getSwaggerColumnMetas.ts
  11. 6
      packages/nocodb/src/services/api-docs/swagger/templates/paths.ts
  12. 17
      packages/nocodb/src/strategies/authtoken.strategy/authtoken.strategy.ts

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

@ -85,7 +85,7 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
}
} else {
/** If page is limited to certain users verify the user have the roles */
if (to.meta.allowedRoles && to.meta.allowedRoles.every((role) => !allRoles.value[role])) {
if (to.meta.allowedRoles && to.meta.allowedRoles.every((role) => !allRoles.value?.[role])) {
message.error("You don't have enough permission to access the page.")
return navigateTo('/')
}
@ -94,7 +94,7 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
if (to.params.projectId && from.params.projectId !== to.params.projectId) {
const user = await api.auth.me({ project_id: to.params.projectId as string })
if (user?.roles?.user) {
if (user?.roles?.guest) {
message.error("You don't have enough permission to access the project.")
return navigateTo('/')

54
packages/nc-gui/package-lock.json generated

@ -37,7 +37,7 @@
"locale-codes": "^1.3.1",
"monaco-editor": "^0.33.0",
"monaco-sql-languages": "^0.11.0",
"nocodb-sdk": "0.111.2",
"nocodb-sdk": "file:../nocodb-sdk",
"papaparse": "^5.3.2",
"parse-github-url": "^1.0.2",
"pinia": "^2.1.4",
@ -131,7 +131,6 @@
},
"../nocodb-sdk": {
"version": "0.111.2",
"extraneous": true,
"license": "AGPL-3.0-or-later",
"dependencies": {
"axios": "^0.21.1",
@ -8944,6 +8943,7 @@
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"devOptional": true,
"funding": [
{
"type": "individual",
@ -11680,21 +11680,8 @@
}
},
"node_modules/nocodb-sdk": {
"version": "0.111.2",
"resolved": "https://registry.npmjs.org/nocodb-sdk/-/nocodb-sdk-0.111.2.tgz",
"integrity": "sha512-daL3VKOzklL9AYhWfIOJeUDYUBdlPaW0xUmcCYzEJX/q/Ca3Jhk3/5o5XjJIneT11s/UaQ6dPv8boD36tEabhA==",
"dependencies": {
"axios": "^0.21.1",
"jsep": "^1.3.6"
}
},
"node_modules/nocodb-sdk/node_modules/axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"dependencies": {
"follow-redirects": "^1.14.0"
}
"resolved": "../nocodb-sdk",
"link": true
},
"node_modules/node-abi": {
"version": "3.45.0",
@ -23611,7 +23598,8 @@
"follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"devOptional": true
},
"for-each": {
"version": "0.3.3",
@ -25631,22 +25619,24 @@
}
},
"nocodb-sdk": {
"version": "0.111.2",
"resolved": "https://registry.npmjs.org/nocodb-sdk/-/nocodb-sdk-0.111.2.tgz",
"integrity": "sha512-daL3VKOzklL9AYhWfIOJeUDYUBdlPaW0xUmcCYzEJX/q/Ca3Jhk3/5o5XjJIneT11s/UaQ6dPv8boD36tEabhA==",
"version": "file:../nocodb-sdk",
"requires": {
"@typescript-eslint/eslint-plugin": "^4.0.1",
"@typescript-eslint/parser": "^4.0.1",
"axios": "^0.21.1",
"jsep": "^1.3.6"
},
"dependencies": {
"axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"requires": {
"follow-redirects": "^1.14.0"
}
}
"cspell": "^4.1.0",
"eslint": "^7.8.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-functional": "^3.0.2",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-prettier": "^4.0.0",
"jsep": "^1.3.6",
"npm-run-all": "^4.1.5",
"prettier": "^2.1.1",
"rimraf": "^5.0.1",
"tsc-alias": "^1.8.7",
"typescript": "^4.7.4"
}
},
"node-abi": {

2
packages/nc-gui/package.json

@ -60,7 +60,7 @@
"locale-codes": "^1.3.1",
"monaco-editor": "^0.33.0",
"monaco-sql-languages": "^0.11.0",
"nocodb-sdk": "0.111.2",
"nocodb-sdk": "file:../nocodb-sdk",
"papaparse": "^5.3.2",
"parse-github-url": "^1.0.2",
"pinia": "^2.1.4",

4
packages/nocodb-sdk/package-lock.json generated

@ -1,12 +1,12 @@
{
"name": "nocodb-sdk",
"version": "0.111.0",
"version": "0.111.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "nocodb-sdk",
"version": "0.111.0",
"version": "0.111.2",
"license": "AGPL-3.0-or-later",
"dependencies": {
"axios": "^0.21.1",

32
packages/nocodb/package-lock.json generated

@ -98,7 +98,7 @@
"ncp": "^2.0.0",
"nestjs-kafka": "^1.0.6",
"nestjs-throttler-storage-redis": "^0.3.0",
"nocodb-sdk": "0.111.2",
"nocodb-sdk": "file:../nocodb-sdk",
"nodemailer": "^6.4.10",
"object-hash": "^3.0.0",
"object-sizeof": "^2.6.1",
@ -211,7 +211,6 @@
},
"../nocodb-sdk": {
"version": "0.111.2",
"extraneous": true,
"license": "AGPL-3.0-or-later",
"dependencies": {
"axios": "^0.21.1",
@ -14383,13 +14382,8 @@
}
},
"node_modules/nocodb-sdk": {
"version": "0.111.2",
"resolved": "https://registry.npmjs.org/nocodb-sdk/-/nocodb-sdk-0.111.2.tgz",
"integrity": "sha512-daL3VKOzklL9AYhWfIOJeUDYUBdlPaW0xUmcCYzEJX/q/Ca3Jhk3/5o5XjJIneT11s/UaQ6dPv8boD36tEabhA==",
"dependencies": {
"axios": "^0.21.1",
"jsep": "^1.3.6"
}
"resolved": "../nocodb-sdk",
"link": true
},
"node_modules/node-abort-controller": {
"version": "3.1.1",
@ -30908,12 +30902,24 @@
"requires": {}
},
"nocodb-sdk": {
"version": "0.111.2",
"resolved": "https://registry.npmjs.org/nocodb-sdk/-/nocodb-sdk-0.111.2.tgz",
"integrity": "sha512-daL3VKOzklL9AYhWfIOJeUDYUBdlPaW0xUmcCYzEJX/q/Ca3Jhk3/5o5XjJIneT11s/UaQ6dPv8boD36tEabhA==",
"version": "file:../nocodb-sdk",
"requires": {
"@typescript-eslint/eslint-plugin": "^4.0.1",
"@typescript-eslint/parser": "^4.0.1",
"axios": "^0.21.1",
"jsep": "^1.3.6"
"cspell": "^4.1.0",
"eslint": "^7.8.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-functional": "^3.0.2",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-prettier": "^4.0.0",
"jsep": "^1.3.6",
"npm-run-all": "^4.1.5",
"prettier": "^2.1.1",
"rimraf": "^5.0.1",
"tsc-alias": "^1.8.7",
"typescript": "^4.7.4"
}
},
"node-abort-controller": {

4
packages/nocodb/package.json

@ -131,7 +131,7 @@
"ncp": "^2.0.0",
"nestjs-kafka": "^1.0.6",
"nestjs-throttler-storage-redis": "^0.3.0",
"nocodb-sdk": "0.111.2",
"nocodb-sdk": "file:../nocodb-sdk",
"nodemailer": "^6.4.10",
"object-hash": "^3.0.0",
"object-sizeof": "^2.6.1",
@ -223,4 +223,4 @@
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
}

267
packages/nocodb/src/helpers/initAdminFromEnv.ts

@ -62,7 +62,7 @@ export default async function initAdminFromEnv(_ncMeta = Noco.ncMeta) {
salt,
);
const email_verification_token = uuidv4();
const roles = 'user,super';
const roles = 'org-level-creator,super';
// if super admin not present
if (await User.isFirst(ncMeta)) {
@ -78,6 +78,7 @@ export default async function initAdminFromEnv(_ncMeta = Noco.ncMeta) {
salt,
password,
email_verification_token,
token_version: randomTokenString(),
roles,
},
ncMeta,
@ -89,122 +90,153 @@ export default async function initAdminFromEnv(_ncMeta = Noco.ncMeta) {
salt,
);
const email_verification_token = uuidv4();
const superUser = await ncMeta.metaGet2(null, null, MetaTable.USERS, {
roles: 'user,super',
});
// TODO improve this
const superUsers = await ncMeta.metaList2(null, null, MetaTable.USERS);
if (!superUser?.id) {
const existingUserWithNewEmail = await User.getByEmail(email, ncMeta);
if (existingUserWithNewEmail?.id) {
// clear cache
await NocoCache.delAll(
CacheScope.USER,
`${existingUserWithNewEmail.email}___*`,
);
await NocoCache.del(
`${CacheScope.USER}:${existingUserWithNewEmail.id}`,
);
await NocoCache.del(
`${CacheScope.USER}:${existingUserWithNewEmail.email}`,
);
let superUserPresent = false;
// Update email and password of super admin account
await User.update(
existingUserWithNewEmail.id,
{
salt,
email,
password,
email_verification_token,
token_version: randomTokenString(),
refresh_token: null,
roles,
},
ncMeta,
);
} else {
T.emit('evt', {
evt_type: 'project:invite',
count: 1,
});
for (const user of superUsers) {
if (!user.roles?.includes('super')) continue;
await User.insert(
{
email,
salt,
password,
email_verification_token,
roles,
},
ncMeta,
);
}
} else if (email !== superUser.email) {
// update admin email and password and migrate projects
// if user already present and associated with some project
superUserPresent = true;
// check user account already present with the new admin email
const existingUserWithNewEmail = await User.getByEmail(email, ncMeta);
if (email !== user.email) {
// update admin email and password and migrate projects
// if user already present and associated with some project
if (existingUserWithNewEmail?.id) {
// get all project access belongs to the existing account
// and migrate to the admin account
const existingUserProjects = await ncMeta.metaList2(
null,
null,
MetaTable.PROJECT_USERS,
{
condition: { fk_user_id: existingUserWithNewEmail.id },
},
// check user account already present with the new admin email
const existingUserWithNewEmail = await User.getByEmail(
email,
ncMeta,
);
for (const existingUserProject of existingUserProjects) {
const userProject = await ProjectUser.get(
existingUserProject.project_id,
superUser.id,
ncMeta,
if (existingUserWithNewEmail?.id) {
// get all project access belongs to the existing account
// and migrate to the admin account
const existingUserProjects = await ncMeta.metaList2(
null,
null,
MetaTable.PROJECT_USERS,
{
condition: { fk_user_id: existingUserWithNewEmail.id },
},
);
// if admin user already have access to the project
// then update role based on the highest access level
if (userProject) {
if (
rolesLevel[userProject.roles] >
rolesLevel[existingUserProject.roles]
) {
await ProjectUser.update(
userProject.project_id,
superUser.id,
existingUserProject.roles,
for (const existingUserProject of existingUserProjects) {
const userProject = await ProjectUser.get(
existingUserProject.project_id,
user.id,
ncMeta,
);
// if admin user already have access to the project
// then update role based on the highest access level
if (userProject) {
if (
rolesLevel[userProject.roles] >
rolesLevel[existingUserProject.roles]
) {
await ProjectUser.update(
userProject.project_id,
user.id,
existingUserProject.roles,
ncMeta,
);
}
} else {
// if super doesn't have access then add the access
await ProjectUser.insert(
{
...existingUserProject,
fk_user_id: user.id,
},
ncMeta,
);
}
} else {
// if super doesn't have access then add the access
await ProjectUser.insert(
{
...existingUserProject,
fk_user_id: superUser.id,
},
// delete the old project access entry from DB
await ProjectUser.delete(
existingUserProject.project_id,
existingUserProject.fk_user_id,
ncMeta,
);
}
// delete the old project access entry from DB
await ProjectUser.delete(
existingUserProject.project_id,
existingUserProject.fk_user_id,
// delete existing user
await ncMeta.metaDelete(
null,
null,
MetaTable.USERS,
existingUserWithNewEmail.id,
);
// clear cache
await NocoCache.delAll(
CacheScope.USER,
`${existingUserWithNewEmail.email}___*`,
);
await NocoCache.del(
`${CacheScope.USER}:${existingUserWithNewEmail.id}`,
);
await NocoCache.del(
`${CacheScope.USER}:${existingUserWithNewEmail.email}`,
);
// Update email and password of super admin account
await User.update(
user.id,
{
salt,
email,
password,
email_verification_token,
token_version: randomTokenString(),
refresh_token: null,
},
ncMeta,
);
} else {
// if no user present with the new admin email update the email and password
await User.update(
user.id,
{
salt,
email,
password,
email_verification_token,
token_version: randomTokenString(),
refresh_token: null,
},
ncMeta,
);
}
// delete existing user
await ncMeta.metaDelete(
null,
null,
MetaTable.USERS,
existingUserWithNewEmail.id,
} else {
const newPasswordHash = await promisify(bcrypt.hash)(
process.env.NC_ADMIN_PASSWORD,
user.salt,
);
if (newPasswordHash !== user.password) {
// if email's are same and passwords are different
// then update the password and token version
await User.update(
user.id,
{
salt,
password,
email_verification_token,
token_version: randomTokenString(),
refresh_token: null,
},
ncMeta,
);
}
}
}
if (!superUserPresent) {
// check user account already present with the new admin email
const existingUserWithNewEmail = await User.getByEmail(email, ncMeta);
if (existingUserWithNewEmail?.id) {
// clear cache
await NocoCache.delAll(
CacheScope.USER,
@ -217,9 +249,9 @@ export default async function initAdminFromEnv(_ncMeta = Noco.ncMeta) {
`${CacheScope.USER}:${existingUserWithNewEmail.email}`,
);
// Update email and password of super admin account
// Update password and roles of existing user
await User.update(
superUser.id,
existingUserWithNewEmail.id,
{
salt,
email,
@ -227,52 +259,37 @@ export default async function initAdminFromEnv(_ncMeta = Noco.ncMeta) {
email_verification_token,
token_version: randomTokenString(),
refresh_token: null,
roles,
},
ncMeta,
);
} else {
// if email's are not different update the password and hash
await User.update(
superUser.id,
{
salt,
email,
password,
email_verification_token,
token_version: randomTokenString(),
refresh_token: null,
},
ncMeta,
);
}
} else {
const newPasswordHash = await promisify(bcrypt.hash)(
process.env.NC_ADMIN_PASSWORD,
superUser.salt,
);
// no super user present and no user present with the new admin email
T.emit('evt', {
evt_type: 'project:invite',
count: 1,
});
if (newPasswordHash !== superUser.password) {
// if email's are same and passwords are different
// then update the password and token version
await User.update(
superUser.id,
await User.insert(
{
email,
salt,
password,
email_verification_token,
token_version: randomTokenString(),
refresh_token: null,
roles,
},
ncMeta,
);
}
}
}
await ncMeta.commit();
} catch (e) {
console.log('Error occurred while updating/creating admin user');
console.log(e);
await ncMeta.rollback(e);
throw e;
}
}
}

11
packages/nocodb/src/middlewares/extract-ids/extract-ids.middleware.ts

@ -180,8 +180,8 @@ export class ExtractIdsMiddleware implements NestMiddleware, CanActivate {
}
function getUserRoleForScope(user: any, scope: string) {
if (scope === 'project' || scope === 'workspace') {
return user?.project_roles || user?.workspace_roles;
if (scope === 'project') {
return user?.project_roles;
} else if (scope === 'org') {
return user?.roles;
}
@ -220,6 +220,13 @@ export class AclMiddleware implements NestInterceptor {
NcError.forbidden('Unauthorized access');
}
// assign owner role to super admin for all projects
if (userScopeRole === OrgUserRoles.SUPER_ADMIN) {
req.user.project_roles = {
[ProjectRoles.OWNER]: true,
};
}
const roles: Record<string, boolean> = extractRolesObj(userScopeRole);
if (req?.user?.is_api_token && blockApiTokenAccess) {

2
packages/nocodb/src/models/User.ts

@ -87,7 +87,7 @@ export default class User implements UserType {
// check if the target email addr is in use or not
const targetUser = await this.getByEmail(updateObj.email, ncMeta);
if (targetUser.id !== id) {
if (targetUser && targetUser.id !== id) {
NcError.badRequest('email is in use');
}
} else {

1
packages/nocodb/src/services/api-docs/swagger/getSwaggerColumnMetas.ts

@ -36,6 +36,7 @@ export default async (
field.type = 'object';
break;
case UITypes.Rollup:
case UITypes.Links:
field.type = 'number';
break;
case UITypes.Attachment:

6
packages/nocodb/src/services/api-docs/swagger/templates/paths.ts

@ -1,4 +1,4 @@
import { ModelTypes, UITypes } from 'nocodb-sdk';
import { isLinksOrLTAR, ModelTypes, UITypes } from 'nocodb-sdk';
import {
columnNameParam,
columnNameQueryParam,
@ -670,7 +670,5 @@ function getPaginatedResponseType(type: string) {
};
}
function isRelationExist(columns: SwaggerColumn[]) {
return columns.some(
(c) => c.column.uidt === UITypes.LinkToAnotherRecord && !c.column.system,
);
return columns.some((c) => isLinksOrLTAR(c.column) && !c.column.system);
}

17
packages/nocodb/src/strategies/authtoken.strategy/authtoken.strategy.ts

@ -1,5 +1,6 @@
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { extractRolesObj, ProjectRoles } from 'nocodb-sdk';
import { Strategy } from 'passport-custom';
import { ApiToken, ProjectUser, User } from '~/models';
import { sanitiseUserObj } from '~/utils';
@ -16,9 +17,12 @@ export class AuthTokenStrategy extends PassportStrategy(Strategy, 'authtoken') {
return callback({ msg: 'Invalid token' });
}
user = {};
user = {
is_api_token: true,
};
if (!apiToken.fk_user_id) {
user.roles = 'editor';
user.project_roles = extractRolesObj(ProjectRoles.EDITOR);
return callback(null, user);
}
@ -29,17 +33,18 @@ export class AuthTokenStrategy extends PassportStrategy(Strategy, 'authtoken') {
Object.assign(user, {
id: dbUser.id,
roles: dbUser.roles,
roles: extractRolesObj(dbUser.roles),
});
dbUser.is_api_token = true;
if (req['ncProjectId']) {
const projectUser = await ProjectUser.get(
req['ncProjectId'],
dbUser.id,
);
user.roles = projectUser?.roles || dbUser.roles;
user.roles = user.roles === 'owner' ? 'owner,creator' : user.roles;
user.project_roles = extractRolesObj(projectUser?.roles);
if (user.project_roles.owner) {
user.project_roles.creator = true;
}
return callback(null, sanitiseUserObj(user));
}
}

Loading…
Cancel
Save