From 36f1c2dd6e5782e857b6b483b91b66ed02f60cd7 Mon Sep 17 00:00:00 2001 From: Vijay Rathore Date: Sat, 21 May 2022 19:27:06 +0200 Subject: [PATCH] fix: google oauth2.0 (#2080) Signed-off-by: Vijay Kumar Rathore --- packages/nocodb/src/lib/noco-models/Plugin.ts | 15 +- .../noco/meta/api/userApi/initStrategies.ts | 86 +++++++++++ .../src/lib/noco/meta/api/userApi/userApis.ts | 142 ++++++++++++------ 3 files changed, 192 insertions(+), 51 deletions(-) diff --git a/packages/nocodb/src/lib/noco-models/Plugin.ts b/packages/nocodb/src/lib/noco-models/Plugin.ts index 0b2e2cd0fd..ac354d0b4e 100644 --- a/packages/nocodb/src/lib/noco-models/Plugin.ts +++ b/packages/nocodb/src/lib/noco-models/Plugin.ts @@ -85,18 +85,25 @@ export default class Plugin implements PluginType { } public static async isPluginActive(title: string) { - const plugin = + return !!(await this.getPluginByTitle(title))?.active; + } + + /** + * get plugin by title + */ + public static async getPluginByTitle(title: string) { + let plugin = title && (await NocoCache.get( `${CacheScope.PLUGIN}:${title}`, CacheGetType.TYPE_OBJECT )); if (!plugin) { - await Noco.ncMeta.metaGet2(null, null, MetaTable.PLUGIN, { + plugin = await Noco.ncMeta.metaGet2(null, null, MetaTable.PLUGIN, { title }); - await NocoCache.set(`${CacheScope.PLUGIN}:${title}`, !!plugin?.active); + await NocoCache.set(`${CacheScope.PLUGIN}:${title}`, plugin); } - return !!plugin?.active; + return plugin; } } diff --git a/packages/nocodb/src/lib/noco/meta/api/userApi/initStrategies.ts b/packages/nocodb/src/lib/noco/meta/api/userApi/initStrategies.ts index 83051b481a..36a4198d24 100644 --- a/packages/nocodb/src/lib/noco/meta/api/userApi/initStrategies.ts +++ b/packages/nocodb/src/lib/noco/meta/api/userApi/initStrategies.ts @@ -7,6 +7,7 @@ import { Strategy } from 'passport-jwt'; import passport from 'passport'; import { ExtractJwt } from 'passport-jwt'; import { Strategy as AuthTokenStrategy } from 'passport-auth-token'; +import { Strategy as GoogleStrategy } from 'passport-google-oauth20'; const PassportLocalStrategy = require('passport-local').Strategy; @@ -21,6 +22,7 @@ import NocoCache from '../../../../noco-cache/NocoCache'; import { CacheGetType, CacheScope } from '../../../../utils/globals'; import ApiToken from '../../../../noco-models/ApiToken'; import Noco from '../../../Noco'; +import Plugin from '../../../../noco-models/Plugin'; export function initStrategies(router): void { passport.use( @@ -188,5 +190,89 @@ export function initStrategies(router): void { }) ); + // mostly copied from older code + Plugin.getPluginByTitle('Google').then(googlePlugin => { + if (googlePlugin && googlePlugin.input) { + const settings = JSON.parse(googlePlugin.input); + process.env.NC_GOOGLE_CLIENT_ID = settings.client_id; + process.env.NC_GOOGLE_CLIENT_SECRET = settings.client_secret; + } + + if ( + process.env.NC_GOOGLE_CLIENT_ID && + process.env.NC_GOOGLE_CLIENT_SECRET + ) { + const googleAuthParamsOrig = GoogleStrategy.prototype.authorizationParams; + GoogleStrategy.prototype.authorizationParams = (options: any) => { + const params = googleAuthParamsOrig.call(this, options); + + if (options.state) { + params.state = options.state; + } + + return params; + }; + + const clientConfig = { + clientID: process.env.NC_GOOGLE_CLIENT_ID, + clientSecret: process.env.NC_GOOGLE_CLIENT_SECRET, + // todo: update url + callbackURL: 'http://localhost:3000', + passReqToCallback: true + }; + + const googleStrategy = new GoogleStrategy( + clientConfig, + async (req, _accessToken, _refreshToken, profile, done) => { + const email = profile.emails[0].value; + + User.getByEmail(email) + .then(async user => { + if (req.ncProjectId) { + ProjectUser.get(req.ncProjectId, user.id) + .then(async projectUser => { + user.roles = projectUser?.roles || 'user'; + user.roles = + user.roles === 'owner' ? 'owner,creator' : user.roles; + // + (user.roles ? `,${user.roles}` : ''); + + done(null, user); + }) + .catch(e => done(e)); + } else { + // const roles = projectUser?.roles ? JSON.parse(projectUser.roles) : {guest: true}; + if (user) { + return done(null, user); + } else { + let roles = 'editor'; + + if (!(await User.isFirst())) { + roles = 'owner'; + } + if (roles === 'editor') { + return done(new Error('User not found')); + } + const salt = await promisify(bcrypt.genSalt)(10); + user = await await User.insert({ + email: profile.emails[0].value, + password: '', + salt, + roles, + email_verified: true + }); + return done(null, user); + } + } + }) + .catch(err => { + return done(err); + }); + } + ); + + passport.use(googleStrategy); + } + }); + router.use(passport.initialize()); } diff --git a/packages/nocodb/src/lib/noco/meta/api/userApi/userApis.ts b/packages/nocodb/src/lib/noco/meta/api/userApi/userApis.ts index de03ab0cba..12cd235061 100644 --- a/packages/nocodb/src/lib/noco/meta/api/userApi/userApis.ts +++ b/packages/nocodb/src/lib/noco/meta/api/userApi/userApis.ts @@ -156,57 +156,93 @@ export async function signup(req: Request, res: Response) { } as any); } +async function successfulSignIn({ + user, + err, + info, + req, + res, + auditDescription +}) { + try { + if (!user || !user.email) { + if (err) { + return res.status(400).send(err); + } + if (info) { + return res.status(400).send(info); + } + return res.status(400).send({ msg: 'Your signin has failed' }); + } + + await promisify((req as any).login.bind(req))(user); + const refreshToken = randomTokenString(); + + await User.update(user.id, { + refresh_token: refreshToken + }); + setTokenCookie(res, refreshToken); + + Audit.insert({ + op_type: 'AUTHENTICATION', + op_sub_type: 'SIGNIN', + user: user.email, + ip: req.clientIp, + description: auditDescription + }); + + res.json({ + token: jwt.sign( + { + email: user.email, + firstname: user.firstname, + lastname: user.lastname, + id: user.id, + roles: user.roles + }, + + Noco.getConfig().auth.jwt.secret, + Noco.getConfig().auth.jwt.options + ) + } as any); + } catch (e) { + console.log(e); + throw e; + } +} + async function signin(req, res, next) { passport.authenticate( 'local', { session: false }, - async (err, user, info): Promise => { - try { - if (!user || !user.email) { - if (err) { - return res.status(400).send(err); - } - if (info) { - return res.status(400).send(info); - } - return res.status(400).send({ msg: 'Your signin has failed' }); - } - - await promisify((req as any).login.bind(req))(user); - const refreshToken = randomTokenString(); - - await User.update(user.id, { - refresh_token: refreshToken - }); - setTokenCookie(res, refreshToken); - - Audit.insert({ - op_type: 'AUTHENTICATION', - op_sub_type: 'SIGNIN', - user: user.email, - ip: req.clientIp, - description: `signed in` - }); - - res.json({ - token: jwt.sign( - { - email: user.email, - firstname: user.firstname, - lastname: user.lastname, - id: user.id, - roles: user.roles - }, - - Noco.getConfig().auth.jwt.secret, - Noco.getConfig().auth.jwt.options - ) - } as any); - } catch (e) { - console.log(e); - throw e; - } - } + async (err, user, info): Promise => + await successfulSignIn({ + user, + err, + info, + req, + res, + auditDescription: 'signed in' + }) + )(req, res, next); +} + +async function googleSignin(req, res, next) { + passport.authenticate( + 'google', + { + session: false, + callbackURL: req.ncSiteUrl + Noco.getConfig().dashboardPath + }, + async (err, user, info): Promise => + await successfulSignIn({ + user, + err, + info, + req, + res, + auditDescription: 'signed in using Google Auth' + }) )(req, res, next); } @@ -461,6 +497,18 @@ const mapRoutes = router => { ); router.post('/auth/token/refresh', ncMetaAclMw(refreshToken, 'refreshToken')); + /* Google auth apis */ + + router.post(`/auth/google/genTokenByCode`, catchError(googleSignin)); + + router.get('/auth/google', (req: any, res, next) => + passport.authenticate('google', { + scope: ['profile', 'email'], + state: req.query.state, + callbackURL: req.ncSiteUrl + Noco.getConfig().dashboardPath + })(req, res, next) + ); + // new API router.post('/api/v1/db/auth/user/signup', catchError(signup)); router.post('/api/v1/db/auth/user/signin', catchError(signin));