diff --git a/packages/nocodb/src/lib/controllers/userController/userApis.ts b/packages/nocodb/src/lib/controllers/userController/userApis.ts index fa35a5bd0d..574862e25a 100644 --- a/packages/nocodb/src/lib/controllers/userController/userApis.ts +++ b/packages/nocodb/src/lib/controllers/userController/userApis.ts @@ -1,82 +1,24 @@ import { Request, Response } from 'express'; import { TableType, validatePassword } from 'nocodb-sdk'; -import { OrgUserRoles } from 'nocodb-sdk'; -import { NC_APP_SETTINGS } from '../../../constants'; -import Store from '../../../models/Store'; import { T } from 'nc-help'; -import catchError, { NcError } from '../../helpers/catchError'; const { isEmail } = require('validator'); import * as ejs from 'ejs'; import bcrypt from 'bcryptjs'; import { promisify } from 'util'; -import User from '../../../models/User'; const { v4: uuidv4 } = require('uuid'); -import Audit from '../../../models/Audit'; -import NcPluginMgrv2 from '../../helpers/NcPluginMgrv2'; import passport from 'passport'; -import extractProjectIdAndAuthenticate from '../../helpers/extractProjectIdAndAuthenticate'; -import ncMetaAclMw from '../../helpers/ncMetaAclMw'; -import { MetaTable } from '../../../utils/globals'; -import Noco from '../../../Noco'; -import { getAjvValidatorMw } from '../helpers'; -import { genJwt } from './helpers'; -import { randomTokenString } from '../../helpers/stringHelpers'; - -export async function registerNewUserIfAllowed({ - firstname, - lastname, - email, - salt, - password, - email_verification_token, -}: { - firstname; - lastname; - email: string; - salt: any; - password; - email_verification_token; -}) { - let roles: string = OrgUserRoles.CREATOR; - - if (await User.isFirst()) { - roles = `${OrgUserRoles.CREATOR},${OrgUserRoles.SUPER_ADMIN}`; - // todo: update in nc_store - // roles = 'owner,creator,editor' - T.emit('evt', { - evt_type: 'project:invite', - count: 1, - }); - } else { - let settings: { invite_only_signup?: boolean } = {}; - try { - settings = JSON.parse((await Store.get(NC_APP_SETTINGS))?.value); - } catch {} - - if (settings?.invite_only_signup) { - NcError.badRequest('Not allowed to signup, contact super admin.'); - } else { - roles = OrgUserRoles.VIEWER; - } - } - - const token_version = randomTokenString(); - - return await User.insert({ - firstname, - lastname, - email, - salt, - password, - email_verification_token, - roles, - token_version, - }); -} +import { getAjvValidatorMw } from '../../meta/api/helpers'; +import catchError, { NcError } from '../../meta/helpers/catchError'; +import extractProjectIdAndAuthenticate from '../../meta/helpers/extractProjectIdAndAuthenticate'; +import ncMetaAclMw from '../../meta/helpers/ncMetaAclMw'; +import NcPluginMgrv2 from '../../meta/helpers/NcPluginMgrv2'; +import { Audit, User } from '../../models'; +import Noco from '../../Noco'; +import { userService } from '../../services'; export async function signup(req: Request, res: Response) { const { @@ -141,7 +83,7 @@ export async function signup(req: Request, res: Response) { NcError.badRequest('User already exist'); } } else { - await registerNewUserIfAllowed({ + await userService.registerNewUserIfAllowed({ firstname, lastname, email, @@ -171,7 +113,7 @@ export async function signup(req: Request, res: Response) { ); } await promisify((req as any).login.bind(req))(user); - const refreshToken = randomTokenString(); + const refreshToken = userService.randomTokenString(); await User.update(user.id, { refresh_token: refreshToken, email: user.email, @@ -190,18 +132,18 @@ export async function signup(req: Request, res: Response) { }); res.json({ - token: genJwt(user, Noco.getConfig()), + token: userService.genJwt(user, Noco.getConfig()), } as any); } async function successfulSignIn({ - user, - err, - info, - req, - res, - auditDescription, -}) { + user, + err, + info, + req, + res, + auditDescription, + }) { try { if (!user || !user.email) { if (err) { @@ -214,10 +156,10 @@ async function successfulSignIn({ } await promisify((req as any).login.bind(req))(user); - const refreshToken = randomTokenString(); + const refreshToken = userService.randomTokenString(); if (!user.token_version) { - user.token_version = randomTokenString(); + user.token_version = userService.randomTokenString(); } await User.update(user.id, { @@ -236,7 +178,7 @@ async function successfulSignIn({ }); res.json({ - token: genJwt(user, Noco.getConfig()), + token: userService.genJwt(user, Noco.getConfig()), } as any); } catch (e) { console.log(e); @@ -279,15 +221,13 @@ async function googleSignin(req, res, next) { )(req, res, next); } -const REFRESH_TOKEN_COOKIE_KEY = 'refresh_token'; - -function setTokenCookie(res, token): void { +function setTokenCookie(res: Response, token): void { // create http only cookie with refresh token that expires in 7 days const cookieOptions = { httpOnly: true, expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), }; - res.cookie(REFRESH_TOKEN_COOKIE_KEY, token, cookieOptions); + res.cookie('refresh_token', token, cookieOptions); } async function me(req, res): Promise { @@ -298,184 +238,47 @@ async function passwordChange(req: Request, res): Promise { if (!(req as any).isAuthenticated()) { NcError.forbidden('Not allowed'); } - const { currentPassword, newPassword } = req.body; - 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, - user.salt - ); - if (hashedPassword !== user.password) { - return NcError.badRequest('Current password is wrong'); - } - - const salt = await promisify(bcrypt.genSalt)(10); - const password = await promisify(bcrypt.hash)(newPassword, salt); - - await User.update(user.id, { - salt, - password, - email: user.email, - token_version: null, - }); - - await Audit.insert({ - op_type: 'AUTHENTICATION', - op_sub_type: 'PASSWORD_CHANGE', - user: user.email, - description: `changed password `, - ip: (req as any).clientIp, + await userService.passwordChange({ + user: req['user'], + req, + body: req.body, }); res.json({ msg: 'Password updated successfully' }); } async function passwordForgot(req: Request, res): Promise { - const _email = req.body.email; - if (!_email) { - NcError.badRequest('Please enter your email address.'); - } - - const email = _email.toLowerCase(); - const user = await User.getByEmail(email); - - if (user) { - const token = uuidv4(); - await User.update(user.id, { - email: user.email, - reset_password_token: token, - reset_password_expires: new Date(Date.now() + 60 * 60 * 1000), - token_version: null, - }); - try { - const template = (await import('./ui/emailTemplates/forgotPassword')) - .default; - await NcPluginMgrv2.emailAdapter().then((adapter) => - adapter.mailSend({ - to: user.email, - subject: 'Password Reset Link', - text: `Visit following link to update your password : ${ - (req as any).ncSiteUrl - }/auth/password/reset/${token}.`, - html: ejs.render(template, { - resetLink: (req as any).ncSiteUrl + `/auth/password/reset/${token}`, - }), - }) - ); - } catch (e) { - console.log(e); - return NcError.badRequest( - 'Email Plugin is not found. Please contact administrators to configure it in App Store first.' - ); - } + await userService.passwordForgot({ + siteUrl: (req as any).ncSiteUrl, + body: req.body, + req, + }); - await Audit.insert({ - op_type: 'AUTHENTICATION', - op_sub_type: 'PASSWORD_FORGOT', - user: user.email, - description: `requested for password reset `, - ip: (req as any).clientIp, - }); - } else { - return NcError.badRequest('Your email has not been registered.'); - } res.json({ msg: 'Please check your email to reset the password' }); } async function tokenValidate(req, res): Promise { - const token = req.params.tokenId; - - const user = await Noco.ncMeta.metaGet(null, null, MetaTable.USERS, { - reset_password_token: token, + await userService.tokenValidate({ + token: req.params.tokenId, }); - - if (!user || !user.email) { - NcError.badRequest('Invalid reset url'); - } - if (new Date(user.reset_password_expires) < new Date()) { - NcError.badRequest('Password reset url expired'); - } res.json(true); } async function passwordReset(req, res): Promise { - const token = req.params.tokenId; - - const user = await Noco.ncMeta.metaGet(null, null, MetaTable.USERS, { - reset_password_token: token, - }); - - if (!user) { - NcError.badRequest('Invalid reset url'); - } - if (user.reset_password_expires < new Date()) { - NcError.badRequest('Password reset url expired'); - } - if (user.provider && user.provider !== 'local') { - 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); - - await User.update(user.id, { - salt, - password, - email: user.email, - reset_password_expires: null, - reset_password_token: '', - token_version: null, - }); - - await Audit.insert({ - op_type: 'AUTHENTICATION', - op_sub_type: 'PASSWORD_RESET', - user: user.email, - description: `did reset password `, - ip: req.clientIp, + await userService.passwordReset({ + token: req.params.tokenId, + body: req.body, + req, }); res.json({ msg: 'Password reset successful' }); } async function emailVerification(req, res): Promise { - const token = req.params.tokenId; - - const user = await Noco.ncMeta.metaGet(null, null, MetaTable.USERS, { - email_verification_token: token, - }); - - if (!user) { - NcError.badRequest('Invalid verification url'); - } - - await User.update(user.id, { - email: user.email, - email_verification_token: '', - email_verified: true, - }); - - await Audit.insert({ - op_type: 'AUTHENTICATION', - op_sub_type: 'EMAIL_VERIFICATION', - user: user.email, - description: `verified email `, - ip: req.clientIp, + await userService.emailVerification({ + token: req.params.tokenId, + req, }); res.json({ msg: 'Email verified successfully' }); @@ -487,15 +290,13 @@ async function refreshToken(req, res): Promise { return res.status(400).json({ msg: 'Missing refresh token' }); } - const user = await User.getByRefreshToken( - req.cookies[REFRESH_TOKEN_COOKIE_KEY] - ); + const user = await User.getByRefreshToken(req.cookies.refresh_token); if (!user) { return res.status(400).json({ msg: 'Invalid refresh token' }); } - const refreshToken = randomTokenString(); + const refreshToken = userService.randomTokenString(); await User.update(user.id, { email: user.email, @@ -505,7 +306,7 @@ async function refreshToken(req, res): Promise { setTokenCookie(res, refreshToken); res.json({ - token: genJwt(user, Noco.getConfig()), + token: userService.genJwt(user, Noco.getConfig()), } as any); } catch (e) { return res.status(400).json({ msg: e.message }); @@ -526,30 +327,6 @@ async function renderPasswordReset(req, res): Promise { } } -// clear refresh token cookie and update user refresh token to null -const signout = async (req, res): Promise => { - const resBody = { msg: 'Success' }; - if (!req.cookies[REFRESH_TOKEN_COOKIE_KEY]) { - return res.json(resBody); - } - - const user = await User.getByRefreshToken( - req.cookies[REFRESH_TOKEN_COOKIE_KEY] - ); - - if (!user) { - return res.json(resBody); - } - - res.clearCookie(REFRESH_TOKEN_COOKIE_KEY); - - await User.update(user.id, { - refresh_token: null, - }); - - res.json(resBody); -}; - const mapRoutes = (router) => { // todo: old api - /auth/signup?tool=1 router.post( @@ -562,7 +339,6 @@ const mapRoutes = (router) => { getAjvValidatorMw('swagger.json#/components/schemas/SignInReq'), catchError(signin) ); - router.post('/auth/user/signout', catchError(signout)); router.get('/auth/user/me', extractProjectIdAndAuthenticate, catchError(me)); router.post( '/auth/password/forgot', @@ -651,7 +427,6 @@ const mapRoutes = (router) => { getAjvValidatorMw('swagger.json#/components/schemas/SignInReq'), catchError(signin) ); - router.post('/api/v1/auth/user/signout', catchError(signout)); router.get( '/api/v1/auth/user/me', extractProjectIdAndAuthenticate, @@ -683,4 +458,4 @@ const mapRoutes = (router) => { // respond with password reset page router.get('/auth/password/reset/:tokenId', catchError(renderPasswordReset)); }; -export { mapRoutes as userApis }; +export { mapRoutes as userController };