diff --git a/packages/nc-gui/pages/user/authentication/passwordValidateMixin.js b/packages/nc-gui/pages/user/authentication/passwordValidateMixin.js
index 71ea5c406c..6422c4920c 100644
--- a/packages/nc-gui/pages/user/authentication/passwordValidateMixin.js
+++ b/packages/nc-gui/pages/user/authentication/passwordValidateMixin.js
@@ -1,3 +1,5 @@
+import { validatePassword } from 'nocodb-sdk'
+
export default {
data: () => ({
passwordProgress: 0,
@@ -20,54 +22,62 @@ export default {
return this.formUtil.progressColorValue
},
PasswordValidate(p) {
- if (!p) {
- this.passwordProgress = 0
- this.passwordValidateMsg = 'At least 8 letters with one Uppercase, one number and one special letter'
- return false
- }
-
- let msg = ''
- let validation = true
- let progress = 0
-
- if (!(p.length >= 8)) {
- msg += 'Atleast 8 letters. '
- validation = validation && false
- } else {
- progress = Math.min(100, progress + 25)
- }
-
- if (!(p.match(/.*[A-Z].*/))) {
- msg += 'One Uppercase Letter. '
- validation = validation && false
- } else {
- progress = Math.min(100, progress + 25)
- }
-
- if (!(p.match(/.*[0-9].*/))) {
- msg += 'One Number. '
- validation = validation && false
- } else {
- progress = Math.min(100, progress + 25)
- }
-
- if (!(p.match(/[$&+,:;=?@#|'<>.^*()%!_-]/))) {
- msg += 'One special letter. '
- validation = validation && false
- } else {
- progress = Math.min(100, progress + 25)
- }
+ const { error, progress, valid } = validatePassword(p)
+ if (valid) { return true }
this.formUtil.passwordProgress = progress
- // console.log('progress', progress);
- // console.log('color', this.progressColor(this.formUtil.passwordProgress));
this.progressColorValue = this.progressColor(this.formUtil.passwordProgress)
- this.formUtil.passwordValidateMsg = msg
-
- // console.log('msg', msg, validation);
-
- return validation
+ this.formUtil.passwordValidateMsg = error
+ return error
+ // if (!p) {
+ // this.passwordProgress = 0
+ // this.passwordValidateMsg = 'At least 8 letters with one Uppercase, one number and one special letter'
+ // return false
+ // }
+ //
+ // let msg = ''
+ // let validation = true
+ // let progress = 0
+ //
+ // if (!(p.length >= 8)) {
+ // msg += 'Atleast 8 letters. '
+ // validation = validation && false
+ // } else {
+ // progress = Math.min(100, progress + 25)
+ // }
+ //
+ // if (!(p.match(/.*[A-Z].*/))) {
+ // msg += 'One Uppercase Letter. '
+ // validation = validation && false
+ // } else {
+ // progress = Math.min(100, progress + 25)
+ // }
+ //
+ // if (!(p.match(/.*[0-9].*/))) {
+ // msg += 'One Number. '
+ // validation = validation && false
+ // } else {
+ // progress = Math.min(100, progress + 25)
+ // }
+ //
+ // if (!(p.match(/[$&+,:;=?@#|'<>.^*()%!_-]/))) {
+ // msg += 'One special letter. '
+ // validation = validation && false
+ // } else {
+ // progress = Math.min(100, progress + 25)
+ // }
+ //
+ // this.formUtil.passwordProgress = progress
+ // // console.log('progress', progress);
+ // // console.log('color', this.progressColor(this.formUtil.passwordProgress));
+ // this.progressColorValue = this.progressColor(this.formUtil.passwordProgress)
+ //
+ // this.formUtil.passwordValidateMsg = msg
+ //
+ // // console.log('msg', msg, validation);
+ //
+ // return validation
}
}
diff --git a/packages/nc-gui/pages/user/authentication/signin.vue b/packages/nc-gui/pages/user/authentication/signin.vue
index ee0583675a..fb4688d27a 100644
--- a/packages/nc-gui/pages/user/authentication/signin.vue
+++ b/packages/nc-gui/pages/user/authentication/signin.vue
@@ -244,10 +244,7 @@ export default {
],
password: [
// Password is required
- v => !!v || this.$t('msg.error.signUpRules.passwdRequired'),
- // You password must be atleast 8 characters
- v =>
- (v && v.length >= 8) || this.$t('msg.error.signUpRules.passwdLength')
+ v => !!v || this.$t('msg.error.signUpRules.passwdRequired')
]
},
formUtil: {
diff --git a/packages/noco-docs/content/en/getting-started/installation.md b/packages/noco-docs/content/en/getting-started/installation.md
index 9fd3b52f7f..ccdac4ac4f 100644
--- a/packages/noco-docs/content/en/getting-started/installation.md
+++ b/packages/noco-docs/content/en/getting-started/installation.md
@@ -206,6 +206,19 @@ It is mandatory to configure `NC_DB` environment variables for production usecas
| AWS_SECRET_ACCESS_KEY | No | For Litestream - S3 secret access key | If Litestream is configured and NC_DB is not present. SQLite gets backed up to S3 | |
| AWS_BUCKET | No | For Litestream - S3 bucket | If Litestream is configured and NC_DB is not present. SQLite gets backed up to S3 | |
| AWS_BUCKET_PATH | No | For Litestream - S3 bucket path (like folder within S3 bucket) | If Litestream is configured and NC_DB is not present. SQLite gets backed up to S3 | |
+| NC_SMTP_FROM | No | For SMTP plugin - Email sender address | | |
+| NC_SMTP_HOST | No | For SMTP plugin - SMTP host value | | |
+| NC_SMTP_PORT | No | For SMTP plugin - SMTP port value | | |
+| NC_SMTP_USERNAME | No | For SMTP plugin (Optional) - SMTP username value for authentication | | |
+| NC_SMTP_PASSWORD | No | For SMTP plugin (Optional) - SMTP password value for authentication | | |
+| NC_SMTP_SECURE | No | For SMTP plugin (Optional) - To enable secure set value as `true` any other value treated as false | | |
+| NC_SMTP_IGNORE_TLS | No | For SMTP plugin (Optional) - To ignore tls set value as `true` any other value treated as false. For more info visit https://nodemailer.com/smtp/ | | |
+| NC_S3_BUCKET_NAME | No | For S3 storage plugin - AWS S3 bucket name | | |
+| NC_S3_REGION | No | For S3 storage plugin - AWS S3 region | | |
+| NC_S3_ACCESS_KEY | No | For S3 storage plugin - AWS access key credential for accessing resource | | |
+| NC_S3_ACCESS_SECRET | No | For S3 storage plugin - AWS access secret credential for accessing resource | | |
+| NC_ADMIN_EMAIL | No | For updating/creating super admin with provided email and password | | |
+| NC_ADMIN_PASSWORD | No | For updating/creating super admin with provided email and password. Your password should have at least 8 letters with one uppercase, one number and one special letter(Allowed special chars $&+,:;=?@#|'.^*()%!_-"
) | | |
### AWS ECS (Fargate)
diff --git a/packages/noco-docs/content/en/setup-and-usages/dashboard.md b/packages/noco-docs/content/en/setup-and-usages/dashboard.md
index 34ed955cac..6a77d03419 100644
--- a/packages/noco-docs/content/en/setup-and-usages/dashboard.md
+++ b/packages/noco-docs/content/en/setup-and-usages/dashboard.md
@@ -16,7 +16,7 @@ Click `Let's Begin` button to sign up.
Enter your work email and your password.
-
+
Your password has at least 8 letters with one uppercase, one number and one special letter
@@ -98,4 +98,4 @@ Tip 3: You can click Edit Connection JSON and specify the schema you want to use
Click `Test Database Connection` to see if the connection can be established or not. NocoDB creates a new **empty database** with specified parameters if the database doesn't exist.
-![image](https://user-images.githubusercontent.com/35857179/163136039-ad521d74-6996-4173-84ba-cfc55392c3b7.png)
\ No newline at end of file
+![image](https://user-images.githubusercontent.com/35857179/163136039-ad521d74-6996-4173-84ba-cfc55392c3b7.png)
diff --git a/packages/nocodb-sdk/src/index.ts b/packages/nocodb-sdk/src/index.ts
index 2d808fcc03..805152d812 100644
--- a/packages/nocodb-sdk/src/index.ts
+++ b/packages/nocodb-sdk/src/index.ts
@@ -5,5 +5,6 @@ export * from './lib/sqlUi';
export * from './lib/globals';
export * from './lib/helperFunctions';
export * from './lib/formulaHelpers';
-export {default as UITypes, isVirtualCol} from './lib/UITypes';
-export {default as CustomAPI} from './lib/CustomAPI';
+export * from './lib/passwordHelpers';
+export { default as UITypes, isVirtualCol } from './lib/UITypes';
+export { default as CustomAPI } from './lib/CustomAPI';
diff --git a/packages/nocodb-sdk/src/lib/passwordHelpers.ts b/packages/nocodb-sdk/src/lib/passwordHelpers.ts
new file mode 100644
index 0000000000..2136a54809
--- /dev/null
+++ b/packages/nocodb-sdk/src/lib/passwordHelpers.ts
@@ -0,0 +1,41 @@
+export function validatePassword(p) {
+ let error = '';
+ let progress = 0;
+ let hint = null;
+ let valid = true;
+ if (!p) {
+ error =
+ 'At least 8 letters with one Uppercase, one number and one special letter';
+ valid = false;
+ } else {
+ if (!(p.length >= 8)) {
+ error += 'Atleast 8 letters. ';
+ valid = false;
+ } else {
+ progress = Math.min(100, progress + 25);
+ }
+
+ if (!p.match(/.*[A-Z].*/)) {
+ error += 'One Uppercase Letter. ';
+ valid = false;
+ } else {
+ progress = Math.min(100, progress + 25);
+ }
+
+ if (!p.match(/.*[0-9].*/)) {
+ error += 'One Number. ';
+ valid = false;
+ } else {
+ progress = Math.min(100, progress + 25);
+ }
+
+ if (!p.match(/[$&+,:;=?@#|'<>.^*()%!_-]/)) {
+ error += 'One special letter. ';
+ hint = "Allowed special character list : $&+,:;=?@#|'<>.^*()%!_-";
+ valid = false;
+ } else {
+ progress = Math.min(100, progress + 25);
+ }
+ }
+ return { error, valid, progress, hint };
+}
diff --git a/packages/nocodb/src/lib/Noco.ts b/packages/nocodb/src/lib/Noco.ts
index 2807cfddb4..c0439bfb75 100644
--- a/packages/nocodb/src/lib/Noco.ts
+++ b/packages/nocodb/src/lib/Noco.ts
@@ -42,6 +42,7 @@ import { Tele } from 'nc-help';
import * as http from 'http';
import weAreHiring from './utils/weAreHiring';
import getInstance from './utils/getInstance';
+import initAdminFromEnv from './meta/api/userApi/initAdminFromEnv';
const log = debug('nc:app');
require('dotenv').config();
@@ -186,8 +187,8 @@ export default class Noco {
}
await Noco._ncMeta.metaInit();
-
await this.readOrGenJwtSecret();
+ await initAdminFromEnv();
await NcUpgrader.upgrade({ ncMeta: Noco._ncMeta });
diff --git a/packages/nocodb/src/lib/meta/api/userApi/initAdminFromEnv.ts b/packages/nocodb/src/lib/meta/api/userApi/initAdminFromEnv.ts
new file mode 100644
index 0000000000..81885ed12d
--- /dev/null
+++ b/packages/nocodb/src/lib/meta/api/userApi/initAdminFromEnv.ts
@@ -0,0 +1,209 @@
+import User from '../../../models/User';
+import { v4 as uuidv4 } from 'uuid';
+import { promisify } from 'util';
+import { Tele } from 'nc-help';
+
+import bcrypt from 'bcryptjs';
+import Noco from '../../../Noco';
+import { MetaTable } from '../../../utils/globals';
+import ProjectUser from '../../../models/ProjectUser';
+import { validatePassword } from 'nocodb-sdk';
+import boxen from 'boxen';
+
+const { isEmail } = require('validator');
+const rolesLevel = { owner: 0, creator: 1, editor: 2, commenter: 3, viewer: 4 };
+
+export default async function initAdminFromEnv(_ncMeta = Noco.ncMeta) {
+ if (process.env.NC_ADMIN_EMAIL && process.env.NC_ADMIN_PASSWORD) {
+ if (!isEmail(process.env.NC_ADMIN_EMAIL?.trim())) {
+ console.log(
+ '\n',
+ boxen(
+ `Provided admin email '${process.env.NC_ADMIN_EMAIL}' is not valid`,
+ {
+ title: 'Invalid admin email',
+ padding: 1,
+ borderStyle: 'double',
+ titleAlignment: 'center',
+ borderColor: 'red'
+ }
+ ),
+ '\n'
+ );
+ process.exit(1);
+ }
+
+ const { valid, error, hint } = validatePassword(
+ process.env.NC_ADMIN_PASSWORD
+ );
+ if (!valid) {
+ console.log(
+ '\n',
+ boxen(`${error}${hint ? `\n\n${hint}` : ''}`, {
+ title: 'Invalid admin password',
+ padding: 1,
+ borderStyle: 'double',
+ titleAlignment: 'center',
+ borderColor: 'red'
+ }),
+ '\n'
+ );
+ process.exit(1);
+ }
+
+ let ncMeta;
+ try {
+ ncMeta = await _ncMeta.startTransaction();
+ const email = process.env.NC_ADMIN_EMAIL.toLowerCase().trim();
+
+ const salt = await promisify(bcrypt.genSalt)(10);
+ const password = await promisify(bcrypt.hash)(
+ process.env.NC_ADMIN_PASSWORD,
+ salt
+ );
+ const email_verification_token = uuidv4();
+
+ // if super admin not present
+ if (await User.isFirst(ncMeta)) {
+ const roles = 'user,super';
+
+ // roles = 'owner,creator,editor'
+ Tele.emit('evt', {
+ evt_type: 'project:invite',
+ count: 1
+ });
+
+ await User.insert(
+ {
+ firstname: '',
+ lastname: '',
+ email,
+ salt,
+ password,
+ email_verification_token,
+ roles
+ },
+ ncMeta
+ );
+ } else {
+ const salt = await promisify(bcrypt.genSalt)(10);
+ const password = await promisify(bcrypt.hash)(
+ process.env.NC_ADMIN_PASSWORD,
+ salt
+ );
+ const email_verification_token = uuidv4();
+ const superUser = await ncMeta.metaGet2(null, null, MetaTable.USERS, {
+ roles: 'user,super'
+ });
+
+ if (email !== superUser.email) {
+ // update admin email and password and migrate projects
+ // if user already present and associated with some project
+
+ // check user account already present with the new admin email
+ const existingUserWithNewEmail = await User.getByEmail(email, ncMeta);
+
+ if (existingUserWithNewEmail) {
+ // 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 }
+ }
+ );
+
+ for (const existingUserProject of existingUserProjects) {
+ const userProject = await ProjectUser.get(
+ existingUserProject.project_id,
+ superUser.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,
+ superUser.id,
+ existingUserProject.roles,
+ ncMeta
+ );
+ }
+ } else {
+ // if super doesn't have access then add the access
+ await ProjectUser.insert(
+ {
+ ...existingUserProject,
+ fk_user_id: superUser.id
+ },
+ ncMeta
+ );
+ }
+ // delete the old project access entry from DB
+ await ProjectUser.delete(
+ existingUserProject.project_id,
+ existingUserProject.fk_user_id,
+ ncMeta
+ );
+ }
+
+ // delete existing user
+ ncMeta.metaDelete(
+ null,
+ null,
+ MetaTable.USERS,
+ existingUserWithNewEmail.id
+ );
+
+ // Update email and password of super admin account
+ await User.update(
+ superUser.id,
+ {
+ salt,
+ email,
+ password,
+ email_verification_token
+ },
+ ncMeta
+ );
+ } else {
+ // if email's are not different update the password and hash
+ await User.update(
+ superUser.id,
+ {
+ salt,
+ email,
+ password,
+ email_verification_token
+ },
+ ncMeta
+ );
+ }
+ } else {
+ // if email's are not different update the password and hash
+ await User.update(
+ superUser.id,
+ {
+ salt,
+ password,
+ email_verification_token
+ },
+ ncMeta
+ );
+ }
+ }
+ await ncMeta.commit();
+ } catch (e) {
+ console.log('Error occurred while updating/creating admin user');
+ console.log(e);
+ await ncMeta.rollback(e);
+ }
+ }
+}
diff --git a/packages/nocodb/src/lib/meta/api/userApi/userApis.ts b/packages/nocodb/src/lib/meta/api/userApi/userApis.ts
index 7a6fa31a3d..18243d9455 100644
--- a/packages/nocodb/src/lib/meta/api/userApi/userApis.ts
+++ b/packages/nocodb/src/lib/meta/api/userApi/userApis.ts
@@ -1,5 +1,5 @@
import { Request, Response } from 'express';
-import { TableType } from 'nocodb-sdk';
+import { TableType, validatePassword } from 'nocodb-sdk';
import catchError, { NcError } from '../../helpers/catchError';
const { isEmail } = require('validator');
import * as ejs from 'ejs';
@@ -31,6 +31,12 @@ export async function signup(req: Request, res: Response) {
} = req.body;
let { password } = req.body;
+ // validate password and throw error if password is satisfying the conditions
+ const { valid, error } = validatePassword(password);
+ if (!valid) {
+ NcError.badRequest(`Password : ${error}`);
+ }
+
if (!isEmail(_email)) {
NcError.badRequest(`Invalid email`);
}
@@ -262,6 +268,13 @@ async function passwordChange(req: Request, res): Promise {
if (!currentPassword || !newPassword) {
return NcError.badRequest('Missing new/old password');
}
+
+ // validate password and throw error if password is satisfying the conditions
+ const { valid, error } = validatePassword(newPassword);
+ if (!valid) {
+ NcError.badRequest(`Password : ${error}`);
+ }
+
const user = await User.getByEmail((req as any).user.email);
const hashedPassword = await promisify(bcrypt.hash)(
currentPassword,
@@ -381,6 +394,12 @@ async function passwordReset(req, res): Promise {
NcError.badRequest('Email registered via social account');
}
+ // validate password and throw error if password is satisfying the conditions
+ const { valid, error } = validatePassword(req.body.password);
+ if (!valid) {
+ NcError.badRequest(`Password : ${error}`);
+ }
+
const salt = await promisify(bcrypt.genSalt)(10);
const password = await promisify(bcrypt.hash)(req.body.password, salt);
diff --git a/packages/nocodb/src/lib/meta/helpers/NcPluginMgrv2.ts b/packages/nocodb/src/lib/meta/helpers/NcPluginMgrv2.ts
index 0e5057887d..4f04838001 100644
--- a/packages/nocodb/src/lib/meta/helpers/NcPluginMgrv2.ts
+++ b/packages/nocodb/src/lib/meta/helpers/NcPluginMgrv2.ts
@@ -31,6 +31,7 @@ import Noco from '../../Noco';
import Local from '../../v1-legacy/plugins/adapters/storage/Local';
import { MetaTable } from '../../utils/globals';
import { PluginCategory } from 'nocodb-sdk';
+import Plugin from '../../models/Plugin';
const defaultPlugins = [
SlackPluginConfig,
@@ -97,25 +98,54 @@ class NcPluginMgrv2 {
pluginConfig.id
);
}
+ }
+ await this.initPluginsFromEnv();
+ }
- /* init only the active plugins */
- // if (pluginConfig?.active) {
- // const tempPlugin = new plugin.builder(this.app, plugin);
- //
- // this.activePlugins.push(tempPlugin);
- //
- // if (pluginConfig?.input) {
- // pluginConfig.input = JSON.parse(pluginConfig.input);
- // }
- //
- // try {
- // await tempPlugin.init(pluginConfig?.input);
- // } catch (e) {
- // console.log(
- // `Plugin(${plugin?.title}) initialization failed : ${e.message}`
- // );
- // }
- // }
+ private static async initPluginsFromEnv() {
+ /*
+ * NC_S3_BUCKET_NAME
+ * NC_S3_REGION
+ * NC_S3_ACCESS_KEY
+ * NC_S3_ACCESS_SECRET
+ * */
+
+ if (
+ process.env.NC_S3_BUCKET_NAME &&
+ process.env.NC_S3_REGION &&
+ process.env.NC_S3_ACCESS_KEY &&
+ process.env.NC_S3_ACCESS_SECRET
+ ) {
+ const s3Plugin = await Plugin.getPluginByTitle(S3PluginConfig.title);
+ await Plugin.update(s3Plugin.id, {
+ active: true,
+ input: JSON.stringify({
+ bucket: process.env.NC_S3_BUCKET_NAME,
+ region: process.env.NC_S3_REGION,
+ access_key: process.env.NC_S3_ACCESS_KEY,
+ access_secret: process.env.NC_S3_ACCESS_SECRET
+ })
+ });
+ }
+
+ if (
+ process.env.NC_SMTP_FROM &&
+ process.env.NC_SMTP_HOST &&
+ process.env.NC_SMTP_PORT
+ ) {
+ const smtpPlugin = await Plugin.getPluginByTitle(SMTPPluginConfig.title);
+ await Plugin.update(smtpPlugin.id, {
+ active: true,
+ input: JSON.stringify({
+ from: process.env.NC_SMTP_FROM,
+ host: process.env.NC_SMTP_HOST,
+ port: process.env.NC_SMTP_PORT,
+ username: process.env.NC_SMTP_USERNAME,
+ password: process.env.NC_SMTP_PASSWORD,
+ secure: process.env.NC_SMTP_SECURE,
+ ignoreTLS: process.env.NC_SMTP_IGNORE_TLS
+ })
+ });
}
}
diff --git a/packages/nocodb/src/lib/models/Plugin.ts b/packages/nocodb/src/lib/models/Plugin.ts
index ee08013651..bd952e4af4 100644
--- a/packages/nocodb/src/lib/models/Plugin.ts
+++ b/packages/nocodb/src/lib/models/Plugin.ts
@@ -91,7 +91,7 @@ export default class Plugin implements PluginType {
/**
* get plugin by title
*/
- public static async getPluginByTitle(title: string) {
+ public static async getPluginByTitle(title: string, ncMeta = Noco.ncMeta) {
let plugin =
title &&
(await NocoCache.get(
@@ -99,7 +99,7 @@ export default class Plugin implements PluginType {
CacheGetType.TYPE_OBJECT
));
if (!plugin) {
- plugin = await Noco.ncMeta.metaGet2(null, null, MetaTable.PLUGIN, {
+ plugin = await ncMeta.metaGet2(null, null, MetaTable.PLUGIN, {
title
});
await NocoCache.set(`${CacheScope.PLUGIN}:${title}`, plugin);