mirror of https://github.com/nocodb/nocodb
Wing-Kam Wong
1 year ago
34 changed files with 419 additions and 83 deletions
@ -0,0 +1,281 @@
|
||||
import { promisify } from 'util'; |
||||
import { v4 as uuidv4 } from 'uuid'; |
||||
import bcrypt from 'bcryptjs'; |
||||
import { validatePassword } from 'nocodb-sdk'; |
||||
import boxen from 'boxen'; |
||||
import { T } from 'nc-help'; |
||||
import { isEmail } from 'validator'; |
||||
import NocoCache from '../cache/NocoCache'; |
||||
import { ProjectUser, User } from '../models'; |
||||
import Noco from '../Noco'; |
||||
import { CacheScope, MetaTable } from '../utils/globals'; |
||||
|
||||
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(); |
||||
const roles = 'user,super'; |
||||
|
||||
// if super admin not present
|
||||
if (await User.isFirst(ncMeta)) { |
||||
// roles = 'owner,creator,editor'
|
||||
T.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 (!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}`, |
||||
); |
||||
|
||||
// Update email and password of super admin account
|
||||
await User.update( |
||||
existingUserWithNewEmail.id, |
||||
{ |
||||
salt, |
||||
email, |
||||
password, |
||||
email_verification_token, |
||||
token_version: null, |
||||
refresh_token: null, |
||||
roles, |
||||
}, |
||||
ncMeta, |
||||
); |
||||
} else { |
||||
T.emit('evt', { |
||||
evt_type: 'project:invite', |
||||
count: 1, |
||||
}); |
||||
|
||||
await User.insert( |
||||
{ |
||||
firstname: '', |
||||
lastname: '', |
||||
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
|
||||
|
||||
// check user account already present with the new admin email
|
||||
const existingUserWithNewEmail = await User.getByEmail(email, 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 }, |
||||
}, |
||||
); |
||||
|
||||
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
|
||||
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( |
||||
superUser.id, |
||||
{ |
||||
salt, |
||||
email, |
||||
password, |
||||
email_verification_token, |
||||
token_version: null, |
||||
refresh_token: null, |
||||
}, |
||||
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: null, |
||||
refresh_token: null, |
||||
}, |
||||
ncMeta, |
||||
); |
||||
} |
||||
} else { |
||||
const newPasswordHash = await promisify(bcrypt.hash)( |
||||
process.env.NC_ADMIN_PASSWORD, |
||||
superUser.salt, |
||||
); |
||||
|
||||
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, |
||||
{ |
||||
salt, |
||||
password, |
||||
email_verification_token, |
||||
token_version: null, |
||||
refresh_token: null, |
||||
}, |
||||
ncMeta, |
||||
); |
||||
} |
||||
} |
||||
} |
||||
await ncMeta.commit(); |
||||
} catch (e) { |
||||
console.log('Error occurred while updating/creating admin user'); |
||||
console.log(e); |
||||
await ncMeta.rollback(e); |
||||
} |
||||
} |
||||
} |
@ -1,7 +0,0 @@
|
||||
import { PublicMiddleware } from './public.middleware'; |
||||
|
||||
describe('PublicMiddleware', () => { |
||||
it('should be defined', () => { |
||||
expect(new PublicMiddleware()).toBeDefined(); |
||||
}); |
||||
}); |
@ -1,23 +0,0 @@
|
||||
import path, { join } from 'path'; |
||||
import { Injectable } from '@nestjs/common'; |
||||
import express from 'express'; |
||||
import isDocker from 'is-docker'; |
||||
import type { NestMiddleware } from '@nestjs/common'; |
||||
|
||||
@Injectable() |
||||
export class PublicMiddleware implements NestMiddleware { |
||||
use(req: any, res: any, next: () => void) { |
||||
// redirect root to dashboard
|
||||
if (req.path === '/') { |
||||
const dashboardPath = process.env.NC_DASHBOARD_URL || '/dashboard'; |
||||
return res.redirect(dashboardPath); |
||||
} |
||||
|
||||
// serve static files from public folder
|
||||
if (isDocker()) { |
||||
express.static(join(process.cwd(), 'docker', 'public'))(req, res, next); |
||||
} else { |
||||
express.static(join(process.cwd(), 'public'))(req, res, next); |
||||
} |
||||
} |
||||
} |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
@ -0,0 +1,48 @@
|
||||
import CryptoJS from 'crypto-js'; |
||||
import { Base } from '../models'; |
||||
import { MetaTable } from '../utils/globals'; |
||||
import type { NcUpgraderCtx } from './NcUpgrader'; |
||||
|
||||
const TEMP_KEY = 'temporary-key'; |
||||
|
||||
// In version 0.107.0 we were used a temporary fallback secret key for JWT token encryption and project base config encryption.
|
||||
// So any project created in version 0.107.0 won't be able to decrypt the project base config.
|
||||
// So we need to update the project base config with the new secret key.
|
||||
// Get all the project bases and update the project config with the new secret key.
|
||||
export default async function ({ ncMeta }: NcUpgraderCtx) { |
||||
const actions = []; |
||||
|
||||
// Get all the project bases
|
||||
const bases = await ncMeta.metaList2(null, null, MetaTable.BASES); |
||||
|
||||
// Update the base config with the new secret key if we could decrypt the base config with the fallback secret key
|
||||
for (const base of bases) { |
||||
let config; |
||||
|
||||
// Try to decrypt the base config with the fallback secret key
|
||||
// if we could decrypt the base config with the fallback secret key then we will update the base config with the new secret key
|
||||
// otherwise we will skip the base config update since it is already encrypted with the new secret key
|
||||
try { |
||||
config = JSON.parse( |
||||
CryptoJS.AES.decrypt(base.config, TEMP_KEY).toString(CryptoJS.enc.Utf8), |
||||
); |
||||
|
||||
// Update the base config with the new secret key
|
||||
actions.push( |
||||
Base.updateBase( |
||||
base.id, |
||||
{ |
||||
id: base.id, |
||||
projectId: base.project_id, |
||||
config, |
||||
skipReorder: true, |
||||
}, |
||||
ncMeta, |
||||
), |
||||
); |
||||
} catch (e) { |
||||
// ignore the error
|
||||
} |
||||
} |
||||
await Promise.all(actions); |
||||
} |
Loading…
Reference in new issue