From 0a1344e03a9ea8b9d4cb10f897b7f1886e5b4499 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 7 Mar 2023 17:18:11 +0800 Subject: [PATCH] refactor(nocodb): refreshToken --- .../src/lib/controllers/user/user.ctl.ts | 14 +- .../nocodb/src/lib/services/user/index.ts | 162 +++++++++++++++++- 2 files changed, 174 insertions(+), 2 deletions(-) diff --git a/packages/nocodb/src/lib/controllers/user/user.ctl.ts b/packages/nocodb/src/lib/controllers/user/user.ctl.ts index 07bbbbcf81..83d93f1b3e 100644 --- a/packages/nocodb/src/lib/controllers/user/user.ctl.ts +++ b/packages/nocodb/src/lib/controllers/user/user.ctl.ts @@ -1,4 +1,4 @@ -import { Request, Response } from 'express'; +import { Request } from 'express'; import * as ejs from 'ejs'; import { promisify } from 'util'; @@ -9,6 +9,7 @@ import ncMetaAclMw from '../../meta/helpers/ncMetaAclMw'; import { Audit, User } from '../../models'; import Noco from '../../Noco'; import { userService } from '../../services'; +import { setTokenCookie } from '../../services/user/helpers'; export async function signup(req: Request, res): Promise { res.json( @@ -20,6 +21,16 @@ export async function signup(req: Request, res): Promise { ); } +export async function refreshToken(req: Request, res): Promise { + res.json( + await userService.refreshToken({ + body: req.body, + req, + res, + }) + ); +} + async function successfulSignIn({ user, err, @@ -40,6 +51,7 @@ async function successfulSignIn({ } await promisify((req as any).login.bind(req))(user); + const refreshToken = userService.randomTokenString(); if (!user.token_version) { diff --git a/packages/nocodb/src/lib/services/user/index.ts b/packages/nocodb/src/lib/services/user/index.ts index d9e42bd651..ba3af75751 100644 --- a/packages/nocodb/src/lib/services/user/index.ts +++ b/packages/nocodb/src/lib/services/user/index.ts @@ -3,6 +3,7 @@ import { PasswordForgotReqType, PasswordResetReqType, UserType, + SignUpReqType, validatePassword, } from 'nocodb-sdk'; import { OrgUserRoles } from 'nocodb-sdk'; @@ -19,9 +20,10 @@ import NcPluginMgrv2 from '../../meta/helpers/NcPluginMgrv2'; import { Audit, Store, User } from '../../models'; import Noco from '../../Noco'; import { MetaTable } from '../../utils/globals'; -import { randomTokenString } from './helpers'; +import { genJwt, randomTokenString, setTokenCookie } from './helpers'; const { v4: uuidv4 } = require('uuid'); +const { isEmail } = require('validator'); export async function registerNewUserIfAllowed({ firstname, @@ -296,5 +298,163 @@ export async function emailVerification(param: { return true; } +export async function refreshToken(param: { + body: SignUpReqType; + req: any; + res: any; +}): Promise { + try { + if (!param.req?.cookies?.refresh_token) { + NcError.badRequest(`Missing refresh token`); + } + + const user = await User.getByRefreshToken(param.req.cookies.refresh_token); + + if (!user) { + NcError.badRequest(`Invalid refresh token`); + } + + const refreshToken = randomTokenString(); + + await User.update(user.id, { + email: user.email, + refresh_token: refreshToken, + }); + + setTokenCookie(param.res, refreshToken); + + return { + token: genJwt(user, Noco.getConfig()), + } as any; + } catch (e) { + NcError.badRequest(e.message); + } +} + +export async function signup(param: { + body: SignUpReqType; + req: any; + res: any; +}): Promise { + validatePayload('swagger.json#/components/schemas/SignUpReq', param.body); + + const { + email: _email, + firstname, + lastname, + token, + ignore_subscribe, + } = param.req.body; + + let { password } = param.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`); + } + + const email = _email.toLowerCase(); + + let user = await User.getByEmail(email); + + if (user) { + if (token) { + if (token !== user.invite_token) { + NcError.badRequest(`Invalid invite url`); + } else if (user.invite_token_expires < new Date()) { + NcError.badRequest( + 'Expired invite url, Please contact super admin to get a new invite url' + ); + } + } else { + // todo : opening up signup for timebeing + // return next(new Error(`Email '${email}' already registered`)); + } + } + + const salt = await promisify(bcrypt.genSalt)(10); + password = await promisify(bcrypt.hash)(password, salt); + const email_verification_token = uuidv4(); + + if (!ignore_subscribe) { + T.emit('evt_subscribe', email); + } + + if (user) { + if (token) { + await User.update(user.id, { + firstname, + lastname, + salt, + password, + email_verification_token, + invite_token: null, + invite_token_expires: null, + email: user.email, + }); + } else { + NcError.badRequest('User already exist'); + } + } else { + await registerNewUserIfAllowed({ + firstname, + lastname, + email, + salt, + password, + email_verification_token, + }); + } + user = await User.getByEmail(email); + + try { + const template = (await import('./ui/emailTemplates/verify')).default; + await ( + await NcPluginMgrv2.emailAdapter() + ).mailSend({ + to: email, + subject: 'Verify email', + html: ejs.render(template, { + verifyLink: + (param.req as any).ncSiteUrl + + `/email/verify/${user.email_verification_token}`, + }), + }); + } catch (e) { + console.log( + 'Warning : `mailSend` failed, Please configure emailClient configuration.' + ); + } + await promisify((param.req as any).login.bind(param.req))(user); + + const refreshToken = randomTokenString(); + + await User.update(user.id, { + refresh_token: refreshToken, + email: user.email, + }); + + setTokenCookie(param.res, refreshToken); + + user = (param.req as any).user; + + await Audit.insert({ + op_type: 'AUTHENTICATION', + op_sub_type: 'SIGNUP', + user: user.email, + description: `signed up `, + ip: (param.req as any).clientIp, + }); + + return { + token: genJwt(user, Noco.getConfig()), + } as any; +} + export * from './helpers'; export { default as initAdminFromEnv } from './initAdminFromEnv';